USD: Add support for Text objects during export

Similar to the existing meta-ball export, and the other exporters,
convert Text objects to meshes for export.

Ref #138883

Pull Request: https://projects.blender.org/blender/blender/pulls/138903
This commit is contained in:
Jesse Yurkovich
2025-05-16 22:12:48 +02:00
committed by Jesse Yurkovich
parent de5d0cfdc5
commit 3b2bbad609
10 changed files with 134 additions and 9 deletions

View File

@@ -811,8 +811,8 @@ bool AbstractHierarchyIterator::mark_as_weak_export(const Object * /*object*/) c
bool AbstractHierarchyIterator::should_visit_dupli_object(const DupliObject *dupli_object) const
{
/* Do not visit dupli objects if their `no_draw` flag is set (things like custom bone shapes) or
* if they are meta-balls. */
if (dupli_object->no_draw || dupli_object->ob->type == OB_MBALL) {
* if they are meta-balls / text objects. */
if (dupli_object->no_draw || ELEM(dupli_object->ob->type, OB_MBALL, OB_FONT)) {
return false;
}

View File

@@ -84,6 +84,7 @@ set(SRC
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_points.cc
intern/usd_writer_text.cc
intern/usd_writer_transform.cc
intern/usd_writer_volume.cc
@@ -133,6 +134,7 @@ set(SRC
intern/usd_writer_mesh.hh
intern/usd_writer_metaball.hh
intern/usd_writer_points.hh
intern/usd_writer_text.hh
intern/usd_writer_transform.hh
intern/usd_writer_volume.hh

View File

@@ -18,6 +18,7 @@
#include "usd_writer_mesh.hh"
#include "usd_writer_metaball.hh"
#include "usd_writer_points.hh"
#include "usd_writer_text.hh"
#include "usd_writer_transform.hh"
#include "usd_writer_volume.hh"
@@ -52,6 +53,7 @@ bool USDHierarchyIterator::mark_as_weak_export(const Object *object) const
return false;
case OB_MESH:
case OB_MBALL:
case OB_FONT:
return !params_.export_meshes;
case OB_CAMERA:
return !params_.export_cameras;
@@ -174,6 +176,9 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
case OB_MBALL:
data_writer = new USDMetaballWriter(usd_export_context);
break;
case OB_FONT:
data_writer = new USDTextWriter(usd_export_context);
break;
case OB_CURVES_LEGACY:
case OB_CURVES:
if (usd_export_context.export_params.export_curves) {
@@ -210,7 +215,6 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
case OB_EMPTY:
case OB_SURF:
case OB_FONT:
case OB_SPEAKER:
case OB_LIGHTPROBE:
case OB_LATTICE:

View File

@@ -0,0 +1,34 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "usd_writer_text.hh"
#include "usd_exporter_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_mesh.hh"
#include "BKE_object.hh"
#include "DNA_mesh_types.h"
namespace blender::io::usd {
USDTextWriter::USDTextWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx) {}
Mesh *USDTextWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
{
Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
if (mesh_eval != nullptr) {
/* Mesh_eval only exists when generative modifiers are in use. */
r_needsfree = false;
return mesh_eval;
}
r_needsfree = true;
return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false, false, true);
}
void USDTextWriter::free_export_mesh(Mesh *mesh)
{
BKE_id_free(nullptr, mesh);
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,22 @@
/* SPDX-FileCopyrightText: 2025 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include "usd_writer_mesh.hh"
struct Mesh;
struct Object;
namespace blender::io::usd {
class USDTextWriter : public USDGenericMeshWriter {
public:
USDTextWriter(const USDExporterContext &ctx);
protected:
Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
void free_export_mesh(Mesh *mesh) override;
};
} // namespace blender::io::usd

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/files/usd/usd_text_test.blend (Stored with Git LFS) Normal file

Binary file not shown.

View File

@@ -975,6 +975,66 @@ class USDExportTest(AbstractUSDTest):
weight_samples = anim.GetBlendShapeWeightsAttr().GetTimeSamples()
self.assertEqual(weight_samples, [1.0, 2.0, 3.0, 4.0, 5.0])
def test_export_text(self):
"""Test various forms of Text/Font export."""
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_text_test.blend"))
export_path = str(self.tempdir / "usd_text_test.usda")
self.export_and_validate(filepath=export_path, export_animation=True, evaluation_mode="RENDER")
stats = UsdUtils.ComputeUsdStageStats(export_path)
stage = Usd.Stage.Open(export_path)
# There should be 4 meshes in the output
self.assertEqual(stats['primary']['primCountsByType']['Mesh'], 4)
bboxcache_frame1 = UsdGeom.BBoxCache(1.0, [UsdGeom.Tokens.default_])
bboxcache_frame5 = UsdGeom.BBoxCache(5.0, [UsdGeom.Tokens.default_])
# Static, flat, text
mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/static/static"))
bounds1 = bboxcache_frame1.ComputeWorldBound(mesh.GetPrim())
bbox1 = bounds1.GetRange().GetMax() - bounds1.GetRange().GetMin()
self.assertEqual(mesh.GetPointsAttr().GetTimeSamples(), [])
self.assertEqual(mesh.GetExtentAttr().GetTimeSamples(), [])
self.assertTrue(bbox1[0] > 0.0)
self.assertTrue(bbox1[1] > 0.0)
self.assertAlmostEqual(bbox1[2], 0.0, 5)
# Dynamic, flat, text
mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/dynamic/dynamic"))
bounds1 = bboxcache_frame1.ComputeWorldBound(mesh.GetPrim())
bounds5 = bboxcache_frame5.ComputeWorldBound(mesh.GetPrim())
bbox1 = bounds1.GetRange().GetMax() - bounds1.GetRange().GetMin()
bbox5 = bounds5.GetRange().GetMax() - bounds5.GetRange().GetMin()
self.assertEqual(mesh.GetPointsAttr().GetTimeSamples(), [1.0, 2.0, 3.0, 4.0, 5.0])
self.assertEqual(mesh.GetExtentAttr().GetTimeSamples(), [1.0, 2.0, 3.0, 4.0, 5.0])
self.assertEqual(bbox1[2], 0.0)
self.assertTrue(bbox1[0] < bbox5[0]) # Text grows on x-axis
self.assertAlmostEqual(bbox1[1], bbox5[1], 5)
self.assertAlmostEqual(bbox1[2], bbox5[2], 5)
# Static, extruded on Z, text
mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/extruded/extruded"))
bounds1 = bboxcache_frame1.ComputeWorldBound(mesh.GetPrim())
bbox1 = bounds1.GetRange().GetMax() - bounds1.GetRange().GetMin()
self.assertEqual(mesh.GetPointsAttr().GetTimeSamples(), [])
self.assertEqual(mesh.GetExtentAttr().GetTimeSamples(), [])
self.assertTrue(bbox1[0] > 0.0)
self.assertTrue(bbox1[1] > 0.0)
self.assertAlmostEqual(bbox1[2], 0.1, 5)
# Static, uses depth, text
mesh = UsdGeom.Mesh(stage.GetPrimAtPath("/root/has_depth/has_depth"))
bounds1 = bboxcache_frame1.ComputeWorldBound(mesh.GetPrim())
bbox1 = bounds1.GetRange().GetMax() - bounds1.GetRange().GetMin()
self.assertEqual(mesh.GetPointsAttr().GetTimeSamples(), [])
self.assertEqual(mesh.GetExtentAttr().GetTimeSamples(), [])
self.assertTrue(bbox1[0] > 0.0)
self.assertTrue(bbox1[1] > 0.0)
self.assertAlmostEqual(bbox1[2], 0.1, 5)
def test_export_volumes(self):
"""Test various combinations of volume export including with all supported volume modifiers."""