diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e98028f..e98dc768 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.18.0-preview] - 2023-08-01 + +### Added +* feat: Blender 3.5 and Blender 3.6.2 support +* feat: Improved instancing support + +### Changed +* change: instances are now sent in a way that allows the same object to be instanced on multiple parents and keep that hierarchy in Unity + +### Fixed +* fix: various instancing fixes + ## [0.17.1-preview] - 2023-06-13 ### Added diff --git a/Documentation~/index.md b/Documentation~/index.md index 83740ff0..40e7e47c 100644 --- a/Documentation~/index.md +++ b/Documentation~/index.md @@ -56,6 +56,8 @@ This allows devs to immediately see how things will look in-game while modelling | Blender 3.2 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Blender 3.3 | :white_check_mark: | :white_check_mark: | :white_check_mark: | | Blender 3.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Blender 3.5 | :white_check_mark: | :x: | :x: | +| Blender 3.6.2 | :white_check_mark: | :x: | :x: | Notes: * :white_check_mark: : Supported diff --git a/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip b/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip index a6c90a93..924511ed 100644 Binary files a/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip and b/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip differ diff --git a/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip.meta b/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip.meta index 85e06612..15797585 100644 --- a/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip.meta +++ b/Editor/Plugins/UnityMeshSync_3DSMAX_Windows.zip.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 39d04d20b9cbc094d8ac287776ffca5f +guid: f166da8225e7c2748925dd8c11ec90df DefaultImporter: externalObjects: {} userData: diff --git a/Editor/Plugins/UnityMeshSync_Blender_Linux.zip b/Editor/Plugins/UnityMeshSync_Blender_Linux.zip deleted file mode 100644 index 2b6063db..00000000 Binary files a/Editor/Plugins/UnityMeshSync_Blender_Linux.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_Blender_Linux.zip.meta b/Editor/Plugins/UnityMeshSync_Blender_Linux.zip.meta deleted file mode 100644 index 63444764..00000000 --- a/Editor/Plugins/UnityMeshSync_Blender_Linux.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 30ea5524ad294234ea44961f3cc08a8a -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Plugins/UnityMeshSync_Blender_Mac.zip b/Editor/Plugins/UnityMeshSync_Blender_Mac.zip deleted file mode 100644 index 0ba8baf3..00000000 Binary files a/Editor/Plugins/UnityMeshSync_Blender_Mac.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_Blender_Mac.zip.meta b/Editor/Plugins/UnityMeshSync_Blender_Mac.zip.meta deleted file mode 100644 index 9a21c726..00000000 --- a/Editor/Plugins/UnityMeshSync_Blender_Mac.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 9291eb3457751f24fa552fcbab8ee527 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Plugins/UnityMeshSync_Blender_Windows.zip b/Editor/Plugins/UnityMeshSync_Blender_Windows.zip index 66e22624..e19c613f 100644 Binary files a/Editor/Plugins/UnityMeshSync_Blender_Windows.zip and b/Editor/Plugins/UnityMeshSync_Blender_Windows.zip differ diff --git a/Editor/Plugins/UnityMeshSync_Blender_Windows.zip.meta b/Editor/Plugins/UnityMeshSync_Blender_Windows.zip.meta index f4aca6de..c473fd55 100644 --- a/Editor/Plugins/UnityMeshSync_Blender_Windows.zip.meta +++ b/Editor/Plugins/UnityMeshSync_Blender_Windows.zip.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 5ee9a191f80f1e44a9bc645d30c9eada +guid: b2c03cd30f36b1c4f93f84f6097039dd DefaultImporter: externalObjects: {} userData: diff --git a/Editor/Plugins/UnityMeshSync_Maya_Linux.zip b/Editor/Plugins/UnityMeshSync_Maya_Linux.zip deleted file mode 100644 index 80c69185..00000000 Binary files a/Editor/Plugins/UnityMeshSync_Maya_Linux.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_Maya_Linux.zip.meta b/Editor/Plugins/UnityMeshSync_Maya_Linux.zip.meta deleted file mode 100644 index b92cc8bc..00000000 --- a/Editor/Plugins/UnityMeshSync_Maya_Linux.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 9bc92c39e594c094592a8fd3d82a5d81 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Plugins/UnityMeshSync_Maya_Mac.zip b/Editor/Plugins/UnityMeshSync_Maya_Mac.zip deleted file mode 100644 index 180debc1..00000000 Binary files a/Editor/Plugins/UnityMeshSync_Maya_Mac.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_Maya_Mac.zip.meta b/Editor/Plugins/UnityMeshSync_Maya_Mac.zip.meta deleted file mode 100644 index 97b81e2a..00000000 --- a/Editor/Plugins/UnityMeshSync_Maya_Mac.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 5c85557fb38fc044cb1fb5885f63e0a7 -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Plugins/UnityMeshSync_Maya_Windows.zip b/Editor/Plugins/UnityMeshSync_Maya_Windows.zip index 307142b4..b5ce71f3 100644 Binary files a/Editor/Plugins/UnityMeshSync_Maya_Windows.zip and b/Editor/Plugins/UnityMeshSync_Maya_Windows.zip differ diff --git a/Editor/Plugins/UnityMeshSync_Maya_Windows.zip.meta b/Editor/Plugins/UnityMeshSync_Maya_Windows.zip.meta index 58d8b97f..5a91d32a 100644 --- a/Editor/Plugins/UnityMeshSync_Maya_Windows.zip.meta +++ b/Editor/Plugins/UnityMeshSync_Maya_Windows.zip.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7a342c35f03183a4495b6dace9e99cd9 +guid: 39c127215d454f84694ffd205798c158 DefaultImporter: externalObjects: {} userData: diff --git a/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip b/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip deleted file mode 100644 index 55abeb17..00000000 Binary files a/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip.meta b/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip.meta deleted file mode 100644 index 8da3171e..00000000 --- a/Editor/Plugins/UnityMeshSync_MotionBuilder_Linux.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 73236ad1ab25aab439561937f083488d -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip b/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip deleted file mode 100644 index 264c7fc1..00000000 Binary files a/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip and /dev/null differ diff --git a/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip.meta b/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip.meta deleted file mode 100644 index 22e608ba..00000000 --- a/Editor/Plugins/UnityMeshSync_MotionBuilder_Windows.zip.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: ec35c7b800a8fe44a91a08a2af4d691d -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/CHANGELOG.md b/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/CHANGELOG.md index 9e98028f..e98dc768 100644 --- a/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/CHANGELOG.md +++ b/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [0.18.0-preview] - 2023-08-01 + +### Added +* feat: Blender 3.5 and Blender 3.6.2 support +* feat: Improved instancing support + +### Changed +* change: instances are now sent in a way that allows the same object to be instanced on multiple parents and keep that hierarchy in Unity + +### Fixed +* fix: various instancing fixes + ## [0.17.1-preview] - 2023-06-13 ### Added diff --git a/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/package.json b/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/package.json index 08e37a53..82e69c1e 100644 --- a/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/package.json +++ b/MeshSyncDCCPlugins~/Packages/com.unity.meshsync-dcc-plugins/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.meshsync.dcc-plugins", "displayName":"MeshSync DCC Plugins", - "version": "0.17.1-preview", + "version": "0.18.0-preview", "unity": "2020.3", "description": "This package contains plugin binaries of DCC tools for using MeshSync, which is another package for synchronizing meshes/models editing in DCC tools into Unity in real time.", "dependencies": { diff --git a/Plugins~/Build/VsDevCmd_2017.bat b/Plugins~/Build/VsDevCmd_2022.bat similarity index 57% rename from Plugins~/Build/VsDevCmd_2017.bat rename to Plugins~/Build/VsDevCmd_2022.bat index af2a012b..c8e13994 100644 --- a/Plugins~/Build/VsDevCmd_2017.bat +++ b/Plugins~/Build/VsDevCmd_2022.bat @@ -1,6 +1,7 @@ -for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" /version [15.0^,16^) /property installationPath`) do ( +for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" /version 17^ /property installationPath`) do ( set VSDIR=%%i echo %%i ) +set VSCMD_DEBUG=True call "%VSDIR%\Common7\Tools\VsDevCmd.bat" cd %~dp0 diff --git a/Plugins~/Build/make_meshsync_dcc_plugin.bat b/Plugins~/Build/make_meshsync_dcc_plugin.bat index 223f53ed..2389ee36 100644 --- a/Plugins~/Build/make_meshsync_dcc_plugin.bat +++ b/Plugins~/Build/make_meshsync_dcc_plugin.bat @@ -22,7 +22,7 @@ IF %ARG_COUNT% GEQ 2 ( SET CMAKE_BUILD_ARGS=%* ) -SET BUILD_SYSTEM=-G "Visual Studio 15 2017" -A x64 +SET BUILD_SYSTEM=-G "Visual Studio 17 2022" -A x64 ECHO cmake build Arguments: %CMAKE_BUILD_ARGS% cmake -UBUILD_*_PLUGIN -UBUILD_*_ALL -DMESHSYNC_VER:STRING=%MESHSYNC_VER% %CMAKE_BUILD_ARGS% %BUILD_SYSTEM% .. diff --git a/Plugins~/CMakeLists.txt b/Plugins~/CMakeLists.txt index 4212f884..519619fc 100644 --- a/Plugins~/CMakeLists.txt +++ b/Plugins~/CMakeLists.txt @@ -146,6 +146,8 @@ list(APPEND BLENDER_VERSIONS BLENDER_3.2.0 BLENDER_3.3.0 BLENDER_3.4.0 + BLENDER_3.5.0 + BLENDER_3.6.2 ) if(BUILD_BLENDER_ALL) diff --git a/Plugins~/Docs/en/BuildDCCPlugins.md b/Plugins~/Docs/en/BuildDCCPlugins.md index 2b2296e4..28e4b0b8 100644 --- a/Plugins~/Docs/en/BuildDCCPlugins.md +++ b/Plugins~/Docs/en/BuildDCCPlugins.md @@ -15,19 +15,19 @@ Make sure to choose one of the "Add CMake to the System PATH ..." options as shown below. ![CMakeInstallation](../Images/CMakeInstallation.png) -1. Install Visual Studio 2017, together with the following components: +1. Install Visual Studio 2022, together with the following components: * Windows 8.1 SDK * Windows Universal CRT SDK 1. Install git. For example: [SourceTree](https://www.sourcetreeapp.com/) 1. Build [Poco](https://pocoproject.org) (static libraries). * Download [Poco 1.10.1](https://github.com/pocoproject/poco/archive/poco-1.10.1-release.zip) and extract the file in a folder. - * Start "Developer Command Prompt for VS 2017" and go to where Poco was extracted. + * Start "Developer Command Prompt for VS 2022" and go to where Poco was extracted. * Execute the following in the command prompt: ``` $ mkdir cmake-build $ cd cmake-build - $ cmake .. -DBUILD_SHARED_LIBS=OFF -G "Visual Studio 15 2017" -A x64 + $ cmake .. -DBUILD_SHARED_LIBS=OFF -G "Visual Studio 17 2022" -A x64 $ cmake --build . --config Release && cmake --build . --config Debug ``` @@ -41,7 +41,7 @@ ### Build Steps (Win) -Start "Developer Command Prompt for VS 2017" and execute the following: +Start "Developer Command Prompt for VS 2022" and execute the following: ``` $ git clone https://github.com/Unity-Technologies/MeshSyncDCCPlugins @@ -57,9 +57,9 @@ $ cmake -DBUILD_TYPE=Release -P cmake_install.cmake * `[optional_arguments]` See [MakeOptionalArguments](MakeOptionalArguments.md) for more details. -> For a regular "Command Prompt", there is a script: *VsDevCmd_2017.bat* +> For a regular "Command Prompt", there is a script: *VsDevCmd_2022.bat* > under the *Build* folder, which if executed, will turn the prompt into a -> "Developer Command Prompt for VS 2017". +> "Developer Command Prompt for VS 2022". #### Notes diff --git a/Plugins~/Docs/en/MakeOptionalArguments.md b/Plugins~/Docs/en/MakeOptionalArguments.md index 06383721..273f57e8 100644 --- a/Plugins~/Docs/en/MakeOptionalArguments.md +++ b/Plugins~/Docs/en/MakeOptionalArguments.md @@ -46,6 +46,7 @@ with commands described in the following sections. * Blender 3.2.0 : `-DBUILD_BLENDER_3.2.0_PLUGIN=ON` * Blender 3.3.0 : `-DBUILD_BLENDER_3.3.0_PLUGIN=ON` * Blender 3.4.0 : `-DBUILD_BLENDER_3.4.0_PLUGIN=ON` +* Blender 3.5.0 : `-DBUILD_BLENDER_3.5.0_PLUGIN=ON` ## Modo diff --git a/Plugins~/Src/MeshSyncClient/Include/MeshSyncClient/msInstancesManager.h b/Plugins~/Src/MeshSyncClient/Include/MeshSyncClient/msInstancesManager.h index a393563c..df1f24b4 100644 --- a/Plugins~/Src/MeshSyncClient/Include/MeshSyncClient/msInstancesManager.h +++ b/Plugins~/Src/MeshSyncClient/Include/MeshSyncClient/msInstancesManager.h @@ -15,9 +15,10 @@ struct InstancesManagerRecord { bool dirtyInstances = false; bool dirtyMesh = false; - InstanceInfoPtr instances = nullptr; TransformPtr entity = nullptr; bool updated = false; + std::map updatedParents; + std::map> instancesPerParent; }; /// @@ -74,6 +75,8 @@ class InstancesManager : public TransformManager /// void eraseStaleEntities() override; + bool erase(const std::string& path); + bool needsToApplyMirrorModifier() override { return false; } }; diff --git a/Plugins~/Src/MeshSyncClient/msInstancesManager.cpp b/Plugins~/Src/MeshSyncClient/msInstancesManager.cpp index 06dff7a9..72bbfcac 100644 --- a/Plugins~/Src/MeshSyncClient/msInstancesManager.cpp +++ b/Plugins~/Src/MeshSyncClient/msInstancesManager.cpp @@ -15,15 +15,25 @@ namespace ms { } return ret; } + vector InstancesManager::getDirtyInstances() { vector ret; for (auto& p : m_records) { InstancesManagerRecord& r = p.second; if (r.dirtyInstances) { - ret.push_back(r.instances); + for (auto& instances : r.instancesPerParent) + { + ret.insert(ret.end(), instances.second.begin(), instances.second.end()); + } } } + + // Sort by parent to ensure children are grouped correctly by the server: + sort(ret.begin(), ret.end(), [](const InstanceInfoPtr& a, const InstanceInfoPtr& b) { + return a->parent_path < b->parent_path; + }); + return ret; } @@ -31,6 +41,16 @@ namespace ms { return m_deleted; } + bool InstancesManager::erase(const std::string& path) { + auto it = m_records.find(path); + if (it != m_records.end()) { + m_records.erase(it); + m_deleted.push_back({ path, InvalidID }); + return true; + } + return false; + } + void InstancesManager::clearDirtyFlags() { for (auto& p : m_records) { @@ -38,6 +58,7 @@ namespace ms { r.dirtyInstances = false; r.dirtyMesh = false; r.updated = false; + r.updatedParents.clear(); } m_deleted.clear(); @@ -60,13 +81,25 @@ namespace ms { void InstancesManager::add(InstanceInfoPtr info) { auto& rec = lockAndGet(info->path); + + for (int i = 0; i < rec.instancesPerParent[info->parent_path].size(); ++i) + { + const auto& instance = rec.instancesPerParent[info->parent_path][i]; - if (m_always_mark_dirty || rec.instances == nullptr) { - rec.dirtyInstances = true; + if (instance->parent_path == info->parent_path) + { + // Remove instance from the list, the new one will be added below: + rec.instancesPerParent[info->parent_path].erase(rec.instancesPerParent[info->parent_path].begin() + i); + + break; + } } - rec.updated = true; - rec.instances = info; + // instanceInfos need to be sent every update because it's how the server knows which instances are still alive: + rec.dirtyInstances = true; + + rec.instancesPerParent[info->parent_path].push_back(info); + rec.updatedParents[info->parent_path] = true; } void InstancesManager::clear() @@ -85,13 +118,34 @@ namespace ms { { for (auto it = m_records.begin(); it != m_records.end(); ) { if (!it->second.updated) { - if (it->second.instances) { - m_deleted.push_back(it->second.instances->getIdentifier()); - } m_records.erase(it++); } else + { + // Remove records to parents that were not updated: + auto& parentMap = it->second.instancesPerParent; + auto& updatedParents = it->second.updatedParents; + + for (auto parentMap_it = parentMap.cbegin(), nextParentMap_it = parentMap_it; + parentMap_it != parentMap.cend(); + parentMap_it = nextParentMap_it) + { + ++nextParentMap_it; + + auto& instances = parentMap_it->second; + + for (auto& instance : instances) + { + auto& parentPath = instance->parent_path; + if (!updatedParents[parentPath]) + { + parentMap.erase(parentPath); + } + } + } + ++it; + } } } } diff --git a/Plugins~/Src/MeshSyncClient/msPropertyManager.cpp b/Plugins~/Src/MeshSyncClient/msPropertyManager.cpp index 18f7b43e..ceea7b69 100644 --- a/Plugins~/Src/MeshSyncClient/msPropertyManager.cpp +++ b/Plugins~/Src/MeshSyncClient/msPropertyManager.cpp @@ -10,6 +10,7 @@ namespace ms { std::unique_lock lock(m_mutex); vector ret; + ret.reserve(m_records.size()); for (const auto& [key, propertyInfo] : m_records) { ret.push_back(propertyInfo); diff --git a/Plugins~/Src/MeshSyncClientBlender/BlenderPyObjects/BlenderPyDepsgraphObjectInstance.h b/Plugins~/Src/MeshSyncClientBlender/BlenderPyObjects/BlenderPyDepsgraphObjectInstance.h index 4a58b272..9453ff09 100644 --- a/Plugins~/Src/MeshSyncClientBlender/BlenderPyObjects/BlenderPyDepsgraphObjectInstance.h +++ b/Plugins~/Src/MeshSyncClientBlender/BlenderPyObjects/BlenderPyDepsgraphObjectInstance.h @@ -15,7 +15,7 @@ namespace blender void world_matrix(mu::float4x4* world_matrix); Object* parent(); Object* object(); - private: + public: PointerRNA& m_instance; }; diff --git a/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/customdata.cpp b/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/customdata.cpp index c839e2cb..45b5bda6 100644 --- a/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/customdata.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/customdata.cpp @@ -6,14 +6,22 @@ #include "pch.h" -int CustomData_get_layer_index(const CustomData *data, int type) + +int CustomData_number_of_layers(const CustomData *data, int type) { - BLI_assert(customdata_typemap_is_valid(data)); - return data->typemap[type]; + int i, number = 0; + for (i = 0; i < data->totlayer; i++) { + if (data->layers[i].type == type) { + number++; + } + } + + return number; } -int CustomData_get_layer_index_n(const struct CustomData *data, int type, int n) -{ +#if BLENDER_VERSION >= 306 +int CustomData_get_layer_index_n(const CustomData* data, const eCustomDataType type, const int n) { + BLI_assert(n >= 0); int i = CustomData_get_layer_index(data, type); if (i != -1) { @@ -24,22 +32,42 @@ int CustomData_get_layer_index_n(const struct CustomData *data, int type, int n) return i; } - -void *CustomData_get_layer_n(const CustomData *data, int type, int n) -{ - /* get the layer index of the active layer of type */ +const void* CustomData_get_layer_n(const CustomData* data, const eCustomDataType type, const int n) { int layer_index = CustomData_get_layer_index_n(data, type, n); if (layer_index == -1) { - return NULL; + return nullptr; } + return data->layers[layer_index].data; +} +const void* CustomData_get_layer_named(const CustomData* data, + const eCustomDataType type, + const char* name) { + int layer_index = CustomData_get_named_layer_index(data, type, name); + if (layer_index == -1) { + return nullptr; + } return data->layers[layer_index].data; } -int CustomData_number_of_layers(const CustomData *data, int type) -{ - int i, number = 0; - for (i = 0; i < data->totlayer; i++) { +int CustomData_get_named_layer_index(const CustomData* data, + const eCustomDataType type, + const char* name) { + for (int i = 0; i < data->totlayer; i++) { + if (data->layers[i].type == type) { + if (STREQ(data->layers[i].name, name)) { + return i; + } + } + } + + return -1; +} + +int CustomData_number_of_layers(const CustomData* data, const eCustomDataType type) { + int number = 0; + + for (int i = 0; i < data->totlayer; i++) { if (data->layers[i].type == type) { number++; } @@ -48,6 +76,15 @@ int CustomData_number_of_layers(const CustomData *data, int type) return number; } +int CustomData_get_layer_index(const CustomData* data, const eCustomDataType type) { + BLI_assert(customdata_typemap_is_valid(data)); + return data->typemap[type]; +} + +#else +#if BLENDER_VERSION >= 305 +const +#endif void* CustomData_get_layer_named(const CustomData* data, const int type, const char* name) { int layer_index = CustomData_get_named_layer_index(data, type, name); @@ -58,6 +95,11 @@ void* CustomData_get_layer_named(const CustomData* data, const int type, const c return data->layers[layer_index].data; } +int CustomData_get_layer_index(const CustomData* data, int type) { + BLI_assert(customdata_typemap_is_valid(data)); + return data->typemap[type]; +} + int CustomData_get_named_layer_index(const CustomData* data, const int type, const char* name) { for (int i = 0; i < data->totlayer; i++) { @@ -69,4 +111,27 @@ int CustomData_get_named_layer_index(const CustomData* data, const int type, con } return -1; -} \ No newline at end of file +} +#if BLENDER_VERSION >= 305 +const +#endif +void* CustomData_get_layer_n(const CustomData* data, int type, int n) { + /* get the layer index of the active layer of type */ + int layer_index = CustomData_get_layer_index_n(data, type, n); + if (layer_index == -1) { + return NULL; + } + + return data->layers[layer_index].data; +} +int CustomData_get_layer_index_n(const struct CustomData* data, int type, int n) { + int i = CustomData_get_layer_index(data, type); + + if (i != -1) { + BLI_assert(i + n < data->totlayer); + i = (data->layers[i + n].type == type) ? (i + n) : (-1); + } + + return i; +} +#endif // #if BLENDER_VERSION < 306 \ No newline at end of file diff --git a/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/material.cpp b/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/material.cpp index 379a9840..fda92c10 100644 --- a/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/material.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/BlenderSrc/material.cpp @@ -98,10 +98,17 @@ Material ***BKE_object_material_array_p(Object *ob) MetaBall *mb = reinterpret_cast(ob->data); return &(mb->mat); } +#if BLENDER_VERSION < 306 if (ob->type == OB_GPENCIL) { bGPdata *gpd = reinterpret_cast(ob->data); return &(gpd->mat); } +#else + if (ob->type == OB_GPENCIL_LEGACY) { + bGPdata* gpd = reinterpret_cast(ob->data); + return &(gpd->mat); + } +#endif if (ob->type == OB_CURVES) { Curves *curves = reinterpret_cast(ob->data); return &(curves->mat); @@ -131,14 +138,22 @@ short *BKE_object_material_len_p(Object *ob) MetaBall *mb = reinterpret_cast(ob->data); return &(mb->totcol); } +#if BLENDER_VERSION < 306 if (ob->type == OB_GPENCIL) { bGPdata *gpd = reinterpret_cast(ob->data); return &(gpd->totcol); } +#else + if (ob->type == OB_GPENCIL_LEGACY) { + bGPdata* gpd = reinterpret_cast(ob->data); + return &(gpd->totcol); + } +#endif if (ob->type == OB_CURVES) { Curves *curves = reinterpret_cast(ob->data); return &(curves->totcol); } + if (ob->type == OB_POINTCLOUD) { PointCloud *pointcloud = reinterpret_cast(ob->data); return &(pointcloud->totcol); diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenBinder.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenBinder.cpp index e67f31fd..46a6e103 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenBinder.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenBinder.cpp @@ -41,6 +41,7 @@ static FunctionRNA* BMesh_polygons_add; static FunctionRNA* BMesh_loops_add; static FunctionRNA* BMesh_edges_add; static FunctionRNA* BMesh_normals_add; +static PropertyRNA* BMesh_polygons; static PropertyRNA* UVLoopLayers_active; static PropertyRNA* LoopColors_active; @@ -91,6 +92,8 @@ extern PropertyRNA* BlenderPyDepsgraphObjectInstance_object; extern PropertyRNA* BlenderPyDepsgraph_object_instances; +static FunctionRNA* BlenderPyAttribute_new; + bool ready() { return g_context != nullptr; @@ -115,131 +118,138 @@ void setup(py::object bpy_context) // resolve blender types and functions #define match_type(N) strcmp(type->identifier, N) == 0 -#define match_func(N) strcmp(func->identifier, N) == 0 -#define match_prop(N) strcmp(prop->identifier, N) == 0 -#define each_func for (auto *func : list_range((FunctionRNA*)type->functions.first)) -#define each_prop for (auto *prop : list_range((PropertyRNA*)type->cont.properties.first)) +#define each_func for (auto *f : list_range((FunctionRNA*)type->functions.first)) +#define each_prop for (auto *f : list_range((PropertyRNA*)type->cont.properties.first)) +#define assign(NAME, FUNC_OR_PROP) if (strcmp(f->identifier, (NAME)) == 0) (FUNC_OR_PROP) = f; for (auto *type : list_range((StructRNA*)first_type)) { + if(match_type("AttributeGroup")) { + each_func{ + assign("new", BlenderPyAttribute_new) + } + } if (match_type("ID")) { BlenderPyID::s_type = type; each_prop{ - if (match_prop("is_updated")) BlenderPyID_is_updated = prop; - if (match_prop("is_updated_data")) BlenderPyID_is_updated_data = prop; + assign("is_updated", BlenderPyID_is_updated) + assign("is_updated_data", BlenderPyID_is_updated_data) } each_func { - if (match_func("evaluated_get")) BlenderPyID_evaluated_get = func; - if (match_func("update_tag")) BlenderPyID_update_tag = func; + assign("evaluated_get", BlenderPyID_evaluated_get) + assign("update_tag", BlenderPyID_update_tag) } } else if (match_type("Object")) { BObject::s_type = type; each_prop{ - if (match_prop("matrix_local")) BObject_matrix_local = prop; - if (match_prop("matrix_world")) BObject_matrix_world = prop; - if (match_prop("hide")) BObject_hide = prop; - if (match_prop("hide_viewport")) BObject_hide_viewport = prop; - if (match_prop("hide_render")) BObject_hide_render = prop; - if (match_prop("select")) BObject_select = prop; + assign("matrix_local", BObject_matrix_local) + assign("matrix_world", BObject_matrix_world) + assign("hide", BObject_hide) + assign("hide_viewport", BObject_hide_viewport) + assign("hide_render", BObject_hide_render) + assign("select", BObject_select) } each_func { - if (match_func("select_get")) BObject_select_get = func; - if (match_func("to_mesh")) BObject_to_mesh = func; - if (match_func("to_mesh_clear")) BObject_to_mesh_clear = func; + assign("select_get", BObject_select_get) + assign("to_mesh", BObject_to_mesh) + assign("to_mesh_clear", BObject_to_mesh_clear) } } else if (match_type("ObjectModifiers")) { each_func{ - if (match_func("clear")) BObject_modifiers_clear = func; + assign("clear", BObject_modifiers_clear) } } else if (match_type("Mesh")) { BMesh::s_type = type; each_func { - if (match_func("calc_normals_split")) BMesh_calc_normals_split = func; - if (match_func("update")) BMesh_update = func; - if (match_func("clear_geometry")) BMesh_clear_geometry = func; + assign("calc_normals_split", BMesh_calc_normals_split) + assign("update", BMesh_update) + assign("clear_geometry", BMesh_clear_geometry) + } + each_prop{ + assign("polygons", BMesh_polygons) } } else if (match_type("MeshVertices")) { each_func{ - if (match_func("add")) BMesh_vertices_add = func; + assign("add", BMesh_vertices_add) } } else if (match_type("MeshPolygons")) { each_func{ - if (match_func("add")) BMesh_polygons_add = func; + assign("add", BMesh_polygons_add) } } else if (match_type("MeshLoops")) { each_func{ - if (match_func("add")) BMesh_loops_add = func; + assign("add", BMesh_loops_add) } } else if (match_type("MeshEdges")) { each_func{ - if (match_func("add")) BMesh_edges_add = func; + assign("add", BMesh_edges_add) } } else if (match_type("Curve")) { BCurve::s_type = type; each_prop{ - if (match_prop("splines")) BCurve_splines = prop; + assign("splines", BCurve_splines) } } else if (match_type("CurveSplines")) { each_func{ - if (match_func("clear")) BCurve_splines_clear = func; - if (match_func("new")) BCurve_splines_new = func; + assign("clear", BCurve_splines_clear) + assign("new", BCurve_splines_new) } } else if (match_type("SplineBezierPoints")) { BNurb::s_type = type; each_func{ - if (match_func("add")) BNurb_splines_bezier_add = func; + assign("add", BNurb_splines_bezier_add) } } else if (match_type("UVLoopLayers")) { each_prop{ - if (match_prop("active")) UVLoopLayers_active = prop; + assign("active", UVLoopLayers_active) } } else if (match_type("LoopColors")) { each_prop{ - if (match_prop("active")) LoopColors_active = prop; + assign("active", LoopColors_active) } } else if (match_type("Camera")) { BCamera::s_type = type; each_prop{ - if (match_prop("clip_start")) BCamera_clip_start = prop; - if (match_prop("clip_end")) BCamera_clip_end = prop; - if (match_prop("angle_x")) BCamera_angle_x = prop; - if (match_prop("angle_y")) BCamera_angle_y = prop; - if (match_prop("lens")) BCamera_lens = prop; - if (match_prop("sensor_fit")) BCamera_sensor_fit = prop; - if (match_prop("sensor_width")) BCamera_sensor_width = prop; - if (match_prop("sensor_height")) BCamera_sensor_height = prop; - if (match_prop("shift_x")) BCamera_shift_x = prop; - if (match_prop("shift_y")) BCamera_shift_y = prop; + assign("clip_start", BCamera_clip_start) + assign("clip_end", BCamera_clip_end) + assign("angle_x", BCamera_angle_x) + assign("angle_y", BCamera_angle_y) + assign("lens", BCamera_lens) + assign("sensor_fit", BCamera_sensor_fit) + assign("sensor_width", BCamera_sensor_width) + assign("sensor_height", BCamera_sensor_height) + assign("shift_x", BCamera_shift_x) + assign("shift_y", BCamera_shift_y) } } else if (match_type("Material")) { BMaterial::s_type = type; each_prop{ - if (match_prop("use_nodes")) BMaterial_use_nodes = prop; - if (match_prop("active_node_material")) BMaterial_active_node_material = prop; + assign("use_nodes", BMaterial_use_nodes) + assign("active_node_material", BMaterial_active_node_material) } } else if (match_type("Scene")) { BlenderPyScene::s_type = type; each_prop{ - if (match_prop("frame_start")) BlenderPyScene_frame_start = prop; - if (match_prop("frame_end")) BlenderPyScene_frame_end = prop; - if (match_prop("frame_current")) BlenderPyScene_frame_current = prop; + assign("frame_start", BlenderPyScene_frame_start) + assign("frame_end", BlenderPyScene_frame_end) + assign("frame_current", BlenderPyScene_frame_current) } each_func{ - if (match_func("frame_set")) BlenderPyScene_frame_set = func; + assign("frame_set", BlenderPyScene_frame_set) } } else if (match_type("BlendData")) { @@ -247,63 +257,45 @@ void setup(py::object bpy_context) } else if (match_type("BlendDataObjects")) { each_prop{ - if (match_prop("is_updated")) BlendDataObjects_is_updated = prop; + assign("is_updated", BlendDataObjects_is_updated) } } else if (match_type("BlendDataMeshes")) { each_func{ - if (match_func("remove")) BlendDataMeshes_remove = func; + assign("remove", BlendDataMeshes_remove) } } else if (match_type("Context")) { BlenderPyContext::s_type = type; each_prop{ - if (match_prop("blend_data")) BlenderPyContext_blend_data = prop; - if (match_prop("scene")) BlenderPyContext_scene = prop; - if (match_prop("view_layer")) BlenderPyContext_view_layer = prop; + assign("blend_data", BlenderPyContext_blend_data) + assign("scene", BlenderPyContext_scene) + assign("view_layer", BlenderPyContext_view_layer) } each_func{ - if (match_func("evaluated_depsgraph_get")) BlenderPyContext_evaluated_depsgraph_get = func; + assign("evaluated_depsgraph_get", BlenderPyContext_evaluated_depsgraph_get) } } else if (match_type("Depsgraph")) { each_prop{ - if (match_prop("object_instances")) { - BlenderPyDepsgraph_object_instances = prop; + assign("object_instances", BlenderPyDepsgraph_object_instances) } - } each_func{ - if (match_func("update")) { - BlenderPyContext_depsgraph_update = func; + assign("update", BlenderPyContext_depsgraph_update) } - } } else if (match_type("DepsgraphObjectInstance")) { each_prop{ - if (match_prop("instance_object")) { - BlenderPyDepsgraphObjectInstance_instance_object = prop; - } - - if (match_prop("is_instance")) { - BlenderPyDepsgraphObjectInstance_is_instance = prop; - } - if (match_prop("matrix_world")) { - BlenderPyDepsgraphObjectInstance_world_matrix = prop; - } - if (match_prop("parent")) { - BlenderPyDepsgraphObjectInstance_parent = prop; - } - if (match_prop("object")) { - BlenderPyDepsgraphObjectInstance_object = prop; - } + assign("instance_object", BlenderPyDepsgraphObjectInstance_instance_object) + assign("is_instance", BlenderPyDepsgraphObjectInstance_is_instance) + assign("matrix_world", BlenderPyDepsgraphObjectInstance_world_matrix) + assign("parent", BlenderPyDepsgraphObjectInstance_parent) + assign("object", BlenderPyDepsgraphObjectInstance_object) } } } -#undef each_iprop -#undef each_nprop #undef each_func -#undef match_prop -#undef match_func +#undef assign #undef match_type // test @@ -413,7 +405,9 @@ blist_range BObject::deform_groups() barray_range BMesh::indices() { -#if BLENDER_VERSION >= 304 +#if BLENDER_VERSION >= 306 + return { (MLoop*)CustomData_get_layer_named(&m_ptr->ldata, CD_PROP_INT32, ".corner_vert"), (size_t)m_ptr->totloop }; +#elif BLENDER_VERSION >= 304 return{ (MLoop*)CustomData_get(m_ptr->ldata, CD_MLOOP), (size_t)m_ptr->totloop }; #else return { m_ptr->mloop, (size_t)m_ptr->totloop }; @@ -423,24 +417,39 @@ barray_range BMesh::edges() { return { m_ptr->medge, (size_t)m_ptr->totedge }; } -barray_range BMesh::polygons() -{ + +#if BLENDER_VERSION < 306 +barray_range BMesh::polygons() { #if BLENDER_VERSION >= 304 return { (MPoly*)CustomData_get(m_ptr->pdata, CD_MPOLY), (size_t)m_ptr->totpoly }; #else return { m_ptr->mpoly, (size_t)m_ptr->totpoly }; #endif } +#else +OffsetIndices BMesh::polygons() +{ + return Span(m_ptr->poly_offset_indices, m_ptr->totpoly + 1); +} +MutableSpan BMesh::polygonsForWrite() +{ + return MutableSpan(m_ptr->poly_offset_indices, m_ptr->totpoly + 1); +} +#endif // #if BLENDER_VERSION < 306 barray_range BMesh::vertices() { -#if BLENDER_VERSION >= 304 +#if BLENDER_VERSION >= 305 + auto positions = (const float(*)[3])CustomData_get_layer_named(&m_ptr->vdata, CD_PROP_FLOAT3, "position"); + return { (MVert*)positions, (size_t)m_ptr->totvert }; +#elif BLENDER_VERSION >= 304 return { (MVert*)CustomData_get(m_ptr->vdata, CD_MVERT),(size_t) m_ptr->totvert}; #else return { m_ptr->mvert, (size_t)m_ptr->totvert }; #endif } -barray_range BMesh::normals() + +blender::barray_range BMesh::normals() { if (CustomData_number_of_layers(&m_ptr->ldata, CD_NORMAL) > 0) { auto data = (mu::float3*)CustomData_get(m_ptr->ldata, CD_NORMAL); @@ -463,17 +472,14 @@ barray_range BMesh::material_indices() //---------------------------------------------------------------------------------------------------------------------- -barray_range BMesh::uv() -{ - CustomDataLayer* layer_data = static_cast(get_pointer(m_ptr, UVLoopLayers_active)); - if (layer_data && layer_data->data) - return { static_cast(layer_data->data), static_cast(m_ptr->totloop) }; - else - return { nullptr, (size_t)0 }; -} MLoopUV* BMesh::GetUV(const int index) const { +#if BLENDER_VERSION < 305 return static_cast(CustomData_get_layer_n(&m_ptr->ldata, CD_MLOOPUV, index)); +#else + auto uvs = CustomData_get_layer_n(&m_ptr->ldata, CD_PROP_FLOAT2, index); + return (MLoopUV*)uvs; +#endif } //---------------------------------------------------------------------------------------------------------------------- @@ -523,6 +529,21 @@ void BMesh::add_normals(int count) { call(g_context, m_ptr, BMesh_normals_add, count); } +#if BLENDER_VERSION >= 306 +void BMesh::shade_flat() { + + bool* sharp_faces = (bool*)CustomData_get_layer_named(&m_ptr->pdata, CD_PROP_BOOL, "sharp_face"); + if (!sharp_faces) { + call(g_context, &m_ptr->id, BlenderPyAttribute_new, "sharp_face", CD_PROP_BOOL, 2, &m_ptr->id); // ATTR_DOMAIN_FACE = 2 + sharp_faces = (bool*)CustomData_get_layer_named(&m_ptr->pdata, CD_PROP_BOOL, "sharp_face"); + } + + if (sharp_faces) { + std::fill(sharp_faces, sharp_faces + m_ptr->totpoly, true); + } +} +#endif + barray_range BEditMesh::polygons() { return { m_ptr->bm->ftable, (size_t)m_ptr->bm->ftable_tot }; @@ -540,7 +561,11 @@ barray_range BEditMesh::triangles() int BEditMesh::uv_data_offset(int index) const { +#if BLENDER_VERSION < 306 int layer_index = CustomData_get_layer_index_n(&m_ptr->bm->ldata, CD_MLOOPUV, index); +#else + int layer_index = CustomData_get_layer_index_n(&m_ptr->bm->ldata, CD_PROP_FLOAT2, index); +#endif if (layer_index == -1) { return NULL; } @@ -549,10 +574,6 @@ int BEditMesh::uv_data_offset(int index) const return layer.offset; } -MLoopUV* BEditMesh::GetUV(const int index) const { - return static_cast(CustomData_get_layer_n(&m_ptr->bm->ldata, CD_MLOOPUV, index)); -} - void BNurb::add_bezier_points(int count, Object* obj) { call(g_context, m_ptr, BNurb_splines_bezier_add, count, obj->id); } diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenBinder.h b/Plugins~/Src/MeshSyncClientBlender/msblenBinder.h index c7050cef..725a2798 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenBinder.h +++ b/Plugins~/Src/MeshSyncClientBlender/msblenBinder.h @@ -6,6 +6,29 @@ struct Depsgraph; +// The data structures have changed in blender 3.5 +// These data structures are wrappers for the new data with the fields we need to keep the plugin code for different blender versions the same: +#if BLENDER_VERSION >= 305 +typedef struct MLoopUV { + float uv[2]; +} MLoopUV; + +typedef struct MVert { + float co[3]; +} MVert; + +#endif + +#if BLENDER_VERSION >= 306 +//typedef struct MPoly { +// int loopstart; +//} MPoly; + +typedef struct MLoop { + int v; +} MLoop; +#endif + namespace blender { bool ready(); @@ -107,7 +130,14 @@ namespace blender barray_range indices(); barray_range edges(); +#if BLENDER_VERSION < 306 barray_range polygons(); +#else + OffsetIndices polygons(); + MutableSpan polygonsForWrite(); +#endif + + barray_range vertices(); barray_range normals(); barray_range uv(); @@ -126,10 +156,20 @@ namespace blender void add_loops(int count); void add_edges(int count); void add_normals(int count); - }; - uint32_t BMesh::GetNumUVs() const { return CustomData_number_of_layers(&m_ptr->ldata, CD_MLOOPUV); } +#if BLENDER_VERSION >= 306 + void shade_flat(); +#endif + }; + uint32_t BMesh::GetNumUVs() const + { +#if BLENDER_VERSION < 305 + return CustomData_number_of_layers(&m_ptr->ldata, CD_MLOOPUV); +#else + return CustomData_number_of_layers(&m_ptr->ldata, CD_PROP_FLOAT2); +#endif + } //---------------------------------------------------------------------------------------------------------------------- @@ -144,11 +184,16 @@ namespace blender barray_range triangles(); int uv_data_offset(int index) const; inline uint32_t GetNumUVs() const; - - MLoopUV* GetUV(const int index) const; }; - uint32_t BEditMesh::GetNumUVs() const { return CustomData_number_of_layers(&m_ptr->bm->ldata, CD_MLOOPUV); } + uint32_t BEditMesh::GetNumUVs() const + { +#if BLENDER_VERSION < 306 + return CustomData_number_of_layers(&m_ptr->bm->ldata, CD_MLOOPUV); +#else + return CustomData_number_of_layers(&m_ptr->bm->ldata, CD_PROP_FLOAT2); +#endif + } //---------------------------------------------------------------------------------------------------------------------- @@ -159,7 +204,12 @@ namespace blender barray_range indices(); barray_range edges(); - barray_range polygons(); +#if BLENDER_VERSION < 306 + barray_range +#else + OffsetIndices +#endif + polygons(); barray_range bezier_points(); void add_bezier_points(int count, Object* obj); diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenContext.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenContext.cpp index bb0a2968..29d50a35 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenContext.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenContext.cpp @@ -88,6 +88,8 @@ msblenContext::msblenContext() { m_settings.scene_settings.handedness = ms::Handedness::RightZUp; m_settings.client_settings.dcc_tool_name = "Blender_" + blender::getBlenderVersion(); + + ms::Mesh::useNormalsForHashing = false; } msblenContext::~msblenContext() @@ -100,27 +102,27 @@ const BlenderSyncSettings& msblenContext::getSettings() const { return m_setting BlenderCacheSettings& msblenContext::getCacheSettings() { return m_cache_settings; } const BlenderCacheSettings& msblenContext::getCacheSettings() const { return m_cache_settings; } -std::vector msblenContext::getNodes(MeshSyncClient::ObjectScope scope) -{ +std::vector msblenContext::getNodes(MeshSyncClient::ObjectScope scope) { std::vector ret; - bl::BlenderPyScene scene = bl::BlenderPyScene(bl::BlenderPyContext::get().scene()); if (scope == MeshSyncClient::ObjectScope::All) { - scene.each_objects([&](Object *obj) { + auto bpy_data = blender::BData(blender::BlenderPyContext::get().data()); + for (auto obj : bpy_data.objects()) { ret.push_back(obj); - }); - } else if (scope == MeshSyncClient::ObjectScope::Selected) { - scene.each_selection([&](Object *obj) { + } + } + else if (scope == MeshSyncClient::ObjectScope::Selected) { + bl::BlenderPyScene scene = bl::BlenderPyScene(bl::BlenderPyContext::get().scene()); + scene.each_selection([&](Object* obj) { ret.push_back(obj); }); - } else if (scope == MeshSyncClient::ObjectScope::Updated) { + } + else if (scope == MeshSyncClient::ObjectScope::Updated) { bl::BData bpy_data = bl::BData(bl::BlenderPyContext::get().data()); if (bpy_data.objects_is_updated()) { - scene.each_objects([&](Object *obj) { - const bl::BlenderPyID bid = bl::BlenderPyID(obj); - if (bid.is_updated() || bid.is_updated_data()) - ret.push_back(obj); - }); + for (auto obj : bpy_data.objects()) { + ret.push_back(obj); + } } } @@ -291,12 +293,16 @@ ms::TransformPtr msblenContext::exportObject(msblenContextState& state, msblenCo switch (obj->type) { case OB_ARMATURE: { - if (!tip || (!settings.BakeModifiers && settings.sync_bones)) { + if (!tip || (!settings.BakeModifiers && settings.sync_bones && state.manager.needsToApplyMirrorModifier())) { handle_parent(); rec.dst = exportArmature(state, paths, settings, obj); } - else if (!tip && parent) - handle_transform(); + else if (tip) + { + // Export bones as transforms if we're baking modifiers. + // Don't handle parent here, baked bone parents need to be handled separately! + rec.dst = exportCustomPropsBoneAsEmpty(state, paths, settings, obj); + } break; } case OB_MESH: @@ -363,8 +369,7 @@ ms::TransformPtr msblenContext::exportObject(msblenContextState& state, msblenCo default: { // Export everything, even if it's an empty object: - handle_parent(); - rec.dst = exportTransform(state, paths, settings, obj); + handle_transform(); break; } } @@ -414,13 +419,33 @@ ms::TransformPtr msblenContext::exportArmature(msblenContextState& state, msblen ms::Transform& dst = *ret; dst.path = paths.get_path(src); msblenEntityHandler::extractTransformData(settings, src, dst); + ret->visibility = { visible_in_collection(src), visible_in_render(src), visible_in_viewport(src) }; + state.manager.add(ret); + + for (struct bPoseChannel* pose : bl::list_range((bPoseChannel*)src->pose->chanbase.first)) { + struct Bone* bone = pose->bone; + std::map>::mapped_type& dst = state.bones[bone]; + dst = exportPose(state, paths, settings, src, pose); + } + return ret; +} + +ms::TransformPtr msblenContext::exportCustomPropsBoneAsEmpty(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object* src) { + // Don't handle parent here, baked bone parents need to be handled separately: + std::shared_ptr ret = ms::Transform::create(); + ms::Transform& dst = *ret; + dst.path = paths.get_path(src); + msblenEntityHandler::extractTransformData(settings, src, dst); + ret->visibility = { visible_in_collection(src), visible_in_render(src), visible_in_viewport(src) }; state.manager.add(ret); for (struct bPoseChannel* pose : bl::list_range((bPoseChannel*)src->pose->chanbase.first)) { struct Bone* bone = pose->bone; std::map>::mapped_type& dst = state.bones[bone]; dst = exportPose(state, paths, settings, src, pose); + dst->visibility = { visible_in_collection(src), visible_in_render(src), visible_in_viewport(src) }; } + return ret; } @@ -513,12 +538,14 @@ ms::TransformPtr msblenContext::exportDupliGroup(msblenContextState& state, msbl ctx2.group_host = src; ctx2.dst = dst; auto gobjects = bl::list_range((CollectionObject*)group->gobject.first); + + // Cannot set visibility from here because of race conditions. + // It shouldn't be needed, we set visibility when exporting the objects based on their visibility in the scene. for (auto go : gobjects) { auto obj = go->ob; - if (auto t = exportObject(state, paths, settings, obj, true, false)) { - const bool non_lib = obj->id.lib == nullptr; - t->visibility = { visible_in_collection(obj), non_lib, non_lib }; - } + + exportObject(state, paths, settings, obj, true, false); + exportReference(state, paths, settings, obj, ctx2); } @@ -598,7 +625,7 @@ void msblenContext::importMesh(ms::Mesh* mesh) { return; } - // Ensure we're in object mode, settiing data on the edit mesh is not supported: + // Ensure we're in object mode, setting data on the edit mesh is not supported: set_object_mode(); // If we're baking modifiers, the mesh would contain the baked version so remove the modifiers now when the mesh is updated: @@ -645,7 +672,7 @@ void msblenContext::importMesh(ms::Mesh* mesh) { } } - int num_vertices = mesh->points.size(); + const int num_vertices = mesh->points.size(); bl::BMesh bmesh(data); @@ -656,7 +683,11 @@ void msblenContext::importMesh(ms::Mesh* mesh) { auto bmeshVerts = bmesh.vertices(); auto bmeshIndices = bmesh.indices(); +#if BLENDER_VERSION < 306 auto bmeshPolygons = bmesh.polygons(); +#else + auto bmeshPolygons = bmesh.polygonsForWrite(); +#endif // vertices for (size_t vi = 0; vi < num_vertices; ++vi) { @@ -665,7 +696,9 @@ void msblenContext::importMesh(ms::Mesh* mesh) { // faces int ii = 0; +#if BLENDER_VERSION < 306 for (size_t pi = 0; pi < num_polygons; ++pi) { + // int count = mesh->counts[pi]; // always 3 for triangles from unity: const int count = 3; @@ -696,6 +729,19 @@ void msblenContext::importMesh(ms::Mesh* mesh) { bmeshIndices[bmeshPolygons[pi].loopstart + 2].v = mesh->indices[ii++]; bmeshIndices[bmeshPolygons[pi].loopstart + 1].v = mesh->indices[ii++]; } +#else + for (size_t pi = 0; pi < num_polygons; ++pi) { + bmeshPolygons[pi] = ii; + + // Ignore material index for now, we probably don't need it: + + // Reverse triangle back because it was reversed in unity during refine step: + bmeshIndices[bmeshPolygons[pi] + 0].v = mesh->indices[ii++]; + bmeshIndices[bmeshPolygons[pi] + 2].v = mesh->indices[ii++]; + bmeshIndices[bmeshPolygons[pi] + 1].v = mesh->indices[ii++]; + } + bmesh.shade_flat(); +#endif // Calculate edges, normals, loops, etc: bmesh.update(); @@ -862,9 +908,9 @@ void msblenContext::doExtractNonEditMeshData(msblenContextState& state, BlenderS bl::BMesh bmesh(data); struct Mesh& mesh = *data; - blender::barray_range indices = bmesh.indices(); - blender::barray_range polygons = bmesh.polygons(); - blender::barray_range vertices = bmesh.vertices(); + auto indices = bmesh.indices(); + auto vertices = bmesh.vertices(); + auto polygons = bmesh.polygons(); const size_t num_indices = indices.size(); const size_t num_polygons = polygons.size(); @@ -914,6 +960,7 @@ void msblenContext::doExtractNonEditMeshData(msblenContextState& state, BlenderS dst.material_ids.resize_discard(num_polygons); { int ii = 0; +#if BLENDER_VERSION < 306 for (size_t pi = 0; pi < num_polygons; ++pi) { struct MPoly& polygon = polygons[pi]; @@ -941,6 +988,31 @@ void msblenContext::doExtractNonEditMeshData(msblenContextState& state, BlenderS dst.indices[ii++] = idx[li].v; } } +#else + for (const int pi : polygons.index_range()) { + const blender::IndexRange polygon = polygons[pi]; + + int material_index = 0; + if (materialIndices.size() > pi) { + material_index = materialIndices[pi]; + } + + // Material indices can be out of range if materials are removed. + // Check for it so we don't crash when this happens: + material_index = max(0, min(material_index, materialCount - 1)); + + const int count = polygon.size(); + dst.counts[pi] = count; + + dst.material_ids[pi] = mid_table[material_index]; + dst.indices.resize(dst.indices.size() + count); + + auto* idx = &indices[polygon.start()]; + for (int li = 0; li < count; ++li) { + dst.indices[ii++] = idx[li].v; + } + } +#endif } // normals @@ -956,8 +1028,11 @@ void msblenContext::doExtractNonEditMeshData(msblenContextState& state, BlenderS blender::barray_range> normals = bmesh.normals(); if (!normals.empty()) { dst.normals.resize_discard(num_indices); - for (size_t ii = 0; ii < num_indices; ++ii) - dst.normals[ii] = ms::ceilToDecimals(normals[ii]); + for (size_t ii = 0; ii < num_indices; ++ii) { + // We're not using the normals for hashing so no need to round them anymore: + //dst.normals[ii] = ms::ceilToDecimals(normals[ii]); + dst.normals[ii] = normals[ii]; + } } } @@ -1589,16 +1664,16 @@ bool msblenContext::sendObjects(MeshSyncClient::ObjectScope scope, bool dirty_al if (!bpy_data.objects_is_updated()) return true; // nothing to send - scene.each_objects([this](Object *obj) { + for (auto obj : bpy_data.objects()) { bl::BlenderPyID bid = bl::BlenderPyID(obj); if (bid.is_updated() || bid.is_updated_data()) exportObject(*m_entities_state, m_default_paths, m_settings, obj, false); else m_entities_state->touchRecord(m_default_paths, obj); // this cannot be covered by getNodes() - }); + } } else { - for(std::vector::value_type obj : getNodes(scope)) + for (std::vector::value_type obj : getNodes(scope)) exportObject(*m_entities_state, m_default_paths, m_settings, obj, true); } @@ -1796,6 +1871,22 @@ void msblenContext::flushPendingList(msblenContextState& state, msblenContextPat } } +void removeExistingByPath(std::vector& listToFilter, std::vector sceneList) +{ + for (size_t i = 0; i < listToFilter.size(); i++) + { + for (size_t j = 0; j < sceneList.size(); j++) + { + if (listToFilter[i]->path == sceneList[j]->path) + { + listToFilter.erase(listToFilter.begin() + i); + i--; + break; + } + } + } +} + void msblenContext::WaitAndKickAsyncExport() { m_asyncTasksController.Wait(); @@ -1841,14 +1932,32 @@ void msblenContext::WaitAndKickAsyncExport() t.instanceInfos = m_instances_manager.getDirtyInstances(); t.instanceMeshes.clear(); - deduplicateGeometry(m_instances_manager.getDirtyMeshes(), t.instanceMeshes, t.transforms); + + auto instanceMeshes = m_instances_manager.getDirtyMeshes(); + + // Remove instance meshes that already exist in scene meshes: + removeExistingByPath(instanceMeshes, t.geometries); + removeExistingByPath(instanceMeshes, t.transforms); + + std::vector duplicates; + + deduplicateGeometry(instanceMeshes, t.instanceMeshes, t.transforms, duplicates); t.propertyInfos = m_property_manager.getAllProperties(); t.animations = m_animations; t.deleted_materials = m_material_manager.getDeleted(); t.deleted_entities = m_entity_manager.getDeleted(); + + for (auto duplicate : duplicates) { + m_instances_manager.erase(duplicate.name); + } + t.deleted_instances = m_instances_manager.getDeleted(); + // Any instanced meshes that were duplicates are now in t.transforms and no longer in t.instanceMeshes so we need to mark them as deleted from instances: + //t.deleted_instances.insert(t.deleted_instances.end(), duplicates.begin(), duplicates.end()); + + if (scale_factor != 1.0f) { ms::ScaleConverter cv(scale_factor); for (std::vector>::value_type& obj : t.transforms) { cv.convert(*obj); } @@ -1883,6 +1992,10 @@ void msblenContext::WaitAndKickAsyncExport() blender::callPythonMethod("meshsync_post_export"); }; + exporter->on_cancel = [this] { + m_entity_manager.clearDirtyFlags(); + }; + exporter->on_before_send = [this] { blender::callPythonMethod("meshsync_pre_export"); }; @@ -1890,19 +2003,39 @@ void msblenContext::WaitAndKickAsyncExport() exporter->kick(); } -void msblenContext::deduplicateGeometry(const std::vector& input, std::vector& geometries, std::vector& transforms) +void msblenContext::deduplicateGeometry(const std::vector& input, + std::vector& geometries, + std::vector& transforms, + std::vector& duplicates) { std::unordered_map cache; for (auto& geometry : input) { auto checksum = geometry->checksumGeom(); auto entry = cache[checksum]; - if (entry.length() > 0) { - // Create a new pointer to avoid issues with change checks - // in auto sync - auto ptr = ms::Transform::create(); - *ptr = *geometry; - ptr->reference = entry; - transforms.push_back(ptr); + + // Don't deduplicate transforms: + if (geometry->getType() != ms::EntityType::Transform && entry.length() > 0) { + bool found = false; + // If the transform is already in the list, update it: + for (auto& t : transforms) + { + if (t->path == geometry->path) + { + t->reference = entry; + found = true; + break; + } + } + + if (!found) { + // Create a new pointer to avoid issues with change checks + // in auto sync + auto ptr = ms::Transform::create(); + *ptr = *geometry; + ptr->reference = entry; + transforms.push_back(ptr); + duplicates.push_back(geometry->getIdentifier()); + } } else { cache[checksum] = geometry->path; diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenContext.h b/Plugins~/Src/MeshSyncClientBlender/msblenContext.h index 59d3f310..b004587c 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenContext.h +++ b/Plugins~/Src/MeshSyncClientBlender/msblenContext.h @@ -32,7 +32,7 @@ #include "MeshSync/Utility/msMaterialExt.h" //AsStandardMaterial #if BLENDER_VERSION >= 300 -#include +#include #endif #include "msblenMaterialsExportHelper.h" @@ -42,6 +42,7 @@ #include + class msblenContext; class msblenContext { @@ -134,7 +135,8 @@ class msblenContext { ms::TransformPtr exportObject(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *obj, bool parent, bool tip = true); ms::TransformPtr exportTransform(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *obj); ms::TransformPtr exportPose(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *armature, bPoseChannel *obj); - ms::TransformPtr exportArmature(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *obj); + ms::TransformPtr exportArmature(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object* obj); + ms::TransformPtr exportCustomPropsBoneAsEmpty(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object* obj); ms::TransformPtr exportReference(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, Object *obj, const DupliGroupContext& ctx); ms::TransformPtr exportDupliGroup(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *obj, const DupliGroupContext& ctx); ms::CameraPtr exportCamera(msblenContextState& state, msblenContextPathProvider& paths, BlenderSyncSettings& settings, const Object *obj); @@ -164,7 +166,7 @@ class msblenContext { void DoExportSceneCache(const std::vector& nodes); void WaitAndKickAsyncExport(); - void deduplicateGeometry(const std::vector& input, std::vector& geometries, std::vector& references); + void deduplicateGeometry(const std::vector& input, std::vector& geometries, std::vector& references, std::vector& duplicates); void deduplicateGeometry(const std::vector>& input, std::vector& geometries, std::vector& references); diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenContextGeometryNodes.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenContextGeometryNodes.cpp index 23d1afef..1e118d9e 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenContextGeometryNodes.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenContextGeometryNodes.cpp @@ -1,4 +1,4 @@ -#include "msblenGeometryNodeUtils.h" +#include "msblenGeometryNodesUtils.h" #include "msblenContext.h" #include "BlenderPyObjects/BlenderPyScene.h" //BlenderPyScene @@ -28,46 +28,63 @@ void msblenContext::exportInstances() { blender::BlenderPyScene scene = blender::BlenderPyScene(blender::BlenderPyContext::get().scene()); std::unordered_set scene_objects; - scene.each_objects([this, &scene_objects](Object* obj) - { - if (obj == nullptr || obj->data == nullptr) - return; - - auto id = (ID*)obj->data; - scene_objects.insert(id->name + 2); - }); - // Assume everything is now dirty - m_instances_state->manager.setAlwaysMarkDirty(true); + auto bpy_data = blender::BData(blender::BlenderPyContext::get().data()); + for (auto obj : bpy_data.objects()) { + if (obj == nullptr || obj->data == nullptr) + continue; + auto id = static_cast(obj->data); + scene_objects.insert(id->name + 2); + } + std::unordered_map exportedTransforms; m_geometryNodeUtils.each_instanced_object( [this, &scene_objects, &exportedTransforms](blender::GeometryNodesUtils::Record& rec) { auto obj = rec.obj; + if (!rec.from_file) { auto settings = m_settings; settings.BakeModifiers = false; settings.multithreaded = false; auto transform = exportObject(*m_instances_state, m_intermediate_paths, settings, rec.obj, false, true); - transform->reset(); - exportedTransforms[rec.id] = transform; + + // Could be null if export is not enabled for the object type: + if (transform) { + // Objects that aren't in the file should always be hidden: + transform->visibility = { false, false, false }; + + transform->reset(); + exportedTransforms[rec.id] = transform; + } } - else if (scene_objects.find(rec.name) == scene_objects.end()) { + else if (scene_objects.find(m_geometryNodeUtils.get_data_path(obj)) == scene_objects.end()) { auto settings = m_settings; settings.multithreaded = false; settings.BakeModifiers = false; exportedTransforms[rec.id] = exportObject(*m_instances_state, m_default_paths, settings, rec.obj, false, true); } else { - exportedTransforms[rec.id] = exportObject(*m_entities_state, m_default_paths, m_settings, rec.obj, true, true); + // The object was already synced as part of the scene + auto& sceneTransform = exportObject(*m_entities_state, m_default_paths, m_settings, rec.obj, true, true); + + if (sceneTransform) { + m_instances_state->manager.add(sceneTransform); + exportedTransforms[rec.id] = sceneTransform; + } } }, [this, &exportedTransforms](blender::GeometryNodesUtils::Record& rec) { + auto& transform = exportedTransforms[rec.id]; + + // If this transform was not exported (happens when the sync setting for lights, etc. is not enabled), skip it: + if(!transform) + return; + auto world_matrix = msblenEntityHandler::getWorldMatrix(rec.parent); auto inverse = mu::invert(world_matrix); - auto& transform = exportedTransforms[rec.id]; //parent is always part of the scene const auto& parent = exportObject(*m_entities_state, m_default_paths, m_settings, rec.parent, false); @@ -78,8 +95,7 @@ void msblenContext::exportInstances() { else { exportInstances(transform, parent, std::move(rec.matrices), inverse, m_intermediate_paths); } - - }); + }); m_geometryNodeUtils.setInstancesDirty(false); diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.cpp index df32abdf..64c83787 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.cpp @@ -3,9 +3,14 @@ #include std::string msblenContextIntermediatePathProvider::append_id(std::string path, const Object* obj) { + // If there is no data, no need to append anything: + if (!obj->data) { + return path; + } + auto data = (ID*)obj->data; - path += "_" + std::string(data->name); + path += "_" + std::string(data->name + 2); // If we already have an object with this name but a different session_uuid, append the session_uuid as well auto it = mappedNames.find(data->name); @@ -25,7 +30,8 @@ std::string msblenContextIntermediatePathProvider::get_path(const Object* obj, c { std::string path; if (bone) { - path = msblenUtils::get_path(obj, bone); + //path = msblenUtils::get_path(obj, bone); + return get_path_with_suffix(obj, bone); } else { path = "/" + msblenUtils::get_name(obj); @@ -33,3 +39,35 @@ std::string msblenContextIntermediatePathProvider::get_path(const Object* obj, c return append_id(path, obj); } + + +std::string msblenContextIntermediatePathProvider::get_path_with_suffix(const Object* obj) { + std::string ret; + if (obj->parent) { + // Build bone path for armatures only, not other objects that are children of armatures: + if (obj->type == OB_ARMATURE && obj->partype == PARBONE) { + if (auto bone = msblenUtils::find_bone(obj->parent, obj->parsubstr)) { + ret += get_path_with_suffix(obj->parent, bone); + } + } + else { + ret += get_path_with_suffix(obj->parent); + } + } + ret += '/'; + ret += msblenUtils::get_name(obj); + ret = append_id(ret, obj); + return ret; +} + +std::string msblenContextIntermediatePathProvider::get_path_with_suffix(const Object* arm, const Bone* obj) { + std::string ret; + if (obj->parent) + ret += get_path_with_suffix(arm, obj->parent); + else + ret += get_path_with_suffix(arm); + ret += '/'; + ret += msblenUtils::get_name(obj); + ret = append_id(ret, arm); + return ret; +} diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.h b/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.h index 9e145057..cbdbd4fb 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.h +++ b/Plugins~/Src/MeshSyncClientBlender/msblenContextIntermediatePathProvider.h @@ -2,11 +2,13 @@ #include class msblenContextIntermediatePathProvider : public msblenContextPathProvider { -private: +protected: std::string append_id(std::string path, const Object* obj); + std::string get_path_with_suffix(const Object* obj); + std::string get_path_with_suffix(const Object* arm, const Bone* obj); public: std::string get_path(const Object* obj, const Bone* bone) override; std::map mappedNames; -}; \ No newline at end of file +}; diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.cpp index c615d02b..e4e70008 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.cpp @@ -1,4 +1,4 @@ -#include "msblenGeometryNodeUtils.h" +#include "msblenGeometryNodesUtils.h" #include #include #include @@ -21,10 +21,10 @@ namespace blender { { auto rotation = rotate_x(-90 * DegToRad); auto rotation180 = rotate_z(180 * DegToRad); - auto scale_z = float3::one(); + auto scale_z = mu::float3::one(); scale_z.z = -1; - auto scale_x = float3::one(); + auto scale_x = mu::float3::one(); scale_x.x = -1; m_blender_to_unity_world = @@ -76,6 +76,14 @@ namespace blender { return m_instances_dirty; } + std::string GeometryNodesUtils::get_data_path(Object* obj) { + auto data = (ID*)obj->data; + if (data) { + return string(data->name) + string(obj->id.name); + } + return string(obj->id.name); + }; + void GeometryNodesUtils::each_instanced_object( std::function obj_handler, std::function matrix_handler) { @@ -83,72 +91,93 @@ namespace blender { std::unordered_map records_by_session_id; std::unordered_map records_by_name; - // Collect object names in the file - auto ctx = blender::BlenderPyContext::get(); - auto objects = ctx.data()->objects; + // Collect object names in the scene std::unordered_set file_objects; - auto get_path = [](Object* obj) { - auto data = (ID*)obj->data; - return string(data->name) + string(obj->id.name); - }; - - LISTBASE_FOREACH(Object*, obj, &objects) { - - if (obj->data == nullptr) - continue; - - auto path = get_path(obj); - + /* BlenderPyScene scene = BlenderPyScene(BlenderPyContext::get().scene()); + scene.each_objects([&](Object* obj) { + auto path = get_data_path(obj); file_objects.insert(path); + });*/ + + auto bpy_data = blender::BData(blender::BlenderPyContext::get().data()); + for (auto obj : bpy_data.objects()) { + if (obj->id.override_library == nullptr) { + auto path = get_data_path(obj); + file_objects.insert(path); + } } - each_instance([&](Object* obj, Object* parent, float4x4 matrix) - { - auto id = (ID*)obj->data; + each_instance([&](Object* obj, Object* parent, float4x4 matrix) { + ID* id; + if (obj->data) { + id = (ID*)obj->data; + } + else { + id = &obj->id; + } - //Some objects, i.e. lights, do not use a session uuid. - bool useName = id->session_uuid == 0; + //Some objects, i.e. lights, do not use a session uuid. + bool useName = id->session_uuid == 0; - // An object might be sharing data with other objects, need to use the object name in keys - auto& rec = useName? records_by_name[std::string(id->name + 2) + obj->id.name] : records_by_session_id[std::to_string(id->session_uuid) + obj->id.name]; + // An object might be sharing data with other objects, need to use the object name in keys + auto& rec = useName ? records_by_name[std::string(parent->id.name) + "_" + std::string(id->name + 2) + "_" + std::string(obj->id.name + 2)] + : records_by_session_id[std::string(parent->id.name + 2) + "_" + std::to_string(id->session_uuid) + "_" + std::string(obj->id.name + 2)]; - if (!rec.handled_object) - { - rec.handled_object = true; - rec.name = std::string(id->name + 2) + std::string(obj->id.name); - rec.obj = obj; - rec.parent = parent; - - rec.from_file = file_objects.find(get_path(obj)) != file_objects.end(); + if (!rec.handled_object) { + rec.handled_object = true; - rec.id = rec.name +"_" + std::to_string(id->session_uuid); - obj_handler(rec); + // If there is no data on the object, the object can be uniquely identified by its name: + if (obj->data) { + rec.name = std::string(id->name + 2) + "_" + std::string(obj->id.name + 2); + } + else { + rec.name = std::string(id->name + 2); } - - rec.matrices.push_back(matrix); - }); + rec.obj = obj; + rec.parent = parent; - // Export transforms - for (auto& rec : records_by_session_id) { - if (rec.second.handled_matrices) - continue; + rec.from_file = file_objects.find(get_data_path(obj)) != file_objects.end(); - rec.second.handled_matrices = true; - matrix_handler(rec.second); + rec.id = std::string(parent->id.name + 2) + "_" + rec.name + "_" + std::to_string(id->session_uuid); + obj_handler(rec); } - for (auto& rec : records_by_name) { - if (rec.second.handled_matrices) - continue; + rec.matrices.push_back(matrix); + }); - rec.second.handled_matrices = true; - matrix_handler(rec.second); - } + // Export transforms + for (auto& rec : records_by_session_id) { + if (rec.second.handled_matrices) + continue; + + rec.second.handled_matrices = true; + matrix_handler(rec.second); + } + + for (auto& rec : records_by_name) { + if (rec.second.handled_matrices) + continue; + + rec.second.handled_matrices = true; + matrix_handler(rec.second); + } } + float4x4 getMatrix(float mat[4][4]) { + auto object_parent_world_matrix = float4x4(); + + for (int x = 0; x < 4; x++) { + for (int y = 0; y < 4; y++) { + object_parent_world_matrix[x][y] = mat[x][y]; + } + } + + return object_parent_world_matrix; + } + void GeometryNodesUtils::each_instance(std::function handler) { auto blender_ctx = BlenderPyContext::get(); @@ -156,12 +185,58 @@ namespace blender { auto depsgraph = BlenderPyDepsgraph(depsgraph_ctx); + // Build map of parents to their children: + std::map> instanceParentsToChildren; + auto bpy_data = blender::BData(blender::BlenderPyContext::get().data()); + + for (auto obj : bpy_data.objects()) { + if (obj->parent) + { + auto parentName = msblenUtils::get_name(obj->parent); + auto objectName = msblenUtils::get_name(obj); + // Objects could appear twice if they have lib overrides: + std::vector& list = instanceParentsToChildren[parentName]; + if (find(list.begin(), list.end(), objectName) == list.end()) { + instanceParentsToChildren[parentName].push_back(objectName); + } + } + } + // Iterate over the object instances collection of depsgraph CollectionPropertyIterator it; - depsgraph.object_instances_begin(&it); + // Blender returns the instances in a flattened list, which causes duplicate instances when we have nested instances. + // For example in an instance hierarchy like this: + // A + // |-B + // |-C + // |-D + // Blender would return the instances in this order: + // C on parent B + // D on parent B + // B on parent A + // C on parent A (Duplicate!) + // D on parent A (Duplicate!) + // To get around this, build a map of child instances to their parents and if we find a parent, we can skip the children of that instance. + + // Parent we're currently under: + std::string currentParent = ""; + + // Iterator to the current position of child instances: + vector::iterator currentObjectChildIterator; + + // currentObjectChildIterator cannot be null, this keeps track whether we have an iterator or not: + bool hasIterator = false; + + // Parent of the object for currentObjectChildIterator + std::string currentObjectChildIteratorParent = ""; + + // Keeps track of the parents we built a child hierarchy for. Once the parent changes, the previous parent's hierarchy is complete. + std::vector processedInstanceParents; + + for (; it.valid; depsgraph.object_instances_next(&it)) { // Get the instance as a Pointer RNA. @@ -181,16 +256,93 @@ namespace blender { auto object = instance.object(); - // Don't instance empties, they have no data we can use to get a session id: - if (object->type == OB_EMPTY) { - continue; - } + // need to export dupli-groups to get linked library override objects to work: + //if(object->instance_collection) + //{ + // // Dupli-groups are exported separately: + // continue; + //} auto world_matrix = float4x4(); instance.world_matrix(&world_matrix); auto parent = instance.parent(); + auto parentName = msblenUtils::get_name(parent); + auto objectName = msblenUtils::get_name(object); + + if (currentParent != parentName || + objectName == currentObjectChildIteratorParent) { + currentParent = parentName; + + if (instanceParentsToChildren.find(objectName) != instanceParentsToChildren.end()) { + hasIterator = true; + currentObjectChildIterator = instanceParentsToChildren[objectName].begin(); + currentObjectChildIteratorParent = objectName; + } + else { + hasIterator = false; + } + } + + // build parentToChildren mapping: + if (std::find(processedInstanceParents.begin(), processedInstanceParents.end(), parentName) == processedInstanceParents.end()) { + processedInstanceParents.push_back(parentName); + } + + // If this is the current instance parent, add any instances to it as children + if (processedInstanceParents[processedInstanceParents.size() - 1] == parentName) { + instanceParentsToChildren[parentName].push_back(objectName); + + // If we modify the current iterator list, the iterator becomes invalid: + if (currentObjectChildIteratorParent == parentName) { + hasIterator = false; + } + } + + // If we're currently iterating over the children of an instance parent, skip this child: + if (objectName != currentObjectChildIteratorParent) { + if (hasIterator && + currentObjectChildIterator != instanceParentsToChildren[currentObjectChildIteratorParent].end()) { + if (*currentObjectChildIterator == objectName) + { + ++currentObjectChildIterator; + + if(currentObjectChildIterator== instanceParentsToChildren[currentObjectChildIteratorParent].end()) + { + hasIterator = false; + } + + continue; + } + + hasIterator = false; + currentObjectChildIteratorParent = ""; + } + else { + if (instanceParentsToChildren.find(objectName) != instanceParentsToChildren.end()) { + hasIterator = true; + currentObjectChildIterator = instanceParentsToChildren[objectName].begin(); + currentObjectChildIteratorParent = objectName; + } + else + { + hasIterator = false; + currentParent = ""; + } + } + } + + // Instances can be hidden if they reference a mesh without vertices, even if the original mesh has verts. + // If that happens, don't pass the instance on, unless it's a parent of another instance: + if (object->type == OB_MESH && + instanceParentsToChildren.count(objectName) == 0) { + auto mesh = (Mesh*)object->data; + if (mesh == nullptr || mesh->totvert == 0) { + continue; + } + } + handler(object, parent, world_matrix); } diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodeUtils.h b/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.h similarity index 83% rename from Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodeUtils.h rename to Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.h index 285c9dc7..6102ee3e 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodeUtils.h +++ b/Plugins~/Src/MeshSyncClientBlender/msblenGeometryNodesUtils.h @@ -37,6 +37,12 @@ class GeometryNodesUtils /// instancedObject is the object that is being instanced. /// transform is the transform of the instance /// + /// + /// The child handling function: + /// Instances that are have a parent that is also instanced are not handled by the handler function. + /// However, they may not exist in the scene and not be exported! + /// The child handler is invoked for these instances to add them to the entity manager. + /// void each_instance(std::function handler); /// @@ -60,6 +66,8 @@ class GeometryNodesUtils void setInstancesDirty(bool dirty); bool getInstancesDirty(); + std::string get_data_path(Object* obj); + private: bool m_instances_dirty; diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.cpp index b0e24097..935bb1c5 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.cpp @@ -10,6 +10,7 @@ namespace blender { #if BLENDER_VERSION < 300 void msblenModifiers::exportProperties(const Object* obj, ms::PropertyManager* propertyManager, msblenContextPathProvider& paths) {} void msblenModifiers::importProperties(std::vector props) {} +bool msblenModifiers::doesObjectHaveCustomProperties(const Object* obj) { return false; } #else // Copied from blender source that we cannot include: @@ -147,8 +148,7 @@ void addCustomProperties(const Object* obj, ms::PropertyManager* propertyManager propertyInfo->set(IDP_Int(property), uiData->min, uiData->max); break; } - case IDP_FLOAT: - { + case IDP_FLOAT: { auto uiData = (IDPropertyUIDataFloat*)property->ui_data; propertyInfo->set(IDP_Float(property), uiData->min, uiData->max); break; @@ -206,6 +206,22 @@ void addCustomProperties(const Object* obj, ms::PropertyManager* propertyManager } } +bool msblenModifiers::doesObjectHaveCustomProperties(const Object* obj) { + if (!obj->id.properties) { + return false; + } + + for (auto property : blender::list_range((IDProperty*)obj->id.properties->data.group.first)) { + if (property->ui_data == nullptr && property->type != IDP_STRING) { + continue; + } + + return true; + } + + return false; +} + void msblenModifiers::exportProperties(const Object* obj, ms::PropertyManager* propertyManager, msblenContextPathProvider& paths) { std::unique_lock lock(m_mutex); diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.h b/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.h index cf44cede..68190d77 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.h +++ b/Plugins~/Src/MeshSyncClientBlender/msblenModifiers.h @@ -11,5 +11,7 @@ namespace blender { static void exportProperties(const Object* obj, ms::PropertyManager* propertyManager, msblenContextPathProvider& paths); static void importProperties(std::vector props); + + static bool doesObjectHaveCustomProperties(const Object* obj); }; } diff --git a/Plugins~/Src/MeshSyncClientBlender/msblenUtils.cpp b/Plugins~/Src/MeshSyncClientBlender/msblenUtils.cpp index 8ff2b0f1..22ee94e9 100644 --- a/Plugins~/Src/MeshSyncClientBlender/msblenUtils.cpp +++ b/Plugins~/Src/MeshSyncClientBlender/msblenUtils.cpp @@ -23,6 +23,13 @@ std::string get_name(const Object *obj) std::string ret; if (obj) { ret.assign(obj->id.name + 2); + + if (obj->id.lib) { + ret += " ["; + ret += obj->id.lib->id.name + 2; + ret += "]"; + } + mu::SanitizeNodeName(ret); } return ret; @@ -73,9 +80,15 @@ Object* get_object_from_path(std::string path) { } auto objName = path.substr(lastIndexOfDivider + 1); + + auto bpy_data = blender::BData(blender::BlenderPyContext::get().data()); + for (auto obj : bpy_data.objects()) { + if (get_name(obj) == objName) { + return obj; + } + } - bl::BlenderPyScene scene = bl::BlenderPyScene(bl::BlenderPyContext::get().scene()); - return scene.get_object_by_name(objName); + return nullptr; } bool visible_in_render(const Object *obj) @@ -88,10 +101,12 @@ bool visible_in_viewport(const Object *obj) } bool visible_in_collection(LayerCollection* lc, const Object* obj) { + std::string objName = get_name(obj); + // Check if the object is in the layer collection, if it is, check if the layer is excluded: if (lc->collection) { for (auto collectionObject : blender::list_range((CollectionObject*)lc->collection->gobject.first)) { - if (collectionObject->ob == obj) { + if(get_name(collectionObject->ob)== objName) { if ((!(lc->flag & LAYER_COLLECTION_EXCLUDE)) && #if BLENDER_VERSION >= 300 (!(lc->collection->flag & COLLECTION_HIDE_RENDER)) diff --git a/Plugins~/Src/MeshSyncClientBlender/pch.h b/Plugins~/Src/MeshSyncClientBlender/pch.h index 3653487e..0e6a1cb1 100644 --- a/Plugins~/Src/MeshSyncClientBlender/pch.h +++ b/Plugins~/Src/MeshSyncClientBlender/pch.h @@ -48,7 +48,11 @@ namespace py = pybind11; #include "DNA_armature_types.h" #include "DNA_camera_types.h" #include "DNA_collection_types.h" +#if BLENDER_VERSION < 306 #include "DNA_gpencil_types.h" //bGPdata +#else +#include "DNA_gpencil_legacy_types.h" //bGPdata +#endif #if BLENDER_VERSION < 302 #include "DNA_hair_types.h" //Hair #endif diff --git a/Plugins~/Src/MeshSyncClientBlender/python/3.5.0/unity_mesh_sync.py b/Plugins~/Src/MeshSyncClientBlender/python/3.5.0/unity_mesh_sync.py new file mode 100644 index 00000000..c9360bbe --- /dev/null +++ b/Plugins~/Src/MeshSyncClientBlender/python/3.5.0/unity_mesh_sync.py @@ -0,0 +1,377 @@ +bl_info = { + "name": "Unity Mesh Sync", + "author": "Unity Technologies", + "blender": (3, 5, 0), + "description": "Sync Meshes with Unity", + "location": "View3D > Mesh Sync", + "tracker_url": "https://github.com/Unity-Technologies/MeshSyncDCCPlugins", + "support": "COMMUNITY", + "category": "Import-Export", +} + +import bpy +from bpy.app.handlers import persistent +from . import MeshSyncClientBlender as ms +from .unity_mesh_sync_common import * +from .unity_mesh_sync_preferences import * +from .unity_mesh_sync_materials import * + + +class MESHSYNC_PT_Main(MESHSYNC_PT, bpy.types.Panel): + bl_label = "MeshSync" + + def draw(self, context): + pass + + +class MESHSYNC_PT_Server(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Server" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_auto_config_server") + + row = layout.row() + row.prop(scene, "meshsync_server_address") + row.enabled = not context.scene.meshsync_auto_config_server + + row = layout.row() + row.prop(scene, "meshsync_server_port") + row.enabled = not context.scene.meshsync_auto_config_server + + row = layout.row() + row.prop(scene, "meshsync_editor_server_port") + row.enabled = not context.scene.meshsync_auto_config_server + + +class MESHSYNC_PT_Scene(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Scene" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_scale_factor") + layout.prop(scene, "meshsync_sync_meshes") + if scene.meshsync_sync_meshes: + b = layout.box() + b.prop(scene, "meshsync_curves_as_mesh") + b.prop(scene, "meshsync_make_double_sided") + b.prop(scene, "meshsync_bake_modifiers") + b.prop(scene, "meshsync_bake_transform") + layout.prop(scene, "meshsync_sync_bones") + layout.prop(scene, "meshsync_sync_blendshapes") + #layout.prop(scene, "meshsync_sync_textures") + layout.prop(scene, "meshsync_sync_cameras") + layout.prop(scene, "meshsync_sync_lights") + + layout.separator() + if MESHSYNC_OT_AutoSync._timer: + layout.operator("meshsync.auto_sync", text="Auto Sync", icon="PAUSE") + else: + layout.operator("meshsync.auto_sync", text="Auto Sync", icon="PLAY") + layout.operator("meshsync.send_objects", text="Manual Sync") + layout.operator("meshsync.send_selected_objects", text="Manual Sync (Selection only)") + + +class MESHSYNC_PT_Animation(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Animation" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_frame_step") + layout.operator("meshsync.send_animations", text="Sync") + + +class MESHSYNC_PT_Cache(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Cache" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.operator("meshsync.export_cache", text="Export Cache") + + +class MESHSYNC_PT_Version(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Plugin Version" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.label(text = msb_context.PLUGIN_VERSION) + +class MESHSYNC_PT_UnityProject(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Unity Project" + bl_parent_id = "MESHSYNC_PT_Main" + + initialized = False + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + preferences = context.preferences + addon_prefs = preferences.addons[__package__].preferences + + layout.prop(addon_prefs, "project_path") + +class MESHSYNC_OT_AutoSync(bpy.types.Operator): + bl_idname = "meshsync.auto_sync" + bl_label = "Auto Sync" + _timer = None + _registered = False + + def __del__(self): + MESHSYNC_OT_AutoSync._timer = None + + def execute(self, context): + return self.invoke(context, None) + + # When a file is loaded, the modal registration is reset: + @persistent + def load_handler(dummy): + MESHSYNC_OT_AutoSync._registered = False + + def invoke(self, context, event): + scene = bpy.context.scene + if not MESHSYNC_OT_AutoSync._timer: + + setup = msb_try_setup_scene_server(context) + if msb_error_messages_for_status(setup, context) == False: + return {'FINISHED'} + + scene.meshsync_auto_sync = True + if not scene.meshsync_auto_sync: + # server not available + return {'FINISHED'} + update_step = 0.01 # 1.0/3.0 + MESHSYNC_OT_AutoSync._timer = context.window_manager.event_timer_add(update_step, window=context.window) + + # There is no way to unregister modal callbacks! + # To ensure this does not get repeatedly registered, keep track of it and only do it once: + if not MESHSYNC_OT_AutoSync._registered: + context.window_manager.modal_handler_add(self) + MESHSYNC_OT_AutoSync._registered = True + + if bpy.app.background: + import time + while True: + time.sleep(update_step) + self.update() + + return {'RUNNING_MODAL'} + else: + scene.meshsync_auto_sync = False + context.window_manager.event_timer_remove(MESHSYNC_OT_AutoSync._timer) + MESHSYNC_OT_AutoSync._timer = None + return {'FINISHED'} + + def modal(self, context, event): + if event.type == "TIMER": + self.update() + return {'PASS_THROUGH'} + + def update(self): + if not bpy.context.scene.meshsync_auto_sync: + return + msb_context.flushPendingList() + msb_apply_scene_settings() + msb_context.setup(bpy.context) + msb_context.exportUpdatedObjects() + +class MESHSYNC_OT_ExportCache(bpy.types.Operator): + bl_idname = "meshsync.export_cache" + bl_label = "Export Cache" + bl_description = "Export Cache" + + def on_bake_modifiers_updated(self = None, context = None): + if not self.bake_modifiers: + self.bake_transform = False + + def on_bake_transform_updated(self = None, context = None): + if self.bake_transform: + self.bake_modifiers = True + + filepath: bpy.props.StringProperty(subtype = "FILE_PATH") + filename: bpy.props.StringProperty() + directory: bpy.props.StringProperty(subtype = "FILE_PATH") + + # cache properties + object_scope: bpy.props.EnumProperty( + name = "Object Scope", + default = "0", + items = { + ("0", "All", "Export all objects"), + ("1", "Selected", "Export selected objects"), + }) + frame_range: bpy.props.EnumProperty( + name = "Frame Range", + default = "1", + items = { + ("0", "Current", "Export current frame"), + ("1", "All", "Export all frames"), + ("2", "Custom", "Export speficied frames"), + }) + frame_begin: bpy.props.IntProperty(name = "Frame Begin", default = 1) + frame_end: bpy.props.IntProperty(name = "Frame End", default = 100) + frame_step: bpy.props.IntProperty(name = "Frame Step", default = 1, min = 1) + material_frame_range: bpy.props.EnumProperty( + name = "Material Range", + default = "1", + items = { + ("0", "None", "Export no materials"), + ("1", "One", "Export one frame of materials"), + ("2", "All", "Export all frames of materials"), + }) + zstd_compression_level: bpy.props.IntProperty(name = "ZSTD Compression", default = 3) + curves_as_mesh: bpy.props.BoolProperty(name = "Curves as Mesh", default = True) + make_double_sided: bpy.props.BoolProperty(name = "Make Double Sided", default = False) + bake_modifiers: bpy.props.BoolProperty(name = "Bake Modifiers", default = True, update = on_bake_modifiers_updated) + bake_transform: bpy.props.BoolProperty(name = "Bake Transform", default = False, update = on_bake_transform_updated) + flatten_hierarchy: bpy.props.BoolProperty(name = "Flatten Hierarchy", default = False) + merge_meshes: bpy.props.BoolProperty(name = "Merge Meshes", default = False) + strip_normals: bpy.props.BoolProperty(name = "Strip Normals", default = False) + strip_tangents: bpy.props.BoolProperty(name = "Strip Tangents", default = False) + + def execute(self, context): + ctx = msb_cache + ctx.object_scope = int(self.object_scope) + ctx.frame_range = int(self.frame_range) + ctx.frame_begin = self.frame_begin + ctx.frame_end = self.frame_end + ctx.material_frame_range = int(self.material_frame_range) + ctx.zstd_compression_level = self.zstd_compression_level + ctx.frame_step = self.frame_step + ctx.curves_as_mesh = self.curves_as_mesh + ctx.make_double_sided = self.make_double_sided + ctx.bake_modifiers = self.bake_modifiers + ctx.bake_transform = self.bake_transform + ctx.flatten_hierarchy = self.flatten_hierarchy + ctx.merge_meshes = self.merge_meshes + ctx.strip_normals = self.strip_normals + ctx.strip_tangents = self.strip_tangents + ctx.export(self.filepath) + MS_MessageBox("Finished writing scene cache to " + self.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + msb_context.setup(bpy.context) + ctx = msb_cache + self.object_scope = str(ctx.object_scope); + self.frame_range = str(ctx.frame_range); + self.frame_begin = ctx.frame_begin; + self.frame_end = ctx.frame_end; + self.material_frame_range = str(ctx.material_frame_range); + self.frame_end = ctx.frame_end; + self.zstd_compression_level = ctx.zstd_compression_level; + self.frame_step = round(ctx.frame_step); + self.curves_as_mesh = ctx.curves_as_mesh; + self.make_double_sided = ctx.make_double_sided; + self.bake_modifiers = ctx.bake_modifiers; + self.bake_transform = ctx.bake_transform; + self.flatten_hierarchy = ctx.flatten_hierarchy; + self.merge_meshes = ctx.merge_meshes; + self.strip_normals = ctx.strip_normals; + self.strip_tangents = ctx.strip_tangents; + + path = bpy.data.filepath + if len(path) != 0: + tmp = os.path.split(path) + self.directory = tmp[0] + self.filename = re.sub(r"\.[^.]+$", ".sc", tmp[1]) + else: + self.directory = "" + self.filename = "Untitled.sc"; + wm = bpy.context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + def draw(self, context): + layout = self.layout + if hasattr(layout, "use_property_split"): # false on 2.79 + layout.use_property_split = True + layout.prop(self, "object_scope") + layout.prop(self, "frame_range") + if self.frame_range == "2": + b = layout.box() + b.prop(self, "frame_begin") + b.prop(self, "frame_end") + layout.prop(self, "material_frame_range") + layout.prop(self, "zstd_compression_level") + layout.prop(self, "frame_step") + layout.prop(self, "curves_as_mesh") + layout.prop(self, "make_double_sided") + layout.prop(self, "bake_modifiers") + layout.prop(self, "bake_transform") + layout.prop(self, "flatten_hierarchy") + #layout.prop(self, "merge_meshes") + layout.prop(self, "strip_normals") + layout.prop(self, "strip_tangents") + +# --------------------------------------------------------------------------------------------------------------------- + +from .unity_mesh_sync_baking import MESHSYNC_PT_Baking, msb_setBakingDefaults + +classes = [ + MESHSYNC_PT_Main, + MESHSYNC_PT_Server, + MESHSYNC_PT_Scene, + MESHSYNC_PT_Materials, + MESHSYNC_PT_Baking, + MESHSYNC_PT_Animation, + MESHSYNC_PT_Cache, + MESHSYNC_PT_Version, + MESHSYNC_OT_SendAnimations, + MESHSYNC_OT_AutoSync, + MESHSYNC_OT_ExportCache, + MESHSYNC_OT_SendMaterials, + MESHSYNC_Preferences +] + sharedClasses + +def register(): + bpy.app.handlers.load_post.append(MESHSYNC_OT_AutoSync.load_handler) + for c in classes: + bpy.utils.register_class(c) + msb_initialize_properties() + bpy.app.handlers.load_post.append(msb_setBakingDefaults) + +def unregister(): + msb_context.Destroy() + for c in classes: + bpy.utils.unregister_class(c) + bpy.app.handlers.load_post.remove(msb_setBakingDefaults) + +def DestroyMeshSyncContext(): + msb_context.Destroy() + +import atexit +atexit.register(DestroyMeshSyncContext) + +@persistent +def on_depsgraph_update_post(scene): + graph = bpy.context.evaluated_depsgraph_get() + msb_context.setup(bpy.context) + msb_context.OnDepsgraphUpdatePost(graph) + +bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post) +bpy.app.handlers.load_post.append(on_depsgraph_update_post) + +# --------------------------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + register() diff --git a/Plugins~/Src/MeshSyncClientBlender/python/3.6.2/unity_mesh_sync.py b/Plugins~/Src/MeshSyncClientBlender/python/3.6.2/unity_mesh_sync.py new file mode 100644 index 00000000..53c8c5bc --- /dev/null +++ b/Plugins~/Src/MeshSyncClientBlender/python/3.6.2/unity_mesh_sync.py @@ -0,0 +1,377 @@ +bl_info = { + "name": "Unity Mesh Sync", + "author": "Unity Technologies", + "blender": (3, 6, 2), + "description": "Sync Meshes with Unity", + "location": "View3D > Mesh Sync", + "tracker_url": "https://github.com/Unity-Technologies/MeshSyncDCCPlugins", + "support": "COMMUNITY", + "category": "Import-Export", +} + +import bpy +from bpy.app.handlers import persistent +from . import MeshSyncClientBlender as ms +from .unity_mesh_sync_common import * +from .unity_mesh_sync_preferences import * +from .unity_mesh_sync_materials import * + + +class MESHSYNC_PT_Main(MESHSYNC_PT, bpy.types.Panel): + bl_label = "MeshSync" + + def draw(self, context): + pass + + +class MESHSYNC_PT_Server(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Server" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_auto_config_server") + + row = layout.row() + row.prop(scene, "meshsync_server_address") + row.enabled = not context.scene.meshsync_auto_config_server + + row = layout.row() + row.prop(scene, "meshsync_server_port") + row.enabled = not context.scene.meshsync_auto_config_server + + row = layout.row() + row.prop(scene, "meshsync_editor_server_port") + row.enabled = not context.scene.meshsync_auto_config_server + + +class MESHSYNC_PT_Scene(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Scene" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_scale_factor") + layout.prop(scene, "meshsync_sync_meshes") + if scene.meshsync_sync_meshes: + b = layout.box() + b.prop(scene, "meshsync_curves_as_mesh") + b.prop(scene, "meshsync_make_double_sided") + b.prop(scene, "meshsync_bake_modifiers") + b.prop(scene, "meshsync_bake_transform") + layout.prop(scene, "meshsync_sync_bones") + layout.prop(scene, "meshsync_sync_blendshapes") + #layout.prop(scene, "meshsync_sync_textures") + layout.prop(scene, "meshsync_sync_cameras") + layout.prop(scene, "meshsync_sync_lights") + + layout.separator() + if MESHSYNC_OT_AutoSync._timer: + layout.operator("meshsync.auto_sync", text="Auto Sync", icon="PAUSE") + else: + layout.operator("meshsync.auto_sync", text="Auto Sync", icon="PLAY") + layout.operator("meshsync.send_objects", text="Manual Sync") + layout.operator("meshsync.send_selected_objects", text="Manual Sync (Selection only)") + + +class MESHSYNC_PT_Animation(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Animation" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.prop(scene, "meshsync_frame_step") + layout.operator("meshsync.send_animations", text="Sync") + + +class MESHSYNC_PT_Cache(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Cache" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + layout.operator("meshsync.export_cache", text="Export Cache") + + +class MESHSYNC_PT_Version(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Plugin Version" + bl_parent_id = "MESHSYNC_PT_Main" + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.label(text = msb_context.PLUGIN_VERSION) + +class MESHSYNC_PT_UnityProject(MESHSYNC_PT, bpy.types.Panel): + bl_label = "Unity Project" + bl_parent_id = "MESHSYNC_PT_Main" + + initialized = False + + def draw(self, context): + scene = bpy.context.scene + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False + preferences = context.preferences + addon_prefs = preferences.addons[__package__].preferences + + layout.prop(addon_prefs, "project_path") + +class MESHSYNC_OT_AutoSync(bpy.types.Operator): + bl_idname = "meshsync.auto_sync" + bl_label = "Auto Sync" + _timer = None + _registered = False + + def __del__(self): + MESHSYNC_OT_AutoSync._timer = None + + def execute(self, context): + return self.invoke(context, None) + + # When a file is loaded, the modal registration is reset: + @persistent + def load_handler(dummy): + MESHSYNC_OT_AutoSync._registered = False + + def invoke(self, context, event): + scene = bpy.context.scene + if not MESHSYNC_OT_AutoSync._timer: + + setup = msb_try_setup_scene_server(context) + if msb_error_messages_for_status(setup, context) == False: + return {'FINISHED'} + + scene.meshsync_auto_sync = True + if not scene.meshsync_auto_sync: + # server not available + return {'FINISHED'} + update_step = 0.01 # 1.0/3.0 + MESHSYNC_OT_AutoSync._timer = context.window_manager.event_timer_add(update_step, window=context.window) + + # There is no way to unregister modal callbacks! + # To ensure this does not get repeatedly registered, keep track of it and only do it once: + if not MESHSYNC_OT_AutoSync._registered: + context.window_manager.modal_handler_add(self) + MESHSYNC_OT_AutoSync._registered = True + + if bpy.app.background: + import time + while True: + time.sleep(update_step) + self.update() + + return {'RUNNING_MODAL'} + else: + scene.meshsync_auto_sync = False + context.window_manager.event_timer_remove(MESHSYNC_OT_AutoSync._timer) + MESHSYNC_OT_AutoSync._timer = None + return {'FINISHED'} + + def modal(self, context, event): + if event.type == "TIMER": + self.update() + return {'PASS_THROUGH'} + + def update(self): + if not bpy.context.scene.meshsync_auto_sync: + return + msb_context.flushPendingList() + msb_apply_scene_settings() + msb_context.setup(bpy.context) + msb_context.exportUpdatedObjects() + +class MESHSYNC_OT_ExportCache(bpy.types.Operator): + bl_idname = "meshsync.export_cache" + bl_label = "Export Cache" + bl_description = "Export Cache" + + def on_bake_modifiers_updated(self = None, context = None): + if not self.bake_modifiers: + self.bake_transform = False + + def on_bake_transform_updated(self = None, context = None): + if self.bake_transform: + self.bake_modifiers = True + + filepath: bpy.props.StringProperty(subtype = "FILE_PATH") + filename: bpy.props.StringProperty() + directory: bpy.props.StringProperty(subtype = "FILE_PATH") + + # cache properties + object_scope: bpy.props.EnumProperty( + name = "Object Scope", + default = "0", + items = { + ("0", "All", "Export all objects"), + ("1", "Selected", "Export selected objects"), + }) + frame_range: bpy.props.EnumProperty( + name = "Frame Range", + default = "1", + items = { + ("0", "Current", "Export current frame"), + ("1", "All", "Export all frames"), + ("2", "Custom", "Export speficied frames"), + }) + frame_begin: bpy.props.IntProperty(name = "Frame Begin", default = 1) + frame_end: bpy.props.IntProperty(name = "Frame End", default = 100) + frame_step: bpy.props.IntProperty(name = "Frame Step", default = 1, min = 1) + material_frame_range: bpy.props.EnumProperty( + name = "Material Range", + default = "1", + items = { + ("0", "None", "Export no materials"), + ("1", "One", "Export one frame of materials"), + ("2", "All", "Export all frames of materials"), + }) + zstd_compression_level: bpy.props.IntProperty(name = "ZSTD Compression", default = 3) + curves_as_mesh: bpy.props.BoolProperty(name = "Curves as Mesh", default = True) + make_double_sided: bpy.props.BoolProperty(name = "Make Double Sided", default = False) + bake_modifiers: bpy.props.BoolProperty(name = "Bake Modifiers", default = True, update = on_bake_modifiers_updated) + bake_transform: bpy.props.BoolProperty(name = "Bake Transform", default = False, update = on_bake_transform_updated) + flatten_hierarchy: bpy.props.BoolProperty(name = "Flatten Hierarchy", default = False) + merge_meshes: bpy.props.BoolProperty(name = "Merge Meshes", default = False) + strip_normals: bpy.props.BoolProperty(name = "Strip Normals", default = False) + strip_tangents: bpy.props.BoolProperty(name = "Strip Tangents", default = False) + + def execute(self, context): + ctx = msb_cache + ctx.object_scope = int(self.object_scope) + ctx.frame_range = int(self.frame_range) + ctx.frame_begin = self.frame_begin + ctx.frame_end = self.frame_end + ctx.material_frame_range = int(self.material_frame_range) + ctx.zstd_compression_level = self.zstd_compression_level + ctx.frame_step = self.frame_step + ctx.curves_as_mesh = self.curves_as_mesh + ctx.make_double_sided = self.make_double_sided + ctx.bake_modifiers = self.bake_modifiers + ctx.bake_transform = self.bake_transform + ctx.flatten_hierarchy = self.flatten_hierarchy + ctx.merge_meshes = self.merge_meshes + ctx.strip_normals = self.strip_normals + ctx.strip_tangents = self.strip_tangents + ctx.export(self.filepath) + MS_MessageBox("Finished writing scene cache to " + self.filepath) + return {'FINISHED'} + + def invoke(self, context, event): + msb_context.setup(bpy.context) + ctx = msb_cache + self.object_scope = str(ctx.object_scope); + self.frame_range = str(ctx.frame_range); + self.frame_begin = ctx.frame_begin; + self.frame_end = ctx.frame_end; + self.material_frame_range = str(ctx.material_frame_range); + self.frame_end = ctx.frame_end; + self.zstd_compression_level = ctx.zstd_compression_level; + self.frame_step = round(ctx.frame_step); + self.curves_as_mesh = ctx.curves_as_mesh; + self.make_double_sided = ctx.make_double_sided; + self.bake_modifiers = ctx.bake_modifiers; + self.bake_transform = ctx.bake_transform; + self.flatten_hierarchy = ctx.flatten_hierarchy; + self.merge_meshes = ctx.merge_meshes; + self.strip_normals = ctx.strip_normals; + self.strip_tangents = ctx.strip_tangents; + + path = bpy.data.filepath + if len(path) != 0: + tmp = os.path.split(path) + self.directory = tmp[0] + self.filename = re.sub(r"\.[^.]+$", ".sc", tmp[1]) + else: + self.directory = "" + self.filename = "Untitled.sc"; + wm = bpy.context.window_manager + wm.fileselect_add(self) + return {'RUNNING_MODAL'} + + def draw(self, context): + layout = self.layout + if hasattr(layout, "use_property_split"): # false on 2.79 + layout.use_property_split = True + layout.prop(self, "object_scope") + layout.prop(self, "frame_range") + if self.frame_range == "2": + b = layout.box() + b.prop(self, "frame_begin") + b.prop(self, "frame_end") + layout.prop(self, "material_frame_range") + layout.prop(self, "zstd_compression_level") + layout.prop(self, "frame_step") + layout.prop(self, "curves_as_mesh") + layout.prop(self, "make_double_sided") + layout.prop(self, "bake_modifiers") + layout.prop(self, "bake_transform") + layout.prop(self, "flatten_hierarchy") + #layout.prop(self, "merge_meshes") + layout.prop(self, "strip_normals") + layout.prop(self, "strip_tangents") + +# --------------------------------------------------------------------------------------------------------------------- + +from .unity_mesh_sync_baking import MESHSYNC_PT_Baking, msb_setBakingDefaults + +classes = [ + MESHSYNC_PT_Main, + MESHSYNC_PT_Server, + MESHSYNC_PT_Scene, + MESHSYNC_PT_Materials, + MESHSYNC_PT_Baking, + MESHSYNC_PT_Animation, + MESHSYNC_PT_Cache, + MESHSYNC_PT_Version, + MESHSYNC_OT_SendAnimations, + MESHSYNC_OT_AutoSync, + MESHSYNC_OT_ExportCache, + MESHSYNC_OT_SendMaterials, + MESHSYNC_Preferences +] + sharedClasses + +def register(): + bpy.app.handlers.load_post.append(MESHSYNC_OT_AutoSync.load_handler) + for c in classes: + bpy.utils.register_class(c) + msb_initialize_properties() + bpy.app.handlers.load_post.append(msb_setBakingDefaults) + +def unregister(): + msb_context.Destroy() + for c in classes: + bpy.utils.unregister_class(c) + bpy.app.handlers.load_post.remove(msb_setBakingDefaults) + +def DestroyMeshSyncContext(): + msb_context.Destroy() + +import atexit +atexit.register(DestroyMeshSyncContext) + +@persistent +def on_depsgraph_update_post(scene): + graph = bpy.context.evaluated_depsgraph_get() + msb_context.setup(bpy.context) + msb_context.OnDepsgraphUpdatePost(graph) + +bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post) +bpy.app.handlers.load_post.append(on_depsgraph_update_post) + +# --------------------------------------------------------------------------------------------------------------------- + +if __name__ == "__main__": + register() diff --git a/package.json b/package.json index 08e37a53..82e69c1e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.meshsync.dcc-plugins", "displayName":"MeshSync DCC Plugins", - "version": "0.17.1-preview", + "version": "0.18.0-preview", "unity": "2020.3", "description": "This package contains plugin binaries of DCC tools for using MeshSync, which is another package for synchronizing meshes/models editing in DCC tools into Unity in real time.", "dependencies": {