Compare commits
40 Commits
temp-ui-cp
...
tmp-usd-ma
Author | SHA1 | Date | |
---|---|---|---|
cebad553c9 | |||
5bddc84c86 | |||
15d1b8f35d | |||
0bfece3525 | |||
b1235c5858 | |||
93a9e201f1 | |||
e0f6dcfc3c | |||
82964294a5 | |||
d742007c8e | |||
2b3f5cb965 | |||
18320355ea | |||
a9c8425de8 | |||
2b6306c1ea | |||
1e13fc105a | |||
803f19b413 | |||
d9ca13066f | |||
8ca67ef025 | |||
8ef0925c83 | |||
27c6f3fca5 | |||
7345fe7c8c | |||
f796b72cf5 | |||
da766dd71c | |||
f71ad78dc1 | |||
85172cb5e1 | |||
e2a783f8eb | |||
265df7b3a6 | |||
f1828d3430 | |||
1496105327 | |||
3f2a1fa87c | |||
63dfc81631 | |||
5724b0dd41 | |||
f42ca488d2 | |||
eb747dbc66 | |||
8513cc6e44 | |||
ffa078a079 | |||
0562c8b250 | |||
182443da4b | |||
2e32a0871f | |||
baeeb1488e | |||
d143e8ff75 |
File diff suppressed because it is too large
Load Diff
@@ -199,7 +199,9 @@ class AbstractHierarchyIterator {
|
|||||||
typedef std::map<ObjectIdentifier, ExportChildren> ExportGraph;
|
typedef std::map<ObjectIdentifier, ExportChildren> ExportGraph;
|
||||||
/* Mapping from ID to its export path. This is used for instancing; given an
|
/* Mapping from ID to its export path. This is used for instancing; given an
|
||||||
* instanced datablock, the export path of the original can be looked up. */
|
* instanced datablock, the export path of the original can be looked up. */
|
||||||
typedef std::map<ID *, std::string> ExportPathMap;
|
typedef std::map<const ID *, std::string> ExportPathMap;
|
||||||
|
/* Set of IDs of objects that are the originals of instances. */
|
||||||
|
typedef std::set<const ID *> PrototypeObjects;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
ExportGraph export_graph_;
|
ExportGraph export_graph_;
|
||||||
@@ -207,6 +209,7 @@ class AbstractHierarchyIterator {
|
|||||||
Depsgraph *depsgraph_;
|
Depsgraph *depsgraph_;
|
||||||
WriterMap writers_;
|
WriterMap writers_;
|
||||||
ExportSubset export_subset_;
|
ExportSubset export_subset_;
|
||||||
|
PrototypeObjects prototypes_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
|
explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
|
||||||
@@ -243,6 +246,19 @@ class AbstractHierarchyIterator {
|
|||||||
* data to be a child of the object. */
|
* data to be a child of the object. */
|
||||||
virtual std::string get_object_data_path(const HierarchyContext *context) const;
|
virtual std::string get_object_data_path(const HierarchyContext *context) const;
|
||||||
|
|
||||||
|
/* Returns the export path computed for the object with the given ID.
|
||||||
|
* This should be called after only all writers have been created for the
|
||||||
|
* dependency graph. This currently works for non-instanced objects only. */
|
||||||
|
std::string get_object_export_path(const ID *id) const;
|
||||||
|
|
||||||
|
/* Return true if the object with the given id is a prototype object
|
||||||
|
* for instancing. Returns false otherwise. */
|
||||||
|
bool is_prototype(const ID *id) const;
|
||||||
|
|
||||||
|
/* Return true if the object is a prototype object
|
||||||
|
* for instancing. Returns false otherwise. */
|
||||||
|
bool is_prototype(const Object *obj) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void debug_print_export_graph(const ExportGraph &graph) const;
|
void debug_print_export_graph(const ExportGraph &graph) const;
|
||||||
|
|
||||||
@@ -330,6 +346,18 @@ class AbstractHierarchyIterator {
|
|||||||
/* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
|
/* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
|
||||||
virtual void release_writer(AbstractHierarchyWriter *writer) = 0;
|
virtual void release_writer(AbstractHierarchyWriter *writer) = 0;
|
||||||
|
|
||||||
|
/* Return true if data writers should be created for this context. */
|
||||||
|
virtual bool include_data_writers(const HierarchyContext *) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return true if children of the context should be converted to writers. */
|
||||||
|
virtual bool include_child_writers(const HierarchyContext *) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
AbstractHierarchyWriter *get_writer(const std::string &export_path) const;
|
AbstractHierarchyWriter *get_writer(const std::string &export_path) const;
|
||||||
ExportChildren &graph_children(const HierarchyContext *parent_context);
|
ExportChildren &graph_children(const HierarchyContext *parent_context);
|
||||||
};
|
};
|
||||||
|
@@ -222,6 +222,28 @@ std::string AbstractHierarchyIterator::get_object_data_path(const HierarchyConte
|
|||||||
return path_concatenate(context->export_path, get_object_data_name(context->object));
|
return path_concatenate(context->export_path, get_object_data_name(context->object));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string AbstractHierarchyIterator::get_object_export_path(const ID *id) const
|
||||||
|
{
|
||||||
|
const ExportPathMap::const_iterator &it = duplisource_export_path_.find(id);
|
||||||
|
|
||||||
|
if (it != duplisource_export_path_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractHierarchyIterator::is_prototype(const ID *id) const
|
||||||
|
{
|
||||||
|
return prototypes_.find(id) != prototypes_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AbstractHierarchyIterator::is_prototype(const Object *obj) const
|
||||||
|
{
|
||||||
|
const ID *obj_id = reinterpret_cast<const ID *>(obj);
|
||||||
|
return is_prototype(obj_id);
|
||||||
|
}
|
||||||
|
|
||||||
void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const
|
void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const
|
||||||
{
|
{
|
||||||
size_t total_graph_size = 0;
|
size_t total_graph_size = 0;
|
||||||
@@ -282,9 +304,14 @@ void AbstractHierarchyIterator::export_graph_construct()
|
|||||||
|
|
||||||
/* Export the duplicated objects instanced by this object. */
|
/* Export the duplicated objects instanced by this object. */
|
||||||
ListBase *lb = object_duplilist(depsgraph_, scene, object);
|
ListBase *lb = object_duplilist(depsgraph_, scene, object);
|
||||||
if (lb) {
|
|
||||||
|
if (lb && object->particlesystem.first == nullptr) {
|
||||||
DupliParentFinder dupli_parent_finder;
|
DupliParentFinder dupli_parent_finder;
|
||||||
|
|
||||||
|
// Construct the set of duplicated objects, so that later we can determine whether a parent
|
||||||
|
// is also duplicated itself.
|
||||||
|
std::set<Object *> dupli_set;
|
||||||
|
|
||||||
LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
|
LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
|
||||||
PersistentID persistent_id(dupli_object);
|
PersistentID persistent_id(dupli_object);
|
||||||
if (!should_visit_dupli_object(dupli_object)) {
|
if (!should_visit_dupli_object(dupli_object)) {
|
||||||
@@ -405,7 +432,7 @@ void AbstractHierarchyIterator::visit_object(Object *object,
|
|||||||
context->export_parent = export_parent;
|
context->export_parent = export_parent;
|
||||||
context->duplicator = nullptr;
|
context->duplicator = nullptr;
|
||||||
context->weak_export = weak_export;
|
context->weak_export = weak_export;
|
||||||
context->animation_check_include_parent = false;
|
context->animation_check_include_parent = true;
|
||||||
context->export_path = "";
|
context->export_path = "";
|
||||||
context->original_export_path = "";
|
context->original_export_path = "";
|
||||||
context->higher_up_export_path = "";
|
context->higher_up_export_path = "";
|
||||||
@@ -439,6 +466,7 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
|
|||||||
Object *duplicator,
|
Object *duplicator,
|
||||||
const DupliParentFinder &dupli_parent_finder)
|
const DupliParentFinder &dupli_parent_finder)
|
||||||
{
|
{
|
||||||
|
|
||||||
HierarchyContext *context = new HierarchyContext();
|
HierarchyContext *context = new HierarchyContext();
|
||||||
context->object = dupli_object->ob;
|
context->object = dupli_object->ob;
|
||||||
context->duplicator = duplicator;
|
context->duplicator = duplicator;
|
||||||
@@ -461,6 +489,56 @@ void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
|
|||||||
context_update_for_graph_index(context, graph_index);
|
context_update_for_graph_index(context, graph_index);
|
||||||
|
|
||||||
export_graph_[graph_index].insert(context);
|
export_graph_[graph_index].insert(context);
|
||||||
|
|
||||||
|
// ExportGraph::key_type graph_index;
|
||||||
|
// bool animation_check_include_parent = true;
|
||||||
|
|
||||||
|
// HierarchyContext *context = new HierarchyContext();
|
||||||
|
// context->object = dupli_object->ob;
|
||||||
|
// context->duplicator = duplicator;
|
||||||
|
// context->persistent_id = PersistentID(dupli_object);
|
||||||
|
// context->weak_export = false;
|
||||||
|
// context->export_path = "";
|
||||||
|
// context->original_export_path = "";
|
||||||
|
// context->export_path = "";
|
||||||
|
// context->animation_check_include_parent = false;
|
||||||
|
|
||||||
|
///* If the dupli-object's parent is also instanced by this object, use that as the
|
||||||
|
// * export parent. Otherwise use the dupli-parent as export parent. */
|
||||||
|
// Object *parent = dupli_object->ob->parent;
|
||||||
|
// if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) {
|
||||||
|
// // The parent object is part of the duplicated collection.
|
||||||
|
// context->export_parent = parent;
|
||||||
|
// graph_index = std::make_pair(parent, duplicator);
|
||||||
|
// // This bool used to be false by default
|
||||||
|
// // This was stopping a certain combination of drivers
|
||||||
|
// // and rigging to not properly export.
|
||||||
|
// // For now, we have switched to only setting to false here
|
||||||
|
// animation_check_include_parent = false;
|
||||||
|
//}
|
||||||
|
// else {
|
||||||
|
// /* The parent object is NOT part of the duplicated collection. This means that the world
|
||||||
|
// * transform of this dupli-object can be influenced by objects that are not part of its
|
||||||
|
// * export graph. */
|
||||||
|
// context->export_parent = duplicator;
|
||||||
|
// graph_index = std::make_pair(duplicator, nullptr);
|
||||||
|
//}
|
||||||
|
|
||||||
|
// context->animation_check_include_parent = animation_check_include_parent;
|
||||||
|
//
|
||||||
|
// copy_m4_m4(context->matrix_world, dupli_object->mat);
|
||||||
|
|
||||||
|
///* Construct export name for the dupli-instance. */
|
||||||
|
// std::stringstream export_name_stream;
|
||||||
|
// export_name_stream << get_object_name(context->object) << "-"
|
||||||
|
// << context->persistent_id.as_object_name_suffix();
|
||||||
|
// context->export_name = make_valid_name(export_name_stream.str());
|
||||||
|
|
||||||
|
// ExportGraph::key_type graph_index = determine_graph_index_dupli(
|
||||||
|
// context, dupli_object, dupli_parent_finder);
|
||||||
|
// context_update_for_graph_index(context, graph_index);
|
||||||
|
|
||||||
|
// export_graph_[graph_index].insert(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator::
|
AbstractHierarchyIterator::ExportGraph::key_type AbstractHierarchyIterator::
|
||||||
@@ -535,6 +613,7 @@ void AbstractHierarchyIterator::determine_duplication_references(
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context->mark_as_instance_of(it->second);
|
context->mark_as_instance_of(it->second);
|
||||||
|
prototypes_.insert(source_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context->object->data) {
|
if (context->object->data) {
|
||||||
@@ -591,13 +670,15 @@ void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_cont
|
|||||||
transform_writer->write(*context);
|
transform_writer->write(*context);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!context->weak_export) {
|
if (!context->weak_export && include_data_writers(context)) {
|
||||||
make_writers_particle_systems(context);
|
make_writers_particle_systems(context);
|
||||||
make_writer_object_data(context);
|
make_writer_object_data(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Recurse into this object's children. */
|
if (include_child_writers(context)) {
|
||||||
make_writers(context);
|
/* Recurse into this object's children. */
|
||||||
|
make_writers(context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something.
|
/* TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something.
|
||||||
@@ -627,6 +708,7 @@ void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *
|
|||||||
data_context.original_export_path = duplisource_export_path_[object_data];
|
data_context.original_export_path = duplisource_export_path_[object_data];
|
||||||
|
|
||||||
/* If the object is marked as an instance, so should the object data. */
|
/* If the object is marked as an instance, so should the object data. */
|
||||||
|
/* TODO(makowalski): this fails when testing with collection instances. */
|
||||||
BLI_assert(data_context.is_instance());
|
BLI_assert(data_context.is_instance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,6 +16,9 @@ add_definitions(-DPXR_STATIC)
|
|||||||
# USD headers use deprecated TBB headers, silence warning.
|
# USD headers use deprecated TBB headers, silence warning.
|
||||||
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
|
add_definitions(-DTBB_SUPPRESS_DEPRECATED_MESSAGES=1)
|
||||||
|
|
||||||
|
# Python is always required
|
||||||
|
add_definitions(-DWITH_PYTHON)
|
||||||
|
|
||||||
# Check if USD has the imaging headers available, if they are
|
# Check if USD has the imaging headers available, if they are
|
||||||
# add a USD_HAS_IMAGING define so code can dynamically detect this.
|
# add a USD_HAS_IMAGING define so code can dynamically detect this.
|
||||||
# Cleanup of this variable is done at the end of the file since
|
# Cleanup of this variable is done at the end of the file since
|
||||||
@@ -45,15 +48,18 @@ set(INC
|
|||||||
../../editors/include
|
../../editors/include
|
||||||
../../makesdna
|
../../makesdna
|
||||||
../../makesrna
|
../../makesrna
|
||||||
|
../../python
|
||||||
../../windowmanager
|
../../windowmanager
|
||||||
../../../../intern/guardedalloc
|
../../../../intern/guardedalloc
|
||||||
../../../../intern/utfconv
|
../../../../intern/utfconv
|
||||||
|
${CMAKE_BINARY_DIR}/source/blender/makesrna/intern
|
||||||
)
|
)
|
||||||
|
|
||||||
set(INC_SYS
|
set(INC_SYS
|
||||||
${USD_INCLUDE_DIRS}
|
${USD_INCLUDE_DIRS}
|
||||||
${BOOST_INCLUDE_DIR}
|
${BOOST_INCLUDE_DIR}
|
||||||
${TBB_INCLUDE_DIR}
|
${TBB_INCLUDE_DIR}
|
||||||
|
${PYTHON_INCLUDE_DIRS}
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SRC
|
set(SRC
|
||||||
@@ -61,18 +67,12 @@ set(SRC
|
|||||||
intern/usd_capi_import.cc
|
intern/usd_capi_import.cc
|
||||||
intern/usd_common.cc
|
intern/usd_common.cc
|
||||||
intern/usd_hierarchy_iterator.cc
|
intern/usd_hierarchy_iterator.cc
|
||||||
intern/usd_writer_abstract.cc
|
intern/usd_light_convert.cc
|
||||||
intern/usd_writer_camera.cc
|
|
||||||
intern/usd_writer_hair.cc
|
|
||||||
intern/usd_writer_light.cc
|
|
||||||
intern/usd_writer_material.cc
|
|
||||||
intern/usd_writer_mesh.cc
|
|
||||||
intern/usd_writer_metaball.cc
|
|
||||||
intern/usd_writer_transform.cc
|
|
||||||
|
|
||||||
intern/usd_reader_camera.cc
|
intern/usd_reader_camera.cc
|
||||||
intern/usd_reader_curve.cc
|
intern/usd_reader_curve.cc
|
||||||
intern/usd_reader_geom.cc
|
intern/usd_reader_geom.cc
|
||||||
|
intern/usd_reader_instance.cc
|
||||||
intern/usd_reader_light.cc
|
intern/usd_reader_light.cc
|
||||||
intern/usd_reader_material.cc
|
intern/usd_reader_material.cc
|
||||||
intern/usd_reader_mesh.cc
|
intern/usd_reader_mesh.cc
|
||||||
@@ -82,31 +82,57 @@ set(SRC
|
|||||||
intern/usd_reader_volume.cc
|
intern/usd_reader_volume.cc
|
||||||
intern/usd_reader_xform.cc
|
intern/usd_reader_xform.cc
|
||||||
|
|
||||||
|
intern/usd_umm.cc
|
||||||
|
|
||||||
|
intern/usd_writer_abstract.cc
|
||||||
|
intern/usd_writer_armature.cc
|
||||||
|
intern/usd_writer_camera.cc
|
||||||
|
intern/usd_writer_curve.cc
|
||||||
|
intern/usd_writer_hair.cc
|
||||||
|
intern/usd_writer_light.cc
|
||||||
|
intern/usd_writer_material.cc
|
||||||
|
intern/usd_writer_mesh.cc
|
||||||
|
intern/usd_writer_metaball.cc
|
||||||
|
intern/usd_writer_particle.cc
|
||||||
|
intern/usd_writer_skel_root.cc
|
||||||
|
intern/usd_writer_skinned_mesh.cc
|
||||||
|
intern/usd_writer_transform.cc
|
||||||
|
|
||||||
usd.h
|
usd.h
|
||||||
|
|
||||||
intern/usd_common.h
|
intern/usd_common.h
|
||||||
intern/usd_exporter_context.h
|
intern/usd_exporter_context.h
|
||||||
intern/usd_hierarchy_iterator.h
|
intern/usd_hierarchy_iterator.h
|
||||||
intern/usd_writer_abstract.h
|
intern/usd_light_convert.h
|
||||||
intern/usd_writer_camera.h
|
|
||||||
intern/usd_writer_hair.h
|
|
||||||
intern/usd_writer_light.h
|
|
||||||
intern/usd_writer_material.h
|
|
||||||
intern/usd_writer_mesh.h
|
|
||||||
intern/usd_writer_metaball.h
|
|
||||||
intern/usd_writer_transform.h
|
|
||||||
|
|
||||||
intern/usd_reader_camera.h
|
intern/usd_reader_camera.h
|
||||||
intern/usd_reader_curve.h
|
intern/usd_reader_curve.h
|
||||||
intern/usd_reader_geom.h
|
intern/usd_reader_geom.h
|
||||||
|
intern/usd_reader_instance.h
|
||||||
intern/usd_reader_light.h
|
intern/usd_reader_light.h
|
||||||
intern/usd_reader_material.h
|
intern/usd_reader_material.h
|
||||||
intern/usd_reader_mesh.h
|
intern/usd_reader_mesh.h
|
||||||
intern/usd_reader_nurbs.h
|
intern/usd_reader_nurbs.h
|
||||||
intern/usd_reader_prim.h
|
intern/usd_reader_prim.h
|
||||||
intern/usd_reader_stage.h
|
intern/usd_reader_stage.h
|
||||||
intern/usd_reader_volume.h
|
intern/usd_reader_volume.h
|
||||||
intern/usd_reader_xform.h
|
intern/usd_reader_xform.h
|
||||||
|
|
||||||
|
intern/usd_umm.h
|
||||||
|
|
||||||
|
intern/usd_writer_abstract.h
|
||||||
|
intern/usd_writer_armature.h
|
||||||
|
intern/usd_writer_camera.h
|
||||||
|
intern/usd_writer_curve.h
|
||||||
|
intern/usd_writer_hair.h
|
||||||
|
intern/usd_writer_light.h
|
||||||
|
intern/usd_writer_material.h
|
||||||
|
intern/usd_writer_mesh.h
|
||||||
|
intern/usd_writer_metaball.h
|
||||||
|
intern/usd_writer_particle.h
|
||||||
|
intern/usd_writer_skel_root.h
|
||||||
|
intern/usd_writer_skinned_mesh.h
|
||||||
|
intern/usd_writer_transform.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(LIB
|
set(LIB
|
||||||
@@ -117,6 +143,8 @@ set(LIB
|
|||||||
|
|
||||||
list(APPEND LIB
|
list(APPEND LIB
|
||||||
${BOOST_LIBRARIES}
|
${BOOST_LIBRARIES}
|
||||||
|
${PYTHON_LINKFLAGS}
|
||||||
|
${PYTHON_LIBRARIES}
|
||||||
)
|
)
|
||||||
|
|
||||||
list(APPEND LIB
|
list(APPEND LIB
|
||||||
|
@@ -4,11 +4,18 @@
|
|||||||
#include "usd.h"
|
#include "usd.h"
|
||||||
#include "usd_common.h"
|
#include "usd_common.h"
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
#include "usd_light_convert.h"
|
||||||
|
#include "usd_umm.h"
|
||||||
|
#include "usd_writer_material.h"
|
||||||
|
#include "usd_writer_skel_root.h"
|
||||||
|
|
||||||
#include <pxr/base/plug/registry.h>
|
#include <pxr/base/plug/registry.h>
|
||||||
#include <pxr/pxr.h>
|
#include <pxr/pxr.h>
|
||||||
#include <pxr/usd/usd/stage.h>
|
#include <pxr/usd/usd/stage.h>
|
||||||
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
|
#include <pxr/usd/usdGeom/scope.h>
|
||||||
#include <pxr/usd/usdGeom/tokens.h>
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
|
|
||||||
#include "MEM_guardedalloc.h"
|
#include "MEM_guardedalloc.h"
|
||||||
|
|
||||||
@@ -22,9 +29,13 @@
|
|||||||
#include "BKE_blender_version.h"
|
#include "BKE_blender_version.h"
|
||||||
#include "BKE_context.h"
|
#include "BKE_context.h"
|
||||||
#include "BKE_global.h"
|
#include "BKE_global.h"
|
||||||
|
#include "BKE_main.h"
|
||||||
#include "BKE_scene.h"
|
#include "BKE_scene.h"
|
||||||
|
|
||||||
#include "BLI_fileops.h"
|
#include "BLI_fileops.h"
|
||||||
|
#include "BLI_math_matrix.h"
|
||||||
|
#include "BLI_math_rotation.h"
|
||||||
|
#include "BLI_math_vector.h"
|
||||||
#include "BLI_path_util.h"
|
#include "BLI_path_util.h"
|
||||||
#include "BLI_string.h"
|
#include "BLI_string.h"
|
||||||
|
|
||||||
@@ -34,6 +45,7 @@
|
|||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
struct ExportJobData {
|
struct ExportJobData {
|
||||||
|
ViewLayer *view_layer;
|
||||||
Main *bmain;
|
Main *bmain;
|
||||||
Depsgraph *depsgraph;
|
Depsgraph *depsgraph;
|
||||||
wmWindowManager *wm;
|
wmWindowManager *wm;
|
||||||
@@ -41,9 +53,141 @@ struct ExportJobData {
|
|||||||
char filename[FILE_MAX];
|
char filename[FILE_MAX];
|
||||||
USDExportParams params;
|
USDExportParams params;
|
||||||
|
|
||||||
|
short *stop;
|
||||||
|
short *do_update;
|
||||||
|
float *progress;
|
||||||
|
|
||||||
|
bool was_canceled;
|
||||||
bool export_ok;
|
bool export_ok;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Perform validation of export parameter settings. Returns
|
||||||
|
* true if the paramters are valid. Returns false otherwise. */
|
||||||
|
static bool validate_params(const USDExportParams ¶ms)
|
||||||
|
{
|
||||||
|
bool valid = true;
|
||||||
|
|
||||||
|
if (params.export_materials && !pxr::SdfPath::IsValidPathString(params.material_prim_path)) {
|
||||||
|
WM_reportf(RPT_ERROR,
|
||||||
|
"USD Export: invalid material prim path parameter '%s'",
|
||||||
|
params.material_prim_path);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(params.root_prim_path) != 0 &&
|
||||||
|
!pxr::SdfPath::IsValidPathString(params.root_prim_path)) {
|
||||||
|
WM_reportf(
|
||||||
|
RPT_ERROR, "USD Export: invalid root prim path parameter '%s'", params.root_prim_path);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(params.default_prim_path) != 0 &&
|
||||||
|
!pxr::SdfPath::IsValidPathString(params.default_prim_path)) {
|
||||||
|
WM_reportf(RPT_ERROR,
|
||||||
|
"USD Export: invalid default prim path parameter '%s'",
|
||||||
|
params.default_prim_path);
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If a root prim path is set in the params, check if a
|
||||||
|
* root object matching the root path name already exists.
|
||||||
|
* If it does, clear the root prim path in the params.
|
||||||
|
* This is to avoid prepending the root prim path
|
||||||
|
* redundantly.
|
||||||
|
* TODO(makowalski): ideally, this functionality belongs
|
||||||
|
* in the USD hierarchy iterator, so that we don't iterate
|
||||||
|
* over the scene graph separately here. */
|
||||||
|
static void validate_unique_root_prim_path(USDExportParams ¶ms, Depsgraph *depsgraph)
|
||||||
|
{
|
||||||
|
if (!depsgraph || strlen(params.root_prim_path) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfPath path(params.root_prim_path);
|
||||||
|
|
||||||
|
if (path.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfPath parent = path.GetParentPath();
|
||||||
|
|
||||||
|
while (!parent.IsEmpty() && !parent.IsAbsoluteRootPath()) {
|
||||||
|
path = parent;
|
||||||
|
parent = path.GetParentPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object *match = nullptr;
|
||||||
|
std::string root_name = path.GetName();
|
||||||
|
|
||||||
|
DEG_OBJECT_ITER_BEGIN(depsgraph,
|
||||||
|
object,
|
||||||
|
DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
|
||||||
|
DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) {
|
||||||
|
|
||||||
|
if (!match && !object->parent) {
|
||||||
|
/* We only care about root objects. */
|
||||||
|
|
||||||
|
if (pxr::TfMakeValidIdentifier(object->id.name + 2) == root_name) {
|
||||||
|
match = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEG_OBJECT_ITER_END;
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
WM_reportf(
|
||||||
|
RPT_WARNING, "USD Export: the root prim will not be added because a root object named '%s' already exists", root_name.c_str());
|
||||||
|
params.root_prim_path[0] = '\0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create root prim if defined. */
|
||||||
|
static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams ¶ms)
|
||||||
|
{
|
||||||
|
if (strlen(params.root_prim_path) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdPrim root_prim = stage->DefinePrim(pxr::SdfPath(params.root_prim_path),
|
||||||
|
pxr::TfToken("Xform"));
|
||||||
|
|
||||||
|
if (!(params.convert_orientation || params.convert_to_cm)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!root_prim) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdGeomXformCommonAPI xf_api(root_prim);
|
||||||
|
|
||||||
|
if (!xf_api) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.convert_to_cm) {
|
||||||
|
xf_api.SetScale(pxr::GfVec3f(100.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.convert_orientation) {
|
||||||
|
float mrot[3][3];
|
||||||
|
mat3_from_axis_conversion(
|
||||||
|
USD_GLOBAL_FORWARD_Y, USD_GLOBAL_UP_Z, params.forward_axis, params.up_axis, mrot);
|
||||||
|
transpose_m3(mrot);
|
||||||
|
|
||||||
|
float eul[3];
|
||||||
|
mat3_to_eul(eul, mrot);
|
||||||
|
|
||||||
|
/* Convert radians to degrees. */
|
||||||
|
mul_v3_fl(eul, 180.0f / M_PI);
|
||||||
|
|
||||||
|
xf_api.SetRotate(pxr::GfVec3f(eul[0], eul[1], eul[2]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void export_startjob(void *customdata,
|
static void export_startjob(void *customdata,
|
||||||
/* Cannot be const, this function implements wm_jobs_start_callback.
|
/* Cannot be const, this function implements wm_jobs_start_callback.
|
||||||
* NOLINTNEXTLINE: readability-non-const-parameter. */
|
* NOLINTNEXTLINE: readability-non-const-parameter. */
|
||||||
@@ -52,12 +196,21 @@ static void export_startjob(void *customdata,
|
|||||||
float *progress)
|
float *progress)
|
||||||
{
|
{
|
||||||
ExportJobData *data = static_cast<ExportJobData *>(customdata);
|
ExportJobData *data = static_cast<ExportJobData *>(customdata);
|
||||||
data->export_ok = false;
|
|
||||||
|
data->stop = stop;
|
||||||
|
data->do_update = do_update;
|
||||||
|
data->progress = progress;
|
||||||
|
data->was_canceled = false;
|
||||||
|
|
||||||
G.is_rendering = true;
|
G.is_rendering = true;
|
||||||
WM_set_locked_interface(data->wm, true);
|
WM_set_locked_interface(data->wm, true);
|
||||||
G.is_break = false;
|
G.is_break = false;
|
||||||
|
|
||||||
|
if (!validate_params(data->params)) {
|
||||||
|
data->export_ok = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Construct the depsgraph for exporting. */
|
/* Construct the depsgraph for exporting. */
|
||||||
Scene *scene = DEG_get_input_scene(data->depsgraph);
|
Scene *scene = DEG_get_input_scene(data->depsgraph);
|
||||||
if (data->params.visible_objects_only) {
|
if (data->params.visible_objects_only) {
|
||||||
@@ -68,42 +221,83 @@ static void export_startjob(void *customdata,
|
|||||||
}
|
}
|
||||||
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
|
BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
|
||||||
|
|
||||||
|
validate_unique_root_prim_path(data->params, data->depsgraph);
|
||||||
|
|
||||||
*progress = 0.0f;
|
*progress = 0.0f;
|
||||||
*do_update = true;
|
*do_update = true;
|
||||||
|
|
||||||
/* For restoring the current frame after exporting animation is done. */
|
/* For restoring the current frame after exporting animation is done. */
|
||||||
const int orig_frame = CFRA;
|
const int orig_frame = CFRA;
|
||||||
|
|
||||||
|
if (!BLI_path_extension_check_glob(data->filename, "*.usd;*.usda;*.usdc"))
|
||||||
|
BLI_path_extension_ensure(data->filename, FILE_MAX, ".usd");
|
||||||
|
|
||||||
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename);
|
pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename);
|
||||||
if (!usd_stage) {
|
if (!usd_stage) {
|
||||||
/* This happens when the USD JSON files cannot be found. When that happens,
|
/* This may happen when the USD JSON files cannot be found. When that happens,
|
||||||
* the USD library doesn't know it has the functionality to write USDA and
|
* the USD library doesn't know it has the functionality to write USDA and
|
||||||
* USDC files, and creating a new UsdStage fails. */
|
* USDC files, and creating a new UsdStage fails. */
|
||||||
WM_reportf(
|
WM_reportf(RPT_ERROR, "USD Export: unable to create a stage for writing %s", data->filename);
|
||||||
RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename);
|
|
||||||
|
pxr::SdfLayerRefPtr existing_layer = pxr::SdfLayer::FindOrOpen(data->filename);
|
||||||
|
if (existing_layer) {
|
||||||
|
WM_reportf(RPT_ERROR,
|
||||||
|
"USD Export: layer %s is currently open in the scene, "
|
||||||
|
"possibly because it's referenced by modifiers, "
|
||||||
|
"and can't be overwritten",
|
||||||
|
data->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
data->export_ok = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
|
if (data->params.export_lights && !data->params.selected_objects_only &&
|
||||||
usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit,
|
data->params.convert_world_material) {
|
||||||
static_cast<double>(scene->unit.scale_length));
|
world_material_to_dome_light(data->params, scene, usd_stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Define the material prim path as a scope. */
|
||||||
|
if (data->params.export_materials) {
|
||||||
|
pxr::SdfPath mtl_prim_path(data->params.material_prim_path);
|
||||||
|
|
||||||
|
blender::io::usd::usd_define_or_over<pxr::UsdGeomScope>(
|
||||||
|
usd_stage, mtl_prim_path, data->params.export_as_overs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtValue upAxis = pxr::VtValue(pxr::UsdGeomTokens->z);
|
||||||
|
if (data->params.convert_orientation) {
|
||||||
|
if (data->params.up_axis == USD_GLOBAL_UP_X)
|
||||||
|
upAxis = pxr::VtValue(pxr::UsdGeomTokens->x);
|
||||||
|
else if (data->params.up_axis == USD_GLOBAL_UP_Y)
|
||||||
|
upAxis = pxr::VtValue(pxr::UsdGeomTokens->y);
|
||||||
|
}
|
||||||
|
|
||||||
|
usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, upAxis);
|
||||||
usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
|
usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender v") +
|
||||||
BKE_blender_version_string());
|
BKE_blender_version_string());
|
||||||
|
|
||||||
/* Set up the stage for animated data. */
|
/* Set up the stage for animated data. */
|
||||||
if (data->params.export_animation) {
|
if (data->params.export_animation) {
|
||||||
usd_stage->SetTimeCodesPerSecond(FPS);
|
usd_stage->SetTimeCodesPerSecond(FPS);
|
||||||
usd_stage->SetStartTimeCode(scene->r.sfra);
|
usd_stage->SetStartTimeCode(data->params.frame_start);
|
||||||
usd_stage->SetEndTimeCode(scene->r.efra);
|
usd_stage->SetEndTimeCode(data->params.frame_end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensure_root_prim(usd_stage, data->params);
|
||||||
|
|
||||||
USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params);
|
USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params);
|
||||||
|
|
||||||
if (data->params.export_animation) {
|
if (data->params.export_animation) {
|
||||||
/* Writing the animated frames is not 100% of the work, but it's our best guess. */
|
|
||||||
float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
|
|
||||||
|
|
||||||
for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
|
// Writing the animated frames is not 100% of the work, but it's our best guess.
|
||||||
|
float progress_per_frame = 1.0f / std::max(1.0f,
|
||||||
|
(float)(data->params.frame_end -
|
||||||
|
data->params.frame_start + 1.0) /
|
||||||
|
data->params.frame_step);
|
||||||
|
|
||||||
|
for (float frame = data->params.frame_start; frame <= data->params.frame_end;
|
||||||
|
frame += data->params.frame_step) {
|
||||||
if (G.is_break || (stop != nullptr && *stop)) {
|
if (G.is_break || (stop != nullptr && *stop)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -126,6 +320,36 @@ static void export_startjob(void *customdata,
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter.release_writers();
|
iter.release_writers();
|
||||||
|
|
||||||
|
if (data->params.export_armatures) {
|
||||||
|
validate_skel_roots(usd_stage, data->params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Stage Default Prim Path
|
||||||
|
if (strlen(data->params.default_prim_path) > 0) {
|
||||||
|
std::string valid_default_prim_path = pxr::TfMakeValidIdentifier(
|
||||||
|
data->params.default_prim_path);
|
||||||
|
|
||||||
|
if (valid_default_prim_path[0] == '_') {
|
||||||
|
valid_default_prim_path[0] = '/';
|
||||||
|
}
|
||||||
|
if (valid_default_prim_path[0] != '/') {
|
||||||
|
valid_default_prim_path = "/" + valid_default_prim_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdPrim defaultPrim = usd_stage->GetPrimAtPath(pxr::SdfPath(valid_default_prim_path));
|
||||||
|
|
||||||
|
if (defaultPrim.IsValid()) {
|
||||||
|
usd_stage->SetDefaultPrim(defaultPrim);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set unit scale.
|
||||||
|
* TODO(makowalsk): Add an option to use scene->unit.scale_length as well? */
|
||||||
|
double meters_per_unit = data->params.convert_to_cm ? pxr::UsdGeomLinearUnits::centimeters :
|
||||||
|
pxr::UsdGeomLinearUnits::meters;
|
||||||
|
pxr::UsdGeomSetStageMetersPerUnit(usd_stage, meters_per_unit);
|
||||||
|
|
||||||
usd_stage->GetRootLayer()->Save();
|
usd_stage->GetRootLayer()->Save();
|
||||||
|
|
||||||
/* Finish up by going back to the keyframe that was current before we started. */
|
/* Finish up by going back to the keyframe that was current before we started. */
|
||||||
@@ -134,7 +358,8 @@ static void export_startjob(void *customdata,
|
|||||||
BKE_scene_graph_update_for_newframe(data->depsgraph);
|
BKE_scene_graph_update_for_newframe(data->depsgraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
data->export_ok = true;
|
data->export_ok = !data->was_canceled;
|
||||||
|
|
||||||
*progress = 1.0f;
|
*progress = 1.0f;
|
||||||
*do_update = true;
|
*do_update = true;
|
||||||
}
|
}
|
||||||
@@ -145,7 +370,11 @@ static void export_endjob(void *customdata)
|
|||||||
|
|
||||||
DEG_graph_free(data->depsgraph);
|
DEG_graph_free(data->depsgraph);
|
||||||
|
|
||||||
if (!data->export_ok && BLI_exists(data->filename)) {
|
MEM_freeN(data->params.default_prim_path);
|
||||||
|
MEM_freeN(data->params.root_prim_path);
|
||||||
|
MEM_freeN(data->params.material_prim_path);
|
||||||
|
|
||||||
|
if (data->was_canceled && BLI_exists(data->filename)) {
|
||||||
BLI_delete(data->filename, false, false);
|
BLI_delete(data->filename, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,3 +449,12 @@ int USD_get_version()
|
|||||||
*/
|
*/
|
||||||
return PXR_VERSION;
|
return PXR_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool USD_umm_module_loaded(void)
|
||||||
|
{
|
||||||
|
#ifdef WITH_PYTHON
|
||||||
|
return blender::io::usd::umm_module_loaded();
|
||||||
|
#else
|
||||||
|
return fasle;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
@@ -5,10 +5,40 @@
|
|||||||
#include "usd.h"
|
#include "usd.h"
|
||||||
#include "usd_common.h"
|
#include "usd_common.h"
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
|
#include "usd_light_convert.h"
|
||||||
#include "usd_reader_geom.h"
|
#include "usd_reader_geom.h"
|
||||||
|
#include "usd_reader_instance.h"
|
||||||
#include "usd_reader_prim.h"
|
#include "usd_reader_prim.h"
|
||||||
#include "usd_reader_stage.h"
|
#include "usd_reader_stage.h"
|
||||||
|
|
||||||
|
#include <pxr/base/plug/registry.h>
|
||||||
|
|
||||||
|
#include "usd_writer_material.h"
|
||||||
|
|
||||||
|
#include <pxr/pxr.h>
|
||||||
|
#include <pxr/usd/usd/stage.h>
|
||||||
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
|
#include <pxr/usd/usdGeom/scope.h>
|
||||||
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
|
#include <pxr/usd/usdLux/domeLight.h>
|
||||||
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
|
|
||||||
|
#include "MEM_guardedalloc.h"
|
||||||
|
|
||||||
|
#include "DEG_depsgraph.h"
|
||||||
|
#include "DEG_depsgraph_build.h"
|
||||||
|
#include "DEG_depsgraph_query.h"
|
||||||
|
|
||||||
|
#include "DNA_cachefile_types.h"
|
||||||
|
|
||||||
|
#include "DNA_collection_types.h"
|
||||||
|
|
||||||
|
#include "DNA_node_types.h"
|
||||||
|
#include "DNA_scene_types.h"
|
||||||
|
#include "DNA_world_types.h"
|
||||||
|
|
||||||
#include "BKE_appdir.h"
|
#include "BKE_appdir.h"
|
||||||
#include "BKE_blender_version.h"
|
#include "BKE_blender_version.h"
|
||||||
#include "BKE_cachefile.h"
|
#include "BKE_cachefile.h"
|
||||||
@@ -85,6 +115,127 @@ static bool gather_objects_paths(const pxr::UsdPrim &object, ListBase *object_pa
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Create a collection with the given parent and name. */
|
||||||
|
static Collection *create_collection(Main *bmain, Collection *parent, const char *name)
|
||||||
|
{
|
||||||
|
if (!bmain) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection *coll = BKE_collection_add(bmain, parent, name);
|
||||||
|
|
||||||
|
if (coll) {
|
||||||
|
id_fake_user_set(&coll->id);
|
||||||
|
DEG_id_tag_update(&coll->id, ID_RECALC_COPY_ON_WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set the instance collection on the given instance reader.
|
||||||
|
* The collection is assigned from the given map based on
|
||||||
|
* the prototype (maser) prim path. */
|
||||||
|
static void set_instance_collection(
|
||||||
|
USDInstanceReader *instance_reader,
|
||||||
|
const std::map<pxr::SdfPath, Collection *> &proto_collection_map)
|
||||||
|
{
|
||||||
|
if (!instance_reader) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfPath proto_path = instance_reader->proto_path();
|
||||||
|
|
||||||
|
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(proto_path);
|
||||||
|
|
||||||
|
if (it != proto_collection_map.end()) {
|
||||||
|
instance_reader->set_instance_collection(it->second);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "WARNING: Couldn't find prototype collection for " << instance_reader->prim_path()
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create instance collections for the USD instance readers. */
|
||||||
|
static void create_proto_collections(Main *bmain,
|
||||||
|
ViewLayer *view_layer,
|
||||||
|
Collection *parent_collection,
|
||||||
|
const ProtoReaderMap &proto_readers,
|
||||||
|
const std::vector<USDPrimReader *> &readers)
|
||||||
|
{
|
||||||
|
Collection *all_protos_collection = create_collection(bmain, parent_collection, "prototypes");
|
||||||
|
|
||||||
|
std::map<pxr::SdfPath, Collection *> proto_collection_map;
|
||||||
|
|
||||||
|
for (const auto &pair : proto_readers) {
|
||||||
|
|
||||||
|
std::string proto_collection_name = pair.first.GetString();
|
||||||
|
|
||||||
|
// TODO(makowalski): Is it acceptable to have slashes in the collection names? Or should we
|
||||||
|
// replace them with another character, like an underscore, as in the following?
|
||||||
|
// std::replace(proto_collection_name.begin(), proto_collection_name.end(), '/', '_');
|
||||||
|
|
||||||
|
Collection *proto_collection = create_collection(
|
||||||
|
bmain, all_protos_collection, proto_collection_name.c_str());
|
||||||
|
|
||||||
|
LayerCollection *proto_lc = BKE_layer_collection_first_from_scene_collection(view_layer,
|
||||||
|
proto_collection);
|
||||||
|
if (proto_lc) {
|
||||||
|
proto_lc->flag |= LAYER_COLLECTION_HIDE;
|
||||||
|
}
|
||||||
|
|
||||||
|
proto_collection_map.insert(std::make_pair(pair.first, proto_collection));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the instance collections on the readers, including the prototype
|
||||||
|
// readers, as instancing may be recursive.
|
||||||
|
|
||||||
|
for (const auto &pair : proto_readers) {
|
||||||
|
for (USDPrimReader *reader : pair.second) {
|
||||||
|
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
|
||||||
|
set_instance_collection(instance_reader, proto_collection_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (USDPrimReader *reader : readers) {
|
||||||
|
if (USDInstanceReader *instance_reader = dynamic_cast<USDInstanceReader *>(reader)) {
|
||||||
|
set_instance_collection(instance_reader, proto_collection_map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the prototype objects to the collections.
|
||||||
|
for (const auto &pair : proto_readers) {
|
||||||
|
|
||||||
|
std::map<pxr::SdfPath, Collection *>::const_iterator it = proto_collection_map.find(
|
||||||
|
pair.first);
|
||||||
|
|
||||||
|
if (it == proto_collection_map.end()) {
|
||||||
|
std::cerr << "WARNING: Couldn't find collection when adding objects for prototype "
|
||||||
|
<< pair.first << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (USDPrimReader *reader : pair.second) {
|
||||||
|
Object *ob = reader->object();
|
||||||
|
|
||||||
|
if (!ob) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection *coll = it->second;
|
||||||
|
|
||||||
|
BKE_collection_object_add(bmain, coll, ob);
|
||||||
|
|
||||||
|
DEG_id_tag_update(&coll->id, ID_RECALC_COPY_ON_WRITE);
|
||||||
|
DEG_id_tag_update_ex(bmain,
|
||||||
|
&ob->id,
|
||||||
|
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
|
||||||
|
ID_RECALC_BASE_FLAGS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Update the given import settings with the global rotation matrix to orient
|
/* Update the given import settings with the global rotation matrix to orient
|
||||||
* imported objects with Z-up, if necessary */
|
* imported objects with Z-up, if necessary */
|
||||||
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings)
|
static void convert_to_z_up(pxr::UsdStageRefPtr stage, ImportSettings *r_settings)
|
||||||
@@ -134,6 +285,50 @@ struct ImportJobData {
|
|||||||
bool import_ok;
|
bool import_ok;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static CacheFile *create_cache_file(const ImportJobData *data)
|
||||||
|
{
|
||||||
|
if (!data) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheFile *cache_file = static_cast<CacheFile *>(
|
||||||
|
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename)));
|
||||||
|
|
||||||
|
/* Decrement the ID ref-count because it is going to be incremented for each
|
||||||
|
* modifier and constraint that it will be attached to, so since currently
|
||||||
|
* it is not used by anyone, its use count will off by one. */
|
||||||
|
id_us_min(&cache_file->id);
|
||||||
|
|
||||||
|
cache_file->is_sequence = data->params.is_sequence;
|
||||||
|
cache_file->scale = data->params.scale;
|
||||||
|
STRNCPY(cache_file->filepath, data->filename);
|
||||||
|
|
||||||
|
cache_file->scale = data->settings.scale;
|
||||||
|
|
||||||
|
return cache_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Apply the given cache file to the given reader, if needed. Will create a cache file
|
||||||
|
* and return it in the r_cache_file out prameter, if needed. */
|
||||||
|
static void apply_cache_file(USDPrimReader *reader,
|
||||||
|
const ImportJobData *data,
|
||||||
|
CacheFile **r_cache_file)
|
||||||
|
{
|
||||||
|
if (!(reader && reader->needs_cachefile())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(data && r_cache_file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*r_cache_file == nullptr) {
|
||||||
|
*r_cache_file = create_cache_file(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
reader->apply_cache_file(*r_cache_file);
|
||||||
|
}
|
||||||
|
|
||||||
static void import_startjob(void *customdata, short *stop, short *do_update, float *progress)
|
static void import_startjob(void *customdata, short *stop, short *do_update, float *progress)
|
||||||
{
|
{
|
||||||
ImportJobData *data = static_cast<ImportJobData *>(customdata);
|
ImportJobData *data = static_cast<ImportJobData *>(customdata);
|
||||||
@@ -166,20 +361,6 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
|
|||||||
|
|
||||||
BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global());
|
BLI_path_abs(data->filename, BKE_main_blendfile_path_from_global());
|
||||||
|
|
||||||
CacheFile *cache_file = static_cast<CacheFile *>(
|
|
||||||
BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename)));
|
|
||||||
|
|
||||||
/* Decrement the ID ref-count because it is going to be incremented for each
|
|
||||||
* modifier and constraint that it will be attached to, so since currently
|
|
||||||
* it is not used by anyone, its use count will off by one. */
|
|
||||||
id_us_min(&cache_file->id);
|
|
||||||
|
|
||||||
cache_file->is_sequence = data->params.is_sequence;
|
|
||||||
cache_file->scale = data->params.scale;
|
|
||||||
STRNCPY(cache_file->filepath, data->filename);
|
|
||||||
|
|
||||||
data->settings.cache_file = cache_file;
|
|
||||||
|
|
||||||
*data->do_update = true;
|
*data->do_update = true;
|
||||||
*data->progress = 0.05f;
|
*data->progress = 0.05f;
|
||||||
|
|
||||||
@@ -201,6 +382,11 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
|
|||||||
|
|
||||||
convert_to_z_up(stage, &data->settings);
|
convert_to_z_up(stage, &data->settings);
|
||||||
|
|
||||||
|
if (data->params.apply_unit_conversion_scale) {
|
||||||
|
const double meters_per_unit = pxr::UsdGeomGetStageMetersPerUnit(stage);
|
||||||
|
data->settings.scale *= meters_per_unit;
|
||||||
|
}
|
||||||
|
|
||||||
/* Set up the stage for animated data. */
|
/* Set up the stage for animated data. */
|
||||||
if (data->params.set_frame_range) {
|
if (data->params.set_frame_range) {
|
||||||
data->scene->r.sfra = stage->GetStartTimeCode();
|
data->scene->r.sfra = stage->GetStartTimeCode();
|
||||||
@@ -215,12 +401,55 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
|
|||||||
|
|
||||||
archive->collect_readers(data->bmain);
|
archive->collect_readers(data->bmain);
|
||||||
|
|
||||||
|
if (data->params.import_lights && data->params.create_background_shader &&
|
||||||
|
!archive->dome_lights().empty()) {
|
||||||
|
dome_light_to_world_material(
|
||||||
|
data->params, data->settings, data->scene, data->bmain, archive->dome_lights().front());
|
||||||
|
}
|
||||||
|
|
||||||
*data->progress = 0.2f;
|
*data->progress = 0.2f;
|
||||||
|
|
||||||
const float size = static_cast<float>(archive->readers().size());
|
const float size = static_cast<float>(archive->readers().size());
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
/* Setup parenthood */
|
/* Read data, set prenting and create a cache file, if needed. */
|
||||||
|
|
||||||
|
/* We defer creating a cache file until we know that we need
|
||||||
|
* one. This is not only more efficient, but also avoids
|
||||||
|
* the problem where we can't overwrite the USD the
|
||||||
|
* cachefile is referencing because it has a pointer to the
|
||||||
|
* open stage for the lifetime of the scene. */
|
||||||
|
CacheFile *cache_file = nullptr;
|
||||||
|
|
||||||
|
/* Handle instance prototypes.
|
||||||
|
* TODO(makowalski): Move this logic inside USDReaderStage? */
|
||||||
|
for (const auto &pair : archive->proto_readers()) {
|
||||||
|
|
||||||
|
for (USDPrimReader *reader : pair.second) {
|
||||||
|
|
||||||
|
if (!reader) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(makowalski): Here and below, should we call
|
||||||
|
* read_object_data() with the actual time? */
|
||||||
|
reader->read_object_data(data->bmain, 0.0);
|
||||||
|
|
||||||
|
apply_cache_file(reader, data, &cache_file);
|
||||||
|
|
||||||
|
Object *ob = reader->object();
|
||||||
|
|
||||||
|
if (!ob) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const USDPrimReader *parent_reader = reader->parent();
|
||||||
|
|
||||||
|
ob->parent = parent_reader ? parent_reader->object() : nullptr;
|
||||||
|
|
||||||
|
/* TODO(makowalski): Handle progress update. */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (USDPrimReader *reader : archive->readers()) {
|
for (USDPrimReader *reader : archive->readers()) {
|
||||||
|
|
||||||
@@ -232,6 +461,8 @@ static void import_startjob(void *customdata, short *stop, short *do_update, flo
|
|||||||
|
|
||||||
reader->read_object_data(data->bmain, 0.0);
|
reader->read_object_data(data->bmain, 0.0);
|
||||||
|
|
||||||
|
apply_cache_file(reader, data, &cache_file);
|
||||||
|
|
||||||
USDPrimReader *parent = reader->parent();
|
USDPrimReader *parent = reader->parent();
|
||||||
|
|
||||||
if (parent == nullptr) {
|
if (parent == nullptr) {
|
||||||
@@ -275,6 +506,17 @@ static void import_endjob(void *customdata)
|
|||||||
BKE_id_free_us(data->bmain, ob);
|
BKE_id_free_us(data->bmain, ob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto &pair : data->archive->proto_readers()) {
|
||||||
|
for (USDPrimReader *reader : pair.second) {
|
||||||
|
|
||||||
|
/* It's possible that cancellation occurred between the creation of
|
||||||
|
* the reader and the creation of the Blender object. */
|
||||||
|
if (Object *ob = reader->object()) {
|
||||||
|
BKE_id_free_us(data->bmain, ob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (data->archive) {
|
else if (data->archive) {
|
||||||
/* Add object to scene. */
|
/* Add object to scene. */
|
||||||
@@ -286,6 +528,14 @@ static void import_endjob(void *customdata)
|
|||||||
|
|
||||||
lc = BKE_layer_collection_get_active(view_layer);
|
lc = BKE_layer_collection_get_active(view_layer);
|
||||||
|
|
||||||
|
if (!data->archive->proto_readers().empty()) {
|
||||||
|
create_proto_collections(data->bmain,
|
||||||
|
view_layer,
|
||||||
|
lc->collection,
|
||||||
|
data->archive->proto_readers(),
|
||||||
|
data->archive->readers());
|
||||||
|
}
|
||||||
|
|
||||||
for (USDPrimReader *reader : data->archive->readers()) {
|
for (USDPrimReader *reader : data->archive->readers()) {
|
||||||
|
|
||||||
if (!reader) {
|
if (!reader) {
|
||||||
@@ -312,6 +562,9 @@ static void import_endjob(void *customdata)
|
|||||||
}
|
}
|
||||||
|
|
||||||
DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
|
DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
|
||||||
|
if (!data->archive->dome_lights().empty()) {
|
||||||
|
DEG_id_tag_update(&data->scene->world->id, ID_RECALC_COPY_ON_WRITE);
|
||||||
|
}
|
||||||
DEG_relations_tag_update(data->bmain);
|
DEG_relations_tag_update(data->bmain);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,10 +729,11 @@ CacheReader *CacheReader_open_usd_object(CacheArchiveHandle *handle,
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(makowalski): The handle does not have the proper import params or settings. */
|
/* TODO(makowalski): The handle does not have the proper import params or settings. */
|
||||||
USDPrimReader *usd_reader = archive->create_reader(prim);
|
pxr::UsdGeomXformCache xf_cache;
|
||||||
|
USDPrimReader *usd_reader = archive->create_reader(prim, &xf_cache);
|
||||||
|
|
||||||
if (usd_reader == nullptr) {
|
if (usd_reader == nullptr) {
|
||||||
/* This object is not supported. */
|
/* This object is not supported */
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
usd_reader->object(object);
|
usd_reader->object(object);
|
||||||
|
@@ -4,11 +4,16 @@
|
|||||||
|
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
#include "usd_writer_abstract.h"
|
#include "usd_writer_abstract.h"
|
||||||
|
#include "usd_writer_armature.h"
|
||||||
#include "usd_writer_camera.h"
|
#include "usd_writer_camera.h"
|
||||||
|
#include "usd_writer_curve.h"
|
||||||
#include "usd_writer_hair.h"
|
#include "usd_writer_hair.h"
|
||||||
#include "usd_writer_light.h"
|
#include "usd_writer_light.h"
|
||||||
#include "usd_writer_mesh.h"
|
#include "usd_writer_mesh.h"
|
||||||
#include "usd_writer_metaball.h"
|
#include "usd_writer_metaball.h"
|
||||||
|
#include "usd_writer_particle.h"
|
||||||
|
#include "usd_writer_skel_root.h"
|
||||||
|
#include "usd_writer_skinned_mesh.h"
|
||||||
#include "usd_writer_transform.h"
|
#include "usd_writer_transform.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -64,44 +69,91 @@ const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
|
|||||||
return export_time_;
|
return export_time_;
|
||||||
}
|
}
|
||||||
|
|
||||||
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
|
USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context,
|
||||||
|
bool mergeTransformAndShape)
|
||||||
{
|
{
|
||||||
return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_};
|
pxr::SdfPath prim_path = pxr::SdfPath(std::string(params_.root_prim_path) +
|
||||||
|
context->export_path);
|
||||||
|
// TODO: Somewhat of a workaround. There could be a better way to incoporate this...
|
||||||
|
bool can_merge_with_xform = !(
|
||||||
|
this->params_.export_armatures &&
|
||||||
|
(is_skinned_mesh(context->object) || context->object->type == OB_ARMATURE));
|
||||||
|
if (can_merge_with_xform && mergeTransformAndShape)
|
||||||
|
prim_path = prim_path.GetParentPath();
|
||||||
|
return USDExporterContext{depsgraph_, stage_, prim_path, this, params_};
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
|
AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
|
||||||
const HierarchyContext *context)
|
const HierarchyContext *context)
|
||||||
{
|
{
|
||||||
|
if (this->params_.export_armatures &&
|
||||||
|
(is_skinned_mesh(context->object) || context->object->type == OB_ARMATURE)) {
|
||||||
|
return new USDSkelRootWriter(create_usd_export_context(context));
|
||||||
|
}
|
||||||
|
|
||||||
return new USDTransformWriter(create_usd_export_context(context));
|
return new USDTransformWriter(create_usd_export_context(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
|
AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
|
||||||
{
|
{
|
||||||
USDExporterContext usd_export_context = create_usd_export_context(context);
|
if (context->is_instance() && params_.use_instancing) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
USDExporterContext usd_export_context = create_usd_export_context(
|
||||||
|
context, params_.merge_transform_and_shape);
|
||||||
USDAbstractWriter *data_writer = nullptr;
|
USDAbstractWriter *data_writer = nullptr;
|
||||||
|
|
||||||
switch (context->object->type) {
|
switch (context->object->type) {
|
||||||
case OB_MESH:
|
case OB_MESH:
|
||||||
data_writer = new USDMeshWriter(usd_export_context);
|
if (usd_export_context.export_params.export_meshes) {
|
||||||
|
if (usd_export_context.export_params.export_armatures &&
|
||||||
|
is_skinned_mesh(context->object)) {
|
||||||
|
data_writer = new USDSkinnedMeshWriter(usd_export_context);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data_writer = new USDMeshWriter(usd_export_context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
break;
|
break;
|
||||||
case OB_CAMERA:
|
case OB_CAMERA:
|
||||||
data_writer = new USDCameraWriter(usd_export_context);
|
if (usd_export_context.export_params.export_cameras)
|
||||||
|
data_writer = new USDCameraWriter(usd_export_context);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
break;
|
break;
|
||||||
case OB_LAMP:
|
case OB_LAMP:
|
||||||
data_writer = new USDLightWriter(usd_export_context);
|
if (usd_export_context.export_params.export_lights)
|
||||||
|
data_writer = new USDLightWriter(usd_export_context);
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
break;
|
break;
|
||||||
case OB_MBALL:
|
case OB_MBALL:
|
||||||
data_writer = new USDMetaballWriter(usd_export_context);
|
data_writer = new USDMetaballWriter(usd_export_context);
|
||||||
break;
|
break;
|
||||||
|
case OB_CURVES_LEGACY:
|
||||||
|
if (usd_export_context.export_params.export_curves) {
|
||||||
|
data_writer = new USDCurveWriter(usd_export_context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
break;
|
||||||
|
case OB_ARMATURE:
|
||||||
|
if (usd_export_context.export_params.export_armatures) {
|
||||||
|
data_writer = new USDArmatureWriter(usd_export_context);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return nullptr;
|
||||||
|
break;
|
||||||
|
|
||||||
case OB_EMPTY:
|
case OB_EMPTY:
|
||||||
case OB_CURVES_LEGACY:
|
|
||||||
case OB_SURF:
|
case OB_SURF:
|
||||||
case OB_FONT:
|
case OB_FONT:
|
||||||
case OB_SPEAKER:
|
case OB_SPEAKER:
|
||||||
case OB_LIGHTPROBE:
|
case OB_LIGHTPROBE:
|
||||||
case OB_LATTICE:
|
case OB_LATTICE:
|
||||||
case OB_ARMATURE:
|
|
||||||
case OB_GPENCIL:
|
case OB_GPENCIL:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
case OB_TYPE_MAX:
|
case OB_TYPE_MAX:
|
||||||
@@ -109,7 +161,7 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data_writer->is_supported(context)) {
|
if (data_writer && !data_writer->is_supported(context)) {
|
||||||
delete data_writer;
|
delete data_writer;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -119,6 +171,10 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const Hierarch
|
|||||||
|
|
||||||
AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
|
AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
|
||||||
{
|
{
|
||||||
|
if (context->is_instance() && params_.use_instancing) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
if (!params_.export_hair) {
|
if (!params_.export_hair) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -126,9 +182,36 @@ AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const Hierarch
|
|||||||
}
|
}
|
||||||
|
|
||||||
AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(
|
AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(
|
||||||
const HierarchyContext *UNUSED(context))
|
const HierarchyContext *context)
|
||||||
{
|
{
|
||||||
return nullptr;
|
if (context->is_instance() && params_.use_instancing) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params_.export_particles) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return new USDParticleWriter(create_usd_export_context(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't generate data writers for instances. */
|
||||||
|
bool USDHierarchyIterator::include_data_writers(const HierarchyContext *context) const
|
||||||
|
{
|
||||||
|
if (!context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(params_.use_instancing && context->is_instance());
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't generate writers for children of instances. */
|
||||||
|
bool USDHierarchyIterator::include_child_writers(const HierarchyContext *context) const
|
||||||
|
{
|
||||||
|
if (!context) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(params_.use_instancing && context->is_instance());
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -48,8 +48,12 @@ class USDHierarchyIterator : public AbstractHierarchyIterator {
|
|||||||
|
|
||||||
virtual void release_writer(AbstractHierarchyWriter *writer) override;
|
virtual void release_writer(AbstractHierarchyWriter *writer) override;
|
||||||
|
|
||||||
|
virtual bool include_data_writers(const HierarchyContext *context) const override;
|
||||||
|
virtual bool include_child_writers(const HierarchyContext *context) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
USDExporterContext create_usd_export_context(const HierarchyContext *context);
|
USDExporterContext create_usd_export_context(const HierarchyContext *context,
|
||||||
|
bool mergeTransformAndShape = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
574
source/blender/io/usd/intern/usd_light_convert.cc
Normal file
574
source/blender/io/usd/intern/usd_light_convert.cc
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "usd_light_convert.h"
|
||||||
|
|
||||||
|
#include "usd.h"
|
||||||
|
#include "usd_reader_prim.h"
|
||||||
|
#include "usd_writer_material.h"
|
||||||
|
|
||||||
|
#include <pxr/base/gf/matrix4f.h>
|
||||||
|
#include <pxr/base/gf/rotation.h>
|
||||||
|
#include <pxr/base/gf/vec3f.h>
|
||||||
|
#include <pxr/usd/usdGeom/scope.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCache.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCommonAPI.h>
|
||||||
|
#include <pxr/usd/usdLux/domeLight.h>
|
||||||
|
#include <pxr/usd/usdShade/material.h>
|
||||||
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
|
|
||||||
|
#include "BKE_image.h"
|
||||||
|
#include "BKE_light.h"
|
||||||
|
#include "BKE_main.h"
|
||||||
|
#include "BKE_node.h"
|
||||||
|
#include "BKE_node_tree_update.h"
|
||||||
|
#include "BKE_scene.h"
|
||||||
|
#include "BLI_listbase.h"
|
||||||
|
#include "BLI_math.h"
|
||||||
|
#include "BLI_path_util.h"
|
||||||
|
#include "BLI_string.h"
|
||||||
|
#include "DNA_light_types.h"
|
||||||
|
#include "DNA_scene_types.h"
|
||||||
|
#include "DNA_world_types.h"
|
||||||
|
#include "ED_node.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace usdtokens {
|
||||||
|
// Attribute names.
|
||||||
|
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken texture_file("texture:file", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool get_authored_value(const pxr::UsdAttribute attr, const double motionSampleTime, T *r_value)
|
||||||
|
{
|
||||||
|
if (attr && attr.HasAuthoredValue()) {
|
||||||
|
return attr.Get<T>(r_value, motionSampleTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WorldNtreeSearchResults {
|
||||||
|
const USDExportParams params;
|
||||||
|
pxr::UsdStageRefPtr stage;
|
||||||
|
|
||||||
|
float world_color[3];
|
||||||
|
float world_intensity;
|
||||||
|
float tex_rot[3];
|
||||||
|
|
||||||
|
std::string file_path;
|
||||||
|
|
||||||
|
float color_mult[3];
|
||||||
|
|
||||||
|
bool background_found;
|
||||||
|
bool env_tex_found;
|
||||||
|
bool mult_found;
|
||||||
|
|
||||||
|
WorldNtreeSearchResults(const USDExportParams &in_params, pxr::UsdStageRefPtr in_stage)
|
||||||
|
: params(in_params),
|
||||||
|
stage(in_stage),
|
||||||
|
world_intensity(0.0f),
|
||||||
|
background_found(false),
|
||||||
|
env_tex_found(false),
|
||||||
|
mult_found(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // End anonymous namespace.
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
static const float nits_to_watts_per_meter_sq = 0.0014641f;
|
||||||
|
|
||||||
|
static const float watts_per_meter_sq_to_nits = 1.0f / nits_to_watts_per_meter_sq;
|
||||||
|
|
||||||
|
static bool node_search(bNode *fromnode, bNode *tonode, void *userdata, const bool reversed)
|
||||||
|
{
|
||||||
|
if (!(userdata && fromnode && tonode)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(makowalski): can we validate that node connectiona are correct? */
|
||||||
|
|
||||||
|
WorldNtreeSearchResults *res = reinterpret_cast<WorldNtreeSearchResults *>(userdata);
|
||||||
|
|
||||||
|
if (!res->background_found && ELEM(fromnode->type, SH_NODE_BACKGROUND)) {
|
||||||
|
/* Get light color and intensity */
|
||||||
|
bNodeSocketValueRGBA *color_data =
|
||||||
|
(bNodeSocketValueRGBA *)((bNodeSocket *)BLI_findlink(&fromnode->inputs, 0))->default_value;
|
||||||
|
bNodeSocketValueFloat *strength_data = (bNodeSocketValueFloat *)((bNodeSocket *)BLI_findlink(
|
||||||
|
&fromnode->inputs, 1))
|
||||||
|
->default_value;
|
||||||
|
|
||||||
|
res->background_found = true;
|
||||||
|
res->world_intensity = strength_data->value;
|
||||||
|
res->world_color[0] = color_data->value[0];
|
||||||
|
res->world_color[1] = color_data->value[1];
|
||||||
|
res->world_color[2] = color_data->value[2];
|
||||||
|
}
|
||||||
|
else if (!res->env_tex_found && ELEM(fromnode->type, SH_NODE_TEX_ENVIRONMENT)) {
|
||||||
|
/* Get env tex path. */
|
||||||
|
|
||||||
|
res->file_path = get_tex_image_asset_path(fromnode, res->stage, res->params);
|
||||||
|
|
||||||
|
if (!res->file_path.empty()) {
|
||||||
|
/* Get the rotation. */
|
||||||
|
NodeTexEnvironment *tex = static_cast<NodeTexEnvironment *>(fromnode->storage);
|
||||||
|
copy_v3_v3(res->tex_rot, tex->base.tex_mapping.rot);
|
||||||
|
|
||||||
|
res->env_tex_found = true;
|
||||||
|
|
||||||
|
if (res->params.export_textures) {
|
||||||
|
export_texture(fromnode, res->stage, res->params.overwrite_textures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!res->env_tex_found && !res->mult_found && ELEM(fromnode->type, SH_NODE_VECTOR_MATH)) {
|
||||||
|
|
||||||
|
if (fromnode->custom1 == NODE_VECTOR_MATH_MULTIPLY) {
|
||||||
|
res->mult_found = true;
|
||||||
|
|
||||||
|
bNodeSocket *vec_sock = nodeFindSocket(fromnode, SOCK_IN, "Vector");
|
||||||
|
if (vec_sock) {
|
||||||
|
vec_sock = vec_sock->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vec_sock) {
|
||||||
|
copy_v3_v3(res->color_mult, ((bNodeSocketValueVector *)vec_sock->default_value)->value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Return the scale factor to convert nits to light energy
|
||||||
|
* (Watts or Watts per meter squared) for the given light. */
|
||||||
|
float nits_to_energy_scale_factor(const Light *light,
|
||||||
|
const float meters_per_unit,
|
||||||
|
const float radius_scale)
|
||||||
|
{
|
||||||
|
if (!light) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compute meters per unit squared. */
|
||||||
|
const float mpu_sq = meters_per_unit * meters_per_unit;
|
||||||
|
|
||||||
|
float scale = nits_to_watts_per_meter_sq;
|
||||||
|
|
||||||
|
/* Scale by the light surface area, for lights other than sun. */
|
||||||
|
switch (light->type) {
|
||||||
|
case LA_AREA:
|
||||||
|
switch (light->area_shape) {
|
||||||
|
case LA_AREA_DISK:
|
||||||
|
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
|
||||||
|
float r = light->area_size / 2.0f;
|
||||||
|
scale *= 2.0f * M_PI * (r * r) * mpu_sq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LA_AREA_RECT: {
|
||||||
|
scale *= light->area_size * light->area_sizey * mpu_sq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LA_AREA_SQUARE: {
|
||||||
|
scale *= light->area_size * light->area_size * mpu_sq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case LA_LOCAL: {
|
||||||
|
float r = light->area_size * radius_scale;
|
||||||
|
scale *= 4.0f * M_PI * (r * r) * mpu_sq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LA_SPOT: {
|
||||||
|
float r = light->area_size * radius_scale;
|
||||||
|
float angle = light->spotsize / 2.0f;
|
||||||
|
scale *= 2.0f * M_PI * (r * r) * (1.0f - cosf(angle)) * mpu_sq;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LA_SUN: {
|
||||||
|
/* Sun energy is Watts per square meter so we don't scale by area. */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the Blender scene has an environment texture,
|
||||||
|
* export it as a USD dome light. */
|
||||||
|
void world_material_to_dome_light(const USDExportParams ¶ms,
|
||||||
|
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 ¶ms,
|
||||||
|
const ImportSettings &settings,
|
||||||
|
Scene *scene,
|
||||||
|
Main *bmain,
|
||||||
|
const pxr::UsdLuxDomeLight &dome_light,
|
||||||
|
const double time)
|
||||||
|
{
|
||||||
|
if (!(scene && scene->world && dome_light)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scene->world->use_nodes) {
|
||||||
|
scene->world->use_nodes = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scene->world->nodetree) {
|
||||||
|
scene->world->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree");
|
||||||
|
if (!scene->world->nodetree) {
|
||||||
|
std::cerr << "WARNING: couldn't create world ntree.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bNodeTree *ntree = scene->world->nodetree;
|
||||||
|
bNode *output = nullptr;
|
||||||
|
bNode *shader = nullptr;
|
||||||
|
|
||||||
|
/* We never delete existing nodes, but we might disconnect them
|
||||||
|
* and move them out of the way. */
|
||||||
|
|
||||||
|
/* Look for the output and background shader nodes, which we will reuse.
|
||||||
|
* TODO(makowalski): add logic to properly verify node connections. */
|
||||||
|
for (bNode *node = static_cast<bNode *>(ntree->nodes.first); node; node = node->next) {
|
||||||
|
if (ELEM(node->type, SH_NODE_OUTPUT_WORLD)) {
|
||||||
|
output = node;
|
||||||
|
}
|
||||||
|
else if (ELEM(node->type, SH_NODE_BACKGROUND)) {
|
||||||
|
shader = node;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* Move node out of the way. */
|
||||||
|
node->locy += 300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Create the output and shader nodes, if they don't exist. */
|
||||||
|
if (!output) {
|
||||||
|
output = nodeAddStaticNode(NULL, ntree, SH_NODE_OUTPUT_WORLD);
|
||||||
|
|
||||||
|
if (!output) {
|
||||||
|
std::cerr << "WARNING: couldn't create world output node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
output->locx = 300.0f;
|
||||||
|
output->locy = 300.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shader) {
|
||||||
|
shader = nodeAddStaticNode(NULL, ntree, SH_NODE_BACKGROUND);
|
||||||
|
|
||||||
|
if (!shader) {
|
||||||
|
std::cerr << "WARNING: couldn't create world shader node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAddLink(scene->world->nodetree,
|
||||||
|
shader,
|
||||||
|
nodeFindSocket(shader, SOCK_OUT, "Background"),
|
||||||
|
output,
|
||||||
|
nodeFindSocket(output, SOCK_IN, "Surface"));
|
||||||
|
|
||||||
|
bNodeSocket *color_sock = nodeFindSocket(shader, SOCK_IN, "Color");
|
||||||
|
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, &scene->world->horr);
|
||||||
|
|
||||||
|
shader->locx = output->locx - 200;
|
||||||
|
shader->locy = output->locy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make sure the first input to the shader node is disconnected. */
|
||||||
|
bNodeSocket *shader_input = static_cast<bNodeSocket *>(BLI_findlink(&shader->inputs, 0));
|
||||||
|
|
||||||
|
if (shader_input && shader_input->link) {
|
||||||
|
nodeRemLink(ntree, shader_input->link);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdAttribute intensity_attr = dome_light.GetIntensityAttr();
|
||||||
|
|
||||||
|
float intensity = 1.0f;
|
||||||
|
intensity_attr.Get(&intensity, time);
|
||||||
|
|
||||||
|
if (!get_authored_value(dome_light.GetIntensityAttr(), time, &intensity)) {
|
||||||
|
dome_light.GetPrim().GetAttribute(usdtokens::intensity).Get(&intensity, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
intensity *= params.light_intensity_scale;
|
||||||
|
|
||||||
|
if (params.convert_light_from_nits) {
|
||||||
|
intensity *= nits_to_watts_per_meter_sq;
|
||||||
|
}
|
||||||
|
|
||||||
|
bNodeSocket *strength_sock = nodeFindSocket(shader, SOCK_IN, "Strength");
|
||||||
|
((bNodeSocketValueFloat *)strength_sock->default_value)->value = intensity;
|
||||||
|
|
||||||
|
pxr::SdfAssetPath tex_path;
|
||||||
|
bool has_tex = get_authored_value(dome_light.GetTextureFileAttr(), time, &tex_path);
|
||||||
|
|
||||||
|
if (!has_tex) {
|
||||||
|
has_tex = dome_light.GetPrim().GetAttribute(usdtokens::texture_file).Get(&tex_path, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::GfVec3f color;
|
||||||
|
bool has_color = get_authored_value(dome_light.GetColorAttr(), time, &color);
|
||||||
|
|
||||||
|
if (!has_color) {
|
||||||
|
has_color = dome_light.GetPrim().GetAttribute(usdtokens::color).Get(&color, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!has_tex) {
|
||||||
|
if (has_color) {
|
||||||
|
bNodeSocket *color_sock = nodeFindSocket(shader, SOCK_IN, "Color");
|
||||||
|
copy_v3_v3(((bNodeSocketValueRGBA *)color_sock->default_value)->value, color.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeSetActive(ntree, output);
|
||||||
|
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If the light has authored color, create the color multiply for the env texture output. */
|
||||||
|
bNode *mult = nullptr;
|
||||||
|
|
||||||
|
if (has_color) {
|
||||||
|
mult = nodeAddStaticNode(NULL, ntree, SH_NODE_VECTOR_MATH);
|
||||||
|
|
||||||
|
if (!mult) {
|
||||||
|
std::cerr << "WARNING: couldn't create vector multiply node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeAddLink(scene->world->nodetree,
|
||||||
|
mult,
|
||||||
|
nodeFindSocket(mult, SOCK_OUT, "Vector"),
|
||||||
|
shader,
|
||||||
|
nodeFindSocket(shader, SOCK_IN, "Color"));
|
||||||
|
|
||||||
|
mult->locx = shader->locx - 200;
|
||||||
|
mult->locy = shader->locy;
|
||||||
|
|
||||||
|
mult->custom1 = NODE_VECTOR_MATH_MULTIPLY;
|
||||||
|
|
||||||
|
bNodeSocket *vec_sock = nodeFindSocket(mult, SOCK_IN, "Vector");
|
||||||
|
if (vec_sock) {
|
||||||
|
vec_sock = vec_sock->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vec_sock) {
|
||||||
|
copy_v3_v3(((bNodeSocketValueVector *)vec_sock->default_value)->value, color.data());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "ERROR: couldn't find vector multiply second vector input.\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bNode *tex = nodeAddStaticNode(NULL, ntree, SH_NODE_TEX_ENVIRONMENT);
|
||||||
|
|
||||||
|
if (!tex) {
|
||||||
|
std::cerr << "WARNING: couldn't create world environment texture node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mult) {
|
||||||
|
nodeAddLink(scene->world->nodetree,
|
||||||
|
tex,
|
||||||
|
nodeFindSocket(tex, SOCK_OUT, "Color"),
|
||||||
|
mult,
|
||||||
|
nodeFindSocket(mult, SOCK_IN, "Vector"));
|
||||||
|
|
||||||
|
tex->locx = mult->locx - 400;
|
||||||
|
tex->locy = mult->locy;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nodeAddLink(scene->world->nodetree,
|
||||||
|
tex,
|
||||||
|
nodeFindSocket(tex, SOCK_OUT, "Color"),
|
||||||
|
shader,
|
||||||
|
nodeFindSocket(shader, SOCK_IN, "Color"));
|
||||||
|
|
||||||
|
tex->locx = shader->locx - 400;
|
||||||
|
tex->locy = shader->locy;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string tex_path_str = tex_path.GetResolvedPath();
|
||||||
|
|
||||||
|
if (tex_path_str.empty()) {
|
||||||
|
std::cerr << "WARNING: Couldn't get resolved path for asset " << tex_path
|
||||||
|
<< " for Texture Image node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Image *image = BKE_image_load_exists(bmain, tex_path_str.c_str());
|
||||||
|
if (!image) {
|
||||||
|
std::cerr << "WARNING: Couldn't open image file '" << tex_path_str
|
||||||
|
<< "' for Texture Image node.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex->id = &image->id;
|
||||||
|
|
||||||
|
/* Set the transform. */
|
||||||
|
pxr::UsdGeomXformCache xf_cache(time);
|
||||||
|
|
||||||
|
pxr::GfMatrix4d xf = xf_cache.GetLocalToWorldTransform(dome_light.GetPrim());
|
||||||
|
|
||||||
|
if (settings.do_convert_mat) {
|
||||||
|
/* Apply matrix for z-up conversion. */
|
||||||
|
pxr::GfMatrix4d convert_xf(pxr::GfMatrix4f(settings.conversion_mat));
|
||||||
|
xf *= convert_xf;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::GfRotation rot = xf.ExtractRotation();
|
||||||
|
|
||||||
|
pxr::GfVec3d rot_vec = rot.Decompose(
|
||||||
|
pxr::GfVec3d::XAxis(), pxr::GfVec3d::YAxis(), pxr::GfVec3d::ZAxis());
|
||||||
|
|
||||||
|
NodeTexEnvironment *tex_env = static_cast<NodeTexEnvironment *>(tex->storage);
|
||||||
|
tex_env->base.tex_mapping.rot[0] = -static_cast<float>(rot_vec[0]);
|
||||||
|
tex_env->base.tex_mapping.rot[1] = -static_cast<float>(rot_vec[1]);
|
||||||
|
tex_env->base.tex_mapping.rot[2] = 180 - static_cast<float>(rot_vec[2]);
|
||||||
|
|
||||||
|
/* Convert radians to degrees. */
|
||||||
|
mul_v3_fl(tex_env->base.tex_mapping.rot, M_PI / 180.0f);
|
||||||
|
|
||||||
|
eul_to_mat4(tex_env->base.tex_mapping.mat, tex_env->base.tex_mapping.rot);
|
||||||
|
|
||||||
|
nodeSetActive(ntree, output);
|
||||||
|
BKE_ntree_update_main_tree(bmain, ntree, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
49
source/blender/io/usd/intern/usd_light_convert.h
Normal file
49
source/blender/io/usd/intern/usd_light_convert.h
Normal 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 ¶ms,
|
||||||
|
const Scene *scene,
|
||||||
|
pxr::UsdStageRefPtr stage);
|
||||||
|
|
||||||
|
void dome_light_to_world_material(const USDImportParams ¶ms,
|
||||||
|
const ImportSettings &settings,
|
||||||
|
Scene *scene,
|
||||||
|
Main *bmain,
|
||||||
|
const pxr::UsdLuxDomeLight &dome_light,
|
||||||
|
const double time = 0.0);
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
@@ -27,7 +27,11 @@ void USDCameraReader::create_object(Main *bmain, const double /* motionSampleTim
|
|||||||
|
|
||||||
void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime)
|
void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTime)
|
||||||
{
|
{
|
||||||
Camera *bcam = (Camera *)object_->data;
|
if (!object_->data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Camera *bcam = static_cast<Camera *>(object_->data);
|
||||||
|
|
||||||
pxr::UsdGeomCamera cam_prim(prim_);
|
pxr::UsdGeomCamera cam_prim(prim_);
|
||||||
|
|
||||||
@@ -35,46 +39,37 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxr::VtValue val;
|
pxr::GfCamera usd_cam = cam_prim.GetCamera(motionSampleTime);
|
||||||
cam_prim.GetFocalLengthAttr().Get(&val, motionSampleTime);
|
|
||||||
pxr::VtValue verApOffset;
|
|
||||||
cam_prim.GetVerticalApertureOffsetAttr().Get(&verApOffset, motionSampleTime);
|
|
||||||
pxr::VtValue horApOffset;
|
|
||||||
cam_prim.GetHorizontalApertureOffsetAttr().Get(&horApOffset, motionSampleTime);
|
|
||||||
pxr::VtValue clippingRangeVal;
|
|
||||||
cam_prim.GetClippingRangeAttr().Get(&clippingRangeVal, motionSampleTime);
|
|
||||||
pxr::VtValue focalDistanceVal;
|
|
||||||
cam_prim.GetFocusDistanceAttr().Get(&focalDistanceVal, motionSampleTime);
|
|
||||||
pxr::VtValue fstopVal;
|
|
||||||
cam_prim.GetFStopAttr().Get(&fstopVal, motionSampleTime);
|
|
||||||
pxr::VtValue projectionVal;
|
|
||||||
cam_prim.GetProjectionAttr().Get(&projectionVal, motionSampleTime);
|
|
||||||
pxr::VtValue verAp;
|
|
||||||
cam_prim.GetVerticalApertureAttr().Get(&verAp, motionSampleTime);
|
|
||||||
pxr::VtValue horAp;
|
|
||||||
cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime);
|
|
||||||
|
|
||||||
bcam->lens = val.Get<float>();
|
const float apperture_x = usd_cam.GetHorizontalAperture();
|
||||||
/* TODO(@makowalski): support sensor size. */
|
const float apperture_y = usd_cam.GetVerticalAperture();
|
||||||
#if 0
|
const float h_film_offset = usd_cam.GetHorizontalApertureOffset();
|
||||||
bcam->sensor_x = 0.0f;
|
const float v_film_offset = usd_cam.GetVerticalApertureOffset();
|
||||||
bcam->sensor_y = 0.0f;
|
const float film_aspect = apperture_x / apperture_y;
|
||||||
#endif
|
|
||||||
bcam->shiftx = verApOffset.Get<float>();
|
|
||||||
bcam->shifty = horApOffset.Get<float>();
|
|
||||||
|
|
||||||
bcam->type = (projectionVal.Get<pxr::TfToken>().GetString() == "perspective") ? CAM_PERSP :
|
bcam->type = usd_cam.GetProjection() == pxr::GfCamera::Perspective ? CAM_PERSP : CAM_ORTHO;
|
||||||
CAM_ORTHO;
|
|
||||||
|
|
||||||
/* Calling UncheckedGet() to silence compiler warnings. */
|
bcam->lens = usd_cam.GetFocalLength();
|
||||||
bcam->clip_start = max_ff(0.1f, clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[0]);
|
|
||||||
bcam->clip_end = clippingRangeVal.UncheckedGet<pxr::GfVec2f>()[1];
|
|
||||||
|
|
||||||
bcam->dof.focus_distance = focalDistanceVal.Get<float>();
|
bcam->sensor_x = apperture_x;
|
||||||
bcam->dof.aperture_fstop = static_cast<float>(fstopVal.Get<float>());
|
bcam->sensor_y = apperture_y;
|
||||||
|
|
||||||
|
bcam->shiftx = h_film_offset / apperture_x;
|
||||||
|
bcam->shifty = v_film_offset / apperture_y / film_aspect;
|
||||||
|
|
||||||
|
pxr::GfRange1f usd_clip_range = usd_cam.GetClippingRange();
|
||||||
|
bcam->clip_start = usd_clip_range.GetMin() * settings_->scale;
|
||||||
|
bcam->clip_end = usd_clip_range.GetMax() * settings_->scale;
|
||||||
|
|
||||||
|
bcam->dof.focus_distance = usd_cam.GetFocusDistance() * settings_->scale;
|
||||||
|
bcam->dof.aperture_fstop = usd_cam.GetFStop();
|
||||||
|
|
||||||
|
if (bcam->dof.focus_distance > 0.0f || bcam->dof.aperture_fstop > 0.0f) {
|
||||||
|
bcam->dof.flag |= CAM_DOF_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
if (bcam->type == CAM_ORTHO) {
|
if (bcam->type == CAM_ORTHO) {
|
||||||
bcam->ortho_scale = max_ff(verAp.Get<float>(), horAp.Get<float>());
|
bcam->ortho_scale = max_ff(apperture_x, apperture_y);
|
||||||
}
|
}
|
||||||
|
|
||||||
USDXformReader::read_object_data(bmain, motionSampleTime);
|
USDXformReader::read_object_data(bmain, motionSampleTime);
|
||||||
|
@@ -20,18 +20,34 @@
|
|||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
void USDGeomReader::apply_cache_file(CacheFile *cache_file)
|
||||||
|
{
|
||||||
|
if (!cache_file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needs_cachefile_ && object_) {
|
||||||
|
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
|
||||||
|
BLI_addtail(&object_->modifiers, md);
|
||||||
|
|
||||||
|
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
|
||||||
|
|
||||||
|
mcmd->cache_file = cache_file;
|
||||||
|
id_us_plus(&mcmd->cache_file->id);
|
||||||
|
mcmd->read_flag = import_params_.mesh_read_flag;
|
||||||
|
|
||||||
|
BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (USDXformReader::needs_cachefile()) {
|
||||||
|
USDXformReader::apply_cache_file(cache_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void USDGeomReader::add_cache_modifier()
|
void USDGeomReader::add_cache_modifier()
|
||||||
{
|
{
|
||||||
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
|
/* Defer creating modifiers until a cache file is provided. */
|
||||||
BLI_addtail(&object_->modifiers, md);
|
needs_cachefile_ = true;
|
||||||
|
|
||||||
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
|
|
||||||
|
|
||||||
mcmd->cache_file = settings_->cache_file;
|
|
||||||
id_us_plus(&mcmd->cache_file->id);
|
|
||||||
mcmd->read_flag = import_params_.mesh_read_flag;
|
|
||||||
|
|
||||||
BLI_strncpy(mcmd->object_path, prim_.GetPath().GetString().c_str(), FILE_MAX);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDGeomReader::add_subdiv_modifier()
|
void USDGeomReader::add_subdiv_modifier()
|
||||||
|
@@ -10,12 +10,14 @@ struct Mesh;
|
|||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
class USDGeomReader : public USDXformReader {
|
class USDGeomReader : public USDXformReader {
|
||||||
|
private:
|
||||||
|
bool needs_cachefile_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
USDGeomReader(const pxr::UsdPrim &prim,
|
USDGeomReader(const pxr::UsdPrim &prim,
|
||||||
const USDImportParams &import_params,
|
const USDImportParams &import_params,
|
||||||
const ImportSettings &settings)
|
const ImportSettings &settings)
|
||||||
: USDXformReader(prim, import_params, settings)
|
: USDXformReader(prim, import_params, settings), needs_cachefile_(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +31,12 @@ class USDGeomReader : public USDXformReader {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool needs_cachefile() override
|
||||||
|
{
|
||||||
|
return needs_cachefile_ || USDXformReader::needs_cachefile();
|
||||||
|
}
|
||||||
|
void apply_cache_file(CacheFile *cache_file) override;
|
||||||
|
|
||||||
void add_cache_modifier();
|
void add_cache_modifier();
|
||||||
void add_subdiv_modifier();
|
void add_subdiv_modifier();
|
||||||
};
|
};
|
||||||
|
64
source/blender/io/usd/intern/usd_reader_instance.cc
Normal file
64
source/blender/io/usd/intern/usd_reader_instance.cc
Normal 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
|
47
source/blender/io/usd/intern/usd_reader_instance.h
Normal file
47
source/blender/io/usd/intern/usd_reader_instance.h
Normal 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
|
@@ -2,6 +2,7 @@
|
|||||||
* Copyright 2021 Tangent Animation. All rights reserved. */
|
* Copyright 2021 Tangent Animation. All rights reserved. */
|
||||||
|
|
||||||
#include "usd_reader_light.h"
|
#include "usd_reader_light.h"
|
||||||
|
#include "usd_light_convert.h"
|
||||||
|
|
||||||
#include "BKE_light.h"
|
#include "BKE_light.h"
|
||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
#include "DNA_light_types.h"
|
#include "DNA_light_types.h"
|
||||||
#include "DNA_object_types.h"
|
#include "DNA_object_types.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdGeom/metrics.h>
|
||||||
#include <pxr/usd/usdLux/diskLight.h>
|
#include <pxr/usd/usdLux/diskLight.h>
|
||||||
#include <pxr/usd/usdLux/distantLight.h>
|
#include <pxr/usd/usdLux/distantLight.h>
|
||||||
#include <pxr/usd/usdLux/rectLight.h>
|
#include <pxr/usd/usdLux/rectLight.h>
|
||||||
@@ -17,8 +19,52 @@
|
|||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace usdtokens {
|
||||||
|
// Attribute names.
|
||||||
|
static const pxr::TfToken angle("angle", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken height("height", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken radius("radius", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken width("width", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool get_authored_value(const pxr::UsdAttribute attr, const double motionSampleTime, T *r_value)
|
||||||
|
{
|
||||||
|
if (attr && attr.HasAuthoredValue()) {
|
||||||
|
return attr.Get<T>(r_value, motionSampleTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // End anonymous namespace.
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
USDLightReader::USDLightReader(const pxr::UsdPrim &prim,
|
||||||
|
const USDImportParams &import_params,
|
||||||
|
const ImportSettings &settings,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache)
|
||||||
|
: USDXformReader(prim, import_params, settings), usd_world_scale_(1.0f)
|
||||||
|
{
|
||||||
|
if (xf_cache && import_params.convert_light_from_nits) {
|
||||||
|
pxr::GfMatrix4d xf = xf_cache->GetLocalToWorldTransform(prim);
|
||||||
|
pxr::GfMatrix4d r;
|
||||||
|
pxr::GfVec3d s;
|
||||||
|
pxr::GfMatrix4d u;
|
||||||
|
pxr::GfVec3d t;
|
||||||
|
pxr::GfMatrix4d p;
|
||||||
|
xf.Factor(&r, &s, &u, &t, &p);
|
||||||
|
|
||||||
|
usd_world_scale_ = (s[0] + s[1] + s[2]) / 3.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */)
|
void USDLightReader::create_object(Main *bmain, const double /* motionSampleTime */)
|
||||||
{
|
{
|
||||||
Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str()));
|
Light *blight = static_cast<Light *>(BKE_light_add(bmain, name_.c_str()));
|
||||||
@@ -76,12 +122,10 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
|
|
||||||
/* Set light values. */
|
/* Set light values. */
|
||||||
|
|
||||||
if (pxr::UsdAttribute intensity_attr = light_api.GetIntensityAttr()) {
|
/* In USD 21, light attributes were renamed to have an 'inputs:' prefix
|
||||||
float intensity = 0.0f;
|
* (e.g., 'inputs:intensity'). Here and below, for backward compatibility
|
||||||
if (intensity_attr.Get(&intensity, motionSampleTime)) {
|
* with older USD versions, we also query attributes using the previous
|
||||||
blight->energy = intensity * this->import_params_.light_intensity_scale;
|
* naming scheme that omits this prefix. */
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO(makowalsk): Not currently supported. */
|
/* TODO(makowalsk): Not currently supported. */
|
||||||
#if 0
|
#if 0
|
||||||
@@ -95,20 +139,18 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime);
|
light_prim.GetDiffuseAttr().Get(&diffuse, motionSampleTime);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (pxr::UsdAttribute spec_attr = light_api.GetSpecularAttr()) {
|
float specular;
|
||||||
float spec = 0.0f;
|
if (get_authored_value(light_api.GetSpecularAttr(), motionSampleTime, &specular) ||
|
||||||
if (spec_attr.Get(&spec, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::specular).Get(&specular, motionSampleTime)) {
|
||||||
blight->spec_fac = spec;
|
blight->spec_fac = specular;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute color_attr = light_api.GetColorAttr()) {
|
pxr::GfVec3f color;
|
||||||
pxr::GfVec3f color;
|
if (get_authored_value(light_api.GetColorAttr(), motionSampleTime, &color) ||
|
||||||
if (color_attr.Get(&color, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::color).Get(&color, motionSampleTime)) {
|
||||||
blight->r = color[0];
|
blight->r = color[0];
|
||||||
blight->g = color[1];
|
blight->g = color[1];
|
||||||
blight->b = color[2];
|
blight->b = color[2];
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO(makowalski): Not currently supported. */
|
/* TODO(makowalski): Not currently supported. */
|
||||||
@@ -123,6 +165,7 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime);
|
light_prim.GetColorTemperatureAttr().Get(&color_temp, motionSampleTime);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// XXX - apply scene scale to local and spot lights but not area lights (?)
|
||||||
switch (blight->type) {
|
switch (blight->type) {
|
||||||
case LA_AREA:
|
case LA_AREA:
|
||||||
if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) {
|
if (blight->area_shape == LA_AREA_RECT && prim_.IsA<pxr::UsdLuxRectLight>()) {
|
||||||
@@ -133,18 +176,16 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute width_attr = rect_light.GetWidthAttr()) {
|
float width;
|
||||||
float width = 0.0f;
|
if (get_authored_value(rect_light.GetWidthAttr(), motionSampleTime, &width) ||
|
||||||
if (width_attr.Get(&width, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::width).Get(&width, motionSampleTime)) {
|
||||||
blight->area_size = width;
|
blight->area_size = width;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute height_attr = rect_light.GetHeightAttr()) {
|
float height;
|
||||||
float height = 0.0f;
|
if (get_authored_value(rect_light.GetHeightAttr(), motionSampleTime, &height) ||
|
||||||
if (height_attr.Get(&height, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::height).Get(&height, motionSampleTime)) {
|
||||||
blight->area_sizey = height;
|
blight->area_sizey = height;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) {
|
else if (blight->area_shape == LA_AREA_DISK && prim_.IsA<pxr::UsdLuxDiskLight>()) {
|
||||||
@@ -155,11 +196,10 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute radius_attr = disk_light.GetRadiusAttr()) {
|
float radius;
|
||||||
float radius = 0.0f;
|
if (get_authored_value(disk_light.GetRadiusAttr(), motionSampleTime, &radius) ||
|
||||||
if (radius_attr.Get(&radius, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
|
||||||
blight->area_size = radius * 2.0f;
|
blight->area_size = radius * 2.0f;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -172,28 +212,25 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) {
|
float radius;
|
||||||
float radius = 0.0f;
|
if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
|
||||||
if (radius_attr.Get(&radius, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
|
||||||
blight->area_size = radius;
|
blight->area_size = radius;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LA_SPOT:
|
case LA_SPOT:
|
||||||
if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
|
if (prim_.IsA<pxr::UsdLuxSphereLight>()) {
|
||||||
|
|
||||||
pxr::UsdLuxSphereLight sphere_light(prim_);
|
pxr::UsdLuxSphereLight sphere_light(prim_);
|
||||||
|
|
||||||
if (!sphere_light) {
|
if (!sphere_light) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute radius_attr = sphere_light.GetRadiusAttr()) {
|
float radius;
|
||||||
float radius = 0.0f;
|
if (get_authored_value(sphere_light.GetRadiusAttr(), motionSampleTime, &radius) ||
|
||||||
if (radius_attr.Get(&radius, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::radius).Get(&radius, motionSampleTime)) {
|
||||||
blight->area_size = radius;
|
blight->area_size = radius;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shaping_api) {
|
if (!shaping_api) {
|
||||||
@@ -203,7 +240,17 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) {
|
if (pxr::UsdAttribute cone_angle_attr = shaping_api.GetShapingConeAngleAttr()) {
|
||||||
float cone_angle = 0.0f;
|
float cone_angle = 0.0f;
|
||||||
if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) {
|
if (cone_angle_attr.Get(&cone_angle, motionSampleTime)) {
|
||||||
blight->spotsize = cone_angle * ((float)M_PI / 180.0f) * 2.0f;
|
float spot_size = cone_angle * ((float)M_PI / 180.0f) * 2.0f;
|
||||||
|
|
||||||
|
if (spot_size <= M_PI) {
|
||||||
|
blight->spotsize = spot_size;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/* The spot size is greter the 180 degrees, which Blender doesn't support so we
|
||||||
|
* make this a sphere light instead. */
|
||||||
|
blight->type = LA_LOCAL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,16 +270,36 @@ void USDLightReader::read_object_data(Main *bmain, const double motionSampleTime
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pxr::UsdAttribute angle_attr = distant_light.GetAngleAttr()) {
|
float angle;
|
||||||
float angle = 0.0f;
|
if (get_authored_value(distant_light.GetAngleAttr(), motionSampleTime, &angle) ||
|
||||||
if (angle_attr.Get(&angle, motionSampleTime)) {
|
prim_.GetAttribute(usdtokens::angle).Get(&angle, motionSampleTime)) {
|
||||||
blight->sun_angle = angle * (float)M_PI / 180.0f;
|
blight->sun_angle = angle * (float)M_PI / 180.0f;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float intensity;
|
||||||
|
if (get_authored_value(light_api.GetIntensityAttr(), motionSampleTime, &intensity) ||
|
||||||
|
prim_.GetAttribute(usdtokens::intensity).Get(&intensity, motionSampleTime)) {
|
||||||
|
|
||||||
|
float intensity_scale = this->import_params_.light_intensity_scale;
|
||||||
|
|
||||||
|
if (import_params_.convert_light_from_nits) {
|
||||||
|
/* It's important that we perform the light unit conversion before applying any scaling to
|
||||||
|
* the light size, so we can use the USD's meters per unit value. */
|
||||||
|
const float meters_per_unit = static_cast<float>(
|
||||||
|
pxr::UsdGeomGetStageMetersPerUnit(prim_.GetStage()));
|
||||||
|
intensity_scale *= nits_to_energy_scale_factor(blight, meters_per_unit * usd_world_scale_);
|
||||||
|
}
|
||||||
|
|
||||||
|
blight->energy = intensity * intensity_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((blight->type == LA_SPOT || blight->type == LA_LOCAL) && import_params_.scale_light_radius) {
|
||||||
|
blight->area_size *= settings_->scale;
|
||||||
|
}
|
||||||
|
|
||||||
USDXformReader::read_object_data(bmain, motionSampleTime);
|
USDXformReader::read_object_data(bmain, motionSampleTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,17 +5,19 @@
|
|||||||
#include "usd.h"
|
#include "usd.h"
|
||||||
#include "usd_reader_xform.h"
|
#include "usd_reader_xform.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdGeom/xformCache.h>
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
class USDLightReader : public USDXformReader {
|
class USDLightReader : public USDXformReader {
|
||||||
|
private:
|
||||||
|
float usd_world_scale_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
USDLightReader(const pxr::UsdPrim &prim,
|
USDLightReader(const pxr::UsdPrim &prim,
|
||||||
const USDImportParams &import_params,
|
const USDImportParams &import_params,
|
||||||
const ImportSettings &settings)
|
const ImportSettings &settings,
|
||||||
: USDXformReader(prim, import_params, settings)
|
pxr::UsdGeomXformCache *xf_cache = nullptr);
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void create_object(Main *bmain, double motionSampleTime) override;
|
void create_object(Main *bmain, double motionSampleTime) override;
|
||||||
|
|
||||||
|
@@ -3,22 +3,30 @@
|
|||||||
|
|
||||||
#include "usd_reader_material.h"
|
#include "usd_reader_material.h"
|
||||||
|
|
||||||
|
#include "usd_umm.h"
|
||||||
|
|
||||||
#include "BKE_image.h"
|
#include "BKE_image.h"
|
||||||
#include "BKE_main.h"
|
#include "BKE_main.h"
|
||||||
#include "BKE_material.h"
|
#include "BKE_material.h"
|
||||||
#include "BKE_node.h"
|
#include "BKE_node.h"
|
||||||
#include "BKE_node_tree_update.h"
|
#include "BKE_node_tree_update.h"
|
||||||
|
|
||||||
|
#include "BLI_fileops.h"
|
||||||
#include "BLI_math_vector.h"
|
#include "BLI_math_vector.h"
|
||||||
|
#include "BLI_path_util.h"
|
||||||
#include "BLI_string.h"
|
#include "BLI_string.h"
|
||||||
|
|
||||||
#include "DNA_material_types.h"
|
#include "DNA_material_types.h"
|
||||||
|
|
||||||
|
#include "WM_api.h"
|
||||||
|
|
||||||
#include <pxr/base/gf/vec3f.h>
|
#include <pxr/base/gf/vec3f.h>
|
||||||
|
#include <pxr/usd/ar/resolver.h>
|
||||||
#include <pxr/usd/usdShade/material.h>
|
#include <pxr/usd/usdShade/material.h>
|
||||||
#include <pxr/usd/usdShade/shader.h>
|
#include <pxr/usd/usdShade/shader.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace usdtokens {
|
namespace usdtokens {
|
||||||
@@ -52,6 +60,13 @@ static const pxr::TfToken varname("varname", pxr::TfToken::Immortal);
|
|||||||
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
|
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
|
||||||
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
|
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
|
||||||
|
|
||||||
|
/* Wrap mode names. */
|
||||||
|
static const pxr::TfToken black("black", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken clamp("clamp", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken repeat("repeat", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken wrapS("wrapS", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken wrapT("wrapT", pxr::TfToken::Immortal);
|
||||||
|
|
||||||
/* USD shader names. */
|
/* USD shader names. */
|
||||||
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
|
static const pxr::TfToken UsdPreviewSurface("UsdPreviewSurface", pxr::TfToken::Immortal);
|
||||||
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
|
static const pxr::TfToken UsdPrimvarReader_float2("UsdPrimvarReader_float2",
|
||||||
@@ -94,6 +109,65 @@ static void link_nodes(
|
|||||||
nodeAddLink(ntree, source, source_socket, dest, dest_socket);
|
nodeAddLink(ntree, source, source_socket, dest, dest_socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Returns a layer handle retrieved from the given attribute's property specs.
|
||||||
|
* Note that the returned handle may be invalid if no layer could be found. */
|
||||||
|
static pxr::SdfLayerHandle get_layer_handle(const pxr::UsdAttribute &Attribute)
|
||||||
|
{
|
||||||
|
for (auto PropertySpec : Attribute.GetPropertyStack(pxr::UsdTimeCode::EarliestTime())) {
|
||||||
|
if (PropertySpec->HasDefaultValue() ||
|
||||||
|
PropertySpec->GetLayer()->GetNumTimeSamplesForPath(PropertySpec->GetPath()) > 0) {
|
||||||
|
return PropertySpec->GetLayer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pxr::SdfLayerHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_udim_path(const std::string &path)
|
||||||
|
{
|
||||||
|
return path.find("<UDIM>") != std::string::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For the given UDIM path (assumed to contain the UDIM token), returns an array
|
||||||
|
* containing valid tile indices.
|
||||||
|
* Returns std::nullopt if no tiles were found. */
|
||||||
|
static std::optional<blender::Vector<int>> get_udim_tiles(const std::string &file_path)
|
||||||
|
{
|
||||||
|
char base_udim_path[FILE_MAX];
|
||||||
|
BLI_strncpy(base_udim_path, file_path.c_str(), sizeof(base_udim_path));
|
||||||
|
|
||||||
|
blender::Vector<int> udim_tiles;
|
||||||
|
|
||||||
|
/* Extract the tile numbers from all files on disk. */
|
||||||
|
ListBase tiles = {nullptr, nullptr};
|
||||||
|
int tile_start, tile_range;
|
||||||
|
bool result = BKE_image_get_tile_info(base_udim_path, &tiles, &tile_start, &tile_range);
|
||||||
|
if (result) {
|
||||||
|
LISTBASE_FOREACH (LinkData *, tile, &tiles) {
|
||||||
|
int tile_number = POINTER_AS_INT(tile->data);
|
||||||
|
udim_tiles.append(tile_number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BLI_freelistN(&tiles);
|
||||||
|
|
||||||
|
if (udim_tiles.is_empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return udim_tiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add tiles with the given indices to the given image. */
|
||||||
|
static void add_udim_tiles(Image *image, const blender::Vector<int> &indices)
|
||||||
|
{
|
||||||
|
image->source = IMA_SRC_TILED;
|
||||||
|
|
||||||
|
for (int tile_number : indices) {
|
||||||
|
BKE_image_add_tile(image, tile_number, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Returns true if the given shader may have opacity < 1.0, based
|
/* Returns true if the given shader may have opacity < 1.0, based
|
||||||
* on heuristics. */
|
* on heuristics. */
|
||||||
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
|
static bool needs_blend(const pxr::UsdShadeShader &usd_shader)
|
||||||
@@ -165,6 +239,40 @@ static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader
|
|||||||
return pxr::TfToken();
|
return pxr::TfToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int get_image_extension(const pxr::UsdShadeShader &usd_shader, const int default_value)
|
||||||
|
{
|
||||||
|
pxr::UsdShadeInput wrap_input = usd_shader.GetInput(usdtokens::wrapS);
|
||||||
|
|
||||||
|
if (!wrap_input) {
|
||||||
|
wrap_input = usd_shader.GetInput(usdtokens::wrapT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wrap_input) {
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtValue wrap_input_val;
|
||||||
|
if (!(wrap_input.Get(&wrap_input_val) && wrap_input_val.IsHolding<pxr::TfToken>())) {
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::TfToken wrap_val = wrap_input_val.Get<pxr::TfToken>();
|
||||||
|
|
||||||
|
if (wrap_val == usdtokens::repeat) {
|
||||||
|
return SHD_IMAGE_EXTENSION_REPEAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrap_val == usdtokens::clamp) {
|
||||||
|
return SHD_IMAGE_EXTENSION_EXTEND;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wrap_val == usdtokens::black) {
|
||||||
|
return SHD_IMAGE_EXTENSION_CLIP;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default_value;
|
||||||
|
}
|
||||||
|
|
||||||
/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
|
/* Attempts to return in r_preview_surface the UsdPreviewSurface shader source
|
||||||
* of the given material. Returns true if a UsdPreviewSurface source was found
|
* of the given material. Returns true if a UsdPreviewSurface source was found
|
||||||
* and returns false otherwise. */
|
* and returns false otherwise. */
|
||||||
@@ -271,11 +379,26 @@ Material *USDMaterialReader::add_material(const pxr::UsdShadeMaterial &usd_mater
|
|||||||
* if there is one. */
|
* if there is one. */
|
||||||
pxr::UsdShadeShader usd_preview;
|
pxr::UsdShadeShader usd_preview;
|
||||||
if (get_usd_preview_surface(usd_material, usd_preview)) {
|
if (get_usd_preview_surface(usd_material, usd_preview)) {
|
||||||
|
/* Always set the viewport material properties from the USD
|
||||||
|
* Preview Surface settings. */
|
||||||
set_viewport_material_props(mtl, usd_preview);
|
set_viewport_material_props(mtl, usd_preview);
|
||||||
|
}
|
||||||
|
|
||||||
/* Optionally, create shader nodes to represent a UsdPreviewSurface. */
|
if (params_.import_shaders_mode == USD_IMPORT_USD_PREVIEW_SURFACE && usd_preview) {
|
||||||
if (params_.import_usd_preview) {
|
/* Create shader nodes to represent a UsdPreviewSurface. */
|
||||||
|
import_usd_preview(mtl, usd_preview);
|
||||||
|
}
|
||||||
|
else if (params_.import_shaders_mode == USD_IMPORT_MDL) {
|
||||||
|
bool has_mdl = false;
|
||||||
|
#ifdef WITH_PYTHON
|
||||||
|
/* Invoke UMM to convert to MDL. */
|
||||||
|
umm_import_mdl_material(mtl, usd_material, true /* Verbose */, &has_mdl);
|
||||||
|
#endif
|
||||||
|
if (!has_mdl && usd_preview) {
|
||||||
|
/* The material has no MDL shader, so fall back on importing UsdPreviewSuface. */
|
||||||
|
std::string message = "Material has no MDL shader " + mtl_name +
|
||||||
|
", importing USD Preview Surface shaders instead";
|
||||||
|
WM_reportf(RPT_INFO, message.c_str());
|
||||||
import_usd_preview(mtl, usd_preview);
|
import_usd_preview(mtl, usd_preview);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -585,6 +708,22 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
|||||||
/* Try to load the texture image. */
|
/* Try to load the texture image. */
|
||||||
pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file);
|
pxr::UsdShadeInput file_input = usd_shader.GetInput(usdtokens::file);
|
||||||
|
|
||||||
|
/* File input may have a connected source, e.g., if it's been overridden by
|
||||||
|
* an input on the mateial. */
|
||||||
|
if (file_input.HasConnectedSource()) {
|
||||||
|
pxr::UsdShadeConnectableAPI source;
|
||||||
|
pxr::TfToken source_name;
|
||||||
|
pxr::UsdShadeAttributeType source_type;
|
||||||
|
|
||||||
|
if (file_input.GetConnectedSource(&source, &source_name, &source_type)) {
|
||||||
|
file_input = source.GetInput(source_name);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "ERROR: couldn't get connected source for file input "
|
||||||
|
<< file_input.GetPrim().GetPath() << " " << file_input.GetFullName() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!file_input) {
|
if (!file_input) {
|
||||||
std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath()
|
std::cerr << "WARNING: Couldn't get file input for USD shader " << usd_shader.GetPath()
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
@@ -599,21 +738,51 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
|
const pxr::SdfAssetPath &asset_path = file_val.Get<pxr::SdfAssetPath>();
|
||||||
|
|
||||||
std::string file_path = asset_path.GetResolvedPath();
|
std::string file_path = asset_path.GetResolvedPath();
|
||||||
|
|
||||||
|
if (file_path.empty()) {
|
||||||
|
/* No resolved path, so use the asset path (usually
|
||||||
|
* necessary for UDIM paths). */
|
||||||
|
file_path = asset_path.GetAssetPath();
|
||||||
|
|
||||||
|
/* Texture paths are frequently relative to the USD, so get
|
||||||
|
* the absolute path. */
|
||||||
|
if (pxr::SdfLayerHandle layer_handle = get_layer_handle(file_input.GetAttr())) {
|
||||||
|
file_path = layer_handle->ComputeAbsolutePath(file_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (file_path.empty()) {
|
if (file_path.empty()) {
|
||||||
std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path
|
std::cerr << "WARNING: Couldn't resolve image asset '" << asset_path
|
||||||
<< "' for Texture Image node." << std::endl;
|
<< "' for Texture Image node." << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If this is a UDIM texture, this will store the
|
||||||
|
* UDIM tile indices. */
|
||||||
|
std::optional<blender::Vector<int>> udim_tiles;
|
||||||
|
|
||||||
|
if (is_udim_path(file_path)) {
|
||||||
|
udim_tiles = get_udim_tiles(file_path);
|
||||||
|
}
|
||||||
|
|
||||||
const char *im_file = file_path.c_str();
|
const char *im_file = file_path.c_str();
|
||||||
|
|
||||||
Image *image = BKE_image_load_exists(bmain_, im_file);
|
Image *image = BKE_image_load_exists(bmain_, im_file);
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node."
|
std::cerr << "WARNING: Couldn't open image file '" << im_file << "' for Texture Image node."
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (udim_tiles) {
|
||||||
|
/* Not calling udim_tiles.value(), which is not
|
||||||
|
* supported in macOS versions prior to 10.14.1. */
|
||||||
|
add_udim_tiles(image, *udim_tiles);
|
||||||
|
}
|
||||||
|
|
||||||
tex_image->id = &image->id;
|
tex_image->id = &image->id;
|
||||||
|
|
||||||
/* Set texture color space.
|
/* Set texture color space.
|
||||||
@@ -625,11 +794,17 @@ void USDMaterialReader::load_tex_image(const pxr::UsdShadeShader &usd_shader,
|
|||||||
|
|
||||||
if (color_space.IsEmpty()) {
|
if (color_space.IsEmpty()) {
|
||||||
color_space = file_input.GetAttr().GetColorSpace();
|
color_space = file_input.GetAttr().GetColorSpace();
|
||||||
|
/* TODO(makowalski): if the input is from a connected source
|
||||||
|
* and fails to return a color space, should we also check the
|
||||||
|
* color space on the current shader's file input? */
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
|
if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
|
||||||
STRNCPY(image->colorspace_settings.name, "Raw");
|
STRNCPY(image->colorspace_settings.name, "Raw");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NodeTexImage *storage = static_cast<NodeTexImage *>(tex_image->storage);
|
||||||
|
storage->extension = get_image_extension(usd_shader, storage->extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDMaterialReader::convert_usd_primvar_reader_float2(
|
void USDMaterialReader::convert_usd_primvar_reader_float2(
|
||||||
|
@@ -51,7 +51,7 @@ struct NodePlacementContext {
|
|||||||
*
|
*
|
||||||
* - #UsdPreviewSurface -> Principled BSDF
|
* - #UsdPreviewSurface -> Principled BSDF
|
||||||
* - #UsdUVTexture -> Texture Image + Normal Map
|
* - #UsdUVTexture -> Texture Image + Normal Map
|
||||||
* - UsdPrimvarReader_float2 -> UV Map
|
* - #UsdPrimvarReader_float2 -> UV Map
|
||||||
*
|
*
|
||||||
* Limitations: arbitrary primvar readers or UsdTransform2d not yet
|
* Limitations: arbitrary primvar readers or UsdTransform2d not yet
|
||||||
* supported. For #UsdUVTexture, only the file, st and #sourceColorSpace
|
* supported. For #UsdUVTexture, only the file, st and #sourceColorSpace
|
||||||
@@ -60,12 +60,10 @@ struct NodePlacementContext {
|
|||||||
* TODO(makowalski): Investigate adding support for converting additional
|
* TODO(makowalski): Investigate adding support for converting additional
|
||||||
* shaders and inputs. Supporting certain types of inputs, such as texture
|
* shaders and inputs. Supporting certain types of inputs, such as texture
|
||||||
* scale and bias, will probably require creating Blender Group nodes with
|
* scale and bias, will probably require creating Blender Group nodes with
|
||||||
* the corresponding inputs.
|
* the corresponding inputs. */
|
||||||
*/
|
|
||||||
class USDMaterialReader {
|
class USDMaterialReader {
|
||||||
protected:
|
protected:
|
||||||
USDImportParams params_;
|
USDImportParams params_;
|
||||||
|
|
||||||
Main *bmain_;
|
Main *bmain_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@@ -66,41 +66,61 @@ static void assign_materials(Main *bmain,
|
|||||||
Object *ob,
|
Object *ob,
|
||||||
const std::map<pxr::SdfPath, int> &mat_index_map,
|
const std::map<pxr::SdfPath, int> &mat_index_map,
|
||||||
const USDImportParams ¶ms,
|
const USDImportParams ¶ms,
|
||||||
pxr::UsdStageRefPtr stage)
|
pxr::UsdStageRefPtr stage,
|
||||||
|
std::map<std::string, std::string> &usd_path_to_mat_name)
|
||||||
{
|
{
|
||||||
if (!(stage && bmain && ob)) {
|
if (!(stage && bmain && ob)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool can_assign = true;
|
|
||||||
std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin();
|
std::map<pxr::SdfPath, int>::const_iterator it = mat_index_map.begin();
|
||||||
|
|
||||||
int matcount = 0;
|
for (; it != mat_index_map.end(); ++it) {
|
||||||
for (; it != mat_index_map.end(); ++it, matcount++) {
|
|
||||||
if (!BKE_object_material_slot_add(bmain, ob)) {
|
if (!BKE_object_material_slot_add(bmain, ob)) {
|
||||||
can_assign = false;
|
std::cout << "WARNING: couldn't create slot for material " << it->first << " on object "
|
||||||
break;
|
<< ob->id.name << std::endl;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!can_assign) {
|
/* TODO(makowalski): use global map? */
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TODO(kevin): use global map? */
|
|
||||||
std::map<std::string, Material *> mat_map;
|
std::map<std::string, Material *> mat_map;
|
||||||
build_mat_map(bmain, &mat_map);
|
build_mat_map(bmain, &mat_map);
|
||||||
|
|
||||||
blender::io::usd::USDMaterialReader mat_reader(params, bmain);
|
blender::io::usd::USDMaterialReader mat_reader(params, bmain);
|
||||||
|
|
||||||
for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) {
|
for (it = mat_index_map.begin(); it != mat_index_map.end(); ++it) {
|
||||||
std::string mat_name = it->first.GetName();
|
|
||||||
|
|
||||||
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
|
|
||||||
|
|
||||||
Material *assigned_mat = nullptr;
|
Material *assigned_mat = nullptr;
|
||||||
|
|
||||||
if (mat_iter == mat_map.end()) {
|
if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MODIFY) {
|
||||||
|
/* Check if we've already created the Blender material with a modified name. */
|
||||||
|
std::map<std::string, std::string>::const_iterator path_to_name_iter =
|
||||||
|
usd_path_to_mat_name.find(it->first.GetAsString());
|
||||||
|
|
||||||
|
if (path_to_name_iter != usd_path_to_mat_name.end()) {
|
||||||
|
std::string mat_name = path_to_name_iter->second;
|
||||||
|
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
|
||||||
|
if (mat_iter != mat_map.end()) {
|
||||||
|
assigned_mat = mat_iter->second;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout
|
||||||
|
<< "WARNING: Couldn't find previously assigned Blender material for USD material "
|
||||||
|
<< it->first << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::string mat_name = it->first.GetName();
|
||||||
|
std::map<std::string, Material *>::iterator mat_iter = mat_map.find(mat_name);
|
||||||
|
|
||||||
|
if (mat_iter != mat_map.end()) {
|
||||||
|
assigned_mat = mat_iter->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!assigned_mat) {
|
||||||
/* Blender material doesn't exist, so create it now. */
|
/* Blender material doesn't exist, so create it now. */
|
||||||
|
|
||||||
/* Look up the USD material. */
|
/* Look up the USD material. */
|
||||||
@@ -122,11 +142,14 @@ static void assign_materials(Main *bmain,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string mat_name = pxr::TfMakeValidIdentifier(assigned_mat->id.name + 2);
|
||||||
mat_map[mat_name] = assigned_mat;
|
mat_map[mat_name] = assigned_mat;
|
||||||
}
|
|
||||||
else {
|
if (params.mtl_name_collision_mode == USD_MTL_NAME_COLLISION_MODIFY) {
|
||||||
/* We found an existing Blender material. */
|
/* Record the name of the Blender material we created for the USD material
|
||||||
assigned_mat = mat_iter->second;
|
* with the given path. */
|
||||||
|
usd_path_to_mat_name[it->first.GetAsString()] = mat_name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (assigned_mat) {
|
if (assigned_mat) {
|
||||||
@@ -134,7 +157,7 @@ static void assign_materials(Main *bmain,
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
/* This shouldn't happen. */
|
/* This shouldn't happen. */
|
||||||
std::cout << "WARNING: Couldn't assign material " << mat_name << std::endl;
|
std::cout << "WARNING: Couldn't assign material " << it->first << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,10 +480,10 @@ void USDMeshReader::read_colors(Mesh *mesh, const double motionSampleTime)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void *cd_ptr = add_customdata_cb(mesh, "displayColors", CD_PROP_BYTE_COLOR);
|
void *cd_ptr = add_customdata_cb(mesh, "displayColor", CD_PROP_BYTE_COLOR);
|
||||||
|
|
||||||
if (!cd_ptr) {
|
if (!cd_ptr) {
|
||||||
std::cerr << "WARNING: Couldn't add displayColors custom data.\n";
|
std::cerr << "WARNING: Couldn't add displayColor custom data.\n";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -715,6 +738,11 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
|
|||||||
|
|
||||||
pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial();
|
pxr::UsdShadeMaterial subset_mtl = subset_api.ComputeBoundMaterial();
|
||||||
|
|
||||||
|
if (!subset_mtl) {
|
||||||
|
/* Check for a preview material as fallback. */
|
||||||
|
subset_mtl = subset_api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
|
||||||
|
}
|
||||||
|
|
||||||
if (!subset_mtl) {
|
if (!subset_mtl) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -745,8 +773,14 @@ void USDMeshReader::assign_facesets_to_mpoly(double motionSampleTime,
|
|||||||
if (r_mat_map->empty()) {
|
if (r_mat_map->empty()) {
|
||||||
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_);
|
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(prim_);
|
||||||
|
|
||||||
if (pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial()) {
|
pxr::UsdShadeMaterial mtl = api.ComputeBoundMaterial();
|
||||||
|
|
||||||
|
if (!mtl) {
|
||||||
|
/* Check for a preview material as fallback. */
|
||||||
|
mtl = api.ComputeBoundMaterial(pxr::UsdShadeTokens->preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtl) {
|
||||||
pxr::SdfPath mtl_path = mtl.GetPath();
|
pxr::SdfPath mtl_path = mtl.GetPath();
|
||||||
|
|
||||||
if (!mtl_path.IsEmpty()) {
|
if (!mtl_path.IsEmpty()) {
|
||||||
@@ -764,7 +798,12 @@ void USDMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const double mot
|
|||||||
|
|
||||||
std::map<pxr::SdfPath, int> mat_map;
|
std::map<pxr::SdfPath, int> mat_map;
|
||||||
assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map);
|
assign_facesets_to_mpoly(motionSampleTime, mesh->mpoly, mesh->totpoly, &mat_map);
|
||||||
utils::assign_materials(bmain, object_, mat_map, this->import_params_, this->prim_.GetStage());
|
utils::assign_materials(bmain,
|
||||||
|
object_,
|
||||||
|
mat_map,
|
||||||
|
this->import_params_,
|
||||||
|
this->prim_.GetStage(),
|
||||||
|
this->settings_->usd_path_to_mat_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
|
Mesh *USDMeshReader::read_mesh(Mesh *existing_mesh,
|
||||||
|
@@ -4,10 +4,238 @@
|
|||||||
|
|
||||||
#include "usd_reader_prim.h"
|
#include "usd_reader_prim.h"
|
||||||
|
|
||||||
|
#include "BKE_idprop.h"
|
||||||
#include "BLI_utildefines.h"
|
#include "BLI_utildefines.h"
|
||||||
|
#include "DNA_object_types.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <pxr/usd/usd/attribute.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename VECT>
|
||||||
|
void set_array_prop(IDProperty *idgroup,
|
||||||
|
const char *prop_name,
|
||||||
|
const pxr::UsdAttribute &attr,
|
||||||
|
const double motionSampleTime)
|
||||||
|
{
|
||||||
|
if (!idgroup || !attr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VECT vec;
|
||||||
|
if (!attr.Get<VECT>(&vec, motionSampleTime)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDPropertyTemplate val = {0};
|
||||||
|
val.array.len = static_cast<int>(vec.dimension);
|
||||||
|
|
||||||
|
if (val.array.len <= 0) {
|
||||||
|
/* Should never happen. */
|
||||||
|
std::cout << "Invalid array length for prop " << prop_name << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::is_same<float, typename VECT::ScalarType>()) {
|
||||||
|
val.array.type = IDP_FLOAT;
|
||||||
|
}
|
||||||
|
else if (std::is_same<double, typename VECT::ScalarType>()) {
|
||||||
|
val.array.type = IDP_DOUBLE;
|
||||||
|
}
|
||||||
|
else if (std::is_same<int, typename VECT::ScalarType>()) {
|
||||||
|
val.array.type = IDP_INT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Couldn't determine array type for prop " << prop_name << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDProperty *prop = IDP_New(IDP_ARRAY, &val, prop_name);
|
||||||
|
|
||||||
|
if (!prop) {
|
||||||
|
std::cout << "Couldn't create array prop " << prop_name << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
typename VECT::ScalarType *prop_data = static_cast<typename VECT::ScalarType *>(
|
||||||
|
prop->data.pointer);
|
||||||
|
|
||||||
|
for (int i = 0; i < val.array.len; ++i) {
|
||||||
|
prop_data[i] = vec[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
IDP_AddToGroup(idgroup, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
/* TfToken objects are not cheap to construct, so we do it once. */
|
||||||
|
namespace usdtokens {
|
||||||
|
static const pxr::TfToken userProperties("userProperties", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
static void set_string_prop(IDProperty *idgroup, const char *prop_name, const char *str_val)
|
||||||
|
{
|
||||||
|
if (!idgroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDPropertyTemplate val = {0};
|
||||||
|
val.string.str = str_val;
|
||||||
|
/* Note length includes null terminator. */
|
||||||
|
val.string.len = strlen(str_val) + 1;
|
||||||
|
val.string.subtype = IDP_STRING_SUB_UTF8;
|
||||||
|
|
||||||
|
IDProperty *prop = IDP_New(IDP_STRING, &val, prop_name);
|
||||||
|
|
||||||
|
IDP_AddToGroup(idgroup, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_int_prop(IDProperty *idgroup, const char *prop_name, const int ival)
|
||||||
|
{
|
||||||
|
if (!idgroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDPropertyTemplate val = {0};
|
||||||
|
val.i = ival;
|
||||||
|
IDProperty *prop = IDP_New(IDP_INT, &val, prop_name);
|
||||||
|
|
||||||
|
IDP_AddToGroup(idgroup, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_float_prop(IDProperty *idgroup, const char *prop_name, const float fval)
|
||||||
|
{
|
||||||
|
if (!idgroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDPropertyTemplate val = {0};
|
||||||
|
val.f = fval;
|
||||||
|
IDProperty *prop = IDP_New(IDP_FLOAT, &val, prop_name);
|
||||||
|
|
||||||
|
IDP_AddToGroup(idgroup, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_double_prop(IDProperty *idgroup, const char *prop_name, const double dval)
|
||||||
|
{
|
||||||
|
if (!idgroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDPropertyTemplate val = {0};
|
||||||
|
val.d = dval;
|
||||||
|
IDProperty *prop = IDP_New(IDP_DOUBLE, &val, prop_name);
|
||||||
|
|
||||||
|
IDP_AddToGroup(idgroup, prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDPrimReader::set_props(ID *id, const pxr::UsdPrim &prim, const double motionSampleTime)
|
||||||
|
{
|
||||||
|
eUSDAttrImportMode attr_import_mode = this->import_params_.attr_import_mode;
|
||||||
|
|
||||||
|
if (attr_import_mode == USD_ATTR_IMPORT_NONE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDProperty *idgroup = IDP_GetProperties(id, 1);
|
||||||
|
|
||||||
|
if (!idgroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool all_custom_attrs = (attr_import_mode == USD_ATTR_IMPORT_ALL);
|
||||||
|
|
||||||
|
pxr::UsdAttributeVector attribs = prim.GetAuthoredAttributes();
|
||||||
|
|
||||||
|
for (const pxr::UsdAttribute &attr : attribs) {
|
||||||
|
if (!attr.IsCustom()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> attr_names = attr.SplitName();
|
||||||
|
|
||||||
|
bool is_user_prop = attr_names[0] == "userProperties";
|
||||||
|
|
||||||
|
if (attr_names.size() > 2 && is_user_prop && attr_names[1] == "blenderName") {
|
||||||
|
/* Skip the deprecated userProperties:blenderName namespace attribs. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!all_custom_attrs && !is_user_prop) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When importing user properties, strip the namespace. */
|
||||||
|
pxr::TfToken attr_name = (attr_import_mode == USD_ATTR_IMPORT_USER) ? attr.GetBaseName() :
|
||||||
|
attr.GetName();
|
||||||
|
|
||||||
|
pxr::SdfValueTypeName type_name = attr.GetTypeName();
|
||||||
|
|
||||||
|
if (type_name == pxr::SdfValueTypeNames->Int) {
|
||||||
|
int ival = 0;
|
||||||
|
if (attr.Get<int>(&ival, motionSampleTime)) {
|
||||||
|
set_int_prop(idgroup, attr_name.GetString().c_str(), ival);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Float) {
|
||||||
|
float fval = 0.0f;
|
||||||
|
if (attr.Get<float>(&fval, motionSampleTime)) {
|
||||||
|
set_float_prop(idgroup, attr_name.GetString().c_str(), fval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Double) {
|
||||||
|
double dval = 0.0;
|
||||||
|
if (attr.Get<double>(&dval, motionSampleTime)) {
|
||||||
|
set_double_prop(idgroup, attr_name.GetString().c_str(), dval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->String) {
|
||||||
|
std::string sval;
|
||||||
|
if (attr.Get<std::string>(&sval, motionSampleTime)) {
|
||||||
|
set_string_prop(idgroup, attr_name.GetString().c_str(), sval.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Token) {
|
||||||
|
pxr::TfToken tval;
|
||||||
|
if (attr.Get<pxr::TfToken>(&tval, motionSampleTime)) {
|
||||||
|
set_string_prop(idgroup, attr_name.GetString().c_str(), tval.GetString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Float2) {
|
||||||
|
set_array_prop<pxr::GfVec2f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Float3) {
|
||||||
|
set_array_prop<pxr::GfVec3f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Float4) {
|
||||||
|
set_array_prop<pxr::GfVec4f>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Double2) {
|
||||||
|
set_array_prop<pxr::GfVec2d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Double3) {
|
||||||
|
set_array_prop<pxr::GfVec3d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Double4) {
|
||||||
|
set_array_prop<pxr::GfVec4d>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Int2) {
|
||||||
|
set_array_prop<pxr::GfVec2i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Int3) {
|
||||||
|
set_array_prop<pxr::GfVec3i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
else if (type_name == pxr::SdfValueTypeNames->Int4) {
|
||||||
|
set_array_prop<pxr::GfVec4i>(idgroup, attr_name.GetString().c_str(), attr, motionSampleTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
|
USDPrimReader::USDPrimReader(const pxr::UsdPrim &prim,
|
||||||
const USDImportParams &import_params,
|
const USDImportParams &import_params,
|
||||||
const ImportSettings &settings)
|
const ImportSettings &settings)
|
||||||
@@ -29,6 +257,17 @@ const pxr::UsdPrim &USDPrimReader::prim() const
|
|||||||
return prim_;
|
return prim_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDPrimReader::read_object_data(Main * /* bmain */, const double motionSampleTime)
|
||||||
|
{
|
||||||
|
if (!prim_ || !object_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID *id = object_->data ? static_cast<ID *>(object_->data) : &object_->id;
|
||||||
|
|
||||||
|
set_props(id, prim_, motionSampleTime);
|
||||||
|
}
|
||||||
|
|
||||||
Object *USDPrimReader::object() const
|
Object *USDPrimReader::object() const
|
||||||
{
|
{
|
||||||
return object_;
|
return object_;
|
||||||
|
@@ -7,6 +7,10 @@
|
|||||||
|
|
||||||
#include <pxr/usd/usd/prim.h>
|
#include <pxr/usd/usd/prim.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct CacheFile;
|
||||||
struct Main;
|
struct Main;
|
||||||
struct Object;
|
struct Object;
|
||||||
|
|
||||||
@@ -31,7 +35,13 @@ struct ImportSettings {
|
|||||||
|
|
||||||
bool validate_meshes;
|
bool validate_meshes;
|
||||||
|
|
||||||
CacheFile *cache_file;
|
/* Map a USD matrial prim path to a Blender material name.
|
||||||
|
* This map might be updated by readers during stage traversal.
|
||||||
|
* TODO(makowalski): Is the ImportSettings struct the best place
|
||||||
|
* to store this map? Maybe we should define an ImportContext
|
||||||
|
* struct that stores USDImportParams, ImportSettings and
|
||||||
|
* mutable values such as this. */
|
||||||
|
mutable std::map<std::string, std::string> usd_path_to_mat_name;
|
||||||
|
|
||||||
ImportSettings()
|
ImportSettings()
|
||||||
: do_convert_mat(false),
|
: do_convert_mat(false),
|
||||||
@@ -43,8 +53,7 @@ struct ImportSettings {
|
|||||||
sequence_len(1),
|
sequence_len(1),
|
||||||
sequence_offset(0),
|
sequence_offset(0),
|
||||||
read_flag(0),
|
read_flag(0),
|
||||||
validate_meshes(false),
|
validate_meshes(false)
|
||||||
cache_file(NULL)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -74,7 +83,15 @@ class USDPrimReader {
|
|||||||
virtual bool valid() const;
|
virtual bool valid() const;
|
||||||
|
|
||||||
virtual void create_object(Main *bmain, double motionSampleTime) = 0;
|
virtual void create_object(Main *bmain, double motionSampleTime) = 0;
|
||||||
virtual void read_object_data(Main * /* bmain */, double /* motionSampleTime */){};
|
virtual void read_object_data(Main *bmain, double motionSampleTime);
|
||||||
|
|
||||||
|
virtual bool needs_cachefile()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual void apply_cache_file(CacheFile * /* cache_file */)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
Object *object() const;
|
Object *object() const;
|
||||||
void object(Object *ob);
|
void object(Object *ob);
|
||||||
@@ -109,6 +126,9 @@ class USDPrimReader {
|
|||||||
{
|
{
|
||||||
return prim_path_;
|
return prim_path_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void set_props(ID *id, const pxr::UsdPrim &prim, double motionSampleTime);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
#include "usd_reader_stage.h"
|
#include "usd_reader_stage.h"
|
||||||
#include "usd_reader_camera.h"
|
#include "usd_reader_camera.h"
|
||||||
#include "usd_reader_curve.h"
|
#include "usd_reader_curve.h"
|
||||||
|
#include "usd_reader_instance.h"
|
||||||
#include "usd_reader_light.h"
|
#include "usd_reader_light.h"
|
||||||
#include "usd_reader_mesh.h"
|
#include "usd_reader_mesh.h"
|
||||||
#include "usd_reader_nurbs.h"
|
#include "usd_reader_nurbs.h"
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
#include <pxr/usd/usdGeom/scope.h>
|
#include <pxr/usd/usdGeom/scope.h>
|
||||||
#include <pxr/usd/usdGeom/xform.h>
|
#include <pxr/usd/usdGeom/xform.h>
|
||||||
|
|
||||||
|
#include <pxr/usd/usdLux/domeLight.h>
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
# include <pxr/usd/usdLux/boundableLightBase.h>
|
# include <pxr/usd/usdLux/boundableLightBase.h>
|
||||||
# include <pxr/usd/usdLux/nonboundableLightBase.h>
|
# include <pxr/usd/usdLux/nonboundableLightBase.h>
|
||||||
@@ -39,6 +41,7 @@ USDStageReader::USDStageReader(pxr::UsdStageRefPtr stage,
|
|||||||
|
|
||||||
USDStageReader::~USDStageReader()
|
USDStageReader::~USDStageReader()
|
||||||
{
|
{
|
||||||
|
clear_proto_readers();
|
||||||
clear_readers();
|
clear_readers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,8 +50,12 @@ bool USDStageReader::valid() const
|
|||||||
return stage_;
|
return stage_;
|
||||||
}
|
}
|
||||||
|
|
||||||
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim)
|
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache)
|
||||||
{
|
{
|
||||||
|
if (params_.use_instancing && prim.IsInstance()) {
|
||||||
|
return new USDInstanceReader(prim, params_, settings_);
|
||||||
|
}
|
||||||
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
|
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
|
||||||
return new USDCameraReader(prim, params_, settings_);
|
return new USDCameraReader(prim, params_, settings_);
|
||||||
}
|
}
|
||||||
@@ -61,6 +68,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
|
|||||||
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
|
if (params_.import_meshes && prim.IsA<pxr::UsdGeomMesh>()) {
|
||||||
return new USDMeshReader(prim, params_, settings_);
|
return new USDMeshReader(prim, params_, settings_);
|
||||||
}
|
}
|
||||||
|
if (params_.import_lights && prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||||
|
return new USDXformReader(prim, params_, settings_);
|
||||||
|
}
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
if (params_.import_lights && (prim.IsA<pxr::UsdLuxBoundableLightBase>() ||
|
if (params_.import_lights && (prim.IsA<pxr::UsdLuxBoundableLightBase>() ||
|
||||||
prim.IsA<pxr::UsdLuxNonboundableLightBase>())) {
|
prim.IsA<pxr::UsdLuxNonboundableLightBase>())) {
|
||||||
@@ -75,11 +85,16 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
|
|||||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||||
return new USDXformReader(prim, params_, settings_);
|
return new USDXformReader(prim, params_, settings_);
|
||||||
}
|
}
|
||||||
|
if (prim.GetPrimTypeInfo() == pxr::UsdPrimTypeInfo::GetEmptyPrimType()) {
|
||||||
|
/* Handle the less common case where the prim has no type specified. */
|
||||||
|
return new USDXformReader(prim, params_, settings_);
|
||||||
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
|
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache)
|
||||||
{
|
{
|
||||||
if (prim.IsA<pxr::UsdGeomCamera>()) {
|
if (prim.IsA<pxr::UsdGeomCamera>()) {
|
||||||
return new USDCameraReader(prim, params_, settings_);
|
return new USDCameraReader(prim, params_, settings_);
|
||||||
@@ -93,6 +108,9 @@ USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
|
|||||||
if (prim.IsA<pxr::UsdGeomMesh>()) {
|
if (prim.IsA<pxr::UsdGeomMesh>()) {
|
||||||
return new USDMeshReader(prim, params_, settings_);
|
return new USDMeshReader(prim, params_, settings_);
|
||||||
}
|
}
|
||||||
|
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||||
|
return new USDXformReader(prim, params_, settings_);
|
||||||
|
}
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
|
if (prim.IsA<pxr::UsdLuxBoundableLightBase>() || prim.IsA<pxr::UsdLuxNonboundableLightBase>()) {
|
||||||
#else
|
#else
|
||||||
@@ -167,7 +185,8 @@ bool USDStageReader::include_by_purpose(const pxr::UsdGeomImageable &imageable)
|
|||||||
/* Determine if the given reader can use the parent of the encapsulated USD prim
|
/* Determine if the given reader can use the parent of the encapsulated USD prim
|
||||||
* to compute the Blender object's transform. If so, the reader is appropriately
|
* to compute the Blender object's transform. If so, the reader is appropriately
|
||||||
* flagged and the function returns true. Otherwise, the function returns false. */
|
* flagged and the function returns true. Otherwise, the function returns false. */
|
||||||
static bool merge_with_parent(USDPrimReader *reader)
|
|
||||||
|
bool USDStageReader::merge_with_parent(USDPrimReader *reader) const
|
||||||
{
|
{
|
||||||
USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
|
USDXformReader *xform_reader = dynamic_cast<USDXformReader *>(reader);
|
||||||
|
|
||||||
@@ -185,9 +204,16 @@ static bool merge_with_parent(USDPrimReader *reader)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Don't merge Xform and Scope prims. */
|
/* Don't merge if instancing is enabled and the parent is an instance. */
|
||||||
|
if (params_.use_instancing && xform_reader->prim().GetParent().IsInstance()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Don't merge Xform, Scope or undefined prims. */
|
||||||
if (xform_reader->prim().IsA<pxr::UsdGeomXform>() ||
|
if (xform_reader->prim().IsA<pxr::UsdGeomXform>() ||
|
||||||
xform_reader->prim().IsA<pxr::UsdGeomScope>()) {
|
xform_reader->prim().IsA<pxr::UsdGeomScope>() ||
|
||||||
|
xform_reader->prim().GetPrimTypeInfo()
|
||||||
|
== pxr::UsdPrimTypeInfo::GetEmptyPrimType()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +228,10 @@ static bool merge_with_parent(USDPrimReader *reader)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &prim)
|
USDPrimReader *USDStageReader::collect_readers(Main *bmain,
|
||||||
|
const pxr::UsdPrim &prim,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache,
|
||||||
|
std::vector<USDPrimReader *> &r_readers)
|
||||||
{
|
{
|
||||||
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
if (prim.IsA<pxr::UsdGeomImageable>()) {
|
||||||
pxr::UsdGeomImageable imageable(prim);
|
pxr::UsdGeomImageable imageable(prim);
|
||||||
@@ -216,9 +245,13 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prim.IsA<pxr::UsdLuxDomeLight>()) {
|
||||||
|
dome_lights_.push_back(pxr::UsdLuxDomeLight(prim));
|
||||||
|
}
|
||||||
|
|
||||||
pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate;
|
pxr::Usd_PrimFlagsPredicate filter_predicate = pxr::UsdPrimDefaultPredicate;
|
||||||
|
|
||||||
if (params_.import_instance_proxies) {
|
if (!params_.use_instancing && params_.import_instance_proxies) {
|
||||||
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
|
filter_predicate = pxr::UsdTraverseInstanceProxies(filter_predicate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,18 +260,20 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
|
|||||||
std::vector<USDPrimReader *> child_readers;
|
std::vector<USDPrimReader *> child_readers;
|
||||||
|
|
||||||
for (const auto &childPrim : children) {
|
for (const auto &childPrim : children) {
|
||||||
if (USDPrimReader *child_reader = collect_readers(bmain, childPrim)) {
|
if (USDPrimReader *child_reader = collect_readers(bmain, childPrim, xf_cache, r_readers)) {
|
||||||
child_readers.push_back(child_reader);
|
child_readers.push_back(child_reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prim.IsPseudoRoot()) {
|
/* We prune the current prim if it's a Scope
|
||||||
|
* and we didn't convert any of its children. */
|
||||||
|
if (child_readers.empty() && prim.IsA<pxr::UsdGeomScope>() &&
|
||||||
|
!(params_.use_instancing && prim.IsInstance())) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check if we can merge an Xform with its child prim. */
|
/* Check if we can merge an Xform with its child prim. */
|
||||||
if (child_readers.size() == 1) {
|
if (child_readers.size() == 1) {
|
||||||
|
|
||||||
USDPrimReader *child_reader = child_readers.front();
|
USDPrimReader *child_reader = child_readers.front();
|
||||||
|
|
||||||
if (merge_with_parent(child_reader)) {
|
if (merge_with_parent(child_reader)) {
|
||||||
@@ -246,7 +281,11 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
USDPrimReader *reader = create_reader_if_allowed(prim);
|
if (prim.IsPseudoRoot() || prim.IsPrototype()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
USDPrimReader *reader = create_reader_if_allowed(prim, xf_cache);
|
||||||
|
|
||||||
if (!reader) {
|
if (!reader) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -254,7 +293,7 @@ USDPrimReader *USDStageReader::collect_readers(Main *bmain, const pxr::UsdPrim &
|
|||||||
|
|
||||||
reader->create_object(bmain, 0.0);
|
reader->create_object(bmain, 0.0);
|
||||||
|
|
||||||
readers_.push_back(reader);
|
r_readers.push_back(reader);
|
||||||
reader->incref();
|
reader->incref();
|
||||||
|
|
||||||
/* Set each child reader's parent. */
|
/* Set each child reader's parent. */
|
||||||
@@ -272,6 +311,8 @@ void USDStageReader::collect_readers(Main *bmain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear_readers();
|
clear_readers();
|
||||||
|
clear_proto_readers();
|
||||||
|
dome_lights_.clear();
|
||||||
|
|
||||||
/* Iterate through the stage. */
|
/* Iterate through the stage. */
|
||||||
pxr::UsdPrim root = stage_->GetPseudoRoot();
|
pxr::UsdPrim root = stage_->GetPseudoRoot();
|
||||||
@@ -290,7 +331,20 @@ void USDStageReader::collect_readers(Main *bmain)
|
|||||||
}
|
}
|
||||||
|
|
||||||
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
|
stage_->SetInterpolationType(pxr::UsdInterpolationType::UsdInterpolationTypeHeld);
|
||||||
collect_readers(bmain, root);
|
|
||||||
|
pxr::UsdGeomXformCache xf_cache;
|
||||||
|
collect_readers(bmain, root, &xf_cache, readers_);
|
||||||
|
|
||||||
|
if (params_.use_instancing) {
|
||||||
|
// Collect the scenegraph instance prototypes.
|
||||||
|
std::vector<pxr::UsdPrim> protos = stage_->GetPrototypes();
|
||||||
|
|
||||||
|
for (const pxr::UsdPrim &proto_prim : protos) {
|
||||||
|
std::vector<USDPrimReader *> proto_readers;
|
||||||
|
collect_readers(bmain, proto_prim, &xf_cache, proto_readers);
|
||||||
|
proto_readers_.insert(std::make_pair(proto_prim.GetPath(), proto_readers));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDStageReader::clear_readers()
|
void USDStageReader::clear_readers()
|
||||||
@@ -310,4 +364,27 @@ void USDStageReader::clear_readers()
|
|||||||
readers_.clear();
|
readers_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDStageReader::clear_proto_readers()
|
||||||
|
{
|
||||||
|
for (auto &pair : proto_readers_) {
|
||||||
|
|
||||||
|
for (USDPrimReader *reader : pair.second) {
|
||||||
|
|
||||||
|
if (!reader) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reader->decref();
|
||||||
|
|
||||||
|
if (reader->refcount() == 0) {
|
||||||
|
delete reader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pair.second.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
proto_readers_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // Namespace blender::io::usd
|
} // Namespace blender::io::usd
|
||||||
|
@@ -9,6 +9,8 @@ struct Main;
|
|||||||
|
|
||||||
#include <pxr/usd/usd/stage.h>
|
#include <pxr/usd/usd/stage.h>
|
||||||
#include <pxr/usd/usdGeom/imageable.h>
|
#include <pxr/usd/usdGeom/imageable.h>
|
||||||
|
#include <pxr/usd/usdGeom/xformCache.h>
|
||||||
|
#include <pxr/usd/usdLux/domeLight.h>
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -27,6 +29,13 @@ class USDStageReader {
|
|||||||
|
|
||||||
std::vector<USDPrimReader *> readers_;
|
std::vector<USDPrimReader *> readers_;
|
||||||
|
|
||||||
|
// Readers for scenegraph instance prototypes.
|
||||||
|
ProtoReaderMap proto_readers_;
|
||||||
|
|
||||||
|
/* USD dome lights are converted to a world material,
|
||||||
|
* rather than light objects, so are handled differently */
|
||||||
|
std::vector<pxr::UsdLuxDomeLight> dome_lights_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
USDStageReader(pxr::UsdStageRefPtr stage,
|
USDStageReader(pxr::UsdStageRefPtr stage,
|
||||||
const USDImportParams ¶ms,
|
const USDImportParams ¶ms,
|
||||||
@@ -34,9 +43,10 @@ class USDStageReader {
|
|||||||
|
|
||||||
~USDStageReader();
|
~USDStageReader();
|
||||||
|
|
||||||
USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim);
|
USDPrimReader *create_reader_if_allowed(const pxr::UsdPrim &prim,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache);
|
||||||
|
|
||||||
USDPrimReader *create_reader(const pxr::UsdPrim &prim);
|
USDPrimReader *create_reader(const pxr::UsdPrim &prim, pxr::UsdGeomXformCache *xf_cache);
|
||||||
|
|
||||||
void collect_readers(struct Main *bmain);
|
void collect_readers(struct Main *bmain);
|
||||||
|
|
||||||
@@ -58,13 +68,28 @@ class USDStageReader {
|
|||||||
|
|
||||||
void clear_readers();
|
void clear_readers();
|
||||||
|
|
||||||
|
void clear_proto_readers();
|
||||||
|
|
||||||
|
const ProtoReaderMap &proto_readers() const
|
||||||
|
{
|
||||||
|
return proto_readers_;
|
||||||
|
};
|
||||||
|
|
||||||
const std::vector<USDPrimReader *> &readers() const
|
const std::vector<USDPrimReader *> &readers() const
|
||||||
{
|
{
|
||||||
return readers_;
|
return readers_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const std::vector<pxr::UsdLuxDomeLight> &dome_lights() const
|
||||||
|
{
|
||||||
|
return dome_lights_;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
USDPrimReader *collect_readers(Main *bmain, const pxr::UsdPrim &prim);
|
USDPrimReader *collect_readers(Main *bmain,
|
||||||
|
const pxr::UsdPrim &prim,
|
||||||
|
pxr::UsdGeomXformCache *xf_cache,
|
||||||
|
std::vector<USDPrimReader *> &r_readers);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given prim should be included in the
|
* Returns true if the given prim should be included in the
|
||||||
@@ -83,6 +108,8 @@ class USDStageReader {
|
|||||||
* toggled off.
|
* toggled off.
|
||||||
*/
|
*/
|
||||||
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
|
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
|
||||||
|
|
||||||
|
bool merge_with_parent(USDPrimReader *reader) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}; // namespace blender::io::usd
|
}; // namespace blender::io::usd
|
||||||
|
@@ -34,30 +34,43 @@ void USDXformReader::create_object(Main *bmain, const double /* motionSampleTime
|
|||||||
object_->data = nullptr;
|
object_->data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDXformReader::read_object_data(Main * /* bmain */, const double motionSampleTime)
|
void USDXformReader::read_object_data(Main *bmain, const double motionSampleTime)
|
||||||
{
|
{
|
||||||
|
USDPrimReader::read_object_data(bmain, motionSampleTime);
|
||||||
|
|
||||||
|
if (use_parent_xform_ && object_ && prim_) {
|
||||||
|
USDPrimReader::set_props(&object_->id, prim_.GetParent(), motionSampleTime);
|
||||||
|
}
|
||||||
|
|
||||||
bool is_constant;
|
bool is_constant;
|
||||||
float transform_from_usd[4][4];
|
float transform_from_usd[4][4];
|
||||||
|
|
||||||
read_matrix(transform_from_usd, motionSampleTime, import_params_.scale, &is_constant);
|
read_matrix(transform_from_usd, motionSampleTime, settings_->scale, &is_constant);
|
||||||
|
|
||||||
if (!is_constant) {
|
needs_cachefile_ = !is_constant;
|
||||||
bConstraint *con = BKE_constraint_add_for_object(
|
|
||||||
object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
|
|
||||||
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
|
|
||||||
|
|
||||||
std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() :
|
|
||||||
prim_path_;
|
|
||||||
|
|
||||||
BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX);
|
|
||||||
|
|
||||||
data->cache_file = settings_->cache_file;
|
|
||||||
id_us_plus(&data->cache_file->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
BKE_object_apply_mat4(object_, transform_from_usd, true, false);
|
BKE_object_apply_mat4(object_, transform_from_usd, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDXformReader::apply_cache_file(CacheFile *cache_file)
|
||||||
|
{
|
||||||
|
if (!(cache_file && needs_cachefile_ && object_)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bConstraint *con = BKE_constraint_add_for_object(
|
||||||
|
object_, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
|
||||||
|
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
|
||||||
|
|
||||||
|
std::string prim_path = use_parent_xform_ ? prim_.GetParent().GetPath().GetAsString() :
|
||||||
|
prim_path_;
|
||||||
|
|
||||||
|
BLI_strncpy(data->object_path, prim_path.c_str(), FILE_MAX);
|
||||||
|
|
||||||
|
data->cache_file = cache_file;
|
||||||
|
id_us_plus(&cache_file->id);
|
||||||
|
}
|
||||||
|
|
||||||
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
|
void USDXformReader::read_matrix(float r_mat[4][4] /* local matrix */,
|
||||||
const float time,
|
const float time,
|
||||||
const float scale,
|
const float scale,
|
||||||
|
@@ -16,20 +16,29 @@ class USDXformReader : public USDPrimReader {
|
|||||||
* transform hierarchy. */
|
* transform hierarchy. */
|
||||||
bool is_root_xform_;
|
bool is_root_xform_;
|
||||||
|
|
||||||
|
bool needs_cachefile_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
USDXformReader(const pxr::UsdPrim &prim,
|
USDXformReader(const pxr::UsdPrim &prim,
|
||||||
const USDImportParams &import_params,
|
const USDImportParams &import_params,
|
||||||
const ImportSettings &settings)
|
const ImportSettings &settings)
|
||||||
: USDPrimReader(prim, import_params, settings),
|
: USDPrimReader(prim, import_params, settings),
|
||||||
use_parent_xform_(false),
|
use_parent_xform_(false),
|
||||||
is_root_xform_(is_root_xform_prim())
|
is_root_xform_(is_root_xform_prim()),
|
||||||
|
needs_cachefile_(false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void create_object(Main *bmain, double motionSampleTime) override;
|
void create_object(Main *bmain, double motionSampleTime) override;
|
||||||
void read_object_data(Main *bmain, double motionSampleTime) override;
|
void read_object_data(Main *bmain, double motionSampleTime) override;
|
||||||
|
|
||||||
void read_matrix(float r_mat[4][4], float time, float scale, bool *r_is_constant);
|
bool needs_cachefile() override
|
||||||
|
{
|
||||||
|
return needs_cachefile_;
|
||||||
|
}
|
||||||
|
void apply_cache_file(CacheFile *cache_file) override;
|
||||||
|
|
||||||
|
void read_matrix(float r_mat[4][4], const float time, const float scale, bool *r_is_constant);
|
||||||
|
|
||||||
bool use_parent_xform() const
|
bool use_parent_xform() const
|
||||||
{
|
{
|
||||||
|
889
source/blender/io/usd/intern/usd_umm.cc
Normal file
889
source/blender/io/usd/intern/usd_umm.cc
Normal file
@@ -0,0 +1,889 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef WITH_PYTHON
|
||||||
|
|
||||||
|
# include "usd_umm.h"
|
||||||
|
# include "usd.h"
|
||||||
|
# include "usd_exporter_context.h"
|
||||||
|
# include "usd_writer_material.h"
|
||||||
|
|
||||||
|
# include "DNA_material_types.h"
|
||||||
|
|
||||||
|
# include <pxr/usd/ar/resolver.h>
|
||||||
|
|
||||||
|
# include <iostream>
|
||||||
|
# include <vector>
|
||||||
|
|
||||||
|
# include "WM_api.h"
|
||||||
|
|
||||||
|
// The following is additional example code for invoking Python and
|
||||||
|
// a Blender Python operator from C++:
|
||||||
|
|
||||||
|
//#include "BPY_extern_python.h"
|
||||||
|
//#include "BPY_extern_run.h"
|
||||||
|
|
||||||
|
// const char *foo[] = { "bpy", 0 };
|
||||||
|
// BPY_run_string_eval(C, nullptr, "print('hi!!')");
|
||||||
|
// BPY_run_string_eval(C, foo, "bpy.ops.universalmaterialmap.instance_to_data_converter()");
|
||||||
|
// BPY_run_string_eval(C, nullptr, "print('test')");
|
||||||
|
|
||||||
|
namespace usdtokens {
|
||||||
|
|
||||||
|
// Render context names.
|
||||||
|
static const pxr::TfToken mdl("mdl", pxr::TfToken::Immortal);
|
||||||
|
|
||||||
|
} // end namespace usdtokens
|
||||||
|
|
||||||
|
static PyObject *g_umm_module = nullptr;
|
||||||
|
|
||||||
|
static const char *k_umm_module_name = "omni.universalmaterialmap.blender.material";
|
||||||
|
static const char *k_omni_pbr_mdl_name = "OmniPBR.mdl";
|
||||||
|
static const char *k_omni_pbr_name = "OmniPBR";
|
||||||
|
static const char *k_udim_tag = "<UDIM>";
|
||||||
|
|
||||||
|
using namespace blender::io::usd;
|
||||||
|
|
||||||
|
static std::string anchor_relative_path(pxr::UsdStagePtr stage, const std::string &asset_path)
|
||||||
|
{
|
||||||
|
if (asset_path.empty() || asset_path.front() != '.') {
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(makowalski): avoid recomputing the USD path, if possible.
|
||||||
|
pxr::SdfLayerHandle layer = stage->GetRootLayer();
|
||||||
|
|
||||||
|
std::string stage_path = layer->GetRealPath();
|
||||||
|
|
||||||
|
if (stage_path.empty()) {
|
||||||
|
return asset_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PXR_VERSION >= 2111
|
||||||
|
return pxr::ArGetResolver().CreateIdentifier(asset_path, pxr::ArResolvedPath(stage_path));
|
||||||
|
#else
|
||||||
|
return pxr::ArGetResolver().AnchorRelativePath(stage_path, asset_path);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_obj(PyObject *obj)
|
||||||
|
{
|
||||||
|
if (!obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *str = PyObject_Str(obj);
|
||||||
|
if (str && PyUnicode_Check(str)) {
|
||||||
|
std::cout << PyUnicode_AsUTF8(str) << std::endl;
|
||||||
|
Py_DECREF(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum eUMMNotification {
|
||||||
|
UMM_NOTIFICATION_NONE = 0,
|
||||||
|
UMM_NOTIFICATION_SUCCESS,
|
||||||
|
UMM_NOTIFICATION_FAILURE
|
||||||
|
};
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
static eUMMNotification report_notification(PyObject *dict)
|
||||||
|
{
|
||||||
|
if (!dict) {
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyDict_Check(dict)) {
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *notification_item = PyDict_GetItemString(dict, "umm_notification");
|
||||||
|
|
||||||
|
if (!notification_item) {
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *message_item = PyDict_GetItemString(dict, "message");
|
||||||
|
|
||||||
|
if (!message_item) {
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyUnicode_Check(notification_item)) {
|
||||||
|
std::cerr << "WARNING: 'umm_notification' value is not a string" << std::endl;
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *notification_str = PyUnicode_AsUTF8(notification_item);
|
||||||
|
|
||||||
|
if (!notification_str) {
|
||||||
|
std::cerr << "WARNING: couldn't get 'umm_notification' string value" << std::endl;
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(notification_str, "success") == 0) {
|
||||||
|
/* We don't report success, do nothing. */
|
||||||
|
return UMM_NOTIFICATION_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyUnicode_Check(message_item)) {
|
||||||
|
std::cerr << "WARNING: 'message' value is not a string" << std::endl;
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *message_str = PyUnicode_AsUTF8(message_item);
|
||||||
|
|
||||||
|
if (!message_str) {
|
||||||
|
std::cerr << "WARNING: couldn't get 'message' string value" << std::endl;
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(message_str) == 0) {
|
||||||
|
std::cerr << "WARNING: empty 'message' string value" << std::endl;
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(notification_str, "incomplete_process") == 0) {
|
||||||
|
WM_reportf(RPT_WARNING, message_str);
|
||||||
|
return UMM_NOTIFICATION_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(notification_str, "unexpected_error") == 0) {
|
||||||
|
WM_reportf(RPT_ERROR, message_str);
|
||||||
|
return UMM_NOTIFICATION_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "WARNING: unknown notification type: " << notification_str << std::endl;
|
||||||
|
|
||||||
|
return UMM_NOTIFICATION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_none_value(PyObject *tup)
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
|
||||||
|
return second == Py_None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sets the source asset and source asset subidenifier properties on the given shader
|
||||||
|
* with values parsed from the given target_class string. */
|
||||||
|
static bool set_source_asset(pxr::UsdShadeShader &usd_shader, const std::string &target_class)
|
||||||
|
{
|
||||||
|
if (!usd_shader || target_class.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split the target_class string on the '|' separator.
|
||||||
|
size_t sep = target_class.find_last_of("|");
|
||||||
|
if (sep == 0 || sep == std::string::npos) {
|
||||||
|
std::cout << "Couldn't parse target_class string " << target_class << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source_asset = target_class.substr(0, sep);
|
||||||
|
usd_shader.SetSourceAsset(pxr::SdfAssetPath(source_asset), usdtokens::mdl);
|
||||||
|
|
||||||
|
std::string source_asset_subidentifier = target_class.substr(sep + 1);
|
||||||
|
|
||||||
|
if (!source_asset_subidentifier.empty()) {
|
||||||
|
usd_shader.SetSourceAssetSubIdentifier(pxr::TfToken(source_asset_subidentifier),
|
||||||
|
usdtokens::mdl);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_data_name(PyObject *tup, std::string &r_name)
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *first = PyTuple_GetItem(tup, 0);
|
||||||
|
|
||||||
|
if (first && PyUnicode_Check(first)) {
|
||||||
|
const char *name = PyUnicode_AsUTF8(first);
|
||||||
|
if (name) {
|
||||||
|
r_name = name;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_string_data(PyObject *tup, std::string &r_data)
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
|
||||||
|
if (second && PyUnicode_Check(second)) {
|
||||||
|
const char *data = PyUnicode_AsUTF8(second);
|
||||||
|
if (data) {
|
||||||
|
r_data = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_float_data(PyObject *tup, float &r_data)
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
|
||||||
|
if (second && PyFloat_Check(second)) {
|
||||||
|
r_data = static_cast<float>(PyFloat_AsDouble(second));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_float3_data(PyObject *tup, float r_data[3])
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
|
||||||
|
if (second && PyTuple_Check(second) && PyTuple_Size(second) > 2) {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
PyObject *comp = PyTuple_GetItem(second, i);
|
||||||
|
if (comp && PyFloat_Check(comp)) {
|
||||||
|
r_data[i] = static_cast<float>(PyFloat_AsDouble(comp));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_rgba_data(PyObject *tup, float r_data[4])
|
||||||
|
{
|
||||||
|
if (!(tup && PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
|
||||||
|
if (!(second && PyTuple_Check(second))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t size = PyTuple_Size(second);
|
||||||
|
|
||||||
|
if (size > 2) {
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
PyObject *comp = PyTuple_GetItem(second, i);
|
||||||
|
if (comp && PyFloat_Check(comp)) {
|
||||||
|
r_data[i] = static_cast<float>(PyFloat_AsDouble(comp));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size > 3) {
|
||||||
|
PyObject *alpha = PyTuple_GetItem(second, 3);
|
||||||
|
if (alpha && PyFloat_Check(alpha)) {
|
||||||
|
r_data[3] = static_cast<float>(PyFloat_AsDouble(alpha));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
r_data[3] = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Be sure to call PyGILState_Ensure() before calling this function. */
|
||||||
|
static bool ensure_module_loaded(bool warn = true)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!g_umm_module) {
|
||||||
|
g_umm_module = PyImport_ImportModule(k_umm_module_name);
|
||||||
|
if (!g_umm_module) {
|
||||||
|
if (warn) {
|
||||||
|
std::cout << "WARNING: couldn't load Python module " << k_umm_module_name << std::endl;
|
||||||
|
if (PyErr_Occurred()) {
|
||||||
|
PyErr_Print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PyErr_Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_umm_module != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_python()
|
||||||
|
{
|
||||||
|
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||||
|
|
||||||
|
PyObject *mod = PyImport_ImportModule("omni.universalmaterialmap.core.converter.util");
|
||||||
|
|
||||||
|
if (mod) {
|
||||||
|
const char *func_name = "get_conversion_manifest";
|
||||||
|
|
||||||
|
if (PyObject_HasAttrString(mod, func_name)) {
|
||||||
|
if (PyObject *func = PyObject_GetAttrString(mod, func_name)) {
|
||||||
|
PyObject *ret = PyObject_CallObject(func, nullptr);
|
||||||
|
Py_DECREF(func);
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
print_obj(ret);
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject *get_shader_source_data(const pxr::UsdShadeShader &usd_shader)
|
||||||
|
{
|
||||||
|
if (!usd_shader) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PyObject *> tuple_items;
|
||||||
|
|
||||||
|
std::vector<pxr::UsdShadeInput> inputs = usd_shader.GetInputs();
|
||||||
|
|
||||||
|
for (auto input : inputs) {
|
||||||
|
|
||||||
|
if (!input) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *tup = nullptr;
|
||||||
|
|
||||||
|
std::string name = input.GetBaseName().GetString();
|
||||||
|
|
||||||
|
if (name.empty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdAttribute usd_attr;
|
||||||
|
|
||||||
|
bool have_connected_source = false;
|
||||||
|
|
||||||
|
if (input.HasConnectedSource()) {
|
||||||
|
pxr::UsdShadeConnectableAPI source;
|
||||||
|
pxr::TfToken source_name;
|
||||||
|
pxr::UsdShadeAttributeType source_type;
|
||||||
|
|
||||||
|
if (input.GetConnectedSource(&source, &source_name, &source_type)) {
|
||||||
|
usd_attr = source.GetInput(source_name).GetAttr();
|
||||||
|
have_connected_source = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "ERROR: couldn't get connected source for usd shader input "
|
||||||
|
<< input.GetPrim().GetPath() << " " << input.GetFullName() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
usd_attr = input.GetAttr();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!usd_attr) {
|
||||||
|
std::cerr << "ERROR: couldn't get attribute for usd shader input " << input.GetPrim().GetPath()
|
||||||
|
<< " " << input.GetFullName() << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtValue val;
|
||||||
|
if (!usd_attr.Get(&val)) {
|
||||||
|
std::cerr << "ERROR: couldn't get value for usd shader input " << input.GetPrim().GetPath()
|
||||||
|
<< " " << input.GetFullName() << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val.IsHolding<float>()) {
|
||||||
|
double dval = val.UncheckedGet<float>();
|
||||||
|
tup = Py_BuildValue("sd", name.c_str(), dval);
|
||||||
|
}
|
||||||
|
else if (val.IsHolding<int>()) {
|
||||||
|
int ival = val.UncheckedGet<int>();
|
||||||
|
tup = Py_BuildValue("si", name.c_str(), ival);
|
||||||
|
}
|
||||||
|
else if (val.IsHolding<bool>()) {
|
||||||
|
int ival = val.UncheckedGet<bool>();
|
||||||
|
tup = Py_BuildValue("si", name.c_str(), ival);
|
||||||
|
}
|
||||||
|
else if (val.IsHolding<pxr::SdfAssetPath>()) {
|
||||||
|
pxr::SdfAssetPath asset_path = val.Get<pxr::SdfAssetPath>();
|
||||||
|
|
||||||
|
std::string resolved_path = asset_path.GetResolvedPath();
|
||||||
|
|
||||||
|
if (resolved_path.empty()) {
|
||||||
|
/* If the path wasn't resolved, it could be because it's a UDIM path,
|
||||||
|
* so try to use the asset path directly, anchoring it if it's a relative path. */
|
||||||
|
resolved_path = anchor_relative_path(usd_shader.GetPrim().GetStage(),
|
||||||
|
asset_path.GetAssetPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::TfToken color_space_tok = usd_attr.GetColorSpace();
|
||||||
|
|
||||||
|
if (color_space_tok.IsEmpty() && have_connected_source) {
|
||||||
|
/* The connected asset input has no color space specified,
|
||||||
|
* so we also check the shader's asset input for this data. */
|
||||||
|
if (pxr::UsdAttribute input_attr = input.GetAttr()) {
|
||||||
|
color_space_tok = input_attr.GetColorSpace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string color_space_str = !color_space_tok.IsEmpty() ? color_space_tok.GetString() :
|
||||||
|
"sRGB";
|
||||||
|
|
||||||
|
PyObject *tex_file_tup = Py_BuildValue("ss", resolved_path.c_str(), color_space_str.c_str());
|
||||||
|
|
||||||
|
tup = Py_BuildValue("sN", name.c_str(), tex_file_tup);
|
||||||
|
}
|
||||||
|
else if (val.IsHolding<pxr::GfVec3f>()) {
|
||||||
|
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
|
||||||
|
pxr::GfVec3d v3d(v3f);
|
||||||
|
PyObject *v3_tup = Py_BuildValue("ddd", v3d[0], v3d[1], v3d[2]);
|
||||||
|
if (v3_tup) {
|
||||||
|
tup = Py_BuildValue("sN", name.c_str(), v3_tup);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Couldn't build v3f tuple for " << usd_shader.GetPath() << " input "
|
||||||
|
<< input.GetFullName() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (val.IsHolding<pxr::GfVec2f>()) {
|
||||||
|
pxr::GfVec2f v2f = val.UncheckedGet<pxr::GfVec2f>();
|
||||||
|
/* std::cout << "Have v2f input " << v2f << " for "
|
||||||
|
<< usd_shader.GetPath() << " " << input.GetFullName() << std::endl;*/
|
||||||
|
pxr::GfVec2d v2d(v2f);
|
||||||
|
PyObject *v2_tup = Py_BuildValue("dd", v2d[0], v2d[1]);
|
||||||
|
if (v2_tup) {
|
||||||
|
tup = Py_BuildValue("sN", name.c_str(), v2_tup);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Couldn't build v2f tuple for " << usd_shader.GetPath() << " input "
|
||||||
|
<< input.GetFullName() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tup) {
|
||||||
|
tuple_items.push_back(tup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *ret = PyTuple_New(tuple_items.size());
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < tuple_items.size(); ++i) {
|
||||||
|
if (PyTuple_SetItem(ret, i, tuple_items[i])) {
|
||||||
|
std::cout << "error setting tuple item" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool import_material(Material *mtl,
|
||||||
|
const pxr::UsdShadeShader &usd_shader,
|
||||||
|
const std::string &source_class)
|
||||||
|
{
|
||||||
|
if (!(usd_shader && mtl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||||
|
|
||||||
|
if (!ensure_module_loaded()) {
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *func_name = "apply_data_to_instance";
|
||||||
|
|
||||||
|
if (!PyObject_HasAttrString(g_umm_module, func_name)) {
|
||||||
|
std::cerr << "WARNING: UMM module has no attribute " << func_name << std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *func = PyObject_GetAttrString(g_umm_module, func_name);
|
||||||
|
|
||||||
|
if (!func) {
|
||||||
|
std::cerr << "WARNING: Couldn't get UMM module attribute " << func_name << std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *source_data = get_shader_source_data(usd_shader);
|
||||||
|
|
||||||
|
if (!source_data) {
|
||||||
|
std::cout << "WARNING: Couldn't get source data for shader " << usd_shader.GetPath()
|
||||||
|
<< std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// std::cout << "source_data:\n";
|
||||||
|
// print_obj(source_data);
|
||||||
|
|
||||||
|
// Create the kwargs dictionary.
|
||||||
|
PyObject *kwargs = PyDict_New();
|
||||||
|
|
||||||
|
if (!kwargs) {
|
||||||
|
std::cout << "WARNING: Couldn't create kwargs dicsionary." << std::endl;
|
||||||
|
Py_DECREF(source_data);
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *instance_name = PyUnicode_FromString(mtl->id.name + 2);
|
||||||
|
PyDict_SetItemString(kwargs, "instance_name", instance_name);
|
||||||
|
Py_DECREF(instance_name);
|
||||||
|
|
||||||
|
PyObject *source_class_obj = PyUnicode_FromString(source_class.c_str());
|
||||||
|
PyDict_SetItemString(kwargs, "source_class", source_class_obj);
|
||||||
|
Py_DECREF(source_class_obj);
|
||||||
|
|
||||||
|
PyObject *render_context = PyUnicode_FromString("Blender");
|
||||||
|
PyDict_SetItemString(kwargs, "render_context", render_context);
|
||||||
|
Py_DECREF(render_context);
|
||||||
|
|
||||||
|
PyDict_SetItemString(kwargs, "source_data", source_data);
|
||||||
|
Py_DECREF(source_data);
|
||||||
|
|
||||||
|
std::cout << func_name << " arguments:\n";
|
||||||
|
print_obj(kwargs);
|
||||||
|
|
||||||
|
PyObject *empty_args = PyTuple_New(0);
|
||||||
|
PyObject *ret = PyObject_Call(func, empty_args, kwargs);
|
||||||
|
Py_DECREF(empty_args);
|
||||||
|
Py_DECREF(func);
|
||||||
|
|
||||||
|
bool success = ret != nullptr;
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
std::cout << "result:\n";
|
||||||
|
print_obj(ret);
|
||||||
|
if (report_notification(ret) == UMM_NOTIFICATION_FAILURE) {
|
||||||
|
/* The function returned a notification object
|
||||||
|
* indicating a failure. */
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(kwargs);
|
||||||
|
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_shader_properties(const USDExporterContext &usd_export_context,
|
||||||
|
pxr::UsdShadeShader &usd_shader,
|
||||||
|
PyObject *data_list)
|
||||||
|
{
|
||||||
|
if (!(data_list && usd_shader)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PyList_Check(data_list)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t len = PyList_Size(data_list);
|
||||||
|
|
||||||
|
for (Py_ssize_t i = 0; i < len; ++i) {
|
||||||
|
PyObject *tup = PyList_GetItem(data_list, i);
|
||||||
|
|
||||||
|
if (!tup) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
if (!get_data_name(tup, name) || name.empty()) {
|
||||||
|
std::cout << "Couldn't get data name\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_none_value(tup)) {
|
||||||
|
/* Receiving None values is not an error. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == "umm_target_class") {
|
||||||
|
std::string target_class;
|
||||||
|
if (!get_string_data(tup, target_class) || target_class.empty()) {
|
||||||
|
std::cout << "Couldn't get target class\n";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
set_source_asset(usd_shader, target_class);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!(PyTuple_Check(tup) && PyTuple_Size(tup) > 1)) {
|
||||||
|
std::cout << "Unexpected data item type or size:\n";
|
||||||
|
print_obj(tup);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *second = PyTuple_GetItem(tup, 1);
|
||||||
|
if (!second) {
|
||||||
|
std::cout << "Couldn't get second tuple value:\n";
|
||||||
|
print_obj(tup);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PyFloat_Check(second)) {
|
||||||
|
float fval = static_cast<float>(PyFloat_AsDouble(second));
|
||||||
|
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Float).Set(fval);
|
||||||
|
}
|
||||||
|
else if (PyBool_Check(second)) {
|
||||||
|
bool bval = static_cast<bool>(PyLong_AsLong(second));
|
||||||
|
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Bool).Set(bval);
|
||||||
|
}
|
||||||
|
else if (PyLong_Check(second)) {
|
||||||
|
int ival = static_cast<int>(PyLong_AsLong(second));
|
||||||
|
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Int).Set(ival);
|
||||||
|
}
|
||||||
|
else if (PyList_Check(second) && PyList_Size(second) == 2) {
|
||||||
|
PyObject *item0 = PyList_GetItem(second, 0);
|
||||||
|
PyObject *item1 = PyList_GetItem(second, 1);
|
||||||
|
|
||||||
|
if (PyUnicode_Check(item0) && PyUnicode_Check(item1)) {
|
||||||
|
const char *asset = PyUnicode_AsUTF8(item0);
|
||||||
|
|
||||||
|
std::string asset_path = get_tex_image_asset_path(
|
||||||
|
asset, usd_export_context.stage, usd_export_context.export_params);
|
||||||
|
|
||||||
|
const char *color_space = PyUnicode_AsUTF8(item1);
|
||||||
|
pxr::UsdShadeInput asset_input = usd_shader.CreateInput(pxr::TfToken(name),
|
||||||
|
pxr::SdfValueTypeNames->Asset);
|
||||||
|
asset_input.Set(pxr::SdfAssetPath(asset_path));
|
||||||
|
asset_input.GetAttr().SetColorSpace(pxr::TfToken(color_space));
|
||||||
|
}
|
||||||
|
else if (PyFloat_Check(item0) && PyFloat_Check(item1)) {
|
||||||
|
float f0 = static_cast<float>(PyFloat_AsDouble(item0));
|
||||||
|
float f1 = static_cast<float>(PyFloat_AsDouble(item1));
|
||||||
|
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Float2)
|
||||||
|
.Set(pxr::GfVec2f(f0, f1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (PyTuple_Check(second) && PyTuple_Size(second) == 3) {
|
||||||
|
pxr::GfVec3f f3val;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
PyObject *comp = PyTuple_GetItem(second, i);
|
||||||
|
if (comp && PyFloat_Check(comp)) {
|
||||||
|
f3val[i] = static_cast<float>(PyFloat_AsDouble(comp));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Couldn't parse color3f " << name << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usd_shader.CreateInput(pxr::TfToken(name), pxr::SdfValueTypeNames->Color3f).Set(f3val);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cout << "Can't handle value:\n";
|
||||||
|
print_obj(second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
bool umm_module_loaded()
|
||||||
|
{
|
||||||
|
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||||
|
|
||||||
|
bool loaded = ensure_module_loaded(false /* warn */);
|
||||||
|
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool umm_import_mdl_material(Material *mtl,
|
||||||
|
const pxr::UsdShadeMaterial &usd_material,
|
||||||
|
bool verbose,
|
||||||
|
bool *r_has_mdl)
|
||||||
|
{
|
||||||
|
if (!(mtl && usd_material)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get the surface shader. */
|
||||||
|
pxr::UsdShadeShader surf_shader = usd_material.ComputeSurfaceSource(usdtokens::mdl);
|
||||||
|
|
||||||
|
if (surf_shader) {
|
||||||
|
/* Check if we have an mdl source asset. */
|
||||||
|
pxr::SdfAssetPath source_asset;
|
||||||
|
if (!surf_shader.GetSourceAsset(&source_asset, usdtokens::mdl)) {
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "No mdl source asset for shader " << surf_shader.GetPath() << std::endl;
|
||||||
|
}
|
||||||
|
if (r_has_mdl) {
|
||||||
|
*r_has_mdl = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pxr::TfToken source_asset_sub_identifier;
|
||||||
|
if (!surf_shader.GetSourceAssetSubIdentifier(&source_asset_sub_identifier, usdtokens::mdl)) {
|
||||||
|
if (verbose) {
|
||||||
|
std::cout << "No mdl source asset sub identifier for shader " << surf_shader.GetPath()
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
if (r_has_mdl) {
|
||||||
|
*r_has_mdl = false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r_has_mdl) {
|
||||||
|
*r_has_mdl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string path = source_asset.GetAssetPath();
|
||||||
|
|
||||||
|
// Get the filename component of the path.
|
||||||
|
size_t last_slash = path.find_last_of("/\\");
|
||||||
|
if (last_slash != std::string::npos) {
|
||||||
|
path = path.substr(last_slash + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string source_class = path + "|" + source_asset_sub_identifier.GetString();
|
||||||
|
return import_material(mtl, surf_shader, source_class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool umm_export_material(const USDExporterContext &usd_export_context,
|
||||||
|
const Material *mtl,
|
||||||
|
pxr::UsdShadeShader &usd_shader,
|
||||||
|
const std::string &render_context)
|
||||||
|
{
|
||||||
|
if (!(usd_shader && mtl)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyGILState_STATE gilstate = PyGILState_Ensure();
|
||||||
|
|
||||||
|
if (!ensure_module_loaded()) {
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *func_name = "convert_instance_to_data";
|
||||||
|
|
||||||
|
if (!PyObject_HasAttrString(g_umm_module, func_name)) {
|
||||||
|
std::cerr << "WARNING: UMM module has no attribute " << func_name << std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *func = PyObject_GetAttrString(g_umm_module, func_name);
|
||||||
|
|
||||||
|
if (!func) {
|
||||||
|
std::cerr << "WARNING: Couldn't get UMM module attribute " << func_name << std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the kwargs dictionary.
|
||||||
|
PyObject *kwargs = PyDict_New();
|
||||||
|
|
||||||
|
if (!kwargs) {
|
||||||
|
std::cout << "WARNING: Couldn't create kwargs dicsionary." << std::endl;
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject *instance_name = PyUnicode_FromString(mtl->id.name + 2);
|
||||||
|
PyDict_SetItemString(kwargs, "instance_name", instance_name);
|
||||||
|
Py_DECREF(instance_name);
|
||||||
|
|
||||||
|
PyObject *render_context_arg = PyUnicode_FromString(render_context.c_str());
|
||||||
|
PyDict_SetItemString(kwargs, "render_context", render_context_arg);
|
||||||
|
Py_DECREF(render_context_arg);
|
||||||
|
|
||||||
|
std::cout << func_name << " arguments:\n";
|
||||||
|
print_obj(kwargs);
|
||||||
|
|
||||||
|
PyObject *empty_args = PyTuple_New(0);
|
||||||
|
PyObject *ret = PyObject_Call(func, empty_args, kwargs);
|
||||||
|
Py_DECREF(empty_args);
|
||||||
|
Py_DECREF(func);
|
||||||
|
|
||||||
|
bool success = ret != nullptr;
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
std::cout << "result:\n";
|
||||||
|
print_obj(ret);
|
||||||
|
|
||||||
|
if (report_notification(ret) == UMM_NOTIFICATION_FAILURE) {
|
||||||
|
/* The function returned a notification object
|
||||||
|
* indicating a failure. */
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
set_shader_properties(usd_export_context, usd_shader, ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_DECREF(kwargs);
|
||||||
|
|
||||||
|
PyGILState_Release(gilstate);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // Namespace blender::io::usd
|
||||||
|
|
||||||
|
#endif // ifdef WITH_PYTHON
|
46
source/blender/io/usd/intern/usd_umm.h
Normal file
46
source/blender/io/usd/intern/usd_umm.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef WITH_PYTHON
|
||||||
|
|
||||||
|
# include <pxr/usd/usdShade/material.h>
|
||||||
|
|
||||||
|
# include "Python.h"
|
||||||
|
|
||||||
|
struct Material;
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
struct USDExporterContext;
|
||||||
|
|
||||||
|
bool umm_module_loaded();
|
||||||
|
|
||||||
|
bool umm_import_mdl_material(Material *mtl,
|
||||||
|
const pxr::UsdShadeMaterial &usd_material,
|
||||||
|
bool verbose,
|
||||||
|
bool *r_has_material);
|
||||||
|
|
||||||
|
bool umm_export_material(const USDExporterContext &usd_export_context,
|
||||||
|
const Material *mtl,
|
||||||
|
pxr::UsdShadeShader &usd_shader,
|
||||||
|
const std::string &render_context);
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
||||||
|
|
||||||
|
#endif
|
@@ -8,7 +8,6 @@
|
|||||||
|
|
||||||
#include "BKE_customdata.h"
|
#include "BKE_customdata.h"
|
||||||
#include "BLI_assert.h"
|
#include "BLI_assert.h"
|
||||||
|
|
||||||
#include "DNA_mesh_types.h"
|
#include "DNA_mesh_types.h"
|
||||||
|
|
||||||
/* TfToken objects are not cheap to construct, so we do it once. */
|
/* TfToken objects are not cheap to construct, so we do it once. */
|
||||||
@@ -20,8 +19,39 @@ static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal
|
|||||||
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
|
static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
|
||||||
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
|
static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
|
||||||
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
|
static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderName("userProperties:blenderName", pxr::TfToken::Immortal);
|
||||||
} // namespace usdtokens
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template<typename VECT>
|
||||||
|
bool set_vec_attrib(const pxr::UsdPrim &prim,
|
||||||
|
const IDProperty *prop,
|
||||||
|
const pxr::TfToken &prop_token,
|
||||||
|
const pxr::SdfValueTypeName &type_name,
|
||||||
|
const pxr::UsdTimeCode &timecode)
|
||||||
|
{
|
||||||
|
if (!prim || !prop || !prop->data.pointer || prop_token.IsEmpty() || !type_name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdAttribute vec_attr = prim.CreateAttribute(prop_token, type_name, true);
|
||||||
|
|
||||||
|
if (!vec_attr) {
|
||||||
|
printf("WARNING: Couldn't USD attribute for array property %s.\n",
|
||||||
|
prop_token.GetString().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
VECT vec_value(static_cast<typename VECT::ScalarType *>(prop->data.pointer));
|
||||||
|
|
||||||
|
return vec_attr.Set(vec_value, timecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
static std::string get_mesh_active_uvlayer_name(const Object *ob)
|
static std::string get_mesh_active_uvlayer_name(const Object *ob)
|
||||||
{
|
{
|
||||||
if (!ob || ob->type != OB_MESH || !ob->data) {
|
if (!ob || ob->type != OB_MESH || !ob->data) {
|
||||||
@@ -35,7 +65,81 @@ static std::string get_mesh_active_uvlayer_name(const Object *ob)
|
|||||||
return name ? name : "";
|
return name ? name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace blender::io::usd {
|
static void create_vector_attrib(const pxr::UsdPrim &prim,
|
||||||
|
const IDProperty *prop,
|
||||||
|
const pxr::TfToken &prop_token,
|
||||||
|
const pxr::UsdTimeCode &timecode)
|
||||||
|
{
|
||||||
|
if (!prim || !prop || prop_token.IsEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop->type != IDP_ARRAY) {
|
||||||
|
printf(
|
||||||
|
"WARNING: Property %s is not an array type and can't be converted to a vector "
|
||||||
|
"attribute.\n",
|
||||||
|
prop_token.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfValueTypeName type_name;
|
||||||
|
bool success = false;
|
||||||
|
|
||||||
|
if (prop->subtype == IDP_FLOAT) {
|
||||||
|
if (prop->len == 2) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Float2;
|
||||||
|
success = set_vec_attrib<pxr::GfVec2f>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 3) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Float3;
|
||||||
|
success = set_vec_attrib<pxr::GfVec3f>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 4) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Float4;
|
||||||
|
success = set_vec_attrib<pxr::GfVec4f>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (prop->subtype == IDP_DOUBLE) {
|
||||||
|
if (prop->len == 2) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Double2;
|
||||||
|
success = set_vec_attrib<pxr::GfVec2d>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 3) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Double3;
|
||||||
|
success = set_vec_attrib<pxr::GfVec3d>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 4) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Double4;
|
||||||
|
success = set_vec_attrib<pxr::GfVec4d>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (prop->subtype == IDP_INT) {
|
||||||
|
if (prop->len == 2) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Int2;
|
||||||
|
success = set_vec_attrib<pxr::GfVec2i>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 3) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Int3;
|
||||||
|
success = set_vec_attrib<pxr::GfVec3i>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
else if (prop->len == 4) {
|
||||||
|
type_name = pxr::SdfValueTypeNames->Int4;
|
||||||
|
success = set_vec_attrib<pxr::GfVec4i>(prim, prop, prop_token, type_name, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type_name) {
|
||||||
|
printf("WARNING: Couldn't determine USD type name for array property %s.\n",
|
||||||
|
prop_token.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
printf("WARNING: Couldn't set USD attribute from array property %s.\n",
|
||||||
|
prop_token.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
|
USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
|
||||||
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
|
: usd_export_context_(usd_export_context), frame_has_been_written_(false), is_animated_(false)
|
||||||
@@ -82,7 +186,27 @@ const pxr::SdfPath &USDAbstractWriter::usd_path() const
|
|||||||
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
|
pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyContext &context,
|
||||||
Material *material)
|
Material *material)
|
||||||
{
|
{
|
||||||
static pxr::SdfPath material_library_path("/_materials");
|
std::string material_prim_path_str;
|
||||||
|
|
||||||
|
/* For instance prototypes, create the material beneath the prototyp prim. */
|
||||||
|
if (usd_export_context_.export_params.use_instancing && !context.is_instance() &&
|
||||||
|
usd_export_context_.hierarchy_iterator->is_prototype(context.object)) {
|
||||||
|
|
||||||
|
material_prim_path_str += std::string(usd_export_context_.export_params.root_prim_path);
|
||||||
|
if (context.object->data) {
|
||||||
|
material_prim_path_str += context.higher_up_export_path;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
material_prim_path_str += context.export_path;
|
||||||
|
}
|
||||||
|
material_prim_path_str += "/Looks";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (material_prim_path_str.empty()) {
|
||||||
|
material_prim_path_str = this->usd_export_context_.export_params.material_prim_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfPath material_library_path(material_prim_path_str);
|
||||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
|
|
||||||
/* Construct the material. */
|
/* Construct the material. */
|
||||||
@@ -92,16 +216,39 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(const HierarchyCont
|
|||||||
if (usd_material) {
|
if (usd_material) {
|
||||||
return usd_material;
|
return usd_material;
|
||||||
}
|
}
|
||||||
usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
|
|
||||||
|
|
||||||
|
usd_material = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdShadeMaterial(usd_export_context_.stage->OverridePrim(usd_path)) :
|
||||||
|
pxr::UsdShadeMaterial::Define(usd_export_context_.stage, usd_path);
|
||||||
|
|
||||||
|
// TODO(bskinner) maybe always export viewport material as variant...
|
||||||
|
if (material->use_nodes && this->usd_export_context_.export_params.generate_cycles_shaders) {
|
||||||
|
create_usd_cycles_material(this->usd_export_context_.stage,
|
||||||
|
material,
|
||||||
|
usd_material,
|
||||||
|
this->usd_export_context_.export_params);
|
||||||
|
}
|
||||||
|
if (material->use_nodes && this->usd_export_context_.export_params.generate_mdl) {
|
||||||
|
create_mdl_material(this->usd_export_context_, material, usd_material);
|
||||||
|
if (this->usd_export_context_.export_params.export_textures) {
|
||||||
|
export_textures(material, this->usd_export_context_.stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
|
if (material->use_nodes && this->usd_export_context_.export_params.generate_preview_surface) {
|
||||||
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
|
std::string active_uv = get_mesh_active_uvlayer_name(context.object);
|
||||||
|
if (usd_export_context_.export_params.convert_uv_to_st && !active_uv.empty()) {
|
||||||
|
active_uv = "st";
|
||||||
|
}
|
||||||
create_usd_preview_surface_material(
|
create_usd_preview_surface_material(
|
||||||
this->usd_export_context_, material, usd_material, active_uv);
|
this->usd_export_context_, material, usd_material, active_uv);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
create_usd_viewport_material(this->usd_export_context_, material, usd_material);
|
create_usd_viewport_material(this->usd_export_context_, material, usd_material);
|
||||||
}
|
}
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && material) {
|
||||||
|
auto prim = usd_material.GetPrim();
|
||||||
|
write_id_properties(prim, material->id, get_export_time_code());
|
||||||
|
}
|
||||||
|
|
||||||
return usd_material;
|
return usd_material;
|
||||||
}
|
}
|
||||||
@@ -130,7 +277,14 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pxr::SdfPath ref_path(context.original_export_path);
|
std::string ref_path_str(usd_export_context_.export_params.root_prim_path);
|
||||||
|
ref_path_str += context.original_export_path;
|
||||||
|
|
||||||
|
pxr::SdfPath ref_path(ref_path_str);
|
||||||
|
|
||||||
|
/* To avoid USD errors, make sure the referenced path exists. */
|
||||||
|
usd_export_context_.stage->DefinePrim(ref_path);
|
||||||
|
|
||||||
if (!prim.GetReferences().AddInternalReference(ref_path)) {
|
if (!prim.GetReferences().AddInternalReference(ref_path)) {
|
||||||
/* See this URL for a description for why referencing may fail"
|
/* See this URL for a description for why referencing may fail"
|
||||||
* https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
|
* https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
|
||||||
@@ -141,7 +295,95 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prim.SetInstanceable(true);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDAbstractWriter::write_id_properties(pxr::UsdPrim &prim,
|
||||||
|
const ID &id,
|
||||||
|
pxr::UsdTimeCode timecode)
|
||||||
|
{
|
||||||
|
if (usd_export_context_.export_params.author_blender_name) {
|
||||||
|
if (GS(id.name) == ID_OB) {
|
||||||
|
// Author property of original blenderName
|
||||||
|
prim.CreateAttribute(pxr::TfToken(usdtokens::blenderName.GetString() + ":object"),
|
||||||
|
pxr::SdfValueTypeNames->String,
|
||||||
|
true)
|
||||||
|
.Set<std::string>(std::string(id.name + 2));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
prim.CreateAttribute(pxr::TfToken(usdtokens::blenderName.GetString() + ":data"),
|
||||||
|
pxr::SdfValueTypeNames->String,
|
||||||
|
true)
|
||||||
|
.Set<std::string>(std::string(id.name + 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id.properties)
|
||||||
|
write_user_properties(prim, (IDProperty *)id.properties, timecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDAbstractWriter::write_user_properties(pxr::UsdPrim &prim,
|
||||||
|
IDProperty *properties,
|
||||||
|
pxr::UsdTimeCode timecode)
|
||||||
|
{
|
||||||
|
if (properties == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (properties->type != IDP_GROUP) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IDProperty *prop;
|
||||||
|
for (prop = (IDProperty *)properties->data.group.first; prop; prop = prop->next) {
|
||||||
|
std::string prop_name = pxr::TfMakeValidIdentifier(prop->name);
|
||||||
|
|
||||||
|
std::string full_prop_name;
|
||||||
|
if (usd_export_context_.export_params.add_properties_namespace) {
|
||||||
|
full_prop_name = "userProperties:";
|
||||||
|
}
|
||||||
|
full_prop_name += prop_name;
|
||||||
|
|
||||||
|
pxr::TfToken prop_token = pxr::TfToken(full_prop_name);
|
||||||
|
|
||||||
|
if (prim.HasAttribute(prop_token)) {
|
||||||
|
/* Don't overwrite existing attributes, as these may have been
|
||||||
|
* created by the exporter logic and shouldn't be changed. */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (prop->type) {
|
||||||
|
case IDP_INT:
|
||||||
|
if (pxr::UsdAttribute int_attr = prim.CreateAttribute(
|
||||||
|
prop_token, pxr::SdfValueTypeNames->Int, true)) {
|
||||||
|
int_attr.Set<int>(prop->data.val, timecode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IDP_FLOAT:
|
||||||
|
if (pxr::UsdAttribute float_attr = prim.CreateAttribute(
|
||||||
|
prop_token, pxr::SdfValueTypeNames->Float, true)) {
|
||||||
|
float_attr.Set<float>(*reinterpret_cast<float *>(&prop->data.val), timecode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IDP_DOUBLE:
|
||||||
|
if (pxr::UsdAttribute double_attr = prim.CreateAttribute(
|
||||||
|
prop_token, pxr::SdfValueTypeNames->Double, true)) {
|
||||||
|
double_attr.Set<double>(*reinterpret_cast<double *>(&prop->data.val), timecode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IDP_STRING:
|
||||||
|
if (pxr::UsdAttribute str_attr = prim.CreateAttribute(
|
||||||
|
prop_token, pxr::SdfValueTypeNames->String, true)) {
|
||||||
|
str_attr.Set<std::string>(static_cast<const char *>(prop->data.pointer), timecode);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IDP_ARRAY:
|
||||||
|
create_vector_attrib(prim, prop, prop_token, timecode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include "DNA_material_types.h"
|
#include "DNA_material_types.h"
|
||||||
|
|
||||||
|
struct Main;
|
||||||
struct Material;
|
struct Material;
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
@@ -55,6 +56,13 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
|
|||||||
|
|
||||||
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
|
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material);
|
||||||
|
|
||||||
|
void write_id_properties(pxr::UsdPrim &prim,
|
||||||
|
const ID &id,
|
||||||
|
pxr::UsdTimeCode = pxr::UsdTimeCode::Default());
|
||||||
|
void write_user_properties(pxr::UsdPrim &prim,
|
||||||
|
IDProperty *properties,
|
||||||
|
pxr::UsdTimeCode = pxr::UsdTimeCode::Default());
|
||||||
|
|
||||||
void write_visibility(const HierarchyContext &context,
|
void write_visibility(const HierarchyContext &context,
|
||||||
const pxr::UsdTimeCode timecode,
|
const pxr::UsdTimeCode timecode,
|
||||||
pxr::UsdGeomImageable &usd_geometry);
|
pxr::UsdGeomImageable &usd_geometry);
|
||||||
|
327
source/blender/io/usd/intern/usd_writer_armature.cc
Normal file
327
source/blender/io/usd/intern/usd_writer_armature.cc
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2021 Blender Foundation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
#include "usd_writer_armature.h"
|
||||||
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
|
#include "BKE_armature.h"
|
||||||
|
#include "DNA_armature_types.h"
|
||||||
|
|
||||||
|
#include "ED_armature.h"
|
||||||
|
|
||||||
|
#include <pxr/base/gf/matrix4d.h>
|
||||||
|
#include <pxr/base/gf/matrix4f.h>
|
||||||
|
#include <pxr/usd/usdSkel/animation.h>
|
||||||
|
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||||
|
#include <pxr/usd/usdSkel/skeleton.h>
|
||||||
|
#include <pxr/usd/usdSkel/tokens.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace usdtokens {
|
||||||
|
static const pxr::TfToken Anim("Anim", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
static std::string build_path(const Bone *bone)
|
||||||
|
{
|
||||||
|
std::string path(pxr::TfMakeValidIdentifier(bone->name));
|
||||||
|
|
||||||
|
const Bone *parent = bone->parent;
|
||||||
|
while (parent) {
|
||||||
|
path = pxr::TfMakeValidIdentifier(parent->name) + std::string("/") + path;
|
||||||
|
parent = parent->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct BoneVisitor {
|
||||||
|
public:
|
||||||
|
virtual void Visit(const Bone *bone) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoneNameList : public BoneVisitor {
|
||||||
|
std::vector<std::string> *names;
|
||||||
|
|
||||||
|
BoneNameList(std::vector<std::string> *in_names) : names(in_names)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Visit(const Bone *bone) override
|
||||||
|
{
|
||||||
|
if (bone && names) {
|
||||||
|
names->push_back(bone->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BoneDataBuilder : public BoneVisitor {
|
||||||
|
|
||||||
|
std::vector<std::string> paths;
|
||||||
|
|
||||||
|
pxr::VtArray<pxr::GfMatrix4d> bind_xforms;
|
||||||
|
|
||||||
|
pxr::VtArray<pxr::GfMatrix4d> rest_xforms;
|
||||||
|
|
||||||
|
pxr::GfMatrix4f world_mat;
|
||||||
|
|
||||||
|
BoneDataBuilder(const pxr::GfMatrix4f &in_world_mat) : world_mat(in_world_mat)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Visit(const Bone *bone) override
|
||||||
|
{
|
||||||
|
if (!bone) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
paths.push_back(build_path(bone));
|
||||||
|
|
||||||
|
pxr::GfMatrix4f arm_mat(bone->arm_mat);
|
||||||
|
|
||||||
|
pxr::GfMatrix4f bind_xf = arm_mat * world_mat;
|
||||||
|
|
||||||
|
bind_xforms.push_back(pxr::GfMatrix4d(bind_xf));
|
||||||
|
|
||||||
|
if (bone->parent) {
|
||||||
|
pxr::GfMatrix4f parent_arm_mat(bone->parent->arm_mat);
|
||||||
|
pxr::GfMatrix4f rest_xf = arm_mat * parent_arm_mat.GetInverse();
|
||||||
|
rest_xforms.push_back(pxr::GfMatrix4d(rest_xf));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
rest_xforms.push_back(pxr::GfMatrix4d(arm_mat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // End anonymous namespace
|
||||||
|
|
||||||
|
static void visit_bones(const Bone *bone, BoneVisitor *visitor)
|
||||||
|
{
|
||||||
|
if (!(bone && visitor)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor->Visit(bone);
|
||||||
|
|
||||||
|
for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
|
||||||
|
visit_bones(child, visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void visit_bones(const Object *ob_arm, BoneVisitor *visitor)
|
||||||
|
{
|
||||||
|
bArmature *armature = (bArmature *)ob_arm->data;
|
||||||
|
|
||||||
|
for (Bone *bone = (Bone *)armature->bonebase.first; bone; bone = bone->next) {
|
||||||
|
visit_bones(bone, visitor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void create_pose_joints(const pxr::UsdSkelAnimation &skel_anim, Object *obj)
|
||||||
|
{
|
||||||
|
if (!(skel_anim && obj && obj->pose)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtTokenArray joints;
|
||||||
|
|
||||||
|
bPose *pose = obj->pose;
|
||||||
|
|
||||||
|
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
|
||||||
|
if (pchan->bone) {
|
||||||
|
joints.push_back(pxr::TfToken(build_path(pchan->bone)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skel_anim.GetJointsAttr().Set(joints);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bPoseChannel *get_parent_pose_chan(bPose *pose, bPoseChannel *in_pchan)
|
||||||
|
{
|
||||||
|
if (!(pose && in_pchan && in_pchan->bone && in_pchan->bone->parent)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bone *parent = in_pchan->bone->parent;
|
||||||
|
|
||||||
|
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
|
||||||
|
if (pchan->bone == parent) {
|
||||||
|
return pchan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_anim_sample(const pxr::UsdSkelAnimation &skel_anim,
|
||||||
|
Object *obj,
|
||||||
|
pxr::UsdTimeCode time)
|
||||||
|
{
|
||||||
|
if (!(skel_anim && obj && obj->pose)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::VtArray<pxr::GfMatrix4d> xforms;
|
||||||
|
|
||||||
|
bPose *pose = obj->pose;
|
||||||
|
|
||||||
|
LISTBASE_FOREACH (bPoseChannel *, pchan, &pose->chanbase) {
|
||||||
|
|
||||||
|
if (!pchan->bone) {
|
||||||
|
printf("WARNING: pchan %s is missing bone.\n", pchan->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::GfMatrix4f pose_mat(pchan->pose_mat);
|
||||||
|
|
||||||
|
if (bPoseChannel *parent_pchan = get_parent_pose_chan(pose, pchan)) {
|
||||||
|
pxr::GfMatrix4f parent_pose_mat(parent_pchan->pose_mat);
|
||||||
|
pxr::GfMatrix4f xf = pose_mat * parent_pose_mat.GetInverse();
|
||||||
|
xforms.push_back(pxr::GfMatrix4d(xf));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xforms.push_back(pxr::GfMatrix4d(pose_mat));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skel_anim.SetTransforms(xforms, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
void USDArmatureWriter::get_armature_bone_names(Object *obj, std::vector<std::string> &r_names)
|
||||||
|
{
|
||||||
|
if (!obj) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoneNameList name_list(&r_names);
|
||||||
|
visit_bones(obj, &name_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
USDArmatureWriter::USDArmatureWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDArmatureWriter::do_write(HierarchyContext &context)
|
||||||
|
{
|
||||||
|
if (!context.object) {
|
||||||
|
printf("WARNING in USDArmatureWriter::do_write: null object\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.object->type != OB_ARMATURE) {
|
||||||
|
printf("WARNING in USDArmatureWriter::do_write: object is not an armature\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.object->data == nullptr) {
|
||||||
|
printf("WARNING in USDArmatureWriter::do_write: null object data\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
pxr::UsdSkelSkeleton usd_skel = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdSkelSkeleton(
|
||||||
|
stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdSkelSkeleton::Define(stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
|
||||||
|
if (!usd_skel) {
|
||||||
|
printf("WARNING: Couldn't define Skeleton %s\n",
|
||||||
|
usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdSkelAnimation usd_skel_anim;
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_animation) {
|
||||||
|
|
||||||
|
/* Create the SkelAnimation primitive. */
|
||||||
|
|
||||||
|
/* TODO: Right now there is a remote possibility that the SkelAnimation path will clash
|
||||||
|
* with the USD path for another object in the scene. Look into extending USDHierarchyIterator
|
||||||
|
* with a function that will provide a USD path that's guranteed to be unique (e.g., by
|
||||||
|
* examining paths of all the writers in the writer map). The USDHierarchyIterator
|
||||||
|
* can be accessed for such a query like this:
|
||||||
|
* this->usd_export_context_.hierarchy_iterator */
|
||||||
|
|
||||||
|
pxr::SdfPath anim_path = usd_export_context_.usd_path.AppendChild(usdtokens::Anim);
|
||||||
|
|
||||||
|
usd_skel_anim = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdSkelAnimation(stage->OverridePrim(anim_path)) :
|
||||||
|
pxr::UsdSkelAnimation::Define(stage, anim_path);
|
||||||
|
|
||||||
|
if (!usd_skel_anim) {
|
||||||
|
printf("WARNING: Couldn't define SkelAnim %s\n", anim_path.GetString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->frame_has_been_written_) {
|
||||||
|
|
||||||
|
pxr::GfMatrix4f world_mat(context.matrix_world);
|
||||||
|
BoneDataBuilder bone_data(world_mat);
|
||||||
|
|
||||||
|
visit_bones(context.object, &bone_data);
|
||||||
|
|
||||||
|
if (!bone_data.paths.empty()) {
|
||||||
|
pxr::VtTokenArray joints(bone_data.paths.size());
|
||||||
|
|
||||||
|
for (int i = 0; i < bone_data.paths.size(); ++i) {
|
||||||
|
joints[i] = pxr::TfToken(bone_data.paths[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
usd_skel.GetJointsAttr().Set(joints);
|
||||||
|
}
|
||||||
|
|
||||||
|
usd_skel.GetBindTransformsAttr().Set(bone_data.bind_xforms);
|
||||||
|
usd_skel.GetRestTransformsAttr().Set(bone_data.rest_xforms);
|
||||||
|
|
||||||
|
if (usd_skel_anim) {
|
||||||
|
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(usd_skel.GetPrim());
|
||||||
|
usd_skel_api.CreateAnimationSourceRel().SetTargets(
|
||||||
|
pxr::SdfPathVector({pxr::SdfPath(usdtokens::Anim)}));
|
||||||
|
|
||||||
|
create_pose_joints(usd_skel_anim, context.object);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_skel_anim) {
|
||||||
|
add_anim_sample(usd_skel_anim, context.object, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDArmatureWriter::check_is_animated(const HierarchyContext &context) const
|
||||||
|
{
|
||||||
|
const Object *obj = context.object;
|
||||||
|
|
||||||
|
if (!(obj && obj->type == OB_ARMATURE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(makowalski): Is this a sufficient check? */
|
||||||
|
return obj->adt != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
42
source/blender/io/usd/intern/usd_writer_armature.h
Normal file
42
source/blender/io/usd/intern/usd_writer_armature.h
Normal 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
|
@@ -12,6 +12,10 @@
|
|||||||
#include "DNA_camera_types.h"
|
#include "DNA_camera_types.h"
|
||||||
#include "DNA_scene_types.h"
|
#include "DNA_scene_types.h"
|
||||||
|
|
||||||
|
#include "MEM_guardedalloc.h"
|
||||||
|
#include "RNA_access.h"
|
||||||
|
#include "RNA_types.h"
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
@@ -52,9 +56,14 @@ static void camera_sensor_size_for_render(const Camera *camera,
|
|||||||
|
|
||||||
void USDCameraWriter::do_write(HierarchyContext &context)
|
void USDCameraWriter::do_write(HierarchyContext &context)
|
||||||
{
|
{
|
||||||
|
const float unit_scale = usd_export_context_.export_params.convert_to_cm ? 100.0f : 1.0f;
|
||||||
|
|
||||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage,
|
pxr::UsdGeomCamera usd_camera = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
usd_export_context_.usd_path);
|
pxr::UsdGeomCamera(usd_export_context_.stage->OverridePrim(
|
||||||
|
usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomCamera::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
|
||||||
Camera *camera = static_cast<Camera *>(context.object->data);
|
Camera *camera = static_cast<Camera *>(context.object->data);
|
||||||
Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
|
Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
|
||||||
@@ -67,8 +76,12 @@ void USDCameraWriter::do_write(HierarchyContext &context)
|
|||||||
*/
|
*/
|
||||||
usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
|
usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
|
||||||
|
|
||||||
float aperture_x, aperture_y;
|
/* TODO(makowalski): Maybe add option to call camera_sensor_size_for_render(). */
|
||||||
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);
|
/*float aperture_x, aperture_y;
|
||||||
|
camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);*/
|
||||||
|
|
||||||
|
float aperture_x = camera->sensor_x;
|
||||||
|
float aperture_y = camera->sensor_y;
|
||||||
|
|
||||||
float film_aspect = aperture_x / aperture_y;
|
float film_aspect = aperture_x / aperture_y;
|
||||||
usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
|
usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
|
||||||
@@ -76,18 +89,37 @@ void USDCameraWriter::do_write(HierarchyContext &context)
|
|||||||
usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
|
usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
|
||||||
usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
|
usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
|
||||||
timecode);
|
timecode);
|
||||||
|
/* TODO(makowalsk): confirm this is the correct way to get the shutter. */
|
||||||
|
float shutter_length = scene->eevee.motion_blur_shutter / 2.0f;
|
||||||
|
|
||||||
|
double shutter_open = -shutter_length;
|
||||||
|
double shutter_close = shutter_length;
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.override_shutter) {
|
||||||
|
shutter_open = usd_export_context_.export_params.shutter_open;
|
||||||
|
shutter_close = usd_export_context_.export_params.shutter_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
usd_camera.CreateShutterOpenAttr().Set(shutter_open);
|
||||||
|
usd_camera.CreateShutterCloseAttr().Set(shutter_close);
|
||||||
|
|
||||||
usd_camera.CreateClippingRangeAttr().Set(
|
usd_camera.CreateClippingRangeAttr().Set(
|
||||||
pxr::VtValue(pxr::GfVec2f(camera->clip_start, camera->clip_end)), timecode);
|
pxr::VtValue(pxr::GfVec2f(camera->clip_start * unit_scale, camera->clip_end * unit_scale)),
|
||||||
|
timecode);
|
||||||
|
|
||||||
/* Write DoF-related attributes. */
|
/* Write DoF-related attributes. */
|
||||||
if (camera->dof.flag & CAM_DOF_ENABLED) {
|
if (camera->dof.flag & CAM_DOF_ENABLED) {
|
||||||
usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
|
usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
|
||||||
|
|
||||||
float focus_distance = scene->unit.scale_length *
|
float focus_distance = scene->unit.scale_length *
|
||||||
BKE_camera_object_dof_distance(context.object);
|
BKE_camera_object_dof_distance(context.object) * unit_scale;
|
||||||
usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
|
usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && camera) {
|
||||||
|
auto prim = usd_camera.GetPrim();
|
||||||
|
write_id_properties(prim, camera->id, timecode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
257
source/blender/io/usd/intern/usd_writer_curve.cc
Normal file
257
source/blender/io/usd/intern/usd_writer_curve.cc
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2019 Blender Foundation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
#include "usd_writer_curve.h"
|
||||||
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||||
|
#include <pxr/usd/usdGeom/curves.h>
|
||||||
|
#include <pxr/usd/usdGeom/nurbsCurves.h>
|
||||||
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
#include <pxr/usd/usdShade/material.h>
|
||||||
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "BKE_curve.h"
|
||||||
|
#include "BKE_material.h"
|
||||||
|
|
||||||
|
#include "BLI_math_geom.h"
|
||||||
|
|
||||||
|
#include "DNA_curve_types.h"
|
||||||
|
|
||||||
|
#include "WM_api.h"
|
||||||
|
#include "WM_types.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
USDCurveWriter::USDCurveWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDCurveWriter::do_write(HierarchyContext &context)
|
||||||
|
{
|
||||||
|
// Because blender allows vector handles and auto handles all bezier curves are bezier
|
||||||
|
// An optimization could be made to set usd type to linear if all controls are vector
|
||||||
|
|
||||||
|
Curve *curve = static_cast<Curve *>(context.object->data);
|
||||||
|
|
||||||
|
pxr::VtArray<pxr::GfVec3f> verts;
|
||||||
|
pxr::VtArray<float> widths;
|
||||||
|
|
||||||
|
std::vector<int> vert_counts;
|
||||||
|
std::vector<float> weights;
|
||||||
|
std::vector<float> knots;
|
||||||
|
std::vector<uint8_t> orders;
|
||||||
|
|
||||||
|
pxr::VtIntArray curve_point_counts;
|
||||||
|
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
pxr::UsdGeomCurves curves;
|
||||||
|
|
||||||
|
Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
|
||||||
|
if (nurbs == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int curveType = nurbs->type;
|
||||||
|
|
||||||
|
for (; nurbs; nurbs = nurbs->next) {
|
||||||
|
if (nurbs->type != curveType) {
|
||||||
|
// We dont yet support writing curves with multiple types of curve data
|
||||||
|
WM_reportf(RPT_WARNING, "Cannot export mixed curves");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curveType == CU_NURBS) {
|
||||||
|
curves = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdGeomNurbsCurves(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomNurbsCurves::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curves = (usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdGeomBasisCurves(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
|
||||||
|
pxr::UsdGeomBasisCurves(curves).CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
|
||||||
|
}
|
||||||
|
|
||||||
|
nurbs = static_cast<Nurb *>(curve->nurb.first);
|
||||||
|
for (; nurbs; nurbs = nurbs->next) {
|
||||||
|
bool is_cyclic = false;
|
||||||
|
|
||||||
|
if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
|
||||||
|
// Not needed for this conversion
|
||||||
|
// curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->periodic));
|
||||||
|
is_cyclic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nurbs->bp) {
|
||||||
|
|
||||||
|
if (nurbs->type != CU_NURBS) {
|
||||||
|
pxr::UsdGeomBasisCurves(curves).CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
|
||||||
|
pxr::UsdGeomBasisCurves(curves).CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
|
||||||
|
}
|
||||||
|
|
||||||
|
const long long totpoint = nurbs->pntsu * nurbs->pntsv;
|
||||||
|
|
||||||
|
const BPoint *point = nurbs->bp;
|
||||||
|
curve_point_counts.push_back(totpoint);
|
||||||
|
|
||||||
|
for (int i = 0; i < totpoint; i++, point++) {
|
||||||
|
verts.push_back(pxr::GfVec3f(point->vec));
|
||||||
|
weights.push_back(point->vec[3]);
|
||||||
|
widths.push_back(point->radius * 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (nurbs->bezt) {
|
||||||
|
if (nurbs->type != CU_NURBS) {
|
||||||
|
pxr::UsdGeomBasisCurves(curves).CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
|
||||||
|
pxr::UsdGeomBasisCurves(curves).CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
||||||
|
}
|
||||||
|
|
||||||
|
const int totpoint = nurbs->pntsu;
|
||||||
|
|
||||||
|
const BezTriple *bezier = nurbs->bezt;
|
||||||
|
curve_point_counts.push_back((totpoint * 3) - (is_cyclic ? -1 : 2));
|
||||||
|
|
||||||
|
/* TODO(kevin): store info about handles, Alembic doesn't have this. */
|
||||||
|
for (int i = 0; i < totpoint; i++, bezier++) {
|
||||||
|
|
||||||
|
if (i > 0) {
|
||||||
|
verts.push_back(pxr::GfVec3f(bezier->vec[0]));
|
||||||
|
widths.push_back(bezier->radius * 2.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
verts.push_back(pxr::GfVec3f(bezier->vec[1]));
|
||||||
|
widths.push_back(bezier->radius * 2.0f);
|
||||||
|
|
||||||
|
if (i < totpoint - 1 || is_cyclic) {
|
||||||
|
verts.push_back(pxr::GfVec3f(bezier->vec[2]));
|
||||||
|
widths.push_back(bezier->radius * 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_cyclic) {
|
||||||
|
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[0]));
|
||||||
|
widths.push_back(nurbs->bezt->radius * 2.0f);
|
||||||
|
|
||||||
|
verts.push_back(pxr::GfVec3f(nurbs->bezt->vec[1]));
|
||||||
|
widths.push_back(nurbs->bezt->radius * 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: Implement knots
|
||||||
|
// if (nurbs->knotsu != NULL) {
|
||||||
|
// const size_t num_knots = KNOTSU(nurbs);
|
||||||
|
|
||||||
|
// /* Add an extra knot at the beginning and end of the array since most apps
|
||||||
|
// * require/expect them. */
|
||||||
|
// knots.resize(num_knots + 2);
|
||||||
|
|
||||||
|
// for (int i = 0; i < num_knots; i++) {
|
||||||
|
// knots[i + 1] = nurbs->knotsu[i];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
|
||||||
|
// knots[0] = nurbs->knotsu[0];
|
||||||
|
// knots[num_knots - 1] = nurbs->knotsu[num_knots - 1];
|
||||||
|
// }
|
||||||
|
// else {
|
||||||
|
// knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]);
|
||||||
|
// knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] -
|
||||||
|
// nurbs->knotsu[num_knots - 2]);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// orders.push_back(nurbs->orderu);
|
||||||
|
// vert_counts.push_back(verts.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
|
||||||
|
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
|
||||||
|
pxr::UsdAttribute attr_widths = curves.CreateWidthsAttr(pxr::VtValue(), true);
|
||||||
|
|
||||||
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
|
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(verts), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(attr_widths, pxr::VtValue(widths), timecode);
|
||||||
|
|
||||||
|
// USDGeomBasisCurves only allow binding one material to each basis curve.
|
||||||
|
// In order to support Blender's curve material assignment we probably
|
||||||
|
// need to create multiple Basis Curves per mat_nr
|
||||||
|
assign_materials(context, curves);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && curve &&
|
||||||
|
curve->id.properties) {
|
||||||
|
auto prim = curves.GetPrim();
|
||||||
|
write_id_properties(prim, curve->id, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDCurveWriter::check_is_animated(const HierarchyContext &) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDCurveWriter::assign_materials(const HierarchyContext &context,
|
||||||
|
pxr::UsdGeomCurves usd_curve)
|
||||||
|
{
|
||||||
|
if (context.object->totcol == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool curve_material_bound = false;
|
||||||
|
for (short mat_num = 0; mat_num < context.object->totcol; mat_num++) {
|
||||||
|
Material *material = BKE_object_material_get(context.object, mat_num + 1);
|
||||||
|
if (material == nullptr) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_curve.GetPrim());
|
||||||
|
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||||
|
api.Bind(usd_material);
|
||||||
|
|
||||||
|
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
|
||||||
|
* use the flag from the first non-empty material slot. */
|
||||||
|
usd_curve.CreateDoubleSidedAttr(
|
||||||
|
pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
|
||||||
|
|
||||||
|
curve_material_bound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!curve_material_bound) {
|
||||||
|
/* Blender defaults to double-sided, but USD to single-sided. */
|
||||||
|
usd_curve.CreateDoubleSidedAttr(pxr::VtValue(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!curve_material_bound) {
|
||||||
|
/* Either all material slots were empty or there is only one material in use. As geometry
|
||||||
|
* subsets are only written when actually used to assign a material, and the mesh already has
|
||||||
|
* the material assigned, there is no need to continue. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
41
source/blender/io/usd/intern/usd_writer_curve.h
Normal file
41
source/blender/io/usd/intern/usd_writer_curve.h
Normal 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__ */
|
@@ -3,12 +3,19 @@
|
|||||||
#include "usd_writer_hair.h"
|
#include "usd_writer_hair.h"
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
#include <pxr/usd/usdGeom/basisCurves.h>
|
|
||||||
#include <pxr/usd/usdGeom/tokens.h>
|
#include <pxr/usd/usdGeom/tokens.h>
|
||||||
|
#include <pxr/usd/usdShade/material.h>
|
||||||
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "BKE_material.h"
|
||||||
|
|
||||||
#include "BKE_particle.h"
|
#include "BKE_particle.h"
|
||||||
|
|
||||||
|
#include "BLI_math_geom.h"
|
||||||
|
|
||||||
#include "DNA_particle_types.h"
|
#include "DNA_particle_types.h"
|
||||||
|
}
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
@@ -16,8 +23,28 @@ USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This was copied from source/intern/cycles/blender/blender_curves.cpp
|
||||||
|
static float shaperadius(float shape, float root, float tip, float time)
|
||||||
|
{
|
||||||
|
assert(time >= 0.0f);
|
||||||
|
assert(time <= 1.0f);
|
||||||
|
float radius = 1.0f - time;
|
||||||
|
|
||||||
|
if (shape != 0.0f) {
|
||||||
|
if (shape < 0.0f)
|
||||||
|
radius = powf(radius, 1.0f + shape);
|
||||||
|
else
|
||||||
|
radius = powf(radius, 1.0f / (1.0f - shape));
|
||||||
|
}
|
||||||
|
return (radius * (root - tip)) + tip;
|
||||||
|
}
|
||||||
|
|
||||||
void USDHairWriter::do_write(HierarchyContext &context)
|
void USDHairWriter::do_write(HierarchyContext &context)
|
||||||
{
|
{
|
||||||
|
/* Get untransformed vertices, there's a xform under the hair. */
|
||||||
|
float inv_mat[4][4];
|
||||||
|
invert_m4_m4_safe(inv_mat, context.object->obmat);
|
||||||
|
|
||||||
ParticleSystem *psys = context.particle_system;
|
ParticleSystem *psys = context.particle_system;
|
||||||
ParticleCacheKey **cache = psys->pathcache;
|
ParticleCacheKey **cache = psys->pathcache;
|
||||||
if (cache == nullptr) {
|
if (cache == nullptr) {
|
||||||
@@ -25,17 +52,34 @@ void USDHairWriter::do_write(HierarchyContext &context)
|
|||||||
}
|
}
|
||||||
|
|
||||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
|
pxr::UsdGeomBasisCurves curves =
|
||||||
usd_export_context_.usd_path);
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdGeomBasisCurves(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage, usd_export_context_.usd_path);
|
||||||
|
|
||||||
/* TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE) */
|
if (psys->part->flag & PART_HAIR_BSPLINE) {
|
||||||
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
|
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
||||||
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
|
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->linear));
|
||||||
|
curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bezier));
|
||||||
|
}
|
||||||
|
|
||||||
|
curves.CreateWrapAttr(pxr::VtValue(pxr::UsdGeomTokens->nonperiodic));
|
||||||
|
|
||||||
pxr::VtArray<pxr::GfVec3f> points;
|
pxr::VtArray<pxr::GfVec3f> points;
|
||||||
|
pxr::VtArray<float> widths;
|
||||||
pxr::VtIntArray curve_point_counts;
|
pxr::VtIntArray curve_point_counts;
|
||||||
curve_point_counts.reserve(psys->totpart);
|
curve_point_counts.reserve(psys->totpart);
|
||||||
|
|
||||||
|
float hair_root_rad = psys->part->rad_root * psys->part->rad_scale * 0.5f;
|
||||||
|
float hair_tip_rad = psys->part->rad_tip * psys->part->rad_scale * 0.5f;
|
||||||
|
float hair_shape = psys->part->shape;
|
||||||
|
|
||||||
|
bool close_tip = (psys->part->shape_flag & PART_SHAPE_CLOSE_TIP) != 0;
|
||||||
|
|
||||||
ParticleCacheKey *strand;
|
ParticleCacheKey *strand;
|
||||||
for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
|
for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
|
||||||
strand = cache[strand_index];
|
strand = cache[strand_index];
|
||||||
@@ -44,24 +88,64 @@ void USDHairWriter::do_write(HierarchyContext &context)
|
|||||||
curve_point_counts.push_back(point_count);
|
curve_point_counts.push_back(point_count);
|
||||||
|
|
||||||
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
|
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
|
||||||
points.push_back(pxr::GfVec3f(strand->co));
|
float vert[3];
|
||||||
|
copy_v3_v3(vert, strand->co);
|
||||||
|
mul_m4_v3(inv_mat, vert);
|
||||||
|
points.push_back(pxr::GfVec3f(vert));
|
||||||
|
float t = (float)point_index / (float)(point_count - 1);
|
||||||
|
// if(point_index == point_count - 1) t = 0.95f;
|
||||||
|
float root_rad = (close_tip && point_index == point_count - 1) ? 0 : hair_tip_rad;
|
||||||
|
widths.push_back(shaperadius(hair_shape, hair_root_rad, root_rad, t) * 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_child_particles) {
|
||||||
|
ParticleCacheKey **child_cache = psys->childcache;
|
||||||
|
if (child_cache != nullptr) {
|
||||||
|
for (int strand_index = 0; strand_index < psys->totchild; ++strand_index) {
|
||||||
|
strand = child_cache[strand_index];
|
||||||
|
|
||||||
|
int point_count = strand->segments + 1;
|
||||||
|
curve_point_counts.push_back(point_count);
|
||||||
|
|
||||||
|
for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
|
||||||
|
float vert[3];
|
||||||
|
copy_v3_v3(vert, strand->co);
|
||||||
|
mul_m4_v3(inv_mat, vert);
|
||||||
|
points.push_back(pxr::GfVec3f(vert));
|
||||||
|
float t = (float)point_index / (float)(point_count - 1);
|
||||||
|
t = clamp_f(t, 0.0f, 0.95f);
|
||||||
|
widths.push_back(shaperadius(hair_shape, hair_root_rad, hair_tip_rad, t) * 2.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
|
pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
|
||||||
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
|
pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
|
||||||
if (!attr_points.HasValue()) {
|
pxr::UsdAttribute attr_widths = curves.CreateWidthsAttr(pxr::VtValue(), true);
|
||||||
attr_points.Set(points, pxr::UsdTimeCode::Default());
|
|
||||||
attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default());
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
}
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode);
|
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode);
|
||||||
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
|
usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(attr_widths, pxr::VtValue(widths), timecode);
|
||||||
|
|
||||||
if (psys->totpart > 0) {
|
if (psys->totpart > 0) {
|
||||||
pxr::VtArray<pxr::GfVec3f> colors;
|
pxr::VtArray<pxr::GfVec3f> colors;
|
||||||
colors.push_back(pxr::GfVec3f(cache[0]->col));
|
colors.push_back(pxr::GfVec3f(cache[0]->col));
|
||||||
curves.CreateDisplayColorAttr(pxr::VtValue(colors));
|
curves.CreateDisplayColorAttr(pxr::VtValue(colors));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_materials) {
|
||||||
|
assign_material(context, curves);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && psys->part) {
|
||||||
|
auto prim = curves.GetPrim();
|
||||||
|
write_id_properties(prim, psys->part->id, timecode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) const
|
bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) const
|
||||||
@@ -69,4 +153,24 @@ bool USDHairWriter::check_is_animated(const HierarchyContext &UNUSED(context)) c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDHairWriter::assign_material(const HierarchyContext &context,
|
||||||
|
pxr::UsdGeomBasisCurves usd_curve)
|
||||||
|
{
|
||||||
|
ParticleSystem *psys = context.particle_system;
|
||||||
|
// In newer Blender builds this becomes: BKE_object_material_get
|
||||||
|
Material *material = BKE_object_material_get(context.object, psys->part->omat);
|
||||||
|
|
||||||
|
if (material == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_curve.GetPrim());
|
||||||
|
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||||
|
api.Bind(usd_material);
|
||||||
|
|
||||||
|
/* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
|
||||||
|
* use the flag from the first non-empty material slot. */
|
||||||
|
usd_curve.CreateDoubleSidedAttr(pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
#include "usd_writer_abstract.h"
|
#include "usd_writer_abstract.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdGeom/basisCurves.h>
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
/* Writer for writing hair particle data as USD curves. */
|
/* Writer for writing hair particle data as USD curves. */
|
||||||
@@ -14,6 +16,7 @@ class USDHairWriter : public USDAbstractWriter {
|
|||||||
protected:
|
protected:
|
||||||
virtual void do_write(HierarchyContext &context) override;
|
virtual void do_write(HierarchyContext &context) override;
|
||||||
virtual bool check_is_animated(const HierarchyContext &context) const override;
|
virtual bool check_is_animated(const HierarchyContext &context) const override;
|
||||||
|
void assign_material(const HierarchyContext &context, pxr::UsdGeomBasisCurves usd_curve);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -2,18 +2,36 @@
|
|||||||
* Copyright 2019 Blender Foundation. All rights reserved. */
|
* Copyright 2019 Blender Foundation. All rights reserved. */
|
||||||
#include "usd_writer_light.h"
|
#include "usd_writer_light.h"
|
||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
#include "usd_light_convert.h"
|
||||||
|
|
||||||
#include <pxr/usd/usdLux/diskLight.h>
|
#include <pxr/usd/usdLux/diskLight.h>
|
||||||
#include <pxr/usd/usdLux/distantLight.h>
|
#include <pxr/usd/usdLux/distantLight.h>
|
||||||
#include <pxr/usd/usdLux/rectLight.h>
|
#include <pxr/usd/usdLux/rectLight.h>
|
||||||
|
#include <pxr/usd/usdLux/shapingAPI.h>
|
||||||
#include <pxr/usd/usdLux/sphereLight.h>
|
#include <pxr/usd/usdLux/sphereLight.h>
|
||||||
|
|
||||||
#include "BLI_assert.h"
|
#include "BLI_assert.h"
|
||||||
|
#include "BLI_math.h"
|
||||||
|
#include "BLI_math_matrix.h"
|
||||||
#include "BLI_utildefines.h"
|
#include "BLI_utildefines.h"
|
||||||
|
|
||||||
#include "DNA_light_types.h"
|
#include "DNA_light_types.h"
|
||||||
#include "DNA_object_types.h"
|
#include "DNA_object_types.h"
|
||||||
|
|
||||||
|
#include "WM_api.h"
|
||||||
|
#include "WM_types.h"
|
||||||
|
|
||||||
|
namespace usdtokens {
|
||||||
|
// Attribute names.
|
||||||
|
static const pxr::TfToken angle("angle", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken color("color", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken height("height", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken intensity("intensity", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken radius("radius", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken specular("specular", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken width("width", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
@@ -23,15 +41,21 @@ USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWrite
|
|||||||
bool USDLightWriter::is_supported(const HierarchyContext *context) const
|
bool USDLightWriter::is_supported(const HierarchyContext *context) const
|
||||||
{
|
{
|
||||||
Light *light = static_cast<Light *>(context->object->data);
|
Light *light = static_cast<Light *>(context->object->data);
|
||||||
return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN);
|
return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN, LA_SPOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDLightWriter::do_write(HierarchyContext &context)
|
void USDLightWriter::do_write(HierarchyContext &context)
|
||||||
{
|
{
|
||||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
|
||||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
/* Need to account for the scene scale when converting to nits
|
||||||
|
* or scaling the radius. */
|
||||||
|
float world_scale = mat4_to_scale(context.matrix_world);
|
||||||
|
|
||||||
|
float radius_scale = usd_export_context_.export_params.scale_light_radius ? 1.0f / world_scale :
|
||||||
|
1.0f;
|
||||||
|
|
||||||
Light *light = static_cast<Light *>(context.object->data);
|
Light *light = static_cast<Light *>(context.object->data);
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
pxr::UsdLuxLightAPI usd_light_api;
|
pxr::UsdLuxLightAPI usd_light_api;
|
||||||
@@ -45,8 +69,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
switch (light->area_shape) {
|
switch (light->area_shape) {
|
||||||
case LA_AREA_DISK:
|
case LA_AREA_DISK:
|
||||||
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
|
case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
|
||||||
pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
|
pxr::UsdLuxDiskLight disk_light =
|
||||||
disk_light.CreateRadiusAttr().Set(light->area_size, timecode);
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxDiskLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxDiskLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
disk_light.CreateRadiusAttr().Set(light->area_size / 2.0f, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
if (pxr::UsdAttribute attr = disk_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
|
||||||
|
attr.Set(light->area_size / 2.0f, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
usd_light_api = disk_light.LightAPI();
|
usd_light_api = disk_light.LightAPI();
|
||||||
#else
|
#else
|
||||||
@@ -55,9 +92,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LA_AREA_RECT: {
|
case LA_AREA_RECT: {
|
||||||
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
|
pxr::UsdLuxRectLight rect_light =
|
||||||
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxRectLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxRectLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
rect_light.CreateWidthAttr().Set(light->area_size, timecode);
|
rect_light.CreateWidthAttr().Set(light->area_size, timecode);
|
||||||
rect_light.CreateHeightAttr().Set(light->area_sizey, timecode);
|
rect_light.CreateHeightAttr().Set(light->area_sizey, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
pxr::UsdAttribute attr = rect_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::width, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(light->area_size, timecode);
|
||||||
|
}
|
||||||
|
attr = rect_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::height, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(light->area_sizey, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
usd_light_api = rect_light.LightAPI();
|
usd_light_api = rect_light.LightAPI();
|
||||||
#else
|
#else
|
||||||
@@ -66,9 +122,28 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LA_AREA_SQUARE: {
|
case LA_AREA_SQUARE: {
|
||||||
pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
|
pxr::UsdLuxRectLight rect_light =
|
||||||
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxRectLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxRectLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
rect_light.CreateWidthAttr().Set(light->area_size, timecode);
|
rect_light.CreateWidthAttr().Set(light->area_size, timecode);
|
||||||
rect_light.CreateHeightAttr().Set(light->area_size, timecode);
|
rect_light.CreateHeightAttr().Set(light->area_size, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
pxr::UsdAttribute attr = rect_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::width, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(light->area_size, timecode);
|
||||||
|
}
|
||||||
|
attr = rect_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::height, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(light->area_size, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
usd_light_api = rect_light.LightAPI();
|
usd_light_api = rect_light.LightAPI();
|
||||||
#else
|
#else
|
||||||
@@ -79,8 +154,21 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case LA_LOCAL: {
|
case LA_LOCAL: {
|
||||||
pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path);
|
pxr::UsdLuxSphereLight sphere_light =
|
||||||
sphere_light.CreateRadiusAttr().Set(light->area_size, timecode);
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxSphereLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxSphereLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
sphere_light.CreateRadiusAttr().Set(light->area_size * radius_scale, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
if (pxr::UsdAttribute attr = sphere_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
|
||||||
|
attr.Set(light->area_size * radius_scale, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
usd_light_api = sphere_light.LightAPI();
|
usd_light_api = sphere_light.LightAPI();
|
||||||
#else
|
#else
|
||||||
@@ -88,13 +176,56 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case LA_SUN: {
|
case LA_SPOT: {
|
||||||
pxr::UsdLuxDistantLight distant_light = pxr::UsdLuxDistantLight::Define(stage, usd_path);
|
pxr::UsdLuxSphereLight spot_light =
|
||||||
/* TODO(makowalski): set angle attribute here. */
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxSphereLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxSphereLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
spot_light.CreateRadiusAttr().Set(light->area_size * radius_scale, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
if (pxr::UsdAttribute attr = spot_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::radius, pxr::SdfValueTypeNames->Float, true)) {
|
||||||
|
attr.Set(light->area_size * radius_scale, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdLuxShapingAPI shapingAPI(spot_light);
|
||||||
|
float angle = (light->spotsize * (180.0f / (float)M_PI)) /
|
||||||
|
2.0f; // Blender angle seems to be half of what USD expectes it to be.
|
||||||
|
shapingAPI.CreateShapingConeAngleAttr(pxr::VtValue(angle), true);
|
||||||
|
shapingAPI.CreateShapingConeSoftnessAttr(pxr::VtValue(light->spotblend), true);
|
||||||
|
spot_light.CreateTreatAsPointAttr(pxr::VtValue(true), true);
|
||||||
|
|
||||||
#if PXR_VERSION >= 2111
|
#if PXR_VERSION >= 2111
|
||||||
usd_light_api = distant_light.LightAPI();
|
usd_light_api = spot_light.LightAPI();
|
||||||
#else
|
#else
|
||||||
usd_light_api = distant_light;
|
usd_light_api = spot_light;
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case LA_SUN: {
|
||||||
|
pxr::UsdLuxDistantLight sun_light =
|
||||||
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdLuxDistantLight(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdLuxDistantLight::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
sun_light.CreateAngleAttr().Set(light->sun_angle, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
if (pxr::UsdAttribute attr = sun_light.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::angle, pxr::SdfValueTypeNames->Float, true)) {
|
||||||
|
attr.Set(light->sun_angle, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if PXR_VERSION >= 2111
|
||||||
|
usd_light_api = sun_light.LightAPI();
|
||||||
|
#else
|
||||||
|
usd_light_api = sun_light;
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -102,21 +233,39 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||||||
BLI_assert_msg(0, "is_supported() returned true for unsupported light type");
|
BLI_assert_msg(0, "is_supported() returned true for unsupported light type");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Scale factor to get to somewhat-similar illumination. Since the USDViewer had similar
|
float usd_intensity = light->energy * usd_export_context_.export_params.light_intensity_scale;
|
||||||
* over-exposure as Blender Internal with the same values, this code applies the reverse of the
|
|
||||||
* versioning code in light_emission_unify(). */
|
if (usd_export_context_.export_params.convert_light_to_nits) {
|
||||||
float usd_intensity;
|
usd_intensity /= nits_to_energy_scale_factor(light, world_scale, radius_scale);
|
||||||
if (light->type == LA_SUN) {
|
|
||||||
/* Untested, as the Hydra GL viewport of USDViewer doesn't support distant lights. */
|
|
||||||
usd_intensity = light->energy;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
usd_intensity = light->energy / 100.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usd_light_api.CreateIntensityAttr().Set(usd_intensity, timecode);
|
usd_light_api.CreateIntensityAttr().Set(usd_intensity, timecode);
|
||||||
|
|
||||||
usd_light_api.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
|
usd_light_api.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
|
||||||
usd_light_api.CreateSpecularAttr().Set(light->spec_fac, timecode);
|
usd_light_api.CreateSpecularAttr().Set(light->spec_fac, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.backward_compatible) {
|
||||||
|
pxr::UsdAttribute attr = usd_light_api.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::intensity, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(usd_intensity, timecode);
|
||||||
|
}
|
||||||
|
attr = usd_light_api.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::color, pxr::SdfValueTypeNames->Color3f, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
|
||||||
|
}
|
||||||
|
attr = usd_light_api.GetPrim().CreateAttribute(
|
||||||
|
usdtokens::specular, pxr::SdfValueTypeNames->Float, true);
|
||||||
|
if (attr) {
|
||||||
|
attr.Set(light->spec_fac, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && light) {
|
||||||
|
auto prim = usd_light_api.GetPrim();
|
||||||
|
write_id_properties(prim, light->id, timecode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -8,15 +8,33 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
struct Material;
|
|
||||||
struct USDExportParams;
|
|
||||||
struct bNode;
|
struct bNode;
|
||||||
struct bNodeTree;
|
struct bNodeTree;
|
||||||
|
struct Material;
|
||||||
|
struct USDExportParams;
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T usd_define_or_over(pxr::UsdStageRefPtr stage, pxr::SdfPath path, bool as_overs = false)
|
||||||
|
{
|
||||||
|
return (as_overs) ? T(stage->OverridePrim(path)) : T::Define(stage, path);
|
||||||
|
}
|
||||||
|
|
||||||
struct USDExporterContext;
|
struct USDExporterContext;
|
||||||
|
|
||||||
|
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
|
||||||
|
bNodeTree *ntree,
|
||||||
|
pxr::UsdShadeMaterial &usd_material,
|
||||||
|
const USDExportParams &export_params);
|
||||||
|
void create_usd_cycles_material(pxr::UsdStageRefPtr a_stage,
|
||||||
|
Material *material,
|
||||||
|
pxr::UsdShadeMaterial &usd_material,
|
||||||
|
const USDExportParams &export_params);
|
||||||
|
void create_mdl_material(const USDExporterContext &usd_export_context,
|
||||||
|
Material *material,
|
||||||
|
pxr::UsdShadeMaterial &usd_material);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
|
* Entry point to create an approximate USD Preview Surface network from a Cycles node graph.
|
||||||
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes
|
* Due to the limited nodes in the USD Preview Surface specification, only the following nodes
|
||||||
@@ -40,4 +58,19 @@ void create_usd_viewport_material(const USDExporterContext &usd_export_context,
|
|||||||
Material *material,
|
Material *material,
|
||||||
pxr::UsdShadeMaterial &usd_material);
|
pxr::UsdShadeMaterial &usd_material);
|
||||||
|
|
||||||
|
void export_texture(bNode *node,
|
||||||
|
const pxr::UsdStageRefPtr stage,
|
||||||
|
const bool allow_overwrite = false);
|
||||||
|
|
||||||
|
std::string get_tex_image_asset_path(bNode *node,
|
||||||
|
const pxr::UsdStageRefPtr stage,
|
||||||
|
const USDExportParams &export_params);
|
||||||
|
|
||||||
|
std::string get_tex_image_asset_path(const std::string &asset_path,
|
||||||
|
const pxr::UsdStageRefPtr stage,
|
||||||
|
const USDExportParams &export_params);
|
||||||
|
|
||||||
|
void export_textures(const Material *material, const pxr::UsdStageRefPtr stage);
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@
|
|||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
#include <pxr/usd/usdGeom/mesh.h>
|
#include <pxr/usd/usdGeom/mesh.h>
|
||||||
|
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||||
#include <pxr/usd/usdShade/material.h>
|
#include <pxr/usd/usdShade/material.h>
|
||||||
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
#include <pxr/usd/usdShade/materialBindingAPI.h>
|
||||||
|
|
||||||
@@ -13,8 +14,10 @@
|
|||||||
#include "BKE_attribute.h"
|
#include "BKE_attribute.h"
|
||||||
#include "BKE_customdata.h"
|
#include "BKE_customdata.h"
|
||||||
#include "BKE_lib_id.h"
|
#include "BKE_lib_id.h"
|
||||||
|
#include "BKE_library.h"
|
||||||
#include "BKE_material.h"
|
#include "BKE_material.h"
|
||||||
#include "BKE_mesh.h"
|
#include "BKE_mesh.h"
|
||||||
|
#include "BKE_mesh_runtime.h"
|
||||||
#include "BKE_modifier.h"
|
#include "BKE_modifier.h"
|
||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
|
|
||||||
@@ -25,27 +28,101 @@
|
|||||||
#include "DNA_meshdata_types.h"
|
#include "DNA_meshdata_types.h"
|
||||||
#include "DNA_modifier_types.h"
|
#include "DNA_modifier_types.h"
|
||||||
#include "DNA_object_fluidsim_types.h"
|
#include "DNA_object_fluidsim_types.h"
|
||||||
|
#include "DNA_object_types.h"
|
||||||
#include "DNA_particle_types.h"
|
#include "DNA_particle_types.h"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
/* TfToken objects are not cheap to construct, so we do it once. */
|
||||||
|
namespace usdtokens {
|
||||||
|
static const pxr::TfToken blenderName("userProperties:blenderName", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderNameNS("userProperties:blenderName:", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderObject("object", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderObjectNS("object:", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderData("data", pxr::TfToken::Immortal);
|
||||||
|
static const pxr::TfToken blenderDataNS("data:", pxr::TfToken::Immortal);
|
||||||
|
} // namespace usdtokens
|
||||||
|
|
||||||
|
/* check if the mesh is a subsurf, ignoring disabled modifiers and
|
||||||
|
* displace if it's after subsurf. */
|
||||||
|
static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob)
|
||||||
|
{
|
||||||
|
ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
|
||||||
|
|
||||||
|
for (; md; md = md->prev) {
|
||||||
|
if (BKE_modifier_is_enabled(scene, md, eModifierMode_Render)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (md->type == eModifierType_Subsurf) {
|
||||||
|
SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
|
||||||
|
|
||||||
|
if (smd->subdivType == ME_CC_SUBSURF) {
|
||||||
|
return md;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* mesh is not a subsurf. break */
|
||||||
|
if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
|
bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
|
||||||
{
|
{
|
||||||
if (usd_export_context_.export_params.visible_objects_only) {
|
// TODO(makowalski) -- Check if we should be calling is_object_visible() below.
|
||||||
return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
|
// if (usd_export_context_.export_params.visible_objects_only) {
|
||||||
|
// return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!usd_export_context_.export_params.visible_objects_only) {
|
||||||
|
// We can skip the visibility test.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
|
Object *object = context->object;
|
||||||
|
bool is_dupli = context->duplicator != nullptr;
|
||||||
|
int base_flag;
|
||||||
|
|
||||||
|
if (is_dupli) {
|
||||||
|
/* Construct the object's base flags from its dupliparent, just like is done in
|
||||||
|
* deg_objects_dupli_iterator_next(). Without this, the visiblity check below will fail. Doing
|
||||||
|
* this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
|
||||||
|
* copying the Object for every dupli. */
|
||||||
|
base_flag = object->base_flag;
|
||||||
|
object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI;
|
||||||
|
}
|
||||||
|
|
||||||
|
int visibility = BKE_object_visibility(object,
|
||||||
|
usd_export_context_.export_params.evaluation_mode);
|
||||||
|
|
||||||
|
if (is_dupli) {
|
||||||
|
object->base_flag = base_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (visibility & OB_VISIBLE_SELF) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
||||||
{
|
{
|
||||||
Object *object_eval = context.object;
|
Object *object_eval = context.object;
|
||||||
|
|
||||||
|
m_subsurf_mod = get_subsurf_modifier(DEG_get_evaluated_scene(usd_export_context_.depsgraph),
|
||||||
|
context.object);
|
||||||
|
|
||||||
|
if (m_subsurf_mod && !usd_export_context_.export_params.apply_subdiv) {
|
||||||
|
m_subsurf_mod->mode |= eModifierMode_DisableTemporary;
|
||||||
|
}
|
||||||
|
|
||||||
bool needsfree = false;
|
bool needsfree = false;
|
||||||
Mesh *mesh = get_export_mesh(object_eval, needsfree);
|
Mesh *mesh = get_export_mesh(object_eval, needsfree);
|
||||||
|
|
||||||
@@ -66,6 +143,17 @@ void USDGenericMeshWriter::do_write(HierarchyContext &context)
|
|||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto prim = usd_export_context_.stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||||
|
if (prim.IsValid() && object_eval)
|
||||||
|
prim.SetActive((object_eval->duplicator_visibility_flag & OB_DUPLI_FLAG_RENDER) != 0);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && mesh)
|
||||||
|
write_id_properties(prim, mesh->id, get_export_time_code());
|
||||||
|
|
||||||
|
if (m_subsurf_mod && !usd_export_context_.export_params.apply_subdiv) {
|
||||||
|
m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
|
void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
|
||||||
@@ -102,106 +190,335 @@ struct USDMeshData {
|
|||||||
pxr::VtFloatArray corner_sharpnesses;
|
pxr::VtFloatArray corner_sharpnesses;
|
||||||
};
|
};
|
||||||
|
|
||||||
void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
|
void USDGenericMeshWriter::write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
|
||||||
|
{
|
||||||
|
const CustomData *ldata = &mesh->ldata;
|
||||||
|
|
||||||
|
/* Index of the UV layer to be renamed "st", set to the active UV layer index if
|
||||||
|
* the convert_uv_to_st option is enabled and set to -1 otherwise. */
|
||||||
|
const int st_layer_idx = usd_export_context_.export_params.convert_uv_to_st ?
|
||||||
|
CustomData_get_active_layer_index(ldata, CD_MLOOPUV) :
|
||||||
|
-1;
|
||||||
|
|
||||||
|
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
|
||||||
|
const CustomDataLayer *layer = &ldata->layers[layer_idx];
|
||||||
|
if (layer->type == CD_MLOOPUV && usd_export_context_.export_params.export_uvmaps) {
|
||||||
|
const char *name_override = st_layer_idx == layer_idx ? "st" : nullptr;
|
||||||
|
write_uv_maps(mesh, usd_mesh, layer, name_override);
|
||||||
|
}
|
||||||
|
else if (layer->type == CD_PROP_BYTE_COLOR &&
|
||||||
|
usd_export_context_.export_params.export_vertex_colors) {
|
||||||
|
write_vertex_colors(mesh, usd_mesh, layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
|
const CustomDataLayer *layer,
|
||||||
|
const char *name_override)
|
||||||
{
|
{
|
||||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
const CustomData *ldata = &mesh->ldata;
|
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
|
||||||
for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
|
* The primvar name is the same as the UV Map name. This is to allow the standard name "st"
|
||||||
const CustomDataLayer *layer = &ldata->layers[layer_idx];
|
* for texture coordinates by naming the UV Map as such, without having to guess which UV Map
|
||||||
if (layer->type != CD_MLOOPUV) {
|
* is the "standard" one. */
|
||||||
|
pxr::TfToken primvar_name(name_override ? name_override :
|
||||||
|
pxr::TfMakeValidIdentifier(layer->name));
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.author_blender_name) {
|
||||||
|
// Store original layer name in blender
|
||||||
|
usd_mesh.GetPrim()
|
||||||
|
.CreateAttribute(pxr::TfToken(usdtokens::blenderNameNS.GetString() +
|
||||||
|
usdtokens::blenderDataNS.GetString() +
|
||||||
|
primvar_name.GetString()),
|
||||||
|
pxr::SdfValueTypeNames->String,
|
||||||
|
true)
|
||||||
|
.Set(std::string(layer->name), pxr::UsdTimeCode::Default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
|
||||||
|
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
|
||||||
|
|
||||||
|
MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
|
||||||
|
pxr::VtArray<pxr::GfVec2f> uv_coords;
|
||||||
|
for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
|
||||||
|
uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
|
const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
|
||||||
|
usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDGenericMeshWriter::write_vertex_colors(const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
|
const CustomDataLayer *layer)
|
||||||
|
{
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
|
||||||
|
|
||||||
|
const float cscale = 1.0f / 255.0f;
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.author_blender_name) {
|
||||||
|
// Store original layer name in blender
|
||||||
|
usd_mesh.GetPrim()
|
||||||
|
.CreateAttribute(pxr::TfToken(usdtokens::blenderNameNS.GetString() +
|
||||||
|
usdtokens::blenderDataNS.GetString() +
|
||||||
|
primvar_name.GetString()),
|
||||||
|
pxr::SdfValueTypeNames->String,
|
||||||
|
true)
|
||||||
|
.Set(std::string(layer->name), pxr::UsdTimeCode::Default());
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdGeomPrimvarsAPI pvApi = pxr::UsdGeomPrimvarsAPI(usd_mesh);
|
||||||
|
|
||||||
|
// TODO: Allow option of vertex varying primvar
|
||||||
|
pxr::UsdGeomPrimvar vertex_colors_pv = pvApi.CreatePrimvar(
|
||||||
|
primvar_name, pxr::SdfValueTypeNames->Color3fArray, pxr::UsdGeomTokens->faceVarying);
|
||||||
|
|
||||||
|
MLoopCol *vertCol = static_cast<MLoopCol *>(layer->data);
|
||||||
|
pxr::VtArray<pxr::GfVec3f> vertex_colors;
|
||||||
|
|
||||||
|
for (int loop_idx = 0; loop_idx < mesh->totloop; ++loop_idx) {
|
||||||
|
pxr::GfVec3f col = pxr::GfVec3f(vertCol[loop_idx].r * cscale,
|
||||||
|
vertCol[loop_idx].g * cscale,
|
||||||
|
vertCol[loop_idx].b * cscale);
|
||||||
|
vertex_colors.push_back(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex_colors_pv.Set(vertex_colors, timecode);
|
||||||
|
|
||||||
|
const pxr::UsdAttribute &vertex_colors_attr = vertex_colors_pv.GetAttr();
|
||||||
|
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(vertex_colors), timecode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDGenericMeshWriter::write_vertex_groups(const Object *ob,
|
||||||
|
const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
|
bool as_point_groups)
|
||||||
|
{
|
||||||
|
if (!ob)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
int i, j;
|
||||||
|
bDeformGroup *def;
|
||||||
|
std::vector<pxr::UsdGeomPrimvar> pv_groups;
|
||||||
|
std::vector<pxr::VtArray<float>> pv_data;
|
||||||
|
|
||||||
|
// Create vertex groups primvars
|
||||||
|
for (def = (bDeformGroup *)ob->defbase.first, i = 0, j = 0; def; def = def->next, i++) {
|
||||||
|
if (!def)
|
||||||
continue;
|
continue;
|
||||||
}
|
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(def->name));
|
||||||
|
pxr::TfToken primvar_interpolation = (as_point_groups) ? pxr::UsdGeomTokens->vertex :
|
||||||
|
pxr::UsdGeomTokens->faceVarying;
|
||||||
|
pv_groups.push_back(usd_mesh.CreatePrimvar(
|
||||||
|
primvar_name, pxr::SdfValueTypeNames->FloatArray, primvar_interpolation));
|
||||||
|
|
||||||
/* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
|
size_t primvar_size = 0;
|
||||||
* The primvar name is the same as the UV Map name. This is to allow the standard name "st"
|
|
||||||
* for texture coordinates by naming the UV Map as such, without having to guess which UV Map
|
|
||||||
* is the "standard" one. */
|
|
||||||
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
|
|
||||||
pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
|
|
||||||
primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
|
|
||||||
|
|
||||||
MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
|
if (as_point_groups) {
|
||||||
pxr::VtArray<pxr::GfVec2f> uv_coords;
|
primvar_size = mesh->totvert;
|
||||||
for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
|
|
||||||
uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
MPoly *mpoly = mesh->mpoly;
|
||||||
|
for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
|
||||||
|
primvar_size += mpoly->totloop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pv_data.push_back(pxr::VtArray<float>(primvar_size));
|
||||||
|
}
|
||||||
|
|
||||||
if (!uv_coords_primvar.HasValue()) {
|
size_t num_groups = pv_groups.size();
|
||||||
uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
|
|
||||||
|
if (num_groups == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Extract vertex groups
|
||||||
|
if (as_point_groups) {
|
||||||
|
for (i = 0; i < mesh->totvert; i++) {
|
||||||
|
// Init to zero
|
||||||
|
for (j = 0; j < num_groups; j++) {
|
||||||
|
pv_data[j][i] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
MDeformVert *vert = &mesh->dvert[i];
|
||||||
|
if (vert) {
|
||||||
|
for (j = 0; j < vert->totweight; j++) {
|
||||||
|
uint idx = vert->dw[j].def_nr;
|
||||||
|
float w = vert->dw[j].weight;
|
||||||
|
/* This out of bounds check is necessary because MDeformVert.totweight can be
|
||||||
|
larger than the number of bDeformGroup structs in Object.defbase. It appears to be
|
||||||
|
a Blender bug that can cause this scenario.*/
|
||||||
|
if (idx < num_groups) {
|
||||||
|
pv_data[idx][i] = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
|
}
|
||||||
usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
|
else {
|
||||||
|
MPoly *mpoly = mesh->mpoly;
|
||||||
|
for (i = 0; i < mesh->totvert; i++) {
|
||||||
|
// Init to zero
|
||||||
|
for (j = 0; j < num_groups; j++) {
|
||||||
|
pv_data[j][i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const MVert *mvert = mesh->mvert;
|
||||||
|
int p_idx = 0;
|
||||||
|
for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
|
||||||
|
MLoop *mloop = mesh->mloop + mpoly->loopstart;
|
||||||
|
for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
|
||||||
|
MDeformVert *vert = &mesh->dvert[mloop->v];
|
||||||
|
|
||||||
|
if (vert) {
|
||||||
|
for (j = 0; j < vert->totweight; j++) {
|
||||||
|
uint idx = vert->dw[j].def_nr;
|
||||||
|
float w = vert->dw[j].weight;
|
||||||
|
/* This out of bounds check is necessary because MDeformVert.totweight can be
|
||||||
|
larger than the number of bDeformGroup structs in Object.defbase. Appears to be
|
||||||
|
a Blender bug that can cause this scenario.*/
|
||||||
|
if (idx < num_groups) {
|
||||||
|
pv_data[idx][p_idx] = w;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p_idx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store data in usd
|
||||||
|
for (i = 0; i < num_groups; i++) {
|
||||||
|
pv_groups[i].Set(pv_data[i], timecode);
|
||||||
|
|
||||||
|
const pxr::UsdAttribute &vertex_colors_attr = pv_groups[i].GetAttr();
|
||||||
|
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(pv_data[i]), timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDGenericMeshWriter::write_face_maps(const Object *ob,
|
||||||
|
const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh)
|
||||||
|
{
|
||||||
|
if (!ob)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
std::vector<pxr::UsdGeomPrimvar> pv_groups;
|
||||||
|
std::vector<pxr::VtArray<float>> pv_data;
|
||||||
|
|
||||||
|
int i;
|
||||||
|
size_t mpoly_len = mesh->totpoly;
|
||||||
|
|
||||||
|
for (bFaceMap *fmap = (bFaceMap *)ob->fmaps.first; fmap; fmap = fmap->next) {
|
||||||
|
if (!fmap)
|
||||||
|
continue;
|
||||||
|
pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(fmap->name));
|
||||||
|
pxr::TfToken primvar_interpolation = pxr::UsdGeomTokens->uniform;
|
||||||
|
pv_groups.push_back(usd_mesh.CreatePrimvar(
|
||||||
|
primvar_name, pxr::SdfValueTypeNames->FloatArray, primvar_interpolation));
|
||||||
|
|
||||||
|
pv_data.push_back(pxr::VtArray<float>(mpoly_len));
|
||||||
|
|
||||||
|
// Init data
|
||||||
|
for (i = 0; i < mpoly_len; i++) {
|
||||||
|
pv_data[pv_data.size() - 1][i] = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t num_groups = pv_groups.size();
|
||||||
|
|
||||||
|
if (num_groups == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const int *facemap_data = (int *)CustomData_get_layer(&mesh->pdata, CD_FACEMAP);
|
||||||
|
|
||||||
|
if (facemap_data) {
|
||||||
|
for (i = 0; i < mpoly_len; i++) {
|
||||||
|
if (facemap_data[i] >= 0) {
|
||||||
|
pv_data[facemap_data[i]][i] = 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store data in usd
|
||||||
|
for (i = 0; i < num_groups; i++) {
|
||||||
|
pv_groups[i].Set(pv_data[i], timecode);
|
||||||
|
|
||||||
|
const pxr::UsdAttribute &vertex_colors_attr = pv_groups[i].GetAttr();
|
||||||
|
usd_value_writer_.SetAttribute(vertex_colors_attr, pxr::VtValue(pv_data[i]), timecode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
||||||
{
|
{
|
||||||
pxr::UsdTimeCode timecode = get_export_time_code();
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
|
|
||||||
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
|
|
||||||
|
|
||||||
pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
|
pxr::UsdGeomMesh usd_mesh =
|
||||||
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdGeomMesh(usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomMesh::Define(usd_export_context_.stage, usd_export_context_.usd_path);
|
||||||
|
|
||||||
write_visibility(context, timecode, usd_mesh);
|
write_visibility(context, timecode, usd_mesh);
|
||||||
|
|
||||||
USDMeshData usd_mesh_data;
|
USDMeshData usd_mesh_data;
|
||||||
get_geometry_data(mesh, usd_mesh_data);
|
get_geometry_data(mesh, usd_mesh_data);
|
||||||
|
|
||||||
if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
|
if (usd_export_context_.export_params.export_vertices) {
|
||||||
if (!mark_as_instance(context, usd_mesh.GetPrim())) {
|
pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
|
||||||
return;
|
pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
|
||||||
}
|
|
||||||
|
|
||||||
/* The material path will be of the form </_materials/{material name}>, which is outside the
|
|
||||||
* sub-tree pointed to by ref_path. As a result, the referenced data is not allowed to point
|
|
||||||
* out of its own sub-tree. It does work when we override the material with exactly the same
|
|
||||||
* path, though. */
|
|
||||||
if (usd_export_context_.export_params.export_materials) {
|
|
||||||
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
|
|
||||||
pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
|
|
||||||
true);
|
|
||||||
pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
|
|
||||||
true);
|
true);
|
||||||
|
|
||||||
if (!attr_points.HasValue()) {
|
pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(
|
||||||
/* Provide the initial value as default. This makes USD write the value as constant if they
|
pxr::VtValue(), true);
|
||||||
* don't change over time. */
|
|
||||||
attr_points.Set(usd_mesh_data.points, defaultTime);
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
|
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(
|
||||||
|
attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(
|
||||||
|
attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
|
||||||
|
|
||||||
|
if (!usd_mesh_data.crease_lengths.empty()) {
|
||||||
|
pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(),
|
||||||
|
true);
|
||||||
|
pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(),
|
||||||
|
true);
|
||||||
|
pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(
|
||||||
|
pxr::VtValue(), true);
|
||||||
|
|
||||||
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
|
usd_value_writer_.SetAttribute(
|
||||||
|
attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(
|
||||||
|
attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
|
||||||
|
usd_value_writer_.SetAttribute(
|
||||||
|
attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
|
write_custom_data(mesh, usd_mesh);
|
||||||
usd_value_writer_.SetAttribute(
|
|
||||||
attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
|
|
||||||
usd_value_writer_.SetAttribute(
|
|
||||||
attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
|
|
||||||
|
|
||||||
if (!usd_mesh_data.crease_lengths.empty()) {
|
if (usd_export_context_.export_params.export_vertex_groups) {
|
||||||
pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
|
write_vertex_groups(context.object,
|
||||||
pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
|
mesh,
|
||||||
pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
|
usd_mesh,
|
||||||
true);
|
!usd_export_context_.export_params.vertex_data_as_face_varying);
|
||||||
|
write_face_maps(context.object, mesh, usd_mesh);
|
||||||
if (!attr_crease_lengths.HasValue()) {
|
|
||||||
attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
|
|
||||||
attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
|
|
||||||
attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
usd_value_writer_.SetAttribute(
|
|
||||||
attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
|
|
||||||
usd_value_writer_.SetAttribute(
|
|
||||||
attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
|
|
||||||
usd_value_writer_.SetAttribute(
|
|
||||||
attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!usd_mesh_data.corner_indices.empty() &&
|
if (!usd_mesh_data.corner_indices.empty() &&
|
||||||
@@ -211,8 +528,8 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
|||||||
pxr::VtValue(), true);
|
pxr::VtValue(), true);
|
||||||
|
|
||||||
if (!attr_corner_indices.HasValue()) {
|
if (!attr_corner_indices.HasValue()) {
|
||||||
attr_corner_indices.Set(usd_mesh_data.corner_indices, defaultTime);
|
attr_corner_indices.Set(usd_mesh_data.corner_indices, timecode);
|
||||||
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, defaultTime);
|
attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, timecode);
|
||||||
}
|
}
|
||||||
|
|
||||||
usd_value_writer_.SetAttribute(
|
usd_value_writer_.SetAttribute(
|
||||||
@@ -221,9 +538,6 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
|||||||
attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
|
attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (usd_export_context_.export_params.export_uvmaps) {
|
|
||||||
write_uv_maps(mesh, usd_mesh);
|
|
||||||
}
|
|
||||||
if (usd_export_context_.export_params.export_normals) {
|
if (usd_export_context_.export_params.export_normals) {
|
||||||
write_normals(mesh, usd_mesh);
|
write_normals(mesh, usd_mesh);
|
||||||
}
|
}
|
||||||
@@ -234,7 +548,10 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
|
if (usd_export_context_.export_params.export_vertices) {
|
||||||
|
usd_mesh.CreateSubdivisionSchemeAttr().Set(
|
||||||
|
(m_subsurf_mod == NULL) ? pxr::UsdGeomTokens->none : pxr::UsdGeomTokens->catmullClark);
|
||||||
|
}
|
||||||
|
|
||||||
if (usd_export_context_.export_params.export_materials) {
|
if (usd_export_context_.export_params.export_materials) {
|
||||||
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
|
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
|
||||||
@@ -382,8 +699,8 @@ void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
|
|||||||
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
|
||||||
pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
|
pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
|
||||||
|
|
||||||
pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
|
pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_mesh.GetPrim());
|
||||||
material_name, face_indices);
|
pxr::UsdGeomSubset usd_face_subset = api.CreateMaterialBindSubset(material_name, face_indices);
|
||||||
pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
|
pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -427,9 +744,10 @@ void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_
|
|||||||
}
|
}
|
||||||
|
|
||||||
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
|
pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
|
||||||
if (!attr_normals.HasValue()) {
|
|
||||||
attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
|
// NOTE (Marcelo Sercheli): Code to set values at default time was removed since
|
||||||
}
|
// `timecode` will be default time in case of non-animation exports. For animated
|
||||||
|
// exports, USD will inter/extrapolate values linearly.
|
||||||
usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
|
usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
|
||||||
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
|
usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
|
||||||
}
|
}
|
||||||
@@ -465,7 +783,10 @@ USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWrit
|
|||||||
|
|
||||||
Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
|
Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
|
||||||
{
|
{
|
||||||
return BKE_object_get_evaluated_mesh(object_eval);
|
Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
|
||||||
|
// Assumed safe because the original depsgraph was nonconst in usd_capi...
|
||||||
|
Depsgraph *dg = const_cast<Depsgraph *>(usd_export_context_.depsgraph);
|
||||||
|
return mesh_get_eval_final(dg, scene, object_eval, &CD_MASK_MESH);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include <pxr/usd/usdGeom/mesh.h>
|
#include <pxr/usd/usdGeom/mesh.h>
|
||||||
|
|
||||||
|
struct ModifierData;
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
struct USDMeshData;
|
struct USDMeshData;
|
||||||
@@ -31,9 +33,23 @@ class USDGenericMeshWriter : public USDAbstractWriter {
|
|||||||
void assign_materials(const HierarchyContext &context,
|
void assign_materials(const HierarchyContext &context,
|
||||||
pxr::UsdGeomMesh usd_mesh,
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
const MaterialFaceGroups &usd_face_groups);
|
const MaterialFaceGroups &usd_face_groups);
|
||||||
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
void write_custom_data(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||||
|
void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh,
|
||||||
|
const CustomDataLayer *layer,
|
||||||
|
const char *name_override = nullptr);
|
||||||
|
void write_vertex_colors(const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
|
const CustomDataLayer *layer);
|
||||||
|
void write_vertex_groups(const Object *ob,
|
||||||
|
const Mesh *mesh,
|
||||||
|
pxr::UsdGeomMesh usd_mesh,
|
||||||
|
bool as_point_groups);
|
||||||
|
void write_face_maps(const Object *ob, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||||
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||||
void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
void write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
ModifierData *m_subsurf_mod;
|
||||||
};
|
};
|
||||||
|
|
||||||
class USDMeshWriter : public USDGenericMeshWriter {
|
class USDMeshWriter : public USDGenericMeshWriter {
|
||||||
|
152
source/blender/io/usd/intern/usd_writer_particle.cc
Normal file
152
source/blender/io/usd/intern/usd_writer_particle.cc
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2019 Blender Foundation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
#include "usd_writer_particle.h"
|
||||||
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdGeom/pointInstancer.h>
|
||||||
|
#include <pxr/usd/usdGeom/sphere.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include "BLI_assert.h"
|
||||||
|
#include "BLI_math.h"
|
||||||
|
#include "BLI_utildefines.h"
|
||||||
|
|
||||||
|
#include "DNA_object_force_types.h"
|
||||||
|
#include "DNA_object_types.h"
|
||||||
|
#include "DNA_particle_types.h"
|
||||||
|
|
||||||
|
#include "BKE_particle.h"
|
||||||
|
|
||||||
|
#include "WM_api.h"
|
||||||
|
#include "WM_types.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
USDParticleWriter::USDParticleWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDParticleWriter::is_supported(const HierarchyContext *context) const
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDParticleWriter::do_write(HierarchyContext &context)
|
||||||
|
{
|
||||||
|
ParticleSystem *psys = context.particle_system;
|
||||||
|
ParticleSettings *psettings = psys->part;
|
||||||
|
|
||||||
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
pxr::UsdGeomPointInstancer usd_pi =
|
||||||
|
(usd_export_context_.export_params.export_as_overs) ?
|
||||||
|
pxr::UsdGeomPointInstancer(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path)) :
|
||||||
|
pxr::UsdGeomPointInstancer::Define(usd_export_context_.stage,
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
|
||||||
|
// Prototypes
|
||||||
|
|
||||||
|
Object *instance_object = psettings->instance_object;
|
||||||
|
if (instance_object) {
|
||||||
|
std::string full_path = "/" +
|
||||||
|
pxr::TfMakeValidIdentifier(std::string(instance_object->id.name + 2));
|
||||||
|
Object *obj_parent = instance_object->parent;
|
||||||
|
|
||||||
|
for (; obj_parent != nullptr; obj_parent = obj_parent->parent) {
|
||||||
|
if (obj_parent != nullptr) {
|
||||||
|
full_path = "/" + pxr::TfMakeValidIdentifier(std::string(obj_parent->id.name + 2)) +
|
||||||
|
full_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::SdfPath inst_path = pxr::SdfPath(
|
||||||
|
std::string(usd_export_context_.export_params.root_prim_path) + full_path);
|
||||||
|
|
||||||
|
pxr::UsdRelationship prototypes = usd_pi.CreatePrototypesRel();
|
||||||
|
prototypes.AddTarget(inst_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdAttribute protoIndicesAttr = usd_pi.CreateProtoIndicesAttr();
|
||||||
|
pxr::VtIntArray indices;
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
|
||||||
|
pxr::UsdAttribute positionAttr = usd_pi.CreatePositionsAttr();
|
||||||
|
pxr::UsdAttribute scaleAttr = usd_pi.CreateScalesAttr();
|
||||||
|
pxr::UsdAttribute orientationAttr = usd_pi.CreateOrientationsAttr();
|
||||||
|
pxr::UsdAttribute velAttr = usd_pi.CreateVelocitiesAttr();
|
||||||
|
pxr::UsdAttribute angVelAttr = usd_pi.CreateAngularVelocitiesAttr();
|
||||||
|
pxr::UsdAttribute invisibleIdsAttr = usd_pi.CreateInvisibleIdsAttr();
|
||||||
|
|
||||||
|
pxr::VtArray<pxr::GfVec3f> points;
|
||||||
|
pxr::VtArray<pxr::GfVec3f> scales;
|
||||||
|
pxr::VtArray<pxr::GfVec3f> velocities;
|
||||||
|
pxr::VtArray<pxr::GfVec3f> angularVelocities;
|
||||||
|
pxr::VtArray<pxr::GfQuath> orientations;
|
||||||
|
pxr::VtInt64Array invisibleIndices;
|
||||||
|
|
||||||
|
ParticleData *pa;
|
||||||
|
int p;
|
||||||
|
for (p = 0, pa = psys->particles; p < psys->totpart; p++, pa++) {
|
||||||
|
indices.push_back(0);
|
||||||
|
|
||||||
|
points.push_back(pxr::GfVec3f(pa->state.co[0], pa->state.co[1], pa->state.co[2]));
|
||||||
|
|
||||||
|
// Apply blender rand...
|
||||||
|
float size = psys->part->size;
|
||||||
|
size *= 1.0f - psys->part->randsize * psys_frand(psys, p + 1);
|
||||||
|
scales.push_back(pxr::GfVec3f(size, size, size));
|
||||||
|
|
||||||
|
orientations.push_back(
|
||||||
|
pxr::GfQuath(pa->state.rot[0], pa->state.rot[1], pa->state.rot[2], pa->state.rot[3]));
|
||||||
|
|
||||||
|
velocities.push_back(pxr::GfVec3f(pa->state.vel[0], pa->state.vel[1], pa->state.vel[2]));
|
||||||
|
angularVelocities.push_back(
|
||||||
|
pxr::GfVec3f(pa->state.ave[0], pa->state.ave[1], pa->state.ave[2]));
|
||||||
|
|
||||||
|
if (pa->alive == PARS_DEAD || pa->alive == PARS_UNBORN) {
|
||||||
|
invisibleIndices.push_back(
|
||||||
|
p); // TODO, seems to be a USD point instance problem with freezing particles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: add simple particle child export
|
||||||
|
/*if(usd_export_context_.export_params.export_child_particles) {
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
protoIndicesAttr.Set(indices, timecode);
|
||||||
|
|
||||||
|
positionAttr.Set(points, timecode);
|
||||||
|
scaleAttr.Set(scales, timecode);
|
||||||
|
velAttr.Set(velocities, timecode);
|
||||||
|
angVelAttr.Set(angularVelocities, timecode);
|
||||||
|
orientationAttr.Set(orientations, timecode);
|
||||||
|
invisibleIdsAttr.Set(invisibleIndices, timecode);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && context.particle_system) {
|
||||||
|
auto prim = usd_pi.GetPrim();
|
||||||
|
write_id_properties(prim, context.particle_system->part->id, timecode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
37
source/blender/io/usd/intern/usd_writer_particle.h
Normal file
37
source/blender/io/usd/intern/usd_writer_particle.h
Normal 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__ */
|
178
source/blender/io/usd/intern/usd_writer_skel_root.cc
Normal file
178
source/blender/io/usd/intern/usd_writer_skel_root.cc
Normal 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 ¶ms)
|
||||||
|
{
|
||||||
|
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
|
45
source/blender/io/usd/intern/usd_writer_skel_root.h
Normal file
45
source/blender/io/usd/intern/usd_writer_skel_root.h
Normal 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 ¶ms);
|
||||||
|
|
||||||
|
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
|
313
source/blender/io/usd/intern/usd_writer_skinned_mesh.cc
Normal file
313
source/blender/io/usd/intern/usd_writer_skinned_mesh.cc
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
#include "usd_writer_skinned_mesh.h"
|
||||||
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
#include "usd_writer_armature.h"
|
||||||
|
|
||||||
|
#include <pxr/base/gf/matrix4d.h>
|
||||||
|
#include <pxr/base/gf/matrix4f.h>
|
||||||
|
#include <pxr/usd/usdGeom/mesh.h>
|
||||||
|
|
||||||
|
#include "BKE_armature.h"
|
||||||
|
#include "BKE_mesh.h"
|
||||||
|
#include "BKE_mesh_runtime.h"
|
||||||
|
#include "BKE_modifier.h"
|
||||||
|
#include "BKE_object.h"
|
||||||
|
|
||||||
|
#include "DNA_armature_types.h"
|
||||||
|
#include "DNA_mesh_types.h"
|
||||||
|
#include "DNA_meshdata_types.h"
|
||||||
|
#include "DNA_meta_types.h"
|
||||||
|
|
||||||
|
#include "ED_armature.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
bool is_skinned_mesh(Object *obj)
|
||||||
|
{
|
||||||
|
if (!(obj && obj->data)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj->type != OB_MESH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BKE_modifiers_findby_type(obj, eModifierType_Armature) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Object *get_armature_obj(Object *obj)
|
||||||
|
{
|
||||||
|
if (!(obj && obj->data)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj->type != OB_MESH) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArmatureModifierData *mod = reinterpret_cast<ArmatureModifierData *>(
|
||||||
|
BKE_modifiers_findby_type(obj, eModifierType_Armature));
|
||||||
|
|
||||||
|
return mod ? mod->object : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
USDSkinnedMeshWriter::USDSkinnedMeshWriter(const USDExporterContext &ctx) : USDMeshWriter(ctx)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDSkinnedMeshWriter::do_write(HierarchyContext &context)
|
||||||
|
{
|
||||||
|
Object *arm_obj = get_armature_obj(context.object);
|
||||||
|
|
||||||
|
if (!arm_obj) {
|
||||||
|
printf("WARNING: couldn't get armature object for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!arm_obj->data) {
|
||||||
|
printf("WARNING: couldn't get armature object data for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Before writing the mesh, we set the artmature to edit mode
|
||||||
|
* so the mesh is saved in its rest position. */
|
||||||
|
|
||||||
|
bArmature *arm = static_cast<bArmature *>(arm_obj->data);
|
||||||
|
|
||||||
|
bool is_edited = arm->edbo != nullptr;
|
||||||
|
|
||||||
|
if (!is_edited) {
|
||||||
|
ED_armature_to_edit(arm);
|
||||||
|
}
|
||||||
|
|
||||||
|
USDGenericMeshWriter::do_write(context);
|
||||||
|
|
||||||
|
if (!is_edited) {
|
||||||
|
ED_armature_edit_free(arm);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdStageRefPtr stage = usd_export_context_.stage;
|
||||||
|
pxr::UsdTimeCode timecode = get_export_time_code();
|
||||||
|
|
||||||
|
pxr::UsdPrim mesh_prim = stage->GetPrimAtPath(usd_export_context_.usd_path);
|
||||||
|
|
||||||
|
if (!mesh_prim.IsValid()) {
|
||||||
|
printf("WARNING: couldn't get valid mesh prim for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxr::UsdSkelBindingAPI usd_skel_api = pxr::UsdSkelBindingAPI::Apply(mesh_prim);
|
||||||
|
|
||||||
|
if (!usd_skel_api) {
|
||||||
|
printf("WARNING: couldn't apply UsdSkelBindingAPI to skinned mesh prim %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ID *arm_id = reinterpret_cast<ID *>(arm_obj->data);
|
||||||
|
|
||||||
|
std::string skel_path = usd_export_context_.hierarchy_iterator->get_object_export_path(arm_id);
|
||||||
|
|
||||||
|
if (skel_path.empty()) {
|
||||||
|
printf("WARNING: couldn't get USD skeleton path for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
|
||||||
|
skel_path = std::string(usd_export_context_.export_params.root_prim_path) + skel_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
usd_skel_api.CreateSkeletonRel().SetTargets(pxr::SdfPathVector({pxr::SdfPath(skel_path)}));
|
||||||
|
|
||||||
|
if (pxr::UsdAttribute geom_bind_attr = usd_skel_api.CreateGeomBindTransformAttr()) {
|
||||||
|
pxr::GfMatrix4f mat_world(context.matrix_world);
|
||||||
|
geom_bind_attr.Set(pxr::GfMatrix4d(mat_world));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("WARNING: couldn't create geom bind transform attribute for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> bone_names;
|
||||||
|
USDArmatureWriter::get_armature_bone_names(arm_obj, bone_names);
|
||||||
|
|
||||||
|
if (bone_names.empty()) {
|
||||||
|
printf("WARNING: no armature bones for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool needs_free = false;
|
||||||
|
Mesh *mesh = get_export_mesh(context.object, needs_free);
|
||||||
|
if (mesh == nullptr) {
|
||||||
|
printf("WARNING: couldn't get Blender mesh for skinned mesh %s\n",
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write_weights(context.object, mesh, usd_skel_api, bone_names);
|
||||||
|
|
||||||
|
if (needs_free) {
|
||||||
|
free_export_mesh(mesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USDSkinnedMeshWriter::write_weights(const Object *ob,
|
||||||
|
const Mesh *mesh,
|
||||||
|
const pxr::UsdSkelBindingAPI &skel_api,
|
||||||
|
const std::vector<std::string> &bone_names) const
|
||||||
|
{
|
||||||
|
if (!(skel_api && ob && mesh && mesh->dvert && mesh->totvert > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bone_names.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> group_to_bone_idx;
|
||||||
|
|
||||||
|
for (const bDeformGroup *def = (const bDeformGroup *)mesh->vertex_group_names.first; def;
|
||||||
|
def = def->next) {
|
||||||
|
|
||||||
|
int bone_idx = -1;
|
||||||
|
/* For now, n-squared search is acceptable. */
|
||||||
|
for (int i = 0; i < bone_names.size(); ++i) {
|
||||||
|
if (bone_names[i] == def->name) {
|
||||||
|
bone_idx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bone_idx == -1) {
|
||||||
|
printf("WARNING: deform group %s in skinned mesh %s doesn't match any bones\n",
|
||||||
|
def->name,
|
||||||
|
this->usd_export_context_.usd_path.GetString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
group_to_bone_idx.push_back(bone_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group_to_bone_idx.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int max_totweight = 1;
|
||||||
|
for (int i = 0; i < mesh->totvert; ++i) {
|
||||||
|
MDeformVert &vert = mesh->dvert[i];
|
||||||
|
if (vert.totweight > max_totweight) {
|
||||||
|
max_totweight = vert.totweight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const int ELEM_SIZE = max_totweight;
|
||||||
|
|
||||||
|
int num_points = mesh->totvert;
|
||||||
|
|
||||||
|
pxr::VtArray<int> joint_indices(num_points * ELEM_SIZE, 0);
|
||||||
|
pxr::VtArray<float> joint_weights(num_points * ELEM_SIZE, 0.0f);
|
||||||
|
|
||||||
|
/* Current offset into the indices and weights arrays. */
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
/* Record number of out of bounds vert group indices, for error reporting. */
|
||||||
|
int num_out_of_bounds = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < mesh->totvert; ++i) {
|
||||||
|
|
||||||
|
MDeformVert &vert = mesh->dvert[i];
|
||||||
|
|
||||||
|
/* Sum of the weights, for normalizing. */
|
||||||
|
float sum_weights = 0.0f;
|
||||||
|
|
||||||
|
for (int j = 0; j < ELEM_SIZE; ++j, ++offset) {
|
||||||
|
|
||||||
|
if (offset >= joint_indices.size()) {
|
||||||
|
printf("Programmer error: out of bounds joint indices array offset.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j >= vert.totweight) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int def_nr = static_cast<int>(vert.dw[j].def_nr);
|
||||||
|
|
||||||
|
/* This out of bounds check is necessary because MDeformVert.totweight can be
|
||||||
|
* larger than the number of bDeformGroup structs in Object.defbase. It appears to be
|
||||||
|
* a Blender bug that can cause this scenario. */
|
||||||
|
if (def_nr >= group_to_bone_idx.size()) {
|
||||||
|
++num_out_of_bounds;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bone_idx = group_to_bone_idx[def_nr];
|
||||||
|
|
||||||
|
if (bone_idx == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
joint_indices[offset] = bone_idx;
|
||||||
|
|
||||||
|
float w = vert.dw[j].weight;
|
||||||
|
|
||||||
|
joint_weights[offset] = w;
|
||||||
|
|
||||||
|
sum_weights += w;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sum_weights > .000001f) {
|
||||||
|
/* Run over the elements again to normalize the weights. */
|
||||||
|
float inv_sum_weights = 1.0f / sum_weights;
|
||||||
|
offset -= ELEM_SIZE;
|
||||||
|
for (int k = 0; k < ELEM_SIZE; ++k, ++offset) {
|
||||||
|
joint_weights[offset] *= inv_sum_weights;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num_out_of_bounds > 0) {
|
||||||
|
printf("WARNING: There were %d deform verts with out of bounds deform group numbers.\n",
|
||||||
|
num_out_of_bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
skel_api.CreateJointIndicesPrimvar(false, ELEM_SIZE).GetAttr().Set(joint_indices);
|
||||||
|
skel_api.CreateJointWeightsPrimvar(false, ELEM_SIZE).GetAttr().Set(joint_weights);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDSkinnedMeshWriter::is_supported(const HierarchyContext *context) const
|
||||||
|
{
|
||||||
|
return is_skinned_mesh(context->object) && USDGenericMeshWriter::is_supported(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDSkinnedMeshWriter::check_is_animated(const HierarchyContext & /*context*/) const
|
||||||
|
{
|
||||||
|
/* We assume that skinned meshes are never animated, as the source of
|
||||||
|
* any animation is the mesh's bound skeleton. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
48
source/blender/io/usd/intern/usd_writer_skinned_mesh.h
Normal file
48
source/blender/io/usd/intern/usd_writer_skinned_mesh.h
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation; either version 2
|
||||||
|
* of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
*
|
||||||
|
* The Original Code is Copyright (C) 2021 NVIDIA Corporation.
|
||||||
|
* All rights reserved.
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "usd_writer_mesh.h"
|
||||||
|
|
||||||
|
#include <pxr/usd/usdSkel/bindingAPI.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
bool is_skinned_mesh(Object *obj);
|
||||||
|
|
||||||
|
class USDSkinnedMeshWriter : public USDMeshWriter {
|
||||||
|
public:
|
||||||
|
USDSkinnedMeshWriter(const USDExporterContext &ctx);
|
||||||
|
|
||||||
|
virtual void do_write(HierarchyContext &context) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual bool is_supported(const HierarchyContext *context) const override;
|
||||||
|
virtual bool check_is_animated(const HierarchyContext &context) const override;
|
||||||
|
|
||||||
|
void write_weights(const Object *ob,
|
||||||
|
const Mesh *mesh,
|
||||||
|
const pxr::UsdSkelBindingAPI &skel_api,
|
||||||
|
const std::vector<std::string> &bone_names) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace blender::io::usd
|
@@ -4,32 +4,160 @@
|
|||||||
#include "usd_hierarchy_iterator.h"
|
#include "usd_hierarchy_iterator.h"
|
||||||
|
|
||||||
#include <pxr/base/gf/matrix4f.h>
|
#include <pxr/base/gf/matrix4f.h>
|
||||||
|
#include <pxr/base/gf/quaternion.h>
|
||||||
#include <pxr/usd/usdGeom/xform.h>
|
#include <pxr/usd/usdGeom/xform.h>
|
||||||
|
|
||||||
#include "BKE_object.h"
|
#include "BKE_object.h"
|
||||||
|
|
||||||
#include "BLI_math_matrix.h"
|
#include "BLI_math_matrix.h"
|
||||||
|
#include "BLI_math_rotation.h"
|
||||||
|
|
||||||
#include "DNA_layer_types.h"
|
#include "DNA_layer_types.h"
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
|
static const float UNIT_M4[4][4] = {
|
||||||
|
{1, 0, 0, 0},
|
||||||
|
{0, 1, 0, 0},
|
||||||
|
{0, 0, 1, 0},
|
||||||
|
{0, 0, 0, 1},
|
||||||
|
};
|
||||||
|
|
||||||
USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pxr::UsdGeomXformable USDTransformWriter::create_xformable() const
|
||||||
|
{
|
||||||
|
pxr::UsdGeomXform xform;
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_as_overs) {
|
||||||
|
// Override existing prim on stage
|
||||||
|
xform = pxr::UsdGeomXform(
|
||||||
|
usd_export_context_.stage->OverridePrim(usd_export_context_.usd_path));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If prim exists, cast to UsdGeomXform (Solves merge transform and shape issue for animated
|
||||||
|
// exports)
|
||||||
|
pxr::UsdPrim existing_prim = usd_export_context_.stage->GetPrimAtPath(
|
||||||
|
usd_export_context_.usd_path);
|
||||||
|
if (existing_prim.IsValid() && existing_prim.IsA<pxr::UsdGeomXform>()) {
|
||||||
|
xform = pxr::UsdGeomXform(existing_prim);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xform = pxr::UsdGeomXform::Define(usd_export_context_.stage, usd_export_context_.usd_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return xform;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDTransformWriter::should_apply_root_xform(const HierarchyContext &context) const
|
||||||
|
{
|
||||||
|
if (!(usd_export_context_.export_params.convert_orientation ||
|
||||||
|
usd_export_context_.export_params.convert_to_cm)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen(usd_export_context_.export_params.root_prim_path) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.export_parent != nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.use_instancing &&
|
||||||
|
usd_export_context_.hierarchy_iterator->is_prototype(context.object)) {
|
||||||
|
/* This is an instancing prototype. */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USDTransformWriter::is_proto_root(const HierarchyContext &context) const
|
||||||
|
{
|
||||||
|
if (!usd_export_context_.export_params.use_instancing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool is_proto = usd_export_context_.hierarchy_iterator->is_prototype(context.object);
|
||||||
|
bool parent_is_proto = context.export_parent &&
|
||||||
|
usd_export_context_.hierarchy_iterator->is_prototype(
|
||||||
|
context.export_parent);
|
||||||
|
|
||||||
|
return is_proto && !parent_is_proto;
|
||||||
|
}
|
||||||
|
|
||||||
void USDTransformWriter::do_write(HierarchyContext &context)
|
void USDTransformWriter::do_write(HierarchyContext &context)
|
||||||
{
|
{
|
||||||
float parent_relative_matrix[4][4]; /* The object matrix relative to the parent. */
|
pxr::UsdGeomXformable xform = create_xformable();
|
||||||
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
|
|
||||||
|
|
||||||
/* Write the transform relative to the parent. */
|
if (!xform) {
|
||||||
pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage,
|
printf("INTERNAL ERROR: USDTransformWriter: couldn't create xformable.\n");
|
||||||
usd_export_context_.usd_path);
|
return;
|
||||||
if (!xformOp_) {
|
}
|
||||||
xformOp_ = xform.AddTransformOp();
|
|
||||||
|
if (usd_export_context_.export_params.export_transforms) {
|
||||||
|
float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
|
||||||
|
|
||||||
|
// TODO(bjs): This is inefficient checking for every transform. should be moved elsewhere
|
||||||
|
if (should_apply_root_xform(context)) {
|
||||||
|
float matrix_world[4][4];
|
||||||
|
copy_m4_m4(matrix_world, context.matrix_world);
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.convert_orientation) {
|
||||||
|
float mrot[3][3];
|
||||||
|
float mat[4][4];
|
||||||
|
mat3_from_axis_conversion(USD_GLOBAL_FORWARD_Y,
|
||||||
|
USD_GLOBAL_UP_Z,
|
||||||
|
usd_export_context_.export_params.forward_axis,
|
||||||
|
usd_export_context_.export_params.up_axis,
|
||||||
|
mrot);
|
||||||
|
transpose_m3(mrot);
|
||||||
|
copy_m4_m3(mat, mrot);
|
||||||
|
mul_m4_m4m4(matrix_world, mat, context.matrix_world);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.convert_to_cm) {
|
||||||
|
float scale_mat[4][4];
|
||||||
|
scale_m4_fl(scale_mat, 100.0f);
|
||||||
|
mul_m4_m4m4(matrix_world, scale_mat, matrix_world);
|
||||||
|
}
|
||||||
|
|
||||||
|
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, matrix_world);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
|
||||||
|
|
||||||
|
// USD Xforms are by default set with an identity transform.
|
||||||
|
// This check ensures transforms of non-identity are authored
|
||||||
|
// preventing usd composition collisions up and down stream.
|
||||||
|
if (usd_export_context_.export_params.export_identity_transforms ||
|
||||||
|
!compare_m4m4(parent_relative_matrix, UNIT_M4, 0.000000001f)) {
|
||||||
|
set_xform_ops(parent_relative_matrix, xform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.export_custom_properties && context.object) {
|
||||||
|
auto prim = xform.GetPrim();
|
||||||
|
write_id_properties(prim, context.object->id, get_export_time_code());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usd_export_context_.export_params.use_instancing) {
|
||||||
|
|
||||||
|
if (context.is_instance()) {
|
||||||
|
mark_as_instance(context, xform.GetPrim());
|
||||||
|
/* Explicitly set visibility, since the prototype might be invisible. */
|
||||||
|
xform.GetVisibilityAttr().Set(pxr::UsdGeomTokens->inherited);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (is_proto_root(context)) {
|
||||||
|
/* TODO(makowalski): perhaps making prototypes invisible should be optional. */
|
||||||
|
xform.GetVisibilityAttr().Set(pxr::UsdGeomTokens->invisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
xformOp_.Set(pxr::GfMatrix4d(parent_relative_matrix), get_export_time_code());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
|
bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
|
||||||
@@ -40,10 +168,79 @@ bool USDTransformWriter::check_is_animated(const HierarchyContext &context) cons
|
|||||||
* depsgraph whether this object instance has a time source. */
|
* depsgraph whether this object instance has a time source. */
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (check_has_physics(context)) {
|
if (check_has_physics(context)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: This fails for a specific set of drivers and rig setups...
|
||||||
|
// Setting 'context.animation_check_include_parent' to true fixed it...
|
||||||
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
|
return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void USDTransformWriter::set_xform_ops(float xf_matrix[4][4], pxr::UsdGeomXformable &xf)
|
||||||
|
{
|
||||||
|
if (!xf) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
eUSDXformOpMode xfOpMode = usd_export_context_.export_params.xform_op_mode;
|
||||||
|
|
||||||
|
if (xformOps_.empty()) {
|
||||||
|
switch (xfOpMode) {
|
||||||
|
case USD_XFORM_OP_SRT:
|
||||||
|
xformOps_.push_back(xf.AddTranslateOp());
|
||||||
|
xformOps_.push_back(xf.AddRotateXYZOp());
|
||||||
|
xformOps_.push_back(xf.AddScaleOp());
|
||||||
|
|
||||||
|
break;
|
||||||
|
case USD_XFORM_OP_SOT:
|
||||||
|
xformOps_.push_back(xf.AddTranslateOp());
|
||||||
|
xformOps_.push_back(xf.AddOrientOp());
|
||||||
|
xformOps_.push_back(xf.AddScaleOp());
|
||||||
|
break;
|
||||||
|
case USD_XFORM_OP_MAT:
|
||||||
|
xformOps_.push_back(xf.AddTransformOp());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printf("Warning: unknown XformOp type\n");
|
||||||
|
xformOps_.push_back(xf.AddTransformOp());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xformOps_.empty()) {
|
||||||
|
/* Shouldn't happen. */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xformOps_.size() == 1) {
|
||||||
|
xformOps_[0].Set(pxr::GfMatrix4d(xf_matrix), get_export_time_code());
|
||||||
|
}
|
||||||
|
else if (xformOps_.size() == 3) {
|
||||||
|
|
||||||
|
float loc[3];
|
||||||
|
float quat[4];
|
||||||
|
float scale[3];
|
||||||
|
|
||||||
|
mat4_decompose(loc, quat, scale, xf_matrix);
|
||||||
|
|
||||||
|
if (xfOpMode == USD_XFORM_OP_SRT) {
|
||||||
|
float rot[3];
|
||||||
|
quat_to_eul(rot, quat);
|
||||||
|
rot[0] *= 180.0 / M_PI;
|
||||||
|
rot[1] *= 180.0 / M_PI;
|
||||||
|
rot[2] *= 180.0 / M_PI;
|
||||||
|
xformOps_[0].Set(pxr::GfVec3d(loc), get_export_time_code());
|
||||||
|
xformOps_[1].Set(pxr::GfVec3f(rot), get_export_time_code());
|
||||||
|
xformOps_[2].Set(pxr::GfVec3f(scale), get_export_time_code());
|
||||||
|
}
|
||||||
|
else if (xfOpMode == USD_XFORM_OP_SOT) {
|
||||||
|
xformOps_[0].Set(pxr::GfVec3d(loc), get_export_time_code());
|
||||||
|
xformOps_[1].Set(pxr::GfQuatf(quat[0], quat[1], quat[2], quat[3]), get_export_time_code());
|
||||||
|
xformOps_[2].Set(pxr::GfVec3f(scale), get_export_time_code());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -6,11 +6,13 @@
|
|||||||
|
|
||||||
#include <pxr/usd/usdGeom/xform.h>
|
#include <pxr/usd/usdGeom/xform.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace blender::io::usd {
|
namespace blender::io::usd {
|
||||||
|
|
||||||
class USDTransformWriter : public USDAbstractWriter {
|
class USDTransformWriter : public USDAbstractWriter {
|
||||||
private:
|
private:
|
||||||
pxr::UsdGeomXformOp xformOp_;
|
std::vector<pxr::UsdGeomXformOp> xformOps_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
USDTransformWriter(const USDExporterContext &ctx);
|
USDTransformWriter(const USDExporterContext &ctx);
|
||||||
@@ -18,6 +20,16 @@ class USDTransformWriter : public USDAbstractWriter {
|
|||||||
protected:
|
protected:
|
||||||
void do_write(HierarchyContext &context) override;
|
void do_write(HierarchyContext &context) override;
|
||||||
bool check_is_animated(const HierarchyContext &context) const override;
|
bool check_is_animated(const HierarchyContext &context) const override;
|
||||||
|
|
||||||
|
void set_xform_ops(float parent_relative_matrix[4][4], pxr::UsdGeomXformable &xf);
|
||||||
|
|
||||||
|
/* Return true if the given context is the root of a protoype. */
|
||||||
|
bool is_proto_root(const HierarchyContext &context) const;
|
||||||
|
|
||||||
|
/* Subclasses may override this to create prims other than UsdGeomXform. */
|
||||||
|
virtual pxr::UsdGeomXformable create_xformable() const;
|
||||||
|
|
||||||
|
bool should_apply_root_xform(const HierarchyContext &context) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace blender::io::usd
|
} // namespace blender::io::usd
|
||||||
|
@@ -9,26 +9,115 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct CacheArchiveHandle;
|
|
||||||
struct CacheFile;
|
|
||||||
struct CacheReader;
|
|
||||||
struct Object;
|
|
||||||
struct bContext;
|
struct bContext;
|
||||||
|
struct Object;
|
||||||
|
struct CacheArchiveHandle;
|
||||||
|
struct CacheReader;
|
||||||
|
struct CacheFile;
|
||||||
|
|
||||||
|
typedef enum USD_global_forward_axis {
|
||||||
|
USD_GLOBAL_FORWARD_X = 0,
|
||||||
|
USD_GLOBAL_FORWARD_Y = 1,
|
||||||
|
USD_GLOBAL_FORWARD_Z = 2,
|
||||||
|
USD_GLOBAL_FORWARD_MINUS_X = 3,
|
||||||
|
USD_GLOBAL_FORWARD_MINUS_Y = 4,
|
||||||
|
USD_GLOBAL_FORWARD_MINUS_Z = 5
|
||||||
|
} USD_global_forward_axis;
|
||||||
|
|
||||||
|
typedef enum USD_global_up_axis {
|
||||||
|
USD_GLOBAL_UP_X = 0,
|
||||||
|
USD_GLOBAL_UP_Y = 1,
|
||||||
|
USD_GLOBAL_UP_Z = 2,
|
||||||
|
USD_GLOBAL_UP_MINUS_X = 3,
|
||||||
|
USD_GLOBAL_UP_MINUS_Y = 4,
|
||||||
|
USD_GLOBAL_UP_MINUS_Z = 5
|
||||||
|
} USD_global_up_axis;
|
||||||
|
|
||||||
|
typedef enum eUSDImportShadersMode {
|
||||||
|
USD_IMPORT_SHADERS_NONE = 0,
|
||||||
|
USD_IMPORT_USD_PREVIEW_SURFACE = 1,
|
||||||
|
USD_IMPORT_MDL = 2,
|
||||||
|
} eUSDImportShadersMode;
|
||||||
|
|
||||||
|
typedef enum eUSDXformOpMode {
|
||||||
|
USD_XFORM_OP_SRT = 0,
|
||||||
|
USD_XFORM_OP_SOT = 1,
|
||||||
|
USD_XFORM_OP_MAT = 2,
|
||||||
|
} eUSDXformOpMode;
|
||||||
|
|
||||||
|
static const USD_global_forward_axis USD_DEFAULT_FORWARD = USD_GLOBAL_FORWARD_MINUS_Z;
|
||||||
|
static const USD_global_up_axis USD_DEFAULT_UP = USD_GLOBAL_UP_Y;
|
||||||
|
|
||||||
|
typedef enum eUSDMtlNameCollisionMode {
|
||||||
|
USD_MTL_NAME_COLLISION_MODIFY = 0,
|
||||||
|
USD_MTL_NAME_COLLISION_SKIP = 1,
|
||||||
|
} eUSDMtlNameCollisionMode;
|
||||||
|
|
||||||
|
typedef enum eUSDAttrImportMode {
|
||||||
|
USD_ATTR_IMPORT_NONE = 0,
|
||||||
|
USD_ATTR_IMPORT_USER = 1,
|
||||||
|
USD_ATTR_IMPORT_ALL = 2,
|
||||||
|
} eUSDAttrImportMode;
|
||||||
|
|
||||||
struct USDExportParams {
|
struct USDExportParams {
|
||||||
|
double frame_start;
|
||||||
|
double frame_end;
|
||||||
|
|
||||||
bool export_animation;
|
bool export_animation;
|
||||||
bool export_hair;
|
bool export_hair;
|
||||||
|
bool export_vertices;
|
||||||
|
bool export_vertex_colors;
|
||||||
|
bool export_vertex_groups;
|
||||||
|
bool export_face_maps;
|
||||||
bool export_uvmaps;
|
bool export_uvmaps;
|
||||||
bool export_normals;
|
bool export_normals;
|
||||||
|
bool export_transforms;
|
||||||
bool export_materials;
|
bool export_materials;
|
||||||
|
bool export_meshes;
|
||||||
|
bool export_lights;
|
||||||
|
bool export_cameras;
|
||||||
|
bool export_curves;
|
||||||
|
bool export_particles;
|
||||||
bool selected_objects_only;
|
bool selected_objects_only;
|
||||||
bool visible_objects_only;
|
bool visible_objects_only;
|
||||||
bool use_instancing;
|
bool use_instancing;
|
||||||
enum eEvaluationMode evaluation_mode;
|
enum eEvaluationMode evaluation_mode;
|
||||||
|
char *default_prim_path; // USD Stage Default Primitive Path
|
||||||
|
char *root_prim_path; // Root path to encapsulate blender stage under. e.g. /shot
|
||||||
|
char *material_prim_path; // Prim path to store all generated USDShade, shaders under e.g.
|
||||||
|
// /materials
|
||||||
bool generate_preview_surface;
|
bool generate_preview_surface;
|
||||||
|
bool convert_uv_to_st;
|
||||||
|
bool convert_orientation;
|
||||||
|
enum USD_global_forward_axis forward_axis;
|
||||||
|
enum USD_global_up_axis up_axis;
|
||||||
|
bool export_child_particles;
|
||||||
|
bool export_as_overs;
|
||||||
|
bool merge_transform_and_shape;
|
||||||
|
bool export_custom_properties;
|
||||||
|
bool add_properties_namespace;
|
||||||
|
bool export_identity_transforms;
|
||||||
|
bool apply_subdiv;
|
||||||
|
bool author_blender_name;
|
||||||
|
bool vertex_data_as_face_varying;
|
||||||
|
float frame_step;
|
||||||
|
bool override_shutter;
|
||||||
|
double shutter_open;
|
||||||
|
double shutter_close;
|
||||||
bool export_textures;
|
bool export_textures;
|
||||||
bool overwrite_textures;
|
|
||||||
bool relative_texture_paths;
|
bool relative_texture_paths;
|
||||||
|
bool backward_compatible;
|
||||||
|
float light_intensity_scale;
|
||||||
|
bool generate_mdl;
|
||||||
|
bool convert_to_cm;
|
||||||
|
bool convert_light_to_nits;
|
||||||
|
bool scale_light_radius;
|
||||||
|
bool convert_world_material;
|
||||||
|
bool generate_cycles_shaders;
|
||||||
|
bool export_armatures;
|
||||||
|
eUSDXformOpMode xform_op_mode;
|
||||||
|
bool fix_skel_root;
|
||||||
|
bool overwrite_textures;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct USDImportParams {
|
struct USDImportParams {
|
||||||
@@ -54,9 +143,15 @@ struct USDImportParams {
|
|||||||
bool import_render;
|
bool import_render;
|
||||||
bool import_visible_only;
|
bool import_visible_only;
|
||||||
bool use_instancing;
|
bool use_instancing;
|
||||||
bool import_usd_preview;
|
eUSDImportShadersMode import_shaders_mode;
|
||||||
bool set_material_blend;
|
bool set_material_blend;
|
||||||
float light_intensity_scale;
|
float light_intensity_scale;
|
||||||
|
bool apply_unit_conversion_scale;
|
||||||
|
bool convert_light_from_nits;
|
||||||
|
bool scale_light_radius;
|
||||||
|
bool create_background_shader;
|
||||||
|
eUSDMtlNameCollisionMode mtl_name_collision_mode;
|
||||||
|
eUSDAttrImportMode attr_import_mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* The USD_export takes a as_background_job parameter, and returns a boolean.
|
/* The USD_export takes a as_background_job parameter, and returns a boolean.
|
||||||
@@ -80,6 +175,8 @@ bool USD_import(struct bContext *C,
|
|||||||
|
|
||||||
int USD_get_version(void);
|
int USD_get_version(void);
|
||||||
|
|
||||||
|
bool USD_umm_module_loaded(void);
|
||||||
|
|
||||||
/* USD Import and Mesh Cache interface. */
|
/* USD Import and Mesh Cache interface. */
|
||||||
|
|
||||||
struct CacheArchiveHandle *USD_create_handle(struct Main *bmain,
|
struct CacheArchiveHandle *USD_create_handle(struct Main *bmain,
|
||||||
|
Reference in New Issue
Block a user