Compare commits

...

40 Commits

Author SHA1 Message Date
cebad553c9 Fixed compiler error. 2022-05-01 23:51:23 -04:00
5bddc84c86 Fix compiler errors. 2022-05-01 23:44:18 -04:00
15d1b8f35d Fixed USD 21.11 compiler errors. 2022-05-01 23:27:55 -04:00
0bfece3525 Fixed compile error. 2022-05-01 21:52:19 -04:00
b1235c5858 Fixed USD 21.11 compile errors. 2022-05-01 21:30:50 -04:00
93a9e201f1 Fixed USD 21.11 compile errors. 2022-05-01 20:33:48 -04:00
e0f6dcfc3c Merge branch 'master' into tmp-usd-mak-c87f6242 2022-05-01 18:01:06 -04:00
82964294a5 USD Import: Incorrect merge of untyped prims.
Fixed bug where prims with undefined types were incorrectly merged
with parent xforms on import.  In some cases, this was causing
root xforms to be lost and prevented unit scale from being applied.

As an example, a scene with a single untyped prim parented to the
top level World xform would result in the World and untyped prim
being merged into a single Blender object representing only the
untyped prim.  Moreover, the UsdXformReader logic for identifying
root transform objects would fail in this case and no scene scale
would be applied to the imported hierarchy.
2022-04-25 14:18:43 -04:00
d742007c8e USD export: convert active UV map to st.
Updated the logic of the convert_uv_to_st option to rename the
active UV set to 'st', to allow specifying the default UV set
when there are multiple UVs.  Previously, this option assumed
a single UV set.
2022-04-12 18:17:13 -04:00
2b3f5cb965 USD export: handle USD Preview Surface emission.
Now setting the USD Preview Surface emissiveColor input
on export.
2022-04-11 21:12:10 -04:00
18320355ea USD Preview Surface import improvements.
Now using the new API for querying UDIM tiles.  Also
refactored based on ongoing patch review.

Updated UsdPreviewSurface color and normal input types to be
Color3f and Normal3f, respectively, to conform to the
specification.
2022-03-27 22:27:06 -04:00
a9c8425de8 Merge master into universal-scene-description.
Merge commit '923b28aab85768e2b4aff89494c321028252cf1e'.
2022-03-25 13:13:23 -04:00
2b6306c1ea USD IO: texture wrap and vertex color fixes.
Added logic to convert between tex image node extension enum
and UsdUVTexture wrapS and wrapT inputs on import and export.

Fixed bug where loop color data was incorrectly cast to MCol
rather than MLoopCol, causing the wrong vertex colors to be
exported.

The 'displayColor' primvar was being incorrectly imported as
'displayColors' (plural), causing this attribute to be
incorrectly named when exporting back to USD in round trips.
2022-03-20 16:33:20 -04:00
1e13fc105a USD IO: register plugins in USD_create_handle().
Added call to ensure that the USD plugins are registered
when opening a USD cache archive.  This is to avoid USD
load errors due to missing USD file format plugins when
opening blender files that contain USD transform cache
constraints and mesh sequence modifilers.
2022-01-03 13:24:20 -05:00
803f19b413 USD material writer code cleanup.
Removed unneeded includes, fixed include order,
replaced includes in header file with forward
declarations.  Replaced include guards with
Removed trailing underscore from local variable
names.  Moved const before type name in parameter
declaration, for consistency with usage elsewhere
in the code.
2022-01-03 11:30:27 -05:00
d9ca13066f USD import: fixed crash getting shader value.
Getting a value from an invalid shader input was causing
a crash in the UMM conversion invocation.  I added validty
checks for the input attribute in several places to
avoid this.  Also, minor formatting fix.
2021-12-08 15:38:09 -05:00
8ca67ef025 USD export: Skel Root validation.
Added function for verifying that skinned prims
and skeletons are properly grouped under a common
SkelRoot.  Also added a Fix Skel Root export
option to attempt to fix the hierarchy if the Skel
Root is invalid.
2021-12-07 12:42:42 -05:00
8ef0925c83 USD export: avoid creating redundant root prim.
If a root prim path is set in the params, now checking
if a root object matching the root path name already
exists in the Blender scene.  Clearing the root prim
path in the params and printing a warning if it does.
This is to avoid prepending the root prim path redundantly.
2021-12-07 12:25:07 -05:00
27c6f3fca5 USD IO: material conversion improvements.
Updated the USD Preview Surface texture node
import code to handle UDIM tiles that don't
start a 1001.  Performed miscellaneous cleanup
to make code more robust.

Fixed logic for the MDL material fallback behavior
to import the USD Preview Surface shaders only if
the material has no MDL shaders.  I.e., it will not
load preview surface as a fallback if an MDL exists
but failed to load for some reason.  This is much
more useful for debugging failures and also gives
the user an opportunity to fix a partially successful
MDL import.  This refactor also fixes a significant
bug where the fallback would be used even if the MDL
import succeeds.  Refactored the report_notification()
utility function in the UMM conversion code to return
more meaningful results.

Added logic to generate file names for packed texture
assets when exporting USD Preview Surface shaders.
Previously, such asset paths were left empty and were
omitted from the export.

Updated the UsdUVTexture shader conversion code to
handle the case where the file input has a connected
source, which may happen if this input is overridden
by an input on the parent material.

Made the logic for determining the color
space for texture assets when collecting UMM
source data more robust by handling the case
where a connected source input has no color
space specified. The fix is to also query
the shader's input attribute for this data.
2021-12-06 12:55:30 -05:00
7345fe7c8c Merge branch 'master' into universal-scene-description 2021-11-22 10:12:37 -05:00
f796b72cf5 USD import options description edit.
Shortened excessively long option descriptions
by removing information that should be included
in the documentation instead.
2021-11-17 17:49:29 -05:00
da766dd71c USD IO format fixes. 2021-11-09 18:42:05 -05:00
f71ad78dc1 USD Preview Surface import as a fallback.
Added logic to fall back on importing existing
USD Preview Surface shaders if importing MDL
is selected as an option but the material has
no MDL shaders.
2021-11-09 13:02:22 -05:00
85172cb5e1 USD Export: Armature export improvements.
Now including the root prim in the skinned mesh
skeleton relationship path.  Also, added logic to
avoid nesting SkelRoot prims in the USD, as such
nesting causes skeleton binding to fail as well
as crashes in Create.

Now iterating over the deform groups of the
evaluated mesh when setting joint weights
and indices, to ensure the vertex group
data is valid.
2021-11-08 16:38:11 -05:00
e2a783f8eb USD export: fixed linux an mac compile error. 2021-10-25 20:52:20 -04:00
265df7b3a6 USD IO: attribute conversion improvements.
Initial implementation of logic to import USD
attibutes as Blender custom properites, with options
to import all custom attributes or only those
attibutes in the 'userProperties' namespace.

New export option to add custom properties to the
'userProperties' USD attribute namespace. This
option is enabled by default.

Removed hidden functionality where custom properties named
with the prefix 'USD_' were being saved to properties on the
USD prim that have the same name, without the prefix.  This
code was not type safe and could lead to unexpected behavior
in case of accidental property name collisions.

Added support for converting between USD int, float and
double vectors and Blender array type custom properties.
2021-10-25 20:12:34 -04:00
f1828d3430 USD IO: enable presets.
Enable Operator Presets drop down menu
for the USD import/export operators.
2021-10-25 16:56:59 -04:00
1496105327 USD IO: handle UMM Python module load error.
Clearing the Python import module error if loading
the UMM module failed.  If we don't do this, the
Python unit test for USD will fail if the UMM
addon isn't installed. Also printing the Python
error in this case, if printing warnings is enabled.
2021-10-24 13:51:20 -04:00
3f2a1fa87c USD export: fix build errors with latest master. 2021-10-22 12:17:59 -04:00
63dfc81631 Merge branch 'master' of git.blender.org:blender into universal-scene-description 2021-10-20 22:08:45 -04:00
5724b0dd41 USD IO improved feedback.
Now parsing the Python notification dictionary object
returned by UMM to output warnings and error to the
Blender log.

Removing unneeded log message when setting the
default prim, as it can obscure more important
warnings and errors in the status bar.
2021-09-24 13:46:40 -04:00
f42ca488d2 USD IO options improvements
Added default values for the Default Prim Path,
Root Prim Path and Material Prim Path export
options. Now validating that these options are
set to well formed USD paths and raising an
error if these paths are invalid. This helps avoid
potential crashes when attempting to define
USD prims with invalid paths. Also updated import
shaders option menu tooltip.
2021-09-23 17:25:46 -04:00
eb747dbc66 USD Import: minor format fix 2021-09-14 21:35:06 -04:00
8513cc6e44 Merge remote-tracking branch 'blender_org/master' into temp-usd-latest-master 2021-09-13 21:47:21 -04:00
ffa078a079 Merge remote-tracking branch 'blender_org/master' into temp-usd-latest-master 2021-09-09 22:24:54 -04:00
0562c8b250 USD export: redundant call to set stage units
Setting the stage meters per unit metadata was being called
in two places unnecessarily. Removed redundant call.
2021-09-09 16:02:08 -04:00
182443da4b USD import: 'preview' purpose material fallback
Added logic to explicitly query bound materials
with purpose 'preview', if querying 'allPurpse' bound
materials returns no result.
2021-08-31 15:49:11 -04:00
2e32a0871f USD IO: material import improvements
UDIM texture support on UsdPreviewSurface import.
New Material Name Collision option for sepcifying behavior when
an imported material name conflicts with the name of an
existing material. Also includes format fixes.
2021-08-30 14:09:17 -04:00
baeeb1488e USD IO: fix compiler warnings and errors
Fixed warnings and errors for linux and darwin
builds.  Also fixed copyright date in
usd_light_convert.cc.
2021-08-08 23:07:26 -04:00
d143e8ff75 USD IO: initial commit of extended features
Instancing import:  Import USD scene instances as Blender collection instances.

Instancing export:  Extend the existing instancing option to support exporting arbitrary object hierarchies as USD scene instances.  Additional support for exporting Blender particle systems as USD point instancers.

Environment map IO:  Logic to convert between USD dome lights and Blender world materials, including environment textures.

Unit conversion scene scale:  Automatically scale the scene for unit conversion on import and export (e.g., scale the imported objects based on the USD’s meters per unit value).

Curve export.

Armature export:  Export armatures and skinned meshes to USD skeletons and skeletal animations.

Light unit conversion:  Experimental code to convert between light intensity units in Nits and Blender’s light energy units, on import and export.

Transform operator options:  Option to save transforms to USD as the combination of scale, rotate and translate operators, where the rotation can be expressed as Euler angles or a quaternion.

Export to USD shader nodes:  Convert Blender shader nodes to UsdPreviewSurface nodes, MDL material nodes or a custom USD representation of Cycles shaders. (MDL export requires UMM addon to be installed.)

Import MDL materials:  Convert MDL materials to Blender shader networks. (Requires UMM addon to be installed.)

Texture export:  An option to save textures to a directory relative to the USD being exported, using either absolute or relative asset paths.  This feature works with UDIM tiles as well as packed and in-memory “baked” textures.

Option to specify a default primitive on export.

Option to add a root primitive on export. This option adds a single prim as the parent of all exported prims.
2021-08-07 21:38:02 -04:00
51 changed files with 8599 additions and 486 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -199,7 +199,9 @@ class AbstractHierarchyIterator {
typedef std::map<ObjectIdentifier, ExportChildren> ExportGraph; typedef std::map<ObjectIdentifier, ExportChildren> ExportGraph;
/* Mapping from ID to its export path. This is used for instancing; given an /* Mapping from ID to its export path. This is used for instancing; given an
* instanced datablock, the export path of the original can be looked up. */ * instanced datablock, the export path of the original can be looked up. */
typedef std::map<ID *, std::string> ExportPathMap; typedef std::map<const ID *, std::string> ExportPathMap;
/* Set of IDs of objects that are the originals of instances. */
typedef std::set<const ID *> PrototypeObjects;
protected: protected:
ExportGraph export_graph_; ExportGraph export_graph_;
@@ -207,6 +209,7 @@ class AbstractHierarchyIterator {
Depsgraph *depsgraph_; Depsgraph *depsgraph_;
WriterMap writers_; WriterMap writers_;
ExportSubset export_subset_; ExportSubset export_subset_;
PrototypeObjects prototypes_;
public: public:
explicit AbstractHierarchyIterator(Depsgraph *depsgraph); explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
@@ -243,6 +246,19 @@ class AbstractHierarchyIterator {
* data to be a child of the object. */ * data to be a child of the object. */
virtual std::string get_object_data_path(const HierarchyContext *context) const; virtual std::string get_object_data_path(const HierarchyContext *context) const;
/* Returns the export path computed for the object with the given ID.
* This should be called after only all writers have been created for the
* dependency graph. This currently works for non-instanced objects only. */
std::string get_object_export_path(const ID *id) const;
/* Return true if the object with the given id is a prototype object
* for instancing. Returns false otherwise. */
bool is_prototype(const ID *id) const;
/* Return true if the object is a prototype object
* for instancing. Returns false otherwise. */
bool is_prototype(const Object *obj) const;
private: private:
void debug_print_export_graph(const ExportGraph &graph) const; void debug_print_export_graph(const ExportGraph &graph) const;
@@ -330,6 +346,18 @@ class AbstractHierarchyIterator {
/* Called by release_writers() to free what the create_XXX_writer() functions allocated. */ /* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
virtual void release_writer(AbstractHierarchyWriter *writer) = 0; virtual void release_writer(AbstractHierarchyWriter *writer) = 0;
/* Return true if data writers should be created for this context. */
virtual bool include_data_writers(const HierarchyContext *) const
{
return true;
}
/* Return true if children of the context should be converted to writers. */
virtual bool include_child_writers(const HierarchyContext *) const
{
return true;
}
AbstractHierarchyWriter *get_writer(const std::string &export_path) const; AbstractHierarchyWriter *get_writer(const std::string &export_path) const;
ExportChildren &graph_children(const HierarchyContext *parent_context); ExportChildren &graph_children(const HierarchyContext *parent_context);
}; };

View File

@@ -222,6 +222,28 @@ std::string AbstractHierarchyIterator::get_object_data_path(const HierarchyConte
return path_concatenate(context->export_path, get_object_data_name(context->object)); return path_concatenate(context->export_path, get_object_data_name(context->object));
} }
std::string AbstractHierarchyIterator::get_object_export_path(const ID *id) const
{
const ExportPathMap::const_iterator &it = duplisource_export_path_.find(id);
if (it != duplisource_export_path_.end()) {
return it->second;
}
return std::string();
}
bool AbstractHierarchyIterator::is_prototype(const ID *id) const
{
return prototypes_.find(id) != prototypes_.end();
}
bool AbstractHierarchyIterator::is_prototype(const Object *obj) const
{
const ID *obj_id = reinterpret_cast<const ID *>(obj);
return is_prototype(obj_id);
}
void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const
{ {
size_t total_graph_size = 0; size_t total_graph_size = 0;
@@ -282,9 +304,14 @@ void AbstractHierarchyIterator::export_graph_construct()
/* Export the duplicated objects instanced by this object. */ /* Export the duplicated objects instanced by this object. */
ListBase *lb = object_duplilist(depsgraph_, scene, object); ListBase *lb = object_duplilist(depsgraph_, scene, object);
if (lb) {
if (lb && object->particlesystem.first == nullptr) {
DupliParentFinder dupli_parent_finder; DupliParentFinder dupli_parent_finder;
// Construct the set of duplicated objects, so that later we can determine whether a parent
// is also duplicated itself.
std::set<Object *> dupli_set;
LISTBASE_FOREACH (DupliObject *, dupli_object, lb) { LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
PersistentID persistent_id(dupli_object); PersistentID persistent_id(dupli_object);
if (!should_visit_dupli_object(dupli_object)) { if (!should_visit_dupli_object(dupli_object)) {
@@ -405,7 +432,7 @@ void AbstractHierarchyIterator::visit_object(Object *object,
context->export_parent = export_parent; context->export_parent = export_parent;
context->duplicator = nullptr; context->duplicator = nullptr;
context->weak_export = weak_export; context->weak_export = weak_export;
context->animation_check_include_parent = false; context->animation_check_include_parent = true;
context->export_path = ""; context->export_path = "";
context->original_export_path = ""; context->original_export_path = "";
context->higher_up_export_path = ""; context->higher_up_export_path = "";
@@ -439,6 +466,7 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
Object *duplicator, Object *duplicator,
const DupliParentFinder &dupli_parent_finder) const DupliParentFinder &dupli_parent_finder)
{ {
HierarchyContext *context = new HierarchyContext(); HierarchyContext *context = new HierarchyContext();
context->object = dupli_object->ob; context->object = dupli_object->ob;
context->duplicator = duplicator; context->duplicator = duplicator;
@@ -461,6 +489,56 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
context_update_for_graph_index(context, graph_index); context_update_for_graph_index(context, graph_index);
export_graph_[graph_index].insert(context); export_graph_[graph_index].insert(context);
// ExportGraph::key_type graph_index;
// bool animation_check_include_parent = true;
// HierarchyContext *context = new HierarchyContext();
// context->object = dupli_object->ob;
// context->duplicator = duplicator;
// context->persistent_id = PersistentID(dupli_object);
// context->weak_export = false;
// context->export_path = "";
// context->original_export_path = "";
// context->export_path = "";
// context->animation_check_include_parent = false;
///* If the dupli-object's parent is also instanced by this object, use that as the
// * export parent. Otherwise use the dupli-parent as export parent. */
// Object *parent = dupli_object->ob->parent;
// if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) {
// // The parent object is part of the duplicated collection.
// context->export_parent = parent;
// graph_index = std::make_pair(parent, duplicator);
// // This bool used to be false by default
// // This was stopping a certain combination of drivers
// // and rigging to not properly export.
// // For now, we have switched to only setting to false here
// animation_check_include_parent = false;
//}
// else {
// /* The parent object is NOT part of the duplicated collection. This means that the world
// * transform of this dupli-object can be influenced by objects that are not part of its
// * export graph. */
// context->export_parent = duplicator;
// graph_index = std::make_pair(duplicator, nullptr);
//}
// context->animation_check_include_parent = animation_check_include_parent;
//
// copy_m4_m4(context->matrix_world, dupli_object->mat);
///* Construct export name for the dupli-instance. */
// std::stringstream export_name_stream;
// export_name_stream << get_object_name(context->object) << "-"
// << context->persistent_id.as_object_name_suffix();
// context->export_name = make_valid_name(export_name_stream.str());
// ExportGraph::key_type graph_index = determine_graph_index_dupli(
// context, dupli_object, dupli_parent_finder);
// context_update_for_graph_index(context, graph_index);
// export_graph_[graph_index].insert(context);
} }
AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator:: AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator::
@@ -535,6 +613,7 @@ void AbstractHierarchyIterator::determine_duplication_references(
} }
else { else {
context->mark_as_instance_of(it->second); context->mark_as_instance_of(it->second);
prototypes_.insert(source_id);
} }
if (context->object->data) { if (context->object->data) {
@@ -591,13 +670,15 @@ void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_cont
transform_writer->write(*context); transform_writer->write(*context);
} }
if (!context->weak_export) { if (!context->weak_export && include_data_writers(context)) {
make_writers_particle_systems(context); make_writers_particle_systems(context);
make_writer_object_data(context); make_writer_object_data(context);
} }
/* Recurse into this object's children. */ if (include_child_writers(context)) {
make_writers(context); /* Recurse into this object's children. */
make_writers(context);
}
} }
/* TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something. /* TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something.
@@ -627,6 +708,7 @@ void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *
data_context.original_export_path = duplisource_export_path_[object_data]; data_context.original_export_path = duplisource_export_path_[object_data];
/* If the object is marked as an instance, so should the object data. */ /* If the object is marked as an instance, so should the object data. */
/* TODO(makowalski): this fails when testing with collection instances. */
BLI_assert(data_context.is_instance()); BLI_assert(data_context.is_instance());
} }

View File

@@ -16,6 +16,9 @@ add_definitions(-DPXR_STATIC)
# USD headers use deprecated TBB headers, silence warning. # USD headers use deprecated TBB headers, silence warning.
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1) add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
# Python is always required
add_definitions(-DWITH_PYTHON)
# Check if USD has the imaging headers available, if they are # Check if USD has the imaging headers available, if they are
# add a USD_HAS_IMAGING define so code can dynamically detect this. # add a USD_HAS_IMAGING define so code can dynamically detect this.
# Cleanup of this variable is done at the end of the file since # Cleanup of this variable is done at the end of the file since
@@ -45,15 +48,18 @@ set(INC
../../editors/include ../../editors/include
../../makesdna ../../makesdna
../../makesrna ../../makesrna
../../python
../../windowmanager ../../windowmanager
../../../../intern/guardedalloc ../../../../intern/guardedalloc
../../../../intern/utfconv ../../../../intern/utfconv
${CMAKE_BINARY_DIR}/source/blender/makesrna/intern
) )
set(INC_SYS set(INC_SYS
${USD_INCLUDE_DIRS} ${USD_INCLUDE_DIRS}
${BOOST_INCLUDE_DIR} ${BOOST_INCLUDE_DIR}
${TBB_INCLUDE_DIR} ${TBB_INCLUDE_DIR}
${PYTHON_INCLUDE_DIRS}
) )
set(SRC set(SRC
@@ -61,18 +67,12 @@ set(SRC
intern/usd_capi_import.cc intern/usd_capi_import.cc
intern/usd_common.cc intern/usd_common.cc
intern/usd_hierarchy_iterator.cc intern/usd_hierarchy_iterator.cc
intern/usd_writer_abstract.cc intern/usd_light_convert.cc
intern/usd_writer_camera.cc
intern/usd_writer_hair.cc
intern/usd_writer_light.cc
intern/usd_writer_material.cc
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_transform.cc
intern/usd_reader_camera.cc intern/usd_reader_camera.cc
intern/usd_reader_curve.cc intern/usd_reader_curve.cc
intern/usd_reader_geom.cc intern/usd_reader_geom.cc
intern/usd_reader_instance.cc
intern/usd_reader_light.cc intern/usd_reader_light.cc
intern/usd_reader_material.cc intern/usd_reader_material.cc
intern/usd_reader_mesh.cc intern/usd_reader_mesh.cc
@@ -82,31 +82,57 @@ set(SRC
intern/usd_reader_volume.cc intern/usd_reader_volume.cc
intern/usd_reader_xform.cc intern/usd_reader_xform.cc
intern/usd_umm.cc
intern/usd_writer_abstract.cc
intern/usd_writer_armature.cc
intern/usd_writer_camera.cc
intern/usd_writer_curve.cc
intern/usd_writer_hair.cc
intern/usd_writer_light.cc
intern/usd_writer_material.cc
intern/usd_writer_mesh.cc
intern/usd_writer_metaball.cc
intern/usd_writer_particle.cc
intern/usd_writer_skel_root.cc
intern/usd_writer_skinned_mesh.cc
intern/usd_writer_transform.cc
usd.h usd.h
intern/usd_common.h intern/usd_common.h
intern/usd_exporter_context.h intern/usd_exporter_context.h
intern/usd_hierarchy_iterator.h intern/usd_hierarchy_iterator.h
intern/usd_writer_abstract.h intern/usd_light_convert.h
intern/usd_writer_camera.h
intern/usd_writer_hair.h
intern/usd_writer_light.h
intern/usd_writer_material.h
intern/usd_writer_mesh.h
intern/usd_writer_metaball.h
intern/usd_writer_transform.h
intern/usd_reader_camera.h intern/usd_reader_camera.h
intern/usd_reader_curve.h intern/usd_reader_curve.h
intern/usd_reader_geom.h intern/usd_reader_geom.h
intern/usd_reader_instance.h
intern/usd_reader_light.h intern/usd_reader_light.h
intern/usd_reader_material.h intern/usd_reader_material.h
intern/usd_reader_mesh.h intern/usd_reader_mesh.h
intern/usd_reader_nurbs.h intern/usd_reader_nurbs.h
intern/usd_reader_prim.h intern/usd_reader_prim.h
intern/usd_reader_stage.h intern/usd_reader_stage.h
intern/usd_reader_volume.h intern/usd_reader_volume.h
intern/usd_reader_xform.h intern/usd_reader_xform.h
intern/usd_umm.h
intern/usd_writer_abstract.h
intern/usd_writer_armature.h
intern/usd_writer_camera.h
intern/usd_writer_curve.h
intern/usd_writer_hair.h
intern/usd_writer_light.h
intern/usd_writer_material.h
intern/usd_writer_mesh.h
intern/usd_writer_metaball.h
intern/usd_writer_particle.h
intern/usd_writer_skel_root.h
intern/usd_writer_skinned_mesh.h
intern/usd_writer_transform.h
) )
set(LIB set(LIB
@@ -117,6 +143,8 @@ set(LIB
list(APPEND LIB list(APPEND LIB
${BOOST_LIBRARIES} ${BOOST_LIBRARIES}
${PYTHON_LINKFLAGS}
${PYTHON_LIBRARIES}
) )
list(APPEND LIB list(APPEND LIB

View File

@@ -4,11 +4,18 @@
#include "usd.h" #include "usd.h"
#include "usd_common.h" #include "usd_common.h"
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include "usd_light_convert.h"
#include "usd_umm.h"
#include "usd_writer_material.h"
#include "usd_writer_skel_root.h"
#include <pxr/base/plug/registry.h> #include <pxr/base/plug/registry.h>
#include <pxr/pxr.h> #include <pxr/pxr.h>
#include <pxr/usd/usd/stage.h> #include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/tokens.h> #include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include "MEM_guardedalloc.h" #include "MEM_guardedalloc.h"
@@ -22,9 +29,13 @@
#include "BKE_blender_version.h" #include "BKE_blender_version.h"
#include "BKE_context.h" #include "BKE_context.h"
#include "BKE_global.h" #include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_scene.h" #include "BKE_scene.h"
#include "BLI_fileops.h" #include "BLI_fileops.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_vector.h"
#include "BLI_path_util.h" #include "BLI_path_util.h"
#include "BLI_string.h" #include "BLI_string.h"
@@ -34,6 +45,7 @@
namespace blender::io::usd { namespace blender::io::usd {
struct ExportJobData { struct ExportJobData {
ViewLayer *view_layer;
Main *bmain; Main *bmain;
Depsgraph *depsgraph; Depsgraph *depsgraph;
wmWindowManager *wm; wmWindowManager *wm;
@@ -41,9 +53,141 @@ struct ExportJobData {
char filename[FILE_MAX]; char filename[FILE_MAX];
USDExportParams params; USDExportParams params;
short *stop;
short *do_update;
float *progress;
bool was_canceled;
bool export_ok; bool export_ok;
}; };
/* Perform validation of export parameter settings. Returns
* true if the paramters are valid. Returns false otherwise. */
static bool validate_params(const USDExportParams &params)
{
bool valid = true;
if (params.export_materials && !pxr::SdfPath::IsValidPathString(params.material_prim_path)) {
WM_reportf(RPT_ERROR,
"USD Export: invalid material prim path parameter '%s'",
params.material_prim_path);
valid = false;
}
if (strlen(params.root_prim_path) != 0 &&
!pxr::SdfPath::IsValidPathString(params.root_prim_path)) {
WM_reportf(
RPT_ERROR, "USD Export: invalid root prim path parameter '%s'", params.root_prim_path);
valid = false;
}
if (strlen(params.default_prim_path) != 0 &&
!pxr::SdfPath::IsValidPathString(params.default_prim_path)) {
WM_reportf(RPT_ERROR,
"USD Export: invalid default prim path parameter '%s'",
params.default_prim_path);
valid = false;
}
return valid;
}
/* If a root prim path is set in the params, check if a
* root object matching the root path name already exists.
* If it does, clear the root prim path in the params.
* This is to avoid prepending the root prim path
* redundantly.
* TODO(makowalski): ideally, this functionality belongs
* in the USD hierarchy iterator, so that we don't iterate
* over the scene graph separately here. */
static void validate_unique_root_prim_path(USDExportParams &params, Depsgraph *depsgraph)
{
if (!depsgraph || strlen(params.root_prim_path) == 0) {
return;
}
pxr::SdfPath path(params.root_prim_path);
if (path.IsEmpty()) {
return;
}
pxr::SdfPath parent = path.GetParentPath();
while (!parent.IsEmpty() && !parent.IsAbsoluteRootPath()) {
path = parent;
parent = path.GetParentPath();
}
Object *match = nullptr;
std::string root_name = path.GetName();
DEG_OBJECT_ITER_BEGIN(depsgraph,
object,
DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) {
if (!match && !object->parent) {
/* We only care about root objects. */
if (pxr::TfMakeValidIdentifier(object->id.name + 2) == root_name) {
match = object;
}
}
}
DEG_OBJECT_ITER_END;
if (match) {
WM_reportf(
RPT_WARNING, "USD Export: the root prim will not be added because a root object named '%s' already exists", root_name.c_str());
params.root_prim_path[0] = '\0';
}
}
/* Create root prim if defined. */
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &params)
{
if (strlen(params.root_prim_path) == 0) {
return;
}
pxr::UsdPrim root_prim = stage->DefinePrim(pxr::SdfPath(params.root_prim_path),
pxr::TfToken("Xform"));
if (!(params.convert_orientation || params.convert_to_cm)) {
return;
}
if (!root_prim) {
return;
}
pxr::UsdGeomXformCommonAPI xf_api(root_prim);
if (!xf_api) {
return;
}
if (params.convert_to_cm) {
xf_api.SetScale(pxr::GfVec3f(100.0f));
}
if (params.convert_orientation) {
float mrot[3][3];
mat3_from_axis_conversion(
USD_GLOBAL_FORWARD_Y, USD_GLOBAL_UP_Z, params.forward_axis, params.up_axis, mrot);
transpose_m3(mrot);
float eul[3];
mat3_to_eul(eul, mrot);
/* Convert radians to degrees. */
mul_v3_fl(eul, 180.0f / M_PI);
xf_api.SetRotate(pxr::GfVec3f(eul[0], eul[1], eul[2]));
}
}
static void export_startjob(void *customdata, static void export_startjob(void *customdata,
/* Cannot be const, this function implements wm_jobs_start_callback. /* Cannot be const, this function implements wm_jobs_start_callback.
* NOLINTNEXTLINE: readability-non-const-parameter. */ * NOLINTNEXTLINE: readability-non-const-parameter. */
@@ -52,12 +196,21 @@ static void export_startjob(void *customdata,
float *progress) float *progress)
{ {
ExportJobData *data = static_cast<ExportJobData *>(customdata); ExportJobData *data = static_cast<ExportJobData *>(customdata);
data->export_ok = false;
data->stop = stop;
data->do_update = do_update;
data->progress = progress;
data->was_canceled = false;
G.is_rendering = true; G.is_rendering = true;
WM_set_locked_interface(data->wm, true); WM_set_locked_interface(data->wm, true);
G.is_break = false; G.is_break = false;
if (!validate_params(data->params)) {
data->export_ok = false;
return;
}
/* Construct the depsgraph for exporting. */ /* Construct the depsgraph for exporting. */
Scene *scene = DEG_get_input_scene(data->depsgraph); Scene *scene = DEG_get_input_scene(data->depsgraph);
if (data->params.visible_objects_only) { if (data->params.visible_objects_only) {
@@ -68,42 +221,83 @@ static void export_startjob(void *customdata,
} }
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain); BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
validate_unique_root_prim_path(data->params, data->depsgraph);
*progress = 0.0f; *progress = 0.0f;
*do_update = true; *do_update = true;
/* For restoring the current frame after exporting animation is done. */ /* For restoring the current frame after exporting animation is done. */
const int orig_frame = CFRA; const int orig_frame = CFRA;
if (!BLI_path_extension_check_glob(data->filename, "*.usd;*.usda;*.usdc"))
BLI_path_extension_ensure(data->filename, FILE_MAX, ".usd");
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename); pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename);
if (!usd_stage) { if (!usd_stage) {
/* This happens when the USD JSON files cannot be found. When that happens, /* This may happen when the USD JSON files cannot be found. When that happens,
* the USD library doesn't know it has the functionality to write USDA and * the USD library doesn't know it has the functionality to write USDA and
* USDC files, and creating a new UsdStage fails. */ * USDC files, and creating a new UsdStage fails. */
WM_reportf( WM_reportf(RPT_ERROR, "USD Export: unable to create a stage for writing %s", data->filename);
RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename);
pxr::SdfLayerRefPtr existing_layer = pxr::SdfLayer::FindOrOpen(data->filename);
if (existing_layer) {
WM_reportf(RPT_ERROR,
"USD Export: layer %s is currently open in the scene, "
"possibly because it's referenced by modifiers, "
"and can't be overwritten",
data->filename);
}
data->export_ok = false;
return; return;
} }
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z)); if (data->params.export_lights && !data->params.selected_objects_only &&
usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, data->params.convert_world_material) {
static_cast<double>(scene->unit.scale_length)); world_material_to_dome_light(data->params, scene, usd_stage);
}
/* Define the material prim path as a scope. */
if (data->params.export_materials) {
pxr::SdfPath mtl_prim_path(data->params.material_prim_path);
blender::io::usd::usd_define_or_over<pxr::UsdGeomScope>(
usd_stage, mtl_prim_path, data->params.export_as_overs);
}
pxr::VtValue upAxis = pxr::VtValue(pxr::UsdGeomTokens->z);
if (data->params.convert_orientation) {
if (data->params.up_axis == USD_GLOBAL_UP_X)
upAxis = pxr::VtValue(pxr::UsdGeomTokens->x);
else if (data->params.up_axis == USD_GLOBAL_UP_Y)
upAxis = pxr::VtValue(pxr::UsdGeomTokens->y);
}
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, upAxis);
usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") + usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
BKE_blender_version_string()); BKE_blender_version_string());
/* Set up the stage for animated data. */ /* Set up the stage for animated data. */
if (data->params.export_animation) { if (data->params.export_animation) {
usd_stage->SetTimeCodesPerSecond(FPS); usd_stage->SetTimeCodesPerSecond(FPS);
usd_stage->SetStartTimeCode(scene->r.sfra); usd_stage->SetStartTimeCode(data->params.frame_start);
usd_stage->SetEndTimeCode(scene->r.efra); usd_stage->SetEndTimeCode(data->params.frame_end);
} }
ensure_root_prim(usd_stage, data->params);
USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params); USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params);
if (data->params.export_animation) { if (data->params.export_animation) {
/* Writing the animated frames is not 100% of the work, but it's our best guess. */
float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) { // Writing the animated frames is not 100% of the work, but it's our best guess.
float progress_per_frame = 1.0f / std::max(1.0f,
(float)(data->params.frame_end -
data->params.frame_start + 1.0) /
data->params.frame_step);
for (float frame = data->params.frame_start; frame <= data->params.frame_end;
frame += data->params.frame_step) {
if (G.is_break || (stop != nullptr && *stop)) { if (G.is_break || (stop != nullptr && *stop)) {
break; break;
} }
@@ -126,6 +320,36 @@ static void export_startjob(void *customdata,
} }
iter.release_writers(); iter.release_writers();
if (data->params.export_armatures) {
validate_skel_roots(usd_stage, data->params);
}
// Set Stage Default Prim Path
if (strlen(data->params.default_prim_path) > 0) {
std::string valid_default_prim_path = pxr::TfMakeValidIdentifier(
data->params.default_prim_path);
if (valid_default_prim_path[0] == '_') {
valid_default_prim_path[0] = '/';
}
if (valid_default_prim_path[0] != '/') {
valid_default_prim_path = "/" + valid_default_prim_path;
}
pxr::UsdPrim defaultPrim = usd_stage->GetPrimAtPath(pxr::SdfPath(valid_default_prim_path));
if (defaultPrim.IsValid()) {
usd_stage->SetDefaultPrim(defaultPrim);
}
}
/* Set unit scale.
* TODO(makowalsk): Add an option to use scene->unit.scale_length as well? */
double meters_per_unit = data->params.convert_to_cm ? pxr::UsdGeomLinearUnits::centimeters :
pxr::UsdGeomLinearUnits::meters;
pxr::UsdGeomSetStageMetersPerUnit(usd_stage, meters_per_unit);
usd_stage->GetRootLayer()->Save(); usd_stage->GetRootLayer()->Save();
/* Finish up by going back to the keyframe that was current before we started. */ /* Finish up by going back to the keyframe that was current before we started. */
@@ -134,7 +358,8 @@ static void export_startjob(void *customdata,
BKE_scene_graph_update_for_newframe(data->depsgraph); BKE_scene_graph_update_for_newframe(data->depsgraph);
} }
data->export_ok = true; data->export_ok = !data->was_canceled;
*progress = 1.0f; *progress = 1.0f;
*do_update = true; *do_update = true;
} }
@@ -145,7 +370,11 @@ static void export_endjob(void *customdata)
DEG_graph_free(data->depsgraph); DEG_graph_free(data->depsgraph);
if (!data->export_ok && BLI_exists(data->filename)) { MEM_freeN(data->params.default_prim_path);
MEM_freeN(data->params.root_prim_path);
MEM_freeN(data->params.material_prim_path);
if (data->was_canceled && BLI_exists(data->filename)) {
BLI_delete(data->filename, false, false); BLI_delete(data->filename, false, false);
} }
@@ -220,3 +449,12 @@ int USD_get_version()
*/ */
return PXR_VERSION; return PXR_VERSION;
} }
bool USD_umm_module_loaded(void)
{
#ifdef WITH_PYTHON
return blender::io::usd::umm_module_loaded();
#else
return fasle;
#endif
}

View File

@@ -5,10 +5,40 @@
#include "usd.h" #include "usd.h"
#include "usd_common.h" #include "usd_common.h"
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include "usd_light_convert.h"
#include "usd_reader_geom.h" #include "usd_reader_geom.h"
#include "usd_reader_instance.h"
#include "usd_reader_prim.h" #include "usd_reader_prim.h"
#include "usd_reader_stage.h" #include "usd_reader_stage.h"
#include <pxr/base/plug/registry.h>
#include "usd_writer_material.h"
#include <pxr/pxr.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include "MEM_guardedalloc.h"
#include "DEG_depsgraph.h"
#include "DEG_depsgraph_build.h"
#include "DEG_depsgraph_query.h"
#include "DNA_cachefile_types.h"
#include "DNA_collection_types.h"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "BKE_appdir.h" #include "BKE_appdir.h"
#include "BKE_blender_version.h" #include "BKE_blender_version.h"
#include "BKE_cachefile.h" #include "BKE_cachefile.h"
@@ -85,6 +115,127 @@ static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_pa
return true; return true;
} }
/* Create a collection with the given parent and name. */
static Collection *create_collection(Main *bmain, Collection *parent, const char *name)
{
if (!bmain) {
return nullptr;
}
Collection *coll = BKE_collection_add(bmain, parent, name);
if (coll) {
id_fake_user_set(&coll->id);
DEG_id_tag_update(&coll->id, ID_RECALC_COPY_ON_WRITE);
}
return coll;
}
/* Set the instance collection on the given instance reader.
* The collection is assigned from the given map based on
* the prototype (maser) prim path. */
static void set_instance_collection(
USDInstanceReader *instance_reader,
const std::map<pxr::SdfPath, Collection *> &proto_collection_map)
{
if (!instance_reader) {
return;
}
pxr::SdfPath proto_path = instance_reader->proto_path();
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(proto_path);
if (it != proto_collection_map.end()) {
instance_reader->set_instance_collection(it->second);
}
else {
std::cerr << "WARNING: Couldn't find prototype collection for " << instance_reader->prim_path()
<< std::endl;
}
}
/* Create instance collections for the USD instance readers. */
static void create_proto_collections(Main *bmain,
ViewLayer *view_layer,
Collection *parent_collection,
const ProtoReaderMap &proto_readers,
const std::vector<USDPrimReader *> &readers)
{
Collection *all_protos_collection = create_collection(bmain, parent_collection, "prototypes");
std::map<pxr::SdfPath, Collection *> proto_collection_map;
for (const auto &pair : proto_readers) {
std::string proto_collection_name = pair.first.GetString();
// TODO(makowalski): Is it acceptable to have slashes in the collection names? Or should we
// replace them with another character, like an underscore, as in the following?
// std::replace(proto_collection_name.begin(), proto_collection_name.end(), '/', '_');
Collection *proto_collection = create_collection(
bmain, all_protos_collection, proto_collection_name.c_str());
LayerCollection *proto_lc = BKE_layer_collection_first_from_scene_collection(view_layer,
proto_collection);
if (proto_lc) {
proto_lc->flag |= LAYER_COLLECTION_HIDE;
}
proto_collection_map.insert(std::make_pair(pair.first, proto_collection));
}
// Set the instance collections on the readers, including the prototype
// readers, as instancing may be recursive.
for (const auto &pair : proto_readers) {
for (USDPrimReader *reader : pair.second) {
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
set_instance_collection(instance_reader, proto_collection_map);
}
}
}
for (USDPrimReader *reader : readers) {
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
set_instance_collection(instance_reader, proto_collection_map);
}
}
// Add the prototype objects to the collections.
for (const auto &pair : proto_readers) {
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(
pair.first);
if (it == proto_collection_map.end()) {
std::cerr << "WARNING: Couldn't find collection when adding objects for prototype "
<< pair.first << std::endl;
continue;
}
for (USDPrimReader *reader : pair.second) {
Object *ob = reader->object();
if (!ob) {
continue;
}
Collection *coll = it->second;
BKE_collection_object_add(bmain, coll, ob);
DEG_id_tag_update(&coll->id, ID_RECALC_COPY_ON_WRITE);
DEG_id_tag_update_ex(bmain,
&ob->id,
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
ID_RECALC_BASE_FLAGS);
}
}
}
/* Update the given import settings with the global rotation matrix to orient /* Update the given import settings with the global rotation matrix to orient
* imported objects with Z-up, if necessary */ * imported objects with Z-up, if necessary */
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings) static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings)
@@ -134,6 +285,50 @@ struct ImportJobData {
bool import_ok; bool import_ok;
}; };
static CacheFile *create_cache_file(const ImportJobData *data)
{
if (!data) {
return nullptr;
}
CacheFile *cache_file = static_cast<CacheFile *>(
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename)));
/* Decrement the ID ref-count because it is going to be incremented for each
* modifier and constraint that it will be attached to, so since currently
* it is not used by anyone, its use count will off by one. */
id_us_min(&cache_file->id);
cache_file->is_sequence = data->params.is_sequence;
cache_file->scale = data->params.scale;
STRNCPY(cache_file->filepath, data->filename);
cache_file->scale = data->settings.scale;
return cache_file;
}
/* Apply the given cache file to the given reader, if needed. Will create a cache file
* and return it in the r_cache_file out prameter, if needed. */
static void apply_cache_file(USDPrimReader *reader,
const ImportJobData *data,
CacheFile **r_cache_file)
{
if (!(reader && reader->needs_cachefile())) {
return;
}
if (!(data && r_cache_file)) {
return;
}
if (*r_cache_file == nullptr) {
*r_cache_file = create_cache_file(data);
}
reader->apply_cache_file(*r_cache_file);
}
static void import_startjob(void *customdata, short *stop, short *do_update, float *progress) static void import_startjob(void *customdata, short *stop, short *do_update, float *progress)
{ {
ImportJobData *data = static_cast<ImportJobData *>(customdata); ImportJobData *data = static_cast<ImportJobData *>(customdata);
@@ -166,20 +361,6 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global()); BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global());
CacheFile *cache_file = static_cast<CacheFile *>(
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename)));
/* Decrement the ID ref-count because it is going to be incremented for each
* modifier and constraint that it will be attached to, so since currently
* it is not used by anyone, its use count will off by one. */
id_us_min(&cache_file->id);
cache_file->is_sequence = data->params.is_sequence;
cache_file->scale = data->params.scale;
STRNCPY(cache_file->filepath, data->filename);
data->settings.cache_file = cache_file;
*data->do_update = true; *data->do_update = true;
*data->progress = 0.05f; *data->progress = 0.05f;
@@ -201,6 +382,11 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
convert_to_z_up(stage, &data->settings); convert_to_z_up(stage, &data->settings);
if (data->params.apply_unit_conversion_scale) {
const double meters_per_unit = pxr::UsdGeomGetStageMetersPerUnit(stage);
data->settings.scale *= meters_per_unit;
}
/* Set up the stage for animated data. */ /* Set up the stage for animated data. */
if (data->params.set_frame_range) { if (data->params.set_frame_range) {
data->scene->r.sfra = stage->GetStartTimeCode(); data->scene->r.sfra = stage->GetStartTimeCode();
@@ -215,12 +401,55 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
archive->collect_readers(data->bmain); archive->collect_readers(data->bmain);
if (data->params.import_lights && data->params.create_background_shader &&
!archive->dome_lights().empty()) {
dome_light_to_world_material(
data->params, data->settings, data->scene, data->bmain, archive->dome_lights().front());
}
*data->progress = 0.2f; *data->progress = 0.2f;
const float size = static_cast<float>(archive->readers().size()); const float size = static_cast<float>(archive->readers().size());
size_t i = 0; size_t i = 0;
/* Setup parenthood */ /* Read data, set prenting and create a cache file, if needed. */
/* We defer creating a cache file until we know that we need
* one. This is not only more efficient, but also avoids
* the problem where we can't overwrite the USD the
* cachefile is referencing because it has a pointer to the
* open stage for the lifetime of the scene. */
CacheFile *cache_file = nullptr;
/* Handle instance prototypes.
* TODO(makowalski): Move this logic inside USDReaderStage? */
for (const auto &pair : archive->proto_readers()) {
for (USDPrimReader *reader : pair.second) {
if (!reader) {
continue;
}
/* TODO(makowalski): Here and below, should we call
* read_object_data() with the actual time? */
reader->read_object_data(data->bmain, 0.0);
apply_cache_file(reader, data, &cache_file);
Object *ob = reader->object();
if (!ob) {
continue;
}
const USDPrimReader *parent_reader = reader->parent();
ob->parent = parent_reader ? parent_reader->object() : nullptr;
/* TODO(makowalski): Handle progress update. */
}
}
for (USDPrimReader *reader : archive->readers()) { for (USDPrimReader *reader : archive->readers()) {
@@ -232,6 +461,8 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
reader->read_object_data(data->bmain, 0.0); reader->read_object_data(data->bmain, 0.0);
apply_cache_file(reader, data, &cache_file);
USDPrimReader *parent = reader->parent(); USDPrimReader *parent = reader->parent();
if (parent == nullptr) { if (parent == nullptr) {
@@ -275,6 +506,17 @@ static void import_endjob(void *customdata)
BKE_id_free_us(data->bmain, ob); BKE_id_free_us(data->bmain, ob);
} }
} }
for (const auto &pair : data->archive->proto_readers()) {
for (USDPrimReader *reader : pair.second) {
/* It's possible that cancellation occurred between the creation of
* the reader and the creation of the Blender object. */
if (Object *ob = reader->object()) {
BKE_id_free_us(data->bmain, ob);
}
}
}
} }
else if (data->archive) { else if (data->archive) {
/* Add object to scene. */ /* Add object to scene. */
@@ -286,6 +528,14 @@ static void import_endjob(void *customdata)
lc = BKE_layer_collection_get_active(view_layer); lc = BKE_layer_collection_get_active(view_layer);
if (!data->archive->proto_readers().empty()) {
create_proto_collections(data->bmain,
view_layer,
lc->collection,
data->archive->proto_readers(),
data->archive->readers());
}
for (USDPrimReader *reader : data->archive->readers()) { for (USDPrimReader *reader : data->archive->readers()) {
if (!reader) { if (!reader) {
@@ -312,6 +562,9 @@ static void import_endjob(void *customdata)
} }
DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS); DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
if (!data->archive->dome_lights().empty()) {
DEG_id_tag_update(&data->scene->world->id, ID_RECALC_COPY_ON_WRITE);
}
DEG_relations_tag_update(data->bmain); DEG_relations_tag_update(data->bmain);
} }
@@ -476,10 +729,11 @@ CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle,
} }
/* TODO(makowalski): The handle does not have the proper import params or settings. */ /* TODO(makowalski): The handle does not have the proper import params or settings. */
USDPrimReader *usd_reader = archive->create_reader(prim); pxr::UsdGeomXformCache xf_cache;
USDPrimReader *usd_reader = archive->create_reader(prim, &xf_cache);
if (usd_reader == nullptr) { if (usd_reader == nullptr) {
/* This object is not supported. */ /* This object is not supported */
return nullptr; return nullptr;
} }
usd_reader->object(object); usd_reader->object(object);

View File

@@ -4,11 +4,16 @@
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include "usd_writer_abstract.h" #include "usd_writer_abstract.h"
#include "usd_writer_armature.h"
#include "usd_writer_camera.h" #include "usd_writer_camera.h"
#include "usd_writer_curve.h"
#include "usd_writer_hair.h" #include "usd_writer_hair.h"
#include "usd_writer_light.h" #include "usd_writer_light.h"
#include "usd_writer_mesh.h" #include "usd_writer_mesh.h"
#include "usd_writer_metaball.h" #include "usd_writer_metaball.h"
#include "usd_writer_particle.h"
#include "usd_writer_skel_root.h"
#include "usd_writer_skinned_mesh.h"
#include "usd_writer_transform.h" #include "usd_writer_transform.h"
#include <string> #include <string>
@@ -64,44 +69,91 @@ const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
return export_time_; return export_time_;
} }
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context) USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context,
bool mergeTransformAndShape)
{ {
return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_}; pxr::SdfPath prim_path = pxr::SdfPath(std::string(params_.root_prim_path) +
context->export_path);
// TODO: Somewhat of a workaround. There could be a better way to incoporate this...
bool can_merge_with_xform = !(
this->params_.export_armatures &&
(is_skinned_mesh(context->object) || context->object->type == OB_ARMATURE));
if (can_merge_with_xform && mergeTransformAndShape)
prim_path = prim_path.GetParentPath();
return USDExporterContext{depsgraph_, stage_, prim_path, this, params_};
} }
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer( AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
const HierarchyContext *context) const HierarchyContext *context)
{ {
if (this->params_.export_armatures &&
(is_skinned_mesh(context->object) || context->object->type == OB_ARMATURE)) {
return new USDSkelRootWriter(create_usd_export_context(context));
}
return new USDTransformWriter(create_usd_export_context(context)); return new USDTransformWriter(create_usd_export_context(context));
} }
AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context) AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
{ {
USDExporterContext usd_export_context = create_usd_export_context(context); if (context->is_instance() && params_.use_instancing) {
return nullptr;
}
USDExporterContext usd_export_context = create_usd_export_context(
context, params_.merge_transform_and_shape);
USDAbstractWriter *data_writer = nullptr; USDAbstractWriter *data_writer = nullptr;
switch (context->object->type) { switch (context->object->type) {
case OB_MESH: case OB_MESH:
data_writer = new USDMeshWriter(usd_export_context); if (usd_export_context.export_params.export_meshes) {
if (usd_export_context.export_params.export_armatures &&
is_skinned_mesh(context->object)) {
data_writer = new USDSkinnedMeshWriter(usd_export_context);
}
else {
data_writer = new USDMeshWriter(usd_export_context);
}
}
else
return nullptr;
break; break;
case OB_CAMERA: case OB_CAMERA:
data_writer = new USDCameraWriter(usd_export_context); if (usd_export_context.export_params.export_cameras)
data_writer = new USDCameraWriter(usd_export_context);
else
return nullptr;
break; break;
case OB_LAMP: case OB_LAMP:
data_writer = new USDLightWriter(usd_export_context); if (usd_export_context.export_params.export_lights)
data_writer = new USDLightWriter(usd_export_context);
else
return nullptr;
break; break;
case OB_MBALL: case OB_MBALL:
data_writer = new USDMetaballWriter(usd_export_context); data_writer = new USDMetaballWriter(usd_export_context);
break; break;
case OB_CURVES_LEGACY:
if (usd_export_context.export_params.export_curves) {
data_writer = new USDCurveWriter(usd_export_context);
}
else
return nullptr;
break;
case OB_ARMATURE:
if (usd_export_context.export_params.export_armatures) {
data_writer = new USDArmatureWriter(usd_export_context);
}
else
return nullptr;
break;
case OB_EMPTY: case OB_EMPTY:
case OB_CURVES_LEGACY:
case OB_SURF: case OB_SURF:
case OB_FONT: case OB_FONT:
case OB_SPEAKER: case OB_SPEAKER:
case OB_LIGHTPROBE: case OB_LIGHTPROBE:
case OB_LATTICE: case OB_LATTICE:
case OB_ARMATURE:
case OB_GPENCIL: case OB_GPENCIL:
return nullptr; return nullptr;
case OB_TYPE_MAX: case OB_TYPE_MAX:
@@ -109,7 +161,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
return nullptr; return nullptr;
} }
if (!data_writer->is_supported(context)) { if (data_writer && !data_writer->is_supported(context)) {
delete data_writer; delete data_writer;
return nullptr; return nullptr;
} }
@@ -119,6 +171,10 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context) AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
{ {
if (context->is_instance() && params_.use_instancing) {
return nullptr;
}
if (!params_.export_hair) { if (!params_.export_hair) {
return nullptr; return nullptr;
} }
@@ -126,9 +182,36 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const Hierarch
} }
AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer( AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(
const HierarchyContext *UNUSED(context)) const HierarchyContext *context)
{ {
return nullptr; if (context->is_instance() && params_.use_instancing) {
return nullptr;
}
if (!params_.export_particles) {
return nullptr;
}
return new USDParticleWriter(create_usd_export_context(context));
}
/* Don't generate data writers for instances. */
bool USDHierarchyIterator::include_data_writers(const HierarchyContext *context) const
{
if (!context) {
return false;
}
return !(params_.use_instancing && context->is_instance());
}
/* Don't generate writers for children of instances. */
bool USDHierarchyIterator::include_child_writers(const HierarchyContext *context) const
{
if (!context) {
return false;
}
return !(params_.use_instancing && context->is_instance());
} }
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -48,8 +48,12 @@ class USDHierarchyIterator : public AbstractHierarchyIterator {
virtual void release_writer(AbstractHierarchyWriter *writer) override; virtual void release_writer(AbstractHierarchyWriter *writer) override;
virtual bool include_data_writers(const HierarchyContext *context) const override;
virtual bool include_child_writers(const HierarchyContext *context) const override;
private: private:
USDExporterContext create_usd_export_context(const HierarchyContext *context); USDExporterContext create_usd_export_context(const HierarchyContext *context,
bool mergeTransformAndShape = false);
}; };
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -0,0 +1,574 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_light_convert.h"
#include "usd.h"
#include "usd_reader_prim.h"
#include "usd_writer_material.h"
#include <pxr/base/gf/matrix4f.h>
#include <pxr/base/gf/rotation.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/xformCache.h>
#include <pxr/usd/usdGeom/xformCommonAPI.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include "BKE_image.h"
#include "BKE_light.h"
#include "BKE_main.h"
#include "BKE_node.h"
#include "BKE_node_tree_update.h"
#include "BKE_scene.h"
#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "DNA_light_types.h"
#include "DNA_scene_types.h"
#include "DNA_world_types.h"
#include "ED_node.h"
#include <iostream>
#include <string>
namespace usdtokens {
// Attribute names.
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
static const pxr::TfToken texture_file("texture:file", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace {
template<typename T>
bool get_authored_value(const pxr::UsdAttribute attr, const double motionSampleTime, T *r_value)
{
if (attr && attr.HasAuthoredValue()) {
return attr.Get<T>(r_value, motionSampleTime);
}
return false;
}
struct WorldNtreeSearchResults {
const USDExportParams params;
pxr::UsdStageRefPtr stage;
float world_color[3];
float world_intensity;
float tex_rot[3];
std::string file_path;
float color_mult[3];
bool background_found;
bool env_tex_found;
bool mult_found;
WorldNtreeSearchResults(const USDExportParams &in_params, pxr::UsdStageRefPtr in_stage)
: params(in_params),
stage(in_stage),
world_intensity(0.0f),
background_found(false),
env_tex_found(false),
mult_found(false)
{
}
};
} // End anonymous namespace.
namespace blender::io::usd {
static const float nits_to_watts_per_meter_sq = 0.0014641f;
static const float watts_per_meter_sq_to_nits = 1.0f / nits_to_watts_per_meter_sq;
static bool node_search(bNode *fromnode, bNode *tonode, void *userdata, const bool reversed)
{
if (!(userdata && fromnode && tonode)) {
return true;
}
/* TODO(makowalski): can we validate that node connectiona are correct? */
WorldNtreeSearchResults *res = reinterpret_cast<WorldNtreeSearchResults *>(userdata);
if (!res->background_found && ELEM(fromnode->type, SH_NODE_BACKGROUND)) {
/* Get light color and intensity */
bNodeSocketValueRGBA *color_data =
(bNodeSocketValueRGBA *)((bNodeSocket *)BLI_findlink(&fromnode->inputs, 0))->default_value;
bNodeSocketValueFloat *strength_data = (bNodeSocketValueFloat *)((bNodeSocket *)BLI_findlink(
&fromnode->inputs, 1))
->default_value;
res->background_found = true;
res->world_intensity = strength_data->value;
res->world_color[0] = color_data->value[0];
res->world_color[1] = color_data->value[1];
res->world_color[2] = color_data->value[2];
}
else if (!res->env_tex_found && ELEM(fromnode->type, SH_NODE_TEX_ENVIRONMENT)) {
/* Get env tex path. */
res->file_path = get_tex_image_asset_path(fromnode, res->stage, res->params);
if (!res->file_path.empty()) {
/* Get the rotation. */
NodeTexEnvironment *tex = static_cast<NodeTexEnvironment *>(fromnode->storage);
copy_v3_v3(res->tex_rot, tex->base.tex_mapping.rot);
res->env_tex_found = true;
if (res->params.export_textures) {
export_texture(fromnode, res->stage, res->params.overwrite_textures);
}
}
}
else if (!res->env_tex_found && !res->mult_found && ELEM(fromnode->type, SH_NODE_VECTOR_MATH)) {
if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) {
res->mult_found = true;
bNodeSocket *vec_sock = nodeFindSocket(fromnode, SOCK_IN, "Vector");
if (vec_sock) {
vec_sock = vec_sock->next;
}
if (vec_sock) {
copy_v3_v3(res->color_mult, ((bNodeSocketValueVector *)vec_sock->default_value)->value);
}
}
}
return true;
}
/* Return the scale factor to convert nits to light energy
* (Watts or Watts per meter squared) for the given light. */
float nits_to_energy_scale_factor(const Light *light,
const float meters_per_unit,
const float radius_scale)
{
if (!light) {
return 1.0f;
}
/* Compute meters per unit squared. */
const float mpu_sq = meters_per_unit * meters_per_unit;
float scale = nits_to_watts_per_meter_sq;
/* Scale by the light surface area, for lights other than sun. */
switch (light->type) {
case LA_AREA:
switch (light->area_shape) {
case LA_AREA_DISK:
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
float r = light->area_size / 2.0f;
scale *= 2.0f * M_PI * (r * r) * mpu_sq;
break;
}
case LA_AREA_RECT: {
scale *= light->area_size * light->area_sizey * mpu_sq;
break;
}
case LA_AREA_SQUARE: {
scale *= light->area_size * light->area_size * mpu_sq;
break;
}
}
break;
case LA_LOCAL: {
float r = light->area_size * radius_scale;
scale *= 4.0f * M_PI * (r * r) * mpu_sq;
break;
}
case LA_SPOT: {
float r = light->area_size * radius_scale;
float angle = light->spotsize / 2.0f;
scale *= 2.0f * M_PI * (r * r) * (1.0f - cosf(angle)) * mpu_sq;
break;
}
case LA_SUN: {
/* Sun energy is Watts per square meter so we don't scale by area. */
break;
}
default:
break;
}
return scale;
}
/* If the Blender scene has an environment texture,
* export it as a USD dome light. */
void world_material_to_dome_light(const USDExportParams &params,
const Scene *scene,
pxr::UsdStageRefPtr stage)
{
if (!(stage && scene && scene->world && scene->world->use_nodes && scene->world->nodetree)) {
return;
}
/* Find the world output. */
bNode *output = ntreeFindType(scene->world->nodetree, SH_NODE_OUTPUT_WORLD);
if (!output) {
/* No output, no valid network to convert. */
return;
}
pxr::SdfPath light_path(std::string(params.root_prim_path) + "/lights");
usd_define_or_over<pxr::UsdGeomScope>(stage, light_path, params.export_as_overs);
WorldNtreeSearchResults res(params, stage);
nodeChainIter(scene->world->nodetree, output, node_search, &res, true);
if (!(res.background_found || res.env_tex_found)) {
/* No nodes to convert */
return;
}
/* Create USD dome light. */
pxr::SdfPath env_light_path = light_path.AppendChild(pxr::TfToken("environment"));
pxr::UsdLuxDomeLight dome_light = usd_define_or_over<pxr::UsdLuxDomeLight>(
stage, env_light_path, params.export_as_overs);
if (res.env_tex_found) {
/* Convert radians to degrees. */
mul_v3_fl(res.tex_rot, 180.0f / M_PI);
/* Note the negative Z rotation with 180 deg offset, to match Create and Maya. */
pxr::GfVec3f rot(-res.tex_rot[0], -res.tex_rot[1], -res.tex_rot[2] - 180.0f);
pxr::UsdGeomXformCommonAPI xform_api(dome_light);
/* We reverse the rotation order to convert between extrinsic and intrinsic euler angles. */
xform_api.SetRotate(rot, pxr::UsdGeomXformCommonAPI::RotationOrderZYX);
pxr::SdfAssetPath path(res.file_path);
dome_light.CreateTextureFileAttr().Set(path);
if (params.backward_compatible) {
pxr::UsdAttribute attr = dome_light.GetPrim().CreateAttribute(
usdtokens::texture_file, pxr::SdfValueTypeNames->Asset, true);
if (attr) {
attr.Set(path);
}
}
if (res.mult_found) {
pxr::GfVec3f color_val(res.color_mult[0], res.color_mult[1], res.color_mult[2]);
dome_light.CreateColorAttr().Set(color_val);
if (params.backward_compatible) {
pxr::UsdAttribute attr = dome_light.GetPrim().CreateAttribute(
usdtokens::color, pxr::SdfValueTypeNames->Color3f, true);
if (attr) {
attr.Set(color_val);
}
}
}
}
else {
pxr::GfVec3f color_val(res.world_color[0], res.world_color[1], res.world_color[2]);
dome_light.CreateColorAttr().Set(color_val);
if (params.backward_compatible) {
pxr::UsdAttribute attr = dome_light.GetPrim().CreateAttribute(
usdtokens::color, pxr::SdfValueTypeNames->Color3f, true);
if (attr) {
attr.Set(color_val);
}
}
}
if (res.background_found) {
float usd_intensity = res.world_intensity * params.light_intensity_scale;
if (params.convert_light_to_nits) {
usd_intensity *= watts_per_meter_sq_to_nits;
}
dome_light.CreateIntensityAttr().Set(usd_intensity);
if (params.backward_compatible) {
pxr::UsdAttribute attr = dome_light.GetPrim().CreateAttribute(
usdtokens::intensity, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(usd_intensity);
}
}
}
}
/* Import the dome light as a world material. */
void dome_light_to_world_material(const USDImportParams &params,
const ImportSettings &settings,
Scene *scene,
Main *bmain,
const pxr::UsdLuxDomeLight &dome_light,
const double time)
{
if (!(scene && scene->world && dome_light)) {
return;
}
if (!scene->world->use_nodes) {
scene->world->use_nodes = true;
}
if (!scene->world->nodetree) {
scene->world->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree");
if (!scene->world->nodetree) {
std::cerr << "WARNING: couldn't create world ntree.\n";
return;
}
}
bNodeTree *ntree = scene->world->nodetree;
bNode *output = nullptr;
bNode *shader = nullptr;
/* We never delete existing nodes, but we might disconnect them
* and move them out of the way. */
/* Look for the output and background shader nodes, which we will reuse.
* TODO(makowalski): add logic to properly verify node connections. */
for (bNode *node = static_cast<bNode *>(ntree->nodes.first); node; node = node->next) {
if (ELEM(node->type, SH_NODE_OUTPUT_WORLD)) {
output = node;
}
else if (ELEM(node->type, SH_NODE_BACKGROUND)) {
shader = node;
}
else {
/* Move node out of the way. */
node->locy += 300;
}
}
/* Create the output and shader nodes, if they don't exist. */
if (!output) {
output = nodeAddStaticNode(NULL, ntree, SH_NODE_OUTPUT_WORLD);
if (!output) {
std::cerr << "WARNING: couldn't create world output node.\n";
return;
}
output->locx = 300.0f;
output->locy = 300.0f;
}
if (!shader) {
shader = nodeAddStaticNode(NULL, ntree, SH_NODE_BACKGROUND);
if (!shader) {
std::cerr << "WARNING: couldn't create world shader node.\n";
return;
}
nodeAddLink(scene->world->nodetree,
shader,
nodeFindSocket(shader, SOCK_OUT, "Background"),
output,
nodeFindSocket(output, SOCK_IN, "Surface"));
bNodeSocket *color_sock = nodeFindSocket(shader, SOCK_IN, "Color");
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, &scene->world->horr);
shader->locx = output->locx - 200;
shader->locy = output->locy;
}
/* Make sure the first input to the shader node is disconnected. */
bNodeSocket *shader_input = static_cast<bNodeSocket *>(BLI_findlink(&shader->inputs, 0));
if (shader_input && shader_input->link) {
nodeRemLink(ntree, shader_input->link);
}
pxr::UsdAttribute intensity_attr = dome_light.GetIntensityAttr();
float intensity = 1.0f;
intensity_attr.Get(&intensity, time);
if (!get_authored_value(dome_light.GetIntensityAttr(), time, &intensity)) {
dome_light.GetPrim().GetAttribute(usdtokens::intensity).Get(&intensity, time);
}
intensity *= params.light_intensity_scale;
if (params.convert_light_from_nits) {
intensity *= nits_to_watts_per_meter_sq;
}
bNodeSocket *strength_sock = nodeFindSocket(shader, SOCK_IN, "Strength");
((bNodeSocketValueFloat *)strength_sock->default_value)->value = intensity;
pxr::SdfAssetPath tex_path;
bool has_tex = get_authored_value(dome_light.GetTextureFileAttr(), time, &tex_path);
if (!has_tex) {
has_tex = dome_light.GetPrim().GetAttribute(usdtokens::texture_file).Get(&tex_path, time);
}
pxr::GfVec3f color;
bool has_color = get_authored_value(dome_light.GetColorAttr(), time, &color);
if (!has_color) {
has_color = dome_light.GetPrim().GetAttribute(usdtokens::color).Get(&color, time);
}
if (!has_tex) {
if (has_color) {
bNodeSocket *color_sock = nodeFindSocket(shader, SOCK_IN, "Color");
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, color.data());
}
nodeSetActive(ntree, output);
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
return;
}
/* If the light has authored color, create the color multiply for the env texture output. */
bNode *mult = nullptr;
if (has_color) {
mult = nodeAddStaticNode(NULL, ntree, SH_NODE_VECTOR_MATH);
if (!mult) {
std::cerr << "WARNING: couldn't create vector multiply node.\n";
return;
}
nodeAddLink(scene->world->nodetree,
mult,
nodeFindSocket(mult, SOCK_OUT, "Vector"),
shader,
nodeFindSocket(shader, SOCK_IN, "Color"));
mult->locx = shader->locx - 200;
mult->locy = shader->locy;
mult->custom1 = NODE_VECTOR_MATH_MULTIPLY;
bNodeSocket *vec_sock = nodeFindSocket(mult, SOCK_IN, "Vector");
if (vec_sock) {
vec_sock = vec_sock->next;
}
if (vec_sock) {
copy_v3_v3(((bNodeSocketValueVector *)vec_sock->default_value)->value, color.data());
}
else {
std::cout << "ERROR: couldn't find vector multiply second vector input.\n";
}
}
bNode *tex = nodeAddStaticNode(NULL, ntree, SH_NODE_TEX_ENVIRONMENT);
if (!tex) {
std::cerr << "WARNING: couldn't create world environment texture node.\n";
return;
}
if (mult) {
nodeAddLink(scene->world->nodetree,
tex,
nodeFindSocket(tex, SOCK_OUT, "Color"),
mult,
nodeFindSocket(mult, SOCK_IN, "Vector"));
tex->locx = mult->locx - 400;
tex->locy = mult->locy;
}
else {
nodeAddLink(scene->world->nodetree,
tex,
nodeFindSocket(tex, SOCK_OUT, "Color"),
shader,
nodeFindSocket(shader, SOCK_IN, "Color"));
tex->locx = shader->locx - 400;
tex->locy = shader->locy;
}
std::string tex_path_str = tex_path.GetResolvedPath();
if (tex_path_str.empty()) {
std::cerr << "WARNING: Couldn't get resolved path for asset " << tex_path
<< " for Texture Image node.\n";
return;
}
Image *image = BKE_image_load_exists(bmain, tex_path_str.c_str());
if (!image) {
std::cerr << "WARNING: Couldn't open image file '" << tex_path_str
<< "' for Texture Image node.\n";
return;
}
tex->id = &image->id;
/* Set the transform. */
pxr::UsdGeomXformCache xf_cache(time);
pxr::GfMatrix4d xf = xf_cache.GetLocalToWorldTransform(dome_light.GetPrim());
if (settings.do_convert_mat) {
/* Apply matrix for z-up conversion. */
pxr::GfMatrix4d convert_xf(pxr::GfMatrix4f(settings.conversion_mat));
xf *= convert_xf;
}
pxr::GfRotation rot = xf.ExtractRotation();
pxr::GfVec3d rot_vec = rot.Decompose(
pxr::GfVec3d::XAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::ZAxis());
NodeTexEnvironment *tex_env = static_cast<NodeTexEnvironment *>(tex->storage);
tex_env->base.tex_mapping.rot[0] = -static_cast<float>(rot_vec[0]);
tex_env->base.tex_mapping.rot[1] = -static_cast<float>(rot_vec[1]);
tex_env->base.tex_mapping.rot[2] = 180 - static_cast<float>(rot_vec[2]);
/* Convert radians to degrees. */
mul_v3_fl(tex_env->base.tex_mapping.rot, M_PI / 180.0f);
eul_to_mat4(tex_env->base.tex_mapping.mat, tex_env->base.tex_mapping.rot);
nodeSetActive(ntree, output);
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,49 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdLux/domeLight.h>
struct Light;
struct Main;
struct Scene;
struct USDExportParams;
struct USDImportParams;
namespace blender::io::usd {
struct ImportSettings;
float nits_to_energy_scale_factor(const Light *light,
float meters_per_unit,
float radius_scale = 1.0f);
void world_material_to_dome_light(const USDExportParams &params,
const Scene *scene,
pxr::UsdStageRefPtr stage);
void dome_light_to_world_material(const USDImportParams &params,
const ImportSettings &settings,
Scene *scene,
Main *bmain,
const pxr::UsdLuxDomeLight &dome_light,
const double time = 0.0);
} // namespace blender::io::usd

View File

@@ -27,7 +27,11 @@ void USDCameraReader::create_object(Main *bmain, const double /* motionSampleTim
void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime) void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime)
{ {
Camera *bcam = (Camera *)object_->data; if (!object_->data) {
return;
}
Camera *bcam = static_cast<Camera *>(object_->data);
pxr::UsdGeomCamera cam_prim(prim_); pxr::UsdGeomCamera cam_prim(prim_);
@@ -35,46 +39,37 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim
return; return;
} }
pxr::VtValue val; pxr::GfCamera usd_cam = cam_prim.GetCamera(motionSampleTime);
cam_prim.GetFocalLengthAttr().Get(&val, motionSampleTime);
pxr::VtValue verApOffset;
cam_prim.GetVerticalApertureOffsetAttr().Get(&verApOffset, motionSampleTime);
pxr::VtValue horApOffset;
cam_prim.GetHorizontalApertureOffsetAttr().Get(&horApOffset, motionSampleTime);
pxr::VtValue clippingRangeVal;
cam_prim.GetClippingRangeAttr().Get(&clippingRangeVal, motionSampleTime);
pxr::VtValue focalDistanceVal;
cam_prim.GetFocusDistanceAttr().Get(&focalDistanceVal, motionSampleTime);
pxr::VtValue fstopVal;
cam_prim.GetFStopAttr().Get(&fstopVal, motionSampleTime);
pxr::VtValue projectionVal;
cam_prim.GetProjectionAttr().Get(&projectionVal, motionSampleTime);
pxr::VtValue verAp;
cam_prim.GetVerticalApertureAttr().Get(&verAp, motionSampleTime);
pxr::VtValue horAp;
cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime);
bcam->lens = val.Get<float>(); const float apperture_x = usd_cam.GetHorizontalAperture();
/* TODO(@makowalski): support sensor size. */ const float apperture_y = usd_cam.GetVerticalAperture();
#if 0 const float h_film_offset = usd_cam.GetHorizontalApertureOffset();
bcam->sensor_x = 0.0f; const float v_film_offset = usd_cam.GetVerticalApertureOffset();
bcam->sensor_y = 0.0f; const float film_aspect = apperture_x / apperture_y;
#endif
bcam->shiftx = verApOffset.Get<float>();
bcam->shifty = horApOffset.Get<float>();
bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP : bcam->type = usd_cam.GetProjection() == pxr::GfCamera::Perspective ? CAM_PERSP : CAM_ORTHO;
CAM_ORTHO;
/* Calling UncheckedGet() to silence compiler warnings. */ bcam->lens = usd_cam.GetFocalLength();
bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[0]);
bcam->clip_end = clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[1];
bcam->dof.focus_distance = focalDistanceVal.Get<float>(); bcam->sensor_x = apperture_x;
bcam->dof.aperture_fstop = static_cast<float>(fstopVal.Get<float>()); bcam->sensor_y = apperture_y;
bcam->shiftx = h_film_offset / apperture_x;
bcam->shifty = v_film_offset / apperture_y / film_aspect;
pxr::GfRange1f usd_clip_range = usd_cam.GetClippingRange();
bcam->clip_start = usd_clip_range.GetMin() * settings_->scale;
bcam->clip_end = usd_clip_range.GetMax() * settings_->scale;
bcam->dof.focus_distance = usd_cam.GetFocusDistance() * settings_->scale;
bcam->dof.aperture_fstop = usd_cam.GetFStop();
if (bcam->dof.focus_distance > 0.0f || bcam->dof.aperture_fstop > 0.0f) {
bcam->dof.flag |= CAM_DOF_ENABLED;
}
if (bcam->type == CAM_ORTHO) { if (bcam->type == CAM_ORTHO) {
bcam->ortho_scale = max_ff(verAp.Get<float>(), horAp.Get<float>()); bcam->ortho_scale = max_ff(apperture_x, apperture_y);
} }
USDXformReader::read_object_data(bmain, motionSampleTime); USDXformReader::read_object_data(bmain, motionSampleTime);

View File

@@ -20,18 +20,34 @@
namespace blender::io::usd { namespace blender::io::usd {
void USDGeomReader::apply_cache_file(CacheFile *cache_file)
{
if (!cache_file) {
return;
}
if (needs_cachefile_ && object_) {
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
BLI_addtail(&object_->modifiers, md);
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
mcmd->cache_file = cache_file;
id_us_plus(&mcmd->cache_file->id);
mcmd->read_flag = import_params_.mesh_read_flag;
BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX);
}
if (USDXformReader::needs_cachefile()) {
USDXformReader::apply_cache_file(cache_file);
}
}
void USDGeomReader::add_cache_modifier() void USDGeomReader::add_cache_modifier()
{ {
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache); /* Defer creating modifiers until a cache file is provided. */
BLI_addtail(&object_->modifiers, md); needs_cachefile_ = true;
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
mcmd->cache_file = settings_->cache_file;
id_us_plus(&mcmd->cache_file->id);
mcmd->read_flag = import_params_.mesh_read_flag;
BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX);
} }
void USDGeomReader::add_subdiv_modifier() void USDGeomReader::add_subdiv_modifier()

View File

@@ -10,12 +10,14 @@ struct Mesh;
namespace blender::io::usd { namespace blender::io::usd {
class USDGeomReader : public USDXformReader { class USDGeomReader : public USDXformReader {
private:
bool needs_cachefile_;
public: public:
USDGeomReader(const pxr::UsdPrim &prim, USDGeomReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params, const USDImportParams &import_params,
const ImportSettings &settings) const ImportSettings &settings)
: USDXformReader(prim, import_params, settings) : USDXformReader(prim, import_params, settings), needs_cachefile_(false)
{ {
} }
@@ -29,6 +31,12 @@ class USDGeomReader : public USDXformReader {
return true; return true;
} }
bool needs_cachefile() override
{
return needs_cachefile_ || USDXformReader::needs_cachefile();
}
void apply_cache_file(CacheFile *cache_file) override;
void add_cache_modifier(); void add_cache_modifier();
void add_subdiv_modifier(); void add_subdiv_modifier();
}; };

View File

@@ -0,0 +1,64 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_reader_instance.h"
#include "BKE_object.h"
#include "DNA_object_types.h"
#include <iostream>
namespace blender::io::usd {
USDInstanceReader::USDInstanceReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDXformReader(prim, import_params, settings)
{
}
bool USDInstanceReader::valid() const
{
return prim_.IsValid() && prim_.IsInstance();
}
void USDInstanceReader::create_object(Main *bmain, const double /* motionSampleTime */)
{
this->object_ = BKE_object_add_only_object(bmain, OB_EMPTY, name_.c_str());
this->object_->data = nullptr;
this->object_->transflag |= OB_DUPLICOLLECTION;
}
void USDInstanceReader::set_instance_collection(Collection *coll)
{
if (this->object_) {
this->object_->instance_collection = coll;
}
}
pxr::SdfPath USDInstanceReader::proto_path() const
{
if (pxr::UsdPrim proto = prim_.GetPrototype()) {
return proto.GetPath();
}
return pxr::SdfPath();
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,47 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include "usd_reader_xform.h"
#include <pxr/usd/usdGeom/xform.h>
struct Collection;
namespace blender::io::usd {
/* Wraps the UsdGeomXform schema. Creates a Blender Empty object. */
class USDInstanceReader : public USDXformReader {
public:
USDInstanceReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings);
bool valid() const override;
void create_object(Main *bmain, double motionSampleTime) override;
void set_instance_collection(Collection *coll);
pxr::SdfPath proto_path() const;
};
} // namespace blender::io::usd

View File

@@ -2,6 +2,7 @@
* Copyright 2021 Tangent Animation. All rights reserved. */ * Copyright 2021 Tangent Animation. All rights reserved. */
#include "usd_reader_light.h" #include "usd_reader_light.h"
#include "usd_light_convert.h"
#include "BKE_light.h" #include "BKE_light.h"
#include "BKE_object.h" #include "BKE_object.h"
@@ -9,6 +10,7 @@
#include "DNA_light_types.h" #include "DNA_light_types.h"
#include "DNA_object_types.h" #include "DNA_object_types.h"
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdLux/diskLight.h> #include <pxr/usd/usdLux/diskLight.h>
#include <pxr/usd/usdLux/distantLight.h> #include <pxr/usd/usdLux/distantLight.h>
#include <pxr/usd/usdLux/rectLight.h> #include <pxr/usd/usdLux/rectLight.h>
@@ -17,8 +19,52 @@
#include <iostream> #include <iostream>
namespace usdtokens {
// Attribute names.
static const pxr::TfToken angle("angle", pxr::TfToken::Immortal);
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
static const pxr::TfToken height("height", pxr::TfToken::Immortal);
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
static const pxr::TfToken radius("radius", pxr::TfToken::Immortal);
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
static const pxr::TfToken width("width", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace {
template<typename T>
bool get_authored_value(const pxr::UsdAttribute attr, const double motionSampleTime, T *r_value)
{
if (attr && attr.HasAuthoredValue()) {
return attr.Get<T>(r_value, motionSampleTime);
}
return false;
}
} // End anonymous namespace.
namespace blender::io::usd { namespace blender::io::usd {
USDLightReader::USDLightReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings,
pxr::UsdGeomXformCache *xf_cache)
: USDXformReader(prim, import_params, settings), usd_world_scale_(1.0f)
{
if (xf_cache && import_params.convert_light_from_nits) {
pxr::GfMatrix4d xf = xf_cache->GetLocalToWorldTransform(prim);
pxr::GfMatrix4d r;
pxr::GfVec3d s;
pxr::GfMatrix4d u;
pxr::GfVec3d t;
pxr::GfMatrix4d p;
xf.Factor(&r, &s, &u, &t, &p);
usd_world_scale_ = (s[0] + s[1] + s[2]) / 3.0f;
}
}
void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */) void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */)
{ {
Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str())); Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str()));
@@ -76,12 +122,10 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
/* Set light values. */ /* Set light values. */
if (pxr::UsdAttribute intensity_attr = light_api.GetIntensityAttr()) { /* In USD 21, light attributes were renamed to have an 'inputs:' prefix
float intensity = 0.0f; * (e.g., 'inputs:intensity'). Here and below, for backward compatibility
if (intensity_attr.Get(&intensity, motionSampleTime)) { * with older USD versions, we also query attributes using the previous
blight->energy = intensity * this->import_params_.light_intensity_scale; * naming scheme that omits this prefix. */
}
}
/* TODO(makowalsk): Not currently supported. */ /* TODO(makowalsk): Not currently supported. */
#if 0 #if 0
@@ -95,20 +139,18 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime); light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime);
#endif #endif
if (pxr::UsdAttribute spec_attr = light_api.GetSpecularAttr()) { float specular;
float spec = 0.0f; if (get_authored_value(light_api.GetSpecularAttr(), motionSampleTime, &specular) ||
if (spec_attr.Get(&spec, motionSampleTime)) { prim_.GetAttribute(usdtokens::specular).Get(&specular, motionSampleTime)) {
blight->spec_fac = spec; blight->spec_fac = specular;
}
} }
if (pxr::UsdAttribute color_attr = light_api.GetColorAttr()) { pxr::GfVec3f color;
pxr::GfVec3f color; if (get_authored_value(light_api.GetColorAttr(), motionSampleTime, &color) ||
if (color_attr.Get(&color, motionSampleTime)) { prim_.GetAttribute(usdtokens::color).Get(&color, motionSampleTime)) {
blight->r = color[0]; blight->r = color[0];
blight->g = color[1]; blight->g = color[1];
blight->b = color[2]; blight->b = color[2];
}
} }
/* TODO(makowalski): Not currently supported. */ /* TODO(makowalski): Not currently supported. */
@@ -123,6 +165,7 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime); light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime);
#endif #endif
// XXX - apply scene scale to local and spot lights but not area lights (?)
switch (blight->type) { switch (blight->type) {
case LA_AREA: case LA_AREA:
if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) { if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) {
@@ -133,18 +176,16 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break; break;
} }
if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) { float width;
float width = 0.0f; if (get_authored_value(rect_light.GetWidthAttr(), motionSampleTime, &width) ||
if (width_attr.Get(&width, motionSampleTime)) { prim_.GetAttribute(usdtokens::width).Get(&width, motionSampleTime)) {
blight->area_size = width; blight->area_size = width;
}
} }
if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) { float height;
float height = 0.0f; if (get_authored_value(rect_light.GetHeightAttr(), motionSampleTime, &height) ||
if (height_attr.Get(&height, motionSampleTime)) { prim_.GetAttribute(usdtokens::height).Get(&height, motionSampleTime)) {
blight->area_sizey = height; blight->area_sizey = height;
}
} }
} }
else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) { else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) {
@@ -155,11 +196,10 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break; break;
} }
if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) { float radius;
float radius = 0.0f; if (get_authored_value(disk_light.GetRadiusAttr(), motionSampleTime, &radius) ||
if (radius_attr.Get(&radius, motionSampleTime)) { prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->area_size = radius * 2.0f; blight->area_size = radius * 2.0f;
}
} }
} }
break; break;
@@ -172,28 +212,25 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break; break;
} }
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { float radius;
float radius = 0.0f; if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
if (radius_attr.Get(&radius, motionSampleTime)) { prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->area_size = radius; blight->area_size = radius;
}
} }
} }
break; break;
case LA_SPOT: case LA_SPOT:
if (prim_.IsA<pxr::UsdLuxSphereLight>()) { if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
pxr::UsdLuxSphereLight sphere_light(prim_); pxr::UsdLuxSphereLight sphere_light(prim_);
if (!sphere_light) { if (!sphere_light) {
break; break;
} }
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) { float radius;
float radius = 0.0f; if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
if (radius_attr.Get(&radius, motionSampleTime)) { prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->area_size = radius; blight->area_size = radius;
}
} }
if (!shaping_api) { if (!shaping_api) {
@@ -203,7 +240,17 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) { if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) {
float cone_angle = 0.0f; float cone_angle = 0.0f;
if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) { if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) {
blight->spotsize = cone_angle * ((float)M_PI / 180.0f) * 2.0f; float spot_size = cone_angle * ((float)M_PI / 180.0f) * 2.0f;
if (spot_size <= M_PI) {
blight->spotsize = spot_size;
}
else {
/* The spot size is greter the 180 degrees, which Blender doesn't support so we
* make this a sphere light instead. */
blight->type = LA_LOCAL;
break;
}
} }
} }
@@ -223,16 +270,36 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break; break;
} }
if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) { float angle;
float angle = 0.0f; if (get_authored_value(distant_light.GetAngleAttr(), motionSampleTime, &angle) ||
if (angle_attr.Get(&angle, motionSampleTime)) { prim_.GetAttribute(usdtokens::angle).Get(&angle, motionSampleTime)) {
blight->sun_angle = angle * (float)M_PI / 180.0f; blight->sun_angle = angle * (float)M_PI / 180.0f;
}
} }
} }
break; break;
} }
float intensity;
if (get_authored_value(light_api.GetIntensityAttr(), motionSampleTime, &intensity) ||
prim_.GetAttribute(usdtokens::intensity).Get(&intensity, motionSampleTime)) {
float intensity_scale = this->import_params_.light_intensity_scale;
if (import_params_.convert_light_from_nits) {
/* It's important that we perform the light unit conversion before applying any scaling to
* the light size, so we can use the USD's meters per unit value. */
const float meters_per_unit = static_cast<float>(
pxr::UsdGeomGetStageMetersPerUnit(prim_.GetStage()));
intensity_scale *= nits_to_energy_scale_factor(blight, meters_per_unit * usd_world_scale_);
}
blight->energy = intensity * intensity_scale;
}
if ((blight->type == LA_SPOT || blight->type == LA_LOCAL) && import_params_.scale_light_radius) {
blight->area_size *= settings_->scale;
}
USDXformReader::read_object_data(bmain, motionSampleTime); USDXformReader::read_object_data(bmain, motionSampleTime);
} }

View File

@@ -5,17 +5,19 @@
#include "usd.h" #include "usd.h"
#include "usd_reader_xform.h" #include "usd_reader_xform.h"
#include <pxr/usd/usdGeom/xformCache.h>
namespace blender::io::usd { namespace blender::io::usd {
class USDLightReader : public USDXformReader { class USDLightReader : public USDXformReader {
private:
float usd_world_scale_;
public: public:
USDLightReader(const pxr::UsdPrim &prim, USDLightReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params, const USDImportParams &import_params,
const ImportSettings &settings) const ImportSettings &settings,
: USDXformReader(prim, import_params, settings) pxr::UsdGeomXformCache *xf_cache = nullptr);
{
}
void create_object(Main *bmain, double motionSampleTime) override; void create_object(Main *bmain, double motionSampleTime) override;

View File

@@ -3,22 +3,30 @@
#include "usd_reader_material.h" #include "usd_reader_material.h"
#include "usd_umm.h"
#include "BKE_image.h" #include "BKE_image.h"
#include "BKE_main.h" #include "BKE_main.h"
#include "BKE_material.h" #include "BKE_material.h"
#include "BKE_node.h" #include "BKE_node.h"
#include "BKE_node_tree_update.h" #include "BKE_node_tree_update.h"
#include "BLI_fileops.h"
#include "BLI_math_vector.h" #include "BLI_math_vector.h"
#include "BLI_path_util.h"
#include "BLI_string.h" #include "BLI_string.h"
#include "DNA_material_types.h" #include "DNA_material_types.h"
#include "WM_api.h"
#include <pxr/base/gf/vec3f.h> #include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/usdShade/material.h> #include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/shader.h> #include <pxr/usd/usdShade/shader.h>
#include <iostream> #include <iostream>
#include <optional>
#include <vector> #include <vector>
namespace usdtokens { namespace usdtokens {
@@ -52,6 +60,13 @@ static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal); static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal); static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
/* Wrap mode names. */
static const pxr::TfToken black("black", pxr::TfToken::Immortal);
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
/* USD shader names. */ /* USD shader names. */
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal); static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2", static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
@@ -94,6 +109,65 @@ static void link_nodes(
nodeAddLink(ntree, source, source_socket, dest, dest_socket); nodeAddLink(ntree, source, source_socket, dest, dest_socket);
} }
/* Returns a layer handle retrieved from the given attribute's property specs.
* Note that the returned handle may be invalid if no layer could be found. */
static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &Attribute)
{
for (auto PropertySpec : Attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) {
if (PropertySpec->HasDefaultValue() ||
PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0) {
return PropertySpec->GetLayer();
}
}
return pxr::SdfLayerHandle();
}
static bool is_udim_path(const std::string &path)
{
return path.find("<UDIM>") != std::string::npos;
}
/* For the given UDIM path (assumed to contain the UDIM token), returns an array
* containing valid tile indices.
* Returns std::nullopt if no tiles were found. */
static std::optional<blender::Vector<int>> get_udim_tiles(const std::string &file_path)
{
char base_udim_path[FILE_MAX];
BLI_strncpy(base_udim_path, file_path.c_str(), sizeof(base_udim_path));
blender::Vector<int> udim_tiles;
/* Extract the tile numbers from all files on disk. */
ListBase tiles = {nullptr, nullptr};
int tile_start, tile_range;
bool result = BKE_image_get_tile_info(base_udim_path, &tiles, &tile_start, &tile_range);
if (result) {
LISTBASE_FOREACH (LinkData *, tile, &tiles) {
int tile_number = POINTER_AS_INT(tile->data);
udim_tiles.append(tile_number);
}
}
BLI_freelistN(&tiles);
if (udim_tiles.is_empty()) {
return std::nullopt;
}
return udim_tiles;
}
/* Add tiles with the given indices to the given image. */
static void add_udim_tiles(Image *image, const blender::Vector<int> &indices)
{
image->source = IMA_SRC_TILED;
for (int tile_number : indices) {
BKE_image_add_tile(image, tile_number, nullptr);
}
}
/* Returns true if the given shader may have opacity < 1.0, based /* Returns true if the given shader may have opacity < 1.0, based
* on heuristics. */ * on heuristics. */
static bool needs_blend(const pxr::UsdShadeShader &usd_shader) static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
@@ -165,6 +239,40 @@ static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader
return pxr::TfToken(); return pxr::TfToken();
} }
static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
{
pxr::UsdShadeInput wrap_input = usd_shader.GetInput(usdtokens::wrapS);
if (!wrap_input) {
wrap_input = usd_shader.GetInput(usdtokens::wrapT);
}
if (!wrap_input) {
return default_value;
}
pxr::VtValue wrap_input_val;
if (!(wrap_input.Get(&wrap_input_val) && wrap_input_val.IsHolding<pxr::TfToken>())) {
return default_value;
}
pxr::TfToken wrap_val = wrap_input_val.Get<pxr::TfToken>();
if (wrap_val == usdtokens::repeat) {
return SHD_IMAGE_EXTENSION_REPEAT;
}
if (wrap_val == usdtokens::clamp) {
return SHD_IMAGE_EXTENSION_EXTEND;
}
if (wrap_val == usdtokens::black) {
return SHD_IMAGE_EXTENSION_CLIP;
}
return default_value;
}
/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source /* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
* of the given material. Returns true if a UsdPreviewSurface source was found * of the given material. Returns true if a UsdPreviewSurface source was found
* and returns false otherwise. */ * and returns false otherwise. */
@@ -271,11 +379,26 @@ Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_mater
* if there is one. */ * if there is one. */
pxr::UsdShadeShader usd_preview; pxr::UsdShadeShader usd_preview;
if (get_usd_preview_surface(usd_material, usd_preview)) { if (get_usd_preview_surface(usd_material, usd_preview)) {
/* Always set the viewport material properties from the USD
* Preview Surface settings. */
set_viewport_material_props(mtl, usd_preview); set_viewport_material_props(mtl, usd_preview);
}
/* Optionally, create shader nodes to represent a UsdPreviewSurface. */ if (params_.import_shaders_mode == USD_IMPORT_USD_PREVIEW_SURFACE && usd_preview) {
if (params_.import_usd_preview) { /* Create shader nodes to represent a UsdPreviewSurface. */
import_usd_preview(mtl, usd_preview);
}
else if (params_.import_shaders_mode == USD_IMPORT_MDL) {
bool has_mdl = false;
#ifdef WITH_PYTHON
/* Invoke UMM to convert to MDL. */
umm_import_mdl_material(mtl, usd_material, true /* Verbose */, &has_mdl);
#endif
if (!has_mdl && usd_preview) {
/* The material has no MDL shader, so fall back on importing UsdPreviewSuface. */
std::string message = "Material has no MDL shader " + mtl_name +
", importing USD Preview Surface shaders instead";
WM_reportf(RPT_INFO, message.c_str());
import_usd_preview(mtl, usd_preview); import_usd_preview(mtl, usd_preview);
} }
} }
@@ -585,6 +708,22 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
/* Try to load the texture image. */ /* Try to load the texture image. */
pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file); pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file);
/* File input may have a connected source, e.g., if it's been overridden by
* an input on the mateial. */
if (file_input.HasConnectedSource()) {
pxr::UsdShadeConnectableAPI source;
pxr::TfToken source_name;
pxr::UsdShadeAttributeType source_type;
if (file_input.GetConnectedSource(&source, &source_name, &source_type)) {
file_input = source.GetInput(source_name);
}
else {
std::cerr << "ERROR: couldn't get connected source for file input "
<< file_input.GetPrim().GetPath() << " " << file_input.GetFullName() << std::endl;
}
}
if (!file_input) { if (!file_input) {
std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath() std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath()
<< std::endl; << std::endl;
@@ -599,21 +738,51 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
} }
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>(); const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
std::string file_path = asset_path.GetResolvedPath(); std::string file_path = asset_path.GetResolvedPath();
if (file_path.empty()) {
/* No resolved path, so use the asset path (usually
* necessary for UDIM paths). */
file_path = asset_path.GetAssetPath();
/* Texture paths are frequently relative to the USD, so get
* the absolute path. */
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
file_path = layer_handle->ComputeAbsolutePath(file_path);
}
}
if (file_path.empty()) { if (file_path.empty()) {
std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path
<< "' for Texture Image node." << std::endl; << "' for Texture Image node." << std::endl;
return; return;
} }
/* If this is a UDIM texture, this will store the
* UDIM tile indices. */
std::optional<blender::Vector<int>> udim_tiles;
if (is_udim_path(file_path)) {
udim_tiles = get_udim_tiles(file_path);
}
const char *im_file = file_path.c_str(); const char *im_file = file_path.c_str();
Image *image = BKE_image_load_exists(bmain_, im_file); Image *image = BKE_image_load_exists(bmain_, im_file);
if (!image) { if (!image) {
std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node." std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node."
<< std::endl; << std::endl;
return; return;
} }
if (udim_tiles) {
/* Not calling udim_tiles.value(), which is not
* supported in macOS versions prior to 10.14.1. */
add_udim_tiles(image, *udim_tiles);
}
tex_image->id = &image->id; tex_image->id = &image->id;
/* Set texture color space. /* Set texture color space.
@@ -625,11 +794,17 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
if (color_space.IsEmpty()) { if (color_space.IsEmpty()) {
color_space = file_input.GetAttr().GetColorSpace(); color_space = file_input.GetAttr().GetColorSpace();
/* TODO(makowalski): if the input is from a connected source
* and fails to return a color space, should we also check the
* color space on the current shader's file input? */
} }
if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) { if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
STRNCPY(image->colorspace_settings.name, "Raw"); STRNCPY(image->colorspace_settings.name, "Raw");
} }
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
storage->extension = get_image_extension(usd_shader, storage->extension);
} }
void USDMaterialReader::convert_usd_primvar_reader_float2( void USDMaterialReader::convert_usd_primvar_reader_float2(

View File

@@ -51,7 +51,7 @@ struct NodePlacementContext {
* *
* - #UsdPreviewSurface -> Principled BSDF * - #UsdPreviewSurface -> Principled BSDF
* - #UsdUVTexture -> Texture Image + Normal Map * - #UsdUVTexture -> Texture Image + Normal Map
* - UsdPrimvarReader_float2 -> UV Map * - #UsdPrimvarReader_float2 -> UV Map
* *
* Limitations: arbitrary primvar readers or UsdTransform2d not yet * Limitations: arbitrary primvar readers or UsdTransform2d not yet
* supported. For #UsdUVTexture, only the file, st and #sourceColorSpace * supported. For #UsdUVTexture, only the file, st and #sourceColorSpace
@@ -60,12 +60,10 @@ struct NodePlacementContext {
* TODO(makowalski): Investigate adding support for converting additional * TODO(makowalski): Investigate adding support for converting additional
* shaders and inputs. Supporting certain types of inputs, such as texture * shaders and inputs. Supporting certain types of inputs, such as texture
* scale and bias, will probably require creating Blender Group nodes with * scale and bias, will probably require creating Blender Group nodes with
* the corresponding inputs. * the corresponding inputs. */
*/
class USDMaterialReader { class USDMaterialReader {
protected: protected:
USDImportParams params_; USDImportParams params_;
Main *bmain_; Main *bmain_;
public: public:

View File

@@ -66,41 +66,61 @@ static void assign_materials(Main *bmain,
Object *ob, Object *ob,
const std::map<pxr::SdfPath, int> &mat_index_map, const std::map<pxr::SdfPath, int> &mat_index_map,
const USDImportParams &params, const USDImportParams &params,
pxr::UsdStageRefPtr stage) pxr::UsdStageRefPtr stage,
std::map<std::string, std::string> &usd_path_to_mat_name)
{ {
if (!(stage && bmain && ob)) { if (!(stage && bmain && ob)) {
return; return;
} }
bool can_assign = true;
std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin(); std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin();
int matcount = 0; for (; it != mat_index_map.end(); ++it) {
for (; it != mat_index_map.end(); ++it, matcount++) {
if (!BKE_object_material_slot_add(bmain, ob)) { if (!BKE_object_material_slot_add(bmain, ob)) {
can_assign = false; std::cout << "WARNING: couldn't create slot for material " << it->first << " on object "
break; << ob->id.name << std::endl;
return;
} }
} }
if (!can_assign) { /* TODO(makowalski): use global map? */
return;
}
/* TODO(kevin): use global map? */
std::map<std::string, Material *> mat_map; std::map<std::string, Material *> mat_map;
build_mat_map(bmain, &mat_map); build_mat_map(bmain, &mat_map);
blender::io::usd::USDMaterialReader mat_reader(params, bmain); blender::io::usd::USDMaterialReader mat_reader(params, bmain);
for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) { for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) {
std::string mat_name = it->first.GetName();
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
Material *assigned_mat = nullptr; Material *assigned_mat = nullptr;
if (mat_iter == mat_map.end()) { if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MODIFY) {
/* Check if we've already created the Blender material with a modified name. */
std::map<std::string, std::string>::const_iterator path_to_name_iter =
usd_path_to_mat_name.find(it->first.GetAsString());
if (path_to_name_iter != usd_path_to_mat_name.end()) {
std::string mat_name = path_to_name_iter->second;
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
if (mat_iter != mat_map.end()) {
assigned_mat = mat_iter->second;
}
else {
std::cout
<< "WARNING: Couldn't find previously assigned Blender material for USD material "
<< it->first << std::endl;
}
}
}
else {
std::string mat_name = it->first.GetName();
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
if (mat_iter != mat_map.end()) {
assigned_mat = mat_iter->second;
}
}
if (!assigned_mat) {
/* Blender material doesn't exist, so create it now. */ /* Blender material doesn't exist, so create it now. */
/* Look up the USD material. */ /* Look up the USD material. */
@@ -122,11 +142,14 @@ static void assign_materials(Main *bmain,
continue; continue;
} }
std::string mat_name = pxr::TfMakeValidIdentifier(assigned_mat->id.name + 2);
mat_map[mat_name] = assigned_mat; mat_map[mat_name] = assigned_mat;
}
else { if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MODIFY) {
/* We found an existing Blender material. */ /* Record the name of the Blender material we created for the USD material
assigned_mat = mat_iter->second; * with the given path. */
usd_path_to_mat_name[it->first.GetAsString()] = mat_name;
}
} }
if (assigned_mat) { if (assigned_mat) {
@@ -134,7 +157,7 @@ static void assign_materials(Main *bmain,
} }
else { else {
/* This shouldn't happen. */ /* This shouldn't happen. */
std::cout << "WARNING: Couldn't assign material " << mat_name << std::endl; std::cout << "WARNING: Couldn't assign material " << it->first << std::endl;
} }
} }
} }
@@ -457,10 +480,10 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
return; return;
} }
void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_PROP_BYTE_COLOR); void *cd_ptr = add_customdata_cb(mesh, "displayColor", CD_PROP_BYTE_COLOR);
if (!cd_ptr) { if (!cd_ptr) {
std::cerr << "WARNING: Couldn't add displayColors custom data.\n"; std::cerr << "WARNING: Couldn't add displayColor custom data.\n";
return; return;
} }
@@ -715,6 +738,11 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial(); pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial();
if (!subset_mtl) {
/* Check for a preview material as fallback. */
subset_mtl = subset_api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
}
if (!subset_mtl) { if (!subset_mtl) {
continue; continue;
} }
@@ -745,8 +773,14 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
if (r_mat_map->empty()) { if (r_mat_map->empty()) {
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_); pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_);
if (pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial()) { pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial();
if (!mtl) {
/* Check for a preview material as fallback. */
mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
}
if (mtl) {
pxr::SdfPath mtl_path = mtl.GetPath(); pxr::SdfPath mtl_path = mtl.GetPath();
if (!mtl_path.IsEmpty()) { if (!mtl_path.IsEmpty()) {
@@ -764,7 +798,12 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot
std::map<pxr::SdfPath, int> mat_map; std::map<pxr::SdfPath, int> mat_map;
assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map); assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map);
utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage()); utils::assign_materials(bmain,
object_,
mat_map,
this->import_params_,
this->prim_.GetStage(),
this->settings_->usd_path_to_mat_name);
} }
Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh, Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,

View File

@@ -4,10 +4,238 @@
#include "usd_reader_prim.h" #include "usd_reader_prim.h"
#include "BKE_idprop.h"
#include "BLI_utildefines.h" #include "BLI_utildefines.h"
#include "DNA_object_types.h"
#include <iostream>
#include <pxr/usd/usd/attribute.h>
namespace {
template<typename VECT>
void set_array_prop(IDProperty *idgroup,
const char *prop_name,
const pxr::UsdAttribute &attr,
const double motionSampleTime)
{
if (!idgroup || !attr) {
return;
}
VECT vec;
if (!attr.Get<VECT>(&vec, motionSampleTime)) {
return;
}
IDPropertyTemplate val = {0};
val.array.len = static_cast<int>(vec.dimension);
if (val.array.len <= 0) {
/* Should never happen. */
std::cout << "Invalid array length for prop " << prop_name << std::endl;
return;
}
if (std::is_same<float, typename VECT::ScalarType>()) {
val.array.type = IDP_FLOAT;
}
else if (std::is_same<double, typename VECT::ScalarType>()) {
val.array.type = IDP_DOUBLE;
}
else if (std::is_same<int, typename VECT::ScalarType>()) {
val.array.type = IDP_INT;
}
else {
std::cout << "Couldn't determine array type for prop " << prop_name << std::endl;
return;
}
IDProperty *prop = IDP_New(IDP_ARRAY, &val, prop_name);
if (!prop) {
std::cout << "Couldn't create array prop " << prop_name << std::endl;
return;
}
typename VECT::ScalarType *prop_data = static_cast<typename VECT::ScalarType *>(
prop->data.pointer);
for (int i = 0; i < val.array.len; ++i) {
prop_data[i] = vec[i];
}
IDP_AddToGroup(idgroup, prop);
}
} // anonymous namespace
namespace blender::io::usd { namespace blender::io::usd {
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
static const pxr::TfToken userProperties("userProperties", pxr::TfToken::Immortal);
} // namespace usdtokens
static void set_string_prop(IDProperty *idgroup, const char *prop_name, const char *str_val)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = {0};
val.string.str = str_val;
/* Note length includes null terminator. */
val.string.len = strlen(str_val) + 1;
val.string.subtype = IDP_STRING_SUB_UTF8;
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_int_prop(IDProperty *idgroup, const char *prop_name, const int ival)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = {0};
val.i = ival;
IDProperty *prop = IDP_New(IDP_INT, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_float_prop(IDProperty *idgroup, const char *prop_name, const float fval)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = {0};
val.f = fval;
IDProperty *prop = IDP_New(IDP_FLOAT, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
static void set_double_prop(IDProperty *idgroup, const char *prop_name, const double dval)
{
if (!idgroup) {
return;
}
IDPropertyTemplate val = {0};
val.d = dval;
IDProperty *prop = IDP_New(IDP_DOUBLE, &val, prop_name);
IDP_AddToGroup(idgroup, prop);
}
void USDPrimReader::set_props(ID *id, const pxr::UsdPrim &prim, const double motionSampleTime)
{
eUSDAttrImportMode attr_import_mode = this->import_params_.attr_import_mode;
if (attr_import_mode == USD_ATTR_IMPORT_NONE) {
return;
}
IDProperty *idgroup = IDP_GetProperties(id, 1);
if (!idgroup) {
return;
}
bool all_custom_attrs = (attr_import_mode == USD_ATTR_IMPORT_ALL);
pxr::UsdAttributeVector attribs = prim.GetAuthoredAttributes();
for (const pxr::UsdAttribute &attr : attribs) {
if (!attr.IsCustom()) {
continue;
}
std::vector<std::string> attr_names = attr.SplitName();
bool is_user_prop = attr_names[0] == "userProperties";
if (attr_names.size() > 2 && is_user_prop && attr_names[1] == "blenderName") {
/* Skip the deprecated userProperties:blenderName namespace attribs. */
continue;
}
if (!all_custom_attrs && !is_user_prop) {
continue;
}
/* When importing user properties, strip the namespace. */
pxr::TfToken attr_name = (attr_import_mode == USD_ATTR_IMPORT_USER) ? attr.GetBaseName() :
attr.GetName();
pxr::SdfValueTypeName type_name = attr.GetTypeName();
if (type_name == pxr::SdfValueTypeNames->Int) {
int ival = 0;
if (attr.Get<int>(&ival, motionSampleTime)) {
set_int_prop(idgroup, attr_name.GetString().c_str(), ival);
}
}
else if (type_name == pxr::SdfValueTypeNames->Float) {
float fval = 0.0f;
if (attr.Get<float>(&fval, motionSampleTime)) {
set_float_prop(idgroup, attr_name.GetString().c_str(), fval);
}
}
else if (type_name == pxr::SdfValueTypeNames->Double) {
double dval = 0.0;
if (attr.Get<double>(&dval, motionSampleTime)) {
set_double_prop(idgroup, attr_name.GetString().c_str(), dval);
}
}
else if (type_name == pxr::SdfValueTypeNames->String) {
std::string sval;
if (attr.Get<std::string>(&sval, motionSampleTime)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), sval.c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Token) {
pxr::TfToken tval;
if (attr.Get<pxr::TfToken>(&tval, motionSampleTime)) {
set_string_prop(idgroup, attr_name.GetString().c_str(), tval.GetString().c_str());
}
}
else if (type_name == pxr::SdfValueTypeNames->Float2) {
set_array_prop<pxr::GfVec2f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Float3) {
set_array_prop<pxr::GfVec3f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Float4) {
set_array_prop<pxr::GfVec4f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double2) {
set_array_prop<pxr::GfVec2d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double3) {
set_array_prop<pxr::GfVec3d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Double4) {
set_array_prop<pxr::GfVec4d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int2) {
set_array_prop<pxr::GfVec2i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int3) {
set_array_prop<pxr::GfVec3i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
else if (type_name == pxr::SdfValueTypeNames->Int4) {
set_array_prop<pxr::GfVec4i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
}
}
}
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim, USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params, const USDImportParams &import_params,
const ImportSettings &settings) const ImportSettings &settings)
@@ -29,6 +257,17 @@ const pxr::UsdPrim &USDPrimReader::prim() const
return prim_; return prim_;
} }
void USDPrimReader::read_object_data(Main * /* bmain */, const double motionSampleTime)
{
if (!prim_ || !object_) {
return;
}
ID *id = object_->data ? static_cast<ID *>(object_->data) : &object_->id;
set_props(id, prim_, motionSampleTime);
}
Object *USDPrimReader::object() const Object *USDPrimReader::object() const
{ {
return object_; return object_;

View File

@@ -7,6 +7,10 @@
#include <pxr/usd/usd/prim.h> #include <pxr/usd/usd/prim.h>
#include <map>
#include <string>
struct CacheFile;
struct Main; struct Main;
struct Object; struct Object;
@@ -31,7 +35,13 @@ struct ImportSettings {
bool validate_meshes; bool validate_meshes;
CacheFile *cache_file; /* Map a USD matrial prim path to a Blender material name.
* This map might be updated by readers during stage traversal.
* TODO(makowalski): Is the ImportSettings struct the best place
* to store this map? Maybe we should define an ImportContext
* struct that stores USDImportParams, ImportSettings and
* mutable values such as this. */
mutable std::map<std::string, std::string> usd_path_to_mat_name;
ImportSettings() ImportSettings()
: do_convert_mat(false), : do_convert_mat(false),
@@ -43,8 +53,7 @@ struct ImportSettings {
sequence_len(1), sequence_len(1),
sequence_offset(0), sequence_offset(0),
read_flag(0), read_flag(0),
validate_meshes(false), validate_meshes(false)
cache_file(NULL)
{ {
} }
}; };
@@ -74,7 +83,15 @@ class USDPrimReader {
virtual bool valid() const; virtual bool valid() const;
virtual void create_object(Main *bmain, double motionSampleTime) = 0; virtual void create_object(Main *bmain, double motionSampleTime) = 0;
virtual void read_object_data(Main * /* bmain */, double /* motionSampleTime */){}; virtual void read_object_data(Main *bmain, double motionSampleTime);
virtual bool needs_cachefile()
{
return false;
}
virtual void apply_cache_file(CacheFile * /* cache_file */)
{
}
Object *object() const; Object *object() const;
void object(Object *ob); void object(Object *ob);
@@ -109,6 +126,9 @@ class USDPrimReader {
{ {
return prim_path_; return prim_path_;
} }
protected:
void set_props(ID *id, const pxr::UsdPrim &prim, double motionSampleTime);
}; };
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -4,6 +4,7 @@
#include "usd_reader_stage.h" #include "usd_reader_stage.h"
#include "usd_reader_camera.h" #include "usd_reader_camera.h"
#include "usd_reader_curve.h" #include "usd_reader_curve.h"
#include "usd_reader_instance.h"
#include "usd_reader_light.h" #include "usd_reader_light.h"
#include "usd_reader_mesh.h" #include "usd_reader_mesh.h"
#include "usd_reader_nurbs.h" #include "usd_reader_nurbs.h"
@@ -19,6 +20,7 @@
#include <pxr/usd/usdGeom/scope.h> #include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/xform.h> #include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdLux/domeLight.h>
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
# include <pxr/usd/usdLux/boundableLightBase.h> # include <pxr/usd/usdLux/boundableLightBase.h>
# include <pxr/usd/usdLux/nonboundableLightBase.h> # include <pxr/usd/usdLux/nonboundableLightBase.h>
@@ -39,6 +41,7 @@ USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
USDStageReader::~USDStageReader() USDStageReader::~USDStageReader()
{ {
clear_proto_readers();
clear_readers(); clear_readers();
} }
@@ -47,8 +50,12 @@ bool USDStageReader::valid() const
return stage_; return stage_;
} }
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim) USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache)
{ {
if (params_.use_instancing && prim.IsInstance()) {
return new USDInstanceReader(prim, params_, settings_);
}
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) { if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
return new USDCameraReader(prim, params_, settings_); return new USDCameraReader(prim, params_, settings_);
} }
@@ -61,6 +68,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) { if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
return new USDMeshReader(prim, params_, settings_); return new USDMeshReader(prim, params_, settings_);
} }
if (params_.import_lights && prim.IsA<pxr::UsdLuxDomeLight>()) {
return new USDXformReader(prim, params_, settings_);
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
if (params_.import_lights && (prim.IsA<pxr::UsdLuxBoundableLightBase>() || if (params_.import_lights && (prim.IsA<pxr::UsdLuxBoundableLightBase>() ||
prim.IsA<pxr::UsdLuxNonboundableLightBase>())) { prim.IsA<pxr::UsdLuxNonboundableLightBase>())) {
@@ -75,11 +85,16 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
if (prim.IsA<pxr::UsdGeomImageable>()) { if (prim.IsA<pxr::UsdGeomImageable>()) {
return new USDXformReader(prim, params_, settings_); return new USDXformReader(prim, params_, settings_);
} }
if (prim.GetPrimTypeInfo() == pxr::UsdPrimTypeInfo::GetEmptyPrimType()) {
/* Handle the less common case where the prim has no type specified. */
return new USDXformReader(prim, params_, settings_);
}
return nullptr; return nullptr;
} }
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim) USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache)
{ {
if (prim.IsA<pxr::UsdGeomCamera>()) { if (prim.IsA<pxr::UsdGeomCamera>()) {
return new USDCameraReader(prim, params_, settings_); return new USDCameraReader(prim, params_, settings_);
@@ -93,6 +108,9 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
if (prim.IsA<pxr::UsdGeomMesh>()) { if (prim.IsA<pxr::UsdGeomMesh>()) {
return new USDMeshReader(prim, params_, settings_); return new USDMeshReader(prim, params_, settings_);
} }
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
return new USDXformReader(prim, params_, settings_);
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) { if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
#else #else
@@ -167,7 +185,8 @@ bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable)
/* Determine if the given reader can use the parent of the encapsulated USD prim /* Determine if the given reader can use the parent of the encapsulated USD prim
* to compute the Blender object's transform. If so, the reader is appropriately * to compute the Blender object's transform. If so, the reader is appropriately
* flagged and the function returns true. Otherwise, the function returns false. */ * flagged and the function returns true. Otherwise, the function returns false. */
static bool merge_with_parent(USDPrimReader *reader)
bool USDStageReader::merge_with_parent(USDPrimReader *reader) const
{ {
USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader); USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
@@ -185,9 +204,16 @@ static bool merge_with_parent(USDPrimReader *reader)
return false; return false;
} }
/* Don't merge Xform and Scope prims. */ /* Don't merge if instancing is enabled and the parent is an instance. */
if (params_.use_instancing && xform_reader->prim().GetParent().IsInstance()) {
return false;
}
/* Don't merge Xform, Scope or undefined prims. */
if (xform_reader->prim().IsA<pxr::UsdGeomXform>() || if (xform_reader->prim().IsA<pxr::UsdGeomXform>() ||
xform_reader->prim().IsA<pxr::UsdGeomScope>()) { xform_reader->prim().IsA<pxr::UsdGeomScope>() ||
xform_reader->prim().GetPrimTypeInfo()
== pxr::UsdPrimTypeInfo::GetEmptyPrimType()) {
return false; return false;
} }
@@ -202,7 +228,10 @@ static bool merge_with_parent(USDPrimReader *reader)
return true; return true;
} }
USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &prim) USDPrimReader *USDStageReader::collect_readers(Main *bmain,
const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache,
std::vector<USDPrimReader *> &r_readers)
{ {
if (prim.IsA<pxr::UsdGeomImageable>()) { if (prim.IsA<pxr::UsdGeomImageable>()) {
pxr::UsdGeomImageable imageable(prim); pxr::UsdGeomImageable imageable(prim);
@@ -216,9 +245,13 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
} }
} }
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
dome_lights_.push_back(pxr::UsdLuxDomeLight(prim));
}
pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate; pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate;
if (params_.import_instance_proxies) { if (!params_.use_instancing && params_.import_instance_proxies) {
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate); filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
} }
@@ -227,18 +260,20 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
std::vector<USDPrimReader *> child_readers; std::vector<USDPrimReader *> child_readers;
for (const auto &childPrim : children) { for (const auto &childPrim : children) {
if (USDPrimReader *child_reader = collect_readers(bmain, childPrim)) { if (USDPrimReader *child_reader = collect_readers(bmain, childPrim, xf_cache, r_readers)) {
child_readers.push_back(child_reader); child_readers.push_back(child_reader);
} }
} }
if (prim.IsPseudoRoot()) { /* We prune the current prim if it's a Scope
* and we didn't convert any of its children. */
if (child_readers.empty() && prim.IsA<pxr::UsdGeomScope>() &&
!(params_.use_instancing && prim.IsInstance())) {
return nullptr; return nullptr;
} }
/* Check if we can merge an Xform with its child prim. */ /* Check if we can merge an Xform with its child prim. */
if (child_readers.size() == 1) { if (child_readers.size() == 1) {
USDPrimReader *child_reader = child_readers.front(); USDPrimReader *child_reader = child_readers.front();
if (merge_with_parent(child_reader)) { if (merge_with_parent(child_reader)) {
@@ -246,7 +281,11 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
} }
} }
USDPrimReader *reader = create_reader_if_allowed(prim); if (prim.IsPseudoRoot() || prim.IsPrototype()) {
return nullptr;
}
USDPrimReader *reader = create_reader_if_allowed(prim, xf_cache);
if (!reader) { if (!reader) {
return nullptr; return nullptr;
@@ -254,7 +293,7 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
reader->create_object(bmain, 0.0); reader->create_object(bmain, 0.0);
readers_.push_back(reader); r_readers.push_back(reader);
reader->incref(); reader->incref();
/* Set each child reader's parent. */ /* Set each child reader's parent. */
@@ -272,6 +311,8 @@ void USDStageReader::collect_readers(Main *bmain)
} }
clear_readers(); clear_readers();
clear_proto_readers();
dome_lights_.clear();
/* Iterate through the stage. */ /* Iterate through the stage. */
pxr::UsdPrim root = stage_->GetPseudoRoot(); pxr::UsdPrim root = stage_->GetPseudoRoot();
@@ -290,7 +331,20 @@ void USDStageReader::collect_readers(Main *bmain)
} }
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld); stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
collect_readers(bmain, root);
pxr::UsdGeomXformCache xf_cache;
collect_readers(bmain, root, &xf_cache, readers_);
if (params_.use_instancing) {
// Collect the scenegraph instance prototypes.
std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
for (const pxr::UsdPrim &proto_prim : protos) {
std::vector<USDPrimReader *> proto_readers;
collect_readers(bmain, proto_prim, &xf_cache, proto_readers);
proto_readers_.insert(std::make_pair(proto_prim.GetPath(), proto_readers));
}
}
} }
void USDStageReader::clear_readers() void USDStageReader::clear_readers()
@@ -310,4 +364,27 @@ void USDStageReader::clear_readers()
readers_.clear(); readers_.clear();
} }
void USDStageReader::clear_proto_readers()
{
for (auto &pair : proto_readers_) {
for (USDPrimReader *reader : pair.second) {
if (!reader) {
continue;
}
reader->decref();
if (reader->refcount() == 0) {
delete reader;
}
}
pair.second.clear();
}
proto_readers_.clear();
}
} // Namespace blender::io::usd } // Namespace blender::io::usd

View File

@@ -9,6 +9,8 @@ struct Main;
#include <pxr/usd/usd/stage.h> #include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/imageable.h> #include <pxr/usd/usdGeom/imageable.h>
#include <pxr/usd/usdGeom/xformCache.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <vector> #include <vector>
@@ -27,6 +29,13 @@ class USDStageReader {
std::vector<USDPrimReader *> readers_; std::vector<USDPrimReader *> readers_;
// Readers for scenegraph instance prototypes.
ProtoReaderMap proto_readers_;
/* USD dome lights are converted to a world material,
* rather than light objects, so are handled differently */
std::vector<pxr::UsdLuxDomeLight> dome_lights_;
public: public:
USDStageReader(pxr::UsdStageRefPtr stage, USDStageReader(pxr::UsdStageRefPtr stage,
const USDImportParams &params, const USDImportParams &params,
@@ -34,9 +43,10 @@ class USDStageReader {
~USDStageReader(); ~USDStageReader();
USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim); USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache);
USDPrimReader *create_reader(const pxr::UsdPrim &prim); USDPrimReader *create_reader(const pxr::UsdPrim &prim, pxr::UsdGeomXformCache *xf_cache);
void collect_readers(struct Main *bmain); void collect_readers(struct Main *bmain);
@@ -58,13 +68,28 @@ class USDStageReader {
void clear_readers(); void clear_readers();
void clear_proto_readers();
const ProtoReaderMap &proto_readers() const
{
return proto_readers_;
};
const std::vector<USDPrimReader *> &readers() const const std::vector<USDPrimReader *> &readers() const
{ {
return readers_; return readers_;
}; };
const std::vector<pxr::UsdLuxDomeLight> &dome_lights() const
{
return dome_lights_;
};
private: private:
USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim); USDPrimReader *collect_readers(Main *bmain,
const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache,
std::vector<USDPrimReader *> &r_readers);
/** /**
* Returns true if the given prim should be included in the * Returns true if the given prim should be included in the
@@ -83,6 +108,8 @@ class USDStageReader {
* toggled off. * toggled off.
*/ */
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const; bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
bool merge_with_parent(USDPrimReader *reader) const;
}; };
}; // namespace blender::io::usd }; // namespace blender::io::usd

View File

@@ -34,30 +34,43 @@ void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime
object_->data = nullptr; object_->data = nullptr;
} }
void USDXformReader::read_object_data(Main * /* bmain */, const double motionSampleTime) void USDXformReader::read_object_data(Main *bmain, const double motionSampleTime)
{ {
USDPrimReader::read_object_data(bmain, motionSampleTime);
if (use_parent_xform_ && object_ && prim_) {
USDPrimReader::set_props(&object_->id, prim_.GetParent(), motionSampleTime);
}
bool is_constant; bool is_constant;
float transform_from_usd[4][4]; float transform_from_usd[4][4];
read_matrix(transform_from_usd, motionSampleTime, import_params_.scale, &is_constant); read_matrix(transform_from_usd, motionSampleTime, settings_->scale, &is_constant);
if (!is_constant) { needs_cachefile_ = !is_constant;
bConstraint *con = BKE_constraint_add_for_object(
object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() :
prim_path_;
BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX);
data->cache_file = settings_->cache_file;
id_us_plus(&data->cache_file->id);
}
BKE_object_apply_mat4(object_, transform_from_usd, true, false); BKE_object_apply_mat4(object_, transform_from_usd, true, false);
} }
void USDXformReader::apply_cache_file(CacheFile *cache_file)
{
if (!(cache_file && needs_cachefile_ && object_)) {
return;
}
bConstraint *con = BKE_constraint_add_for_object(
object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() :
prim_path_;
BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX);
data->cache_file = cache_file;
id_us_plus(&cache_file->id);
}
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */, void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
const float time, const float time,
const float scale, const float scale,

View File

@@ -16,20 +16,29 @@ class USDXformReader : public USDPrimReader {
* transform hierarchy. */ * transform hierarchy. */
bool is_root_xform_; bool is_root_xform_;
bool needs_cachefile_;
public: public:
USDXformReader(const pxr::UsdPrim &prim, USDXformReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params, const USDImportParams &import_params,
const ImportSettings &settings) const ImportSettings &settings)
: USDPrimReader(prim, import_params, settings), : USDPrimReader(prim, import_params, settings),
use_parent_xform_(false), use_parent_xform_(false),
is_root_xform_(is_root_xform_prim()) is_root_xform_(is_root_xform_prim()),
needs_cachefile_(false)
{ {
} }
void create_object(Main *bmain, double motionSampleTime) override; void create_object(Main *bmain, double motionSampleTime) override;
void read_object_data(Main *bmain, double motionSampleTime) override; void read_object_data(Main *bmain, double motionSampleTime) override;
void read_matrix(float r_mat[4][4], float time, float scale, bool *r_is_constant); bool needs_cachefile() override
{
return needs_cachefile_;
}
void apply_cache_file(CacheFile *cache_file) override;
void read_matrix(float r_mat[4][4], const float time, const float scale, bool *r_is_constant);
bool use_parent_xform() const bool use_parent_xform() const
{ {

View File

@@ -0,0 +1,889 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#ifdef WITH_PYTHON
# include "usd_umm.h"
# include "usd.h"
# include "usd_exporter_context.h"
# include "usd_writer_material.h"
# include "DNA_material_types.h"
# include <pxr/usd/ar/resolver.h>
# include <iostream>
# include <vector>
# include "WM_api.h"
// The following is additional example code for invoking Python and
// a Blender Python operator from C++:
//#include "BPY_extern_python.h"
//#include "BPY_extern_run.h"
// const char *foo[] = { "bpy", 0 };
// BPY_run_string_eval(C, nullptr, "print('hi!!')");
// BPY_run_string_eval(C, foo, "bpy.ops.universalmaterialmap.instance_to_data_converter()");
// BPY_run_string_eval(C, nullptr, "print('test')");
namespace usdtokens {
// Render context names.
static const pxr::TfToken mdl("mdl", pxr::TfToken::Immortal);
} // end namespace usdtokens
static PyObject *g_umm_module = nullptr;
static const char *k_umm_module_name = "omni.universalmaterialmap.blender.material";
static const char *k_omni_pbr_mdl_name = "OmniPBR.mdl";
static const char *k_omni_pbr_name = "OmniPBR";
static const char *k_udim_tag = "<UDIM>";
using namespace blender::io::usd;
static std::string anchor_relative_path(pxr::UsdStagePtr stage, const std::string &asset_path)
{
if (asset_path.empty() || asset_path.front() != '.') {
return std::string();
}
// TODO(makowalski): avoid recomputing the USD path, if possible.
pxr::SdfLayerHandle layer = stage->GetRootLayer();
std::string stage_path = layer->GetRealPath();
if (stage_path.empty()) {
return asset_path;
}
#if PXR_VERSION >= 2111
return pxr::ArGetResolver().CreateIdentifier(asset_path, pxr::ArResolvedPath(stage_path));
#else
return pxr::ArGetResolver().AnchorRelativePath(stage_path, asset_path);
#endif
}
static void print_obj(PyObject *obj)
{
if (!obj) {
return;
}
PyObject *str = PyObject_Str(obj);
if (str && PyUnicode_Check(str)) {
std::cout << PyUnicode_AsUTF8(str) << std::endl;
Py_DECREF(str);
}
}
namespace {
enum eUMMNotification {
UMM_NOTIFICATION_NONE = 0,
UMM_NOTIFICATION_SUCCESS,
UMM_NOTIFICATION_FAILURE
};
} // anonymous namespace
static eUMMNotification report_notification(PyObject *dict)
{
if (!dict) {
return UMM_NOTIFICATION_NONE;
}
if (!PyDict_Check(dict)) {
return UMM_NOTIFICATION_NONE;
}
PyObject *notification_item = PyDict_GetItemString(dict, "umm_notification");
if (!notification_item) {
return UMM_NOTIFICATION_NONE;
}
PyObject *message_item = PyDict_GetItemString(dict, "message");
if (!message_item) {
return UMM_NOTIFICATION_NONE;
}
if (!PyUnicode_Check(notification_item)) {
std::cerr << "WARNING: 'umm_notification' value is not a string" << std::endl;
return UMM_NOTIFICATION_NONE;
}
const char *notification_str = PyUnicode_AsUTF8(notification_item);
if (!notification_str) {
std::cerr << "WARNING: couldn't get 'umm_notification' string value" << std::endl;
return UMM_NOTIFICATION_NONE;
}
if (strcmp(notification_str, "success") == 0) {
/* We don't report success, do nothing. */
return UMM_NOTIFICATION_SUCCESS;
}
if (!PyUnicode_Check(message_item)) {
std::cerr << "WARNING: 'message' value is not a string" << std::endl;
return UMM_NOTIFICATION_NONE;
}
const char *message_str = PyUnicode_AsUTF8(message_item);
if (!message_str) {
std::cerr << "WARNING: couldn't get 'message' string value" << std::endl;
return UMM_NOTIFICATION_NONE;
}
if (strlen(message_str) == 0) {
std::cerr << "WARNING: empty 'message' string value" << std::endl;
return UMM_NOTIFICATION_NONE;
}
if (strcmp(notification_str, "incomplete_process") == 0) {
WM_reportf(RPT_WARNING, message_str);
return UMM_NOTIFICATION_FAILURE;
}
if (strcmp(notification_str, "unexpected_error") == 0) {
WM_reportf(RPT_ERROR, message_str);
return UMM_NOTIFICATION_FAILURE;
}
std::cout << "WARNING: unknown notification type: " << notification_str << std::endl;
return UMM_NOTIFICATION_NONE;
}
static bool is_none_value(PyObject *tup)
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *second = PyTuple_GetItem(tup, 1);
return second == Py_None;
}
/* Sets the source asset and source asset subidenifier properties on the given shader
* with values parsed from the given target_class string. */
static bool set_source_asset(pxr::UsdShadeShader &usd_shader, const std::string &target_class)
{
if (!usd_shader || target_class.empty()) {
return false;
}
// Split the target_class string on the '|' separator.
size_t sep = target_class.find_last_of("|");
if (sep == 0 || sep == std::string::npos) {
std::cout << "Couldn't parse target_class string " << target_class << std::endl;
return false;
}
std::string source_asset = target_class.substr(0, sep);
usd_shader.SetSourceAsset(pxr::SdfAssetPath(source_asset), usdtokens::mdl);
std::string source_asset_subidentifier = target_class.substr(sep + 1);
if (!source_asset_subidentifier.empty()) {
usd_shader.SetSourceAssetSubIdentifier(pxr::TfToken(source_asset_subidentifier),
usdtokens::mdl);
}
return true;
}
static bool get_data_name(PyObject *tup, std::string &r_name)
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *first = PyTuple_GetItem(tup, 0);
if (first && PyUnicode_Check(first)) {
const char *name = PyUnicode_AsUTF8(first);
if (name) {
r_name = name;
return true;
}
}
return false;
}
static bool get_string_data(PyObject *tup, std::string &r_data)
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *second = PyTuple_GetItem(tup, 1);
if (second && PyUnicode_Check(second)) {
const char *data = PyUnicode_AsUTF8(second);
if (data) {
r_data = data;
return true;
}
}
return false;
}
static bool get_float_data(PyObject *tup, float &r_data)
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *second = PyTuple_GetItem(tup, 1);
if (second && PyFloat_Check(second)) {
r_data = static_cast<float>(PyFloat_AsDouble(second));
return true;
}
return false;
}
static bool get_float3_data(PyObject *tup, float r_data[3])
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *second = PyTuple_GetItem(tup, 1);
if (second && PyTuple_Check(second) && PyTuple_Size(second) > 2) {
for (int i = 0; i < 3; ++i) {
PyObject *comp = PyTuple_GetItem(second, i);
if (comp && PyFloat_Check(comp)) {
r_data[i] = static_cast<float>(PyFloat_AsDouble(comp));
}
else {
return false;
}
}
return true;
}
return false;
}
static bool get_rgba_data(PyObject *tup, float r_data[4])
{
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
return false;
}
PyObject *second = PyTuple_GetItem(tup, 1);
if (!(second && PyTuple_Check(second))) {
return false;
}
Py_ssize_t size = PyTuple_Size(second);
if (size > 2) {
for (int i = 0; i < 3; ++i) {
PyObject *comp = PyTuple_GetItem(second, i);
if (comp && PyFloat_Check(comp)) {
r_data[i] = static_cast<float>(PyFloat_AsDouble(comp));
}
else {
return false;
}
}
if (size > 3) {
PyObject *alpha = PyTuple_GetItem(second, 3);
if (alpha && PyFloat_Check(alpha)) {
r_data[3] = static_cast<float>(PyFloat_AsDouble(alpha));
}
else {
return false;
}
}
else {
r_data[3] = 1.0;
}
return true;
}
return false;
}
/* Be sure to call PyGILState_Ensure() before calling this function. */
static bool ensure_module_loaded(bool warn = true)
{
if (!g_umm_module) {
g_umm_module = PyImport_ImportModule(k_umm_module_name);
if (!g_umm_module) {
if (warn) {
std::cout << "WARNING: couldn't load Python module " << k_umm_module_name << std::endl;
if (PyErr_Occurred()) {
PyErr_Print();
}
}
PyErr_Clear();
}
}
return g_umm_module != nullptr;
}
static void test_python()
{
PyGILState_STATE gilstate = PyGILState_Ensure();
PyObject *mod = PyImport_ImportModule("omni.universalmaterialmap.core.converter.util");
if (mod) {
const char *func_name = "get_conversion_manifest";
if (PyObject_HasAttrString(mod, func_name)) {
if (PyObject *func = PyObject_GetAttrString(mod, func_name)) {
PyObject *ret = PyObject_CallObject(func, nullptr);
Py_DECREF(func);
if (ret) {
print_obj(ret);
Py_DECREF(ret);
}
}
}
}
PyGILState_Release(gilstate);
}
static PyObject *get_shader_source_data(const pxr::UsdShadeShader &usd_shader)
{
if (!usd_shader) {
return nullptr;
}
std::vector<PyObject *> tuple_items;
std::vector<pxr::UsdShadeInput> inputs = usd_shader.GetInputs();
for (auto input : inputs) {
if (!input) {
continue;
}
PyObject *tup = nullptr;
std::string name = input.GetBaseName().GetString();
if (name.empty()) {
continue;
}
pxr::UsdAttribute usd_attr;
bool have_connected_source = false;
if (input.HasConnectedSource()) {
pxr::UsdShadeConnectableAPI source;
pxr::TfToken source_name;
pxr::UsdShadeAttributeType source_type;
if (input.GetConnectedSource(&source, &source_name, &source_type)) {
usd_attr = source.GetInput(source_name).GetAttr();
have_connected_source = true;
}
else {
std::cerr << "ERROR: couldn't get connected source for usd shader input "
<< input.GetPrim().GetPath() << " " << input.GetFullName() << std::endl;
}
}
else {
usd_attr = input.GetAttr();
}
if (!usd_attr) {
std::cerr << "ERROR: couldn't get attribute for usd shader input " << input.GetPrim().GetPath()
<< " " << input.GetFullName() << std::endl;
continue;
}
pxr::VtValue val;
if (!usd_attr.Get(&val)) {
std::cerr << "ERROR: couldn't get value for usd shader input " << input.GetPrim().GetPath()
<< " " << input.GetFullName() << std::endl;
continue;
}
if (val.IsHolding<float>()) {
double dval = val.UncheckedGet<float>();
tup = Py_BuildValue("sd", name.c_str(), dval);
}
else if (val.IsHolding<int>()) {
int ival = val.UncheckedGet<int>();
tup = Py_BuildValue("si", name.c_str(), ival);
}
else if (val.IsHolding<bool>()) {
int ival = val.UncheckedGet<bool>();
tup = Py_BuildValue("si", name.c_str(), ival);
}
else if (val.IsHolding<pxr::SdfAssetPath>()) {
pxr::SdfAssetPath asset_path = val.Get<pxr::SdfAssetPath>();
std::string resolved_path = asset_path.GetResolvedPath();
if (resolved_path.empty()) {
/* If the path wasn't resolved, it could be because it's a UDIM path,
* so try to use the asset path directly, anchoring it if it's a relative path. */
resolved_path = anchor_relative_path(usd_shader.GetPrim().GetStage(),
asset_path.GetAssetPath());
}
pxr::TfToken color_space_tok = usd_attr.GetColorSpace();
if (color_space_tok.IsEmpty() && have_connected_source) {
/* The connected asset input has no color space specified,
* so we also check the shader's asset input for this data. */
if (pxr::UsdAttribute input_attr = input.GetAttr()) {
color_space_tok = input_attr.GetColorSpace();
}
}
std::string color_space_str = !color_space_tok.IsEmpty() ? color_space_tok.GetString() :
"sRGB";
PyObject *tex_file_tup = Py_BuildValue("ss", resolved_path.c_str(), color_space_str.c_str());
tup = Py_BuildValue("sN", name.c_str(), tex_file_tup);
}
else if (val.IsHolding<pxr::GfVec3f>()) {
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
pxr::GfVec3d v3d(v3f);
PyObject *v3_tup = Py_BuildValue("ddd", v3d[0], v3d[1], v3d[2]);
if (v3_tup) {
tup = Py_BuildValue("sN", name.c_str(), v3_tup);
}
else {
std::cout << "Couldn't build v3f tuple for " << usd_shader.GetPath() << " input "
<< input.GetFullName() << std::endl;
}
}
else if (val.IsHolding<pxr::GfVec2f>()) {
pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>();
/* std::cout << "Have v2f input " << v2f << " for "
<< usd_shader.GetPath() << " " << input.GetFullName() << std::endl;*/
pxr::GfVec2d v2d(v2f);
PyObject *v2_tup = Py_BuildValue("dd", v2d[0], v2d[1]);
if (v2_tup) {
tup = Py_BuildValue("sN", name.c_str(), v2_tup);
}
else {
std::cout << "Couldn't build v2f tuple for " << usd_shader.GetPath() << " input "
<< input.GetFullName() << std::endl;
}
}
if (tup) {
tuple_items.push_back(tup);
}
}
PyObject *ret = PyTuple_New(tuple_items.size());
if (!ret) {
return nullptr;
}
for (int i = 0; i < tuple_items.size(); ++i) {
if (PyTuple_SetItem(ret, i, tuple_items[i])) {
std::cout << "error setting tuple item" << std::endl;
}
}
return ret;
}
static bool import_material(Material *mtl,
const pxr::UsdShadeShader &usd_shader,
const std::string &source_class)
{
if (!(usd_shader && mtl)) {
return false;
}
PyGILState_STATE gilstate = PyGILState_Ensure();
if (!ensure_module_loaded()) {
PyGILState_Release(gilstate);
return false;
}
const char *func_name = "apply_data_to_instance";
if (!PyObject_HasAttrString(g_umm_module, func_name)) {
std::cerr << "WARNING: UMM module has no attribute " << func_name << std::endl;
PyGILState_Release(gilstate);
return false;
}
PyObject *func = PyObject_GetAttrString(g_umm_module, func_name);
if (!func) {
std::cerr << "WARNING: Couldn't get UMM module attribute " << func_name << std::endl;
PyGILState_Release(gilstate);
return false;
}
PyObject *source_data = get_shader_source_data(usd_shader);
if (!source_data) {
std::cout << "WARNING: Couldn't get source data for shader " << usd_shader.GetPath()
<< std::endl;
PyGILState_Release(gilstate);
return false;
}
// std::cout << "source_data:\n";
// print_obj(source_data);
// Create the kwargs dictionary.
PyObject *kwargs = PyDict_New();
if (!kwargs) {
std::cout << "WARNING: Couldn't create kwargs dicsionary." << std::endl;
Py_DECREF(source_data);
PyGILState_Release(gilstate);
return false;
}
PyObject *instance_name = PyUnicode_FromString(mtl->id.name + 2);
PyDict_SetItemString(kwargs, "instance_name", instance_name);
Py_DECREF(instance_name);
PyObject *source_class_obj = PyUnicode_FromString(source_class.c_str());
PyDict_SetItemString(kwargs, "source_class", source_class_obj);
Py_DECREF(source_class_obj);
PyObject *render_context = PyUnicode_FromString("Blender");
PyDict_SetItemString(kwargs, "render_context", render_context);
Py_DECREF(render_context);
PyDict_SetItemString(kwargs, "source_data", source_data);
Py_DECREF(source_data);
std::cout << func_name << " arguments:\n";
print_obj(kwargs);
PyObject *empty_args = PyTuple_New(0);
PyObject *ret = PyObject_Call(func, empty_args, kwargs);
Py_DECREF(empty_args);
Py_DECREF(func);
bool success = ret != nullptr;
if (ret) {
std::cout << "result:\n";
print_obj(ret);
if (report_notification(ret) == UMM_NOTIFICATION_FAILURE) {
/* The function returned a notification object
* indicating a failure. */
success = false;
}
Py_DECREF(ret);
}
Py_DECREF(kwargs);
PyGILState_Release(gilstate);
return success;
}
static void set_shader_properties(const USDExporterContext &usd_export_context,
pxr::UsdShadeShader &usd_shader,
PyObject *data_list)
{
if (!(data_list && usd_shader)) {
return;
}
if (!PyList_Check(data_list)) {
return;
}
Py_ssize_t len = PyList_Size(data_list);
for (Py_ssize_t i = 0; i < len; ++i) {
PyObject *tup = PyList_GetItem(data_list, i);
if (!tup) {
continue;
}
std::string name;
if (!get_data_name(tup, name) || name.empty()) {
std::cout << "Couldn't get data name\n";
continue;
}
if (is_none_value(tup)) {
/* Receiving None values is not an error. */
continue;
}
if (name == "umm_target_class") {
std::string target_class;
if (!get_string_data(tup, target_class) || target_class.empty()) {
std::cout << "Couldn't get target class\n";
continue;
}
set_source_asset(usd_shader, target_class);
}
else {
if (!(PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
std::cout << "Unexpected data item type or size:\n";
print_obj(tup);
continue;
}
PyObject *second = PyTuple_GetItem(tup, 1);
if (!second) {
std::cout << "Couldn't get second tuple value:\n";
print_obj(tup);
continue;
}
if (PyFloat_Check(second)) {
float fval = static_cast<float>(PyFloat_AsDouble(second));
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Float).Set(fval);
}
else if (PyBool_Check(second)) {
bool bval = static_cast<bool>(PyLong_AsLong(second));
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Bool).Set(bval);
}
else if (PyLong_Check(second)) {
int ival = static_cast<int>(PyLong_AsLong(second));
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Int).Set(ival);
}
else if (PyList_Check(second) && PyList_Size(second) == 2) {
PyObject *item0 = PyList_GetItem(second, 0);
PyObject *item1 = PyList_GetItem(second, 1);
if (PyUnicode_Check(item0) && PyUnicode_Check(item1)) {
const char *asset = PyUnicode_AsUTF8(item0);
std::string asset_path = get_tex_image_asset_path(
asset, usd_export_context.stage, usd_export_context.export_params);
const char *color_space = PyUnicode_AsUTF8(item1);
pxr::UsdShadeInput asset_input = usd_shader.CreateInput(pxr::TfToken(name),
pxr::SdfValueTypeNames->Asset);
asset_input.Set(pxr::SdfAssetPath(asset_path));
asset_input.GetAttr().SetColorSpace(pxr::TfToken(color_space));
}
else if (PyFloat_Check(item0) && PyFloat_Check(item1)) {
float f0 = static_cast<float>(PyFloat_AsDouble(item0));
float f1 = static_cast<float>(PyFloat_AsDouble(item1));
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Float2)
.Set(pxr::GfVec2f(f0, f1));
}
}
else if (PyTuple_Check(second) && PyTuple_Size(second) == 3) {
pxr::GfVec3f f3val;
for (int i = 0; i < 3; ++i) {
PyObject *comp = PyTuple_GetItem(second, i);
if (comp && PyFloat_Check(comp)) {
f3val[i] = static_cast<float>(PyFloat_AsDouble(comp));
}
else {
std::cout << "Couldn't parse color3f " << name << std::endl;
}
}
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Color3f).Set(f3val);
}
else {
std::cout << "Can't handle value:\n";
print_obj(second);
}
}
}
}
namespace blender::io::usd {
bool umm_module_loaded()
{
PyGILState_STATE gilstate = PyGILState_Ensure();
bool loaded = ensure_module_loaded(false /* warn */);
PyGILState_Release(gilstate);
return loaded;
}
bool umm_import_mdl_material(Material *mtl,
const pxr::UsdShadeMaterial &usd_material,
bool verbose,
bool *r_has_mdl)
{
if (!(mtl && usd_material)) {
return false;
}
/* Get the surface shader. */
pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource(usdtokens::mdl);
if (surf_shader) {
/* Check if we have an mdl source asset. */
pxr::SdfAssetPath source_asset;
if (!surf_shader.GetSourceAsset(&source_asset, usdtokens::mdl)) {
if (verbose) {
std::cout << "No mdl source asset for shader " << surf_shader.GetPath() << std::endl;
}
if (r_has_mdl) {
*r_has_mdl = false;
}
return false;
}
pxr::TfToken source_asset_sub_identifier;
if (!surf_shader.GetSourceAssetSubIdentifier(&source_asset_sub_identifier, usdtokens::mdl)) {
if (verbose) {
std::cout << "No mdl source asset sub identifier for shader " << surf_shader.GetPath()
<< std::endl;
}
if (r_has_mdl) {
*r_has_mdl = false;
}
return false;
}
if (r_has_mdl) {
*r_has_mdl = true;
}
std::string path = source_asset.GetAssetPath();
// Get the filename component of the path.
size_t last_slash = path.find_last_of("/\\");
if (last_slash != std::string::npos) {
path = path.substr(last_slash + 1);
}
std::string source_class = path + "|" + source_asset_sub_identifier.GetString();
return import_material(mtl, surf_shader, source_class);
}
return false;
}
bool umm_export_material(const USDExporterContext &usd_export_context,
const Material *mtl,
pxr::UsdShadeShader &usd_shader,
const std::string &render_context)
{
if (!(usd_shader && mtl)) {
return false;
}
PyGILState_STATE gilstate = PyGILState_Ensure();
if (!ensure_module_loaded()) {
PyGILState_Release(gilstate);
return false;
}
const char *func_name = "convert_instance_to_data";
if (!PyObject_HasAttrString(g_umm_module, func_name)) {
std::cerr << "WARNING: UMM module has no attribute " << func_name << std::endl;
PyGILState_Release(gilstate);
return false;
}
PyObject *func = PyObject_GetAttrString(g_umm_module, func_name);
if (!func) {
std::cerr << "WARNING: Couldn't get UMM module attribute " << func_name << std::endl;
PyGILState_Release(gilstate);
return false;
}
// Create the kwargs dictionary.
PyObject *kwargs = PyDict_New();
if (!kwargs) {
std::cout << "WARNING: Couldn't create kwargs dicsionary." << std::endl;
PyGILState_Release(gilstate);
return false;
}
PyObject *instance_name = PyUnicode_FromString(mtl->id.name + 2);
PyDict_SetItemString(kwargs, "instance_name", instance_name);
Py_DECREF(instance_name);
PyObject *render_context_arg = PyUnicode_FromString(render_context.c_str());
PyDict_SetItemString(kwargs, "render_context", render_context_arg);
Py_DECREF(render_context_arg);
std::cout << func_name << " arguments:\n";
print_obj(kwargs);
PyObject *empty_args = PyTuple_New(0);
PyObject *ret = PyObject_Call(func, empty_args, kwargs);
Py_DECREF(empty_args);
Py_DECREF(func);
bool success = ret != nullptr;
if (ret) {
std::cout << "result:\n";
print_obj(ret);
if (report_notification(ret) == UMM_NOTIFICATION_FAILURE) {
/* The function returned a notification object
* indicating a failure. */
success = false;
}
else {
set_shader_properties(usd_export_context, usd_shader, ret);
}
Py_DECREF(ret);
}
Py_DECREF(kwargs);
PyGILState_Release(gilstate);
return success;
}
} // Namespace blender::io::usd
#endif // ifdef WITH_PYTHON

View File

@@ -0,0 +1,46 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#pragma once
#ifdef WITH_PYTHON
# include <pxr/usd/usdShade/material.h>
# include "Python.h"
struct Material;
namespace blender::io::usd {
struct USDExporterContext;
bool umm_module_loaded();
bool umm_import_mdl_material(Material *mtl,
const pxr::UsdShadeMaterial &usd_material,
bool verbose,
bool *r_has_material);
bool umm_export_material(const USDExporterContext &usd_export_context,
const Material *mtl,
pxr::UsdShadeShader &usd_shader,
const std::string &render_context);
} // namespace blender::io::usd
#endif

View File

@@ -8,7 +8,6 @@
#include "BKE_customdata.h" #include "BKE_customdata.h"
#include "BLI_assert.h" #include "BLI_assert.h"
#include "DNA_mesh_types.h" #include "DNA_mesh_types.h"
/* TfToken objects are not cheap to construct, so we do it once. */ /* TfToken objects are not cheap to construct, so we do it once. */
@@ -20,8 +19,39 @@ static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal); static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal); static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal); static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
static const pxr::TfToken blenderName("userProperties:blenderName", pxr::TfToken::Immortal);
} // namespace usdtokens } // namespace usdtokens
namespace {
template<typename VECT>
bool set_vec_attrib(const pxr::UsdPrim &prim,
const IDProperty *prop,
const pxr::TfToken &prop_token,
const pxr::SdfValueTypeName &type_name,
const pxr::UsdTimeCode &timecode)
{
if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
return false;
}
pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
if (!vec_attr) {
printf("WARNING: Couldn't USD attribute for array property %s.\n",
prop_token.GetString().c_str());
return false;
}
VECT vec_value(static_cast<typename VECT::ScalarType *>(prop->data.pointer));
return vec_attr.Set(vec_value, timecode);
}
} // anonymous namespace
namespace blender::io::usd {
static std::string get_mesh_active_uvlayer_name(const Object *ob) static std::string get_mesh_active_uvlayer_name(const Object *ob)
{ {
if (!ob || ob->type != OB_MESH || !ob->data) { if (!ob || ob->type != OB_MESH || !ob->data) {
@@ -35,7 +65,81 @@ static std::string get_mesh_active_uvlayer_name(const Object *ob)
return name ? name : ""; return name ? name : "";
} }
namespace blender::io::usd { static void create_vector_attrib(const pxr::UsdPrim &prim,
const IDProperty *prop,
const pxr::TfToken &prop_token,
const pxr::UsdTimeCode &timecode)
{
if (!prim || !prop || prop_token.IsEmpty()) {
return;
}
if (prop->type != IDP_ARRAY) {
printf(
"WARNING: Property %s is not an array type and can't be converted to a vector "
"attribute.\n",
prop_token.GetString().c_str());
return;
}
pxr::SdfValueTypeName type_name;
bool success = false;
if (prop->subtype == IDP_FLOAT) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Float2;
success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Float3;
success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Float4;
success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, timecode);
}
}
else if (prop->subtype == IDP_DOUBLE) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Double2;
success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Double3;
success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Double4;
success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, timecode);
}
}
else if (prop->subtype == IDP_INT) {
if (prop->len == 2) {
type_name = pxr::SdfValueTypeNames->Int2;
success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 3) {
type_name = pxr::SdfValueTypeNames->Int3;
success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, timecode);
}
else if (prop->len == 4) {
type_name = pxr::SdfValueTypeNames->Int4;
success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, timecode);
}
}
if (!type_name) {
printf("WARNING: Couldn't determine USD type name for array property %s.\n",
prop_token.GetString().c_str());
return;
}
if (!success) {
printf("WARNING: Couldn't set USD attribute from array property %s.\n",
prop_token.GetString().c_str());
return;
}
}
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context) USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false) : usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
@@ -82,7 +186,27 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context, pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
Material *material) Material *material)
{ {
static pxr::SdfPath material_library_path("/_materials"); std::string material_prim_path_str;
/* For instance prototypes, create the material beneath the prototyp prim. */
if (usd_export_context_.export_params.use_instancing && !context.is_instance() &&
usd_export_context_.hierarchy_iterator->is_prototype(context.object)) {
material_prim_path_str += std::string(usd_export_context_.export_params.root_prim_path);
if (context.object->data) {
material_prim_path_str += context.higher_up_export_path;
}
else {
material_prim_path_str += context.export_path;
}
material_prim_path_str += "/Looks";
}
if (material_prim_path_str.empty()) {
material_prim_path_str = this->usd_export_context_.export_params.material_prim_path;
}
pxr::SdfPath material_library_path(material_prim_path_str);
pxr::UsdStageRefPtr stage = usd_export_context_.stage; pxr::UsdStageRefPtr stage = usd_export_context_.stage;
/* Construct the material. */ /* Construct the material. */
@@ -92,16 +216,39 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
if (usd_material) { if (usd_material) {
return usd_material; return usd_material;
} }
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
usd_material = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdShadeMaterial(usd_export_context_.stage->OverridePrim(usd_path)) :
pxr::UsdShadeMaterial::Define(usd_export_context_.stage, usd_path);
// TODO(bskinner) maybe always export viewport material as variant...
if (material->use_nodes && this->usd_export_context_.export_params.generate_cycles_shaders) {
create_usd_cycles_material(this->usd_export_context_.stage,
material,
usd_material,
this->usd_export_context_.export_params);
}
if (material->use_nodes && this->usd_export_context_.export_params.generate_mdl) {
create_mdl_material(this->usd_export_context_, material, usd_material);
if (this->usd_export_context_.export_params.export_textures) {
export_textures(material, this->usd_export_context_.stage);
}
}
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) { if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
std::string active_uv = get_mesh_active_uvlayer_name(context.object); std::string active_uv = get_mesh_active_uvlayer_name(context.object);
if (usd_export_context_.export_params.convert_uv_to_st && !active_uv.empty()) {
active_uv = "st";
}
create_usd_preview_surface_material( create_usd_preview_surface_material(
this->usd_export_context_, material, usd_material, active_uv); this->usd_export_context_, material, usd_material, active_uv);
} }
else { else {
create_usd_viewport_material(this->usd_export_context_, material, usd_material); create_usd_viewport_material(this->usd_export_context_, material, usd_material);
} }
if (usd_export_context_.export_params.export_custom_properties && material) {
auto prim = usd_material.GetPrim();
write_id_properties(prim, material->id, get_export_time_code());
}
return usd_material; return usd_material;
} }
@@ -130,7 +277,14 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
return false; return false;
} }
pxr::SdfPath ref_path(context.original_export_path); std::string ref_path_str(usd_export_context_.export_params.root_prim_path);
ref_path_str += context.original_export_path;
pxr::SdfPath ref_path(ref_path_str);
/* To avoid USD errors, make sure the referenced path exists. */
usd_export_context_.stage->DefinePrim(ref_path);
if (!prim.GetReferences().AddInternalReference(ref_path)) { if (!prim.GetReferences().AddInternalReference(ref_path)) {
/* See this URL for a description for why referencing may fail" /* See this URL for a description for why referencing may fail"
* https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
@@ -141,7 +295,95 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
return false; return false;
} }
prim.SetInstanceable(true);
return true; return true;
} }
void USDAbstractWriter::write_id_properties(pxr::UsdPrim &prim,
const ID &id,
pxr::UsdTimeCode timecode)
{
if (usd_export_context_.export_params.author_blender_name) {
if (GS(id.name) == ID_OB) {
// Author property of original blenderName
prim.CreateAttribute(pxr::TfToken(usdtokens::blenderName.GetString() + ":object"),
pxr::SdfValueTypeNames->String,
true)
.Set<std::string>(std::string(id.name + 2));
}
else {
prim.CreateAttribute(pxr::TfToken(usdtokens::blenderName.GetString() + ":data"),
pxr::SdfValueTypeNames->String,
true)
.Set<std::string>(std::string(id.name + 2));
}
}
if (id.properties)
write_user_properties(prim, (IDProperty *)id.properties, timecode);
}
void USDAbstractWriter::write_user_properties(pxr::UsdPrim &prim,
IDProperty *properties,
pxr::UsdTimeCode timecode)
{
if (properties == nullptr) {
return;
}
if (properties->type != IDP_GROUP) {
return;
}
IDProperty *prop;
for (prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
std::string prop_name = pxr::TfMakeValidIdentifier(prop->name);
std::string full_prop_name;
if (usd_export_context_.export_params.add_properties_namespace) {
full_prop_name = "userProperties:";
}
full_prop_name += prop_name;
pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
if (prim.HasAttribute(prop_token)) {
/* Don't overwrite existing attributes, as these may have been
* created by the exporter logic and shouldn't be changed. */
continue;
}
switch (prop->type) {
case IDP_INT:
if (pxr::UsdAttribute int_attr = prim.CreateAttribute(
prop_token, pxr::SdfValueTypeNames->Int, true)) {
int_attr.Set<int>(prop->data.val, timecode);
}
break;
case IDP_FLOAT:
if (pxr::UsdAttribute float_attr = prim.CreateAttribute(
prop_token, pxr::SdfValueTypeNames->Float, true)) {
float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), timecode);
}
break;
case IDP_DOUBLE:
if (pxr::UsdAttribute double_attr = prim.CreateAttribute(
prop_token, pxr::SdfValueTypeNames->Double, true)) {
double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), timecode);
}
break;
case IDP_STRING:
if (pxr::UsdAttribute str_attr = prim.CreateAttribute(
prop_token, pxr::SdfValueTypeNames->String, true)) {
str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), timecode);
}
break;
case IDP_ARRAY:
create_vector_attrib(prim, prop, prop_token, timecode);
break;
}
}
}
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -16,6 +16,7 @@
#include "DNA_material_types.h" #include "DNA_material_types.h"
struct Main;
struct Material; struct Material;
namespace blender::io::usd { namespace blender::io::usd {
@@ -55,6 +56,13 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material); pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
void write_id_properties(pxr::UsdPrim &prim,
const ID &id,
pxr::UsdTimeCode = pxr::UsdTimeCode::Default());
void write_user_properties(pxr::UsdPrim &prim,
IDProperty *properties,
pxr::UsdTimeCode = pxr::UsdTimeCode::Default());
void write_visibility(const HierarchyContext &context, void write_visibility(const HierarchyContext &context,
const pxr::UsdTimeCode timecode, const pxr::UsdTimeCode timecode,
pxr::UsdGeomImageable &usd_geometry); pxr::UsdGeomImageable &usd_geometry);

View File

@@ -0,0 +1,327 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 Blender Foundation.
* All rights reserved.
*/
#include "usd_writer_armature.h"
#include "usd_hierarchy_iterator.h"
#include "BKE_armature.h"
#include "DNA_armature_types.h"
#include "ED_armature.h"
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/matrix4f.h>
#include <pxr/usd/usdSkel/animation.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/skeleton.h>
#include <pxr/usd/usdSkel/tokens.h>
#include <iostream>
#include <string>
#include <vector>
namespace usdtokens {
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
} // namespace usdtokens
static std::string build_path(const Bone *bone)
{
std::string path(pxr::TfMakeValidIdentifier(bone->name));
const Bone *parent = bone->parent;
while (parent) {
path = pxr::TfMakeValidIdentifier(parent->name) + std::string("/") + path;
parent = parent->parent;
}
return path;
}
namespace {
struct BoneVisitor {
public:
virtual void Visit(const Bone *bone) = 0;
};
struct BoneNameList : public BoneVisitor {
std::vector<std::string> *names;
BoneNameList(std::vector<std::string> *in_names) : names(in_names)
{
}
void Visit(const Bone *bone) override
{
if (bone && names) {
names->push_back(bone->name);
}
}
};
struct BoneDataBuilder : public BoneVisitor {
std::vector<std::string> paths;
pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
pxr::GfMatrix4f world_mat;
BoneDataBuilder(const pxr::GfMatrix4f &in_world_mat) : world_mat(in_world_mat)
{
}
void Visit(const Bone *bone) override
{
if (!bone) {
return;
}
paths.push_back(build_path(bone));
pxr::GfMatrix4f arm_mat(bone->arm_mat);
pxr::GfMatrix4f bind_xf = arm_mat * world_mat;
bind_xforms.push_back(pxr::GfMatrix4d(bind_xf));
if (bone->parent) {
pxr::GfMatrix4f parent_arm_mat(bone->parent->arm_mat);
pxr::GfMatrix4f rest_xf = arm_mat * parent_arm_mat.GetInverse();
rest_xforms.push_back(pxr::GfMatrix4d(rest_xf));
}
else {
rest_xforms.push_back(pxr::GfMatrix4d(arm_mat));
}
}
};
} // End anonymous namespace
static void visit_bones(const Bone *bone, BoneVisitor *visitor)
{
if (!(bone && visitor)) {
return;
}
visitor->Visit(bone);
for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
visit_bones(child, visitor);
}
}
static void visit_bones(const Object *ob_arm, BoneVisitor *visitor)
{
bArmature *armature = (bArmature *)ob_arm->data;
for (Bone *bone = (Bone *)armature->bonebase.first; bone; bone = bone->next) {
visit_bones(bone, visitor);
}
}
static void create_pose_joints(const pxr::UsdSkelAnimation &skel_anim, Object *obj)
{
if (!(skel_anim && obj && obj->pose)) {
return;
}
pxr::VtTokenArray joints;
bPose *pose = obj->pose;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
if (pchan->bone) {
joints.push_back(pxr::TfToken(build_path(pchan->bone)));
}
}
skel_anim.GetJointsAttr().Set(joints);
}
static bPoseChannel *get_parent_pose_chan(bPose *pose, bPoseChannel *in_pchan)
{
if (!(pose && in_pchan && in_pchan->bone && in_pchan->bone->parent)) {
return nullptr;
}
Bone *parent = in_pchan->bone->parent;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
if (pchan->bone == parent) {
return pchan;
}
}
return nullptr;
}
static void add_anim_sample(const pxr::UsdSkelAnimation &skel_anim,
Object *obj,
pxr::UsdTimeCode time)
{
if (!(skel_anim && obj && obj->pose)) {
return;
}
pxr::VtArray<pxr::GfMatrix4d> xforms;
bPose *pose = obj->pose;
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
if (!pchan->bone) {
printf("WARNING: pchan %s is missing bone.\n", pchan->name);
continue;
}
pxr::GfMatrix4f pose_mat(pchan->pose_mat);
if (bPoseChannel *parent_pchan = get_parent_pose_chan(pose, pchan)) {
pxr::GfMatrix4f parent_pose_mat(parent_pchan->pose_mat);
pxr::GfMatrix4f xf = pose_mat * parent_pose_mat.GetInverse();
xforms.push_back(pxr::GfMatrix4d(xf));
}
else {
xforms.push_back(pxr::GfMatrix4d(pose_mat));
}
}
skel_anim.SetTransforms(xforms, time);
}
namespace blender::io::usd {
void USDArmatureWriter::get_armature_bone_names(Object *obj, std::vector<std::string> &r_names)
{
if (!obj) {
return;
}
BoneNameList name_list(&r_names);
visit_bones(obj, &name_list);
}
USDArmatureWriter::USDArmatureWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{
}
void USDArmatureWriter::do_write(HierarchyContext &context)
{
if (!context.object) {
printf("WARNING in USDArmatureWriter::do_write: null object\n");
return;
}
if (context.object->type != OB_ARMATURE) {
printf("WARNING in USDArmatureWriter::do_write: object is not an armature\n");
return;
}
if (context.object->data == nullptr) {
printf("WARNING in USDArmatureWriter::do_write: null object data\n");
return;
}
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdSkelSkeleton usd_skel = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdSkelSkeleton(
stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdSkelSkeleton::Define(stage,
usd_export_context_.usd_path);
if (!usd_skel) {
printf("WARNING: Couldn't define Skeleton %s\n",
usd_export_context_.usd_path.GetString().c_str());
return;
}
pxr::UsdSkelAnimation usd_skel_anim;
if (usd_export_context_.export_params.export_animation) {
/* Create the SkelAnimation primitive. */
/* TODO: Right now there is a remote possibility that the SkelAnimation path will clash
* with the USD path for another object in the scene. Look into extending USDHierarchyIterator
* with a function that will provide a USD path that's guranteed to be unique (e.g., by
* examining paths of all the writers in the writer map). The USDHierarchyIterator
* can be accessed for such a query like this:
* this->usd_export_context_.hierarchy_iterator */
pxr::SdfPath anim_path = usd_export_context_.usd_path.AppendChild(usdtokens::Anim);
usd_skel_anim = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdSkelAnimation(stage->OverridePrim(anim_path)) :
pxr::UsdSkelAnimation::Define(stage, anim_path);
if (!usd_skel_anim) {
printf("WARNING: Couldn't define SkelAnim %s\n", anim_path.GetString().c_str());
}
}
if (!this->frame_has_been_written_) {
pxr::GfMatrix4f world_mat(context.matrix_world);
BoneDataBuilder bone_data(world_mat);
visit_bones(context.object, &bone_data);
if (!bone_data.paths.empty()) {
pxr::VtTokenArray joints(bone_data.paths.size());
for (int i = 0; i < bone_data.paths.size(); ++i) {
joints[i] = pxr::TfToken(bone_data.paths[i]);
}
usd_skel.GetJointsAttr().Set(joints);
}
usd_skel.GetBindTransformsAttr().Set(bone_data.bind_xforms);
usd_skel.GetRestTransformsAttr().Set(bone_data.rest_xforms);
if (usd_skel_anim) {
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(usd_skel.GetPrim());
usd_skel_api.CreateAnimationSourceRel().SetTargets(
pxr::SdfPathVector({pxr::SdfPath(usdtokens::Anim)}));
create_pose_joints(usd_skel_anim, context.object);
}
}
if (usd_skel_anim) {
add_anim_sample(usd_skel_anim, context.object, timecode);
}
}
bool USDArmatureWriter::check_is_animated(const HierarchyContext &context) const
{
const Object *obj = context.object;
if (!(obj && obj->type == OB_ARMATURE)) {
return false;
}
/* TODO(makowalski): Is this a sufficient check? */
return obj->adt != nullptr;
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,42 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 Blender Foundation.
* All rights reserved.
*/
#pragma once
#include "usd_writer_abstract.h"
#include <string>
#include <vector>
struct Object;
namespace blender::io::usd {
class USDArmatureWriter : public USDAbstractWriter {
public:
static void get_armature_bone_names(Object *obj, std::vector<std::string> &r_names);
USDArmatureWriter(const USDExporterContext &ctx);
protected:
virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
};
} // namespace blender::io::usd

View File

@@ -12,6 +12,10 @@
#include "DNA_camera_types.h" #include "DNA_camera_types.h"
#include "DNA_scene_types.h" #include "DNA_scene_types.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_types.h"
namespace blender::io::usd { namespace blender::io::usd {
USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
@@ -52,9 +56,14 @@ static void camera_sensor_size_for_render(const Camera *camera,
void USDCameraWriter::do_write(HierarchyContext &context) void USDCameraWriter::do_write(HierarchyContext &context)
{ {
const float unit_scale = usd_export_context_.export_params.convert_to_cm ? 100.0f : 1.0f;
pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage, pxr::UsdGeomCamera usd_camera = (usd_export_context_.export_params.export_as_overs) ?
usd_export_context_.usd_path); pxr::UsdGeomCamera(usd_export_context_.stage->OverridePrim(
usd_export_context_.usd_path)) :
pxr::UsdGeomCamera::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
Camera *camera = static_cast<Camera *>(context.object->data); Camera *camera = static_cast<Camera *>(context.object->data);
Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph); Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
@@ -67,8 +76,12 @@ void USDCameraWriter::do_write(HierarchyContext &context)
*/ */
usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode); usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
float aperture_x, aperture_y; /* TODO(makowalski): Maybe add option to call camera_sensor_size_for_render(). */
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y); /*float aperture_x, aperture_y;
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);*/
float aperture_x = camera->sensor_x;
float aperture_y = camera->sensor_y;
float film_aspect = aperture_x / aperture_y; float film_aspect = aperture_x / aperture_y;
usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode); usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
@@ -76,18 +89,37 @@ void USDCameraWriter::do_write(HierarchyContext &context)
usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode); usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect, usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
timecode); timecode);
/* TODO(makowalsk): confirm this is the correct way to get the shutter. */
float shutter_length = scene->eevee.motion_blur_shutter / 2.0f;
double shutter_open = -shutter_length;
double shutter_close = shutter_length;
if (usd_export_context_.export_params.override_shutter) {
shutter_open = usd_export_context_.export_params.shutter_open;
shutter_close = usd_export_context_.export_params.shutter_close;
}
usd_camera.CreateShutterOpenAttr().Set(shutter_open);
usd_camera.CreateShutterCloseAttr().Set(shutter_close);
usd_camera.CreateClippingRangeAttr().Set( usd_camera.CreateClippingRangeAttr().Set(
pxr::VtValue(pxr::GfVec2f(camera->clip_start, camera->clip_end)), timecode); pxr::VtValue(pxr::GfVec2f(camera->clip_start * unit_scale, camera->clip_end * unit_scale)),
timecode);
/* Write DoF-related attributes. */ /* Write DoF-related attributes. */
if (camera->dof.flag & CAM_DOF_ENABLED) { if (camera->dof.flag & CAM_DOF_ENABLED) {
usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode); usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
float focus_distance = scene->unit.scale_length * float focus_distance = scene->unit.scale_length *
BKE_camera_object_dof_distance(context.object); BKE_camera_object_dof_distance(context.object) * unit_scale;
usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode); usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
} }
if (usd_export_context_.export_params.export_custom_properties && camera) {
auto prim = usd_camera.GetPrim();
write_id_properties(prim, camera->id, timecode);
}
} }
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -0,0 +1,257 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2019 Blender Foundation.
* All rights reserved.
*/
#include "usd_writer_curve.h"
#include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/curves.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
extern "C" {
#include "BKE_curve.h"
#include "BKE_material.h"
#include "BLI_math_geom.h"
#include "DNA_curve_types.h"
#include "WM_api.h"
#include "WM_types.h"
}
namespace blender::io::usd {
USDCurveWriter::USDCurveWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{
}
void USDCurveWriter::do_write(HierarchyContext &context)
{
// Because blender allows vector handles and auto handles all bezier curves are bezier
// An optimization could be made to set usd type to linear if all controls are vector
Curve *curve = static_cast<Curve *>(context.object->data);
pxr::VtArray<pxr::GfVec3f> verts;
pxr::VtArray<float> widths;
std::vector<int> vert_counts;
std::vector<float> weights;
std::vector<float> knots;
std::vector<uint8_t> orders;
pxr::VtIntArray curve_point_counts;
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdGeomCurves curves;
Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
if (nurbs == nullptr)
return;
int curveType = nurbs->type;
for (; nurbs; nurbs = nurbs->next) {
if (nurbs->type != curveType) {
// We dont yet support writing curves with multiple types of curve data
WM_reportf(RPT_WARNING, "Cannot export mixed curves");
return;
}
}
if (curveType == CU_NURBS) {
curves = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomNurbsCurves(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomNurbsCurves::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
}
else {
curves = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomBasisCurves(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
pxr::UsdGeomBasisCurves(curves).CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
}
nurbs = static_cast<Nurb *>(curve->nurb.first);
for (; nurbs; nurbs = nurbs->next) {
bool is_cyclic = false;
if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
// Not needed for this conversion
// curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->periodic));
is_cyclic = true;
}
if (nurbs->bp) {
if (nurbs->type != CU_NURBS) {
pxr::UsdGeomBasisCurves(curves).CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
pxr::UsdGeomBasisCurves(curves).CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
}
const long long totpoint = nurbs->pntsu * nurbs->pntsv;
const BPoint *point = nurbs->bp;
curve_point_counts.push_back(totpoint);
for (int i = 0; i < totpoint; i++, point++) {
verts.push_back(pxr::GfVec3f(point->vec));
weights.push_back(point->vec[3]);
widths.push_back(point->radius * 2.0f);
}
}
else if (nurbs->bezt) {
if (nurbs->type != CU_NURBS) {
pxr::UsdGeomBasisCurves(curves).CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
pxr::UsdGeomBasisCurves(curves).CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
}
const int totpoint = nurbs->pntsu;
const BezTriple *bezier = nurbs->bezt;
curve_point_counts.push_back((totpoint * 3) - (is_cyclic ? -1 : 2));
/* TODO(kevin): store info about handles, Alembic doesn't have this. */
for (int i = 0; i < totpoint; i++, bezier++) {
if (i > 0) {
verts.push_back(pxr::GfVec3f(bezier->vec[0]));
widths.push_back(bezier->radius * 2.0f);
}
verts.push_back(pxr::GfVec3f(bezier->vec[1]));
widths.push_back(bezier->radius * 2.0f);
if (i < totpoint - 1 || is_cyclic) {
verts.push_back(pxr::GfVec3f(bezier->vec[2]));
widths.push_back(bezier->radius * 2.0f);
}
}
if (is_cyclic) {
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[0]));
widths.push_back(nurbs->bezt->radius * 2.0f);
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[1]));
widths.push_back(nurbs->bezt->radius * 2.0f);
}
}
// TODO: Implement knots
// if (nurbs->knotsu != NULL) {
// const size_t num_knots = KNOTSU(nurbs);
// /* Add an extra knot at the beginning and end of the array since most apps
// * require/expect them. */
// knots.resize(num_knots + 2);
// for (int i = 0; i < num_knots; i++) {
// knots[i + 1] = nurbs->knotsu[i];
// }
// if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
// knots[0] = nurbs->knotsu[0];
// knots[num_knots - 1] = nurbs->knotsu[num_knots - 1];
// }
// else {
// knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]);
// knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] -
// nurbs->knotsu[num_knots - 2]);
// }
// }
// orders.push_back(nurbs->orderu);
// vert_counts.push_back(verts.size());
}
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
pxr::UsdAttribute attr_widths = curves.CreateWidthsAttr(pxr::VtValue(), true);
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
// `timecode` will be default time in case of non-animation exports. For animated
// exports, USD will inter/extrapolate values linearly.
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(verts), timecode);
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
usd_value_writer_.SetAttribute(attr_widths, pxr::VtValue(widths), timecode);
// USDGeomBasisCurves only allow binding one material to each basis curve.
// In order to support Blender's curve material assignment we probably
// need to create multiple Basis Curves per mat_nr
assign_materials(context, curves);
if (usd_export_context_.export_params.export_custom_properties && curve &&
curve->id.properties) {
auto prim = curves.GetPrim();
write_id_properties(prim, curve->id, timecode);
}
}
bool USDCurveWriter::check_is_animated(const HierarchyContext &) const
{
return true;
}
void USDCurveWriter::assign_materials(const HierarchyContext &context,
pxr::UsdGeomCurves usd_curve)
{
if (context.object->totcol == 0) {
return;
}
bool curve_material_bound = false;
for (short mat_num = 0; mat_num < context.object->totcol; mat_num++) {
Material *material = BKE_object_material_get(context.object, mat_num + 1);
if (material == nullptr) {
continue;
}
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_curve.GetPrim());
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
api.Bind(usd_material);
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
* use the flag from the first non-empty material slot. */
usd_curve.CreateDoubleSidedAttr(
pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
curve_material_bound = true;
break;
}
if (!curve_material_bound) {
/* Blender defaults to double-sided, but USD to single-sided. */
usd_curve.CreateDoubleSidedAttr(pxr::VtValue(true));
}
if (!curve_material_bound) {
/* Either all material slots were empty or there is only one material in use. As geometry
* subsets are only written when actually used to assign a material, and the mesh already has
* the material assigned, there is no need to continue. */
return;
}
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,41 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2019 Blender Foundation.
* All rights reserved.
*/
#ifndef __USD_WRITER_CURVE_H__
#define __USD_WRITER_CURVE_H__
#include "usd_writer_abstract.h"
#include <pxr/usd/usdGeom/basisCurves.h>
namespace blender::io::usd {
/* Writer for writing Curve data as USD curves. */
class USDCurveWriter : public USDAbstractWriter {
public:
USDCurveWriter(const USDExporterContext &ctx);
protected:
virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
void assign_materials(const HierarchyContext &context, pxr::UsdGeomCurves usd_curve);
};
} // namespace blender::io::usd
#endif /* __USD_WRITER_CURVE_H__ */

View File

@@ -3,12 +3,19 @@
#include "usd_writer_hair.h" #include "usd_writer_hair.h"
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/tokens.h> #include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
extern "C" {
#include "BKE_material.h"
#include "BKE_particle.h" #include "BKE_particle.h"
#include "BLI_math_geom.h"
#include "DNA_particle_types.h" #include "DNA_particle_types.h"
}
namespace blender::io::usd { namespace blender::io::usd {
@@ -16,8 +23,28 @@ USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(
{ {
} }
// This was copied from source/intern/cycles/blender/blender_curves.cpp
static float shaperadius(float shape, float root, float tip, float time)
{
assert(time >= 0.0f);
assert(time <= 1.0f);
float radius = 1.0f - time;
if (shape != 0.0f) {
if (shape < 0.0f)
radius = powf(radius, 1.0f + shape);
else
radius = powf(radius, 1.0f / (1.0f - shape));
}
return (radius * (root - tip)) + tip;
}
void USDHairWriter::do_write(HierarchyContext &context) void USDHairWriter::do_write(HierarchyContext &context)
{ {
/* Get untransformed vertices, there's a xform under the hair. */
float inv_mat[4][4];
invert_m4_m4_safe(inv_mat, context.object->obmat);
ParticleSystem *psys = context.particle_system; ParticleSystem *psys = context.particle_system;
ParticleCacheKey **cache = psys->pathcache; ParticleCacheKey **cache = psys->pathcache;
if (cache == nullptr) { if (cache == nullptr) {
@@ -25,17 +52,34 @@ void USDHairWriter::do_write(HierarchyContext &context)
} }
pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage, pxr::UsdGeomBasisCurves curves =
usd_export_context_.usd_path); (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomBasisCurves(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage, usd_export_context_.usd_path);
/* TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE) */ if (psys->part->flag & PART_HAIR_BSPLINE) {
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline)); curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic)); curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
}
else {
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
}
curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
pxr::VtArray<pxr::GfVec3f> points; pxr::VtArray<pxr::GfVec3f> points;
pxr::VtArray<float> widths;
pxr::VtIntArray curve_point_counts; pxr::VtIntArray curve_point_counts;
curve_point_counts.reserve(psys->totpart); curve_point_counts.reserve(psys->totpart);
float hair_root_rad = psys->part->rad_root * psys->part->rad_scale * 0.5f;
float hair_tip_rad = psys->part->rad_tip * psys->part->rad_scale * 0.5f;
float hair_shape = psys->part->shape;
bool close_tip = (psys->part->shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
ParticleCacheKey *strand; ParticleCacheKey *strand;
for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) { for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
strand = cache[strand_index]; strand = cache[strand_index];
@@ -44,24 +88,64 @@ void USDHairWriter::do_write(HierarchyContext &context)
curve_point_counts.push_back(point_count); curve_point_counts.push_back(point_count);
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) { for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
points.push_back(pxr::GfVec3f(strand->co)); float vert[3];
copy_v3_v3(vert, strand->co);
mul_m4_v3(inv_mat, vert);
points.push_back(pxr::GfVec3f(vert));
float t = (float)point_index / (float)(point_count - 1);
// if(point_index == point_count - 1) t = 0.95f;
float root_rad = (close_tip && point_index == point_count - 1) ? 0 : hair_tip_rad;
widths.push_back(shaperadius(hair_shape, hair_root_rad, root_rad, t) * 2.0f);
}
}
if (usd_export_context_.export_params.export_child_particles) {
ParticleCacheKey **child_cache = psys->childcache;
if (child_cache != nullptr) {
for (int strand_index = 0; strand_index < psys->totchild; ++strand_index) {
strand = child_cache[strand_index];
int point_count = strand->segments + 1;
curve_point_counts.push_back(point_count);
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
float vert[3];
copy_v3_v3(vert, strand->co);
mul_m4_v3(inv_mat, vert);
points.push_back(pxr::GfVec3f(vert));
float t = (float)point_index / (float)(point_count - 1);
t = clamp_f(t, 0.0f, 0.95f);
widths.push_back(shaperadius(hair_shape, hair_root_rad, hair_tip_rad, t) * 2.0f);
}
}
} }
} }
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true); pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true); pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
if (!attr_points.HasValue()) { pxr::UsdAttribute attr_widths = curves.CreateWidthsAttr(pxr::VtValue(), true);
attr_points.Set(points, pxr::UsdTimeCode::Default());
attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default()); // NOTE (Marcelo Sercheli): Code to set values at default time was removed since
} // `timecode` will be default time in case of non-animation exports. For animated
// exports, USD will inter/extrapolate values linearly.
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode); usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode);
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode); usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
usd_value_writer_.SetAttribute(attr_widths, pxr::VtValue(widths), timecode);
if (psys->totpart > 0) { if (psys->totpart > 0) {
pxr::VtArray<pxr::GfVec3f> colors; pxr::VtArray<pxr::GfVec3f> colors;
colors.push_back(pxr::GfVec3f(cache[0]->col)); colors.push_back(pxr::GfVec3f(cache[0]->col));
curves.CreateDisplayColorAttr(pxr::VtValue(colors)); curves.CreateDisplayColorAttr(pxr::VtValue(colors));
} }
if (usd_export_context_.export_params.export_materials) {
assign_material(context, curves);
}
if (usd_export_context_.export_params.export_custom_properties && psys->part) {
auto prim = curves.GetPrim();
write_id_properties(prim, psys->part->id, timecode);
}
} }
bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) const bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) const
@@ -69,4 +153,24 @@ bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) c
return true; return true;
} }
void USDHairWriter::assign_material(const HierarchyContext &context,
pxr::UsdGeomBasisCurves usd_curve)
{
ParticleSystem *psys = context.particle_system;
// In newer Blender builds this becomes: BKE_object_material_get
Material *material = BKE_object_material_get(context.object, psys->part->omat);
if (material == nullptr) {
return;
}
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_curve.GetPrim());
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
api.Bind(usd_material);
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
* use the flag from the first non-empty material slot. */
usd_curve.CreateDoubleSidedAttr(pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
}
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -4,6 +4,8 @@
#include "usd_writer_abstract.h" #include "usd_writer_abstract.h"
#include <pxr/usd/usdGeom/basisCurves.h>
namespace blender::io::usd { namespace blender::io::usd {
/* Writer for writing hair particle data as USD curves. */ /* Writer for writing hair particle data as USD curves. */
@@ -14,6 +16,7 @@ class USDHairWriter : public USDAbstractWriter {
protected: protected:
virtual void do_write(HierarchyContext &context) override; virtual void do_write(HierarchyContext &context) override;
virtual bool check_is_animated(const HierarchyContext &context) const override; virtual bool check_is_animated(const HierarchyContext &context) const override;
void assign_material(const HierarchyContext &context, pxr::UsdGeomBasisCurves usd_curve);
}; };
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -2,18 +2,36 @@
* Copyright 2019 Blender Foundation. All rights reserved. */ * Copyright 2019 Blender Foundation. All rights reserved. */
#include "usd_writer_light.h" #include "usd_writer_light.h"
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include "usd_light_convert.h"
#include <pxr/usd/usdLux/diskLight.h> #include <pxr/usd/usdLux/diskLight.h>
#include <pxr/usd/usdLux/distantLight.h> #include <pxr/usd/usdLux/distantLight.h>
#include <pxr/usd/usdLux/rectLight.h> #include <pxr/usd/usdLux/rectLight.h>
#include <pxr/usd/usdLux/shapingAPI.h>
#include <pxr/usd/usdLux/sphereLight.h> #include <pxr/usd/usdLux/sphereLight.h>
#include "BLI_assert.h" #include "BLI_assert.h"
#include "BLI_math.h"
#include "BLI_math_matrix.h"
#include "BLI_utildefines.h" #include "BLI_utildefines.h"
#include "DNA_light_types.h" #include "DNA_light_types.h"
#include "DNA_object_types.h" #include "DNA_object_types.h"
#include "WM_api.h"
#include "WM_types.h"
namespace usdtokens {
// Attribute names.
static const pxr::TfToken angle("angle", pxr::TfToken::Immortal);
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
static const pxr::TfToken height("height", pxr::TfToken::Immortal);
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
static const pxr::TfToken radius("radius", pxr::TfToken::Immortal);
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
static const pxr::TfToken width("width", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace blender::io::usd { namespace blender::io::usd {
USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
@@ -23,15 +41,21 @@ USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWrite
bool USDLightWriter::is_supported(const HierarchyContext *context) const bool USDLightWriter::is_supported(const HierarchyContext *context) const
{ {
Light *light = static_cast<Light *>(context->object->data); Light *light = static_cast<Light *>(context->object->data);
return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN); return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN, LA_SPOT);
} }
void USDLightWriter::do_write(HierarchyContext &context) void USDLightWriter::do_write(HierarchyContext &context)
{ {
pxr::UsdStageRefPtr stage = usd_export_context_.stage; pxr::UsdStageRefPtr stage = usd_export_context_.stage;
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdTimeCode timecode = get_export_time_code();
/* Need to account for the scene scale when converting to nits
* or scaling the radius. */
float world_scale = mat4_to_scale(context.matrix_world);
float radius_scale = usd_export_context_.export_params.scale_light_radius ? 1.0f / world_scale :
1.0f;
Light *light = static_cast<Light *>(context.object->data); Light *light = static_cast<Light *>(context.object->data);
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
pxr::UsdLuxLightAPI usd_light_api; pxr::UsdLuxLightAPI usd_light_api;
@@ -45,8 +69,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
switch (light->area_shape) { switch (light->area_shape) {
case LA_AREA_DISK: case LA_AREA_DISK:
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */ case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path); pxr::UsdLuxDiskLight disk_light =
disk_light.CreateRadiusAttr().Set(light->area_size, timecode); (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxDiskLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxDiskLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
disk_light.CreateRadiusAttr().Set(light->area_size / 2.0f, timecode);
if (usd_export_context_.export_params.backward_compatible) {
if (pxr::UsdAttribute attr = disk_light.GetPrim().CreateAttribute(
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
attr.Set(light->area_size / 2.0f, timecode);
}
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
usd_light_api = disk_light.LightAPI(); usd_light_api = disk_light.LightAPI();
#else #else
@@ -55,9 +92,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
break; break;
} }
case LA_AREA_RECT: { case LA_AREA_RECT: {
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path); pxr::UsdLuxRectLight rect_light =
(usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxRectLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxRectLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
rect_light.CreateWidthAttr().Set(light->area_size, timecode); rect_light.CreateWidthAttr().Set(light->area_size, timecode);
rect_light.CreateHeightAttr().Set(light->area_sizey, timecode); rect_light.CreateHeightAttr().Set(light->area_sizey, timecode);
if (usd_export_context_.export_params.backward_compatible) {
pxr::UsdAttribute attr = rect_light.GetPrim().CreateAttribute(
usdtokens::width, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(light->area_size, timecode);
}
attr = rect_light.GetPrim().CreateAttribute(
usdtokens::height, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(light->area_sizey, timecode);
}
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
usd_light_api = rect_light.LightAPI(); usd_light_api = rect_light.LightAPI();
#else #else
@@ -66,9 +122,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
break; break;
} }
case LA_AREA_SQUARE: { case LA_AREA_SQUARE: {
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path); pxr::UsdLuxRectLight rect_light =
(usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxRectLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxRectLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
rect_light.CreateWidthAttr().Set(light->area_size, timecode); rect_light.CreateWidthAttr().Set(light->area_size, timecode);
rect_light.CreateHeightAttr().Set(light->area_size, timecode); rect_light.CreateHeightAttr().Set(light->area_size, timecode);
if (usd_export_context_.export_params.backward_compatible) {
pxr::UsdAttribute attr = rect_light.GetPrim().CreateAttribute(
usdtokens::width, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(light->area_size, timecode);
}
attr = rect_light.GetPrim().CreateAttribute(
usdtokens::height, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(light->area_size, timecode);
}
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
usd_light_api = rect_light.LightAPI(); usd_light_api = rect_light.LightAPI();
#else #else
@@ -79,8 +154,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
} }
break; break;
case LA_LOCAL: { case LA_LOCAL: {
pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path); pxr::UsdLuxSphereLight sphere_light =
sphere_light.CreateRadiusAttr().Set(light->area_size, timecode); (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxSphereLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxSphereLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
sphere_light.CreateRadiusAttr().Set(light->area_size * radius_scale, timecode);
if (usd_export_context_.export_params.backward_compatible) {
if (pxr::UsdAttribute attr = sphere_light.GetPrim().CreateAttribute(
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
attr.Set(light->area_size * radius_scale, timecode);
}
}
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
usd_light_api = sphere_light.LightAPI(); usd_light_api = sphere_light.LightAPI();
#else #else
@@ -88,13 +176,56 @@ void USDLightWriter::do_write(HierarchyContext &context)
#endif #endif
break; break;
} }
case LA_SUN: { case LA_SPOT: {
pxr::UsdLuxDistantLight distant_light = pxr::UsdLuxDistantLight::Define(stage, usd_path); pxr::UsdLuxSphereLight spot_light =
/* TODO(makowalski): set angle attribute here. */ (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxSphereLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxSphereLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
spot_light.CreateRadiusAttr().Set(light->area_size * radius_scale, timecode);
if (usd_export_context_.export_params.backward_compatible) {
if (pxr::UsdAttribute attr = spot_light.GetPrim().CreateAttribute(
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
attr.Set(light->area_size * radius_scale, timecode);
}
}
pxr::UsdLuxShapingAPI shapingAPI(spot_light);
float angle = (light->spotsize * (180.0f / (float)M_PI)) /
2.0f; // Blender angle seems to be half of what USD expectes it to be.
shapingAPI.CreateShapingConeAngleAttr(pxr::VtValue(angle), true);
shapingAPI.CreateShapingConeSoftnessAttr(pxr::VtValue(light->spotblend), true);
spot_light.CreateTreatAsPointAttr(pxr::VtValue(true), true);
#if PXR_VERSION >= 2111 #if PXR_VERSION >= 2111
usd_light_api = distant_light.LightAPI(); usd_light_api = spot_light.LightAPI();
#else #else
usd_light_api = distant_light; usd_light_api = spot_light;
#endif
break;
}
case LA_SUN: {
pxr::UsdLuxDistantLight sun_light =
(usd_export_context_.export_params.export_as_overs) ?
pxr::UsdLuxDistantLight(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdLuxDistantLight::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
sun_light.CreateAngleAttr().Set(light->sun_angle, timecode);
if (usd_export_context_.export_params.backward_compatible) {
if (pxr::UsdAttribute attr = sun_light.GetPrim().CreateAttribute(
usdtokens::angle, pxr::SdfValueTypeNames->Float, true)) {
attr.Set(light->sun_angle, timecode);
}
}
#if PXR_VERSION >= 2111
usd_light_api = sun_light.LightAPI();
#else
usd_light_api = sun_light;
#endif #endif
break; break;
} }
@@ -102,21 +233,39 @@ void USDLightWriter::do_write(HierarchyContext &context)
BLI_assert_msg(0, "is_supported() returned true for unsupported light type"); BLI_assert_msg(0, "is_supported() returned true for unsupported light type");
} }
/* Scale factor to get to somewhat-similar illumination. Since the USDViewer had similar float usd_intensity = light->energy * usd_export_context_.export_params.light_intensity_scale;
* over-exposure as Blender Internal with the same values, this code applies the reverse of the
* versioning code in light_emission_unify(). */ if (usd_export_context_.export_params.convert_light_to_nits) {
float usd_intensity; usd_intensity /= nits_to_energy_scale_factor(light, world_scale, radius_scale);
if (light->type == LA_SUN) {
/* Untested, as the Hydra GL viewport of USDViewer doesn't support distant lights. */
usd_intensity = light->energy;
}
else {
usd_intensity = light->energy / 100.0f;
} }
usd_light_api.CreateIntensityAttr().Set(usd_intensity, timecode); usd_light_api.CreateIntensityAttr().Set(usd_intensity, timecode);
usd_light_api.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode); usd_light_api.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
usd_light_api.CreateSpecularAttr().Set(light->spec_fac, timecode); usd_light_api.CreateSpecularAttr().Set(light->spec_fac, timecode);
if (usd_export_context_.export_params.backward_compatible) {
pxr::UsdAttribute attr = usd_light_api.GetPrim().CreateAttribute(
usdtokens::intensity, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(usd_intensity, timecode);
}
attr = usd_light_api.GetPrim().CreateAttribute(
usdtokens::color, pxr::SdfValueTypeNames->Color3f, true);
if (attr) {
attr.Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
}
attr = usd_light_api.GetPrim().CreateAttribute(
usdtokens::specular, pxr::SdfValueTypeNames->Float, true);
if (attr) {
attr.Set(light->spec_fac, timecode);
}
}
if (usd_export_context_.export_params.export_custom_properties && light) {
auto prim = usd_light_api.GetPrim();
write_id_properties(prim, light->id, timecode);
}
} }
} // namespace blender::io::usd } // namespace blender::io::usd

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,33 @@
#include <string> #include <string>
struct Material;
struct USDExportParams;
struct bNode; struct bNode;
struct bNodeTree; struct bNodeTree;
struct Material;
struct USDExportParams;
namespace blender::io::usd { namespace blender::io::usd {
template<typename T>
T usd_define_or_over(pxr::UsdStageRefPtr stage, pxr::SdfPath path, bool as_overs = false)
{
return (as_overs) ? T(stage->OverridePrim(path)) : T::Define(stage, path);
}
struct USDExporterContext; struct USDExporterContext;
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
bNodeTree *ntree,
pxr::UsdShadeMaterial &usd_material,
const USDExportParams &export_params);
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
Material *material,
pxr::UsdShadeMaterial &usd_material,
const USDExportParams &export_params);
void create_mdl_material(const USDExporterContext &usd_export_context,
Material *material,
pxr::UsdShadeMaterial &usd_material);
/** /**
* Entry point to create an approximate USD Preview Surface network from a Cycles node graph. * Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes * Due to the limited nodes in the USD Preview Surface specification, only the following nodes
@@ -40,4 +58,19 @@ void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *material, Material *material,
pxr::UsdShadeMaterial &usd_material); pxr::UsdShadeMaterial &usd_material);
void export_texture(bNode *node,
const pxr::UsdStageRefPtr stage,
const bool allow_overwrite = false);
std::string get_tex_image_asset_path(bNode *node,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
std::string get_tex_image_asset_path(const std::string &asset_path,
const pxr::UsdStageRefPtr stage,
const USDExportParams &export_params);
void export_textures(const Material *material, const pxr::UsdStageRefPtr stage);
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -4,6 +4,7 @@
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/mesh.h> #include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdShade/material.h> #include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h> #include <pxr/usd/usdShade/materialBindingAPI.h>
@@ -13,8 +14,10 @@
#include "BKE_attribute.h" #include "BKE_attribute.h"
#include "BKE_customdata.h" #include "BKE_customdata.h"
#include "BKE_lib_id.h" #include "BKE_lib_id.h"
#include "BKE_library.h"
#include "BKE_material.h" #include "BKE_material.h"
#include "BKE_mesh.h" #include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h" #include "BKE_modifier.h"
#include "BKE_object.h" #include "BKE_object.h"
@@ -25,27 +28,101 @@
#include "DNA_meshdata_types.h" #include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h" #include "DNA_modifier_types.h"
#include "DNA_object_fluidsim_types.h" #include "DNA_object_fluidsim_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h" #include "DNA_particle_types.h"
#include <iostream> #include <iostream>
namespace blender::io::usd { namespace blender::io::usd {
/* TfToken objects are not cheap to construct, so we do it once. */
namespace usdtokens {
static const pxr::TfToken blenderName("userProperties:blenderName", pxr::TfToken::Immortal);
static const pxr::TfToken blenderNameNS("userProperties:blenderName:", pxr::TfToken::Immortal);
static const pxr::TfToken blenderObject("object", pxr::TfToken::Immortal);
static const pxr::TfToken blenderObjectNS("object:", pxr::TfToken::Immortal);
static const pxr::TfToken blenderData("data", pxr::TfToken::Immortal);
static const pxr::TfToken blenderDataNS("data:", pxr::TfToken::Immortal);
} // namespace usdtokens
/* check if the mesh is a subsurf, ignoring disabled modifiers and
* displace if it's after subsurf. */
static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob)
{
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
for (; md; md = md->prev) {
if (BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) {
continue;
}
if (md->type == eModifierType_Subsurf) {
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
if (smd->subdivType == ME_CC_SUBSURF) {
return md;
}
}
/* mesh is not a subsurf. break */
if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) {
return NULL;
}
}
return NULL;
}
USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{ {
} }
bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
{ {
if (usd_export_context_.export_params.visible_objects_only) { // TODO(makowalski) -- Check if we should be calling is_object_visible() below.
return context->is_object_visible(usd_export_context_.export_params.evaluation_mode); // if (usd_export_context_.export_params.visible_objects_only) {
// return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
// }
if (!usd_export_context_.export_params.visible_objects_only) {
// We can skip the visibility test.
return true;
} }
return true;
Object *object = context->object;
bool is_dupli = context->duplicator != nullptr;
int base_flag;
if (is_dupli) {
/* Construct the object's base flags from its dupliparent, just like is done in
* deg_objects_dupli_iterator_next(). Without this, the visiblity check below will fail. Doing
* this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
* copying the Object for every dupli. */
base_flag = object->base_flag;
object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI;
}
int visibility = BKE_object_visibility(object,
usd_export_context_.export_params.evaluation_mode);
if (is_dupli) {
object->base_flag = base_flag;
}
return (visibility & OB_VISIBLE_SELF) != 0;
} }
void USDGenericMeshWriter::do_write(HierarchyContext &context) void USDGenericMeshWriter::do_write(HierarchyContext &context)
{ {
Object *object_eval = context.object; Object *object_eval = context.object;
m_subsurf_mod = get_subsurf_modifier(DEG_get_evaluated_scene(usd_export_context_.depsgraph),
context.object);
if (m_subsurf_mod && !usd_export_context_.export_params.apply_subdiv) {
m_subsurf_mod->mode |= eModifierMode_DisableTemporary;
}
bool needsfree = false; bool needsfree = false;
Mesh *mesh = get_export_mesh(object_eval, needsfree); Mesh *mesh = get_export_mesh(object_eval, needsfree);
@@ -66,6 +143,17 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
} }
throw; throw;
} }
auto prim = usd_export_context_.stage->GetPrimAtPath(usd_export_context_.usd_path);
if (prim.IsValid() && object_eval)
prim.SetActive((object_eval->duplicator_visibility_flag & OB_DUPLI_FLAG_RENDER) != 0);
if (usd_export_context_.export_params.export_custom_properties && mesh)
write_id_properties(prim, mesh->id, get_export_time_code());
if (m_subsurf_mod && !usd_export_context_.export_params.apply_subdiv) {
m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
}
} }
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh) void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
@@ -102,106 +190,335 @@ struct USDMeshData {
pxr::VtFloatArray corner_sharpnesses; pxr::VtFloatArray corner_sharpnesses;
}; };
void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh) void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
{
const CustomData *ldata = &mesh->ldata;
/* Index of the UV layer to be renamed "st", set to the active UV layer index if
* the convert_uv_to_st option is enabled and set to -1 otherwise. */
const int st_layer_idx = usd_export_context_.export_params.convert_uv_to_st ?
CustomData_get_active_layer_index(ldata, CD_MLOOPUV) :
-1;
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
const CustomDataLayer *layer = &ldata->layers[layer_idx];
if (layer->type == CD_MLOOPUV && usd_export_context_.export_params.export_uvmaps) {
const char *name_override = st_layer_idx == layer_idx ? "st" : nullptr;
write_uv_maps(mesh, usd_mesh, layer, name_override);
}
else if (layer->type == CD_PROP_BYTE_COLOR &&
usd_export_context_.export_params.export_vertex_colors) {
write_vertex_colors(mesh, usd_mesh, layer);
}
}
}
void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
const CustomDataLayer *layer,
const char *name_override)
{ {
pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdTimeCode timecode = get_export_time_code();
const CustomData *ldata = &mesh->ldata; /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) { * The primvar name is the same as the UV Map name. This is to allow the standard name "st"
const CustomDataLayer *layer = &ldata->layers[layer_idx]; * for texture coordinates by naming the UV Map as such, without having to guess which UV Map
if (layer->type != CD_MLOOPUV) { * is the "standard" one. */
pxr::TfToken primvar_name(name_override ? name_override :
pxr::TfMakeValidIdentifier(layer->name));
if (usd_export_context_.export_params.author_blender_name) {
// Store original layer name in blender
usd_mesh.GetPrim()
.CreateAttribute(pxr::TfToken(usdtokens::blenderNameNS.GetString() +
usdtokens::blenderDataNS.GetString() +
primvar_name.GetString()),
pxr::SdfValueTypeNames->String,
true)
.Set(std::string(layer->name), pxr::UsdTimeCode::Default());
}
pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
pxr::VtArray<pxr::GfVec2f> uv_coords;
for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
}
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
// `timecode` will be default time in case of non-animation exports. For animated
// exports, USD will inter/extrapolate values linearly.
const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
}
void USDGenericMeshWriter::write_vertex_colors(const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
const CustomDataLayer *layer)
{
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
const float cscale = 1.0f / 255.0f;
if (usd_export_context_.export_params.author_blender_name) {
// Store original layer name in blender
usd_mesh.GetPrim()
.CreateAttribute(pxr::TfToken(usdtokens::blenderNameNS.GetString() +
usdtokens::blenderDataNS.GetString() +
primvar_name.GetString()),
pxr::SdfValueTypeNames->String,
true)
.Set(std::string(layer->name), pxr::UsdTimeCode::Default());
}
pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh);
// TODO: Allow option of vertex varying primvar
pxr::UsdGeomPrimvar vertex_colors_pv = pvApi.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->Color3fArray, pxr::UsdGeomTokens->faceVarying);
MLoopCol *vertCol = static_cast<MLoopCol *>(layer->data);
pxr::VtArray<pxr::GfVec3f> vertex_colors;
for (int loop_idx = 0; loop_idx < mesh->totloop; ++loop_idx) {
pxr::GfVec3f col = pxr::GfVec3f(vertCol[loop_idx].r * cscale,
vertCol[loop_idx].g * cscale,
vertCol[loop_idx].b * cscale);
vertex_colors.push_back(col);
}
vertex_colors_pv.Set(vertex_colors, timecode);
const pxr::UsdAttribute &vertex_colors_attr = vertex_colors_pv.GetAttr();
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(vertex_colors), timecode);
}
void USDGenericMeshWriter::write_vertex_groups(const Object *ob,
const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
bool as_point_groups)
{
if (!ob)
return;
pxr::UsdTimeCode timecode = get_export_time_code();
int i, j;
bDeformGroup *def;
std::vector<pxr::UsdGeomPrimvar> pv_groups;
std::vector<pxr::VtArray<float>> pv_data;
// Create vertex groups primvars
for (def = (bDeformGroup *)ob->defbase.first, i = 0, j = 0; def; def = def->next, i++) {
if (!def)
continue; continue;
} pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(def->name));
pxr::TfToken primvar_interpolation = (as_point_groups) ? pxr::UsdGeomTokens->vertex :
pxr::UsdGeomTokens->faceVarying;
pv_groups.push_back(usd_mesh.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->FloatArray, primvar_interpolation));
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials. size_t primvar_size = 0;
* The primvar name is the same as the UV Map name. This is to allow the standard name "st"
* for texture coordinates by naming the UV Map as such, without having to guess which UV Map
* is the "standard" one. */
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data); if (as_point_groups) {
pxr::VtArray<pxr::GfVec2f> uv_coords; primvar_size = mesh->totvert;
for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
} }
else {
MPoly *mpoly = mesh->mpoly;
for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
primvar_size += mpoly->totloop;
}
}
pv_data.push_back(pxr::VtArray<float>(primvar_size));
}
if (!uv_coords_primvar.HasValue()) { size_t num_groups = pv_groups.size();
uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
if (num_groups == 0)
return;
// Extract vertex groups
if (as_point_groups) {
for (i = 0; i < mesh->totvert; i++) {
// Init to zero
for (j = 0; j < num_groups; j++) {
pv_data[j][i] = 0.0f;
}
MDeformVert *vert = &mesh->dvert[i];
if (vert) {
for (j = 0; j < vert->totweight; j++) {
uint idx = vert->dw[j].def_nr;
float w = vert->dw[j].weight;
/* This out of bounds check is necessary because MDeformVert.totweight can be
larger than the number of bDeformGroup structs in Object.defbase. It appears to be
a Blender bug that can cause this scenario.*/
if (idx < num_groups) {
pv_data[idx][i] = w;
}
}
}
} }
const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr(); }
usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode); else {
MPoly *mpoly = mesh->mpoly;
for (i = 0; i < mesh->totvert; i++) {
// Init to zero
for (j = 0; j < num_groups; j++) {
pv_data[j][i] = 0.0f;
}
}
// const MVert *mvert = mesh->mvert;
int p_idx = 0;
for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
MLoop *mloop = mesh->mloop + mpoly->loopstart;
for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
MDeformVert *vert = &mesh->dvert[mloop->v];
if (vert) {
for (j = 0; j < vert->totweight; j++) {
uint idx = vert->dw[j].def_nr;
float w = vert->dw[j].weight;
/* This out of bounds check is necessary because MDeformVert.totweight can be
larger than the number of bDeformGroup structs in Object.defbase. Appears to be
a Blender bug that can cause this scenario.*/
if (idx < num_groups) {
pv_data[idx][p_idx] = w;
}
}
}
p_idx++;
}
}
}
// Store data in usd
for (i = 0; i < num_groups; i++) {
pv_groups[i].Set(pv_data[i], timecode);
const pxr::UsdAttribute &vertex_colors_attr = pv_groups[i].GetAttr();
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(pv_data[i]), timecode);
}
}
void USDGenericMeshWriter::write_face_maps(const Object *ob,
const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh)
{
if (!ob)
return;
pxr::UsdTimeCode timecode = get_export_time_code();
std::vector<pxr::UsdGeomPrimvar> pv_groups;
std::vector<pxr::VtArray<float>> pv_data;
int i;
size_t mpoly_len = mesh->totpoly;
for (bFaceMap *fmap = (bFaceMap *)ob->fmaps.first; fmap; fmap = fmap->next) {
if (!fmap)
continue;
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(fmap->name));
pxr::TfToken primvar_interpolation = pxr::UsdGeomTokens->uniform;
pv_groups.push_back(usd_mesh.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->FloatArray, primvar_interpolation));
pv_data.push_back(pxr::VtArray<float>(mpoly_len));
// Init data
for (i = 0; i < mpoly_len; i++) {
pv_data[pv_data.size() - 1][i] = 0.0f;
}
}
size_t num_groups = pv_groups.size();
if (num_groups == 0)
return;
const int *facemap_data = (int *)CustomData_get_layer(&mesh->pdata, CD_FACEMAP);
if (facemap_data) {
for (i = 0; i < mpoly_len; i++) {
if (facemap_data[i] >= 0) {
pv_data[facemap_data[i]][i] = 1.0f;
}
}
}
// Store data in usd
for (i = 0; i < num_groups; i++) {
pv_groups[i].Set(pv_data[i], timecode);
const pxr::UsdAttribute &vertex_colors_attr = pv_groups[i].GetAttr();
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(pv_data[i]), timecode);
} }
} }
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh) void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
{ {
pxr::UsdTimeCode timecode = get_export_time_code(); pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
pxr::UsdStageRefPtr stage = usd_export_context_.stage; pxr::UsdStageRefPtr stage = usd_export_context_.stage;
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path); pxr::UsdGeomMesh usd_mesh =
(usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomMesh(usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomMesh::Define(usd_export_context_.stage, usd_export_context_.usd_path);
write_visibility(context, timecode, usd_mesh); write_visibility(context, timecode, usd_mesh);
USDMeshData usd_mesh_data; USDMeshData usd_mesh_data;
get_geometry_data(mesh, usd_mesh_data); get_geometry_data(mesh, usd_mesh_data);
if (usd_export_context_.export_params.use_instancing && context.is_instance()) { if (usd_export_context_.export_params.export_vertices) {
if (!mark_as_instance(context, usd_mesh.GetPrim())) { pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
return; pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
}
/* The material path will be of the form </_materials/{material name}>, which is outside the
* sub-tree pointed to by ref_path. As a result, the referenced data is not allowed to point
* out of its own sub-tree. It does work when we override the material with exactly the same
* path, though. */
if (usd_export_context_.export_params.export_materials) {
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
}
return;
}
pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
true);
pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
true); true);
if (!attr_points.HasValue()) { pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(
/* Provide the initial value as default. This makes USD write the value as constant if they pxr::VtValue(), true);
* don't change over time. */
attr_points.Set(usd_mesh_data.points, defaultTime); // NOTE (Marcelo Sercheli): Code to set values at default time was removed since
attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime); // `timecode` will be default time in case of non-animation exports. For animated
attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime); // exports, USD will inter/extrapolate values linearly.
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
usd_value_writer_.SetAttribute(
attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
usd_value_writer_.SetAttribute(
attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
if (!usd_mesh_data.crease_lengths.empty()) {
pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(),
true);
pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(),
true);
pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(
pxr::VtValue(), true);
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
// `timecode` will be default time in case of non-animation exports. For animated
// exports, USD will inter/extrapolate values linearly.
usd_value_writer_.SetAttribute(
attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
usd_value_writer_.SetAttribute(
attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
usd_value_writer_.SetAttribute(
attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
}
} }
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode); write_custom_data(mesh, usd_mesh);
usd_value_writer_.SetAttribute(
attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
usd_value_writer_.SetAttribute(
attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
if (!usd_mesh_data.crease_lengths.empty()) { if (usd_export_context_.export_params.export_vertex_groups) {
pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true); write_vertex_groups(context.object,
pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true); mesh,
pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(), usd_mesh,
true); !usd_export_context_.export_params.vertex_data_as_face_varying);
write_face_maps(context.object, mesh, usd_mesh);
if (!attr_crease_lengths.HasValue()) {
attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
}
usd_value_writer_.SetAttribute(
attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
usd_value_writer_.SetAttribute(
attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
usd_value_writer_.SetAttribute(
attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
} }
if (!usd_mesh_data.corner_indices.empty() && if (!usd_mesh_data.corner_indices.empty() &&
@@ -211,8 +528,8 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
pxr::VtValue(), true); pxr::VtValue(), true);
if (!attr_corner_indices.HasValue()) { if (!attr_corner_indices.HasValue()) {
attr_corner_indices.Set(usd_mesh_data.corner_indices, defaultTime); attr_corner_indices.Set(usd_mesh_data.corner_indices, timecode);
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, defaultTime); attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, timecode);
} }
usd_value_writer_.SetAttribute( usd_value_writer_.SetAttribute(
@@ -221,9 +538,6 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode); attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
} }
if (usd_export_context_.export_params.export_uvmaps) {
write_uv_maps(mesh, usd_mesh);
}
if (usd_export_context_.export_params.export_normals) { if (usd_export_context_.export_params.export_normals) {
write_normals(mesh, usd_mesh); write_normals(mesh, usd_mesh);
} }
@@ -234,7 +548,10 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
return; return;
} }
usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none); if (usd_export_context_.export_params.export_vertices) {
usd_mesh.CreateSubdivisionSchemeAttr().Set(
(m_subsurf_mod == NULL) ? pxr::UsdGeomTokens->none : pxr::UsdGeomTokens->catmullClark);
}
if (usd_export_context_.export_params.export_materials) { if (usd_export_context_.export_params.export_materials) {
assign_materials(context, usd_mesh, usd_mesh_data.face_groups); assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
@@ -382,8 +699,8 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material); pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
pxr::TfToken material_name = usd_material.GetPath().GetNameToken(); pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset( pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_mesh.GetPrim());
material_name, face_indices); pxr::UsdGeomSubset usd_face_subset = api.CreateMaterialBindSubset(material_name, face_indices);
pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material); pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
} }
} }
@@ -427,9 +744,10 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
} }
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true); pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
if (!attr_normals.HasValue()) {
attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default()); // NOTE (Marcelo Sercheli): Code to set values at default time was removed since
} // `timecode` will be default time in case of non-animation exports. For animated
// exports, USD will inter/extrapolate values linearly.
usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode); usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying); usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
} }
@@ -465,7 +783,10 @@ USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWrit
Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/) Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
{ {
return BKE_object_get_evaluated_mesh(object_eval); Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
// Assumed safe because the original depsgraph was nonconst in usd_capi...
Depsgraph *dg = const_cast<Depsgraph *>(usd_export_context_.depsgraph);
return mesh_get_eval_final(dg, scene, object_eval, &CD_MASK_MESH);
} }
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -6,6 +6,8 @@
#include <pxr/usd/usdGeom/mesh.h> #include <pxr/usd/usdGeom/mesh.h>
struct ModifierData;
namespace blender::io::usd { namespace blender::io::usd {
struct USDMeshData; struct USDMeshData;
@@ -31,9 +33,23 @@ class USDGenericMeshWriter : public USDAbstractWriter {
void assign_materials(const HierarchyContext &context, void assign_materials(const HierarchyContext &context,
pxr::UsdGeomMesh usd_mesh, pxr::UsdGeomMesh usd_mesh,
const MaterialFaceGroups &usd_face_groups); const MaterialFaceGroups &usd_face_groups);
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); void write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh,
const CustomDataLayer *layer,
const char *name_override = nullptr);
void write_vertex_colors(const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
const CustomDataLayer *layer);
void write_vertex_groups(const Object *ob,
const Mesh *mesh,
pxr::UsdGeomMesh usd_mesh,
bool as_point_groups);
void write_face_maps(const Object *ob, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh); void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
protected:
ModifierData *m_subsurf_mod;
}; };
class USDMeshWriter : public USDGenericMeshWriter { class USDMeshWriter : public USDGenericMeshWriter {

View File

@@ -0,0 +1,152 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2019 Blender Foundation.
* All rights reserved.
*/
#include "usd_writer_particle.h"
#include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/pointInstancer.h>
#include <pxr/usd/usdGeom/sphere.h>
extern "C" {
#include "BLI_assert.h"
#include "BLI_math.h"
#include "BLI_utildefines.h"
#include "DNA_object_force_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include "BKE_particle.h"
#include "WM_api.h"
#include "WM_types.h"
}
namespace blender::io::usd {
USDParticleWriter::USDParticleWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{
}
bool USDParticleWriter::is_supported(const HierarchyContext *context) const
{
return true;
}
void USDParticleWriter::do_write(HierarchyContext &context)
{
ParticleSystem *psys = context.particle_system;
ParticleSettings *psettings = psys->part;
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdGeomPointInstancer usd_pi =
(usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomPointInstancer(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomPointInstancer::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
// Prototypes
Object *instance_object = psettings->instance_object;
if (instance_object) {
std::string full_path = "/" +
pxr::TfMakeValidIdentifier(std::string(instance_object->id.name + 2));
Object *obj_parent = instance_object->parent;
for (; obj_parent != nullptr; obj_parent = obj_parent->parent) {
if (obj_parent != nullptr) {
full_path = "/" + pxr::TfMakeValidIdentifier(std::string(obj_parent->id.name + 2)) +
full_path;
}
}
pxr::SdfPath inst_path = pxr::SdfPath(
std::string(usd_export_context_.export_params.root_prim_path) + full_path);
pxr::UsdRelationship prototypes = usd_pi.CreatePrototypesRel();
prototypes.AddTarget(inst_path);
}
pxr::UsdAttribute protoIndicesAttr = usd_pi.CreateProtoIndicesAttr();
pxr::VtIntArray indices;
// Attributes
pxr::UsdAttribute positionAttr = usd_pi.CreatePositionsAttr();
pxr::UsdAttribute scaleAttr = usd_pi.CreateScalesAttr();
pxr::UsdAttribute orientationAttr = usd_pi.CreateOrientationsAttr();
pxr::UsdAttribute velAttr = usd_pi.CreateVelocitiesAttr();
pxr::UsdAttribute angVelAttr = usd_pi.CreateAngularVelocitiesAttr();
pxr::UsdAttribute invisibleIdsAttr = usd_pi.CreateInvisibleIdsAttr();
pxr::VtArray<pxr::GfVec3f> points;
pxr::VtArray<pxr::GfVec3f> scales;
pxr::VtArray<pxr::GfVec3f> velocities;
pxr::VtArray<pxr::GfVec3f> angularVelocities;
pxr::VtArray<pxr::GfQuath> orientations;
pxr::VtInt64Array invisibleIndices;
ParticleData *pa;
int p;
for (p = 0, pa = psys->particles; p < psys->totpart; p++, pa++) {
indices.push_back(0);
points.push_back(pxr::GfVec3f(pa->state.co[0], pa->state.co[1], pa->state.co[2]));
// Apply blender rand...
float size = psys->part->size;
size *= 1.0f - psys->part->randsize * psys_frand(psys, p + 1);
scales.push_back(pxr::GfVec3f(size, size, size));
orientations.push_back(
pxr::GfQuath(pa->state.rot[0], pa->state.rot[1], pa->state.rot[2], pa->state.rot[3]));
velocities.push_back(pxr::GfVec3f(pa->state.vel[0], pa->state.vel[1], pa->state.vel[2]));
angularVelocities.push_back(
pxr::GfVec3f(pa->state.ave[0], pa->state.ave[1], pa->state.ave[2]));
if (pa->alive == PARS_DEAD || pa->alive == PARS_UNBORN) {
invisibleIndices.push_back(
p); // TODO, seems to be a USD point instance problem with freezing particles
}
}
// TODO: add simple particle child export
/*if(usd_export_context_.export_params.export_child_particles) {
}*/
protoIndicesAttr.Set(indices, timecode);
positionAttr.Set(points, timecode);
scaleAttr.Set(scales, timecode);
velAttr.Set(velocities, timecode);
angVelAttr.Set(angularVelocities, timecode);
orientationAttr.Set(orientations, timecode);
invisibleIdsAttr.Set(invisibleIndices, timecode);
if (usd_export_context_.export_params.export_custom_properties && context.particle_system) {
auto prim = usd_pi.GetPrim();
write_id_properties(prim, context.particle_system->part->id, timecode);
}
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,37 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2019 Blender Foundation.
* All rights reserved.
*/
#ifndef __USD_WRITER_PARTICLE_H__
#define __USD_WRITER_PARTICLE_H__
#include "usd_writer_abstract.h"
namespace blender::io::usd {
class USDParticleWriter : public USDAbstractWriter {
public:
USDParticleWriter(const USDExporterContext &ctx);
protected:
virtual bool is_supported(const HierarchyContext *context) const override;
virtual void do_write(HierarchyContext &context) override;
};
} // namespace blender::io::usd
#endif /* __USD_WRITER_PARTICLE_H__ */

View File

@@ -0,0 +1,178 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_writer_skel_root.h"
#include "WM_api.h"
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/root.h>
#include <iostream>
namespace blender::io::usd {
bool USDSkelRootWriter::is_under_skel_root() const
{
pxr::SdfPath parent_path(usd_export_context_.usd_path);
parent_path = parent_path.GetParentPath();
if (parent_path.IsEmpty()) {
return false;
}
pxr::UsdPrim prim = usd_export_context_.stage->GetPrimAtPath(parent_path);
if (!prim.IsValid()) {
return false;
}
pxr::UsdSkelRoot root = pxr::UsdSkelRoot::Find(prim);
return static_cast<bool>(root);
}
pxr::UsdGeomXformable USDSkelRootWriter::create_xformable() const
{
/* Create a UsdSkelRoot primitive, unless this prim is already
beneath a UsdSkelRoot, in which case create an Xform. */
pxr::UsdGeomXformable root;
if (is_under_skel_root()) {
root = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdGeomXform(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdGeomXform::Define(usd_export_context_.stage, usd_export_context_.usd_path);
}
else {
root = (usd_export_context_.export_params.export_as_overs) ?
pxr::UsdSkelRoot(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
pxr::UsdSkelRoot::Define(usd_export_context_.stage, usd_export_context_.usd_path);
}
return root;
}
static pxr::UsdGeomXform get_xform_ancestor(const pxr::UsdPrim &prim1,
const pxr::UsdPrim &prim2)
{
if (!prim1 || !prim2) {
return pxr::UsdGeomXform();
}
pxr::SdfPath prefix = prim1.GetPath().GetCommonPrefix(prim2.GetPath());
if (prefix.IsEmpty()) {
return pxr::UsdGeomXform();
}
pxr::UsdPrim ancestor = prim1.GetStage()->GetPrimAtPath(prefix);
if (!ancestor.IsA<pxr::UsdGeomXform>()) {
ancestor = ancestor.GetParent();
}
if (ancestor.IsA<pxr::UsdGeomXform>()) {
return pxr::UsdGeomXform(ancestor);
}
return pxr::UsdGeomXform();
}
void validate_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams &params)
{
if (!params.export_armatures || !stage) {
return;
}
bool created_skel_root = false;
pxr::UsdPrimRange it = stage->Traverse();
for (pxr::UsdPrim prim : it) {
if (prim.HasAPI<pxr::UsdSkelBindingAPI>() && !prim.IsA<pxr::UsdSkelSkeleton>()) {
pxr::UsdSkelBindingAPI skel_bind_api(prim);
if (skel_bind_api) {
pxr::UsdSkelSkeleton skel;
if (skel_bind_api.GetSkeleton(&skel)) {
if (!skel.GetPrim().IsValid()) {
std::cout << "WARNING in validate_skel_roots(): invalid skeleton for prim " << prim.GetPath() << std::endl;
continue;
}
pxr::UsdSkelRoot prim_root = pxr::UsdSkelRoot::Find(prim);
pxr::UsdSkelRoot arm_root = pxr::UsdSkelRoot::Find(skel.GetPrim());
bool common_root = false;
if (prim_root && arm_root && prim_root.GetPath() == arm_root.GetPath()) {
common_root = true;
}
if (!common_root) {
WM_reportf(RPT_WARNING, "USD Export: skinned prim %s and skeleton %s do not share a common SkelRoot and may not bind correctly. See the documentation for possible solutions.\n",
prim.GetPath().GetAsString().c_str(), skel.GetPrim().GetPath().GetAsString().c_str());
std::cout << "WARNING: skinned prim " << prim.GetPath() << " and skeleton " << skel.GetPrim().GetPath()
<< " do not share a common SkelRoot and may not bind correctly. See the documentation for possible solutions." << std::endl;
if (params.fix_skel_root) {
std::cout << "Attempting to fix the Skel Root hierarchy." << std::endl;
WM_reportf(RPT_WARNING, "Attempting to fix the Skel Root hierarchy. See the console for information");
if (pxr::UsdGeomXform xf = get_xform_ancestor(prim, skel.GetPrim())) {
/* Enable skeletal processing by setting the type to UsdSkelRoot. */
std::cout << "Converting Xform prim " << xf.GetPath() << " to a SkelRoot" << std::endl;
pxr::UsdSkelRoot::Define(stage, xf.GetPath());
created_skel_root = true;
}
else {
std::cout << "Couldn't find a commone Xform ancestor for skinned prim " << prim.GetPath()
<< " and skeleton " << skel.GetPrim().GetPath() << " to convert to a USDSkelRoot\n";
std::cout << "You might wish to group these objects under an Empty in the Blender scene.\n";
}
}
}
}
}
}
}
if (!created_skel_root) {
return;
}
it = stage->Traverse();
for (pxr::UsdPrim prim : it) {
if (prim.IsA<pxr::UsdSkelRoot>()) {
if (pxr::UsdSkelRoot root = pxr::UsdSkelRoot::Find(prim.GetParent())) {
/* This is a nested SkelRoot, so convert it to an Xform. */
std::cout << "Converting nested SkelRoot " << prim.GetPath() << " to an Xform." << std::endl;
pxr::UsdGeomXform::Define(stage, prim.GetPath());
}
}
}
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,45 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include "usd_writer_transform.h"
#include <pxr/usd/usdGeom/xformable.h>
namespace blender::io::usd {
void validate_skel_roots(pxr::UsdStageRefPtr stage, const USDExportParams &params);
class USDSkelRootWriter : public USDTransformWriter {
public:
USDSkelRootWriter(const USDExporterContext &ctx) : USDTransformWriter(ctx)
{
}
protected:
/* Override to create UsdSkelRoot prim. */
pxr::UsdGeomXformable create_xformable() const override;
/* Rturns true if the prim to be created is
* already unde a USD SkeRoot. */
bool is_under_skel_root() const;
};
} // namespace blender::io::usd

View File

@@ -0,0 +1,313 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_writer_skinned_mesh.h"
#include "usd_hierarchy_iterator.h"
#include "usd_writer_armature.h"
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/matrix4f.h>
#include <pxr/usd/usdGeom/mesh.h>
#include "BKE_armature.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "DNA_armature_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_meta_types.h"
#include "ED_armature.h"
#include <string>
namespace blender::io::usd {
bool is_skinned_mesh(Object *obj)
{
if (!(obj && obj->data)) {
return false;
}
if (obj->type != OB_MESH) {
return false;
}
return BKE_modifiers_findby_type(obj, eModifierType_Armature) != nullptr;
}
static Object *get_armature_obj(Object *obj)
{
if (!(obj && obj->data)) {
return nullptr;
}
if (obj->type != OB_MESH) {
return nullptr;
}
ArmatureModifierData *mod = reinterpret_cast<ArmatureModifierData *>(
BKE_modifiers_findby_type(obj, eModifierType_Armature));
return mod ? mod->object : nullptr;
}
USDSkinnedMeshWriter::USDSkinnedMeshWriter(const USDExporterContext &ctx) : USDMeshWriter(ctx)
{
}
void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
{
Object *arm_obj = get_armature_obj(context.object);
if (!arm_obj) {
printf("WARNING: couldn't get armature object for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
if (!arm_obj->data) {
printf("WARNING: couldn't get armature object data for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
/* Before writing the mesh, we set the artmature to edit mode
* so the mesh is saved in its rest position. */
bArmature *arm = static_cast<bArmature *>(arm_obj->data);
bool is_edited = arm->edbo != nullptr;
if (!is_edited) {
ED_armature_to_edit(arm);
}
USDGenericMeshWriter::do_write(context);
if (!is_edited) {
ED_armature_edit_free(arm);
}
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
if (!mesh_prim.IsValid()) {
printf("WARNING: couldn't get valid mesh prim for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
if (!usd_skel_api) {
printf("WARNING: couldn't apply UsdSkelBindingAPI to skinned mesh prim %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
ID *arm_id = reinterpret_cast<ID *>(arm_obj->data);
std::string skel_path = usd_export_context_.hierarchy_iterator->get_object_export_path(arm_id);
if (skel_path.empty()) {
printf("WARNING: couldn't get USD skeleton path for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
skel_path = std::string(usd_export_context_.export_params.root_prim_path) + skel_path;
}
usd_skel_api.CreateSkeletonRel().SetTargets(pxr::SdfPathVector({pxr::SdfPath(skel_path)}));
if (pxr::UsdAttribute geom_bind_attr = usd_skel_api.CreateGeomBindTransformAttr()) {
pxr::GfMatrix4f mat_world(context.matrix_world);
geom_bind_attr.Set(pxr::GfMatrix4d(mat_world));
}
else {
printf("WARNING: couldn't create geom bind transform attribute for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
}
std::vector<std::string> bone_names;
USDArmatureWriter::get_armature_bone_names(arm_obj, bone_names);
if (bone_names.empty()) {
printf("WARNING: no armature bones for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
bool needs_free = false;
Mesh *mesh = get_export_mesh(context.object, needs_free);
if (mesh == nullptr) {
printf("WARNING: couldn't get Blender mesh for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
write_weights(context.object, mesh, usd_skel_api, bone_names);
if (needs_free) {
free_export_mesh(mesh);
}
}
void USDSkinnedMeshWriter::write_weights(const Object *ob,
const Mesh *mesh,
const pxr::UsdSkelBindingAPI &skel_api,
const std::vector<std::string> &bone_names) const
{
if (!(skel_api && ob && mesh && mesh->dvert && mesh->totvert > 0)) {
return;
}
if (bone_names.empty()) {
return;
}
std::vector<int> group_to_bone_idx;
for (const bDeformGroup *def = (const bDeformGroup *)mesh->vertex_group_names.first; def;
def = def->next) {
int bone_idx = -1;
/* For now, n-squared search is acceptable. */
for (int i = 0; i < bone_names.size(); ++i) {
if (bone_names[i] == def->name) {
bone_idx = i;
break;
}
}
if (bone_idx == -1) {
printf("WARNING: deform group %s in skinned mesh %s doesn't match any bones\n",
def->name,
this->usd_export_context_.usd_path.GetString().c_str());
}
group_to_bone_idx.push_back(bone_idx);
}
if (group_to_bone_idx.empty()) {
return;
}
int max_totweight = 1;
for (int i = 0; i < mesh->totvert; ++i) {
MDeformVert &vert = mesh->dvert[i];
if (vert.totweight > max_totweight) {
max_totweight = vert.totweight;
}
}
const int ELEM_SIZE = max_totweight;
int num_points = mesh->totvert;
pxr::VtArray<int> joint_indices(num_points * ELEM_SIZE, 0);
pxr::VtArray<float> joint_weights(num_points * ELEM_SIZE, 0.0f);
/* Current offset into the indices and weights arrays. */
int offset = 0;
/* Record number of out of bounds vert group indices, for error reporting. */
int num_out_of_bounds = 0;
for (int i = 0; i < mesh->totvert; ++i) {
MDeformVert &vert = mesh->dvert[i];
/* Sum of the weights, for normalizing. */
float sum_weights = 0.0f;
for (int j = 0; j < ELEM_SIZE; ++j, ++offset) {
if (offset >= joint_indices.size()) {
printf("Programmer error: out of bounds joint indices array offset.\n");
return;
}
if (j >= vert.totweight) {
continue;
}
int def_nr = static_cast<int>(vert.dw[j].def_nr);
/* This out of bounds check is necessary because MDeformVert.totweight can be
* larger than the number of bDeformGroup structs in Object.defbase. It appears to be
* a Blender bug that can cause this scenario. */
if (def_nr >= group_to_bone_idx.size()) {
++num_out_of_bounds;
continue;
}
int bone_idx = group_to_bone_idx[def_nr];
if (bone_idx == -1) {
continue;
}
joint_indices[offset] = bone_idx;
float w = vert.dw[j].weight;
joint_weights[offset] = w;
sum_weights += w;
}
if (sum_weights > .000001f) {
/* Run over the elements again to normalize the weights. */
float inv_sum_weights = 1.0f / sum_weights;
offset -= ELEM_SIZE;
for (int k = 0; k < ELEM_SIZE; ++k, ++offset) {
joint_weights[offset] *= inv_sum_weights;
}
}
}
if (num_out_of_bounds > 0) {
printf("WARNING: There were %d deform verts with out of bounds deform group numbers.\n",
num_out_of_bounds);
}
skel_api.CreateJointIndicesPrimvar(false, ELEM_SIZE).GetAttr().Set(joint_indices);
skel_api.CreateJointWeightsPrimvar(false, ELEM_SIZE).GetAttr().Set(joint_weights);
}
bool USDSkinnedMeshWriter::is_supported(const HierarchyContext *context) const
{
return is_skinned_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
}
bool USDSkinnedMeshWriter::check_is_animated(const HierarchyContext & /*context*/) const
{
/* We assume that skinned meshes are never animated, as the source of
* any animation is the mesh's bound skeleton. */
return false;
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,48 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include "usd_writer_mesh.h"
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <string>
#include <vector>
namespace blender::io::usd {
bool is_skinned_mesh(Object *obj);
class USDSkinnedMeshWriter : public USDMeshWriter {
public:
USDSkinnedMeshWriter(const USDExporterContext &ctx);
virtual void do_write(HierarchyContext &context) override;
protected:
virtual bool is_supported(const HierarchyContext *context) const override;
virtual bool check_is_animated(const HierarchyContext &context) const override;
void write_weights(const Object *ob,
const Mesh *mesh,
const pxr::UsdSkelBindingAPI &skel_api,
const std::vector<std::string> &bone_names) const;
};
} // namespace blender::io::usd

View File

@@ -4,32 +4,160 @@
#include "usd_hierarchy_iterator.h" #include "usd_hierarchy_iterator.h"
#include <pxr/base/gf/matrix4f.h> #include <pxr/base/gf/matrix4f.h>
#include <pxr/base/gf/quaternion.h>
#include <pxr/usd/usdGeom/xform.h> #include <pxr/usd/usdGeom/xform.h>
#include "BKE_object.h" #include "BKE_object.h"
#include "BLI_math_matrix.h" #include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "DNA_layer_types.h" #include "DNA_layer_types.h"
namespace blender::io::usd { namespace blender::io::usd {
static const float UNIT_M4[4][4] = {
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
};
USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx) USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
{ {
} }
pxr::UsdGeomXformable USDTransformWriter::create_xformable() const
{
pxr::UsdGeomXform xform;
if (usd_export_context_.export_params.export_as_overs) {
// Override existing prim on stage
xform = pxr::UsdGeomXform(
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path));
}
else {
// If prim exists, cast to UsdGeomXform (Solves merge transform and shape issue for animated
// exports)
pxr::UsdPrim existing_prim = usd_export_context_.stage->GetPrimAtPath(
usd_export_context_.usd_path);
if (existing_prim.IsValid() && existing_prim.IsA<pxr::UsdGeomXform>()) {
xform = pxr::UsdGeomXform(existing_prim);
}
else {
xform = pxr::UsdGeomXform::Define(usd_export_context_.stage, usd_export_context_.usd_path);
}
}
return xform;
}
bool USDTransformWriter::should_apply_root_xform(const HierarchyContext &context) const
{
if (!(usd_export_context_.export_params.convert_orientation ||
usd_export_context_.export_params.convert_to_cm)) {
return false;
}
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
return false;
}
if (context.export_parent != nullptr) {
return false;
}
if (usd_export_context_.export_params.use_instancing &&
usd_export_context_.hierarchy_iterator->is_prototype(context.object)) {
/* This is an instancing prototype. */
return false;
}
return true;
}
bool USDTransformWriter::is_proto_root(const HierarchyContext &context) const
{
if (!usd_export_context_.export_params.use_instancing) {
return false;
}
bool is_proto = usd_export_context_.hierarchy_iterator->is_prototype(context.object);
bool parent_is_proto = context.export_parent &&
usd_export_context_.hierarchy_iterator->is_prototype(
context.export_parent);
return is_proto && !parent_is_proto;
}
void USDTransformWriter::do_write(HierarchyContext &context) void USDTransformWriter::do_write(HierarchyContext &context)
{ {
float parent_relative_matrix[4][4]; /* The object matrix relative to the parent. */ pxr::UsdGeomXformable xform = create_xformable();
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
/* Write the transform relative to the parent. */ if (!xform) {
pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage, printf("INTERNAL ERROR: USDTransformWriter: couldn't create xformable.\n");
usd_export_context_.usd_path); return;
if (!xformOp_) { }
xformOp_ = xform.AddTransformOp();
if (usd_export_context_.export_params.export_transforms) {
float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
// TODO(bjs): This is inefficient checking for every transform. should be moved elsewhere
if (should_apply_root_xform(context)) {
float matrix_world[4][4];
copy_m4_m4(matrix_world, context.matrix_world);
if (usd_export_context_.export_params.convert_orientation) {
float mrot[3][3];
float mat[4][4];
mat3_from_axis_conversion(USD_GLOBAL_FORWARD_Y,
USD_GLOBAL_UP_Z,
usd_export_context_.export_params.forward_axis,
usd_export_context_.export_params.up_axis,
mrot);
transpose_m3(mrot);
copy_m4_m3(mat, mrot);
mul_m4_m4m4(matrix_world, mat, context.matrix_world);
}
if (usd_export_context_.export_params.convert_to_cm) {
float scale_mat[4][4];
scale_m4_fl(scale_mat, 100.0f);
mul_m4_m4m4(matrix_world, scale_mat, matrix_world);
}
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, matrix_world);
}
else
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
// USD Xforms are by default set with an identity transform.
// This check ensures transforms of non-identity are authored
// preventing usd composition collisions up and down stream.
if (usd_export_context_.export_params.export_identity_transforms ||
!compare_m4m4(parent_relative_matrix, UNIT_M4, 0.000000001f)) {
set_xform_ops(parent_relative_matrix, xform);
}
}
if (usd_export_context_.export_params.export_custom_properties && context.object) {
auto prim = xform.GetPrim();
write_id_properties(prim, context.object->id, get_export_time_code());
}
if (usd_export_context_.export_params.use_instancing) {
if (context.is_instance()) {
mark_as_instance(context, xform.GetPrim());
/* Explicitly set visibility, since the prototype might be invisible. */
xform.GetVisibilityAttr().Set(pxr::UsdGeomTokens->inherited);
}
else {
if (is_proto_root(context)) {
/* TODO(makowalski): perhaps making prototypes invisible should be optional. */
xform.GetVisibilityAttr().Set(pxr::UsdGeomTokens->invisible);
}
}
} }
xformOp_.Set(pxr::GfMatrix4d(parent_relative_matrix), get_export_time_code());
} }
bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
@@ -40,10 +168,79 @@ bool USDTransformWriter::check_is_animated(const HierarchyContext &context) cons
* depsgraph whether this object instance has a time source. */ * depsgraph whether this object instance has a time source. */
return true; return true;
} }
if (check_has_physics(context)) { if (check_has_physics(context)) {
return true; return true;
} }
// TODO: This fails for a specific set of drivers and rig setups...
// Setting 'context.animation_check_include_parent' to true fixed it...
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent); return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
} }
void USDTransformWriter::set_xform_ops(float xf_matrix[4][4], pxr::UsdGeomXformable &xf)
{
if (!xf) {
return;
}
eUSDXformOpMode xfOpMode = usd_export_context_.export_params.xform_op_mode;
if (xformOps_.empty()) {
switch (xfOpMode) {
case USD_XFORM_OP_SRT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddRotateXYZOp());
xformOps_.push_back(xf.AddScaleOp());
break;
case USD_XFORM_OP_SOT:
xformOps_.push_back(xf.AddTranslateOp());
xformOps_.push_back(xf.AddOrientOp());
xformOps_.push_back(xf.AddScaleOp());
break;
case USD_XFORM_OP_MAT:
xformOps_.push_back(xf.AddTransformOp());
break;
default:
printf("Warning: unknown XformOp type\n");
xformOps_.push_back(xf.AddTransformOp());
break;
}
}
if (xformOps_.empty()) {
/* Shouldn't happen. */
return;
}
if (xformOps_.size() == 1) {
xformOps_[0].Set(pxr::GfMatrix4d(xf_matrix), get_export_time_code());
}
else if (xformOps_.size() == 3) {
float loc[3];
float quat[4];
float scale[3];
mat4_decompose(loc, quat, scale, xf_matrix);
if (xfOpMode == USD_XFORM_OP_SRT) {
float rot[3];
quat_to_eul(rot, quat);
rot[0] *= 180.0 / M_PI;
rot[1] *= 180.0 / M_PI;
rot[2] *= 180.0 / M_PI;
xformOps_[0].Set(pxr::GfVec3d(loc), get_export_time_code());
xformOps_[1].Set(pxr::GfVec3f(rot), get_export_time_code());
xformOps_[2].Set(pxr::GfVec3f(scale), get_export_time_code());
}
else if (xfOpMode == USD_XFORM_OP_SOT) {
xformOps_[0].Set(pxr::GfVec3d(loc), get_export_time_code());
xformOps_[1].Set(pxr::GfQuatf(quat[0], quat[1], quat[2], quat[3]), get_export_time_code());
xformOps_[2].Set(pxr::GfVec3f(scale), get_export_time_code());
}
}
}
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -6,11 +6,13 @@
#include <pxr/usd/usdGeom/xform.h> #include <pxr/usd/usdGeom/xform.h>
#include <vector>
namespace blender::io::usd { namespace blender::io::usd {
class USDTransformWriter : public USDAbstractWriter { class USDTransformWriter : public USDAbstractWriter {
private: private:
pxr::UsdGeomXformOp xformOp_; std::vector<pxr::UsdGeomXformOp> xformOps_;
public: public:
USDTransformWriter(const USDExporterContext &ctx); USDTransformWriter(const USDExporterContext &ctx);
@@ -18,6 +20,16 @@ class USDTransformWriter : public USDAbstractWriter {
protected: protected:
void do_write(HierarchyContext &context) override; void do_write(HierarchyContext &context) override;
bool check_is_animated(const HierarchyContext &context) const override; bool check_is_animated(const HierarchyContext &context) const override;
void set_xform_ops(float parent_relative_matrix[4][4], pxr::UsdGeomXformable &xf);
/* Return true if the given context is the root of a protoype. */
bool is_proto_root(const HierarchyContext &context) const;
/* Subclasses may override this to create prims other than UsdGeomXform. */
virtual pxr::UsdGeomXformable create_xformable() const;
bool should_apply_root_xform(const HierarchyContext &context) const;
}; };
} // namespace blender::io::usd } // namespace blender::io::usd

View File

@@ -9,26 +9,115 @@
extern "C" { extern "C" {
#endif #endif
struct CacheArchiveHandle;
struct CacheFile;
struct CacheReader;
struct Object;
struct bContext; struct bContext;
struct Object;
struct CacheArchiveHandle;
struct CacheReader;
struct CacheFile;
typedef enum USD_global_forward_axis {
USD_GLOBAL_FORWARD_X = 0,
USD_GLOBAL_FORWARD_Y = 1,
USD_GLOBAL_FORWARD_Z = 2,
USD_GLOBAL_FORWARD_MINUS_X = 3,
USD_GLOBAL_FORWARD_MINUS_Y = 4,
USD_GLOBAL_FORWARD_MINUS_Z = 5
} USD_global_forward_axis;
typedef enum USD_global_up_axis {
USD_GLOBAL_UP_X = 0,
USD_GLOBAL_UP_Y = 1,
USD_GLOBAL_UP_Z = 2,
USD_GLOBAL_UP_MINUS_X = 3,
USD_GLOBAL_UP_MINUS_Y = 4,
USD_GLOBAL_UP_MINUS_Z = 5
} USD_global_up_axis;
typedef enum eUSDImportShadersMode {
USD_IMPORT_SHADERS_NONE = 0,
USD_IMPORT_USD_PREVIEW_SURFACE = 1,
USD_IMPORT_MDL = 2,
} eUSDImportShadersMode;
typedef enum eUSDXformOpMode {
USD_XFORM_OP_SRT = 0,
USD_XFORM_OP_SOT = 1,
USD_XFORM_OP_MAT = 2,
} eUSDXformOpMode;
static const USD_global_forward_axis USD_DEFAULT_FORWARD = USD_GLOBAL_FORWARD_MINUS_Z;
static const USD_global_up_axis USD_DEFAULT_UP = USD_GLOBAL_UP_Y;
typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_MODIFY = 0,
USD_MTL_NAME_COLLISION_SKIP = 1,
} eUSDMtlNameCollisionMode;
typedef enum eUSDAttrImportMode {
USD_ATTR_IMPORT_NONE = 0,
USD_ATTR_IMPORT_USER = 1,
USD_ATTR_IMPORT_ALL = 2,
} eUSDAttrImportMode;
struct USDExportParams { struct USDExportParams {
double frame_start;
double frame_end;
bool export_animation; bool export_animation;
bool export_hair; bool export_hair;
bool export_vertices;
bool export_vertex_colors;
bool export_vertex_groups;
bool export_face_maps;
bool export_uvmaps; bool export_uvmaps;
bool export_normals; bool export_normals;
bool export_transforms;
bool export_materials; bool export_materials;
bool export_meshes;
bool export_lights;
bool export_cameras;
bool export_curves;
bool export_particles;
bool selected_objects_only; bool selected_objects_only;
bool visible_objects_only; bool visible_objects_only;
bool use_instancing; bool use_instancing;
enum eEvaluationMode evaluation_mode; enum eEvaluationMode evaluation_mode;
char *default_prim_path; // USD Stage Default Primitive Path
char *root_prim_path; // Root path to encapsulate blender stage under. e.g. /shot
char *material_prim_path; // Prim path to store all generated USDShade, shaders under e.g.
// /materials
bool generate_preview_surface; bool generate_preview_surface;
bool convert_uv_to_st;
bool convert_orientation;
enum USD_global_forward_axis forward_axis;
enum USD_global_up_axis up_axis;
bool export_child_particles;
bool export_as_overs;
bool merge_transform_and_shape;
bool export_custom_properties;
bool add_properties_namespace;
bool export_identity_transforms;
bool apply_subdiv;
bool author_blender_name;
bool vertex_data_as_face_varying;
float frame_step;
bool override_shutter;
double shutter_open;
double shutter_close;
bool export_textures; bool export_textures;
bool overwrite_textures;
bool relative_texture_paths; bool relative_texture_paths;
bool backward_compatible;
float light_intensity_scale;
bool generate_mdl;
bool convert_to_cm;
bool convert_light_to_nits;
bool scale_light_radius;
bool convert_world_material;
bool generate_cycles_shaders;
bool export_armatures;
eUSDXformOpMode xform_op_mode;
bool fix_skel_root;
bool overwrite_textures;
}; };
struct USDImportParams { struct USDImportParams {
@@ -54,9 +143,15 @@ struct USDImportParams {
bool import_render; bool import_render;
bool import_visible_only; bool import_visible_only;
bool use_instancing; bool use_instancing;
bool import_usd_preview; eUSDImportShadersMode import_shaders_mode;
bool set_material_blend; bool set_material_blend;
float light_intensity_scale; float light_intensity_scale;
bool apply_unit_conversion_scale;
bool convert_light_from_nits;
bool scale_light_radius;
bool create_background_shader;
eUSDMtlNameCollisionMode mtl_name_collision_mode;
eUSDAttrImportMode attr_import_mode;
}; };
/* The USD_export takes a as_background_job parameter, and returns a boolean. /* The USD_export takes a as_background_job parameter, and returns a boolean.
@@ -80,6 +175,8 @@ bool USD_import(struct bContext *C,
int USD_get_version(void); int USD_get_version(void);
bool USD_umm_module_loaded(void);
/* USD Import and Mesh Cache interface. */ /* USD Import and Mesh Cache interface. */
struct CacheArchiveHandle *USD_create_handle(struct Main *bmain, struct CacheArchiveHandle *USD_create_handle(struct Main *bmain,