Compare commits

...

110 Commits

Author SHA1 Message Date
82ffff9a91 USD: support importing dome light textures. 2023-03-17 17:49:26 -04:00
5ea31db2ce USD import fix: set active mesh color.
Fixed a bug where the active color wasn't being
set on imported meshes, resulting in no colors
displaying in the viewport.
2023-03-15 23:33:06 -04:00
c1cd7c6b4c USD import: fix missing packed textures for UMM.
The material importer now always attempts to pack textures
of any texture image node that were created, even if the UMM
conversion was only partially complete, since it is useful
to render a parially-constructed material.  For example,
if the material has a valid color texture but the
normal texture is missing, we still want to load the color
texture in the viewport, regardless of any UMM error due
to the missing normal texture.
2023-03-13 13:00:08 -04:00
4755927229 USD IO: fix camera property scaling round trip.
Now setting camera focal length and aperature
in tenths of scene units on export, to match recently
added behavior scaling these camera properties on
import.  Also, no longer setting film offset properties
to tenths of scene units on import, as this was causing
incorrect offsets.
2023-03-12 15:05:17 -04:00
22b0019e94 USD IO: Fixed error in Sun light angle export. 2023-03-11 13:44:08 -05:00
11b33f3a3d USD import: fix error importing texture for UMM.
When importing textures for UMM, now making sure
the source asset path isn't empty, to avoid an
error when attempting to copy the texture.
2023-03-10 20:54:08 -05:00
ac02bc5390 USD: updated import textures mode option description. 2023-03-08 20:40:53 -05:00
beff110ab1 USD: disable vertex group export by default.
Changed export_vertex_groups option default to false.
2023-03-08 20:22:59 -05:00
3b9df7b9bb USD: panels UX change for import/export options.
Operator UX adjusted to group functionality into panels
for groupings of related settings.

Some defaults have been adjusted.  Not all options in the
Operators may be visible in the new UI, instead allowing
their defaults to become the only behavior. However, those
options are not removed from the Operators so that script
authors can take advantage of the extra flexibility.
2023-03-08 19:53:57 -05:00
f8faefab13 USD export: fixed vertex group export crash. 2023-03-08 10:21:15 -05:00
2aaa085f4c USD export: fix warnings setting axis options. 2023-03-08 10:15:29 -05:00
353f83ddda USD: fix linux/mac compiler warnings. 2023-02-28 09:47:20 -05:00
94a35ab24a USD: fix linux/mac compiler warnings. 2023-02-24 11:30:13 -05:00
c30ad3ad30 USD: Fixed linux/mac build error. 2023-02-24 06:40:31 -05:00
c0948d75c1 USD: Fixed linux/mac build warning and error. 2023-02-24 06:21:24 -05:00
5f2d3decd3 USD: fix import options errors.
Fixed two errors introduced during the last merge with
main:

- The mtl_name_collision_mode property was incorrectly
drawn in two places.

- The set_material_blend is now correctly disabled when
importing USD Preview Surface shaders.
2023-02-23 22:26:54 -05:00
d68c0afd23 USD: New export UI.
Change authored by Charles Wardlaw.

UI for export is now split into multiple collapsible tool
panels on the right side.  Added a function to io_ops.c to
facilitate this, as there is no current API for this
(checked with the BF).

As per discussions, defaults have also been updated and
some options, while remaining available through the
Python API for the operator, are now removed from the
Export list.
2023-02-23 21:58:57 -05:00
ad9446b08d Merge branch 'main' into universal-scene-description 2023-02-21 18:32:41 -05:00
b17e75f876 USD IO: use asset resolver to copy textures.
Updated the code to invoke the USD asset resolver
for texture import and export.  This removes the
assumption that assets are specified as file system
paths.

Added logic to allow importing textures from paths that
are not package relative. The new heuristics will attempt
to import files that don't exist on the file system, but
which can be resolved with the USD asset resolver, to
allow importing textures from URIs.
2023-02-21 11:41:58 -05:00
7187ccb998 USD: export generic float2 attributes as UVs.
Updated code to convert custom mesh data of type CD_PROP_FLOAT2
to USD texture coordinates, since UVs are no longer represented
as CD_MLOOPUV data.
2023-02-18 20:24:11 -05:00
e104d2f7d4 Merge branch 'main' into universal-scene-description 2023-02-14 17:23:31 -05:00
f0361cada6 USD import: allow selecting USD for cache files.
Update the file selection operator filter to include USD
when loading Transform Cache constraint and Mesh Sequence Cache
modifier cache files.
2023-02-08 12:12:49 -05:00
363aff2ad0 USD export: fix apply_subdiv option.
Fixed logic to disable applying subdivision modifiers
on mesh export when the apply_subdiv USD export
option is disabled.
2023-02-06 11:37:22 -05:00
1f9e90bb1c USD export: incorrect blend shape base mesh.
Fixed error acquiring un-modified Blender mesh
when creating USD blendshape base meshes.
2023-01-09 11:32:24 -05:00
56a97ba816 USD import: fix crash on empty blendshapes.
Now guarding against an empty offsets array and
out-of-bounds offset indices.
2023-01-08 20:54:57 -05:00
921fc1e44c USD export: fix export parameter memory leak.
Now freeing default_prim_custom_kind export option string.
2022-12-28 17:58:01 -05:00
91368f7b7d USD import: fix crash adding event notifiers.
Fixed crash due to race condition adding event notifiers
when converting materials when the importer is invoked
in a background job.  Now acquiring the main thread lock
before reading object data, to avoid possible crashes
when event notifiers are added by timers for progress
updates in the main thread.
2022-12-28 15:53:07 -05:00
edc59cefb6 USD import: fix prim_path_mask storage.
Now accessing the prm_path_mask string property as
an allocated string, as the string is of arbitrary
length.
2022-12-14 12:18:08 -05:00
27eea5e69e Merge branch 'master' into universal-scene-description 2022-12-13 18:40:08 -05:00
6a47862215 USD: Path Mask import option improvements.
The prim_path_mask USD import option string property
length is now unlimited.

Updated the Path Mask option tooltip.
2022-12-12 12:26:29 -05:00
6ce3e0495a USD export: support authoring Kind
Added a switch to the exporter to write USD Kind.
Added options to add USD Kind to the Default Prim.
Added a special case for the IDProperty "usdkind",
which is now written as the Kind through the UsdModelAPI.
2022-12-09 18:37:54 -05:00
98670cfe82 USD import: Defined Primitives Only option.
New option to load defined primitives only. This may be
turned off to allow loading overrides with the Path Mask.
2022-11-29 14:18:06 -05:00
90f1d1f4b6 USD Import: Support multiple prim path masks.
Extended the Prim Path option to support multiple path
entries, delimited by commas, spaces or semicolons.
2022-11-23 14:13:17 -05:00
4d8c634820 USD export: access deform verts as custom data.
Due to a recent Blender API change, MDeformVert entries
must be read as custom data.
2022-11-07 22:34:26 -05:00
0f7433a4c8 USD export: fix curve widths calculation.
Now multiplying the curve radius values by the bevel radius
when calculating curve widths.  Change authored by
Charles Wardlaw.
2022-11-02 16:27:57 -04:00
f428fe774b USD import: replace deprecated shape import code.
The static functions GetMeshPoints() and GetTopology()
have been removed from the adapter classes in USD 22.11.
In anticipation of this change, the code was updated to
call the corresponding virtual functions instead.
Change authored by Charles Wardlaw.
2022-11-02 14:30:00 -04:00
7e0fb88a68 USD export: export textures for Cycles materials. 2022-10-27 15:25:47 -04:00
4fa478c724 USD Export: handle meshes in edit mode.
Small change to ensure Edit mode meshes are committed during the
export process.  Without this change, meshes may be empty in the
USD.  This update was authored by Charles Wardlaw.
2022-10-26 19:58:28 -04:00
784ea87375 Merge branch 'master' into universal-scene-description 2022-10-24 15:28:20 -04:00
75064c7024 USD import: crash reading shapes.
Updated the mesh reading code when reading shapes,
to fix a crash due to the updated mesh API introduced
in the last merge from master.
2022-10-24 11:17:57 -04:00
ae342e00ca USD Import: hide instance prototype collections.
Now hiding the instance prototype parent collection in
both the viewport and render.  This change was also
necessary because the previous code for hiding
prototype layer collections stopped working with the
latest merge from master.
2022-10-20 16:37:11 -04:00
471636ffcd USD Import: fix error messages loading instances.
Added logic to avoid attempting to bind the pxr::UsdSkelBindingAPI
to instance proxies and prototypes, as this was generating errors.
2022-10-19 15:15:47 -04:00
cf8cf884d1 Merge branch 'master' into universal-scene-description 2022-10-18 14:16:53 -04:00
f8e871168a Fix linux/mac build warnings and errors. 2022-10-17 16:33:09 -04:00
50a3004328 USD Import: support reading USD shapes.
Added readers for importing USD shapes (capsule, cylinder,
cone, cube and sphere) as meshes.  Implemented by Charles
Wardlaw.
2022-10-17 15:11:42 -04:00
a067e59004 USD Import: validate meshes option.
Added feature flag "Validate Meshes" to strip invalid geometry
from meshes on import.
2022-10-10 12:48:43 -04:00
61a9ee88a3 USD import: handle context-dependent UDIM paths.
This change helps address the issue where contex-dependent
UDIM texture paths (e.g., '0/foo.<UDIM>.png') were not getting
resolved by the call to SdfLayer::::ComputeAbsolutePath().
To work around this limitation, I updated the code to compute
the absolute path on just the parent directory portion of
the UDIM file path. This makes the simplifying assumption that
context-dependent asset paths are essentially relative paths,
which might not always be correct.  It's not clear how
else to efficiently address this without performing a
potentially expensive search.
2022-09-29 20:16:07 -04:00
69251411bc USD Export: Added triangulate options 2022-09-28 14:36:41 -04:00
fb1f756bd1 USD export to USDZ.
USDZ export code developed by Sonny Campbell in
patch D15623, which is currently under review.

Charles added USDZ Texture downsample export functionality
and a switch for creating ARKit assets during USDZ export.
2022-09-26 18:19:42 -04:00
90e1c892b7 USD Export: Save Blender file path to metadata.
Write source Blender file path to stage customData.
2022-09-19 16:03:11 -04:00
19fa05d37c USD skel export fixes.
Fixed error in USDSkinnedMeshWriter which was causing
the mesh to be written more than once when exporting
blendshapes is disabled.  Also removed unnecessary
warnings when the mesh has deform groups that don't
match any bones.

Updated USDBlendShapeMeshWriter to skip creating a
blendshape neutral mesh if exporting blendshapes is
disabled.

Added more descriptive error message when the shape key
offset count doesn't match the mesh vertex count.  Now
exporting the default mesh when this size mismatch is
detected.
2022-09-02 18:51:55 -04:00
95185f6e7e USD IO: fixed includes.
Fixed include order to fix build error.
Also removed unneeded includes.
2022-08-30 19:41:37 -04:00
219a71a427 USD import: fix mac build warnings and error. 2022-08-30 19:03:16 -04:00
5156e12a0a Merge branch 'master' into universal-scene-description 2022-08-30 18:13:18 -04:00
5e54c0cbf1 USD export: fix skel root by default.
Now enabling fixing the skel root hierarchy on export
by default.  Also removed 'Experimental' from the
'Armatures' export option label.
2022-08-30 17:06:21 -04:00
095f016fc0 USD import: read skeletons.
Added new option to import USD skeletons as Blender
armatures.  Added new USDSkeletonReader class and
updated the mesh import code to optionally create
armature modifiers for meshes bound to skeletons.
Added logic to the mesh reader to allow overriding
the mesh transform to ensure the mesh is aligned
with the authored geom bind transform.
2022-08-30 15:15:42 -04:00
1dfe4938cd Merge branch 'master' into universal-scene-description 2022-08-14 17:16:40 -04:00
3f3910966a USD import: invalid UsdUVTexture input crash.
Fixed crash when accessing invalud texture file
inputs.
2022-08-14 15:58:02 -04:00
a61fdcd349 USD blendshape import errors for instances.
No longer attempting to create a skel binding on
instance proxies and proxies, as doing so generates
USD errors.
2022-08-14 14:24:09 -04:00
9a2680a626 USD import: fixed typo in debug message.
This was also causing mac and linux compile errors.
2022-08-11 20:06:28 -04:00
a37e7e682f USD import: fix errors loading pixar example.
Fixed errors loading Pixar UsdSkel sample
HumanFemaleKeepAlive.usd: now reading the inherited
skeleton and animation and no longer creating curves
for blendshapes that weren't imported as shapekeys
for a given shape.
2022-08-11 19:12:19 -04:00
833df7ebc1 USD import blendshapes.
Importing USD blendshapes as shapekeys and creating
animation curves for animated blendshape weights.
2022-08-10 23:19:57 -04:00
b665ae266d USD export armatures with shapekeys WIP.
Extended USDSkinnedMeshWriter to support exporting
blendshapes.
2022-08-07 14:07:07 -04:00
1770d462cc USD export: fix armature export.
Fixed a bug that was preventing armature writers
from being created, due to a missing break statement,
introduced after the latest merge from master.
2022-07-27 12:23:21 -04:00
1417bf6525 Added missing include to fix linux/mac build. 2022-07-26 22:19:35 -04:00
df1edf2934 Added missing include.
To fix linux/mac build errors.
2022-07-26 21:48:58 -04:00
fa2de72f86 USD export: convert shapekeys to blendshapes.
Added option to export shapekeys as UdsSkel blendshapes.
Implemented a new USDBlendShapeMeshWriter class to export
meshes that have shapekeys defined.  Currently, only relative
shapekeys are converted to blendshapes.  Absolute shapekeys
are exported as deformed meshes.  The current implementation
doesn't handle shapekeys on meshes bound to armatures that are
expored as skinned meshes.
2022-07-26 21:08:59 -04:00
9374a3dbf0 Merge branch 'master' into universal-scene-description 2022-07-17 12:23:26 -04:00
0869c6b46d Revert "USD import: temp fix for broken UDIMS."
This reverts commit 7aa4b6f9.

An alternate fix was applied in the latest master branch.
2022-07-16 20:46:31 -04:00
1d5d49775c USD import: fix light intensity scaling.
Updated light intensity calculations on import
to preserve values on round trip Blender ->
USD -> Blender.
2022-06-14 23:56:43 -04:00
9d39948871 USD export: ensure packed texture file extension.
Updated in-memory texture path generation logic to
ensure the path has a valid extension for the image
format.  Also moved duplicate code for identifying
in-memory textures into a common function.
2022-06-06 12:26:02 -04:00
06022f7891 USD export: Convert uv to st by default.
Enabling the "Convert uv to st" export option by default.
2022-05-14 18:54:53 -04:00
7aa4b6f93f USD import: temp fix for broken UDIMS.
Applying patch authored by Jesse Yurkovich to support
single file UDIMs, while this code is still under review,
to temporarily prevent a regression for those testing USD
features.  If the patch is rejected or rewritten, it might
be necessary to back out this commit or merge it with the
latest changes.
2022-05-14 18:12:10 -04:00
69c2c9de0f USD import: preview surface fallback.
Added logic to fall back on importing USD Preview Surface
shaders, if possible, if importing MDL is specified but
an MDL couldn't be imported.
2022-05-14 16:05:32 -04:00
900ddd763a USD import: read all color primvars.
Now converting all color primvars to custom mesh data,
if the Color Attributes import option is enabled.  Also,
now enabling the Color Attributes option by default.
2022-05-11 21:37:42 -04:00
00aa4252c5 Updates to build with USD 21.11. 2022-05-02 20:22:01 -04:00
8f17cb2052 Merge branch 'master' into universal-scene-description 2022-05-02 20:17:10 -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
71 changed files with 12169 additions and 717 deletions

View File

@@ -491,7 +491,7 @@ class TOPBAR_MT_file_export(Menu):
self.layout.operator("wm.alembic_export", text="Alembic (.abc)")
if bpy.app.build_options.usd:
self.layout.operator(
"wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)")
"wm.usd_export", text="Universal Scene Description (.usd*)")
if bpy.app.build_options.io_gpencil:
# Pugixml lib dependency

View File

@@ -165,6 +165,7 @@ void BKE_image_alpha_mode_from_extension(struct Image *image);
/**
* Returns a new image or NULL if it can't load.
*/
struct Image *BKE_image_load_ex(struct Main *bmain, const char *filepath, int flag);
struct Image *BKE_image_load(struct Main *bmain, const char *filepath);
/**
* Returns existing Image when filename/type is same.

View File

@@ -387,7 +387,17 @@ bool BKE_cachefile_filepath_get(const Main *bmain,
char r_filepath[FILE_MAX])
{
BLI_strncpy(r_filepath, cache_file->filepath, FILE_MAX);
#ifdef WITH_USD
if (BLI_path_extension_check_glob(r_filepath, "*.usd;*.usda;*.usdc;*.usdz")) {
USD_path_abs(r_filepath, ID_BLEND_PATH(bmain, &cache_file->id), true /* for import */);
}
else {
BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &cache_file->id));
}
#else
BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &cache_file->id));
#endif
int fframe;
int frame_len;

View File

@@ -672,11 +672,11 @@ static void image_init(Image *ima, short source, short type)
ima->stereo3d_format = MEM_cnew<Stereo3dFormat>("Image Stereo Format");
}
static Image *image_alloc(Main *bmain, const char *name, short source, short type)
static Image *image_alloc_ex(Main *bmain, const char *name, short source, short type, int flag)
{
Image *ima;
ima = static_cast<Image *>(BKE_libblock_alloc(bmain, ID_IM, name, 0));
ima = static_cast<Image *>(BKE_libblock_alloc(bmain, ID_IM, name, flag));
if (ima) {
image_init(ima, source, type);
}
@@ -684,6 +684,11 @@ static Image *image_alloc(Main *bmain, const char *name, short source, short typ
return ima;
}
static Image *image_alloc(Main *bmain, const char *name, short source, short type)
{
return image_alloc_ex(bmain, name, source, type, 0);
}
/**
* Get the ibuf from an image cache by its index and entry.
* Local use here only.
@@ -1012,7 +1017,7 @@ void BKE_image_alpha_mode_from_extension(Image *image)
image->alpha_mode = BKE_image_alpha_mode_from_extension_ex(image->filepath);
}
Image *BKE_image_load(Main *bmain, const char *filepath)
Image *BKE_image_load_ex(Main *bmain, const char *filepath, int flag)
{
Image *ima;
int file;
@@ -1032,7 +1037,7 @@ Image *BKE_image_load(Main *bmain, const char *filepath)
close(file);
}
ima = image_alloc(bmain, BLI_path_basename(filepath), IMA_SRC_FILE, IMA_TYPE_IMAGE);
ima = image_alloc_ex(bmain, BLI_path_basename(filepath), IMA_SRC_FILE, IMA_TYPE_IMAGE, flag);
STRNCPY(ima->filepath, filepath);
if (BLI_path_extension_check_array(filepath, imb_ext_movie)) {
@@ -1044,6 +1049,11 @@ Image *BKE_image_load(Main *bmain, const char *filepath)
return ima;
}
Image *BKE_image_load(Main *bmain, const char *filepath)
{
return BKE_image_load_ex(bmain, filepath, 0);
}
Image *BKE_image_load_exists_ex(Main *bmain, const char *filepath, bool *r_exists)
{
Image *ima;

View File

@@ -458,7 +458,7 @@ int BLI_delete_soft(const char *file, const char **error_message)
return err;
}
/* Not used anywhere! */
/* Not used anywhere! Convention is to use BLI_rename. */
# if 0
int BLI_move(const char *file, const char *to)
{
@@ -822,8 +822,8 @@ static int delete_soft(const char *file, const char **error_message)
Class NSStringClass = objc_getClass("NSString");
SEL stringWithUTF8StringSel = sel_registerName("stringWithUTF8String:");
id pathString = ((
id(*)(Class, SEL, const char *))objc_msgSend)(NSStringClass, stringWithUTF8StringSel, file);
id pathString = ((id(*)(Class, SEL, const char *))objc_msgSend)(
NSStringClass, stringWithUTF8StringSel, file);
Class NSFileManagerClass = objc_getClass("NSFileManager");
SEL defaultManagerSel = sel_registerName("defaultManager");
@@ -834,8 +834,8 @@ static int delete_soft(const char *file, const char **error_message)
id nsurl = ((id(*)(Class, SEL, id))objc_msgSend)(NSURLClass, fileURLWithPathSel, pathString);
SEL trashItemAtURLSel = sel_registerName("trashItemAtURL:resultingItemURL:error:");
BOOL deleteSuccessful = ((
BOOL(*)(id, SEL, id, id, id))objc_msgSend)(fileManager, trashItemAtURLSel, nsurl, nil, nil);
BOOL deleteSuccessful = ((BOOL(*)(id, SEL, id, id, id))objc_msgSend)(
fileManager, trashItemAtURLSel, nsurl, nil, nil);
if (deleteSuccessful) {
ret = 0;
@@ -1123,7 +1123,7 @@ static int copy_single_file(const char *from, const char *to)
return RecursiveOp_Callback_OK;
}
/* Not used anywhere! */
/* Not used anywhere! Convention is to use BLI_rename. */
# if 0
static int move_callback_pre(const char *from, const char *to)
{

View File

@@ -120,7 +120,7 @@ void CACHEFILE_OT_open(wmOperatorType *ot)
ot->cancel = open_cancel;
WM_operator_properties_filesel(ot,
FILE_TYPE_ALEMBIC | FILE_TYPE_FOLDER,
FILE_TYPE_ALEMBIC | FILE_TYPE_USD | FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,
@@ -218,7 +218,7 @@ void CACHEFILE_OT_layer_add(wmOperatorType *ot)
ot->exec = cachefile_layer_add_exec;
WM_operator_properties_filesel(ot,
FILE_TYPE_ALEMBIC | FILE_TYPE_FOLDER,
FILE_TYPE_ALEMBIC | FILE_TYPE_USD | FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH,

View File

@@ -5,8 +5,18 @@
* \ingroup collada
*/
#include <assert.h>
#include <string.h>
#include "io_ops.h" /* own include */
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "BLI_listbase.h"
#include "BKE_context.h"
#include "BKE_screen.h"
#include "WM_api.h"
#ifdef WITH_COLLADA
@@ -26,6 +36,40 @@
#include "io_obj.h"
#include "io_stl_ops.h"
bool IO_paneltype_set_parent(struct PanelType *panel) {
PanelType *parent = NULL;
SpaceType *space_type = BKE_spacetype_from_id(SPACE_FILE);
assert(space_type);
ARegionType *region = BKE_regiontype_from_id(space_type, RGN_TYPE_TOOL_PROPS);
assert(region);
LISTBASE_FOREACH (PanelType *, pt, &region->paneltypes) {
if (strcasecmp(pt->idname, panel->parent_id) == 0) {
parent = pt;
break;
}
}
if (parent) {
panel->parent = parent;
LinkData *pt_child_iter = parent->children.last;
for (; pt_child_iter; pt_child_iter = pt_child_iter->prev) {
PanelType *pt_child = pt_child_iter->data;
if (pt_child->order <= panel->order) {
break;
}
}
BLI_insertlinkafter(&parent->children, pt_child_iter, BLI_genericNodeN(panel));
return true;
}
return false;
}
void ED_operatortypes_io(void)
{
#ifdef WITH_COLLADA
@@ -40,6 +84,9 @@ void ED_operatortypes_io(void)
#ifdef WITH_USD
WM_operatortype_append(WM_OT_usd_import);
WM_operatortype_append(WM_OT_usd_export);
WM_PT_USDExportPanelsRegister();
WM_PT_USDImportPanelsRegister();
#endif
#ifdef WITH_IO_GPENCIL

View File

@@ -7,4 +7,10 @@
#pragma once
#include <stdbool.h>
struct PanelType;
bool IO_paneltype_set_parent(struct PanelType *panel);
void ED_operatortypes_io(void);

File diff suppressed because it is too large Load Diff

View File

@@ -12,3 +12,7 @@ struct wmOperatorType;
void WM_OT_usd_export(struct wmOperatorType *ot);
void WM_OT_usd_import(struct wmOperatorType *ot);
void WM_PT_USDExportPanelsRegister(void);
void WM_PT_USDImportPanelsRegister(void);

View File

@@ -199,7 +199,9 @@ class AbstractHierarchyIterator {
typedef std::map<ObjectIdentifier, ExportChildren> ExportGraph;
/* 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. */
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:
ExportGraph export_graph_;
@@ -208,6 +210,7 @@ class AbstractHierarchyIterator {
Depsgraph *depsgraph_;
WriterMap writers_;
ExportSubset export_subset_;
PrototypeObjects prototypes_;
public:
explicit AbstractHierarchyIterator(Main *bmain, Depsgraph *depsgraph);
@@ -244,6 +247,19 @@ class AbstractHierarchyIterator {
* data to be a child of the object. */
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:
void debug_print_export_graph(const ExportGraph &graph) const;
@@ -331,6 +347,18 @@ class AbstractHierarchyIterator {
/* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
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;
ExportChildren &graph_children(const HierarchyContext *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));
}
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
{
size_t total_graph_size = 0;
@@ -288,9 +310,14 @@ void AbstractHierarchyIterator::export_graph_construct()
/* Export the duplicated objects instanced by this object. */
ListBase *lb = object_duplilist(depsgraph_, scene, object);
if (lb) {
if (lb && object->particlesystem.first == nullptr) {
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) {
PersistentID persistent_id(dupli_object);
if (!should_visit_dupli_object(dupli_object)) {
@@ -411,7 +438,7 @@ void AbstractHierarchyIterator::visit_object(Object *object,
context->export_parent = export_parent;
context->duplicator = nullptr;
context->weak_export = weak_export;
context->animation_check_include_parent = false;
context->animation_check_include_parent = true;
context->export_path = "";
context->original_export_path = "";
context->higher_up_export_path = "";
@@ -445,6 +472,7 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
Object *duplicator,
const DupliParentFinder &dupli_parent_finder)
{
HierarchyContext *context = new HierarchyContext();
context->object = dupli_object->ob;
context->duplicator = duplicator;
@@ -467,6 +495,56 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
context_update_for_graph_index(context, graph_index);
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::
@@ -541,6 +619,7 @@ void AbstractHierarchyIterator::determine_duplication_references(
}
else {
context->mark_as_instance_of(it->second);
prototypes_.insert(source_id);
}
if (context->object->data) {
@@ -597,13 +676,15 @@ void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_cont
transform_writer->write(*context);
}
if (!context->weak_export) {
if (!context->weak_export && include_data_writers(context)) {
make_writers_particle_systems(context);
make_writer_object_data(context);
}
/* Recurse into this object's children. */
make_writers(context);
if (include_child_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.
@@ -633,6 +714,7 @@ void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *
data_context.original_export_path = duplisource_export_path_[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());
}

View File

@@ -16,6 +16,9 @@ add_definitions(-DBOOST_ALL_NO_LIB)
# USD headers use deprecated TBB headers, silence warning.
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
# 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
@@ -47,9 +50,11 @@ set(INC
../../imbuf
../../makesdna
../../makesrna
../../python
../../windowmanager
../../../../intern/guardedalloc
../../../../intern/utfconv
${CMAKE_BINARY_DIR}/source/blender/makesrna/intern
)
set(INC_SYS
@@ -65,57 +70,83 @@ set(SRC
intern/usd_capi_import.cc
intern/usd_common.cc
intern/usd_hierarchy_iterator.cc
intern/usd_writer_abstract.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_writer_volume.cc
intern/usd_light_convert.cc
intern/usd_skel_convert.cc
intern/usd_reader_camera.cc
intern/usd_reader_curve.cc
intern/usd_reader_geom.cc
intern/usd_reader_instance.cc
intern/usd_reader_light.cc
intern/usd_reader_material.cc
intern/usd_reader_mesh.cc
intern/usd_reader_nurbs.cc
intern/usd_reader_prim.cc
intern/usd_reader_skeleton.cc
intern/usd_reader_shape.cc
intern/usd_reader_stage.cc
intern/usd_reader_volume.cc
intern/usd_reader_xform.cc
intern/usd_umm.cc
intern/usd_writer_abstract.cc
intern/usd_writer_armature.cc
intern/usd_writer_blendshape_mesh.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
intern/usd_writer_volume.cc
usd.h
intern/usd_asset_utils.h
intern/usd_common.h
intern/usd_exporter_context.h
intern/usd_hierarchy_iterator.h
intern/usd_writer_abstract.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_writer_volume.h
intern/usd_light_convert.h
intern/usd_skel_convert.h
intern/usd_reader_camera.h
intern/usd_reader_curve.h
intern/usd_reader_geom.h
intern/usd_reader_instance.h
intern/usd_reader_light.h
intern/usd_reader_material.h
intern/usd_reader_mesh.h
intern/usd_reader_nurbs.h
intern/usd_reader_prim.h
intern/usd_reader_skeleton.h
intern/usd_reader_shape.h
intern/usd_reader_stage.h
intern/usd_reader_volume.h
intern/usd_reader_volume.h
intern/usd_reader_xform.h
intern/usd_umm.h
intern/usd_writer_abstract.h
intern/usd_writer_armature.h
intern/usd_writer_blendshape_mesh.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
intern/usd_writer_volume.h
)
set(LIB
@@ -127,6 +158,7 @@ set(LIB
list(APPEND LIB
${BOOST_LIBRARIES}
${PYTHON_LINKFLAGS}
${BOOST_PYTHON_LIBRARIES}
${PYTHON_LIBRARIES}
${USD_LIBRARIES}

View File

@@ -8,6 +8,7 @@
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/ar/writableAsset.h>
#include "BKE_appdir.h"
#include "BKE_main.h"
#include "BLI_fileops.h"
@@ -300,4 +301,112 @@ bool is_udim_path(const std::string &path)
path.find(UDIM_PATTERN2) != std::string::npos;
}
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage)
{
pxr::SdfLayerHandle layer = stage->GetRootLayer();
if (layer->IsAnonymous()) {
WM_reportf(
RPT_WARNING, "%s: Can't generate a textures directory path for anonymous stage", __func__);
return "";
}
pxr::ArResolvedPath stage_path = layer->GetResolvedPath();
if (stage_path.empty()) {
WM_reportf(
RPT_WARNING, "%s: Can't get resolved path for stage", __func__);
return "";
}
pxr::ArResolver &ar = pxr::ArGetResolver();
/* Resolove the './textures' relative path, with the stage path as an anchor. */
std::string textures_dir = ar.CreateIdentifierForNewAsset("./textures", stage_path);
/* If parent of the stage path exists as a file system directory, try to create the
* textures directory. */
if (parent_dir_exists_on_file_system(stage_path.GetPathString().c_str())) {
BLI_dir_create_recursive(textures_dir.c_str());
}
return textures_dir;
}
bool parent_dir_exists_on_file_system(const char *path)
{
char dir_path[FILE_MAX];
BLI_split_dir_part(path, dir_path, FILE_MAX);
return BLI_is_dir(dir_path);
}
bool should_import_asset(const std::string &path)
{
if (path.empty()) {
return false;
}
if (BLI_path_is_rel(path.c_str())) {
return false;
}
if (pxr::ArIsPackageRelativePath(path)) {
return true;
}
return !BLI_is_file(path.c_str()) && asset_exists(path.c_str());
}
bool paths_equal(const char *p1, const char *p2)
{
BLI_assert_msg(!BLI_path_is_rel(p1) && !BLI_path_is_rel(p2),
"Paths arguments must be absolute");
pxr::ArResolver &ar = pxr::ArGetResolver();
std::string resolved_p1 = ar.ResolveForNewAsset(p1).GetPathString();
std::string resolved_p2 = ar.ResolveForNewAsset(p2).GetPathString();
return resolved_p1 == resolved_p2;
}
const char *temp_textures_dir()
{
static bool inited = false;
static char temp_dir[FILE_MAXDIR] = {'\0'};
if (!inited) {
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
inited = true;
}
return temp_dir;
}
} // namespace blender::io::usd
void USD_path_abs(char *path, const char *basepath, bool for_import)
{
if (!BLI_path_is_rel(path)) {
pxr::ArResolvedPath resolved_path = for_import ? pxr::ArGetResolver().Resolve(path) :
pxr::ArGetResolver().ResolveForNewAsset(path);
std::string path_str = resolved_path.GetPathString();
if (!path_str.empty()) {
if (path_str.length() < FILE_MAX) {
BLI_strncpy(path, path_str.c_str(), FILE_MAX);
return;
}
WM_reportf(RPT_ERROR,
"In %s: resolved path %s exceeds path buffer length.", __func__,
path_str.c_str());
}
}
/* If we got here, the path couldn't be resolved by the ArResolver, so we
* fall back on the standard Blender absolute path resolution. */
BLI_path_abs(path, basepath);
}

View File

@@ -54,4 +54,64 @@ std::string import_asset(const char *src,
*/
bool is_udim_path(const std::string &path);
/**
* Invoke the USD asset resolver to return an identifier for a 'textures' directory
* which is a sibling of the given stage. The resulting path is created by
* resolving the './textures' relative path with the stage's root layer path as
* the anchor. If the parent of the stage root layer path resolves to a file
* system path, the textures directory will be created, if it doesn't exist.
*
* \param stage: The stage whose root layer is a sibling of the 'textures'
* directory
* \return the path to the 'textures' directory
*/
std::string get_export_textures_dir(const pxr::UsdStageRefPtr stage);
/**
* Returns true if the parent directory of the given path exists on the
* file system.
*
* \param path: input file path
* \return true if the parent directory exists
*/
bool parent_dir_exists_on_file_system(const char *path);
/**
* Return true if the asset at the given path is a candidate for importing
* with the USD asset resolver. The following heuristics are currently
* applied for this test:
* - Returns false if it's a Blender relative path.
* - Returns true if the path is package-relative.
* - Returns true is the path doesn't exist on the file system but can
* nonetheles be resolved by the USD asset resolver.
* - Returns false otherwise.
*
* TODO(makowalski): the test currently requires a file-system stat.
* Consider possible ways around this, e.g., by determining if the
* path is a supported URI.
*
* \param path: input file path
* \return true if the path should be imported, false otherwise
*/
bool should_import_asset(const std::string &path);
/**
* Invokes the USD asset resolver to resolve the givn paths and
* returns true if the resolved paths are equal.
*
* \param p1: first path to compare
* \param p2: second path to compare
* \return true if the resolved input paths are equal, returns
* false otherwise.
*
*/
bool paths_equal(const char *p1, const char *p2);
/**
* Returns path to temporary folder for saving imported textures prior to packing.
* CAUTION: this directory is recursively deleted after material import.
*/
const char *temp_textures_dir();
} // namespace blender::io::usd

View File

@@ -2,15 +2,26 @@
* Copyright 2019 Blender Foundation. All rights reserved. */
#include "usd.h"
#include "usd_asset_utils.h"
#include "usd_common.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/pxr.h>
#include <pxr/usd/kind/registry.h>
#include <pxr/usd/usd/modelAPI.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/primRange.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/usdUtils/dependencies.h>
#include "MEM_guardedalloc.h"
@@ -24,9 +35,17 @@
#include "BKE_blender_version.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_main.h"
#include "BKE_scene.h"
#include "BKE_image.h"
#include "BKE_image_save.h"
#include "BKE_image_format.h"
#include "BKE_lib_id.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_string.h"
#include "BLI_timeit.hh"
@@ -37,25 +56,304 @@
namespace blender::io::usd {
struct ExportJobData {
Scene *scene;
ViewLayer *view_layer;
Main *bmain;
Depsgraph *depsgraph;
wmWindowManager *wm;
char filepath[FILE_MAX];
char usdz_filepath[FILE_MAX];
bool is_usdz_export;
USDExportParams params;
float *progress;
bool was_canceled;
bool export_ok;
timeit::TimePoint start_time;
};
/* 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;
}
if (params.export_usd_kind && params.default_prim_kind == USD_KIND_CUSTOM && strlen(params.default_prim_custom_kind) == 0) {
WM_reportf(RPT_ERROR,
"USD Export: Default Prim Kind is set to Custom, but the value is empty.");
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();
DEGObjectIterSettings deg_iter_settings{};
deg_iter_settings.depsgraph = depsgraph;
deg_iter_settings.flags = DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET;
DEG_OBJECT_ITER_BEGIN (&deg_iter_settings, object) {
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]));
}
/* Handle root prim USD Kind. */
if (params.export_usd_kind && params.default_prim_kind) {
pxr::UsdModelAPI api(root_prim);
switch (params.default_prim_kind) {
case USD_KIND_COMPONENT:
api.SetKind(pxr::KindTokens->component);
break;
case USD_KIND_GROUP:
api.SetKind(pxr::KindTokens->group);
break;
case USD_KIND_ASSEMBLY:
api.SetKind(pxr::KindTokens->assembly);
break;
case USD_KIND_CUSTOM:
api.SetKind(pxr::TfToken(params.default_prim_custom_kind));
break;
default:
break;
}
}
}
static void report_job_duration(const ExportJobData *data)
{
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
std::cout << "USD export of '" << data->filepath << "' took ";
const char *export_filepath = data->is_usdz_export ? data->usdz_filepath : data->filepath;
std::cout << "USD export of '" << export_filepath << "' took ";
timeit::print_duration(duration);
std::cout << '\n';
}
static void process_usdz_textures(const ExportJobData *data, const char *path) {
const eUSDZTextureDownscaleSize enum_value = data->params.usdz_downscale_size;
if (enum_value == USD_TEXTURE_SIZE_KEEP) {
return;
}
int image_size = (
(enum_value == USD_TEXTURE_SIZE_CUSTOM ? data->params.usdz_downscale_custom_size : enum_value)
);
image_size = image_size < 128 ? 128 : image_size;
char texture_path[4096];
BLI_strcpy_rlen(texture_path, path);
BLI_path_append(texture_path, 4096, "textures");
BLI_path_slash_ensure(texture_path, sizeof(texture_path));
struct direntry *entries;
unsigned int num_files = BLI_filelist_dir_contents(texture_path, &entries);
for (int index = 0; index < num_files; index++) {
/* We can skip checking extensions as this folder is only created
* when we're doing a USDZ export. */
if (!BLI_is_dir(entries[index].path)) {
Image *im = BKE_image_load_ex(data->bmain, entries[index].path, LIB_ID_CREATE_NO_MAIN);
if (!im) {
std::cerr << "-- Unable to open file for downscaling: " << entries[index].path << std::endl;
continue;
}
int width, height;
BKE_image_get_size(im, NULL, &width, &height);
const int longest = width >= height ? width : height;
const float scale = 1.0 / ((float)longest / (float)image_size);
if (longest > image_size) {
const int width_adjusted = (float)width * scale;
const int height_adjusted = (float)height * scale;
BKE_image_scale(im, width_adjusted, height_adjusted);
ImageSaveOptions opts;
if (BKE_image_save_options_init(&opts, data->bmain, data->scene, im, NULL, false, false)) {
bool result = BKE_image_save(NULL, data->bmain, im, NULL, &opts);
if (!result) {
std::cerr << "-- Unable to resave " << data->filepath << " (new size: "
<< width_adjusted << "x" << height_adjusted << ")" << std::endl;
}
else {
std::cout << "Downscaled " << entries[index].path << " to "
<< width_adjusted << "x" << height_adjusted << std::endl;
}
}
BKE_image_save_options_free(&opts);
}
/* Make sure to free the image so it doesn't stick
* around in the library of the open file. */
BKE_id_free(data->bmain, (void*)im);
}
}
BLI_filelist_free(entries, num_files);
}
static bool perform_usdz_conversion(const ExportJobData *data)
{
char usdc_temp_dir[FILE_MAX], usdc_file[FILE_MAX];
BLI_split_dirfile(data->filepath, usdc_temp_dir, usdc_file, FILE_MAX, FILE_MAX);
char usdz_file[FILE_MAX];
BLI_split_file_part(data->usdz_filepath, usdz_file, FILE_MAX);
char original_working_dir[FILE_MAX];
BLI_current_working_dir(original_working_dir, FILE_MAX);
BLI_change_working_dir(usdc_temp_dir);
process_usdz_textures(data, usdc_temp_dir);
if (data->params.usdz_is_arkit) {
std::cout << "USDZ Export: Creating ARKit Asset" << std::endl;
pxr::UsdUtilsCreateNewARKitUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file);
}
else {
pxr::UsdUtilsCreateNewUsdzPackage(pxr::SdfAssetPath(usdc_file), usdz_file);
}
BLI_change_working_dir(original_working_dir);
char usdz_temp_dirfile[FILE_MAX];
BLI_path_join(usdz_temp_dirfile, FILE_MAX, usdc_temp_dir, usdz_file);
int result = 0;
if (BLI_exists(data->usdz_filepath)) {
result = BLI_delete(data->usdz_filepath, false, false);
if (result != 0) {
WM_reportf(
RPT_ERROR, "USD Export: Unable to delete existing usdz file %s", data->usdz_filepath);
return false;
}
}
if (!copy_asset(usdz_temp_dirfile, data->usdz_filepath, USD_TEX_NAME_COLLISION_OVERWRITE)) {
WM_reportf(RPT_ERROR,
"USD Export: Couldn't copy new usdz file from temporary location %s to %s",
usdz_temp_dirfile,
data->usdz_filepath);
return false;
}
return true;
}
static void export_startjob(void *customdata,
/* Cannot be const, this function implements wm_jobs_start_callback.
* NOLINTNEXTLINE: readability-non-const-parameter. */
@@ -64,7 +362,9 @@ static void export_startjob(void *customdata,
float *progress)
{
ExportJobData *data = static_cast<ExportJobData *>(customdata);
data->export_ok = false;
data->progress = progress;
data->was_canceled = false;
data->start_time = timeit::Clock::now();
G.is_rendering = true;
@@ -73,6 +373,11 @@ static void export_startjob(void *customdata,
}
G.is_break = false;
if (!validate_params(data->params)) {
data->export_ok = false;
return;
}
/* Construct the depsgraph for exporting. */
Scene *scene = DEG_get_input_scene(data->depsgraph);
if (data->params.visible_objects_only) {
@@ -83,41 +388,103 @@ static void export_startjob(void *customdata,
}
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
validate_unique_root_prim_path(data->params, data->depsgraph);
*progress = 0.0f;
*do_update = true;
/* For restoring the current frame after exporting animation is done. */
const int orig_frame = scene->r.cfra;
if (!BLI_path_extension_check_glob(data->filepath, "*.usd;*.usda;*.usdc"))
BLI_path_extension_ensure(data->filepath, FILE_MAX, ".usd");
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filepath);
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
* USDC files, and creating a new UsdStage fails. */
WM_reportf(
RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filepath);
WM_reportf(RPT_ERROR, "USD Export: unable to create a stage for writing %s", data->filepath);
pxr::SdfLayerRefPtr existing_layer = pxr::SdfLayer::FindOrOpen(data->filepath);
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->filepath);
}
data->export_ok = false;
return;
}
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit, double(scene->unit.scale_length));
if (data->params.export_lights && !data->params.selected_objects_only &&
data->params.convert_world_material) {
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") +
BKE_blender_version_string());
/* Add any Blender-specific custom export data */
if (data->params.export_blender_metadata && strlen(data->bmain->filepath)) {
auto root_layer = usd_stage->GetRootLayer();
char full_path[1024];
strcpy(full_path, data->bmain->filepath);
// make all paths uniformly unix-like
BLI_str_replace_char(full_path + 2, SEP, ALTSEP);
char basename[128];
strcpy(basename, BLI_path_basename(full_path));
BLI_split_dir_part(full_path, full_path, 1024);
pxr::VtDictionary custom_data;
custom_data.SetValueAtPath(std::string("sourceFilename"), pxr::VtValue(basename));
custom_data.SetValueAtPath(std::string("sourceDirPath"), pxr::VtValue(full_path));
root_layer->SetCustomLayerData(custom_data);
}
/* Set up the stage for animated data. */
if (data->params.export_animation) {
usd_stage->SetTimeCodesPerSecond(FPS);
usd_stage->SetStartTimeCode(scene->r.sfra);
usd_stage->SetEndTimeCode(scene->r.efra);
usd_stage->SetStartTimeCode(data->params.frame_start);
usd_stage->SetEndTimeCode(data->params.frame_end);
}
ensure_root_prim(usd_stage, data->params);
USDHierarchyIterator iter(data->bmain, data->depsgraph, usd_stage, data->params);
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)) {
break;
}
@@ -141,6 +508,29 @@ static void export_startjob(void *customdata,
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 the default prim if it doesn't exist */
if (!usd_stage->GetDefaultPrim()) {
/* Use TraverseAll since it's guaranteed to be depth first and will get the first top level
@@ -151,15 +541,28 @@ static void export_startjob(void *customdata,
}
}
/* 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();
if (data->is_usdz_export) {
if (!perform_usdz_conversion(data)) {
return;
}
}
/* Finish up by going back to the keyframe that was current before we started. */
if (scene->r.cfra != orig_frame) {
scene->r.cfra = orig_frame;
BKE_scene_graph_update_for_newframe(data->depsgraph);
}
data->export_ok = true;
data->export_ok = !data->was_canceled;
*progress = 1.0f;
*do_update = true;
}
@@ -170,7 +573,22 @@ static void export_endjob(void *customdata)
DEG_graph_free(data->depsgraph);
if (!data->export_ok && BLI_exists(data->filepath)) {
if (data->is_usdz_export && BLI_exists(data->filepath))
{
char dir[FILE_MAX];
BLI_split_dir_part(data->filepath, dir, FILE_MAX);
char usdc_temp_dir[FILE_MAX];
BLI_path_join(usdc_temp_dir, FILE_MAX, BKE_tempdir_session(), "USDZ", SEP_STR);
BLI_assert(BLI_strcasecmp(dir, usdc_temp_dir) == 0);
BLI_delete(usdc_temp_dir, true, true);
}
MEM_freeN(data->params.default_prim_path);
MEM_freeN(data->params.root_prim_path);
MEM_freeN(data->params.material_prim_path);
MEM_freeN(data->params.default_prim_custom_kind);
if (data->was_canceled && BLI_exists(data->filepath)) {
BLI_delete(data->filepath, false, false);
}
@@ -183,6 +601,25 @@ static void export_endjob(void *customdata)
} // namespace blender::io::usd
/* To create a usdz file, we must first create a .usd/a/c file and then covert it to .usdz. The
* temporary files will be created in Blender's temporary session storage. The .usdz file will then
* copied to job->usdz_filepath. */
static void create_temp_path_for_usdz_export(const char *filepath,
blender::io::usd::ExportJobData *job)
{
char file[FILE_MAX];
BLI_split_file_part(filepath, file, FILE_MAX);
char *usdc_file = BLI_str_replaceN(file, ".usdz", ".usdc");
char usdc_temp_filepath[FILE_MAX];
BLI_path_join(usdc_temp_filepath, FILE_MAX, BKE_tempdir_session(), "USDZ", usdc_file);
BLI_strncpy(job->filepath, usdc_temp_filepath, strlen(usdc_temp_filepath) + 1);
BLI_strncpy(job->usdz_filepath, filepath, strlen(filepath) + 1);
MEM_freeN(usdc_file);
}
bool USD_export(bContext *C,
const char *filepath,
const USDExportParams *params,
@@ -194,10 +631,18 @@ bool USD_export(bContext *C,
blender::io::usd::ExportJobData *job = static_cast<blender::io::usd::ExportJobData *>(
MEM_mallocN(sizeof(blender::io::usd::ExportJobData), "ExportJobData"));
job->scene = scene;
job->bmain = CTX_data_main(C);
job->wm = CTX_wm_manager(C);
job->export_ok = false;
BLI_strncpy(job->filepath, filepath, sizeof(job->filepath));
job->is_usdz_export = false;
if (BLI_path_extension_check_n(filepath, ".usd", ".usda", ".usdc", NULL)) {
BLI_strncpy(job->filepath, filepath, sizeof(job->filepath));
}
else if (BLI_path_extension_check_n(filepath, ".usdz", NULL)) {
create_temp_path_for_usdz_export(filepath, job);
job->is_usdz_export = true;
}
job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
job->params = *params;
@@ -246,3 +691,12 @@ int USD_get_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_common.h"
#include "usd_hierarchy_iterator.h"
#include "usd_light_convert.h"
#include "usd_reader_geom.h"
#include "usd_reader_instance.h"
#include "usd_reader_prim.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_blender_version.h"
#include "BKE_cachefile.h"
@@ -86,6 +116,126 @@ static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_pa
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");
if (all_protos_collection) {
all_protos_collection->flag |= COLLECTION_HIDE_VIEWPORT;
all_protos_collection->flag |= COLLECTION_HIDE_RENDER;
}
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());
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
* imported objects with Z-up, if necessary */
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings)
@@ -134,8 +284,69 @@ struct ImportJobData {
bool was_canceled;
bool import_ok;
timeit::TimePoint start_time;
wmJob *wm_job;
};
static void main_thread_lock_acquire(ImportJobData *data)
{
if (data->wm_job) {
WM_job_main_thread_lock_acquire(data->wm_job);
}
}
static void main_thread_lock_release(ImportJobData *data)
{
if (data->wm_job) {
WM_job_main_thread_lock_release(data->wm_job);
}
}
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->filepath)));
/* 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->filepath);
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 report_job_duration(const ImportJobData *data)
{
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
@@ -175,21 +386,7 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
data->view_layer, import_collection);
}
BLI_path_abs(data->filepath, BKE_main_blendfile_path_from_global());
CacheFile *cache_file = static_cast<CacheFile *>(
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filepath)));
/* 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->filepath);
data->settings.cache_file = cache_file;
USD_path_abs(data->filepath, BKE_main_blendfile_path_from_global(), true);
*data->do_update = true;
*data->progress = 0.05f;
@@ -202,8 +399,21 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
*data->do_update = true;
*data->progress = 0.1f;
pxr::UsdStageRefPtr stage = pxr::UsdStage::Open(data->filepath);
std::string prim_path_mask(data->params.prim_path_mask);
pxr::UsdStagePopulationMask pop_mask;
if (!prim_path_mask.empty()) {
const std::vector<std::string> mask_tokens = pxr::TfStringTokenize(prim_path_mask, " ,;");
for (const std::string &tok : mask_tokens) {
pxr::SdfPath prim_path(tok);
if (!prim_path.IsEmpty()) {
pop_mask.Add(prim_path);
}
}
}
pxr::UsdStageRefPtr stage = pop_mask.IsEmpty() ?
pxr::UsdStage::Open(data->filepath) :
pxr::UsdStage::OpenMasked(data->filepath, pop_mask);
if (!stage) {
WM_reportf(RPT_ERROR, "USD Import: unable to open stage to read %s", data->filepath);
data->import_ok = false;
@@ -214,6 +424,11 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
convert_to_z_up(stage, &data->settings);
data->settings.stage_meters_per_unit = UsdGeomGetStageMetersPerUnit(stage);
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. */
if (data->params.set_frame_range) {
data->scene->r.sfra = stage->GetStartTimeCode();
@@ -229,6 +444,12 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
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());
}
if (data->params.import_materials && data->params.import_all_materials) {
archive->import_all_materials(data->bmain);
}
@@ -239,6 +460,56 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
const float size = float(archive->readers().size());
size_t i = 0;
/* 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? */
/* Create prototype objects.
* TODO(makowalski): Sort prototype objects by name, as below? */
for (const auto &pair : archive->proto_readers()) {
for (USDPrimReader *reader : pair.second) {
if (reader) {
reader->create_object(data->bmain, 0.0);
}
}
}
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. */
}
}
/* Sort readers by name: when creating a lot of objects in Blender,
* it is much faster if the order is sorted by name. */
archive->sort_readers();
@@ -257,6 +528,17 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
}
}
*data->do_update = true;
*data->progress = 0.5f;
/* Reading materials may trigger adding event notifiers, which
* isn't thread safe when the importer is invoked in a background
* job. We therefore acquire the main thread lock before reading
* object data, to avoid possible crashes when events are added
* in job timers for progress updates in the main thread.
* (See wm_jobs_timer()). */
main_thread_lock_acquire(data);
/* Setup parenthood and read actual object data. */
i = 0;
for (USDPrimReader *reader : archive->readers()) {
@@ -269,6 +551,8 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
reader->read_object_data(data->bmain, 0.0);
apply_cache_file(reader, data, &cache_file);
USDPrimReader *parent = reader->parent();
if (parent == nullptr) {
@@ -278,15 +562,26 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float
ob->parent = parent->object();
}
*data->progress = 0.5f + 0.5f * (++i / size);
*data->do_update = true;
if ((++i & 255) == 0) {
main_thread_lock_release(data);
*data->progress = 0.5f + 0.5f * (i / size);
*data->do_update = true;
main_thread_lock_acquire(data);
}
if (G.is_break) {
data->was_canceled = true;
main_thread_lock_release(data);
return;
}
}
main_thread_lock_release(data);
if (data->params.import_skeletons) {
archive->process_armature_modifiers();
}
data->import_ok = !data->was_canceled;
*progress = 1.0f;
@@ -312,6 +607,17 @@ static void import_endjob(void *customdata)
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) {
Base *base;
@@ -323,6 +629,14 @@ static void import_endjob(void *customdata)
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());
}
/* Add all objects to the collection. */
for (USDPrimReader *reader : data->archive->readers()) {
if (!reader) {
@@ -357,6 +671,9 @@ static void import_endjob(void *customdata)
}
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);
if (data->params.import_materials && data->params.import_all_materials) {
@@ -376,6 +693,8 @@ static void import_endjob(void *customdata)
break;
}
MEM_freeN(data->params.prim_path_mask);
WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene);
report_job_duration(data);
}
@@ -434,6 +753,8 @@ bool USD_import(struct bContext *C,
WM_JOB_PROGRESS,
WM_JOB_TYPE_ALEMBIC);
job->wm_job = wm_job;
/* setup job */
WM_jobs_customdata_set(wm_job, job, import_freejob);
WM_jobs_timer(wm_job, 0.1, NC_SCENE, NC_SCENE);
@@ -534,10 +855,11 @@ CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle,
}
/* 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) {
/* This object is not supported. */
/* This object is not supported */
return nullptr;
}
usd_reader->object(object);

View File

@@ -2,10 +2,17 @@
* Copyright 2021 Blender Foundation. All rights reserved. */
#include "usd_common.h"
#include "usd.h"
#include <pxr/usd/ar/resolver.h>
#include <pxr/base/plug/registry.h>
#include "BKE_appdir.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "WM_api.h"
#include "WM_types.h"
namespace blender::io::usd {

View File

@@ -6,6 +6,8 @@
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/common.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
struct Depsgraph;
struct Main;
@@ -21,6 +23,11 @@ struct USDExporterContext {
const pxr::SdfPath usd_path;
const USDHierarchyIterator *hierarchy_iterator;
const USDExportParams &export_params;
template<typename T> T usd_define_or_over(pxr::SdfPath path) const
{
return (export_params.export_as_overs) ? T(stage->OverridePrim(path)) : T::Define(stage, path);
}
};
} // namespace blender::io::usd

View File

@@ -4,11 +4,17 @@
#include "usd_hierarchy_iterator.h"
#include "usd_writer_abstract.h"
#include "usd_writer_armature.h"
#include "usd_writer_blendshape_mesh.h"
#include "usd_writer_camera.h"
#include "usd_writer_curve.h"
#include "usd_writer_hair.h"
#include "usd_writer_light.h"
#include "usd_writer_mesh.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_volume.h"
@@ -75,48 +81,110 @@ const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
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{
bmain_, 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 = true;
if (this->params_.export_armatures &&
(is_skinned_mesh(context->object) || context->object->type == OB_ARMATURE)) {
can_merge_with_xform = false;
}
if (this->params_.export_blendshapes && is_blendshape_mesh(context->object)) {
can_merge_with_xform = false;
}
if (can_merge_with_xform && mergeTransformAndShape)
prim_path = prim_path.GetParentPath();
return USDExporterContext{bmain_, depsgraph_, stage_, prim_path, this, params_};
}
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
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));
}
if (this->params_.export_blendshapes && is_blendshape_mesh(context->object)) {
return new USDSkelRootWriter(create_usd_export_context(context));
}
return new USDTransformWriter(create_usd_export_context(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;
switch (context->object->type) {
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 if (usd_export_context.export_params.export_blendshapes &&
is_blendshape_mesh(context->object)) {
data_writer = new USDBlendShapeMeshWriter(usd_export_context);
}
else {
data_writer = new USDMeshWriter(usd_export_context);
}
}
else
return nullptr;
break;
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;
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;
case OB_MBALL:
data_writer = new USDMetaballWriter(usd_export_context);
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_VOLUME:
data_writer = new USDVolumeWriter(usd_export_context);
break;
case OB_EMPTY:
case OB_CURVES_LEGACY:
case OB_SURF:
case OB_FONT:
case OB_SPEAKER:
case OB_LIGHTPROBE:
case OB_LATTICE:
case OB_ARMATURE:
case OB_GPENCIL:
case OB_POINTCLOUD:
case OB_CURVES:
@@ -129,7 +197,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
return nullptr;
}
if (!data_writer->is_supported(context)) {
if (data_writer && !data_writer->is_supported(context)) {
delete data_writer;
return nullptr;
}
@@ -139,6 +207,10 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
{
if (context->is_instance() && params_.use_instancing) {
return nullptr;
}
if (!params_.export_hair) {
return nullptr;
}
@@ -146,9 +218,36 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const Hierarch
}
AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(
const HierarchyContext * /*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

View File

@@ -51,8 +51,12 @@ class USDHierarchyIterator : public AbstractHierarchyIterator {
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:
USDExporterContext create_usd_export_context(const HierarchyContext *context);
USDExporterContext create_usd_export_context(const HierarchyContext *context,
bool mergeTransformAndShape = false);
};
} // namespace blender::io::usd

View File

@@ -0,0 +1,604 @@
/*
* 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_asset_utils.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_fileops.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;
}
/* Optionally copy the asset if it's inside a USDZ package or has a URI. */
const bool import_textures = params.import_textures_mode != USD_TEX_IMPORT_NONE &&
should_import_asset(tex_path_str);
if (import_textures) {
/* If we are packing the imported textures, we first write them
* to a temporary directory. */
const char *textures_dir = params.import_textures_mode == USD_TEX_IMPORT_PACK ?
temp_textures_dir() :
params.import_textures_dir;
const eUSDTexNameCollisionMode name_collision_mode = params.import_textures_mode ==
USD_TEX_IMPORT_PACK ?
USD_TEX_NAME_COLLISION_OVERWRITE :
params.tex_name_collision_mode;
tex_path_str = import_asset(tex_path_str.c_str(), textures_dir, name_collision_mode);
}
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;
if (import_textures && params.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image)) {
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain, &image->id));
if (BLI_is_dir(temp_textures_dir())) {
BLI_delete(temp_textures_dir(), true, true);
}
}
/* 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)
{
Camera *bcam = (Camera *)object_->data;
if (!object_->data) {
return;
}
Camera *bcam = static_cast<Camera *>(object_->data);
pxr::UsdGeomCamera cam_prim(prim_);
@@ -35,24 +39,15 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim
return;
}
pxr::VtValue val;
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);
pxr::GfCamera usd_cam = cam_prim.GetCamera(motionSampleTime);
const float apperture_x = usd_cam.GetHorizontalAperture();
const float apperture_y = usd_cam.GetVerticalAperture();
const float h_film_offset = usd_cam.GetHorizontalApertureOffset();
const float v_film_offset = usd_cam.GetVerticalApertureOffset();
const float film_aspect = apperture_x / apperture_y;
bcam->type = usd_cam.GetProjection() == pxr::GfCamera::Perspective ? CAM_PERSP : CAM_ORTHO;
/*
* For USD, these camera properties are in tenths of a world unit.
@@ -62,24 +57,28 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim
* val_in_millimeters = val_in_meters * 1000
*/
const double scale_to_mm = 100.0 * settings_->stage_meters_per_unit;
bcam->lens = val.Get<float>() * scale_to_mm;
bcam->sensor_x = horAp.Get<float>() * scale_to_mm;
bcam->sensor_y = verAp.Get<float>() * scale_to_mm;
bcam->shiftx = verApOffset.Get<float>() * scale_to_mm;
bcam->shifty = horApOffset.Get<float>() * scale_to_mm;
bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP :
CAM_ORTHO;
bcam->lens = usd_cam.GetFocalLength() * scale_to_mm;
/* Calling UncheckedGet() to silence compiler warnings. */
bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[0]);
bcam->clip_end = clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[1];
bcam->sensor_x = apperture_x * scale_to_mm;
bcam->sensor_y = apperture_y * scale_to_mm;
bcam->dof.focus_distance = focalDistanceVal.Get<float>();
bcam->dof.aperture_fstop = float(fstopVal.Get<float>());
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) {
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);

View File

@@ -20,18 +20,34 @@
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()
{
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
BLI_addtail(&object_->modifiers, md);
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);
/* Defer creating modifiers until a cache file is provided. */
needs_cachefile_ = true;
}
void USDGeomReader::add_subdiv_modifier()

View File

@@ -10,12 +10,14 @@ struct Mesh;
namespace blender::io::usd {
class USDGeomReader : public USDXformReader {
private:
bool needs_cachefile_;
public:
USDGeomReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
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;
}
bool needs_cachefile() override
{
return needs_cachefile_ || USDXformReader::needs_cachefile();
}
void apply_cache_file(CacheFile *cache_file) override;
void add_cache_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. */
#include "usd_reader_light.h"
#include "usd_light_convert.h"
#include "BKE_light.h"
#include "BKE_object.h"
@@ -9,6 +10,7 @@
#include "DNA_light_types.h"
#include "DNA_object_types.h"
#include <pxr/usd/usdGeom/metrics.h>
#include <pxr/usd/usdLux/diskLight.h>
#include <pxr/usd/usdLux/distantLight.h>
#include <pxr/usd/usdLux/rectLight.h>
@@ -17,8 +19,52 @@
#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 {
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 */)
{
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. */
if (pxr::UsdAttribute intensity_attr = light_api.GetIntensityAttr()) {
float intensity = 0.0f;
if (intensity_attr.Get(&intensity, motionSampleTime)) {
blight->energy = intensity * this->import_params_.light_intensity_scale;
}
}
/* In USD 21, light attributes were renamed to have an 'inputs:' prefix
* (e.g., 'inputs:intensity'). Here and below, for backward compatibility
* with older USD versions, we also query attributes using the previous
* naming scheme that omits this prefix. */
/* TODO(makowalsk): Not currently supported. */
#if 0
@@ -95,20 +139,18 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime);
#endif
if (pxr::UsdAttribute spec_attr = light_api.GetSpecularAttr()) {
float spec = 0.0f;
if (spec_attr.Get(&spec, motionSampleTime)) {
blight->spec_fac = spec;
}
float specular;
if (get_authored_value(light_api.GetSpecularAttr(), motionSampleTime, &specular) ||
prim_.GetAttribute(usdtokens::specular).Get(&specular, motionSampleTime)) {
blight->spec_fac = specular;
}
if (pxr::UsdAttribute color_attr = light_api.GetColorAttr()) {
pxr::GfVec3f color;
if (color_attr.Get(&color, motionSampleTime)) {
blight->r = color[0];
blight->g = color[1];
blight->b = color[2];
}
pxr::GfVec3f color;
if (get_authored_value(light_api.GetColorAttr(), motionSampleTime, &color) ||
prim_.GetAttribute(usdtokens::color).Get(&color, motionSampleTime)) {
blight->r = color[0];
blight->g = color[1];
blight->b = color[2];
}
/* 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);
#endif
// XXX - apply scene scale to local and spot lights but not area lights (?)
switch (blight->type) {
case LA_AREA:
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;
}
if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) {
float width = 0.0f;
if (width_attr.Get(&width, motionSampleTime)) {
blight->area_size = width;
}
float width;
if (get_authored_value(rect_light.GetWidthAttr(), motionSampleTime, &width) ||
prim_.GetAttribute(usdtokens::width).Get(&width, motionSampleTime)) {
blight->area_size = width;
}
if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) {
float height = 0.0f;
if (height_attr.Get(&height, motionSampleTime)) {
blight->area_sizey = height;
}
float height;
if (get_authored_value(rect_light.GetHeightAttr(), motionSampleTime, &height) ||
prim_.GetAttribute(usdtokens::height).Get(&height, motionSampleTime)) {
blight->area_sizey = height;
}
}
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;
}
if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) {
float radius = 0.0f;
if (radius_attr.Get(&radius, motionSampleTime)) {
blight->area_size = radius * 2.0f;
}
float radius;
if (get_authored_value(disk_light.GetRadiusAttr(), motionSampleTime, &radius) ||
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->area_size = radius * 2.0f;
}
}
break;
@@ -172,28 +212,25 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break;
}
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) {
float radius = 0.0f;
if (radius_attr.Get(&radius, motionSampleTime)) {
blight->radius = radius;
}
float radius;
if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->radius = radius;
}
}
break;
case LA_SPOT:
if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
pxr::UsdLuxSphereLight sphere_light(prim_);
if (!sphere_light) {
break;
}
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) {
float radius = 0.0f;
if (radius_attr.Get(&radius, motionSampleTime)) {
blight->radius = radius;
}
float radius;
if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
blight->radius = radius;
}
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()) {
float cone_angle = 0.0f;
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,37 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
break;
}
if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) {
float angle = 0.0f;
if (angle_attr.Get(&angle, motionSampleTime)) {
blight->sun_angle = angle * float(M_PI) / 180.0f;
}
float angle;
if (get_authored_value(distant_light.GetAngleAttr(), motionSampleTime, &angle) ||
prim_.GetAttribute(usdtokens::angle).Get(&angle, motionSampleTime)) {
blight->sun_angle = angle * float(M_PI) / 180.0f;
}
}
break;
}
const float meters_per_unit = static_cast<float>(
pxr::UsdGeomGetStageMetersPerUnit(prim_.GetStage()));
const float radius_scale = meters_per_unit * usd_world_scale_;
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) {
intensity_scale *= nits_to_energy_scale_factor(blight, radius_scale);
}
blight->energy = intensity * intensity_scale;
}
if ((blight->type == LA_SPOT || blight->type == LA_LOCAL) && import_params_.scale_light_radius) {
blight->area_size *= radius_scale;
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}

View File

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

View File

@@ -3,6 +3,9 @@
#include "usd_reader_material.h"
#include "usd_umm.h"
#include "usd_asset_utils.h"
#include "BKE_appdir.h"
@@ -21,8 +24,12 @@
#include "DNA_material_types.h"
#include "WM_api.h"
#include <pxr/base/gf/vec3f.h>
#include <pxr/usd/ar/resolver.h>
#include <pxr/usd/ar/packageUtils.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/shader.h>
@@ -60,6 +67,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);
/* 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. */
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
@@ -67,23 +81,6 @@ static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
static const pxr::TfToken UsdUVTexture("UsdUVTexture", pxr::TfToken::Immortal);
} // namespace usdtokens
/* Temporary folder for saving imported textures prior to packing.
* CAUTION: this directory is recursively deleted after material
* import. */
static const char *temp_textures_dir()
{
static bool inited = false;
static char temp_dir[FILE_MAXDIR] = {'\0'};
if (!inited) {
BLI_path_join(temp_dir, sizeof(temp_dir), BKE_tempdir_session(), "usd_textures_tmp", SEP_STR);
inited = true;
}
return temp_dir;
}
/* Add a node of the given type at the given location coordinates. */
static bNode *add_node(
const bContext *C, bNodeTree *ntree, const int type, const float locx, const float locy)
@@ -239,6 +236,40 @@ static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader
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
* of the given material. Returns true if a UsdPreviewSurface source was found
* and returns false otherwise. */
@@ -346,11 +377,32 @@ Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_mater
* if there is one. */
pxr::UsdShadeShader 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);
}
/* Optionally, create shader nodes to represent a UsdPreviewSurface. */
if (params_.import_usd_preview) {
if (params_.import_shaders_mode == USD_IMPORT_USD_PREVIEW_SURFACE && 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;
bool mdl_imported = false;
#ifdef WITH_PYTHON
/* Invoke UMM to convert to MDL. */
mdl_imported = umm_import_mdl_material(params_, mtl, usd_material, true /* Verbose */, &has_mdl);
if (params_.import_textures_mode == USD_TEX_IMPORT_PACK) {
/* Process the imported material to pack the textures. */
pack_imported_textures(mtl);
}
#endif
if (!(has_mdl && mdl_imported) && usd_preview) {
/* The material has no MDL shader or we couldn't convert the MDL,
* so fall back on importing UsdPreviewSuface. */
WM_reportf(RPT_INFO, "Couldn't import MDL shader for material %s, importing USD Preview Surface shaders instead",
mtl_name.c_str());
import_usd_preview(mtl, usd_preview);
}
}
@@ -665,6 +717,22 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
return;
}
/* 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;
}
}
pxr::VtValue file_val;
if (!file_input.Get(&file_val) || !file_val.IsHolding<pxr::SdfAssetPath>()) {
std::cerr << "WARNING: Couldn't get file input value for USD shader " << usd_shader.GetPath()
@@ -673,16 +741,45 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
}
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
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() && is_udim_path(file_path)) {
/* Texture paths are frequently relative to the USD, so get
* the absolute path. */
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
/* SdfLayer::ComputeAbsolutePath() doesn' work for context-dependent paths
* where the file name has a UDIM token (e.g., '0/foo.<UDIM>.png').
* We therefore compute the absolube path of the parent directory of the
* UDIM file. */
char file[FILE_MAXFILE];
char dir[FILE_MAXDIR];
BLI_split_dirfile(file_path.c_str(), dir, file, sizeof(dir), sizeof(file));
if (strlen(dir) == 0) {
/* No directory in path, assume asset is a sibling of the layer. */
dir[0] = '.';
dir[1] = '\0';
}
/* Get the absolute path of the directory relative to the layer. */
std::string dir_abs_path = layer_handle->ComputeAbsolutePath(dir);
char result[FILE_MAX];
/* Finally, join the original file name with the absolute path. */
BLI_path_join(result, FILE_MAX, dir_abs_path.c_str(), file);
/* Use forward slashes. */
BLI_str_replace_char(result, SEP, ALTSEP);
file_path = result;
}
}
}
@@ -695,7 +792,7 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
/* Optionally copy the asset if it's inside a USDZ package. */
const bool import_textures = params_.import_textures_mode != USD_TEX_IMPORT_NONE &&
pxr::ArIsPackageRelativePath(file_path);
should_import_asset(file_path);
if (import_textures) {
/* If we are packing the imported textures, we first write them
@@ -721,7 +818,9 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
}
const char *im_file = file_path.c_str();
Image *image = BKE_image_load_exists(bmain_, im_file);
if (!image) {
std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node."
<< std::endl;
@@ -743,12 +842,17 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
if (color_space.IsEmpty()) {
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)) {
STRNCPY(image->colorspace_settings.name, "Raw");
}
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
storage->extension = get_image_extension(usd_shader, storage->extension);
if (import_textures && params_.import_textures_mode == USD_TEX_IMPORT_PACK &&
!BKE_image_has_packedfile(image)) {
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain_, &image->id));
@@ -801,6 +905,39 @@ void USDMaterialReader::convert_usd_primvar_reader_float2(
link_nodes(ntree, uv_map, "UV", dest_node, dest_socket_name);
}
void USDMaterialReader::pack_imported_textures(Material *material, bool delete_temp_textures_dir) const
{
if (!(material && material->use_nodes)) {
return;
}
for (bNode *node = (bNode *)material->nodetree->nodes.first; node; node = node->next) {
if (!(ELEM(node->type, SH_NODE_TEX_IMAGE, SH_NODE_TEX_ENVIRONMENT))) {
continue;
}
Image *image = reinterpret_cast<Image *>(node->id);
if (!image || BKE_image_has_packedfile(image)) {
continue;
}
if (image->filepath[0] == '\0') {
continue;
}
char dir_path[FILE_MAXDIR];
BLI_split_dir_part(image->filepath, dir_path, sizeof(dir_path));
if (BLI_path_cmp_normalized(dir_path, temp_textures_dir()) == 0) {
/* Texture was saved to the temporary import directory, so pack it. */
BKE_image_packfiles(nullptr, image, ID_BLEND_PATH(bmain_, &image->id));
}
}
if (delete_temp_textures_dir && BLI_is_dir(temp_textures_dir())) {
BLI_delete(temp_textures_dir(), true, true);
}
}
void build_material_map(const Main *bmain, std::map<std::string, Material *> *r_mat_map)
{
BLI_assert_msg(r_mat_map, "...");

View File

@@ -53,7 +53,7 @@ struct NodePlacementContext {
*
* - #UsdPreviewSurface -> Principled BSDF
* - #UsdUVTexture -> Texture Image + Normal Map
* - UsdPrimvarReader_float2 -> UV Map
* - #UsdPrimvarReader_float2 -> UV Map
*
* Limitations: arbitrary primvar readers or UsdTransform2d not yet
* supported. For #UsdUVTexture, only the file, st and #sourceColorSpace
@@ -62,12 +62,10 @@ struct NodePlacementContext {
* TODO(makowalski): Investigate adding support for converting additional
* shaders and inputs. Supporting certain types of inputs, such as texture
* scale and bias, will probably require creating Blender Group nodes with
* the corresponding inputs.
*/
* the corresponding inputs. */
class USDMaterialReader {
protected:
USDImportParams params_;
Main *bmain_;
public:
@@ -129,6 +127,12 @@ class USDMaterialReader {
bNodeTree *ntree,
int column,
NodePlacementContext *r_ctx) const;
/**
* Pack imported textures referenced by this material and optionally delete
* the temporary textures import directory when done processing.
*/
void pack_imported_textures(Material *material, bool delete_temp_textures_dir=true) const;
};
/* Utility functions. */

View File

@@ -5,6 +5,7 @@
#include "usd_reader_mesh.h"
#include "usd_reader_material.h"
#include "usd_skel_convert.h"
#include "BKE_attribute.hh"
#include "BKE_customdata.h"
@@ -17,7 +18,6 @@
#include "BLI_math_geom.h"
#include "BLI_math_vector_types.hh"
#include "BLI_span.hh"
#include "BLI_string.h"
#include "DNA_customdata_types.h"
#include "DNA_material_types.h"
@@ -28,6 +28,8 @@
#include "MEM_guardedalloc.h"
#include "WM_api.h"
#include <pxr/base/vt/array.h>
#include <pxr/base/vt/types.h>
#include <pxr/base/vt/value.h>
@@ -36,6 +38,7 @@
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdGeom/subset.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <iostream>
@@ -221,6 +224,14 @@ void USDMeshReader::read_object_data(Main *bmain, const double motionSampleTime)
}
}
if (import_params_.import_blendshapes) {
import_blendshapes(bmain, object_, prim_);
}
if (import_params_.import_skeletons) {
import_skel_bindings(bmain, object_, prim_);
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}
@@ -266,9 +277,25 @@ void USDMeshReader::read_mpolys(Mesh *mesh)
int loop_index = 0;
std::vector<int> degenerate_faces;
for (int i = 0; i < face_counts_.size(); i++) {
const int face_size = face_counts_[i];
/* Check for faces with the same vertex specified twice in a row. */
if (face_indices_[loop_index] == face_indices_[loop_index+face_size-1]) {
/* Loop below does not test first to last. */
degenerate_faces.push_back(i);
}
else {
for (int j = loop_index+1; j < loop_index + face_size; j++) {
if (face_indices_[j] == face_indices_[j-1]) {
degenerate_faces.push_back(i);
break;
}
}
}
MPoly &poly = polys[i];
poly.loopstart = loop_index;
poly.totloop = face_size;
@@ -291,6 +318,10 @@ void USDMeshReader::read_mpolys(Mesh *mesh)
}
BKE_mesh_calc_edges(mesh, false, false);
if (!degenerate_faces.empty() && !import_params_.validate_meshes) {
WM_reportf(RPT_WARNING, "Prim %s has degenerate faces-- please consider importing with Validate Meshes enabled.", prim_.GetName().GetText());
}
}
void USDMeshReader::read_uvs(Mesh *mesh, const double motionSampleTime, const bool load_uvs)
@@ -416,52 +447,95 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
return;
}
/* Early out if we read the display color before and if this attribute isn't animated. */
if (primvar_varying_map_.find(usdtokens::displayColor) != primvar_varying_map_.end() &&
!primvar_varying_map_.at(usdtokens::displayColor)) {
pxr::UsdGeomPrimvarsAPI primvarsAPI = pxr::UsdGeomPrimvarsAPI(mesh_prim_);
std::vector<pxr::UsdGeomPrimvar> primvars = primvarsAPI.GetPrimvarsWithValues();
pxr::TfToken active_color_name;
/* Convert all color primvars to custom layer data. */
for (pxr::UsdGeomPrimvar pv : primvars) {
if (!pv.HasValue()) {
continue;
}
pxr::SdfValueTypeName type = pv.GetTypeName();
if (!ELEM(type,
pxr::SdfValueTypeNames->Color3hArray,
pxr::SdfValueTypeNames->Color3fArray,
pxr::SdfValueTypeNames->Color3dArray)) {
continue;
}
pxr::TfToken name = pv.GetPrimvarName();
/* Set the active color name to 'displayColor', if a color primvar
* with this name exists. Otherwise, use the name of the first
* color primvar we find for the active color. */
if (active_color_name.IsEmpty() || name == usdtokens::displayColor) {
active_color_name = name;
}
/* Skip if we read this primvar before and it isn't animated. */
if (primvar_varying_map_.find(name) != primvar_varying_map_.end() &&
!primvar_varying_map_.at(name)) {
continue;
}
read_colors(mesh, pv, motionSampleTime);
}
if (!active_color_name.IsEmpty()) {
BKE_id_attributes_active_color_set(&mesh->id, active_color_name.GetText());
}
}
void USDMeshReader::read_colors(Mesh *mesh,
pxr::UsdGeomPrimvar &color_primvar,
double motionSampleTime)
{
if (!(mesh && color_primvar && color_primvar.HasValue())) {
return;
}
pxr::UsdGeomPrimvar color_primvar = mesh_prim_.GetDisplayColorPrimvar();
if (!color_primvar.HasValue()) {
return;
}
pxr::TfToken interp = color_primvar.GetInterpolation();
if (interp == pxr::UsdGeomTokens->varying) {
std::cerr << "WARNING: Unsupported varying interpolation for display colors\n" << std::endl;
return;
}
if (primvar_varying_map_.find(usdtokens::displayColor) == primvar_varying_map_.end()) {
if (primvar_varying_map_.find(color_primvar.GetPrimvarName()) == primvar_varying_map_.end()) {
bool might_be_time_varying = color_primvar.ValueMightBeTimeVarying();
primvar_varying_map_.insert(std::make_pair(usdtokens::displayColor, might_be_time_varying));
primvar_varying_map_.insert(std::make_pair(color_primvar.GetPrimvarName(), might_be_time_varying));
if (might_be_time_varying) {
is_time_varying_ = true;
}
}
pxr::VtArray<pxr::GfVec3f> display_colors;
pxr::VtArray<pxr::GfVec3f> usd_colors;
if (!color_primvar.ComputeFlattened(&display_colors, motionSampleTime)) {
std::cerr << "WARNING: Couldn't compute display colors\n" << std::endl;
if (!color_primvar.ComputeFlattened(&usd_colors, motionSampleTime)) {
WM_reportf(RPT_WARNING,
"USD Import: couldn't compute values for color primvar '%s'",
color_primvar.GetName().GetText());
return;
}
if ((interp == pxr::UsdGeomTokens->faceVarying && display_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->vertex && display_colors.size() != mesh->totvert) ||
(interp == pxr::UsdGeomTokens->constant && display_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && display_colors.size() != mesh->totpoly)) {
std::cerr << "WARNING: display colors count mismatch\n" << std::endl;
pxr::TfToken interp = color_primvar.GetInterpolation();
if ((interp == pxr::UsdGeomTokens->faceVarying && usd_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->varying && usd_colors.size() != mesh->totloop) ||
(interp == pxr::UsdGeomTokens->vertex && usd_colors.size() != mesh->totvert) ||
(interp == pxr::UsdGeomTokens->constant && usd_colors.size() != 1) ||
(interp == pxr::UsdGeomTokens->uniform && usd_colors.size() != mesh->totpoly)) {
WM_reportf(RPT_WARNING,
"USD Import: color primvar value '%s' count inconsistent with interpolation type",
color_primvar.GetName().GetText());
return;
}
void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_PROP_BYTE_COLOR);
void *cd_ptr = add_customdata_cb(mesh, color_primvar.GetBaseName().GetText(), CD_PROP_BYTE_COLOR);
if (!cd_ptr) {
std::cerr << "WARNING: Couldn't add displayColors custom data.\n";
WM_reportf(RPT_WARNING,
"USD Import: couldn't add color custom data '%s'",
color_primvar.GetBaseName().GetText());
return;
}
@@ -474,14 +548,16 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
for (int j = 0; j < poly.totloop; ++j) {
int loop_index = poly.loopstart + j;
/* Default for constant varying interpolation. */
/* Default for constant interpolation. */
int usd_index = 0;
if (interp == pxr::UsdGeomTokens->vertex) {
usd_index = loops[loop_index].v;
}
else if (interp == pxr::UsdGeomTokens->faceVarying) {
else if (interp == pxr::UsdGeomTokens->faceVarying ||
interp == pxr::UsdGeomTokens->varying) {
usd_index = poly.loopstart;
if (is_left_handed_) {
usd_index += poly.totloop - 1 - j;
}
@@ -494,13 +570,13 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
usd_index = i;
}
if (usd_index >= display_colors.size()) {
if (usd_index >= usd_colors.size()) {
continue;
}
colors[loop_index].r = unit_float_to_uchar_clamp(display_colors[usd_index][0]);
colors[loop_index].g = unit_float_to_uchar_clamp(display_colors[usd_index][1]);
colors[loop_index].b = unit_float_to_uchar_clamp(display_colors[usd_index][2]);
colors[loop_index].r = unit_float_to_uchar_clamp(usd_colors[usd_index][0]);
colors[loop_index].g = unit_float_to_uchar_clamp(usd_colors[usd_index][1]);
colors[loop_index].b = unit_float_to_uchar_clamp(usd_colors[usd_index][2]);
colors[loop_index].a = unit_float_to_uchar_clamp(1.0);
}
}
@@ -701,6 +777,7 @@ void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
for (const pxr::UsdGeomSubset &subset : subsets) {
pxr::UsdShadeMaterial subset_mtl = utils::compute_bound_material(subset.GetPrim());
if (!subset_mtl) {
continue;
}
@@ -728,7 +805,6 @@ void USDMeshReader::assign_facesets_to_material_indices(double motionSampleTime,
}
if (r_mat_map->empty()) {
pxr::UsdShadeMaterial mtl = utils::compute_bound_material(prim_);
if (mtl) {
pxr::SdfPath mtl_path = mtl.GetPath();
@@ -753,10 +829,12 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot
"material_index", ATTR_DOMAIN_FACE);
this->assign_facesets_to_material_indices(motionSampleTime, material_indices.span, &mat_map);
material_indices.finish();
/* Build material name map if it's not built yet. */
if (this->settings_->mat_name_to_mat.empty()) {
build_material_map(bmain, &this->settings_->mat_name_to_mat);
}
utils::assign_materials(bmain,
object_,
mat_map,
@@ -838,7 +916,10 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
* the topology is consistent, as in the Alembic importer. */
ImportSettings settings;
settings.read_flag |= read_flag;
if (settings_) {
settings.validate_meshes = settings_->validate_meshes;
}
settings.read_flag |= read_flag; settings.read_flag |= read_flag;
if (topology_changed(existing_mesh, motionSampleTime)) {
new_mesh = true;
@@ -867,7 +948,138 @@ Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
}
}
if (settings.validate_meshes) {
if (BKE_mesh_validate(active_mesh, false, false)) {
WM_reportf(RPT_INFO, "Fixed mesh for prim: %s", mesh_prim_.GetPath().GetText());
}
}
return active_mesh;
}
std::string USDMeshReader::get_skeleton_path() const
{
if (!prim_) {
return "";
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_);
if (!skel_api) {
return "";
}
if (pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton()) {
return skel.GetPath().GetAsString();
}
return "";
}
/* Return a local transform to place the mesh in its world bind position.
* In some cases, the bind transform and prim world transform might be
* be different, in which case we must adjust the local transform
* to ensure the mesh is correctly aligned for bininding. A use
* case where this might be needed is if a skel animation is exported
* from Blender and both the skeleton and mesh are transformed in Create
* or another DCC, without modifying the original mesh bind transform. */
bool USDMeshReader::get_geom_bind_xform_correction(const pxr::GfMatrix4d &bind_xf,
pxr::GfMatrix4d *r_xform,
const float time) const
{
if (!r_xform) {
return false;
}
pxr::GfMatrix4d world_xf = get_world_matrix(prim_, time);
if (pxr::GfIsClose(bind_xf, world_xf, .000000001)) {
/* The world and bind matrices are equal, so we don't
* need to correct the local transfor. Get the transform
* with the standard API. */
pxr::UsdGeomXformable xformable;
if (use_parent_xform_) {
xformable = pxr::UsdGeomXformable(prim_.GetParent());
}
else {
xformable = pxr::UsdGeomXformable(prim_);
}
if (!xformable) {
/* This shouldn't happen. */
*r_xform = pxr::GfMatrix4d(1.0);
return false;
}
bool reset_xform_stack;
return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time);
}
/* If we got here, then the bind transform and prim
* world transform differ, so we must adjust the local
* transform to ensure the mesh is aligned in the correct
* bind position */
pxr::GfMatrix4d parent_world_xf(1.0);
pxr::UsdPrim parent;
if (use_parent_xform_) {
if (prim_.GetParent()) {
parent = prim_.GetParent().GetParent();
}
}
else {
parent = prim_.GetParent();
}
if (parent) {
parent_world_xf = get_world_matrix(parent, time);
}
pxr::GfMatrix4d corrected_local_xf = bind_xf * parent_world_xf.GetInverse();
*r_xform = corrected_local_xf;
return true;
}
/* Override transform computation to account for the binding
* transformation for skinned meshes. */
bool USDMeshReader::get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const
{
if (!r_xform) {
return false;
}
if (!import_params_.import_skeletons) {
/* Use the standard transform computation, since we are ignoring
* skinning data. */
return USDXformReader::get_local_usd_xform(r_xform, r_is_constant, time);
}
if (!(prim_.IsInstanceProxy() || prim_.IsInPrototype())) {
if (pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim_)) {
if (skel_api.GetGeomBindTransformAttr().HasAuthoredValue()) {
pxr::GfMatrix4d bind_xf;
if (skel_api.GetGeomBindTransformAttr().Get(&bind_xf)) {
/* Assume that if a bind transform is defined, then the
* transform is constant. */
if (r_is_constant) {
*r_is_constant = true;
}
return get_geom_bind_xform_correction(bind_xf, r_xform, time);
}
else {
std::cout << "WARNING: couldn't compute geom bind transform for " << prim_.GetPath()
<< std::endl;
}
}
}
}
return USDXformReader::get_local_usd_xform(r_xform, r_is_constant, time);
}
} // namespace blender::io::usd

View File

@@ -55,6 +55,8 @@ class USDMeshReader : public USDGeomReader {
bool topology_changed(const Mesh *existing_mesh, double motionSampleTime) override;
std::string get_skeleton_path() const;
private:
void process_normals_vertex_varying(Mesh *mesh);
void process_normals_face_varying(Mesh *mesh);
@@ -68,12 +70,21 @@ class USDMeshReader : public USDGeomReader {
void read_mpolys(Mesh *mesh);
void read_uvs(Mesh *mesh, double motionSampleTime, bool load_uvs = false);
void read_colors(Mesh *mesh, double motionSampleTime);
void read_colors(Mesh *mesh, pxr::UsdGeomPrimvar &color_primvar, double motionSampleTime);
void read_vertex_creases(Mesh *mesh, double motionSampleTime);
void read_mesh_sample(ImportSettings *settings,
Mesh *mesh,
double motionSampleTime,
bool new_mesh);
bool get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const override;
bool get_geom_bind_xform_correction(const pxr::GfMatrix4d &bind_xf,
pxr::GfMatrix4d *r_xform,
const float time) const;
};
} // namespace blender::io::usd

View File

@@ -4,10 +4,238 @@
#include "usd_reader_prim.h"
#include "BKE_idprop.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 {
/* 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,
const USDImportParams &import_params,
const ImportSettings &settings)
@@ -29,6 +257,17 @@ const pxr::UsdPrim &USDPrimReader::prim() const
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
{
return object_;

View File

@@ -36,8 +36,6 @@ struct ImportSettings {
bool validate_meshes;
CacheFile *cache_file;
/* Map a USD material prim path to a Blender material name.
* This map is updated by readers during stage traversal.
* This field is mutable because it is used to keep track
@@ -64,7 +62,6 @@ struct ImportSettings {
sequence_offset(0),
read_flag(0),
validate_meshes(false),
cache_file(NULL),
stage_meters_per_unit(1.0)
{
}
@@ -95,7 +92,15 @@ class USDPrimReader {
virtual bool valid() const;
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;
void object(Object *ob);
@@ -130,6 +135,9 @@ class USDPrimReader {
{
return prim_path_;
}
protected:
void set_props(ID *id, const pxr::UsdPrim &prim, double motionSampleTime);
};
} // namespace blender::io::usd

View File

@@ -0,0 +1,373 @@
/*
* 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_skeleton.h"
#include "usd_skel_convert.h"
#include "BKE_idprop.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_fcurve.h"
#include "BKE_object.h"
#include "BLI_math.h"
#include "BLI_string.h"
#include "BLI_string_utils.h"
#include "DNA_armature_types.h"
#include "DNA_object_types.h"
#include "ED_armature.h"
#include "ED_keyframing.h"
#include "MEM_guardedalloc.h"
#include "WM_api.h"
#include <pxr/pxr.h>
#include <pxr/usd/usdSkel/cache.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
#include <iostream>
namespace blender::io::usd {
/* Debugging utility to print the given skeleton's joint paths. */
static void print_joints(pxr::UsdSkelSkeleton &skel,
const double motionSampleTime)
{
if (!skel) {
return;
}
if (pxr::UsdAttribute joints_attr = skel.GetJointsAttr()) {
pxr::VtArray<pxr::TfToken> joints;
if (joints_attr.Get(&joints, motionSampleTime)) {
std::cout << "Num joints " << joints.size() << std::endl;
for (pxr::TfToken &joint : joints) {
pxr::SdfPath joint_path(joint);
std::cout << joint_path << std::endl;
}
}
}
}
/* Code from the Collada importer's AnimationImporter class. */
static FCurve *create_fcurve(int array_index, const char *rna_path)
{
FCurve *fcu = BKE_fcurve_create();
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
fcu->array_index = array_index;
return fcu;
}
/* Code from the Collada importer's AnimationImporter class. */
static void add_bezt(FCurve *fcu,
float frame,
float value,
eBezTriple_Interpolation ipo = BEZT_IPO_LIN)
{
BezTriple bez;
memset(&bez, 0, sizeof(BezTriple));
bez.vec[1][0] = frame;
bez.vec[1][1] = value;
bez.ipo = ipo; /* use default interpolation mode here... */
bez.f1 = bez.f2 = bez.f3 = SELECT;
bez.h1 = bez.h2 = HD_AUTO;
insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
BKE_fcurve_handles_recalc(fcu);
}
/* Example code for adding a bone and creating curves for
* animating its translation. */
static void test_add_bone_anim(Main *bmain, Object *obj)
{
if (!obj || !obj->data || !bmain) {
return;
}
bArmature *arm = static_cast<bArmature *>(obj->data);
ED_armature_to_edit(arm);
const char *bone_name = "Bone";
EditBone * bone = ED_armature_ebone_add(arm, bone_name);
/* Blender will cull zero-length bones, so we give
* the bone an arbitrary size. */
float v0[3] = { 0.0f, 0.0f, 0.0f };
float v1[3] = { 0.0f, 1.0f, 0.0f };
copy_v3_v3(bone->head, v0);
copy_v3_v3(bone->tail, v1);
ED_armature_from_edit(bmain, arm);
ED_armature_edit_free(arm);
/* As an arbitrary test, translate the bone in Y for 60 frames. */
bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id);
bActionGroup *grp = action_groups_add_new(act, bone_name);
const char *rna_path = "pose.bones[\"Bone\"].location";
FCurve *fcu0 = create_fcurve(0, rna_path);
fcu0->totvert = 60;
FCurve *fcu1 = create_fcurve(1, rna_path);
fcu1->totvert = 60;
FCurve *fcu2 = create_fcurve(2, rna_path);
fcu2->totvert = 60;
for (int i = 1; i < 61; ++i) {
add_bezt(fcu0, static_cast<float>(i), 0.0f);
add_bezt(fcu1, static_cast<float>(i), static_cast<float>(i) * 0.1f);
add_bezt(fcu2, static_cast<float>(i), 0.0f);
}
action_groups_add_channel(act, grp, fcu0);
action_groups_add_channel(act, grp, fcu1);
action_groups_add_channel(act, grp, fcu2);
}
bool USDSkeletonReader::valid() const
{
return skel_ && USDXformReader::valid();
}
void USDSkeletonReader::create_object(Main *bmain, const double /* motionSampleTime */)
{
object_ = BKE_object_add_only_object(bmain, OB_ARMATURE, name_.c_str());
bArmature *arm = BKE_armature_add(bmain, name_.c_str());
object_->data = arm;
}
void USDSkeletonReader::read_object_data(Main *bmain, const double motionSampleTime)
{
if (!object_ || !object_->data || !skel_) {
return;
}
pxr::UsdSkelCache skel_cache;
pxr::UsdSkelSkeletonQuery skel_query = skel_cache.GetSkelQuery(skel_);
if (!skel_query.IsValid()) {
std::cout << "WARNING: couldn't query skeleton " << skel_.GetPath() << std::endl;
return;
}
const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology();
pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
if (joint_order.size() != skel_topology.size()) {
std::cout << "WARNING: skel topology and joint order size mismatch\n";
return;
}
bArmature *arm = static_cast<bArmature *>(object_->data);
ED_armature_to_edit(arm);
/* The bones we create, stored in the skeleton's joint order. */
std::vector<EditBone *> edit_bones;
size_t num_joints = skel_topology.GetNumJoints();
/* Keep track of the bones we create for each joint. */
std::map<pxr::TfToken, std::string> joint_to_bone_map;
/* Create the bones. */
for (const pxr::TfToken &joint : joint_order) {
std::string name = pxr::SdfPath(joint).GetName();
EditBone * bone = ED_armature_ebone_add(arm, name.c_str());
if (!bone) {
std::cout << "WARNING: couldn't add bone for joint " << joint << std::endl;
edit_bones.push_back(nullptr);
continue;
}
joint_to_bone_map.insert(std::make_pair(joint, bone->name));
edit_bones.push_back(bone);
}
/* Sanity check: we should have created a bone for each joint. */
if (edit_bones.size() != num_joints) {
std::cout << "WARNING: mismatch in bone and joint counts for skeleton " << skel_.GetPath() << std::endl;
return;
}
/* Record the child bone indices per parent bone. */
std::vector<std::vector<int>> child_bones(num_joints);
/* Set bone parenting. */
for (size_t i = 0; i < num_joints; ++i) {
int parent_idx = skel_topology.GetParent(i);
if (parent_idx < 0) {
continue;
}
if (parent_idx >= edit_bones.size()) {
std::cout << "WARNING: out of bounds parent index for bone " << pxr::SdfPath(joint_order[i])
<< " for skeleton " << skel_.GetPath() << std::endl;
continue;
}
child_bones[parent_idx].push_back(i);
if (edit_bones[i] && edit_bones[parent_idx]) {
edit_bones[i]->parent = edit_bones[parent_idx];
}
}
/* Skeleton-space joint bind transforms. */
pxr::VtMatrix4dArray bind_xforms;
if (!compute_skel_space_bind_transforms(skel_query, bind_xforms, 0.0f)) {
std::cout << "WARNING: couldn't get skeleton space bind xforms for skeleton "
<< skel_.GetPath() << std::endl;
return;
}
if (bind_xforms.size() != num_joints) {
std::cout << "WARNING: mismatch in local space rest xforms and joint counts for skeleton " << skel_.GetPath() << std::endl;
return;
}
/* Check if any bone natrices have negative determinants,
* indicating negative scales, possibly due to mirroring
* operations. Such matrices can't be propery converted
* to Blender's axis/roll bone representation (see
* https://developer.blender.org/T82930). If we detect
* such matrices, we will flag an error and won't try
* to import the animation, since the rotations would
* be incorrect in such cases. Unfortunately, the Pixar
* UsdSkel examples of the "HumanFemale" suffer from
* this issue. */
bool negative_determinant = false;
/* Set bone rest transforms. */
for (size_t i = 0; i < num_joints; ++i) {
EditBone *ebone = edit_bones[i];
if (!ebone) {
continue;
}
pxr::GfMatrix4f mat(bind_xforms[i]);
float mat4[4][4];
mat.Get(mat4);
pxr::GfVec3f head(0.0f, 0.0f, 0.0f);
pxr::GfVec3f tail(0.0f, 1.0f, 0.0f);
copy_v3_v3(ebone->head, head.data());
copy_v3_v3(ebone->tail, tail.data());
ED_armature_ebone_from_mat4(ebone, mat4);
if (mat.GetDeterminant() < 0.0) {
negative_determinant = true;
}
}
bool valid_skeleton = true;
if (negative_determinant) {
valid_skeleton = false;
WM_reportf(RPT_WARNING,
"USD Skeleton Import: bone matrices with negative determinants detected in prim %s."
"Such matrices may indicate negative scales, possibly due to mirroring operations, "
"and can't currently be converted to Blender's bone representation. "
"The skeletal animation won't be imported", prim_.GetPath().GetAsString().c_str());
}
/* Scale bones to account for separation between parents and
* children, so that the bone size is in proportion with the
* overall skeleton hierarchy. USD skeletons are composed of
* joints which we imperfectly represent as bones. */
float avg_len_scale = 0;
for (size_t i = 0; i < num_joints; ++i) {
/* If the bone has any children, scale its length
* by the distance between this bone's head
* and the average head location of its children. */
if (child_bones[i].empty()) {
continue;
}
EditBone *parent = edit_bones[i];
if (!parent) {
continue;
}
pxr::GfVec3f avg_child_head(0);
for (int j : child_bones[i]) {
EditBone *child = edit_bones[j];
if (!child) {
continue;
}
pxr::GfVec3f child_head(child->head);
avg_child_head += child_head;
}
avg_child_head /= child_bones[i].size();
pxr::GfVec3f parent_head(parent->head);
pxr::GfVec3f parent_tail(parent->tail);
float new_len = (avg_child_head - parent_head).GetLength();
/* Be sure not to scale by zero. */
if (new_len > .00001) {
parent_tail = parent_head + (parent_tail - parent_head).GetNormalized() * new_len;
copy_v3_v3(parent->tail, parent_tail.data());
avg_len_scale += new_len;
}
}
/* Scale terminal bones by the average length scale. */
avg_len_scale /= num_joints;
if (avg_len_scale > .00001) {
for (size_t i = 0; i < num_joints; ++i) {
if (!child_bones[i].empty()) {
continue;
}
EditBone *bone = edit_bones[i];
if (!bone) {
continue;
}
pxr::GfVec3f head(bone->head);
pxr::GfVec3f tail(bone->tail);
tail = head + (tail - head).GetNormalized() * avg_len_scale;
copy_v3_v3(bone->tail, tail.data());
}
}
ED_armature_from_edit(bmain, arm);
ED_armature_edit_free(arm);
if (valid_skeleton) {
create_skeleton_curves(bmain, object_, skel_query, joint_to_bone_map);
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}
} // 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.h"
#include "usd_reader_xform.h"
#include <pxr/usd/usdSkel/skeleton.h>
namespace blender::io::usd {
class USDSkeletonReader : public USDXformReader {
private:
pxr::UsdSkelSkeleton skel_;
public:
USDSkeletonReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDXformReader(prim, import_params, settings), skel_(prim)
{
}
bool valid() const override;
void create_object(Main *bmain, double motionSampleTime) override;
void read_object_data(Main *bmain, double motionSampleTime) override;
};
} // namespace blender::io::usd

View File

@@ -4,12 +4,14 @@
#include "usd_reader_stage.h"
#include "usd_reader_camera.h"
#include "usd_reader_curve.h"
#include "usd_reader_instance.h"
#include "usd_reader_light.h"
#include "usd_reader_material.h"
#include "usd_reader_mesh.h"
#include "usd_reader_nurbs.h"
#include "usd_reader_prim.h"
#include "usd_reader_shape.h"
#include "usd_reader_skeleton.h"
#include "usd_reader_volume.h"
#include "usd_reader_xform.h"
@@ -26,6 +28,7 @@
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/sphere.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <pxr/usd/usdShade/material.h>
#if PXR_VERSION >= 2111
@@ -34,16 +37,25 @@
#else
# include <pxr/usd/usdLux/light.h>
#endif
#include <pxr/usd/usdSkel/skeleton.h>
#include <pxr/usd/usdGeom/capsule.h>
#include <pxr/usd/usdGeom/cone.h>
#include <pxr/usd/usdGeom/cube.h>
#include <pxr/usd/usdGeom/cylinder.h>
#include <pxr/usd/usdGeom/sphere.h>
#include <iostream>
#include "BKE_lib_id.h"
#include "BKE_modifier.h"
#include "BLI_sort.hh"
#include "BLI_string.h"
#include "BKE_lib_id.h"
#include "DNA_material_types.h"
struct Object;
namespace blender::io::usd {
USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
@@ -55,6 +67,7 @@ USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
USDStageReader::~USDStageReader()
{
clear_proto_readers();
clear_readers();
}
@@ -66,14 +79,15 @@ bool USDStageReader::valid() const
bool USDStageReader::is_primitive_prim(const pxr::UsdPrim &prim) const
{
return (prim.IsA<pxr::UsdGeomCapsule>() || prim.IsA<pxr::UsdGeomCylinder>() ||
prim.IsA<pxr::UsdGeomCone>() || prim.IsA<pxr::UsdGeomCube>() ||
prim.IsA<pxr::UsdGeomCone>() || prim.IsA<pxr::UsdGeomCube>() ||
prim.IsA<pxr::UsdGeomSphere>());
}
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_.import_shapes && is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
if (params_.use_instancing && prim.IsInstance()) {
return new USDInstanceReader(prim, params_, settings_);
}
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
return new USDCameraReader(prim, params_, settings_);
@@ -87,25 +101,39 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
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 (params_.import_lights && (prim.IsA<pxr::UsdLuxBoundableLightBase>() ||
prim.IsA<pxr::UsdLuxNonboundableLightBase>())) {
#else
if (params_.import_lights && prim.IsA<pxr::UsdLuxLight>()) {
#endif
return new USDLightReader(prim, params_, settings_);
return new USDLightReader(prim, params_, settings_, xf_cache);
}
if (params_.import_volumes && prim.IsA<pxr::UsdVolVolume>()) {
return new USDVolumeReader(prim, params_, settings_);
}
if (params_.import_skeletons && prim.IsA<pxr::UsdSkelSkeleton>()) {
return new USDSkeletonReader(prim, params_, settings_);
}
if (params_.import_shapes && is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdGeomImageable>()) {
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;
}
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim,
pxr::UsdGeomXformCache *xf_cache)
{
if (is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
@@ -122,16 +150,25 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
if (prim.IsA<pxr::UsdGeomMesh>()) {
return new USDMeshReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
return new USDXformReader(prim, params_, settings_);
}
#if PXR_VERSION >= 2111
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
#else
if (prim.IsA<pxr::UsdLuxLight>()) {
#endif
return new USDLightReader(prim, params_, settings_);
return new USDLightReader(prim, params_, settings_, xf_cache);
}
if (prim.IsA<pxr::UsdVolVolume>()) {
return new USDVolumeReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdSkelSkeleton>()) {
return new USDSkeletonReader(prim, params_, settings_);
}
if (is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdGeomImageable>()) {
return new USDXformReader(prim, params_, settings_);
}
@@ -165,6 +202,11 @@ bool USDStageReader::include_by_visibility(const pxr::UsdGeomImageable &imageabl
bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable) const
{
if (params_.import_skeletons && imageable.GetPrim().IsA<pxr::UsdSkelSkeleton>()) {
/* Always include skeletons, if requested by the user, regardless of purpose. */
return true;
}
if (params_.import_guide && params_.import_proxy && params_.import_render) {
/* The options allow any purpose, so we trivially include the prim. */
return true;
@@ -196,7 +238,8 @@ bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable)
/* 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
* 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);
@@ -214,9 +257,16 @@ static bool merge_with_parent(USDPrimReader *reader)
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>() ||
xform_reader->prim().IsA<pxr::UsdGeomScope>()) {
xform_reader->prim().IsA<pxr::UsdGeomScope>() ||
xform_reader->prim().GetPrimTypeInfo()
== pxr::UsdPrimTypeInfo::GetEmptyPrimType()) {
return false;
}
@@ -231,7 +281,10 @@ static bool merge_with_parent(USDPrimReader *reader)
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>()) {
pxr::UsdGeomImageable imageable(prim);
@@ -245,10 +298,18 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
}
}
pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate;
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
dome_lights_.push_back(pxr::UsdLuxDomeLight(prim));
}
if (params_.import_instance_proxies) {
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
pxr::Usd_PrimFlagsConjunction filter_predicate = pxr::UsdPrimIsActive && pxr::UsdPrimIsLoaded &&
!pxr::UsdPrimIsAbstract;
if (params_.import_defined_only) {
filter_predicate &= pxr::UsdPrimIsDefined;
}
if (!params_.use_instancing && params_.import_instance_proxies) {
filter_predicate.TraverseInstanceProxies(true);
}
pxr::UsdPrimSiblingRange children = prim.GetFilteredChildren(filter_predicate);
@@ -256,18 +317,20 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
std::vector<USDPrimReader *> child_readers;
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);
}
}
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;
}
/* Check if we can merge an Xform with its child prim. */
if (child_readers.size() == 1) {
USDPrimReader *child_reader = child_readers.front();
if (merge_with_parent(child_reader)) {
@@ -275,6 +338,10 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
}
}
if (prim.IsPseudoRoot() || prim.IsPrototype()) {
return nullptr;
}
if (prim.IsA<pxr::UsdShadeMaterial>()) {
/* Record material path for later processing, if needed,
* e.g., when importing all materials. */
@@ -284,13 +351,13 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
return nullptr;
}
USDPrimReader *reader = create_reader_if_allowed(prim);
USDPrimReader *reader = create_reader_if_allowed(prim, xf_cache);
if (!reader) {
return nullptr;
}
readers_.push_back(reader);
r_readers.push_back(reader);
reader->incref();
/* Set each child reader's parent. */
@@ -308,25 +375,62 @@ void USDStageReader::collect_readers(Main *bmain)
}
clear_readers();
clear_proto_readers();
dome_lights_.clear();
/* Iterate through the stage. */
pxr::UsdPrim root = stage_->GetPseudoRoot();
std::string prim_path_mask(params_.prim_path_mask);
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
if (!prim_path_mask.empty()) {
pxr::UsdPrim prim = stage_->GetPrimAtPath(pxr::SdfPath(prim_path_mask));
if (prim.IsValid()) {
root = prim;
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));
}
else {
std::cerr << "WARNING: Prim Path Mask " << prim_path_mask
<< " does not specify a valid prim.\n";
}
}
void USDStageReader::process_armature_modifiers() const
{
/* Create armature object map. */
std::map<std::string, Object *> usd_path_to_armature;
for (const USDPrimReader *reader : readers_) {
if (dynamic_cast<const USDSkeletonReader *>(reader) && reader->object()) {
usd_path_to_armature.insert(std::make_pair(reader->prim_path(), reader->object()));
}
}
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
collect_readers(bmain, root);
/* Set armature objects on armature modifiers. */
for (const USDPrimReader *reader : readers_) {
if (!reader->object()) {
/* This should never happen. */
continue;
}
if (const USDMeshReader * mesh_reader = dynamic_cast<const USDMeshReader *>(reader)) {
ModifierData *md = BKE_modifiers_findby_type(reader->object(), eModifierType_Armature);
if (!md) {
continue;
}
ArmatureModifierData *amd = reinterpret_cast<ArmatureModifierData *>(md);
std::string skel_path = mesh_reader->get_skeleton_path();
std::map<std::string, Object *>::const_iterator it = usd_path_to_armature.find(skel_path);
if (it != usd_path_to_armature.end()) {
amd->object = it->second;
}
else {
std::cout << "WARNING: couldn't find armature object for armature modifier for USD prim "
<< reader->prim_path() << " bound to skeleton " << skel_path << std::endl;
}
}
}
}
void USDStageReader::import_all_materials(Main *bmain)
@@ -408,6 +512,29 @@ void USDStageReader::clear_readers()
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();
}
void USDStageReader::sort_readers()
{
blender::parallel_sort(

View File

@@ -9,6 +9,8 @@ struct Main;
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/imageable.h>
#include <pxr/usd/usdGeom/xformCache.h>
#include <pxr/usd/usdLux/domeLight.h>
#include <vector>
@@ -27,6 +29,13 @@ class USDStageReader {
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_;
/* USD material prim paths encountered during stage
* traversal, for importing unused materials. */
std::vector<std::string> material_paths_;
@@ -38,12 +47,15 @@ class 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 process_armature_modifiers() const;
/* Convert every material prim on the stage to a Blender
* material, including materials not used by any geometry.
* Note that collect_readers() must be called before calling
@@ -73,15 +85,30 @@ class USDStageReader {
void clear_readers();
void clear_proto_readers();
const ProtoReaderMap &proto_readers() const
{
return proto_readers_;
};
const std::vector<USDPrimReader *> &readers() const
{
return readers_;
};
const std::vector<pxr::UsdLuxDomeLight> &dome_lights() const
{
return dome_lights_;
};
void sort_readers();
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
@@ -101,6 +128,8 @@ class USDStageReader {
*/
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
bool merge_with_parent(USDPrimReader *reader) const;
/*
* Returns true if the specified UsdPrim is a UsdGeom primitive,
* procedural shape, such as UsdGeomCube.
@@ -108,4 +137,4 @@ class USDStageReader {
bool is_primitive_prim(const pxr::UsdPrim &prim) const;
};
}; // namespace blender::io::usd
} // namespace blender::io::usd

View File

@@ -34,30 +34,73 @@ void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime
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;
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) {
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);
}
needs_cachefile_ = !is_constant;
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);
}
bool USDXformReader::get_local_usd_xform(pxr::GfMatrix4d * r_xform,
bool *r_is_constant,
const float time) const
{
if (!r_xform) {
return false;
}
pxr::UsdGeomXformable xformable;
if (use_parent_xform_) {
xformable = pxr::UsdGeomXformable(prim_.GetParent());
}
else {
xformable = pxr::UsdGeomXformable(prim_);
}
if (!xformable) {
/* This might happen if the prim is a Scope. */
return false;
}
if (r_is_constant) {
*r_is_constant = !xformable.TransformMightBeTimeVarying();
}
bool reset_xform_stack;
return xformable.GetLocalTransformation(r_xform, &reset_xform_stack, time);
}
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
const float time,
const float scale,
@@ -69,28 +112,11 @@ void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
unit_m4(r_mat);
pxr::UsdGeomXformable xformable;
if (use_parent_xform_) {
xformable = pxr::UsdGeomXformable(prim_.GetParent());
}
else {
xformable = pxr::UsdGeomXformable(prim_);
}
if (!xformable) {
/* This might happen if the prim is a Scope. */
pxr::GfMatrix4d usd_local_xf;
if (!get_local_usd_xform(&usd_local_xf, r_is_constant, time)) {
return;
}
if (r_is_constant) {
*r_is_constant = !xformable.TransformMightBeTimeVarying();
}
pxr::GfMatrix4d usd_local_xf;
bool reset_xform_stack;
xformable.GetLocalTransformation(&usd_local_xf, &reset_xform_stack, time);
/* Convert the result to a float matrix. */
pxr::GfMatrix4f mat4f = pxr::GfMatrix4f(usd_local_xf);
mat4f.Get(r_mat);

View File

@@ -9,27 +9,36 @@
namespace blender::io::usd {
class USDXformReader : public USDPrimReader {
private:
protected:
bool use_parent_xform_;
/* Indicates if the created object is the root of a
* transform hierarchy. */
bool is_root_xform_;
bool needs_cachefile_;
public:
USDXformReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDPrimReader(prim, import_params, settings),
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 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
{
@@ -46,6 +55,10 @@ class USDXformReader : public USDPrimReader {
protected:
/* Returns true if the contained USD prim is the root of a transform hierarchy. */
bool is_root_xform_prim() const;
virtual bool get_local_usd_xform(pxr::GfMatrix4d *r_xform,
bool *r_is_constant,
const float time) const;
};
} // namespace blender::io::usd

View File

@@ -0,0 +1,779 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_skel_convert.h"
#include "usd.h"
#include <pxr/usd/usdSkel/animation.h>
#include <pxr/usd/usdSkel/blendShape.h>
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/utils.h>
#include "DNA_anim_types.h"
#include "DNA_armature_types.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_meta_types.h"
#include "DNA_scene_types.h"
#include "BKE_action.h"
#include "BKE_armature.h"
#include "BKE_deform.h"
#include "BKE_fcurve.h"
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BKE_object_deform.h"
#include "BLI_math_vector.h"
#include "ED_keyframing.h"
#include "ED_mesh.h"
#include <string>
#include <vector>
namespace usdtokens {
// Attribute names.
//static const pxr::TfToken color("color", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace {
FCurve *create_fcurve(int array_index, const char *rna_path)
{
FCurve *fcu = BKE_fcurve_create();
fcu->flag = (FCURVE_VISIBLE | FCURVE_SELECTED);
fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
fcu->array_index = array_index;
return fcu;
}
FCurve *create_chan_fcurve(bAction *act,
bActionGroup *grp,
int array_index,
const char *rna_path,
int totvert)
{
FCurve *fcu = create_fcurve(array_index, rna_path);
fcu->totvert = totvert;
action_groups_add_channel(act, grp, fcu);
return fcu;
}
void add_bezt(FCurve *fcu,
float frame,
float value,
eBezTriple_Interpolation ipo = BEZT_IPO_LIN)
{
BezTriple bez;
memset(&bez, 0, sizeof(BezTriple));
bez.vec[1][0] = frame;
bez.vec[1][1] = value;
bez.ipo = ipo; /* use default interpolation mode here... */
bez.f1 = bez.f2 = bez.f3 = SELECT;
bez.h1 = bez.h2 = HD_AUTO;
insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
BKE_fcurve_handles_recalc(fcu);
}
/* Generate dummy curve samples for testing. */
//void add_dummy_samples(FCurve *fcu)
//{
// int totvert = fcu->totvert;
// for (int i = 0; i < totvert; ++i) {
// add_bezt(fcu, static_cast<float>(i+1), static_cast<float>(i) * 0.1f);
// }
//}
} // End anonymous namespace.
namespace blender::io::usd {
pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time)
{
pxr::GfMatrix4d local_xf(1.0f);
if (!prim) {
return local_xf;
}
pxr::UsdGeomXformable xformable(prim);
if (xformable) {
bool reset_xform_stack = false;
if (!xformable.GetLocalTransformation(&local_xf, &reset_xform_stack, time)) {
std::cout << "WARNING: couldn't get local xform for prim " << prim.GetPath() << std::endl;
return local_xf;
}
}
return local_xf * get_world_matrix(prim.GetParent(), time);
}
void test_create_shapekeys(Main *bmain, Object *obj)
{
if (!(obj && obj->data && obj->type == OB_MESH)) {
return;
}
Mesh *mesh = static_cast<Mesh *>(obj->data);
/* insert key to source mesh */
Key *key = BKE_key_add(bmain, (ID *)mesh);
key->type = KEY_RELATIVE;
mesh->key = key;
/* insert basis key */
KeyBlock *kb = BKE_keyblock_add(key, "Basis");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
kb = BKE_keyblock_add(key, "Key1");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
float offsets[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
BKE_keyblock_update_from_offset(obj, kb, (float(*)[3])&offsets);
bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id);
FCurve *fcu = create_fcurve(0, "key_blocks[\"Key1\"].value");
fcu->totvert = 3;
add_bezt(fcu, 0.f, 0.f);
add_bezt(fcu, 30.f, 1.f);
add_bezt(fcu, 60.f, 0.3f);
BLI_addtail(&act->curves, fcu);
}
void import_blendshapes(Main *bmain, Object *obj, pxr::UsdPrim prim)
{
if (!(obj && obj->data && obj->type == OB_MESH && prim)) {
return;
}
if (prim.IsInstanceProxy() || prim.IsInPrototype()) {
/* Attempting to create a UsdSkelBindingAPI for
* instance proxies and prototypes generates USD errors. */
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
return;
}
if (!skel_api.GetBlendShapeTargetsRel().HasAuthoredTargets()) {
return;
}
pxr::SdfPathVector targets;
if (!skel_api.GetBlendShapeTargetsRel().GetTargets(&targets)) {
std::cout << "Couldn't get blendshape targets for prim " << prim.GetPath() << std::endl;
return;
}
if (targets.empty()) {
return;
}
if (!skel_api.GetBlendShapesAttr().HasAuthoredValue()) {
return;
}
pxr::VtTokenArray blendshapes;
if (!skel_api.GetBlendShapesAttr().Get(&blendshapes)) {
return;
}
if (blendshapes.empty()) {
return;
}
if (targets.size() != blendshapes.size()) {
std::cout << "Number of blendshapes doesn't match number of blendshape targets for prim " << prim.GetPath() << std::endl;
return;
}
Mesh *mesh = static_cast<Mesh *>(obj->data);
/* insert key to source mesh */
Key *key = BKE_key_add(bmain, (ID *)mesh);
key->type = KEY_RELATIVE;
mesh->key = key;
/* insert basis key */
KeyBlock *kb = BKE_keyblock_add(key, "Basis");
BKE_keyblock_convert_from_mesh(mesh, key, kb);
pxr::UsdStageRefPtr stage = prim.GetStage();
if (!stage) {
return;
}
/* Keep track of the shapkeys we're adding, for
* validation when creating curves later. */
std::set<pxr::TfToken> shapekey_names;
for (int i = 0; i < targets.size(); ++i) {
const pxr::SdfPath &path = targets[i];
pxr::UsdSkelBlendShape blendshape(stage->GetPrimAtPath(path));
if (!blendshape) {
continue;
}
if (!blendshape.GetOffsetsAttr().HasAuthoredValue()) {
continue;
}
pxr::VtVec3fArray offsets;
if (!blendshape.GetOffsetsAttr().Get(&offsets)) {
std::cout << "Couldn't get offsets for blendshape " << path << std::endl;
continue;
}
if (offsets.empty()) {
std::cout << "No offsets for blendshape " << path << std::endl;
continue;
}
shapekey_names.insert(blendshapes[i]);
kb = BKE_keyblock_add(key, blendshapes[i].GetString().c_str());
BKE_keyblock_convert_from_mesh(mesh, key, kb);
pxr::VtArray<int> point_indices;
if (blendshape.GetPointIndicesAttr().HasAuthoredValue()) {
blendshape.GetPointIndicesAttr().Get(&point_indices);
}
float *fp = static_cast<float *>(kb->data);
if (point_indices.empty()) {
for (int a = 0; a < kb->totelem; ++a, fp += 3) {
if (a >= offsets.size()) {
std::cout << "Number of offsets greater than number of mesh vertices for blendshape "
<< path << std::endl;
break;
}
add_v3_v3(fp, offsets[a].data());
}
}
else {
int a = 0;
for (int i : point_indices) {
if (i < 0 || i > kb->totelem) {
std::cout << "Out of bounds point index " << i << " for blendshape " << path << std::endl;
++a;
continue;
}
if (a >= offsets.size()) {
std::cout << "Number of offsets greater than number of mesh vertices for blendshape " << path << std::endl;
break;
}
add_v3_v3(&fp[3 * i], offsets[a].data());
++a;
}
}
}
pxr::UsdSkelSkeleton skel_prim = skel_api.GetInheritedSkeleton();
if (!skel_prim) {
return;
}
skel_api = pxr::UsdSkelBindingAPI::Apply(skel_prim.GetPrim());
if (!skel_api) {
return;
}
pxr::UsdPrim anim_prim = skel_api.GetInheritedAnimationSource();
if (!anim_prim) {
return;
}
pxr::UsdSkelAnimation skel_anim(anim_prim);
if (!skel_anim) {
return;
}
if (!skel_anim.GetBlendShapesAttr().HasAuthoredValue()) {
return;
}
pxr::UsdAttribute weights_attr = skel_anim.GetBlendShapeWeightsAttr();
if (!(weights_attr && weights_attr.HasAuthoredValue())) {
return;
}
std::vector<double> times;
if (!weights_attr.GetTimeSamples(&times)) {
return;
}
if (times.empty()) {
return;
}
if (!skel_anim.GetBlendShapesAttr().Get(&blendshapes)) {
return;
}
if (blendshapes.empty()) {
return;
}
size_t num_samples = times.size();
/* Create the animation and curves. */
bAction *act = ED_id_action_ensure(bmain, (ID *)&key->id);
std::vector<FCurve *> curves;
for (auto blendshape_name : blendshapes) {
if (shapekey_names.find(blendshape_name) == shapekey_names.end()) {
/* We didn't create a shapekey fo this blendshape, so we don't
* create a curve and insert a null placeholder in the curve array. */
curves.push_back(nullptr);
continue;
}
std::string rna_path = "key_blocks[\"" + blendshape_name.GetString() + "\"].value";
FCurve *fcu = create_fcurve(0, rna_path.c_str());
fcu->totvert = num_samples;
curves.push_back(fcu);
BLI_addtail(&act->curves, fcu);
}
for (double frame : times) {
pxr::VtFloatArray weights;
if (!weights_attr.Get(&weights, frame)) {
std::cout << "Couldn't get blendshape weights for time " << frame << std::endl;
continue;
}
if (weights.size() != curves.size()) {
std::cout << "Programmer error: number of weight samples doesn't match number of shapekey curve entries for frame " << frame << std::endl;
continue;
}
for (int wi = 0; wi < weights.size(); ++wi) {
if (curves[wi] != nullptr) {
add_bezt(curves[wi], frame, weights[wi]);
}
}
}
}
void create_skeleton_curves(Main *bmain,
Object *obj,
const pxr::UsdSkelSkeletonQuery &skel_query,
const std::map<pxr::TfToken, std::string> &joint_to_bone_map)
{
if (!(bmain && obj && skel_query)) {
return;
}
if (joint_to_bone_map.empty()) {
return;
}
const pxr::UsdSkelAnimQuery &anim_query = skel_query.GetAnimQuery();
if (!anim_query) {
return;
}
std::vector<double> samples;
anim_query.GetJointTransformTimeSamples(&samples);
if (samples.empty()) {
return;
}
size_t num_samples = samples.size();
bAction *act = ED_id_action_ensure(bmain, (ID *)&obj->id);
pxr::VtTokenArray joint_order = skel_query.GetJointOrder();
std::vector<FCurve *> loc_curves;
std::vector<FCurve *> rot_curves;
std::vector<FCurve *> scale_curves;
for (const pxr::TfToken &joint : joint_order) {
std::map<pxr::TfToken, std::string>::const_iterator it = joint_to_bone_map.find(joint);
if (it == joint_to_bone_map.end()) {
/* This joint doesn't correspond to any bone we created.
* Add null placeholders for the channel curves. */
loc_curves.push_back(nullptr);
loc_curves.push_back(nullptr);
loc_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
rot_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
scale_curves.push_back(nullptr);
continue;
}
bActionGroup *grp = action_groups_add_new(act, it->second.c_str());
/* Add translation curves. */
std::string rna_path = "pose.bones[\"" + it->second + "\"].location";
loc_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
loc_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
loc_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
/* Add rotation curves. */
rna_path = "pose.bones[\"" + it->second + "\"].rotation_quaternion";
rot_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
rot_curves.push_back(create_chan_fcurve(act, grp, 3, rna_path.c_str(), num_samples));
/* Add scale curves. */
rna_path = "pose.bones[\"" + it->second + "\"].scale";
scale_curves.push_back(create_chan_fcurve(act, grp, 0, rna_path.c_str(), num_samples));
scale_curves.push_back(create_chan_fcurve(act, grp, 1, rna_path.c_str(), num_samples));
scale_curves.push_back(create_chan_fcurve(act, grp, 2, rna_path.c_str(), num_samples));
}
if (loc_curves.size() != joint_order.size() * 3) {
std::cout << "PROGRAMMER ERROR: location curve count mismatch\n";
return;
}
if (rot_curves.size() != joint_order.size() * 4) {
std::cout << "PROGRAMMER ERROR: rotation curve count mismatch\n";
return;
}
if (scale_curves.size() != joint_order.size() * 3) {
std::cout << "PROGRAMMER ERROR: scale curve count mismatch\n";
return;
}
/* Skeleton-space joint bind transforms. */
pxr::VtMatrix4dArray bind_xforms;
if (!compute_skel_space_bind_transforms(skel_query, bind_xforms, 0.0f)) {
std::cout << "WARNING: couldn't get skeleton space bind xforms for skeleton query "
<< skel_query.GetPrim().GetPath() << std::endl;
return;
}
if (bind_xforms.size() != joint_order.size()) {
std::cout << "WARNING: number of bind transforms doesn't match the number of joints\n";
return;
}
const pxr::UsdSkelTopology &skel_topology = skel_query.GetTopology();
/* This will store the inverse of the parent-relative bind xforms. */
pxr::VtMatrix4dArray inv_bind_xforms(bind_xforms.size());
for (int i = 0; i < bind_xforms.size(); ++i) {
int parent_id = skel_topology.GetParent(i);
if (parent_id >= 0) {
/* This is a non-root bone. Compute the transform of the joint
* relative to its parent. */
pxr::GfMatrix4d parent_relative_xf = bind_xforms[i] * bind_xforms[parent_id].GetInverse();
inv_bind_xforms[i] = parent_relative_xf.GetInverse();
} else {
inv_bind_xforms[i] = bind_xforms[i].GetInverse();
}
}
for (double frame : samples) {
pxr::VtMatrix4dArray joint_local_xforms;
if (!skel_query.ComputeJointLocalTransforms(&joint_local_xforms, frame)) {
std::cout << "WARNING: couldn't compute joint local transforms on frame " << frame << std::endl;
continue;
}
if (joint_local_xforms.size() != joint_order.size()) {
std::cout << "WARNING: number of joint local transform entries " << joint_local_xforms.size()
<< " doesn't match the number of joints " << joint_order.size() << std::endl;
continue;
}
for (int i = 0; i < joint_local_xforms.size(); ++i) {
pxr::GfMatrix4d bind_relative_xf = joint_local_xforms[i] * inv_bind_xforms[i];
pxr::GfVec3f t;
pxr::GfQuatf qrot;
pxr::GfVec3h s;
if (!pxr::UsdSkelDecomposeTransform(bind_relative_xf, &t, &qrot, &s)) {
std::cout << "WARNING: error decomposing matrix on frame " << frame << std::endl;
continue;
}
float re = qrot.GetReal();
pxr::GfVec3f im = qrot.GetImaginary();
for (int j = 0; j < 3; ++j) {
int k = 3 * i + j;
if (k >= loc_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds translation curve index." << std::endl;
break;
}
if (FCurve *fcu = loc_curves[k]) {
add_bezt(fcu, frame, t[j]);
}
}
for (int j = 0; j < 4; ++j) {
int k = 4 * i + j;
if (k >= rot_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds rotation curve index." << std::endl;
break;
}
if (FCurve *fcu = rot_curves[k]) {
if (j == 0) {
add_bezt(fcu, frame, re);
}
else {
add_bezt(fcu, frame, im[j - 1]);
}
}
}
for (int j = 0; j < 3; ++j) {
int k = 3 * i + j;
if (k >= scale_curves.size()) {
std::cout << "PROGRAMMER ERROR: out of bounds scale curve index." << std::endl;
break;
}
if (FCurve *fcu = scale_curves[k]) {
add_bezt(fcu, frame, s[j]);
}
}
}
}
}
void import_skel_bindings(Main *bmain, Object *mesh_obj, pxr::UsdPrim prim)
{
if (!(bmain && mesh_obj && prim)) {
return;
}
if (prim.IsInstanceProxy() || prim.IsInPrototype()) {
/* Attempting to create a UsdSkelBindingAPI for
* instance proxies and prototypes generates USD errors. */
return;
}
if (mesh_obj->type != OB_MESH) {
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(prim);
if (!skel_api) {
return;
}
pxr::UsdSkelSkeleton skel = skel_api.GetInheritedSkeleton();
if (!skel) {
return;
}
pxr::VtArray<pxr::TfToken> joints;
if (skel_api.GetJointsAttr().HasAuthoredValue()) {
skel_api.GetJointsAttr().Get(&joints);
}
else if (skel.GetJointsAttr().HasAuthoredValue()) {
skel.GetJointsAttr().Get(&joints);
}
if (joints.empty()) {
return;
}
pxr::UsdGeomPrimvar joint_indices_primvar = skel_api.GetJointIndicesPrimvar();
if (!(joint_indices_primvar && joint_indices_primvar.HasAuthoredValue())) {
return;
}
pxr::UsdGeomPrimvar joint_weights_primvar = skel_api.GetJointWeightsPrimvar();
if (!(joint_weights_primvar && joint_weights_primvar.HasAuthoredValue())) {
return;
}
int joint_indices_elem_size = joint_indices_primvar.GetElementSize();
int joint_weights_elem_size = joint_weights_primvar.GetElementSize();
if (joint_indices_elem_size != joint_weights_elem_size) {
std::cout << "WARNING: joint weights and joint indices element size mismatch." << std::endl;
return;
}
/* The set of unique joint indices referenced in the joint indices
* attribute. */
pxr::VtIntArray joint_indices;
joint_indices_primvar.ComputeFlattened(&joint_indices);
pxr::VtFloatArray joint_weights;
joint_weights_primvar.ComputeFlattened(&joint_weights);
if (joint_indices.empty() || joint_weights.empty()) {
return;
}
if (joint_indices.size() != joint_weights.size()) {
std::cout << "WARNING: joint weights and joint indices size mismatch." << std::endl;
return;
}
Mesh *mesh = static_cast<Mesh *>(mesh_obj->data);
pxr::TfToken interp = joint_weights_primvar.GetInterpolation();
if (interp != pxr::UsdGeomTokens->vertex && interp != pxr::UsdGeomTokens->constant) {
std::cout << "WARNING: unexpected joint weights interpolation type " << interp
<< std::endl;
return;
}
if (interp == pxr::UsdGeomTokens->vertex && joint_weights.size() != mesh->totvert * joint_weights_elem_size) {
std::cout << "WARNING: joint weights of unexpected size for vertex interpolation." << std::endl;
return;
}
if (interp == pxr::UsdGeomTokens->constant && joint_weights.size() != joint_weights_elem_size) {
std::cout << "WARNING: joint weights of unexpected size for constant interpolation."
<< std::endl;
return;
}
std::vector<int> used_indices;
for (int index : joint_indices) {
if (std::find(used_indices.begin(), used_indices.end(), index) == used_indices.end()) {
/* We haven't accounted for this index yet. */
if (index < 0 || index >= joints.size()) {
std::cout << "Out of bound joint index " << index << std::endl;
continue;
}
used_indices.push_back(index);
}
}
if (used_indices.empty()) {
return;
}
if (BKE_object_defgroup_data_create(static_cast<ID *>(mesh_obj->data)) == NULL) {
return;
}
/* Add the armature modifier, if one doesn't exist. */
if (!BKE_modifiers_findby_type(mesh_obj, eModifierType_Armature)) {
ModifierData *md = BKE_modifier_new(eModifierType_Armature);
BLI_addtail(&mesh_obj->modifiers, md);
}
std::vector<bDeformGroup *> joint_def_grps(joints.size(), nullptr);
for (int idx : used_indices) {
std::string joint_name = pxr::SdfPath(joints[idx]).GetName();
if (!BKE_object_defgroup_find_name(mesh_obj, joint_name.c_str())) {
bDeformGroup *def_grp = BKE_object_defgroup_add_name(mesh_obj, joint_name.c_str());
joint_def_grps[idx] = def_grp;
}
}
for (int i = 0; i < mesh->totvert; ++i) {
/* Offset into the weights array, which is
* always 0 for constant interpolation. */
int offset = 0;
if (interp == pxr::UsdGeomTokens->vertex) {
offset = i * joint_weights_elem_size;
}
for (int j = 0; j < joint_weights_elem_size; ++j) {
int k = offset + j;
float w = joint_weights[k];
if (w < .00001) {
/* No deform group if zero weight. */
continue;
}
int joint_idx = joint_indices[k];
bDeformGroup *def_grp = joint_def_grps[joint_idx];
if (def_grp) {
ED_vgroup_vert_add(mesh_obj, def_grp, i, w, WEIGHT_REPLACE);
}
}
}
}
bool compute_skel_space_bind_transforms(const pxr::UsdSkelSkeletonQuery &skel_query,
pxr::VtMatrix4dArray &out_xforms,
pxr::UsdTimeCode time)
{
if (!skel_query) {
return false;
}
pxr::GfMatrix4d skel_mat_inv = get_world_matrix(skel_query.GetSkeleton().GetPrim(), time).GetInverse();
if (!skel_query.GetJointWorldBindTransforms(&out_xforms)) {
std::cout << "WARNING: couldn't get local xform for prim "
<< skel_query.GetSkeleton().GetPrim().GetPath() << std::endl;
return false;
}
for (int i = 0; i < out_xforms.size(); ++i) {
out_xforms[i] = out_xforms[i] * skel_mat_inv;
}
return true;
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,52 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usdSkel/skeletonQuery.h>
#include <map>
struct Main;
struct Object;
struct Scene;
struct USDExportParams;
struct USDImportParams;
namespace blender::io::usd {
struct ImportSettings;
void test_create_shapekeys(Main *bmain, Object *shape_obj);
void import_blendshapes(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
void create_skeleton_curves(Main *bmain,
Object *obj,
const pxr::UsdSkelSkeletonQuery &skel_query,
const std::map<pxr::TfToken, std::string> &joint_to_bone_map);
void import_skel_bindings(Main *bmain, Object *shape_obj, pxr::UsdPrim prim);
bool compute_skel_space_bind_transforms(const pxr::UsdSkelSkeletonQuery &skel_query,
pxr::VtMatrix4dArray &out_xforms,
pxr::UsdTimeCode time);
pxr::GfMatrix4d get_world_matrix(const pxr::UsdPrim &prim, pxr::UsdTimeCode time);
} // namespace blender::io::usd

View File

@@ -0,0 +1,909 @@
/*
* 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_asset_utils.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";
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, "%s", message_str);
return UMM_NOTIFICATION_FAILURE;
}
if (strcmp(notification_str, "unexpected_error") == 0) {
WM_reportf(RPT_ERROR, "%s", 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 USDImportParams &params,
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());
}
const bool import_textures = !resolved_path.empty() &&
params.import_textures_mode != USD_TEX_IMPORT_NONE &&
should_import_asset(resolved_path);
if (import_textures) {
/* If we are packing the imported textures, we first write them
* to a temporary directory. */
const char *textures_dir = params.import_textures_mode == USD_TEX_IMPORT_PACK ?
temp_textures_dir() :
params.import_textures_dir;
const eUSDTexNameCollisionMode name_collision_mode = params.import_textures_mode ==
USD_TEX_IMPORT_PACK ?
USD_TEX_NAME_COLLISION_OVERWRITE :
params.tex_name_collision_mode;
resolved_path = import_asset(resolved_path.c_str(), textures_dir, name_collision_mode);
}
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(const USDImportParams &params,
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(params, 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(const USDImportParams &params,
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(params, 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,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.
*
*/
#pragma once
#ifdef WITH_PYTHON
# include <pxr/usd/usdShade/material.h>
# include "Python.h"
struct Material;
struct USDImportParams;
namespace blender::io::usd {
struct USDExporterContext;
bool umm_module_loaded();
bool umm_import_mdl_material(const USDImportParams &params,
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

@@ -5,11 +5,12 @@
#include "usd_writer_material.h"
#include <pxr/base/tf/stringUtils.h>
#include <pxr/usd/kind/registry.h>
#include <pxr/usd/usd/modelAPI.h>
#include <pxr/usd/usdGeom/bboxCache.h>
#include "BKE_customdata.h"
#include "BLI_assert.h"
#include "DNA_mesh_types.h"
#include "WM_api.h"
@@ -23,8 +24,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 roughness("roughness", 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 {
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)
{
if (!ob || ob->type != OB_MESH || !ob->data) {
@@ -38,7 +70,81 @@ static std::string get_mesh_active_uvlayer_name(const Object *ob)
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)
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
@@ -90,7 +196,27 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
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;
/* Construct the material. */
@@ -100,16 +226,42 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
if (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 (this->usd_export_context_.export_params.export_textures) {
export_textures(material, this->usd_export_context_.stage, this->usd_export_context_.export_params.overwrite_textures);
}
}
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, this->usd_export_context_.export_params.overwrite_textures);
}
}
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
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(
this->usd_export_context_, material, usd_material, active_uv);
}
else {
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;
}
@@ -128,6 +280,12 @@ void USDAbstractWriter::write_visibility(const HierarchyContext &context,
usd_value_writer_.SetAttribute(attr_visibility, pxr::VtValue(visibility), timecode);
}
void USDAbstractWriter::write_kind(pxr::UsdPrim& prim, pxr::TfToken kind)
{
pxr::UsdModelAPI api(prim);
api.SetKind(kind);
}
bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
{
BLI_assert(context.is_instance());
@@ -138,7 +296,14 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
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)) {
/* 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
@@ -149,9 +314,107 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
return false;
}
prim.SetInstanceable(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;
}
const StringRef kind_identifier = "usdkind";
IDProperty *prop;
for (prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
if (kind_identifier == prop->name) {
if (prop->type == IDP_STRING && usd_export_context_.export_params.export_usd_kind &&
prop->data.pointer) {
write_kind(prim, pxr::TfToken(static_cast<char *>(prop->data.pointer)));
}
continue;
}
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;
}
}
}
void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdGeomBoundable &prim)
{
/* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when

View File

@@ -17,6 +17,7 @@
#include "DNA_material_types.h"
struct Main;
struct Material;
namespace blender::io::usd {
@@ -57,10 +58,19 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
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,
const pxr::UsdTimeCode timecode,
pxr::UsdGeomImageable &usd_geometry);
void write_kind(pxr::UsdPrim& prim, pxr::TfToken kind);
/**
* Turn `prim` into an instance referencing `context.original_export_path`.
* Return true when the instancing was successful, false otherwise.

View File

@@ -0,0 +1,335 @@
/*
* 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 "usd_writer_transform.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);
/* The context world matrix does not include the unit
* conversion scaling or axis rotation that may be applied
* to root primitives on export, so we must include those,
* if necessary. */
float convert_mat[4][4];
get_export_conversion_matrix(usd_export_context_.export_params, convert_mat);
BoneDataBuilder bone_data(world_mat * pxr::GfMatrix4f(convert_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

@@ -0,0 +1,409 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#include "usd_writer_blendshape_mesh.h"
#include "usd_hierarchy_iterator.h"
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/matrix4f.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdSkel/animation.h>
#include <pxr/usd/usdSkel/blendShape.h>
#include "BKE_key.h"
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "BLI_math_vector.h"
#include "DNA_key_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_meta_types.h"
#include "WM_api.h"
#include "WM_types.h"
#include <string>
namespace usdtokens {
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
static const pxr::TfToken Skel("Skel", pxr::TfToken::Immortal);
static const pxr::TfToken joint1("joint1", pxr::TfToken::Immortal);
} // namespace usdtokens
namespace blender::io::usd {
static pxr::VtFloatArray get_blendshape_weights(const Key *key)
{
pxr::VtFloatArray weights;
LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
if (kb == key->block.first) {
// Skip the first key, which is the basis.
continue;
}
weights.push_back(kb->curval);
}
return weights;
}
static const Key *get_shape_key(Object *obj)
{
if (!(obj && obj->data)) {
return nullptr;
}
if (obj->type != OB_MESH) {
return nullptr;
}
const Mesh *mesh = static_cast<Mesh *>(obj->data);
return mesh->key;
}
//static void print_blendshape_info(Object *obj)
//{
// const Key *key = get_shape_key(obj);
//
// if (!key) {
// return;
// }
//
// printf("have shape key\n");
// const int num_keys = key->totkey;
// printf("num keys %d\n", num_keys);
// printf("type %d\n", key->type);
// printf("ctime: %f\n", key->ctime);
// // BKE_keyblock_convert_to_mesh()
// // BKE_keyblock_mesh_calc_normals()
// // BKE_keyblock_element_count_from_shape()
// LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
// printf("%s %f %f\n", kb->name, kb->curval, kb->pos);
// }
//
// printf("anim pointer %p\n", key->adt);
//}
bool is_blendshape_mesh(Object *obj)
{
const Key *key = get_shape_key(obj);
return key && key->totkey > 0 && key->type == KEY_RELATIVE;
}
USDBlendShapeMeshWriter::USDBlendShapeMeshWriter(const USDExporterContext &ctx)
: USDMeshWriter(ctx)
{
}
void USDBlendShapeMeshWriter::do_write(HierarchyContext &context)
{
if (!this->frame_has_been_written_) {
USDGenericMeshWriter::do_write(context);
}
write_blendshape(context);
}
bool USDBlendShapeMeshWriter::is_supported(const HierarchyContext *context) const
{
return is_blendshape_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
}
bool USDBlendShapeMeshWriter::check_is_animated(const HierarchyContext &context) const
{
const Key *key = get_shape_key(context.object);
return key && key->totkey > 0 && key->adt != nullptr;
}
void USDBlendShapeMeshWriter::write_blendshape(HierarchyContext &context) const
{
/* A blendshape writer might be created even if
* there are no blendshapes, so check that blendshapes
* exist before continuting. */
if (!is_blendshape_mesh(context.object)) {
return;
}
const Key *key = get_shape_key(context.object);
if (!key || !key->block.first) {
WM_reportf(RPT_WARNING,
"WARNING: couldn't get shape key for blendshape mesh prim %s",
usd_export_context_.usd_path.GetString().c_str());
return;
}
/* Validate the offset counts. */
Mesh *src_mesh = static_cast<Mesh *>(context.object->data);
KeyBlock *basis = reinterpret_cast<KeyBlock *>(src_mesh->key->block.first);
if (src_mesh->totvert != basis->totelem) {
/* No need for a warning, as we would have warned about
* the vert count mismatch when creating the mesh. */
return;
}
pxr::UsdSkelSkeleton skel = get_skeleton(context);
if (!skel) {
printf("WARNING: couldn't get skeleton for blendshape mesh prim %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
if (!this->frame_has_been_written_) {
pxr::UsdPrim mesh_prim = usd_export_context_.stage->GetPrimAtPath(
usd_export_context_.usd_path);
if (!mesh_prim.IsValid()) {
printf("WARNING: couldn't get valid mesh prim for blendshape mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
create_blend_shapes(key, mesh_prim, skel);
}
if (exporting_anim(key)) {
add_weights_sample(key, skel);
}
}
void USDBlendShapeMeshWriter::create_blend_shapes(const Key *key,
const pxr::UsdPrim &mesh_prim,
const pxr::UsdSkelSkeleton &skel) const
{
if (!(key && mesh_prim && skel)) {
return;
}
pxr::UsdSkelBindingAPI skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
if (!skel_api) {
printf("WARNING: couldn't apply UsdSkelBindingAPI to blendshape mesh prim %s\n",
mesh_prim.GetPath().GetAsString().c_str());
return;
}
skel_api.CreateSkeletonRel().AddTarget(skel.GetPath());
pxr::VtTokenArray blendshape_names;
std::vector<pxr::SdfPath> blendshape_paths;
/* Get the basis, which we'll use to calculate offsets. */
KeyBlock *basis_key = static_cast<KeyBlock *>(key->block.first);
if (!basis_key) {
return;
}
int basis_totelem = basis_key->totelem;
LISTBASE_FOREACH (KeyBlock *, kb, &key->block) {
if (!kb) {
continue;
}
if (kb == basis_key) {
// Skip the first key, which is the basis.
continue;
}
pxr::TfToken name(pxr::TfMakeValidIdentifier(kb->name));
blendshape_names.push_back(name);
pxr::SdfPath path = usd_export_context_.usd_path.AppendChild(name);
blendshape_paths.push_back(path);
pxr::UsdSkelBlendShape blendshape =
usd_export_context_.usd_define_or_over<pxr::UsdSkelBlendShape>(path);
pxr::UsdAttribute offsets_attr = blendshape.CreateOffsetsAttr();
/* Some applications, like Houdini, don't render blend shapes unless the point
* indices are set, so we always create this attribute, even when every index
* is included. */
pxr::UsdAttribute point_indices_attr = blendshape.CreatePointIndicesAttr();
pxr::VtVec3fArray offsets(kb->totelem);
pxr::VtIntArray indices(kb->totelem);
const float(*fp)[3] = static_cast<float(*)[3]>(kb->data);
const float(*basis_fp)[3] = static_cast<float(*)[3]>(basis_key->data);
for (int i = 0; i < kb->totelem; ++i) {
/* Subtract the key positions from the
* basis positions to get the offsets. */
sub_v3_v3v3(offsets[i].data(), fp[i], basis_fp[i]);
indices[i] = i;
}
offsets_attr.Set(offsets);
point_indices_attr.Set(indices);
}
/* Set the blendshape names and targets on the shape. */
pxr::UsdAttribute blendshape_attr = skel_api.CreateBlendShapesAttr();
blendshape_attr.Set(blendshape_names);
skel_api.CreateBlendShapeTargetsRel().SetTargets(blendshape_paths);
/* Some DCCs seem to require joint indices and weights to
* bind the skeleton for blendshapes, so we we create these
* primvars, if needed. */
if (!skel_api.GetJointIndicesAttr().HasAuthoredValue()) {
pxr::VtArray<int> joint_indices(basis_totelem, 0);
skel_api.CreateJointIndicesPrimvar(false, 1).GetAttr().Set(joint_indices);
}
if (!skel_api.GetJointWeightsAttr().HasAuthoredValue()) {
pxr::VtArray<float> joint_weights(basis_totelem, 1.0f);
skel_api.CreateJointWeightsPrimvar(false, 1).GetAttr().Set(joint_weights);
}
/* Create the skeleton animation. */
pxr::SdfPath anim_path = skel.GetPath().AppendChild(usdtokens::Anim);
const pxr::UsdSkelAnimation anim = usd_export_context_.usd_define_or_over<pxr::UsdSkelAnimation>(
anim_path);
if (anim) {
/* Set the blendshape names on the animation. */
pxr::UsdAttribute blendshape_attr = anim.CreateBlendShapesAttr();
blendshape_attr.Set(blendshape_names);
pxr::VtFloatArray weights = get_blendshape_weights(key);
pxr::UsdAttribute weights_attr = anim.CreateBlendShapeWeightsAttr();
weights_attr.Set(weights);
}
}
void USDBlendShapeMeshWriter::add_weights_sample(const Key *key,
const pxr::UsdSkelSkeleton &skel) const
{
/* Create the skeleton animation. */
pxr::SdfPath anim_path = skel.GetPath().AppendChild(usdtokens::Anim);
const pxr::UsdSkelAnimation anim = usd_export_context_.usd_define_or_over<pxr::UsdSkelAnimation>(
anim_path);
if (anim) {
pxr::VtFloatArray weights = get_blendshape_weights(key);
pxr::UsdAttribute weights_attr = anim.CreateBlendShapeWeightsAttr();
pxr::UsdTimeCode timecode = get_export_time_code();
weights_attr.Set(weights, timecode);
}
}
pxr::UsdSkelSkeleton USDBlendShapeMeshWriter::get_skeleton(const HierarchyContext &context) const
{
pxr::SdfPath skel_path = usd_export_context_.usd_path.GetParentPath().AppendChild(
usdtokens::Skel);
pxr::UsdSkelSkeleton skel = usd_export_context_.usd_define_or_over<pxr::UsdSkelSkeleton>(
skel_path);
/* Initialize the skeleton. */
pxr::VtMatrix4dArray bind_transforms(1, pxr::GfMatrix4d(1.0));
pxr::VtMatrix4dArray rest_transforms(1, pxr::GfMatrix4d(1.0));
skel.CreateBindTransformsAttr().Set(bind_transforms);
skel.GetRestTransformsAttr().Set(rest_transforms);
/* Some DCCs seem to require joint names to bind the
* skeleton to blendshapes. */
pxr::VtTokenArray joints({usdtokens::joint1});
skel.CreateJointsAttr().Set(joints);
/* Specify the animation source on the skeleton. */
pxr::UsdSkelBindingAPI skel_api(skel.GetPrim());
skel_api.CreateAnimationSourceRel().AddTarget(pxr::SdfPath(usdtokens::Anim));
return skel;
}
Mesh *USDBlendShapeMeshWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
{
/* We must check if blendshapes are enabled before attempting to create the
* blendshape mesh. */
if (!(usd_export_context_.export_params.export_blendshapes && is_blendshape_mesh(object_eval))) {
/* Get the default mesh. */
return USDMeshWriter::get_export_mesh(object_eval, r_needsfree);
}
if (object_eval->type != OB_MESH) {
return nullptr;
}
Mesh *src_mesh = BKE_object_get_pre_modified_mesh(object_eval);
if (!src_mesh || !src_mesh->key || !src_mesh->key->block.first) {
return nullptr;
}
KeyBlock *basis = reinterpret_cast<KeyBlock *>(src_mesh->key->block.first);
if (src_mesh->totvert != basis->totelem) {
WM_reportf(RPT_WARNING,
"USD Export: mesh %s can't be exported as a blendshape because the mesh vertex count %d "
"doesn't match shape key number of elements %d'. This may be because the mesh topology was "
"changed by a modifier. Exporting meshes with modifiers as blendshapes isn't currently supported",
object_eval->id.name + 2,
src_mesh->totvert,
basis->totelem);
return USDMeshWriter::get_export_mesh(object_eval, r_needsfree);
}
Mesh *temp_mesh = reinterpret_cast<Mesh *>(
BKE_id_copy_ex(nullptr, &src_mesh->id, nullptr, LIB_ID_COPY_LOCALIZE));
BKE_keyblock_convert_to_mesh(
basis,
reinterpret_cast<float(*)[3]>(temp_mesh->vert_positions_for_write().data()),
temp_mesh->totvert);
r_needsfree = true;
return temp_mesh;
}
/* Blend shape meshes are never animated, but the blendshape writer itself
* might be animating as it must add time samples to skeletal animations.
* This function ensures that the mesh data is written as non-timesampled.
* This is currently required to work around a bug in Create which causes
* a crash if the blendhshape mesh is timesampled. */
pxr::UsdTimeCode USDBlendShapeMeshWriter::get_mesh_export_time_code() const
{
/* By using the default timecode USD won't even write a single `timeSample` for non-animated
* data. Instead, it writes it as non-timesampled. */
static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default();
return default_timecode;
}
bool USDBlendShapeMeshWriter::exporting_anim(const Key *shape_key) const
{
return usd_export_context_.export_params.export_animation && shape_key && shape_key->adt;
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,59 @@
/*
* 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) 2022 NVIDIA Corporation.
* All rights reserved.
*/
#pragma once
#include "usd_writer_mesh.h"
#include <pxr/usd/usdSkel/bindingAPI.h>
#include <pxr/usd/usdSkel/skeleton.h>
struct Key;
namespace blender::io::usd {
bool is_blendshape_mesh(Object *obj);
class USDBlendShapeMeshWriter : public USDMeshWriter {
public:
USDBlendShapeMeshWriter(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;
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
virtual pxr::UsdTimeCode get_mesh_export_time_code() const override;
virtual pxr::UsdSkelSkeleton get_skeleton(const HierarchyContext &context) const;
void write_blendshape(HierarchyContext &context) const;
void create_blend_shapes(const Key *shape_key,
const pxr::UsdPrim &mesh_prim,
const pxr::UsdSkelSkeleton &skel) const;
void add_weights_sample(const Key *shape_key, const pxr::UsdSkelSkeleton &skel) const;
bool exporting_anim(const Key *shape_key) const;
};
} // namespace blender::io::usd

View File

@@ -12,6 +12,10 @@
#include "DNA_camera_types.h"
#include "DNA_scene_types.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_types.h"
namespace blender::io::usd {
USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
@@ -24,51 +28,64 @@ bool USDCameraWriter::is_supported(const HierarchyContext *context) const
return camera->type == CAM_PERSP;
}
static void camera_sensor_size_for_render(const Camera *camera,
const struct RenderData *rd,
float *r_sensor_x,
float *r_sensor_y)
{
/* Compute the final image size in pixels. */
float sizex = rd->xsch * rd->xasp;
float sizey = rd->ysch * rd->yasp;
int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey);
switch (sensor_fit) {
case CAMERA_SENSOR_FIT_HOR:
*r_sensor_x = camera->sensor_x;
*r_sensor_y = camera->sensor_x * sizey / sizex;
break;
case CAMERA_SENSOR_FIT_VERT:
*r_sensor_x = camera->sensor_y * sizex / sizey;
*r_sensor_y = camera->sensor_y;
break;
case CAMERA_SENSOR_FIT_AUTO:
BLI_assert_msg(0, "Camera fit should be either horizontal or vertical");
break;
}
}
//static void camera_sensor_size_for_render(const Camera *camera,
// const struct RenderData *rd,
// float *r_sensor_x,
// float *r_sensor_y)
//{
// /* Compute the final image size in pixels. */
// float sizex = rd->xsch * rd->xasp;
// float sizey = rd->ysch * rd->yasp;
//
// int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey);
//
// switch (sensor_fit) {
// case CAMERA_SENSOR_FIT_HOR:
// *r_sensor_x = camera->sensor_x;
// *r_sensor_y = camera->sensor_x * sizey / sizex;
// break;
// case CAMERA_SENSOR_FIT_VERT:
// *r_sensor_x = camera->sensor_y * sizex / sizey;
// *r_sensor_y = camera->sensor_y;
// break;
// case CAMERA_SENSOR_FIT_AUTO:
// BLI_assert_msg(0, "Camera fit should be either horizontal or vertical");
// break;
// }
//}
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::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
pxr::UsdGeomCamera usd_camera = (usd_export_context_.export_params.export_as_overs) ?
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);
Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
usd_camera.CreateProjectionAttr().Set(pxr::UsdGeomTokens->perspective);
/* USD stores the focal length in "millimeters or tenths of world units", because at some point
* they decided world units might be centimeters. Quite confusing, as the USD Viewer shows the
* correct FoV when we write millimeters and not "tenths of world units".
*/
usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
/*
* For USD, these camera properties are in tenths of a world unit.
* https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits
* val_in_tenths_of_units = val_in_meters * 10.0f * (units / meter)
*/
const float scale_from_mm = .001f * 10.0f * unit_scale;
float aperture_x, aperture_y;
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);
const float focal_length = camera->lens * scale_from_mm;
usd_camera.CreateFocalLengthAttr().Set(focal_length, timecode);
/* TODO(makowalski): Maybe add option to call camera_sensor_size_for_render(). */
/*float aperture_x, aperture_y;
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);*/
float aperture_x = camera->sensor_x * scale_from_mm;
float aperture_y = camera->sensor_y * scale_from_mm;
float film_aspect = aperture_x / aperture_y;
usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
@@ -76,18 +93,37 @@ void USDCameraWriter::do_write(HierarchyContext &context)
usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
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(
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. */
if (camera->dof.flag & CAM_DOF_ENABLED) {
usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
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);
}
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

View File

@@ -0,0 +1,255 @@
/*
* 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>
#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 * curve->bevel_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 * curve->bevel_radius * 2.0f);
}
verts.push_back(pxr::GfVec3f(bezier->vec[1]));
widths.push_back(bezier->radius * curve->bevel_radius * 2.0f);
if (i < totpoint - 1 || is_cyclic) {
verts.push_back(pxr::GfVec3f(bezier->vec[2]));
widths.push_back(bezier->radius * curve->bevel_radius * 2.0f);
}
}
if (is_cyclic) {
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[0]));
widths.push_back(nurbs->bezt->radius * curve->bevel_radius * 2.0f);
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[1]));
widths.push_back(nurbs->bezt->radius * curve->bevel_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,11 +3,16 @@
#include "usd_writer_hair.h"
#include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/basisCurves.h>
#include <pxr/usd/usdGeom/tokens.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
#include "BKE_material.h"
#include "BKE_particle.h"
#include "BLI_math_geom.h"
#include "DNA_particle_types.h"
namespace blender::io::usd {
@@ -16,8 +21,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)
{
/* Get untransformed vertices, there's a xform under the hair. */
float inv_mat[4][4];
invert_m4_m4_safe(inv_mat, context.object->object_to_world);
ParticleSystem *psys = context.particle_system;
ParticleCacheKey **cache = psys->pathcache;
if (cache == nullptr) {
@@ -25,17 +50,34 @@ void USDHairWriter::do_write(HierarchyContext &context)
}
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
pxr::UsdGeomBasisCurves 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);
/* TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE) */
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
if (psys->part->flag & PART_HAIR_BSPLINE) {
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<float> widths;
pxr::VtIntArray curve_point_counts;
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;
for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
strand = cache[strand_index];
@@ -44,18 +86,49 @@ void USDHairWriter::do_write(HierarchyContext &context)
curve_point_counts.push_back(point_count);
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_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
if (!attr_points.HasValue()) {
attr_points.Set(points, pxr::UsdTimeCode::Default());
attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default());
}
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(points), 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) {
pxr::VtArray<pxr::GfVec3f> colors;
@@ -63,6 +136,15 @@ void USDHairWriter::do_write(HierarchyContext &context)
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);
}
this->author_extent(timecode, curves);
}
@@ -71,4 +153,24 @@ bool USDHairWriter::check_is_animated(const HierarchyContext & /*context*/) cons
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

View File

@@ -4,6 +4,8 @@
#include "usd_writer_abstract.h"
#include <pxr/usd/usdGeom/basisCurves.h>
namespace blender::io::usd {
/* Writer for writing hair particle data as USD curves. */
@@ -14,6 +16,7 @@ class USDHairWriter : public USDAbstractWriter {
protected:
virtual void do_write(HierarchyContext &context) 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

View File

@@ -2,18 +2,36 @@
* Copyright 2019 Blender Foundation. All rights reserved. */
#include "usd_writer_light.h"
#include "usd_hierarchy_iterator.h"
#include "usd_light_convert.h"
#include <pxr/usd/usdLux/diskLight.h>
#include <pxr/usd/usdLux/distantLight.h>
#include <pxr/usd/usdLux/rectLight.h>
#include <pxr/usd/usdLux/shapingAPI.h>
#include <pxr/usd/usdLux/sphereLight.h>
#include "BLI_assert.h"
#include "BLI_math.h"
#include "BLI_math_matrix.h"
#include "BLI_utildefines.h"
#include "DNA_light_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 {
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
{
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)
{
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
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);
#if PXR_VERSION >= 2111
pxr::UsdLuxLightAPI usd_light_api;
@@ -45,8 +69,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
switch (light->area_shape) {
case LA_AREA_DISK:
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
disk_light.CreateRadiusAttr().Set(light->area_size, timecode);
pxr::UsdLuxDiskLight disk_light =
(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
usd_light_api = disk_light.LightAPI();
#else
@@ -55,9 +92,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
break;
}
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.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
usd_light_api = rect_light.LightAPI();
#else
@@ -66,9 +122,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
break;
}
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.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
usd_light_api = rect_light.LightAPI();
#else
@@ -79,8 +154,22 @@ void USDLightWriter::do_write(HierarchyContext &context)
}
break;
case LA_LOCAL: {
pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path);
sphere_light.CreateRadiusAttr().Set(light->radius, timecode);
pxr::UsdLuxSphereLight sphere_light =
(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->radius * 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->radius * radius_scale, timecode);
}
}
#if PXR_VERSION >= 2111
usd_light_api = sphere_light.LightAPI();
#else
@@ -88,13 +177,56 @@ void USDLightWriter::do_write(HierarchyContext &context)
#endif
break;
}
case LA_SUN: {
pxr::UsdLuxDistantLight distant_light = pxr::UsdLuxDistantLight::Define(stage, usd_path);
/* TODO(makowalski): set angle attribute here. */
case LA_SPOT: {
pxr::UsdLuxSphereLight spot_light =
(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
usd_light_api = distant_light.LightAPI();
usd_light_api = spot_light.LightAPI();
#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 * (180.0f / (float)M_PI), 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 * (180.0f / (float)M_PI), timecode);
}
}
#if PXR_VERSION >= 2111
usd_light_api = sun_light.LightAPI();
#else
usd_light_api = sun_light;
#endif
break;
}
@@ -102,21 +234,39 @@ void USDLightWriter::do_write(HierarchyContext &context)
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
* over-exposure as Blender Internal with the same values, this code applies the reverse of the
* versioning code in light_emission_unify(). */
float usd_intensity;
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;
float usd_intensity = light->energy * usd_export_context_.export_params.light_intensity_scale;
if (usd_export_context_.export_params.convert_light_to_nits) {
usd_intensity /= nits_to_energy_scale_factor(light, world_scale, radius_scale);
}
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.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

File diff suppressed because it is too large Load Diff

View File

@@ -8,12 +8,33 @@
#include <string>
struct bNode;
struct bNodeTree;
struct Material;
struct USDExportParams;
struct Material;
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;
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);
/* Returns a USDPreviewSurface token name for a given Blender shader Socket name,
* or an empty TfToken if the input name is not found in the map. */
const pxr::TfToken token_for_input(const char *input_name);
@@ -41,4 +62,21 @@ void create_usd_viewport_material(const USDExporterContext &usd_export_context,
Material *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,
bool allow_overwrite = false);
} // namespace blender::io::usd

View File

@@ -17,40 +17,123 @@
#include "BKE_attribute.hh"
#include "BKE_customdata.h"
#include "BKE_lib_id.h"
#include "BKE_library.h"
#include "BKE_material.h"
#include "BKE_mesh.h"
#include "BKE_mesh_runtime.h"
#include "BKE_mesh_wrapper.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "DEG_depsgraph.h"
#include "bmesh.h"
#include "bmesh_tools.h"
#include "DEG_depsgraph.h"
#include "DNA_layer_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_fluidsim_types.h"
#include "DNA_object_types.h"
#include "DNA_particle_types.h"
#include <iostream>
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(Object *ob, ModifierMode mode)
{
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
for (; md; md = md->prev) {
if (!BKE_modifier_is_enabled(nullptr, md, mode)) {
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)
{
}
bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
{
if (usd_export_context_.export_params.visible_objects_only) {
return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
// TODO(makowalski) -- Check if we should be calling is_object_visible() below.
// 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)
{
Object *object_eval = context.object;
const ModifierMode mode = usd_export_context_.export_params.evaluation_mode ==
DAG_EVAL_VIEWPORT ?
eModifierMode_Realtime :
eModifierMode_Render;
m_subsurf_mod = get_subsurf_modifier(context.object, mode);
const bool should_disable_temporary = m_subsurf_mod && !usd_export_context_.export_params.apply_subdiv;
if (should_disable_temporary) {
m_subsurf_mod->mode |= eModifierMode_DisableTemporary;
}
bool needsfree = false;
Mesh *mesh = get_export_mesh(object_eval, needsfree);
@@ -58,6 +141,29 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
return;
}
if (usd_export_context_.export_params.triangulate_meshes) {
const bool tag_only = false;
const int quad_method = usd_export_context_.export_params.quad_method;
const int ngon_method = usd_export_context_.export_params.ngon_method;
BMeshCreateParams bmesh_create_params{};
BMeshFromMeshParams bmesh_from_mesh_params{};
bmesh_from_mesh_params.calc_face_normal = true;
bmesh_from_mesh_params.calc_vert_normal = true;
BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmesh_create_params, &bmesh_from_mesh_params);
BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, nullptr, nullptr, nullptr);
Mesh *triangulated_mesh = BKE_mesh_from_bmesh_for_eval_nomain(bm, nullptr, mesh);
BM_mesh_free(bm);
if (needsfree) {
free_export_mesh(mesh);
}
mesh = triangulated_mesh;
needsfree = true;
}
try {
write_mesh(context, mesh);
@@ -71,6 +177,17 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
}
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 (should_disable_temporary) {
m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
}
}
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
@@ -78,6 +195,11 @@ void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
BKE_id_free(nullptr, mesh);
}
pxr::UsdTimeCode USDGenericMeshWriter::get_mesh_export_time_code() const
{
return get_export_time_code();
}
struct USDMeshData {
pxr::VtArray<pxr::GfVec3f> points;
pxr::VtIntArray face_vertex_counts;
@@ -107,49 +229,292 @@ struct USDMeshData {
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_PROP_FLOAT2) :
-1;
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
const CustomDataLayer *layer = &ldata->layers[layer_idx];
if (layer->type == CD_PROP_FLOAT2 && 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::UsdGeomPrimvarsAPI primvarsAPI(usd_mesh.GetPrim());
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
* 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(name_override ? name_override :
pxr::TfMakeValidIdentifier(layer->name));
const CustomData *ldata = &mesh->ldata;
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
const CustomDataLayer *layer = &ldata->layers[layer_idx];
if (layer->type != CD_PROP_FLOAT2) {
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 primvarsAPI = pxr::UsdGeomPrimvarsAPI(usd_mesh);
pxr::UsdGeomPrimvar uv_coords_primvar = primvarsAPI.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
const float2 *mloopuv = static_cast<const float2 *>(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].x, mloopuv[loop_idx].y));
}
// 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;
}
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
* 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 = primvarsAPI.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(def->name));
pxr::TfToken primvar_interpolation = (as_point_groups) ? pxr::UsdGeomTokens->vertex :
pxr::UsdGeomTokens->faceVarying;
const float2 *mloopuv = static_cast<const float2 *>(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].x, mloopuv[loop_idx].y));
}
pxr::UsdGeomPrimvarsAPI primvarsAPI(usd_mesh.GetPrim());
if (!uv_coords_primvar.HasValue()) {
uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
pv_groups.push_back(primvarsAPI.CreatePrimvar(
primvar_name, pxr::SdfValueTypeNames->FloatArray, primvar_interpolation));
size_t primvar_size = 0;
if (as_point_groups) {
primvar_size = mesh->totvert;
}
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 (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));
}
size_t num_groups = pv_groups.size();
if (num_groups == 0)
return;
// Extract vertex groups
if (as_point_groups) {
const blender::Span<MDeformVert> verts = mesh->deform_verts();
for (i = 0; i < verts.size(); i++) {
// Init to zero
for (j = 0; j < num_groups; j++) {
pv_data[j][i] = 0.0f;
}
for (j = 0; j < verts[i].totweight; j++) {
uint idx = verts[i].dw[j].def_nr;
float w = verts[i].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;
}
}
}
}
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;
pxr::UsdGeomPrimvarsAPI primvarsAPI = pxr::UsdGeomPrimvarsAPI(usd_mesh);
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(primvarsAPI.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)
{
pxr::UsdTimeCode timecode = get_export_time_code();
pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
pxr::UsdTimeCode timecode = get_mesh_export_time_code();
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);
USDMeshData usd_mesh_data;
@@ -157,60 +522,51 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
BKE_mesh_wrapper_ensure_mdata(mesh);
get_geometry_data(mesh, usd_mesh_data);
if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
if (!mark_as_instance(context, usd_mesh.GetPrim())) {
return;
}
/* 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(),
if (usd_export_context_.export_params.export_vertices) {
pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
true);
if (!attr_points.HasValue()) {
/* Provide the initial value as default. This makes USD write the value as constant if they
* don't change over time. */
attr_points.Set(usd_mesh_data.points, defaultTime);
attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(
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(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);
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);
write_custom_data(mesh, usd_mesh);
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);
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_export_context_.export_params.export_vertex_groups) {
write_vertex_groups(context.object,
mesh,
usd_mesh,
!usd_export_context_.export_params.vertex_data_as_face_varying);
write_face_maps(context.object, mesh, usd_mesh);
}
if (!usd_mesh_data.corner_indices.empty() &&
@@ -220,8 +576,8 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
pxr::VtValue(), true);
if (!attr_corner_indices.HasValue()) {
attr_corner_indices.Set(usd_mesh_data.corner_indices, defaultTime);
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, defaultTime);
attr_corner_indices.Set(usd_mesh_data.corner_indices, timecode);
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, timecode);
}
usd_value_writer_.SetAttribute(
@@ -230,9 +586,6 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
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) {
write_normals(mesh, usd_mesh);
}
@@ -243,7 +596,10 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
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) {
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
@@ -462,9 +818,10 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
}
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_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
}
@@ -500,7 +857,10 @@ USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWrit
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

View File

@@ -6,6 +6,8 @@
#include <pxr/usd/usdGeom/mesh.h>
struct ModifierData;
namespace blender::io::usd {
struct USDMeshData;
@@ -22,6 +24,11 @@ class USDGenericMeshWriter : public USDAbstractWriter {
virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
virtual void free_export_mesh(Mesh *mesh);
/* Get time code for writing mesh properties.
* The default implementation is equivalent to
* calling USDAbstractWriter::get_export_time_code(). */
virtual pxr::UsdTimeCode get_mesh_export_time_code() const;
private:
/* Mapping from material slot number to array of face indices with that material. */
typedef std::map<short, pxr::VtIntArray> MaterialFaceGroups;
@@ -31,9 +38,23 @@ class USDGenericMeshWriter : public USDAbstractWriter {
void assign_materials(const HierarchyContext &context,
pxr::UsdGeomMesh usd_mesh,
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_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
protected:
ModifierData *m_subsurf_mod;
};
class USDMeshWriter : public USDGenericMeshWriter {

View File

@@ -0,0 +1,150 @@
/*
* 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>
#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,347 @@
/*
* 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 "usd_writer_transform.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)
: USDBlendShapeMeshWriter(ctx)
{
}
void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
{
if (this->frame_has_been_written_) {
/* Only blendshapes may be animated on skinned meshes. */
if (usd_export_context_.export_params.export_blendshapes) {
write_blendshape(context);
}
return;
}
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::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;
}
pxr::SdfPath skel_path = get_skel_path(arm_obj);
if (skel_path.IsEmpty()) {
printf("WARNING: couldn't get USD skeleton path for skinned mesh %s\n",
this->usd_export_context_.usd_path.GetString().c_str());
return;
}
usd_skel_api.CreateSkeletonRel().SetTargets(pxr::SdfPathVector({skel_path}));
if (pxr::UsdAttribute geom_bind_attr = usd_skel_api.CreateGeomBindTransformAttr()) {
pxr::GfMatrix4f mat_world(context.matrix_world);
/* The context world matrix does not include the unit
* conversion scaling or axis rotation that may be applied
* to root primitives on export, so we must include those,
* if necessary. */
float convert_mat[4][4];
get_export_conversion_matrix(usd_export_context_.export_params, convert_mat);
geom_bind_attr.Set(pxr::GfMatrix4d(mat_world) * pxr::GfMatrix4d(convert_mat));
}
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);
}
if (usd_export_context_.export_params.export_blendshapes) {
write_blendshape(context);
}
}
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->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;
}
}
group_to_bone_idx.push_back(bone_idx);
}
if (group_to_bone_idx.empty()) {
return;
}
const Span<MDeformVert> dverts = mesh->deform_verts();
int max_totweight = 1;
for (const int i : dverts.index_range()) {
const MDeformVert &vert = dverts[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 (const int i : dverts.index_range()) {
const MDeformVert &vert = dverts[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
{
return usd_export_context_.export_params.export_blendshapes &&
USDBlendShapeMeshWriter::check_is_animated(context);
}
pxr::SdfPath USDSkinnedMeshWriter::get_skel_path(Object *arm_obj) const
{
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()) {
return pxr::SdfPath();
}
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;
}
return pxr::SdfPath(skel_path);
}
pxr::UsdSkelSkeleton USDSkinnedMeshWriter::get_skeleton(const HierarchyContext &context) const
{
Object *arm_obj = get_armature_obj(context.object);
if (!arm_obj) {
return pxr::UsdSkelSkeleton();
}
pxr::SdfPath skel_path = get_skel_path(arm_obj);
return usd_export_context_.usd_define_or_over<pxr::UsdSkelSkeleton>(skel_path);
}
} // namespace blender::io::usd

View File

@@ -0,0 +1,52 @@
/*
* 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_blendshape_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 USDBlendShapeMeshWriter {
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;
pxr::SdfPath get_skel_path(Object *arm_obj) const;
virtual pxr::UsdSkelSkeleton get_skeleton(const HierarchyContext &context) const override;
};
} // namespace blender::io::usd

View File

@@ -4,32 +4,182 @@
#include "usd_hierarchy_iterator.h"
#include <pxr/base/gf/matrix4f.h>
#include <pxr/base/gf/quaternion.h>
#include <pxr/usd/usdGeom/xform.h>
#include "BKE_object.h"
#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "DNA_layer_types.h"
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},
};
/* Returns in r_mat tha unit scaling and axis rotation transforms
* applied to root prims on export. */
void get_export_conversion_matrix(const USDExportParams &params, float r_mat[4][4])
{
unit_m4(r_mat);
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);
copy_m4_m3(r_mat, mrot);
}
if (params.convert_to_cm) {
float scale_mat[4][4];
scale_m4_fl(scale_mat, 100.0f);
mul_m4_m4m4(r_mat, scale_mat, r_mat);
}
}
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)
{
float parent_relative_matrix[4][4]; /* The object matrix relative to the parent. */
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
pxr::UsdGeomXformable xform = create_xformable();
/* Write the transform relative to the parent. */
pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage,
usd_export_context_.usd_path);
if (!xformOp_) {
xformOp_ = xform.AddTransformOp();
if (!xform) {
printf("INTERNAL ERROR: USDTransformWriter: couldn't create xformable.\n");
return;
}
if (usd_export_context_.export_params.export_transforms) {
float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
// TODO(makowalski): This is inefficient checking for every transform and should be moved elsewhere.
// TODO(makowalski): Use get_export_conversion_matrix() here, to avoid duplicating code.
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
@@ -40,10 +190,79 @@ bool USDTransformWriter::check_is_animated(const HierarchyContext &context) cons
* depsgraph whether this object instance has a time source. */
return true;
}
if (check_has_physics(context)) {
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);
}
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

View File

@@ -6,11 +6,15 @@
#include <pxr/usd/usdGeom/xform.h>
#include <vector>
namespace blender::io::usd {
void get_export_conversion_matrix(const USDExportParams &params, float r_mat[4][4]);
class USDTransformWriter : public USDAbstractWriter {
private:
pxr::UsdGeomXformOp xformOp_;
std::vector<pxr::UsdGeomXformOp> xformOps_;
public:
USDTransformWriter(const USDExporterContext &ctx);
@@ -18,6 +22,16 @@ class USDTransformWriter : public USDAbstractWriter {
protected:
void do_write(HierarchyContext &context) 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

View File

@@ -9,10 +9,54 @@
extern "C" {
#endif
struct bContext;
struct CacheArchiveHandle;
struct CacheReader;
struct CacheFile;
struct Object;
struct bContext;
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;
typedef enum eUSDZTextureDownscaleSize {
USD_TEXTURE_SIZE_CUSTOM = -1,
USD_TEXTURE_SIZE_KEEP = 0,
USD_TEXTURE_SIZE_256 = 256,
USD_TEXTURE_SIZE_512 = 512,
USD_TEXTURE_SIZE_1024 = 1024,
USD_TEXTURE_SIZE_2048 = 2048,
USD_TEXTURE_SIZE_4096 = 4096
} eUSDZTextureDownscaleSize;
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;
/* Behavior when the name of an imported material
* conflicts with an existing material. */
@@ -21,6 +65,20 @@ typedef enum eUSDMtlNameCollisionMode {
USD_MTL_NAME_COLLISION_REFERENCE_EXISTING = 1,
} eUSDMtlNameCollisionMode;
typedef enum eUSDAttrImportMode {
USD_ATTR_IMPORT_NONE = 0,
USD_ATTR_IMPORT_USER = 1,
USD_ATTR_IMPORT_ALL = 2,
} eUSDAttrImportMode;
typedef enum eUSDDefaultPrimKind {
USD_KIND_NONE = 0,
USD_KIND_COMPONENT,
USD_KIND_GROUP,
USD_KIND_ASSEMBLY,
USD_KIND_CUSTOM
} eUSDDefaultPrimKind;
/* Behavior when importing textures from a package
* (e.g., USDZ archive) or from a URI path. */
typedef enum eUSDTexImportMode {
@@ -37,19 +95,75 @@ typedef enum eUSDTexNameCollisionMode {
} eUSDTexNameCollisionMode;
struct USDExportParams {
double frame_start;
double frame_end;
bool export_animation;
bool export_hair;
bool export_vertices;
bool export_vertex_colors;
bool export_vertex_groups;
bool export_face_maps;
bool export_uvmaps;
bool export_normals;
bool export_transforms;
bool export_materials;
bool export_meshes;
bool export_lights;
bool export_cameras;
bool export_curves;
bool export_particles;
bool selected_objects_only;
bool visible_objects_only;
bool use_instancing;
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 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 overwrite_textures;
bool relative_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;
bool export_blendshapes;
eUSDZTextureDownscaleSize usdz_downscale_size;
int usdz_downscale_custom_size;
bool usdz_is_arkit;
bool export_blender_metadata;
bool triangulate_meshes;
int quad_method;
int ngon_method;
bool export_usd_kind;
eUSDDefaultPrimKind default_prim_kind;
char *default_prim_custom_kind;
};
struct USDImportParams {
@@ -65,9 +179,10 @@ struct USDImportParams {
bool import_lights;
bool import_materials;
bool import_meshes;
bool import_blendshapes;
bool import_volumes;
bool import_shapes;
char prim_path_mask[1024];
bool import_skeletons;
char *prim_path_mask;
bool import_subdiv;
bool import_instance_proxies;
bool create_collection;
@@ -76,10 +191,18 @@ struct USDImportParams {
bool import_render;
bool import_visible_only;
bool use_instancing;
bool import_usd_preview;
eUSDImportShadersMode import_shaders_mode;
bool set_material_blend;
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;
bool triangulate_meshes;
bool import_shapes;
bool import_defined_only;
eUSDTexImportMode import_textures_mode;
char import_textures_dir[768]; /* FILE_MAXDIR */
eUSDTexNameCollisionMode tex_name_collision_mode;
@@ -107,8 +230,16 @@ bool USD_import(struct bContext *C,
int USD_get_version(void);
bool USD_umm_module_loaded(void);
/* USD Import and Mesh Cache interface. */
/* Similar to BLI_path_abs(), but also invokes the USD asset resolver
* to determine the absolute path. This is necessary for resolving
* paths with URIs that BLI_path_abs() would otherwise alter when
* attempting to normalize the path. */
void USD_path_abs(char *path, const char *basepath, bool for_import);
struct CacheArchiveHandle *USD_create_handle(struct Main *bmain,
const char *filepath,
struct ListBase *object_paths);