From 02cfb350d83f723af3e3025f49fc718f257074b3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 11 Jun 2023 03:07:43 +1000 Subject: [PATCH 001/295] Fix missing Solver Camera Widget UI build step. --- python/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 1dec26513..ee6ef25f1 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -129,6 +129,11 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/solver/widget/ui_solver_standard_widget.py ) + compile_qt_ui_to_python_file("solver_solver_camera_widget" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/solver/widget/solver_camera_widget.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/solver/widget/ui_solver_camera_widget.py + ) + compile_qt_ui_to_python_file("solver_solverstate_widget" ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/solver/widget/solverstate_widget.ui ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/solver/widget/ui_solverstate_widget.py From bf59c5dc77a5c1442145510c81ed4ac8e7ad3974 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 11 Jun 2023 03:15:44 +1000 Subject: [PATCH 002/295] Fix incorrect directories in clang format scripts. From 5fc5f70da917be742145fdbba5fa7223a8726a38 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 27 Jun 2023 13:32:11 +1000 Subject: [PATCH 003/295] Save Lens File - Fix Python3 error. This error caused the Lens files not to be exported at all :( --- python/mmSolver/tools/savelensfile/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/savelensfile/lib.py b/python/mmSolver/tools/savelensfile/lib.py index 2a250a374..1e04f5d3b 100644 --- a/python/mmSolver/tools/savelensfile/lib.py +++ b/python/mmSolver/tools/savelensfile/lib.py @@ -87,7 +87,7 @@ def generate(cam, lens, frame_range): assert isinstance(cam, mmapi.Camera) assert isinstance(lens, mmapi.Lens) - frames = range(frame_range.start, frame_range.end + 1) + frames = list(range(frame_range.start, frame_range.end + 1)) assert len(frames) > 0 lens_node = lens.get_node() From 25568f89b7bc2bc78056844e9dfb87045d5f2896 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 27 Jun 2023 13:33:17 +1000 Subject: [PATCH 004/295] Fix CMake install_target_plugin_to_module to use variable. We don't want to use a global variable inside a function. --- share/cmake/modules/MMSolverUtils.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/share/cmake/modules/MMSolverUtils.cmake b/share/cmake/modules/MMSolverUtils.cmake index afc2d274b..83ff774cc 100644 --- a/share/cmake/modules/MMSolverUtils.cmake +++ b/share/cmake/modules/MMSolverUtils.cmake @@ -285,8 +285,8 @@ function(install_target_plugin_to_module target module_dir) ARCHIVE_OUTPUT_DIRECTORY "${module_dir}") install(TARGETS ${target} - RUNTIME DESTINATION "${MODULE_FULL_NAME}/plug-ins" - LIBRARY DESTINATION "${MODULE_FULL_NAME}/plug-ins") + RUNTIME DESTINATION "${module_dir}/plug-ins" + LIBRARY DESTINATION "${module_dir}/plug-ins") endfunction() From 20489c08e2d25b720d30ef8ef5bff31361b87c29 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 27 Jun 2023 13:34:29 +1000 Subject: [PATCH 005/295] Update global C++ flags, and explain the usage. This is mostly intended to improve the debug flags, to help catching bugs easier. --- share/cmake/modules/MMSolverUtils.cmake | 58 ++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/share/cmake/modules/MMSolverUtils.cmake b/share/cmake/modules/MMSolverUtils.cmake index 83ff774cc..9076b69c6 100644 --- a/share/cmake/modules/MMSolverUtils.cmake +++ b/share/cmake/modules/MMSolverUtils.cmake @@ -38,8 +38,15 @@ function(set_global_maya_plugin_compile_options) if (MSVC) # For Visual Studio 11 2012 set(CMAKE_CXX_FLAGS "") # Zero out the C++ flags, we have complete control. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS /Zc:wchar_t /Zi /fp:precise /Zc:forScope /GR /Gd /EHsc") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS") # Buffer Security Check + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:wchar_t") # wchar_t Is Native Type + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") # Separate .pdb debug info file. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") # Precise floating-point behavior + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:forScope") # Force Conformance in for Loop Scope + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR") # Enable Run-Time Type Information + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Gd") # __cdecl Calling Convention + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # Handle standard C++ Exceptions. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") # Warning Levels 1-4 enabled. add_compile_definitions(OSWin_) add_compile_definitions(WIN32) @@ -60,12 +67,38 @@ function(set_global_maya_plugin_compile_options) add_compile_definitions(NT_PLUGIN) add_compile_definitions(USERDLL) + # Use multithread-specific Run-Time Library. set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /MD") - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Gy /Gm- /O2 /Ob1 /GF") + add_compile_options("/arch:AVX2") + + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Gy") # Enable Function-Level Linking + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GF") # Eliminate Duplicate Strings + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") # Optimize for Maximize Speed + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi") # Generate Intrinsic Functions + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") # Whole program optimization + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:AVX2") # Use AVX2 instructions + + # Link-time code generation. + # + # /LTGC and /GL work together and therefore are both enabled. + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG") + + # Use debug-specific Run-Time Library. set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} /MDd") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gm /Od /RTC1") - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob0 /GR /GL /Oi /Gy /Zi /EHsc") + + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od") # Optimize for Debug. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /RTC1") # Run-time error checks + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob0") # Disables inline expansions. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GR") # Enable Run-Time Type Information + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GL") # Whole program optimization + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Oi") # Generate Intrinsic Functions + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gy") # Enable Function-Level Linking + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi") # Debug Information Format + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /EHsc") # Handle standard C++ Exceptions. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /analyze") # Code analysis # Ensure Google Logging does not use "ERROR" macro, because Windows # doesn't support it. @@ -174,8 +207,19 @@ function(set_global_maya_plugin_compile_options) # the C++11 standard says 1024 is the default. add_definitions(-ftemplate-depth-900) - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -m64") + set(CMAKE_CXX_FLAGS_DEBUG "-O0") # No optmizations. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g") # Include debug symbols + + # Enable AddressSanitizer. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") + + # Nicer stack traces in error messages + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer") + + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") # Optimize for maximum performance. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -m64") # Set 64-bit machine. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=skylake") # Use AVX2 instructions + endif () endfunction() From 315481fe62a4c9bb3ea6304c5b433f653032c779 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 27 Jun 2023 13:37:18 +1000 Subject: [PATCH 006/295] MM ImagePlane Geometry Override - replace debug prints. Rather than commented code, now we can enable/disable the logging with the 'verbose' variable. --- .../shape/ImagePlaneGeometryOverride.cpp | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index c2ea207d4..356eb98a5 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -102,9 +102,9 @@ bool getUpstreamNodeFromConnection(const MObject &this_node, return false; } if (plug.isNull()) { - MMSOLVER_WRN("Could not get plug for \"" << mfn_depend_node.name() - << "." << attr_name.asChar() - << "\" node."); + MMSOLVER_WRN("Could not get plug for \"" + << mfn_depend_node.name().asChar() << "." + << attr_name.asChar() << "\" node."); return false; } @@ -118,15 +118,16 @@ bool getUpstreamNodeFromConnection(const MObject &this_node, return false; } if (out_connections.length() == 0) { - MMSOLVER_WRN("No connections to the \"" << mfn_depend_node.name() << "." - << attr_name.asChar() - << "\" attribute."); + MMSOLVER_WRN("No connections to the \"" + << mfn_depend_node.name().asChar() << "." + << attr_name.asChar() << "\" attribute."); return false; } return true; } void ImagePlaneGeometryOverride::updateDG() { + const auto verbose = false; if (!m_geometry_node_path.isValid()) { MString attr_name = "geometryNode"; MPlugArray connections; @@ -142,11 +143,10 @@ void ImagePlaneGeometryOverride::updateDG() { MDagPath::getAPathTo(node, path); m_geometry_node_path = path; m_geometry_node_type = path.apiType(); - // MMSOLVER_INFO( - // "Validated geometry node: " - // << " path=" << - // m_geometry_node_path.fullPathName().asChar() - // << " type=" << node.apiTypeStr()); + MMSOLVER_VRB("Validated geometry node: " + << " path=" + << m_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); break; } else { MMSOLVER_WRN("Geometry node is not correct type:" @@ -175,10 +175,9 @@ void ImagePlaneGeometryOverride::updateDG() { node.hasFn(MFn::kPluginHwShaderNode)) { m_shader_node = node; m_shader_node_type = node.apiType(); - // MMSOLVER_INFO( - // "Validated shader node:" - // << " name=" << mfn_depend_node.name().asChar() - // << " type=" << node.apiTypeStr()); + MMSOLVER_VRB("Validated shader node:" + << " name=" << mfn_depend_node.name().asChar() + << " type=" << node.apiTypeStr()); break; } else { MMSOLVER_WRN("Shader node is not correct type: " @@ -204,11 +203,10 @@ void ImagePlaneGeometryOverride::updateDG() { MDagPath::getAPathTo(node, path); m_camera_node_path = path; m_camera_node_type = path.apiType(); - // MMSOLVER_INFO( - // "Validated camera node: " - // << " path=" << - // m_camera_node_path.fullPathName().asChar() - // << " type=" << node.apiTypeStr()); + MMSOLVER_VRB("Validated camera node: " + << " path=" + << m_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); break; } else { MMSOLVER_WRN("Camera node is not correct type:" @@ -346,10 +344,10 @@ void ImagePlaneGeometryOverride::updateDG() { void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, MRenderItemList &list) { + const bool verbose = false; if (!m_geometry_node_path.isValid()) { - // MMSOLVER_WRN( - // "mmImagePlaneShape: " - // << "Geometry node DAG path is not valid."); + MMSOLVER_VRB("mmImagePlaneShape: " + << "Geometry node DAG path is not valid."); return; } @@ -404,7 +402,7 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, if (index >= 0) { shadedItem = list.itemAt(index); } else { - // MMSOLVER_INFO("mmImagePlaneShape: Generate shaded MRenderItem..."); + MMSOLVER_VRB("mmImagePlaneShape: Generate shaded MRenderItem..."); shadedItem = MRenderItem::Create(renderItemName_imagePlaneShaded, MRenderItem::NonMaterialSceneItem, MGeometry::kTriangles); @@ -462,9 +460,9 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, void ImagePlaneGeometryOverride::populateGeometry( const MGeometryRequirements &requirements, const MRenderItemList &renderItems, MGeometry &data) { + const bool verbose = false; if (!m_geometry_node_path.isValid()) { - // MMSOLVER_WRN("mmImagePlaneShape: Geometry node DAG path is not - // valid."); + MMSOLVER_VRB("mmImagePlaneShape: Geometry node DAG path is not valid."); return; } @@ -572,21 +570,21 @@ void ImagePlaneGeometryOverride::cleanUp() {} #if MAYA_API_VERSION >= 20190000 bool ImagePlaneGeometryOverride::requiresGeometryUpdate() const { + const bool verbose = false; if (m_geometry_node_path.isValid() && !m_shader_node.isNull()) { - // MMSOLVER_INFO("ImagePlaneGeometryOverride::requiresGeometryUpdate: - // false"); + MMSOLVER_VRB( + "ImagePlaneGeometryOverride::requiresGeometryUpdate: false"); return false; } - // MMSOLVER_INFO("ImagePlaneGeometryOverride::requiresGeometryUpdate: - // true"); + MMSOLVER_VRB("ImagePlaneGeometryOverride::requiresGeometryUpdate: true"); return true; } bool ImagePlaneGeometryOverride::requiresUpdateRenderItems( const MDagPath &path) const { - // MMSOLVER_INFO( - // "ImagePlaneGeometryOverride::requiresUpdateRenderItems: true: " - // << path.fullPathName()); + const bool verbose = false; + MMSOLVER_VRB("ImagePlaneGeometryOverride::requiresUpdateRenderItems: true: " + << path.fullPathName().asChar()); return true; // Always update the render items. } #endif From eca7dc68d265b474efce7256008d58c5324d4434 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 29 Jun 2023 21:57:14 +1000 Subject: [PATCH 007/295] Docs - Describe a technique for Solving Maya Rigs. --- docs/source/index.rst | 1 + docs/source/techniques.rst | 9 +- docs/source/techniques_rigs.rst | 247 ++++++++++++++++++++++++++++++++ docs/source/tools_solver_ui.rst | 2 + docs/source/tutorial.rst | 2 + 5 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 docs/source/techniques_rigs.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 7cd2d00c2..0cef2f435 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -27,6 +27,7 @@ An overview of the features available inside mmSolver. introduction tutorial tools + techniques Technical Details +++++++++++++++++ diff --git a/docs/source/techniques.rst b/docs/source/techniques.rst index b665adf0d..67a754597 100644 --- a/docs/source/techniques.rst +++ b/docs/source/techniques.rst @@ -1,5 +1,5 @@ -MM Solver Techniques -==================== +Techniques +========== MM Solver is not like other 3D MatchMove software and you can use different techniques that is only really possible in MM Solver. Below @@ -8,7 +8,4 @@ lists some example techniques for common tasks. .. toctree:: :maxdepth: 2 - techniques_lens_distortion - techniques_rotomation - techniques_solving_cameras - techniques_solving_rigid_objects + techniques_rigs diff --git a/docs/source/techniques_rigs.rst b/docs/source/techniques_rigs.rst new file mode 100644 index 000000000..7c58e0802 --- /dev/null +++ b/docs/source/techniques_rigs.rst @@ -0,0 +1,247 @@ +.. _techniques-rigs-solving-maya-rigs-ref: + +Solving Maya Rigs +================= + +MM Solver can be used to solve the controls of a Maya rig, for example +a deforming biped or quadruped character, or rigid object, such as a +vehicle or prop. + +To get started, you will need: + +- 3D Geometry representing a real-world object. + +- A Maya Rig with Controls to deform or transform the 3D geometry. + +For very simple rigs, such as a rigid object, a rig may not be needed. + +Rigid Transformations ++++++++++++++++++++++ + +Rigid transformations are the easiest and fastest to solve with MM +Solver. Rigid transformations use the translation, rotation and scale +XYZ values to move 3D geometry. + +If your target object is moving with rigid motion, always solve rigid +transformations because these will provide the best quality, with the +least number of 2D Markers and 3D Bundles. Even if an object is not +perfectly rigid, solving rigidly may provide a good starting point to +manually tweak or solve deformations. + +For an in-depth example of rigid solving (with a hierarchy) the +:ref:`Robot Arm - Rigid Hierarchy Solving +` tutorial will +provide details. + +To solve a rigid object: + +#. Create 2D Markers for your object. + +#. Parent the 3D Bundles of your Markers under the object's + transform node. + +#. Move the 3D Bundles to the correct 3D position on the object, and + lock the translation XYZ attributes. + +#. Open the :ref:`Solver UI `. + +#. Select and add the 2D Markers to the *Input Objects* section of the + *Solver UI* (with the *+* button). + +#. Select your object's transform node, in the Channel Box, select the + attributes that you wish to solve. + +#. Add the selected attributes to the *Output Attributes* section + (with the *+* button). + +#. Press *Solve* button in the *Solver UI*. + +Rigid Solving Tips +------------------ + +- Limit the number of attributes, needed for solving; lock all + attributes that cannot move. + + - e.g. If a vehicle is moving along the flat ground, place the car + on the ground and lock the translation Y attribute. + + - e.g. A door can only rotate from the pivot point of the hinges, + create a transform node at the hinges, and parent your object + under the transform node, then solve for the rotation of the door + hinge - probably only a single attribute. + +- Avoid solving scale because the solver can quickly get confused. + + - If you must solve an object's scale, try to limit to a single + attribute (X, Y or Z). + + - If you need to solve the uniform scale of an object you must + create connections from *Scale Y* to *Scale X* and *Scale Z*. + This will allow you to solve only *Scale Y*, but both *Scale X* + and *Scale Z* will have the exact same value. You can create these + connections using Maya's *Connection Editor* window. + +- Avoid large attribute values. MM Solver can get confused when an + attribute is very large. Try to avoid using very large values for + transformations (for example 10,000 units away from origin). + +- When talking about rigid transforms, it's easy to refer to + translation attributes as TXYZ rather than "Translation X, Y and + Z". Same goes for Rotation ("RXYZ"), and Scale ("SXYZ"). + +Solving Rig Controls +++++++++++++++++++++ + +Maya Rigs will contain *Controls* that are used to move the rig in +specific ways. For example Rig Controls may perform the following: + +- Inverse Kinematics (IK) - e.g. a control for a hand or foot. + +- Forward Kinematics (FK) - just regular rigid transformations in a + hierarchy. + +- Control Space Switching - controls moving *with* different parts of + a character; head, hips, chest, etc. + +- Special attributes controlling blend shapes, hand poses, doors + opening, or anything else. + +MM Solver is (technically) able to solve any floating-point of +attribute in the Maya scene. As a result any of the IK, FK or +"Special" attributes can be solved with MM Solver, but there is a +trick (see note below). + +It is **highly recommended** to avoid solving rig controls directly, +if possible, because of the solver performance. As soon as a Rig +control is added (even indirectly) into MM Solver, MM Solver *must* +switch to a slower mode to evaluate Maya nodes. If only transforms, +cameras, markers and bundles are used, MM Solver can use a highly +optimized mode to speed up the solver performance. Transform +hierarchies and direct Maya DG connections are also supported by this +special mode. + +The fastest way to solve rig controls is to not solve rig controls +directly, and instead use controller transforms, such as *locators* +nodes, or use the :ref:`create-remove-controller-tool-ref` tool. + +For example, lets solve the head of a biped character: + +#. Select the character's head, and body controls, and use the + :ref:`create-remove-controller-tool-ref` tool to create locators + parent constrained back to the character's body and head. + + This will allow our head to move using the controller locator, + rather than the rig control. + +#. Parent the body controller under the head controller. + +#. Create 2D Markers for the character's head. + +#. Parent the 3D Bundles of your Markers under the head controller + node. + +#. Move (snap) the 3D Bundles to the correct 3D position on the head, + and lock the translation XYZ attributes. + + If you align your character's head to the plate, you can use the + :ref:`project-marker-on-mesh-tool-ref` tool to position the bundles + easily on the surface of the mesh. + +#. Open the :ref:`Solver UI `. + +#. Select and add the 2D Markers to the *Input Objects* section of the + *Solver UI* (with the *+* button). + +#. Select the head controller node, in the Channel Box, select the + attributes that you wish to solve. + +#. Add the selected attributes to the *Output Attributes* section + (with the *+* button). + +#. Press *Solve* button in the *Solver UI*. + +This approach treats the character rig as a rigid transformation, and +avoids using the character rig directly in the solver. Sometimes this +is not possible, and you must evaluate and solve the Maya Rig's 3D +geometry in the solver, to do this you can :ref:`Solve Rivets +`. + +.. note:: In Maya 2020+ you may experience **incorrect solves** when + solving Maya rig controls directly. If you do experience MM + Solver evaluating/solving incorrectly, the workaround is to + enable the :ref:`solver-ui-evaluate-mesh-rivets-ref` + check-box in the *Solver UI*. This will slow down the solver + performance but will ensure correct evaluation of the Maya + nodes. This is a known issue and is trying to be improved. + +.. _techniques-rigs-solving-rivets-ref: + +Solving Rivets +++++++++++++++ + +Sometimes it is very important to solve a Maya rig deforming, for a +"skin-tight" MatchMove. Common examples are for deformations of faces, +jaws, arms or chest of a character. + +When solving deformations, we must evaluate the Maya rig geometry at +specific positions on the surface. For example, we could track a 2D +Marker for a character's eye lid. We must then find the 3D surface +position and "rivet" the 3D Bundle to the surface, so that it locks +and moves along the surface of the character's geometry. + +Unfortunately, solving rivets can be **very slow** in MM Solver, so it +is always recommended to use rigid solving approaches first, before +solving Rivets. + +MM Solver does not contain Riveting tools (yet), however the "classic" +`rivet.mel`_ script has been tested with MM Solver and is known to +work. Other riveting scripts and tools (such as the new Maya 2020 +Rivet tool) may work, but are untested. + +To make sure that MM Solver can evaluate the Maya Rivet nodes, it is +critical to **enable** the :ref:`solver-ui-evaluate-mesh-rivets-ref` +check-box in the Solver UI before solving. + +Therefore the rough steps for solving rivets are: + +#. Track 2D Markers for deforming surfaces (such as human skin). + +#. Unlock the Maya Rig's geometry to make it selectable (this is + dependent on the rig). + +#. Create Rivets for the 3D positions on the character's geometry. + + - After `rivet.mel`_ script is installed, select 2 adjacent Mesh + edges and type "rivet" in the Maya MEL Command Line, then press + enter; a new locator node named "rivet1" will be created. + +#. Parent your 3D Bundle under the "rivet" locator, position the + Bundle and lock the translation XYZ attributes. + + If you align your character's head to the plate, you can use the + :ref:`project-marker-on-mesh-tool-ref` tool to position the bundles + easily on the surface of the mesh. + +#. Open the :ref:`Solver UI `. + +#. Select and add the 2D Markers to the *Input Objects* section of the + *Solver UI* (with the *+* button). + +#. Select your object's transform node, in the Channel Box, select the + attributes (such as the character's head and jaw control) that you + wish to solve. + +#. Add the selected attributes to the *Output Attributes* section + (with the *+* button). + +#. It is **important** to enable + :ref:`solver-ui-evaluate-mesh-rivets-ref` in the *Solver UI* to + correctly evaluate and solve the rivet positions. + +#. Press *Solve* button in the *Solver UI*. + +.. note:: MM Solver is *not* a facial animation tool, and has not been + designed for that purpose. + +.. _rivet.mel: + https://www.highend3d.com/maya/script/rivet-button-for-maya diff --git a/docs/source/tools_solver_ui.rst b/docs/source/tools_solver_ui.rst index 503c581e3..7d31f3b0c 100644 --- a/docs/source/tools_solver_ui.rst +++ b/docs/source/tools_solver_ui.rst @@ -235,6 +235,8 @@ value is disabled, all camera lens distortion attributes will be ignored. This option is helpful to quickly enable/disable lens distortion attributes in the solver. +.. _solver-ui-evaluate-mesh-rivets-ref: + Evaluate Mesh Rivets ++++++++++++++++++++ diff --git a/docs/source/tutorial.rst b/docs/source/tutorial.rst index affd48219..57fdca619 100644 --- a/docs/source/tutorial.rst +++ b/docs/source/tutorial.rst @@ -142,6 +142,8 @@ to stick to the same feature on the door, only the edge of the door. +.. _tutorial-robot-arm-rigid-hierarchy-solving: + Robot Arm - Rigid Hierarchy Solving ----------------------------------- From 97f5bcb1dae464e4564734193d3891289bed9d77 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 17 Aug 2023 21:55:48 +1000 Subject: [PATCH 008/295] Fix Windows Visual Studio path for Maya 2024 build script. --- scripts/build_mmSolver_windows64_maya2024.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build_mmSolver_windows64_maya2024.bat b/scripts/build_mmSolver_windows64_maya2024.bat index 7d27d2bb2..171154f70 100644 --- a/scripts/build_mmSolver_windows64_maya2024.bat +++ b/scripts/build_mmSolver_windows64_maya2024.bat @@ -37,7 +37,7 @@ SET RUST_CARGO_EXE=cargo SET CXX_STANDARD=14 :: Setup Compiler environment. Change for your install path as needed. -CALL "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 :: These scripts assume 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. From 1ef13afdc32a6e14a9b3ac070c7c18147311a23a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 17 Aug 2023 22:14:54 +1000 Subject: [PATCH 009/295] Fix Node Editor crash when mmImagePlane is included in graph It appears that if a shape draw classification string differs from the node classification string, Maya's Node Editor will crash - or perhaps the crash was caused by the use of multiple colons. --- include/mmSolver/nodeTypeIds.h | 3 --- src/mmSolver/pluginMain.cpp | 2 +- src/mmSolver/render/ops/scene_utils.cpp | 2 +- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index fd6b13928..af2d985bb 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -127,9 +127,6 @@ #define MM_IMAGE_PLANE_SHAPE_TYPE_ID 0x0012F187 #define MM_IMAGE_PLANE_SHAPE_TYPE_NAME "mmImagePlaneShape" -#define MM_IMAGE_PLANE_SHAPE_NODE_CLASSIFY \ - "auxiliary/camera:geometry/camera:imageplane:drawdb/geometry/mmSolver/" \ - "imagePlane" #define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane" #define MM_IMAGE_PLANE_SHAPE_DRAW_REGISTRANT_ID "mmImagePlaneShape" #define MM_IMAGE_PLANE_SHAPE_SELECTION_TYPE_NAME "mmImagePlaneShapeSelection" diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index db53c6eb4..f196efd0c 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -313,7 +313,7 @@ MStatus initializePlugin(MObject obj) { const MString markerClassification = MM_MARKER_DRAW_CLASSIFY; const MString bundleClassification = MM_BUNDLE_DRAW_CLASSIFY; const MString imagePlaneShapeClassification = - MM_IMAGE_PLANE_SHAPE_NODE_CLASSIFY; + MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY; const MString skyDomeClassification = MM_SKY_DOME_DRAW_CLASSIFY; const MString lineClassification = MM_LINE_DRAW_CLASSIFY; REGISTER_LOCATOR_NODE(plugin, mmsolver::MarkerShapeNode::nodeName(), diff --git a/src/mmSolver/render/ops/scene_utils.cpp b/src/mmSolver/render/ops/scene_utils.cpp index ef467cf7a..3cbdbd2f5 100644 --- a/src/mmSolver/render/ops/scene_utils.cpp +++ b/src/mmSolver/render/ops/scene_utils.cpp @@ -67,7 +67,7 @@ bool node_classification_is_geometry(const MString& node_type_classification) { bool node_classification_is_image_plane( const MString& node_type_classification) { - const MString accepted_token("imageplane"); + const MString accepted_token("drawdb/geometry/mmSolver/imagePlane"); int32_t result = node_type_classification.indexW(accepted_token); return result != -1; } From 104baeddccac68ea2e43ceb389b4bc1cfd0af2c0 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 27 Aug 2023 01:32:33 +1000 Subject: [PATCH 010/295] Add User Preferences for Load Marker UI options. The Distortion Mode, Use (Embedded) Overscan and Load Bundle Positions default values can be controlled by the User Preferences window. --- python/mmSolver/tools/loadmarker/constant.py | 10 +-- .../tools/loadmarker/ui/loadmarker_layout.py | 46 +++++++++++--- .../tools/userpreferences/constant.py | 63 +++++++++++++++++++ .../tools/userprefswindow/ui/pref_layout.py | 54 ++++++++++++++++ .../tools/userprefswindow/ui/pref_layout.ui | 47 +++++++++++++- .../tools/userprefswindow/ui/pref_window.py | 12 ++++ 6 files changed, 216 insertions(+), 16 deletions(-) diff --git a/python/mmSolver/tools/loadmarker/constant.py b/python/mmSolver/tools/loadmarker/constant.py index 894256923..c98ee4e74 100644 --- a/python/mmSolver/tools/loadmarker/constant.py +++ b/python/mmSolver/tools/loadmarker/constant.py @@ -19,6 +19,9 @@ Contains constant values for the Load Marker tool. """ +import mmSolver.tools.userpreferences.constant as userprefs_const + + CONFIG_FILE_NAME = "tools_loadmarker.json" WINDOW_TITLE = 'Load Markers - mmSolver' @@ -52,12 +55,9 @@ NEW_MARKER_GROUP_VALUE = '' NEW_COLLECTION_VALUE = '' -DISTORTION_MODE_VALUE = 'Distorted' -UNDISTORTION_MODE_VALUE = 'Undistorted' +DISTORTION_MODE_VALUE = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL +UNDISTORTION_MODE_VALUE = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL # Default values LOAD_MODE_DEFAULT_VALUE = LOAD_MODE_NEW_VALUE -DISTORTION_MODE_DEFAULT_VALUE = UNDISTORTION_MODE_VALUE -LOAD_BUNDLE_POS_DEFAULT_VALUE = True -USE_OVERSCAN_DEFAULT_VALUE = True diff --git a/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py b/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py index a2de2d0ab..6230a328b 100644 --- a/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py +++ b/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py @@ -77,6 +77,32 @@ def get_user_prefs_add_marker_to_collection(): return value != userprefs_const.REG_EVNT_ADD_NEW_MKR_TO_NONE_VALUE +def get_user_prefs_distortion_mode_default(): + config = userprefs_lib.get_config() + key = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + result = None + if value == userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_VALUE: + result = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL + elif value == userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_VALUE: + result = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL + return result + + +def get_user_prefs_use_overscan_default(): + config = userprefs_lib.get_config() + key = userprefs_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + return value == userprefs_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_VALUE + + +def get_user_prefs_load_bundle_positions_default(): + config = userprefs_lib.get_config() + key = userprefs_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + return value == userprefs_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE + + class LoadMarkerLayout(QtWidgets.QWidget, ui_loadmarker_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): super(LoadMarkerLayout, self).__init__(*args, **kwargs) @@ -143,9 +169,8 @@ def populateUi(self): self.fileInfo_plainTextEdit.setReadOnly(True) - value = get_config_value( - config, 'data/load_bundle_position', const.LOAD_BUNDLE_POS_DEFAULT_VALUE - ) + default_value = get_user_prefs_load_bundle_positions_default() + value = get_config_value(config, 'data/load_bundle_position', default_value) self.loadBndPositions_checkBox.setChecked(value) # Get the file path from the clipboard. @@ -197,16 +222,19 @@ def populateUi(self): index = self.loadMode_model.stringList().index(value) self.loadMode_comboBox.setCurrentIndex(index) - value = get_config_value( - config, "data/distortion_mode", const.DISTORTION_MODE_DEFAULT_VALUE - ) + default_value = get_user_prefs_distortion_mode_default() + value = get_config_value(config, "data/distortion_mode", default_value) self.populateDistortionModeModel(self.distortionMode_model) index = self.distortionMode_model.stringList().index(value) self.distortionMode_comboBox.setCurrentIndex(index) - value = get_config_value( - config, 'data/use_overscan', const.USE_OVERSCAN_DEFAULT_VALUE - ) + default_value = get_user_prefs_use_overscan_default() + value = get_config_value(config, 'data/use_overscan', default_value) + self.overscan_checkBox.setChecked(value) + self.updateOverscanValues() + + default_value = get_user_prefs_load_bundle_positions_default() + value = get_config_value(config, 'data/load_bundle_positions', default_value) self.overscan_checkBox.setChecked(value) self.updateOverscanValues() return diff --git a/python/mmSolver/tools/userpreferences/constant.py b/python/mmSolver/tools/userpreferences/constant.py index c2a9a6fad..20c3bd2f9 100644 --- a/python/mmSolver/tools/userpreferences/constant.py +++ b/python/mmSolver/tools/userpreferences/constant.py @@ -64,6 +64,57 @@ ] +# "Load Marker UI - Distortion Mode Default" preference. +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY = 'load_marker_ui/distortion_mode_default' +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_TYPE = TYPE_ENUMERATION_INT +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_VALUE = 1 +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL = 'Undistorted' +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_VALUE = 0 +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL = 'Distorted' +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_VALUES = [ + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_VALUE, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_VALUE, +] +LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_LABELS = [ + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL, +] + + +# "Load Marker UI - Use Overscan Default" preference. +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY = 'load_marker_ui/use_overscan_default' +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TYPE = TYPE_ENUMERATION_INT +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_VALUE = 1 +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_LABEL = 'Yes' +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_FALSE_VALUE = 0 +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_FALSE_LABEL = 'No' +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_VALUES = [ + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_VALUE, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_FALSE_VALUE, +] +LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_LABELS = [ + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_LABEL, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_FALSE_LABEL, +] + + +# "Load Marker UI - Load Bundle Positions Default" preference. +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY = 'load_marker_ui/load_bundle_positions_default' +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TYPE = TYPE_ENUMERATION_INT +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE = 1 +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_LABEL = 'Yes' +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_FALSE_VALUE = 0 +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_FALSE_LABEL = 'No' +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_VALUES = [ + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_FALSE_VALUE, +] +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_LABELS = [ + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_LABEL, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_FALSE_LABEL, +] + + # "Solver UI - Validate on open" preference. # # This option is deprecated and is not used. @@ -159,6 +210,9 @@ VALUES_MAP = { REG_EVNT_ADD_NEW_MKR_TO_KEY: REG_EVNT_ADD_NEW_MKR_TO_VALUES, REG_EVNT_ADD_NEW_LINE_TO_KEY: REG_EVNT_ADD_NEW_LINE_TO_VALUES, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY: LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_VALUES, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY: LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_VALUES, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY: LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_VALUES, SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_KEY: SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_VALUES, CREATE_CONTROLLER_SHAPE_KEY: CREATE_CONTROLLER_SHAPE_VALUES, } @@ -168,6 +222,9 @@ LABELS_MAP = { REG_EVNT_ADD_NEW_MKR_TO_KEY: REG_EVNT_ADD_NEW_MKR_TO_LABELS, REG_EVNT_ADD_NEW_LINE_TO_KEY: REG_EVNT_ADD_NEW_LINE_TO_LABELS, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY: LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_LABELS, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY: LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_LABELS, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY: LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_LABELS, SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_KEY: SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_LABELS, CREATE_CONTROLLER_SHAPE_KEY: CREATE_CONTROLLER_SHAPE_LABELS, } @@ -177,6 +234,9 @@ DEFAULT_VALUE_MAP = { REG_EVNT_ADD_NEW_MKR_TO_KEY: REG_EVNT_ADD_NEW_MKR_TO_ACTIVE_COL_VALUE, REG_EVNT_ADD_NEW_LINE_TO_KEY: REG_EVNT_ADD_NEW_LINE_TO_ACTIVE_COL_VALUE, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY: LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_VALUE, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY: LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_VALUE, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY: LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE, SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_KEY: SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_TRUE_VALUE, CREATE_CONTROLLER_SHAPE_KEY: CREATE_CONTROLLER_SHAPE_LOCATOR_VALUE, } @@ -186,6 +246,9 @@ VALUE_TYPE_MAP = { REG_EVNT_ADD_NEW_MKR_TO_KEY: REG_EVNT_ADD_NEW_MKR_TO_TYPE, REG_EVNT_ADD_NEW_LINE_TO_KEY: REG_EVNT_ADD_NEW_LINE_TO_TYPE, + LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY: LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_TYPE, + LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY: LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TYPE, + LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY: LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TYPE, SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_KEY: SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_TYPE, CREATE_CONTROLLER_SHAPE_KEY: CREATE_CONTROLLER_SHAPE_TYPE, } diff --git a/python/mmSolver/tools/userprefswindow/ui/pref_layout.py b/python/mmSolver/tools/userprefswindow/ui/pref_layout.py index 4297b1b54..4d7ae0a2d 100644 --- a/python/mmSolver/tools/userprefswindow/ui/pref_layout.py +++ b/python/mmSolver/tools/userprefswindow/ui/pref_layout.py @@ -59,6 +59,9 @@ def populateUI(self, config): self.updateAddNewMarkersToWidget(config) self.updateAddNewLinesToWidget(config) self.updateSolverUIMinimalUIWhileSolvingWidget(config) + self.updateLoadMarkerUIDistortionModeDefaultWidget(config) + self.updateLoadMarkerUIUseOverscanDefaultWidget(config) + self.updateLoadMarkerUILoadBundlePositionsDefaultWidget(config) # Deprecated options, kept for backwards compatibility, but # they are hidden by default anyway. @@ -101,6 +104,57 @@ def getAddNewLinesToConfigValue(self): value = userprefs_lib.get_value_from_label(key, label) return value + def updateLoadMarkerUIDistortionModeDefaultWidget(self, config): + key = pref_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + label = userprefs_lib.get_label_from_value(key, value) + assert isinstance(label, pycompat.TEXT_TYPE) + labels = userprefs_lib.get_labels(key) + self.distortionModeDefaultComboBox.clear() + self.distortionModeDefaultComboBox.addItems(labels) + self.distortionModeDefaultComboBox.setCurrentText(label) + return + + def getLoadMarkerUIDistortionModeDefaultConfigValue(self): + key = pref_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY + label = self.distortionModeDefaultComboBox.currentText() + value = userprefs_lib.get_value_from_label(key, label) + return value + + def updateLoadMarkerUIUseOverscanDefaultWidget(self, config): + key = pref_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + label = userprefs_lib.get_label_from_value(key, value) + assert isinstance(label, pycompat.TEXT_TYPE) + labels = userprefs_lib.get_labels(key) + self.useOverscanDefaultComboBox.clear() + self.useOverscanDefaultComboBox.addItems(labels) + self.useOverscanDefaultComboBox.setCurrentText(label) + return + + def getLoadMarkerUIUseOverscanDefaultConfigValue(self): + key = pref_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY + label = self.useOverscanDefaultComboBox.currentText() + value = userprefs_lib.get_value_from_label(key, label) + return value + + def updateLoadMarkerUILoadBundlePositionsDefaultWidget(self, config): + key = pref_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY + value = userprefs_lib.get_value(config, key) + label = userprefs_lib.get_label_from_value(key, value) + assert isinstance(label, pycompat.TEXT_TYPE) + labels = userprefs_lib.get_labels(key) + self.loadBundlePositionsDefaultComboBox.clear() + self.loadBundlePositionsDefaultComboBox.addItems(labels) + self.loadBundlePositionsDefaultComboBox.setCurrentText(label) + return + + def getLoadMarkerUILoadBundlePositionsDefaultConfigValue(self): + key = pref_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY + label = self.loadBundlePositionsDefaultComboBox.currentText() + value = userprefs_lib.get_value_from_label(key, label) + return value + def updateSolverUIValidateOnOpenWidget(self, config): # This feature is deprecated and is no longer allowed. return diff --git a/python/mmSolver/tools/userprefswindow/ui/pref_layout.ui b/python/mmSolver/tools/userprefswindow/ui/pref_layout.ui index 10aaaf0de..ead973d25 100644 --- a/python/mmSolver/tools/userprefswindow/ui/pref_layout.ui +++ b/python/mmSolver/tools/userprefswindow/ui/pref_layout.ui @@ -6,8 +6,8 @@ 0 0 - 501 - 428 + 1144 + 826 @@ -70,6 +70,49 @@ + + + + Load Marker UI Preferences + + + + + + + + Use Embedded Overscan Default: + + + + + + + + + + Load Bundle Positions Default: + + + + + + + + + + Distortion Mode Default: + + + + + + + + + + + diff --git a/python/mmSolver/tools/userprefswindow/ui/pref_window.py b/python/mmSolver/tools/userprefswindow/ui/pref_window.py index dad029d09..f80fcdbe0 100644 --- a/python/mmSolver/tools/userprefswindow/ui/pref_window.py +++ b/python/mmSolver/tools/userprefswindow/ui/pref_window.py @@ -110,6 +110,18 @@ def save_prefs(self): pref_const.REG_EVNT_ADD_NEW_LINE_TO_KEY, self.subForm.getAddNewLinesToConfigValue, ), + ( + pref_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY, + self.subForm.getLoadMarkerUIDistortionModeDefaultConfigValue, + ), + ( + pref_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY, + self.subForm.getLoadMarkerUIUseOverscanDefaultConfigValue, + ), + ( + pref_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY, + self.subForm.getLoadMarkerUILoadBundlePositionsDefaultConfigValue, + ), ( pref_const.SOLVER_UI_MINIMAL_UI_WHILE_SOLVING_KEY, self.subForm.getSolverUIMinimalUIWhileSolvingConfigValue, From 9ea508aa516e885db743c1ce11e6ba0c2ae78ae6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 27 Aug 2023 11:30:02 +1000 Subject: [PATCH 011/295] Format Load Marker and User Preferences Python files --- python/mmSolver/tools/loadmarker/constant.py | 8 ++++++-- .../mmSolver/tools/loadmarker/ui/loadmarker_layout.py | 10 ++++++---- python/mmSolver/tools/userpreferences/constant.py | 4 +++- .../mmSolver/tools/userprefswindow/ui/pref_layout.py | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/python/mmSolver/tools/loadmarker/constant.py b/python/mmSolver/tools/loadmarker/constant.py index c98ee4e74..419f42738 100644 --- a/python/mmSolver/tools/loadmarker/constant.py +++ b/python/mmSolver/tools/loadmarker/constant.py @@ -55,8 +55,12 @@ NEW_MARKER_GROUP_VALUE = '' NEW_COLLECTION_VALUE = '' -DISTORTION_MODE_VALUE = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL -UNDISTORTION_MODE_VALUE = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL +DISTORTION_MODE_VALUE = ( + userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_DISTORT_LABEL +) +UNDISTORTION_MODE_VALUE = ( + userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_UNDISTORT_LABEL +) # Default values diff --git a/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py b/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py index 6230a328b..c630d7964 100644 --- a/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py +++ b/python/mmSolver/tools/loadmarker/ui/loadmarker_layout.py @@ -78,7 +78,7 @@ def get_user_prefs_add_marker_to_collection(): def get_user_prefs_distortion_mode_default(): - config = userprefs_lib.get_config() + config = userprefs_lib.get_config() key = userprefs_const.LOAD_MARKER_UI_DISTORTION_MODE_DEFAULT_KEY value = userprefs_lib.get_value(config, key) result = None @@ -90,17 +90,19 @@ def get_user_prefs_distortion_mode_default(): def get_user_prefs_use_overscan_default(): - config = userprefs_lib.get_config() + config = userprefs_lib.get_config() key = userprefs_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY value = userprefs_lib.get_value(config, key) return value == userprefs_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_TRUE_VALUE def get_user_prefs_load_bundle_positions_default(): - config = userprefs_lib.get_config() + config = userprefs_lib.get_config() key = userprefs_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY value = userprefs_lib.get_value(config, key) - return value == userprefs_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE + return ( + value == userprefs_const.LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE + ) class LoadMarkerLayout(QtWidgets.QWidget, ui_loadmarker_layout.Ui_Form): diff --git a/python/mmSolver/tools/userpreferences/constant.py b/python/mmSolver/tools/userpreferences/constant.py index 20c3bd2f9..d7445dede 100644 --- a/python/mmSolver/tools/userpreferences/constant.py +++ b/python/mmSolver/tools/userpreferences/constant.py @@ -99,7 +99,9 @@ # "Load Marker UI - Load Bundle Positions Default" preference. -LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY = 'load_marker_ui/load_bundle_positions_default' +LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_KEY = ( + 'load_marker_ui/load_bundle_positions_default' +) LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TYPE = TYPE_ENUMERATION_INT LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_VALUE = 1 LOAD_MARKER_UI_LOAD_BUNDLE_POSITIONS_DEFAULT_TRUE_LABEL = 'Yes' diff --git a/python/mmSolver/tools/userprefswindow/ui/pref_layout.py b/python/mmSolver/tools/userprefswindow/ui/pref_layout.py index 4d7ae0a2d..f68d5a963 100644 --- a/python/mmSolver/tools/userprefswindow/ui/pref_layout.py +++ b/python/mmSolver/tools/userprefswindow/ui/pref_layout.py @@ -120,7 +120,7 @@ def getLoadMarkerUIDistortionModeDefaultConfigValue(self): label = self.distortionModeDefaultComboBox.currentText() value = userprefs_lib.get_value_from_label(key, label) return value - + def updateLoadMarkerUIUseOverscanDefaultWidget(self, config): key = pref_const.LOAD_MARKER_UI_USE_OVERSCAN_DEFAULT_KEY value = userprefs_lib.get_value(config, key) @@ -154,7 +154,7 @@ def getLoadMarkerUILoadBundlePositionsDefaultConfigValue(self): label = self.loadBundlePositionsDefaultComboBox.currentText() value = userprefs_lib.get_value_from_label(key, label) return value - + def updateSolverUIValidateOnOpenWidget(self, config): # This feature is deprecated and is no longer allowed. return From e169ecd9807cc5f3abfd9eb9049d5b7327000f62 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 27 Aug 2023 22:56:31 +1000 Subject: [PATCH 012/295] Move the Releases further down the README.md Because the list is getting very long, and takes up a lot of screen-space. Readers may miss the important information lower down the page - so the important information has been moved closer to the top of the page. --- README.md | 78 +++++++++++++++++++++++++++---------------------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index dffc3cb30..9f035dd7a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,45 @@ matchmoving tasks inside Autodesk Maya. This tool is not intended as a one-click-solution - this tool is for advanced techniques that are encountered on a daily-basis in the Film and TV industry. +## Documentation + +For all tutorials, documentation of Tools, Python API and Maya Plug-In +features, take a look at the +[Documentation Home Page](https://david-cattermole.github.io/mayaMatchMoveSolver/). + +The official YouTube channel is +[mmSolver](https://www.youtube.com/channel/UCndLPvFXd9Os7m9sc2Bbbsw), +it contains video tutorials for mmSolver on a range of topics. + +A copy of the documentation is also installed with *Maya MatchMove +Solver*, you can find it by pressing the *help* button in the Solver +UI, or in the module install directory, for example this path: + +(On Windows) +``` +C:/Users//My Documents/maya/2017/modules/mayaMatchMoveSolver-0.1.0-maya2018-win64/docs/html/index.html +``` + +## Community + +Do you have a question about mmSolver? +The mailing list is the perfect place to ask! + +There is a Google Group mailing list: +[maya-matchmove-solver](https://groups.google.com/forum/#!forum/maya-matchmove-solver). + +The mailing list is a place for user questions and discussions, and +will have release announcements of new versions. + +If you find a bug, please report it on the GitHub project +[issues page](https://github.com/david-cattermole/mayaMatchMoveSolver/issues). + +## Installation + +If you have a 'mayaMatchMoveSolver' archive package and need to +install it, follow the instructions in +[INSTALL.md](https://github.com/david-cattermole/mayaMatchMoveSolver/blob/master/INSTALL.md). + ## Releases The following releases are below. The latest bug-fix version should @@ -48,45 +87,6 @@ changes. | [v0.1.1](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.1.1) | Bug fix release | | [v0.1.0](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.1.0) | Initial release | -## Documentation - -For all tutorials, documentation of Tools, Python API and Maya Plug-In -features, take a look at the -[Documentation Home Page](https://david-cattermole.github.io/mayaMatchMoveSolver/). - -The official YouTube channel is -[mmSolver](https://www.youtube.com/channel/UCndLPvFXd9Os7m9sc2Bbbsw), -it contains video tutorials for mmSolver on a range of topics. - -A copy of the documentation is also installed with *Maya MatchMove -Solver*, you can find it by pressing the *help* button in the Solver -UI, or in the module install directory, for example this path: - -(On Windows) -``` -C:/Users//My Documents/maya/2017/modules/mayaMatchMoveSolver-0.1.0-maya2018-win64/docs/html/index.html -``` - -## Community - -Do you have a question about mmSolver? -The mailing list is the perfect place to ask! - -There is a Google Group mailing list: -[maya-matchmove-solver](https://groups.google.com/forum/#!forum/maya-matchmove-solver). - -The mailing list is a place for user questions and discussions, and -will have release announcements of new versions. - -If you find a bug, please report it on the GitHub project -[issues page](https://github.com/david-cattermole/mayaMatchMoveSolver/issues). - -## Installation - -If you have a 'mayaMatchMoveSolver' archive package and need to -install it, follow the instructions in -[INSTALL.md](https://github.com/david-cattermole/mayaMatchMoveSolver/blob/master/INSTALL.md). - ## Building To build (compile) the plug-in follow the steps in From 646dbc7b74932980ba49033898c3daa04cf49721 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 22:50:43 +1000 Subject: [PATCH 013/295] Fix incorrect counting in SolverObjectCountResult The SolverObjectCountResult struct, used to keep track of the number of objects to be solved was incorrectly keeping track of the number of parameters, because of an "off-by-one" error. When 1 parameter was solved with 3 frames, the initial SolverObjectCountResult struct was constructed with an initial count of 1. When the 'SolverObjectCountResult.divide()' method is run the count of '4' (3 + 1) was divided by '3' (3 frames with one parameter each), with the result being '0'. The issue is fixed by making the initial count of the SolverObjectCountResult struct be 0, not 1. A similar issue can be seen in the other structs: SolveValuesResult, TimerResult and SolverResult. Additionally this commit moves some complicated functions into 'fill' methods so that the setting, adding, dividing and appendToMStringArray logic is in one struct. The 'test_solve_robotArm.py' test has been added to catch this issue from happening again. Issue #248. --- src/mmSolver/adjust/adjust_base.cpp | 87 +--------- src/mmSolver/adjust/adjust_results.h | 142 +++++++++++++-- tests/data | 2 +- tests/test/test_api/test_solve_robotArm.py | 193 +++++++++++++++++++++ 4 files changed, 325 insertions(+), 99 deletions(-) create mode 100644 tests/test/test_api/test_solve_robotArm.py diff --git a/src/mmSolver/adjust/adjust_base.cpp b/src/mmSolver/adjust/adjust_base.cpp index 6b76d7a58..44b9fda28 100644 --- a/src/mmSolver/adjust/adjust_base.cpp +++ b/src/mmSolver/adjust/adjust_base.cpp @@ -473,13 +473,8 @@ MStatus logResultsObjectCounts(const int numberOfParameters, const int numberOfAttrSmoothnessErrors, SolverObjectCountResult &out_result) { MStatus status = MStatus::kSuccess; - - out_result.parameter_count = numberOfParameters; - out_result.error_count = numberOfErrors; - out_result.marker_error_count = numberOfMarkerErrors; - out_result.attr_stiffness_error_count = numberOfAttrStiffnessErrors; - out_result.attr_smoothness_error_count = numberOfAttrSmoothnessErrors; - + out_result.fill(numberOfParameters, numberOfErrors, numberOfMarkerErrors, + numberOfAttrStiffnessErrors, numberOfAttrSmoothnessErrors); return status; } @@ -494,52 +489,7 @@ MStatus logResultsMarkerAffectsAttribute(const MarkerPtrList &markerList, const BoolList2D &markerToAttrList, AffectsResult &out_result) { MStatus status = MStatus::kSuccess; - std::string resultStr; - - std::vector::const_iterator cit_inner; - BoolList2D::const_iterator cit_outer; - int markerIndex = 0; - for (cit_outer = markerToAttrList.cbegin(); - cit_outer != markerToAttrList.cend(); ++cit_outer) { - int attrIndex = 0; - std::vector inner = *cit_outer; - for (cit_inner = inner.cbegin(); cit_inner != inner.cend(); - ++cit_inner) { - MarkerPtr marker = markerList[markerIndex]; - AttrPtr attr = attrList[attrIndex]; - - // Get node names. - const char *markerName = marker->getNodeName().asChar(); - - // Get attribute full path. - MPlug plug = attr->getPlug(); - MObject attrNode = plug.node(); - MFnDagNode attrFnDagNode(attrNode); - MString attrNodeName = attrFnDagNode.fullPathName(); - - const bool includeNodeName = false; - const bool includeNonMandatoryIndices = true; - const bool includeInstancedIndices = true; - const bool useAlias = false; - const bool useFullAttributePath = false; - const bool useLongNames = true; - MString attrAttrName = - plug.partialName(includeNodeName, includeNonMandatoryIndices, - includeInstancedIndices, useAlias, - useFullAttributePath, useLongNames); - - MString attrNameString = attrNodeName + "." + attrAttrName; - const char *attrName = attrNameString.asChar(); - - auto key = MarkerAttrNamePair(markerName, attrName); - bool value = *cit_inner; - out_result.marker_affects_attribute.insert({key, value}); - - ++attrIndex; - } - - ++markerIndex; - } + out_result.fill(markerList, attrList, markerToAttrList); return status; } @@ -553,35 +503,8 @@ MStatus logResultsSolveObjectUsage(MarkerPtrList &usedMarkerList, AttrPtrList &unusedAttrList, SolverObjectUsageResult &out_result) { MStatus status = MStatus::kSuccess; - - for (MarkerPtrListCIt mit = usedMarkerList.cbegin(); - mit != usedMarkerList.cend(); ++mit) { - MarkerPtr marker = *mit; - auto marker_name_char = marker->getLongNodeName().asChar(); - out_result.markers_used.insert(marker_name_char); - } - - for (MarkerPtrListCIt mit = unusedMarkerList.cbegin(); - mit != unusedMarkerList.cend(); ++mit) { - MarkerPtr marker = *mit; - auto marker_name_char = marker->getLongNodeName().asChar(); - out_result.markers_unused.insert(marker_name_char); - } - - for (AttrPtrListCIt ait = usedAttrList.cbegin(); ait != usedAttrList.cend(); - ++ait) { - AttrPtr attr = *ait; - auto attr_name_char = attr->getLongName().asChar(); - out_result.attributes_used.insert(attr_name_char); - } - - for (AttrPtrListCIt ait = unusedAttrList.cbegin(); - ait != unusedAttrList.cend(); ++ait) { - AttrPtr attr = *ait; - auto attr_name_char = attr->getLongName().asChar(); - out_result.attributes_unused.insert(attr_name_char); - } - + out_result.fill(usedMarkerList, unusedMarkerList, usedAttrList, + unusedAttrList); return status; } diff --git a/src/mmSolver/adjust/adjust_results.h b/src/mmSolver/adjust/adjust_results.h index 59f153e2e..d921118e0 100644 --- a/src/mmSolver/adjust/adjust_results.h +++ b/src/mmSolver/adjust/adjust_results.h @@ -39,12 +39,15 @@ #include // Maya +#include +#include #include #include // MM Solver #include "adjust_data.h" #include "adjust_defines.h" +#include "adjust_relationships.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/string_utils.h" @@ -76,7 +79,7 @@ struct SolverResult { , functionEvals(0) , jacobianEvals(0) , user_interrupted(false) - , count(1) {} + , count(0) {} void add(const Self &other) { Self::success = std::min(Self::success, other.success); @@ -169,7 +172,7 @@ struct TimerResult { debug::Ticks ticks_error; TimerResult() - : count(1) + : count(0) , timer_solve(0.0) , timer_function(0.0) , timer_jacobian(0.0) @@ -182,6 +185,8 @@ struct TimerResult { , ticks_error(0) {} void fill(const SolverTimer &timer) { + Self::count = 1; + Self::timer_solve = timer.solveBenchTimer.get_seconds(); Self::timer_function = timer.funcBenchTimer.get_seconds(); Self::timer_jacobian = timer.jacBenchTimer.get_seconds(); @@ -284,7 +289,7 @@ struct SolveValuesResult { std::vector solve_parameter_list; std::vector solve_error_list; - SolveValuesResult() : count(1) {} + SolveValuesResult() : count(0) {} void fill(const int numberOfParameters, const int numberOfErrors, const std::vector ¶mList, @@ -298,23 +303,35 @@ struct SolveValuesResult { const double err_value = errorList[i]; Self::solve_error_list.push_back(err_value); } + + Self::count = 1; } void add(const Self &other) { - auto solve_parameter_count = - std::min(Self::solve_parameter_list.size(), - other.solve_parameter_list.size()); - for (auto i = 0; i < solve_parameter_count; ++i) { - Self::solve_parameter_list[i] += other.solve_parameter_list[i]; - } + if (other.count == 0) { + return; + } + + if (Self::count == 0) { + Self::solve_parameter_list = other.solve_parameter_list; + Self::solve_error_list = other.solve_error_list; + Self::count = other.count; + } else { + auto solve_parameter_count = + std::min(Self::solve_parameter_list.size(), + other.solve_parameter_list.size()); + for (auto i = 0; i < solve_parameter_count; ++i) { + Self::solve_parameter_list[i] += other.solve_parameter_list[i]; + } - auto solve_error_count = std::min(Self::solve_error_list.size(), - other.solve_error_list.size()); - for (auto i = 0; i < solve_error_count; ++i) { - Self::solve_error_list[i] += other.solve_error_list[i]; - } + auto solve_error_count = std::min(Self::solve_error_list.size(), + other.solve_error_list.size()); + for (auto i = 0; i < solve_error_count; ++i) { + Self::solve_error_list[i] += other.solve_error_list[i]; + } - Self::count += other.count; + Self::count += other.count; + } } void divide() { @@ -579,6 +596,56 @@ struct AffectsResult { AffectsResult() = default; + void fill(const MarkerPtrList &markerList, const AttrPtrList &attrList, + const BoolList2D &markerToAttrList) { + std::string resultStr; + + std::vector::const_iterator cit_inner; + BoolList2D::const_iterator cit_outer; + int markerIndex = 0; + for (cit_outer = markerToAttrList.cbegin(); + cit_outer != markerToAttrList.cend(); ++cit_outer) { + int attrIndex = 0; + std::vector inner = *cit_outer; + for (cit_inner = inner.cbegin(); cit_inner != inner.cend(); + ++cit_inner) { + MarkerPtr marker = markerList[markerIndex]; + AttrPtr attr = attrList[attrIndex]; + + // Get node names. + const char *markerName = marker->getNodeName().asChar(); + + // Get attribute full path. + MPlug plug = attr->getPlug(); + MObject attrNode = plug.node(); + MFnDagNode attrFnDagNode(attrNode); + MString attrNodeName = attrFnDagNode.fullPathName(); + + const bool includeNodeName = false; + const bool includeNonMandatoryIndices = true; + const bool includeInstancedIndices = true; + const bool useAlias = false; + const bool useFullAttributePath = false; + const bool useLongNames = true; + MString attrAttrName = plug.partialName( + includeNodeName, includeNonMandatoryIndices, + includeInstancedIndices, useAlias, useFullAttributePath, + useLongNames); + + MString attrNameString = attrNodeName + "." + attrAttrName; + const char *attrName = attrNameString.asChar(); + + auto key = MarkerAttrNamePair(markerName, attrName); + bool value = *cit_inner; + Self::marker_affects_attribute.insert({key, value}); + + ++attrIndex; + } + + ++markerIndex; + } + } + void add(const Self &other) { for (const auto &kv : other.marker_affects_attribute) { auto search = Self::marker_affects_attribute.find(kv.first); @@ -618,6 +685,37 @@ struct SolverObjectUsageResult { SolverObjectUsageResult() = default; + void fill(MarkerPtrList &usedMarkerList, MarkerPtrList &unusedMarkerList, + AttrPtrList &usedAttrList, AttrPtrList &unusedAttrList) { + for (MarkerPtrListCIt mit = usedMarkerList.cbegin(); + mit != usedMarkerList.cend(); ++mit) { + MarkerPtr marker = *mit; + auto marker_name_char = marker->getLongNodeName().asChar(); + Self::markers_used.insert(marker_name_char); + } + + for (MarkerPtrListCIt mit = unusedMarkerList.cbegin(); + mit != unusedMarkerList.cend(); ++mit) { + MarkerPtr marker = *mit; + auto marker_name_char = marker->getLongNodeName().asChar(); + Self::markers_unused.insert(marker_name_char); + } + + for (AttrPtrListCIt ait = usedAttrList.cbegin(); + ait != usedAttrList.cend(); ++ait) { + AttrPtr attr = *ait; + auto attr_name_char = attr->getLongName().asChar(); + Self::attributes_used.insert(attr_name_char); + } + + for (AttrPtrListCIt ait = unusedAttrList.cbegin(); + ait != unusedAttrList.cend(); ++ait) { + AttrPtr attr = *ait; + auto attr_name_char = attr->getLongName().asChar(); + Self::attributes_unused.insert(attr_name_char); + } + } + void add(const Self &other) { for (const auto &value : other.markers_used) { Self::markers_used.insert(value); @@ -700,13 +798,25 @@ struct SolverObjectCountResult { int attr_smoothness_error_count; SolverObjectCountResult() - : count(1) + : count(0) , parameter_count(0) , error_count(0) , marker_error_count(0) , attr_stiffness_error_count(0) , attr_smoothness_error_count(0) {} + void fill(const int numberOfParameters, const int numberOfErrors, + const int numberOfMarkerErrors, + const int numberOfAttrStiffnessErrors, + const int numberOfAttrSmoothnessErrors) { + Self::count = 1; + Self::parameter_count = numberOfParameters; + Self::error_count = numberOfErrors; + Self::marker_error_count = numberOfMarkerErrors; + Self::attr_stiffness_error_count = numberOfAttrStiffnessErrors; + Self::attr_smoothness_error_count = numberOfAttrSmoothnessErrors; + } + void add(const Self &other) { Self::parameter_count += other.parameter_count; Self::error_count += other.error_count; diff --git a/tests/data b/tests/data index 4d9cd4d80..ed8e1c5d7 160000 --- a/tests/data +++ b/tests/data @@ -1 +1 @@ -Subproject commit 4d9cd4d80245844c556ef812bf3a1c2bfe2d7a28 +Subproject commit ed8e1c5d7e0278a941e2b4e6258aecfa04d8f348 diff --git a/tests/test/test_api/test_solve_robotArm.py b/tests/test/test_api/test_solve_robotArm.py new file mode 100644 index 000000000..5b3a84147 --- /dev/null +++ b/tests/test/test_api/test_solve_robotArm.py @@ -0,0 +1,193 @@ +# Copyright (C) 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +A test solve for the tutorial "Solve Robot Arm Rig Hierarchy with +mmSolver": +https://www.youtube.com/watch?v=y9U2QZT-VxA + +This test intends to reproduce the issue #248: +https://github.com/david-cattermole/mayaMatchMoveSolver/issues/248 +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +import maya.cmds + +import mmSolver.logger +import mmSolver.api as mmapi +import mmSolver.tools.loadmarker.lib.mayareadfile as marker_read +import mmSolver.tools.createlens.lib as createlens_lib +import test.test_api.apiutils as test_api_utils + + +LOG = mmSolver.logger.get_logger() + + +# @unittest.skip +class TestSolveRobotArm(test_api_utils.APITestCase): + def do_solve(self, solver_name, solver_type_index, scene_graph_mode): + if self.haveSolverType(name=solver_name) is False: + msg = '%r solver is not available!' % solver_name + raise unittest.SkipTest(msg) + scene_graph_name = mmapi.SCENE_GRAPH_MODE_NAME_LIST[scene_graph_mode] + scene_graph_label = mmapi.SCENE_GRAPH_MODE_LABEL_LIST[scene_graph_mode] + print('Scene Graph:', scene_graph_label) + + # Open the scene file. + scene_file_path = self.get_data_path('scenes', 'robotArm_v001.ma') + maya.cmds.file(scene_file_path, open=True, force=True, ignoreVersion=True) + + # Time Range + start = 1087 # 1001 + end = 1089 # 1387 + maya.cmds.playbackOptions( + animationStartTime=start, minTime=start, animationEndTime=end, maxTime=end + ) + + # Camera + cam_tfm = '|camera_GRP|trackCamera' + cam_shp = '|camera_GRP|trackCamera|trackCameraShape' + cam = mmapi.Camera(shape=cam_shp) + + # Marker Group + mkr_grp = mmapi.MarkerGroup(node='|camera_GRP|trackCamera|markerGroup1') + + # Markers + mkr_nodes = maya.cmds.ls('*_MKR', long=True) + mkr_list = [] + for mkr_node in mkr_nodes: + mkr = mmapi.Marker(node=mkr_node) + mkr_list.append(mkr) + + # Root frames + root_frm_list = [] + min_frames_per_marker = 3 + frame_nums = mmapi.get_root_frames_from_markers( + mkr_list, min_frames_per_marker, start, end + ) + frame_nums = mmapi.root_frames_list_combine(frame_nums, [start, end]) + max_frame_span = 5 + frame_nums = mmapi.root_frames_subdivide(frame_nums, max_frame_span) + for f in frame_nums: + frm = mmapi.Frame(f) + root_frm_list.append(frm) + + # Frames + frm_list = [] + for f in range(start, end + 1): + frm = mmapi.Frame(f) + frm_list.append(frm) + + # Solver. + # + # We could use a 'mmapi.SolverStandard()' instead and get a + # proper solve. + use_standard_solver = True + sol_list = [] + if use_standard_solver is True: + sol = mmapi.SolverStandard() + sol.set_root_frame_list(root_frm_list) + else: + sol = mmapi.SolverBasic() + sol.set_frame_list(frm_list) + sol.set_solver_type(solver_type_index) + sol.set_scene_graph_mode(scene_graph_mode) + sol_list.append(sol) + + # Collection + col = mmapi.Collection(node='collection1') + col.add_solver_list(sol_list) + + # Add markers + col.add_marker_list(mkr_list) + + # save the output + file_name = 'test_solve_robotArm_{}_{}_before.ma'.format( + solver_name, scene_graph_name + ) + path = self.get_data_path(file_name) + maya.cmds.file(rename=path) + maya.cmds.file(save=True, type='mayaAscii', force=True) + + # Run solver! + results = mmapi.execute(col) + + # Ensure the values are correct + for res in results: + success = res.get_success() + err = res.get_final_error() + print('err', err, 'success', success) + + # Set Deviation + mmapi.update_deviation_on_markers(mkr_list, results) + mmapi.update_deviation_on_collection(col, results) + + # save the output + file_name = 'test_solve_robotArm_{}_{}_after.ma'.format( + solver_name, scene_graph_name + ) + path = self.get_data_path(file_name) + maya.cmds.file(rename=path) + maya.cmds.file(save=True, type='mayaAscii', force=True) + + self.checkSolveResults(results, allow_max_avg_error=6.14, allow_max_error=6.51) + return + + # def test_ceres_maya_dag(self): + # self.do_solve('ceres', mmapi.SOLVER_TYPE_CERES, mmapi.SCENE_GRAPH_MODE_MAYA_DAG) + + # def test_ceres_mmscenegraph(self): + # self.do_solve( + # 'ceres', mmapi.SOLVER_TYPE_CERES, mmapi.SCENE_GRAPH_MODE_AUTO + # ) + + # def test_cminpack_lmdif_maya_dag(self): + # self.do_solve( + # 'cminpack_lmdif', + # mmapi.SOLVER_TYPE_CMINPACK_LMDIF, + # mmapi.SCENE_GRAPH_MODE_MAYA_DAG, + # ) + + # def test_cminpack_lmdif_mmscenegraph(self): + # self.do_solve( + # 'cminpack_lmdif', + # mmapi.SOLVER_TYPE_CMINPACK_LMDIF, + # mmapi.SCENE_GRAPH_MODE_AUTO, + # ) + + def test_cminpack_lmder_maya_dag(self): + self.do_solve( + 'cminpack_lmder', + mmapi.SOLVER_TYPE_CMINPACK_LMDER, + mmapi.SCENE_GRAPH_MODE_MAYA_DAG, + ) + + def test_cminpack_lmder_mmscenegraph(self): + self.do_solve( + 'cminpack_lmder', + mmapi.SOLVER_TYPE_CMINPACK_LMDER, + mmapi.SCENE_GRAPH_MODE_AUTO, + ) + + +if __name__ == '__main__': + prog = unittest.main() From 8281258f9ab13beb12b298bb157e55f98eed8781 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 22:55:34 +1000 Subject: [PATCH 014/295] Fix Solver Test 3 - Use larger delta for Maya evaluation This test (test_solver/test3.py) was a little flakey because when using the cminpack_lmder solver and evaluated with Maya DAG, the correct values would not solve. This seems to be caused by using a very small delta value. In the specific evaluation, the small delta value was not picking up the changes (because it could not produce Differentiable solves). --- tests/test/test_solver/test3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test/test_solver/test3.py b/tests/test/test_solver/test3.py index a1bcb443c..6c41c1c8e 100644 --- a/tests/test/test_solver/test3.py +++ b/tests/test/test_solver/test3.py @@ -100,7 +100,7 @@ def do_solve(self, solver_name, solver_index, scene_graph_mode): frame=frames, solverType=solver_index, sceneGraphMode=scene_graph_mode, - delta=0.000001, + delta=0.00001, verbose=True, **kwargs ) From 9fdba85199c137311f5d7fc4ba5efe7761179489 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 22:57:42 +1000 Subject: [PATCH 015/295] Add early return to testing attribute connection complexity This is intended to help make more readable code. --- src/mmSolver/mayahelper/maya_scene_graph.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/mmSolver/mayahelper/maya_scene_graph.cpp b/src/mmSolver/mayahelper/maya_scene_graph.cpp index cb5ad0048..e1e7bbc67 100644 --- a/src/mmSolver/mayahelper/maya_scene_graph.cpp +++ b/src/mmSolver/mayahelper/maya_scene_graph.cpp @@ -92,6 +92,9 @@ bool attribute_source_plug(MFnDependencyNode &depend_node, const MString &name, bool attribute_has_complex_connection(MFnDependencyNode &depend_node, const MString &name) { + const bool verbose = false; + MMSOLVER_VRB("attribute_has_complex_connection"); + MStatus status = MS::kSuccess; MPlug source_plug; bool ok = attribute_source_plug(depend_node, name, source_plug); From 422c30144d18114acdf3c973872ef44c2b3784a4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 23:00:08 +1000 Subject: [PATCH 016/295] Docs Correct the return type of run_validate_action() The full function path is: mmSolver._api._execute.actionstate.run_validate_action() --- python/mmSolver/_api/_execute/actionstate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mmSolver/_api/_execute/actionstate.py b/python/mmSolver/_api/_execute/actionstate.py index 681c0de19..bbea59309 100644 --- a/python/mmSolver/_api/_execute/actionstate.py +++ b/python/mmSolver/_api/_execute/actionstate.py @@ -77,10 +77,10 @@ def run_validate_action(vaction): :type vaction: Action :return: - A tuple of 3 parts; First, did the validation succeed (as - boolean)? Second, the user message we present for the state. - Third, metrics about the solve (number of parameters, number - of errors, and number of frames to solve) + An ActionState object containing, if the validation succeeded, + the user message we present for the state, metrics about the + solve (number of parameters, number of errors, and number of + frames to solve) :rtype: ActionState """ if not isinstance(vaction, api_action.Action): From 146ed44537433c3b9b0a48b328aec865c73cc460 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 23:02:45 +1000 Subject: [PATCH 017/295] Add 'assertApproxEqual' method for TestBase class This makes tests that require approximately equal tests to be made easier to write and when the test fails a helpful explaination is given via the exception message - to help debugging. --- tests/test/baseutils.py | 7 +++ tests/test/test_api/test_solve_init.py | 8 ++-- .../test/test_api/test_solve_markerEnable.py | 4 +- tests/test/test_api/test_solveresult.py | 18 +++---- tests/test/test_solver/test1.py | 4 +- tests/test/test_solver/test12.py | 4 +- tests/test/test_solver/test3.py | 7 ++- .../test_solver/test_deviation_calculation.py | 20 ++++---- tests/test/test_solver/test_issue54.py | 12 ++--- tests/test/test_solver/test_marker_enabled.py | 16 +++---- .../test_solver/test_marker_scale_node.py | 48 +++++++++---------- tests/test/test_solver/test_marker_weight.py | 20 ++++---- tests/test/test_solver/test_min_max_values.py | 12 ++--- .../test/test_solver/test_reprojection_cmd.py | 6 +-- .../test/test_tools/test_createcontroller.py | 26 +++++----- 15 files changed, 110 insertions(+), 102 deletions(-) diff --git a/tests/test/baseutils.py b/tests/test/baseutils.py index 78ad5b8ca..6e030caa1 100644 --- a/tests/test/baseutils.py +++ b/tests/test/baseutils.py @@ -80,6 +80,13 @@ def quit_maya(self): def approx_equal(self, x, y, eps=0.0001): return x == y or (x < (y + eps) and x > (y - eps)) + def assertApproxEqual(self, x, y, eps=0.0001): + if self.approx_equal(x, y, eps=eps) is False: + raise AssertionError( + '{x} != {y} (epsilon is {eps})'.format(x=x, y=y, eps=eps) + ) + return + def get_data_root(self): path = os.path.join(os.path.dirname(__file__), '..', 'data') path = os.path.abspath(path) diff --git a/tests/test/test_api/test_solve_init.py b/tests/test/test_api/test_solve_init.py index ccc60094b..956f1eac3 100644 --- a/tests/test/test_api/test_solve_init.py +++ b/tests/test/test_api/test_solve_init.py @@ -102,8 +102,8 @@ def test_init(self): # Ensure the values are correct self.checkSolveResults(results) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) return def do_solve_init_solverstandard( @@ -186,8 +186,8 @@ def do_solve_init_solverstandard( self.checkSolveResults( results, allow_max_avg_error=0.001, allow_max_error=0.001 ) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) return # def test_init_solverstandard_ceres_maya_dag(self): diff --git a/tests/test/test_api/test_solve_markerEnable.py b/tests/test/test_api/test_solve_markerEnable.py index c54212c8f..fab3f8b2a 100644 --- a/tests/test/test_api/test_solve_markerEnable.py +++ b/tests/test/test_api/test_solve_markerEnable.py @@ -238,8 +238,8 @@ def do_solve(self, solver_name, solver_type_index, scene_graph_mode): self.assertTrue(success) # self.assertGreater(0.001, err) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) - # assert self.approx_equal(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.tx'), -6.0) + # self.assertApproxEqual(maya.cmds.getAttr(bundle_tfm+'.ty'), 3.6) # Set Deviation mmapi.update_deviation_on_markers([mkr], results) diff --git a/tests/test/test_api/test_solveresult.py b/tests/test/test_api/test_solveresult.py index 38b8f11ff..dd9950831 100644 --- a/tests/test/test_api/test_solveresult.py +++ b/tests/test/test_api/test_solveresult.py @@ -149,15 +149,15 @@ def test_merge_frame_list(self): def test_get_average_frame_error_list(self): frame_error_list = {1: 0, 2: 0.5, 3: 1.0} v = mmapi.get_average_frame_error_list(frame_error_list) - assert self.approx_equal(v, 0.5) + self.assertApproxEqual(v, 0.5) frame_error_list = {1: 1.0} v = mmapi.get_average_frame_error_list(frame_error_list) - assert self.approx_equal(v, 1.0) + self.assertApproxEqual(v, 1.0) frame_error_list = {} v = mmapi.get_average_frame_error_list(frame_error_list) - assert self.approx_equal(v, 0.0) + self.assertApproxEqual(v, 0.0) col = create_example_solve_scene() results = col.execute() @@ -174,18 +174,20 @@ def test_get_average_frame_error_list(self): def test_get_max_frame_error(self): frame_error_list = {1: 0, 2: 0.5, 3: 1.0} frm, val = mmapi.get_max_frame_error(frame_error_list) - assert self.approx_equal(frm, 3) and isinstance(frm, int) - assert self.approx_equal(val, 1.0) + assert isinstance(frm, int) + self.assertApproxEqual(frm, 3) + self.assertApproxEqual(val, 1.0) frame_error_list = {1: 1.0} frm, val = mmapi.get_max_frame_error(frame_error_list) - assert self.approx_equal(frm, 1) and isinstance(frm, int) - assert self.approx_equal(val, 1.0) + assert isinstance(frm, int) + self.assertApproxEqual(frm, 1) + self.assertApproxEqual(val, 1.0) frame_error_list = {} frm, val = mmapi.get_max_frame_error(frame_error_list) assert frm is None - assert self.approx_equal(val, -0.0) + self.assertApproxEqual(val, -0.0) col = create_example_solve_scene() results = col.execute() diff --git a/tests/test/test_solver/test1.py b/tests/test/test_solver/test1.py index b4ebc643d..9e17458b8 100644 --- a/tests/test/test_solver/test1.py +++ b/tests/test/test_solver/test1.py @@ -118,8 +118,8 @@ def do_solve(self, solver_name, solver_index, scene_graph_mode): ty = maya.cmds.getAttr(bundle_tfm + '.ty') print('tx:', tx) print('ty:', ty) - assert self.approx_equal(tx, -6.0) - assert self.approx_equal(ty, 3.6) + self.assertApproxEqual(tx, -6.0) + self.assertApproxEqual(ty, 3.6) def test_init_ceres_maya_dag(self): self.do_solve('ceres', mmapi.SOLVER_TYPE_CERES, mmapi.SCENE_GRAPH_MODE_MAYA_DAG) diff --git a/tests/test/test_solver/test12.py b/tests/test/test_solver/test12.py index 82f30af63..5ac8c27fd 100644 --- a/tests/test/test_solver/test12.py +++ b/tests/test/test_solver/test12.py @@ -154,8 +154,8 @@ def do_solve(self, solver_name, solver_index, scene_graph_mode, single_frame): ty = maya.cmds.getAttr(bundle_tfm + '.ty', time=start_frame) print('tx:', tx) print('ty:', ty) - assert self.approx_equal(tx, -6.0) - assert self.approx_equal(ty, 3.6) + self.assertApproxEqual(tx, -6.0) + self.assertApproxEqual(ty, 3.6) def test_init_ceres_maya_dag_single_frame(self): single_frame = True diff --git a/tests/test/test_solver/test3.py b/tests/test/test_solver/test3.py index 6c41c1c8e..2539c801b 100644 --- a/tests/test/test_solver/test3.py +++ b/tests/test/test_solver/test3.py @@ -117,8 +117,11 @@ def do_solve(self, solver_name, solver_index, scene_graph_mode): self.assertEqual(result[0], 'success=1') rx = maya.cmds.getAttr(cam_tfm + '.rx') ry = maya.cmds.getAttr(cam_tfm + '.ry') - assert self.approx_equal(rx, 7.44014, eps=0.001) - assert self.approx_equal(ry, -32.3891, eps=0.001) + print('rx =', rx) + print('ry =', ry) + + self.assertApproxEqual(rx, 7.44014, eps=0.001) + self.assertApproxEqual(ry, -32.3891, eps=0.001) def test_init_ceres_maya_dag(self): self.do_solve('ceres', mmapi.SOLVER_TYPE_CERES, mmapi.SCENE_GRAPH_MODE_MAYA_DAG) diff --git a/tests/test/test_solver/test_deviation_calculation.py b/tests/test/test_solver/test_deviation_calculation.py index e8a3f04f7..fae0fded2 100644 --- a/tests/test/test_solver/test_deviation_calculation.py +++ b/tests/test/test_solver/test_deviation_calculation.py @@ -132,18 +132,14 @@ def do_solve(self, solver_name, solver_index): print('mkr_middleTop_values:', mkr_middleTop_values) print('mkr_middleLeft_values:', mkr_middleLeft_values) eps = 0.00001 - self.assertTrue(self.approx_equal(mkr_topRight_values[0], 2048.0, eps=eps)) - self.assertTrue( - self.approx_equal(mkr_topRight_values[1], 1258.6666666, eps=eps) - ) - self.assertTrue(self.approx_equal(mkr_topLeft_values[0], 0.0, eps=eps)) - self.assertTrue(self.approx_equal(mkr_topLeft_values[1], 1258.6666666, eps=eps)) - self.assertTrue(self.approx_equal(mkr_middleTop_values[0], 1024.0, eps=eps)) - self.assertTrue( - self.approx_equal(mkr_middleTop_values[1], 1258.6666666, eps=eps) - ) - self.assertTrue(self.approx_equal(mkr_middleLeft_values[0], 0.0, eps=eps)) - self.assertTrue(self.approx_equal(mkr_middleLeft_values[1], 576.0, eps=eps)) + self.assertApproxEqual(mkr_topRight_values[0], 2048.0, eps=eps) + self.assertApproxEqual(mkr_topRight_values[1], 1258.6666666, eps=eps) + self.assertApproxEqual(mkr_topLeft_values[0], 0.0, eps=eps) + self.assertApproxEqual(mkr_topLeft_values[1], 1258.6666666, eps=eps) + self.assertApproxEqual(mkr_middleTop_values[0], 1024.0, eps=eps) + self.assertApproxEqual(mkr_middleTop_values[1], 1258.6666666, eps=eps) + self.assertApproxEqual(mkr_middleLeft_values[0], 0.0, eps=eps) + self.assertApproxEqual(mkr_middleLeft_values[1], 576.0, eps=eps) return def test_init_ceres(self): diff --git a/tests/test/test_solver/test_issue54.py b/tests/test/test_solver/test_issue54.py index e6b87cdab..ab2672c02 100644 --- a/tests/test/test_solver/test_issue54.py +++ b/tests/test/test_solver/test_issue54.py @@ -127,8 +127,8 @@ def do_solve_with_initial_value_zero(self, solver_name, solver_index): ry = maya.cmds.getAttr(cam_tfm + '.ry') print('rx', rx) print('ry', ry) - assert self.approx_equal(rx, -2.85, eps=0.1) - assert self.approx_equal(ry, -2.86, eps=0.1) + self.assertApproxEqual(rx, -2.85, eps=0.1) + self.assertApproxEqual(ry, -2.86, eps=0.1) def do_solve_with_initial_value_twenty(self, solver_name, solver_index): if self.haveSolverType(name=solver_name) is False: @@ -179,8 +179,8 @@ def do_solve_with_initial_value_twenty(self, solver_name, solver_index): ry = maya.cmds.getAttr(cam_tfm + '.ry') print('rx', rx) print('ry', ry) - assert self.approx_equal(rx, 0.0, eps=0.01) - assert self.approx_equal(ry, 0.0, eps=0.01) + self.assertApproxEqual(rx, 0.0, eps=0.01) + self.assertApproxEqual(ry, 0.0, eps=0.01) def do_solve_with_initial_value_threeSixty(self, solver_name, solver_index): if self.haveSolverType(name=solver_name) is False: @@ -231,8 +231,8 @@ def do_solve_with_initial_value_threeSixty(self, solver_name, solver_index): ry = maya.cmds.getAttr(cam_tfm + '.ry') print('rx', rx) print('ry', ry) - assert self.approx_equal(rx, 360.0, eps=0.01) - assert self.approx_equal(ry, 360.0, eps=0.01) + self.assertApproxEqual(rx, 360.0, eps=0.01) + self.assertApproxEqual(ry, 360.0, eps=0.01) def test_init_ceres(self): """ diff --git a/tests/test/test_solver/test_marker_enabled.py b/tests/test/test_solver/test_marker_enabled.py index 5093aa5d9..ecbdb6461 100644 --- a/tests/test/test_solver/test_marker_enabled.py +++ b/tests/test/test_solver/test_marker_enabled.py @@ -108,8 +108,8 @@ def test_single_frame(self): # Ensure the values are correct self.assertEqual(result[0], 'success=1') - assert self.approx_equal(maya.cmds.getAttr(grp + '.tx'), -2.24999755) - assert self.approx_equal(maya.cmds.getAttr(grp + '.ty'), 1.65000644) + self.assertApproxEqual(maya.cmds.getAttr(grp + '.tx'), -2.24999755) + self.assertApproxEqual(maya.cmds.getAttr(grp + '.ty'), 1.65000644) def test_multi_frame(self): """ @@ -229,12 +229,12 @@ def test_multi_frame(self): ty_start = maya.cmds.getAttr(grp + '.ty', time=start) ty_mid = maya.cmds.getAttr(grp + '.ty', time=mid) ty_end = maya.cmds.getAttr(grp + '.ty', time=end) - assert self.approx_equal(tx_start, -0.51855463, eps=0.001) - assert self.approx_equal(tx_mid, -2.266493967, eps=0.001) - assert self.approx_equal(tx_end, -1.48144697, eps=0.001) - assert self.approx_equal(ty_start, 1.30993172, eps=0.001) - assert self.approx_equal(ty_mid, 1.631927621, eps=0.001) - assert self.approx_equal(ty_end, 2.10503952, eps=0.001) + self.assertApproxEqual(tx_start, -0.51855463, eps=0.001) + self.assertApproxEqual(tx_mid, -2.266493967, eps=0.001) + self.assertApproxEqual(tx_end, -1.48144697, eps=0.001) + self.assertApproxEqual(ty_start, 1.30993172, eps=0.001) + self.assertApproxEqual(ty_mid, 1.631927621, eps=0.001) + self.assertApproxEqual(ty_end, 2.10503952, eps=0.001) if __name__ == '__main__': diff --git a/tests/test/test_solver/test_marker_scale_node.py b/tests/test/test_solver/test_marker_scale_node.py index b5e23f320..6b1f11e18 100644 --- a/tests/test/test_solver/test_marker_scale_node.py +++ b/tests/test/test_solver/test_marker_scale_node.py @@ -57,26 +57,26 @@ def test_marker_scale_node(self): maya.cmds.setAttr(node + '.overscan', 1.0) scale = maya.cmds.getAttr(node + '.outScale') - assert self.approx_equal(scale[0][0], 1.0285714285714285) - assert self.approx_equal(scale[0][1], 0.6857129142857141) - assert self.approx_equal(scale[0][2], 1.0) + self.assertApproxEqual(scale[0][0], 1.0285714285714285) + self.assertApproxEqual(scale[0][1], 0.6857129142857141) + self.assertApproxEqual(scale[0][2], 1.0) translate = maya.cmds.getAttr(node + '.outTranslate') - assert self.approx_equal(translate[0][0], 0.0) - assert self.approx_equal(translate[0][1], 0.0) - assert self.approx_equal(translate[0][2], 0.0) + self.assertApproxEqual(translate[0][0], 0.0) + self.assertApproxEqual(translate[0][1], 0.0) + self.assertApproxEqual(translate[0][2], 0.0) # Test with 10% overscan value. maya.cmds.setAttr(node + '.overscan', 1.1) scale = maya.cmds.getAttr(node + '.outScale') - assert self.approx_equal(scale[0][0], 1.0285714285714285 * 1.1) - assert self.approx_equal(scale[0][1], 0.6857129142857141 * 1.1) - assert self.approx_equal(scale[0][2], 1.0) + self.assertApproxEqual(scale[0][0], 1.0285714285714285 * 1.1) + self.assertApproxEqual(scale[0][1], 0.6857129142857141 * 1.1) + self.assertApproxEqual(scale[0][2], 1.0) translate = maya.cmds.getAttr(node + '.outTranslate') - assert self.approx_equal(translate[0][0], 0.0) - assert self.approx_equal(translate[0][1], 0.0) - assert self.approx_equal(translate[0][2], 0.0) + self.assertApproxEqual(translate[0][0], 0.0) + self.assertApproxEqual(translate[0][1], 0.0) + self.assertApproxEqual(translate[0][2], 0.0) return def test_marker_scale_node_with_translate(self): @@ -93,26 +93,26 @@ def test_marker_scale_node_with_translate(self): maya.cmds.setAttr(node + '.depth', 1.0) scale = maya.cmds.getAttr(node + '.outScale') - assert self.approx_equal(scale[0][0], 1.0285714285714285) - assert self.approx_equal(scale[0][1], 0.6857129142857141) - assert self.approx_equal(scale[0][2], 1.0) + self.assertApproxEqual(scale[0][0], 1.0285714285714285) + self.assertApproxEqual(scale[0][1], 0.6857129142857141) + self.assertApproxEqual(scale[0][2], 1.0) translate = maya.cmds.getAttr(node + '.outTranslate') - assert self.approx_equal(translate[0][0], 1.0285714285714285 * 0.5) - assert self.approx_equal(translate[0][1], 0.6857129142857141 * 0.5) - assert self.approx_equal(translate[0][2], 0.0) + self.assertApproxEqual(translate[0][0], 1.0285714285714285 * 0.5) + self.assertApproxEqual(translate[0][1], 0.6857129142857141 * 0.5) + self.assertApproxEqual(translate[0][2], 0.0) # Test with 10% overscan value. maya.cmds.setAttr(node + '.overscan', 1.1) scale = maya.cmds.getAttr(node + '.outScale') - assert self.approx_equal(scale[0][0], 1.0285714285714285 * 1.1) - assert self.approx_equal(scale[0][1], 0.6857129142857141 * 1.1) - assert self.approx_equal(scale[0][2], 1.0) + self.assertApproxEqual(scale[0][0], 1.0285714285714285 * 1.1) + self.assertApproxEqual(scale[0][1], 0.6857129142857141 * 1.1) + self.assertApproxEqual(scale[0][2], 1.0) translate = maya.cmds.getAttr(node + '.outTranslate') - assert self.approx_equal(translate[0][0], 1.0285714285714285 * 0.5 * 1.1) - assert self.approx_equal(translate[0][1], 0.6857129142857141 * 0.5 * 1.1) - assert self.approx_equal(translate[0][2], 0.0) + self.assertApproxEqual(translate[0][0], 1.0285714285714285 * 0.5 * 1.1) + self.assertApproxEqual(translate[0][1], 0.6857129142857141 * 0.5 * 1.1) + self.assertApproxEqual(translate[0][2], 0.0) return diff --git a/tests/test/test_solver/test_marker_weight.py b/tests/test/test_solver/test_marker_weight.py index b1e0fbc30..61804248e 100644 --- a/tests/test/test_solver/test_marker_weight.py +++ b/tests/test/test_solver/test_marker_weight.py @@ -142,8 +142,8 @@ def test_single_frame_high_weight(self): tx = maya.cmds.getAttr(grp + '.tx') ty = maya.cmds.getAttr(grp + '.ty') self.assertEqual(result[0], 'success=1') - assert self.approx_equal(tx, -2.2252424) - assert self.approx_equal(ty, 1.65000000) + self.assertApproxEqual(tx, -2.2252424) + self.assertApproxEqual(ty, 1.65000000) def test_single_frame_low_weight(self): """ @@ -202,8 +202,8 @@ def test_single_frame_low_weight(self): tx = maya.cmds.getAttr(grp + '.tx') ty = maya.cmds.getAttr(grp + '.ty') self.assertEqual(result[0], 'success=1') - assert self.approx_equal(tx, -2.2252424) - assert self.approx_equal(ty, 1.65000000) + self.assertApproxEqual(tx, -2.2252424) + self.assertApproxEqual(ty, 1.65000000) def test_single_frame_ratio_weight(self): """ @@ -270,8 +270,8 @@ def test_single_frame_ratio_weight(self): tx = maya.cmds.getAttr(grp + '.tx') ty = maya.cmds.getAttr(grp + '.ty') self.assertEqual(result[0], 'success=1') - assert self.approx_equal(tx, -0.333333333333) - assert self.approx_equal(ty, 1.300000000000) + self.assertApproxEqual(tx, -0.333333333333) + self.assertApproxEqual(ty, 1.300000000000) def test_single_frame_same_weight(self): """ @@ -332,8 +332,8 @@ def test_single_frame_same_weight(self): tx = maya.cmds.getAttr(grp + '.tx') ty = maya.cmds.getAttr(grp + '.ty') self.assertEqual(result[0], 'success=1') - assert self.approx_equal(tx, -1.00000134) - assert self.approx_equal(ty, 1.65000055) + self.assertApproxEqual(tx, -1.00000134) + self.assertApproxEqual(ty, 1.65000055) def test_single_frame_no_weight(self): """ @@ -394,8 +394,8 @@ def test_single_frame_no_weight(self): tx = maya.cmds.getAttr(grp + '.tx') ty = maya.cmds.getAttr(grp + '.ty') self.assertEqual(result[0], 'success=1') - assert self.approx_equal(tx, -2.25, eps=0.001) - assert self.approx_equal(ty, 1.65, eps=0.001) + self.assertApproxEqual(tx, -2.25, eps=0.001) + self.assertApproxEqual(ty, 1.65, eps=0.001) if __name__ == '__main__': diff --git a/tests/test/test_solver/test_min_max_values.py b/tests/test/test_solver/test_min_max_values.py index ba2b4fcc4..0e120243e 100644 --- a/tests/test/test_solver/test_min_max_values.py +++ b/tests/test/test_solver/test_min_max_values.py @@ -95,8 +95,8 @@ def test_single_frame(self): self.assertEqual(result[0], 'success=1') tx = maya.cmds.getAttr(bundle_01_tfm + '.tx') ty = maya.cmds.getAttr(bundle_01_tfm + '.ty') - assert self.approx_equal(tx, -5.0) - assert self.approx_equal(ty, 2.3) + self.assertApproxEqual(tx, -5.0) + self.assertApproxEqual(ty, 2.3) def test_single_frame_lower_bound_only(self): """ @@ -157,8 +157,8 @@ def test_single_frame_lower_bound_only(self): self.assertEqual(result[0], 'success=1') tx = maya.cmds.getAttr(bundle_01_tfm + '.tx') ty = maya.cmds.getAttr(bundle_01_tfm + '.ty') - assert self.approx_equal(tx, -5.0) - assert self.approx_equal(ty, 2.3) + self.assertApproxEqual(tx, -5.0) + self.assertApproxEqual(ty, 2.3) def test_single_frame_upper_bound_only(self): """ @@ -216,8 +216,8 @@ def test_single_frame_upper_bound_only(self): self.assertEqual(result[0], 'success=1') tx = maya.cmds.getAttr(bundle_01_tfm + '.tx') ty = maya.cmds.getAttr(bundle_01_tfm + '.ty') - assert self.approx_equal(tx, -6.0) - assert self.approx_equal(ty, 2.3) + self.assertApproxEqual(tx, -6.0) + self.assertApproxEqual(ty, 2.3) if __name__ == '__main__': diff --git a/tests/test/test_solver/test_reprojection_cmd.py b/tests/test/test_solver/test_reprojection_cmd.py index f05332a27..a6cf0f488 100644 --- a/tests/test/test_solver/test_reprojection_cmd.py +++ b/tests/test/test_solver/test_reprojection_cmd.py @@ -145,9 +145,9 @@ def test_reprojection_cmd(self): x = world_point_values[(i * 3) + 0] y = world_point_values[(i * 3) + 1] z = world_point_values[(i * 3) + 2] - assert self.approx_equal(x, pnt_x), 'X a=%r b=%r' % (x, pnt_x) - assert self.approx_equal(y, pnt_y), 'Y a=%r b=%r' % (y, pnt_y) - assert self.approx_equal(z, pnt_z), 'Z a=%r b=%r' % (z, pnt_z) + self.assertApproxEqual(x, pnt_x), 'X a=%r b=%r' % (x, pnt_x) + self.assertApproxEqual(y, pnt_y), 'Y a=%r b=%r' % (y, pnt_y) + self.assertApproxEqual(z, pnt_z), 'Z a=%r b=%r' % (z, pnt_z) # save the output path = self.get_data_path('reprojection_cmd_test_after.ma') diff --git a/tests/test/test_tools/test_createcontroller.py b/tests/test/test_tools/test_createcontroller.py index 292ea1de6..e5f7f3804 100644 --- a/tests/test/test_tools/test_createcontroller.py +++ b/tests/test/test_tools/test_createcontroller.py @@ -164,16 +164,16 @@ def test_create_hierarchy_with_pivot(self): tx = maya.cmds.getAttr(ctrl + '.translateX') ty = maya.cmds.getAttr(ctrl + '.translateY') tz = maya.cmds.getAttr(ctrl + '.translateZ') - self.assertTrue(self.approx_equal(tx, 15.67139279)) - self.assertTrue(self.approx_equal(ty, 143.5280034)) - self.assertTrue(self.approx_equal(tz, 0.43112753)) + self.assertApproxEqual(tx, 15.67139279) + self.assertApproxEqual(ty, 143.5280034) + self.assertApproxEqual(tz, 0.43112753) rx = maya.cmds.getAttr(ctrl + '.rotateX') ry = maya.cmds.getAttr(ctrl + '.rotateY') rz = maya.cmds.getAttr(ctrl + '.rotateZ') - self.assertTrue(self.approx_equal(rx, 0.0)) - self.assertTrue(self.approx_equal(ry, 0.0)) - self.assertTrue(self.approx_equal(rz, 45.0)) + self.assertApproxEqual(rx, 0.0) + self.assertApproxEqual(ry, 0.0) + self.assertApproxEqual(rz, 45.0) return def test_remove_hierarchy_with_pivot(self): @@ -199,16 +199,16 @@ def test_remove_hierarchy_with_pivot(self): tx = maya.cmds.getAttr(node + '.translateX') ty = maya.cmds.getAttr(node + '.translateY') tz = maya.cmds.getAttr(node + '.translateZ') - self.assertTrue(self.approx_equal(tx, 0)) - self.assertTrue(self.approx_equal(ty, 0)) - self.assertTrue(self.approx_equal(tz, 0)) + self.assertApproxEqual(tx, 0) + self.assertApproxEqual(ty, 0) + self.assertApproxEqual(tz, 0) rx = maya.cmds.getAttr(node + '.rotateX') ry = maya.cmds.getAttr(node + '.rotateY') rz = maya.cmds.getAttr(node + '.rotateZ') - self.assertTrue(self.approx_equal(rx, 0.0)) - self.assertTrue(self.approx_equal(ry, 0.0)) - self.assertTrue(self.approx_equal(rz, 45.0)) + self.assertApproxEqual(rx, 0.0) + self.assertApproxEqual(ry, 0.0) + self.assertApproxEqual(rz, 45.0) return def create_one_keyframe_scene(self): @@ -303,7 +303,7 @@ def test_create_three(self): self.assertEqual(maya.cmds.getAttr(ctrl + '.translateX', time=mid), 20.0) self.assertEqual(maya.cmds.getAttr(ctrl + '.translateY', time=mid), 30.0) self.assertEqual(maya.cmds.getAttr(ctrl + '.translateZ', time=mid), 10.0) - self.approx_equal( + self.assertApproxEqual( maya.cmds.getAttr(ctrl + '.rotateY', time=mid), 19.545454545454547 ) return From 6e8a2cce610aa05cda7d73e7e8c2377980b5ac1f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 3 Sep 2023 23:34:06 +1000 Subject: [PATCH 018/295] Fix test_createcontroller.py The value tested against was wrong, now the value has been added correctly. --- tests/test/test_tools/test_createcontroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test/test_tools/test_createcontroller.py b/tests/test/test_tools/test_createcontroller.py index e5f7f3804..4df6f06f0 100644 --- a/tests/test/test_tools/test_createcontroller.py +++ b/tests/test/test_tools/test_createcontroller.py @@ -304,7 +304,7 @@ def test_create_three(self): self.assertEqual(maya.cmds.getAttr(ctrl + '.translateY', time=mid), 30.0) self.assertEqual(maya.cmds.getAttr(ctrl + '.translateZ', time=mid), 10.0) self.assertApproxEqual( - maya.cmds.getAttr(ctrl + '.rotateY', time=mid), 19.545454545454547 + maya.cmds.getAttr(ctrl + '.rotateY', time=mid), 29.47950580181985 ) return From e317d88c42f235c95e59da251777b24107b6a44f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 8 Sep 2023 22:44:29 +1000 Subject: [PATCH 019/295] Add TODO comment for CameraSolver. This TODO is something that should be done in the future. --- python/mmSolver/tools/solver/lib/collection.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/python/mmSolver/tools/solver/lib/collection.py b/python/mmSolver/tools/solver/lib/collection.py index fda35ec08..f241e8af5 100644 --- a/python/mmSolver/tools/solver/lib/collection.py +++ b/python/mmSolver/tools/solver/lib/collection.py @@ -774,6 +774,20 @@ def execute_collection( msg = 'log_level value is invalid; value=%r' raise ValueError(msg % log_level) + # TODO: Check common issues in the scene before solving to avoid + # errors and warn the user. + # + # - When using CameraSolver: + # + # - Make sure bundles and camera share the same parent + # world-space matrix. + # + # - All bundle values are unlocked and are static (not + # animated). + # + # TODO: For any bundle, if the connected Marker is not an input + # object, the Bundle will be skipped. + # Execute the solve. s = time.time() solres_list = mmapi.execute( From 27024149d045fd9c0e9b7be4fac72e003cce1cac Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 10 Sep 2023 13:39:15 +1000 Subject: [PATCH 020/295] Create Controller 2 - Fix Smart Bake and Object Space Combination When Smart Bake and Object Space were used together a specific code path would be hit which assumed a node, but Maya returns a list of nodes from the maya.cmds.duplicate() function. --- python/mmSolver/tools/createcontroller2/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/createcontroller2/lib.py b/python/mmSolver/tools/createcontroller2/lib.py index ce6f970ed..beb70db5c 100644 --- a/python/mmSolver/tools/createcontroller2/lib.py +++ b/python/mmSolver/tools/createcontroller2/lib.py @@ -296,7 +296,7 @@ def _create_controller_object_space( smart_bake=False, dynamic_pivot=dynamic_pivot, ) - zero_loc = maya.cmds.duplicate(loc_grp_node) + zero_loc = maya.cmds.duplicate(loc_grp_node)[0] maya.cmds.parent(zero_loc, loc_grp_node) maya.cmds.xform( zero_loc, From 55f270ce334cc56b380f32d1215aaf70a39cc846 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 10 Sep 2023 17:19:37 +1000 Subject: [PATCH 021/295] Create Controller 2 - Add Tests and Rename Variables The variable rename is to make the code easier to understand. The tests are added to help catch future issues, and to build stability confidence in the tool. Added type asserts to help pin-down issues and make the code more robust in the future. --- .../mmSolver/tools/createcontroller2/lib.py | 259 +++++++++++------- .../mmSolver/tools/createcontroller2/tool.py | 4 +- .../test/test_tools/test_createcontroller2.py | 224 +++++++++++++++ 3 files changed, 392 insertions(+), 95 deletions(-) create mode 100644 tests/test/test_tools/test_createcontroller2.py diff --git a/python/mmSolver/tools/createcontroller2/lib.py b/python/mmSolver/tools/createcontroller2/lib.py index beb70db5c..294f3321c 100644 --- a/python/mmSolver/tools/createcontroller2/lib.py +++ b/python/mmSolver/tools/createcontroller2/lib.py @@ -138,6 +138,7 @@ def _set_keyframes_at_source_node_key_times(src_node, dst_node, start_frame, end def _set_lod_visibility(node, visibility=False): """Sets shape node LOD visibility on/off.""" + assert isinstance(node, pycompat.TEXT_TYPE) assert isinstance(visibility, bool) shape = maya.cmds.listRelatives(node, shapes=True) or [] if len(shape) > 0: @@ -149,57 +150,89 @@ def _set_lod_visibility(node, visibility=False): def _world_bake( - pivot, main, loc_grp, start, end, smart_bake=False, dynamic_pivot=False + pivot_node, + main_node, + loc_grp_node, + start_frame, + end_frame, + smart_bake=False, + dynamic_pivot=False, ): + assert isinstance(pivot_node, pycompat.TEXT_TYPE) + assert isinstance(main_node, pycompat.TEXT_TYPE) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) assert isinstance(smart_bake, bool) assert isinstance(dynamic_pivot, bool) attrs = [] - if 'vtx' in pivot: + if 'vtx' in pivot_node: current_time = maya.cmds.currentTime(query=True) - for frame in range(start, end + 1): + for frame in range(start_frame, end_frame + 1): maya.cmds.currentTime(frame, edit=True) - point_pos = maya.cmds.pointPosition(pivot, world=True) - maya.cmds.xform(loc_grp, worldSpace=True, translation=point_pos) - maya.cmds.setKeyframe(loc_grp) + point_pos = maya.cmds.pointPosition(pivot_node, world=True) + maya.cmds.xform(loc_grp_node, worldSpace=True, translation=point_pos) + maya.cmds.setKeyframe(loc_grp_node) maya.cmds.currentTime(current_time, edit=True) else: - # point constraint, parent is pivot and child is loc_grp + # point constraint, parent is pivot_node and child is loc_grp_node if dynamic_pivot is True: - con = maya.cmds.pointConstraint(pivot, loc_grp, maintainOffset=False) + con = maya.cmds.pointConstraint( + pivot_node, loc_grp_node, maintainOffset=False + ) else: - point_con = maya.cmds.pointConstraint(pivot, loc_grp, maintainOffset=False) - orient_con = maya.cmds.orientConstraint(main, loc_grp, maintainOffset=False) + point_con = maya.cmds.pointConstraint( + pivot_node, loc_grp_node, maintainOffset=False + ) + orient_con = maya.cmds.orientConstraint( + main_node, loc_grp_node, maintainOffset=False + ) maya.cmds.delete(point_con, orient_con) - con = maya.cmds.parentConstraint(main, loc_grp, maintainOffset=True) - fastbake_lib.bake_attributes(loc_grp, attrs, start, end, smart_bake=smart_bake) + con = maya.cmds.parentConstraint( + main_node, loc_grp_node, maintainOffset=True + ) + fastbake_lib.bake_attributes( + [loc_grp_node], attrs, start_frame, end_frame, smart_bake=smart_bake + ) maya.cmds.delete(con) - # orient constraint, parent is main and child is loc_grp - orient_con = maya.cmds.orientConstraint(main, loc_grp, maintainOffset=False) + # orient constraint, parent is main_node and child is loc_grp_node + orient_con = maya.cmds.orientConstraint( + main_node, loc_grp_node, maintainOffset=False + ) - fastbake_lib.bake_attributes(loc_grp, attrs, start, end, smart_bake) + fastbake_lib.bake_attributes( + [loc_grp_node], attrs, start_frame, end_frame, smart_bake + ) maya.cmds.delete(orient_con) - return loc_grp + return loc_grp_node -def _create_main_driver(parent, main): - start, end = time_utils.get_maya_timeline_range_inner() - main_driver_loc = maya.cmds.duplicate(parent) - maya.cmds.setAttr(main_driver_loc[0] + '.visibility', 0) - maya.cmds.parent(main_driver_loc, parent) - parent_con = maya.cmds.parentConstraint(main, main_driver_loc) +def _create_main_driver(parent_node, main_node): + assert isinstance(parent_node, pycompat.TEXT_TYPE) + assert isinstance(main_node, pycompat.TEXT_TYPE) + + start_frame, end_frame = time_utils.get_maya_timeline_range_inner() + main_driver_loc_node = maya.cmds.duplicate(parent_node)[0] + maya.cmds.setAttr(main_driver_loc_node + '.visibility', 0) + maya.cmds.parent(main_driver_loc_node, parent_node) + parent_con = maya.cmds.parentConstraint(main_node, main_driver_loc_node) # bake attributes attrs = [] - fastbake_lib.bake_attributes(main_driver_loc, attrs, start, end, smart_bake=False) + fastbake_lib.bake_attributes( + [main_driver_loc_node], attrs, start_frame, end_frame, smart_bake=False + ) maya.cmds.delete(parent_con) - maya.cmds.setAttr(main_driver_loc[0] + '.hiddenInOutliner', 1) - return main_driver_loc + maya.cmds.setAttr(main_driver_loc_node + '.hiddenInOutliner', 1) + return main_driver_loc_node def _find_constraints_from_node(node): + assert isinstance(node, pycompat.TEXT_TYPE) + constraints = ( maya.cmds.listConnections( node + '.parentMatrix[0]', destination=True, source=False, type='constraint' @@ -232,7 +265,14 @@ def _create_controller_world_space( current_frame, dynamic_pivot=False, ): + assert isinstance(pivot_node, pycompat.TEXT_TYPE) + assert isinstance(main_node, pycompat.TEXT_TYPE) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) + assert isinstance(current_frame, bool) assert isinstance(dynamic_pivot, bool) + skip_translate_attr = _skip_translate_attributes(main_node) skip_rotate_attr = _skip_rotate_attributes(main_node) @@ -246,10 +286,10 @@ def _create_controller_world_space( dynamic_pivot=dynamic_pivot, ) - main_driver_loc = _create_main_driver(loc_grp_node, main_node) + main_driver_loc_node = _create_main_driver(loc_grp_node, main_node) maya.cmds.parentConstraint( - main_driver_loc, + main_driver_loc_node, main_node, maintainOffset=True, skipTranslate=skip_translate_attr, @@ -259,14 +299,16 @@ def _create_controller_world_space( if current_frame is True: maya.cmds.cutKey(loc_grp_node, time=(end_frame, end_frame)) inner_start_frame, inner_end_frame = time_utils.get_maya_timeline_range_inner() - maya.cmds.cutKey(main_driver_loc, time=(inner_start_frame, inner_end_frame)) - maya.cmds.setKeyframe(main_driver_loc) + maya.cmds.cutKey( + main_driver_loc_node, time=(inner_start_frame, inner_end_frame) + ) + maya.cmds.setKeyframe(main_driver_loc_node) # LOD visibility _set_lod_visibility(loc_grp_node, True) - _set_lod_visibility(main_driver_loc, False) + _set_lod_visibility(main_driver_loc_node, False) - maya.cmds.rename(main_driver_loc, str(name) + MAIN_DRIVER_SUFFIX_NAME) + maya.cmds.rename(main_driver_loc_node, str(name) + MAIN_DRIVER_SUFFIX_NAME) return loc_grp_node @@ -281,8 +323,15 @@ def _create_controller_object_space( current_frame, dynamic_pivot=False, ): + assert isinstance(pivot_node, pycompat.TEXT_TYPE) + assert isinstance(main_node, pycompat.TEXT_TYPE) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) assert isinstance(smart_bake, bool) + assert isinstance(current_frame, bool) assert isinstance(dynamic_pivot, bool) + skip_translate_attr = _skip_translate_attributes(main_node) skip_rotate_attr = _skip_rotate_attributes(main_node) @@ -296,32 +345,34 @@ def _create_controller_object_space( smart_bake=False, dynamic_pivot=dynamic_pivot, ) - zero_loc = maya.cmds.duplicate(loc_grp_node)[0] - maya.cmds.parent(zero_loc, loc_grp_node) + zero_loc_node = maya.cmds.duplicate(loc_grp_node)[0] + maya.cmds.parent(zero_loc_node, loc_grp_node) maya.cmds.xform( - zero_loc, + zero_loc_node, translation=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), objectSpace=True, ) - main_driver_loc = _create_main_driver(zero_loc, main_node) + main_driver_loc_node = _create_main_driver(zero_loc_node, main_node) # Smart bake if smart_bake is True: _set_keyframes_at_source_node_key_times( - main_node, zero_loc, start_frame, end_frame + main_node, zero_loc_node, start_frame, end_frame ) # Current frame if current_frame is True: - maya.cmds.setKeyframe(zero_loc, time=(start_frame, start_frame)) - maya.cmds.cutKey(loc_grp_node, zero_loc, time=(end_frame, end_frame)) + maya.cmds.setKeyframe(zero_loc_node, time=(start_frame, start_frame)) + maya.cmds.cutKey(loc_grp_node, zero_loc_node, time=(end_frame, end_frame)) inner_start_frame, inner_end_frame = time_utils.get_maya_timeline_range_inner() - maya.cmds.cutKey(main_driver_loc, time=(inner_start_frame, inner_end_frame)) - maya.cmds.setKeyframe(main_driver_loc) + maya.cmds.cutKey( + main_driver_loc_node, time=(inner_start_frame, inner_end_frame) + ) + maya.cmds.setKeyframe(main_driver_loc_node) maya.cmds.parentConstraint( - main_driver_loc, + main_driver_loc_node, main_node, maintainOffset=True, skipTranslate=skip_translate_attr, @@ -330,12 +381,12 @@ def _create_controller_object_space( # LOD visibility _set_lod_visibility(loc_grp_node, False) - _set_lod_visibility(zero_loc, True) - _set_lod_visibility(main_driver_loc, False) + _set_lod_visibility(zero_loc_node, True) + _set_lod_visibility(main_driver_loc_node, False) # Rename - maya.cmds.rename(zero_loc, str(name) + OBJECT_SPACE_RIG_ZERO_SUFFIX) - maya.cmds.rename(main_driver_loc, str(name) + MAIN_DRIVER_SUFFIX_NAME) + maya.cmds.rename(zero_loc_node, str(name) + OBJECT_SPACE_RIG_ZERO_SUFFIX) + maya.cmds.rename(main_driver_loc_node, str(name) + MAIN_DRIVER_SUFFIX_NAME) return loc_grp_node @@ -348,9 +399,18 @@ def _create_controller_screen_space( end_frame, smart_bake, current_frame, - camera, + camera_node, dynamic_pivot=False, ): + assert isinstance(pivot_node, pycompat.TEXT_TYPE) + assert isinstance(main_node, pycompat.TEXT_TYPE) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) + assert isinstance(camera_node, pycompat.TEXT_TYPE) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) + assert isinstance(smart_bake, bool) + assert isinstance(dynamic_pivot, bool) + skip_translate_attr = _skip_translate_attributes(main_node) if len(skip_translate_attr) != 0: LOG.error( @@ -370,10 +430,10 @@ def _create_controller_screen_space( smart_bake=False, dynamic_pivot=dynamic_pivot, ) - screen_loc = maya.cmds.duplicate(loc_grp_node) - maya.cmds.parent(screen_loc, loc_grp_node) + screen_loc_node = maya.cmds.duplicate(loc_grp_node)[0] + maya.cmds.parent(screen_loc_node, loc_grp_node) maya.cmds.xform( - screen_loc, + screen_loc_node, translation=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), objectSpace=True, @@ -382,54 +442,60 @@ def _create_controller_screen_space( # Bake attributes attrs = [] fastbake_lib.bake_attributes( - screen_loc, attrs, start_frame, end_frame, smart_bake=False + [screen_loc_node], attrs, start_frame, end_frame, smart_bake=False + ) + aim_con = maya.cmds.aimConstraint( + camera_node, screen_loc_node, aimVector=(0.0, 0.0, 1.0) ) - aim_con = maya.cmds.aimConstraint(camera, screen_loc, aimVector=(0.0, 0.0, 1.0)) # Bake attributes fastbake_lib.bake_attributes( - screen_loc, attrs, start_frame, end_frame, smart_bake=False + [screen_loc_node], attrs, start_frame, end_frame, smart_bake=False ) maya.cmds.delete(aim_con) - zero_loc = maya.cmds.duplicate(screen_loc) - maya.cmds.parent(zero_loc, screen_loc) + zero_loc_node = maya.cmds.duplicate(screen_loc_node)[0] + maya.cmds.parent(zero_loc_node, screen_loc_node) maya.cmds.xform( - zero_loc, + zero_loc_node, translation=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), objectSpace=True, ) - main_driver_loc = _create_main_driver(zero_loc, main_node) + main_driver_loc_node = _create_main_driver(zero_loc_node, main_node) # Smart bake if smart_bake is True: _set_keyframes_at_source_node_key_times( - main_node, zero_loc, start_frame, end_frame + main_node, zero_loc_node, start_frame, end_frame ) # Current frame if current_frame is True: - maya.cmds.setKeyframe(screen_loc, zero_loc, time=(start_frame, start_frame)) + maya.cmds.setKeyframe( + screen_loc_node, zero_loc_node, time=(start_frame, start_frame) + ) maya.cmds.cutKey( - loc_grp_node, screen_loc, zero_loc, time=(end_frame, end_frame) + loc_grp_node, screen_loc_node, zero_loc_node, time=(end_frame, end_frame) ) inner_start_frame, inner_end_frame = time_utils.get_maya_timeline_range_inner() - maya.cmds.cutKey(main_driver_loc, time=(inner_start_frame, inner_end_frame)) - maya.cmds.setKeyframe(main_driver_loc) + maya.cmds.cutKey( + main_driver_loc_node, time=(inner_start_frame, inner_end_frame) + ) + maya.cmds.setKeyframe(main_driver_loc_node) - maya.cmds.pointConstraint(main_driver_loc, main_node, maintainOffset=True) + maya.cmds.pointConstraint(main_driver_loc_node, main_node, maintainOffset=True) # LOD visibility _set_lod_visibility(loc_grp_node, False) - _set_lod_visibility(screen_loc, False) - _set_lod_visibility(zero_loc, True) - _set_lod_visibility(main_driver_loc, False) + _set_lod_visibility(screen_loc_node, False) + _set_lod_visibility(zero_loc_node, True) + _set_lod_visibility(main_driver_loc_node, False) # Rename - maya.cmds.rename(screen_loc, name + SCREEN_SPACE_RIG_SUFFIX) - maya.cmds.rename(zero_loc, name + SCREEN_SPACE_RIG_ZERO_SUFFIX) - maya.cmds.rename(main_driver_loc, str(name) + MAIN_DRIVER_SUFFIX_NAME) + maya.cmds.rename(screen_loc_node, name + SCREEN_SPACE_RIG_SUFFIX) + maya.cmds.rename(zero_loc_node, name + SCREEN_SPACE_RIG_ZERO_SUFFIX) + maya.cmds.rename(main_driver_loc_node, str(name) + MAIN_DRIVER_SUFFIX_NAME) return loc_grp_node @@ -437,10 +503,10 @@ def create_controller( name, pivot_node, main_node, - loc_grp_node, + loc_grp_nodes, start_frame, end_frame, - controller_type, + controller_space, smart_bake=False, camera=None, dynamic_pivot=False, @@ -459,11 +525,11 @@ def create_controller( :param main_node: The node to be controlled :type main_node: str - :param loc_grp_node: The nodes for the Locator or Group node that + :param loc_grp_nodes: The nodes for the Locator or Group node that will be used to create rig. If a Locator is used, a list of both transform and shape nodes should be provided. If a Group is provided, just use a list with the transform node. - :type loc_grp_node: [str] + :type loc_grp_nodes: [str] :param start_frame: bake range start frame :type start_frame: int @@ -471,8 +537,8 @@ def create_controller( :param end_frame: bake range end frame :type end_frame: int - :param controller_type: In which space rig to be created? A value in - mmSolver.tools.createcontroller2.constant.CONTROLLER_TYPE_LIST. + :param controller_space: In which space rig to be created? A value in + mmSolver.tools.createcontroller2.constant.CONTROLLER_SPACE_LIST. :type: str :param smart_bake: Enable or disable baking only "smart" keyframe times. @@ -485,8 +551,8 @@ def create_controller( :type: bool :returns: The controller node locator/group created. This should - be the same as 'loc_grp_node' given to the function. - :rtype: [str, ..] + be the same as 'loc_grp_nodes' given to the function. + :rtype: [str] """ current_frame = False if start_frame == end_frame: @@ -496,20 +562,21 @@ def create_controller( assert isinstance(end_frame, int) assert isinstance(dynamic_pivot, bool) assert isinstance(smart_bake, bool) - assert len(loc_grp_node) == 1 + assert len(loc_grp_nodes) == 1 + loc_grp_node = loc_grp_nodes[0] # Add custom identify attribute maya.cmds.addAttr( - loc_grp_node[0], longName=IDENTIFIER_ATTR_NAME, dataType='string', keyable=False + loc_grp_node, longName=IDENTIFIER_ATTR_NAME, dataType='string', keyable=False ) maya.cmds.setAttr( - str(loc_grp_node[0]) + '.' + IDENTIFIER_ATTR_NAME, - str(loc_grp_node[0] + str(random.randint(1, 100000000))), + str(loc_grp_node) + '.' + IDENTIFIER_ATTR_NAME, + str(loc_grp_node + str(random.randint(1, 100000000))), type='string', lock=True, ) - if controller_type == const.CONTROLLER_SPACE_WORLD: + if controller_space == const.CONTROLLER_SPACE_WORLD: loc_grp_node = _create_controller_world_space( name, pivot_node, @@ -521,8 +588,9 @@ def create_controller( current_frame, dynamic_pivot, ) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) - elif controller_type == const.CONTROLLER_SPACE_OBJECT: + elif controller_space == const.CONTROLLER_SPACE_OBJECT: loc_grp_node = _create_controller_object_space( name, pivot_node, @@ -534,8 +602,12 @@ def create_controller( current_frame, dynamic_pivot, ) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) - elif controller_type == const.CONTROLLER_SPACE_SCREEN: + elif controller_space == const.CONTROLLER_SPACE_SCREEN: + if camera is None: + LOG.error("Camera is not valid: %r", camera) + return loc_grp_node loc_grp_node = _create_controller_screen_space( name, pivot_node, @@ -548,25 +620,26 @@ def create_controller( camera, dynamic_pivot, ) + assert isinstance(loc_grp_node, pycompat.TEXT_TYPE) else: LOG.error('Invalid space.') - return loc_grp_node + return [loc_grp_node] -def remove_controller(controller_node, frame_start, frame_end, attrs=None): +def remove_controller(controller_node, start_frame, end_frame, attrs=None): """ Bake the affects of the controller node, and delete the controller. :param controller_node: The controller node. :type controller_node: str - :param frame_start: First frame to bake. - :type frame_start: int + :param start_frame: First frame to bake. + :type start_frame: int - :param frame_end: Last frame to bake. - :type frame_end: int + :param end_frame: Last frame to bake. + :type end_frame: int :param attrs: List of attributes to bake. If None, all transform, rotate and scale attributes are baked. @@ -578,8 +651,8 @@ def remove_controller(controller_node, frame_start, frame_end, attrs=None): """ assert isinstance(controller_node, pycompat.TEXT_TYPE) assert maya.cmds.objExists(controller_node) - assert isinstance(frame_start, int) - assert isinstance(frame_end, int) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) if attrs is None: attrs = TRANSFORM_ATTRS assert isinstance(attrs, list) @@ -640,11 +713,11 @@ def remove_controller(controller_node, frame_start, frame_end, attrs=None): # baked", then set keys on all the same frames, therefore # re-creating the original keyframe times. _set_keyframes_at_source_node_key_times( - controller_node, driven_node, frame_start, frame_end + controller_node, driven_node, start_frame, end_frame ) else: fastbake_lib.bake_attributes( - driven_nodes, attrs, frame_start, frame_end, smart_bake=True + driven_nodes, attrs, start_frame, end_frame, smart_bake=True ) # Delete nodes and clean up. diff --git a/python/mmSolver/tools/createcontroller2/tool.py b/python/mmSolver/tools/createcontroller2/tool.py index 6edbdadc4..415c2bd76 100644 --- a/python/mmSolver/tools/createcontroller2/tool.py +++ b/python/mmSolver/tools/createcontroller2/tool.py @@ -85,7 +85,7 @@ def create_world_controllers(): name_tfm = '{}_CTRL'.format(node.rpartition('|')[-1]) # This node is used as the controller. - loc_grp_node = maya.cmds.spaceLocator(name=name_tfm) + loc_grp_nodes = maya.cmds.spaceLocator(name=name_tfm) pivot_node = node main_node = node @@ -93,7 +93,7 @@ def create_world_controllers(): name_tfm, pivot_node, main_node, - loc_grp_node, + loc_grp_nodes, start_frame, end_frame, controller_type, diff --git a/tests/test/test_tools/test_createcontroller2.py b/tests/test/test_tools/test_createcontroller2.py new file mode 100644 index 000000000..49efcde08 --- /dev/null +++ b/tests/test/test_tools/test_createcontroller2.py @@ -0,0 +1,224 @@ +# Copyright (C) 2019, 2020, 2022 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Test functions for createcontroller2 tool. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import unittest + +import maya.cmds + +import test.test_tools.toolsutils as test_tools_utils +import mmSolver.tools.createcontroller2.lib as lib +import mmSolver.tools.createcontroller2.constant as const + + +# @unittest.skip +class TestCreateController2(test_tools_utils.ToolsTestCase): + def create_no_keyframe_scene(self): + tfm = maya.cmds.createNode('transform') + maya.cmds.setAttr(tfm + '.translateX', 10.0) + maya.cmds.setAttr(tfm + '.translateY', 20.0) + maya.cmds.setAttr(tfm + '.translateZ', 30.0) + return tfm + + def create_no_keyframe_scene_with_pivot(self): + tfm = maya.cmds.createNode('transform') + maya.cmds.setAttr(tfm + '.translateX', 10.0) + maya.cmds.setAttr(tfm + '.translateY', 20.0) + maya.cmds.setAttr(tfm + '.translateZ', 30.0) + + pivot = (-10.0, -20.0, -30.0) + maya.cmds.setAttr(tfm + '.rotatePivotX', pivot[0]) + maya.cmds.setAttr(tfm + '.rotatePivotY', pivot[1]) + maya.cmds.setAttr(tfm + '.rotatePivotZ', pivot[2]) + maya.cmds.setAttr(tfm + '.scalePivotX', pivot[0]) + maya.cmds.setAttr(tfm + '.scalePivotY', pivot[1]) + maya.cmds.setAttr(tfm + '.scalePivotZ', pivot[2]) + return tfm + + def create_one_keyframe_scene(self): + tfm = maya.cmds.createNode('transform') + maya.cmds.setKeyframe(tfm, attribute='translateX', value=10.0) + maya.cmds.setKeyframe(tfm, attribute='translateY', value=20.0) + maya.cmds.setKeyframe(tfm, attribute='translateZ', value=30.0) + return tfm + + def create_multi_keyframe_scene(self, start, mid, end): + maya.cmds.playbackOptions(edit=True, minTime=start) + maya.cmds.playbackOptions(edit=True, animationStartTime=start) + maya.cmds.playbackOptions(edit=True, animationEndTime=end) + maya.cmds.playbackOptions(edit=True, maxTime=end) + + tfm = maya.cmds.createNode('transform') + maya.cmds.setKeyframe(tfm, attribute='translateX', value=10.0, time=start) + maya.cmds.setKeyframe(tfm, attribute='translateY', value=20.0, time=start) + maya.cmds.setKeyframe(tfm, attribute='translateZ', value=30.0, time=start) + maya.cmds.setKeyframe(tfm, attribute='rotateY', value=45.0, time=start) + + maya.cmds.setKeyframe(tfm, attribute='translateX', value=20.0, time=mid) + maya.cmds.setKeyframe(tfm, attribute='translateY', value=30.0, time=mid) + maya.cmds.setKeyframe(tfm, attribute='translateZ', value=10.0, time=mid) + + maya.cmds.setKeyframe(tfm, attribute='translateX', value=30.0, time=end) + maya.cmds.setKeyframe(tfm, attribute='translateY', value=10.0, time=end) + maya.cmds.setKeyframe(tfm, attribute='translateZ', value=20.0, time=end) + maya.cmds.setKeyframe(tfm, attribute='rotateY', value=-60.0, time=end) + return tfm + + def create_hierarchy_scene(self, start, end): + maya.cmds.playbackOptions(edit=True, minTime=start) + maya.cmds.playbackOptions(edit=True, animationStartTime=start) + maya.cmds.playbackOptions(edit=True, animationEndTime=end) + maya.cmds.playbackOptions(edit=True, maxTime=end) + + tfm_a = maya.cmds.createNode('transform') + maya.cmds.setKeyframe(tfm_a, attribute='translateY', value=20.0, time=start) + maya.cmds.setKeyframe(tfm_a, attribute='rotateY', value=345.0, time=start) + + maya.cmds.setKeyframe(tfm_a, attribute='translateY', value=10.0, time=end) + maya.cmds.setKeyframe(tfm_a, attribute='rotateY', value=-360.0, time=end) + + tfm_b = maya.cmds.createNode('transform', parent=tfm_a) + maya.cmds.setKeyframe(tfm_b, attribute='translateX', value=10.0, time=start) + maya.cmds.setKeyframe(tfm_b, attribute='translateY', value=20.0, time=start) + maya.cmds.setKeyframe(tfm_b, attribute='translateZ', value=30.0, time=start) + maya.cmds.setKeyframe(tfm_b, attribute='rotateY', value=45.0, time=start) + + maya.cmds.setKeyframe(tfm_b, attribute='translateX', value=30.0, time=end) + maya.cmds.setKeyframe(tfm_b, attribute='translateY', value=10.0, time=end) + maya.cmds.setKeyframe(tfm_b, attribute='translateZ', value=20.0, time=end) + maya.cmds.setKeyframe(tfm_b, attribute='rotateY', value=-60.0, time=end) + + maya.cmds.createNode('transform', parent=tfm_a) + maya.cmds.createNode('transform', parent=tfm_b) + return tfm_a, tfm_b + + def run_tool( + self, name, pivot_node, main_node, camera_tfm_node, start_frame, end_frame + ): + """ + Runs the given nodes with all The combination of options + that the tool can take. + """ + smart_bakes = [False, True] + dynamic_pivots = [False, True] + controller_spaces = const.CONTROLLER_SPACE_LIST + for smart_bake in smart_bakes: + for dynamic_pivot in dynamic_pivots: + for controller_space in controller_spaces: + loc_grp_nodes = maya.cmds.spaceLocator(name=name) + ctrls = lib.create_controller( + name, + pivot_node, + main_node, + loc_grp_nodes, + start_frame, + end_frame, + controller_space, + smart_bake=smart_bake, + camera=camera_tfm_node, + dynamic_pivot=dynamic_pivot, + ) + lib.remove_controller(ctrls[0], start_frame, end_frame) + + def test_no_keyframe(self): + """ + Create/Remove Controller node with no keyframes. + """ + tfm = self.create_no_keyframe_scene() + + name = 'controller' + camera_tfm_node = '|persp' + start_frame = 1 + end_frame = 100 + self.run_tool(name, tfm, tfm, camera_tfm_node, start_frame, end_frame) + return + + def test_one_keyframe(self): + """ + Create/Remove Controller node with a single keyframe. + """ + tfm = self.create_one_keyframe_scene() + + name = 'controller' + camera_tfm_node = '|persp' + start_frame = 1 + end_frame = 100 + self.run_tool(name, tfm, tfm, camera_tfm_node, start_frame, end_frame) + return + + def test_three_keyframes(self): + """ + Create/Remove Controller node with three keyframes. + """ + start = 1 + mid = 25 + end = 100 + tfm = self.create_multi_keyframe_scene(start, mid, end) + + name = 'controller' + camera_tfm_node = '|persp' + start_frame = 1 + end_frame = 100 + self.run_tool(name, tfm, tfm, camera_tfm_node, start_frame, end_frame) + return + + def test_hierarchy(self): + """ + Create/Remove Controller node in a hierarchy. + """ + start = 1 + end = 100 + tfm_a, tfm_b = self.create_hierarchy_scene(start, end) + + name = 'controller' + camera_tfm_node = '|persp' + start_frame = 1 + end_frame = 100 + self.run_tool(name, tfm_a, tfm_a, camera_tfm_node, start_frame, end_frame) + self.run_tool(name, tfm_b, tfm_b, camera_tfm_node, start_frame, end_frame) + return + + def test_object_hierarchy(self): + """ + Create and destroy controllers on a hierarchy of transforms. + """ + path = self.get_data_path('scenes', 'objectHierarchy.ma') + maya.cmds.file(path, open=True, force=True) + + tfm_a = 'group1' + tfm_b = 'pSphere1' + tfm_c = 'pSphere2' + + name = 'controller' + camera_tfm_node = '|persp' + start_frame = 1 + end_frame = 100 + self.run_tool(name, tfm_a, tfm_a, camera_tfm_node, start_frame, end_frame) + self.run_tool(name, tfm_b, tfm_b, camera_tfm_node, start_frame, end_frame) + self.run_tool(name, tfm_c, tfm_c, camera_tfm_node, start_frame, end_frame) + return + + +if __name__ == '__main__': + prog = unittest.main() From a0ef2158490100719079e503c0361285afa96a7f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 24 Sep 2023 16:26:28 +1000 Subject: [PATCH 022/295] Fix spelling mistake in 'Set Object Color' tool warning. --- python/mmSolver/tools/setobjectcolour/tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/setobjectcolour/tool.py b/python/mmSolver/tools/setobjectcolour/tool.py index e6b02e979..37c9b9ebb 100644 --- a/python/mmSolver/tools/setobjectcolour/tool.py +++ b/python/mmSolver/tools/setobjectcolour/tool.py @@ -72,7 +72,7 @@ def open_window(mini_ui=None): maya.cmds.colorEditor(rgbValue=rgb, alpha=alpha, **kwargs) if not maya.cmds.colorEditor(query=True, result=True): - LOG.warn('Color editor cancelled.') + LOG.warn('Color editor canceled.') return rgb = maya.cmds.colorEditor(query=True, rgbValue=True) From 8adbc10e51f065203e50018aff5d33f080c2b591 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 24 Sep 2023 16:27:30 +1000 Subject: [PATCH 023/295] Add 'mmSolver.utils.viewport.get/set_selection_highlight()' Used to control the model panel's selection highlighting toggle. --- python/mmSolver/utils/viewport.py | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/python/mmSolver/utils/viewport.py b/python/mmSolver/utils/viewport.py index f8dbfd8dd..827602775 100644 --- a/python/mmSolver/utils/viewport.py +++ b/python/mmSolver/utils/viewport.py @@ -376,6 +376,38 @@ def set_isolated_nodes(model_panel, nodes, enable): return +def get_selection_highlight(model_panel): + """ + Query the display of selection highlighting. + + :param model_panel: Model panel name to set visibility. + :type model_panel: str + + :return: True, if the selection highlighting is enable in the model + panel/editor. + :rtype: bool + """ + model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) + value = maya.cmds.modelEditor(model_editor, query=True, selectionHiliteDisplay=True) + return value + + +def set_selection_highlight(model_panel, value): + """ + Query the display of selection highlighting. + + :param model_panel: Model panel name to set visibility. + :type model_panel: str + + :param value: Enable or disable selection highlighting? + :type value: bool + """ + assert isinstance(value, bool) + model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) + maya.cmds.modelEditor(model_editor, edit=True, selectionHiliteDisplay=value) + return + + def _get_node_type_visibility(model_panel, node_type): """ Query the image plane visibility. From 46c6da971c7cd7671ebfbbc9d81d58abb26eadd9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 24 Sep 2023 19:33:00 +1000 Subject: [PATCH 024/295] Make sure libXtst is installed for Maya 2019 tests to run Strangely, without libXtst "mayapy" will not run the latest mmSolver test suite. This was never an issue before, so I'm not sure what has changed. --- share/docker/Dockerfile_maya2019 | 1 + 1 file changed, 1 insertion(+) diff --git a/share/docker/Dockerfile_maya2019 b/share/docker/Dockerfile_maya2019 index 8b39fcfdf..b4705f078 100644 --- a/share/docker/Dockerfile_maya2019 +++ b/share/docker/Dockerfile_maya2019 @@ -61,6 +61,7 @@ RUN yum install --assumeyes \ libXrandr \ libXrender \ libXt \ + libXtst \ libxcb \ libxkbcommon \ libxkbcommon-x11 \ From 716ca42d45388de776f1f42db6745f26e1d71616 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 9 Oct 2023 22:38:50 +1100 Subject: [PATCH 025/295] Add Windows libraries to mmscenegraph Strange, but without this change mmSolver doesn't link on Windows. --- share/cmake/modules/MMRustUtils.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/share/cmake/modules/MMRustUtils.cmake b/share/cmake/modules/MMRustUtils.cmake index a27b5b301..b2a097fff 100644 --- a/share/cmake/modules/MMRustUtils.cmake +++ b/share/cmake/modules/MMRustUtils.cmake @@ -81,6 +81,8 @@ function(mm_rust_get_depend_on_libraries depend_on_libraries) # https://cmake.org/cmake/help/latest/command/set.html#set-normal-variable if (MSVC) set(depend_on_libraries + Kernel32 + Ntdll ws2_32 userenv advapi32 shell32 msvcrt Bcrypt PARENT_SCOPE ) From b7ebdb5533cedd01d545d51611499c025cb11201 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 26 Oct 2023 23:06:39 +1100 Subject: [PATCH 026/295] Set project version to v0.4.7 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9386b9c13..e6df90228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() project(mayaMatchMoveSolver) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 4) -set(PROJECT_VERSION_PATCH 6) +set(PROJECT_VERSION_PATCH 7) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "Bundle Adjustment solver for MatchMove tasks in Autodesk Maya.") From c93ee25076077da87e3013a0ebbeff36062bb59d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 27 Oct 2023 23:20:20 +1100 Subject: [PATCH 027/295] Convert Frame Range to List Python 2.7 returns a list from 'range()', but in Python 3.x 'range()' returns an iterator. To be compatible with both, we must force-convert the frame ranges to lists. GitHub Issue #249 --- python/mmSolver/_api/solvercamerautils.py | 2 +- python/mmSolver/tools/convertmarker/lib.py | 2 +- python/mmSolver/tools/savemarkerfile/lib.py | 2 +- python/mmSolver/utils/time.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/python/mmSolver/_api/solvercamerautils.py b/python/mmSolver/_api/solvercamerautils.py index c6be74711..1abfa8fea 100644 --- a/python/mmSolver/_api/solvercamerautils.py +++ b/python/mmSolver/_api/solvercamerautils.py @@ -748,7 +748,7 @@ def _set_camera_origin_frame( previous_rotation = None cam_tfm_values = [] - frames = range(start_frame, end_frame + 1) + frames = list(range(start_frame, end_frame + 1)) for frame in frames: ctx = tfm_utils.create_dg_context_apitwo(frame) matrix = tfm_utils.get_matrix_from_plug_apitwo(cam_matrix_plug, ctx) diff --git a/python/mmSolver/tools/convertmarker/lib.py b/python/mmSolver/tools/convertmarker/lib.py index 30c627974..10558eaaa 100644 --- a/python/mmSolver/tools/convertmarker/lib.py +++ b/python/mmSolver/tools/convertmarker/lib.py @@ -44,7 +44,7 @@ def convert_nodes_to_marker_data_list(cam_tfm, cam_shp, nodes, start_frame, end_ cur_time = maya.cmds.currentTime(query=True) mkr_data_list = [] - frames = range(start_frame, end_frame + 1) + frames = list(range(start_frame, end_frame + 1)) for node in nodes: # TODO: If a camera has 'camera scale' attribute set other than # 1.0, the reprojected values will not be correct. diff --git a/python/mmSolver/tools/savemarkerfile/lib.py b/python/mmSolver/tools/savemarkerfile/lib.py index 286023c74..4dd9d5fa8 100644 --- a/python/mmSolver/tools/savemarkerfile/lib.py +++ b/python/mmSolver/tools/savemarkerfile/lib.py @@ -157,7 +157,7 @@ def generate(mkr_list, frame_range): :returns: List of Markers :rtype: [Marker] """ - frames = range(frame_range.start, frame_range.end + 1) + frames = list(range(frame_range.start, frame_range.end + 1)) assert len(frames) > 0 cameras_map = {} diff --git a/python/mmSolver/utils/time.py b/python/mmSolver/utils/time.py index ca5a5ae05..6a13ce7d5 100644 --- a/python/mmSolver/utils/time.py +++ b/python/mmSolver/utils/time.py @@ -148,5 +148,5 @@ def convert_frame_range_to_frame_list(frame_range): :rtype: [int, ...] or [] """ start_frame, end_frame = frame_range - frames = range(int(start_frame), int(end_frame) + 1) + frames = list(range(int(start_frame), int(end_frame) + 1)) return frames From aaf015683c958c4ac5c1f8c2d27c9ff7723d39ca Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 27 Oct 2023 23:21:01 +1100 Subject: [PATCH 028/295] Marker - Convert frame range to list --- python/mmSolver/_api/marker.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python/mmSolver/_api/marker.py b/python/mmSolver/_api/marker.py index 93c7cb9aa..24783b30b 100644 --- a/python/mmSolver/_api/marker.py +++ b/python/mmSolver/_api/marker.py @@ -829,8 +829,7 @@ def get_deviation_frames(self, frame_range_start=None, frame_range_end=None): anim_curve_fn = self.get_deviation_anim_curve_fn() if anim_curve_fn is None: - enable_times = range(frame_range_start, frame_range_end + 1) - enable_times = list(enable_times) + enable_times = list(range(frame_range_start, frame_range_end + 1)) else: num_keys = anim_curve_fn.numKeys() enable_times = [None] * num_keys @@ -838,8 +837,7 @@ def get_deviation_frames(self, frame_range_start=None, frame_range_end=None): mtime = anim_curve_fn.time(i) enable_times[i] = int(mtime.value()) if num_keys == 0: - enable_times = range(frame_range_start, frame_range_end + 1) - enable_times = list(enable_times) + enable_times = list(range(frame_range_start, frame_range_end + 1)) start_frame = int(min(enable_times)) end_frame = int(max(enable_times)) From 3304c270be3127030068c193b40149ed59213bb8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 27 Oct 2023 23:21:59 +1100 Subject: [PATCH 029/295] Convert To Marker - Add assertions for camera node existance. Issue #249 --- python/mmSolver/tools/convertmarker/lib.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/mmSolver/tools/convertmarker/lib.py b/python/mmSolver/tools/convertmarker/lib.py index 10558eaaa..deafaec11 100644 --- a/python/mmSolver/tools/convertmarker/lib.py +++ b/python/mmSolver/tools/convertmarker/lib.py @@ -39,6 +39,8 @@ def convert_nodes_to_marker_data_list(cam_tfm, cam_shp, nodes, start_frame, end_frame): + assert maya.cmds.objExists(cam_tfm) + assert maya.cmds.objExists(cam_shp) # Ensure the plug-in is loaded, to use mmReprojection command. mmapi.load_plugin() From 6ea04abfc263900618976a49d5e5d17240193c26 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 27 Oct 2023 23:23:21 +1100 Subject: [PATCH 030/295] Fix 'mmSolver.utils.time.get_frame_range()' function The wrong frame range will be returned given the frame_range_mode. --- python/mmSolver/utils/time.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/utils/time.py b/python/mmSolver/utils/time.py index 6a13ce7d5..b37f99647 100644 --- a/python/mmSolver/utils/time.py +++ b/python/mmSolver/utils/time.py @@ -124,9 +124,9 @@ def get_frame_range(frame_range_mode, start_frame=None, end_frame=None): assert frame_range_mode in const.FRAME_RANGE_MODE_VALUES if frame_range_mode == const.FRAME_RANGE_MODE_TIMELINE_INNER_VALUE: - start_frame, end_frame = get_maya_timeline_range_outer() - elif frame_range_mode == const.FRAME_RANGE_MODE_TIMELINE_OUTER_VALUE: start_frame, end_frame = get_maya_timeline_range_inner() + elif frame_range_mode == const.FRAME_RANGE_MODE_TIMELINE_OUTER_VALUE: + start_frame, end_frame = get_maya_timeline_range_outer() elif frame_range_mode == const.FRAME_RANGE_MODE_CUSTOM_VALUE: assert start_frame is not None assert end_frame is not None From b6a058691e7975d185e1c9d4aad393db7839bc48 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 27 Oct 2023 23:24:32 +1100 Subject: [PATCH 031/295] Fix Convert to Marker - The timeline frame range is now used. Before the current frame range values were (incorrectly) ignored. --- python/mmSolver/tools/convertmarker/tool.py | 2 +- .../mmSolver/tools/convertmarker/ui/convertmarker_layout.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/tools/convertmarker/tool.py b/python/mmSolver/tools/convertmarker/tool.py index b36198fa9..7d9eb81b4 100644 --- a/python/mmSolver/tools/convertmarker/tool.py +++ b/python/mmSolver/tools/convertmarker/tool.py @@ -100,7 +100,7 @@ def main( delete_static_anim_curves = value # Frame range - time_utils.get_frame_range( + start_frame, end_frame = time_utils.get_frame_range( frame_range_mode, start_frame=start_frame, end_frame=end_frame ) diff --git a/python/mmSolver/tools/convertmarker/ui/convertmarker_layout.py b/python/mmSolver/tools/convertmarker/ui/convertmarker_layout.py index 6f21ea59c..dec3b4c0f 100644 --- a/python/mmSolver/tools/convertmarker/ui/convertmarker_layout.py +++ b/python/mmSolver/tools/convertmarker/ui/convertmarker_layout.py @@ -91,7 +91,9 @@ def frameRangeModeIndexChanged(self, index): frame_end = configmaya.get_scene_option( const.CONFIG_FRAME_END_KEY, default=const.DEFAULT_FRAME_END ) - time_utils.get_frame_range(value, start_frame=frame_start, end_frame=frame_end) + frame_start, frame_end = time_utils.get_frame_range( + value, start_frame=frame_start, end_frame=frame_end + ) self.frameRangeStartSpinBox.setValue(frame_start) self.frameRangeEndSpinBox.setValue(frame_end) From ac15f910069e5b2108e0a19fa0984f7b1d18ec98 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 28 Oct 2023 21:40:42 +1100 Subject: [PATCH 032/295] Convert to Marker - Add Option Box to Shelf Marker Menu --- share/config/shelf.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/config/shelf.json b/share/config/shelf.json index 1e88dcb3a..d4aa995b3 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -46,7 +46,7 @@ "marker_tools/marker_popup/raycast_marker", "marker_tools/marker_popup/---Create Marker", "marker_tools/marker_popup/create_marker", - "marker_tools/marker_popup/convert_to_marker_ui", + "marker_tools/marker_popup/convert_to_marker", "marker_tools/marker_popup/duplicate_marker", "marker_tools/marker_popup/average_markers", "marker_tools/marker_popup/---Deform Marker", From f5bb313a0449012d744f9ead4bc4fa36ba28a188 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 29 Dec 2023 22:44:05 +1100 Subject: [PATCH 033/295] Add 'Create Rivet' tool. The Create Rivet tool supports both "mesh two edges" and "point on poly constraint" styles of rivets. --- docs/source/mmSolver.tools.createrivet.rst | 13 + docs/source/mmSolver.tools.rst | 1 + docs/source/mmSolver.utils.rst | 25 +- docs/source/tools_generaltools.rst | 48 ++++ python/mmSolver/tools/createrivet/__init__.py | 20 ++ python/mmSolver/tools/createrivet/tool.py | 92 +++++++ python/mmSolver/utils/rivet/__init__.py | 20 ++ python/mmSolver/utils/rivet/meshtwoedge.py | 185 ++++++++++++++ .../utils/rivet/nearestpointonmesh.py | 77 ++++++ python/mmSolver/utils/rivet/pointonpoly.py | 233 ++++++++++++++++++ python/mmSolver/utils/selection.py | 149 +++++++++++ share/config/functions.json | 9 + share/config/menu.json | 2 + share/config/shelf.json | 2 + share/config/shelf_minimal.json | 2 + 15 files changed, 876 insertions(+), 2 deletions(-) create mode 100644 docs/source/mmSolver.tools.createrivet.rst create mode 100644 python/mmSolver/tools/createrivet/__init__.py create mode 100644 python/mmSolver/tools/createrivet/tool.py create mode 100644 python/mmSolver/utils/rivet/__init__.py create mode 100644 python/mmSolver/utils/rivet/meshtwoedge.py create mode 100644 python/mmSolver/utils/rivet/nearestpointonmesh.py create mode 100644 python/mmSolver/utils/rivet/pointonpoly.py create mode 100644 python/mmSolver/utils/selection.py diff --git a/docs/source/mmSolver.tools.createrivet.rst b/docs/source/mmSolver.tools.createrivet.rst new file mode 100644 index 000000000..c109529e1 --- /dev/null +++ b/docs/source/mmSolver.tools.createrivet.rst @@ -0,0 +1,13 @@ +mmSolver.tools.createrivet +========================== + +.. automodule:: mmSolver.tools.createrivet + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.createrivet.tool + :members: + :undoc-members: diff --git a/docs/source/mmSolver.tools.rst b/docs/source/mmSolver.tools.rst index 6654dca74..f24209d11 100644 --- a/docs/source/mmSolver.tools.rst +++ b/docs/source/mmSolver.tools.rst @@ -28,6 +28,7 @@ mmSolver.tools mmSolver.tools.createlens mmSolver.tools.createline mmSolver.tools.createmarker + mmSolver.tools.createrivet mmSolver.tools.createskydome mmSolver.tools.deformmarker mmSolver.tools.duplicatemarker diff --git a/docs/source/mmSolver.utils.rst b/docs/source/mmSolver.utils.rst index 476274458..bc458801c 100644 --- a/docs/source/mmSolver.utils.rst +++ b/docs/source/mmSolver.utils.rst @@ -89,7 +89,6 @@ Load File :members: :undoc-members: - Load Marker +++++++++++ @@ -131,7 +130,7 @@ Formats .. automodule:: mmSolver.utils.loadmarker.formats.uvtrack :members: :undoc-members: - + Node ++++ @@ -167,6 +166,28 @@ Re-Project :members: :undoc-members: +Rivet ++++++ + +.. automodule:: mmSolver.utils.rivet.meshtwoedge + :members: + :undoc-members: + +.. automodule:: mmSolver.utils.rivet.nearestpointonmesh + :members: + :undoc-members: + +.. automodule:: mmSolver.utils.rivet.pointonpoly + :members: + :undoc-members: + +Selection ++++++++++ + +.. automodule:: mmSolver.utils.selection + :members: + :undoc-members: + Smooth ++++++ diff --git a/docs/source/tools_generaltools.rst b/docs/source/tools_generaltools.rst index 76070b5f4..a342e2245 100644 --- a/docs/source/tools_generaltools.rst +++ b/docs/source/tools_generaltools.rst @@ -228,6 +228,54 @@ version 1, use this python code to run it. # Remove selected Controller tool.remove() +.. _create-rivet-tool-ref: + +Create Rivet +------------ + +Create a transform locator node that follows the surface of a Mesh, i.e. the +transform is 'riveted' to the mesh. + +There are two types of rivet types currently supported: + + - **Mesh Two Edges** rivets are created from 2 Mesh shape edge + components, the same as using the classic `rivet.mel`_ script. If + the topology of the underlying mesh changes, the rivet will move + across the surface. + + - **Point On Poly Constraint** rivets are created at selected + vertices and can be moved along the surface with the UV + coordinates. If the UV coordinates of the underlying mesh changes, + the rivet may move. This rivet-style will not work with UV + coordinates outside the regular 0.0 to 1.0 UV space as is commonly + used with texture UDIMs. + +Usage: + +1) Select a Maya Mesh components. + + - Select Mesh Vertices to create **Point On Poly Constraint** + rivets. + + - Select 2 Mesh Edges to create a single **Mesh Two Edges** rivet. + +2) Run tool. + + - A rivet locator will be created. + + - For a **Point On Poly Constraint** rivet, you can adjust the `U` + and `V` coordinates from the rivet locator, if needed. + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.createrivet.tool as tool + tool.main() + +.. _rivet.mel: + https://www.highend3d.com/maya/script/rivet-button-for-maya + .. _marker-bundle-rename-tool-ref: Marker Bundle Rename diff --git a/python/mmSolver/tools/createrivet/__init__.py b/python/mmSolver/tools/createrivet/__init__.py new file mode 100644 index 000000000..63c3ea430 --- /dev/null +++ b/python/mmSolver/tools/createrivet/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Create a Rivet on a surface. +""" diff --git a/python/mmSolver/tools/createrivet/tool.py b/python/mmSolver/tools/createrivet/tool.py new file mode 100644 index 000000000..80b0d1449 --- /dev/null +++ b/python/mmSolver/tools/createrivet/tool.py @@ -0,0 +1,92 @@ +# Copyright (C) 2014, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The Create Rivet tool. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.utils.rivet.meshtwoedge as utils_rivet_meshtwoedge +import mmSolver.utils.rivet.pointonpoly as utils_rivet_pointonpoly +import mmSolver.utils.selection as utils_selection + + +LOG = mmSolver.logger.get_logger() + + +def _create_mesh_two_edge_rivet(edges): + assert len(edges) == 2 + edge_a = edges[0] + edge_b = edges[1] + mesh_shape = edges[0].partition('.')[0] + assert maya.cmds.objExists(mesh_shape) + rivet = utils_rivet_meshtwoedge.create(mesh_shape, edge_a, edge_b) + return [rivet.rivet_transform] + + +def _create_point_on_poly_rivets(vertices): + assert len(vertices) > 0 + select_nodes = [] + for vertex in vertices: + mesh_shape = vertex.partition('.')[0] + mesh_transform = maya.cmds.listRelatives( + mesh_shape, parent=True, fullPath=True, type='transform' + )[0] + vertex_world_position = maya.cmds.xform( + vertex, query=True, worldSpace=True, translation=True + ) + rivet = utils_rivet_pointonpoly.create( + mesh_transform, mesh_shape, in_position=vertex_world_position + ) + select_nodes.append(rivet.rivet_transform) + return select_nodes + + +def main(): + """ + Create Rivet(s) from the selected mesh components. + + If 2 edges are selected, this tool will create a two-edge rivet, + like the 'rivet.mel'. + """ + error_msg = 'Please select Mesh Vertices or 2 Mesh Edges.' + selection = maya.cmds.ls(selection=True, long=True) or [] + if len(selection) == 0: + LOG.error(error_msg) + return + + vertices = utils_selection.filter_mesh_vertex_selection(selection) + edges = utils_selection.filter_mesh_edge_selection(selection) + if len(vertices + edges) == 0: + LOG.error(error_msg) + return + + if len(vertices) > 0: + select_nodes = _create_point_on_poly_rivets(vertices) + maya.cmds.select(select_nodes, replace=True) + elif len(edges) == 2: + select_nodes = _create_mesh_two_edge_rivet(edges) + maya.cmds.select(select_nodes, replace=True) + else: + LOG.error(error_msg) + return diff --git a/python/mmSolver/utils/rivet/__init__.py b/python/mmSolver/utils/rivet/__init__.py new file mode 100644 index 000000000..2b9c87281 --- /dev/null +++ b/python/mmSolver/utils/rivet/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Rivet utilities. +""" diff --git a/python/mmSolver/utils/rivet/meshtwoedge.py b/python/mmSolver/utils/rivet/meshtwoedge.py new file mode 100644 index 000000000..547d54023 --- /dev/null +++ b/python/mmSolver/utils/rivet/meshtwoedge.py @@ -0,0 +1,185 @@ +# Copyright (C) 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The 'rivet.mel technique' for creating rivets. + +'rivet.mel' was written by Michael Bazhutkin in 2001. +https://www.highend3d.com/maya/script/rivet-button-for-maya + +This method uses polygon edges converted into NURBS curves, then +placing a point at the average of two different NURBS curves. + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import maya.cmds + +import mmSolver.logger + + +LOG = mmSolver.logger.get_logger() + + +RivetMeshTwoEdge = collections.namedtuple( + 'RivetMeshTwoEdge', + [ + 'rivet_transform', + 'rivet_shape', + 'constraint_node', + 'loft_node', + 'point_on_surface_info_node', + 'curve_from_mesh_edge_a_node', + 'curve_from_mesh_edge_b_node', + ], +) + + +def create(mesh_shape, edge_a, edge_b, name=None): + """ + Create a Rivet position between two edges. + + :param mesh_shape: Mesh shape node. + :type mesh_shape: str + + :param edge_a: First edge component path. + :type edge_a: str + + :param edge_b: Second edge component path. + :type edge_b: str + + :returns: A rivet object containing names to all the nodes. + :rtype: RivetMeshTwoEdge + """ + assert maya.cmds.objExists(mesh_shape) + assert maya.cmds.objExists(edge_a) + assert maya.cmds.objExists(edge_b) + if name is None: + name = 'mmRivetMeshTwoEdge1' + assert isinstance(name, str) + + edge_index_a = edge_a.split('[')[-1] + edge_index_b = edge_b.split('[')[-1] + edge_index_a = int(edge_index_a.split(']')[0]) + edge_index_b = int(edge_index_b.split(']')[0]) + + # Create Curve From Edge A. + curve_from_mesh_edge_a = maya.cmds.createNode( + 'curveFromMeshEdge', name='mmRivetCurveFromMeshEdge_a1' + ) + maya.cmds.setAttr(curve_from_mesh_edge_a + '.isHistoricallyInteresting', 1) + maya.cmds.setAttr(curve_from_mesh_edge_a + '.edgeIndex[0]', edge_index_a) + + # Create Curve From Edge B. + curve_from_mesh_edge_b = maya.cmds.createNode( + 'curveFromMeshEdge', name='mmRivetCurveFromMeshEdge_b1' + ) + maya.cmds.setAttr(curve_from_mesh_edge_b + '.isHistoricallyInteresting', 1) + maya.cmds.setAttr(curve_from_mesh_edge_b + '.edgeIndex[0]', edge_index_b) + + # Create Loft node. + loft_node = maya.cmds.createNode('loft', name='mmRivetLoft1') + maya.cmds.setAttr(loft_node + '.inputCurve', size=2) + maya.cmds.setAttr(loft_node + '.uniform', True) + maya.cmds.setAttr(loft_node + '.reverseSurfaceNormals', True) + + # Create Point On Surface Info. + surface_info = maya.cmds.createNode( + 'pointOnSurfaceInfo', name='mmRivetPointOnSurfaceInfo1' + ) + maya.cmds.setAttr(surface_info + '.turnOnPercentage', 1) + maya.cmds.setAttr(surface_info + '.parameterU', 0.5) + maya.cmds.setAttr(surface_info + '.parameterV', 0.5) + + maya.cmds.connectAttr( + loft_node + '.outputSurface', + surface_info + '.inputSurface', + force=True, + ) + output_surface_attr_a = curve_from_mesh_edge_a + '.outputCurve' + output_surface_attr_b = curve_from_mesh_edge_b + '.outputCurve' + maya.cmds.connectAttr(output_surface_attr_a, loft_node + '.inputCurve[0]') + maya.cmds.connectAttr(output_surface_attr_b, loft_node + '.inputCurve[1]') + mesh_world_mesh_attr = mesh_shape + '.worldMesh' + maya.cmds.connectAttr(mesh_world_mesh_attr, curve_from_mesh_edge_a + '.inputMesh') + maya.cmds.connectAttr(mesh_world_mesh_attr, curve_from_mesh_edge_b + '.inputMesh') + + # Create Locator. + rivet_tfm = maya.cmds.createNode('transform', name=name) + rivet_shp_name = rivet_tfm.split('|')[-1] + 'Shape' + rivet_shp = maya.cmds.createNode('locator', name=rivet_shp_name, parent=rivet_tfm) + + # Create Aim Constraint. + aim_constraint = maya.cmds.createNode( + 'aimConstraint', parent=rivet_tfm, name=(rivet_tfm + '_mmRivetAimConstraint1') + ) + maya.cmds.setAttr(aim_constraint + '.target[0].targetWeight', 1) + maya.cmds.setAttr(aim_constraint + '.aimVector', 0.0, 1.0, 0.0, typ='double3') + maya.cmds.setAttr(aim_constraint + '.upVector', 0.0, 0.0, 1.0, typ='double3') + # Don't show users the aim constraint internals they should not + # change anyway. + maya.cmds.setAttr(aim_constraint + '.v', keyable=False) + maya.cmds.setAttr(aim_constraint + '.tx', keyable=False) + maya.cmds.setAttr(aim_constraint + '.ty', keyable=False) + maya.cmds.setAttr(aim_constraint + '.tz', keyable=False) + maya.cmds.setAttr(aim_constraint + '.rx', keyable=False) + maya.cmds.setAttr(aim_constraint + '.ry', keyable=False) + maya.cmds.setAttr(aim_constraint + '.rz', keyable=False) + maya.cmds.setAttr(aim_constraint + '.sx', keyable=False) + maya.cmds.setAttr(aim_constraint + '.sy', keyable=False) + maya.cmds.setAttr(aim_constraint + '.sz', keyable=False) + + maya.cmds.connectAttr(surface_info + '.position', rivet_tfm + '.translate') + maya.cmds.connectAttr( + surface_info + '.normal', aim_constraint + '.target[0].targetTranslate' + ) + maya.cmds.connectAttr(surface_info + '.tangentV', aim_constraint + '.worldUpVector') + maya.cmds.connectAttr(aim_constraint + '.constraintRotateX', rivet_tfm + '.rotateX') + maya.cmds.connectAttr(aim_constraint + '.constraintRotateY', rivet_tfm + '.rotateY') + maya.cmds.connectAttr(aim_constraint + '.constraintRotateZ', rivet_tfm + '.rotateZ') + + # Do not allow users to disconnect or scale the rivet transform. + attr_names = [ + 'translateX', + 'translateY', + 'translateZ', + 'rotateX', + 'rotateY', + 'rotateZ', + 'scaleX', + 'scaleY', + 'scaleZ', + ] + for attr_name in attr_names: + attr = '{}.{}'.format(rivet_tfm, attr_name) + maya.cmds.setAttr(attr, lock=True) + + rivet = RivetMeshTwoEdge( + rivet_transform=rivet_tfm, + rivet_shape=rivet_shp, + constraint_node=aim_constraint, + loft_node=loft_node, + point_on_surface_info_node=surface_info, + curve_from_mesh_edge_a_node=curve_from_mesh_edge_a, + curve_from_mesh_edge_b_node=curve_from_mesh_edge_b, + ) + return rivet diff --git a/python/mmSolver/utils/rivet/nearestpointonmesh.py b/python/mmSolver/utils/rivet/nearestpointonmesh.py new file mode 100644 index 000000000..f0e32e4fd --- /dev/null +++ b/python/mmSolver/utils/rivet/nearestpointonmesh.py @@ -0,0 +1,77 @@ +# Copyright (C) 2014, 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Query the Nearest Point On a Mesh surface. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import maya.cmds + +import mmSolver.logger + + +LOG = mmSolver.logger.get_logger() + +NearestPointData = collections.namedtuple( + 'NearestPointData', ['position', 'normal', 'coords', 'face_index'] +) + + +def get_nearest_point_on_mesh(mesh_shape, in_position): + """ + Get data on the mesh surface, for the nearest position to + 'in_position'. + """ + assert maya.cmds.objExists(mesh_shape) + assert isinstance(in_position, (tuple, list)) and len(in_position) == 3 + + maya.cmds.loadPlugin('nearestPointOnMesh', quiet=True) + + tmp_node = maya.cmds.createNode('nearestPointOnMesh') + maya.cmds.connectAttr(mesh_shape + '.worldMesh[0]', tmp_node + '.inMesh') + + maya.cmds.setAttr(tmp_node + '.inPositionX', in_position[0]) + maya.cmds.setAttr(tmp_node + '.inPositionY', in_position[1]) + maya.cmds.setAttr(tmp_node + '.inPositionZ', in_position[2]) + + position = ( + maya.cmds.getAttr(tmp_node + '.positionX'), + maya.cmds.getAttr(tmp_node + '.positionY'), + maya.cmds.getAttr(tmp_node + '.positionZ'), + ) + normal = ( + maya.cmds.getAttr(tmp_node + '.normalX'), + maya.cmds.getAttr(tmp_node + '.normalY'), + maya.cmds.getAttr(tmp_node + '.normalZ'), + ) + coords = ( + maya.cmds.getAttr(tmp_node + '.parameterU'), + maya.cmds.getAttr(tmp_node + '.parameterV'), + ) + face_index = maya.cmds.getAttr(tmp_node + '.nearestFaceIndex') + result = NearestPointData( + position=position, normal=normal, coords=coords, face_index=face_index + ) + + maya.cmds.delete(tmp_node) + return result diff --git a/python/mmSolver/utils/rivet/pointonpoly.py b/python/mmSolver/utils/rivet/pointonpoly.py new file mode 100644 index 000000000..73e4dde7d --- /dev/null +++ b/python/mmSolver/utils/rivet/pointonpoly.py @@ -0,0 +1,233 @@ +# Copyright (C) 2014, 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Create a Rivet using a Point-On-Poly Constraint. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import maya.cmds + +import mmSolver.utils.node as node_utils +import mmSolver.utils.rivet.nearestpointonmesh as nearestpointonmesh_utils +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def _create_locator(name, parent=None): + tfm = maya.cmds.createNode('transform', name=name, parent=parent) + tfm = node_utils.get_long_name(tfm) + + name_shp = tfm.split('|')[-1] + 'Shape' + shp = maya.cmds.createNode('locator', name=name_shp, parent=tfm) + shp = node_utils.get_long_name(shp) + return tfm, shp + + +def _lock_node_attrs(node, attrs, lock=None, keyable=None, channelBox=None): + assert lock is None or isinstance(lock, bool) + assert keyable is None or isinstance(keyable, bool) + assert channelBox is None or isinstance(channelBox, bool) + + kwargs = {} + if lock is not None: + kwargs['lock'] = lock + if keyable is not None: + kwargs['keyable'] = keyable + if channelBox is not None: + kwargs['channelBox'] = channelBox + + for attr in attrs: + if node_utils.attribute_exists(attr, node): + node_attr = node + '.' + attr + maya.cmds.setAttr(node_attr, **kwargs) + return + + +def _lock_constraint_offset_attrs(node, lock=None, keyable=None, channelBox=None): + attrs = ['ox', 'oy', 'oz', 'otx', 'oty', 'otz', 'orx', 'ory', 'orz'] + return _lock_node_attrs( + node, attrs, lock=lock, keyable=keyable, channelBox=channelBox + ) + + +def _lock_transform_attrs(node, lock=None, keyable=None, channelBox=None): + attrs = [ + 'tx', + 'ty', + 'tz', + 'rx', + 'ry', + 'rz', + 'sx', + 'sy', + 'sz', + 'shxy', + 'shxz', + 'shyz', + ] + return _lock_node_attrs( + node, attrs, lock=lock, keyable=keyable, channelBox=channelBox + ) + + +class PointOnPolyConstraintNode(object): + def __init__(self, node): + super(PointOnPolyConstraintNode, self).__init__() + self._node = node + + def get_node(self): + return self._node + + def get_attr_name_target_u(self): + attr_name = maya.cmds.connectionInfo( + self._node + '.target[0].targetU', sourceFromDestination=True + ) + return attr_name + + def get_attr_name_target_v(self): + attr_name = maya.cmds.connectionInfo( + self._node + '.target[0].targetV', sourceFromDestination=True + ) + return attr_name + + def get_attr_name_target_weight(self): + attr_name = maya.cmds.connectionInfo( + self._node + '.target[0].targetWeight', sourceFromDestination=True + ) + return attr_name + + +def _create_point_on_poly_constraint(mesh_tfm, rivet_tfm): + constraint = maya.cmds.pointOnPolyConstraint(mesh_tfm, rivet_tfm, weight=1) + if len(constraint) == 0: + msg = 'Could not create point on poly constraint.' + LOG.error(msg) + return None + + constraint = constraint[0] + point_on_poly = PointOnPolyConstraintNode(constraint) + + _lock_constraint_offset_attrs(constraint, lock=True) + node_attr = constraint + '.target[0].targetUseNormal' + maya.cmds.setAttr(node_attr, 1) + + _lock_transform_attrs(rivet_tfm, lock=True) + + return point_on_poly + + +RivetPointOnPoly = collections.namedtuple( + 'RivetPointOnPoly', ['rivet_transform', 'rivet_shape', 'point_on_poly_constraint'] +) + + +def create( + mesh_transform, + mesh_shape, + name=None, + parent=None, + coordinate_uv=None, + in_position=None, +): + """ + Create a Point-on-Poly Rivet. + + :param name: Name of the Rivet to create. + :type name: None or str + + :param parent: The parent node of the newly created Rivet. + :type parent: None or str + + :param coordinate_uv: The default UV Coordinate of the newly created Rivet. + :type coordinate_uv: None or (float, float) + + :param in_position: The world-space position of the created Rivet, + if None, fall back to the default coordinate_uv. + :type in_position: None or (float, float, float) + + :returns: Rivet data structure containing all nodes for rivet. + :rtype: RivetPointOnPoly + """ + if name is None: + name = 'mmRivetPointOnPoly1' + assert isinstance(name, str) + assert parent is None or maya.cmds.objExists(parent) + assert in_position is None or ( + isinstance(in_position, (tuple, list)) and len(in_position) == 3 + ) + if coordinate_uv is None: + coordinate_uv = (0.5, 0.5) + assert isinstance(in_position, (tuple, list)) and len(coordinate_uv) == 2 + + rivet_tfm, rivet_shp = _create_locator(name, parent=parent) + + point_on_poly = _create_point_on_poly_constraint(mesh_transform, rivet_tfm) + assert point_on_poly is not None + + coordinate_u = coordinate_uv[0] + coordinate_v = coordinate_uv[1] + if in_position is not None: + nearest_point_data = nearestpointonmesh_utils.get_nearest_point_on_mesh( + mesh_shape, in_position + ) + coordinate_u = nearest_point_data.coords[0] + coordinate_v = nearest_point_data.coords[1] + + # Add attributes and create connections for locator control. + coord_u_attr_name = 'coordinateU' + coord_v_attr_name = 'coordinateV' + maya.cmds.addAttr( + rivet_tfm, + longName=coord_u_attr_name, + shortName='crdu', + niceName='U', + at='double', + defaultValue=coordinate_u, + keyable=True, + ) + maya.cmds.addAttr( + rivet_tfm, + longName=coord_v_attr_name, + shortName='crdv', + niceName='V', + at='double', + defaultValue=coordinate_v, + keyable=True, + ) + rivet_attr_name_u = point_on_poly.get_attr_name_target_u() + rivet_attr_name_v = point_on_poly.get_attr_name_target_v() + rivet_tfm_coord_u_attr = '{}.{}'.format(rivet_tfm, coord_u_attr_name) + rivet_tfm_coord_v_attr = '{}.{}'.format(rivet_tfm, coord_v_attr_name) + maya.cmds.connectAttr(rivet_tfm_coord_u_attr, rivet_attr_name_u) + maya.cmds.connectAttr(rivet_tfm_coord_v_attr, rivet_attr_name_v) + _lock_node_attrs( + point_on_poly.get_node(), [rivet_attr_name_u, rivet_attr_name_v], lock=True + ) + + rivet = RivetPointOnPoly( + rivet_transform=rivet_tfm, + rivet_shape=rivet_shp, + point_on_poly_constraint=point_on_poly, + ) + return rivet diff --git a/python/mmSolver/utils/selection.py b/python/mmSolver/utils/selection.py new file mode 100644 index 000000000..1d4508ed1 --- /dev/null +++ b/python/mmSolver/utils/selection.py @@ -0,0 +1,149 @@ +# Copyright (C) 2014, 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Selection utilities. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds +import maya.OpenMaya as OpenMaya + +import mmSolver.logger + + +LOG = mmSolver.logger.get_logger() + +# Magic numbers for the component masks in Maya. +_COMPONENT_MASK_POLYGON_VERTEX = 31 +_COMPONENT_MASK_POLYGON_EDGE = 32 +_COMPONENT_MASK_POLYGON_FACE = 34 + + +def filter_mesh_vertex_selection(selection): + component_selection = ( + maya.cmds.filterExpand( + selection, + expand=True, + fullPath=True, + selectionMask=_COMPONENT_MASK_POLYGON_VERTEX, + ) + or [] + ) + return component_selection + + +def filter_mesh_edge_selection(selection): + component_selection = ( + maya.cmds.filterExpand( + selection, + expand=True, + fullPath=True, + selectionMask=_COMPONENT_MASK_POLYGON_EDGE, + ) + or [] + ) + return component_selection + + +def filter_mesh_face_selection(selection): + component_selection = ( + maya.cmds.filterExpand( + selection, + expand=True, + fullPath=True, + selectionMask=_COMPONENT_MASK_POLYGON_FACE, + ) + or [] + ) + return component_selection + + +def get_mesh_vertex_selection(): + selection = maya.cmds.ls(selection=True, long=True) or [] + return filter_mesh_vertex_selection(selection) + + +def get_mesh_edge_selection(): + selection = maya.cmds.ls(selection=True, long=True) or [] + return filter_mesh_edge_selection(selection) + + +def get_mesh_face_selection(): + selection = maya.cmds.ls(selection=True, long=True) or [] + return filter_mesh_face_selection(selection) + + +def get_soft_selection_weights(only_node=None): + """ + Get the currently 'soft' selected components. + + Soft selection may return a list for multiple different nodes. + + If 'only_node' is given, then soft selections on only that node + will be returned, all else will be ignored. + """ + all_weights = [] + + soft_select_enabled = maya.cmds.softSelect(query=True, softSelectEnabled=True) + if not soft_select_enabled: + return all_weights + + rich_selection = OpenMaya.MRichSelection() + try: + # get currently active soft selection + OpenMaya.MGlobal.getRichSelection(rich_selection) + except RuntimeError as e: + LOG.error(str(e)) + LOG.error('Error getting soft selection.') + return all_weights + + rich_selection_list = OpenMaya.MSelectionList() + rich_selection.getSelection(rich_selection_list) + selection_count = rich_selection_list.length() + + for i in range(selection_count): + shape_dag_path = OpenMaya.MDagPath() + shape_component = OpenMaya.MObject() + try: + rich_selection_list.getDagPath(i, shape_dag_path, shape_component) + except RuntimeError: + continue + + if only_node is not None: + if shape_dag_path.fullPathName() != only_node: + continue + + # Get weight value. + component_weights = {} + component_fn = OpenMaya.MFnSingleIndexedComponent(shape_component) + try: + for i in range(component_fn.elementCount()): + weight = component_fn.weight(i) + component_weights[component_fn.element(i)] = weight.influence() + except RuntimeError as e: + LOG.error(str(e)) + short_name = shape_dag_path.partialPathName() + msg = 'Soft selection appears invalid, skipping for shape "%s".' + LOG.error(msg, short_name) + + all_weights.append(component_weights) + + return all_weights diff --git a/share/config/functions.json b/share/config/functions.json index 52ebd07e1..339f27b61 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -717,6 +717,15 @@ "mmSolver.tools.removecontroller2.tool.main();" ] }, + "create_rivet": { + "name": "Create Rivet", + "name_shelf": "Rivet", + "tooltip": "Create a new Rivet on the selected mesh components.", + "command": [ + "import mmSolver.tools.createrivet.tool;", + "mmSolver.tools.createrivet.tool.main();" + ] + }, "camera_object_scale_adjust": { "name": "Adjust Camera/Object Scale...", "name_shelf": "CrSclRig", diff --git a/share/config/menu.json b/share/config/menu.json index 392a972dc..7995dc3e1 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -102,6 +102,8 @@ "general_tools/create_world_controllers2", "general_tools/create_controller2", "general_tools/remove_controller2", + "general_tools/---Rivets", + "general_tools/create_rivet", "general_tools/---ML Tools", "general_tools/ml_convert_rotation_order", "general_tools/---Naming & Organisation", diff --git a/share/config/shelf.json b/share/config/shelf.json index d4aa995b3..0fba54125 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -99,6 +99,8 @@ "general_tools/gen_tools_popup/create_world_controllers2", "general_tools/gen_tools_popup/create_controller2", "general_tools/gen_tools_popup/remove_controller2", + "general_tools/gen_tools_popup/---Rivets", + "general_tools/gen_tools_popup/create_rivet", "general_tools/gen_tools_popup/---ML Tools", "general_tools/gen_tools_popup/ml_convert_rotation_order", "general_tools/gen_tools_popup/---Naming & Organisation", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 538808ef1..7b0fb7117 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -110,6 +110,8 @@ "general_tools/gen_tools_popup/create_world_controllers2", "general_tools/gen_tools_popup/create_controller2", "general_tools/gen_tools_popup/remove_controller2", + "general_tools/gen_tools_popup/---Rivets", + "general_tools/gen_tools_popup/create_rivet", "general_tools/gen_tools_popup/---ML Tools", "general_tools/gen_tools_popup/ml_convert_rotation_order", "general_tools/gen_tools_popup/---Naming & Organisation", From b4caddd2f7814e96a5e7c3f9aebe517987448e61 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 30 Dec 2023 20:11:16 +1100 Subject: [PATCH 034/295] Add 'Surface Cluster' tool. This is based on my original "surfaceCluster.py" script from 2014, but re-written to be more modular and reusable and easier to understand. This new version has a lot of improvements, such as: - A tool to update the soft-selection weights for a surface cluster. - A UI enable/disable opening the paint-weights tool, after creation. - A tool to open the paint-weights tool for the selected surface cluster control node. - The ability to create multiple surface clusters from multiple selected mesh components. GitHub issue #111. --- docs/source/mmSolver.tools.rst | 1 + docs/source/mmSolver.tools.surfacecluster.rst | 42 ++ docs/source/tools_generaltools.rst | 118 ++++ python/CMakeLists.txt | 5 + .../mmSolver/tools/surfacecluster/constant.py | 42 ++ python/mmSolver/tools/surfacecluster/lib.py | 519 ++++++++++++++++++ python/mmSolver/tools/surfacecluster/tool.py | 282 ++++++++++ .../tools/surfacecluster/ui/__init__.py | 20 + .../ui/surfacecluster_layout.py | 97 ++++ .../ui/surfacecluster_layout.ui | 63 +++ .../ui/surfacecluster_window.py | 118 ++++ python/mmSolver/utils/rivet/meshtwoedge.py | 3 +- python/mmSolver/utils/rivet/pointonpoly.py | 58 +- python/mmSolver/utils/selection.py | 21 +- share/config/functions.json | 32 ++ share/config/menu.json | 4 + share/config/shelf.json | 4 + share/config/shelf_minimal.json | 4 + 18 files changed, 1411 insertions(+), 22 deletions(-) create mode 100644 docs/source/mmSolver.tools.surfacecluster.rst create mode 100644 python/mmSolver/tools/surfacecluster/constant.py create mode 100644 python/mmSolver/tools/surfacecluster/lib.py create mode 100644 python/mmSolver/tools/surfacecluster/tool.py create mode 100644 python/mmSolver/tools/surfacecluster/ui/__init__.py create mode 100644 python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.py create mode 100644 python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.ui create mode 100644 python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py diff --git a/docs/source/mmSolver.tools.rst b/docs/source/mmSolver.tools.rst index f24209d11..9dbd951c6 100644 --- a/docs/source/mmSolver.tools.rst +++ b/docs/source/mmSolver.tools.rst @@ -65,6 +65,7 @@ mmSolver.tools mmSolver.tools.solver mmSolver.tools.sortoutlinernodes mmSolver.tools.subdivideline + mmSolver.tools.surfacecluster mmSolver.tools.sysinfowindow mmSolver.tools.togglebundlelock mmSolver.tools.togglecameradistort diff --git a/docs/source/mmSolver.tools.surfacecluster.rst b/docs/source/mmSolver.tools.surfacecluster.rst new file mode 100644 index 000000000..55f402a8c --- /dev/null +++ b/docs/source/mmSolver.tools.surfacecluster.rst @@ -0,0 +1,42 @@ +============================= +mmSolver.tools.surfacecluster +============================= + +.. automodule:: mmSolver.tools.surfacecluster + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.surfacecluster.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.surfacecluster.lib + :members: + :undoc-members: + +UI - Layout ++++++++++++ + +.. automodule:: mmSolver.tools.surfacecluster.ui.surfacecluster_layout + :members: + :undoc-members: + +UI - Window ++++++++++++ + +.. automodule:: mmSolver.tools.surfacecluster.ui.surfacecluster_window + :members: + :undoc-members: + +Constants ++++++++++ + +.. automodule:: mmSolver.tools.surfacecluster.constant + :members: + :undoc-members: diff --git a/docs/source/tools_generaltools.rst b/docs/source/tools_generaltools.rst index a342e2245..7c13e7052 100644 --- a/docs/source/tools_generaltools.rst +++ b/docs/source/tools_generaltools.rst @@ -276,6 +276,124 @@ To run the tool, use this Python command: .. _rivet.mel: https://www.highend3d.com/maya/script/rivet-button-for-maya +.. _create-rivet-tool-ref: + +Surface Cluster +--------------- + +A Surface Cluster is a "cluster" deformer that will be riveted to the +surface of a mesh object. All movement of the underlying surface is +inherited by the Surface Cluster, so the cluster "sits on" the +surface, even if the underlying surface is animated/deformed. + +Surface Clusters can be very helpful for subtly adjusting the +silhouette of an object, or adding a bulge, especially when the change +needs to be animated. + +.. note:: This old `Surface Cluster YouTube Video`_ shows the general + usage of the tool, however the tool has been re-written and + improved with features for editing the deforming weights. + +.. _Surface Cluster YouTube Video: + https://youtu.be/7SFP4TgVbEI + +Create Single Surface Cluster +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create a Surface Cluster on the selected Mesh component. + +Usage: + +1) Select 1 or more components (vertices, edges, faces, etc). + +2) Run this tool. + + - create a single surface cluster at the average position of all selected + components. + + - (Optionally) Use current Soft Selection as default weighting - the + same as the "update_weights_with_soft_selection" tool. + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.surfacecluster.tool as tool + tool.create_single_surface_cluster() + + # Open the UI window change settings before creation. + tool.open_window() + +Create Multiple Surface Cluster +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Create multiple surface clusters, one for each component selected. + +Usage: + +1) Select 1 or more components (vertices, edges, faces, etc). + +2) Run this tool. + + - For each component, create a surface cluster is created. + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.surfacecluster.tool as tool + tool.create_multiple_surface_clusters() + + # Open the UI window change settings before creation. + tool.open_window() + +Update Weights With Soft-Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Update the cluster deformer weights using the current component +soft-selection. + + +Usage: +1) Enable Soft Selection ('b' hotkey) + +2) Select 1 or more components (vertices, edges, faces, etc). + +3) Select surface cluster control. + +4) Run this tool. + + - The weights of the surface cluster are updated with the soft + selection. + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.surfacecluster.tool as tool + tool.update_weights_with_soft_selection() + +Open Surface Cluster Paint Weights +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Open the paint weights tool for the selected surface cluster Control. + +Usage: +1) Select a surface cluster control. + +2) Run tool. + + - The paint weights tool is opened. + +3) User paints weights. + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.surfacecluster.tool as tool + tool.open_paint_weights_tool() + .. _marker-bundle-rename-tool-ref: Marker Bundle Rename diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index ee6ef25f1..01538bc25 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -189,6 +189,11 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/screenspacerigbake/ui/ui_screenspacerigbake_layout.py ) + compile_qt_ui_to_python_file("surfacecluster" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/surfacecluster/ui/ui_surfacecluster_layout.py + ) + # Install generated Python UI files install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/" DESTINATION "${MODULE_FULL_NAME}/python" diff --git a/python/mmSolver/tools/surfacecluster/constant.py b/python/mmSolver/tools/surfacecluster/constant.py new file mode 100644 index 000000000..6c6b53993 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/constant.py @@ -0,0 +1,42 @@ +# Copyright (C) 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Surface Cluster constants. +""" + +WINDOW_TITLE = 'Create Surface Cluster' + +CONFIG_CREATE_MODE_KEY = 'mmSolver_surfacecluster_createMode' +CONFIG_OPEN_PAINT_WEIGHTS_KEY = 'mmSolver_surfacecluster_openPaintWeights' + +CREATE_MODE_SINGLE_VALUE = 'single_surface_cluster' +CREATE_MODE_MULTIPLE_VALUE = 'multiple_surface_cluster' +CREATE_MODE_VALUES = [ + CREATE_MODE_SINGLE_VALUE, + CREATE_MODE_MULTIPLE_VALUE, +] + +CREATE_MODE_SINGLE_LABEL = 'Create Single Cluster On Components' +CREATE_MODE_MULTIPLE_LABEL = 'Create Multiple Clusters On Each Component' +CREATE_MODE_LABELS = [ + CREATE_MODE_SINGLE_LABEL, + CREATE_MODE_MULTIPLE_LABEL, +] + +DEFAULT_CREATE_MODE = CREATE_MODE_SINGLE_VALUE +DEFAULT_OPEN_PAINT_WEIGHTS = True diff --git a/python/mmSolver/tools/surfacecluster/lib.py b/python/mmSolver/tools/surfacecluster/lib.py new file mode 100644 index 000000000..445fe6767 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/lib.py @@ -0,0 +1,519 @@ +# Copyright (C) 2014, 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to create and edit Surface Clusters. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import maya.cmds +import maya.mel + +import mmSolver.logger +import mmSolver.utils.node as node_utils +import mmSolver.utils.python_compat as pycompat +import mmSolver.utils.rivet.nearestpointonmesh as nearestpointonmesh +import mmSolver.utils.rivet.pointonpoly as rivet_pointonpoly +import mmSolver.utils.selection as selection_utils + + +LOG = mmSolver.logger.get_logger() +OPEN_PAINT_EDITOR_MEL_CMD = ( + 'artSetToolAndSelectAttr("artAttrCtx", "cluster.{node}.weights");' + # Show Widow + 'toolPropertyWindow;' + 'toolPropertyShow;' + # Set weight slider to 1.0. + 'artAttrPaintOperation artAttrCtx Replace;' + 'artAttrCtx -e -value 1.0 `currentCtx`;' + 'artAttrValues artAttrContext;' +) + + +def _lock_node_attr(nodeAttr, lock=True, keyable=False, channelBox=False): + node = nodeAttr.split('.')[0] + attr = nodeAttr[len(node) + 1 :] + if node_utils.attribute_exists(attr, node): + return maya.cmds.setAttr( + nodeAttr, lock=lock, keyable=keyable, channelBox=channelBox + ) + return False + + +def _lock_node_attrs(node, attrs, lock=True, keyable=False, channelBox=False): + for attr in attrs: + if node_utils.attribute_exists(attr, node): + maya.cmds.setAttr( + node + '.' + attr, lock=lock, keyable=keyable, channelBox=channelBox + ) + pass + return True + + +def _lock_transform_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, + ['tx', 'ty', 'tz', 'rx', 'ry', 'rz', 'sx', 'sy', 'sz', 'shxy', 'shxz', 'shyz'], + lock=lock, + keyable=keyable, + channelBox=channelBox, + ) + + +def _lock_translate_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, ['tx', 'ty', 'tz'], lock=lock, keyable=keyable, channelBox=channelBox + ) + + +def _lock_rotate_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, ['rx', 'ry', 'rz'], lock=lock, keyable=keyable, channelBox=channelBox + ) + + +def _lock_scale_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, ['sx', 'sy', 'sz'], lock=lock, keyable=keyable, channelBox=channelBox + ) + + +def _lock_shear_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, + ['shxy', 'shxz', 'shyz'], + lock=lock, + keyable=keyable, + channelBox=channelBox, + ) + + +def _lock_constraint_offset_attrs(node, lock=True, keyable=False, channelBox=False): + return _lock_node_attrs( + node, + ['ox', 'oy', 'oz', 'otx', 'oty', 'otz', 'orx', 'ory', 'orz'], + lock=lock, + keyable=keyable, + channelBox=channelBox, + ) + + +def _reset_transform_values(node): + maya.cmds.xform(node, objectSpace=True, translation=(0.0, 0.0, 0.0)) + maya.cmds.xform(node, objectSpace=True, rotation=(0.0, 0.0, 0.0)) + maya.cmds.xform(node, objectSpace=True, scale=(1.0, 1.0, 1.0)) + maya.cmds.xform(node, objectSpace=True, shear=(0.0, 0.0, 0.0)) + maya.cmds.xform(node, objectSpace=True, shear=(0.0, 0.0, 0.0)) + maya.cmds.xform(node, objectSpace=True, pivots=(0.0, 0.0, 0.0)) + return True + + +def _create_constraint_to( + targetTransform, + objectTransform, + lockAttrs=True, + lock=True, + keyable=False, + channelBox=False, +): + constPoint = maya.cmds.pointConstraint(targetTransform, objectTransform)[0] + constOrient = maya.cmds.orientConstraint(targetTransform, objectTransform)[0] + constScale = maya.cmds.scaleConstraint(targetTransform, objectTransform)[0] + if lockAttrs: + _lock_constraint_offset_attrs( + constPoint, lock=lock, keyable=keyable, channelBox=channelBox + ) + _lock_constraint_offset_attrs( + constOrient, lock=lock, keyable=keyable, channelBox=channelBox + ) + _lock_constraint_offset_attrs( + constScale, lock=lock, keyable=keyable, channelBox=channelBox + ) + return constPoint, constOrient, constScale + + +def _create_transform(name=None, parent=None): + assert isinstance(name, pycompat.TEXT_TYPE) + node = maya.cmds.createNode("transform", name=name, parent=parent) + node = node_utils.get_long_name(node) + return node + + +def _create_locator(name=None, parent=None): + assert isinstance(name, pycompat.TEXT_TYPE) + tfm = maya.cmds.createNode('transform', name=name, parent=parent) + tfm = node_utils.get_long_name(tfm) + + name_shp = tfm.split('|')[-1] + 'Shape' + shp = maya.cmds.createNode('locator', name=name_shp, parent=tfm) + shp = node_utils.get_long_name(shp) + return tfm, shp + + +def _find_existing_rivet_shape(mesh_shape): + """ + Find a mesh shape node with the name 'RivetShape' that has + previously been created and can be reused. + + :returns: Mesh shape node, or None. + :rtype: None or str + """ + rivet_shape = None + nodes = maya.cmds.listHistory(mesh_shape, allConnections=True, future=False) or [] + for node in nodes: + conns = ( + maya.cmds.listConnections( + node, shapes=True, connections=False, destination=True + ) + or [] + ) + for conn in conns: + if conn.find('RivetShape') != -1: + rivet_shape = conn + break + return rivet_shape + + +def _create_rivet_shape(mesh_transform, mesh_shape): + """ + Creates a new 'RivetShape' mesh shape node on the mesh given. + """ + # Find attribute. + original_src_attr = maya.cmds.connectionInfo( + mesh_shape + '.inMesh', sourceFromDestination=True + ) + if len(original_src_attr) == 0: + attr = mesh_shape + '.inMesh' + LOG.error('No incoming connection to mesh "%s".', attr) + return None + + # If we could not find a mesh shape, make one. + duplicate_transform = maya.cmds.duplicate(mesh_shape, returnRootsOnly=True)[0] + duplicate_shapes = maya.cmds.listRelatives( + duplicate_transform, shapes=True, noIntermediate=True, fullPath=True + ) + duplicate_shape = duplicate_shapes[0] + parented_shape = maya.cmds.parent( + duplicate_shape, mesh_transform, addObject=True, shape=True + )[0] + maya.cmds.delete(duplicate_transform) + rivet_shape = maya.cmds.rename(parented_shape, mesh_transform + 'RivetShape') + + # Connect up to previous node. + maya.cmds.connectAttr(original_src_attr, rivet_shape + '.inMesh', force=True) + return rivet_shape + + +def _setup_control_attrs( + control_tfm, cluster_deformer, point_poly_rivet_transform, uv_coordinate +): + """ + Add attributes and create connections for locator control. + """ + assert maya.cmds.objExists(control_tfm) + assert maya.cmds.objExists(cluster_deformer) + assert maya.cmds.objExists(point_poly_rivet_transform) + assert len(uv_coordinate) >= 2 + + maya.cmds.addAttr( + control_tfm, + longName='deformerWeight', + shortName='dfmwgt', + at='double', + defaultValue=1.0, + keyable=True, + ) + maya.cmds.addAttr( + control_tfm, + longName='coordinateU', + shortName='crdu', + niceName='U', + at='double', + defaultValue=uv_coordinate[0], + keyable=True, + ) + maya.cmds.addAttr( + control_tfm, + longName='coordinateV', + shortName='crdv', + niceName='V', + at='double', + defaultValue=uv_coordinate[1], + keyable=True, + ) + + rivet_attr_u = point_poly_rivet_transform + '.coordinateU' + rivet_attr_v = point_poly_rivet_transform + '.coordinateV' + rivet_attr_weight = point_poly_rivet_transform + '.coordinateWeight' + maya.cmds.connectAttr( + control_tfm + '.deformerWeight', cluster_deformer + '.envelope' + ) + maya.cmds.connectAttr(control_tfm + '.coordinateU', rivet_attr_u) + maya.cmds.connectAttr(control_tfm + '.coordinateV', rivet_attr_v) + + _lock_node_attr(rivet_attr_u) + _lock_node_attr(rivet_attr_v) + _lock_node_attr(rivet_attr_weight) + return + + +def paint_cluster_weights_on_mesh(mesh_tfm, cluster_shp): + """ + Opens the Paint Editor on a mesh node to allow user painting + of weights. + """ + maya.cmds.select(mesh_tfm, replace=True) + mel_cmd = OPEN_PAINT_EDITOR_MEL_CMD.format(node=cluster_shp) + maya.mel.eval(mel_cmd) + return + + +SurfaceCluster = collections.namedtuple( + 'SurfaceCluster', + ['control_transform', 'mesh_transform', 'mesh_shape', 'cluster_deformer_node'], +) + + +def get_surface_cluster_from_control_transform(control_tfm): + assert maya.cmds.objExists(control_tfm) + assert maya.cmds.nodeType(control_tfm) == 'transform' + + cluster_deformers = ( + maya.cmds.listConnections( + control_tfm, + destination=True, + source=False, + skipConversionNodes=True, + shapes=True, + type='cluster', + exactType=True, + ) + or [] + ) + if len(cluster_deformers) == 0: + LOG.error( + 'Could not find Cluster Deformer node from Control transform: %r', + control_tfm, + ) + return + + mesh_shps = maya.cmds.listHistory(cluster_deformers[0], future=True) or [] + mesh_shps = [ + node_utils.get_long_name(x) + for x in mesh_shps + if maya.cmds.nodeType(x) == 'mesh' + ] + if len(mesh_shps) == 0: + LOG.error( + 'Could not find Mesh shape node from Control transform: %r', control_tfm + ) + return + + mesh_tfms = ( + maya.cmds.listRelatives( + mesh_shps[0], parent=True, type='transform', fullPath=True + ) + or [] + ) + if len(mesh_tfms) == 0: + LOG.error( + 'Could not find Mesh transform node from Control transform: %r', control_tfm + ) + return + + return SurfaceCluster( + control_transform=control_tfm, + mesh_transform=mesh_tfms[0], + mesh_shape=mesh_shps[0], + cluster_deformer_node=cluster_deformers[0], + ) + + +def create_surface_cluster_on_mesh_and_component( + mesh_tfm, mesh_shp, component, uv_coordinate +): + """ + Create a Surface Cluster on a mesh component (face, edge or + vertex), positioned at a UV coordinate on the mesh_shp. + + :rtype: None or SurfaceCluster + """ + assert len(uv_coordinate) >= 2 + + # Group all nodes under a single group. + main_group = _create_transform(name='mmSurfaceCluster1') + _lock_transform_attrs(main_group) + + # Duplicate mesh shape node, keep all incoming connections. + rivet_shp = _find_existing_rivet_shape(mesh_shp) + if rivet_shp is None: + rivet_shp = _create_rivet_shape(mesh_tfm, mesh_shp) + if rivet_shp is None: + LOG.error('Could not create rivet shape.') + return None + + # Set non-rivet mesh as intermediate, and set rivet mesh as normal. + maya.cmds.setAttr(rivet_shp + '.intermediateObject', 0) + maya.cmds.setAttr(mesh_shp + '.intermediateObject', 1) + + point_on_poly = rivet_pointonpoly.create( + mesh_tfm, + mesh_shp, + parent=main_group, + as_locator=False, + uv_coordinate=uv_coordinate, + ) + if point_on_poly is None: + LOG.error('Could not create point on poly constraint.') + return None + rivet_tfm = point_on_poly.rivet_transform + + # Set rivet mesh as intermediate, and set non-rivet mesh as normal. + maya.cmds.setAttr(rivet_shp + '.intermediateObject', 1) + maya.cmds.setAttr(mesh_shp + '.intermediateObject', 0) + + # Create locator controller. + control_tfm, _control_shp = _create_locator(name='mmControl1', parent=rivet_tfm) + _reset_transform_values(control_tfm) + + # Create cluster on mesh. + cluster_deformer, cluster_handle = maya.cmds.cluster( + mesh_tfm, name='mmSurfaceCluster1' + ) + maya.cmds.setAttr(cluster_deformer + '.relative', 1) + maya.cmds.setAttr(cluster_handle + '.visibility', 0) + cluster_handle = node_utils.get_long_name( + maya.cmds.parent(cluster_handle, rivet_tfm)[0] + ) + maya.cmds.disconnectAttr( + cluster_handle + '.worldMatrix[0]', cluster_deformer + '.matrix' + ) + _create_constraint_to(control_tfm, cluster_handle) + _reset_transform_values(cluster_handle) + _lock_transform_attrs(cluster_handle) + + # Create Object Null, + object_null = _create_transform(name='objectNull1', parent=main_group) + _create_constraint_to(mesh_tfm, object_null) + _lock_transform_attrs(object_null) + + # Create Object Rivet Nulls. + object_rivet_null = _create_transform(name='objectRivet1', parent=object_null) + _create_constraint_to(rivet_tfm, object_rivet_null) + _lock_transform_attrs(object_rivet_null) + + # Connect GeomMatrix Cluster attribute. + maya.cmds.connectAttr( + object_rivet_null + '.inverseMatrix', + cluster_deformer + '.geomMatrix[0]', + force=True, + ) + + _setup_control_attrs( + control_tfm, + cluster_deformer, + point_on_poly.rivet_transform, + uv_coordinate, + ) + + return SurfaceCluster(control_tfm, mesh_tfm, mesh_shp, cluster_deformer) + + +def set_cluster_deformer_weights(cluster_shp, mesh_shp, weights): + assert maya.cmds.objExists(cluster_shp) + assert maya.cmds.objExists(mesh_shp) + assert maya.cmds.nodeType(cluster_shp) == 'cluster' + assert maya.cmds.nodeType(mesh_shp) == 'mesh' + assert isinstance(weights, dict) + vertex_count = maya.cmds.polyEvaluate(mesh_shp, vertex=True) + for i in range(vertex_count): + weights_attr = '{}.weightList[0].weights[{}]'.format(cluster_shp, i) + if i in weights: + maya.cmds.setAttr(weights_attr, weights[i]) + else: + maya.cmds.setAttr(weights_attr, 0.0) + return + + +def create_surface_cluster_on_component(component): + """ + Create a Surface Cluster on a mesh component (face, edge or + vertex). + + :rtype: None or SurfaceCluster + """ + # Get selection for mesh and locators. + meshes = maya.cmds.listRelatives(component, parent=True, fullPath=True) or [] + if len(meshes) == 0: + msg = 'Could not find mesh shape; component=%r meshes=%r.' + LOG.warn(msg, component, meshes) + return + mesh = meshes[0] + + # Get the mesh transform and shape. + mesh_tfm = None + mesh_shp = None + tmp = maya.cmds.listRelatives(mesh, parent=True) + if len(tmp) > 0: + mesh_tfm = tmp[0] + mesh_shp = mesh + else: + LOG.warn( + 'Mesh transform and shape could not be found' + ' for surface cluster; component=%r', + component, + ) + return + + sel = maya.cmds.ls(selection=True, long=True) or [] + try: + # The weights are calculated for the single component only. + maya.cmds.select(component, replace=True) + soft_selection_weights = selection_utils.get_soft_selection_weights( + only_shape_node=mesh_shp + ) + if len(soft_selection_weights) > 0: + soft_selection_weights = soft_selection_weights[0] + + # Get position from the component. + in_position = maya.cmds.xform( + component, query=True, worldSpace=True, translation=True + ) + + point_data = nearestpointonmesh.get_nearest_point_on_mesh(mesh_shp, in_position) + uv_coordinate = point_data.coords + + surface_cluster = create_surface_cluster_on_mesh_and_component( + mesh_tfm, mesh_shp, component, uv_coordinate + ) + if surface_cluster is None: + LOG.error('Could not create surface cluster!') + return + + if len(soft_selection_weights) > 0: + set_cluster_deformer_weights( + surface_cluster.cluster_deformer_node, mesh_shp, soft_selection_weights + ) + finally: + maya.cmds.select(sel, replace=True) + + return surface_cluster diff --git a/python/mmSolver/tools/surfacecluster/tool.py b/python/mmSolver/tools/surfacecluster/tool.py new file mode 100644 index 000000000..9d2553381 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/tool.py @@ -0,0 +1,282 @@ +# Copyright (C) 2014, 2022, 2023 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Create (and Edit) Surface Clusters. + +Surface Clusters can be used to deform a surface, starting from the +surface position and orientation. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds +import maya.mel + +import mmSolver.logger +import mmSolver.tools.surfacecluster.constant as const +import mmSolver.tools.surfacecluster.lib as lib +import mmSolver.utils.configmaya as configmaya +import mmSolver.utils.selection as selection_utils + + +LOG = mmSolver.logger.get_logger() + + +def create_single_surface_cluster(): + """ + Create a Surface Cluster on the selected Mesh component. + + Usage: + 1) Select 1 or more components (vertices, edges, faces, etc). + 2) Run this tool. + - create a single surface cluster at the average position of all selected + components. + - (Optionally) Use current Soft Selection as default weighting - the + same as the "update_weights_with_soft_selection" tool. + + :returns: None or a SurfaceCluster object with the nodes making up + the setup. + :rtype: SurfaceCluster or None + """ + # Find the selected vertex/edge/face components. + verts = selection_utils.get_mesh_vertex_selection() + edges = selection_utils.get_mesh_edge_selection() + faces = selection_utils.get_mesh_face_selection() + component = None + if (verts is not None) and (len(verts) == 1): + component = verts[0] + elif (edges is not None) and (len(edges) == 1): + component = edges[0] + elif (faces is not None) and (len(faces) == 1): + component = faces[0] + else: + LOG.error( + 'Must have only one vertex, edge or face selected ' + '(and optionally a soft selection).' + ) + return + + surface_cluster = lib.create_surface_cluster_on_component(component) + if surface_cluster is None: + LOG.error('Could not create surface cluster!') + return + + maya.cmds.select(surface_cluster.control_transform, replace=True) + return surface_cluster + + +def create_multiple_surface_clusters(): + """ + Create multiple surface clusters, one for each component + selected. + + Usage: + 1) Select 1 or more components (vertices, edges, faces, etc). + 2) Run this tool. + - For each component, create a surface cluster is created. + + :returns: A list of SurfaceCluster objects with the nodes making up + the setup. The list may be empty. + :rtype: [SurfaceCluster, ..] or [] + """ + # Find the selected vertex/edge/face components. + verts = selection_utils.get_mesh_vertex_selection() + edges = selection_utils.get_mesh_edge_selection() + faces = selection_utils.get_mesh_face_selection() + components = [] + if (verts is not None) and (len(verts) > 0): + components += verts + elif (edges is not None) and (len(edges) > 0): + components += edges + elif (faces is not None) and (len(faces) > 0): + components += faces + else: + LOG.error('Please select at least one Mesh vertex, edge or face.') + return + + surface_clusters = [] + for component in components: + surface_cluster = lib.create_surface_cluster_on_component(component) + if isinstance(surface_cluster, lib.SurfaceCluster): + surface_clusters.append(surface_cluster) + + control_tfms = [x.control_transform for x in surface_clusters] + if len(control_tfms) > 0: + maya.cmds.select(control_tfms, replace=True) + return surface_clusters + + +def update_weights_with_soft_selection(): + """ + Update the cluster deformer weights using the current + component soft-selection. + + Usage: + 1) Enable Soft Selection. + 2) Select 1 or more components (vertices, edges, faces, etc). + 3) Select surface cluster control. + 4) Run this tool. + - The weights of the surface cluster are updated with the soft + selection. + + :rtype: None + """ + sel = maya.cmds.ls(selection=True, long=True) or [] + tfms = [x for x in sel if maya.cmds.nodeType(x) == 'transform'] + if len(sel) == 0 or len(tfms) != 1: + LOG.error( + 'Please select 1 Surface Cluster Control ' + 'and some mesh components (with soft-selection enabled).' + ) + return + control_tfm = tfms[0] + + # Ensure soft-selection is enabled, if not error. + soft_select_enabled = maya.cmds.softSelect(query=True, softSelectEnabled=True) + if soft_select_enabled is False: + LOG.warn( + 'Soft-Selection is not enabled, ' + 'please enable soft-selection to update surface cluster weights.' + ) + return + + # Get the selected surface cluster transform. + surface_cluster = lib.get_surface_cluster_from_control_transform(control_tfm) + if surface_cluster is None: + LOG.error( + 'Could not find Surface Cluster from Control transform: %r', control_tfm + ) + return + + # Get the selected vertex/edge/face components. + verts = selection_utils.get_mesh_vertex_selection() + edges = selection_utils.get_mesh_edge_selection() + faces = selection_utils.get_mesh_face_selection() + components = [] + if (verts is not None) and (len(verts) > 0): + components += verts + elif (edges is not None) and (len(edges) > 0): + components += edges + elif (faces is not None) and (len(faces) > 0): + components += faces + else: + LOG.error('Please select at least one Mesh vertex, edge or face.') + return + + try: + # Update the surface cluster weights with the soft-selection + # weights. + maya.cmds.select(components, replace=True) + soft_selection_weights = selection_utils.get_soft_selection_weights( + only_shape_node=surface_cluster.mesh_shape + ) + if len(soft_selection_weights) > 0: + soft_selection_weights = soft_selection_weights[0] + lib.set_cluster_deformer_weights( + surface_cluster.cluster_deformer_node, + surface_cluster.mesh_shape, + soft_selection_weights, + ) + finally: + maya.cmds.select(sel, replace=True) + return + + +def open_paint_weights_tool(): + """ + Open the paint weights tool for the selected surface cluster. + + Usage: + 1) Select a surface cluster control. + 2) Run tool. + - The paint weights tool is opened. + 3) User paints weights. + + :rtype: None + """ + sel = maya.cmds.ls(selection=True, long=True) or [] + tfms = [x for x in sel if maya.cmds.nodeType(x) == 'transform'] + if len(sel) == 0 or len(tfms) != 1: + LOG.error('Please select only 1 Surface Cluster Control.') + return + control_tfm = tfms[0] + + # Get the selected surface cluster transform. + surface_cluster = lib.get_surface_cluster_from_control_transform(control_tfm) + if surface_cluster is None: + LOG.error( + 'Could not find Surface Cluster from Control transform: %r', control_tfm + ) + return + + # Open paint weights tool. + lib.paint_cluster_weights_on_mesh( + surface_cluster.mesh_transform, surface_cluster.cluster_deformer_node + ) + return + + +def main(): + """ + Create surface clusters using the values defined in the UI. + + Use open_window() function to open the UI to edit the values as + needed. + + :rtype: None + """ + name = const.CONFIG_CREATE_MODE_KEY + create_mode = configmaya.get_scene_option(name, default=const.DEFAULT_CREATE_MODE) + LOG.debug('key=%r value=%r', name, create_mode) + + name = const.CONFIG_OPEN_PAINT_WEIGHTS_KEY + open_paint_weights = configmaya.get_scene_option( + name, default=const.DEFAULT_OPEN_PAINT_WEIGHTS + ) + LOG.debug('key=%r value=%r', name, open_paint_weights) + assert isinstance(open_paint_weights, bool) + + if create_mode == const.CREATE_MODE_SINGLE_VALUE: + surface_cluster = create_single_surface_cluster() + if surface_cluster is not None and open_paint_weights is True: + lib.paint_cluster_weights_on_mesh( + surface_cluster.mesh_transform, surface_cluster.cluster_deformer_node + ) + elif create_mode == const.CREATE_MODE_MULTIPLE_VALUE: + surface_clusters = create_multiple_surface_clusters() + if len(surface_clusters) > 0 and open_paint_weights is True: + surface_cluster = surface_cluster[0] + lib.paint_cluster_weights_on_mesh( + surface_cluster.mesh_transform, surface_cluster.cluster_deformer_node + ) + else: + LOG.error("Surface Cluster Create Mode is invalid; %r", create_mode) + return + + +def open_window(): + """ + Open the Surface Cluster UI. + + :rtype: None + """ + import mmSolver.tools.surfacecluster.ui.surfacecluster_window as window + + window.main() diff --git a/python/mmSolver/tools/surfacecluster/ui/__init__.py b/python/mmSolver/tools/surfacecluster/ui/__init__.py new file mode 100644 index 000000000..367d7b681 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/ui/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2023 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Surface Cluster user interface. +""" diff --git a/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.py b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.py new file mode 100644 index 000000000..c0a846180 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.py @@ -0,0 +1,97 @@ +# Copyright (C) 2023 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The main component of the user interface for the surface cluster +window. +""" + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import mmSolver.logger +import mmSolver.tools.surfacecluster.ui.ui_surfacecluster_layout as ui_layout +import mmSolver.tools.surfacecluster.constant as const +import mmSolver.utils.configmaya as configmaya +import mmSolver.utils.constant as utils_const + + +LOG = mmSolver.logger.get_logger() + + +class SurfaceClusterLayout(QtWidgets.QWidget, ui_layout.Ui_Form): + def __init__(self, parent=None, *args, **kwargs): + super(SurfaceClusterLayout, self).__init__(*args, **kwargs) + self.setupUi(self) + + # Create Mode + self.createModeComboBox.addItems(const.CREATE_MODE_LABELS) + self.createModeComboBox.currentIndexChanged.connect(self.createModeIndexChanged) + + # Open_Paint_Weights + self.openPaintWeightsCheckBox.stateChanged.connect( + self.openPaintWeightsCheckedChanged + ) + + # Populate the UI with data + self.populateUi() + + def createModeIndexChanged(self, index): + name = const.CONFIG_CREATE_MODE_KEY + value = const.CREATE_MODE_VALUES[index] + configmaya.set_scene_option(name, value, add_attr=True) + LOG.debug('key=%r value=%r', name, value) + + def openPaintWeightsCheckedChanged(self, value): + name = const.CONFIG_OPEN_PAINT_WEIGHTS_KEY + value = bool(value) + configmaya.set_scene_option(name, value, add_attr=True) + LOG.debug('key=%r value=%r', name, value) + + def reset_options(self): + name = const.CONFIG_CREATE_MODE_KEY + value = const.DEFAULT_CREATE_MODE + configmaya.set_scene_option(name, value) + LOG.debug('key=%r value=%r', name, value) + + name = const.CONFIG_OPEN_PAINT_WEIGHTS_KEY + value = const.DEFAULT_OPEN_PAINT_WEIGHTS + configmaya.set_scene_option(name, value) + LOG.debug('key=%r value=%r', name, value) + self.populateUi() + + def populateUi(self): + """ + Update the UI for the first time the class is created. + """ + name = const.CONFIG_CREATE_MODE_KEY + value = configmaya.get_scene_option(name, default=const.DEFAULT_CREATE_MODE) + value_index = const.CREATE_MODE_VALUES.index(value) + label = const.CREATE_MODE_LABELS[value_index] + LOG.debug('key=%r value=%r', name, value) + self.createModeComboBox.setCurrentText(label) + + name = const.CONFIG_OPEN_PAINT_WEIGHTS_KEY + value = configmaya.get_scene_option( + name, default=const.DEFAULT_OPEN_PAINT_WEIGHTS + ) + LOG.debug('key=%r value=%r', name, value) + self.openPaintWeightsCheckBox.setChecked(value) + return diff --git a/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.ui b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.ui new file mode 100644 index 000000000..1d5f52d00 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_layout.ui @@ -0,0 +1,63 @@ + + + Form + + + + 0 + 0 + 374 + 174 + + + + Form + + + + + + Options + + + + + + Create Mode + + + + + + + + + + Open Paint Weights + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py new file mode 100644 index 000000000..1608bc87c --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py @@ -0,0 +1,118 @@ +# Copyright (C) 2023 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Window for the Surface Cluster tool. + +Usage:: + + import mmSolver.tools.surfacecluster.ui.surfacecluster_window as window + window.main() + +""" + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtCore as QtCore +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import mmSolver.logger +import mmSolver.ui.uiutils as uiutils +import mmSolver.ui.helputils as helputils +import mmSolver.ui.commonmenus as commonmenus +import mmSolver.tools.surfacecluster.constant as const +import mmSolver.tools.surfacecluster.tool as tool +import mmSolver.tools.surfacecluster.ui.surfacecluster_layout as surfacecluster_layout + + +LOG = mmSolver.logger.get_logger() +baseModule, BaseWindow = uiutils.getBaseWindow() + + +def _open_help(): + src = helputils.get_help_source() + page = 'tools_generaltools.html#surface-cluster' + helputils.open_help_in_browser(page=page, help_source=src) + return + + +class SurfaceClusterWindow(BaseWindow): + name = 'SurfaceClusterWindow' + + def __init__(self, parent=None, name=None): + super(SurfaceClusterWindow, self).__init__(parent, name=name) + self.setupUi(self) + self.addSubForm(surfacecluster_layout.SurfaceClusterLayout) + + self.setWindowTitle(const.WINDOW_TITLE) + self.setWindowFlags(QtCore.Qt.Tool) + + # Standard Buttons + self.baseHideStandardButtons() + self.applyBtn.show() + self.closeBtn.show() + self.applyBtn.setText('Create') + + self.applyBtn.clicked.connect(tool.main) + + # Hide irrelevant stuff + self.baseHideProgressBar() + + self.add_menus(self.menubar) + self.menubar.show() + + def add_menus(self, menubar): + edit_menu = QtWidgets.QMenu('Edit', menubar) + commonmenus.create_edit_menu_items( + edit_menu, reset_settings_func=self.reset_options + ) + menubar.addMenu(edit_menu) + + help_menu = QtWidgets.QMenu('Help', menubar) + commonmenus.create_help_menu_items(help_menu, tool_help_func=_open_help) + menubar.addMenu(help_menu) + + def reset_options(self): + form = self.getSubForm() + form.reset_options() + return + + +def main(show=True, auto_raise=True, delete=False): + """ + Open the Surface Cluster UI window. + + :param show: Show the UI. + :type show: bool + + :param auto_raise: If the UI is open, raise it to the front? + :type auto_raise: bool + + :param delete: Delete the existing UI and rebuild it? Helpful when + developing the UI in Maya script editor. + :type delete: bool + + :returns: A new solver window, or None if the window cannot be + opened. + :rtype: SolverWindow or None. + """ + win = SurfaceClusterWindow.open_window( + show=show, auto_raise=auto_raise, delete=delete + ) + return win diff --git a/python/mmSolver/utils/rivet/meshtwoedge.py b/python/mmSolver/utils/rivet/meshtwoedge.py index 547d54023..c6e516a2b 100644 --- a/python/mmSolver/utils/rivet/meshtwoedge.py +++ b/python/mmSolver/utils/rivet/meshtwoedge.py @@ -35,6 +35,7 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.python_compat as pycompat LOG = mmSolver.logger.get_logger() @@ -75,7 +76,7 @@ def create(mesh_shape, edge_a, edge_b, name=None): assert maya.cmds.objExists(edge_b) if name is None: name = 'mmRivetMeshTwoEdge1' - assert isinstance(name, str) + assert isinstance(name, pycompat.TEXT_TYPE) edge_index_a = edge_a.split('[')[-1] edge_index_b = edge_b.split('[')[-1] diff --git a/python/mmSolver/utils/rivet/pointonpoly.py b/python/mmSolver/utils/rivet/pointonpoly.py index 73e4dde7d..b622fa994 100644 --- a/python/mmSolver/utils/rivet/pointonpoly.py +++ b/python/mmSolver/utils/rivet/pointonpoly.py @@ -30,10 +30,18 @@ import mmSolver.utils.node as node_utils import mmSolver.utils.rivet.nearestpointonmesh as nearestpointonmesh_utils import mmSolver.logger +import mmSolver.utils.python_compat as pycompat LOG = mmSolver.logger.get_logger() +def _create_transform(name=None, parent=None): + assert isinstance(name, pycompat.TEXT_TYPE) + node = maya.cmds.createNode("transform", name=name, parent=parent) + node = node_utils.get_long_name(node) + return node + + def _create_locator(name, parent=None): tfm = maya.cmds.createNode('transform', name=name, parent=parent) tfm = node_utils.get_long_name(tfm) @@ -147,7 +155,8 @@ def create( mesh_shape, name=None, parent=None, - coordinate_uv=None, + as_locator=None, + uv_coordinate=None, in_position=None, ): """ @@ -159,11 +168,15 @@ def create( :param parent: The parent node of the newly created Rivet. :type parent: None or str - :param coordinate_uv: The default UV Coordinate of the newly created Rivet. - :type coordinate_uv: None or (float, float) + :param as_locator: Should the Point-on-Poly Rivet be created as a + locator, or just a transform? By default it a locator. + :type as_locator: bool + + :param uv_coordinate: The default UV Coordinate of the newly created Rivet. + :type uv_coordinate: None or (float, float) :param in_position: The world-space position of the created Rivet, - if None, fall back to the default coordinate_uv. + if None, fall back to the default uv_coordinate. :type in_position: None or (float, float, float) :returns: Rivet data structure containing all nodes for rivet. @@ -171,22 +184,29 @@ def create( """ if name is None: name = 'mmRivetPointOnPoly1' - assert isinstance(name, str) + if as_locator is None: + as_locator = True + assert isinstance(name, pycompat.TEXT_TYPE) + assert isinstance(as_locator, bool) assert parent is None or maya.cmds.objExists(parent) assert in_position is None or ( isinstance(in_position, (tuple, list)) and len(in_position) == 3 ) - if coordinate_uv is None: - coordinate_uv = (0.5, 0.5) - assert isinstance(in_position, (tuple, list)) and len(coordinate_uv) == 2 + if uv_coordinate is None: + uv_coordinate = (0.5, 0.5) + assert isinstance(uv_coordinate, (tuple, list)) and len(uv_coordinate) == 2 - rivet_tfm, rivet_shp = _create_locator(name, parent=parent) + if as_locator is True: + rivet_tfm, rivet_shp = _create_locator(name, parent=parent) + else: + rivet_tfm = _create_transform(name, parent=parent) + rivet_shp = None point_on_poly = _create_point_on_poly_constraint(mesh_transform, rivet_tfm) assert point_on_poly is not None - coordinate_u = coordinate_uv[0] - coordinate_v = coordinate_uv[1] + coordinate_u = uv_coordinate[0] + coordinate_v = uv_coordinate[1] if in_position is not None: nearest_point_data = nearestpointonmesh_utils.get_nearest_point_on_mesh( mesh_shape, in_position @@ -197,6 +217,7 @@ def create( # Add attributes and create connections for locator control. coord_u_attr_name = 'coordinateU' coord_v_attr_name = 'coordinateV' + coord_w_attr_name = 'coordinateWeight' maya.cmds.addAttr( rivet_tfm, longName=coord_u_attr_name, @@ -215,14 +236,27 @@ def create( defaultValue=coordinate_v, keyable=True, ) + maya.cmds.addAttr( + rivet_tfm, + longName=coord_w_attr_name, + shortName='crdwgt', + at='double', + defaultValue=1.0, + keyable=True, + ) rivet_attr_name_u = point_on_poly.get_attr_name_target_u() rivet_attr_name_v = point_on_poly.get_attr_name_target_v() + rivet_attr_name_w = point_on_poly.get_attr_name_target_weight() rivet_tfm_coord_u_attr = '{}.{}'.format(rivet_tfm, coord_u_attr_name) rivet_tfm_coord_v_attr = '{}.{}'.format(rivet_tfm, coord_v_attr_name) + rivet_tfm_coord_w_attr = '{}.{}'.format(rivet_tfm, coord_w_attr_name) maya.cmds.connectAttr(rivet_tfm_coord_u_attr, rivet_attr_name_u) maya.cmds.connectAttr(rivet_tfm_coord_v_attr, rivet_attr_name_v) + maya.cmds.connectAttr(rivet_tfm_coord_w_attr, rivet_attr_name_w) _lock_node_attrs( - point_on_poly.get_node(), [rivet_attr_name_u, rivet_attr_name_v], lock=True + point_on_poly.get_node(), + [rivet_attr_name_u, rivet_attr_name_v, rivet_attr_name_w], + lock=True, ) rivet = RivetPointOnPoly( diff --git a/python/mmSolver/utils/selection.py b/python/mmSolver/utils/selection.py index 1d4508ed1..4a05da285 100644 --- a/python/mmSolver/utils/selection.py +++ b/python/mmSolver/utils/selection.py @@ -91,34 +91,37 @@ def get_mesh_face_selection(): return filter_mesh_face_selection(selection) -def get_soft_selection_weights(only_node=None): +def get_soft_selection_weights(only_shape_node=None): """ Get the currently 'soft' selected components. Soft selection may return a list for multiple different nodes. - If 'only_node' is given, then soft selections on only that node + If 'only_shape_node' is given, then soft selections on only that node will be returned, all else will be ignored. - """ - all_weights = [] + :returns: List of object mappings of soft selection vertex index + to weights, or an empty list. + :rtype: [] or [{int: float}, ..] + """ soft_select_enabled = maya.cmds.softSelect(query=True, softSelectEnabled=True) if not soft_select_enabled: - return all_weights + return [] rich_selection = OpenMaya.MRichSelection() try: - # get currently active soft selection + # Get currently active soft selection OpenMaya.MGlobal.getRichSelection(rich_selection) except RuntimeError as e: LOG.error(str(e)) LOG.error('Error getting soft selection.') - return all_weights + return [] rich_selection_list = OpenMaya.MSelectionList() rich_selection.getSelection(rich_selection_list) selection_count = rich_selection_list.length() + all_weights = [] for i in range(selection_count): shape_dag_path = OpenMaya.MDagPath() shape_component = OpenMaya.MObject() @@ -127,8 +130,8 @@ def get_soft_selection_weights(only_node=None): except RuntimeError: continue - if only_node is not None: - if shape_dag_path.fullPathName() != only_node: + if only_shape_node is not None: + if shape_dag_path.fullPathName() != only_shape_node: continue # Get weight value. diff --git a/share/config/functions.json b/share/config/functions.json index 339f27b61..71c42796c 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -726,6 +726,38 @@ "mmSolver.tools.createrivet.tool.main();" ] }, + "create_surface_cluster": { + "name": "Create Surface Cluster", + "name_shelf": "SrfClst", + "tooltip": "Create a new Surface Cluster on the selected mesh components.", + "command": [ + "import mmSolver.tools.surfacecluster.tool;", + "mmSolver.tools.surfacecluster.tool.main();" + ], + "option_box": true, + "command_option_box": [ + "import mmSolver.tools.surfacecluster.tool;", + "mmSolver.tools.surfacecluster.tool.open_window();" + ] + }, + "update_surface_cluster_weights_with_soft_selection": { + "name": "Update Surface Cluster Weights with Soft-Selection", + "name_shelf": "UpdWgts", + "tooltip": "", + "command": [ + "import mmSolver.tools.surfacecluster.tool;", + "mmSolver.tools.surfacecluster.tool.update_weights_with_soft_selection();" + ] + }, + "open_surface_cluster_paint_weights_tool": { + "name": "Open Cluster Paint Weights Tool...", + "name_shelf": "PntWgts", + "tooltip": "", + "command": [ + "import mmSolver.tools.surfacecluster.tool;", + "mmSolver.tools.surfacecluster.tool.open_paint_weights_tool();" + ] + }, "camera_object_scale_adjust": { "name": "Adjust Camera/Object Scale...", "name_shelf": "CrSclRig", diff --git a/share/config/menu.json b/share/config/menu.json index 7995dc3e1..c655ab6c6 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -104,6 +104,10 @@ "general_tools/remove_controller2", "general_tools/---Rivets", "general_tools/create_rivet", + "general_tools/---Surface Cluster", + "general_tools/create_surface_cluster", + "general_tools/update_surface_cluster_weights_with_soft_selection", + "general_tools/open_surface_cluster_paint_weights_tool", "general_tools/---ML Tools", "general_tools/ml_convert_rotation_order", "general_tools/---Naming & Organisation", diff --git a/share/config/shelf.json b/share/config/shelf.json index 0fba54125..aa1b35c5d 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -101,6 +101,10 @@ "general_tools/gen_tools_popup/remove_controller2", "general_tools/gen_tools_popup/---Rivets", "general_tools/gen_tools_popup/create_rivet", + "general_tools/gen_tools_popup/---Surface Cluster", + "general_tools/gen_tools_popup/create_surface_cluster", + "general_tools/gen_tools_popup/update_surface_cluster_weights_with_soft_selection", + "general_tools/gen_tools_popup/open_surface_cluster_paint_weights_tool", "general_tools/gen_tools_popup/---ML Tools", "general_tools/gen_tools_popup/ml_convert_rotation_order", "general_tools/gen_tools_popup/---Naming & Organisation", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 7b0fb7117..c1218d333 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -112,6 +112,10 @@ "general_tools/gen_tools_popup/remove_controller2", "general_tools/gen_tools_popup/---Rivets", "general_tools/gen_tools_popup/create_rivet", + "general_tools/gen_tools_popup/---Surface Cluster", + "general_tools/gen_tools_popup/create_surface_cluster", + "general_tools/gen_tools_popup/update_surface_cluster_weights_with_soft_selection", + "general_tools/gen_tools_popup/open_surface_cluster_paint_weights_tool", "general_tools/gen_tools_popup/---ML Tools", "general_tools/gen_tools_popup/ml_convert_rotation_order", "general_tools/gen_tools_popup/---Naming & Organisation", From 64a729cfa6e0f74055aa9758a623d7be41c21803 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 1 Jan 2024 14:23:56 +1100 Subject: [PATCH 035/295] Add v0.4.7 to the versions list in README.md. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9f035dd7a..1d209a647 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ changes. | Releases | Description | | --------------------------------------------------------------------------------------- | -------------------------------------------------- | +| [v0.4.7](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.7) | Bug fix for "Convert to Marker" tool. | +| [v0.4.6](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.6) | Bug fix for solver and minor features. | | [v0.4.5](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.5) | Introduction of MM Renderer and Maya 2024 support. | | [v0.4.4](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.4) | Solver, 2D Reprojection and Blender-to-3DE fixes. | | [v0.4.3](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.3) | Bug fix for performance. | From 5abb69c2077417b9947a3d8fa69537f16571af88 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 1 Jan 2024 14:50:16 +1100 Subject: [PATCH 036/295] Add v0.4.8 to the README.md releases. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1d209a647..f687e1cb4 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ changes. | Releases | Description | | --------------------------------------------------------------------------------------- | -------------------------------------------------- | +| [v0.4.8](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.8) | Add Create Rivet and Surface Cluster tools. | | [v0.4.7](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.7) | Bug fix for "Convert to Marker" tool. | | [v0.4.6](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.6) | Bug fix for solver and minor features. | | [v0.4.5](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.5) | Introduction of MM Renderer and Maya 2024 support. | From 75160edeb5513cbbd4fdbcc6860e71be2620562e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 10 Feb 2024 13:51:33 +1100 Subject: [PATCH 037/295] Fix Surface Cluster - __init__.py is missing. Fixes GitHub issue #250. --- .../mmSolver/tools/surfacecluster/__init__.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 python/mmSolver/tools/surfacecluster/__init__.py diff --git a/python/mmSolver/tools/surfacecluster/__init__.py b/python/mmSolver/tools/surfacecluster/__init__.py new file mode 100644 index 000000000..5c2f00de7 --- /dev/null +++ b/python/mmSolver/tools/surfacecluster/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Surface Cluster tool. +""" From 1865ee39e44ea87a15f5a1927124e3d6f68ab335 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 10 Feb 2024 14:04:03 +1100 Subject: [PATCH 038/295] Set project version to 0.4.8 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e6df90228..7e53fd8ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() project(mayaMatchMoveSolver) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 4) -set(PROJECT_VERSION_PATCH 7) +set(PROJECT_VERSION_PATCH 8) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "Bundle Adjustment solver for MatchMove tasks in Autodesk Maya.") From 2eb10b1614c564036befbb4143a0c654cfde35e8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 10 Feb 2024 14:04:50 +1100 Subject: [PATCH 039/295] Set project version to v0.4.9 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e53fd8ae..9791969f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ endif() project(mayaMatchMoveSolver) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 4) -set(PROJECT_VERSION_PATCH 8) +set(PROJECT_VERSION_PATCH 9) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "Bundle Adjustment solver for MatchMove tasks in Autodesk Maya.") From 724b7601ba9cc0da0171d70c5ad1d60c0c07a0c0 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 20 Apr 2024 16:57:21 +1000 Subject: [PATCH 040/295] Bump black from 22.3.0 to 24.3.0 --- share/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/requirements-dev.txt b/share/requirements-dev.txt index f7c1a8fb4..86ac84c72 100644 --- a/share/requirements-dev.txt +++ b/share/requirements-dev.txt @@ -1,4 +1,4 @@ -black==22.3.0 +black==24.3.0 pylint==2.13.9 flake8==4.0.1 cpplint==1.6.0 From ae7ddc7359f142d49285acaacdac04a402086a9e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 16 Mar 2024 20:53:59 +1100 Subject: [PATCH 041/295] Add get node attribute plug from MDagPath and MObject attribute. --- src/mmSolver/mayahelper/maya_utils.h | 33 +++++++++++++++++++--------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/mmSolver/mayahelper/maya_utils.h b/src/mmSolver/mayahelper/maya_utils.h index 324fc2f65..e26743ede 100644 --- a/src/mmSolver/mayahelper/maya_utils.h +++ b/src/mmSolver/mayahelper/maya_utils.h @@ -105,9 +105,22 @@ MStatus constructAttrAffectsName(const MString &attrName, namespace mmsolver { +static inline MStatus getNodeAttrPlug(const MDagPath &objPath, + const MObject &attr, MPlug &out_plug) { + MStatus status = MS::kSuccess; + MObject node = objPath.node(&status); + if (status) { + out_plug = MPlug(node, attr); + if (out_plug.isNull()) { + return MS::kFailure; + } + } + return status; +} + static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, MDistance &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -121,7 +134,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, bool &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -135,7 +148,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, int32_t &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -149,7 +162,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, uint32_t &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -163,7 +176,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, short &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -177,7 +190,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, float &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -191,7 +204,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, double &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -205,7 +218,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, MColor &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -224,7 +237,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, MMatrix &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); @@ -240,7 +253,7 @@ static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, static inline MStatus getNodeAttr(const MDagPath &objPath, const MObject &attr, MString &value) { - MStatus status; + MStatus status = MS::kSuccess; MObject node = objPath.node(&status); if (status) { MPlug plug(node, attr); From 48984e0def983759318e55a666512f94ffb3cd69 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 16 Mar 2024 20:55:37 +1100 Subject: [PATCH 042/295] Remove unneeded MFnEnumAttribute from Bundle, Line and Marker shapes. --- src/mmSolver/shape/BundleShapeNode.cpp | 2 -- src/mmSolver/shape/LineShapeNode.cpp | 2 -- src/mmSolver/shape/MarkerShapeNode.cpp | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/mmSolver/shape/BundleShapeNode.cpp b/src/mmSolver/shape/BundleShapeNode.cpp index 590a31b4d..8b209f579 100644 --- a/src/mmSolver/shape/BundleShapeNode.cpp +++ b/src/mmSolver/shape/BundleShapeNode.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -136,7 +135,6 @@ MStatus BundleShapeNode::initialize() { MStatus status; MFnUnitAttribute uAttr; MFnNumericAttribute nAttr; - MFnEnumAttribute eAttr; // Color m_color = nAttr.createColor("color", "clr"); diff --git a/src/mmSolver/shape/LineShapeNode.cpp b/src/mmSolver/shape/LineShapeNode.cpp index 89437b9f1..2b8d490f6 100644 --- a/src/mmSolver/shape/LineShapeNode.cpp +++ b/src/mmSolver/shape/LineShapeNode.cpp @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include @@ -270,7 +269,6 @@ MStatus LineShapeNode::initialize() { MStatus status; MFnUnitAttribute uAttr; MFnNumericAttribute nAttr; - MFnEnumAttribute eAttr; MFnMessageAttribute msgAttr; MFnCompoundAttribute compoundAttr; MFnMatrixAttribute matrixAttr; diff --git a/src/mmSolver/shape/MarkerShapeNode.cpp b/src/mmSolver/shape/MarkerShapeNode.cpp index 725bb8a00..0c7cf333a 100644 --- a/src/mmSolver/shape/MarkerShapeNode.cpp +++ b/src/mmSolver/shape/MarkerShapeNode.cpp @@ -33,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -147,7 +146,6 @@ MStatus MarkerShapeNode::initialize() { MStatus status; MFnUnitAttribute uAttr; MFnNumericAttribute nAttr; - MFnEnumAttribute eAttr; // Color m_color = nAttr.createColor("color", "clr"); From 12c0d624d6c6e9ccbbcf28b62609b232f6ba1dcd Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 16 Mar 2024 21:01:30 +1100 Subject: [PATCH 043/295] Add MM Image Plane shader. This is a WIP commit demonstrating the 'MShaderInstance::setIsTransparent()' call, to control the transparency, as seen in GitHub issue #252. --- share/shader/CMakeLists.txt | 1 + share/shader/mmImagePlane.ogsfx | 140 +++ src/mmSolver/render/shader/shader_utils.cpp | 4 +- .../shape/ImagePlaneGeometryOverride.cpp | 1072 +++++++++++++---- .../shape/ImagePlaneGeometryOverride.h | 66 +- src/mmSolver/shape/ImagePlaneShapeNode.cpp | 129 ++ src/mmSolver/shape/ImagePlaneShapeNode.h | 18 + src/mmSolver/shape/constant_texture_data.h | 122 ++ 8 files changed, 1330 insertions(+), 222 deletions(-) create mode 100644 share/shader/mmImagePlane.ogsfx create mode 100644 src/mmSolver/shape/constant_texture_data.h diff --git a/share/shader/CMakeLists.txt b/share/shader/CMakeLists.txt index ad6be6614..d5559ab3c 100644 --- a/share/shader/CMakeLists.txt +++ b/share/shader/CMakeLists.txt @@ -4,6 +4,7 @@ install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/mmEdge.ogsfx ${CMAKE_CURRENT_SOURCE_DIR}/mmLayerMerge.ogsfx ${CMAKE_CURRENT_SOURCE_DIR}/mmSilhouette.ogsfx + ${CMAKE_CURRENT_SOURCE_DIR}/mmImagePlane.ogsfx ${CMAKE_CURRENT_SOURCE_DIR}/ocgImagePlaneSolid.ogsfx ${CMAKE_CURRENT_SOURCE_DIR}/ocgImagePlaneTextured.ogsfx DESTINATION "${MODULE_FULL_NAME}/shader") diff --git a/share/shader/mmImagePlane.ogsfx b/share/shader/mmImagePlane.ogsfx new file mode 100644 index 000000000..368e05ffb --- /dev/null +++ b/share/shader/mmImagePlane.ogsfx @@ -0,0 +1,140 @@ +// Copyright (C) 2021, 2023, 2024 David Cattermole. +// +// This file is part of mmSolver. +// +// mmSolver is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mmSolver is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with mmSolver. If not, see . +// -------------------------------------------------------------------- +// +// Draw a texture on an image plane. + +// Global variables provided by Maya. +uniform mat4 gWVPXf : WorldViewProjection; + +// The solid color uniform, its default value and several extra +// parameters +uniform vec4 gSolidColor : DIFFUSE = {1, 1, 1, 1}; + +uniform float gColorGain = 1.0f; +uniform float gAlphaGain = 1.0f; +uniform float gColorGamma = 1.0f; +uniform bool gIgnoreAlpha = false; +uniform bool gFlip = false; +uniform bool gFlop = false; +uniform bool gShowChannelRed = false; +uniform bool gShowChannelGreen = false; +uniform bool gShowChannelBlue = false; +uniform bool gShowChannelAlpha = false; + +// The main image texture displaying RGBA values to the user. +uniform texture2D gImageTexture +< + string ResourceName = ""; + string ResourceType = "2D"; +>; + +uniform sampler2D gImageTextureSampler = sampler_state +{ + Texture = ; + TEXTURE_MIN_FILTER = NEAREST; + TEXTURE_MAG_FILTER = NEAREST; + TEXTURE_WRAP_S = CLAMP_TO_EDGE; + TEXTURE_WRAP_T = CLAMP_TO_EDGE; + TEXTURE_WRAP_R = CLAMP_TO_EDGE; +}; + + +// Vertex Shader inputs. +attribute VS_INPUT { + vec3 Pos : POSITION; + vec2 UV : TEXCOORD0; +}; + +// Vertex Shader data outputs, to be used by the Pixel Shader. +attribute SHADER_DATA { + vec2 UV; +}; + +// Vertex Shader +GLSLShader VS_mmImagePlane { + void main() { + gl_Position = gWVPXf * vec4(Pos, 1); + + vec2 uv = UV; + if (gFlop) { + uv.x -= 0.5f; + uv.x *= -1.0f; + uv.x += 0.5f; + } + + if (gFlip) { + uv.y -= 0.5f; + uv.y *= -1.0f; + uv.y += 0.5f; + } + + VS_OUTPUT.UV = uv; + } +} + +// Pixel Shader Outputs +attribute PIXEL_DATA { + vec4 colorOut : COLOR0; +} + +GLSLShader PS_mmImagePlane_Main { + void main() { + vec4 texture_color = texture2D(gImageTextureSampler, PS_INPUT.UV); + if (gIgnoreAlpha) { + texture_color.a = 1.0f; + } + + vec3 gamma = vec3(gColorGamma, gColorGamma, gColorGamma); + texture_color.rgb = max(vec3(0.0f, 0.0f, 0.0f), texture_color.rgb); + texture_color.rgb = pow(texture_color.rgb, (1.0f / gamma)); + + texture_color.r *= gColorGain; + texture_color.g *= gColorGain; + texture_color.b *= gColorGain; + texture_color.a *= gAlphaGain; + + if (texture_color.a > 1.0f) { + texture_color.a = 1.0f; + } else if (texture_color.a < 0.0f) { + texture_color.a = 0.0f; + } + + colorOut = gSolidColor * texture_color; + if (gShowChannelRed) { + colorOut = vec4(colorOut.r, colorOut.r, colorOut.r, 1.0f); + } else if (gShowChannelGreen) { + colorOut = vec4(colorOut.g, colorOut.g, colorOut.g, 1.0f); + } else if (gShowChannelBlue) { + colorOut = vec4(colorOut.b, colorOut.b, colorOut.b, 1.0f); + } else if (gShowChannelAlpha) { + colorOut = vec4(colorOut.a, colorOut.a, colorOut.a, 1.0f); + } + } +} + +technique Main +< +// Tell Maya to support transparency for this technique. +string Transparency = "Transparent"; // or "Opaque". +> +{ + pass p0 { + VertexShader(in VS_INPUT, out SHADER_DATA VS_OUTPUT) = VS_mmImagePlane; + PixelShader(in SHADER_DATA PS_INPUT, out PIXEL_DATA) = PS_mmImagePlane_Main; + } +} diff --git a/src/mmSolver/render/shader/shader_utils.cpp b/src/mmSolver/render/shader/shader_utils.cpp index ddcb9830f..41557a8b3 100644 --- a/src/mmSolver/render/shader/shader_utils.cpp +++ b/src/mmSolver/render/shader/shader_utils.cpp @@ -75,7 +75,9 @@ MHWRender::MShaderInstance *compile_shader_file(const MString &shader_file_name, const bool verbose = false; MStatus status = MS::kSuccess; - MMSOLVER_MAYA_VRB("MM Renderer compiling shader file..."); + MMSOLVER_MAYA_VRB("MM Renderer compiling shader file..." + << " shader_file_name=" << shader_file_name.asChar() + << " technique_name=" << technique_name.asChar()); const MHWRender::MShaderManager *shader_manager = get_shader_manager(); if (!shader_manager) { diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index 082db7474..23d906042 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -41,10 +42,13 @@ #include #include #include +#include #include // MM Solver #include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" #include "mmSolver/utilities/number_utils.h" namespace mmsolver { @@ -60,9 +64,30 @@ ImagePlaneGeometryOverride::ImagePlaneGeometryOverride(const MObject &obj) , m_draw_hud(false) , m_draw_image_size(false) , m_draw_camera_size(false) - , m_geometry_node_type(MFn::kInvalid) { + , m_geometry_node_type(MFn::kInvalid) + , m_use_shader_node(true) + , m_use_image_read(false) + , m_use_color_bars(false) + , m_image_display_channel(ImageDisplayChannel::kAll) + , m_color_gain(1.0f) + , m_alpha_gain(1.0f) + , m_ignore_alpha(false) + , m_flip(false) + , m_flop(false) + , m_is_transparent(false) + , m_file_path() + , m_shader(nullptr) + , m_color_texture(nullptr) + , m_texture_sampler(nullptr) { m_model_editor_changed_callback_id = MEventMessage::addEventCallback( - "modelEditorChanged", on_model_editor_changed_func, this); + "modelEditorChanged", + ImagePlaneGeometryOverride::on_model_editor_changed_func, this); +#if MAYA_API_VERSION >= 20200000 + m_shader_link_lost_user_data_ptr = + ShaderLinkLostUserDataPtr(new ShaderLinkLostUserData()); +#elif + m_shader_link_lost_user_data = ShaderLinkLostUserData(); +#endif } ImagePlaneGeometryOverride::~ImagePlaneGeometryOverride() { @@ -70,6 +95,30 @@ ImagePlaneGeometryOverride::~ImagePlaneGeometryOverride() { MMessage::removeCallback(m_model_editor_changed_callback_id); m_model_editor_changed_callback_id = 0; } + + if (m_color_texture) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + MHWRender::MTextureManager *textureMgr = + renderer ? renderer->getTextureManager() : nullptr; + if (textureMgr) { + textureMgr->releaseTexture(m_color_texture); + m_color_texture = nullptr; + } + } + + if (m_texture_sampler) { + MHWRender::MStateManager::releaseSamplerState(m_texture_sampler); + m_texture_sampler = nullptr; + } + + if (m_shader) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + const MHWRender::MShaderManager *shaderManager = + renderer ? renderer->getShaderManager() : nullptr; + if (shaderManager) { + shaderManager->releaseShader(m_shader); + } + } } void ImagePlaneGeometryOverride::on_model_editor_changed_func( @@ -83,6 +132,16 @@ void ImagePlaneGeometryOverride::on_model_editor_changed_func( } } +void ImagePlaneGeometryOverride::shader_link_lost_func( + ShaderLinkLostUserData *userData) { + // TODO: What should this function do? Does it need to do anything? + MMSOLVER_MAYA_DBG( + "mmImagePlaneShape: shader_link_lost_func: link_lost_count=" + << (*userData).link_lost_count + << " set_shader_count=" << (*userData).set_shader_count); + (*userData).link_lost_count += 1; +} + MHWRender::DrawAPI ImagePlaneGeometryOverride::supportedDrawAPIs() const { return (MHWRender::kOpenGL | MHWRender::kDirectX11 | MHWRender::kOpenGLCoreProfile); @@ -126,226 +185,717 @@ bool getUpstreamNodeFromConnection(const MObject &this_node, return true; } -void ImagePlaneGeometryOverride::updateDG() { +void calculate_node_image_size_string(MDagPath &objPath, + const uint32_t int_precision, + const uint32_t double_precision, + bool &out_draw_image_size, + MString &out_image_size) { + MStatus status = MS::kSuccess; + + double width = 1.0; + double height = 1.0; + double pixel_aspect = 1.0; + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_image_size, + out_draw_image_size); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_width, width); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_height, height); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_pixel_aspect, + pixel_aspect); + CHECK_MSTATUS(status); + + double aspect = (width * pixel_aspect) / height; + + MString width_string; + MString height_string; + MString pixel_aspect_string; + MString aspect_string; + + width_string.set(width, int_precision); + height_string.set(height, int_precision); + pixel_aspect_string.set(pixel_aspect, double_precision); + aspect_string.set(aspect, double_precision); + + out_image_size = MString("Image: ") + width_string + MString(" x ") + + height_string + MString(" | PAR ") + pixel_aspect_string + + MString(" | ") + aspect_string; + return; +} + +void calculate_node_camera_size_string(MDagPath &objPath, + const uint32_t double_precision, + bool &out_draw_camera_size, + MString &out_camera_size) { + MStatus status = MS::kSuccess; + + double width = 0.0; + double height = 0.0; + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_camera_size, + out_draw_camera_size); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_camera_width_inch, width); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_camera_height_inch, height); + CHECK_MSTATUS(status); + + double aspect = width / height; + + MString width_string; + MString height_string; + MString aspect_string; + + width_string.set(width * INCH_TO_MM, double_precision); + height_string.set(height * INCH_TO_MM, double_precision); + aspect_string.set(aspect, double_precision); + + out_camera_size = MString("Camera: ") + width_string + MString("mm x ") + + height_string + MString("mm | ") + aspect_string; +} + +void ImagePlaneGeometryOverride::query_node_attributes( + MObject &node, MDagPath &out_camera_node_path, bool &out_visible, + bool &out_visible_to_camera_only, bool &out_is_under_camera, + bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, + bool &out_draw_camera_size, MString &out_camera_size, + bool &out_use_shader_node, bool &out_use_image_read, + bool &out_use_color_bars, ImageDisplayChannel &out_image_display_channel, + float &out_color_gain, float &out_alpha_gain, bool &out_ignore_alpha, + bool &out_flip, bool &out_flop, bool &out_is_transparent, + MString &out_file_path, MPlug &out_color_plug) { + MDagPath objPath; + MDagPath::getAPathTo(node, objPath); + + if (!objPath.isValid()) { + return; + } + MStatus status; + + auto frame_context = MPxGeometryOverride::getFrameContext(); + MDagPath camera_node_path = frame_context->getCurrentCameraPath(&status); + CHECK_MSTATUS(status); + + // By default the draw is visible, unless overridden by + // out_visible_to_camera_only or out_is_under_camera. + out_visible = true; + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_visible_to_camera_only, + out_visible_to_camera_only); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_hud, out_draw_hud); + CHECK_MSTATUS(status); + + out_is_under_camera = true; + if (camera_node_path.isValid() && out_camera_node_path.isValid()) { + // Using an explicit camera node path to compare + // against ensures that if a rouge camera is parented + // under the attached camera, the node will be + // invisible. + out_is_under_camera = out_camera_node_path == camera_node_path; + } + + if (!out_is_under_camera) { + if (out_visible_to_camera_only) { + out_visible = false; + } + // Do not draw the HUD if we are not under the camera, + // the HUD must only be visible from the point of view + // of the intended camera, otherwise it will look + // wrong. + out_draw_hud = false; + } + + const auto int_precision = 0; + const auto double_precision = 3; + calculate_node_image_size_string(objPath, int_precision, double_precision, + out_draw_image_size, out_image_size); + calculate_node_camera_size_string(objPath, double_precision, + out_draw_camera_size, out_camera_size); + + // "use" attributes. + { + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_shader_node, + out_use_shader_node); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_image_read, + out_use_image_read); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_color_bars, + out_use_color_bars); + CHECK_MSTATUS(status); + } + + // Shader attributes. + { + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_color_gain, + out_color_gain); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_alpha_gain, + out_alpha_gain); + CHECK_MSTATUS(status); + + short image_display_channel_value = 0; + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_image_display_channel, + image_display_channel_value); + CHECK_MSTATUS(status); + out_image_display_channel = + static_cast(image_display_channel_value); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_ignore_alpha, + out_ignore_alpha); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_flip, out_flip); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_flop, out_flop); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_is_transparent, + out_is_transparent); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_file_path, + out_file_path); + CHECK_MSTATUS(status); + + status = getNodeAttrPlug(objPath, ImagePlaneShapeNode::m_color, + out_color_plug); + CHECK_MSTATUS(status); + } +} + +void find_geometry_node_path(MObject &node, MString &attr_name, + MDagPath &out_geometry_node_path, + MFn::Type &out_geometry_node_type) { const auto verbose = false; - if (!m_geometry_node_path.isValid()) { - MString attr_name = "geometryNode"; - MPlugArray connections; - bool ok = - getUpstreamNodeFromConnection(m_this_node, attr_name, connections); - - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kMesh)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - m_geometry_node_path = path; - m_geometry_node_type = path.apiType(); - MMSOLVER_MAYA_VRB( - "Validated geometry node: " - << " path=" - << m_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN( - "Geometry node is not correct type:" - << " path=" - << m_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + + if (ok) { + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kMesh)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_geometry_node_path = path; + out_geometry_node_type = path.apiType(); + MMSOLVER_MAYA_VRB( + "Validated geometry node: " + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN( + "Geometry node is not correct type:" + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); } } } +} - if (m_shader_node.isNull()) { - MString attr_name = "shaderNode"; - MPlugArray connections; - bool ok = - getUpstreamNodeFromConnection(m_this_node, attr_name, connections); - - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - MFnDependencyNode mfn_depend_node(node); - if (node.hasFn(MFn::kSurfaceShader) || - node.hasFn(MFn::kHwShaderNode) || - node.hasFn(MFn::kPluginHardwareShader) || - node.hasFn(MFn::kPluginHwShaderNode)) { - m_shader_node = node; - m_shader_node_type = node.apiType(); - MMSOLVER_MAYA_VRB("Validated shader node:" - << " name=" - << mfn_depend_node.name().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN("Shader node is not correct type: " - << " name=" - << mfn_depend_node.name().asChar() - << " type=" << node.apiTypeStr()); - } +void find_shader_node(MObject &node, MString &attr_name, + MObject &out_shader_node, + MFn::Type &out_shader_node_type) { + const auto verbose = false; + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + + if (ok) { + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + MFnDependencyNode mfn_depend_node(node); + if (node.hasFn(MFn::kSurfaceShader) || + node.hasFn(MFn::kHwShaderNode) || + node.hasFn(MFn::kPluginHardwareShader) || + node.hasFn(MFn::kPluginHwShaderNode)) { + out_shader_node = node; + out_shader_node_type = node.apiType(); + MMSOLVER_MAYA_VRB("Validated shader node:" + << " name=" << mfn_depend_node.name().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Shader node is not correct type: " + << " name=" << mfn_depend_node.name().asChar() + << " type=" << node.apiTypeStr()); } } } +} - if (!m_camera_node_path.isValid()) { - MString attr_name = "cameraNode"; - MPlugArray connections; - bool ok = - getUpstreamNodeFromConnection(m_this_node, attr_name, connections); - - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kCamera)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - m_camera_node_path = path; - m_camera_node_type = path.apiType(); - MMSOLVER_MAYA_VRB( - "Validated camera node: " - << " path=" - << m_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN( - "Camera node is not correct type:" - << " path=" - << m_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } +void find_camera_node_path(MObject &node, MString &attr_name, + MDagPath &out_camera_node_path, + MFn::Type &out_camera_node_type) { + const auto verbose = false; + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + + if (ok) { + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kCamera)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_camera_node_path = path; + out_camera_node_type = path.apiType(); + MMSOLVER_MAYA_VRB( + "Validated camera node: " + << " path=" << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN( + "Camera node is not correct type:" + << " path=" << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); } } } +} + +void ImagePlaneGeometryOverride::updateDG() { + if (!m_geometry_node_path.isValid()) { + MString attr_name = "geometryNode"; + find_geometry_node_path(m_this_node, attr_name, m_geometry_node_path, + m_geometry_node_type); + } + + if (m_shader_node.isNull()) { + MString attr_name = "shaderNode"; + find_shader_node(m_this_node, attr_name, m_shader_node, + m_shader_node_type); + } + + if (!m_camera_node_path.isValid()) { + MString attr_name = "cameraNode"; + find_camera_node_path(m_this_node, attr_name, m_camera_node_path, + m_camera_node_type); + } // Query Attributes from the base node. - { - MDagPath objPath; - MDagPath::getAPathTo(m_this_node, objPath); - - if (objPath.isValid()) { - MStatus status; - - auto frame_context = getFrameContext(); - MDagPath camera_node_path = - frame_context->getCurrentCameraPath(&status); - CHECK_MSTATUS(status); - - // By default the draw is visible, unless overridden by - // m_visible_to_camera_only or m_is_under_camera. - m_visible = true; - - status = getNodeAttr(objPath, - ImagePlaneShapeNode::m_visible_to_camera_only, - m_visible_to_camera_only); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_hud, - m_draw_hud); - CHECK_MSTATUS(status); - - m_is_under_camera = true; - if (camera_node_path.isValid() && m_camera_node_path.isValid()) { - // Using an explicit camera node path to compare - // against ensures that if a rouge camera is parented - // under the attached camera, the node will be - // invisible. - m_is_under_camera = m_camera_node_path == camera_node_path; - } + ImagePlaneGeometryOverride::query_node_attributes( + m_this_node, m_camera_node_path, m_visible, m_visible_to_camera_only, + m_is_under_camera, m_draw_hud, m_draw_image_size, m_image_size, + m_draw_camera_size, m_camera_size, m_use_shader_node, m_use_image_read, + m_use_color_bars, m_image_display_channel, m_color_gain, m_alpha_gain, + m_ignore_alpha, m_flip, m_flop, m_is_transparent, m_file_path, + m_color_plug); +} - if (!m_is_under_camera) { - if (m_visible_to_camera_only) { - m_visible = false; - } - // Do not draw the HUD if we are not under the camera, - // the HUD must only be visible from the point of view - // of the intended camera, otherwise it will look - // wrong. - m_draw_hud = false; - } +MTexture *create_color_bars_texture( + MHWRender::MTextureManager *textureManager) { + MHWRender::MTextureDescription texture_desc; + texture_desc.setToDefault2DTexture(); + texture_desc.fWidth = COLOR_BARS_F32_4X4_PIXEL_WIDTH; + texture_desc.fHeight = COLOR_BARS_F32_4X4_PIXEL_HEIGHT; + texture_desc.fDepth = 1; + texture_desc.fBytesPerSlice = + COLOR_BARS_F32_4X4_PIXEL_COUNT * COLOR_BARS_F32_4X4_PIXEL_BYTE_COUNT; + texture_desc.fBytesPerRow = + COLOR_BARS_F32_4X4_PIXEL_WIDTH * COLOR_BARS_F32_4X4_PIXEL_BYTE_COUNT; + texture_desc.fMipmaps = 1; + texture_desc.fArraySlices = 1; + texture_desc.fTextureType = MHWRender::kImage2D; + texture_desc.fFormat = MHWRender::kR32G32B32A32_FLOAT; + + const bool generate_mip_maps = false; + return textureManager->acquireTexture( + "", texture_desc, &(COLOR_BARS_F32_4X4[0]), generate_mip_maps); +} - const auto int_precision = 0; - const auto double_precision = 3; - { - double width = 1.0; - double height = 1.0; - double pixel_aspect = 1.0; - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_image_size, - m_draw_image_size); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, - ImagePlaneShapeNode::m_image_width, width); - CHECK_MSTATUS(status); - - status = getNodeAttr( - objPath, ImagePlaneShapeNode::m_image_height, height); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, - ImagePlaneShapeNode::m_image_pixel_aspect, - pixel_aspect); - CHECK_MSTATUS(status); - - double aspect = (width * pixel_aspect) / height; - - MString width_string; - MString height_string; - MString pixel_aspect_string; - MString aspect_string; - - width_string.set(width, int_precision); - height_string.set(height, int_precision); - pixel_aspect_string.set(pixel_aspect, double_precision); - aspect_string.set(aspect, double_precision); - - m_image_size = MString("Image: ") + width_string + - MString(" x ") + height_string + - MString(" | PAR ") + pixel_aspect_string + - MString(" | ") + aspect_string; - } +MTexture *create_plug_texture(MHWRender::MTextureManager *textureManager, + MPlug &texture_plug) { + MObject texture_node; + MStatus status = get_connected_node(texture_plug, texture_node); + CHECK_MSTATUS(status); + if (status != MS::kSuccess) { + return nullptr; + } + + if (texture_node.isNull()) { + // For when the plug is just a color value, but doesn't have + // any input texture. + const bool generate_mip_maps = false; + const int width = 2; + const int height = 2; + return textureManager->acquireTexture("", texture_plug, width, height, + generate_mip_maps); + } - { - double width = 0.0; - double height = 0.0; + const bool allowBackgroundLoad = false; + return textureManager->acquireTexture(texture_node, allowBackgroundLoad); +} + +void *get_image_pixel_data(const MImage &image, + const MImage::MPixelType pixel_type, + const uint8_t number_of_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format) { + const bool verbose = false; - status = getNodeAttr(objPath, - ImagePlaneShapeNode::m_draw_camera_size, - m_draw_camera_size); - CHECK_MSTATUS(status); + const uint32_t print_num_pixels = 8; + void *pixel_data = nullptr; + if (pixel_type == MImage::MPixelType::kByte) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" + << " pixel_type=MImage::MPixelType::kByte"); - status = getNodeAttr( - objPath, ImagePlaneShapeNode::m_camera_width_inch, width); - CHECK_MSTATUS(status); + // 8-bit unsigned integers use 1 byte. + out_bytes_per_channel = 1; - status = getNodeAttr( - objPath, ImagePlaneShapeNode::m_camera_height_inch, height); - CHECK_MSTATUS(status); + const bool is_rgba = image.isRGBA(); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" + << " is_rgba=" << is_rgba); + if (is_rgba) { + out_texture_format = MHWRender::kR8G8B8A8_UNORM; + } else { + out_texture_format = MHWRender::kB8G8R8A8; + } + + unsigned char *pixels = image.pixels(); + + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const uint32_t r = static_cast(pixels[index + 0]); + const uint32_t g = static_cast(pixels[index + 1]); + const uint32_t b = static_cast(pixels[index + 2]); + const uint32_t a = static_cast(pixels[index + 3]); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" + << " row=" << row << " pixel=" << r << ", " << g + << ", " << b << ", " << a); + } - double aspect = width / height; + pixel_data = static_cast(pixels); + } else if (pixel_type == MImage::MPixelType::kFloat) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" + << " pixel_type=MImage::MPixelType::kFloat"); - MString width_string; - MString height_string; - MString aspect_string; + // 32-bit floats use 4 bytes. + out_bytes_per_channel = 4; - width_string.set(width * INCH_TO_MM, double_precision); - height_string.set(height * INCH_TO_MM, double_precision); - aspect_string.set(aspect, double_precision); + out_texture_format = MHWRender::kR32G32B32A32_FLOAT; + + float *floatPixels = image.floatPixels(); + + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const float r = floatPixels[index + 0]; + const float g = floatPixels[index + 1]; + const float b = floatPixels[index + 2]; + const float a = floatPixels[index + 3]; + MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" + << " row=" << row << " pixel=" << r << ", " << g + << ", " << b << ", " << a); + } + + pixel_data = static_cast(floatPixels); + } else { + MMSOLVER_MAYA_ERR("mmImagePlaneShape: get_image_pixel_data: " + << "Invalid pixel type is " + << static_cast(pixel_type)); + return nullptr; + } + + return pixel_data; +} + +MStatus update_texture(MHWRender::MTextureManager *textureManager, + MImage &image, const MImage::MPixelType pixel_type, + MTexture *texture, const bool generate_mip_maps, + const uint8_t number_of_channels) { + const bool verbose = false; + + MHWRender::MRasterFormat texture_format; + uint8_t bytes_per_channel = 0; + const void *pixel_data = + get_image_pixel_data(image, pixel_type, number_of_channels, + bytes_per_channel, texture_format); + + // The default value of this argument is 0. This means to use + // the texture's width * the number of bytes per pixel. + unsigned int rowPitch = 0; + + MHWRender::MTextureUpdateRegion *region = nullptr; + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: update_texture: update"); + MStatus status = + texture->update(pixel_data, generate_mip_maps, rowPitch, region); + + return status; +} + +MTexture *acquire_texture(MHWRender::MTextureManager *textureManager, + MImage &image, const MString &file_path, + const MImage::MPixelType pixel_type, + const bool generate_mip_maps, + const uint8_t number_of_channels) { + const bool verbose = false; + + unsigned int width = 0; + unsigned int height = 0; + image.getSize(width, height); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture:" + << " width=" << width << " height=" << height); + + MHWRender::MTextureDescription texture_desc; + texture_desc.setToDefault2DTexture(); + texture_desc.fWidth = width; + texture_desc.fHeight = height; + texture_desc.fDepth = 1; + + texture_desc.fMipmaps = 1; + texture_desc.fArraySlices = 1; + texture_desc.fTextureType = MHWRender::kImage2D; + + uint8_t bytes_per_channel = 0; + const void *pixel_data = + get_image_pixel_data(image, pixel_type, number_of_channels, + bytes_per_channel, texture_desc.fFormat); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture: " + << "pixel_data=" << pixel_data); + if (!pixel_data) { + MMSOLVER_MAYA_ERR("mmImagePlaneShape: acquire_texture: " + << "Invalid pixel data! "); + return nullptr; + } + + // MImage seems to always convert images into 4 channels. + texture_desc.fBytesPerRow = number_of_channels * bytes_per_channel * width; + texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * height; + + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: acquire_texture:" + << " number_of_channels=" << static_cast(number_of_channels) + << " bytes_per_channel=" << static_cast(bytes_per_channel)); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture:" + << " fBytesPerRow=" << texture_desc.fBytesPerRow + << " fBytesPerSlice=" << texture_desc.fBytesPerSlice); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture: acquireTexture"); + return textureManager->acquireTexture(file_path, texture_desc, pixel_data, + generate_mip_maps); +} + +MTexture *read_image_file(MHWRender::MTextureManager *textureManager, + MImage &image, const MString &file_path, + const MImage::MPixelType pixel_type, + const bool do_texture_update) { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" + << " file_path=" << file_path.asChar()); + + MTexture *texture = textureManager->findTexture(file_path); + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: read_image_file: findTexture: " << texture); + if (texture && !do_texture_update) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" + << " texture=" << texture); + return texture; + } - m_camera_size = MString("Camera: ") + width_string + - MString("mm x ") + height_string + - MString("mm | ") + aspect_string; + // TODO: Should we test to see if the file exists first, before + // attempting to read, or just catch the failure? + MStatus status = image.readFromFile(file_path, pixel_type); + CHECK_MSTATUS(status); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: read_image_file:" + << " failed to read image \"" << file_path.asChar() + << "\"."); + return nullptr; + } + + image.verticalFlip(); + + // TODO: Apply colour correction via OCIO. + + const bool generate_mip_maps = false; + const uint8_t number_of_channels = 4; + if (texture) { + status = update_texture(textureManager, image, pixel_type, texture, + generate_mip_maps, number_of_channels); + CHECK_MSTATUS(status); + if (status == MS::kSuccess) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" + << " texture updated!"); + } + } else { + texture = acquire_texture(textureManager, image, file_path, pixel_type, + generate_mip_maps, number_of_channels); + } + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" + << " texture=" << texture); + + return texture; +} + +void ImagePlaneGeometryOverride::set_shader_instance_parameters( + MShaderInstance *shader, MHWRender::MTextureManager *textureManager, + const float color_gain, const float alpha_gain, const bool ignore_alpha, + const bool flip, const bool flop, const bool is_transparent, + const ImageDisplayChannel image_display_channel, const MString file_path, + MHWRender::MTexture *out_color_texture, + const MHWRender::MSamplerState *out_texture_sampler, + MPlug &out_color_plug) { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); + + static const float color[] = {1.0f, 1.0f, 1.0f, 1.0f}; + shader->setParameter("gSolidColor", color); + shader->setParameter("gColorGain", color_gain); + shader->setParameter("gAlphaGain", alpha_gain); + shader->setParameter("gFlip", flip); + shader->setParameter("gFlop", flop); + shader->setParameter("gIgnoreAlpha", m_ignore_alpha); + shader->setParameter("gShowChannelRed", + image_display_channel == ImageDisplayChannel::kRed); + shader->setParameter("gShowChannelGreen", + image_display_channel == ImageDisplayChannel::kGreen); + shader->setParameter("gShowChannelBlue", + image_display_channel == ImageDisplayChannel::kBlue); + shader->setParameter("gShowChannelAlpha", + image_display_channel == ImageDisplayChannel::kAlpha); + + shader->setIsTransparent(is_transparent); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: shader->isTransparent()=" + << shader->isTransparent()); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: start out_color_texture=" << out_color_texture); + + if (!out_color_texture) { + if (m_use_color_bars) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); + out_color_texture = create_color_bars_texture(textureManager); + } else if (m_use_image_read) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); + const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; + + // // TODO: using kFloat crashes. + // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; + + const bool do_texture_update = false; + out_color_texture = + read_image_file(textureManager, m_image, file_path, pixel_type, + do_texture_update); + } else if (!out_color_plug.isNull()) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color plug texture"); + out_color_texture = + create_plug_texture(textureManager, out_color_plug); + } else { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); + out_color_texture = create_color_bars_texture(textureManager); + } + + if (out_color_texture) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" + << out_color_texture->name().asChar()); + const void *resource_handle = out_color_texture->resourceHandle(); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->resourceHandle()=" + << resource_handle); + if (resource_handle) { + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: *texture->resourceHandle()=" + << *(uint32_t *)resource_handle); } + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasAlpha()=" + << out_color_texture->hasAlpha()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasZeroAlpha()=" + << out_color_texture->hasZeroAlpha()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: texture->hasTransparentAlpha()=" + << out_color_texture->hasTransparentAlpha()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->bytesPerPixel()=" + << out_color_texture->bytesPerPixel()); + + MTextureDescription texture_desc; + out_color_texture->textureDescription(texture_desc); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fWidth=" + << texture_desc.fWidth); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fHeight=" + << texture_desc.fHeight); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fDepth=" + << texture_desc.fDepth); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerRow=" + << texture_desc.fBytesPerRow); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerSlice=" + << texture_desc.fBytesPerSlice); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fMipmaps=" + << texture_desc.fMipmaps); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fArraySlices=" + << texture_desc.fArraySlices); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fFormat=" + << texture_desc.fFormat); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fTextureType=" + << texture_desc.fTextureType); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fEnvMapType=" + << texture_desc.fEnvMapType); } } + + if (!out_texture_sampler) { + MHWRender::MSamplerStateDesc sampler_desc; + sampler_desc.addressU = MHWRender::MSamplerState::kTexWrap; + sampler_desc.addressV = MHWRender::MSamplerState::kTexWrap; + sampler_desc.addressW = MHWRender::MSamplerState::kTexWrap; + // kMinMagMipPoint is "nearest pixel" filtering. + sampler_desc.filter = MHWRender::MSamplerState::kMinMagMipPoint; + out_texture_sampler = + MHWRender::MStateManager::acquireSamplerState(sampler_desc); + } + + if (out_texture_sampler) { + shader->setParameter("gImageTextureSampler", *out_texture_sampler); + } else { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get texture sampler." + << " out_texture_sampler=" << out_texture_sampler); + } + + if (out_color_texture) { + MHWRender::MTextureAssignment texture_assignment; + texture_assignment.texture = out_color_texture; + shader->setParameter("gImageTexture", texture_assignment); + + textureManager->releaseTexture(out_color_texture); + out_color_texture = nullptr; + } else { + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: Could not get color texture; " + "did not assign texture." + << " out_color_texture=" << out_color_texture); + } + + return; } void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, @@ -357,22 +907,23 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, return; } - MRenderer *renderer = MRenderer::theRenderer(); + MHWRender::MRenderer *renderer = MRenderer::theRenderer(); if (!renderer) { MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MRenderer."); return; } - const MShaderManager *shaderManager = renderer->getShaderManager(); + const MHWRender::MShaderManager *shaderManager = + renderer->getShaderManager(); if (!shaderManager) { MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MShaderManager."); return; } if (m_geometry_node_type != MFn::kMesh) { - MMSOLVER_MAYA_WRN( - "mmImagePlaneShape: " - << "Only Meshes are supported, geometry node given is not a mesh."); + MMSOLVER_MAYA_WRN("mmImagePlaneShape: " + << "Only Meshes are supported, geometry node " + "given is not a mesh."); return; } @@ -438,26 +989,109 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, if (shadedItem) { shadedItem->enable(m_visible); - if (!m_shader_node.isNull()) { - // TODO: Implement callback to detect when the shader - // needs to be re-compiled. - auto linkLostCb = nullptr; - auto linkLostUserData = nullptr; - bool nonTextured = false; - shadedItem->setShaderFromNode(m_shader_node, m_geometry_node_path, - linkLostCb, linkLostUserData, - nonTextured); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isEnabled()=" + << shadedItem->isEnabled()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isShaderFromNode()=" + << shadedItem->isShaderFromNode()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isMultiDraw()=" + << shadedItem->isMultiDraw()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isConsolidated()=" + << shadedItem->isConsolidated()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->wantConsolidation()=" + << shadedItem->wantConsolidation()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->castsShadows()=" + << shadedItem->castsShadows()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->receivesShadows()=" + << shadedItem->receivesShadows()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->excludedFromPostEffects()=" + << shadedItem->excludedFromPostEffects()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->supportsAdvancedTransparency()=" + << shadedItem->supportsAdvancedTransparency()); + + if (m_use_shader_node) { + if (!m_shader_node.isNull()) { + bool nonTextured = false; + auto linkLostCb = (MHWRender::MRenderItem::LinkLostCallback) + ImagePlaneGeometryOverride::shader_link_lost_func; +#if MAYA_API_VERSION >= 20200000 + MMSOLVER_MAYA_DBG( + "mmImagePlaneShape: " + << "shadedItem->setShaderFromNode2: " + << "link_lost_count=" + << m_shader_link_lost_user_data_ptr->link_lost_count + << " set_shader_count=" + << m_shader_link_lost_user_data_ptr->set_shader_count); + m_shader_link_lost_user_data_ptr->set_shader_count += 1; + + shadedItem->setShaderFromNode2( + m_shader_node, m_geometry_node_path, linkLostCb, + m_shader_link_lost_user_data_ptr, nonTextured); +#elif + MMSOLVER_MAYA_DBG( + "mmImagePlaneShape: " + << "shadedItem->setShaderFromNode: " + << "link_lost_count=" + << m_shader_link_lost_user_data.link_lost_count + << " set_shader_count=" + << m_shader_link_lost_user_data.set_shader_count); + m_shader_link_lost_user_data.set_shader_count += 1; + + // NOTE: 'MRenderItem::setShaderFromNode()' is deprecated + // in Maya 2020 and above. + shadedItem->setShaderFromNode( + m_shader_node, m_geometry_node_path, linkLostCb, + &m_shader_link_lost_user_data, nonTextured); +#endif + + shadedItem->setTreatAsTransparent(m_is_transparent); + + } else { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: " + << "Shader node is not valid, " + << "using fallback blue shader."); + MShaderInstance *shader = shaderManager->getStockShader( + MShaderManager::k3dSolidShader); + if (shader) { + static const float color[] = {0.0f, 0.0f, 1.0f, 1.0f}; + shader->setParameter("solidColor", color); + shadedItem->setShader(shader); + shaderManager->releaseShader(shader); + } + } } else { - MMSOLVER_MAYA_WRN( - "mmImagePlaneShape: " - << "Shader node is not valid, using fallback blue shader."); - MShaderInstance *shader = - shaderManager->getStockShader(MShaderManager::k3dSolidShader); - if (shader) { - static const float color[] = {0.0f, 0.0f, 1.0f, 1.0f}; - shader->setParameter("solidColor", color); - shadedItem->setShader(shader); - shaderManager->releaseShader(shader); + if (!m_shader) { + m_shader = mmsolver::render::compile_shader_file("mmImagePlane", + "Main"); + } + + if (m_shader) { + MHWRender::MTextureManager *textureManager = + renderer->getTextureManager(); + if (!textureManager) { + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: Could not get MTextureManager."); + return; + } + + set_shader_instance_parameters( + m_shader, textureManager, m_color_gain, m_alpha_gain, + m_ignore_alpha, m_flip, m_flop, m_is_transparent, + m_image_display_channel, m_file_path, m_color_texture, + m_texture_sampler, m_color_plug); + + shadedItem->setShader(m_shader); + + // // Once assigned, no need to hold on to shader instance + // shaderManager->releaseShader(m_shader); } } } diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.h b/src/mmSolver/shape/ImagePlaneGeometryOverride.h index 55ea16a5a..b031546ac 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.h +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,20 @@ namespace mmsolver { +class ShaderLinkLostUserData : public MUserData { +public: + ShaderLinkLostUserData() + : MUserData(), link_lost_count(0), set_shader_count(0) {} + + // Keep track of the number of times stuff happens, just for + // interest sake (maybe to help debugging?) - doesn't really mean + // or do anything special. + uint32_t link_lost_count; + uint32_t set_shader_count; +}; + +using ShaderLinkLostUserDataPtr = MSharedPtr; + class ImagePlaneGeometryOverride : public MPxGeometryOverride { public: static MPxGeometryOverride *Creator(const MObject &obj) { @@ -88,6 +103,28 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { ImagePlaneGeometryOverride(const MObject &obj); static void on_model_editor_changed_func(void *clientData); + static void shader_link_lost_func(ShaderLinkLostUserData *userData); + + void query_node_attributes( + MObject &node, MDagPath &out_camera_node_path, bool &out_visible, + bool &out_visible_to_camera_only, bool &out_is_under_camera, + bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, + bool &out_draw_camera_size, MString &out_camera_size, + bool &out_use_shader_node, bool &out_use_image_read, + bool &out_use_color_bars, + ImageDisplayChannel &out_image_display_channel, float &out_color_gain, + float &out_alpha_gain, bool &out_ignore_alpha, bool &out_flip, + bool &out_flop, bool &out_is_transparent, MString &out_file_path, + MPlug &out_color_plug); + + void set_shader_instance_parameters( + MShaderInstance *shader, MHWRender::MTextureManager *textureManager, + const float color_gain, const float alpha_gain, const bool ignore_alpha, + const bool flip, const bool flop, const bool is_transparent, + const ImageDisplayChannel image_display_channel, + const MString file_path, MHWRender::MTexture *out_color_texture, + const MHWRender::MSamplerState *out_texture_sampler, + MPlug &out_color_plug); MObject m_this_node; MDagPath m_geometry_node_path; @@ -95,9 +132,7 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { MFn::Type m_geometry_node_type; MFn::Type m_shader_node_type; MFn::Type m_camera_node_type; - MObject m_geometry_node; MObject m_shader_node; - MObject m_camera_node; bool m_visible; bool m_visible_to_camera_only; @@ -108,6 +143,33 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { MString m_image_size; MString m_camera_size; MCallbackId m_model_editor_changed_callback_id; + + bool m_use_shader_node; + bool m_use_image_read; + bool m_use_color_bars; + + // Shader attributes. + MShaderInstance *m_shader; + ImageDisplayChannel m_image_display_channel; + float m_color_gain; + float m_alpha_gain; + bool m_ignore_alpha; + bool m_flip; + bool m_flop; + bool m_is_transparent; + MString m_file_path; + MPlug m_color_plug; + + // Texture caching + MImage m_image; + MHWRender::MTexture *m_color_texture; + const MHWRender::MSamplerState *m_texture_sampler; + +#if MAYA_API_VERSION >= 20200000 + ShaderLinkLostUserDataPtr m_shader_link_lost_user_data_ptr; +#elif + ShaderLinkLostUserData m_shader_link_lost_user_data; +#endif }; } // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.cpp b/src/mmSolver/shape/ImagePlaneShapeNode.cpp index 5bed68699..7e91100ef 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.cpp +++ b/src/mmSolver/shape/ImagePlaneShapeNode.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include #include @@ -78,6 +80,21 @@ MObject ImagePlaneShapeNode::m_geometry_node; MObject ImagePlaneShapeNode::m_shader_node; MObject ImagePlaneShapeNode::m_camera_node; +MObject ImagePlaneShapeNode::m_use_shader_node; +MObject ImagePlaneShapeNode::m_use_image_read; +MObject ImagePlaneShapeNode::m_use_color_bars; + +// Shader Attributes +MObject ImagePlaneShapeNode::m_image_display_channel; +MObject ImagePlaneShapeNode::m_color_gain; +MObject ImagePlaneShapeNode::m_alpha_gain; +MObject ImagePlaneShapeNode::m_ignore_alpha; +MObject ImagePlaneShapeNode::m_flip; +MObject ImagePlaneShapeNode::m_flop; +MObject ImagePlaneShapeNode::m_is_transparent; +MObject ImagePlaneShapeNode::m_file_path; +MObject ImagePlaneShapeNode::m_color; + ImagePlaneShapeNode::ImagePlaneShapeNode() {} ImagePlaneShapeNode::~ImagePlaneShapeNode() {} @@ -153,6 +170,8 @@ void *ImagePlaneShapeNode::creator() { return new ImagePlaneShapeNode(); } MStatus ImagePlaneShapeNode::initialize() { MStatus status; MFnNumericAttribute nAttr; + MFnTypedAttribute tAttr; + MFnEnumAttribute eAttr; MFnMessageAttribute msgAttr; m_visible_to_camera_only = nAttr.create("visibleToCameraOnly", "viscamony", @@ -255,6 +274,116 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(msgAttr.setKeyable(false)); CHECK_MSTATUS(addAttribute(m_camera_node)); + m_use_shader_node = nAttr.create("useShaderNode", "useshdnd", + MFnNumericData::kBoolean, true); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_use_shader_node)); + + m_use_image_read = nAttr.create("useImageRead", "useimgrd", + MFnNumericData::kBoolean, true); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_use_image_read)); + + m_use_color_bars = nAttr.create("useColorBars", "usecolrbrs", + MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_use_color_bars)); + + // Which channel of the image should be displayed? + short value_all = static_cast(ImageDisplayChannel::kAll); + short value_red = static_cast(ImageDisplayChannel::kRed); + short value_green = static_cast(ImageDisplayChannel::kGreen); + short value_blue = static_cast(ImageDisplayChannel::kBlue); + short value_alpha = static_cast(ImageDisplayChannel::kAlpha); + m_image_display_channel = eAttr.create("shaderImageDisplayChannel", + "shdimgdspchan", value_all, &status); + CHECK_MSTATUS(status); + CHECK_MSTATUS(eAttr.addField("RGBA", value_all)); + CHECK_MSTATUS(eAttr.addField("Red", value_red)); + CHECK_MSTATUS(eAttr.addField("Green", value_green)); + CHECK_MSTATUS(eAttr.addField("Blue", value_blue)); + CHECK_MSTATUS(eAttr.addField("Alpha", value_alpha)); + CHECK_MSTATUS(eAttr.setStorable(true)); + CHECK_MSTATUS(eAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_image_display_channel)); + + m_color_gain = nAttr.create("shaderColorGain", "shdclgn", + MFnNumericData::kDouble, 1.0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setMin(0.0)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Color Gain (Shader)"))); + CHECK_MSTATUS(addAttribute(m_color_gain)); + + m_alpha_gain = nAttr.create("shaderAlphaGain", "shdalpgn", + MFnNumericData::kDouble, 1.0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setMin(0.0)); + CHECK_MSTATUS(nAttr.setMax(1.0)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Alpha Gain (Shader)"))); + CHECK_MSTATUS(addAttribute(m_alpha_gain)); + + m_ignore_alpha = nAttr.create("shaderIgnoreAlpha", "shdignalp", + MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Ignore Alpha (Shader)"))); + CHECK_MSTATUS(addAttribute(m_ignore_alpha)); + + m_flip = + nAttr.create("shaderFlip", "shdflip", MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Flip (Shader)"))); + CHECK_MSTATUS(addAttribute(m_flip)); + + m_flop = + nAttr.create("shaderFlop", "shdflop", MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Flop (Shader)"))); + CHECK_MSTATUS(addAttribute(m_flop)); + + m_is_transparent = nAttr.create("shaderIsTransparent", "shdistrnsp", + MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS( + nAttr.setNiceNameOverride(MString("Is Transparent (Shader)"))); + CHECK_MSTATUS(addAttribute(m_is_transparent)); + + // Create empty string data to be used as attribute default + // (string) value. + MFnStringData empty_string_data; + MObject empty_string_data_obj = empty_string_data.create(""); + + m_file_path = tAttr.create("shaderFilePath", "shdflpth", MFnData::kString, + empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(true)); + CHECK_MSTATUS(addAttribute(m_file_path)); + + m_color = nAttr.createColor("shaderColor", "shdcl"); + CHECK_MSTATUS(nAttr.setDefault(0.0f, 0.58824f, 0.644f)); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Color (Shader)"))); + CHECK_MSTATUS(addAttribute(m_color)); + return MS::kSuccess; } diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.h b/src/mmSolver/shape/ImagePlaneShapeNode.h index ca2dfd6cf..f8d255346 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.h +++ b/src/mmSolver/shape/ImagePlaneShapeNode.h @@ -44,6 +44,8 @@ namespace mmsolver { +enum class ImageDisplayChannel { kAll = 0, kRed, kGreen, kBlue, kAlpha }; + class ImagePlaneShapeNode : public MPxLocatorNode { public: ImagePlaneShapeNode(); @@ -99,6 +101,22 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MObject m_geometry_node; static MObject m_shader_node; static MObject m_camera_node; + + // "Use" Attributes + static MObject m_use_shader_node; + static MObject m_use_image_read; + static MObject m_use_color_bars; + + // Shader Attributes + static MObject m_image_display_channel; + static MObject m_color_gain; + static MObject m_alpha_gain; + static MObject m_ignore_alpha; + static MObject m_flip; + static MObject m_flop; + static MObject m_is_transparent; + static MObject m_file_path; + static MObject m_color; }; } // namespace mmsolver diff --git a/src/mmSolver/shape/constant_texture_data.h b/src/mmSolver/shape/constant_texture_data.h new file mode 100644 index 000000000..e9c982f55 --- /dev/null +++ b/src/mmSolver/shape/constant_texture_data.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Unchanging texture data to be used as hard-coded images that are + * embedded in mmSolver. + */ + +#ifndef MM_SOLVER_CONSTANT_TEXTURE_DATA_H +#define MM_SOLVER_CONSTANT_TEXTURE_DATA_H + +namespace mmsolver { + +/* + * Color Bars Texture, for debug. + * + * https://en.wikipedia.org/wiki/SMPTE_color_bars + * + * ------------------------------ + * R - G - B - COLOR NAME + * ------------------------------ + * 235 - 235 - 235 - 100% White + * 180 - 180 - 180 - 75% White + * 235 - 235 - 16 - Yellow + * 16 - 235 - 235 - Cyan + * 16 - 235 - 16 - Green + * 235 - 16 - 235 - Magenta + * 235 - 16 - 16 - Red + * 16 - 16 - 235 - Blue + * 16 - 16 - 16 - Black + * ------------------------------ + * + * The texture block (below) starts at the lower-left (zeroth index) + * and continues the upper-right (last index). + * + * Note: To make things even (only 8 entries), we skip the "75% white" + * value. + */ +static const float COLOR_BARS_F32_4X4[] = { + // Row 0 + // + // 235, 16, 235 - Magenta + 0.9215f, 0.0627f, 0.9215f, 1.0f, + + // 235, 16, 16 - Red + 0.9215f, 0.0627f, 0.0627f, 1.0f, + + // 16, 16, 235 - Blue + 0.0627f, 0.0627f, 0.9215f, 1.0f, + + // 16, 16, 16 - Black + 0.0627f, 0.0627f, 0.0627f, 1.0f, + + // Row 1 + // + // 235, 16, 235 - Magenta + 0.9215f, 0.0627f, 0.9215f, 0.8f, + + // 235, 16, 16 - Red + 0.9215f, 0.0627f, 0.0627f, 0.6f, + + // 16, 16, 235 - Blue + 0.0627f, 0.0627f, 0.9215f, 0.4f, + + // 16, 16, 16 - Black + 0.0627f, 0.0627f, 0.0627f, 0.2f, + + // Row 2 + // + // 235, 235, 235 - 100% White + 0.9215f, 0.9215f, 0.9215f, 1.0f, + + // 235, 235, 16 - Yellow + 0.9215f, 0.9215f, 0.0627f, 1.0f, + + // 16, 235, 235 - Cyan + 0.0627f, 0.9215f, 0.9215f, 1.0f, + + // 16, 235, 16 - Green + 0.0627f, 0.9215f, 0.0627f, 1.0f, + + // Row 3 + // + // 235, 235, 235 - 100% White + 0.9215f, 0.9215f, 0.9215f, 0.2f, + + // 235, 235, 16 - Yellow + 0.9215f, 0.9215f, 0.0627f, 0.4f, + + // 16, 235, 235 - Cyan + 0.0627f, 0.9215f, 0.9215f, 0.6f, + + // 16, 235, 16 - Green + 0.0627f, 0.9215f, 0.0627f, 0.8f}; +static const uint32_t COLOR_BARS_F32_4X4_PIXEL_WIDTH = 4; +static const uint32_t COLOR_BARS_F32_4X4_PIXEL_HEIGHT = 4; +static const uint32_t COLOR_BARS_F32_4X4_PIXEL_COUNT = + COLOR_BARS_F32_4X4_PIXEL_WIDTH * COLOR_BARS_F32_4X4_PIXEL_HEIGHT; +static const uint32_t COLOR_BARS_F32_4X4_CHANNEL_COUNT = 4; +// float data type has 4 bytes. +static const uint32_t COLOR_BARS_F32_4X4_CHANNEL_BYTE_COUNT = 4; +static const uint32_t COLOR_BARS_F32_4X4_PIXEL_BYTE_COUNT = + COLOR_BARS_F32_4X4_CHANNEL_COUNT * COLOR_BARS_F32_4X4_CHANNEL_BYTE_COUNT; + +} // namespace mmsolver + +#endif // MM_SOLVER_CONSTANT_TEXTURE_DATA_H From eadafc5677434f103450f046230916a577aa852e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 29 May 2024 22:43:22 +1000 Subject: [PATCH 044/295] Fix MM Image Plane Transparency issue This adds OpenColorIO in order to support Color Spaces and transparency. GitHub issue #252 Adds OpenColorIO as a dependency, compiling on both Windows and Linux, as static libraries embedded into the mmSolver plug-in. Currently only Maya 2024 has been updated for the build process. The dependency for OpenColorIO also includes: - zlib - pystring - expat - Imath - yaml-cpp - minizip-ng Adds "mmcolorio" library, which wraps the OpenColorIO library into common features needed by mmSolver. Add frame number path expansion to the "mmcore", part of mmsolverlibs. The path expansion uses Rust. Increases the minimum OpenGL version to at least OpenGL Core profile (v4.0+). DirectX is no longer supported in any way. Adds new "src/mmSolver/utilities/path_utils.cpp/h" file which uses Maya classes to resolve file paths and check for file existance. Add "mmColorIO" MEL/Python command to query colour spaces and details about the OCIO config. --- CMakeLists.txt | 12 +- lib/CMakeLists.txt | 25 +- lib/README.md | 8 + lib/cppbind/mmcolorio/README.md | 14 + .../include/mmcolorio/_symbol_export.h | 30 + .../mmcolorio/include/mmcolorio/_types.h | 63 + lib/cppbind/mmcolorio/include/mmcolorio/lib.h | 55 + .../mmcolorio/include/mmcolorio/mmcolorio.h | 28 + lib/cppbind/mmcolorio/src/lib.cpp | 691 +++++++++++ lib/cppbind/mmcore/Cargo.toml | 3 + lib/cppbind/mmcore/README.md | 9 +- lib/cppbind/mmcore/include/mmcore/_cxx.h | 1009 +++++++++-------- .../mmcore/include/mmcore/_cxxbridge.h | 150 +++ lib/cppbind/mmcore/include/mmcore/_types.h | 8 +- lib/cppbind/mmcore/include/mmcore/lib.h | 7 +- lib/cppbind/mmcore/include/mmcore/mmcore.h | 3 +- lib/cppbind/mmcore/src/_cxxbridge.cpp | 180 +++ lib/cppbind/mmcore/src/cxxbridge.rs | 8 +- lib/cppbind/mmcore/src/lib.cpp | 8 +- lib/cppbind/mmcore/src/lib.rs | 6 + lib/mmsolverlibs/Cargo.lock | 198 ++++ lib/mmsolverlibs/src/CMakeLists.txt | 75 +- lib/rust/Cargo.toml | 1 + lib/rust/mmcore/.gitignore | 5 + lib/rust/mmcore/Cargo.toml | 33 + lib/rust/mmcore/src/lib.rs | 21 + lib/rust/mmcore/src/pathutils.rs | 123 ++ scripts/build_mmSolver_linux_maya2024.bash | 6 + scripts/build_mmSolver_windows64_maya2024.bat | 14 +- .../internal/build_mmSolverLibs_linux.bash | 30 +- .../internal/build_mmSolverLibs_windows64.bat | 36 +- scripts/internal/build_mmSolver_linux.bash | 30 +- scripts/internal/build_mmSolver_windows64.bat | 27 +- scripts/internal/build_openColorIO_linux.bash | 118 ++ .../internal/build_openColorIO_windows64.bat | 161 +++ share/shader/mmImagePlane.ogsfx | 8 +- src/CMakeLists.txt | 83 +- src/mmSolver/cmd/MMColorIOCmd.cpp | 401 +++++++ src/mmSolver/cmd/MMColorIOCmd.h | 153 +++ src/mmSolver/pluginMain.cpp | 6 + src/mmSolver/render/shader/shader_utils.cpp | 123 +- src/mmSolver/render/shader/shader_utils.h | 7 + src/mmSolver/shape/ImageCache.cpp | 323 ++++++ src/mmSolver/shape/ImageCache.h | 383 +++++++ .../shape/ImagePlaneGeometryOverride.cpp | 472 +++----- .../shape/ImagePlaneGeometryOverride.h | 30 +- src/mmSolver/shape/ImagePlaneShapeNode.cpp | 45 +- src/mmSolver/shape/ImagePlaneShapeNode.h | 9 +- src/mmSolver/utilities/path_utils.cpp | 66 ++ src/mmSolver/utilities/path_utils.h | 42 + tools/lensdistortion/src/CMakeLists.txt | 5 +- 51 files changed, 4422 insertions(+), 929 deletions(-) create mode 100644 lib/README.md create mode 100644 lib/cppbind/mmcolorio/README.md create mode 100644 lib/cppbind/mmcolorio/include/mmcolorio/_symbol_export.h create mode 100644 lib/cppbind/mmcolorio/include/mmcolorio/_types.h create mode 100644 lib/cppbind/mmcolorio/include/mmcolorio/lib.h create mode 100644 lib/cppbind/mmcolorio/include/mmcolorio/mmcolorio.h create mode 100644 lib/cppbind/mmcolorio/src/lib.cpp create mode 100644 lib/cppbind/mmcore/include/mmcore/_cxxbridge.h create mode 100644 lib/cppbind/mmcore/src/_cxxbridge.cpp create mode 100644 lib/rust/mmcore/.gitignore create mode 100644 lib/rust/mmcore/Cargo.toml create mode 100644 lib/rust/mmcore/src/lib.rs create mode 100644 lib/rust/mmcore/src/pathutils.rs create mode 100644 scripts/internal/build_openColorIO_linux.bash create mode 100644 scripts/internal/build_openColorIO_windows64.bat create mode 100644 src/mmSolver/cmd/MMColorIOCmd.cpp create mode 100644 src/mmSolver/cmd/MMColorIOCmd.h create mode 100644 src/mmSolver/shape/ImageCache.cpp create mode 100644 src/mmSolver/shape/ImageCache.h create mode 100644 src/mmSolver/utilities/path_utils.cpp create mode 100644 src/mmSolver/utilities/path_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e0645923..3c2e401f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,17 +33,17 @@ cmake_policy(SET CMP0074 NEW) # https://cmake.org/cmake/help/latest/policy/CMP0048.html cmake_policy(SET CMP0048 NEW) -# # Changes how timestamps of downloaded files with -# # ExternalProject_Add() are set. -# # -# # https://cmake.org/cmake/help/latest/policy/CMP0135.html -# cmake_policy(SET CMP0135 NEW) - # Honor visibility properties for all target types. # # https://cmake.org/cmake/help/latest/policy/CMP0063.html cmake_policy(SET CMP0063 NEW) +# Changes how timestamps of downloaded files with +# ExternalProject_Add() are set. +# +# https://cmake.org/cmake/help/latest/policy/CMP0135.html +cmake_policy(SET CMP0135 NEW) + # Do not allow using GNU extensions (such as '-std=g++11'), because # it's not compatible with Maya. set(CXX_EXTENSIONS OFF) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index db5eb9129..589ff0179 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,4 +1,4 @@ -# Copyright (C) 2023 David Cattermole. +# Copyright (C) 2023, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -19,6 +19,25 @@ cmake_minimum_required(VERSION 3.15) + +# The "project" command will overwrite the "VERSION" variables. We set +# the VERSION variables after the "project" command, so it should not +# affect us. +# +# https://cmake.org/cmake/help/latest/policy/CMP0048.html +cmake_policy(SET CMP0048 NEW) + +# # Honor visibility properties for all target types. +# # +# # https://cmake.org/cmake/help/latest/policy/CMP0063.html +# cmake_policy(SET CMP0063 NEW) + +# Changes how timestamps of downloaded files with +# ExternalProject_Add() are set. +# +# https://cmake.org/cmake/help/latest/policy/CMP0135.html +cmake_policy(SET CMP0135 NEW) + # Do not allow using GNU extensions (such as '-std=g++11'), because # it's not compatible with Maya. set(CXX_EXTENSIONS OFF) @@ -33,7 +52,7 @@ set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "mmSolver libraries.") set(PROJECT_AUTHOR "David Cattermole") -set(PROJECT_COPYRIGHT "2023, David Cattermole.") +set(PROJECT_COPYRIGHT "2023, 2024, David Cattermole.") set(MMSOLVERLIBS_LIB_DIR "/path/to/rust/build/directory/" CACHE PATH "The path to the directory containing the compiled library.") @@ -45,8 +64,6 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} set(cpp_lib_name "mmsolverlibs_cpp") set(rust_lib_name "mmsolverlibs_rust") -set(CMAKE_CXX_STANDARD 11) - include(MMSolverUtils) set_global_maya_plugin_compile_options() diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 000000000..57ae1ca7e --- /dev/null +++ b/lib/README.md @@ -0,0 +1,8 @@ +# Rust and C++ Libraries + +mmSolver has a number of Rust and C++ libraries, which are abstracted +for general use by the mmSolver Maya Plug-in (but could one day be +reused outside Maya). + +TODO... write more to describe the directory layout and why and how +it's all built with Cargo and CMake. diff --git a/lib/cppbind/mmcolorio/README.md b/lib/cppbind/mmcolorio/README.md new file mode 100644 index 000000000..02da6d4f4 --- /dev/null +++ b/lib/cppbind/mmcolorio/README.md @@ -0,0 +1,14 @@ +# MM Color IO + +The 'mmcolorio' library is a wrapper around the OpenColorIO library. + +# Structure + +mmcolorio is split into 2 different directories; 'rust/mmcolorio' and 'cppbind/mmcolorio'. + +'cppbind/mmcolorio' is a Rust crate to define C++ bindings with the help +of the CXX crate. + +# Build Process + +'cppbind/mmcolorio' are built as part of 'mmsolverlibs'. diff --git a/lib/cppbind/mmcolorio/include/mmcolorio/_symbol_export.h b/lib/cppbind/mmcolorio/include/mmcolorio/_symbol_export.h new file mode 100644 index 000000000..47d886315 --- /dev/null +++ b/lib/cppbind/mmcolorio/include/mmcolorio/_symbol_export.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020, 2021, 2023 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ +#pragma once + +// Cross-platform symbol visibility macro. +#if defined(_MSC_VER) +#define MMCOLORIO_API_EXPORT __declspec(dllexport) +#elif defined(__GNUC__) +#define MMCOLORIO_API_EXPORT __attribute__((visibility("default"))) +#else +#define MMCOLORIO_API_EXPORT +#endif diff --git a/lib/cppbind/mmcolorio/include/mmcolorio/_types.h b/lib/cppbind/mmcolorio/include/mmcolorio/_types.h new file mode 100644 index 000000000..46ada7126 --- /dev/null +++ b/lib/cppbind/mmcolorio/include/mmcolorio/_types.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#pragma once + +#include + +namespace mmcolorio { + +enum class ColorSpaceVisibility : uint8_t { + kActive = 0, + kInactive, + kAll, + + // Must be second to last entry. Used to calculate the full list + // of entries. + kCount, + + // Must be last entry. + kUnknown = 255, +}; + +enum class ColorSpaceRole : uint8_t { + kDefault = 0, + kReference, + kData, + kColorPicking, + kSceneLinear, + kCompositingLog, + kColorTiming, + kTexturePaint, + kMattePaint, + kRendering, + kInterchangeScene, + kInterchangeDisplay, + + // Must be second to last entry. Used to calculate the full list + // of entries. + kCount, + + // Must be last entry. + kUnknown = 255, +}; + +} // namespace mmcolorio diff --git a/lib/cppbind/mmcolorio/include/mmcolorio/lib.h b/lib/cppbind/mmcolorio/include/mmcolorio/lib.h new file mode 100644 index 000000000..31ca967c7 --- /dev/null +++ b/lib/cppbind/mmcolorio/include/mmcolorio/lib.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_COLOR_IO_LIB_H +#define MM_COLOR_IO_LIB_H + +// STD +#include +#include +#include + +#include "_symbol_export.h" +#include "_types.h" + +namespace mmcolorio { + +const char *get_config_name(); +const char *get_config_description(); +const char *get_config_search_path(); +const char *get_config_working_directory(); + +bool color_space_name_exists(const char *color_space_name); + +const char *guess_color_space_name_from_file_path(const char *file_path); + +const char *get_role_color_space_name(const ColorSpaceRole value); + +std::vector get_color_space_names( + const ColorSpaceVisibility visibility); + +void generate_shader_text(const char *input_color_space_name, + const char *output_color_space_name, + std::string &out_shader_text); + +} // namespace mmcolorio + +#endif // MM_COLOR_IO_LIB_H diff --git a/lib/cppbind/mmcolorio/include/mmcolorio/mmcolorio.h b/lib/cppbind/mmcolorio/include/mmcolorio/mmcolorio.h new file mode 100644 index 000000000..9bf3d3b45 --- /dev/null +++ b/lib/cppbind/mmcolorio/include/mmcolorio/mmcolorio.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2023, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_COLOR_IO_MM_COLOR_IO_H +#define MM_COLOR_IO_MM_COLOR_IO_H + +#include "_types.h" +#include "lib.h" + +#endif // MM_COLOR_IO_MM_COLOR_IO_H diff --git a/lib/cppbind/mmcolorio/src/lib.cpp b/lib/cppbind/mmcolorio/src/lib.cpp new file mode 100644 index 000000000..c7b6be329 --- /dev/null +++ b/lib/cppbind/mmcolorio/src/lib.cpp @@ -0,0 +1,691 @@ +/* + * Copyright (C) 2023 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +// Stop OpenColorIO header error on Windows. +// +// https://stackoverflow.com/questions/6884093/warning-c4003-not-enough-actual-parameters-for-macro-max-visual-studio-2010 +#ifndef NOMINMAX +#define NOMINMAX +#endif + +// C++ Standard Library +#include +#include +#include +#include +#include +#include + +// MM Solver +#include +#include + +// OpenColorIO +#include + +namespace OCIO = OCIO_NAMESPACE; + +namespace mmcolorio { + +void print_ocio_config_details(OCIO::ConstConfigRcPtr &config) { + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: ----------------------------"); + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: OpenColorIO Version=" << OCIO_VERSION); + + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, "mmcolorio: print_ocio_config_details: config is null."); + return; + } + + try { + MMSOLVER_CORE_INFO( + std::cout, "mmcolorio: OCIO Config:" + << " MajorVersion=" << config->getMajorVersion() + << " MinorVersion=" << config->getMinorVersion()); + + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config:" + << " Name=\"" << config->getName() + << "\""); + + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config:" + << " FamilySeparator=\"" + << config->getFamilySeparator() + << "\""); + + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config:" + << " Description=\"" + << config->getDescription() << "\""); + + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config:" + << " SearchPath=\"" + << config->getSearchPath() << "\""); + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config:" + << " WorkingDir=\"" + << config->getWorkingDir() << "\""); + + const auto searchReferenceType = + OCIO::SearchReferenceSpaceType::SEARCH_REFERENCE_SPACE_SCENE; + const auto visibility = OCIO::ColorSpaceVisibility::COLORSPACE_ACTIVE; + + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: ----------------------------"); + const int32_t numColorSpaces = + config->getNumColorSpaces(searchReferenceType, visibility); + // const int32_t numColorSpaces = config->getNumColorSpaces(); + for (auto i = 0; i < numColorSpaces; i++) { + const char *colorSpaceName = config->getColorSpaceNameByIndex(i); + OCIO::ConstColorSpaceRcPtr colorSpaceRcPtr = + config->getColorSpace(colorSpaceName); + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config: i=" + << i << " colorSpaceName=\"" + << colorSpaceName << "\""); + } + + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: ----------------------------"); + const int32_t numRoles = config->getNumRoles(); + for (auto i = 0; i < numRoles; i++) { + const char *roleName = config->getRoleName(i); + const char *roleColorSpaceName = config->getRoleColorSpace(i); + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config: i=" + << i << " roleName=\"" << roleName + << "\" roleColorSpaceName=\"" + << roleColorSpaceName << "\""); + } + + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: ----------------------------"); + const char *defaultDisplayName = config->getDefaultDisplay(); + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: OCIO Config: defaultDisplayName=\"" + << defaultDisplayName << "\""); + + const char *activeDisplays = config->getActiveDisplays(); + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: OCIO Config: activeDisplays=\"" + << activeDisplays << "\""); + + const char *activeViews = config->getActiveViews(); + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config: activeViews=\"" + << activeViews << "\""); + + MMSOLVER_CORE_INFO(std::cout, + "mmcolorio: ----------------------------"); + const int numDisplay = config->getNumDisplays(); + for (auto i = 0; i < numDisplay; i++) { + const char *displayName = config->getDisplay(i); + MMSOLVER_CORE_INFO(std::cout, "mmcolorio: OCIO Config: i=" + << i << " displayName=\"" + << displayName << "\""); + } + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: OpenColorIO Error: print_ocio_config_details: " + << exception.what()); + } + + return; +} + +// This is an example function used to validate OpenColorIO was +// working. This should not be used and can be removed at a later +// date. +void test_opencolorio(uint8_t *pixels, const uint32_t width, + const uint32_t height, const uint8_t number_of_channels) { + const bool verbose = false; + + mmsolver::debug::TimestampBenchmark timer_total; + mmsolver::debug::TimestampBenchmark timer_a; + mmsolver::debug::TimestampBenchmark timer_b; + mmsolver::debug::TimestampBenchmark timer_c; + mmsolver::debug::TimestampBenchmark timer_d; + mmsolver::debug::TimestampBenchmark timer_e; + + timer_total.start(); + try { + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + if (verbose) { + timer_c.start(); + print_ocio_config_details(config); + timer_c.stop(); + } + + // Built-in OCIO config (used as a fallback) + if (std::strlen(config->getName()) == 0) { + timer_a.start(); + + MMSOLVER_CORE_WRN(std::cerr, + "mmcolorio: OpenColorIO could not get config, " + "using default config."); + + const char *filename = "ocio://default"; + config = OCIO::Config::CreateFromFile(filename); + OCIO::SetCurrentConfig(config); + timer_a.stop(); + + if (verbose) { + timer_c.start(); + print_ocio_config_details(config); + timer_c.stop(); + } + } + + OCIO::BitDepth inBitDepth = OCIO::BitDepth::BIT_DEPTH_UINT8; + OCIO::BitDepth outBitDepth = OCIO::BitDepth::BIT_DEPTH_UINT8; + + // // TODO: Support 16-bit half or 32-bit float output. + // OCIO::BitDepth outBitDepth = OCIO::BitDepth::BIT_DEPTH_F16; + // OCIO::BitDepth outBitDepth = OCIO::BitDepth::BIT_DEPTH_F32; + + { + timer_d.start(); + + OCIO::ConstProcessorRcPtr processor = config->getProcessor( + OCIO::ROLE_COLOR_PICKING, OCIO::ROLE_SCENE_LINEAR); + + OCIO::OptimizationFlags oFlags = + OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT; + + OCIO::ConstCPUProcessorRcPtr cpu = + processor->getOptimizedCPUProcessor(inBitDepth, outBitDepth, + oFlags); + + timer_d.stop(); + + { + timer_e.start(); + + // Apply the color transform to an existing RGB(A) image. + void *imageData = static_cast(pixels); + const long width_long = static_cast(width); + const long height_long = static_cast(height); + const long numChannels = static_cast(number_of_channels); + const ptrdiff_t chanStrideBytes = sizeof(uint8_t); + const ptrdiff_t xStrideBytes = numChannels * sizeof(uint8_t); + const ptrdiff_t yStrideBytes = + numChannels * width_long * sizeof(uint8_t); + OCIO::PackedImageDesc img( + imageData, width_long, height_long, numChannels, inBitDepth, + chanStrideBytes, xStrideBytes, yStrideBytes); + + // TODO: Allow a different image data type as the output. We + // need to pass another 'OCIO::PackedImageDesc' as a second + // argument here. + cpu->apply(img); + timer_e.stop(); + } + } + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: OpenColorIO Error: test_opencolorio: " + << exception.what()); + } + + timer_total.stop(); + + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: timer_a: " << timer_a.get_seconds() << " seconds"); + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: timer_b: " << timer_b.get_seconds() << " seconds"); + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: timer_c: " << timer_c.get_seconds() << " seconds"); + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: timer_d: " << timer_d.get_seconds() << " seconds"); + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: timer_e: " << timer_e.get_seconds() << " seconds"); + MMSOLVER_CORE_VRB( + std::cout, + "mmcolor: timer_total: " << timer_total.get_seconds() << " seconds"); + + return; +} + +// Never make this function public, otherwise we leak the OCIO data +// types. +// +// This function is expected to be used inside a try/catch. +OCIO::ConstConfigRcPtr get_config() { + const bool verbose = false; + + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + if (verbose) { + print_ocio_config_details(config); + } + + // Built-in OCIO config (used as a fallback) + if (!config || std::strlen(config->getName()) == 0) { + MMSOLVER_CORE_WRN(std::cerr, + "mmcolorio: OpenColorIO could not get config, " + "using default config."); + + const char *filename = "ocio://default"; + config = OCIO::Config::CreateFromFile(filename); + OCIO::SetCurrentConfig(config); + + if (verbose) { + print_ocio_config_details(config); + } + } + + return config; +} + +const char *get_config_name() { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_config_name: config is null."); + return ""; + } + + return config->getName(); + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_config_name: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +const char *get_config_description() { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: get_config_description: config is null."); + return ""; + } + + return config->getDescription(); + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_config_description: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +const char *get_config_search_path() { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: get_config_search_path: config is null."); + return ""; + } + + return config->getSearchPath(); + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_config_search_path: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +const char *get_config_working_directory() { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: config_working_directory: config is null."); + return ""; + } + + return config->getWorkingDir(); + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_config_working_directory: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +bool color_space_name_exists(const char *color_space_name) { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: color_space_name_exists: config is null."); + return false; + } + + OCIO::ConstColorSpaceRcPtr color_space = + config->getColorSpace(color_space_name); + if (color_space) { + const char *found_name = color_space->getName(); + if (std::strlen(found_name) > 0) { + return true; + } + } + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: guess_color_space_name_from_file_path: " + "OpenColorIO Error: " + << exception.what()); + } + + return false; +} + +const char *guess_color_space_name_from_file_path(const char *file_path) { + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: guess_color_space_name_from_file_path: config is " + "null."); + return ""; + } + + const char *color_space_name = + config->getColorSpaceFromFilepath(file_path); + return color_space_name; + + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: guess_color_space_name_from_file_path: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +static const char *color_space_role_convert_mmcolorio_to_ocio( + const ColorSpaceRole value) { + if (value == ColorSpaceRole::kDefault) { + return OCIO::ROLE_DEFAULT; + } else if (value == ColorSpaceRole::kReference) { + return OCIO::ROLE_REFERENCE; + } else if (value == ColorSpaceRole::kData) { + return OCIO::ROLE_DATA; + } else if (value == ColorSpaceRole::kColorPicking) { + return OCIO::ROLE_COLOR_PICKING; + } else if (value == ColorSpaceRole::kSceneLinear) { + return OCIO::ROLE_SCENE_LINEAR; + } else if (value == ColorSpaceRole::kCompositingLog) { + return OCIO::ROLE_COMPOSITING_LOG; + } else if (value == ColorSpaceRole::kColorTiming) { + return OCIO::ROLE_COLOR_TIMING; + } else if (value == ColorSpaceRole::kTexturePaint) { + return OCIO::ROLE_TEXTURE_PAINT; + } else if (value == ColorSpaceRole::kMattePaint) { + return OCIO::ROLE_MATTE_PAINT; + } else if (value == ColorSpaceRole::kRendering) { + return OCIO::ROLE_RENDERING; + } else if (value == ColorSpaceRole::kInterchangeScene) { + return OCIO::ROLE_INTERCHANGE_SCENE; + } else if (value == ColorSpaceRole::kInterchangeDisplay) { + return OCIO::ROLE_INTERCHANGE_DISPLAY; + } else { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: color_space_role_convert_mmcolorio_to_ocio: " + "Invalid mmcolorio::ColorSpaceRole: " + << static_cast(value)); + } + return OCIO::ROLE_DEFAULT; +} + +const char *get_role_color_space_name(const ColorSpaceRole role) { + const bool verbose = false; + + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: get_role_color_space_name: config is null."); + return ""; + } + + const char *role_name = + color_space_role_convert_mmcolorio_to_ocio(role); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: get_role_color_space_name: role_name=\"" + << role_name << "\""); + + OCIO::ConstColorSpaceRcPtr role_color_space = + config->getColorSpace(role_name); + if (role_color_space) { + return role_color_space->getName(); + } + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_role_color_space_name: " + "OpenColorIO Error: " + << exception.what()); + } + + return ""; +} + +static OCIO::ColorSpaceVisibility +color_space_visibility_convert_mmcolorio_to_ocio( + const ColorSpaceVisibility value) { + if (value == ColorSpaceVisibility::kActive) { + return OCIO::ColorSpaceVisibility::COLORSPACE_ACTIVE; + } else if (value == ColorSpaceVisibility::kInactive) { + return OCIO::ColorSpaceVisibility::COLORSPACE_INACTIVE; + } else if (value == ColorSpaceVisibility::kAll) { + return OCIO::ColorSpaceVisibility::COLORSPACE_ALL; + } else { + MMSOLVER_CORE_ERR( + std::cerr, + "mmcolorio: color_space_visibility_convert_mmcolorio_to_ocio: " + "Invalid mmcolorio::ColorSpaceVisibility: " + << static_cast(value)); + } + return OCIO::ColorSpaceVisibility::COLORSPACE_ALL; +} + +std::vector get_color_space_names( + const ColorSpaceVisibility visibility) { + try { + OCIO::ConstConfigRcPtr config = get_config(); + + auto names = std::vector(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, "mmcolorio: get_color_space_names: config is null."); + return names; + } + + const auto searchReferenceType = + OCIO::SearchReferenceSpaceType::SEARCH_REFERENCE_SPACE_SCENE; + + const auto ocio_visibility = + color_space_visibility_convert_mmcolorio_to_ocio(visibility); + + const int32_t num_color_spaces = + config->getNumColorSpaces(searchReferenceType, ocio_visibility); + + for (auto i = 0; i < num_color_spaces; i++) { + std::string color_space_name = + std::string(config->getColorSpaceNameByIndex(i)); + names.push_back(color_space_name); + } + + return names; + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: get_color_space_names: " + "OpenColorIO Error: " + << exception.what()); + } + + auto names = std::vector(); + return names; +} + +void generate_shader_text(const char *input_color_space_name, + const char *output_color_space_name, + std::string &out_shader_text) { + const bool verbose = false; + + MMSOLVER_CORE_VRB( + std::cout, "mmcolorio: generate_shader_text: input_color_space_name=\"" + << input_color_space_name << "\"."); + MMSOLVER_CORE_VRB( + std::cout, "mmcolorio: generate_shader_text: output_color_space_name=\"" + << output_color_space_name << "\"."); + + mmsolver::debug::TimestampBenchmark timer_total; + timer_total.start(); + + out_shader_text.clear(); + try { + OCIO::ConstConfigRcPtr config = get_config(); + if (!config) { + MMSOLVER_CORE_ERR( + std::cerr, "mmcolorio: generate_shader_text: config is null."); + return; + } + + OCIO::ConstColorSpaceRcPtr input_color_space = + config->getColorSpace(input_color_space_name); + OCIO::ConstColorSpaceRcPtr output_color_space = + config->getColorSpace(output_color_space_name); + + OCIO::ConstProcessorRcPtr processor = + config->getProcessor(input_color_space, output_color_space); + + // NOTE: Cache ID will change when the processor values + // change. + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: generate_shader_text: processor cache id: \"" + << processor->getCacheID() << "\""); + + OCIO::OptimizationFlags oFlags = + OCIO::OptimizationFlags::OPTIMIZATION_DEFAULT; + OCIO::ConstGPUProcessorRcPtr gpu_processor = + processor->getOptimizedGPUProcessor(oFlags); + + // unsigned edgelen = 32; + // OCIO::ConstGPUProcessorRcPtr gpu_processor = + // processor->getOptimizedLegacyGPUProcessor(oFlags, edgelen); + + // NOTE: Cache ID will change when the gpu_processor values + // change. + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: generate_shader_text: processor cache id: \"" + << gpu_processor->getCacheID() << "\""); + + // OCIO::GpuShaderDescRcPtr shader_desc = + // OCIO::GpuShaderDesc::Create(); + OCIO::GpuShaderDescRcPtr shader_desc = + OCIO::GpuShaderDesc::CreateShaderDesc(); // ->clone() + // OCIO::GPU_LANGUAGE_GLSL_1_2 + // OCIO::GPU_LANGUAGE_GLSL_1_3 + // OCIO::GPU_LANGUAGE_GLSL_4_0 + // OCIO::GPU_LANGUAGE_GLSL_ES_1_0 + // OCIO::GPU_LANGUAGE_GLSL_ES_3_0 + shader_desc->setLanguage(OCIO::GPU_LANGUAGE_GLSL_4_0); + shader_desc->setFunctionName("OCIODisplay"); + shader_desc->setResourcePrefix("ocio_"); + shader_desc->finalize(); + gpu_processor->extractGpuShaderInfo(shader_desc); + + MMSOLVER_CORE_VRB( + std::cout, + "mmcolor: generate_shader_text: shader desc unique id: \"" + << shader_desc->getUniqueID() << "\""); + MMSOLVER_CORE_VRB( + std::cout, + "mmcolor: generate_shader_text: shader desc pixel name: \"" + << shader_desc->getPixelName() << "\""); + + // NOTE: Cache ID will change when the shader_desc values + // change. + MMSOLVER_CORE_VRB( + std::cout, "mmcolor: generate_shader_text: shader desc cache id: \"" + << shader_desc->getCacheID() << "\""); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader desc " + "texture max width: " + << shader_desc->getTextureMaxWidth()); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader desc " + "num dynamic properties: " + << shader_desc->getNumDynamicProperties()); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader desc " + "num uniform: " + << shader_desc->getNumUniforms()); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader desc " + "num textures: " + << shader_desc->getNumTextures()); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader desc " + "num 3d textures: " + << shader_desc->getNum3DTextures()); + + const char *shader_text = shader_desc->getShaderText(); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader text start:"); + MMSOLVER_CORE_VRB(std::cout, shader_text); + MMSOLVER_CORE_VRB(std::cout, + "mmcolor: generate_shader_text: shader text end."); + out_shader_text += shader_text; + + // TODO: Work out how to get and upload all the shader + // and texture information. + + // const unsigned numUniforms = shader_desc->getNumUniforms(); + // const unsigned numTextures = shader_desc->getNumTextures(); + // const unsigned num3dTextures = shader_desc->getNum3DTextures(); + // shader_desc->get3DTexture(index, textureName, samplerName, + // edgelen, interpolation); + + } catch (OCIO::Exception &exception) { + MMSOLVER_CORE_ERR(std::cerr, + "mmcolorio: generate_shader_text: OpenColorIO Error: " + << exception.what()); + } + + timer_total.stop(); + + MMSOLVER_CORE_VRB(std::cout, "mmcolor: generate_shader_text: timer_total: " + << timer_total.get_seconds() + << " seconds"); + + return; +} + +} // namespace mmcolorio diff --git a/lib/cppbind/mmcore/Cargo.toml b/lib/cppbind/mmcore/Cargo.toml index bd3269d8b..58cc09621 100644 --- a/lib/cppbind/mmcore/Cargo.toml +++ b/lib/cppbind/mmcore/Cargo.toml @@ -15,6 +15,9 @@ path = "./src/lib.rs" # here: './scripts/internal/build_rust_library_*.*' cxx = "=1.0.75" +[dependencies.mmcore_rust] +path = "../../rust/mmcore" + [profile.release] opt-level = 3 rpath = false diff --git a/lib/cppbind/mmcore/README.md b/lib/cppbind/mmcore/README.md index 56bcd1bc7..8ba79fdab 100644 --- a/lib/cppbind/mmcore/README.md +++ b/lib/cppbind/mmcore/README.md @@ -14,14 +14,15 @@ used in Maya. # Structure -Unlike the other similar sub-libraries 'mmcore' is only defined in -'cppbind/mmcore', the Rust component is not yet needed and has not -been added. +mmcore is split into 2 different directories; 'rust/mmcore' and 'cppbind/mmcore'. + +'rust/mmcore' is the core library written in Rust without the +requirements of C++. 'cppbind/mmcore' is a Rust crate to define C++ bindings with the help of the CXX crate. # Build Process -'cppbind/mmcore' are built as part of +'cppbind/mmcore' and 'rust/mmcore' are built as part of 'mmsolverlibs'. diff --git a/lib/cppbind/mmcore/include/mmcore/_cxx.h b/lib/cppbind/mmcore/include/mmcore/_cxx.h index 41e1f98a3..907ee829f 100644 --- a/lib/cppbind/mmcore/include/mmcore/_cxx.h +++ b/lib/cppbind/mmcore/include/mmcore/_cxx.h @@ -35,122 +35,122 @@ class impl; // https://cxx.rs/binding/string.html class String final { public: - String() noexcept; - String(const String &) noexcept; - String(String &&) noexcept; - ~String() noexcept; - - String(const std::string &); - String(const char *); - String(const char *, std::size_t); - String(const char16_t *); - String(const char16_t *, std::size_t); - - // Replace invalid Unicode data with the replacement character (U+FFFD). - static String lossy(const std::string &) noexcept; - static String lossy(const char *) noexcept; - static String lossy(const char *, std::size_t) noexcept; - static String lossy(const char16_t *) noexcept; - static String lossy(const char16_t *, std::size_t) noexcept; - - String &operator=(const String &) &noexcept; - String &operator=(String &&) &noexcept; - - explicit operator std::string() const; - - // Note: no null terminator. - const char *data() const noexcept; - std::size_t size() const noexcept; - std::size_t length() const noexcept; - bool empty() const noexcept; - - const char *c_str() noexcept; - - std::size_t capacity() const noexcept; - void reserve(size_t new_cap) noexcept; - - using iterator = char *; - iterator begin() noexcept; - iterator end() noexcept; - - using const_iterator = const char *; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; - const_iterator cbegin() const noexcept; - const_iterator cend() const noexcept; - - bool operator==(const String &) const noexcept; - bool operator!=(const String &) const noexcept; - bool operator<(const String &) const noexcept; - bool operator<=(const String &) const noexcept; - bool operator>(const String &) const noexcept; - bool operator>=(const String &) const noexcept; - - void swap(String &) noexcept; - - // Internal API only intended for the cxxbridge code generator. - String(unsafe_bitcopy_t, const String &) noexcept; + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + String(const std::string &); + String(const char *); + String(const char *, std::size_t); + String(const char16_t *); + String(const char16_t *, std::size_t); + + // Replace invalid Unicode data with the replacement character (U+FFFD). + static String lossy(const std::string &) noexcept; + static String lossy(const char *) noexcept; + static String lossy(const char *, std::size_t) noexcept; + static String lossy(const char16_t *) noexcept; + static String lossy(const char16_t *, std::size_t) noexcept; + + String &operator=(const String &) &noexcept; + String &operator=(String &&) &noexcept; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + const char *c_str() noexcept; + + std::size_t capacity() const noexcept; + void reserve(size_t new_cap) noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; + + // Internal API only intended for the cxxbridge code generator. + String(unsafe_bitcopy_t, const String &) noexcept; private: - struct lossy_t; - String(lossy_t, const char *, std::size_t) noexcept; - String(lossy_t, const char16_t *, std::size_t) noexcept; - friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } + struct lossy_t; + String(lossy_t, const char *, std::size_t) noexcept; + String(lossy_t, const char16_t *, std::size_t) noexcept; + friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } - // Size and alignment statically verified by rust_string.rs. - std::array repr; + // Size and alignment statically verified by rust_string.rs. + std::array repr; }; -#endif // CXXBRIDGE1_RUST_STRING +#endif // CXXBRIDGE1_RUST_STRING #ifndef CXXBRIDGE1_RUST_STR #define CXXBRIDGE1_RUST_STR // https://cxx.rs/binding/str.html class Str final { public: - Str() noexcept; - Str(const String &) noexcept; - Str(const std::string &); - Str(const char *); - Str(const char *, std::size_t); - - Str &operator=(const Str &) &noexcept = default; - - explicit operator std::string() const; - - // Note: no null terminator. - const char *data() const noexcept; - std::size_t size() const noexcept; - std::size_t length() const noexcept; - bool empty() const noexcept; - - // Important in order for System V ABI to pass in registers. - Str(const Str &) noexcept = default; - ~Str() noexcept = default; - - using iterator = const char *; - using const_iterator = const char *; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; - const_iterator cbegin() const noexcept; - const_iterator cend() const noexcept; - - bool operator==(const Str &) const noexcept; - bool operator!=(const Str &) const noexcept; - bool operator<(const Str &) const noexcept; - bool operator<=(const Str &) const noexcept; - bool operator>(const Str &) const noexcept; - bool operator>=(const Str &) const noexcept; - - void swap(Str &) noexcept; + Str() noexcept; + Str(const String &) noexcept; + Str(const std::string &); + Str(const char *); + Str(const char *, std::size_t); + + Str &operator=(const Str &) &noexcept = default; + + explicit operator std::string() const; + + // Note: no null terminator. + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + // Important in order for System V ABI to pass in registers. + Str(const Str &) noexcept = default; + ~Str() noexcept = default; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; private: - class uninit; - Str(uninit) noexcept; - friend impl; + class uninit; + Str(uninit) noexcept; + friend impl; - std::array repr; + std::array repr; }; -#endif // CXXBRIDGE1_RUST_STR +#endif // CXXBRIDGE1_RUST_STR #ifndef CXXBRIDGE1_RUST_SLICE namespace detail { @@ -159,211 +159,210 @@ struct copy_assignable_if {}; template <> struct copy_assignable_if { - copy_assignable_if() noexcept = default; - copy_assignable_if(const copy_assignable_if &) noexcept = default; - copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = - delete; - copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default; + copy_assignable_if() noexcept = default; + copy_assignable_if(const copy_assignable_if &) noexcept = default; + copy_assignable_if &operator=(const copy_assignable_if &) &noexcept = delete; + copy_assignable_if &operator=(copy_assignable_if &&) &noexcept = default; }; -} // namespace detail +} // namespace detail // https://cxx.rs/binding/slice.html template class Slice final : private detail::copy_assignable_if::value> { public: - using value_type = T; + using value_type = T; - Slice() noexcept; - Slice(T *, std::size_t count) noexcept; + Slice() noexcept; + Slice(T *, std::size_t count) noexcept; - Slice &operator=(const Slice &) &noexcept = default; - Slice &operator=(Slice &&) &noexcept = default; + Slice &operator=(const Slice &) &noexcept = default; + Slice &operator=(Slice &&) &noexcept = default; - T *data() const noexcept; - std::size_t size() const noexcept; - std::size_t length() const noexcept; - bool empty() const noexcept; + T *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; - T &operator[](std::size_t n) const noexcept; - T &at(std::size_t n) const; - T &front() const noexcept; - T &back() const noexcept; + T &operator[](std::size_t n) const noexcept; + T &at(std::size_t n) const; + T &front() const noexcept; + T &back() const noexcept; - // Important in order for System V ABI to pass in registers. - Slice(const Slice &) noexcept = default; - ~Slice() noexcept = default; + // Important in order for System V ABI to pass in registers. + Slice(const Slice &) noexcept = default; + ~Slice() noexcept = default; - class iterator; - iterator begin() const noexcept; - iterator end() const noexcept; + class iterator; + iterator begin() const noexcept; + iterator end() const noexcept; - void swap(Slice &) noexcept; + void swap(Slice &) noexcept; private: - class uninit; - Slice(uninit) noexcept; - friend impl; - friend void sliceInit(void *, const void *, std::size_t) noexcept; - friend void *slicePtr(const void *) noexcept; - friend std::size_t sliceLen(const void *) noexcept; - - std::array repr; + class uninit; + Slice(uninit) noexcept; + friend impl; + friend void sliceInit(void *, const void *, std::size_t) noexcept; + friend void *slicePtr(const void *) noexcept; + friend std::size_t sliceLen(const void *) noexcept; + + std::array repr; }; template class Slice::iterator final { public: - using iterator_category = std::random_access_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = typename std::add_pointer::type; - using reference = typename std::add_lvalue_reference::type; - - reference operator*() const noexcept; - pointer operator->() const noexcept; - reference operator[](difference_type) const noexcept; - - iterator &operator++() noexcept; - iterator operator++(int) noexcept; - iterator &operator--() noexcept; - iterator operator--(int) noexcept; - - iterator &operator+=(difference_type) noexcept; - iterator &operator-=(difference_type) noexcept; - iterator operator+(difference_type) const noexcept; - iterator operator-(difference_type) const noexcept; - difference_type operator-(const iterator &) const noexcept; - - bool operator==(const iterator &) const noexcept; - bool operator!=(const iterator &) const noexcept; - bool operator<(const iterator &) const noexcept; - bool operator<=(const iterator &) const noexcept; - bool operator>(const iterator &) const noexcept; - bool operator>=(const iterator &) const noexcept; + using iterator_category = std::random_access_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = typename std::add_pointer::type; + using reference = typename std::add_lvalue_reference::type; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + reference operator[](difference_type) const noexcept; + + iterator &operator++() noexcept; + iterator operator++(int) noexcept; + iterator &operator--() noexcept; + iterator operator--(int) noexcept; + + iterator &operator+=(difference_type) noexcept; + iterator &operator-=(difference_type) noexcept; + iterator operator+(difference_type) const noexcept; + iterator operator-(difference_type) const noexcept; + difference_type operator-(const iterator &) const noexcept; + + bool operator==(const iterator &) const noexcept; + bool operator!=(const iterator &) const noexcept; + bool operator<(const iterator &) const noexcept; + bool operator<=(const iterator &) const noexcept; + bool operator>(const iterator &) const noexcept; + bool operator>=(const iterator &) const noexcept; private: - friend class Slice; - void *pos; - std::size_t stride; + friend class Slice; + void *pos; + std::size_t stride; }; -#endif // CXXBRIDGE1_RUST_SLICE +#endif // CXXBRIDGE1_RUST_SLICE #ifndef CXXBRIDGE1_RUST_BOX // https://cxx.rs/binding/box.html template class Box final { public: - using element_type = T; - using const_pointer = - typename std::add_pointer::type>::type; - using pointer = typename std::add_pointer::type; + using element_type = T; + using const_pointer = + typename std::add_pointer::type>::type; + using pointer = typename std::add_pointer::type; - Box() = delete; - Box(Box &&) noexcept; - ~Box() noexcept; + Box() = delete; + Box(Box &&) noexcept; + ~Box() noexcept; - explicit Box(const T &); - explicit Box(T &&); + explicit Box(const T &); + explicit Box(T &&); - Box &operator=(Box &&) &noexcept; + Box &operator=(Box &&) &noexcept; - const T *operator->() const noexcept; - const T &operator*() const noexcept; - T *operator->() noexcept; - T &operator*() noexcept; + const T *operator->() const noexcept; + const T &operator*() const noexcept; + T *operator->() noexcept; + T &operator*() noexcept; - template - static Box in_place(Fields &&...); + template + static Box in_place(Fields &&...); - void swap(Box &) noexcept; + void swap(Box &) noexcept; - // Important: requires that `raw` came from an into_raw call. Do not pass a - // pointer from `new` or any other source. - static Box from_raw(T *) noexcept; + // Important: requires that `raw` came from an into_raw call. Do not pass a + // pointer from `new` or any other source. + static Box from_raw(T *) noexcept; - T *into_raw() noexcept; + T *into_raw() noexcept; - /* Deprecated */ using value_type = element_type; + /* Deprecated */ using value_type = element_type; private: - class uninit; - class allocation; - Box(uninit) noexcept; - void drop() noexcept; + class uninit; + class allocation; + Box(uninit) noexcept; + void drop() noexcept; - friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } + friend void swap(Box &lhs, Box &rhs) noexcept { lhs.swap(rhs); } - T *ptr; + T *ptr; }; -#endif // CXXBRIDGE1_RUST_BOX +#endif // CXXBRIDGE1_RUST_BOX #ifndef CXXBRIDGE1_RUST_VEC // https://cxx.rs/binding/vec.html template class Vec final { public: - using value_type = T; - - Vec() noexcept; - Vec(std::initializer_list); - Vec(const Vec &); - Vec(Vec &&) noexcept; - ~Vec() noexcept; - - Vec &operator=(Vec &&) &noexcept; - Vec &operator=(const Vec &) &; - - std::size_t size() const noexcept; - bool empty() const noexcept; - const T *data() const noexcept; - T *data() noexcept; - std::size_t capacity() const noexcept; - - const T &operator[](std::size_t n) const noexcept; - const T &at(std::size_t n) const; - const T &front() const noexcept; - const T &back() const noexcept; - - T &operator[](std::size_t n) noexcept; - T &at(std::size_t n); - T &front() noexcept; - T &back() noexcept; - - void reserve(std::size_t new_cap); - void push_back(const T &value); - void push_back(T &&value); - template - void emplace_back(Args &&...args); - void truncate(std::size_t len); - void clear(); - - using iterator = typename Slice::iterator; - iterator begin() noexcept; - iterator end() noexcept; - - using const_iterator = typename Slice::iterator; - const_iterator begin() const noexcept; - const_iterator end() const noexcept; - const_iterator cbegin() const noexcept; - const_iterator cend() const noexcept; - - void swap(Vec &) noexcept; - - // Internal API only intended for the cxxbridge code generator. - Vec(unsafe_bitcopy_t, const Vec &) noexcept; + using value_type = T; + + Vec() noexcept; + Vec(std::initializer_list); + Vec(const Vec &); + Vec(Vec &&) noexcept; + ~Vec() noexcept; + + Vec &operator=(Vec &&) &noexcept; + Vec &operator=(const Vec &) &; + + std::size_t size() const noexcept; + bool empty() const noexcept; + const T *data() const noexcept; + T *data() noexcept; + std::size_t capacity() const noexcept; + + const T &operator[](std::size_t n) const noexcept; + const T &at(std::size_t n) const; + const T &front() const noexcept; + const T &back() const noexcept; + + T &operator[](std::size_t n) noexcept; + T &at(std::size_t n); + T &front() noexcept; + T &back() noexcept; + + void reserve(std::size_t new_cap); + void push_back(const T &value); + void push_back(T &&value); + template + void emplace_back(Args &&...args); + void truncate(std::size_t len); + void clear(); + + using iterator = typename Slice::iterator; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = typename Slice::iterator; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + void swap(Vec &) noexcept; + + // Internal API only intended for the cxxbridge code generator. + Vec(unsafe_bitcopy_t, const Vec &) noexcept; private: - void reserve_total(std::size_t new_cap) noexcept; - void set_len(std::size_t len) noexcept; - void drop() noexcept; + void reserve_total(std::size_t new_cap) noexcept; + void set_len(std::size_t len) noexcept; + void drop() noexcept; - friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); } + friend void swap(Vec &lhs, Vec &rhs) noexcept { lhs.swap(rhs); } - // Size and alignment statically verified by rust_vec.rs. - std::array repr; + // Size and alignment statically verified by rust_vec.rs. + std::array repr; }; -#endif // CXXBRIDGE1_RUST_VEC +#endif // CXXBRIDGE1_RUST_VEC #ifndef CXXBRIDGE1_RUST_FN // https://cxx.rs/binding/fn.html @@ -373,36 +372,36 @@ class Fn; template class Fn final { public: - Ret operator()(Args... args) const noexcept; - Fn operator*() const noexcept; + Ret operator()(Args... args) const noexcept; + Fn operator*() const noexcept; private: - Ret (*trampoline)(Args..., void *fn) noexcept; - void *fn; + Ret (*trampoline)(Args..., void *fn) noexcept; + void *fn; }; -#endif // CXXBRIDGE1_RUST_FN +#endif // CXXBRIDGE1_RUST_FN #ifndef CXXBRIDGE1_RUST_ERROR #define CXXBRIDGE1_RUST_ERROR // https://cxx.rs/binding/result.html class Error final : public std::exception { public: - Error(const Error &); - Error(Error &&) noexcept; - ~Error() noexcept override; + Error(const Error &); + Error(Error &&) noexcept; + ~Error() noexcept override; - Error &operator=(const Error &) &; - Error &operator=(Error &&) &noexcept; + Error &operator=(const Error &) &; + Error &operator=(Error &&) &noexcept; - const char *what() const noexcept override; + const char *what() const noexcept override; private: - Error() noexcept = default; - friend impl; - const char *msg; - std::size_t len; + Error() noexcept = default; + friend impl; + const char *msg; + std::size_t len; }; -#endif // CXXBRIDGE1_RUST_ERROR +#endif // CXXBRIDGE1_RUST_ERROR #ifndef CXXBRIDGE1_RUST_ISIZE #define CXXBRIDGE1_RUST_ISIZE @@ -411,7 +410,7 @@ using isize = SSIZE_T; #else using isize = ssize_t; #endif -#endif // CXXBRIDGE1_RUST_ISIZE +#endif // CXXBRIDGE1_RUST_ISIZE std::ostream &operator<<(std::ostream &, const String &); std::ostream &operator<<(std::ostream &, const Str &); @@ -421,11 +420,11 @@ std::ostream &operator<<(std::ostream &, const Str &); // Base class of generated opaque Rust types. class Opaque { public: - Opaque() = delete; - Opaque(const Opaque &) = delete; - ~Opaque() = delete; + Opaque() = delete; + Opaque(const Opaque &) = delete; + ~Opaque() = delete; }; -#endif // CXXBRIDGE1_RUST_OPAQUE +#endif // CXXBRIDGE1_RUST_OPAQUE template std::size_t size_of(); @@ -457,7 +456,7 @@ using u8 = std::uint8_t; using u16 = std::uint16_t; using u32 = std::uint32_t; using u64 = std::uint64_t; -using usize = std::size_t; // see static asserts in cxx.cc +using usize = std::size_t; // see static asserts in cxx.cc using i8 = std::int8_t; using i16 = std::int16_t; using i32 = std::int32_t; @@ -480,6 +479,8 @@ using fn = Fn; template using is_relocatable = IsRelocatable; + + //////////////////////////////////////////////////////////////////////////////// /// end public API, begin implementation details @@ -487,230 +488,230 @@ using is_relocatable = IsRelocatable; #define CXXBRIDGE1_PANIC template void panic [[noreturn]] (const char *msg); -#endif // CXXBRIDGE1_PANIC +#endif // CXXBRIDGE1_PANIC #ifndef CXXBRIDGE1_RUST_FN #define CXXBRIDGE1_RUST_FN template Ret Fn::operator()(Args... args) const noexcept { - return (*this->trampoline)(std::forward(args)..., this->fn); + return (*this->trampoline)(std::forward(args)..., this->fn); } template Fn Fn::operator*() const noexcept { - return *this; + return *this; } -#endif // CXXBRIDGE1_RUST_FN +#endif // CXXBRIDGE1_RUST_FN #ifndef CXXBRIDGE1_RUST_BITCOPY_T #define CXXBRIDGE1_RUST_BITCOPY_T struct unsafe_bitcopy_t final { - explicit unsafe_bitcopy_t() = default; + explicit unsafe_bitcopy_t() = default; }; -#endif // CXXBRIDGE1_RUST_BITCOPY_T +#endif // CXXBRIDGE1_RUST_BITCOPY_T #ifndef CXXBRIDGE1_RUST_BITCOPY #define CXXBRIDGE1_RUST_BITCOPY constexpr unsafe_bitcopy_t unsafe_bitcopy{}; -#endif // CXXBRIDGE1_RUST_BITCOPY +#endif // CXXBRIDGE1_RUST_BITCOPY #ifndef CXXBRIDGE1_RUST_SLICE #define CXXBRIDGE1_RUST_SLICE template Slice::Slice() noexcept { - sliceInit(this, reinterpret_cast(align_of()), 0); + sliceInit(this, reinterpret_cast(align_of()), 0); } template Slice::Slice(T *s, std::size_t count) noexcept { - assert(s != nullptr || count == 0); - sliceInit(this, - s == nullptr && count == 0 - ? reinterpret_cast(align_of()) - : const_cast::type *>(s), - count); + assert(s != nullptr || count == 0); + sliceInit(this, + s == nullptr && count == 0 + ? reinterpret_cast(align_of()) + : const_cast::type *>(s), + count); } template T *Slice::data() const noexcept { - return reinterpret_cast(slicePtr(this)); + return reinterpret_cast(slicePtr(this)); } template std::size_t Slice::size() const noexcept { - return sliceLen(this); + return sliceLen(this); } template std::size_t Slice::length() const noexcept { - return this->size(); + return this->size(); } template bool Slice::empty() const noexcept { - return this->size() == 0; + return this->size() == 0; } template T &Slice::operator[](std::size_t n) const noexcept { - assert(n < this->size()); - auto ptr = static_cast(slicePtr(this)) + size_of() * n; - return *reinterpret_cast(ptr); + assert(n < this->size()); + auto ptr = static_cast(slicePtr(this)) + size_of() * n; + return *reinterpret_cast(ptr); } template T &Slice::at(std::size_t n) const { - if (n >= this->size()) { - panic("rust::Slice index out of range"); - } - return (*this)[n]; + if (n >= this->size()) { + panic("rust::Slice index out of range"); + } + return (*this)[n]; } template T &Slice::front() const noexcept { - assert(!this->empty()); - return (*this)[0]; + assert(!this->empty()); + return (*this)[0]; } template T &Slice::back() const noexcept { - assert(!this->empty()); - return (*this)[this->size() - 1]; + assert(!this->empty()); + return (*this)[this->size() - 1]; } template -typename Slice::iterator::reference Slice::iterator::operator*() - const noexcept { - return *static_cast(this->pos); +typename Slice::iterator::reference +Slice::iterator::operator*() const noexcept { + return *static_cast(this->pos); } template -typename Slice::iterator::pointer Slice::iterator::operator->() - const noexcept { - return static_cast(this->pos); +typename Slice::iterator::pointer +Slice::iterator::operator->() const noexcept { + return static_cast(this->pos); } template typename Slice::iterator::reference Slice::iterator::operator[]( typename Slice::iterator::difference_type n) const noexcept { - auto ptr = static_cast(this->pos) + this->stride * n; - return *reinterpret_cast(ptr); + auto ptr = static_cast(this->pos) + this->stride * n; + return *reinterpret_cast(ptr); } template typename Slice::iterator &Slice::iterator::operator++() noexcept { - this->pos = static_cast(this->pos) + this->stride; - return *this; + this->pos = static_cast(this->pos) + this->stride; + return *this; } template typename Slice::iterator Slice::iterator::operator++(int) noexcept { - auto ret = iterator(*this); - this->pos = static_cast(this->pos) + this->stride; - return ret; + auto ret = iterator(*this); + this->pos = static_cast(this->pos) + this->stride; + return ret; } template typename Slice::iterator &Slice::iterator::operator--() noexcept { - this->pos = static_cast(this->pos) - this->stride; - return *this; + this->pos = static_cast(this->pos) - this->stride; + return *this; } template typename Slice::iterator Slice::iterator::operator--(int) noexcept { - auto ret = iterator(*this); - this->pos = static_cast(this->pos) - this->stride; - return ret; + auto ret = iterator(*this); + this->pos = static_cast(this->pos) - this->stride; + return ret; } template typename Slice::iterator &Slice::iterator::operator+=( typename Slice::iterator::difference_type n) noexcept { - this->pos = static_cast(this->pos) + this->stride * n; - return *this; + this->pos = static_cast(this->pos) + this->stride * n; + return *this; } template typename Slice::iterator &Slice::iterator::operator-=( typename Slice::iterator::difference_type n) noexcept { - this->pos = static_cast(this->pos) - this->stride * n; - return *this; + this->pos = static_cast(this->pos) - this->stride * n; + return *this; } template typename Slice::iterator Slice::iterator::operator+( typename Slice::iterator::difference_type n) const noexcept { - auto ret = iterator(*this); - ret.pos = static_cast(this->pos) + this->stride * n; - return ret; + auto ret = iterator(*this); + ret.pos = static_cast(this->pos) + this->stride * n; + return ret; } template typename Slice::iterator Slice::iterator::operator-( typename Slice::iterator::difference_type n) const noexcept { - auto ret = iterator(*this); - ret.pos = static_cast(this->pos) - this->stride * n; - return ret; + auto ret = iterator(*this); + ret.pos = static_cast(this->pos) - this->stride * n; + return ret; } template -typename Slice::iterator::difference_type Slice::iterator::operator-( - const iterator &other) const noexcept { - auto diff = std::distance(static_cast(other.pos), - static_cast(this->pos)); - return diff / this->stride; +typename Slice::iterator::difference_type +Slice::iterator::operator-(const iterator &other) const noexcept { + auto diff = std::distance(static_cast(other.pos), + static_cast(this->pos)); + return diff / this->stride; } template bool Slice::iterator::operator==(const iterator &other) const noexcept { - return this->pos == other.pos; + return this->pos == other.pos; } template bool Slice::iterator::operator!=(const iterator &other) const noexcept { - return this->pos != other.pos; + return this->pos != other.pos; } template bool Slice::iterator::operator<(const iterator &other) const noexcept { - return this->pos < other.pos; + return this->pos < other.pos; } template bool Slice::iterator::operator<=(const iterator &other) const noexcept { - return this->pos <= other.pos; + return this->pos <= other.pos; } template bool Slice::iterator::operator>(const iterator &other) const noexcept { - return this->pos > other.pos; + return this->pos > other.pos; } template bool Slice::iterator::operator>=(const iterator &other) const noexcept { - return this->pos >= other.pos; + return this->pos >= other.pos; } template typename Slice::iterator Slice::begin() const noexcept { - iterator it; - it.pos = slicePtr(this); - it.stride = size_of(); - return it; + iterator it; + it.pos = slicePtr(this); + it.stride = size_of(); + return it; } template typename Slice::iterator Slice::end() const noexcept { - iterator it = this->begin(); - it.pos = static_cast(it.pos) + it.stride * this->size(); - return it; + iterator it = this->begin(); + it.pos = static_cast(it.pos) + it.stride * this->size(); + return it; } template void Slice::swap(Slice &rhs) noexcept { - std::swap(*this, rhs); + std::swap(*this, rhs); } -#endif // CXXBRIDGE1_RUST_SLICE +#endif // CXXBRIDGE1_RUST_SLICE #ifndef CXXBRIDGE1_RUST_BOX #define CXXBRIDGE1_RUST_BOX @@ -719,287 +720,287 @@ class Box::uninit {}; template class Box::allocation { - static T *alloc() noexcept; - static void dealloc(T *) noexcept; + static T *alloc() noexcept; + static void dealloc(T *) noexcept; public: - allocation() noexcept : ptr(alloc()) {} - ~allocation() noexcept { - if (this->ptr) { - dealloc(this->ptr); - } + allocation() noexcept : ptr(alloc()) {} + ~allocation() noexcept { + if (this->ptr) { + dealloc(this->ptr); } - T *ptr; + } + T *ptr; }; template Box::Box(Box &&other) noexcept : ptr(other.ptr) { - other.ptr = nullptr; + other.ptr = nullptr; } template Box::Box(const T &val) { - allocation alloc; - ::new (alloc.ptr) T(val); - this->ptr = alloc.ptr; - alloc.ptr = nullptr; + allocation alloc; + ::new (alloc.ptr) T(val); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; } template Box::Box(T &&val) { - allocation alloc; - ::new (alloc.ptr) T(std::move(val)); - this->ptr = alloc.ptr; - alloc.ptr = nullptr; + allocation alloc; + ::new (alloc.ptr) T(std::move(val)); + this->ptr = alloc.ptr; + alloc.ptr = nullptr; } template Box::~Box() noexcept { - if (this->ptr) { - this->drop(); - } + if (this->ptr) { + this->drop(); + } } template Box &Box::operator=(Box &&other) &noexcept { - if (this->ptr) { - this->drop(); - } - this->ptr = other.ptr; - other.ptr = nullptr; - return *this; + if (this->ptr) { + this->drop(); + } + this->ptr = other.ptr; + other.ptr = nullptr; + return *this; } template const T *Box::operator->() const noexcept { - return this->ptr; + return this->ptr; } template const T &Box::operator*() const noexcept { - return *this->ptr; + return *this->ptr; } template T *Box::operator->() noexcept { - return this->ptr; + return this->ptr; } template T &Box::operator*() noexcept { - return *this->ptr; + return *this->ptr; } template template Box Box::in_place(Fields &&...fields) { - allocation alloc; - auto ptr = alloc.ptr; - ::new (ptr) T{std::forward(fields)...}; - alloc.ptr = nullptr; - return from_raw(ptr); + allocation alloc; + auto ptr = alloc.ptr; + ::new (ptr) T{std::forward(fields)...}; + alloc.ptr = nullptr; + return from_raw(ptr); } template void Box::swap(Box &rhs) noexcept { - using std::swap; - swap(this->ptr, rhs.ptr); + using std::swap; + swap(this->ptr, rhs.ptr); } template Box Box::from_raw(T *raw) noexcept { - Box box = uninit{}; - box.ptr = raw; - return box; + Box box = uninit{}; + box.ptr = raw; + return box; } template T *Box::into_raw() noexcept { - T *raw = this->ptr; - this->ptr = nullptr; - return raw; + T *raw = this->ptr; + this->ptr = nullptr; + return raw; } template Box::Box(uninit) noexcept {} -#endif // CXXBRIDGE1_RUST_BOX +#endif // CXXBRIDGE1_RUST_BOX #ifndef CXXBRIDGE1_RUST_VEC #define CXXBRIDGE1_RUST_VEC template Vec::Vec(std::initializer_list init) : Vec{} { - this->reserve_total(init.size()); - std::move(init.begin(), init.end(), std::back_inserter(*this)); + this->reserve_total(init.size()); + std::move(init.begin(), init.end(), std::back_inserter(*this)); } template Vec::Vec(const Vec &other) : Vec() { - this->reserve_total(other.size()); - std::copy(other.begin(), other.end(), std::back_inserter(*this)); + this->reserve_total(other.size()); + std::copy(other.begin(), other.end(), std::back_inserter(*this)); } template Vec::Vec(Vec &&other) noexcept : repr(other.repr) { - new (&other) Vec(); + new (&other) Vec(); } template Vec::~Vec() noexcept { - this->drop(); + this->drop(); } template Vec &Vec::operator=(Vec &&other) &noexcept { - this->drop(); - this->repr = other.repr; - new (&other) Vec(); - return *this; + this->drop(); + this->repr = other.repr; + new (&other) Vec(); + return *this; } template Vec &Vec::operator=(const Vec &other) & { - if (this != &other) { - this->drop(); - new (this) Vec(other); - } - return *this; + if (this != &other) { + this->drop(); + new (this) Vec(other); + } + return *this; } template bool Vec::empty() const noexcept { - return this->size() == 0; + return this->size() == 0; } template T *Vec::data() noexcept { - return const_cast(const_cast *>(this)->data()); + return const_cast(const_cast *>(this)->data()); } template const T &Vec::operator[](std::size_t n) const noexcept { - assert(n < this->size()); - auto data = reinterpret_cast(this->data()); - return *reinterpret_cast(data + n * size_of()); + assert(n < this->size()); + auto data = reinterpret_cast(this->data()); + return *reinterpret_cast(data + n * size_of()); } template const T &Vec::at(std::size_t n) const { - if (n >= this->size()) { - panic("rust::Vec index out of range"); - } - return (*this)[n]; + if (n >= this->size()) { + panic("rust::Vec index out of range"); + } + return (*this)[n]; } template const T &Vec::front() const noexcept { - assert(!this->empty()); - return (*this)[0]; + assert(!this->empty()); + return (*this)[0]; } template const T &Vec::back() const noexcept { - assert(!this->empty()); - return (*this)[this->size() - 1]; + assert(!this->empty()); + return (*this)[this->size() - 1]; } template T &Vec::operator[](std::size_t n) noexcept { - assert(n < this->size()); - auto data = reinterpret_cast(this->data()); - return *reinterpret_cast(data + n * size_of()); + assert(n < this->size()); + auto data = reinterpret_cast(this->data()); + return *reinterpret_cast(data + n * size_of()); } template T &Vec::at(std::size_t n) { - if (n >= this->size()) { - panic("rust::Vec index out of range"); - } - return (*this)[n]; + if (n >= this->size()) { + panic("rust::Vec index out of range"); + } + return (*this)[n]; } template T &Vec::front() noexcept { - assert(!this->empty()); - return (*this)[0]; + assert(!this->empty()); + return (*this)[0]; } template T &Vec::back() noexcept { - assert(!this->empty()); - return (*this)[this->size() - 1]; + assert(!this->empty()); + return (*this)[this->size() - 1]; } template void Vec::reserve(std::size_t new_cap) { - this->reserve_total(new_cap); + this->reserve_total(new_cap); } template void Vec::push_back(const T &value) { - this->emplace_back(value); + this->emplace_back(value); } template void Vec::push_back(T &&value) { - this->emplace_back(std::move(value)); + this->emplace_back(std::move(value)); } template template void Vec::emplace_back(Args &&...args) { - auto size = this->size(); - this->reserve_total(size + 1); - ::new (reinterpret_cast(reinterpret_cast(this->data()) + - size * size_of())) - T(std::forward(args)...); - this->set_len(size + 1); + auto size = this->size(); + this->reserve_total(size + 1); + ::new (reinterpret_cast(reinterpret_cast(this->data()) + + size * size_of())) + T(std::forward(args)...); + this->set_len(size + 1); } template void Vec::clear() { - this->truncate(0); + this->truncate(0); } template typename Vec::iterator Vec::begin() noexcept { - return Slice(this->data(), this->size()).begin(); + return Slice(this->data(), this->size()).begin(); } template typename Vec::iterator Vec::end() noexcept { - return Slice(this->data(), this->size()).end(); + return Slice(this->data(), this->size()).end(); } template typename Vec::const_iterator Vec::begin() const noexcept { - return this->cbegin(); + return this->cbegin(); } template typename Vec::const_iterator Vec::end() const noexcept { - return this->cend(); + return this->cend(); } template typename Vec::const_iterator Vec::cbegin() const noexcept { - return Slice(this->data(), this->size()).begin(); + return Slice(this->data(), this->size()).begin(); } template typename Vec::const_iterator Vec::cend() const noexcept { - return Slice(this->data(), this->size()).end(); + return Slice(this->data(), this->size()).end(); } template void Vec::swap(Vec &rhs) noexcept { - using std::swap; - swap(this->repr, rhs.repr); + using std::swap; + swap(this->repr, rhs.repr); } // Internal API only intended for the cxxbridge code generator. template Vec::Vec(unsafe_bitcopy_t, const Vec &bits) noexcept : repr(bits.repr) {} -#endif // CXXBRIDGE1_RUST_VEC +#endif // CXXBRIDGE1_RUST_VEC #ifndef CXXBRIDGE1_IS_COMPLETE #define CXXBRIDGE1_IS_COMPLETE @@ -1009,72 +1010,72 @@ template struct is_complete : std::false_type {}; template struct is_complete : std::true_type {}; -} // namespace -} // namespace detail -#endif // CXXBRIDGE1_IS_COMPLETE +} // namespace +} // namespace detail +#endif // CXXBRIDGE1_IS_COMPLETE #ifndef CXXBRIDGE1_LAYOUT #define CXXBRIDGE1_LAYOUT class layout { - template - friend std::size_t size_of(); - template - friend std::size_t align_of(); - template - static typename std::enable_if::value, - std::size_t>::type - do_size_of() { - return T::layout::size(); - } - template - static typename std::enable_if::value, - std::size_t>::type - do_size_of() { - return sizeof(T); - } - template - static typename std::enable_if::value, - std::size_t>::type - size_of() { - return do_size_of(); - } - template - static typename std::enable_if::value, - std::size_t>::type - do_align_of() { - return T::layout::align(); - } - template - static typename std::enable_if::value, - std::size_t>::type - do_align_of() { - return alignof(T); - } - template - static typename std::enable_if::value, - std::size_t>::type - align_of() { - return do_align_of(); - } + template + friend std::size_t size_of(); + template + friend std::size_t align_of(); + template + static typename std::enable_if::value, + std::size_t>::type + do_size_of() { + return T::layout::size(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_size_of() { + return sizeof(T); + } + template + static + typename std::enable_if::value, std::size_t>::type + size_of() { + return do_size_of(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_align_of() { + return T::layout::align(); + } + template + static typename std::enable_if::value, + std::size_t>::type + do_align_of() { + return alignof(T); + } + template + static + typename std::enable_if::value, std::size_t>::type + align_of() { + return do_align_of(); + } }; template std::size_t size_of() { - return layout::size_of(); + return layout::size_of(); } template std::size_t align_of() { - return layout::align_of(); + return layout::align_of(); } -#endif // CXXBRIDGE1_LAYOUT +#endif // CXXBRIDGE1_LAYOUT #ifndef CXXBRIDGE1_RELOCATABLE #define CXXBRIDGE1_RELOCATABLE namespace detail { template struct make_void { - using type = void; + using type = void; }; template @@ -1094,7 +1095,7 @@ using detect_IsRelocatable = typename T::IsRelocatable; template struct get_IsRelocatable : std::is_same {}; -} // namespace detail +} // namespace detail template struct IsRelocatable @@ -1104,7 +1105,7 @@ struct IsRelocatable std::integral_constant< bool, std::is_trivially_move_constructible::value && std::is_trivially_destructible::value>>::type {}; -#endif // CXXBRIDGE1_RELOCATABLE +#endif // CXXBRIDGE1_RELOCATABLE -} // namespace cxxbridge1 -} // namespace rust +} // namespace cxxbridge1 +} // namespace rust diff --git a/lib/cppbind/mmcore/include/mmcore/_cxxbridge.h b/lib/cppbind/mmcore/include/mmcore/_cxxbridge.h new file mode 100644 index 000000000..75d9e5d22 --- /dev/null +++ b/lib/cppbind/mmcore/include/mmcore/_cxxbridge.h @@ -0,0 +1,150 @@ +#pragma once +#include "mmcore/_cxx.h" +#include "mmcore/_symbol_export.h" +#include +#include +#include + +namespace rust { +inline namespace cxxbridge1 { +// #include "rust/cxx.h" + +struct unsafe_bitcopy_t; + +namespace { +template +class impl; +} // namespace + +#ifndef CXXBRIDGE1_RUST_STRING +#define CXXBRIDGE1_RUST_STRING +class String final { +public: + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + String(const std::string &); + String(const char *); + String(const char *, std::size_t); + String(const char16_t *); + String(const char16_t *, std::size_t); + + static String lossy(const std::string &) noexcept; + static String lossy(const char *) noexcept; + static String lossy(const char *, std::size_t) noexcept; + static String lossy(const char16_t *) noexcept; + static String lossy(const char16_t *, std::size_t) noexcept; + + String &operator=(const String &) &noexcept; + String &operator=(String &&) &noexcept; + + explicit operator std::string() const; + + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + const char *c_str() noexcept; + + std::size_t capacity() const noexcept; + void reserve(size_t new_cap) noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; + + String(unsafe_bitcopy_t, const String &) noexcept; + +private: + struct lossy_t; + String(lossy_t, const char *, std::size_t) noexcept; + String(lossy_t, const char16_t *, std::size_t) noexcept; + friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } + + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STRING + +#ifndef CXXBRIDGE1_RUST_STR +#define CXXBRIDGE1_RUST_STR +class Str final { +public: + Str() noexcept; + Str(const String &) noexcept; + Str(const std::string &); + Str(const char *); + Str(const char *, std::size_t); + + Str &operator=(const Str &) &noexcept = default; + + explicit operator std::string() const; + + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + Str(const Str &) noexcept = default; + ~Str() noexcept = default; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; + +private: + class uninit; + Str(uninit) noexcept; + friend impl; + + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STR +} // namespace cxxbridge1 +} // namespace rust + +namespace mmcore { + enum class DistortionDirection : ::std::uint8_t; +} + +namespace mmcore { +#ifndef CXXBRIDGE1_ENUM_mmcore$DistortionDirection +#define CXXBRIDGE1_ENUM_mmcore$DistortionDirection +enum class DistortionDirection : ::std::uint8_t { + kUndistort = 0, + kRedistort = 1, + kNumDistortionDirection = 2, +}; +#endif // CXXBRIDGE1_ENUM_mmcore$DistortionDirection + +MMCORE_API_EXPORT ::rust::String shim_expand_file_path_string(::rust::Str value, ::std::int32_t frame) noexcept; +} // namespace mmcore diff --git a/lib/cppbind/mmcore/include/mmcore/_types.h b/lib/cppbind/mmcore/include/mmcore/_types.h index ca5b63f87..0d14b3ed0 100644 --- a/lib/cppbind/mmcore/include/mmcore/_types.h +++ b/lib/cppbind/mmcore/include/mmcore/_types.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020, 2021, 2023 David Cattermole. + * Copyright (C) 2020, 2021, 2023, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -23,4 +23,8 @@ #include -namespace mmcore {} // namespace mmcore +namespace mmcore { + +using FrameValue = int32_t; + +} // namespace mmcore diff --git a/lib/cppbind/mmcore/include/mmcore/lib.h b/lib/cppbind/mmcore/include/mmcore/lib.h index 0bdd2bf25..546ea682b 100644 --- a/lib/cppbind/mmcore/include/mmcore/lib.h +++ b/lib/cppbind/mmcore/include/mmcore/lib.h @@ -26,6 +26,7 @@ #include #include "_cxx.h" +#include "_cxxbridge.h" #include "_symbol_export.h" #include "_types.h" #include "mmcamera.h" @@ -35,6 +36,10 @@ #include "mmhash.h" #include "mmmath.h" -namespace mmcore {} // namespace mmcore +namespace mmcore { + +rust::String expand_file_path_string(const rust::Str& value, FrameValue frame); + +} // namespace mmcore #endif // MM_CORE_LIB_H diff --git a/lib/cppbind/mmcore/include/mmcore/mmcore.h b/lib/cppbind/mmcore/include/mmcore/mmcore.h index d3a388538..e2aada631 100644 --- a/lib/cppbind/mmcore/include/mmcore/mmcore.h +++ b/lib/cppbind/mmcore/include/mmcore/mmcore.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 David Cattermole. + * Copyright (C) 2023, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -23,6 +23,7 @@ #define MM_CORE_MM_CORE_H #include "_cxx.h" +#include "_cxxbridge.h" #include "_types.h" #include "lib.h" #include "mmcamera.h" diff --git a/lib/cppbind/mmcore/src/_cxxbridge.cpp b/lib/cppbind/mmcore/src/_cxxbridge.cpp new file mode 100644 index 000000000..515d304e7 --- /dev/null +++ b/lib/cppbind/mmcore/src/_cxxbridge.cpp @@ -0,0 +1,180 @@ +#include "mmcore/_cxx.h" +#include "mmcore/_symbol_export.h" +#include +#include +#include +#include +#include +#include + +namespace rust { +inline namespace cxxbridge1 { +// #include "rust/cxx.h" + +struct unsafe_bitcopy_t; + +namespace { +template +class impl; +} // namespace + +#ifndef CXXBRIDGE1_RUST_STRING +#define CXXBRIDGE1_RUST_STRING +class String final { +public: + String() noexcept; + String(const String &) noexcept; + String(String &&) noexcept; + ~String() noexcept; + + String(const std::string &); + String(const char *); + String(const char *, std::size_t); + String(const char16_t *); + String(const char16_t *, std::size_t); + + static String lossy(const std::string &) noexcept; + static String lossy(const char *) noexcept; + static String lossy(const char *, std::size_t) noexcept; + static String lossy(const char16_t *) noexcept; + static String lossy(const char16_t *, std::size_t) noexcept; + + String &operator=(const String &) &noexcept; + String &operator=(String &&) &noexcept; + + explicit operator std::string() const; + + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + const char *c_str() noexcept; + + std::size_t capacity() const noexcept; + void reserve(size_t new_cap) noexcept; + + using iterator = char *; + iterator begin() noexcept; + iterator end() noexcept; + + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const String &) const noexcept; + bool operator!=(const String &) const noexcept; + bool operator<(const String &) const noexcept; + bool operator<=(const String &) const noexcept; + bool operator>(const String &) const noexcept; + bool operator>=(const String &) const noexcept; + + void swap(String &) noexcept; + + String(unsafe_bitcopy_t, const String &) noexcept; + +private: + struct lossy_t; + String(lossy_t, const char *, std::size_t) noexcept; + String(lossy_t, const char16_t *, std::size_t) noexcept; + friend void swap(String &lhs, String &rhs) noexcept { lhs.swap(rhs); } + + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STRING + +#ifndef CXXBRIDGE1_RUST_STR +#define CXXBRIDGE1_RUST_STR +class Str final { +public: + Str() noexcept; + Str(const String &) noexcept; + Str(const std::string &); + Str(const char *); + Str(const char *, std::size_t); + + Str &operator=(const Str &) &noexcept = default; + + explicit operator std::string() const; + + const char *data() const noexcept; + std::size_t size() const noexcept; + std::size_t length() const noexcept; + bool empty() const noexcept; + + Str(const Str &) noexcept = default; + ~Str() noexcept = default; + + using iterator = const char *; + using const_iterator = const char *; + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + bool operator==(const Str &) const noexcept; + bool operator!=(const Str &) const noexcept; + bool operator<(const Str &) const noexcept; + bool operator<=(const Str &) const noexcept; + bool operator>(const Str &) const noexcept; + bool operator>=(const Str &) const noexcept; + + void swap(Str &) noexcept; + +private: + class uninit; + Str(uninit) noexcept; + friend impl; + + std::array repr; +}; +#endif // CXXBRIDGE1_RUST_STR + +namespace detail { +template +struct operator_new { + void *operator()(::std::size_t sz) { return ::operator new(sz); } +}; + +template +struct operator_new { + void *operator()(::std::size_t sz) { return T::operator new(sz); } +}; +} // namespace detail + +template +union MaybeUninit { + T value; + void *operator new(::std::size_t sz) { return detail::operator_new{}(sz); } + MaybeUninit() {} + ~MaybeUninit() {} +}; +} // namespace cxxbridge1 +} // namespace rust + +namespace mmcore { + enum class DistortionDirection : ::std::uint8_t; +} + +namespace mmcore { +#ifndef CXXBRIDGE1_ENUM_mmcore$DistortionDirection +#define CXXBRIDGE1_ENUM_mmcore$DistortionDirection +enum class DistortionDirection : ::std::uint8_t { + kUndistort = 0, + kRedistort = 1, + kNumDistortionDirection = 2, +}; +#endif // CXXBRIDGE1_ENUM_mmcore$DistortionDirection + +extern "C" { +void mmcore$cxxbridge1$shim_expand_file_path_string(::rust::Str value, ::std::int32_t frame, ::rust::String *return$) noexcept; +} // extern "C" + +MMCORE_API_EXPORT ::rust::String shim_expand_file_path_string(::rust::Str value, ::std::int32_t frame) noexcept { + ::rust::MaybeUninit<::rust::String> return$; + mmcore$cxxbridge1$shim_expand_file_path_string(value, frame, &return$.value); + return ::std::move(return$.value); +} +} // namespace mmcore diff --git a/lib/cppbind/mmcore/src/cxxbridge.rs b/lib/cppbind/mmcore/src/cxxbridge.rs index 19ce90368..ed4c64e8c 100644 --- a/lib/cppbind/mmcore/src/cxxbridge.rs +++ b/lib/cppbind/mmcore/src/cxxbridge.rs @@ -1,5 +1,5 @@ // -// Copyright (C) 2023 David Cattermole. +// Copyright (C) 2023, 2024 David Cattermole. // // This file is part of mmSolver. // @@ -18,6 +18,8 @@ // ==================================================================== // +use crate::shim_expand_file_path_string; + #[cxx::bridge(namespace = "mmcore")] pub mod ffi { unsafe extern "C++" { @@ -38,4 +40,8 @@ pub mod ffi { #[cxx_name = "kNumDistortionDirection"] NumDistortionDirection, } + + extern "Rust" { + pub fn shim_expand_file_path_string(value: &str, frame: i32) -> String; + } } diff --git a/lib/cppbind/mmcore/src/lib.cpp b/lib/cppbind/mmcore/src/lib.cpp index c719927fd..78c0b07a4 100644 --- a/lib/cppbind/mmcore/src/lib.cpp +++ b/lib/cppbind/mmcore/src/lib.cpp @@ -21,4 +21,10 @@ #include -namespace mmcore {} // namespace mmcore +namespace mmcore { + +rust::String expand_file_path_string(const rust::Str& value, FrameValue frame) { + return shim_expand_file_path_string(value, frame); +} + +} // namespace mmcore diff --git a/lib/cppbind/mmcore/src/lib.rs b/lib/cppbind/mmcore/src/lib.rs index 463705b8e..1130a110a 100644 --- a/lib/cppbind/mmcore/src/lib.rs +++ b/lib/cppbind/mmcore/src/lib.rs @@ -19,3 +19,9 @@ // pub mod cxxbridge; + +use mmcore_rust::pathutils::expand_file_path_string as core_expand_file_path_string; + +pub fn shim_expand_file_path_string(value: &str, frame: i32) -> String { + core_expand_file_path_string(value, frame) +} diff --git a/lib/mmsolverlibs/Cargo.lock b/lib/mmsolverlibs/Cargo.lock index 761123186..59d883a3b 100644 --- a/lib/mmsolverlibs/Cargo.lock +++ b/lib/mmsolverlibs/Cargo.lock @@ -44,6 +44,23 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + [[package]] name = "bumpalo" version = "3.12.2" @@ -146,6 +163,27 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "either" version = "1.8.1" @@ -284,6 +322,16 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -322,6 +370,12 @@ dependencies = [ "rawpointer", ] +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + [[package]] name = "memoffset" version = "0.8.0" @@ -345,6 +399,15 @@ name = "mmcore_cppbind" version = "0.1.0" dependencies = [ "cxx", + "mmcore_rust", +] + +[[package]] +name = "mmcore_rust" +version = "0.1.0" +dependencies = [ + "log", + "shellexpand", ] [[package]] @@ -525,6 +588,21 @@ version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +dependencies = [ + "memchr", +] + [[package]] name = "paste" version = "1.0.12" @@ -664,6 +742,23 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom 0.2.9", + "libredox", + "thiserror", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -685,6 +780,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" + +[[package]] +name = "shellexpand" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +dependencies = [ + "bstr", + "dirs", + "os_str_bytes", +] + [[package]] name = "simba" version = "0.6.0" @@ -741,6 +853,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + [[package]] name = "typenum" version = "1.16.0" @@ -829,6 +961,72 @@ dependencies = [ "safe_arch", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/lib/mmsolverlibs/src/CMakeLists.txt b/lib/mmsolverlibs/src/CMakeLists.txt index fe638b5cb..1e072afa5 100644 --- a/lib/mmsolverlibs/src/CMakeLists.txt +++ b/lib/mmsolverlibs/src/CMakeLists.txt @@ -22,6 +22,8 @@ set(MMSOLVERLIBS_CXXBRIDGE_EXE "/path/to/cxxbridge/executable/cxxbridge" CACHE P "The path to the cxxbridge executable file.") set(main_include_dir ${CMAKE_CURRENT_SOURCE_DIR}/../include) +set(mmcolorio_include_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmcolorio/include) +set(mmcolorio_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmcolorio/src) set(mmcore_include_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmcore/include) set(mmcore_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmcore/src) set(mmimage_include_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmimage/include) @@ -33,6 +35,9 @@ set(mmscenegraph_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../cppbind/mmscenegra # C++ Source Code set(lib_source_files + ${mmcolorio_source_dir}/lib.cpp + + ${mmcore_source_dir}/_cxxbridge.cpp ${mmcore_source_dir}/lib.cpp ${mmcore_source_dir}/mmcamera.cpp ${mmcore_source_dir}/mmcoord.cpp @@ -41,8 +46,8 @@ set(lib_source_files ${mmcore_source_dir}/mmmath.cpp ${mmlens_source_dir}/_cxxbridge.cpp - ${mmlens_source_dir}/distortion_process.cpp ${mmlens_source_dir}/distortion_layers.cpp + ${mmlens_source_dir}/distortion_process.cpp ${mmlens_source_dir}/lens_model_3de_anamorphic_deg_4_rotate_squeeze_xy.cpp ${mmlens_source_dir}/lens_model_3de_anamorphic_deg_4_rotate_squeeze_xy_rescaled.cpp ${mmlens_source_dir}/lens_model_3de_classic.cpp @@ -57,11 +62,11 @@ set(lib_source_files ${mmscenegraph_source_dir}/_cxxbridge.cpp ${mmscenegraph_source_dir}/attrdatablock.cpp + ${mmscenegraph_source_dir}/evaluationobjects.cpp ${mmscenegraph_source_dir}/flatscene.cpp ${mmscenegraph_source_dir}/line.cpp ${mmscenegraph_source_dir}/scenebake.cpp ${mmscenegraph_source_dir}/scenegraph.cpp - ${mmscenegraph_source_dir}/evaluationobjects.cpp ) include(MMCommonUtils) @@ -97,8 +102,71 @@ target_link_libraries(${cpp_lib_name} # ldpk to be installed so we have the 'ldpk_INCLUDE_DIR' variable. add_dependencies(${cpp_lib_name} ldpk::ldpk) + +# OpenColorIO +message(STATUS "OpenColorIO_DIR: ${OpenColorIO_DIR}") +find_package(OpenColorIO REQUIRED) + +get_target_property(OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES OpenColorIO::OpenColorIO INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(OpenColorIO_IMPORTED_LOCATION_RELEASE OpenColorIO::OpenColorIO IMPORTED_LOCATION_RELEASE) +get_target_property(OpenColorIO_INTERFACE_LINK_LIBRARIES OpenColorIO::OpenColorIO INTERFACE_LINK_LIBRARIES) + +message(STATUS "OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES: ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES}") +message(STATUS "OpenColorIO_IMPORTED_LOCATION_RELEASE: ${OpenColorIO_IMPORTED_LOCATION_RELEASE}") +message(STATUS "OpenColorIO_INTERFACE_LINK_LIBRARIES: ${OpenColorIO_INTERFACE_LINK_LIBRARIES}") + +target_include_directories(${cpp_lib_name} + PRIVATE ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES} +) + +find_package(ZLIB REQUIRED) +message(STATUS "ZLIB: Found: ${ZLIB_FOUND}") +message(STATUS "ZLIB: Version: ${ZLIB_VERSION}") +message(STATUS "ZLIB: Libraries: ${ZLIB_LIBRARIES}") +message(STATUS "ZLIB: Include Dirs: ${ZLIB_INCLUDE_DIRS}") + +find_package(pystring REQUIRED) +message(STATUS "pystring: Found: ${pystring_FOUND}") +message(STATUS "pystring: Libraries: ${pystring_LIBRARY}") +message(STATUS "pystring: Include Dir: ${pystring_INCLUDE_DIR}") + +find_package(Imath REQUIRED) +message(STATUS "Imath: Found: ${Imath_FOUND}") +message(STATUS "Imath: Version: ${Imath_VERSION}") +message(STATUS "Imath: Libraries: ${Imath_LIBRARY}") +message(STATUS "Imath: Include Dir: ${Imath_INCLUDE_DIR}") + +find_package(expat REQUIRED) +message(STATUS "expat: Found: ${expat_FOUND}") +message(STATUS "expat: Version: ${expat_VERSION}") +message(STATUS "expat: Libraries: ${expat_LIBRARY}") +message(STATUS "expat: Include Dir: ${expat_INCLUDE_DIR}") + +find_package(minizip-ng REQUIRED) +message(STATUS "minizip-ng: Found: ${minizip-ng_FOUND}") +message(STATUS "minizip-ng: Version: ${minizip-ng_VERSION}") +message(STATUS "minizip-ng: Libraries: ${minizip-ng_LIBRARY}") +message(STATUS "minizip-ng: Include Dir: ${minizip-ng_INCLUDE_DIR}") + +find_package(yaml-cpp REQUIRED) +message(STATUS "yaml-cpp: Found: ${yaml-cpp_FOUND}") +message(STATUS "yaml-cpp: Version: ${yaml-cpp_VERSION}") +message(STATUS "yaml-cpp: Libraries: ${yaml-cpp_LIBRARY}") +message(STATUS "yaml-cpp: Include Dir: ${yaml-cpp_INCLUDE_DIR}") + +target_link_libraries(${cpp_lib_name} + PRIVATE OpenColorIO::OpenColorIO + PRIVATE ZLIB::ZLIB + PRIVATE expat::expat + PRIVATE Imath::Imath + PRIVATE pystring::pystring + PRIVATE yaml-cpp + PRIVATE MINIZIP::minizip-ng + ) + target_include_directories(${cpp_lib_name} PUBLIC $ + PUBLIC $ PUBLIC $ PUBLIC $ PUBLIC $ @@ -106,13 +174,14 @@ target_include_directories(${cpp_lib_name} PRIVATE $ PRIVATE $ PUBLIC $ -) + ) mm_common_install_target_library("mmsolverlibs_cpp" ${cpp_lib_name}) # Install public headers include(GNUInstallDirs) install(DIRECTORY + "${mmcolorio_include_dir}/" "${mmcore_include_dir}/" "${mmimage_include_dir}/" "${mmlens_include_dir}/" diff --git a/lib/rust/Cargo.toml b/lib/rust/Cargo.toml index 7ae7e42dc..67c9d2c9b 100644 --- a/lib/rust/Cargo.toml +++ b/lib/rust/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "mmcore", "mmimage", "mmscenegraph", ] diff --git a/lib/rust/mmcore/.gitignore b/lib/rust/mmcore/.gitignore new file mode 100644 index 000000000..06f52c05a --- /dev/null +++ b/lib/rust/mmcore/.gitignore @@ -0,0 +1,5 @@ +/target* +**/*.rs.bk +Cargo.lock +*~ +*.org diff --git a/lib/rust/mmcore/Cargo.toml b/lib/rust/mmcore/Cargo.toml new file mode 100644 index 000000000..73a21a2b4 --- /dev/null +++ b/lib/rust/mmcore/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "mmcore_rust" +version = "0.1.0" +authors = ["david-cattermole "] +edition = "2018" +publish = false + +[lib] +name = "mmcore_rust" +path = "./src/lib.rs" +crate_type = ["lib"] + +[dependencies] +log = "0.4.0" + +[dependencies.shellexpand] +version = "3.1" +default-features = false +features = ["full"] + +[profile.release] +opt-level = 3 +rpath = false +codegen-units = 1 + +# 'lto = true' Performs "fat" LTO which attempts to perform +# optimizations across all crates within the dependency graph. +lto = true + +# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. +# # https://github.com/rust-lang/cargo/issues/6313 +# +# panic = "abort" diff --git a/lib/rust/mmcore/src/lib.rs b/lib/rust/mmcore/src/lib.rs new file mode 100644 index 000000000..d1cfd1186 --- /dev/null +++ b/lib/rust/mmcore/src/lib.rs @@ -0,0 +1,21 @@ +// +// Copyright (C) 2024 David Cattermole. +// +// This file is part of mmSolver. +// +// mmSolver is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mmSolver is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with mmSolver. If not, see . +// ==================================================================== +// + +pub mod pathutils; diff --git a/lib/rust/mmcore/src/pathutils.rs b/lib/rust/mmcore/src/pathutils.rs new file mode 100644 index 000000000..656d9d24d --- /dev/null +++ b/lib/rust/mmcore/src/pathutils.rs @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2020, 2021, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +use log::{debug, warn}; +use shellexpand; + +struct FramePaddingIndexRange { + start_index: usize, + end_index: usize, + padding_count: usize, +} + +fn find_frame_padding_index_range(value: &String) -> FramePaddingIndexRange { + debug!("find_frame_padding_index: {}", value); + let mut start_index = 0; + let mut end_index = 0; + let mut last_char_valid = false; + for (i, byte) in value.bytes().enumerate() { + if byte == b'#' { + if last_char_valid == false { + // This is the first time we see a '#' character. + start_index = i; + end_index = i + 1; + } else { + // This might be the last time we see a '#' character. + end_index = i + 1; + } + last_char_valid = true; + } else { + if last_char_valid == true { + // The previous character was the last time we saw + // '#'. + break; + } + } + } + let padding_count = end_index - start_index; + FramePaddingIndexRange { + start_index, + end_index, + padding_count, + } +} + +fn create_expanded_string( + value: &String, + index_range: FramePaddingIndexRange, + frame: i32, +) -> String { + let mut expanded_string = value.clone(); + debug!( + "index: start={} end={} pad={}", + index_range.start_index, + index_range.end_index, + index_range.padding_count + ); + + // FIXME: Create a way to use the padding_count to dynamically + // change the number formatted into the string. "format!" is a + // compile time creation. This is a pretty bad hack. + let frame_string = match index_range.padding_count { + 0 => "".to_string(), + 1 => format!("{:01}", frame), + 2 => format!("{:02}", frame), + 3 => format!("{:03}", frame), + 4 => format!("{:04}", frame), + 5 => format!("{:05}", frame), + 6 => format!("{:06}", frame), + 7 => format!("{:07}", frame), + 8 => format!("{:08}", frame), + 9 => format!("{:09}", frame), + _ => "".to_string(), + }; + expanded_string.replace_range( + index_range.start_index..index_range.end_index, + &frame_string, + ); + expanded_string +} + +fn expand_file_path(value: &str) -> String { + let expanded_path = match shellexpand::full(value) { + Ok(v) => (*v).to_string(), + Err(e) => { + warn!( + "Environment variable unknown: name={} cause={}", + e.var_name, e.cause + ); + value.to_string() + } + }; + expanded_path.to_string() +} + +pub fn expand_file_path_string(value: &str, frame: i32) -> String { + debug!("expand_string: {} frame={}", value, frame); + let expanded_path = expand_file_path(&value); + let index_range = find_frame_padding_index_range(&expanded_path); + if index_range.padding_count > 0 { + create_expanded_string(&expanded_path, index_range, frame) + } else { + // No expansion needed. + expanded_path + } +} diff --git a/scripts/build_mmSolver_linux_maya2024.bash b/scripts/build_mmSolver_linux_maya2024.bash index c02b055ce..d48715c08 100644 --- a/scripts/build_mmSolver_linux_maya2024.bash +++ b/scripts/build_mmSolver_linux_maya2024.bash @@ -29,6 +29,11 @@ PYTHON_EXE=python3.9 CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_VERSION=2.4.1 + # Manually override OpenGL include headers, because CMake doesn't seem # to automatically find OpenGL headers on RockyLinux8 (which is used # in the Docker containers). @@ -45,6 +50,7 @@ CWD=`pwd` # These scripts assume 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_windows64_maya2024.bat b/scripts/build_mmSolver_windows64_maya2024.bat index dbd4e3814..c80b3a6dc 100644 --- a/scripts/build_mmSolver_windows64_maya2024.bat +++ b/scripts/build_mmSolver_windows64_maya2024.bat @@ -1,7 +1,7 @@ @ECHO OFF SETLOCAL :: -:: Copyright (C) 2019 David Cattermole. +:: Copyright (C) 2019, 2024 David Cattermole. :: :: This file is part of mmSolver. :: @@ -33,12 +33,20 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 +SET EXPAT_VERSION=2.4.1 + :: C++ Standard to use. SET CXX_STANDARD=14 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -48,6 +56,10 @@ CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/internal/build_mmSolverLibs_linux.bash b/scripts/internal/build_mmSolverLibs_linux.bash index 351f9251b..b5a7608bd 100644 --- a/scripts/internal/build_mmSolverLibs_linux.bash +++ b/scripts/internal/build_mmSolverLibs_linux.bash @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (C) 2022, 2023 David Cattermole. +# Copyright (C) 2022, 2023, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -60,6 +60,19 @@ MMSOLVERLIBS_CPP_TARGET_DIR="${BUILD_DIR_BASE}/build_mmsolverlibs/rust_linux_may MMSOLVERLIBS_LIB_DIR="${MMSOLVERLIBS_CPP_TARGET_DIR}/${BUILD_TYPE_DIR}" MMSOLVERLIBS_INCLUDE_DIR="${MMSOLVERLIBS_ROOT}/include" +# Paths for dependencies. +EXTERNAL_BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/cmake_linux_maya${MAYA_VERSION}_${BUILD_TYPE}/ext/dist" +OPENCOLORIO_INSTALL_DIR="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA_VERSION}_linux/" +OPENCOLORIO_CMAKE_CONFIG_DIR="${OPENCOLORIO_INSTALL_DIR}/lib64/cmake/OpenColorIO/" +ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/lib/libz.a" +ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +expat_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/expat-${EXPAT_VERSION}" +Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" +minizip_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/minizip-ng" +yaml_DIR="${EXTERNAL_BUILD_DIR}/share/cmake/yaml-cpp" +pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/lib64/libpystring.a" +pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" + MMSOLVERLIBS_BUILD_TESTS=1 echo "Building mmsolverlibs... (${MMSOLVERLIBS_ROOT})" @@ -83,7 +96,7 @@ then # './scripts/internal/build_mmSolverLibs_windows64.bat' ${RUST_CARGO_EXE} install cxxbridge-cmd --version 1.0.75 fi -MMSOLVERLIBS_CXXBRIDGE_EXE="${HOME}\.cargo\bin\cxxbridge" +MMSOLVERLIBS_CXXBRIDGE_EXE="${HOME}/.cargo/bin/cxxbridge" cd ${MMSOLVERLIBS_RUST_ROOT} ${RUST_CARGO_EXE} build ${RELEASE_FLAG} --target-dir ${MMSOLVERLIBS_CPP_TARGET_DIR} @@ -101,6 +114,7 @@ cd ${BUILD_DIR} export MAYA_VERSION=${MAYA_VERSION} ${CMAKE_EXE} \ + -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_INSTALL_PREFIX=${MMSOLVERLIBS_INSTALL_PATH} \ -DCMAKE_POSITION_INDEPENDENT_CODE=1 \ @@ -109,7 +123,17 @@ ${CMAKE_EXE} \ -DMMSOLVERLIBS_BUILD_TESTS=${MMSOLVERLIBS_BUILD_TESTS} \ -DMMSOLVERLIBS_LIB_DIR=${MMSOLVERLIBS_LIB_DIR} \ -Dldpk_URL=${LDPK_URL} \ - -DBUILD_SHARED_LIBS=OFF \ + -DOpenColorIO_DIR=${OPENCOLORIO_CMAKE_CONFIG_DIR} \ + -DOCIO_INSTALL_EXT_PACKAGES=NONE \ + -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ + -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ + -DZLIB_STATIC_LIBRARY=ON \ + -Dexpat_DIR=${expat_DIR} \ + -DImath_DIR=${Imath_DIR} \ + -Dminizip-ng_DIR=${minizip_DIR} \ + -Dpystring_LIBRARY=${pystring_LIBRARY} \ + -Dpystring_INCLUDE_DIR=${pystring_INCLUDE_DIR} \ + -Dyaml-cpp_DIR=${yaml_DIR} \ ${MMSOLVERLIBS_ROOT} ${CMAKE_EXE} --build . --parallel diff --git a/scripts/internal/build_mmSolverLibs_windows64.bat b/scripts/internal/build_mmSolverLibs_windows64.bat index c6d5302ac..ea194a469 100644 --- a/scripts/internal/build_mmSolverLibs_windows64.bat +++ b/scripts/internal/build_mmSolverLibs_windows64.bat @@ -1,6 +1,6 @@ @ECHO OFF :: -:: Copyright (C) 2022, 2023 David Cattermole. +:: Copyright (C) 2022, 2023, 2024 David Cattermole. :: :: This file is part of mmSolver. :: @@ -18,7 +18,7 @@ :: along with mmSolver. If not, see . :: --------------------------------------------------------------------- :: -:: Build the mmscenegraph library. +:: Build the mmSolverLibs library. :: :: NOTE: Do not call this script directly! This file should be called by :: the build_mmSolver_windows64_mayaXXXX.bat files. @@ -47,12 +47,25 @@ IF "%BUILD_TYPE%"=="Release" ( SET MMSOLVERLIBS_INSTALL_PATH=%BUILD_DIR_BASE%\build_mmsolverlibs\install\maya%MAYA_VERSION%_windows64\ SET MMSOLVERLIBS_ROOT=%PROJECT_ROOT%\lib SET MMSOLVERLIBS_RUST_ROOT=%MMSOLVERLIBS_ROOT%\mmsolverlibs -SET MMSOLVERLIBS_CPP_TARGET_DIR="%BUILD_DIR_BASE%\build_mmsolverlibs\rust_windows64_maya%MAYA_VERSION%" -SET MMSOLVERLIBS_LIB_DIR="%MMSOLVERLIBS_CPP_TARGET_DIR%\%BUILD_TYPE_DIR%" -SET MMSOLVERLIBS_INCLUDE_DIR="%MMSOLVERLIBS_ROOT%\include" +SET MMSOLVERLIBS_CPP_TARGET_DIR=%BUILD_DIR_BASE%\build_mmsolverlibs\rust_windows64_maya%MAYA_VERSION% +SET MMSOLVERLIBS_LIB_DIR=%MMSOLVERLIBS_CPP_TARGET_DIR%\%BUILD_TYPE_DIR% +SET MMSOLVERLIBS_INCLUDE_DIR=%MMSOLVERLIBS_ROOT%\include SET MMSOLVERLIBS_BUILD_TESTS=1 +:: Paths for dependencies. +SET EXTERNAL_BUILD_DIR=%BUILD_DIR_BASE%\build_opencolorio\cmake_win64_maya%MAYA_VERSION%_%BUILD_TYPE%\ext\dist +SET OPENCOLORIO_INSTALL_DIR=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAYA_VERSION%_windows64\ +SET OPENCOLORIO_CMAKE_CONFIG_DIR=%OPENCOLORIO_INSTALL_DIR%\lib\cmake\OpenColorIO\ +SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath +SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\zlibstatic.lib +SET expat_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\expat-%EXPAT_VERSION% +SET minizip_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\minizip-ng +SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include +SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\pystring.lib +SET yaml_DIR=%EXTERNAL_BUILD_DIR%\share\cmake\yaml-cpp + ECHO Building mmsolverlibs... (%MMSOLVERLIBS_ROOT%) :: Check if 'cxxbridge.exe' is installed or not, and then install it if @@ -121,6 +134,7 @@ MKDIR "%BUILD_DIR_NAME%" CHDIR "%BUILD_DIR%" %CMAKE_EXE% -G %CMAKE_GENERATOR% ^ + -DBUILD_SHARED_LIBS=OFF ^ -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^ -DCMAKE_INSTALL_PREFIX=%MMSOLVERLIBS_INSTALL_PATH% ^ -DCMAKE_IGNORE_PATH=%IGNORE_INCLUDE_DIRECTORIES% ^ @@ -131,7 +145,17 @@ CHDIR "%BUILD_DIR%" -DMMSOLVERLIBS_BUILD_TESTS=%MMSOLVERLIBS_BUILD_TESTS% ^ -DMMSOLVERLIBS_LIB_DIR=%MMSOLVERLIBS_LIB_DIR% ^ -Dldpk_URL=%LDPK_URL% ^ - -DBUILD_SHARED_LIBS=OFF ^ + -DOpenColorIO_DIR=%OPENCOLORIO_CMAKE_CONFIG_DIR% ^ + -DOCIO_INSTALL_EXT_PACKAGES=NONE ^ + -DZLIB_LIBRARY=%ZLIB_LIBRARY% ^ + -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ + -DZLIB_STATIC_LIBRARY=ON ^ + -Dexpat_DIR=%expat_DIR% ^ + -DImath_DIR=%Imath_DIR% ^ + -Dminizip-ng_DIR=%minizip_DIR% ^ + -Dpystring_LIBRARY=%pystring_LIBRARY% ^ + -Dpystring_INCLUDE_DIR=%pystring_INCLUDE_DIR% ^ + -Dyaml-cpp_DIR=%yaml_DIR% ^ %MMSOLVERLIBS_ROOT% if errorlevel 1 goto failed_to_generate_cpp diff --git a/scripts/internal/build_mmSolver_linux.bash b/scripts/internal/build_mmSolver_linux.bash index 9a91b4762..7aa853637 100644 --- a/scripts/internal/build_mmSolver_linux.bash +++ b/scripts/internal/build_mmSolver_linux.bash @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Copyright (C) 2019, 2022 David Cattermole. +# Copyright (C) 2019, 2022, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -52,7 +52,7 @@ INSTALL_MODULE_DIR="${HOME}/maya/${MAYA_VERSION}/modules" # Build ZIP Package. # For developer use. Make ZIP packages ready to distribute to others. -BUILD_PACKAGE=1 +BUILD_PACKAGE=0 # What directory to build the project in? BUILD_DIR_BASE="${PROJECT_ROOT}/../" @@ -86,11 +86,24 @@ echo "Project Root: ${PROJECT_ROOT}" PYTHON_VIRTUAL_ENV_DIR_NAME="python_venv_linux_maya${MAYA_VERSION}" source "${PROJECT_ROOT}/scripts/internal/python_venv_activate.bash" -# Paths for dependencies. +# Where to find the mmsolverlibs Rust libraries and headers. MMSOLVERLIBS_INSTALL_DIR="${BUILD_DIR_BASE}/build_mmsolverlibs/install/maya${MAYA_VERSION}_linux/" MMSOLVERLIBS_CMAKE_CONFIG_DIR="${MMSOLVERLIBS_INSTALL_DIR}/lib64/cmake/mmsolverlibs_cpp" MMSOLVERLIBS_RUST_DIR="${BUILD_DIR_BASE}/build_mmsolverlibs/rust_linux_maya${MAYA_VERSION}/${BUILD_TYPE_DIR}" +# Paths for dependencies. +EXTERNAL_BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/cmake_linux_maya${MAYA_VERSION}_${BUILD_TYPE}/ext/dist" +OPENCOLORIO_INSTALL_DIR="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA_VERSION}_linux/" +OPENCOLORIO_CMAKE_CONFIG_DIR="${OPENCOLORIO_INSTALL_DIR}/lib64/cmake/OpenColorIO/" +ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/lib/libz.a" +ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +expat_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/expat-${EXPAT_VERSION}" +Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" +minizip_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/minizip-ng" +yaml_DIR="${EXTERNAL_BUILD_DIR}/share/cmake/yaml-cpp" +pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/lib64/libpystring.a" +pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" + # We don't want to find system packages. CMAKE_IGNORE_PATH="/lib;/lib64;/usr;/usr/lib;/usr/lib64;/usr/local;/usr/local/lib;/usr/local/lib64;" @@ -126,6 +139,17 @@ ${CMAKE_EXE} \ -DMAYA_VERSION=${MAYA_VERSION} \ -Dmmsolverlibs_rust_DIR=${MMSOLVERLIBS_RUST_DIR} \ -Dmmsolverlibs_cpp_DIR=${MMSOLVERLIBS_CMAKE_CONFIG_DIR} \ + -DOpenColorIO_DIR=${OPENCOLORIO_CMAKE_CONFIG_DIR} \ + -DOCIO_INSTALL_EXT_PACKAGES=NONE \ + -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ + -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ + -DZLIB_STATIC_LIBRARY=ON \ + -Dexpat_DIR=${expat_DIR} \ + -DImath_DIR=${Imath_DIR} \ + -Dminizip-ng_DIR=${minizip_DIR} \ + -Dpystring_LIBRARY=${pystring_LIBRARY} \ + -Dpystring_INCLUDE_DIR=${pystring_INCLUDE_DIR} \ + -Dyaml-cpp_DIR=${yaml_DIR} \ ${PROJECT_ROOT} ${CMAKE_EXE} --build . --parallel diff --git a/scripts/internal/build_mmSolver_windows64.bat b/scripts/internal/build_mmSolver_windows64.bat index 62fa41203..74e63b827 100644 --- a/scripts/internal/build_mmSolver_windows64.bat +++ b/scripts/internal/build_mmSolver_windows64.bat @@ -1,7 +1,7 @@ @ECHO OFF SETLOCAL :: -:: Copyright (C) 2019 David Cattermole. +:: Copyright (C) 2019, 2024 David Cattermole. :: :: This file is part of mmSolver. :: @@ -52,7 +52,7 @@ SET INSTALL_MODULE_DIR="%USERPROFILE%\My Documents\maya\%MAYA_VERSION%\modules" :: Build ZIP Package. :: For developer use. Make ZIP packages ready to distribute to others. -SET BUILD_PACKAGE=1 +SET BUILD_PACKAGE=0 :: Do not edit below, unless you know what you're doing. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: @@ -88,6 +88,18 @@ SET MMSOLVERLIBS_INSTALL_DIR="%BUILD_DIR_BASE%\build_mmsolverlibs\install\maya%M SET MMSOLVERLIBS_CMAKE_CONFIG_DIR="%MMSOLVERLIBS_INSTALL_DIR%\lib\cmake\mmsolverlibs_cpp" SET MMSOLVERLIBS_RUST_DIR="%BUILD_DIR_BASE%\build_mmsolverlibs\rust_windows64_maya%MAYA_VERSION%\%BUILD_TYPE_DIR%" +SET EXTERNAL_BUILD_DIR=%BUILD_DIR_BASE%\build_opencolorio\cmake_win64_maya%MAYA_VERSION%_%BUILD_TYPE%\ext\dist +SET OPENCOLORIO_INSTALL_DIR=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAYA_VERSION%_windows64\ +SET OPENCOLORIO_CMAKE_CONFIG_DIR=%OPENCOLORIO_INSTALL_DIR%\lib\cmake\OpenColorIO\ +SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath +SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\zlibstatic.lib +SET expat_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\expat-%EXPAT_VERSION% +SET minizip_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\minizip-ng +SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include +SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\pystring.lib +SET yaml_DIR=%EXTERNAL_BUILD_DIR%\share\cmake\yaml-cpp + :: MinGW is a common install for developers on Windows and :: if installed and used it will cause build conflicts and :: errors, so we disable it. @@ -141,6 +153,17 @@ CHDIR "%BUILD_DIR%" -DMAYA_VERSION=%MAYA_VERSION% ^ -Dmmsolverlibs_rust_DIR=%MMSOLVERLIBS_RUST_DIR% ^ -Dmmsolverlibs_cpp_DIR=%MMSOLVERLIBS_CMAKE_CONFIG_DIR% ^ + -DOpenColorIO_DIR=%OPENCOLORIO_CMAKE_CONFIG_DIR% ^ + -DOCIO_INSTALL_EXT_PACKAGES=NONE ^ + -DZLIB_LIBRARY=%ZLIB_LIBRARY% ^ + -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ + -DZLIB_STATIC_LIBRARY=ON ^ + -Dexpat_DIR=%expat_DIR% ^ + -DImath_DIR=%Imath_DIR% ^ + -Dminizip-ng_DIR=%minizip_DIR% ^ + -Dpystring_LIBRARY=%pystring_LIBRARY% ^ + -Dpystring_INCLUDE_DIR=%pystring_INCLUDE_DIR% ^ + -Dyaml-cpp_DIR=%yaml_DIR% ^ %PROJECT_ROOT% if errorlevel 1 goto failed_to_generate diff --git a/scripts/internal/build_openColorIO_linux.bash b/scripts/internal/build_openColorIO_linux.bash new file mode 100644 index 000000000..30ca89e94 --- /dev/null +++ b/scripts/internal/build_openColorIO_linux.bash @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2019, 2022, 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# --------------------------------------------------------------------- +# +# Builds the OpenColorIO project. +# +# By default the minimal build is performed that will give us the +# library. This is not a "full" build with all features, bindings and +# programs. +# +# A static library is also preferred, to avoid shipping shared/dynamic +# libraries with mmSolver (which is a pain when dealing with a +# third-party studio's environment - it's easiest to embed everything +# and be done with it). +# +# This script is assumed to be called with a number of variables +# already set: +# +# - MAYA_VERSION +# - MAYA_LOCATION +# - PYTHON_EXE +# - CMAKE_EXE +# - CXX_STANDARD +# - OPENCOLORIO_TARBALL_NAME +# - OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME + +# The -e flag causes the script to exit as soon as one command returns +# a non-zero exit code. +set -ev + +# Store the current working directory, to return to. +CWD=`pwd` + +# What directory to build the project in? +BUILD_DIR_BASE="${PROJECT_ROOT}/../" + +# Install directory. +OPENCOLORIO_INSTALL_PATH="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA_VERSION}_linux/" + +# What type of build? "Release" or "Debug"? +BUILD_TYPE=Release + +# Path to this script. +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +# The root of this project. +PROJECT_ROOT=`readlink -f ${DIR}/../..` +echo "Project Root: ${PROJECT_ROOT}" + +# Make sure source code archive is downloaded and exists. +SOURCE_TARBALL="${PROJECT_ROOT}/external/archives/${OPENCOLORIO_TARBALL_NAME}" +if [ ! -f "${SOURCE_TARBALL}" ]; then + echo "${SOURCE_TARBALL} does not exist." + echo "Please download the tar.gz file from https://github.com/AcademySoftwareFoundation/OpenColorIO/releases" + exit 1 +fi + +EXTRACT_OUT_DIR="${PROJECT_ROOT}/external/working/maya${MAYA_VERSION}_linux" +if [ ! -d "${EXTRACT_OUT_DIR}" ]; then + echo "${EXTRACT_OUT_DIR} does not exist, creating it..." + mkdir -p "${EXTRACT_OUT_DIR}" +fi +SOURCE_ROOT="${EXTRACT_OUT_DIR}/${OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME}/" +if [ ! -d "${SOURCE_ROOT}" ]; then + echo "${SOURCE_ROOT} does not exist, extracting tarball..." + # NOTE: Uses CMake to mirror the Windows build script. 'tar' is + # unlikely to be available on Windows. + ${CMAKE_EXE} -E chdir ${EXTRACT_OUT_DIR} tar xf ${SOURCE_TARBALL} +fi + +# We don't want to find system packages. +CMAKE_IGNORE_PATH="/lib;/lib64;/usr;/usr/lib;/usr/lib64;/usr/local;/usr/local/lib;/usr/local/lib64;" + +# Build OpenColorIO project +cd ${BUILD_DIR_BASE} +BUILD_DIR_NAME="cmake_linux_maya${MAYA_VERSION}_${BUILD_TYPE}" +BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/${BUILD_DIR_NAME}" +mkdir -p ${BUILD_DIR} +cd ${BUILD_DIR} + +${CMAKE_EXE} \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -DCMAKE_INSTALL_PREFIX=${OPENCOLORIO_INSTALL_PATH} \ + -DCMAKE_IGNORE_PATH=${CMAKE_IGNORE_PATH} \ + -DCMAKE_POSITION_INDEPENDENT_CODE=1 \ + -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ + -DOCIO_INSTALL_EXT_PACKAGES=ALL \ + -DOCIO_BUILD_APPS=OFF \ + -DOCIO_USE_OIIO_FOR_APPS=OFF \ + -DOCIO_BUILD_TESTS=OFF \ + -DOCIO_BUILD_GPU_TESTS=OFF \ + -DOCIO_BUILD_DOCS=OFF \ + -DOCIO_BUILD_FROZEN_DOCS=OFF \ + -DOCIO_BUILD_PYTHON=OFF \ + -DOCIO_BUILD_OPENFX=OFF \ + ${SOURCE_ROOT} + +${CMAKE_EXE} --build . --parallel +${CMAKE_EXE} --install . + +# Return back project root directory. +cd ${CWD} diff --git a/scripts/internal/build_openColorIO_windows64.bat b/scripts/internal/build_openColorIO_windows64.bat new file mode 100644 index 000000000..ca0ea34a4 --- /dev/null +++ b/scripts/internal/build_openColorIO_windows64.bat @@ -0,0 +1,161 @@ +@ECHO OFF +:: +:: Copyright (C) 2022, 2023, 2024 David Cattermole. +:: +:: This file is part of mmSolver. +:: +:: mmSolver is free software: you can redistribute it and/or modify it +:: under the terms of the GNU Lesser General Public License as +:: published by the Free Software Foundation, either version 3 of the +:: License, or (at your option) any later version. +:: +:: mmSolver is distributed in the hope that it will be useful, +:: but WITHOUT ANY WARRANTY; without even the implied warranty of +:: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +:: GNU Lesser General Public License for more details. +:: +:: You should have received a copy of the GNU Lesser General Public License +:: along with mmSolver. If not, see . +:: --------------------------------------------------------------------- +:: +:: Builds the OpenColorIO project. +:: +:: By default the minimal build is performed that will give us the +:: library. This is not a "full" build with all features, bindings and +:: programs. +:: +:: A static library is also preferred, to avoid shipping shared/dynamic +:: libraries with mmSolver (which is a pain when dealing with a +:: third-party studio's environment - it's easiest to embed everything +:: and be done with it). +:: +:: This script is assumed to be called with a number of variables +:: already set: +:: +:: - MAYA_VERSION +:: - MAYA_LOCATION +:: - PYTHON_EXE +:: - CMAKE_EXE +:: - CXX_STANDARD +:: - OPENCOLORIO_TARBALL_NAME +:: - OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME +:: +:: NOTE: Do not call this script directly! This file should be called by +:: the build_mmSolver_windows64_mayaXXXX.bat files. + +:: The root of this project. +SET PROJECT_ROOT=%CD% +ECHO Project Root: %PROJECT_ROOT% + +:: What directory to build the project in? +SET BUILD_DIR_BASE=%PROJECT_ROOT%\.. + +:: Install directory. +SET OPENCOLORIO_INSTALL_PATH=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAYA_VERSION%_windows64\ + +:: What type of build? "Release" or "Debug"? +SET BUILD_TYPE=Release + +:: Make sure source code archive is downloaded and exists. +SET SOURCE_TARBALL=%PROJECT_ROOT%\external\archives\%OPENCOLORIO_TARBALL_NAME% +IF NOT EXIST %SOURCE_TARBALL% ( + ECHO %SOURCE_TARBALL% does not exist. + ECHO Please download the tar.gz file from https://github.com/AcademySoftwareFoundation/OpenColorIO/releases + EXIT /b 1 +) + +SET EXTRACT_OUT_DIR=%PROJECT_ROOT%\external\working\maya%MAYA_VERSION%_windows64 +IF NOT EXIST %EXTRACT_OUT_DIR% ( + ECHO %EXTRACT_OUT_DIR% does not exist, creating it... + MKDIR %EXTRACT_OUT_DIR% +) +SET SOURCE_ROOT=%EXTRACT_OUT_DIR%\%OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME%\ +IF NOT EXIST %SOURCE_ROOT% ( + ECHO %SOURCE_ROOT% does not exist, extracting tarball... + :: The 'tar' command is unlikely to be available on Windows, so we + :: use CMake because we know it has 'tar'. + %CMAKE_EXE% -E chdir %EXTRACT_OUT_DIR% tar xf %SOURCE_TARBALL% +) + + +ECHO Building opencolorio... (%SOURCE_ROOT%) + +:: MinGW is a common install for developers on Windows and +:: if installed and used it will cause build conflicts and +:: errors, so we disable it. +SET IGNORE_INCLUDE_DIRECTORIES="" +IF EXIST "C:\MinGW" ( + SET IGNORE_INCLUDE_DIRECTORIES="C:\MinGW\bin;C:\MinGW\include" +) + +:: Optionally use "NMake Makefiles" as the build system generator. +SET CMAKE_GENERATOR=Ninja + +:: Force the compilier to be MSVC's cl.exe, so that if other +:: compiliers are installed, CMake doesn't get confused and try to use +:: it (such as clang). +SET CMAKE_C_COMPILER=cl +SET CMAKE_CXX_COMPILER=cl + +:: Build OpenColorIO project +SET BUILD_DIR_NAME=cmake_win64_maya%MAYA_VERSION%_%BUILD_TYPE% +SET BUILD_DIR=%BUILD_DIR_BASE%\build_opencolorio\%BUILD_DIR_NAME% +ECHO BUILD_DIR_BASE: %BUILD_DIR_BASE% +ECHO BUILD_DIR_NAME: %BUILD_DIR_NAME% +ECHO BUILD_DIR: %BUILD_DIR% +CHDIR "%BUILD_DIR_BASE%" +MKDIR "build_opencolorio" +CHDIR "%BUILD_DIR_BASE%\build_opencolorio\" +MKDIR "%BUILD_DIR_NAME%" +CHDIR "%BUILD_DIR%" + +%CMAKE_EXE% -G %CMAKE_GENERATOR% ^ + -DBUILD_SHARED_LIBS=OFF ^ + -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^ + -DCMAKE_INSTALL_PREFIX=%OPENCOLORIO_INSTALL_PATH% ^ + -DCMAKE_IGNORE_PATH=%IGNORE_INCLUDE_DIRECTORIES% ^ + -DCMAKE_C_COMPILER=%CMAKE_C_COMPILER% ^ + -DCMAKE_CXX_COMPILER=%CMAKE_CXX_COMPILER% ^ + -DCMAKE_CXX_STANDARD=%CXX_STANDARD% ^ + -DOCIO_INSTALL_EXT_PACKAGES=ALL ^ + -DOCIO_BUILD_APPS=OFF ^ + -DOCIO_USE_OIIO_FOR_APPS=OFF ^ + -DOCIO_BUILD_TESTS=OFF ^ + -DOCIO_BUILD_GPU_TESTS=OFF ^ + -DOCIO_BUILD_DOCS=OFF ^ + -DOCIO_BUILD_FROZEN_DOCS=OFF ^ + -DOCIO_BUILD_PYTHON=OFF ^ + -DOCIO_BUILD_OPENFX=OFF ^ + -DOCIO_USE_SSE=ON ^ + %SOURCE_ROOT% +IF errorlevel 1 GOTO failed_to_generate_cpp + +%CMAKE_EXE% --build . --parallel +IF errorlevel 1 GOTO failed_to_build_cpp + +%CMAKE_EXE% --install . +IF errorlevel 1 GOTO failed_to_install_cpp + +:: Return back project root directory. +CHDIR "%PROJECT_ROOT%" +EXIT /b 0 + +:failed_to_generate_cpp_header +ECHO Failed to Generate C++ header files from Rust. +EXIT /b 1 + +:failed_to_build_rust +ECHO Failed to build Rust code. +EXIT /b 1 + +:failed_to_generate_cpp +ECHO Failed to generate C++ build files. +EXIT /b 1 + +:failed_to_build_cpp +ECHO Failed to build C++ code. +EXIT /b 1 + +:failed_to_install_cpp +ECHO Failed to install C++ artifacts. +EXIT /b 1 diff --git a/share/shader/mmImagePlane.ogsfx b/share/shader/mmImagePlane.ogsfx index 368e05ffb..5c8cca55b 100644 --- a/share/shader/mmImagePlane.ogsfx +++ b/share/shader/mmImagePlane.ogsfx @@ -1,3 +1,4 @@ +#version 400 // Copyright (C) 2021, 2023, 2024 David Cattermole. // // This file is part of mmSolver. @@ -93,8 +94,13 @@ attribute PIXEL_DATA { } GLSLShader PS_mmImagePlane_Main { + + // The OCIO function will be replaced and injected at runtime into + // this line. This function is used as a stand-in. + vec4 OCIODisplay(vec4 passthrough) { return passthrough; } + void main() { - vec4 texture_color = texture2D(gImageTextureSampler, PS_INPUT.UV); + vec4 texture_color = OCIODisplay(texture2D(gImageTextureSampler, PS_INPUT.UV)); if (gIgnoreAlpha) { texture_color.a = 1.0f; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 922147c16..dd1d18630 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,40 +29,41 @@ set(SOURCE_FILES mmSolver/adjust/adjust_cminpack_base.cpp mmSolver/adjust/adjust_cminpack_lmder.cpp mmSolver/adjust/adjust_cminpack_lmdif.cpp + mmSolver/adjust/adjust_measureErrors.cpp mmSolver/adjust/adjust_relationships.cpp mmSolver/adjust/adjust_results_helpers.cpp mmSolver/adjust/adjust_results_setMarkerData.cpp mmSolver/adjust/adjust_results_setSolveData.cpp mmSolver/adjust/adjust_setParameters.cpp - mmSolver/adjust/adjust_measureErrors.cpp mmSolver/adjust/adjust_solveFunc.cpp mmSolver/calibrate/calibrate_common.cpp mmSolver/calibrate/vanishing_point.cpp - mmSolver/cmd/arg_flags_attr_details.cpp - mmSolver/cmd/arg_flags_attr_details.h - mmSolver/cmd/arg_flags_solve_frames.cpp - mmSolver/cmd/arg_flags_solve_frames.h - mmSolver/cmd/arg_flags_solve_info.cpp - mmSolver/cmd/arg_flags_solve_info.h - mmSolver/cmd/arg_flags_solve_log.cpp - mmSolver/cmd/arg_flags_solve_log.h - mmSolver/cmd/arg_flags_solve_object.cpp - mmSolver/cmd/arg_flags_solve_object.h - mmSolver/cmd/arg_flags_solve_scene_graph.cpp - mmSolver/cmd/arg_flags_solve_scene_graph.h mmSolver/cmd/MMCameraPoseFromPointsCmd.cpp mmSolver/cmd/MMCameraRelativePoseCmd.cpp mmSolver/cmd/MMCameraSolveCmd.cpp + mmSolver/cmd/MMColorIOCmd.cpp mmSolver/cmd/MMConvertImageCmd.cpp mmSolver/cmd/MMMarkerHomographyCmd.cpp mmSolver/cmd/MMReadImageCmd.cpp mmSolver/cmd/MMReprojectionCmd.cpp + mmSolver/cmd/MMSolver2Cmd.cpp mmSolver/cmd/MMSolverAffectsCmd.cpp mmSolver/cmd/MMSolverCmd.cpp - mmSolver/cmd/MMSolver2Cmd.cpp mmSolver/cmd/MMSolverSceneGraphCmd.cpp mmSolver/cmd/MMSolverTypeCmd.cpp mmSolver/cmd/MMTestCameraMatrixCmd.cpp + mmSolver/cmd/arg_flags_attr_details.cpp + mmSolver/cmd/arg_flags_attr_details.h + mmSolver/cmd/arg_flags_solve_frames.cpp + mmSolver/cmd/arg_flags_solve_frames.h + mmSolver/cmd/arg_flags_solve_info.cpp + mmSolver/cmd/arg_flags_solve_info.h + mmSolver/cmd/arg_flags_solve_log.cpp + mmSolver/cmd/arg_flags_solve_log.h + mmSolver/cmd/arg_flags_solve_object.cpp + mmSolver/cmd/arg_flags_solve_object.h + mmSolver/cmd/arg_flags_solve_scene_graph.cpp + mmSolver/cmd/arg_flags_solve_scene_graph.h mmSolver/core/reprojection.cpp mmSolver/mayahelper/maya_attr.cpp mmSolver/mayahelper/maya_bundle.cpp @@ -88,25 +89,27 @@ set(SOURCE_FILES mmSolver/node/MMMarkerTransformNode.cpp mmSolver/node/MMReprojectionNode.cpp mmSolver/node/node_line_utils.cpp - mmSolver/sfm/camera_relative_pose.cpp + mmSolver/pluginMain.cpp mmSolver/sfm/camera_from_known_points.cpp + mmSolver/sfm/camera_relative_pose.cpp mmSolver/sfm/homography.cpp mmSolver/sfm/sfm_utils.cpp - mmSolver/shape/ShapeDrawUtils.cpp - mmSolver/shape/MarkerShapeNode.cpp - mmSolver/shape/MarkerDrawOverride.cpp - mmSolver/shape/BundleShapeNode.cpp mmSolver/shape/BundleDrawOverride.cpp - mmSolver/shape/ImagePlaneShapeNode.cpp + mmSolver/shape/BundleShapeNode.cpp + mmSolver/shape/ImageCache.cpp mmSolver/shape/ImagePlaneGeometryOverride.cpp - mmSolver/shape/LineShapeNode.cpp + mmSolver/shape/ImagePlaneShapeNode.cpp mmSolver/shape/LineDrawOverride.cpp - mmSolver/shape/SkyDomeShapeNode.cpp + mmSolver/shape/LineShapeNode.cpp + mmSolver/shape/MarkerDrawOverride.cpp + mmSolver/shape/MarkerShapeNode.cpp + mmSolver/shape/ShapeDrawUtils.cpp mmSolver/shape/SkyDomeDrawOverride.cpp + mmSolver/shape/SkyDomeShapeNode.cpp mmSolver/utilities/debug_utils.cpp mmSolver/utilities/number_utils.cpp + mmSolver/utilities/path_utils.cpp mmSolver/utilities/string_utils.cpp - mmSolver/pluginMain.cpp ) if (MMSOLVER_BUILD_RENDERER) @@ -200,17 +203,39 @@ if(MAYA_FOUND) ) endif() +find_package(ZLIB REQUIRED) # Link all libraries. target_link_libraries(mmSolver - PRIVATE - cminpack::cminpack - glog::glog - Eigen3::Eigen - ceres - openMVG + PRIVATE ZLIB::ZLIB + PRIVATE cminpack::cminpack + PRIVATE glog::glog + PRIVATE Eigen3::Eigen + PRIVATE ceres + PRIVATE openMVG ) +# OpenColorIO +find_package(OpenColorIO REQUIRED) + +get_target_property(OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES OpenColorIO::OpenColorIO INTERFACE_INCLUDE_DIRECTORIES) +get_target_property(OpenColorIO_IMPORTED_LOCATION_RELEASE OpenColorIO::OpenColorIO IMPORTED_LOCATION_RELEASE) +get_target_property(OpenColorIO_INTERFACE_LINK_LIBRARIES OpenColorIO::OpenColorIO INTERFACE_LINK_LIBRARIES) + +message(STATUS "OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES: ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES}") +message(STATUS "OpenColorIO_IMPORTED_LOCATION_RELEASE: ${OpenColorIO_IMPORTED_LOCATION_RELEASE}") +message(STATUS "OpenColorIO_INTERFACE_LINK_LIBRARIES: ${OpenColorIO_INTERFACE_LINK_LIBRARIES}") + +if (MSVC) + target_link_libraries(mmSolver + PRIVATE ${OpenColorIO_IMPORTED_LOCATION_RELEASE} + ) +else() + target_link_libraries(mmSolver + PRIVATE ${OpenColorIO_IMPORTED_LOCATION_RELEASE} + ) +endif() + target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_CMINPACK) target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_OPENMVG) diff --git a/src/mmSolver/cmd/MMColorIOCmd.cpp b/src/mmSolver/cmd/MMColorIOCmd.cpp new file mode 100644 index 000000000..065b941fb --- /dev/null +++ b/src/mmSolver/cmd/MMColorIOCmd.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2022 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Command for running mmColorIO. + * + * The mmColorIO command is responsible for querying and setting + * details about the input/output of colours in mmSolver, for + * converting colours, images and textures from one colour space to + * another (using OpenColorIO under the hood). + * + * MEL: + * // List all known colour spaces. + * mmColorIO -listColorSpacesAll + * + * // List all known active colour spaces. + * mmColorIO -listColorSpacesActive + * + * // List all known inactive colour spaces. + * mmColorIO -listColorSpacesInactive + * + * // Get "scene linear" role colour space. + * mmColorIO -roleSceneLinear + * + * // Check if the given colour space exists. + * mmColorIO -colorSpaceExists "ACEScg" + * + * // Guess colour space from given file. + * mmColorIO -guessColorSpaceFromFile "/path/to/file.jpg" + * + * // Get the current config details. + * mmColorIO -configName + * mmColorIO -configDescription + * mmColorIO -configSearchPath + * mmColorIO -configWorkingDirectory + * + */ + +#include "MMColorIOCmd.h" + +// STD +#include +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// MM Solver +#include + +#include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { + +MMColorIOCmd::~MMColorIOCmd() {} + +void *MMColorIOCmd::creator() { return new MMColorIOCmd(); } + +MString MMColorIOCmd::cmdName() { return MString("mmColorIO"); } + +/* + * Tell Maya we have a syntax function. + */ +bool MMColorIOCmd::hasSyntax() const { return true; } + +bool MMColorIOCmd::isUndoable() const { return false; } + +/* + * Add flags to the command syntax + */ +MSyntax MMColorIOCmd::newSyntax() { + MSyntax syntax; + syntax.enableQuery(false); + syntax.enableEdit(false); + + syntax.addFlag(LIST_COLOR_SPACES_ALL_FLAG, LIST_COLOR_SPACES_ALL_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(LIST_COLOR_SPACES_ACTIVE_FLAG, + LIST_COLOR_SPACES_ACTIVE_FLAG_LONG, MSyntax::kBoolean); + syntax.addFlag(LIST_COLOR_SPACES_INACTIVE_FLAG, + LIST_COLOR_SPACES_INACTIVE_FLAG_LONG, MSyntax::kBoolean); + + syntax.addFlag(ROLE_DEFAULT_FLAG, ROLE_DEFAULT_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_REFERENCE_FLAG, ROLE_REFERENCE_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_DATA_FLAG, ROLE_DATA_FLAG_LONG, MSyntax::kBoolean); + syntax.addFlag(ROLE_COLOR_PICKING_FLAG, ROLE_COLOR_PICKING_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_SCENE_LINEAR_FLAG, ROLE_SCENE_LINEAR_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_COMPOSITING_LOG_FLAG, ROLE_COMPOSITING_LOG_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_COLOR_TIMING_FLAG, ROLE_COLOR_TIMING_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_TEXTURE_PAINT_FLAG, ROLE_TEXTURE_PAINT_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_MATTE_PAINT_FLAG, ROLE_MATTE_PAINT_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(ROLE_RENDERING_FLAG, ROLE_RENDERING_FLAG_LONG, + MSyntax::kBoolean); + + syntax.addFlag(CONFIG_NAME_FLAG, CONFIG_NAME_FLAG_LONG, MSyntax::kBoolean); + syntax.addFlag(CONFIG_DESCRIPTION_FLAG, CONFIG_DESCRIPTION_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(CONFIG_SEARCH_PATH_FLAG, CONFIG_SEARCH_PATH_FLAG_LONG, + MSyntax::kBoolean); + syntax.addFlag(CONFIG_WORKING_DIRECTORY_FLAG, + CONFIG_WORKING_DIRECTORY_FLAG_LONG, MSyntax::kBoolean); + + syntax.addFlag(COLOR_SPACE_EXISTS_FLAG, COLOR_SPACE_EXISTS_FLAG_LONG, + MSyntax::kString); + + syntax.addFlag(GUESS_COLOR_SPACE_FROM_FILE_FLAG, + GUESS_COLOR_SPACE_FROM_FILE_FLAG_LONG, MSyntax::kString); + + return syntax; +} + +/* + * Parse command line arguments + */ +MStatus MMColorIOCmd::parseArgs(const MArgList &args) { + MStatus status = MStatus::kSuccess; + + MArgDatabase argData(syntax(), args, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_list_color_spaces_all = + argData.isFlagSet(LIST_COLOR_SPACES_ALL_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_list_color_spaces_active = + argData.isFlagSet(LIST_COLOR_SPACES_ACTIVE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_list_color_spaces_inactive = + argData.isFlagSet(LIST_COLOR_SPACES_INACTIVE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_color_picking = argData.isFlagSet(ROLE_COLOR_PICKING_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_color_timing = argData.isFlagSet(ROLE_COLOR_TIMING_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_compositing_log = + argData.isFlagSet(ROLE_COMPOSITING_LOG_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_data = argData.isFlagSet(ROLE_DATA_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_default = argData.isFlagSet(ROLE_DEFAULT_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_matte_paint = argData.isFlagSet(ROLE_MATTE_PAINT_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_reference = argData.isFlagSet(ROLE_REFERENCE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_rendering = argData.isFlagSet(ROLE_RENDERING_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_scene_linear = argData.isFlagSet(ROLE_SCENE_LINEAR_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_role_texture_paint = argData.isFlagSet(ROLE_TEXTURE_PAINT_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_config_name = argData.isFlagSet(CONFIG_NAME_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_config_description = argData.isFlagSet(CONFIG_DESCRIPTION_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_config_search_path = argData.isFlagSet(CONFIG_SEARCH_PATH_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_config_working_directory = + argData.isFlagSet(CONFIG_WORKING_DIRECTORY_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_guess_color_space_from_file = + argData.isFlagSet(GUESS_COLOR_SPACE_FROM_FILE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + if (m_guess_color_space_from_file) { + status = argData.getFlagArgument(GUESS_COLOR_SPACE_FROM_FILE_FLAG, 0, + m_file_path); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + m_color_space_exists = argData.isFlagSet(COLOR_SPACE_EXISTS_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + if (m_color_space_exists) { + status = argData.getFlagArgument(COLOR_SPACE_EXISTS_FLAG, 0, + m_color_space_name); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + return status; +} + +MStatus MMColorIOCmd::doIt(const MArgList &args) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + // Read all the flag arguments. + status = parseArgs(args); + CHECK_MSTATUS_AND_RETURN_IT(status); + + if (m_list_color_spaces_all || m_list_color_spaces_active || + m_list_color_spaces_inactive) { + auto visibility = mmcolorio::ColorSpaceVisibility::kUnknown; + if (m_list_color_spaces_all) { + visibility = mmcolorio::ColorSpaceVisibility::kAll; + } else if (m_list_color_spaces_active) { + visibility = mmcolorio::ColorSpaceVisibility::kActive; + } else if (m_list_color_spaces_inactive) { + visibility = mmcolorio::ColorSpaceVisibility::kInactive; + } else { + // Should not get here. + assert(false); + } + + std::vector color_space_names = + mmcolorio::get_color_space_names(visibility); + + MStringArray outResult; + for (auto i = 0; i < color_space_names.size(); i++) { + std::string color_space_name = color_space_names[i]; + MMSOLVER_MAYA_VRB("mmColorIO: get color space names: i=" + << i << "color_space_name=\"" << color_space_name + << "\"."); + + outResult.append(MString(color_space_name.c_str())); + } + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (outResult.length() > 0) { + MMColorIOCmd::setResult(outResult); + } + } else if (m_role_color_picking || m_role_color_timing || + m_role_compositing_log || m_role_data || m_role_default || + m_role_matte_paint || m_role_reference || m_role_rendering || + m_role_scene_linear || m_role_texture_paint) { + const char *color_space_name = ""; + if (m_role_color_picking) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kColorPicking); + } else if (m_role_color_timing) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kColorTiming); + } else if (m_role_compositing_log) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kCompositingLog); + } else if (m_role_data) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kData); + } else if (m_role_default) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kDefault); + } else if (m_role_matte_paint) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kMattePaint); + } else if (m_role_reference) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kReference); + } else if (m_role_rendering) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kRendering); + } else if (m_role_scene_linear) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kSceneLinear); + } else if (m_role_texture_paint) { + color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kTexturePaint); + } else { + MMSOLVER_MAYA_ERR("mmColorIO: The role type to query is invalid!"); + return MStatus::kFailure; + } + + MMSOLVER_MAYA_VRB("mmColorIO: role name: " + << "\"" << color_space_name << "\"."); + + MString outResult = MString(color_space_name); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (outResult.length() > 0) { + MMColorIOCmd::setResult(outResult); + } + } else if (m_guess_color_space_from_file) { + MMSOLVER_MAYA_VRB("mmColorIO: guess color space from file: " + << "\"" << m_file_path.asChar() << "\"."); + + if (m_file_path.length() > 0) { + MString resolved_file_path = m_file_path; + status = mmpath::resolve_input_file_path(resolved_file_path); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const char *color_space_name = + mmcolorio::guess_color_space_name_from_file_path( + resolved_file_path.asChar()); + MString outResult = MString(color_space_name); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (outResult.length() > 0) { + MMColorIOCmd::setResult(outResult); + } + } + } else if (m_color_space_exists) { + MMSOLVER_MAYA_VRB("mmColorIO: color space: " + << "\"" << m_color_space_name.asChar() << "\"."); + + if (m_color_space_name.length() > 0) { + const bool exists = + mmcolorio::color_space_name_exists(m_color_space_name.asChar()); + MMSOLVER_MAYA_VRB("mmColorIO: color space exists: " << exists); + + MMColorIOCmd::setResult(exists); + } + } else if (m_config_name) { + const char *config_name = mmcolorio::get_config_name(); + MMSOLVER_MAYA_VRB("mmColorIO: config name: " + << "\"" << config_name << "\"."); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (std::strlen(config_name) > 0) { + MMColorIOCmd::setResult(MString(config_name)); + } + } else if (m_config_description) { + const char *config_description = mmcolorio::get_config_description(); + MMSOLVER_MAYA_VRB("mmColorIO: config description: " + << "\"" << config_description << "\"."); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (std::strlen(config_description) > 0) { + MMColorIOCmd::setResult(MString(config_description)); + } + } else if (m_config_search_path) { + const char *config_search_path = mmcolorio::get_config_search_path(); + MMSOLVER_MAYA_VRB("mmColorIO: config search path: " + << "\"" << config_search_path << "\"."); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (std::strlen(config_search_path) > 0) { + MMColorIOCmd::setResult(MString(config_search_path)); + } + } else if (m_config_working_directory) { + const char *config_working_directory = + mmcolorio::get_config_working_directory(); + MMSOLVER_MAYA_VRB("mmColorIO: config working directory: " + << "\"" << config_working_directory << "\"."); + + // Maya idiosyncrasy: Return None/nothing if a command does + // not list anything. + if (std::strlen(config_working_directory) > 0) { + MMColorIOCmd::setResult(MString(config_working_directory)); + } + } + + return status; +} + +} // namespace mmsolver diff --git a/src/mmSolver/cmd/MMColorIOCmd.h b/src/mmSolver/cmd/MMColorIOCmd.h new file mode 100644 index 000000000..e346d9df2 --- /dev/null +++ b/src/mmSolver/cmd/MMColorIOCmd.h @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2022 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Header for mmColorIO Maya command. + */ + +#ifndef MAYA_MM_COLOR_IO_CMD_H +#define MAYA_MM_COLOR_IO_CMD_H + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include + +// Command arguments and command name: +#define COLOR_SPACE_EXISTS_FLAG "-ce" +#define COLOR_SPACE_EXISTS_FLAG_LONG "-colorSpaceExists" + +#define CONFIG_DESCRIPTION_FLAG "-de" +#define CONFIG_DESCRIPTION_FLAG_LONG "-configDescription" + +#define CONFIG_NAME_FLAG "-nm" +#define CONFIG_NAME_FLAG_LONG "-configName" + +#define CONFIG_SEARCH_PATH_FLAG "-sp" +#define CONFIG_SEARCH_PATH_FLAG_LONG "-configSearchPath" + +#define CONFIG_WORKING_DIRECTORY_FLAG "-wd" +#define CONFIG_WORKING_DIRECTORY_FLAG_LONG "-configWorkingDirectory" + +#define GUESS_COLOR_SPACE_FROM_FILE_FLAG "-gc" +#define GUESS_COLOR_SPACE_FROM_FILE_FLAG_LONG "-guessColorSpaceFromFile" + +#define LIST_COLOR_SPACES_ACTIVE_FLAG "-la" +#define LIST_COLOR_SPACES_ACTIVE_FLAG_LONG "-listColorSpacesActive" + +#define LIST_COLOR_SPACES_ALL_FLAG "-lc" +#define LIST_COLOR_SPACES_ALL_FLAG_LONG "-listColorSpacesAll" + +#define LIST_COLOR_SPACES_INACTIVE_FLAG "-li" +#define LIST_COLOR_SPACES_INACTIVE_FLAG_LONG "-listColorSpacesInactive" + +// TODO: Add the InterchangeScene and InterchangeDisplay roles? +#define ROLE_COLOR_PICKING_FLAG "-rp" +#define ROLE_COLOR_PICKING_FLAG_LONG "-roleColorPicking" +#define ROLE_COLOR_TIMING_FLAG "-rt" +#define ROLE_COLOR_TIMING_FLAG_LONG "-roleColorTiming" +#define ROLE_COMPOSITING_LOG_FLAG "-rg" +#define ROLE_COMPOSITING_LOG_FLAG_LONG "-roleCompositingLog" +#define ROLE_DATA_FLAG "-ra" +#define ROLE_DATA_FLAG_LONG "-roleData" +#define ROLE_DEFAULT_FLAG "-rd" +#define ROLE_DEFAULT_FLAG_LONG "-roleDefault" +#define ROLE_MATTE_PAINT_FLAG "-ri" +#define ROLE_MATTE_PAINT_FLAG_LONG "-roleMattePaint" +#define ROLE_REFERENCE_FLAG "-rr" +#define ROLE_REFERENCE_FLAG_LONG "-roleReference" +#define ROLE_RENDERING_FLAG "-rn" +#define ROLE_RENDERING_FLAG_LONG "-roleRendering" +#define ROLE_SCENE_LINEAR_FLAG "-rl" +#define ROLE_SCENE_LINEAR_FLAG_LONG "-roleSceneLinear" +#define ROLE_TEXTURE_PAINT_FLAG "-rx" +#define ROLE_TEXTURE_PAINT_FLAG_LONG "-roleTexturePaint" + +namespace mmsolver { + +class MMColorIOCmd : public MPxCommand { +public: + MMColorIOCmd() + : m_color_space_exists(false) + , m_config_description(false) + , m_config_name(false) + , m_config_search_path(false) + , m_config_working_directory(false) + , m_guess_color_space_from_file(false) + , m_list_color_spaces_active(false) + , m_list_color_spaces_all(false) + , m_list_color_spaces_inactive(false) + , m_role_color_picking(false) + , m_role_color_timing(false) + , m_role_compositing_log(false) + , m_role_data(false) + , m_role_default(false) + , m_role_matte_paint(false) + , m_role_reference(false) + , m_role_rendering(false) + , m_role_scene_linear(false) + , m_role_texture_paint(false){}; + + virtual ~MMColorIOCmd(); + + virtual bool hasSyntax() const; + static MSyntax newSyntax(); + + virtual MStatus doIt(const MArgList &args); + + virtual bool isUndoable() const; + + static void *creator(); + + static MString cmdName(); + +private: + MStatus parseArgs(const MArgList &args); + + bool m_color_space_exists; + bool m_config_description; + bool m_config_name; + bool m_config_search_path; + bool m_config_working_directory; + bool m_guess_color_space_from_file; + bool m_list_color_spaces_active; + bool m_list_color_spaces_all; + bool m_list_color_spaces_inactive; + bool m_role_color_picking; + bool m_role_color_timing; + bool m_role_compositing_log; + bool m_role_data; + bool m_role_default; + bool m_role_matte_paint; + bool m_role_reference; + bool m_role_rendering; + bool m_role_scene_linear; + bool m_role_texture_paint; + + MString m_file_path; + MString m_color_space_name; +}; + +} // namespace mmsolver + +#endif // MAYA_MM_COLOR_IO_CMD_H diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index e47dd128a..d04359b37 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -39,6 +39,7 @@ #include "mmSolver/cmd/MMCameraPoseFromPointsCmd.h" #include "mmSolver/cmd/MMCameraRelativePoseCmd.h" #include "mmSolver/cmd/MMCameraSolveCmd.h" +#include "mmSolver/cmd/MMColorIOCmd.h" #include "mmSolver/cmd/MMConvertImageCmd.h" #include "mmSolver/cmd/MMMarkerHomographyCmd.h" #include "mmSolver/cmd/MMReadImageCmd.h" @@ -251,6 +252,10 @@ MStatus initializePlugin(MObject obj) { mmsolver::MMCameraSolveCmd::creator, mmsolver::MMCameraSolveCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMColorIOCmd::cmdName(), + mmsolver::MMColorIOCmd::creator, + mmsolver::MMColorIOCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMConvertImageCmd::cmdName(), mmsolver::MMConvertImageCmd::creator, mmsolver::MMConvertImageCmd::newSyntax, status); @@ -603,6 +608,7 @@ MStatus uninitializePlugin(MObject obj) { DEREGISTER_COMMAND(plugin, mmsolver::MMCameraRelativePoseCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMCameraSolveCmd::cmdName(), status); + DEREGISTER_COMMAND(plugin, mmsolver::MMColorIOCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMConvertImageCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMMarkerHomographyCmd::cmdName(), status); diff --git a/src/mmSolver/render/shader/shader_utils.cpp b/src/mmSolver/render/shader/shader_utils.cpp index 41557a8b3..d71cfd2e2 100644 --- a/src/mmSolver/render/shader/shader_utils.cpp +++ b/src/mmSolver/render/shader/shader_utils.cpp @@ -21,6 +21,10 @@ #include "shader_utils.h" +// STL +#include +#include + // Maya #include #include @@ -31,6 +35,7 @@ // MM Solver #include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/path_utils.h" namespace mmsolver { namespace render { @@ -70,6 +75,62 @@ const MHWRender::MShaderManager *get_shader_manager() { return shader_manager; } +MString find_shader_file_path(const MString &shader_file_name) { + const bool verbose = false; + MStatus status = MS::kSuccess; + + MMSOLVER_MAYA_VRB("MM Renderer finding shader file..." + << " shader_file_name=" << shader_file_name.asChar()); + + MString shader_file_path = ""; + const MHWRender::MShaderManager *shader_manager = get_shader_manager(); + if (!shader_manager) { + return shader_file_path; + } + + MStringArray shader_paths; + status = shader_manager->shaderPaths(shader_paths); + if (status != MStatus::kSuccess) { + return shader_file_path; + } + + for (auto i = 0; i < shader_paths.length(); i++) { + MMSOLVER_MAYA_VRB("MM Renderer look for shader in: " + << " shader_paths[" << i + << "]=" << shader_paths[i].asChar()); + + MString test_file_path = shader_paths[i]; + const auto found_forward_slash_index = test_file_path.rindex('/'); + MMSOLVER_MAYA_VRB( + "MM Renderer test_file_path.length(): " << test_file_path.length()); + MMSOLVER_MAYA_VRB("MM Renderer found_forward_slash_index: " + << found_forward_slash_index); + if (found_forward_slash_index != (test_file_path.length() - 1)) { + test_file_path += MString("/"); + } + test_file_path += shader_file_name; + // TODO: Should we automatically try and find the file name + // with the '.ogsfx' appended? + MMSOLVER_MAYA_VRB( + "MM Renderer test_file_path: " << test_file_path.asChar()); + + status = mmpath::resolve_input_file_path(test_file_path); + if ((status == MStatus::kSuccess) && (test_file_path.length() > 0)) { + shader_file_path = test_file_path; + break; + } + } + + return shader_file_path; +} + +MString read_shader_file(const MString &shader_file_path) { + std::ifstream file_stream(shader_file_path.asChar()); + const std::string content((std::istreambuf_iterator(file_stream)), + (std::istreambuf_iterator())); + return MString(content.c_str()); +} + MHWRender::MShaderInstance *compile_shader_file(const MString &shader_file_name, const MString &technique_name) { const bool verbose = false; @@ -86,8 +147,8 @@ MHWRender::MShaderInstance *compile_shader_file(const MString &shader_file_name, // Shader compiling options. MShaderCompileMacro *macros = nullptr; - unsigned int number_of_macros = 0; - bool use_effect_cache = true; + const unsigned int number_of_macros = 0; + const bool use_effect_cache = true; // Get Techniques. MMSOLVER_MAYA_VRB("MM Renderer: Get techniques..."); @@ -138,5 +199,63 @@ MHWRender::MShaderInstance *compile_shader_file(const MString &shader_file_name, return shader_instance; } +MHWRender::MShaderInstance *compile_shader_text(const MString &shader_text, + const MString &technique_name) { + const bool verbose = false; + MStatus status = MS::kSuccess; + + MMSOLVER_MAYA_VRB("MM Renderer compiling shader file..." + << " technique_name=" << technique_name.asChar()); + MMSOLVER_MAYA_VRB("Shader text: "); + MMSOLVER_MAYA_VRB(shader_text.asChar()); + + const MHWRender::MShaderManager *shader_manager = get_shader_manager(); + if (!shader_manager) { + return nullptr; + } + + const void *text_buffer = static_cast(shader_text.asChar()); + const unsigned int text_buffer_size = + static_cast(shader_text.length()); + + // Shader compiling options. + MShaderCompileMacro *macros = nullptr; + const unsigned int number_of_macros = 0; + const bool use_effect_cache = true; + + // Compile shader. + MMSOLVER_MAYA_VRB("MM Renderer: Compiling shader..."); + MHWRender::MShaderInstance *shader_instance = + shader_manager->getEffectsBufferShader( + text_buffer, text_buffer_size, technique_name, macros, + number_of_macros, use_effect_cache); + if (!shader_instance) { + MString error_message = + MString("MM Renderer failed to compile shader."); + bool display_line_number = true; + bool filter_source = true; + uint32_t num_lines = 3; + MGlobal::displayError(error_message); + MGlobal::displayError(MHWRender::MShaderManager::getLastError()); + MGlobal::displayError(MHWRender::MShaderManager::getLastErrorSource( + display_line_number, filter_source, num_lines)); + MMSOLVER_MAYA_ERR("MM Renderer failed to compile shader."); + MMSOLVER_MAYA_ERR(MHWRender::MShaderManager::getLastError().asChar()); + MMSOLVER_MAYA_ERR(MHWRender::MShaderManager::getLastErrorSource( + display_line_number, filter_source, num_lines) + .asChar()); + return nullptr; + } + + MStringArray parameter_list; + shader_instance->parameterList(parameter_list); + for (uint32_t i = 0; i < parameter_list.length(); ++i) { + MMSOLVER_MAYA_VRB("MM Renderer: param " << i << ": " + << parameter_list[i].asChar()); + } + + return shader_instance; +} + } // namespace render } // namespace mmsolver diff --git a/src/mmSolver/render/shader/shader_utils.h b/src/mmSolver/render/shader/shader_utils.h index 879dca355..17f58da66 100644 --- a/src/mmSolver/render/shader/shader_utils.h +++ b/src/mmSolver/render/shader/shader_utils.h @@ -31,9 +31,16 @@ namespace render { const MHWRender::MShaderManager *get_shader_manager(); +MString find_shader_file_path(const MString &shader_file_name); + +MString read_shader_file(const MString &shader_file_path); + MHWRender::MShaderInstance *compile_shader_file(const MString &shader_file_name, const MString &technique_name); +MHWRender::MShaderInstance *compile_shader_text(const MString &shader_text, + const MString &technique_name); + } // namespace render } // namespace mmsolver diff --git a/src/mmSolver/shape/ImageCache.cpp b/src/mmSolver/shape/ImageCache.cpp new file mode 100644 index 000000000..d01c65d4b --- /dev/null +++ b/src/mmSolver/shape/ImageCache.cpp @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "ImageCache.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include +#include + +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { + +void *get_mimage_pixel_data(const MImage &image, + const CachePixelDataType pixel_data_type, + const uint32_t width, const uint32_t height, + const uint8_t number_of_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format) { + const bool verbose = false; + + const uint32_t print_num_pixels = 8; + void *pixel_data = nullptr; + if (pixel_data_type == CachePixelDataType::kU8) { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" + << " pixel_data_type=CachePixelDataType::kU8"); + + // 8-bit unsigned integers use 1 byte. + out_bytes_per_channel = 1; + + const bool is_rgba = image.isRGBA(); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" + << " is_rgba=" << is_rgba); + if (is_rgba) { + out_texture_format = MHWRender::kR8G8B8A8_UNORM; + } else { + out_texture_format = MHWRender::kB8G8R8A8; + } + + unsigned char *pixels = image.pixels(); + + if (verbose) { + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const uint32_t r = static_cast(pixels[index + 0]); + const uint32_t g = static_cast(pixels[index + 1]); + const uint32_t b = static_cast(pixels[index + 2]); + const uint32_t a = static_cast(pixels[index + 3]); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " + << g << ", " << b << ", " << a); + } + } + + // TODO: Allow giving the explicit input and output color + // space names. + // + // TODO: Allow outputting 32-bit/16-bit float pixel data, to + // ensure the plate doesn't get quantize. + // + // mmcolorio::image_convert_srgb_to_linear_srgb_u8(pixels, width, + // height, + // number_of_channels); + + // mmcolorio::test_opencolorio(pixels, width, height, + // number_of_channels); + + pixel_data = static_cast(pixels); + } else if (pixel_data_type == CachePixelDataType::kF32) { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" + << " pixel_data_type=CachePixelDataType::kF32"); + + // 32-bit floats use 4 bytes. + out_bytes_per_channel = 4; + + out_texture_format = MHWRender::kR32G32B32A32_FLOAT; + + float *floatPixels = image.floatPixels(); + + if (verbose) { + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const float r = floatPixels[index + 0]; + const float g = floatPixels[index + 1]; + const float b = floatPixels[index + 2]; + const float a = floatPixels[index + 3]; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " + << g << ", " << b << ", " << a); + } + } + + // mmcolorio::image_convert_srgb_to_linear_srgb_f32( + // floatPixels, width, height, number_of_channels); + + pixel_data = static_cast(floatPixels); + } else { + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: get_mimage_pixel_data: " + << "Invalid pixel type is " + << static_cast(pixel_data_type)); + return nullptr; + } + + return pixel_data; +} + +CachePixelDataType convert_mpixel_type_to_pixel_data_type( + MImage::MPixelType pixel_type) { + CachePixelDataType pixel_data_type = CachePixelDataType::kUnknown; + if (pixel_type == MImage::MPixelType::kByte) { + pixel_data_type = CachePixelDataType::kU8; + } else if (pixel_type == MImage::MPixelType::kFloat) { + pixel_data_type = CachePixelDataType::kF32; + } else { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache: convert_mpixel_type_to_pixel_data_type: " + "Invalid MImage::MPixelType value."); + } + return pixel_data_type; +} + +MStatus read_with_mimage(MImage &image, MString &file_path, + const MImage::MPixelType pixel_type, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_number_of_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + CachePixelDataType &out_pixel_data_type, + void *&out_pixel_data) { + const bool verbose = false; + + MStatus status = MStatus::kSuccess; + + // TODO: This code can be changed to whatever reading function + // that reads the input file path. + status = image.readFromFile(file_path, pixel_type); + CHECK_MSTATUS(status); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_WRN("mmsolver::ImageCache: read_with_mimage:" + << " failed to read image \"" << file_path.asChar() + << "\"."); + return status; + } + + image.getSize(out_width, out_height); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_with_mimage:" + << " width=" << out_width << " height=" << out_height); + + out_pixel_data_type = convert_mpixel_type_to_pixel_data_type(pixel_type); + + out_pixel_data = get_mimage_pixel_data( + image, out_pixel_data_type, out_width, out_height, + out_number_of_channels, out_bytes_per_channel, out_texture_format); + + return status; +} + +MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, + ImageCache &image_cache, MImage &temp_image, + const MString &file_path, + const MImage::MPixelType pixel_type, + const bool do_texture_update) { + assert(texture_manager != nullptr); + + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file:" + << " file_path=" << file_path.asChar()); + + MString resolved_file_path = file_path; + MStatus status = mmpath::resolve_input_file_path(resolved_file_path); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file:" + << " file does not exist \"" + << resolved_file_path.asChar() << "\"."); + return nullptr; + } + + std::string key = std::string(resolved_file_path.asChar()); + MTexture *texture = image_cache.gpu_find(key); + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache: read_image_file: findTexture: " << texture); + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache: read_image_file: do_texture_update=" + << do_texture_update); + if (texture && (do_texture_update == false)) { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE1:" + << " texture=" << texture); + return texture; + } + + // TODO: We should test if the file exists, then cache + // result. This avoids us having to check the disk each time we + // hit a frame that is outside the frame range of the image + // sequence. This would mean that we would then need to be able to + // flush that cached result - for example if the image sequence + // changes. Alternatively, we could could pre-cache the file path + // existence as soon as the user changes the file path. Another + // approach would be to expose a Maya command that would allow + // Python user code to add the list of valid images into the + // cache. + + CacheImagePixelData image_pixel_data = image_cache.cpu_find(key); + + uint32_t width = 0; + uint32_t height = 0; + uint8_t number_of_channels = 4; + uint8_t bytes_per_channel = 0; + MHWRender::MRasterFormat texture_format; + CachePixelDataType pixel_data_type = CachePixelDataType::kUnknown; + void *maya_owned_pixel_data = nullptr; + + const bool image_pixel_data_valid = image_pixel_data.is_valid(); + if (image_pixel_data_valid) { + maya_owned_pixel_data = image_pixel_data.pixel_data(); + width = image_pixel_data.width(); + height = image_pixel_data.height(); + number_of_channels = image_pixel_data.num_channels(); + pixel_data_type = image_pixel_data.pixel_data_type(); + bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); + + if (pixel_data_type == CachePixelDataType::kU8) { + // Assumes the 8-bit data is "RGBA". + texture_format = MHWRender::kR8G8B8A8_UNORM; + } else if (pixel_data_type == CachePixelDataType::kF32) { + texture_format = MHWRender::kR32G32B32A32_FLOAT; + } + + } else { + // TODO: This code can be changed to whatever reading function + // that reads the input file path. + + status = read_with_mimage(temp_image, resolved_file_path, pixel_type, + width, height, number_of_channels, + bytes_per_channel, texture_format, + pixel_data_type, maya_owned_pixel_data); + if (status != MS::kSuccess) { + return nullptr; + } + + // TODO: any image manipulations required can be done here. + // TODO: Apply colour correction via OIIO. + // // image.verticalFlip(); + } + + if (!maya_owned_pixel_data) { + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_image_file: " + << "Invalid pixel data!"); + return nullptr; + } + + CacheImagePixelData gpu_image_pixel_data = + CacheImagePixelData(static_cast(maya_owned_pixel_data), width, + height, number_of_channels, pixel_data_type); + + texture = + image_cache.gpu_insert(texture_manager, key, gpu_image_pixel_data); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " + << "gpu_inserted=" << texture); + + // Duplicate the Maya-owned pixel data for our image cache. + const size_t pixel_data_byte_count = + width * height * number_of_channels * bytes_per_channel; + std::vector pixel_data_vec; + pixel_data_vec.resize(pixel_data_byte_count); + std::memcpy(pixel_data_vec.data(), maya_owned_pixel_data, + pixel_data_byte_count); + image_pixel_data = + CacheImagePixelData(static_cast(pixel_data_vec.data()), width, + height, number_of_channels, pixel_data_type); + + const bool cpu_inserted = image_cache.cpu_insert(key, image_pixel_data); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " + << "cpu_inserted=" << cpu_inserted); + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE2:" + << " texture=" << texture); + + return texture; +} + +} // namespace mmsolver diff --git a/src/mmSolver/shape/ImageCache.h b/src/mmSolver/shape/ImageCache.h new file mode 100644 index 000000000..00f17f880 --- /dev/null +++ b/src/mmSolver/shape/ImageCache.h @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_IMAGE_CACHE_H +#define MM_IMAGE_CACHE_H + +// STL +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "ImagePlaneShapeNode.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { + +enum class CachePixelDataType : uint8_t { + kU8 = 0, + kF32, + + // Always the second to last, so it's equal to the number of + // options. + kCount, + + // When the pixel data type is not initialized or invalid. + kUnknown = 255, +}; + +static uint8_t convert_pixel_data_type_to_bytes_per_channel( + CachePixelDataType pixel_data_type) { + uint8_t bytes_per_channel = 0; + if (pixel_data_type == CachePixelDataType::kU8) { + // 8-bit unsigned integers use 1 byte. + bytes_per_channel = 1; + } else if (pixel_data_type == CachePixelDataType::kF32) { + // 32-bit floats use 4 bytes. + bytes_per_channel = 4; + } else { + bytes_per_channel = 0; + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: get_mimage_pixel_data: " + << "Invalid pixel type is " + << static_cast(pixel_data_type)); + } + return bytes_per_channel; +} + +static MHWRender::MRasterFormat convert_pixel_data_type_to_texture_format( + const CachePixelDataType pixel_data_type) { + if (pixel_data_type == CachePixelDataType::kU8) { + // Assumes the 8-bit data is "RGBA". + return MHWRender::kR8G8B8A8_UNORM; + } else if (pixel_data_type == CachePixelDataType::kF32) { + return MHWRender::kR32G32B32A32_FLOAT; + } + + return MHWRender::MRasterFormat(); +} + +struct CacheImagePixelData { + CacheImagePixelData() + : m_pixel_data(nullptr) + , m_width(0) + , m_height(0) + , m_num_channels(0) + , m_pixel_data_type(CachePixelDataType::kUnknown){}; + + CacheImagePixelData(void *pixel_data, const uint32_t width, + const uint32_t height, const uint8_t num_channels, + const CachePixelDataType pixel_data_type) + : m_pixel_data(pixel_data) + , m_width(width) + , m_height(height) + , m_num_channels(num_channels) + , m_pixel_data_type(pixel_data_type){}; + + ~CacheImagePixelData() = default; + + bool is_valid() const { + return (m_pixel_data != nullptr) && (m_width != 0) && (m_height != 0) && + (m_num_channels != 0) && + (m_pixel_data_type != CachePixelDataType::kUnknown); + }; + + void *pixel_data() const { return m_pixel_data; }; + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + uint8_t num_channels() const { return m_num_channels; } + CachePixelDataType pixel_data_type() const { return m_pixel_data_type; } + +private: + void *m_pixel_data; + uint32_t m_width; + uint32_t m_height; + uint8_t m_num_channels; + CachePixelDataType m_pixel_data_type; +}; + +// The ImageCache, used to load and cache images into GPU, CPU and +// Disk. +// +// Singleton design pattern for C++11 +// https://stackoverflow.com/a/1008289 +// +// TODO: Use 4 layers of reading textures. +// +// Layer 0: The texture is GPU memory, in the +// MHWRender::MTextureManager. If the texture is not in GPU memory, +// look in the (CPU) Image Cache. +// +// Layer 1: The texture pixel data is stored in RAM, in the (CPU) +// Image Cache. If the texture pixel data cannot be found in the Image +// Cache, look in the Disk Cache. +// +// Layer 2: The texture pixel data is stored on disk in a fast-to-read +// (8-bit) image format (https://qoiformat.org/ +// https://crates.io/crates/qoi) which as been converted to sRGB +// colour space. If the file cannot be found, read the original file +// path. +// +// Layer 3: The original file path should be read and decoded, and +// then placed into a queue to be saved into the disk cache. The queue +// of images are processed off the main thread, so that the +// interactive session does not slow down. + +// TODO: Read image (and insert into cache). +// +// TODO: Set/Get CPU memory allowed. +// +// TODO: Set/Get GPU memory allowed. +// +// TODO: Set/Get Disk space allowed. +// +// TODO: Set/Get Disk cache location. Used to find disk-cached +// files. This should be a directory on a very fast disk. +// +// TODO: Support converting to 8-bit LDR image pixels before +// storing. For example converting to sRGB colour space. +struct ImageCache { + using CPUCacheKey = std::string; + using GPUCacheKey = std::string; + using CPUMap = std::unordered_map; + using GPUMap = std::unordered_map; + +public: + static ImageCache &getInstance() { + static ImageCache instance; // Guaranteed to be destroyed. + // Instantiated on first use. + return instance; + } + +private: + // Constructor. The {} brackets are needed here. + ImageCache() {} + +public: + // Insert pixels into image CPU cache. + // + // If the key is already in the image cache, the previous value is + // erased. + bool cpu_insert(const CPUCacheKey &key, + const CacheImagePixelData &image_pixel_data) { + auto search = m_cpu_cache_map.find(key); + + const bool found = search != m_cpu_cache_map.end(); + if (found) { + ImageCache::cpu_erase(key); + } + + const bool ok = m_cpu_cache_map.insert({key, image_pixel_data}).second; + return ok; + } + + // Insert + MTexture *gpu_insert(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey &key, + const CacheImagePixelData &image_pixel_data) { + assert(texture_manager != nullptr); + assert(image_pixel_data.is_valid()); + const bool verbose = false; + + // No need for MIP-maps. + const bool generate_mip_maps = false; + + MTexture *texture = nullptr; + auto search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (!found) { + const uint32_t width = image_pixel_data.width(); + const uint32_t height = image_pixel_data.height(); + const uint8_t number_of_channels = image_pixel_data.num_channels(); + const CachePixelDataType pixel_data_type = + image_pixel_data.pixel_data_type(); + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); + void *pixel_data = image_pixel_data.pixel_data(); + + MHWRender::MTextureDescription texture_desc; + texture_desc.setToDefault2DTexture(); + texture_desc.fWidth = width; + texture_desc.fHeight = height; + texture_desc.fDepth = 1; + + texture_desc.fMipmaps = 1; + texture_desc.fArraySlices = 1; + texture_desc.fTextureType = MHWRender::kImage2D; + texture_desc.fFormat = + convert_pixel_data_type_to_texture_format(pixel_data_type); + + texture_desc.fBytesPerRow = + number_of_channels * bytes_per_channel * width; + texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * height; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " + << "width=" << width << " height=" << height + << " number_of_channels=" + << static_cast(number_of_channels)); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " + << "bytes_per_channel=" + << static_cast(bytes_per_channel)); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " + << "pixel_data_type=" + << static_cast(pixel_data_type)); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " + << "pixel_data=" << pixel_data); + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert:" + << " fBytesPerRow=" << texture_desc.fBytesPerRow + << " fBytesPerSlice=" + << texture_desc.fBytesPerSlice); + + // If the texture name provided is an empty string then the + // texture will not be cached as part of the internal texture + // caching system. Thus each such call to this method will + // create a new texture. + texture = texture_manager->acquireTexture( + m_empty_string, texture_desc, pixel_data, generate_mip_maps); + if (texture == nullptr) { + MMSOLVER_MAYA_ERR( + "mmsolver::ImageCache: gpu_insert: " + "Could not acquire texture!"); + return false; + } + + } else { + texture = search->second; + if (texture == nullptr) { + MMSOLVER_MAYA_ERR( + "mmsolver::ImageCache: gpu_insert: " + "Found texture is invalid!"); + return false; + } + + m_gpu_cache_map.erase(search); + + // TODO: If the 'texture_desc' is different than the + // current texture, it cannot be updated, and must be + // released and a new texture created instead. + // // ImageCache::gpu_erase(key); + + // The default value of this argument is 0. This means to use + // the texture's width * the number of bytes per pixel. + uint32_t rowPitch = 0; + + MHWRender::MTextureUpdateRegion *region = nullptr; + void *pixel_data = image_pixel_data.pixel_data(); + MStatus status = texture->update(pixel_data, generate_mip_maps, + rowPitch, region); + CHECK_MSTATUS(status); + } + + const bool ok = m_gpu_cache_map.insert({key, texture}).second; + assert(ok == true); + return texture; + } + + // Find the key in the image GPU cache. + MTexture *gpu_find(const GPUCacheKey &key) { + auto search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (found) { + return search->second; + } + return nullptr; + } + + // Find the key in the image CPU cache. + CacheImagePixelData cpu_find(const CPUCacheKey &key) { + auto search = m_cpu_cache_map.find(key); + const bool found = search != m_cpu_cache_map.end(); + if (found) { + return search->second; + } + return CacheImagePixelData(); + } + + // Remove the key from the image GPU cache. + bool gpu_erase(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey &key) { + const auto search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (found) { + MTexture *texture = search->second; + + assert(texture_manager != nullptr); + texture_manager->releaseTexture(texture); + m_gpu_cache_map.erase(search); + } + return found; + } + + // Remove the key from the image CPU cache. + bool cpu_erase(const CPUCacheKey &key) { + auto search = m_cpu_cache_map.find(key); + const bool found = search != m_cpu_cache_map.end(); + if (found) { + CacheImagePixelData value = search->second; + // TODO: De-allocate the cache value. We must clear the + // existing memory before adding the new pixel data. + m_cpu_cache_map.erase(search); + } + return found; + } + + // C++ 11; deleting the methods we don't want to ensure they can never be + // used. + // + // Note: Scott Meyers mentions in his Effective Modern C++ book, + // that deleted functions should generally be public as it results + // in better error messages due to the compilers behavior to check + // accessibility before deleted status. + ImageCache(ImageCache const &) = delete; + void operator=(ImageCache const &) = delete; + +private: + // TODO: Implement an LRU with standard C++. + // + // https://web.archive.org/web/20161203001546/http://timday.bitbucket.org/lru.html + // https://bitbucket.org/timday/lru_cache/src/master/include/lru_cache_using_std.h + // + CPUMap m_cpu_cache_map; + GPUMap m_gpu_cache_map; + + // Pre-allocated empty string to be re-used inside the class, to + // avoid reallocations. + MString m_empty_string; +}; + +MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, + ImageCache &image_cache, MImage &temp_image, + const MString &file_path, + const MImage::MPixelType pixel_type, + const bool do_texture_update); + +} // namespace mmsolver + +#endif // MM_IMAGE_CACHE_H diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index 23d906042..777c79449 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2022, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -25,6 +25,11 @@ #define _USE_MATH_DEFINES #include +// STL +#include +#include +#include + // Maya #include #include @@ -46,6 +51,10 @@ #include // MM Solver +#include +#include + +#include "ImageCache.h" #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/render/shader/shader_utils.h" #include "mmSolver/shape/constant_texture_data.h" @@ -65,9 +74,7 @@ ImagePlaneGeometryOverride::ImagePlaneGeometryOverride(const MObject &obj) , m_draw_image_size(false) , m_draw_camera_size(false) , m_geometry_node_type(MFn::kInvalid) - , m_use_shader_node(true) - , m_use_image_read(false) - , m_use_color_bars(false) + , m_use_color_plug(false) , m_image_display_channel(ImageDisplayChannel::kAll) , m_color_gain(1.0f) , m_alpha_gain(1.0f) @@ -75,8 +82,12 @@ ImagePlaneGeometryOverride::ImagePlaneGeometryOverride(const MObject &obj) , m_flip(false) , m_flop(false) , m_is_transparent(false) + , m_frame(0) , m_file_path() + , m_input_color_space_name() + , m_output_color_space_name() , m_shader(nullptr) + , m_update_shader(false) , m_color_texture(nullptr) , m_texture_sampler(nullptr) { m_model_editor_changed_callback_id = MEventMessage::addEventCallback( @@ -143,8 +154,9 @@ void ImagePlaneGeometryOverride::shader_link_lost_func( } MHWRender::DrawAPI ImagePlaneGeometryOverride::supportedDrawAPIs() const { - return (MHWRender::kOpenGL | MHWRender::kDirectX11 | - MHWRender::kOpenGLCoreProfile); + // TODO: We cannot support DirectX, unless we also write another + // mmImagePlane for DirectX. Legacy OpenGL is also unsupported. + return MHWRender::kOpenGLCoreProfile; } bool getUpstreamNodeFromConnection(const MObject &this_node, @@ -268,11 +280,14 @@ void ImagePlaneGeometryOverride::query_node_attributes( bool &out_visible_to_camera_only, bool &out_is_under_camera, bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, bool &out_draw_camera_size, MString &out_camera_size, - bool &out_use_shader_node, bool &out_use_image_read, - bool &out_use_color_bars, ImageDisplayChannel &out_image_display_channel, + bool &out_use_color_plug, ImageDisplayChannel &out_image_display_channel, float &out_color_gain, float &out_alpha_gain, bool &out_ignore_alpha, bool &out_flip, bool &out_flop, bool &out_is_transparent, - MString &out_file_path, MPlug &out_color_plug) { + mmcore::FrameValue &out_frame, MString &out_file_path, + MString &out_input_color_space_name, MString &out_output_color_space_name, + MPlug &out_color_plug) { + const bool verbose = false; + MDagPath objPath; MDagPath::getAPathTo(node, objPath); @@ -324,23 +339,12 @@ void ImagePlaneGeometryOverride::query_node_attributes( calculate_node_camera_size_string(objPath, double_precision, out_draw_camera_size, out_camera_size); - // "use" attributes. + // Shader attributes. { - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_shader_node, - out_use_shader_node); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_image_read, - out_use_image_read); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_color_plug, + out_use_color_plug); CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_color_bars, - out_use_color_bars); - CHECK_MSTATUS(status); - } - - // Shader attributes. - { status = getNodeAttr(objPath, ImagePlaneShapeNode::m_color_gain, out_color_gain); CHECK_MSTATUS(status); @@ -371,14 +375,44 @@ void ImagePlaneGeometryOverride::query_node_attributes( out_is_transparent); CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_frame_number, + out_frame); + CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_file_path, out_file_path); CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_input_color_space, + out_input_color_space_name); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_output_color_space, + out_output_color_space_name); + CHECK_MSTATUS(status); + status = getNodeAttrPlug(objPath, ImagePlaneShapeNode::m_color, out_color_plug); CHECK_MSTATUS(status); } + + // Find the input/output file color spaces. + // + // TODO: Do not re-calculate this each update. Compute once and + // cache the results. + const char *file_color_space_name = + mmcolorio::guess_color_space_name_from_file_path( + out_file_path.asChar()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" + << " file_color_space_name=\"" << file_color_space_name + << "\"."); + + const char *output_color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kSceneLinear); + out_output_color_space_name = MString(output_color_space_name); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" + << " out_output_color_space_name=\"" + << out_output_color_space_name.asChar() << "\"."); } void find_geometry_node_path(MObject &node, MString &attr_name, @@ -499,13 +533,24 @@ void ImagePlaneGeometryOverride::updateDG() { } // Query Attributes from the base node. + MString temp_input_color_space_name = ""; + MString temp_output_color_space_name = ""; ImagePlaneGeometryOverride::query_node_attributes( m_this_node, m_camera_node_path, m_visible, m_visible_to_camera_only, m_is_under_camera, m_draw_hud, m_draw_image_size, m_image_size, - m_draw_camera_size, m_camera_size, m_use_shader_node, m_use_image_read, - m_use_color_bars, m_image_display_channel, m_color_gain, m_alpha_gain, - m_ignore_alpha, m_flip, m_flop, m_is_transparent, m_file_path, + m_draw_camera_size, m_camera_size, m_use_color_plug, + m_image_display_channel, m_color_gain, m_alpha_gain, m_ignore_alpha, + m_flip, m_flop, m_is_transparent, m_frame, m_file_path, + temp_input_color_space_name, temp_output_color_space_name, m_color_plug); + if ((m_input_color_space_name.asChar() != + temp_input_color_space_name.asChar()) || + (m_input_color_space_name.asChar() != + temp_input_color_space_name.asChar())) { + m_input_color_space_name = temp_input_color_space_name; + m_output_color_space_name = temp_output_color_space_name; + m_update_shader = true; + } } MTexture *create_color_bars_texture( @@ -552,218 +597,17 @@ MTexture *create_plug_texture(MHWRender::MTextureManager *textureManager, return textureManager->acquireTexture(texture_node, allowBackgroundLoad); } -void *get_image_pixel_data(const MImage &image, - const MImage::MPixelType pixel_type, - const uint8_t number_of_channels, - uint8_t &out_bytes_per_channel, - MHWRender::MRasterFormat &out_texture_format) { - const bool verbose = false; - - const uint32_t print_num_pixels = 8; - void *pixel_data = nullptr; - if (pixel_type == MImage::MPixelType::kByte) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" - << " pixel_type=MImage::MPixelType::kByte"); - - // 8-bit unsigned integers use 1 byte. - out_bytes_per_channel = 1; - - const bool is_rgba = image.isRGBA(); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" - << " is_rgba=" << is_rgba); - if (is_rgba) { - out_texture_format = MHWRender::kR8G8B8A8_UNORM; - } else { - out_texture_format = MHWRender::kB8G8R8A8; - } - - unsigned char *pixels = image.pixels(); - - for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; - const uint32_t r = static_cast(pixels[index + 0]); - const uint32_t g = static_cast(pixels[index + 1]); - const uint32_t b = static_cast(pixels[index + 2]); - const uint32_t a = static_cast(pixels[index + 3]); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" - << " row=" << row << " pixel=" << r << ", " << g - << ", " << b << ", " << a); - } - - pixel_data = static_cast(pixels); - } else if (pixel_type == MImage::MPixelType::kFloat) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" - << " pixel_type=MImage::MPixelType::kFloat"); - - // 32-bit floats use 4 bytes. - out_bytes_per_channel = 4; - - out_texture_format = MHWRender::kR32G32B32A32_FLOAT; - - float *floatPixels = image.floatPixels(); - - for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; - const float r = floatPixels[index + 0]; - const float g = floatPixels[index + 1]; - const float b = floatPixels[index + 2]; - const float a = floatPixels[index + 3]; - MMSOLVER_MAYA_VRB("mmImagePlaneShape: get_image_pixel_data:" - << " row=" << row << " pixel=" << r << ", " << g - << ", " << b << ", " << a); - } - - pixel_data = static_cast(floatPixels); - } else { - MMSOLVER_MAYA_ERR("mmImagePlaneShape: get_image_pixel_data: " - << "Invalid pixel type is " - << static_cast(pixel_type)); - return nullptr; - } - - return pixel_data; -} - -MStatus update_texture(MHWRender::MTextureManager *textureManager, - MImage &image, const MImage::MPixelType pixel_type, - MTexture *texture, const bool generate_mip_maps, - const uint8_t number_of_channels) { - const bool verbose = false; - - MHWRender::MRasterFormat texture_format; - uint8_t bytes_per_channel = 0; - const void *pixel_data = - get_image_pixel_data(image, pixel_type, number_of_channels, - bytes_per_channel, texture_format); - - // The default value of this argument is 0. This means to use - // the texture's width * the number of bytes per pixel. - unsigned int rowPitch = 0; - - MHWRender::MTextureUpdateRegion *region = nullptr; - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: update_texture: update"); - MStatus status = - texture->update(pixel_data, generate_mip_maps, rowPitch, region); - - return status; -} - -MTexture *acquire_texture(MHWRender::MTextureManager *textureManager, - MImage &image, const MString &file_path, - const MImage::MPixelType pixel_type, - const bool generate_mip_maps, - const uint8_t number_of_channels) { - const bool verbose = false; - - unsigned int width = 0; - unsigned int height = 0; - image.getSize(width, height); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture:" - << " width=" << width << " height=" << height); - - MHWRender::MTextureDescription texture_desc; - texture_desc.setToDefault2DTexture(); - texture_desc.fWidth = width; - texture_desc.fHeight = height; - texture_desc.fDepth = 1; - - texture_desc.fMipmaps = 1; - texture_desc.fArraySlices = 1; - texture_desc.fTextureType = MHWRender::kImage2D; - - uint8_t bytes_per_channel = 0; - const void *pixel_data = - get_image_pixel_data(image, pixel_type, number_of_channels, - bytes_per_channel, texture_desc.fFormat); - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture: " - << "pixel_data=" << pixel_data); - if (!pixel_data) { - MMSOLVER_MAYA_ERR("mmImagePlaneShape: acquire_texture: " - << "Invalid pixel data! "); - return nullptr; - } - - // MImage seems to always convert images into 4 channels. - texture_desc.fBytesPerRow = number_of_channels * bytes_per_channel * width; - texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * height; - - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: acquire_texture:" - << " number_of_channels=" << static_cast(number_of_channels) - << " bytes_per_channel=" << static_cast(bytes_per_channel)); - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture:" - << " fBytesPerRow=" << texture_desc.fBytesPerRow - << " fBytesPerSlice=" << texture_desc.fBytesPerSlice); - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: acquire_texture: acquireTexture"); - return textureManager->acquireTexture(file_path, texture_desc, pixel_data, - generate_mip_maps); -} - -MTexture *read_image_file(MHWRender::MTextureManager *textureManager, - MImage &image, const MString &file_path, - const MImage::MPixelType pixel_type, - const bool do_texture_update) { - const bool verbose = false; - MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" - << " file_path=" << file_path.asChar()); - - MTexture *texture = textureManager->findTexture(file_path); - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: read_image_file: findTexture: " << texture); - if (texture && !do_texture_update) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" - << " texture=" << texture); - return texture; - } - - // TODO: Should we test to see if the file exists first, before - // attempting to read, or just catch the failure? - MStatus status = image.readFromFile(file_path, pixel_type); - CHECK_MSTATUS(status); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: read_image_file:" - << " failed to read image \"" << file_path.asChar() - << "\"."); - return nullptr; - } - - image.verticalFlip(); - - // TODO: Apply colour correction via OCIO. - - const bool generate_mip_maps = false; - const uint8_t number_of_channels = 4; - if (texture) { - status = update_texture(textureManager, image, pixel_type, texture, - generate_mip_maps, number_of_channels); - CHECK_MSTATUS(status); - if (status == MS::kSuccess) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" - << " texture updated!"); - } - } else { - texture = acquire_texture(textureManager, image, file_path, pixel_type, - generate_mip_maps, number_of_channels); - } - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: read_image_file:" - << " texture=" << texture); - - return texture; -} - void ImagePlaneGeometryOverride::set_shader_instance_parameters( MShaderInstance *shader, MHWRender::MTextureManager *textureManager, const float color_gain, const float alpha_gain, const bool ignore_alpha, const bool flip, const bool flop, const bool is_transparent, - const ImageDisplayChannel image_display_channel, const MString file_path, + const ImageDisplayChannel image_display_channel, + const mmcore::FrameValue frame, const MString &file_path, + const MString &input_color_space_name, + const MString &output_color_space_name, MHWRender::MTexture *out_color_texture, const MHWRender::MSamplerState *out_texture_sampler, - MPlug &out_color_plug) { + const bool use_color_plug, MPlug &out_color_plug) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); @@ -788,14 +632,22 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( << shader->isTransparent()); MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); + + rust::Str file_path_rust_str = rust::Str(file_path.asChar()); + rust::String expanded_file_path_rust_string = + mmcore::expand_file_path_string(file_path_rust_str, frame); + MString expanded_file_path(expanded_file_path_rust_string.data(), + expanded_file_path_rust_string.length()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: expanded_file_path=" + << expanded_file_path.asChar()); + + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: start use_color_plug=" << use_color_plug); MMSOLVER_MAYA_VRB( "mmImagePlaneShape: start out_color_texture=" << out_color_texture); if (!out_color_texture) { - if (m_use_color_bars) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); - out_color_texture = create_color_bars_texture(textureManager); - } else if (m_use_image_read) { + if (!use_color_plug) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; @@ -803,16 +655,20 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; const bool do_texture_update = false; - out_color_texture = - read_image_file(textureManager, m_image, file_path, pixel_type, - do_texture_update); - } else if (!out_color_plug.isNull()) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color plug texture"); - out_color_texture = - create_plug_texture(textureManager, out_color_plug); + ImageCache &image_cache = ImageCache::getInstance(); + out_color_texture = read_image_file( + textureManager, image_cache, m_temp_image, expanded_file_path, + pixel_type, do_texture_update); + } else { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); - out_color_texture = create_color_bars_texture(textureManager); + if (!out_color_plug.isNull()) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color plug texture"); + out_color_texture = + create_plug_texture(textureManager, out_color_plug); + } else { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); + out_color_texture = create_color_bars_texture(textureManager); + } } if (out_color_texture) { @@ -885,11 +741,9 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( MHWRender::MTextureAssignment texture_assignment; texture_assignment.texture = out_color_texture; shader->setParameter("gImageTexture", texture_assignment); - - textureManager->releaseTexture(out_color_texture); out_color_texture = nullptr; } else { - MMSOLVER_MAYA_WRN( + MMSOLVER_MAYA_VRB( "mmImagePlaneShape: Could not get color texture; " "did not assign texture." << " out_color_texture=" << out_color_texture); @@ -937,8 +791,8 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, if (index >= 0) { wireframeItem = list.itemAt(index); } else { - // MMSOLVER_MAYA_INFO("mmImagePlaneShape: Generate wireframe - // MRenderItem..."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: Generate wireframe MRenderItem..."); wireframeItem = MRenderItem::Create( renderItemName_imagePlaneWireframe, MRenderItem::DecorationItem, MGeometry::kLines); @@ -1017,82 +871,64 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, << "shadedItem->supportsAdvancedTransparency()=" << shadedItem->supportsAdvancedTransparency()); - if (m_use_shader_node) { - if (!m_shader_node.isNull()) { - bool nonTextured = false; - auto linkLostCb = (MHWRender::MRenderItem::LinkLostCallback) - ImagePlaneGeometryOverride::shader_link_lost_func; -#if MAYA_API_VERSION >= 20200000 - MMSOLVER_MAYA_DBG( - "mmImagePlaneShape: " - << "shadedItem->setShaderFromNode2: " - << "link_lost_count=" - << m_shader_link_lost_user_data_ptr->link_lost_count - << " set_shader_count=" - << m_shader_link_lost_user_data_ptr->set_shader_count); - m_shader_link_lost_user_data_ptr->set_shader_count += 1; - - shadedItem->setShaderFromNode2( - m_shader_node, m_geometry_node_path, linkLostCb, - m_shader_link_lost_user_data_ptr, nonTextured); -#elif - MMSOLVER_MAYA_DBG( - "mmImagePlaneShape: " - << "shadedItem->setShaderFromNode: " - << "link_lost_count=" - << m_shader_link_lost_user_data.link_lost_count - << " set_shader_count=" - << m_shader_link_lost_user_data.set_shader_count); - m_shader_link_lost_user_data.set_shader_count += 1; - - // NOTE: 'MRenderItem::setShaderFromNode()' is deprecated - // in Maya 2020 and above. - shadedItem->setShaderFromNode( - m_shader_node, m_geometry_node_path, linkLostCb, - &m_shader_link_lost_user_data, nonTextured); -#endif - - shadedItem->setTreatAsTransparent(m_is_transparent); + if (!m_shader || m_update_shader) { + if (m_shader) { + shaderManager->releaseShader(m_shader); + } - } else { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: " - << "Shader node is not valid, " - << "using fallback blue shader."); - MShaderInstance *shader = shaderManager->getStockShader( - MShaderManager::k3dSolidShader); - if (shader) { - static const float color[] = {0.0f, 0.0f, 1.0f, 1.0f}; - shader->setParameter("solidColor", color); - shadedItem->setShader(shader); - shaderManager->releaseShader(shader); + const MString shader_file_path = + mmsolver::render::find_shader_file_path("mmImagePlane.ogsfx"); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: found shader_file_path=\"" + << shader_file_path << "\""); + + if (shader_file_path.length() > 0) { + MString shader_text = + mmsolver::render::read_shader_file(shader_file_path); + + std::string ocio_shader_text; + mmcolorio::generate_shader_text( + m_input_color_space_name.asChar(), + m_output_color_space_name.asChar(), ocio_shader_text); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: ocio_shader_text=\"" + << ocio_shader_text << "\""); + if (ocio_shader_text.size() > 0) { + const MString ocio_function_declare_text = MString( + "vec4 OCIODisplay(vec4 passthrough) { return " + "passthrough; " + "}"); + MStatus status = shader_text.substitute( + ocio_function_declare_text, + MString(ocio_shader_text.c_str())); + CHECK_MSTATUS(status); } - } - } else { - if (!m_shader) { - m_shader = mmsolver::render::compile_shader_file("mmImagePlane", - "Main"); + + m_shader = + mmsolver::render::compile_shader_text(shader_text, "Main"); } if (m_shader) { - MHWRender::MTextureManager *textureManager = - renderer->getTextureManager(); - if (!textureManager) { - MMSOLVER_MAYA_WRN( - "mmImagePlaneShape: Could not get MTextureManager."); - return; - } + m_update_shader = false; + } + } - set_shader_instance_parameters( - m_shader, textureManager, m_color_gain, m_alpha_gain, - m_ignore_alpha, m_flip, m_flop, m_is_transparent, - m_image_display_channel, m_file_path, m_color_texture, - m_texture_sampler, m_color_plug); + if (m_shader) { + MHWRender::MTextureManager *textureManager = + renderer->getTextureManager(); + if (!textureManager) { + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: Could not get MTextureManager."); + return; + } - shadedItem->setShader(m_shader); + set_shader_instance_parameters( + m_shader, textureManager, m_color_gain, m_alpha_gain, + m_ignore_alpha, m_flip, m_flop, m_is_transparent, + m_image_display_channel, m_frame, m_file_path, + m_input_color_space_name, m_output_color_space_name, + m_color_texture, m_texture_sampler, m_use_color_plug, + m_color_plug); - // // Once assigned, no need to hold on to shader instance - // shaderManager->releaseShader(m_shader); - } + shadedItem->setShader(m_shader); } } } diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.h b/src/mmSolver/shape/ImagePlaneGeometryOverride.h index b031546ac..cbaa87130 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.h +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2022, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -39,6 +39,9 @@ #include // MM Solver +#include + +#include "ImageCache.h" #include "ImagePlaneShapeNode.h" #include "mmSolver/utilities/debug_utils.h" @@ -110,21 +113,24 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { bool &out_visible_to_camera_only, bool &out_is_under_camera, bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, bool &out_draw_camera_size, MString &out_camera_size, - bool &out_use_shader_node, bool &out_use_image_read, - bool &out_use_color_bars, + bool &out_use_color_plug, ImageDisplayChannel &out_image_display_channel, float &out_color_gain, float &out_alpha_gain, bool &out_ignore_alpha, bool &out_flip, - bool &out_flop, bool &out_is_transparent, MString &out_file_path, - MPlug &out_color_plug); + bool &out_flop, bool &out_is_transparent, mmcore::FrameValue &out_frame, + MString &out_file_path, MString &out_input_color_space_name, + MString &out_output_color_space_name, MPlug &out_color_plug); void set_shader_instance_parameters( MShaderInstance *shader, MHWRender::MTextureManager *textureManager, const float color_gain, const float alpha_gain, const bool ignore_alpha, const bool flip, const bool flop, const bool is_transparent, const ImageDisplayChannel image_display_channel, - const MString file_path, MHWRender::MTexture *out_color_texture, + const mmcore::FrameValue frame, const MString &file_path, + const MString &input_color_space_name, + const MString &output_color_space_name, + MHWRender::MTexture *out_color_texture, const MHWRender::MSamplerState *out_texture_sampler, - MPlug &out_color_plug); + const bool use_color_plug, MPlug &out_color_plug); MObject m_this_node; MDagPath m_geometry_node_path; @@ -140,13 +146,12 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { bool m_draw_hud; bool m_draw_image_size; bool m_draw_camera_size; + bool m_update_shader; MString m_image_size; MString m_camera_size; MCallbackId m_model_editor_changed_callback_id; - bool m_use_shader_node; - bool m_use_image_read; - bool m_use_color_bars; + bool m_use_color_plug; // Shader attributes. MShaderInstance *m_shader; @@ -157,11 +162,14 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { bool m_flip; bool m_flop; bool m_is_transparent; + mmcore::FrameValue m_frame; MString m_file_path; + MString m_input_color_space_name; + MString m_output_color_space_name; MPlug m_color_plug; // Texture caching - MImage m_image; + MImage m_temp_image; MHWRender::MTexture *m_color_texture; const MHWRender::MSamplerState *m_texture_sampler; diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.cpp b/src/mmSolver/shape/ImagePlaneShapeNode.cpp index 7e91100ef..81d92396d 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.cpp +++ b/src/mmSolver/shape/ImagePlaneShapeNode.cpp @@ -80,11 +80,8 @@ MObject ImagePlaneShapeNode::m_geometry_node; MObject ImagePlaneShapeNode::m_shader_node; MObject ImagePlaneShapeNode::m_camera_node; -MObject ImagePlaneShapeNode::m_use_shader_node; -MObject ImagePlaneShapeNode::m_use_image_read; -MObject ImagePlaneShapeNode::m_use_color_bars; - // Shader Attributes +MObject ImagePlaneShapeNode::m_use_color_plug; MObject ImagePlaneShapeNode::m_image_display_channel; MObject ImagePlaneShapeNode::m_color_gain; MObject ImagePlaneShapeNode::m_alpha_gain; @@ -92,7 +89,10 @@ MObject ImagePlaneShapeNode::m_ignore_alpha; MObject ImagePlaneShapeNode::m_flip; MObject ImagePlaneShapeNode::m_flop; MObject ImagePlaneShapeNode::m_is_transparent; +MObject ImagePlaneShapeNode::m_frame_number; MObject ImagePlaneShapeNode::m_file_path; +MObject ImagePlaneShapeNode::m_input_color_space; +MObject ImagePlaneShapeNode::m_output_color_space; MObject ImagePlaneShapeNode::m_color; ImagePlaneShapeNode::ImagePlaneShapeNode() {} @@ -274,26 +274,12 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(msgAttr.setKeyable(false)); CHECK_MSTATUS(addAttribute(m_camera_node)); - m_use_shader_node = nAttr.create("useShaderNode", "useshdnd", - MFnNumericData::kBoolean, true); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_use_shader_node)); - - m_use_image_read = nAttr.create("useImageRead", "useimgrd", + m_use_color_plug = nAttr.create("useColorPlug", "useclrplg", MFnNumericData::kBoolean, true); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_use_image_read)); - - m_use_color_bars = nAttr.create("useColorBars", "usecolrbrs", - MFnNumericData::kBoolean, false); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_use_color_bars)); + CHECK_MSTATUS(addAttribute(m_use_color_plug)); // Which channel of the image should be displayed? short value_all = static_cast(ImageDisplayChannel::kAll); @@ -365,6 +351,12 @@ MStatus ImagePlaneShapeNode::initialize() { nAttr.setNiceNameOverride(MString("Is Transparent (Shader)"))); CHECK_MSTATUS(addAttribute(m_is_transparent)); + m_frame_number = + nAttr.create("frameNumber", "frmnmb", MFnNumericData::kInt, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_frame_number)); + // Create empty string data to be used as attribute default // (string) value. MFnStringData empty_string_data; @@ -376,6 +368,19 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(tAttr.setUsedAsFilename(true)); CHECK_MSTATUS(addAttribute(m_file_path)); + m_input_color_space = tAttr.create("inputColorSpace", "incolspc", + MFnData::kString, empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); + CHECK_MSTATUS(addAttribute(m_input_color_space)); + + m_output_color_space = + tAttr.create("outputColorSpace", "outcolspc", MFnData::kString, + empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); + CHECK_MSTATUS(addAttribute(m_output_color_space)); + m_color = nAttr.createColor("shaderColor", "shdcl"); CHECK_MSTATUS(nAttr.setDefault(0.0f, 0.58824f, 0.644f)); CHECK_MSTATUS(nAttr.setStorable(true)); diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.h b/src/mmSolver/shape/ImagePlaneShapeNode.h index f8d255346..d03b6279d 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.h +++ b/src/mmSolver/shape/ImagePlaneShapeNode.h @@ -102,12 +102,8 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MObject m_shader_node; static MObject m_camera_node; - // "Use" Attributes - static MObject m_use_shader_node; - static MObject m_use_image_read; - static MObject m_use_color_bars; - // Shader Attributes + static MObject m_use_color_plug; static MObject m_image_display_channel; static MObject m_color_gain; static MObject m_alpha_gain; @@ -115,7 +111,10 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MObject m_flip; static MObject m_flop; static MObject m_is_transparent; + static MObject m_frame_number; static MObject m_file_path; + static MObject m_input_color_space; + static MObject m_output_color_space; static MObject m_color; }; diff --git a/src/mmSolver/utilities/path_utils.cpp b/src/mmSolver/utilities/path_utils.cpp new file mode 100644 index 000000000..766e303e5 --- /dev/null +++ b/src/mmSolver/utilities/path_utils.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "path_utils.h" + +// Maya +#include +#include +#include + +// MM Solver +#include "debug_utils.h" + +namespace mmpath { + +// This changes the given 'file_path' directly to a resolve file path, +// or returns a non-MStatus::kSuccess status. +MStatus resolve_input_file_path(MString &file_path) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + auto file_object = MFileObject(); + file_object.setRawFullName(file_path); + file_object.setResolveMethod(MFileObject::kInputFile); + + bool path_exists = file_object.exists(); + if (!path_exists) { + MString resolved_file_path = file_object.resolvedFullName(); + status = MStatus::kFailure; + MMSOLVER_MAYA_VRB( + "mmpath::resolve_input_file_path: Could not find file path " + << "\"" << file_path.asChar() << "\", resolved path " + << "\"" << resolved_file_path.asChar() << "\"."); + return status; + } + + MString resolved_file_path = file_object.resolvedFullName(); + status = MStatus::kFailure; + if (resolved_file_path.length() > 0) { + MMSOLVER_MAYA_VRB("mmpath::resolve_input_file_path: resolved file path " + << "\"" << resolved_file_path.asChar() << "\"."); + file_path = file_object.resolvedFullName(); + status = MStatus::kSuccess; + } + return status; +} + +} // namespace mmpath diff --git a/src/mmSolver/utilities/path_utils.h b/src/mmSolver/utilities/path_utils.h new file mode 100644 index 000000000..0caa62216 --- /dev/null +++ b/src/mmSolver/utilities/path_utils.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Generic path helper functions. + */ + +#ifndef PATH_UTILS_H +#define PATH_UTILS_H + +// STL +#include // fabs +#include +#include // stringstream +#include + +// Maya +#include +#include + +namespace mmpath { + +MStatus resolve_input_file_path(MString &file_path); + +} // namespace mmpath + +#endif // PATH_UTILS_H diff --git a/tools/lensdistortion/src/CMakeLists.txt b/tools/lensdistortion/src/CMakeLists.txt index 4ab37c891..b0655ebe8 100644 --- a/tools/lensdistortion/src/CMakeLists.txt +++ b/tools/lensdistortion/src/CMakeLists.txt @@ -28,6 +28,9 @@ set(source_files # Add test executable using the C++ bindings. add_executable(${lensdistortion_exe_name} ${source_files}) +# OpenColorIO is needed by 'mmsolverlibs_cpp'. +find_package(OpenColorIO REQUIRED) + # MM Solver standalone libraries. find_package(mmsolverlibs_cpp REQUIRED) find_package(mmsolverlibs_rust REQUIRED) @@ -37,7 +40,7 @@ target_link_libraries(${lensdistortion_exe_name} # 'target_link_libraries()' in '${PROJECT_ROOT}/src/CMakeLists.txt'. PUBLIC mmsolverlibs_rust::mmsolverlibs_rust PUBLIC mmsolverlibs_cpp::mmsolverlibs_cpp - + PRIVATE OpenColorIO::OpenColorIO PRIVATE ${rust_depend_on_libraries} ) From 9e5f1d8ff8f62aad5dea72e468078b3f3520499a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 29 May 2024 22:43:58 +1000 Subject: [PATCH 045/295] Hide *.orig files from git --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ed002761b..bc80187da 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ #* *~ *.bak +*.orig *.pyc *cppcheck-build-* cmake-build-* From 7941b1f9ea76b6cee720d6e864e2cdb7f4230304 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 29 May 2024 22:44:20 +1000 Subject: [PATCH 046/295] Fix typo in Findldpk.cmake --- share/cmake/modules/Findldpk.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/share/cmake/modules/Findldpk.cmake b/share/cmake/modules/Findldpk.cmake index a57b8c25c..b5272d5e9 100644 --- a/share/cmake/modules/Findldpk.cmake +++ b/share/cmake/modules/Findldpk.cmake @@ -142,7 +142,7 @@ if(NOT ldpk_FOUND AND MMSOLVER_DOWNLOAD_DEPENDENCIES AND ldpk_ALLOW_DOWNLOAD) set(ldpk_FOUND TRUE) set(ldpk_VERSION ${ldpk_FIND_VERSION}) - set(Ldpk_DIR "${_EXTERNAL_INSTALL_DIR}/ldpk/share/ldpk/cmake") + set(ldpk_DIR "${_EXTERNAL_INSTALL_DIR}/ldpk/share/ldpk/cmake") set(ldpk_INCLUDE_DIR "${_EXTERNAL_INSTALL_DIR}/ldpk/${CMAKE_INSTALL_INCLUDEDIR}") set(ldpk_URL From 492c3877cf3c5b01c496e4a58c1ec654e1c8f4bd Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 29 May 2024 22:45:14 +1000 Subject: [PATCH 047/295] Use fully qualified names for variables To avoid confusion when reading the code. --- lib/mmsolverlibs/include/mmsolverlibs/debug.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mmsolverlibs/include/mmsolverlibs/debug.h b/lib/mmsolverlibs/include/mmsolverlibs/debug.h index 5865508f8..6703b0d2a 100644 --- a/lib/mmsolverlibs/include/mmsolverlibs/debug.h +++ b/lib/mmsolverlibs/include/mmsolverlibs/debug.h @@ -272,11 +272,11 @@ struct TimestampBenchmark { Timestamp timestamp; Timestamp timestampTotal; - void start() { timestamp = get_timestamp(); }; - Timestamp stop() { return timestampTotal += get_timestamp() - timestamp; }; + void start() { TimestampBenchmark::timestamp = get_timestamp(); }; + Timestamp stop() { return TimestampBenchmark::timestampTotal += get_timestamp() - TimestampBenchmark::timestamp; }; double get_seconds(const uint32_t loopNums = 0) const { - double secs = timestamp_as_seconds(timestampTotal); + double secs = timestamp_as_seconds(TimestampBenchmark::timestampTotal); if (loopNums > 0) { secs /= loopNums; } From 9881dbceddbc1df455df70fe0a43adbd819eec06 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 29 May 2024 22:46:18 +1000 Subject: [PATCH 048/295] Remove unused commented code. --- tests/test/test_solver/test_camera_pose_from_points.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test/test_solver/test_camera_pose_from_points.py b/tests/test/test_solver/test_camera_pose_from_points.py index 8eaa1b665..027f9da6a 100644 --- a/tests/test/test_solver/test_camera_pose_from_points.py +++ b/tests/test/test_solver/test_camera_pose_from_points.py @@ -126,7 +126,6 @@ def test_six_point_pose_resection1(self): # Run solver! assert 'mmCameraPoseFromPoints' in dir(maya.cmds) s = time.time() - # for frame in frames: result = maya.cmds.mmCameraPoseFromPoints(**kwargs) print('result:', pprint.pformat(result)) e = time.time() From 6e334e584df10420f984e513ce4e1e908ecf6891 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 1 Jun 2024 18:10:52 +1000 Subject: [PATCH 049/295] Add shader display adjustments to MM ImagePlane. Adds new features and replaces the old Maya-shader shader node approach. The shader node was removed from the image plane. This change is not backwards compatible with previous mmSolver releases :( New display shader features: - Color Gain as RGB colour, not just float. - Exposure adjustment. - Gamma adjustment. - Saturation adjustment. - Soft-clip adjustment. - Saturation Image Channel mode. GitHub issue #254. --- .../AEmmImagePlaneShapeTemplate.mel | 72 +-- .../tools/createimageplane/_lib/constant.py | 19 +- .../tools/createimageplane/_lib/main.py | 97 ++-- .../createimageplane/_lib/mmimageplane.py | 155 +----- .../tools/createimageplane/constant.py | 22 +- python/mmSolver/utils/imageseq.py | 13 + share/shader/mmImagePlane.ogsfx | 98 +++- .../shape/ImagePlaneGeometryOverride.cpp | 442 +++++++++--------- .../shape/ImagePlaneGeometryOverride.h | 34 +- src/mmSolver/shape/ImagePlaneShapeNode.cpp | 223 +++++---- src/mmSolver/shape/ImagePlaneShapeNode.h | 42 +- 11 files changed, 653 insertions(+), 564 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel index 075ba7ffe..73dbd9dc4 100644 --- a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel +++ b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel @@ -1,5 +1,5 @@ // -// Copyright (C) 2022 David Cattermole. +// Copyright (C) 2022, 2024 David Cattermole. // // This file is part of mmSolver. // @@ -97,11 +97,14 @@ global proc AEmmImagePlaneShape_browser( string $cmd = ""; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "\n"; $cmd = $cmd + "image_seq = tool.prompt_user_for_image_sequence();\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; + $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; + $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; + $cmd = $cmd + "\n"; $cmd = $cmd + "if image_seq:\n"; - $cmd = $cmd + " mm_ip_shp = \"" + $image_plane_shp + "\";\n"; - $cmd = $cmd + " attr_name = \"" + $attr_name + "\";\n"; - $cmd = $cmd + " current_slot_attr = \"" + $slot_attr_name + "\";\n"; $cmd = $cmd + " if attr_name == current_slot_attr:\n"; $cmd = $cmd + " lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; @@ -132,16 +135,18 @@ global proc AEmmImagePlaneShape_sequenceSlotChanged( AEmmImagePlaneShape_setSlotValue($image_plane_shp, $slot_attr_value); string $cmd = ""; - $cmd = $cmd + "import os.path;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "\n"; $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; + $cmd = $cmd + "\n"; $cmd = $cmd + "image_seq = maya.cmds.getAttr(\n"; $cmd = $cmd + " mm_ip_shp + '.' + current_slot_attr) or ''\n"; - $cmd = $cmd + "if len(image_seq) == 0 or not os.path.isfile(image_seq):\n"; + $cmd = $cmd + "if len(image_seq) == 0:\n"; $cmd = $cmd + " image_seq = lib.get_default_image_path();\n"; + $cmd = $cmd + "\n"; $cmd = $cmd + "lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; $cmd = $cmd + " image_seq,\n"; @@ -233,12 +238,15 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) editorTemplate -beginLayout "Display" -collapse 0; editorTemplate -addControl "visibleToCameraOnly"; editorTemplate -addSeparator; - editorTemplate -addControl "exposure"; - editorTemplate -addControl "gamma"; editorTemplate -addControl "colorGain"; + editorTemplate -addControl "colorExposure"; + editorTemplate -addControl "colorGamma"; + editorTemplate -addControl "colorSaturation"; + editorTemplate -addControl "colorSoftClip"; editorTemplate -addControl "alphaGain"; - // editorTemplate -addSeparator; - // editorTemplate -addControl "colorSpace"; // Might not be possible. + editorTemplate -addSeparator; + editorTemplate -addControl "imageIgnoreAlpha"; + editorTemplate -addControl "displayChannel"; editorTemplate -endLayout; editorTemplate -beginLayout "Image Sequence" -collapse 0; @@ -270,6 +278,18 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) "AEmmImagePlaneShape_imageSequenceReplace" "imageSequenceAlternate3"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageWidth"; + editorTemplate -addControl "imageHeight"; + editorTemplate -addControl "imagePixelAspect"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageSequenceStartFrame"; + editorTemplate -addControl "imageSequenceEndFrame"; + editorTemplate -addSeparator; + // TODO: Use mmColorIO results to allow users to manually give the + // inputColorSpace. + editorTemplate -addControl "inputColorSpace"; + editorTemplate -addSeparator; // TODO: Add radio button to choose what will connect to the // 'imageSequenceFrame' value? Options are: // - Scene Time (time1) @@ -279,15 +299,8 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) editorTemplate -addControl "imageSequenceFirstFrame"; editorTemplate -addControl "imageSequenceFrameOutput"; editorTemplate -addSeparator; - editorTemplate -addControl "imageLoadEnable"; - editorTemplate -addControl "imageUseAlphaChannel"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageWidth"; - editorTemplate -addControl "imageHeight"; - editorTemplate -addControl "imagePixelAspect"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageSequenceStartFrame"; - editorTemplate -addControl "imageSequenceEndFrame"; + editorTemplate -addControl "imageFlip"; + editorTemplate -addControl "imageFlop"; editorTemplate -endLayout; editorTemplate -beginLayout "HUD" -collapse 0; @@ -298,24 +311,31 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) // TODO: Add 'hudTextColor' - control the HUD text color. editorTemplate -endLayout; + // editorTemplate -beginLayout "Image Cache" -collapse 1; + // // TODO: Add controls to view and edit the image cache. + // editorTemplate -endLayout; + editorTemplate -beginLayout "Miscellaneous" -collapse 1; editorTemplate -addControl "meshResolution"; editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. + editorTemplate -addControl "shaderIsTransparent"; editorTemplate -endLayout; editorTemplate -beginLayout "Nodes" -collapse 1; - editorTemplate -addControl "shaderNode"; - editorTemplate -addControl "shaderFileNode"; editorTemplate -addControl "geometryNode"; editorTemplate -addControl "cameraNode"; editorTemplate -addControl "imagePlaneShapeNode"; editorTemplate -endLayout; - editorTemplate -suppress "imageSequencePadding"; - editorTemplate -suppress "cameraWidthInch"; - editorTemplate -suppress "cameraHeightInch"; - editorTemplate -suppress "lensHashCurrent"; - editorTemplate -suppress "lensHashPrevious"; + // // Internals that we don't want the user to see. + // editorTemplate -suppress "imageSequencePadding"; + // editorTemplate -suppress "cameraWidthInch"; + // editorTemplate -suppress "cameraHeightInch"; + // editorTemplate -suppress "lensHashCurrent"; + // editorTemplate -suppress "lensHashPrevious"; + // editorTemplate -suppress "imageFilePath"; + // editorTemplate -suppress "inputColorSpace"; + // editorTemplate -suppress "outputColorSpace"; AEmmNodeShapeTemplateCommonEnd($nodeName); } diff --git a/python/mmSolver/tools/createimageplane/_lib/constant.py b/python/mmSolver/tools/createimageplane/_lib/constant.py index c8fa6d3bd..20761606c 100644 --- a/python/mmSolver/tools/createimageplane/_lib/constant.py +++ b/python/mmSolver/tools/createimageplane/_lib/constant.py @@ -1,4 +1,4 @@ -# Copyright (C) 2022 David Cattermole. +# Copyright (C) 2022, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -17,3 +17,20 @@ # DEFAULT_IMAGE_SEQUENCE_ATTR_NAME = 'imageSequenceMain' +ALT_1_IMAGE_SEQUENCE_ATTR_NAME = 'imageSequenceAlternate1' +ALT_2_IMAGE_SEQUENCE_ATTR_NAME = 'imageSequenceAlternate2' +ALT_3_IMAGE_SEQUENCE_ATTR_NAME = 'imageSequenceAlternate3' + +VALID_INPUT_IMAGE_SEQUENCE_ATTR_NAMES = [ + DEFAULT_IMAGE_SEQUENCE_ATTR_NAME, + ALT_1_IMAGE_SEQUENCE_ATTR_NAME, + ALT_2_IMAGE_SEQUENCE_ATTR_NAME, + ALT_3_IMAGE_SEQUENCE_ATTR_NAME, +] + +SHADER_FILE_PATH_ATTR_NAME = 'imageFilePath' +INPUT_COLOR_SPACE_ATTR_NAME = 'inputColorSpace' +OUTPUT_COLOR_SPACE_ATTR_NAME = 'outputColorSpace' + +SCENE_LINEAR_FILE_EXTENSIONS = ['exr', 'sxr'] +SRGB_FILE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'tif', 'tiff', 'tga', 'iff'] diff --git a/python/mmSolver/tools/createimageplane/_lib/main.py b/python/mmSolver/tools/createimageplane/_lib/main.py index 18a8a555f..8f272dd32 100644 --- a/python/mmSolver/tools/createimageplane/_lib/main.py +++ b/python/mmSolver/tools/createimageplane/_lib/main.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020, 2022 David Cattermole. +# Copyright (C) 2020, 2022, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -25,12 +25,13 @@ import mmSolver.logger import mmSolver.api as mmapi import mmSolver.utils.camera as camera_utils +import mmSolver.utils.constant as const_utils +import mmSolver.utils.imageseq as imageseq_utils import mmSolver.utils.python_compat as pycompat import mmSolver.tools.createimageplane.constant as const import mmSolver.tools.createimageplane._lib.constant as lib_const import mmSolver.tools.createimageplane._lib.utilities as lib_utils -import mmSolver.tools.createimageplane._lib.shader as lib_shader import mmSolver.tools.createimageplane._lib.mmimageplane as lib_mmimageplane import mmSolver.tools.createimageplane._lib.polyplane as lib_polyplane import mmSolver.tools.createimageplane._lib.nativeimageplane as lib_nativeimageplane @@ -56,20 +57,18 @@ def create_image_plane_on_camera(cam, name=None): poly_plane_name, mm_ip_tfm, cam_shp ) - name_shade = name + 'Shader' - shader_network = lib_shader.create_network(name_shade, mm_ip_tfm) - name_img_shp = name + 'Shape' mm_ip_shp = lib_mmimageplane.create_shape_node( - name_img_shp, mm_ip_tfm, cam_shp, poly_plane_network, shader_network - ) - - # Shortcut connections to nodes. - lib_utils.force_connect_attr( - shader_network.file_node + '.message', mm_ip_tfm + '.shaderFileNode' + name_img_shp, + mm_ip_tfm, + cam_shp, + poly_plane_network, ) # Logic to calculate the frame number. + # + # TODO: Move this expression into a Maya node, because expressions + # are buggy and not flexible. frame_expr = const.FRAME_EXPRESSION.format(node=mm_ip_shp) frame_expr = frame_expr.replace('{{', '{') frame_expr = frame_expr.replace('}}', '}') @@ -79,17 +78,6 @@ def create_image_plane_on_camera(cam, name=None): shp_node_attr = mm_ip_shp + '.imageSequenceFrameOutput' maya.cmds.setAttr(shp_node_attr, lock=True) - # Set useFrameExtension temporarily. Setting useFrameExtension to - # False causes frameOffset to be locked (but we need to edit it). - is_seq = maya.cmds.getAttr(shader_network.file_node + '.useFrameExtension') - maya.cmds.setAttr(shader_network.file_node + '.useFrameExtension', True) - - file_node_attr = shader_network.file_node + '.frameExtension' - lib_utils.force_connect_attr(shp_node_attr, file_node_attr) - maya.cmds.setAttr(file_node_attr, lock=True) - - maya.cmds.setAttr(shader_network.file_node + '.useFrameExtension', is_seq) - # Image sequence. image_sequence_path = lib_utils.get_default_image_path() set_image_sequence(mm_ip_tfm, image_sequence_path) @@ -120,12 +108,12 @@ def convert_image_planes_on_camera(cam): lib_nativeimageplane.copy_depth_value(mm_ip_tfm, native_ip_shp) - name_shader = name + 'Shader' - shader_network = lib_shader.create_network(name_shader, mm_ip_tfm) - name_img_shp = name + 'Shape' mm_ip_shp = lib_mmimageplane.create_shape_node( - name_img_shp, mm_ip_tfm, cam_shp, poly_plane_network, shader_network + name_img_shp, + mm_ip_tfm, + cam_shp, + poly_plane_network, ) # Disable/hide the Maya image plane. @@ -138,20 +126,65 @@ def convert_image_planes_on_camera(cam): return ip_node_pairs +def _guess_color_space(file_path): + file_extension = file_path.lower().split('.')[-1] + if file_extension in lib_const.SCENE_LINEAR_FILE_EXTENSIONS: + color_space = maya.cmds.mmColorIO(roleSceneLinear=True) + elif file_extension in lib_const.SRGB_FILE_EXTENSIONS: + color_space = maya.cmds.mmColorIO(roleColorPicking=True) + else: + color_space = maya.cmds.mmColorIO(guessColorSpaceFromFile=file_path) + + if not color_space: + color_space = maya.cmds.mmColorIO(roleData=True) + if not color_space: + color_space = maya.cmds.mmColorIO(roleDefault=True) + + exists = maya.cmds.mmColorIO(colorSpaceExists=color_space) + if exists is False: + color_space = None + + return color_space + + def set_image_sequence(mm_image_plane_node, image_sequence_path, attr_name=None): if attr_name is None: attr_name = lib_const.DEFAULT_IMAGE_SEQUENCE_ATTR_NAME + assert isinstance(attr_name, str) + assert attr_name in lib_const.VALID_INPUT_IMAGE_SEQUENCE_ATTR_NAMES tfm, shp = lib_mmimageplane.get_image_plane_node_pair(mm_image_plane_node) if tfm is None or shp is None: LOG.warn('mmImagePlane transform/shape could not be found.') - file_node = lib_mmimageplane.get_file_node(tfm) - if file_node is None: - LOG.warn('mmImagePlane shader file node is invalid.') - if shp is not None: lib_mmimageplane.set_image_sequence(shp, image_sequence_path, attr_name) - if file_node is not None: - lib_shader.set_file_path(file_node, image_sequence_path) + lib_mmimageplane.set_image_sequence( + shp, image_sequence_path, lib_const.SHADER_FILE_PATH_ATTR_NAME + ) + + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME + ( + file_pattern, + _, + _, + _, + _, + ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) + first_frame_file_seq = file_pattern.replace('\\', '/') + + input_color_space = _guess_color_space(first_frame_file_seq) + output_color_space = maya.cmds.mmColorIO(roleSceneLinear=True) + + maya.cmds.setAttr( + shp + '.' + lib_const.INPUT_COLOR_SPACE_ATTR_NAME, + input_color_space, + type='string', + ) + maya.cmds.setAttr( + shp + '.' + lib_const.OUTPUT_COLOR_SPACE_ATTR_NAME, + output_color_space, + type='string', + ) + return diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index 93040e4be..121109eeb 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020, 2022 David Cattermole. +# Copyright (C) 2020, 2022, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -121,68 +121,6 @@ def _create_transform_attrs(image_plane_tfm): def _create_image_plane_shape_attrs(image_plane_shp): - # Exposure attribute - attr = 'exposure' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - attributeType='double', - softMinValue=-5.0, - softMaxValue=5.0, - defaultValue=0.0, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - - # Gamma attribute - attr = 'gamma' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - attributeType='double', - minValue=0.0, - softMaxValue=3.0, - defaultValue=1.0, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - - # Color Gain attribute - attr = 'colorGain' - default_value = 1.0 - lib_utils.add_attr_float3_color(image_plane_shp, attr, default_value) - - # Alpha Gain attribute - attr = 'alphaGain' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - attributeType='double', - minValue=0.0, - softMaxValue=1.0, - defaultValue=1.0, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - - # Use Image Alpha Channel attribute - attr = 'imageUseAlphaChannel' - maya.cmds.addAttr( - image_plane_shp, longName=attr, attributeType='bool', defaultValue=0 - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - - # Default Image Color attribute, display a dark-red color when an - # image is not found. - attr = 'imageDefaultColor' - default_value = 0.0 - lib_utils.add_attr_float3_color(image_plane_shp, attr, default_value) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr + 'R', 0.3) - maya.cmds.setAttr(node_attr + 'G', 0.0) - maya.cmds.setAttr(node_attr + 'B', 0.0) - # Image Load Enable attribute attr = 'imageLoadEnable' maya.cmds.addAttr( @@ -248,6 +186,8 @@ def _create_image_plane_shape_attrs(image_plane_shp): ) node_attr = image_plane_shp + '.' + attr maya.cmds.setAttr(node_attr, keyable=True) + dst_node_attr = image_plane_shp + '.imageFrameNumber' + lib_utils.force_connect_attr(node_attr, dst_node_attr) # Image Sequence details. maya.cmds.addAttr( @@ -320,7 +260,10 @@ def create_transform_node(name_tfm, cam_tfm, cam_shp): def create_shape_node( - name_img_shp, tfm, cam_shp, poly_plane_node_network, shader_node_network + name_img_shp, + tfm, + cam_shp, + poly_plane_node_network, ): """ Convert mesh to a mmImagePlaneShape. @@ -331,13 +274,6 @@ def create_shape_node( img_plane_poly_shp_original = poly_plane_node_network.mesh_shape_original poly_creator = poly_plane_node_network.plane_creator - shd_node = shader_node_network.shd_node - file_node = shader_node_network.file_node - color_gamma_node = shader_node_network.color_gamma_node - alpha_channel_blend_node = shader_node_network.alpha_channel_blend_node - image_load_invert_boolean_node = shader_node_network.image_load_invert_boolean_node - alpha_channel_reverse_node = shader_node_network.alpha_channel_reverse_node - mmapi.load_plugin() shp = maya.cmds.createNode('mmImagePlaneShape', name=name_img_shp, parent=tfm) @@ -365,45 +301,11 @@ def create_shape_node( # Nodes to drive the image plane shape. lib_utils.force_connect_attr(img_plane_poly_shp + '.outMesh', shp + '.geometryNode') - lib_utils.force_connect_attr(shd_node + '.outColor', shp + '.shaderNode') lib_utils.force_connect_attr(cam_shp + '.message', shp + '.cameraNode') # The image drives the pixel aspect ratio of the image plane. lib_utils.force_connect_attr(shp + '.imagePixelAspect', tfm + '.pixelAspect') - # Use the image alpha channel, or not - lib_utils.force_connect_attr( - shp + '.imageUseAlphaChannel', alpha_channel_blend_node + '.blender' - ) - - # Allow user to load the image, or not. - lib_utils.force_connect_attr( - shp + '.imageLoadEnable', image_load_invert_boolean_node + '.inputX' - ) - - # Color Exposure control. - lib_utils.force_connect_attr(shp + '.exposure', file_node + '.exposure') - - # Color Gamma control. - lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaX') - lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaY') - lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaZ') - - # Control file color multiplier - lib_utils.force_connect_attr(shp + '.colorGain', file_node + '.colorGain') - - # Control the alpha gain when 'imageUseAlphaChannel' is disabled. - lib_utils.force_connect_attr(shp + '.alphaGain', file_node + '.alphaGain') - lib_utils.force_connect_attr( - shp + '.alphaGain', alpha_channel_reverse_node + '.inputX' - ) - lib_utils.force_connect_attr( - shp + '.alphaGain', alpha_channel_reverse_node + '.inputY' - ) - lib_utils.force_connect_attr( - shp + '.alphaGain', alpha_channel_reverse_node + '.inputZ' - ) - # Set the camera size of the image plane shape HUD. lib_utils.force_connect_attr( tfm + '.horizontalFilmAperture', shp + '.cameraWidthInch' @@ -412,11 +314,6 @@ def create_shape_node( tfm + '.verticalFilmAperture', shp + '.cameraHeightInch' ) - # Default color for the image plane, when nothing is loaded. - lib_utils.force_connect_attr( - shp + '.imageDefaultColor', file_node + '.defaultColor' - ) - # Mesh Resolution attr drives the plane sub-divisions. node_attr = shp + '.meshResolution' lib_utils.force_connect_attr(node_attr, poly_creator + '.subdivisionsWidth') @@ -427,7 +324,6 @@ def create_shape_node( maya.cmds.setAttr(img_plane_poly_shp + '.intermediateObject', 1) # Add extra message attributes for finding nodes during callbacks. - maya.cmds.addAttr(shp, longName='shaderFileNode', attributeType='message') maya.cmds.addAttr(shp, longName='imagePlaneShapeNode', attributeType='message') return shp @@ -439,16 +335,15 @@ def set_image_sequence(shp, image_sequence_path, attr_name): format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME ( file_pattern, - start_frame, - end_frame, - pad_num, - is_seq, + _, + _, + _, + _, ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) - first_frame_file_seq = file_pattern + first_frame_file_seq = file_pattern.replace('\\', '/') mmapi.load_plugin() try: - first_frame_file_seq = first_frame_file_seq.replace('\\', '/') image_width_height = maya.cmds.mmReadImage( first_frame_file_seq, query=True, widthHeight=True ) @@ -471,6 +366,15 @@ def set_image_sequence(shp, image_sequence_path, attr_name): maya.cmds.setAttr(shp + '.imageWidth', lock=True) maya.cmds.setAttr(shp + '.imageHeight', lock=True) + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED + ( + file_pattern, + start_frame, + end_frame, + pad_num, + is_seq, + ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) + maya.cmds.setAttr(shp + '.' + attr_name, file_pattern, type='string') if not node_utils.node_is_referenced(shp): @@ -530,20 +434,3 @@ def get_image_plane_node_pair(node): tfm = get_transform_node(node) shp = node return tfm, shp - - -def get_file_node(image_plane_tfm): - file_node = None - conns = ( - maya.cmds.listConnections( - image_plane_tfm + '.shaderFileNode', - destination=False, - source=True, - plugs=False, - type='file', - ) - or [] - ) - if len(conns) > 0: - file_node = conns[0] - return file_node diff --git a/python/mmSolver/tools/createimageplane/constant.py b/python/mmSolver/tools/createimageplane/constant.py index 14982842c..ea0aa28e0 100644 --- a/python/mmSolver/tools/createimageplane/constant.py +++ b/python/mmSolver/tools/createimageplane/constant.py @@ -16,6 +16,8 @@ # along with mmSolver. If not, see . # +# NOTE: '{{' and '}}' is used in place of real '{' and '}' characters, +# to allow Python's 'str.format()' to work. DISPLAY_MODE_EXPRESSION = ''' if ({image_plane_tfm}.displayMode == 0) {{ @@ -27,9 +29,25 @@ }} ''' +# NOTE: '{{' and '}}' is used in place of real '{' and '}' characters, +# to allow Python's 'str.format()' to work. FRAME_EXPRESSION = ''' int $start_frame = {node}.imageSequenceStartFrame; +int $end_frame = {node}.imageSequenceEndFrame; int $first_frame = {node}.imageSequenceFirstFrame; -int $user_frame = {node}.imageSequenceFrame; -{node}.imageSequenceFrameOutput = ($start_frame - $first_frame) + $user_frame; +int $input_frame = {node}.imageSequenceFrame; + +int $result = ($start_frame - $first_frame) + $input_frame; + +// // Clamp to start and end frames. +// if ($result < $start_frame) +// {{ +// $result = $start_frame; +// }} +// else if ($result > $end_frame) +// {{ +// $result = $end_frame; +// }} + +{node}.imageSequenceFrameOutput = $result; ''' diff --git a/python/mmSolver/utils/imageseq.py b/python/mmSolver/utils/imageseq.py index 696d0d79e..290888e4f 100644 --- a/python/mmSolver/utils/imageseq.py +++ b/python/mmSolver/utils/imageseq.py @@ -91,6 +91,19 @@ def _get_image_sequence_start_end_frames(base_dir, file_name, file_extension): def expand_image_sequence_path(image_sequence_path, format_style): + """ + Expand a given image sequence path into tokens. + + The tokens are: + - file_pattern: str, the pattern of the file path. + - start_frame: int, first frame of the image sequence. + - end_frame: int, last frame of the image sequence. + - padding_num: int, number of padding digits for the frame number. + - is_seq: bool, is this a sequence? Otherwise it's a single frame. + + :returns: + Tuple of (file_pattern, start_frame, end_frame, padding_num, is_seq) + """ image_sequence_path = os.path.abspath(image_sequence_path) ( diff --git a/share/shader/mmImagePlane.ogsfx b/share/shader/mmImagePlane.ogsfx index 5c8cca55b..faa3b8b27 100644 --- a/share/shader/mmImagePlane.ogsfx +++ b/share/shader/mmImagePlane.ogsfx @@ -22,20 +22,37 @@ // Global variables provided by Maya. uniform mat4 gWVPXf : WorldViewProjection; -// The solid color uniform, its default value and several extra -// parameters -uniform vec4 gSolidColor : DIFFUSE = {1, 1, 1, 1}; -uniform float gColorGain = 1.0f; -uniform float gAlphaGain = 1.0f; +// Color and alpha modifications. +uniform vec4 gColorGain : DIFFUSE = {1, 1, 1, 1}; +uniform float gColorExposure = 0.0f; uniform float gColorGamma = 1.0f; +uniform float gColorSoftClip = 1.0f; +uniform float gAlphaGain = 1.0f; +uniform float4x4 gColorSaturationMatrix < string UIWidget = "None"; >; + +// Should we use the alpha channel from the image? uniform bool gIgnoreAlpha = false; + +// Flip or flop the image. uniform bool gFlip = false; uniform bool gFlop = false; -uniform bool gShowChannelRed = false; -uniform bool gShowChannelGreen = false; -uniform bool gShowChannelBlue = false; -uniform bool gShowChannelAlpha = false; + +// What type of channels to display to the user. +// +// NOTE: Matches the mmsolver::ImageDisplayChannel enum class. +#define DISPLAY_CHANNEL_RGBA (0) +#define DISPLAY_CHANNEL_RGB (1) +#define DISPLAY_CHANNEL_RED (2) +#define DISPLAY_CHANNEL_GREEN (3) +#define DISPLAY_CHANNEL_BLUE (4) +#define DISPLAY_CHANNEL_ALPHA (5) +#define DISPLAY_CHANNEL_LUMINANCE (6) +uniform int gDisplayChannel = 0; + +// When the image is not found the fallback color should be shown. +uniform bool gImageNotFound = false; +uniform vec4 gFallbackColor : DIFFUSE = {0.3f, 0.0f, 0.0f, 1}; // The main image texture displaying RGBA values to the user. uniform texture2D gImageTexture @@ -90,11 +107,38 @@ GLSLShader VS_mmImagePlane { // Pixel Shader Outputs attribute PIXEL_DATA { - vec4 colorOut : COLOR0; + vec4 color_out : COLOR0; } GLSLShader PS_mmImagePlane_Main { + // Simple Reinhard tone-mapping operator. + // + // blend_value is a blend between no adjustment and the full + // tone-mapping (0.0 to 1.0). + vec3 apply_soft_clip(vec3 in_color, float blend_value) { + vec3 adjusted = in_color / (1.0 + in_color); + return mix(in_color, adjusted, blend_value); + } + + vec4 convert_to_luminance_only(vec4 in_color) { + // Luminance weights + // + // From Mozilla: + // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance + float r_weight = 0.2126f; // Luminance Red + float g_weight = 0.7152f; // Luminance Green + float b_weight = 0.0722f; // Luminance Blue + + mat4 matrix; + matrix[0] = vec4(r_weight, g_weight, b_weight, 0.0); + matrix[1] = vec4(r_weight, g_weight, b_weight, 0.0); + matrix[2] = vec4(r_weight, g_weight, b_weight, 0.0); + matrix[3] = vec4(0.0, 0.0, 0.0, 1.0); + + return in_color * matrix; + } + // The OCIO function will be replaced and injected at runtime into // this line. This function is used as a stand-in. vec4 OCIODisplay(vec4 passthrough) { return passthrough; } @@ -105,13 +149,15 @@ GLSLShader PS_mmImagePlane_Main { texture_color.a = 1.0f; } - vec3 gamma = vec3(gColorGamma, gColorGamma, gColorGamma); + vec3 gamma = 1.0f / vec3(gColorGamma, gColorGamma, gColorGamma); + + texture_color.rgb *= gColorGain.rgb; + texture_color.rgb *= pow(2.0, gColorExposure); texture_color.rgb = max(vec3(0.0f, 0.0f, 0.0f), texture_color.rgb); - texture_color.rgb = pow(texture_color.rgb, (1.0f / gamma)); + texture_color.rgb = pow(texture_color.rgb, gamma); + texture_color *= gColorSaturationMatrix; + texture_color.rgb = apply_soft_clip(texture_color.rgb, gColorSoftClip); - texture_color.r *= gColorGain; - texture_color.g *= gColorGain; - texture_color.b *= gColorGain; texture_color.a *= gAlphaGain; if (texture_color.a > 1.0f) { @@ -120,15 +166,19 @@ GLSLShader PS_mmImagePlane_Main { texture_color.a = 0.0f; } - colorOut = gSolidColor * texture_color; - if (gShowChannelRed) { - colorOut = vec4(colorOut.r, colorOut.r, colorOut.r, 1.0f); - } else if (gShowChannelGreen) { - colorOut = vec4(colorOut.g, colorOut.g, colorOut.g, 1.0f); - } else if (gShowChannelBlue) { - colorOut = vec4(colorOut.b, colorOut.b, colorOut.b, 1.0f); - } else if (gShowChannelAlpha) { - colorOut = vec4(colorOut.a, colorOut.a, colorOut.a, 1.0f); + color_out = texture_color; + if (gDisplayChannel == DISPLAY_CHANNEL_RGB) { + color_out = vec4(color_out.rgb, 1.0f); + } else if (gDisplayChannel == DISPLAY_CHANNEL_RED) { + color_out = vec4(color_out.rrr, 1.0f); + } else if (gDisplayChannel == DISPLAY_CHANNEL_GREEN) { + color_out = vec4(color_out.ggg, 1.0f); + } else if (gDisplayChannel == DISPLAY_CHANNEL_BLUE) { + color_out = vec4(color_out.bbb, 1.0f); + } else if (gDisplayChannel == DISPLAY_CHANNEL_ALPHA) { + color_out = vec4(color_out.aaa, 1.0f); + } else if (gDisplayChannel == DISPLAY_CHANNEL_LUMINANCE) { + color_out = convert_to_luminance_only(color_out); } } } diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index 777c79449..59470bcf7 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -74,10 +74,14 @@ ImagePlaneGeometryOverride::ImagePlaneGeometryOverride(const MObject &obj) , m_draw_image_size(false) , m_draw_camera_size(false) , m_geometry_node_type(MFn::kInvalid) - , m_use_color_plug(false) , m_image_display_channel(ImageDisplayChannel::kAll) - , m_color_gain(1.0f) + , m_color_gain(1.0, 1.0, 1.0, 1.0) + , m_color_exposure(0.0f) + , m_color_gamma(1.0f) + , m_color_saturation(1.0f) + , m_color_soft_clip(0.0f) , m_alpha_gain(1.0f) + , m_default_color(0.3, 0.0, 0.0, 1.0) , m_ignore_alpha(false) , m_flip(false) , m_flop(false) @@ -280,12 +284,13 @@ void ImagePlaneGeometryOverride::query_node_attributes( bool &out_visible_to_camera_only, bool &out_is_under_camera, bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, bool &out_draw_camera_size, MString &out_camera_size, - bool &out_use_color_plug, ImageDisplayChannel &out_image_display_channel, - float &out_color_gain, float &out_alpha_gain, bool &out_ignore_alpha, + ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, + float &out_color_exposure, float &out_color_gamma, + float &out_color_saturation, float &out_color_soft_clip, + float &out_alpha_gain, MColor &out_default_color, bool &out_ignore_alpha, bool &out_flip, bool &out_flop, bool &out_is_transparent, mmcore::FrameValue &out_frame, MString &out_file_path, - MString &out_input_color_space_name, MString &out_output_color_space_name, - MPlug &out_color_plug) { + MString &out_input_color_space_name, MString &out_output_color_space_name) { const bool verbose = false; MDagPath objPath; @@ -339,62 +344,68 @@ void ImagePlaneGeometryOverride::query_node_attributes( calculate_node_camera_size_string(objPath, double_precision, out_draw_camera_size, out_camera_size); - // Shader attributes. - { - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_use_color_plug, - out_use_color_plug); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_gain, + out_color_gain); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_color_gain, - out_color_gain); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_exposure, + out_color_exposure); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_alpha_gain, - out_alpha_gain); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_gamma, + out_color_gamma); + CHECK_MSTATUS(status); - short image_display_channel_value = 0; - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_image_display_channel, - image_display_channel_value); - CHECK_MSTATUS(status); - out_image_display_channel = - static_cast(image_display_channel_value); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_saturation, + out_color_saturation); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_ignore_alpha, - out_ignore_alpha); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_soft_clip, + out_color_soft_clip); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_flip, out_flip); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_alpha_gain, + out_alpha_gain); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_flop, out_flop); - CHECK_MSTATUS(status); + short image_display_channel_value = 0; + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_display_channel, + image_display_channel_value); + CHECK_MSTATUS(status); + out_image_display_channel = + static_cast(image_display_channel_value); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_is_transparent, - out_is_transparent); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_ignore_alpha, + out_ignore_alpha); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_frame_number, - out_frame); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_flip, out_flip); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_file_path, - out_file_path); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_flop, out_flop); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_input_color_space, - out_input_color_space_name); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_shader_is_transparent, + out_is_transparent); + CHECK_MSTATUS(status); - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_output_color_space, - out_output_color_space_name); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_frame_number, + out_frame); + CHECK_MSTATUS(status); - status = getNodeAttrPlug(objPath, ImagePlaneShapeNode::m_color, - out_color_plug); - CHECK_MSTATUS(status); - } + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_file_path, + out_file_path); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_image_input_color_space, + out_input_color_space_name); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShapeNode::m_image_output_color_space, + out_output_color_space_name); + CHECK_MSTATUS(status); // Find the input/output file color spaces. // @@ -422,61 +433,28 @@ void find_geometry_node_path(MObject &node, MString &attr_name, MPlugArray connections; bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); - - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kMesh)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - out_geometry_node_path = path; - out_geometry_node_type = path.apiType(); - MMSOLVER_MAYA_VRB( - "Validated geometry node: " - << " path=" - << out_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN( - "Geometry node is not correct type:" - << " path=" - << out_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } - } + if (!ok) { + return; } -} - -void find_shader_node(MObject &node, MString &attr_name, - MObject &out_shader_node, - MFn::Type &out_shader_node_type) { - const auto verbose = false; - MPlugArray connections; - bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); - - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - MFnDependencyNode mfn_depend_node(node); - if (node.hasFn(MFn::kSurfaceShader) || - node.hasFn(MFn::kHwShaderNode) || - node.hasFn(MFn::kPluginHardwareShader) || - node.hasFn(MFn::kPluginHwShaderNode)) { - out_shader_node = node; - out_shader_node_type = node.apiType(); - MMSOLVER_MAYA_VRB("Validated shader node:" - << " name=" << mfn_depend_node.name().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN("Shader node is not correct type: " - << " name=" << mfn_depend_node.name().asChar() - << " type=" << node.apiTypeStr()); - } + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kMesh)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_geometry_node_path = path; + out_geometry_node_type = path.apiType(); + MMSOLVER_MAYA_VRB("Validated geometry node: " + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Geometry node is not correct type:" + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); } } } @@ -488,27 +466,28 @@ void find_camera_node_path(MObject &node, MString &attr_name, MPlugArray connections; bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + if (!ok) { + return; + } - if (ok) { - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kCamera)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - out_camera_node_path = path; - out_camera_node_type = path.apiType(); - MMSOLVER_MAYA_VRB( - "Validated camera node: " - << " path=" << out_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN( - "Camera node is not correct type:" - << " path=" << out_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kCamera)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_camera_node_path = path; + out_camera_node_type = path.apiType(); + MMSOLVER_MAYA_VRB("Validated camera node: " + << " path=" + << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Camera node is not correct type:" + << " path=" + << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); } } } @@ -520,12 +499,6 @@ void ImagePlaneGeometryOverride::updateDG() { m_geometry_node_type); } - if (m_shader_node.isNull()) { - MString attr_name = "shaderNode"; - find_shader_node(m_this_node, attr_name, m_shader_node, - m_shader_node_type); - } - if (!m_camera_node_path.isValid()) { MString attr_name = "cameraNode"; find_camera_node_path(m_this_node, attr_name, m_camera_node_path, @@ -538,11 +511,12 @@ void ImagePlaneGeometryOverride::updateDG() { ImagePlaneGeometryOverride::query_node_attributes( m_this_node, m_camera_node_path, m_visible, m_visible_to_camera_only, m_is_under_camera, m_draw_hud, m_draw_image_size, m_image_size, - m_draw_camera_size, m_camera_size, m_use_color_plug, - m_image_display_channel, m_color_gain, m_alpha_gain, m_ignore_alpha, + m_draw_camera_size, m_camera_size, m_image_display_channel, + m_color_gain, m_color_exposure, m_color_gamma, m_color_saturation, + m_color_soft_clip, m_alpha_gain, m_default_color, m_ignore_alpha, m_flip, m_flop, m_is_transparent, m_frame, m_file_path, - temp_input_color_space_name, temp_output_color_space_name, - m_color_plug); + temp_input_color_space_name, temp_output_color_space_name); + if ((m_input_color_space_name.asChar() != temp_input_color_space_name.asChar()) || (m_input_color_space_name.asChar() != @@ -553,83 +527,104 @@ void ImagePlaneGeometryOverride::updateDG() { } } -MTexture *create_color_bars_texture( - MHWRender::MTextureManager *textureManager) { - MHWRender::MTextureDescription texture_desc; - texture_desc.setToDefault2DTexture(); - texture_desc.fWidth = COLOR_BARS_F32_4X4_PIXEL_WIDTH; - texture_desc.fHeight = COLOR_BARS_F32_4X4_PIXEL_HEIGHT; - texture_desc.fDepth = 1; - texture_desc.fBytesPerSlice = - COLOR_BARS_F32_4X4_PIXEL_COUNT * COLOR_BARS_F32_4X4_PIXEL_BYTE_COUNT; - texture_desc.fBytesPerRow = - COLOR_BARS_F32_4X4_PIXEL_WIDTH * COLOR_BARS_F32_4X4_PIXEL_BYTE_COUNT; - texture_desc.fMipmaps = 1; - texture_desc.fArraySlices = 1; - texture_desc.fTextureType = MHWRender::kImage2D; - texture_desc.fFormat = MHWRender::kR32G32B32A32_FLOAT; - - const bool generate_mip_maps = false; - return textureManager->acquireTexture( - "", texture_desc, &(COLOR_BARS_F32_4X4[0]), generate_mip_maps); -} - -MTexture *create_plug_texture(MHWRender::MTextureManager *textureManager, - MPlug &texture_plug) { - MObject texture_node; - MStatus status = get_connected_node(texture_plug, texture_node); - CHECK_MSTATUS(status); - if (status != MS::kSuccess) { - return nullptr; - } - - if (texture_node.isNull()) { - // For when the plug is just a color value, but doesn't have - // any input texture. - const bool generate_mip_maps = false; - const int width = 2; - const int height = 2; - return textureManager->acquireTexture("", texture_plug, width, height, - generate_mip_maps); - } - - const bool allowBackgroundLoad = false; - return textureManager->acquireTexture(texture_node, allowBackgroundLoad); +inline MFloatMatrix create_saturation_matrix(const float saturation) { + // Luminance weights + // + // From Mozilla: + // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance + const float kLuminanceRed = 0.2126f; + const float kLuminanceGreen = 0.7152f; + const float kLuminanceBlue = 0.0722f; + + const float r_weight = kLuminanceRed; + const float g_weight = kLuminanceGreen; + const float b_weight = kLuminanceBlue; + + const float r1 = (1.0 - saturation) * r_weight + saturation; + const float r2 = (1.0 - saturation) * r_weight; + const float r3 = (1.0 - saturation) * r_weight; + + const float g1 = (1.0 - saturation) * g_weight; + const float g2 = (1.0 - saturation) * g_weight + saturation; + const float g3 = (1.0 - saturation) * g_weight; + + const float b1 = (1.0 - saturation) * b_weight; + const float b2 = (1.0 - saturation) * b_weight; + const float b3 = (1.0 - saturation) * b_weight + saturation; + + const float saturation_matrix_values[4][4] = { + // Column 0 + {r1, g1, b1, 0.0}, + // Column 1 + {r2, g2, b2, 0.0}, + // Column 2 + {r3, g3, b3, 0.0}, + // Column 3 + {0.0, 0.0, 0.0, 1.0}, + }; + + return MFloatMatrix(saturation_matrix_values); } void ImagePlaneGeometryOverride::set_shader_instance_parameters( MShaderInstance *shader, MHWRender::MTextureManager *textureManager, - const float color_gain, const float alpha_gain, const bool ignore_alpha, - const bool flip, const bool flop, const bool is_transparent, + const MColor &color_gain, const float color_exposure, + const float color_gamma, const float color_saturation, + const float color_soft_clip, const float alpha_gain, + const MColor &default_color, const bool ignore_alpha, const bool flip, + const bool flop, const bool is_transparent, const ImageDisplayChannel image_display_channel, const mmcore::FrameValue frame, const MString &file_path, const MString &input_color_space_name, const MString &output_color_space_name, MHWRender::MTexture *out_color_texture, - const MHWRender::MSamplerState *out_texture_sampler, - const bool use_color_plug, MPlug &out_color_plug) { + const MHWRender::MSamplerState *out_texture_sampler) { + MStatus status = MStatus::kSuccess; const bool verbose = false; MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); - static const float color[] = {1.0f, 1.0f, 1.0f, 1.0f}; - shader->setParameter("gSolidColor", color); - shader->setParameter("gColorGain", color_gain); - shader->setParameter("gAlphaGain", alpha_gain); - shader->setParameter("gFlip", flip); - shader->setParameter("gFlop", flop); - shader->setParameter("gIgnoreAlpha", m_ignore_alpha); - shader->setParameter("gShowChannelRed", - image_display_channel == ImageDisplayChannel::kRed); - shader->setParameter("gShowChannelGreen", - image_display_channel == ImageDisplayChannel::kGreen); - shader->setParameter("gShowChannelBlue", - image_display_channel == ImageDisplayChannel::kBlue); - shader->setParameter("gShowChannelAlpha", - image_display_channel == ImageDisplayChannel::kAlpha); - - shader->setIsTransparent(is_transparent); + const float color[] = {color_gain[0], color_gain[1], color_gain[2], 1.0f}; + status = shader->setParameter("gColorGain", color); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorExposure", color_exposure); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorGamma", color_gamma); + CHECK_MSTATUS(status); + + MFloatMatrix saturation_matrix = create_saturation_matrix(color_saturation); + status = shader->setParameter("gColorSaturationMatrix", saturation_matrix); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorSoftClip", color_soft_clip); + CHECK_MSTATUS(status); + + status = shader->setParameter("gAlphaGain", alpha_gain); + CHECK_MSTATUS(status); + + const float temp_default_color[] = {default_color[0], default_color[1], + default_color[2], 1.0f}; + status = shader->setParameter("gFallbackColor", temp_default_color); + CHECK_MSTATUS(status); + + status = shader->setParameter("gFlip", flip); + CHECK_MSTATUS(status); + + status = shader->setParameter("gFlop", flop); + CHECK_MSTATUS(status); + + status = shader->setParameter("gIgnoreAlpha", m_ignore_alpha); + CHECK_MSTATUS(status); + + status = shader->setParameter("gDisplayChannel", + static_cast(image_display_channel)); + CHECK_MSTATUS(status); + + status = shader->setIsTransparent(is_transparent); MMSOLVER_MAYA_VRB("mmImagePlaneShape: shader->isTransparent()=" << shader->isTransparent()); + CHECK_MSTATUS(status); MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); @@ -641,35 +636,21 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( MMSOLVER_MAYA_VRB("mmImagePlaneShape: expanded_file_path=" << expanded_file_path.asChar()); - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: start use_color_plug=" << use_color_plug); MMSOLVER_MAYA_VRB( "mmImagePlaneShape: start out_color_texture=" << out_color_texture); if (!out_color_texture) { - if (!use_color_plug) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); - const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; - - // // TODO: using kFloat crashes. - // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); + const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; - const bool do_texture_update = false; - ImageCache &image_cache = ImageCache::getInstance(); - out_color_texture = read_image_file( - textureManager, image_cache, m_temp_image, expanded_file_path, - pixel_type, do_texture_update); + // // TODO: using kFloat crashes. + // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; - } else { - if (!out_color_plug.isNull()) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color plug texture"); - out_color_texture = - create_plug_texture(textureManager, out_color_plug); - } else { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use color bars"); - out_color_texture = create_color_bars_texture(textureManager); - } - } + const bool do_texture_update = false; + ImageCache &image_cache = ImageCache::getInstance(); + out_color_texture = + read_image_file(textureManager, image_cache, m_temp_image, + expanded_file_path, pixel_type, do_texture_update); if (out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" @@ -731,7 +712,9 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( } if (out_texture_sampler) { - shader->setParameter("gImageTextureSampler", *out_texture_sampler); + status = + shader->setParameter("gImageTextureSampler", *out_texture_sampler); + CHECK_MSTATUS(status); } else { MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get texture sampler." << " out_texture_sampler=" << out_texture_sampler); @@ -740,7 +723,9 @@ void ImagePlaneGeometryOverride::set_shader_instance_parameters( if (out_color_texture) { MHWRender::MTextureAssignment texture_assignment; texture_assignment.texture = out_color_texture; - shader->setParameter("gImageTexture", texture_assignment); + status = shader->setParameter("gImageTexture", texture_assignment); + CHECK_MSTATUS(status); + out_color_texture = nullptr; } else { MMSOLVER_MAYA_VRB( @@ -834,7 +819,8 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, shaderManager->getStockShader(MShaderManager::k3dSolidShader); if (shader) { static const float color[] = {1.0f, 0.0f, 0.0f, 1.0f}; - shader->setParameter("solidColor", color); + MStatus status = shader->setParameter("solidColor", color); + CHECK_MSTATUS(status); wireframeItem->setShader(shader); shaderManager->releaseShader(shader); } @@ -921,12 +907,12 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, } set_shader_instance_parameters( - m_shader, textureManager, m_color_gain, m_alpha_gain, - m_ignore_alpha, m_flip, m_flop, m_is_transparent, - m_image_display_channel, m_frame, m_file_path, + m_shader, textureManager, m_color_gain, m_color_exposure, + m_color_gamma, m_color_saturation, m_color_soft_clip, + m_alpha_gain, m_default_color, m_ignore_alpha, m_flip, m_flop, + m_is_transparent, m_image_display_channel, m_frame, m_file_path, m_input_color_space_name, m_output_color_space_name, - m_color_texture, m_texture_sampler, m_use_color_plug, - m_color_plug); + m_color_texture, m_texture_sampler); shadedItem->setShader(m_shader); } @@ -1048,7 +1034,7 @@ void ImagePlaneGeometryOverride::cleanUp() {} #if MAYA_API_VERSION >= 20190000 bool ImagePlaneGeometryOverride::requiresGeometryUpdate() const { const bool verbose = false; - if (m_geometry_node_path.isValid() && !m_shader_node.isNull()) { + if (m_geometry_node_path.isValid()) { MMSOLVER_MAYA_VRB( "ImagePlaneGeometryOverride::requiresGeometryUpdate: false"); return false; diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.h b/src/mmSolver/shape/ImagePlaneGeometryOverride.h index cbaa87130..1c752e116 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.h +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.h @@ -113,32 +113,34 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { bool &out_visible_to_camera_only, bool &out_is_under_camera, bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, bool &out_draw_camera_size, MString &out_camera_size, - bool &out_use_color_plug, - ImageDisplayChannel &out_image_display_channel, float &out_color_gain, - float &out_alpha_gain, bool &out_ignore_alpha, bool &out_flip, - bool &out_flop, bool &out_is_transparent, mmcore::FrameValue &out_frame, + ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, + float &out_color_exposure, float &out_color_gamma, + float &out_color_saturation, float &out_color_soft_clip, + float &out_alpha_gain, MColor &out_default_color, + bool &out_ignore_alpha, bool &out_flip, bool &out_flop, + bool &out_is_transparent, mmcore::FrameValue &out_frame, MString &out_file_path, MString &out_input_color_space_name, - MString &out_output_color_space_name, MPlug &out_color_plug); + MString &out_output_color_space_name); void set_shader_instance_parameters( MShaderInstance *shader, MHWRender::MTextureManager *textureManager, - const float color_gain, const float alpha_gain, const bool ignore_alpha, - const bool flip, const bool flop, const bool is_transparent, + const MColor &color_gain, const float color_exposure, + const float color_gamma, const float color_saturation, + const float color_soft_clip, const float alpha_gain, + const MColor &default_color, const bool ignore_alpha, const bool flip, + const bool flop, const bool is_transparent, const ImageDisplayChannel image_display_channel, const mmcore::FrameValue frame, const MString &file_path, const MString &input_color_space_name, const MString &output_color_space_name, MHWRender::MTexture *out_color_texture, - const MHWRender::MSamplerState *out_texture_sampler, - const bool use_color_plug, MPlug &out_color_plug); + const MHWRender::MSamplerState *out_texture_sampler); MObject m_this_node; MDagPath m_geometry_node_path; MDagPath m_camera_node_path; MFn::Type m_geometry_node_type; - MFn::Type m_shader_node_type; MFn::Type m_camera_node_type; - MObject m_shader_node; bool m_visible; bool m_visible_to_camera_only; @@ -151,13 +153,16 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { MString m_camera_size; MCallbackId m_model_editor_changed_callback_id; - bool m_use_color_plug; - // Shader attributes. MShaderInstance *m_shader; ImageDisplayChannel m_image_display_channel; - float m_color_gain; + MColor m_color_gain; float m_alpha_gain; + float m_color_exposure; + float m_color_gamma; + float m_color_saturation; + float m_color_soft_clip; + MColor m_default_color; bool m_ignore_alpha; bool m_flip; bool m_flop; @@ -166,7 +171,6 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { MString m_file_path; MString m_input_color_space_name; MString m_output_color_space_name; - MPlug m_color_plug; // Texture caching MImage m_temp_image; diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.cpp b/src/mmSolver/shape/ImagePlaneShapeNode.cpp index 81d92396d..35a1d4ce9 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.cpp +++ b/src/mmSolver/shape/ImagePlaneShapeNode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2022, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -77,23 +77,25 @@ MObject ImagePlaneShapeNode::m_camera_height_inch; MObject ImagePlaneShapeNode::m_lens_hash_current; MObject ImagePlaneShapeNode::m_lens_hash_previous; MObject ImagePlaneShapeNode::m_geometry_node; -MObject ImagePlaneShapeNode::m_shader_node; MObject ImagePlaneShapeNode::m_camera_node; -// Shader Attributes -MObject ImagePlaneShapeNode::m_use_color_plug; +// Image Attributes MObject ImagePlaneShapeNode::m_image_display_channel; -MObject ImagePlaneShapeNode::m_color_gain; -MObject ImagePlaneShapeNode::m_alpha_gain; -MObject ImagePlaneShapeNode::m_ignore_alpha; -MObject ImagePlaneShapeNode::m_flip; -MObject ImagePlaneShapeNode::m_flop; -MObject ImagePlaneShapeNode::m_is_transparent; -MObject ImagePlaneShapeNode::m_frame_number; -MObject ImagePlaneShapeNode::m_file_path; -MObject ImagePlaneShapeNode::m_input_color_space; -MObject ImagePlaneShapeNode::m_output_color_space; -MObject ImagePlaneShapeNode::m_color; +MObject ImagePlaneShapeNode::m_image_color_gain; +MObject ImagePlaneShapeNode::m_image_color_exposure; +MObject ImagePlaneShapeNode::m_image_color_gamma; +MObject ImagePlaneShapeNode::m_image_color_saturation; +MObject ImagePlaneShapeNode::m_image_color_soft_clip; +MObject ImagePlaneShapeNode::m_image_alpha_gain; +MObject ImagePlaneShapeNode::m_image_default_color; +MObject ImagePlaneShapeNode::m_image_ignore_alpha; +MObject ImagePlaneShapeNode::m_image_flip; +MObject ImagePlaneShapeNode::m_image_flop; +MObject ImagePlaneShapeNode::m_image_frame_number; +MObject ImagePlaneShapeNode::m_image_file_path; +MObject ImagePlaneShapeNode::m_image_input_color_space; +MObject ImagePlaneShapeNode::m_image_output_color_space; +MObject ImagePlaneShapeNode::m_shader_is_transparent; ImagePlaneShapeNode::ImagePlaneShapeNode() {} @@ -260,13 +262,6 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(msgAttr.setKeyable(false)); CHECK_MSTATUS(addAttribute(m_geometry_node)); - m_shader_node = msgAttr.create("shaderNode", "shdnd", &status); - CHECK_MSTATUS(status); - CHECK_MSTATUS(msgAttr.setStorable(true)); - CHECK_MSTATUS(msgAttr.setConnectable(true)); - CHECK_MSTATUS(msgAttr.setKeyable(false)); - CHECK_MSTATUS(addAttribute(m_shader_node)); - m_camera_node = msgAttr.create("cameraNode", "camnd", &status); CHECK_MSTATUS(status); CHECK_MSTATUS(msgAttr.setStorable(true)); @@ -274,120 +269,176 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(msgAttr.setKeyable(false)); CHECK_MSTATUS(addAttribute(m_camera_node)); - m_use_color_plug = nAttr.create("useColorPlug", "useclrplg", - MFnNumericData::kBoolean, true); + m_image_color_gain = nAttr.createColor("colorGain", "colgn"); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setReadable(true)); + CHECK_MSTATUS(nAttr.setWritable(true)); + CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); + CHECK_MSTATUS(addAttribute(m_image_color_gain)); + + const float exposure_soft_min = -9.0f; + const float exposure_soft_max = +9.0f; + const float exposure_default = 0.0f; + m_image_color_exposure = nAttr.create( + "colorExposure", "colexpsr", MFnNumericData::kFloat, exposure_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setSoftMin(exposure_soft_min)); + CHECK_MSTATUS(nAttr.setSoftMax(exposure_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_exposure)); + + const float gamma_min = 0.0f; + const float gamma_soft_max = +2.0f; + const float gamma_default = 1.0f; + m_image_color_gamma = nAttr.create("colorGamma", "colgmma", + MFnNumericData::kFloat, gamma_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(gamma_min)); + CHECK_MSTATUS(nAttr.setSoftMax(gamma_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_gamma)); + + const float saturation_min = 0.0f; + const float saturation_soft_max = 2.0f; + const float saturation_default = 1.0f; + m_image_color_saturation = + nAttr.create("colorSaturation", "colstrtn", MFnNumericData::kFloat, + saturation_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(saturation_min)); + CHECK_MSTATUS(nAttr.setSoftMax(saturation_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_saturation)); + + const float soft_clip_min = 0.0f; + const float soft_clip_max = 1.0f; + const float soft_clip_default = 0.0f; + m_image_color_soft_clip = + nAttr.create("colorSoftClip", "colsftclp", MFnNumericData::kFloat, + soft_clip_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(soft_clip_min)); + CHECK_MSTATUS(nAttr.setMax(soft_clip_max)); + CHECK_MSTATUS(addAttribute(m_image_color_soft_clip)); + + const double alpha_min = 0.0; + const double alpha_max = 1.0; + const double alpha_default = 1.0; + m_image_alpha_gain = nAttr.create("alphaGain", "alpgn", + MFnNumericData::kDouble, alpha_default); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_use_color_plug)); + CHECK_MSTATUS(nAttr.setMin(alpha_min)); + CHECK_MSTATUS(nAttr.setMax(alpha_max)); + CHECK_MSTATUS(addAttribute(m_image_alpha_gain)); // Which channel of the image should be displayed? - short value_all = static_cast(ImageDisplayChannel::kAll); - short value_red = static_cast(ImageDisplayChannel::kRed); - short value_green = static_cast(ImageDisplayChannel::kGreen); - short value_blue = static_cast(ImageDisplayChannel::kBlue); - short value_alpha = static_cast(ImageDisplayChannel::kAlpha); - m_image_display_channel = eAttr.create("shaderImageDisplayChannel", - "shdimgdspchan", value_all, &status); + const short value_all = static_cast(ImageDisplayChannel::kAll); + const short value_rgb = static_cast(ImageDisplayChannel::kRGB); + const short value_red = static_cast(ImageDisplayChannel::kRed); + const short value_green = static_cast(ImageDisplayChannel::kGreen); + const short value_blue = static_cast(ImageDisplayChannel::kBlue); + const short value_alpha = static_cast(ImageDisplayChannel::kAlpha); + const short value_luminance = + static_cast(ImageDisplayChannel::kLuminance); + m_image_display_channel = + eAttr.create("displayChannel", "dspchan", value_all, &status); CHECK_MSTATUS(status); CHECK_MSTATUS(eAttr.addField("RGBA", value_all)); + CHECK_MSTATUS(eAttr.addField("RGB", value_rgb)); CHECK_MSTATUS(eAttr.addField("Red", value_red)); CHECK_MSTATUS(eAttr.addField("Green", value_green)); CHECK_MSTATUS(eAttr.addField("Blue", value_blue)); CHECK_MSTATUS(eAttr.addField("Alpha", value_alpha)); + CHECK_MSTATUS(eAttr.addField("Luminance", value_luminance)); CHECK_MSTATUS(eAttr.setStorable(true)); CHECK_MSTATUS(eAttr.setKeyable(true)); CHECK_MSTATUS(addAttribute(m_image_display_channel)); - m_color_gain = nAttr.create("shaderColorGain", "shdclgn", - MFnNumericData::kDouble, 1.0); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(false)); - CHECK_MSTATUS(nAttr.setMin(0.0)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Color Gain (Shader)"))); - CHECK_MSTATUS(addAttribute(m_color_gain)); - - m_alpha_gain = nAttr.create("shaderAlphaGain", "shdalpgn", - MFnNumericData::kDouble, 1.0); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(false)); - CHECK_MSTATUS(nAttr.setMin(0.0)); - CHECK_MSTATUS(nAttr.setMax(1.0)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Alpha Gain (Shader)"))); - CHECK_MSTATUS(addAttribute(m_alpha_gain)); - - m_ignore_alpha = nAttr.create("shaderIgnoreAlpha", "shdignalp", - MFnNumericData::kBoolean, false); + m_image_ignore_alpha = nAttr.create("imageIgnoreAlpha", "imgignalp", + MFnNumericData::kBoolean, false); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Ignore Alpha (Shader)"))); - CHECK_MSTATUS(addAttribute(m_ignore_alpha)); + CHECK_MSTATUS(addAttribute(m_image_ignore_alpha)); - m_flip = - nAttr.create("shaderFlip", "shdflip", MFnNumericData::kBoolean, false); + m_image_flip = + nAttr.create("imageFlip", "imgflip", MFnNumericData::kBoolean, false); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Flip (Shader)"))); - CHECK_MSTATUS(addAttribute(m_flip)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Image Flip (Vertical)"))); + CHECK_MSTATUS(addAttribute(m_image_flip)); - m_flop = - nAttr.create("shaderFlop", "shdflop", MFnNumericData::kBoolean, false); + m_image_flop = + nAttr.create("imageFlop", "imgflop", MFnNumericData::kBoolean, false); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Flop (Shader)"))); - CHECK_MSTATUS(addAttribute(m_flop)); + CHECK_MSTATUS( + nAttr.setNiceNameOverride(MString("Image Flop (Horizontal)"))); + CHECK_MSTATUS(addAttribute(m_image_flop)); - m_is_transparent = nAttr.create("shaderIsTransparent", "shdistrnsp", - MFnNumericData::kBoolean, false); + m_shader_is_transparent = nAttr.create("shaderIsTransparent", "shdistrnsp", + MFnNumericData::kBoolean, false); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setConnectable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); CHECK_MSTATUS( - nAttr.setNiceNameOverride(MString("Is Transparent (Shader)"))); - CHECK_MSTATUS(addAttribute(m_is_transparent)); + nAttr.setNiceNameOverride(MString("Shader Is Transparent (Debug)"))); + CHECK_MSTATUS(addAttribute(m_shader_is_transparent)); - m_frame_number = - nAttr.create("frameNumber", "frmnmb", MFnNumericData::kInt, 1); + m_image_default_color = nAttr.createColor("imageDefaultColor", "imgdefcol"); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setReadable(true)); + CHECK_MSTATUS(nAttr.setWritable(true)); + CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); + CHECK_MSTATUS(addAttribute(m_image_default_color)); + + m_image_frame_number = + nAttr.create("imageFrameNumber", "imgfrmnmb", MFnNumericData::kInt, 1); CHECK_MSTATUS(nAttr.setStorable(true)); CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_frame_number)); + CHECK_MSTATUS(addAttribute(m_image_frame_number)); + + // // Pixel Data Type + // m_cache_pixel_data_type = eAttr.create( + // "cachePixelDataType", "cchpxldtyp", + // kDataTypeUnknown); + // CHECK_MSTATUS(eAttr.addField("auto", kDataTypeUnknown)); + // CHECK_MSTATUS(eAttr.addField("uint8", kDataTypeUInt8)); + // CHECK_MSTATUS(eAttr.addField("uint16", kDataTypeUInt16)); + // CHECK_MSTATUS(eAttr.addField("half16", kDataTypeHalf16)); + // CHECK_MSTATUS(eAttr.addField("float32", kDataTypeFloat32)); + // CHECK_MSTATUS(eAttr.setStorable(true)); // Create empty string data to be used as attribute default // (string) value. MFnStringData empty_string_data; MObject empty_string_data_obj = empty_string_data.create(""); - m_file_path = tAttr.create("shaderFilePath", "shdflpth", MFnData::kString, - empty_string_data_obj); + m_image_file_path = tAttr.create("imageFilePath", "imgflpth", + MFnData::kString, empty_string_data_obj); CHECK_MSTATUS(tAttr.setStorable(true)); CHECK_MSTATUS(tAttr.setUsedAsFilename(true)); - CHECK_MSTATUS(addAttribute(m_file_path)); + CHECK_MSTATUS(addAttribute(m_image_file_path)); - m_input_color_space = tAttr.create("inputColorSpace", "incolspc", - MFnData::kString, empty_string_data_obj); + m_image_input_color_space = tAttr.create( + "inputColorSpace", "incolspc", MFnData::kString, empty_string_data_obj); CHECK_MSTATUS(tAttr.setStorable(true)); CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); - CHECK_MSTATUS(addAttribute(m_input_color_space)); + CHECK_MSTATUS(addAttribute(m_image_input_color_space)); - m_output_color_space = + m_image_output_color_space = tAttr.create("outputColorSpace", "outcolspc", MFnData::kString, empty_string_data_obj); CHECK_MSTATUS(tAttr.setStorable(true)); CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); - CHECK_MSTATUS(addAttribute(m_output_color_space)); - - m_color = nAttr.createColor("shaderColor", "shdcl"); - CHECK_MSTATUS(nAttr.setDefault(0.0f, 0.58824f, 0.644f)); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(false)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Color (Shader)"))); - CHECK_MSTATUS(addAttribute(m_color)); + CHECK_MSTATUS(addAttribute(m_image_output_color_space)); return MS::kSuccess; } diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.h b/src/mmSolver/shape/ImagePlaneShapeNode.h index d03b6279d..7f2977cd1 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.h +++ b/src/mmSolver/shape/ImagePlaneShapeNode.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2022, 2024 David Cattermole. * * This file is part of mmSolver. * @@ -44,7 +44,15 @@ namespace mmsolver { -enum class ImageDisplayChannel { kAll = 0, kRed, kGreen, kBlue, kAlpha }; +enum class ImageDisplayChannel { + kAll = 0, + kRGB, + kRed, + kGreen, + kBlue, + kAlpha, + kLuminance +}; class ImagePlaneShapeNode : public MPxLocatorNode { public: @@ -99,23 +107,25 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MObject m_lens_hash_current; static MObject m_lens_hash_previous; static MObject m_geometry_node; - static MObject m_shader_node; static MObject m_camera_node; + static MObject m_shader_is_transparent; - // Shader Attributes - static MObject m_use_color_plug; + // Image Attributes static MObject m_image_display_channel; - static MObject m_color_gain; - static MObject m_alpha_gain; - static MObject m_ignore_alpha; - static MObject m_flip; - static MObject m_flop; - static MObject m_is_transparent; - static MObject m_frame_number; - static MObject m_file_path; - static MObject m_input_color_space; - static MObject m_output_color_space; - static MObject m_color; + static MObject m_image_color_gain; + static MObject m_image_color_exposure; + static MObject m_image_color_gamma; + static MObject m_image_color_saturation; + static MObject m_image_color_soft_clip; + static MObject m_image_alpha_gain; + static MObject m_image_default_color; + static MObject m_image_ignore_alpha; + static MObject m_image_flip; + static MObject m_image_flop; + static MObject m_image_file_path; + static MObject m_image_frame_number; + static MObject m_image_input_color_space; + static MObject m_image_output_color_space; }; } // namespace mmsolver From 4fff52a41048a11cf66ed58dcd7ed1283a7a2a46 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 2 Jun 2024 16:44:40 +1000 Subject: [PATCH 050/295] Revert mmImagePlane and add version 2 This will ensure that older Maya scenes with mmImagePlane already in use can still open correctly, so there is backwards compatibility. The new default image plane is mmImagePlane version 2, which uses OpenColorIO, and an OGSFX shader rather than a Maya shader network. --- include/mmSolver/nodeTypeIds.h | 10 + .../AEmmImagePlaneShape2Template.mel | 338 +++++ .../AEmmImagePlaneShapeTemplate.mel | 72 +- .../tools/createimageplane/_lib/constant.py | 17 + .../tools/createimageplane/_lib/main.py | 82 +- .../createimageplane/_lib/mmimageplane.py | 423 ++----- .../createimageplane/_lib/mmimageplane_v1.py | 424 +++++++ .../createimageplane/_lib/mmimageplane_v2.py | 344 +++++ .../mmSolver/tools/createimageplane/tool.py | 16 +- share/config/functions.json | 12 +- share/config/menu.json | 4 +- share/config/shelf.json | 2 +- share/config/shelf_minimal.json | 4 +- share/icons/out_mmImagePlaneShape2.png | Bin 0 -> 841 bytes src/CMakeLists.txt | 3 + src/mmSolver/pluginMain.cpp | 20 +- src/mmSolver/shape/ImageCache.h | 1 - .../shape/ImagePlaneGeometry2Override.cpp | 1113 +++++++++++++++++ .../shape/ImagePlaneGeometry2Override.h | 190 +++ .../shape/ImagePlaneGeometryOverride.cpp | 816 ++---------- .../shape/ImagePlaneGeometryOverride.h | 84 +- src/mmSolver/shape/ImagePlaneShape2Node.cpp | 448 +++++++ src/mmSolver/shape/ImagePlaneShape2Node.h | 121 ++ src/mmSolver/shape/ImagePlaneShapeNode.cpp | 208 +-- src/mmSolver/shape/ImagePlaneShapeNode.h | 36 +- src/mmSolver/shape/ImagePlaneUtils.cpp | 277 ++++ src/mmSolver/shape/ImagePlaneUtils.h | 92 ++ 27 files changed, 3763 insertions(+), 1394 deletions(-) create mode 100644 mel/AETemplates/AEmmImagePlaneShape2Template.mel create mode 100644 python/mmSolver/tools/createimageplane/_lib/mmimageplane_v1.py create mode 100644 python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py create mode 100644 share/icons/out_mmImagePlaneShape2.png create mode 100644 src/mmSolver/shape/ImagePlaneGeometry2Override.cpp create mode 100644 src/mmSolver/shape/ImagePlaneGeometry2Override.h create mode 100644 src/mmSolver/shape/ImagePlaneShape2Node.cpp create mode 100644 src/mmSolver/shape/ImagePlaneShape2Node.h create mode 100644 src/mmSolver/shape/ImagePlaneUtils.cpp create mode 100644 src/mmSolver/shape/ImagePlaneUtils.h diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index 605d8826f..f928f0060 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -134,9 +134,19 @@ #define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane" #define MM_IMAGE_PLANE_SHAPE_DRAW_REGISTRANT_ID "mmImagePlaneShape" #define MM_IMAGE_PLANE_SHAPE_SELECTION_TYPE_NAME "mmImagePlaneShapeSelection" +// Same as v2. #define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_NAME "mmImagePlaneDisplayFilter" #define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_LABEL "MM ImagePlane" +#define MM_IMAGE_PLANE_SHAPE_2_TYPE_ID 0x0012F18F +#define MM_IMAGE_PLANE_SHAPE_2_TYPE_NAME "mmImagePlaneShape2" +#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane" +#define MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID "mmImagePlaneShape2" +#define MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME "mmImagePlaneShape2Selection" +// Same as v1. +#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME "mmImagePlaneDisplayFilter" +#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_LABEL "MM ImagePlane" + #define CAMERA_INFERNO_TYPE_ID 0x0012F183 // Not used in mmSolver. #define MM_LENS_DATA_TYPE_ID 0x0012F184 diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel new file mode 100644 index 000000000..77bbf0d9e --- /dev/null +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -0,0 +1,338 @@ +// +// Copyright (C) 2022, 2024 David Cattermole. +// +// This file is part of mmSolver. +// +// mmSolver is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mmSolver is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with mmSolver. If not, see . +// --------------------------------------------------------------------- +// +// Image plane shape node Template file. +// + + +source "AEmmNodeTemplateCommon"; + +global proc string AEmmImagePlaneShape2_slotAttrNameFromInteger(int $value) +{ + string $attr_name = "unknown"; + if ($value == 0) { + $attr_name = "imageSequenceMain"; + } else if ($value == 1) { + $attr_name = "imageSequenceAlternate1"; + } else if ($value == 2) { + $attr_name = "imageSequenceAlternate2"; + } else if ($value == 3) { + $attr_name = "imageSequenceAlternate3"; + } + return $attr_name; +} + + +global proc string AEmmImagePlaneShape2_slotAttrNameFromOptionMenu(string $option_name) +{ + string $attr_name = "unknown"; + if ($option_name == "Main") { + $attr_name = "imageSequenceMain"; + } else if ($option_name == "Alternate 1") { + $attr_name = "imageSequenceAlternate1"; + } else if ($option_name == "Alternate 2") { + $attr_name = "imageSequenceAlternate2"; + } else if ($option_name == "Alternate 3") { + $attr_name = "imageSequenceAlternate3"; + } + return $attr_name; +} + + +global proc int AEmmImagePlaneShape2_slotAttrValueFromOptionMenu(string $option_name) +{ + int $attr_value = -1; + if ($option_name == "Main") { + $attr_value = 0; + } else if ($option_name == "Alternate 1") { + $attr_value = 1; + } else if ($option_name == "Alternate 2") { + $attr_value = 2; + } else if ($option_name == "Alternate 3") { + $attr_value = 3; + } + return $attr_value; +} + + +global proc int AEmmImagePlaneShape2_getSlotValue(string $image_plane_shp) +{ + int $slot = `getAttr ($image_plane_shp + ".imageSequenceSlot")`; + return $slot; +} + + +global proc AEmmImagePlaneShape2_setSlotValue( + string $image_plane_shp, + int $value) +{ + setAttr ($image_plane_shp + ".imageSequenceSlot") $value; +} + + +global proc AEmmImagePlaneShape2_browser( + string $attr_name, + string $image_plane_shp) +{ + int $slot = AEmmImagePlaneShape2_getSlotValue($image_plane_shp); + string $slot_attr_name = + AEmmImagePlaneShape2_slotAttrNameFromInteger($slot); + + string $cmd = ""; + $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; + $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "image_seq = tool.prompt_user_for_image_sequence();\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; + $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; + $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "if image_seq:\n"; + $cmd = $cmd + " if attr_name == current_slot_attr:\n"; + $cmd = $cmd + " lib.set_image_sequence(\n"; + $cmd = $cmd + " mm_ip_shp,\n"; + $cmd = $cmd + " image_seq,\n"; + $cmd = $cmd + " attr_name=attr_name\n"; + $cmd = $cmd + " )\n"; + $cmd = $cmd + " else:\n"; + $cmd = $cmd + " maya.cmds.setAttr(\n"; + $cmd = $cmd + " mm_ip_shp + '.' + attr_name,\n"; + $cmd = $cmd + " image_seq,\n"; + $cmd = $cmd + " type='string'\n"; + $cmd = $cmd + " )\n"; + + python($cmd); +} + + +global proc AEmmImagePlaneShape2_sequenceSlotChanged( + string $attr_name, + string $image_plane_shp, + string $slot_option_value) +{ + string $slot_attr_name = + AEmmImagePlaneShape2_slotAttrNameFromOptionMenu($slot_option_value); + int $slot_attr_value = + AEmmImagePlaneShape2_slotAttrValueFromOptionMenu($slot_option_value); + + AEmmImagePlaneShape2_setSlotValue($image_plane_shp, $slot_attr_value); + + string $cmd = ""; + $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; + $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; + $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; + $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "image_seq = maya.cmds.getAttr(\n"; + $cmd = $cmd + " mm_ip_shp + '.' + current_slot_attr) or ''\n"; + $cmd = $cmd + "if len(image_seq) == 0:\n"; + $cmd = $cmd + " image_seq = lib.get_default_image_path();\n"; + $cmd = $cmd + "\n"; + $cmd = $cmd + "lib.set_image_sequence(\n"; + $cmd = $cmd + " mm_ip_shp,\n"; + $cmd = $cmd + " image_seq,\n"; + $cmd = $cmd + " attr_name=current_slot_attr\n"; + $cmd = $cmd + ")\n"; + + python($cmd); +} + +global proc AEmmImagePlaneShape2_sequenceSlotNew(string $file_attr) +{ + string $nice_name = `attributeName -nice $file_attr`; + + setUITemplate -pst attributeEditorTemplate; + rowLayout -nc 3 imageSeqSlotLayout; + + text -label $nice_name; + optionMenu -alwaysCallChangeCommand imageSeqSlotField; + + menuItem -label "Main"; + menuItem -label "Alternate 1"; + menuItem -label "Alternate 2"; + menuItem -label "Alternate 3"; + + setParent ..; + setUITemplate -ppt; + + AEmmImagePlaneShape2_sequenceSlotReplace $file_attr; +} + + +global proc AEmmImagePlaneShape2_sequenceSlotReplace(string $file_attr) +{ + string $tokens[]; + tokenize($file_attr, ".", $tokens); + if(size($tokens) < 1) { + return; + } + + // In MEL the '#1' text will be replaced by the chosen value from + // the 'changeCommand'. + string $cmd = + "AEmmImagePlaneShape2_sequenceSlotChanged \"" + $tokens[1] + "\" " + $tokens[0] + " \"#1\";"; + + optionMenu -edit -changeCommand $cmd imageSeqSlotField; +} + + +global proc AEmmImagePlaneShape2_imageSequenceNew(string $file_attr) +{ + string $nice_name = `attributeName -nice $file_attr`; + + setUITemplate -pst attributeEditorTemplate; + + rowLayout -nc 3 textureNameLayout; + text -label $nice_name; + textField textureNameField; + symbolButton -image "navButtonBrowse.png" browser; + setParent ..; + + setUITemplate -ppt; + + AEmmImagePlaneShape2_imageSequenceReplace $file_attr; +} + + +global proc AEmmImagePlaneShape2_imageSequenceReplace(string $file_attr) +{ + string $image_plane_shp[]; + tokenize($file_attr, ".", $image_plane_shp); + if(size($image_plane_shp) < 1) { + return; + } + + string $attr_names[]; + tokenize ($file_attr, ".", $attr_names); + + string $cmd = "AEmmImagePlaneShape2_browser \"" + $attr_names[1] + "\" " + $image_plane_shp[0]; + + connectControl -fileName textureNameField $file_attr; + button -edit -command $cmd browser; +} + + +global proc AEmmImagePlaneShape2Template(string $nodeName) +{ + AEmmNodeShapeTemplateCommonBegin($nodeName); + + editorTemplate -beginLayout "Display" -collapse 0; + editorTemplate -addControl "visibleToCameraOnly"; + editorTemplate -addSeparator; + editorTemplate -addControl "colorGain"; + editorTemplate -addControl "colorExposure"; + editorTemplate -addControl "colorGamma"; + editorTemplate -addControl "colorSaturation"; + editorTemplate -addControl "colorSoftClip"; + editorTemplate -addControl "alphaGain"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageIgnoreAlpha"; + editorTemplate -addControl "displayChannel"; + editorTemplate -endLayout; + + editorTemplate -beginLayout "Image Sequence" -collapse 0; + + editorTemplate + -callCustom + "AEmmImagePlaneShape2_sequenceSlotNew" + "AEmmImagePlaneShape2_sequenceSlotReplace" + "imageSequenceSlot"; + + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceMain"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate1"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate2"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate3"; + + editorTemplate -addSeparator; + editorTemplate -addControl "imageWidth"; + editorTemplate -addControl "imageHeight"; + editorTemplate -addControl "imagePixelAspect"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageSequenceStartFrame"; + editorTemplate -addControl "imageSequenceEndFrame"; + editorTemplate -addSeparator; + // TODO: Use mmColorIO results to allow users to manually give the + // inputColorSpace. + editorTemplate -addControl "inputColorSpace"; + editorTemplate -addSeparator; + // TODO: Add radio button to choose what will connect to the + // 'imageSequenceFrame' value? Options are: + // - Scene Time (time1) + // - Animation Curve + // + editorTemplate -addControl "imageSequenceFrame"; + editorTemplate -addControl "imageSequenceFirstFrame"; + editorTemplate -addControl "imageSequenceFrameOutput"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageFlip"; + editorTemplate -addControl "imageFlop"; + editorTemplate -endLayout; + + editorTemplate -beginLayout "HUD" -collapse 0; + editorTemplate -addControl "drawHud"; + editorTemplate -addSeparator; + editorTemplate -addControl "drawCameraSize"; + editorTemplate -addControl "drawImageSize"; + // TODO: Add 'hudTextColor' - control the HUD text color. + editorTemplate -endLayout; + + // editorTemplate -beginLayout "Image Cache" -collapse 1; + // // TODO: Add controls to view and edit the image cache. + // editorTemplate -endLayout; + + editorTemplate -beginLayout "Miscellaneous" -collapse 1; + editorTemplate -addControl "meshResolution"; + editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. + editorTemplate -addControl "shaderIsTransparent"; + editorTemplate -endLayout; + + editorTemplate -beginLayout "Nodes" -collapse 1; + editorTemplate -addControl "geometryNode"; + editorTemplate -addControl "cameraNode"; + editorTemplate -addControl "imagePlaneShapeNode"; + editorTemplate -endLayout; + + // Internals that we don't want the user to see. + editorTemplate -suppress "imageSequencePadding"; + editorTemplate -suppress "cameraWidthInch"; + editorTemplate -suppress "cameraHeightInch"; + editorTemplate -suppress "lensHashCurrent"; + editorTemplate -suppress "lensHashPrevious"; + + AEmmNodeShapeTemplateCommonEnd($nodeName); +} diff --git a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel index 73dbd9dc4..075ba7ffe 100644 --- a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel +++ b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel @@ -1,5 +1,5 @@ // -// Copyright (C) 2022, 2024 David Cattermole. +// Copyright (C) 2022 David Cattermole. // // This file is part of mmSolver. // @@ -97,14 +97,11 @@ global proc AEmmImagePlaneShape_browser( string $cmd = ""; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; - $cmd = $cmd + "\n"; $cmd = $cmd + "image_seq = tool.prompt_user_for_image_sequence();\n"; - $cmd = $cmd + "\n"; - $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; - $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; - $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; - $cmd = $cmd + "\n"; $cmd = $cmd + "if image_seq:\n"; + $cmd = $cmd + " mm_ip_shp = \"" + $image_plane_shp + "\";\n"; + $cmd = $cmd + " attr_name = \"" + $attr_name + "\";\n"; + $cmd = $cmd + " current_slot_attr = \"" + $slot_attr_name + "\";\n"; $cmd = $cmd + " if attr_name == current_slot_attr:\n"; $cmd = $cmd + " lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; @@ -135,18 +132,16 @@ global proc AEmmImagePlaneShape_sequenceSlotChanged( AEmmImagePlaneShape_setSlotValue($image_plane_shp, $slot_attr_value); string $cmd = ""; + $cmd = $cmd + "import os.path;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; - $cmd = $cmd + "\n"; $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; - $cmd = $cmd + "\n"; $cmd = $cmd + "image_seq = maya.cmds.getAttr(\n"; $cmd = $cmd + " mm_ip_shp + '.' + current_slot_attr) or ''\n"; - $cmd = $cmd + "if len(image_seq) == 0:\n"; + $cmd = $cmd + "if len(image_seq) == 0 or not os.path.isfile(image_seq):\n"; $cmd = $cmd + " image_seq = lib.get_default_image_path();\n"; - $cmd = $cmd + "\n"; $cmd = $cmd + "lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; $cmd = $cmd + " image_seq,\n"; @@ -238,15 +233,12 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) editorTemplate -beginLayout "Display" -collapse 0; editorTemplate -addControl "visibleToCameraOnly"; editorTemplate -addSeparator; + editorTemplate -addControl "exposure"; + editorTemplate -addControl "gamma"; editorTemplate -addControl "colorGain"; - editorTemplate -addControl "colorExposure"; - editorTemplate -addControl "colorGamma"; - editorTemplate -addControl "colorSaturation"; - editorTemplate -addControl "colorSoftClip"; editorTemplate -addControl "alphaGain"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageIgnoreAlpha"; - editorTemplate -addControl "displayChannel"; + // editorTemplate -addSeparator; + // editorTemplate -addControl "colorSpace"; // Might not be possible. editorTemplate -endLayout; editorTemplate -beginLayout "Image Sequence" -collapse 0; @@ -278,18 +270,6 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) "AEmmImagePlaneShape_imageSequenceReplace" "imageSequenceAlternate3"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageWidth"; - editorTemplate -addControl "imageHeight"; - editorTemplate -addControl "imagePixelAspect"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageSequenceStartFrame"; - editorTemplate -addControl "imageSequenceEndFrame"; - editorTemplate -addSeparator; - // TODO: Use mmColorIO results to allow users to manually give the - // inputColorSpace. - editorTemplate -addControl "inputColorSpace"; - editorTemplate -addSeparator; // TODO: Add radio button to choose what will connect to the // 'imageSequenceFrame' value? Options are: // - Scene Time (time1) @@ -299,8 +279,15 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) editorTemplate -addControl "imageSequenceFirstFrame"; editorTemplate -addControl "imageSequenceFrameOutput"; editorTemplate -addSeparator; - editorTemplate -addControl "imageFlip"; - editorTemplate -addControl "imageFlop"; + editorTemplate -addControl "imageLoadEnable"; + editorTemplate -addControl "imageUseAlphaChannel"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageWidth"; + editorTemplate -addControl "imageHeight"; + editorTemplate -addControl "imagePixelAspect"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageSequenceStartFrame"; + editorTemplate -addControl "imageSequenceEndFrame"; editorTemplate -endLayout; editorTemplate -beginLayout "HUD" -collapse 0; @@ -311,31 +298,24 @@ global proc AEmmImagePlaneShapeTemplate(string $nodeName) // TODO: Add 'hudTextColor' - control the HUD text color. editorTemplate -endLayout; - // editorTemplate -beginLayout "Image Cache" -collapse 1; - // // TODO: Add controls to view and edit the image cache. - // editorTemplate -endLayout; - editorTemplate -beginLayout "Miscellaneous" -collapse 1; editorTemplate -addControl "meshResolution"; editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. - editorTemplate -addControl "shaderIsTransparent"; editorTemplate -endLayout; editorTemplate -beginLayout "Nodes" -collapse 1; + editorTemplate -addControl "shaderNode"; + editorTemplate -addControl "shaderFileNode"; editorTemplate -addControl "geometryNode"; editorTemplate -addControl "cameraNode"; editorTemplate -addControl "imagePlaneShapeNode"; editorTemplate -endLayout; - // // Internals that we don't want the user to see. - // editorTemplate -suppress "imageSequencePadding"; - // editorTemplate -suppress "cameraWidthInch"; - // editorTemplate -suppress "cameraHeightInch"; - // editorTemplate -suppress "lensHashCurrent"; - // editorTemplate -suppress "lensHashPrevious"; - // editorTemplate -suppress "imageFilePath"; - // editorTemplate -suppress "inputColorSpace"; - // editorTemplate -suppress "outputColorSpace"; + editorTemplate -suppress "imageSequencePadding"; + editorTemplate -suppress "cameraWidthInch"; + editorTemplate -suppress "cameraHeightInch"; + editorTemplate -suppress "lensHashCurrent"; + editorTemplate -suppress "lensHashPrevious"; AEmmNodeShapeTemplateCommonEnd($nodeName); } diff --git a/python/mmSolver/tools/createimageplane/_lib/constant.py b/python/mmSolver/tools/createimageplane/_lib/constant.py index 20761606c..374ec7fa6 100644 --- a/python/mmSolver/tools/createimageplane/_lib/constant.py +++ b/python/mmSolver/tools/createimageplane/_lib/constant.py @@ -34,3 +34,20 @@ SCENE_LINEAR_FILE_EXTENSIONS = ['exr', 'sxr'] SRGB_FILE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'tif', 'tiff', 'tga', 'iff'] + +MM_IMAGE_PLANE_VERSION_ONE = 'mmImagePlaneVersion1' +MM_IMAGE_PLANE_VERSION_TWO = 'mmImagePlaneVersion2' +MM_IMAGE_PLANE_VERSION_LIST = [ + MM_IMAGE_PLANE_VERSION_ONE, + MM_IMAGE_PLANE_VERSION_TWO, +] + +MM_IMAGE_PLANE_SHAPE_V1 = 'mmImagePlaneShape' +MM_IMAGE_PLANE_SHAPE_V2 = 'mmImagePlaneShape2' +MM_IMAGE_PLANE_SHAPE_LIST = [MM_IMAGE_PLANE_SHAPE_V1, MM_IMAGE_PLANE_SHAPE_V2] +MM_IMAGE_PLANE_SHAPE_MAP = { + MM_IMAGE_PLANE_VERSION_ONE: MM_IMAGE_PLANE_SHAPE_V1, + MM_IMAGE_PLANE_VERSION_TWO: MM_IMAGE_PLANE_SHAPE_V2, +} + +MM_IMAGE_PLANE_TRANSFORM = 'mmImagePlaneTransform' diff --git a/python/mmSolver/tools/createimageplane/_lib/main.py b/python/mmSolver/tools/createimageplane/_lib/main.py index 8f272dd32..8b67e5009 100644 --- a/python/mmSolver/tools/createimageplane/_lib/main.py +++ b/python/mmSolver/tools/createimageplane/_lib/main.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020, 2022, 2024 David Cattermole. +# Copyright (C) 2020, 2022 David Cattermole. # # This file is part of mmSolver. # @@ -28,41 +28,55 @@ import mmSolver.utils.constant as const_utils import mmSolver.utils.imageseq as imageseq_utils import mmSolver.utils.python_compat as pycompat - import mmSolver.tools.createimageplane.constant as const import mmSolver.tools.createimageplane._lib.constant as lib_const -import mmSolver.tools.createimageplane._lib.utilities as lib_utils import mmSolver.tools.createimageplane._lib.mmimageplane as lib_mmimageplane -import mmSolver.tools.createimageplane._lib.polyplane as lib_polyplane +import mmSolver.tools.createimageplane._lib.mmimageplane_v1 as lib_mmimageplane_v1 import mmSolver.tools.createimageplane._lib.nativeimageplane as lib_nativeimageplane +import mmSolver.tools.createimageplane._lib.polyplane as lib_polyplane +import mmSolver.tools.createimageplane._lib.shader as lib_shader +import mmSolver.tools.createimageplane._lib.utilities as lib_utils LOG = mmSolver.logger.get_logger() -def create_image_plane_on_camera(cam, name=None): +def create_image_plane_on_camera(cam, name=None, version=None): """Create an Image Plane that can be distorted in Maya's viewport (realtime). """ + if version is None: + version = lib_const.MM_IMAGE_PLANE_VERSION_TWO if name is None: name = 'mmImagePlane1' assert isinstance(cam, mmapi.Camera) assert isinstance(name, pycompat.TEXT_TYPE) + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + cam_tfm = cam.get_transform_node() cam_shp = cam.get_shape_node() - mm_ip_tfm = lib_mmimageplane.create_transform_node(name, cam_tfm, cam_shp) + mm_ip_tfm = lib_mmimageplane.create_transform_node( + name, cam_tfm, cam_shp, version=version + ) poly_plane_name = name + 'MeshShape' poly_plane_network = lib_polyplane.create_poly_plane( poly_plane_name, mm_ip_tfm, cam_shp ) + shader_network = None + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + name_shade = name + 'Shader' + shader_network = lib_shader.create_network(name_shade, mm_ip_tfm) + name_img_shp = name + 'Shape' mm_ip_shp = lib_mmimageplane.create_shape_node( name_img_shp, mm_ip_tfm, cam_shp, poly_plane_network, + shader_network, + version=version, ) # Logic to calculate the frame number. @@ -147,20 +161,52 @@ def _guess_color_space(file_path): return color_space -def set_image_sequence(mm_image_plane_node, image_sequence_path, attr_name=None): +def _set_image_sequence_v1(mm_image_plane_node, image_sequence_path, attr_name=None): + if attr_name is None: + attr_name = lib_const.DEFAULT_IMAGE_SEQUENCE_ATTR_NAME + version = lib_const.MM_IMAGE_PLANE_VERSION_ONE + + tfm, shp = lib_mmimageplane.get_image_plane_node_pair( + mm_image_plane_node, version=version + ) + if tfm is None or shp is None: + LOG.warn('mmImagePlane transform/shape could not be found.') + + file_node = lib_mmimageplane_v1.get_file_node(tfm) + if file_node is None: + LOG.warn('mmImagePlane shader file node is invalid.') + + if shp is not None: + lib_mmimageplane.set_image_sequence( + shp, image_sequence_path, attr_name, version=version + ) + if file_node is not None: + lib_shader.set_file_path(file_node, image_sequence_path) + return + + +def _set_image_sequence_v2(mm_image_plane_node, image_sequence_path, attr_name=None): if attr_name is None: attr_name = lib_const.DEFAULT_IMAGE_SEQUENCE_ATTR_NAME assert isinstance(attr_name, str) assert attr_name in lib_const.VALID_INPUT_IMAGE_SEQUENCE_ATTR_NAMES + version = lib_const.MM_IMAGE_PLANE_VERSION_TWO - tfm, shp = lib_mmimageplane.get_image_plane_node_pair(mm_image_plane_node) + tfm, shp = lib_mmimageplane.get_image_plane_node_pair( + mm_image_plane_node, version=version + ) if tfm is None or shp is None: LOG.warn('mmImagePlane transform/shape could not be found.') if shp is not None: - lib_mmimageplane.set_image_sequence(shp, image_sequence_path, attr_name) lib_mmimageplane.set_image_sequence( - shp, image_sequence_path, lib_const.SHADER_FILE_PATH_ATTR_NAME + shp, image_sequence_path, attr_name, version=version + ) + lib_mmimageplane.set_image_sequence( + shp, + image_sequence_path, + lib_const.SHADER_FILE_PATH_ATTR_NAME, + version=version, ) format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME @@ -188,3 +234,19 @@ def set_image_sequence(mm_image_plane_node, image_sequence_path, attr_name=None) ) return + + +def set_image_sequence( + mm_image_plane_node, image_sequence_path, attr_name=None, version=None +): + if version is None: + version = lib_const.MM_IMAGE_PLANE_VERSION_TWO + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + return _set_image_sequence_v1( + mm_image_plane_node, image_sequence_path, attr_name=attr_name + ) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + return _set_image_sequence_v2( + mm_image_plane_node, image_sequence_path, attr_name=attr_name + ) diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index 121109eeb..ddbac6803 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -1,4 +1,4 @@ -# Copyright (C) 2020, 2022, 2024 David Cattermole. +# Copyright (C) 2020, 2022 David Cattermole. # # This file is part of mmSolver. # @@ -27,216 +27,34 @@ import mmSolver.logger import mmSolver.api as mmapi -import mmSolver.utils.node as node_utils -import mmSolver.utils.constant as const_utils import mmSolver.utils.python_compat as pycompat -import mmSolver.utils.imageseq as imageseq_utils +import mmSolver.tools.createimageplane._lib.constant as lib_const +import mmSolver.tools.createimageplane._lib.mmimageplane_v1 as lib_mmimageplane_v1 +import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as lib_mmimageplane_v2 import mmSolver.tools.createimageplane._lib.utilities as lib_utils LOG = mmSolver.logger.get_logger() -def _create_transform_attrs(image_plane_tfm): - # Depth attribute - attr = 'depth' - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.0, - defaultValue=1.0, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Focal Length attribute - attr = 'focalLength' - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.1, - defaultValue=35.0, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Horizontal Film Aperture attribute - attr = 'horizontalFilmAperture' - value = 36.0 / 25.4 # 35mm film in inches. - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.001, - defaultValue=value, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Vertical Film Aperture attribute - attr = 'verticalFilmAperture' - value = 24.0 / 25.4 # 35mm film in inches. - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.001, - defaultValue=value, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Pixel Aspect Ratio attribute - attr = 'pixelAspect' - value = 1.0 - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.001, - defaultValue=value, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Horizontal Film Offset attribute - attr = 'horizontalFilmOffset' - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.0, - defaultValue=0.0, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - - # Vertical Film Offset attribute - attr = 'verticalFilmOffset' - maya.cmds.addAttr( - image_plane_tfm, - longName=attr, - attributeType='double', - minValue=0.0, - defaultValue=0.0, - ) - maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) - return - - -def _create_image_plane_shape_attrs(image_plane_shp): - # Image Load Enable attribute - attr = 'imageLoadEnable' - maya.cmds.addAttr( - image_plane_shp, longName=attr, attributeType='bool', defaultValue=1 - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - - # Choose which image sequence path to use. This is only visible in - # the Attribute Editor, because this attribute must trigger a - # callback when changed to update the underlying 'file' node. - attr = 'imageSequenceSlot' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - attributeType='enum', - enumName="main=0:alternate1=1:alternate2=2:alternate3=3", - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=False) - - # Image Sequence attribute - attr = 'imageSequence' - nice_name = 'Image Sequence' - attr_suffix_list = ['Main', 'Alternate1', 'Alternate2', 'Alternate3'] - nice_suffix_list = [' (Main)', ' (Alt 1)', ' (Alt 2)', ' (Alt 3)'] - for attr_suffix, nice_suffix in zip(attr_suffix_list, nice_suffix_list): - maya.cmds.addAttr( - image_plane_shp, - longName=attr + attr_suffix, - niceName=nice_name + nice_suffix, - dataType='string', - ) - - # Image Sequence Frame attribute - attr = 'imageSequenceFrame' - maya.cmds.addAttr( - image_plane_shp, longName=attr, attributeType='double', defaultValue=0.0 - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - lib_utils.force_connect_attr('time1.outTime', node_attr) - - attr = 'imageSequenceFirstFrame' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - niceName='First Frame', - attributeType='long', - defaultValue=0, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=False) - maya.cmds.setAttr(node_attr, channelBox=True) - - attr = 'imageSequenceFrameOutput' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - niceName='Frame Output', - attributeType='double', - defaultValue=0.0, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - dst_node_attr = image_plane_shp + '.imageFrameNumber' - lib_utils.force_connect_attr(node_attr, dst_node_attr) - - # Image Sequence details. - maya.cmds.addAttr( - image_plane_shp, - longName='imageSequenceStartFrame', - niceName='Start Frame', - attributeType='long', - defaultValue=0, - ) - maya.cmds.addAttr( - image_plane_shp, - longName='imageSequenceEndFrame', - niceName='End Frame', - attributeType='long', - defaultValue=0, - ) - maya.cmds.addAttr( - image_plane_shp, - longName='imageSequencePadding', - niceName='Padding', - attributeType='long', - defaultValue=0, - ) - - # Mesh Resolution attribute - attr = 'meshResolution' - maya.cmds.addAttr( - image_plane_shp, - longName=attr, - attributeType='long', - minValue=1, - maxValue=256, - defaultValue=32, - ) - node_attr = image_plane_shp + '.' + attr - maya.cmds.setAttr(node_attr, keyable=True) - return - - -def create_transform_node(name_tfm, cam_tfm, cam_shp): +def create_transform_node(name_tfm, cam_tfm, cam_shp, version=None): """ Create a default polygon image plane under camera. """ assert isinstance(name_tfm, pycompat.TEXT_TYPE) + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + tfm_node_type = lib_const.MM_IMAGE_PLANE_TRANSFORM + mmapi.load_plugin() - tfm = maya.cmds.createNode('mmImagePlaneTransform', name=name_tfm, parent=cam_tfm) + tfm = maya.cmds.createNode(tfm_node_type, name=name_tfm, parent=cam_tfm) # Create (dynamic) attributes. - _create_transform_attrs(tfm) + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + lib_mmimageplane_v1.create_transform_attrs(tfm) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + lib_mmimageplane_v2.create_transform_attrs(tfm) + else: + assert False # Image plane depth be far away, but still visible. far_clip_plane = maya.cmds.getAttr(cam_tfm + '.farClipPlane') @@ -264,18 +82,33 @@ def create_shape_node( tfm, cam_shp, poly_plane_node_network, + shader_node_network, + version=None, ): """ Convert mesh to a mmImagePlaneShape. """ assert isinstance(name_img_shp, pycompat.TEXT_TYPE) + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + shp_node_type = lib_const.MM_IMAGE_PLANE_SHAPE_MAP[version] + assert shp_node_type in lib_const.MM_IMAGE_PLANE_SHAPE_LIST img_plane_poly_shp = poly_plane_node_network.mesh_shape img_plane_poly_shp_original = poly_plane_node_network.mesh_shape_original poly_creator = poly_plane_node_network.plane_creator + if shader_node_network is not None: + shd_node = shader_node_network.shd_node + file_node = shader_node_network.file_node + color_gamma_node = shader_node_network.color_gamma_node + alpha_channel_blend_node = shader_node_network.alpha_channel_blend_node + image_load_invert_boolean_node = ( + shader_node_network.image_load_invert_boolean_node + ) + alpha_channel_reverse_node = shader_node_network.alpha_channel_reverse_node + mmapi.load_plugin() - shp = maya.cmds.createNode('mmImagePlaneShape', name=name_img_shp, parent=tfm) + shp = maya.cmds.createNode(shp_node_type, name=name_img_shp, parent=tfm) maya.cmds.setAttr(shp + '.localPositionX', channelBox=False) maya.cmds.setAttr(shp + '.localPositionY', channelBox=False) @@ -297,11 +130,18 @@ def create_shape_node( maya.cmds.reorder(img_plane_poly_shp_original, back=True) maya.cmds.reorder(img_plane_poly_shp, back=True) - _create_image_plane_shape_attrs(shp) + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + lib_mmimageplane_v1.create_image_plane_shape_attrs(shp) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + lib_mmimageplane_v2.create_image_plane_shape_attrs(shp) + else: + assert False # Nodes to drive the image plane shape. lib_utils.force_connect_attr(img_plane_poly_shp + '.outMesh', shp + '.geometryNode') lib_utils.force_connect_attr(cam_shp + '.message', shp + '.cameraNode') + if shader_node_network is not None: + lib_utils.force_connect_attr(shd_node + '.outColor', shp + '.shaderNode') # The image drives the pixel aspect ratio of the image plane. lib_utils.force_connect_attr(shp + '.imagePixelAspect', tfm + '.pixelAspect') @@ -314,6 +154,45 @@ def create_shape_node( tfm + '.verticalFilmAperture', shp + '.cameraHeightInch' ) + if shader_node_network is not None: + # Use the image alpha channel, or not + lib_utils.force_connect_attr( + shp + '.imageUseAlphaChannel', alpha_channel_blend_node + '.blender' + ) + + # Allow user to load the image, or not. + lib_utils.force_connect_attr( + shp + '.imageLoadEnable', image_load_invert_boolean_node + '.inputX' + ) + + # Color Exposure control. + lib_utils.force_connect_attr(shp + '.exposure', file_node + '.exposure') + + # Color Gamma control. + lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaX') + lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaY') + lib_utils.force_connect_attr(shp + '.gamma', color_gamma_node + '.gammaZ') + + # Control file color multiplier + lib_utils.force_connect_attr(shp + '.colorGain', file_node + '.colorGain') + + # Control the alpha gain when 'imageUseAlphaChannel' is disabled. + lib_utils.force_connect_attr(shp + '.alphaGain', file_node + '.alphaGain') + lib_utils.force_connect_attr( + shp + '.alphaGain', alpha_channel_reverse_node + '.inputX' + ) + lib_utils.force_connect_attr( + shp + '.alphaGain', alpha_channel_reverse_node + '.inputY' + ) + lib_utils.force_connect_attr( + shp + '.alphaGain', alpha_channel_reverse_node + '.inputZ' + ) + + # Default color for the image plane, when nothing is loaded. + lib_utils.force_connect_attr( + shp + '.imageDefaultColor', file_node + '.defaultColor' + ) + # Mesh Resolution attr drives the plane sub-divisions. node_attr = shp + '.meshResolution' lib_utils.force_connect_attr(node_attr, poly_creator + '.subdivisionsWidth') @@ -324,113 +203,59 @@ def create_shape_node( maya.cmds.setAttr(img_plane_poly_shp + '.intermediateObject', 1) # Add extra message attributes for finding nodes during callbacks. - maya.cmds.addAttr(shp, longName='imagePlaneShapeNode', attributeType='message') + # maya.cmds.addAttr(shp, longName='imagePlaneShapeNode', attributeType='message') + if shader_node_network is not None: + maya.cmds.addAttr(shp, longName='shaderFileNode', attributeType='message') return shp -def set_image_sequence(shp, image_sequence_path, attr_name): - assert maya.cmds.nodeType(shp) == 'mmImagePlaneShape' - assert node_utils.attribute_exists(attr_name, shp) is True - - format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME - ( - file_pattern, - _, - _, - _, - _, - ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) - first_frame_file_seq = file_pattern.replace('\\', '/') - - mmapi.load_plugin() - try: - image_width_height = maya.cmds.mmReadImage( - first_frame_file_seq, query=True, widthHeight=True +def set_image_sequence(shp, image_sequence_path, attr_name, version=None): + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + result = None + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + result = lib_mmimageplane_v1.set_image_sequence( + shp, image_sequence_path, attr_name ) - except RuntimeError: - image_width_height = None - LOG.warn('Failed to read file: %r', first_frame_file_seq) - - if image_width_height is not None: - image_width = image_width_height[0] - image_height = image_width_height[1] - - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageWidth', lock=False) - maya.cmds.setAttr(shp + '.imageHeight', lock=False) - - maya.cmds.setAttr(shp + '.imageWidth', image_width) - maya.cmds.setAttr(shp + '.imageHeight', image_height) - - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageWidth', lock=True) - maya.cmds.setAttr(shp + '.imageHeight', lock=True) - - format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED - ( - file_pattern, - start_frame, - end_frame, - pad_num, - is_seq, - ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) - - maya.cmds.setAttr(shp + '.' + attr_name, file_pattern, type='string') - - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=False) - maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=False) - maya.cmds.setAttr(shp + '.imageSequencePadding', lock=False) - - maya.cmds.setAttr(shp + '.imageSequenceFirstFrame', start_frame) - maya.cmds.setAttr(shp + '.imageSequenceStartFrame', start_frame) - maya.cmds.setAttr(shp + '.imageSequenceEndFrame', end_frame) - maya.cmds.setAttr(shp + '.imageSequencePadding', pad_num) - - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=True) - maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=True) - maya.cmds.setAttr(shp + '.imageSequencePadding', lock=True) - return - - -def get_shape_node(image_plane_tfm): - shape = None - shapes = ( - maya.cmds.listRelatives( - image_plane_tfm, shapes=True, fullPath=True, type='mmImagePlaneShape' + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + result = lib_mmimageplane_v2.set_image_sequence( + shp, image_sequence_path, attr_name ) - or [] - ) - if len(shapes) > 0: - shape = shapes[0] - return shape + else: + assert False + return result -def get_transform_node(image_plane_shp): - tfm = None - tfms = ( - maya.cmds.listRelatives( - image_plane_shp, parent=True, fullPath=True, type='mmImagePlaneTransform' - ) - or [] - ) - if len(tfms) > 0: - tfm = tfms[0] - return tfm +def get_shape_node(image_plane_tfm, version=None): + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + result = None + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + result = lib_mmimageplane_v1.get_shape_node(image_plane_tfm) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + result = lib_mmimageplane_v2.get_shape_node(image_plane_tfm) + else: + assert False + return result + + +def get_transform_node(image_plane_shp, version=None): + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + result = None + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + result = lib_mmimageplane_v1.get_transform_node(image_plane_shp) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + result = lib_mmimageplane_v2.get_transform_node(image_plane_shp) + else: + assert False + return result -def get_image_plane_node_pair(node): - node_type = maya.cmds.nodeType(node) - assert node_type in ['mmImagePlaneShape', 'mmImagePlaneTransform'] - tfm = None - shp = None - if node_type == 'mmImagePlaneTransform': - shp = get_shape_node(node) - tfm = node +def get_image_plane_node_pair(node, version=None): + assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST + result = None + if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: + result = lib_mmimageplane_v1.get_image_plane_node_pair(node) + elif version == lib_const.MM_IMAGE_PLANE_VERSION_TWO: + result = lib_mmimageplane_v2.get_image_plane_node_pair(node) else: - # Given a shape, we can look at our parent node to find the - # transform. - tfm = get_transform_node(node) - shp = node - return tfm, shp + assert False + return result diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v1.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v1.py new file mode 100644 index 000000000..135cbdc3b --- /dev/null +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v1.py @@ -0,0 +1,424 @@ +# Copyright (C) 2020, 2022 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Library functions for creating and modifying image planes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.api as mmapi +import mmSolver.utils.constant as const_utils +import mmSolver.utils.imageseq as imageseq_utils +import mmSolver.utils.node as node_utils +import mmSolver.tools.createimageplane._lib.constant as lib_const +import mmSolver.tools.createimageplane._lib.utilities as lib_utils + + +LOG = mmSolver.logger.get_logger() + + +def create_transform_attrs(image_plane_tfm): + assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM + + # Depth attribute + attr = 'depth' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=1.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Focal Length attribute + attr = 'focalLength' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.1, + defaultValue=35.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Horizontal Film Aperture attribute + attr = 'horizontalFilmAperture' + value = 36.0 / 25.4 # 35mm film in inches. + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Vertical Film Aperture attribute + attr = 'verticalFilmAperture' + value = 24.0 / 25.4 # 35mm film in inches. + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Pixel Aspect Ratio attribute + attr = 'pixelAspect' + value = 1.0 + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Horizontal Film Offset attribute + attr = 'horizontalFilmOffset' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=0.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Vertical Film Offset attribute + attr = 'verticalFilmOffset' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=0.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + return + + +def create_image_plane_shape_attrs(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V1 + + # Exposure attribute + attr = 'exposure' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='double', + softMinValue=-5.0, + softMaxValue=5.0, + defaultValue=0.0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Gamma attribute + attr = 'gamma' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='double', + minValue=0.0, + softMaxValue=3.0, + defaultValue=1.0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Color Gain attribute + attr = 'colorGain' + default_value = 1.0 + lib_utils.add_attr_float3_color(image_plane_shp, attr, default_value) + + # Alpha Gain attribute + attr = 'alphaGain' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='double', + minValue=0.0, + softMaxValue=1.0, + defaultValue=1.0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Use Image Alpha Channel attribute + attr = 'imageUseAlphaChannel' + maya.cmds.addAttr( + image_plane_shp, longName=attr, attributeType='bool', defaultValue=0 + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Default Image Color attribute, display a dark-red color when an + # image is not found. + attr = 'imageDefaultColor' + default_value = 0.0 + lib_utils.add_attr_float3_color(image_plane_shp, attr, default_value) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr + 'R', 0.3) + maya.cmds.setAttr(node_attr + 'G', 0.0) + maya.cmds.setAttr(node_attr + 'B', 0.0) + + # Image Load Enable attribute + attr = 'imageLoadEnable' + maya.cmds.addAttr( + image_plane_shp, longName=attr, attributeType='bool', defaultValue=1 + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Choose which image sequence path to use. This is only visible in + # the Attribute Editor, because this attribute must trigger a + # callback when changed to update the underlying 'file' node. + attr = 'imageSequenceSlot' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='enum', + enumName="main=0:alternate1=1:alternate2=2:alternate3=3", + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=False) + + # Image Sequence attribute + attr = 'imageSequence' + nice_name = 'Image Sequence' + attr_suffix_list = ['Main', 'Alternate1', 'Alternate2', 'Alternate3'] + nice_suffix_list = [' (Main)', ' (Alt 1)', ' (Alt 2)', ' (Alt 3)'] + for attr_suffix, nice_suffix in zip(attr_suffix_list, nice_suffix_list): + maya.cmds.addAttr( + image_plane_shp, + longName=attr + attr_suffix, + niceName=nice_name + nice_suffix, + dataType='string', + ) + + # Image Sequence Frame attribute + attr = 'imageSequenceFrame' + maya.cmds.addAttr( + image_plane_shp, longName=attr, attributeType='double', defaultValue=0.0 + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + lib_utils.force_connect_attr('time1.outTime', node_attr) + + attr = 'imageSequenceFirstFrame' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + niceName='First Frame', + attributeType='long', + defaultValue=0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=False) + maya.cmds.setAttr(node_attr, channelBox=True) + + attr = 'imageSequenceFrameOutput' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + niceName='Frame Output', + attributeType='double', + defaultValue=0.0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + + # Image Sequence details. + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequenceStartFrame', + niceName='Start Frame', + attributeType='long', + defaultValue=0, + ) + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequenceEndFrame', + niceName='End Frame', + attributeType='long', + defaultValue=0, + ) + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequencePadding', + niceName='Padding', + attributeType='long', + defaultValue=0, + ) + + # Mesh Resolution attribute + attr = 'meshResolution' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='long', + minValue=1, + maxValue=256, + defaultValue=32, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + return + + +def set_image_sequence(shp, image_sequence_path, attr_name): + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V1 + assert node_utils.attribute_exists(attr_name, shp) is True + + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME + ( + file_pattern, + start_frame, + end_frame, + pad_num, + is_seq, + ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) + first_frame_file_seq = file_pattern + + mmapi.load_plugin() + try: + first_frame_file_seq = first_frame_file_seq.replace('\\', '/') + image_width_height = maya.cmds.mmReadImage( + first_frame_file_seq, query=True, widthHeight=True + ) + except RuntimeError: + image_width_height = None + LOG.warn('Failed to read file: %r', first_frame_file_seq) + + if image_width_height is not None: + image_width = image_width_height[0] + image_height = image_width_height[1] + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=False) + maya.cmds.setAttr(shp + '.imageHeight', lock=False) + + maya.cmds.setAttr(shp + '.imageWidth', image_width) + maya.cmds.setAttr(shp + '.imageHeight', image_height) + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=True) + maya.cmds.setAttr(shp + '.imageHeight', lock=True) + + maya.cmds.setAttr(shp + '.' + attr_name, file_pattern, type='string') + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=False) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=False) + maya.cmds.setAttr(shp + '.imageSequencePadding', lock=False) + + maya.cmds.setAttr(shp + '.imageSequenceFirstFrame', start_frame) + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', start_frame) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', end_frame) + maya.cmds.setAttr(shp + '.imageSequencePadding', pad_num) + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=True) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=True) + maya.cmds.setAttr(shp + '.imageSequencePadding', lock=True) + return + + +def get_shape_node(image_plane_tfm): + assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM + + shape = None + shapes = ( + maya.cmds.listRelatives( + image_plane_tfm, + shapes=True, + fullPath=True, + type=lib_const.MM_IMAGE_PLANE_SHAPE_V1, + ) + or [] + ) + if len(shapes) > 0: + shape = shapes[0] + return shape + + +def get_transform_node(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V1 + + tfm = None + tfms = ( + maya.cmds.listRelatives( + image_plane_shp, + parent=True, + fullPath=True, + type=lib_const.MM_IMAGE_PLANE_TRANSFORM, + ) + or [] + ) + if len(tfms) > 0: + tfm = tfms[0] + return tfm + + +def get_image_plane_node_pair(node): + node_type = maya.cmds.nodeType(node) + assert node_type in [ + lib_const.MM_IMAGE_PLANE_SHAPE_V1, + lib_const.MM_IMAGE_PLANE_TRANSFORM, + ] + + tfm = None + shp = None + if node_type == lib_const.MM_IMAGE_PLANE_TRANSFORM: + shp = get_shape_node(node) + tfm = node + else: + # Given a shape, we can look at our parent node to find the + # transform. + tfm = get_transform_node(node) + shp = node + return tfm, shp + + +def get_file_node(image_plane_tfm): + assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM + + file_node = None + conns = ( + maya.cmds.listConnections( + image_plane_tfm + '.shaderFileNode', + destination=False, + source=True, + plugs=False, + type='file', + ) + or [] + ) + if len(conns) > 0: + file_node = conns[0] + return file_node diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py new file mode 100644 index 000000000..3fbd413dd --- /dev/null +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -0,0 +1,344 @@ +# Copyright (C) 2020, 2022, 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Library functions for creating and modifying image planes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.api as mmapi +import mmSolver.utils.constant as const_utils +import mmSolver.utils.imageseq as imageseq_utils +import mmSolver.utils.node as node_utils +import mmSolver.tools.createimageplane._lib.constant as lib_const +import mmSolver.tools.createimageplane._lib.utilities as lib_utils + + +LOG = mmSolver.logger.get_logger() + + +def create_transform_attrs(image_plane_tfm): + assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM + + # Depth attribute + attr = 'depth' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=1.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Focal Length attribute + attr = 'focalLength' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.1, + defaultValue=35.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Horizontal Film Aperture attribute + attr = 'horizontalFilmAperture' + value = 36.0 / 25.4 # 35mm film in inches. + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Vertical Film Aperture attribute + attr = 'verticalFilmAperture' + value = 24.0 / 25.4 # 35mm film in inches. + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Pixel Aspect Ratio attribute + attr = 'pixelAspect' + value = 1.0 + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.001, + defaultValue=value, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Horizontal Film Offset attribute + attr = 'horizontalFilmOffset' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=0.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + + # Vertical Film Offset attribute + attr = 'verticalFilmOffset' + maya.cmds.addAttr( + image_plane_tfm, + longName=attr, + attributeType='double', + minValue=0.0, + defaultValue=0.0, + ) + maya.cmds.setAttr(image_plane_tfm + '.' + attr, keyable=True) + return + + +def create_image_plane_shape_attrs(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + + # Choose which image sequence path to use. This is only visible in + # the Attribute Editor, because this attribute must trigger a + # callback when changed to update the underlying 'file' node. + attr = 'imageSequenceSlot' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='enum', + enumName="main=0:alternate1=1:alternate2=2:alternate3=3", + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=False) + + # Image Sequence attribute + attr = 'imageSequence' + nice_name = 'Image Sequence' + attr_suffix_list = ['Main', 'Alternate1', 'Alternate2', 'Alternate3'] + nice_suffix_list = [' (Main)', ' (Alt 1)', ' (Alt 2)', ' (Alt 3)'] + for attr_suffix, nice_suffix in zip(attr_suffix_list, nice_suffix_list): + maya.cmds.addAttr( + image_plane_shp, + longName=attr + attr_suffix, + niceName=nice_name + nice_suffix, + dataType='string', + ) + + # Image Sequence Frame attribute + attr = 'imageSequenceFrame' + maya.cmds.addAttr( + image_plane_shp, longName=attr, attributeType='double', defaultValue=0.0 + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + lib_utils.force_connect_attr('time1.outTime', node_attr) + + attr = 'imageSequenceFirstFrame' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + niceName='First Frame', + attributeType='long', + defaultValue=0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=False) + maya.cmds.setAttr(node_attr, channelBox=True) + + attr = 'imageSequenceFrameOutput' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + niceName='Frame Output', + attributeType='double', + defaultValue=0.0, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + dst_node_attr = image_plane_shp + '.imageFrameNumber' + lib_utils.force_connect_attr(node_attr, dst_node_attr) + + # Image Sequence details. + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequenceStartFrame', + niceName='Start Frame', + attributeType='long', + defaultValue=0, + ) + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequenceEndFrame', + niceName='End Frame', + attributeType='long', + defaultValue=0, + ) + maya.cmds.addAttr( + image_plane_shp, + longName='imageSequencePadding', + niceName='Padding', + attributeType='long', + defaultValue=0, + ) + + # Mesh Resolution attribute + attr = 'meshResolution' + maya.cmds.addAttr( + image_plane_shp, + longName=attr, + attributeType='long', + minValue=1, + maxValue=256, + defaultValue=32, + ) + node_attr = image_plane_shp + '.' + attr + maya.cmds.setAttr(node_attr, keyable=True) + return + + +def set_image_sequence(shp, image_sequence_path, attr_name): + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + assert node_utils.attribute_exists(attr_name, shp) is True + + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME + ( + file_pattern, + _, + _, + _, + _, + ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) + first_frame_file_seq = file_pattern.replace('\\', '/') + + mmapi.load_plugin() + try: + image_width_height = maya.cmds.mmReadImage( + first_frame_file_seq, query=True, widthHeight=True + ) + except RuntimeError: + image_width_height = None + LOG.warn('Failed to read file: %r', first_frame_file_seq) + + if image_width_height is not None: + image_width = image_width_height[0] + image_height = image_width_height[1] + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=False) + maya.cmds.setAttr(shp + '.imageHeight', lock=False) + + maya.cmds.setAttr(shp + '.imageWidth', image_width) + maya.cmds.setAttr(shp + '.imageHeight', image_height) + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=True) + maya.cmds.setAttr(shp + '.imageHeight', lock=True) + + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED + ( + file_pattern, + start_frame, + end_frame, + pad_num, + is_seq, + ) = imageseq_utils.expand_image_sequence_path(image_sequence_path, format_style) + + maya.cmds.setAttr(shp + '.' + attr_name, file_pattern, type='string') + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=False) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=False) + maya.cmds.setAttr(shp + '.imageSequencePadding', lock=False) + + maya.cmds.setAttr(shp + '.imageSequenceFirstFrame', start_frame) + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', start_frame) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', end_frame) + maya.cmds.setAttr(shp + '.imageSequencePadding', pad_num) + + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageSequenceStartFrame', lock=True) + maya.cmds.setAttr(shp + '.imageSequenceEndFrame', lock=True) + maya.cmds.setAttr(shp + '.imageSequencePadding', lock=True) + return + + +def get_shape_node(image_plane_tfm): + assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM + + shape = None + shapes = ( + maya.cmds.listRelatives( + image_plane_tfm, + shapes=True, + fullPath=True, + type=lib_const.MM_IMAGE_PLANE_SHAPE_V2, + ) + or [] + ) + if len(shapes) > 0: + shape = shapes[0] + return shape + + +def get_transform_node(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + + tfm = None + tfms = ( + maya.cmds.listRelatives( + image_plane_shp, + parent=True, + fullPath=True, + type=lib_const.MM_IMAGE_PLANE_TRANSFORM, + ) + or [] + ) + if len(tfms) > 0: + tfm = tfms[0] + return tfm + + +def get_image_plane_node_pair(node): + node_type = maya.cmds.nodeType(node) + assert node_type in [ + lib_const.MM_IMAGE_PLANE_SHAPE_V2, + lib_const.MM_IMAGE_PLANE_TRANSFORM, + ] + tfm = None + shp = None + if node_type == lib_const.MM_IMAGE_PLANE_TRANSFORM: + shp = get_shape_node(node) + tfm = node + else: + # Given a shape, we can look at our parent node to find the + # transform. + tfm = get_transform_node(node) + shp = node + return tfm, shp diff --git a/python/mmSolver/tools/createimageplane/tool.py b/python/mmSolver/tools/createimageplane/tool.py index 1f434b6ad..792724120 100644 --- a/python/mmSolver/tools/createimageplane/tool.py +++ b/python/mmSolver/tools/createimageplane/tool.py @@ -32,6 +32,7 @@ import mmSolver.api as mmapi import mmSolver.utils.viewport as utils_viewport import mmSolver.utils.camera as utils_camera +import mmSolver.tools.createimageplane._lib.constant as const import mmSolver.tools.createimageplane.lib as lib @@ -92,10 +93,11 @@ def prompt_user_for_image_sequence(start_dir=None): return image_sequence_path -def main(): +def _run(version): """ Create a new Image Plane on the selected camera. """ + assert version in const.MM_IMAGE_PLANE_VERSION_LIST mmapi.load_plugin() # Get selected camera(s). @@ -163,3 +165,15 @@ def main(): else: maya.cmds.select(sel, replace=True) return + + +def main(): + _run(const.MM_IMAGE_PLANE_VERSION_ONE) + + +def main_version_one(): + _run(const.MM_IMAGE_PLANE_VERSION_ONE) + + +def main_version_two(): + _run(const.MM_IMAGE_PLANE_VERSION_TWO) diff --git a/share/config/functions.json b/share/config/functions.json index 71c42796c..e803bfd7e 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -847,13 +847,23 @@ ] }, "create_image_plane": { + "name": "Create Image Plane... (version 1)", + "name_shelf": "", + "tooltip": "Create a new Image Plane.", + "icon_shelf": "createImagePlane_32x32.png", + "command": [ + "import mmSolver.tools.createimageplane.tool;", + "mmSolver.tools.createimageplane.tool.main_version_one();" + ] + }, + "create_image_plane2": { "name": "Create Image Plane...", "name_shelf": "", "tooltip": "Create a new Image Plane.", "icon_shelf": "createImagePlane_32x32.png", "command": [ "import mmSolver.tools.createimageplane.tool;", - "mmSolver.tools.createimageplane.tool.main();" + "mmSolver.tools.createimageplane.tool.main_version_two();" ] }, "camera_toggle_distortion": { diff --git a/share/config/menu.json b/share/config/menu.json index c655ab6c6..6cc40bcd0 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -8,7 +8,7 @@ "solver_run_current_frame", "---Create", "create_camera", - "create_image_plane", + "create_image_plane2", "create_lens", "create_marker", "convert_to_marker", @@ -37,7 +37,7 @@ "attr_tools/attribute_bake", "camera_tools/---Camera & Lens", "camera_tools/create_camera", - "camera_tools/create_image_plane", + "camera_tools/create_image_plane2", "camera_tools/create_lens", "camera_tools/---Distortion", "camera_tools/camera_toggle_distortion", diff --git a/share/config/shelf.json b/share/config/shelf.json index aa1b35c5d..1f39329af 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -31,7 +31,7 @@ "attr_tools/attr_popup/attribute_bake", "camera_tools/camera_popup/---Camera & Lens", "camera_tools/camera_popup/create_camera", - "camera_tools/camera_popup/create_image_plane", + "camera_tools/camera_popup/create_image_plane2", "camera_tools/camera_popup/create_lens", "camera_tools/camera_popup/---Distortion", "camera_tools/camera_popup/camera_toggle_distortion", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index c1218d333..5c631c964 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -8,7 +8,7 @@ "solver_tools/solver_popup/solver_run_current_frame", "create_tools/create_popup/---Camera & Lens", "create_tools/create_popup/create_camera", - "create_tools/create_popup/create_image_plane", + "create_tools/create_popup/create_image_plane2", "create_tools/create_popup/create_lens", "create_tools/create_popup/---Create Marker", "create_tools/create_popup/create_marker", @@ -43,7 +43,7 @@ "attr_tools/attr_popup/attribute_bake", "camera_tools/camera_popup/---Camera & Lens", "camera_tools/camera_popup/create_camera", - "camera_tools/camera_popup/create_image_plane", + "camera_tools/camera_popup/create_image_plane2", "camera_tools/camera_popup/create_lens", "camera_tools/camera_popup/---Transform", "camera_tools/camera_popup/set_camera_origin_frame", diff --git a/share/icons/out_mmImagePlaneShape2.png b/share/icons/out_mmImagePlaneShape2.png new file mode 100644 index 0000000000000000000000000000000000000000..2c0d8526aec09a1842808d2d340b6edaa1e82d6d GIT binary patch literal 841 zcmV-P1GfB$P)R-RFOwa}R;L zbO6o+;IO-L0x$yjzyiokO3N)QEUA<;gziccMUm8$7e%)=yx<6c%C{YJnqbwwlwEcQ z0I)7C^juC-jZgqAC-YNE3%tuYZSMWtIr($Q1i-OVkYQvT0M?z6fB=BY<(#^Eb-y(x zDowD=MceendA&PsQ3L}3(nq5J(AQ%afBxDs8>=O$*Kd&vEUjk*o}b={M=0?C2>|Q~ zR@2ep^_VMDzhnV~QvV%+WCKXnTuqc#`>HNSeft`3Iv0nrygSXagb>wM0S!&HEi;p& zCNCI3bwl@Jyux33LfX9Qu>W9XTnMF9y%pB-zPi=0SfO0_I`99esNEKNBqGUSUmnhm z_wV(HEIo5_KaDTU&{*5tW~m5_dy<@x(WMJ7d7Pn~n)u!=ZCH!!0j`(LYB3u>*0%u= z*RiFid$r}s@c>v51f?4bhvC?G8g1Wa+!TOew3q$9mO@KnWl-5>~B{z9@qvn ztu1@*O<>i$*UxLSTFm+R$0aNr025o5kQ90`{Lfh3$QNIU2ule7-eV}6It$;hpNTC? zNIsYzCSP5!_xnPD4MSPBEe;gi(8x0TvbjyzI;uDnkT|68|NFaV&giu&LIBo=GBq(W z`LHx6ENf85QjR&gO#>vR?9>@-acPL6=#)APdT=l0Sk|CUq;e)=yEsy(02E#zFE|14 zEP(a{X<=#+I4rCRzAgfXC -#include "ImagePlaneShapeNode.h" #include "mmSolver/utilities/debug_utils.h" namespace mmsolver { diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp new file mode 100644 index 000000000..a6732c269 --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -0,0 +1,1113 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "ImagePlaneGeometry2Override.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Maya Viewport 2.0 +#include +#include +#include +#include +#include +#include + +// MM Solver +#include +#include + +#include "ImageCache.h" +#include "ImagePlaneShape2Node.h" +#include "ImagePlaneUtils.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" + +namespace mmsolver { + +const MString renderItemName_imagePlaneWireframe = + MString("imagePlaneWireframe"); +const MString renderItemName_imagePlaneShaded = MString("imagePlaneShaded"); + +ImagePlaneGeometry2Override::ImagePlaneGeometry2Override(const MObject &obj) + : MHWRender::MPxGeometryOverride(obj) + , m_this_node(obj) + , m_visible(true) + , m_draw_hud(false) + , m_draw_image_size(false) + , m_draw_camera_size(false) + , m_geometry_node_type(MFn::kInvalid) + , m_image_display_channel(ImageDisplayChannel::kRGBA) + , m_color_gain(1.0, 1.0, 1.0, 1.0) + , m_color_exposure(0.0f) + , m_color_gamma(1.0f) + , m_color_saturation(1.0f) + , m_color_soft_clip(0.0f) + , m_alpha_gain(1.0f) + , m_default_color(0.3, 0.0, 0.0, 1.0) + , m_ignore_alpha(false) + , m_flip(false) + , m_flop(false) + , m_is_transparent(false) + , m_frame(0) + , m_file_path() + , m_input_color_space_name() + , m_output_color_space_name() + , m_shader(nullptr) + , m_update_shader(false) + , m_color_texture(nullptr) + , m_texture_sampler(nullptr) { + m_model_editor_changed_callback_id = MEventMessage::addEventCallback( + "modelEditorChanged", + ImagePlaneGeometry2Override::on_model_editor_changed_func, this); +#if MAYA_API_VERSION >= 20200000 + m_shader_link_lost_user_data_ptr = + ShaderLinkLostUserData2Ptr(new ShaderLinkLostUserData2()); +#elif + m_shader_link_lost_user_data = ShaderLinkLostUserData2(); +#endif +} + +ImagePlaneGeometry2Override::~ImagePlaneGeometry2Override() { + if (m_model_editor_changed_callback_id != 0) { + MMessage::removeCallback(m_model_editor_changed_callback_id); + m_model_editor_changed_callback_id = 0; + } + + if (m_color_texture) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + MHWRender::MTextureManager *textureMgr = + renderer ? renderer->getTextureManager() : nullptr; + if (textureMgr) { + textureMgr->releaseTexture(m_color_texture); + m_color_texture = nullptr; + } + } + + if (m_texture_sampler) { + MHWRender::MStateManager::releaseSamplerState(m_texture_sampler); + m_texture_sampler = nullptr; + } + + if (m_shader) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + const MHWRender::MShaderManager *shaderManager = + renderer ? renderer->getShaderManager() : nullptr; + if (shaderManager) { + shaderManager->releaseShader(m_shader); + } + } +} + +void ImagePlaneGeometry2Override::on_model_editor_changed_func( + void *clientData) { + // Mark the node as being dirty so that it can update on display + // appearance switch among wireframe and shaded. + ImagePlaneGeometry2Override *ovr = + static_cast(clientData); + if (ovr && !ovr->m_this_node.isNull()) { + MHWRender::MRenderer::setGeometryDrawDirty(ovr->m_this_node); + } +} + +void ImagePlaneGeometry2Override::shader_link_lost_func( + ShaderLinkLostUserData2 *userData) { + // TODO: What should this function do? Does it need to do anything? + MMSOLVER_MAYA_DBG( + "mmImagePlaneShape: shader_link_lost_func: link_lost_count=" + << (*userData).link_lost_count + << " set_shader_count=" << (*userData).set_shader_count); + (*userData).link_lost_count += 1; +} + +MHWRender::DrawAPI ImagePlaneGeometry2Override::supportedDrawAPIs() const { + // TODO: We cannot support DirectX, unless we also write another + // mmImagePlane for DirectX. Legacy OpenGL is also unsupported. + return MHWRender::kOpenGLCoreProfile; +} + +// bool getUpstreamNodeFromConnection(const MObject &this_node, +// const MString &attr_name, +// MPlugArray &out_connections) { +// MStatus status; +// MFnDependencyNode mfn_depend_node(this_node); + +// bool wantNetworkedPlug = true; +// MPlug plug = +// mfn_depend_node.findPlug(attr_name, wantNetworkedPlug, &status); +// if (status != MStatus::kSuccess) { +// CHECK_MSTATUS(status); +// return false; +// } +// if (plug.isNull()) { +// MMSOLVER_MAYA_WRN("Could not get plug for \"" +// << mfn_depend_node.name().asChar() << "." +// << attr_name.asChar() << "\" node."); +// return false; +// } + +// bool as_destination = true; +// bool as_source = false; +// // Ask for plugs connecting to this node's ".shaderNode" +// // attribute. +// plug.connectedTo(out_connections, as_destination, as_source, &status); +// if (status != MStatus::kSuccess) { +// CHECK_MSTATUS(status); +// return false; +// } +// if (out_connections.length() == 0) { +// MMSOLVER_MAYA_WRN("No connections to the \"" +// << mfn_depend_node.name().asChar() << "." +// << attr_name.asChar() << "\" attribute."); +// return false; +// } +// return true; +// } + +// void calculate_node_image_size_string(MDagPath &objPath, +// const uint32_t int_precision, +// const uint32_t double_precision, +// bool &out_draw_image_size, +// MString &out_image_size) { +// MStatus status = MS::kSuccess; + +// double width = 1.0; +// double height = 1.0; +// double pixel_aspect = 1.0; + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_draw_image_size, +// out_draw_image_size); +// CHECK_MSTATUS(status); + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_width, +// width); CHECK_MSTATUS(status); + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_height, +// height); CHECK_MSTATUS(status); + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_pixel_aspect, +// pixel_aspect); +// CHECK_MSTATUS(status); + +// double aspect = (width * pixel_aspect) / height; + +// MString width_string; +// MString height_string; +// MString pixel_aspect_string; +// MString aspect_string; + +// width_string.set(width, int_precision); +// height_string.set(height, int_precision); +// pixel_aspect_string.set(pixel_aspect, double_precision); +// aspect_string.set(aspect, double_precision); + +// out_image_size = MString("Image: ") + width_string + MString(" x ") + +// height_string + MString(" | PAR ") + pixel_aspect_string +// + MString(" | ") + aspect_string; +// return; +// } + +// void calculate_node_camera_size_string(MDagPath &objPath, +// const uint32_t double_precision, +// bool &out_draw_camera_size, +// MString &out_camera_size) { +// MStatus status = MS::kSuccess; + +// double width = 0.0; +// double height = 0.0; + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_draw_camera_size, +// out_draw_camera_size); +// CHECK_MSTATUS(status); + +// status = +// getNodeAttr(objPath, ImagePlaneShape2Node::m_camera_width_inch, +// width); +// CHECK_MSTATUS(status); + +// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_camera_height_inch, +// height); +// CHECK_MSTATUS(status); + +// double aspect = width / height; + +// MString width_string; +// MString height_string; +// MString aspect_string; + +// width_string.set(width * INCH_TO_MM, double_precision); +// height_string.set(height * INCH_TO_MM, double_precision); +// aspect_string.set(aspect, double_precision); + +// out_camera_size = MString("Camera: ") + width_string + MString("mm x ") + +// height_string + MString("mm | ") + aspect_string; +// } + +void ImagePlaneGeometry2Override::query_node_attributes( + MObject &node, MDagPath &out_camera_node_path, bool &out_visible, + bool &out_visible_to_camera_only, bool &out_is_under_camera, + bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, + bool &out_draw_camera_size, MString &out_camera_size, + ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, + float &out_color_exposure, float &out_color_gamma, + float &out_color_saturation, float &out_color_soft_clip, + float &out_alpha_gain, MColor &out_default_color, bool &out_ignore_alpha, + bool &out_flip, bool &out_flop, bool &out_is_transparent, + mmcore::FrameValue &out_frame, MString &out_file_path, + MString &out_input_color_space_name, MString &out_output_color_space_name) { + const bool verbose = false; + + MDagPath objPath; + MDagPath::getAPathTo(node, objPath); + + if (!objPath.isValid()) { + return; + } + MStatus status; + + auto frame_context = MPxGeometryOverride::getFrameContext(); + MDagPath camera_node_path = frame_context->getCurrentCameraPath(&status); + CHECK_MSTATUS(status); + + // By default the draw is visible, unless overridden by + // out_visible_to_camera_only or out_is_under_camera. + out_visible = true; + + status = + getNodeAttr(objPath, ImagePlaneShape2Node::m_visible_to_camera_only, + out_visible_to_camera_only); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShape2Node::m_draw_hud, out_draw_hud); + CHECK_MSTATUS(status); + + out_is_under_camera = true; + if (camera_node_path.isValid() && out_camera_node_path.isValid()) { + // Using an explicit camera node path to compare + // against ensures that if a rouge camera is parented + // under the attached camera, the node will be + // invisible. + out_is_under_camera = out_camera_node_path == camera_node_path; + } + + if (!out_is_under_camera) { + if (out_visible_to_camera_only) { + out_visible = false; + } + // Do not draw the HUD if we are not under the camera, + // the HUD must only be visible from the point of view + // of the intended camera, otherwise it will look + // wrong. + out_draw_hud = false; + } + + const auto int_precision = 0; + const auto double_precision = 3; + calculate_node_image_size_string( + objPath, ImagePlaneShape2Node::m_draw_image_size, + ImagePlaneShape2Node::m_image_width, + ImagePlaneShape2Node::m_image_height, + ImagePlaneShape2Node::m_image_pixel_aspect, int_precision, + double_precision, out_draw_image_size, out_image_size); + calculate_node_camera_size_string( + objPath, ImagePlaneShape2Node::m_draw_camera_size, + ImagePlaneShape2Node::m_camera_width_inch, + ImagePlaneShape2Node::m_camera_height_inch, double_precision, + out_draw_camera_size, out_camera_size); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_color_gain, + out_color_gain); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_color_exposure, + out_color_exposure); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_color_gamma, + out_color_gamma); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShape2Node::m_image_color_saturation, + out_color_saturation); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_color_soft_clip, + out_color_soft_clip); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_alpha_gain, + out_alpha_gain); + CHECK_MSTATUS(status); + + short image_display_channel_value = 0; + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_display_channel, + image_display_channel_value); + CHECK_MSTATUS(status); + out_image_display_channel = + static_cast(image_display_channel_value); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_ignore_alpha, + out_ignore_alpha); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_flip, out_flip); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_flop, out_flop); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_shader_is_transparent, + out_is_transparent); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_frame_number, + out_frame); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_file_path, + out_file_path); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShape2Node::m_image_input_color_space, + out_input_color_space_name); + CHECK_MSTATUS(status); + + status = + getNodeAttr(objPath, ImagePlaneShape2Node::m_image_output_color_space, + out_output_color_space_name); + CHECK_MSTATUS(status); + + // Find the input/output file color spaces. + // + // TODO: Do not re-calculate this each update. Compute once and + // cache the results. + const char *file_color_space_name = + mmcolorio::guess_color_space_name_from_file_path( + out_file_path.asChar()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" + << " file_color_space_name=\"" << file_color_space_name + << "\"."); + + const char *output_color_space_name = mmcolorio::get_role_color_space_name( + mmcolorio::ColorSpaceRole::kSceneLinear); + out_output_color_space_name = MString(output_color_space_name); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" + << " out_output_color_space_name=\"" + << out_output_color_space_name.asChar() << "\"."); +} + +// void find_geometry_node_path(MObject &node, MString &attr_name, +// MDagPath &out_geometry_node_path, +// MFn::Type &out_geometry_node_type) { +// const auto verbose = false; + +// MPlugArray connections; +// bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); +// if (!ok) { +// return; +// } + +// for (uint32_t i = 0; i < connections.length(); ++i) { +// MObject node = connections[i].node(); + +// if (node.hasFn(MFn::kMesh)) { +// MDagPath path; +// MDagPath::getAPathTo(node, path); +// out_geometry_node_path = path; +// out_geometry_node_type = path.apiType(); +// MMSOLVER_MAYA_VRB("Validated geometry node: " +// << " path=" +// << +// out_geometry_node_path.fullPathName().asChar() +// << " type=" << node.apiTypeStr()); +// break; +// } else { +// MMSOLVER_MAYA_WRN("Geometry node is not correct type:" +// << " path=" +// << +// out_geometry_node_path.fullPathName().asChar() +// << " type=" << node.apiTypeStr()); +// } +// } +// } + +// void find_camera_node_path(MObject &node, MString &attr_name, +// MDagPath &out_camera_node_path, +// MFn::Type &out_camera_node_type) { +// const auto verbose = false; + +// MPlugArray connections; +// bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); +// if (!ok) { +// return; +// } + +// for (uint32_t i = 0; i < connections.length(); ++i) { +// MObject node = connections[i].node(); + +// if (node.hasFn(MFn::kCamera)) { +// MDagPath path; +// MDagPath::getAPathTo(node, path); +// out_camera_node_path = path; +// out_camera_node_type = path.apiType(); +// MMSOLVER_MAYA_VRB("Validated camera node: " +// << " path=" +// << out_camera_node_path.fullPathName().asChar() +// << " type=" << node.apiTypeStr()); +// break; +// } else { +// MMSOLVER_MAYA_WRN("Camera node is not correct type:" +// << " path=" +// << out_camera_node_path.fullPathName().asChar() +// << " type=" << node.apiTypeStr()); +// } +// } +// } + +void ImagePlaneGeometry2Override::updateDG() { + if (!m_geometry_node_path.isValid()) { + MString attr_name = "geometryNode"; + find_geometry_node_path(m_this_node, attr_name, m_geometry_node_path, + m_geometry_node_type); + } + + if (!m_camera_node_path.isValid()) { + MString attr_name = "cameraNode"; + find_camera_node_path(m_this_node, attr_name, m_camera_node_path, + m_camera_node_type); + } + + // Query Attributes from the base node. + MString temp_input_color_space_name = ""; + MString temp_output_color_space_name = ""; + ImagePlaneGeometry2Override::query_node_attributes( + m_this_node, m_camera_node_path, m_visible, m_visible_to_camera_only, + m_is_under_camera, m_draw_hud, m_draw_image_size, m_image_size, + m_draw_camera_size, m_camera_size, m_image_display_channel, + m_color_gain, m_color_exposure, m_color_gamma, m_color_saturation, + m_color_soft_clip, m_alpha_gain, m_default_color, m_ignore_alpha, + m_flip, m_flop, m_is_transparent, m_frame, m_file_path, + temp_input_color_space_name, temp_output_color_space_name); + + if ((m_input_color_space_name.asChar() != + temp_input_color_space_name.asChar()) || + (m_input_color_space_name.asChar() != + temp_input_color_space_name.asChar())) { + m_input_color_space_name = temp_input_color_space_name; + m_output_color_space_name = temp_output_color_space_name; + m_update_shader = true; + } +} + +inline MFloatMatrix create_saturation_matrix(const float saturation) { + // Luminance weights + // + // From Mozilla: + // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance + const float kLuminanceRed = 0.2126f; + const float kLuminanceGreen = 0.7152f; + const float kLuminanceBlue = 0.0722f; + + const float r_weight = kLuminanceRed; + const float g_weight = kLuminanceGreen; + const float b_weight = kLuminanceBlue; + + const float r1 = (1.0 - saturation) * r_weight + saturation; + const float r2 = (1.0 - saturation) * r_weight; + const float r3 = (1.0 - saturation) * r_weight; + + const float g1 = (1.0 - saturation) * g_weight; + const float g2 = (1.0 - saturation) * g_weight + saturation; + const float g3 = (1.0 - saturation) * g_weight; + + const float b1 = (1.0 - saturation) * b_weight; + const float b2 = (1.0 - saturation) * b_weight; + const float b3 = (1.0 - saturation) * b_weight + saturation; + + const float saturation_matrix_values[4][4] = { + // Column 0 + {r1, g1, b1, 0.0}, + // Column 1 + {r2, g2, b2, 0.0}, + // Column 2 + {r3, g3, b3, 0.0}, + // Column 3 + {0.0, 0.0, 0.0, 1.0}, + }; + + return MFloatMatrix(saturation_matrix_values); +} + +void ImagePlaneGeometry2Override::set_shader_instance_parameters( + MShaderInstance *shader, MHWRender::MTextureManager *textureManager, + const MColor &color_gain, const float color_exposure, + const float color_gamma, const float color_saturation, + const float color_soft_clip, const float alpha_gain, + const MColor &default_color, const bool ignore_alpha, const bool flip, + const bool flop, const bool is_transparent, + const ImageDisplayChannel image_display_channel, + const mmcore::FrameValue frame, const MString &file_path, + const MString &input_color_space_name, + const MString &output_color_space_name, + MHWRender::MTexture *out_color_texture, + const MHWRender::MSamplerState *out_texture_sampler) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); + + const float color[] = {color_gain[0], color_gain[1], color_gain[2], 1.0f}; + status = shader->setParameter("gColorGain", color); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorExposure", color_exposure); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorGamma", color_gamma); + CHECK_MSTATUS(status); + + MFloatMatrix saturation_matrix = create_saturation_matrix(color_saturation); + status = shader->setParameter("gColorSaturationMatrix", saturation_matrix); + CHECK_MSTATUS(status); + + status = shader->setParameter("gColorSoftClip", color_soft_clip); + CHECK_MSTATUS(status); + + status = shader->setParameter("gAlphaGain", alpha_gain); + CHECK_MSTATUS(status); + + const float temp_default_color[] = {default_color[0], default_color[1], + default_color[2], 1.0f}; + status = shader->setParameter("gFallbackColor", temp_default_color); + CHECK_MSTATUS(status); + + status = shader->setParameter("gFlip", flip); + CHECK_MSTATUS(status); + + status = shader->setParameter("gFlop", flop); + CHECK_MSTATUS(status); + + status = shader->setParameter("gIgnoreAlpha", m_ignore_alpha); + CHECK_MSTATUS(status); + + status = shader->setParameter("gDisplayChannel", + static_cast(image_display_channel)); + CHECK_MSTATUS(status); + + status = shader->setIsTransparent(is_transparent); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: shader->isTransparent()=" + << shader->isTransparent()); + CHECK_MSTATUS(status); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); + + rust::Str file_path_rust_str = rust::Str(file_path.asChar()); + rust::String expanded_file_path_rust_string = + mmcore::expand_file_path_string(file_path_rust_str, frame); + MString expanded_file_path(expanded_file_path_rust_string.data(), + expanded_file_path_rust_string.length()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: expanded_file_path=" + << expanded_file_path.asChar()); + + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: start out_color_texture=" << out_color_texture); + + if (!out_color_texture) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); + const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; + + // // TODO: using kFloat crashes. + // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; + + const bool do_texture_update = false; + ImageCache &image_cache = ImageCache::getInstance(); + out_color_texture = + read_image_file(textureManager, image_cache, m_temp_image, + expanded_file_path, pixel_type, do_texture_update); + + if (out_color_texture) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" + << out_color_texture->name().asChar()); + const void *resource_handle = out_color_texture->resourceHandle(); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->resourceHandle()=" + << resource_handle); + if (resource_handle) { + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: *texture->resourceHandle()=" + << *(uint32_t *)resource_handle); + } + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasAlpha()=" + << out_color_texture->hasAlpha()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasZeroAlpha()=" + << out_color_texture->hasZeroAlpha()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: texture->hasTransparentAlpha()=" + << out_color_texture->hasTransparentAlpha()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->bytesPerPixel()=" + << out_color_texture->bytesPerPixel()); + + MTextureDescription texture_desc; + out_color_texture->textureDescription(texture_desc); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fWidth=" + << texture_desc.fWidth); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fHeight=" + << texture_desc.fHeight); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fDepth=" + << texture_desc.fDepth); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerRow=" + << texture_desc.fBytesPerRow); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerSlice=" + << texture_desc.fBytesPerSlice); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fMipmaps=" + << texture_desc.fMipmaps); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fArraySlices=" + << texture_desc.fArraySlices); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fFormat=" + << texture_desc.fFormat); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fTextureType=" + << texture_desc.fTextureType); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fEnvMapType=" + << texture_desc.fEnvMapType); + } + } + + if (!out_texture_sampler) { + MHWRender::MSamplerStateDesc sampler_desc; + sampler_desc.addressU = MHWRender::MSamplerState::kTexWrap; + sampler_desc.addressV = MHWRender::MSamplerState::kTexWrap; + sampler_desc.addressW = MHWRender::MSamplerState::kTexWrap; + // kMinMagMipPoint is "nearest pixel" filtering. + sampler_desc.filter = MHWRender::MSamplerState::kMinMagMipPoint; + out_texture_sampler = + MHWRender::MStateManager::acquireSamplerState(sampler_desc); + } + + if (out_texture_sampler) { + status = + shader->setParameter("gImageTextureSampler", *out_texture_sampler); + CHECK_MSTATUS(status); + } else { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get texture sampler." + << " out_texture_sampler=" << out_texture_sampler); + } + + if (out_color_texture) { + MHWRender::MTextureAssignment texture_assignment; + texture_assignment.texture = out_color_texture; + status = shader->setParameter("gImageTexture", texture_assignment); + CHECK_MSTATUS(status); + + out_color_texture = nullptr; + } else { + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: Could not get color texture; " + "did not assign texture." + << " out_color_texture=" << out_color_texture); + } + + return; +} + +void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, + MRenderItemList &list) { + const bool verbose = false; + if (!m_geometry_node_path.isValid()) { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "Geometry node DAG path is not valid."); + return; + } + + MHWRender::MRenderer *renderer = MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MRenderer."); + return; + } + + const MHWRender::MShaderManager *shaderManager = + renderer->getShaderManager(); + if (!shaderManager) { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MShaderManager."); + return; + } + + if (m_geometry_node_type != MFn::kMesh) { + MMSOLVER_MAYA_WRN("mmImagePlaneShape: " + << "Only Meshes are supported, geometry node " + "given is not a mesh."); + return; + } + + bool draw_wireframe = false; + int index = 0; + + MRenderItem *wireframeItem = nullptr; + if (draw_wireframe) { + // Add render item for drawing wireframe on the mesh + index = list.indexOf(renderItemName_imagePlaneWireframe); + if (index >= 0) { + wireframeItem = list.itemAt(index); + } else { + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: Generate wireframe MRenderItem..."); + wireframeItem = MRenderItem::Create( + renderItemName_imagePlaneWireframe, MRenderItem::DecorationItem, + MGeometry::kLines); + + auto draw_mode = MGeometry::kAll; // Draw in all visible modes. + auto depth_priority = MRenderItem::sActiveWireDepthPriority; + + wireframeItem->setDrawMode(draw_mode); + wireframeItem->depthPriority(depth_priority); + + list.append(wireframeItem); + } + } + + // Add render item for drawing shaded on the mesh + MRenderItem *shadedItem = nullptr; + index = list.indexOf(renderItemName_imagePlaneShaded); + if (index >= 0) { + shadedItem = list.itemAt(index); + } else { + MMSOLVER_MAYA_VRB("mmImagePlaneShape: Generate shaded MRenderItem..."); + shadedItem = MRenderItem::Create(renderItemName_imagePlaneShaded, + MRenderItem::NonMaterialSceneItem, + MGeometry::kTriangles); + + auto draw_mode = MGeometry::kAll; // Draw in all visible modes. + auto depth_priority = MRenderItem::sDormantWireDepthPriority; + + shadedItem->setDrawMode(draw_mode); + shadedItem->depthPriority(depth_priority); + + list.append(shadedItem); + } + + if (wireframeItem) { + wireframeItem->enable(m_visible); + + MShaderInstance *shader = + shaderManager->getStockShader(MShaderManager::k3dSolidShader); + if (shader) { + static const float color[] = {1.0f, 0.0f, 0.0f, 1.0f}; + MStatus status = shader->setParameter("solidColor", color); + CHECK_MSTATUS(status); + wireframeItem->setShader(shader); + shaderManager->releaseShader(shader); + } + } + + if (shadedItem) { + shadedItem->enable(m_visible); + + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isEnabled()=" + << shadedItem->isEnabled()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isShaderFromNode()=" + << shadedItem->isShaderFromNode()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isMultiDraw()=" + << shadedItem->isMultiDraw()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->isConsolidated()=" + << shadedItem->isConsolidated()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->wantConsolidation()=" + << shadedItem->wantConsolidation()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->castsShadows()=" + << shadedItem->castsShadows()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->receivesShadows()=" + << shadedItem->receivesShadows()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->excludedFromPostEffects()=" + << shadedItem->excludedFromPostEffects()); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + << "shadedItem->supportsAdvancedTransparency()=" + << shadedItem->supportsAdvancedTransparency()); + + if (!m_shader || m_update_shader) { + if (m_shader) { + shaderManager->releaseShader(m_shader); + } + + const MString shader_file_path = + mmsolver::render::find_shader_file_path("mmImagePlane.ogsfx"); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: found shader_file_path=\"" + << shader_file_path << "\""); + + if (shader_file_path.length() > 0) { + MString shader_text = + mmsolver::render::read_shader_file(shader_file_path); + + std::string ocio_shader_text; + mmcolorio::generate_shader_text( + m_input_color_space_name.asChar(), + m_output_color_space_name.asChar(), ocio_shader_text); + MMSOLVER_MAYA_VRB("mmImagePlaneShape: ocio_shader_text=\"" + << ocio_shader_text << "\""); + if (ocio_shader_text.size() > 0) { + const MString ocio_function_declare_text = MString( + "vec4 OCIODisplay(vec4 passthrough) { return " + "passthrough; " + "}"); + MStatus status = shader_text.substitute( + ocio_function_declare_text, + MString(ocio_shader_text.c_str())); + CHECK_MSTATUS(status); + } + + m_shader = + mmsolver::render::compile_shader_text(shader_text, "Main"); + } + + if (m_shader) { + m_update_shader = false; + } + } + + if (m_shader) { + MHWRender::MTextureManager *textureManager = + renderer->getTextureManager(); + if (!textureManager) { + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: Could not get MTextureManager."); + return; + } + + set_shader_instance_parameters( + m_shader, textureManager, m_color_gain, m_color_exposure, + m_color_gamma, m_color_saturation, m_color_soft_clip, + m_alpha_gain, m_default_color, m_ignore_alpha, m_flip, m_flop, + m_is_transparent, m_image_display_channel, m_frame, m_file_path, + m_input_color_space_name, m_output_color_space_name, + m_color_texture, m_texture_sampler); + + shadedItem->setShader(m_shader); + } + } +} + +void ImagePlaneGeometry2Override::populateGeometry( + const MGeometryRequirements &requirements, + const MRenderItemList &renderItems, MGeometry &data) { + const bool verbose = false; + if (!m_geometry_node_path.isValid()) { + MMSOLVER_MAYA_VRB( + "mmImagePlaneShape: Geometry node DAG path is not valid."); + return; + } + + MStatus status; + + // kPolyGeom_Normal = Normal Indicates the polygon performs the + // default geometry. + // + // kPolyGeom_NotSharing = NotSharing Indicates if you don't want + // vertex sharing to be computed by the extractor. Vertex buffer + // size will not be reduced if sharing can be performed. + // + // kPolyGeom_BaseMesh = BaseMesh Indicates if you want the base + // geometry in smoothCage mode. The geometry in extractor is + // always the base geometry in normal mode. + MHWRender::MPolyGeomOptions polygon_geometry_options = + MHWRender::kPolyGeom_Normal | MHWRender::kPolyGeom_BaseMesh; + + MGeometryExtractor extractor(requirements, m_geometry_node_path, + polygon_geometry_options, &status); + if (status == MS::kFailure) { + CHECK_MSTATUS(status); + return; + } + + const MVertexBufferDescriptorList &descList = + requirements.vertexRequirements(); + for (int reqNum = 0; reqNum < descList.length(); ++reqNum) { + MVertexBufferDescriptor desc; + if (!descList.getDescriptor(reqNum, desc)) { + continue; + } + + auto desc_semantic = desc.semantic(); + if ((desc_semantic == MGeometry::kPosition) || + (desc_semantic == MGeometry::kNormal) || + (desc_semantic == MGeometry::kTexture) || + (desc_semantic == MGeometry::kTangent) || + (desc_semantic == MGeometry::kBitangent) || + (desc_semantic == MGeometry::kColor)) { + MVertexBuffer *vertexBuffer = data.createVertexBuffer(desc); + if (vertexBuffer) { + uint32_t vertexCount = extractor.vertexCount(); + bool writeOnly = + true; // We don't need the current buffer values. + float *data = static_cast( + vertexBuffer->acquire(vertexCount, writeOnly)); + if (data) { + status = + extractor.populateVertexBuffer(data, vertexCount, desc); + if (status == MS::kFailure) return; + vertexBuffer->commit(data); + } + } + } + } + + for (int i = 0; i < renderItems.length(); ++i) { + const MRenderItem *item = renderItems.itemAt(i); + if (!item) { + continue; + } + + MIndexBuffer *indexBuffer = + data.createIndexBuffer(MGeometry::kUnsignedInt32); + if (!indexBuffer) { + continue; + } + + if (item->primitive() == MGeometry::kTriangles) { + MIndexBufferDescriptor triangleDesc( + MIndexBufferDescriptor::kTriangle, MString(), + MGeometry::kTriangles, 3); + uint32_t numTriangles = extractor.primitiveCount(triangleDesc); + bool writeOnly = true; // We don't need the current buffer values. + uint32_t *indices = static_cast( + indexBuffer->acquire(3 * numTriangles, writeOnly)); + + status = extractor.populateIndexBuffer(indices, numTriangles, + triangleDesc); + if (status == MS::kFailure) { + return; + } + indexBuffer->commit(indices); + } else if (item->primitive() == MGeometry::kLines) { + MIndexBufferDescriptor edgeDesc(MIndexBufferDescriptor::kEdgeLine, + MString(), MGeometry::kLines, 2); + uint32_t numEdges = extractor.primitiveCount(edgeDesc); + bool writeOnly = true; // We don't need the current buffer values. + uint32_t *indices = static_cast( + indexBuffer->acquire(2 * numEdges, writeOnly)); + + status = extractor.populateIndexBuffer(indices, numEdges, edgeDesc); + if (status == MS::kFailure) { + return; + } + indexBuffer->commit(indices); + } + + item->associateWithIndexBuffer(indexBuffer); + } +} + +void ImagePlaneGeometry2Override::cleanUp() {} + +#if MAYA_API_VERSION >= 20190000 +bool ImagePlaneGeometry2Override::requiresGeometryUpdate() const { + const bool verbose = false; + if (m_geometry_node_path.isValid()) { + MMSOLVER_MAYA_VRB( + "ImagePlaneGeometry2Override::requiresGeometryUpdate: false"); + return false; + } + MMSOLVER_MAYA_VRB( + "ImagePlaneGeometry2Override::requiresGeometryUpdate: true"); + return true; +} + +bool ImagePlaneGeometry2Override::requiresUpdateRenderItems( + const MDagPath &path) const { + const bool verbose = false; + MMSOLVER_MAYA_VRB( + "ImagePlaneGeometry2Override::requiresUpdateRenderItems: true: " + << path.fullPathName().asChar()); + return true; // Always update the render items. +} +#endif + +bool ImagePlaneGeometry2Override::hasUIDrawables() const { return true; } + +void ImagePlaneGeometry2Override::addUIDrawables( + const MDagPath &path, MUIDrawManager &drawManager, + const MFrameContext &frameContext) { + if (!m_draw_hud) { + return; + } + + const float pos_coord_x = 0.48f; + const float pos_coord_y = 0.52f; + const MColor text_color = MColor(1.0f, 0.0f, 0.0f); + const uint32_t font_size = 12; + const int *background_size = nullptr; + const MColor *background_color = nullptr; + auto dynamic = false; + + if (m_draw_image_size) { + auto text_position = MPoint(pos_coord_x, pos_coord_y, 0.0); + auto font_alignment = MUIDrawManager::kRight; + + drawManager.beginDrawable(); + drawManager.setColor(text_color); + drawManager.setFontSize(font_size); + drawManager.text(text_position, m_image_size, font_alignment, + background_size, background_color, dynamic); + drawManager.endDrawable(); + } + + if (m_draw_camera_size) { + auto text_position = MPoint(-pos_coord_x, pos_coord_y, 0.0); + auto font_alignment = MUIDrawManager::kLeft; + + drawManager.beginDrawable(); + drawManager.setColor(text_color); + drawManager.setFontSize(font_size); + drawManager.text(text_position, m_camera_size, font_alignment, + background_size, background_color, dynamic); + drawManager.endDrawable(); + } +} + +} // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h new file mode 100644 index 000000000..f0a805df6 --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_IMAGE_PLANE_GEOMETRY_2_OVERRIDE_H +#define MM_IMAGE_PLANE_GEOMETRY_2_OVERRIDE_H + +// Maya +#include +#include +#include +#include +#include +#include +#include + +// Maya Viewport 2.0 +#include +#include +#include +#include +#include + +// MM Solver +#include + +#include "ImageCache.h" +#include "ImagePlaneShape2Node.h" +#include "ImagePlaneUtils.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { + +class ShaderLinkLostUserData2 : public MUserData { +public: + ShaderLinkLostUserData2() + : MUserData(), link_lost_count(0), set_shader_count(0) {} + + // Keep track of the number of times stuff happens, just for + // interest sake (maybe to help debugging?) - doesn't really mean + // or do anything special. + uint32_t link_lost_count; + uint32_t set_shader_count; +}; + +using ShaderLinkLostUserData2Ptr = MSharedPtr; + +class ImagePlaneGeometry2Override : public MPxGeometryOverride { +public: + static MPxGeometryOverride *Creator(const MObject &obj) { + return new ImagePlaneGeometry2Override(obj); + } + + ~ImagePlaneGeometry2Override() override; + + MHWRender::DrawAPI supportedDrawAPIs() const override; + + bool hasUIDrawables() const override; + + void updateDG() override; + + void updateRenderItems(const MDagPath &path, + MRenderItemList &list) override; + + void addUIDrawables(const MDagPath &path, MUIDrawManager &drawManager, + const MFrameContext &frameContext) override; + + void populateGeometry(const MGeometryRequirements &requirements, + const MRenderItemList &renderItems, + MGeometry &data) override; + + void cleanUp() override; + +#if MAYA_API_VERSION >= 20190000 + bool requiresGeometryUpdate() const override; + bool requiresUpdateRenderItems(const MDagPath &path) const; +#endif + + bool traceCallSequence() const override { + // Return true if internal tracing is desired. + return false; + } + + void handleTraceMessage(const MString &message) const override { + MGlobal::displayInfo("ImagePlaneGeometry2Override: " + message); + MMSOLVER_MAYA_INFO("ImagePlaneGeometry2Override: " << message.asChar()); + } + +protected: + ImagePlaneGeometry2Override(const MObject &obj); + + static void on_model_editor_changed_func(void *clientData); + static void shader_link_lost_func(ShaderLinkLostUserData2 *userData); + + void query_node_attributes( + MObject &node, MDagPath &out_camera_node_path, bool &out_visible, + bool &out_visible_to_camera_only, bool &out_is_under_camera, + bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, + bool &out_draw_camera_size, MString &out_camera_size, + ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, + float &out_color_exposure, float &out_color_gamma, + float &out_color_saturation, float &out_color_soft_clip, + float &out_alpha_gain, MColor &out_default_color, + bool &out_ignore_alpha, bool &out_flip, bool &out_flop, + bool &out_is_transparent, mmcore::FrameValue &out_frame, + MString &out_file_path, MString &out_input_color_space_name, + MString &out_output_color_space_name); + + void set_shader_instance_parameters( + MShaderInstance *shader, MHWRender::MTextureManager *textureManager, + const MColor &color_gain, const float color_exposure, + const float color_gamma, const float color_saturation, + const float color_soft_clip, const float alpha_gain, + const MColor &default_color, const bool ignore_alpha, const bool flip, + const bool flop, const bool is_transparent, + const ImageDisplayChannel image_display_channel, + const mmcore::FrameValue frame, const MString &file_path, + const MString &input_color_space_name, + const MString &output_color_space_name, + MHWRender::MTexture *out_color_texture, + const MHWRender::MSamplerState *out_texture_sampler); + + MObject m_this_node; + MDagPath m_geometry_node_path; + MDagPath m_camera_node_path; + MFn::Type m_geometry_node_type; + MFn::Type m_camera_node_type; + + bool m_visible; + bool m_visible_to_camera_only; + bool m_is_under_camera; + bool m_draw_hud; + bool m_draw_image_size; + bool m_draw_camera_size; + bool m_update_shader; + MString m_image_size; + MString m_camera_size; + MCallbackId m_model_editor_changed_callback_id; + + // Shader attributes. + MShaderInstance *m_shader; + ImageDisplayChannel m_image_display_channel; + MColor m_color_gain; + float m_alpha_gain; + float m_color_exposure; + float m_color_gamma; + float m_color_saturation; + float m_color_soft_clip; + MColor m_default_color; + bool m_ignore_alpha; + bool m_flip; + bool m_flop; + bool m_is_transparent; + mmcore::FrameValue m_frame; + MString m_file_path; + MString m_input_color_space_name; + MString m_output_color_space_name; + + // Texture caching + MImage m_temp_image; + MHWRender::MTexture *m_color_texture; + const MHWRender::MSamplerState *m_texture_sampler; + +#if MAYA_API_VERSION >= 20200000 + ShaderLinkLostUserData2Ptr m_shader_link_lost_user_data_ptr; +#elif + ShaderLinkLostUserData2 m_shader_link_lost_user_data; +#endif +}; + +} // namespace mmsolver + +#endif // MM_IMAGE_PLANE_GEOMETRY_2_OVERRIDE_H diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index 59470bcf7..e6e79874f 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022, 2024 David Cattermole. + * Copyright (C) 2022 David Cattermole. * * This file is part of mmSolver. * @@ -25,17 +25,11 @@ #define _USE_MATH_DEFINES #include -// STL -#include -#include -#include - // Maya #include #include #include #include -#include #include #include #include @@ -47,17 +41,12 @@ #include #include #include -#include #include // MM Solver -#include -#include - -#include "ImageCache.h" +#include "ImagePlaneShapeNode.h" +#include "ImagePlaneUtils.h" #include "mmSolver/mayahelper/maya_utils.h" -#include "mmSolver/render/shader/shader_utils.h" -#include "mmSolver/shape/constant_texture_data.h" #include "mmSolver/utilities/number_utils.h" namespace mmsolver { @@ -73,36 +62,9 @@ ImagePlaneGeometryOverride::ImagePlaneGeometryOverride(const MObject &obj) , m_draw_hud(false) , m_draw_image_size(false) , m_draw_camera_size(false) - , m_geometry_node_type(MFn::kInvalid) - , m_image_display_channel(ImageDisplayChannel::kAll) - , m_color_gain(1.0, 1.0, 1.0, 1.0) - , m_color_exposure(0.0f) - , m_color_gamma(1.0f) - , m_color_saturation(1.0f) - , m_color_soft_clip(0.0f) - , m_alpha_gain(1.0f) - , m_default_color(0.3, 0.0, 0.0, 1.0) - , m_ignore_alpha(false) - , m_flip(false) - , m_flop(false) - , m_is_transparent(false) - , m_frame(0) - , m_file_path() - , m_input_color_space_name() - , m_output_color_space_name() - , m_shader(nullptr) - , m_update_shader(false) - , m_color_texture(nullptr) - , m_texture_sampler(nullptr) { + , m_geometry_node_type(MFn::kInvalid) { m_model_editor_changed_callback_id = MEventMessage::addEventCallback( - "modelEditorChanged", - ImagePlaneGeometryOverride::on_model_editor_changed_func, this); -#if MAYA_API_VERSION >= 20200000 - m_shader_link_lost_user_data_ptr = - ShaderLinkLostUserDataPtr(new ShaderLinkLostUserData()); -#elif - m_shader_link_lost_user_data = ShaderLinkLostUserData(); -#endif + "modelEditorChanged", on_model_editor_changed_func, this); } ImagePlaneGeometryOverride::~ImagePlaneGeometryOverride() { @@ -110,30 +72,6 @@ ImagePlaneGeometryOverride::~ImagePlaneGeometryOverride() { MMessage::removeCallback(m_model_editor_changed_callback_id); m_model_editor_changed_callback_id = 0; } - - if (m_color_texture) { - MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); - MHWRender::MTextureManager *textureMgr = - renderer ? renderer->getTextureManager() : nullptr; - if (textureMgr) { - textureMgr->releaseTexture(m_color_texture); - m_color_texture = nullptr; - } - } - - if (m_texture_sampler) { - MHWRender::MStateManager::releaseSamplerState(m_texture_sampler); - m_texture_sampler = nullptr; - } - - if (m_shader) { - MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); - const MHWRender::MShaderManager *shaderManager = - renderer ? renderer->getShaderManager() : nullptr; - if (shaderManager) { - shaderManager->releaseShader(m_shader); - } - } } void ImagePlaneGeometryOverride::on_model_editor_changed_func( @@ -147,358 +85,26 @@ void ImagePlaneGeometryOverride::on_model_editor_changed_func( } } -void ImagePlaneGeometryOverride::shader_link_lost_func( - ShaderLinkLostUserData *userData) { - // TODO: What should this function do? Does it need to do anything? - MMSOLVER_MAYA_DBG( - "mmImagePlaneShape: shader_link_lost_func: link_lost_count=" - << (*userData).link_lost_count - << " set_shader_count=" << (*userData).set_shader_count); - (*userData).link_lost_count += 1; -} - MHWRender::DrawAPI ImagePlaneGeometryOverride::supportedDrawAPIs() const { - // TODO: We cannot support DirectX, unless we also write another - // mmImagePlane for DirectX. Legacy OpenGL is also unsupported. - return MHWRender::kOpenGLCoreProfile; + return (MHWRender::kOpenGL | MHWRender::kDirectX11 | + MHWRender::kOpenGLCoreProfile); } -bool getUpstreamNodeFromConnection(const MObject &this_node, - const MString &attr_name, - MPlugArray &out_connections) { - MStatus status; - MFnDependencyNode mfn_depend_node(this_node); - - bool wantNetworkedPlug = true; - MPlug plug = - mfn_depend_node.findPlug(attr_name, wantNetworkedPlug, &status); - if (status != MStatus::kSuccess) { - CHECK_MSTATUS(status); - return false; - } - if (plug.isNull()) { - MMSOLVER_MAYA_WRN("Could not get plug for \"" - << mfn_depend_node.name().asChar() << "." - << attr_name.asChar() << "\" node."); - return false; - } - - bool as_destination = true; - bool as_source = false; - // Ask for plugs connecting to this node's ".shaderNode" - // attribute. - plug.connectedTo(out_connections, as_destination, as_source, &status); - if (status != MStatus::kSuccess) { - CHECK_MSTATUS(status); - return false; - } - if (out_connections.length() == 0) { - MMSOLVER_MAYA_WRN("No connections to the \"" - << mfn_depend_node.name().asChar() << "." - << attr_name.asChar() << "\" attribute."); - return false; - } - return true; -} - -void calculate_node_image_size_string(MDagPath &objPath, - const uint32_t int_precision, - const uint32_t double_precision, - bool &out_draw_image_size, - MString &out_image_size) { - MStatus status = MS::kSuccess; - - double width = 1.0; - double height = 1.0; - double pixel_aspect = 1.0; - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_image_size, - out_draw_image_size); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_width, width); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_height, height); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_pixel_aspect, - pixel_aspect); - CHECK_MSTATUS(status); - - double aspect = (width * pixel_aspect) / height; - - MString width_string; - MString height_string; - MString pixel_aspect_string; - MString aspect_string; - - width_string.set(width, int_precision); - height_string.set(height, int_precision); - pixel_aspect_string.set(pixel_aspect, double_precision); - aspect_string.set(aspect, double_precision); - - out_image_size = MString("Image: ") + width_string + MString(" x ") + - height_string + MString(" | PAR ") + pixel_aspect_string + - MString(" | ") + aspect_string; - return; -} - -void calculate_node_camera_size_string(MDagPath &objPath, - const uint32_t double_precision, - bool &out_draw_camera_size, - MString &out_camera_size) { - MStatus status = MS::kSuccess; - - double width = 0.0; - double height = 0.0; - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_camera_size, - out_draw_camera_size); - CHECK_MSTATUS(status); - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_camera_width_inch, width); - CHECK_MSTATUS(status); - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_camera_height_inch, height); - CHECK_MSTATUS(status); - - double aspect = width / height; - - MString width_string; - MString height_string; - MString aspect_string; - - width_string.set(width * INCH_TO_MM, double_precision); - height_string.set(height * INCH_TO_MM, double_precision); - aspect_string.set(aspect, double_precision); - - out_camera_size = MString("Camera: ") + width_string + MString("mm x ") + - height_string + MString("mm | ") + aspect_string; -} - -void ImagePlaneGeometryOverride::query_node_attributes( - MObject &node, MDagPath &out_camera_node_path, bool &out_visible, - bool &out_visible_to_camera_only, bool &out_is_under_camera, - bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, - bool &out_draw_camera_size, MString &out_camera_size, - ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, - float &out_color_exposure, float &out_color_gamma, - float &out_color_saturation, float &out_color_soft_clip, - float &out_alpha_gain, MColor &out_default_color, bool &out_ignore_alpha, - bool &out_flip, bool &out_flop, bool &out_is_transparent, - mmcore::FrameValue &out_frame, MString &out_file_path, - MString &out_input_color_space_name, MString &out_output_color_space_name) { - const bool verbose = false; - - MDagPath objPath; - MDagPath::getAPathTo(node, objPath); - - if (!objPath.isValid()) { - return; - } - MStatus status; - - auto frame_context = MPxGeometryOverride::getFrameContext(); - MDagPath camera_node_path = frame_context->getCurrentCameraPath(&status); - CHECK_MSTATUS(status); - - // By default the draw is visible, unless overridden by - // out_visible_to_camera_only or out_is_under_camera. - out_visible = true; - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_visible_to_camera_only, - out_visible_to_camera_only); - CHECK_MSTATUS(status); - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_hud, out_draw_hud); - CHECK_MSTATUS(status); - - out_is_under_camera = true; - if (camera_node_path.isValid() && out_camera_node_path.isValid()) { - // Using an explicit camera node path to compare - // against ensures that if a rouge camera is parented - // under the attached camera, the node will be - // invisible. - out_is_under_camera = out_camera_node_path == camera_node_path; - } - - if (!out_is_under_camera) { - if (out_visible_to_camera_only) { - out_visible = false; - } - // Do not draw the HUD if we are not under the camera, - // the HUD must only be visible from the point of view - // of the intended camera, otherwise it will look - // wrong. - out_draw_hud = false; - } - - const auto int_precision = 0; - const auto double_precision = 3; - calculate_node_image_size_string(objPath, int_precision, double_precision, - out_draw_image_size, out_image_size); - calculate_node_camera_size_string(objPath, double_precision, - out_draw_camera_size, out_camera_size); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_gain, - out_color_gain); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_exposure, - out_color_exposure); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_gamma, - out_color_gamma); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_saturation, - out_color_saturation); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_color_soft_clip, - out_color_soft_clip); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_alpha_gain, - out_alpha_gain); - CHECK_MSTATUS(status); - - short image_display_channel_value = 0; - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_display_channel, - image_display_channel_value); - CHECK_MSTATUS(status); - out_image_display_channel = - static_cast(image_display_channel_value); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_ignore_alpha, - out_ignore_alpha); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_flip, out_flip); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_flop, out_flop); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_shader_is_transparent, - out_is_transparent); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_frame_number, - out_frame); - CHECK_MSTATUS(status); - - status = getNodeAttr(objPath, ImagePlaneShapeNode::m_image_file_path, - out_file_path); - CHECK_MSTATUS(status); - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_image_input_color_space, - out_input_color_space_name); - CHECK_MSTATUS(status); - - status = - getNodeAttr(objPath, ImagePlaneShapeNode::m_image_output_color_space, - out_output_color_space_name); - CHECK_MSTATUS(status); - - // Find the input/output file color spaces. - // - // TODO: Do not re-calculate this each update. Compute once and - // cache the results. - const char *file_color_space_name = - mmcolorio::guess_color_space_name_from_file_path( - out_file_path.asChar()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" - << " file_color_space_name=\"" << file_color_space_name - << "\"."); - - const char *output_color_space_name = mmcolorio::get_role_color_space_name( - mmcolorio::ColorSpaceRole::kSceneLinear); - out_output_color_space_name = MString(output_color_space_name); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" - << " out_output_color_space_name=\"" - << out_output_color_space_name.asChar() << "\"."); -} - -void find_geometry_node_path(MObject &node, MString &attr_name, - MDagPath &out_geometry_node_path, - MFn::Type &out_geometry_node_type) { - const auto verbose = false; - - MPlugArray connections; - bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); - if (!ok) { - return; - } - - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kMesh)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - out_geometry_node_path = path; - out_geometry_node_type = path.apiType(); - MMSOLVER_MAYA_VRB("Validated geometry node: " - << " path=" - << out_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN("Geometry node is not correct type:" - << " path=" - << out_geometry_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } - } -} - -void find_camera_node_path(MObject &node, MString &attr_name, - MDagPath &out_camera_node_path, - MFn::Type &out_camera_node_type) { +void ImagePlaneGeometryOverride::updateDG() { const auto verbose = false; - MPlugArray connections; - bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); - if (!ok) { - return; - } - - for (uint32_t i = 0; i < connections.length(); ++i) { - MObject node = connections[i].node(); - - if (node.hasFn(MFn::kCamera)) { - MDagPath path; - MDagPath::getAPathTo(node, path); - out_camera_node_path = path; - out_camera_node_type = path.apiType(); - MMSOLVER_MAYA_VRB("Validated camera node: " - << " path=" - << out_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - break; - } else { - MMSOLVER_MAYA_WRN("Camera node is not correct type:" - << " path=" - << out_camera_node_path.fullPathName().asChar() - << " type=" << node.apiTypeStr()); - } - } -} - -void ImagePlaneGeometryOverride::updateDG() { if (!m_geometry_node_path.isValid()) { MString attr_name = "geometryNode"; find_geometry_node_path(m_this_node, attr_name, m_geometry_node_path, m_geometry_node_type); } + if (m_shader_node.isNull()) { + MString attr_name = "shaderNode"; + find_shader_node_path(m_this_node, attr_name, m_shader_node, + m_shader_node_type); + } + if (!m_camera_node_path.isValid()) { MString attr_name = "cameraNode"; find_camera_node_path(m_this_node, attr_name, m_camera_node_path, @@ -506,235 +112,68 @@ void ImagePlaneGeometryOverride::updateDG() { } // Query Attributes from the base node. - MString temp_input_color_space_name = ""; - MString temp_output_color_space_name = ""; - ImagePlaneGeometryOverride::query_node_attributes( - m_this_node, m_camera_node_path, m_visible, m_visible_to_camera_only, - m_is_under_camera, m_draw_hud, m_draw_image_size, m_image_size, - m_draw_camera_size, m_camera_size, m_image_display_channel, - m_color_gain, m_color_exposure, m_color_gamma, m_color_saturation, - m_color_soft_clip, m_alpha_gain, m_default_color, m_ignore_alpha, - m_flip, m_flop, m_is_transparent, m_frame, m_file_path, - temp_input_color_space_name, temp_output_color_space_name); - - if ((m_input_color_space_name.asChar() != - temp_input_color_space_name.asChar()) || - (m_input_color_space_name.asChar() != - temp_input_color_space_name.asChar())) { - m_input_color_space_name = temp_input_color_space_name; - m_output_color_space_name = temp_output_color_space_name; - m_update_shader = true; - } -} - -inline MFloatMatrix create_saturation_matrix(const float saturation) { - // Luminance weights - // - // From Mozilla: - // https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_Colors_and_Luminance - const float kLuminanceRed = 0.2126f; - const float kLuminanceGreen = 0.7152f; - const float kLuminanceBlue = 0.0722f; - - const float r_weight = kLuminanceRed; - const float g_weight = kLuminanceGreen; - const float b_weight = kLuminanceBlue; - - const float r1 = (1.0 - saturation) * r_weight + saturation; - const float r2 = (1.0 - saturation) * r_weight; - const float r3 = (1.0 - saturation) * r_weight; - - const float g1 = (1.0 - saturation) * g_weight; - const float g2 = (1.0 - saturation) * g_weight + saturation; - const float g3 = (1.0 - saturation) * g_weight; - - const float b1 = (1.0 - saturation) * b_weight; - const float b2 = (1.0 - saturation) * b_weight; - const float b3 = (1.0 - saturation) * b_weight + saturation; - - const float saturation_matrix_values[4][4] = { - // Column 0 - {r1, g1, b1, 0.0}, - // Column 1 - {r2, g2, b2, 0.0}, - // Column 2 - {r3, g3, b3, 0.0}, - // Column 3 - {0.0, 0.0, 0.0, 1.0}, - }; - - return MFloatMatrix(saturation_matrix_values); -} - -void ImagePlaneGeometryOverride::set_shader_instance_parameters( - MShaderInstance *shader, MHWRender::MTextureManager *textureManager, - const MColor &color_gain, const float color_exposure, - const float color_gamma, const float color_saturation, - const float color_soft_clip, const float alpha_gain, - const MColor &default_color, const bool ignore_alpha, const bool flip, - const bool flop, const bool is_transparent, - const ImageDisplayChannel image_display_channel, - const mmcore::FrameValue frame, const MString &file_path, - const MString &input_color_space_name, - const MString &output_color_space_name, - MHWRender::MTexture *out_color_texture, - const MHWRender::MSamplerState *out_texture_sampler) { - MStatus status = MStatus::kSuccess; - const bool verbose = false; - MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); - - const float color[] = {color_gain[0], color_gain[1], color_gain[2], 1.0f}; - status = shader->setParameter("gColorGain", color); - CHECK_MSTATUS(status); - - status = shader->setParameter("gColorExposure", color_exposure); - CHECK_MSTATUS(status); - - status = shader->setParameter("gColorGamma", color_gamma); - CHECK_MSTATUS(status); - - MFloatMatrix saturation_matrix = create_saturation_matrix(color_saturation); - status = shader->setParameter("gColorSaturationMatrix", saturation_matrix); - CHECK_MSTATUS(status); - - status = shader->setParameter("gColorSoftClip", color_soft_clip); - CHECK_MSTATUS(status); - - status = shader->setParameter("gAlphaGain", alpha_gain); - CHECK_MSTATUS(status); + { + MDagPath objPath; + MDagPath::getAPathTo(m_this_node, objPath); - const float temp_default_color[] = {default_color[0], default_color[1], - default_color[2], 1.0f}; - status = shader->setParameter("gFallbackColor", temp_default_color); - CHECK_MSTATUS(status); + if (objPath.isValid()) { + MStatus status; - status = shader->setParameter("gFlip", flip); - CHECK_MSTATUS(status); - - status = shader->setParameter("gFlop", flop); - CHECK_MSTATUS(status); - - status = shader->setParameter("gIgnoreAlpha", m_ignore_alpha); - CHECK_MSTATUS(status); + auto frame_context = getFrameContext(); + MDagPath camera_node_path = + frame_context->getCurrentCameraPath(&status); + CHECK_MSTATUS(status); - status = shader->setParameter("gDisplayChannel", - static_cast(image_display_channel)); - CHECK_MSTATUS(status); + // By default the draw is visible, unless overridden by + // m_visible_to_camera_only or m_is_under_camera. + m_visible = true; - status = shader->setIsTransparent(is_transparent); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: shader->isTransparent()=" - << shader->isTransparent()); - CHECK_MSTATUS(status); + status = getNodeAttr(objPath, + ImagePlaneShapeNode::m_visible_to_camera_only, + m_visible_to_camera_only); + CHECK_MSTATUS(status); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); + status = getNodeAttr(objPath, ImagePlaneShapeNode::m_draw_hud, + m_draw_hud); + CHECK_MSTATUS(status); - rust::Str file_path_rust_str = rust::Str(file_path.asChar()); - rust::String expanded_file_path_rust_string = - mmcore::expand_file_path_string(file_path_rust_str, frame); - MString expanded_file_path(expanded_file_path_rust_string.data(), - expanded_file_path_rust_string.length()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: expanded_file_path=" - << expanded_file_path.asChar()); + m_is_under_camera = true; + if (camera_node_path.isValid() && m_camera_node_path.isValid()) { + // Using an explicit camera node path to compare + // against ensures that if a rouge camera is parented + // under the attached camera, the node will be + // invisible. + m_is_under_camera = m_camera_node_path == camera_node_path; + } - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: start out_color_texture=" << out_color_texture); - - if (!out_color_texture) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); - const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; - - // // TODO: using kFloat crashes. - // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; - - const bool do_texture_update = false; - ImageCache &image_cache = ImageCache::getInstance(); - out_color_texture = - read_image_file(textureManager, image_cache, m_temp_image, - expanded_file_path, pixel_type, do_texture_update); - - if (out_color_texture) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" - << out_color_texture->name().asChar()); - const void *resource_handle = out_color_texture->resourceHandle(); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->resourceHandle()=" - << resource_handle); - if (resource_handle) { - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: *texture->resourceHandle()=" - << *(uint32_t *)resource_handle); + if (!m_is_under_camera) { + if (m_visible_to_camera_only) { + m_visible = false; + } + // Do not draw the HUD if we are not under the camera, + // the HUD must only be visible from the point of view + // of the intended camera, otherwise it will look + // wrong. + m_draw_hud = false; } - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasAlpha()=" - << out_color_texture->hasAlpha()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasZeroAlpha()=" - << out_color_texture->hasZeroAlpha()); - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: texture->hasTransparentAlpha()=" - << out_color_texture->hasTransparentAlpha()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->bytesPerPixel()=" - << out_color_texture->bytesPerPixel()); - - MTextureDescription texture_desc; - out_color_texture->textureDescription(texture_desc); - - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fWidth=" - << texture_desc.fWidth); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fHeight=" - << texture_desc.fHeight); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fDepth=" - << texture_desc.fDepth); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerRow=" - << texture_desc.fBytesPerRow); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerSlice=" - << texture_desc.fBytesPerSlice); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fMipmaps=" - << texture_desc.fMipmaps); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fArraySlices=" - << texture_desc.fArraySlices); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fFormat=" - << texture_desc.fFormat); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fTextureType=" - << texture_desc.fTextureType); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fEnvMapType=" - << texture_desc.fEnvMapType); + const auto int_precision = 0; + const auto double_precision = 3; + calculate_node_image_size_string( + objPath, ImagePlaneShapeNode::m_draw_image_size, + ImagePlaneShapeNode::m_image_width, + ImagePlaneShapeNode::m_image_height, + ImagePlaneShapeNode::m_image_pixel_aspect, int_precision, + double_precision, + + m_draw_image_size, m_image_size); + calculate_node_camera_size_string( + objPath, ImagePlaneShapeNode::m_draw_camera_size, + ImagePlaneShapeNode::m_camera_width_inch, + ImagePlaneShapeNode::m_camera_height_inch, double_precision, + m_draw_camera_size, m_camera_size); } } - - if (!out_texture_sampler) { - MHWRender::MSamplerStateDesc sampler_desc; - sampler_desc.addressU = MHWRender::MSamplerState::kTexWrap; - sampler_desc.addressV = MHWRender::MSamplerState::kTexWrap; - sampler_desc.addressW = MHWRender::MSamplerState::kTexWrap; - // kMinMagMipPoint is "nearest pixel" filtering. - sampler_desc.filter = MHWRender::MSamplerState::kMinMagMipPoint; - out_texture_sampler = - MHWRender::MStateManager::acquireSamplerState(sampler_desc); - } - - if (out_texture_sampler) { - status = - shader->setParameter("gImageTextureSampler", *out_texture_sampler); - CHECK_MSTATUS(status); - } else { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get texture sampler." - << " out_texture_sampler=" << out_texture_sampler); - } - - if (out_color_texture) { - MHWRender::MTextureAssignment texture_assignment; - texture_assignment.texture = out_color_texture; - status = shader->setParameter("gImageTexture", texture_assignment); - CHECK_MSTATUS(status); - - out_color_texture = nullptr; - } else { - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: Could not get color texture; " - "did not assign texture." - << " out_color_texture=" << out_color_texture); - } - - return; } void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, @@ -746,23 +185,22 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, return; } - MHWRender::MRenderer *renderer = MRenderer::theRenderer(); + MRenderer *renderer = MRenderer::theRenderer(); if (!renderer) { MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MRenderer."); return; } - const MHWRender::MShaderManager *shaderManager = - renderer->getShaderManager(); + const MShaderManager *shaderManager = renderer->getShaderManager(); if (!shaderManager) { MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MShaderManager."); return; } if (m_geometry_node_type != MFn::kMesh) { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: " - << "Only Meshes are supported, geometry node " - "given is not a mesh."); + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: " + << "Only Meshes are supported, geometry node given is not a mesh."); return; } @@ -776,8 +214,8 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, if (index >= 0) { wireframeItem = list.itemAt(index); } else { - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: Generate wireframe MRenderItem..."); + // MMSOLVER_MAYA_INFO("mmImagePlaneShape: Generate wireframe + // MRenderItem..."); wireframeItem = MRenderItem::Create( renderItemName_imagePlaneWireframe, MRenderItem::DecorationItem, MGeometry::kLines); @@ -819,8 +257,7 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, shaderManager->getStockShader(MShaderManager::k3dSolidShader); if (shader) { static const float color[] = {1.0f, 0.0f, 0.0f, 1.0f}; - MStatus status = shader->setParameter("solidColor", color); - CHECK_MSTATUS(status); + shader->setParameter("solidColor", color); wireframeItem->setShader(shader); shaderManager->releaseShader(shader); } @@ -829,92 +266,27 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, if (shadedItem) { shadedItem->enable(m_visible); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->isEnabled()=" - << shadedItem->isEnabled()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->isShaderFromNode()=" - << shadedItem->isShaderFromNode()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->isMultiDraw()=" - << shadedItem->isMultiDraw()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->isConsolidated()=" - << shadedItem->isConsolidated()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->wantConsolidation()=" - << shadedItem->wantConsolidation()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->castsShadows()=" - << shadedItem->castsShadows()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->receivesShadows()=" - << shadedItem->receivesShadows()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->excludedFromPostEffects()=" - << shadedItem->excludedFromPostEffects()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "shadedItem->supportsAdvancedTransparency()=" - << shadedItem->supportsAdvancedTransparency()); - - if (!m_shader || m_update_shader) { - if (m_shader) { - shaderManager->releaseShader(m_shader); - } - - const MString shader_file_path = - mmsolver::render::find_shader_file_path("mmImagePlane.ogsfx"); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: found shader_file_path=\"" - << shader_file_path << "\""); - - if (shader_file_path.length() > 0) { - MString shader_text = - mmsolver::render::read_shader_file(shader_file_path); - - std::string ocio_shader_text; - mmcolorio::generate_shader_text( - m_input_color_space_name.asChar(), - m_output_color_space_name.asChar(), ocio_shader_text); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: ocio_shader_text=\"" - << ocio_shader_text << "\""); - if (ocio_shader_text.size() > 0) { - const MString ocio_function_declare_text = MString( - "vec4 OCIODisplay(vec4 passthrough) { return " - "passthrough; " - "}"); - MStatus status = shader_text.substitute( - ocio_function_declare_text, - MString(ocio_shader_text.c_str())); - CHECK_MSTATUS(status); - } - - m_shader = - mmsolver::render::compile_shader_text(shader_text, "Main"); - } - - if (m_shader) { - m_update_shader = false; - } - } - - if (m_shader) { - MHWRender::MTextureManager *textureManager = - renderer->getTextureManager(); - if (!textureManager) { - MMSOLVER_MAYA_WRN( - "mmImagePlaneShape: Could not get MTextureManager."); - return; + if (!m_shader_node.isNull()) { + // TODO: Implement callback to detect when the shader + // needs to be re-compiled. + auto linkLostCb = nullptr; + auto linkLostUserData = nullptr; + bool nonTextured = false; + shadedItem->setShaderFromNode(m_shader_node, m_geometry_node_path, + linkLostCb, linkLostUserData, + nonTextured); + } else { + MMSOLVER_MAYA_WRN( + "mmImagePlaneShape: " + << "Shader node is not valid, using fallback blue shader."); + MShaderInstance *shader = + shaderManager->getStockShader(MShaderManager::k3dSolidShader); + if (shader) { + static const float color[] = {0.0f, 0.0f, 1.0f, 1.0f}; + shader->setParameter("solidColor", color); + shadedItem->setShader(shader); + shaderManager->releaseShader(shader); } - - set_shader_instance_parameters( - m_shader, textureManager, m_color_gain, m_color_exposure, - m_color_gamma, m_color_saturation, m_color_soft_clip, - m_alpha_gain, m_default_color, m_ignore_alpha, m_flip, m_flop, - m_is_transparent, m_image_display_channel, m_frame, m_file_path, - m_input_color_space_name, m_output_color_space_name, - m_color_texture, m_texture_sampler); - - shadedItem->setShader(m_shader); } } } @@ -1034,7 +406,7 @@ void ImagePlaneGeometryOverride::cleanUp() {} #if MAYA_API_VERSION >= 20190000 bool ImagePlaneGeometryOverride::requiresGeometryUpdate() const { const bool verbose = false; - if (m_geometry_node_path.isValid()) { + if (m_geometry_node_path.isValid() && !m_shader_node.isNull()) { MMSOLVER_MAYA_VRB( "ImagePlaneGeometryOverride::requiresGeometryUpdate: false"); return false; diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.h b/src/mmSolver/shape/ImagePlaneGeometryOverride.h index 1c752e116..55ea16a5a 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.h +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022, 2024 David Cattermole. + * Copyright (C) 2022 David Cattermole. * * This file is part of mmSolver. * @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -39,28 +38,11 @@ #include // MM Solver -#include - -#include "ImageCache.h" #include "ImagePlaneShapeNode.h" #include "mmSolver/utilities/debug_utils.h" namespace mmsolver { -class ShaderLinkLostUserData : public MUserData { -public: - ShaderLinkLostUserData() - : MUserData(), link_lost_count(0), set_shader_count(0) {} - - // Keep track of the number of times stuff happens, just for - // interest sake (maybe to help debugging?) - doesn't really mean - // or do anything special. - uint32_t link_lost_count; - uint32_t set_shader_count; -}; - -using ShaderLinkLostUserDataPtr = MSharedPtr; - class ImagePlaneGeometryOverride : public MPxGeometryOverride { public: static MPxGeometryOverride *Creator(const MObject &obj) { @@ -106,41 +88,16 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { ImagePlaneGeometryOverride(const MObject &obj); static void on_model_editor_changed_func(void *clientData); - static void shader_link_lost_func(ShaderLinkLostUserData *userData); - - void query_node_attributes( - MObject &node, MDagPath &out_camera_node_path, bool &out_visible, - bool &out_visible_to_camera_only, bool &out_is_under_camera, - bool &out_draw_hud, bool &out_draw_image_size, MString &out_image_size, - bool &out_draw_camera_size, MString &out_camera_size, - ImageDisplayChannel &out_image_display_channel, MColor &out_color_gain, - float &out_color_exposure, float &out_color_gamma, - float &out_color_saturation, float &out_color_soft_clip, - float &out_alpha_gain, MColor &out_default_color, - bool &out_ignore_alpha, bool &out_flip, bool &out_flop, - bool &out_is_transparent, mmcore::FrameValue &out_frame, - MString &out_file_path, MString &out_input_color_space_name, - MString &out_output_color_space_name); - - void set_shader_instance_parameters( - MShaderInstance *shader, MHWRender::MTextureManager *textureManager, - const MColor &color_gain, const float color_exposure, - const float color_gamma, const float color_saturation, - const float color_soft_clip, const float alpha_gain, - const MColor &default_color, const bool ignore_alpha, const bool flip, - const bool flop, const bool is_transparent, - const ImageDisplayChannel image_display_channel, - const mmcore::FrameValue frame, const MString &file_path, - const MString &input_color_space_name, - const MString &output_color_space_name, - MHWRender::MTexture *out_color_texture, - const MHWRender::MSamplerState *out_texture_sampler); MObject m_this_node; MDagPath m_geometry_node_path; MDagPath m_camera_node_path; MFn::Type m_geometry_node_type; + MFn::Type m_shader_node_type; MFn::Type m_camera_node_type; + MObject m_geometry_node; + MObject m_shader_node; + MObject m_camera_node; bool m_visible; bool m_visible_to_camera_only; @@ -148,40 +105,9 @@ class ImagePlaneGeometryOverride : public MPxGeometryOverride { bool m_draw_hud; bool m_draw_image_size; bool m_draw_camera_size; - bool m_update_shader; MString m_image_size; MString m_camera_size; MCallbackId m_model_editor_changed_callback_id; - - // Shader attributes. - MShaderInstance *m_shader; - ImageDisplayChannel m_image_display_channel; - MColor m_color_gain; - float m_alpha_gain; - float m_color_exposure; - float m_color_gamma; - float m_color_saturation; - float m_color_soft_clip; - MColor m_default_color; - bool m_ignore_alpha; - bool m_flip; - bool m_flop; - bool m_is_transparent; - mmcore::FrameValue m_frame; - MString m_file_path; - MString m_input_color_space_name; - MString m_output_color_space_name; - - // Texture caching - MImage m_temp_image; - MHWRender::MTexture *m_color_texture; - const MHWRender::MSamplerState *m_texture_sampler; - -#if MAYA_API_VERSION >= 20200000 - ShaderLinkLostUserDataPtr m_shader_link_lost_user_data_ptr; -#elif - ShaderLinkLostUserData m_shader_link_lost_user_data; -#endif }; } // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp new file mode 100644 index 000000000..1151032ea --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -0,0 +1,448 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "ImagePlaneShape2Node.h" + +// STL +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if MAYA_API_VERSION >= 20190000 +#include +#endif + +// MM Solver +#include "ImagePlaneUtils.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/nodeTypeIds.h" + +namespace mmsolver { + +MTypeId ImagePlaneShape2Node::m_id(MM_IMAGE_PLANE_SHAPE_2_TYPE_ID); +MString ImagePlaneShape2Node::m_draw_db_classification( + MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY); +MString ImagePlaneShape2Node::m_draw_registrant_id( + MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID); +MString ImagePlaneShape2Node::m_selection_type_name( + MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME); +MString ImagePlaneShape2Node::m_display_filter_name( + MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME); +MString ImagePlaneShape2Node::m_display_filter_label( + MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_LABEL); + +// Attributes +MObject ImagePlaneShape2Node::m_visible_to_camera_only; +MObject ImagePlaneShape2Node::m_draw_hud; +MObject ImagePlaneShape2Node::m_draw_image_size; +MObject ImagePlaneShape2Node::m_draw_camera_size; +MObject ImagePlaneShape2Node::m_image_width; +MObject ImagePlaneShape2Node::m_image_height; +MObject ImagePlaneShape2Node::m_image_pixel_aspect; +MObject ImagePlaneShape2Node::m_camera_width_inch; +MObject ImagePlaneShape2Node::m_camera_height_inch; +MObject ImagePlaneShape2Node::m_lens_hash_current; +MObject ImagePlaneShape2Node::m_lens_hash_previous; +MObject ImagePlaneShape2Node::m_geometry_node; +MObject ImagePlaneShape2Node::m_camera_node; + +// Image Attributes +MObject ImagePlaneShape2Node::m_image_display_channel; +MObject ImagePlaneShape2Node::m_image_color_gain; +MObject ImagePlaneShape2Node::m_image_color_exposure; +MObject ImagePlaneShape2Node::m_image_color_gamma; +MObject ImagePlaneShape2Node::m_image_color_saturation; +MObject ImagePlaneShape2Node::m_image_color_soft_clip; +MObject ImagePlaneShape2Node::m_image_alpha_gain; +MObject ImagePlaneShape2Node::m_image_default_color; +MObject ImagePlaneShape2Node::m_image_ignore_alpha; +MObject ImagePlaneShape2Node::m_image_flip; +MObject ImagePlaneShape2Node::m_image_flop; +MObject ImagePlaneShape2Node::m_image_frame_number; +MObject ImagePlaneShape2Node::m_image_file_path; +MObject ImagePlaneShape2Node::m_image_input_color_space; +MObject ImagePlaneShape2Node::m_image_output_color_space; +MObject ImagePlaneShape2Node::m_shader_is_transparent; + +ImagePlaneShape2Node::ImagePlaneShape2Node() {} + +ImagePlaneShape2Node::~ImagePlaneShape2Node() {} + +MString ImagePlaneShape2Node::nodeName() { + return MString(MM_IMAGE_PLANE_SHAPE_2_TYPE_NAME); +} + +MStatus ImagePlaneShape2Node::compute(const MPlug & /*plug*/, + MDataBlock & /*dataBlock*/ +) { + return MS::kUnknownParameter; +} + +bool ImagePlaneShape2Node::isBounded() const { return true; } + +MBoundingBox ImagePlaneShape2Node::boundingBox() const { + MObject this_node = thisMObject(); + + MPlug current_plug(this_node, m_lens_hash_current); + int64_t current_hash = current_plug.asInt64(); + + MPlug previous_plug(this_node, m_lens_hash_previous); + int64_t previous_hash = previous_plug.asInt64(); + + // Limit the number of calls to + // 'setGeometryDrawDirty', because this causes the viewport update + // to run constantly, running up CPU for no reason. + if (current_hash != previous_hash) { + MHWRender::MRenderer::setGeometryDrawDirty(this_node); + previous_plug.setInt64(current_hash); + } + + MPoint corner1(-1.0, -1.0, -1.0); + MPoint corner2(1.0, 1.0, 1.0); + return MBoundingBox(corner1, corner2); +} + +bool ImagePlaneShape2Node::excludeAsLocator() const { + // Returning 'false' here means that when the user toggles + // locators on/off with the (per-viewport) "Show" menu, this shape + // node will not be affected. + return false; +} + +// Called before this node is evaluated by Evaluation Manager. +#if MAYA_API_VERSION >= 20190000 +MStatus ImagePlaneShape2Node::preEvaluation( + const MDGContext &context, const MEvaluationNode &evaluationNode) { + if (context.isNormal()) { + MHWRender::MRenderer::setGeometryDrawDirty(thisMObject()); + } + + return MStatus::kSuccess; +} +#endif + +#if MAYA_API_VERSION >= 20200000 +void ImagePlaneShape2Node::getCacheSetup( + const MEvaluationNode &evalNode, MNodeCacheDisablingInfo &disablingInfo, + MNodeCacheSetupInfo &cacheSetupInfo, + MObjectArray &monitoredAttributes) const { + MPxLocatorNode::getCacheSetup(evalNode, disablingInfo, cacheSetupInfo, + monitoredAttributes); + assert(!disablingInfo.getCacheDisabled()); + cacheSetupInfo.setPreference(MNodeCacheSetupInfo::kWantToCacheByDefault, + true); +} +#endif + +void *ImagePlaneShape2Node::creator() { return new ImagePlaneShape2Node(); } + +MStatus ImagePlaneShape2Node::initialize() { + MStatus status; + MFnNumericAttribute nAttr; + MFnTypedAttribute tAttr; + MFnEnumAttribute eAttr; + MFnMessageAttribute msgAttr; + + m_visible_to_camera_only = nAttr.create("visibleToCameraOnly", "viscamony", + MFnNumericData::kBoolean, 0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_visible_to_camera_only)); + + m_draw_hud = nAttr.create("drawHud", "enbhud", MFnNumericData::kBoolean, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_draw_hud)); + + m_draw_image_size = + nAttr.create("drawImageSize", "enbimgsz", MFnNumericData::kBoolean, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_draw_image_size)); + + m_draw_camera_size = + nAttr.create("drawCameraSize", "enbcamsz", MFnNumericData::kBoolean, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_draw_camera_size)); + + m_image_width = + nAttr.create("imageWidth", "imgwdth", MFnNumericData::kInt, 1920); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(1)); + CHECK_MSTATUS(addAttribute(m_image_width)); + + m_image_height = + nAttr.create("imageHeight", "imghght", MFnNumericData::kInt, 1080); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(1)); + CHECK_MSTATUS(addAttribute(m_image_height)); + + m_image_pixel_aspect = nAttr.create("imagePixelAspect", "imgpxasp", + MFnNumericData::kDouble, 1.0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(0.1)); + CHECK_MSTATUS(nAttr.setMax(4.0)); + CHECK_MSTATUS(addAttribute(m_image_pixel_aspect)); + + m_camera_width_inch = nAttr.create("cameraWidthInch", "camwdthin", + MFnNumericData::kDouble, 1.0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setMin(0)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Camera Width (inches)"))); + CHECK_MSTATUS(addAttribute(m_camera_width_inch)); + + m_camera_height_inch = nAttr.create("cameraHeightInch", "camhghtin", + MFnNumericData::kDouble, 1.0); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setMin(0)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Camera Height (inches)"))); + CHECK_MSTATUS(addAttribute(m_camera_height_inch)); + + m_lens_hash_current = + nAttr.create("lensHashCurrent", "lnshshcur", MFnNumericData::kInt64, 0); + CHECK_MSTATUS(nAttr.setStorable(false)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setHidden(true)); + CHECK_MSTATUS(addAttribute(m_lens_hash_current)); + + m_lens_hash_previous = nAttr.create("lensHashPrevious", "lnshshprv", + MFnNumericData::kInt64, 0); + CHECK_MSTATUS(nAttr.setStorable(false)); + CHECK_MSTATUS(nAttr.setConnectable(false)); + CHECK_MSTATUS(nAttr.setKeyable(false)); + CHECK_MSTATUS(nAttr.setHidden(true)); + CHECK_MSTATUS(addAttribute(m_lens_hash_previous)); + + m_geometry_node = msgAttr.create("geometryNode", "geond", &status); + CHECK_MSTATUS(status); + CHECK_MSTATUS(msgAttr.setStorable(true)); + CHECK_MSTATUS(msgAttr.setConnectable(true)); + CHECK_MSTATUS(msgAttr.setKeyable(false)); + CHECK_MSTATUS(addAttribute(m_geometry_node)); + + m_camera_node = msgAttr.create("cameraNode", "camnd", &status); + CHECK_MSTATUS(status); + CHECK_MSTATUS(msgAttr.setStorable(true)); + CHECK_MSTATUS(msgAttr.setConnectable(true)); + CHECK_MSTATUS(msgAttr.setKeyable(false)); + CHECK_MSTATUS(addAttribute(m_camera_node)); + + m_image_color_gain = nAttr.createColor("colorGain", "colgn"); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setReadable(true)); + CHECK_MSTATUS(nAttr.setWritable(true)); + CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); + CHECK_MSTATUS(addAttribute(m_image_color_gain)); + + const float exposure_soft_min = -9.0f; + const float exposure_soft_max = +9.0f; + const float exposure_default = 0.0f; + m_image_color_exposure = nAttr.create( + "colorExposure", "colexpsr", MFnNumericData::kFloat, exposure_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setSoftMin(exposure_soft_min)); + CHECK_MSTATUS(nAttr.setSoftMax(exposure_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_exposure)); + + const float gamma_min = 0.0f; + const float gamma_soft_max = +2.0f; + const float gamma_default = 1.0f; + m_image_color_gamma = nAttr.create("colorGamma", "colgmma", + MFnNumericData::kFloat, gamma_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(gamma_min)); + CHECK_MSTATUS(nAttr.setSoftMax(gamma_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_gamma)); + + const float saturation_min = 0.0f; + const float saturation_soft_max = 2.0f; + const float saturation_default = 1.0f; + m_image_color_saturation = + nAttr.create("colorSaturation", "colstrtn", MFnNumericData::kFloat, + saturation_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(saturation_min)); + CHECK_MSTATUS(nAttr.setSoftMax(saturation_soft_max)); + CHECK_MSTATUS(addAttribute(m_image_color_saturation)); + + const float soft_clip_min = 0.0f; + const float soft_clip_max = 1.0f; + const float soft_clip_default = 0.0f; + m_image_color_soft_clip = + nAttr.create("colorSoftClip", "colsftclp", MFnNumericData::kFloat, + soft_clip_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(soft_clip_min)); + CHECK_MSTATUS(nAttr.setMax(soft_clip_max)); + CHECK_MSTATUS(addAttribute(m_image_color_soft_clip)); + + const double alpha_min = 0.0; + const double alpha_max = 1.0; + const double alpha_default = 1.0; + m_image_alpha_gain = nAttr.create("alphaGain", "alpgn", + MFnNumericData::kDouble, alpha_default); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(alpha_min)); + CHECK_MSTATUS(nAttr.setMax(alpha_max)); + CHECK_MSTATUS(addAttribute(m_image_alpha_gain)); + + // Which channel of the image should be displayed? + const short value_rgba = static_cast(ImageDisplayChannel::kRGBA); + const short value_rgb = static_cast(ImageDisplayChannel::kRGB); + const short value_red = static_cast(ImageDisplayChannel::kRed); + const short value_green = static_cast(ImageDisplayChannel::kGreen); + const short value_blue = static_cast(ImageDisplayChannel::kBlue); + const short value_alpha = static_cast(ImageDisplayChannel::kAlpha); + const short value_luminance = + static_cast(ImageDisplayChannel::kLuminance); + m_image_display_channel = + eAttr.create("displayChannel", "dspchan", value_rgba, &status); + CHECK_MSTATUS(status); + CHECK_MSTATUS(eAttr.addField("RGBA", value_rgba)); + CHECK_MSTATUS(eAttr.addField("RGB", value_rgb)); + CHECK_MSTATUS(eAttr.addField("Red", value_red)); + CHECK_MSTATUS(eAttr.addField("Green", value_green)); + CHECK_MSTATUS(eAttr.addField("Blue", value_blue)); + CHECK_MSTATUS(eAttr.addField("Alpha", value_alpha)); + CHECK_MSTATUS(eAttr.addField("Luminance", value_luminance)); + CHECK_MSTATUS(eAttr.setStorable(true)); + CHECK_MSTATUS(eAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_image_display_channel)); + + m_image_ignore_alpha = nAttr.create("imageIgnoreAlpha", "imgignalp", + MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_image_ignore_alpha)); + + m_image_flip = + nAttr.create("imageFlip", "imgflip", MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Image Flip (Vertical)"))); + CHECK_MSTATUS(addAttribute(m_image_flip)); + + m_image_flop = + nAttr.create("imageFlop", "imgflop", MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS( + nAttr.setNiceNameOverride(MString("Image Flop (Horizontal)"))); + CHECK_MSTATUS(addAttribute(m_image_flop)); + + m_shader_is_transparent = nAttr.create("shaderIsTransparent", "shdistrnsp", + MFnNumericData::kBoolean, false); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setConnectable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS( + nAttr.setNiceNameOverride(MString("Shader Is Transparent (Debug)"))); + CHECK_MSTATUS(addAttribute(m_shader_is_transparent)); + + m_image_default_color = nAttr.createColor("imageDefaultColor", "imgdefcol"); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setReadable(true)); + CHECK_MSTATUS(nAttr.setWritable(true)); + CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); + CHECK_MSTATUS(addAttribute(m_image_default_color)); + + m_image_frame_number = + nAttr.create("imageFrameNumber", "imgfrmnmb", MFnNumericData::kInt, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(m_image_frame_number)); + + // // Pixel Data Type + // m_cache_pixel_data_type = eAttr.create( + // "cachePixelDataType", "cchpxldtyp", + // kDataTypeUnknown); + // CHECK_MSTATUS(eAttr.addField("auto", kDataTypeUnknown)); + // CHECK_MSTATUS(eAttr.addField("uint8", kDataTypeUInt8)); + // CHECK_MSTATUS(eAttr.addField("uint16", kDataTypeUInt16)); + // CHECK_MSTATUS(eAttr.addField("half16", kDataTypeHalf16)); + // CHECK_MSTATUS(eAttr.addField("float32", kDataTypeFloat32)); + // CHECK_MSTATUS(eAttr.setStorable(true)); + + // Create empty string data to be used as attribute default + // (string) value. + MFnStringData empty_string_data; + MObject empty_string_data_obj = empty_string_data.create(""); + + m_image_file_path = tAttr.create("imageFilePath", "imgflpth", + MFnData::kString, empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(true)); + CHECK_MSTATUS(addAttribute(m_image_file_path)); + + m_image_input_color_space = tAttr.create( + "inputColorSpace", "incolspc", MFnData::kString, empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); + CHECK_MSTATUS(addAttribute(m_image_input_color_space)); + + m_image_output_color_space = + tAttr.create("outputColorSpace", "outcolspc", MFnData::kString, + empty_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); + CHECK_MSTATUS(addAttribute(m_image_output_color_space)); + + return MS::kSuccess; +} + +} // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.h b/src/mmSolver/shape/ImagePlaneShape2Node.h new file mode 100644 index 000000000..4c068dbec --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneShape2Node.h @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_IMAGE_PLANE_SHAPE_2_NODE_H +#define MM_IMAGE_PLANE_SHAPE_2_NODE_H + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if MAYA_API_VERSION >= 20190000 +#include +#endif + +namespace mmsolver { + +class ImagePlaneShape2Node : public MPxLocatorNode { +public: + ImagePlaneShape2Node(); + + ~ImagePlaneShape2Node() override; + + MStatus compute(const MPlug &plug, MDataBlock &data) override; + + bool isBounded() const override; + + MBoundingBox boundingBox() const override; + + bool excludeAsLocator() const; + +#if MAYA_API_VERSION >= 20190000 + MStatus preEvaluation(const MDGContext &context, + const MEvaluationNode &evaluationNode) override; +#endif + +#if MAYA_API_VERSION >= 20200000 + void getCacheSetup(const MEvaluationNode &evalNode, + MNodeCacheDisablingInfo &disablingInfo, + MNodeCacheSetupInfo &cacheSetupInfo, + MObjectArray &monitoredAttributes) const override; +#endif + + static void *creator(); + + static MStatus initialize(); + + static MString nodeName(); + + // Node specific meta-data. + static MTypeId m_id; + static MString m_draw_db_classification; + static MString m_draw_registrant_id; + static MString m_selection_type_name; + static MString m_display_filter_name; + static MString m_display_filter_label; + + // Attributes + static MObject m_visible_to_camera_only; + static MObject m_draw_hud; + static MObject m_draw_image_size; + static MObject m_draw_camera_size; + static MObject m_image_width; + static MObject m_image_height; + static MObject m_image_pixel_aspect; + static MObject m_camera_width_inch; + static MObject m_camera_height_inch; + static MObject m_lens_hash_current; + static MObject m_lens_hash_previous; + static MObject m_geometry_node; + static MObject m_camera_node; + static MObject m_shader_is_transparent; + + // Image Attributes + static MObject m_image_display_channel; + static MObject m_image_color_gain; + static MObject m_image_color_exposure; + static MObject m_image_color_gamma; + static MObject m_image_color_saturation; + static MObject m_image_color_soft_clip; + static MObject m_image_alpha_gain; + static MObject m_image_default_color; + static MObject m_image_ignore_alpha; + static MObject m_image_flip; + static MObject m_image_flop; + static MObject m_image_file_path; + static MObject m_image_frame_number; + static MObject m_image_input_color_space; + static MObject m_image_output_color_space; +}; + +} // namespace mmsolver + +#endif // MM_IMAGE_PLANE_SHAPE_2_NODE_H diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.cpp b/src/mmSolver/shape/ImagePlaneShapeNode.cpp index 35a1d4ce9..d1452e55b 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.cpp +++ b/src/mmSolver/shape/ImagePlaneShapeNode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022, 2024 David Cattermole. + * Copyright (C) 2022 David Cattermole. * * This file is part of mmSolver. * @@ -21,6 +21,9 @@ #include "ImagePlaneShapeNode.h" +// STL +#include + // Maya #include #include @@ -30,8 +33,6 @@ #include #include #include -#include -#include #include #include #include @@ -44,8 +45,6 @@ #include #endif -#include - // MM Solver #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/nodeTypeIds.h" @@ -77,26 +76,9 @@ MObject ImagePlaneShapeNode::m_camera_height_inch; MObject ImagePlaneShapeNode::m_lens_hash_current; MObject ImagePlaneShapeNode::m_lens_hash_previous; MObject ImagePlaneShapeNode::m_geometry_node; +MObject ImagePlaneShapeNode::m_shader_node; MObject ImagePlaneShapeNode::m_camera_node; -// Image Attributes -MObject ImagePlaneShapeNode::m_image_display_channel; -MObject ImagePlaneShapeNode::m_image_color_gain; -MObject ImagePlaneShapeNode::m_image_color_exposure; -MObject ImagePlaneShapeNode::m_image_color_gamma; -MObject ImagePlaneShapeNode::m_image_color_saturation; -MObject ImagePlaneShapeNode::m_image_color_soft_clip; -MObject ImagePlaneShapeNode::m_image_alpha_gain; -MObject ImagePlaneShapeNode::m_image_default_color; -MObject ImagePlaneShapeNode::m_image_ignore_alpha; -MObject ImagePlaneShapeNode::m_image_flip; -MObject ImagePlaneShapeNode::m_image_flop; -MObject ImagePlaneShapeNode::m_image_frame_number; -MObject ImagePlaneShapeNode::m_image_file_path; -MObject ImagePlaneShapeNode::m_image_input_color_space; -MObject ImagePlaneShapeNode::m_image_output_color_space; -MObject ImagePlaneShapeNode::m_shader_is_transparent; - ImagePlaneShapeNode::ImagePlaneShapeNode() {} ImagePlaneShapeNode::~ImagePlaneShapeNode() {} @@ -172,8 +154,6 @@ void *ImagePlaneShapeNode::creator() { return new ImagePlaneShapeNode(); } MStatus ImagePlaneShapeNode::initialize() { MStatus status; MFnNumericAttribute nAttr; - MFnTypedAttribute tAttr; - MFnEnumAttribute eAttr; MFnMessageAttribute msgAttr; m_visible_to_camera_only = nAttr.create("visibleToCameraOnly", "viscamony", @@ -262,183 +242,19 @@ MStatus ImagePlaneShapeNode::initialize() { CHECK_MSTATUS(msgAttr.setKeyable(false)); CHECK_MSTATUS(addAttribute(m_geometry_node)); - m_camera_node = msgAttr.create("cameraNode", "camnd", &status); + m_shader_node = msgAttr.create("shaderNode", "shdnd", &status); CHECK_MSTATUS(status); CHECK_MSTATUS(msgAttr.setStorable(true)); CHECK_MSTATUS(msgAttr.setConnectable(true)); CHECK_MSTATUS(msgAttr.setKeyable(false)); - CHECK_MSTATUS(addAttribute(m_camera_node)); + CHECK_MSTATUS(addAttribute(m_shader_node)); - m_image_color_gain = nAttr.createColor("colorGain", "colgn"); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setReadable(true)); - CHECK_MSTATUS(nAttr.setWritable(true)); - CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); - CHECK_MSTATUS(addAttribute(m_image_color_gain)); - - const float exposure_soft_min = -9.0f; - const float exposure_soft_max = +9.0f; - const float exposure_default = 0.0f; - m_image_color_exposure = nAttr.create( - "colorExposure", "colexpsr", MFnNumericData::kFloat, exposure_default); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setSoftMin(exposure_soft_min)); - CHECK_MSTATUS(nAttr.setSoftMax(exposure_soft_max)); - CHECK_MSTATUS(addAttribute(m_image_color_exposure)); - - const float gamma_min = 0.0f; - const float gamma_soft_max = +2.0f; - const float gamma_default = 1.0f; - m_image_color_gamma = nAttr.create("colorGamma", "colgmma", - MFnNumericData::kFloat, gamma_default); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setMin(gamma_min)); - CHECK_MSTATUS(nAttr.setSoftMax(gamma_soft_max)); - CHECK_MSTATUS(addAttribute(m_image_color_gamma)); - - const float saturation_min = 0.0f; - const float saturation_soft_max = 2.0f; - const float saturation_default = 1.0f; - m_image_color_saturation = - nAttr.create("colorSaturation", "colstrtn", MFnNumericData::kFloat, - saturation_default); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setMin(saturation_min)); - CHECK_MSTATUS(nAttr.setSoftMax(saturation_soft_max)); - CHECK_MSTATUS(addAttribute(m_image_color_saturation)); - - const float soft_clip_min = 0.0f; - const float soft_clip_max = 1.0f; - const float soft_clip_default = 0.0f; - m_image_color_soft_clip = - nAttr.create("colorSoftClip", "colsftclp", MFnNumericData::kFloat, - soft_clip_default); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setMin(soft_clip_min)); - CHECK_MSTATUS(nAttr.setMax(soft_clip_max)); - CHECK_MSTATUS(addAttribute(m_image_color_soft_clip)); - - const double alpha_min = 0.0; - const double alpha_max = 1.0; - const double alpha_default = 1.0; - m_image_alpha_gain = nAttr.create("alphaGain", "alpgn", - MFnNumericData::kDouble, alpha_default); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setMin(alpha_min)); - CHECK_MSTATUS(nAttr.setMax(alpha_max)); - CHECK_MSTATUS(addAttribute(m_image_alpha_gain)); - - // Which channel of the image should be displayed? - const short value_all = static_cast(ImageDisplayChannel::kAll); - const short value_rgb = static_cast(ImageDisplayChannel::kRGB); - const short value_red = static_cast(ImageDisplayChannel::kRed); - const short value_green = static_cast(ImageDisplayChannel::kGreen); - const short value_blue = static_cast(ImageDisplayChannel::kBlue); - const short value_alpha = static_cast(ImageDisplayChannel::kAlpha); - const short value_luminance = - static_cast(ImageDisplayChannel::kLuminance); - m_image_display_channel = - eAttr.create("displayChannel", "dspchan", value_all, &status); + m_camera_node = msgAttr.create("cameraNode", "camnd", &status); CHECK_MSTATUS(status); - CHECK_MSTATUS(eAttr.addField("RGBA", value_all)); - CHECK_MSTATUS(eAttr.addField("RGB", value_rgb)); - CHECK_MSTATUS(eAttr.addField("Red", value_red)); - CHECK_MSTATUS(eAttr.addField("Green", value_green)); - CHECK_MSTATUS(eAttr.addField("Blue", value_blue)); - CHECK_MSTATUS(eAttr.addField("Alpha", value_alpha)); - CHECK_MSTATUS(eAttr.addField("Luminance", value_luminance)); - CHECK_MSTATUS(eAttr.setStorable(true)); - CHECK_MSTATUS(eAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_image_display_channel)); - - m_image_ignore_alpha = nAttr.create("imageIgnoreAlpha", "imgignalp", - MFnNumericData::kBoolean, false); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_image_ignore_alpha)); - - m_image_flip = - nAttr.create("imageFlip", "imgflip", MFnNumericData::kBoolean, false); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Image Flip (Vertical)"))); - CHECK_MSTATUS(addAttribute(m_image_flip)); - - m_image_flop = - nAttr.create("imageFlop", "imgflop", MFnNumericData::kBoolean, false); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS( - nAttr.setNiceNameOverride(MString("Image Flop (Horizontal)"))); - CHECK_MSTATUS(addAttribute(m_image_flop)); - - m_shader_is_transparent = nAttr.create("shaderIsTransparent", "shdistrnsp", - MFnNumericData::kBoolean, false); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setConnectable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS( - nAttr.setNiceNameOverride(MString("Shader Is Transparent (Debug)"))); - CHECK_MSTATUS(addAttribute(m_shader_is_transparent)); - - m_image_default_color = nAttr.createColor("imageDefaultColor", "imgdefcol"); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setReadable(true)); - CHECK_MSTATUS(nAttr.setWritable(true)); - CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); - CHECK_MSTATUS(addAttribute(m_image_default_color)); - - m_image_frame_number = - nAttr.create("imageFrameNumber", "imgfrmnmb", MFnNumericData::kInt, 1); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(addAttribute(m_image_frame_number)); - - // // Pixel Data Type - // m_cache_pixel_data_type = eAttr.create( - // "cachePixelDataType", "cchpxldtyp", - // kDataTypeUnknown); - // CHECK_MSTATUS(eAttr.addField("auto", kDataTypeUnknown)); - // CHECK_MSTATUS(eAttr.addField("uint8", kDataTypeUInt8)); - // CHECK_MSTATUS(eAttr.addField("uint16", kDataTypeUInt16)); - // CHECK_MSTATUS(eAttr.addField("half16", kDataTypeHalf16)); - // CHECK_MSTATUS(eAttr.addField("float32", kDataTypeFloat32)); - // CHECK_MSTATUS(eAttr.setStorable(true)); - - // Create empty string data to be used as attribute default - // (string) value. - MFnStringData empty_string_data; - MObject empty_string_data_obj = empty_string_data.create(""); - - m_image_file_path = tAttr.create("imageFilePath", "imgflpth", - MFnData::kString, empty_string_data_obj); - CHECK_MSTATUS(tAttr.setStorable(true)); - CHECK_MSTATUS(tAttr.setUsedAsFilename(true)); - CHECK_MSTATUS(addAttribute(m_image_file_path)); - - m_image_input_color_space = tAttr.create( - "inputColorSpace", "incolspc", MFnData::kString, empty_string_data_obj); - CHECK_MSTATUS(tAttr.setStorable(true)); - CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); - CHECK_MSTATUS(addAttribute(m_image_input_color_space)); - - m_image_output_color_space = - tAttr.create("outputColorSpace", "outcolspc", MFnData::kString, - empty_string_data_obj); - CHECK_MSTATUS(tAttr.setStorable(true)); - CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); - CHECK_MSTATUS(addAttribute(m_image_output_color_space)); + CHECK_MSTATUS(msgAttr.setStorable(true)); + CHECK_MSTATUS(msgAttr.setConnectable(true)); + CHECK_MSTATUS(msgAttr.setKeyable(false)); + CHECK_MSTATUS(addAttribute(m_camera_node)); return MS::kSuccess; } diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.h b/src/mmSolver/shape/ImagePlaneShapeNode.h index 7f2977cd1..d6ee10472 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.h +++ b/src/mmSolver/shape/ImagePlaneShapeNode.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022, 2024 David Cattermole. + * Copyright (C) 2022 David Cattermole. * * This file is part of mmSolver. * @@ -22,6 +22,9 @@ #ifndef MM_IMAGE_PLANE_SHAPE_NODE_H #define MM_IMAGE_PLANE_SHAPE_NODE_H +// STL +#include + // Maya #include #include @@ -40,20 +43,8 @@ #include #endif -#include - namespace mmsolver { -enum class ImageDisplayChannel { - kAll = 0, - kRGB, - kRed, - kGreen, - kBlue, - kAlpha, - kLuminance -}; - class ImagePlaneShapeNode : public MPxLocatorNode { public: ImagePlaneShapeNode(); @@ -107,25 +98,8 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MObject m_lens_hash_current; static MObject m_lens_hash_previous; static MObject m_geometry_node; + static MObject m_shader_node; static MObject m_camera_node; - static MObject m_shader_is_transparent; - - // Image Attributes - static MObject m_image_display_channel; - static MObject m_image_color_gain; - static MObject m_image_color_exposure; - static MObject m_image_color_gamma; - static MObject m_image_color_saturation; - static MObject m_image_color_soft_clip; - static MObject m_image_alpha_gain; - static MObject m_image_default_color; - static MObject m_image_ignore_alpha; - static MObject m_image_flip; - static MObject m_image_flop; - static MObject m_image_file_path; - static MObject m_image_frame_number; - static MObject m_image_input_color_space; - static MObject m_image_output_color_space; }; } // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneUtils.cpp b/src/mmSolver/shape/ImagePlaneUtils.cpp new file mode 100644 index 000000000..0589d7478 --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneUtils.cpp @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "ImagePlaneUtils.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Maya Viewport 2.0 +#include +#include +#include +#include +#include +#include + +// MM Solver +#include +#include + +#include "ImageCache.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" + +namespace mmsolver { + +bool getUpstreamNodeFromConnection(const MObject &this_node, + const MString &attr_name, + MPlugArray &out_connections) { + MStatus status; + MFnDependencyNode mfn_depend_node(this_node); + + bool wantNetworkedPlug = true; + MPlug plug = + mfn_depend_node.findPlug(attr_name, wantNetworkedPlug, &status); + if (status != MStatus::kSuccess) { + CHECK_MSTATUS(status); + return false; + } + if (plug.isNull()) { + MMSOLVER_MAYA_WRN("Could not get plug for \"" + << mfn_depend_node.name().asChar() << "." + << attr_name.asChar() << "\" node."); + return false; + } + + bool as_destination = true; + bool as_source = false; + // Ask for plugs connecting to this node's ".shaderNode" + // attribute. + plug.connectedTo(out_connections, as_destination, as_source, &status); + if (status != MStatus::kSuccess) { + CHECK_MSTATUS(status); + return false; + } + if (out_connections.length() == 0) { + MMSOLVER_MAYA_WRN("No connections to the \"" + << mfn_depend_node.name().asChar() << "." + << attr_name.asChar() << "\" attribute."); + return false; + } + return true; +} + +void calculate_node_image_size_string( + MDagPath &objPath, MObject &draw_image_size_attr, MObject &image_width_attr, + MObject &image_height_attr, MObject &image_pixel_aspect_attr, + const uint32_t int_precision, const uint32_t double_precision, + bool &out_draw_image_size, MString &out_image_size) { + MStatus status = MS::kSuccess; + + double width = 1.0; + double height = 1.0; + double pixel_aspect = 1.0; + + status = getNodeAttr(objPath, draw_image_size_attr, out_draw_image_size); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, image_width_attr, width); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, image_height_attr, height); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, image_pixel_aspect_attr, pixel_aspect); + CHECK_MSTATUS(status); + + double aspect = (width * pixel_aspect) / height; + + MString width_string; + MString height_string; + MString pixel_aspect_string; + MString aspect_string; + + width_string.set(width, int_precision); + height_string.set(height, int_precision); + pixel_aspect_string.set(pixel_aspect, double_precision); + aspect_string.set(aspect, double_precision); + + out_image_size = MString("Image: ") + width_string + MString(" x ") + + height_string + MString(" | PAR ") + pixel_aspect_string + + MString(" | ") + aspect_string; + return; +} + +void calculate_node_camera_size_string(MDagPath &objPath, + MObject &draw_camera_size_attr, + MObject &camera_width_inch_attr, + MObject &camera_height_inch_attr, + const uint32_t double_precision, + bool &out_draw_camera_size, + MString &out_camera_size) { + MStatus status = MS::kSuccess; + + double width = 0.0; + double height = 0.0; + + status = getNodeAttr(objPath, draw_camera_size_attr, out_draw_camera_size); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, camera_width_inch_attr, width); + CHECK_MSTATUS(status); + + status = getNodeAttr(objPath, camera_height_inch_attr, height); + CHECK_MSTATUS(status); + + double aspect = width / height; + + MString width_string; + MString height_string; + MString aspect_string; + + width_string.set(width * INCH_TO_MM, double_precision); + height_string.set(height * INCH_TO_MM, double_precision); + aspect_string.set(aspect, double_precision); + + out_camera_size = MString("Camera: ") + width_string + MString("mm x ") + + height_string + MString("mm | ") + aspect_string; +} + +void find_geometry_node_path(MObject &node, MString &attr_name, + MDagPath &out_geometry_node_path, + MFn::Type &out_geometry_node_type) { + const auto verbose = false; + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + if (!ok) { + return; + } + + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kMesh)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_geometry_node_path = path; + out_geometry_node_type = path.apiType(); + MMSOLVER_MAYA_VRB("Validated geometry node: " + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Geometry node is not correct type:" + << " path=" + << out_geometry_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + } + } +} + +void find_shader_node_path(MObject &node, MString &attr_name, + MObject &out_shader_node, + MFn::Type &out_shader_node_type) { + const auto verbose = false; + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + if (!ok) { + return; + } + + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + MFnDependencyNode mfn_depend_node(node); + if (node.hasFn(MFn::kSurfaceShader) || node.hasFn(MFn::kHwShaderNode) || + node.hasFn(MFn::kPluginHardwareShader) || + node.hasFn(MFn::kPluginHwShaderNode)) { + out_shader_node = node; + out_shader_node_type = node.apiType(); + MMSOLVER_MAYA_VRB("Validated shader node: " + << " path=" << mfn_depend_node.name().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Shader node is not correct type:" + << " path=" << mfn_depend_node.name().asChar() + << " type=" << node.apiTypeStr()); + } + } +} + +void find_camera_node_path(MObject &node, MString &attr_name, + MDagPath &out_camera_node_path, + MFn::Type &out_camera_node_type) { + const auto verbose = false; + + MPlugArray connections; + bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); + if (!ok) { + return; + } + + for (uint32_t i = 0; i < connections.length(); ++i) { + MObject node = connections[i].node(); + + if (node.hasFn(MFn::kCamera)) { + MDagPath path; + MDagPath::getAPathTo(node, path); + out_camera_node_path = path; + out_camera_node_type = path.apiType(); + MMSOLVER_MAYA_VRB("Validated camera node: " + << " path=" + << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + break; + } else { + MMSOLVER_MAYA_WRN("Camera node is not correct type:" + << " path=" + << out_camera_node_path.fullPathName().asChar() + << " type=" << node.apiTypeStr()); + } + } +} + +} // namespace mmsolver diff --git a/src/mmSolver/shape/ImagePlaneUtils.h b/src/mmSolver/shape/ImagePlaneUtils.h new file mode 100644 index 000000000..81b00b6f7 --- /dev/null +++ b/src/mmSolver/shape/ImagePlaneUtils.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_IMAGE_PLANE_UTILS_H +#define MM_IMAGE_PLANE_UTILS_H + +// Maya +#include +#include +#include +#include +#include +#include +#include + +// Maya Viewport 2.0 +#include +#include +#include +#include +#include + +// MM Solver +#include + +#include "ImageCache.h" +#include "ImagePlaneShapeNode.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { + +enum class ImageDisplayChannel { + kRGBA = 0, + kRGB, + kRed, + kGreen, + kBlue, + kAlpha, + kLuminance +}; + +bool getUpstreamNodeFromConnection(const MObject &this_node, + const MString &attr_name, + MPlugArray &out_connections); + +void calculate_node_image_size_string( + MDagPath &objPath, MObject &draw_image_size_attr, MObject &image_width_attr, + MObject &image_height_attr, MObject &image_pixel_aspect_attr, + const uint32_t int_precision, const uint32_t double_precision, + bool &out_draw_image_size, MString &out_image_size); + +void calculate_node_camera_size_string(MDagPath &objPath, + MObject &draw_camera_size_attr, + MObject &camera_width_inch_attr, + MObject &camera_height_inch_attr, + const uint32_t double_precision, + bool &out_draw_camera_size, + MString &out_camera_size); + +void find_geometry_node_path(MObject &node, MString &attr_name, + MDagPath &out_geometry_node_path, + MFn::Type &out_geometry_node_type); + +void find_shader_node_path(MObject &node, MString &attr_name, + MObject &out_shader_node, + MFn::Type &out_shader_node_type); + +void find_camera_node_path(MObject &node, MString &attr_name, + MDagPath &out_camera_node_path, + MFn::Type &out_camera_node_type); + +} // namespace mmsolver + +#endif // MM_IMAGE_PLANE_UTILS_H From 72b61cfa63510661a8613ace9faa4887590321a3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 2 Jun 2024 22:57:35 +1000 Subject: [PATCH 051/295] Add GPU/CPU capacity and eviction to ImageCache. This allows allocating CPU and GPU memory, and automatic eviction (deallocation) of cached GPU/CPU memory, to keep a spesified number of images in the cache. The capacity is currently based on the number of images, not the amount of memory - this will be changed in the future. Tests show that the number of GPU images stored can be quite low, and we can upload to the GPU from the CPU cache fairly fast (50 fps). The next steps would be to pre-allocate the anticipated GPU images that will be loaded in the future and allocate them from a secondary thread (or concurrently) to ensure smooth playback. This would allow a small number of GPU images backed by a much larger number of cached CPU images. GitHub Issue #252. --- src/mmSolver/shape/ImageCache.cpp | 20 +- src/mmSolver/shape/ImageCache.h | 369 +++++++++++++++--- .../shape/ImagePlaneGeometry2Override.cpp | 4 + 3 files changed, 333 insertions(+), 60 deletions(-) diff --git a/src/mmSolver/shape/ImageCache.cpp b/src/mmSolver/shape/ImageCache.cpp index d01c65d4b..6c1f62f44 100644 --- a/src/mmSolver/shape/ImageCache.cpp +++ b/src/mmSolver/shape/ImageCache.cpp @@ -26,11 +26,10 @@ #include // STL -#include #include +#include #include #include -#include // Maya #include @@ -302,13 +301,18 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, // Duplicate the Maya-owned pixel data for our image cache. const size_t pixel_data_byte_count = width * height * number_of_channels * bytes_per_channel; - std::vector pixel_data_vec; - pixel_data_vec.resize(pixel_data_byte_count); - std::memcpy(pixel_data_vec.data(), maya_owned_pixel_data, + image_pixel_data = CacheImagePixelData(); + const bool allocated_ok = image_pixel_data.allocate_pixels( + width, height, number_of_channels, pixel_data_type); + if (allocated_ok == false) { + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_image_file: " + << "Could not allocate pixel data!"); + return nullptr; + } + assert(image_pixel_data.is_valid() == true); + assert(image_pixel_data.byte_count() == pixel_data_byte_count); + std::memcpy(image_pixel_data.pixel_data(), maya_owned_pixel_data, pixel_data_byte_count); - image_pixel_data = - CacheImagePixelData(static_cast(pixel_data_vec.data()), width, - height, number_of_channels, pixel_data_type); const bool cpu_inserted = image_cache.cpu_insert(key, image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " diff --git a/src/mmSolver/shape/ImageCache.h b/src/mmSolver/shape/ImageCache.h index 6f088fcb5..2dd2f0faa 100644 --- a/src/mmSolver/shape/ImageCache.h +++ b/src/mmSolver/shape/ImageCache.h @@ -23,6 +23,8 @@ #define MM_IMAGE_CACHE_H // STL +#include +#include #include #include @@ -101,6 +103,46 @@ struct CacheImagePixelData { ~CacheImagePixelData() = default; + bool allocate_pixels(const uint32_t width, const uint32_t height, + const uint8_t num_channels, + const CachePixelDataType pixel_data_type) { + m_width = width; + m_height = height; + m_num_channels = num_channels; + m_pixel_data_type = pixel_data_type; + + const size_t pixel_data_byte_count = CacheImagePixelData::byte_count(); + + if (pixel_data_byte_count == 0) { + MMSOLVER_MAYA_ERR("mmsolver::CacheImagePixelData:allocate_pixels: " + << "Invalid image size for allocating pixel data!" + << " width=" << m_width << " height=" << m_height + << " num_channels=" << m_num_channels + << " pixel_data_type=" + << static_cast(m_pixel_data_type)); + return false; + } + + bool ok = false; + void *data = std::malloc(pixel_data_byte_count); + if (data) { + m_pixel_data = data; + ok = true; + } else { + ok = false; + MMSOLVER_MAYA_ERR( + "mmsolver::CacheImagePixelData:allocate_pixels: " + << "Could not allocate pixel data!" + << " requested=" << pixel_data_byte_count << "B" + << " requested=" + << (static_cast(pixel_data_byte_count) * 1e-6) << "MB"); + } + + return ok; + } + + void deallocate_pixels() { std::free(m_pixel_data); } + bool is_valid() const { return (m_pixel_data != nullptr) && (m_width != 0) && (m_height != 0) && (m_num_channels != 0) && @@ -113,6 +155,12 @@ struct CacheImagePixelData { uint8_t num_channels() const { return m_num_channels; } CachePixelDataType pixel_data_type() const { return m_pixel_data_type; } + size_t byte_count() const { + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); + return m_width * m_height * m_num_channels * bytes_per_channel; + } + private: void *m_pixel_data; uint32_t m_width; @@ -124,7 +172,13 @@ struct CacheImagePixelData { // The ImageCache, used to load and cache images into GPU, CPU and // Disk. // -// Singleton design pattern for C++11 +// Least-Recently-Used (LRU) Cache using standard C++11 data +// structures. +// +// Inspired by: +// https://web.archive.org/web/20161203001546/http://timday.bitbucket.org/lru.html +// +// Singleton design pattern for C++11: // https://stackoverflow.com/a/1008289 // // TODO: Use 4 layers of reading textures. @@ -147,25 +201,26 @@ struct CacheImagePixelData { // then placed into a queue to be saved into the disk cache. The queue // of images are processed off the main thread, so that the // interactive session does not slow down. - -// TODO: Read image (and insert into cache). -// -// TODO: Set/Get CPU memory allowed. -// -// TODO: Set/Get GPU memory allowed. -// -// TODO: Set/Get Disk space allowed. -// -// TODO: Set/Get Disk cache location. Used to find disk-cached -// files. This should be a directory on a very fast disk. // // TODO: Support converting to 8-bit LDR image pixels before // storing. For example converting to sRGB colour space. +// struct ImageCache { - using CPUCacheKey = std::string; using GPUCacheKey = std::string; - using CPUMap = std::unordered_map; - using GPUMap = std::unordered_map; + using GPUCacheValue = MTexture *; + using GPUKeyList = std::list; + using GPUKeyListIt = GPUKeyList::iterator; + using GPUMap = + std::unordered_map>; + using GPUMapIt = GPUMap::iterator; + + using CPUCacheKey = std::string; + using CPUCacheValue = CacheImagePixelData; + using CPUKeyList = std::list; + using CPUKeyListIt = CPUKeyList::iterator; + using CPUMap = + std::unordered_map>; + using CPUMapIt = CPUMap::iterator; public: static ImageCache &getInstance() { @@ -176,41 +231,119 @@ struct ImageCache { private: // Constructor. The {} brackets are needed here. - ImageCache() {} + ImageCache() : m_cpu_capacity(1), m_gpu_capacity(1) {} + + template + void move_iterator_to_back_of_key_list(KeyList &key_list, + KeyListIt key_list_iterator) { + // The back of the list is the "most recently used" key. + key_list.splice(key_list.end(), key_list, key_list_iterator); + } public: - // Insert pixels into image CPU cache. + // TODO: Set/Get GPU memory allowed. + // TODO: Set/Get CPU memory allowed. + size_t get_gpu_capacity() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_capacity: " + << "m_cpu_capacity=" << m_cpu_capacity); + + return m_gpu_capacity; + } + size_t get_cpu_capacity() const { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity: " + << "m_gpu_capacity=" << m_gpu_capacity); + + return m_cpu_capacity; + } + + void set_gpu_capacity(const size_t capacity) { + const bool verbose = false; + + // TODO: Evict the contents of the cache, if the current + // number of cached items exceeds the new capacity size. + m_gpu_capacity = capacity; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity: " + << "m_gpu_capacity=" << m_gpu_capacity); + } + + void set_cpu_capacity(const size_t capacity) { + const bool verbose = false; + + // TODO: Evict the contents of the cache, if the current + // number of cached items exceeds the new capacity size. + m_cpu_capacity = capacity; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity: " + << "m_cpu_capacity=" << m_cpu_capacity); + } + + // TODO: Set/Get Disk space allowed. + // + // TODO: Set/Get Disk cache location. Used to find disk-cached + // files. This should be a directory on a very fast disk. + + // Insert pixels into CPU cache. // // If the key is already in the image cache, the previous value is // erased. + // + // Returns true/false, if the the data was inserted or not. bool cpu_insert(const CPUCacheKey &key, - const CacheImagePixelData &image_pixel_data) { - auto search = m_cpu_cache_map.find(key); + const CPUCacheValue &image_pixel_data) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " + << "key=" << key); + + const CPUMapIt search = m_cpu_cache_map.find(key); const bool found = search != m_cpu_cache_map.end(); if (found) { ImageCache::cpu_erase(key); } - const bool ok = m_cpu_cache_map.insert({key, image_pixel_data}).second; + // 'key' is the most-recently-used key. + CPUKeyListIt key_iterator = + m_cpu_cache_key_list.insert(m_cpu_cache_key_list.end(), key); + + const auto pair = m_cpu_cache_map.insert(std::make_pair( + key, std::make_pair(key_iterator, image_pixel_data))); + + const auto inserted_key_iterator = pair.first; + const bool ok = pair.second; + assert(ok == true); return ok; } - // Insert - MTexture *gpu_insert(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey &key, - const CacheImagePixelData &image_pixel_data) { + // Insert and upload some pixels to the GPU and cache the result. + // + // Returns the GPUCacheValue inserted into the GPU cache. + GPUCacheValue gpu_insert(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey &key, + const CPUCacheValue &image_pixel_data) { assert(texture_manager != nullptr); assert(image_pixel_data.is_valid()); const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert: " + << "key=" << key); + // No need for MIP-maps. const bool generate_mip_maps = false; MTexture *texture = nullptr; - auto search = m_gpu_cache_map.find(key); + const GPUMapIt search = m_gpu_cache_map.find(key); const bool found = search != m_gpu_cache_map.end(); if (!found) { + // If we are at capacity, make room for new entry. + if (m_gpu_cache_map.size() >= m_gpu_capacity) { + ImageCache::gpu_evict_one(texture_manager); + } + const uint32_t width = image_pixel_data.width(); const uint32_t height = image_pixel_data.height(); const uint8_t number_of_channels = image_pixel_data.num_channels(); @@ -267,8 +400,21 @@ struct ImageCache { return false; } + // Make 'key' the most-recently-used key. + GPUKeyListIt key_iterator = + m_gpu_cache_key_list.insert(m_gpu_cache_key_list.end(), key); + + // Create the key-value entry, linked to the usage record. + const auto pair = m_gpu_cache_map.insert( + std::make_pair(key, std::make_pair(key_iterator, texture))); + + const auto inserted_key_iterator = pair.first; + const bool ok = pair.second; + assert(ok == true); + } else { - texture = search->second; + GPUKeyListIt iterator = search->second.first; + texture = search->second.second; if (texture == nullptr) { MMSOLVER_MAYA_ERR( "mmsolver::ImageCache: gpu_insert: " @@ -276,15 +422,14 @@ struct ImageCache { return false; } - m_gpu_cache_map.erase(search); + move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); // TODO: If the 'texture_desc' is different than the // current texture, it cannot be updated, and must be // released and a new texture created instead. - // // ImageCache::gpu_erase(key); - // The default value of this argument is 0. This means to use - // the texture's width * the number of bytes per pixel. + // The default value of this argument is 0. This means to + // use the texture's "width * number of bytes per pixel". uint32_t rowPitch = 0; MHWRender::MTextureUpdateRegion *region = nullptr; @@ -294,55 +439,152 @@ struct ImageCache { CHECK_MSTATUS(status); } - const bool ok = m_gpu_cache_map.insert({key, texture}).second; - assert(ok == true); return texture; } - // Find the key in the image GPU cache. - MTexture *gpu_find(const GPUCacheKey &key) { - auto search = m_gpu_cache_map.find(key); + // Find the key in the GPU cache. + // + // Returns the GPUCacheValue at the key, or nullptr. + GPUCacheValue gpu_find(const GPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " + << "key=" << key); + + const GPUMapIt search = m_gpu_cache_map.find(key); const bool found = search != m_gpu_cache_map.end(); if (found) { - return search->second; + GPUKeyListIt iterator = search->second.first; + GPUCacheValue value = search->second.second; + move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); + return value; } return nullptr; } - // Find the key in the image CPU cache. - CacheImagePixelData cpu_find(const CPUCacheKey &key) { - auto search = m_cpu_cache_map.find(key); + // Find the key in the CPU cache. + // + // Returns the CPUCacheValue at the key, or constructs a default + // value and returns it. + CPUCacheValue cpu_find(const CPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " + << "key=" << key); + + const CPUMapIt search = m_cpu_cache_map.find(key); const bool found = search != m_cpu_cache_map.end(); if (found) { - return search->second; + CPUKeyListIt iterator = search->second.first; + CPUCacheValue value = search->second.second; + move_iterator_to_back_of_key_list(m_cpu_cache_key_list, iterator); + return value; + } + return CPUCacheValue(); + } + + // Evict the least recently used item from the GPU cache. + // + // Returns true/false, if an item was removed from the cache or + // not. + bool gpu_evict_one(MHWRender::MTextureManager *texture_manager) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); + + assert(texture_manager != nullptr); + if (m_gpu_cache_key_list.empty()) { + return false; + } + + const GPUCacheKey lru_key = m_gpu_cache_key_list.front(); + const GPUMapIt lru_key_iterator = m_gpu_cache_map.find(lru_key); + assert(lru_key_iterator != m_gpu_cache_map.end()); + + GPUCacheValue texture = lru_key_iterator->second.second; + texture_manager->releaseTexture(texture); + + m_gpu_cache_map.erase(lru_key_iterator); + m_gpu_cache_key_list.pop_front(); + return true; + } + + // Evict the least recently used item from the CPU cache. + // + // Returns true/false, if an item was removed from the cache or + // not. + bool cpu_evict_one() { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); + + if (m_cpu_cache_key_list.empty()) { + return false; } - return CacheImagePixelData(); + + const CPUCacheKey lru_key = m_cpu_cache_key_list.front(); + const CPUMapIt lru_key_iterator = m_cpu_cache_map.find(lru_key); + assert(lru_key_iterator != m_cpu_cache_map.end()); + + CPUCacheValue value = lru_key_iterator->second.second; + value.deallocate_pixels(); + + m_cpu_cache_map.erase(lru_key_iterator); + m_cpu_cache_key_list.pop_front(); + return true; } // Remove the key from the image GPU cache. + // + // NOTE: Due to the way the LRU cache works, this can be quite + // slow to to remove a specific key from the cache. + // + // Returns true/false, if the key was removed or not. bool gpu_erase(MHWRender::MTextureManager *texture_manager, const GPUCacheKey &key) { - const auto search = m_gpu_cache_map.find(key); + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: "); + + const GPUMapIt search = m_gpu_cache_map.find(key); const bool found = search != m_gpu_cache_map.end(); if (found) { - MTexture *texture = search->second; + GPUCacheValue value = search->second.second; assert(texture_manager != nullptr); - texture_manager->releaseTexture(texture); + texture_manager->releaseTexture(value); + m_gpu_cache_map.erase(search); + + // NOTE: This is a O(n) linear operation, and can be very + // slow since the list items is spread out in memory. + m_gpu_cache_key_list.remove(key); } return found; } // Remove the key from the image CPU cache. + // + // NOTE: Due to the way the LRU cache works, this can be quite + // slow to to remove a specific key from the cache. + // + // Returns true/false, if the key was removed or not. bool cpu_erase(const CPUCacheKey &key) { - auto search = m_cpu_cache_map.find(key); + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: "); + + const CPUMapIt search = m_cpu_cache_map.find(key); const bool found = search != m_cpu_cache_map.end(); if (found) { - CacheImagePixelData value = search->second; - // TODO: De-allocate the cache value. We must clear the - // existing memory before adding the new pixel data. + CPUCacheValue value = search->second.second; + value.deallocate_pixels(); + m_cpu_cache_map.erase(search); + + // NOTE: This is a O(n) linear operation, and can be very + // slow since the list items is spread out in memory. + m_cpu_cache_key_list.remove(key); } return found; } @@ -358,13 +600,36 @@ struct ImageCache { void operator=(ImageCache const &) = delete; private: - // TODO: Implement an LRU with standard C++. + // A Map of keys to values. // - // https://web.archive.org/web/20161203001546/http://timday.bitbucket.org/lru.html - // https://bitbucket.org/timday/lru_cache/src/master/include/lru_cache_using_std.h + // An unordered hash map is used to map file path strings to CPU + // Image Data and GPU texture resources. // - CPUMap m_cpu_cache_map; + // These maps also contain pointers into the 'key list' data + // structures, allowing us to map from the values to the 'key + // list's. GPUMap m_gpu_cache_map; + CPUMap m_cpu_cache_map; + + // List of keys. + // + // A double-ended linked list (std::list) is used because we want + // to be able to push/pop and access the (front/back) ends of the + // list as fast as possible. + // + // Additionally, the linked list ensure that the pointers of each + // item in the list stays consistent over time, so that pointers + // into the memory can be used directly without fear that the + // underlying memory will be cleared. + // + // The 'front' of the list is the "least recently used" key. The + // 'back' of the list is the "most recently used" key. + GPUKeyList m_gpu_cache_key_list; + CPUKeyList m_cpu_cache_key_list; + + // Number of items allowed in the cache. + size_t m_gpu_capacity; + size_t m_cpu_capacity; // Pre-allocated empty string to be re-used inside the class, to // avoid reallocations. diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index a6732c269..b7c1dd06f 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -662,6 +662,10 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const bool do_texture_update = false; ImageCache &image_cache = ImageCache::getInstance(); + // TODO: Set the capacity using a command, and use sensible + // defaults. + image_cache.set_gpu_capacity(10); + image_cache.set_cpu_capacity(1000); out_color_texture = read_image_file(textureManager, image_cache, m_temp_image, expanded_file_path, pixel_type, do_texture_update); From 0623b24a6fd884e9a64fc983638317ca0af1a600 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 5 Jun 2024 20:45:43 +1000 Subject: [PATCH 052/295] Respects Image Cache CPU/GPU memory capacity. We add a "capacity bytes" getter/setter for the GPU and CPU. We also keep track of how much memory is currently used inside the cache, not just the total capacity. The MTexture pointer is now wrapped in a struct, CacheTextureData, so we can calculate the amount of memory that was used, and dimensions of the texture. CacheTextureData takes control of it's own GPU memory allocation, and deallocation using the underlying MTextureManager pointer. GitHub issue #252. --- src/mmSolver/shape/ImageCache.cpp | 406 +++++++++++- src/mmSolver/shape/ImageCache.h | 596 +++++++++--------- .../shape/ImagePlaneGeometry2Override.cpp | 13 +- 3 files changed, 717 insertions(+), 298 deletions(-) diff --git a/src/mmSolver/shape/ImageCache.cpp b/src/mmSolver/shape/ImageCache.cpp index 6c1f62f44..01eeb499e 100644 --- a/src/mmSolver/shape/ImageCache.cpp +++ b/src/mmSolver/shape/ImageCache.cpp @@ -50,6 +50,53 @@ namespace mmsolver { +// Get GPU memory info from the Maya API: +// +// MRenderer::GPUtotalMemorySize() +// MRenderer::GPUUsedMemorySize() +// +// +// These methods can be used to inform Maya's internal system of any +// GPU memory that we allocate/deallocate: +// +// MRenderer::holdGPUMemory(); +// MRenderer::releaseGPUMemory(); +// +// +// How to get the amount of (CPU) system memory? +// https://stackoverflow.com/questions/2513505/how-to-get-available-memory-c-g +// https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ +// +// +// On UNIX-like operating systems, use sysconf to get the amount of +// system memory: +// +// #include +// unsigned long long getTotalSystemMemory() +// { +// long pages = sysconf(_SC_PHYS_PAGES); +// long page_size = sysconf(_SC_PAGE_SIZE); +// return pages * page_size; +// } +// +// +// On Windows, use GlobalMemoryStatusEx: +// +// #include +// unsigned long long getTotalSystemMemory() +// { +// MEMORYSTATUSEX status; +// status.dwLength = sizeof(status); +// GlobalMemoryStatusEx(&status); +// return status.ullTotalPhys; +// } +// +// +// Helpful code: +// https://github.com/david-cattermole/cpp-utilities/blob/master/include/fileSystemUtils.h +// https://github.com/david-cattermole/cpp-utilities/blob/master/include/hashUtils.h +// https://github.com/david-cattermole/cpp-utilities/blob/master/include/osMemoryUtils.h + void *get_mimage_pixel_data(const MImage &image, const CachePixelDataType pixel_data_type, const uint32_t width, const uint32_t height, @@ -215,17 +262,17 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, } std::string key = std::string(resolved_file_path.asChar()); - MTexture *texture = image_cache.gpu_find(key); + CacheTextureData texture_data = image_cache.gpu_find(key); - MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache: read_image_file: findTexture: " << texture); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: findTexture: " + << texture_data.is_valid()); MMSOLVER_MAYA_VRB( "mmsolver::ImageCache: read_image_file: do_texture_update=" << do_texture_update); - if (texture && (do_texture_update == false)) { + if (texture_data.is_valid() && (do_texture_update == false)) { MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE1:" - << " texture=" << texture); - return texture; + << " texture=" << texture_data.texture()); + return texture_data.texture(); } // TODO: We should test if the file exists, then cache @@ -293,10 +340,10 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, CacheImagePixelData(static_cast(maya_owned_pixel_data), width, height, number_of_channels, pixel_data_type); - texture = + texture_data = image_cache.gpu_insert(texture_manager, key, gpu_image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " - << "gpu_inserted=" << texture); + << "gpu_inserted=" << texture_data.texture()); // Duplicate the Maya-owned pixel data for our image cache. const size_t pixel_data_byte_count = @@ -319,9 +366,348 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, << "cpu_inserted=" << cpu_inserted); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE2:" - << " texture=" << texture); + << " texture=" << texture_data.texture()); + + return texture_data.texture(); +} + +bool ImageCache::cpu_insert(const CPUCacheKey &key, + const CPUCacheValue &image_pixel_data) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " + << "key=" << key); + + const CPUMapIt search = m_cpu_cache_map.find(key); + const bool found = search != m_cpu_cache_map.end(); + if (found) { + ImageCache::cpu_erase(key); + } + + // If we are at capacity, make room for new entry. + const size_t image_data_size = image_pixel_data.byte_count(); + const bool evict_ok = + ImageCache::cpu_evict_enough_for_new_entry(image_data_size); + if (!evict_ok) { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache::cpu_insert: evicting memory failed!"); + } + + m_cpu_used_bytes += image_data_size; + assert(m_cpu_used_bytes <= m_cpu_capacity_bytes); + + // Because we are inserting into the cache, the 'key' is the + // most-recently-used item. + CPUKeyListIt key_iterator = + m_cpu_cache_key_list.insert(m_cpu_cache_key_list.end(), key); + + const auto pair = m_cpu_cache_map.insert( + std::make_pair(key, std::make_pair(key_iterator, image_pixel_data))); + + const auto inserted_key_iterator = pair.first; + const bool ok = pair.second; + assert(ok == true); + return ok; +} + +void update_texture(MTexture *texture, + const ImageCache::CPUCacheValue &image_pixel_data) { + const bool verbose = false; + + // No need for MIP-maps. + const bool generate_mip_maps = false; + + // TODO: If the 'texture_desc' is different than the + // current texture, it cannot be updated, and must be + // released and a new texture created instead. + + // The default value of this argument is 0. This means to + // use the texture's "width * number of bytes per pixel". + uint32_t rowPitch = 0; + + MHWRender::MTextureUpdateRegion *region = nullptr; + void *pixel_data = image_pixel_data.pixel_data(); + MStatus status = + texture->update(pixel_data, generate_mip_maps, rowPitch, region); + CHECK_MSTATUS(status); +} + +ImageCache::GPUCacheValue ImageCache::gpu_insert( + MHWRender::MTextureManager *texture_manager, const GPUCacheKey &key, + const ImageCache::CPUCacheValue &image_pixel_data) { + assert(texture_manager != nullptr); + assert(image_pixel_data.is_valid()); + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert: " + << "key=" << key); + + GPUCacheValue texture_data = GPUCacheValue(); + const ImageCache::GPUMapIt search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (!found) { + // If we are at capacity, make room for new entry. + const size_t image_data_size = image_pixel_data.byte_count(); + const bool evict_ok = ImageCache::gpu_evict_enough_for_new_entry( + texture_manager, image_data_size); + if (!evict_ok) { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache::gpu_insert: evicting memory failed!"); + } + + const bool allocate_ok = texture_data.allocate_texture( + texture_manager, image_pixel_data.pixel_data(), + image_pixel_data.width(), image_pixel_data.height(), + image_pixel_data.num_channels(), + image_pixel_data.pixel_data_type()); + if (!allocate_ok) { + MMSOLVER_MAYA_ERR( + "mmsolver::ImageCache: gpu_insert: " + "Could not allocate texture!"); + } + + if (!texture_data.is_valid()) { + return GPUCacheValue(); + } + + m_gpu_used_bytes += texture_data.byte_count(); + assert(m_gpu_used_bytes <= m_gpu_capacity_bytes); + + // Make 'key' the most-recently-used key, because when we + // insert an item into the cache, it's used most recently. + ImageCache::GPUKeyListIt key_iterator = + m_gpu_cache_key_list.insert(m_gpu_cache_key_list.end(), key); + + // Create the key-value entry, linked to the usage record. + const auto pair = m_gpu_cache_map.insert( + std::make_pair(key, std::make_pair(key_iterator, texture_data))); + + const auto inserted_key_iterator = pair.first; + const bool ok = pair.second; + assert(ok == true); + } else { + ImageCache::GPUKeyListIt iterator = search->second.first; + texture_data = search->second.second; + if (!texture_data.is_valid()) { + MMSOLVER_MAYA_ERR( + "mmsolver::ImageCache: gpu_insert: " + "Found texture is invalid!"); + return ImageCache::GPUCacheValue(); + } + + move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); + + update_texture(texture_data.texture(), image_pixel_data); + } + + return texture_data; +} + +ImageCache::GPUCacheValue ImageCache::gpu_find(const GPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " + << "key=" << key); + + const GPUMapIt search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (found) { + GPUKeyListIt iterator = search->second.first; + GPUCacheValue value = search->second.second; + move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); + return value; + } + return GPUCacheValue(); +} + +ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " + << "key=" << key); + + const CPUMapIt search = m_cpu_cache_map.find(key); + const bool found = search != m_cpu_cache_map.end(); + if (found) { + CPUKeyListIt iterator = search->second.first; + CPUCacheValue value = search->second.second; + move_iterator_to_back_of_key_list(m_cpu_cache_key_list, iterator); + return value; + } + return CPUCacheValue(); +} + +bool ImageCache::gpu_evict_one(MHWRender::MTextureManager *texture_manager) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); - return texture; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_evict_one: " + "before m_gpu_used_bytes=" + << m_gpu_used_bytes); + + assert(texture_manager != nullptr); + if (m_gpu_cache_key_list.empty()) { + return false; + } + + const GPUCacheKey lru_key = m_gpu_cache_key_list.front(); + const GPUMapIt lru_key_iterator = m_gpu_cache_map.find(lru_key); + assert(lru_key_iterator != m_gpu_cache_map.end()); + + GPUCacheValue texture_data = lru_key_iterator->second.second; + m_gpu_used_bytes -= texture_data.byte_count(); + texture_data.deallocate_texture(texture_manager); + + m_gpu_cache_map.erase(lru_key_iterator); + m_gpu_cache_key_list.pop_front(); + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_evict_one: " + "after m_gpu_used_bytes=" + << m_gpu_used_bytes); + return true; +} + +bool ImageCache::cpu_evict_one() { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_evict_one: " + "before m_cpu_used_bytes=" + << m_cpu_used_bytes); + + if (m_cpu_cache_key_list.empty()) { + return false; + } + + const CPUCacheKey lru_key = m_cpu_cache_key_list.front(); + const CPUMapIt lru_key_iterator = m_cpu_cache_map.find(lru_key); + assert(lru_key_iterator != m_cpu_cache_map.end()); + + CPUCacheValue image_pixel_data = lru_key_iterator->second.second; + m_cpu_used_bytes -= image_pixel_data.byte_count(); + image_pixel_data.deallocate_pixels(); + + m_cpu_cache_map.erase(lru_key_iterator); + m_cpu_cache_key_list.pop_front(); + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_evict_one: " + "after m_cpu_used_bytes=" + << m_cpu_used_bytes); + return true; +} + +bool ImageCache::gpu_evict_enough_for_new_entry( + MHWRender::MTextureManager *texture_manager, + const size_t new_memory_chunk_size) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_entry: "); + + if (m_gpu_cache_key_list.empty()) { + return false; + } + + // If we are at capacity remove the least recently used items + // until we have enough room to store 'new_memory_chunk_size'. + size_t new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " + "new_used_bytes=" + << new_used_bytes); + while (new_used_bytes > m_gpu_capacity_bytes) { + const bool ok = ImageCache::gpu_evict_one(texture_manager); + if (!ok) { + break; + } + new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " + "new_used_bytes=" + << new_used_bytes); + } + + return true; +} + +bool ImageCache::cpu_evict_enough_for_new_entry( + const size_t new_memory_chunk_size) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_entry: "); + + if (m_cpu_cache_key_list.empty()) { + return false; + } + + // If we are at capacity remove the least recently used items + // until we have enough room to store 'new_memory_chunk_size'. + size_t new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " + "new_used_bytes=" + << new_used_bytes); + while (new_used_bytes > m_cpu_capacity_bytes) { + const bool ok = ImageCache::cpu_evict_one(); + if (!ok) { + break; + } + new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " + "new_used_bytes=" + << new_used_bytes); + } + + return true; +} + +bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: "); + + const GPUMapIt search = m_gpu_cache_map.find(key); + const bool found = search != m_gpu_cache_map.end(); + if (found) { + GPUCacheValue texture_data = search->second.second; + m_gpu_used_bytes -= texture_data.byte_count(); + texture_data.deallocate_texture(texture_manager); + + m_gpu_cache_map.erase(search); + + // NOTE: This is a O(n) linear operation, and can be very + // slow since the list items is spread out in memory. + m_gpu_cache_key_list.remove(key); + } + return found; +} + +bool ImageCache::cpu_erase(const CPUCacheKey &key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: "); + + const CPUMapIt search = m_cpu_cache_map.find(key); + const bool found = search != m_cpu_cache_map.end(); + if (found) { + CPUCacheValue image_pixel_data = search->second.second; + m_cpu_used_bytes -= image_pixel_data.byte_count(); + image_pixel_data.deallocate_pixels(); + + m_cpu_cache_map.erase(search); + + // NOTE: This is a O(n) linear operation, and can be very + // slow since the list items is spread out in memory. + m_cpu_cache_key_list.remove(key); + } + return found; } } // namespace mmsolver diff --git a/src/mmSolver/shape/ImageCache.h b/src/mmSolver/shape/ImageCache.h index 2dd2f0faa..5faca7b0f 100644 --- a/src/mmSolver/shape/ImageCache.h +++ b/src/mmSolver/shape/ImageCache.h @@ -42,6 +42,10 @@ namespace mmsolver { +// Pre-allocated empty string to be re-used inside the class, to +// avoid reallocations. +const MString g_empty_string; + enum class CachePixelDataType : uint8_t { kU8 = 0, kF32, @@ -169,6 +173,124 @@ struct CacheImagePixelData { CachePixelDataType m_pixel_data_type; }; +struct CacheTextureData { + CacheTextureData() + : m_texture(nullptr) + , m_width(0) + , m_height(0) + , m_num_channels(0) + , m_pixel_data_type(CachePixelDataType::kUnknown){}; + + CacheTextureData(MTexture *texture, const uint32_t width, + const uint32_t height, const uint8_t num_channels, + const CachePixelDataType pixel_data_type) + : m_texture(texture) + , m_width(width) + , m_height(height) + , m_num_channels(num_channels) + , m_pixel_data_type(pixel_data_type){}; + + ~CacheTextureData() = default; + + bool allocate_texture(MHWRender::MTextureManager *texture_manager, + void *pixel_data, const uint32_t width, + const uint32_t height, const uint8_t num_channels, + const CachePixelDataType pixel_data_type) { + assert(texture_manager != nullptr); + + m_width = width; + m_height = height; + m_num_channels = num_channels; + m_pixel_data_type = pixel_data_type; + + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); + const size_t pixel_data_byte_count = CacheTextureData::byte_count(); + + if (pixel_data_byte_count == 0) { + MMSOLVER_MAYA_ERR("mmsolver::CacheTextureData:allocate_pixels: " + << "Invalid image size for allocating pixel data!" + << " width=" << m_width << " height=" << m_height + << " num_channels=" << m_num_channels + << " pixel_data_type=" + << static_cast(m_pixel_data_type)); + return false; + } + + bool ok = false; + + MHWRender::MTextureDescription texture_desc; + texture_desc.setToDefault2DTexture(); + texture_desc.fWidth = m_width; + texture_desc.fHeight = m_height; + texture_desc.fDepth = 1; + + texture_desc.fMipmaps = 1; + texture_desc.fArraySlices = 1; + texture_desc.fTextureType = MHWRender::kImage2D; + texture_desc.fFormat = + convert_pixel_data_type_to_texture_format(m_pixel_data_type); + + texture_desc.fBytesPerRow = + m_num_channels * bytes_per_channel * m_width; + texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * m_height; + + // No need for MIP-maps. + const bool generate_mip_maps = false; + + // If the texture name provided is an empty string then the + // texture will not be cached as part of the internal texture + // caching system. Thus each such call to this method will + // create a new texture. + MTexture *texture = texture_manager->acquireTexture( + g_empty_string, texture_desc, pixel_data, generate_mip_maps); + if (texture) { + m_texture = texture; + ok = true; + } else { + ok = false; + MMSOLVER_MAYA_ERR( + "mmsolver::CacheTextureData:allocate_texture: " + << "Could not acquire texture!" + << " requested=" << pixel_data_byte_count << "B" + << " requested=" + << (static_cast(pixel_data_byte_count) * 1e-6) << "MB"); + } + + return ok; + } + + void deallocate_texture(MHWRender::MTextureManager *texture_manager) { + assert(texture_manager != nullptr); + texture_manager->releaseTexture(m_texture); + } + + bool is_valid() const { + return (m_texture != nullptr) && (m_width != 0) && (m_height != 0) && + (m_num_channels != 0) && + (m_pixel_data_type != CachePixelDataType::kUnknown); + }; + + MTexture *texture() const { return m_texture; }; + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + uint8_t num_channels() const { return m_num_channels; } + CachePixelDataType pixel_data_type() const { return m_pixel_data_type; } + + size_t byte_count() const { + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); + return m_width * m_height * m_num_channels * bytes_per_channel; + } + +private: + MTexture *m_texture; + uint32_t m_width; + uint32_t m_height; + uint8_t m_num_channels; + CachePixelDataType m_pixel_data_type; +}; + // The ImageCache, used to load and cache images into GPU, CPU and // Disk. // @@ -203,11 +325,16 @@ struct CacheImagePixelData { // interactive session does not slow down. // // TODO: Support converting to 8-bit LDR image pixels before -// storing. For example converting to sRGB colour space. +// storing. For example converting to sRGB colour space, and applying +// tone-mapping to avoid clipping colours, so that as much detail is +// retained - even if it's not colour accurate. // +const size_t MEGABYTES_TO_BYTES = 1e+6; +const double kBYTES_TO_MEGABYTES = 1e-6; + struct ImageCache { using GPUCacheKey = std::string; - using GPUCacheValue = MTexture *; + using GPUCacheValue = CacheTextureData; using GPUKeyList = std::list; using GPUKeyListIt = GPUKeyList::iterator; using GPUMap = @@ -231,7 +358,13 @@ struct ImageCache { private: // Constructor. The {} brackets are needed here. - ImageCache() : m_cpu_capacity(1), m_gpu_capacity(1) {} + ImageCache() + : m_gpu_min_item_count(1) + , m_cpu_min_item_count(1) + , m_gpu_capacity_bytes(200 * MEGABYTES_TO_BYTES) + , m_cpu_capacity_bytes(1000 * MEGABYTES_TO_BYTES) + , m_gpu_used_bytes(0) + , m_cpu_used_bytes(0) {} template void move_iterator_to_back_of_key_list(KeyList &key_list, @@ -241,50 +374,171 @@ struct ImageCache { } public: - // TODO: Set/Get GPU memory allowed. - // TODO: Set/Get CPU memory allowed. - size_t get_gpu_capacity() const { + // TODO: Allow a minimum number of items to be cached. For example + // even if the GPU memory capacity is zero bytes, we allow at + // least N items, "no questions asked". + size_t get_gpu_min_item_count() const { const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_capacity: " - << "m_cpu_capacity=" << m_cpu_capacity); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_min_item_count: " + << "m_gpu_min_item_count=" << m_gpu_min_item_count); - return m_gpu_capacity; + return m_gpu_min_item_count; } - size_t get_cpu_capacity() const { + size_t get_cpu_min_item_count() const { const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity: " - << "m_gpu_capacity=" << m_gpu_capacity); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_min_item_count: " + << "m_cpu_min_item_count=" << m_cpu_min_item_count); - return m_cpu_capacity; + return m_cpu_min_item_count; } - void set_gpu_capacity(const size_t capacity) { + void set_gpu_min_item_count(const size_t value) { const bool verbose = false; // TODO: Evict the contents of the cache, if the current // number of cached items exceeds the new capacity size. - m_gpu_capacity = capacity; + m_gpu_min_item_count = value; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity: " - << "m_gpu_capacity=" << m_gpu_capacity); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_min_item_count: " + << "m_gpu_min_item_count=" << m_gpu_min_item_count); } - void set_cpu_capacity(const size_t capacity) { + void set_cpu_min_item_count(const size_t value) { const bool verbose = false; // TODO: Evict the contents of the cache, if the current // number of cached items exceeds the new capacity size. - m_cpu_capacity = capacity; + m_cpu_min_item_count = value; + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_min_item_count: " + << "m_cpu_min_item_count=" << m_cpu_min_item_count); + } + + // Get the capacity of the GPU and CPU. + size_t get_gpu_capacity_bytes() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity_bytes: " + << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); + return m_gpu_capacity_bytes; + } + size_t get_cpu_capacity_bytes() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_capacity_bytes: " + << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); + return m_cpu_capacity_bytes; + } + size_t get_gpu_used_bytes() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_used_bytes: " + << "m_gpu_used_bytes=" << m_gpu_used_bytes); + return m_gpu_used_bytes; + } + size_t get_cpu_used_bytes() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_used_bytes: " + << "m_cpu_used_bytes=" << m_cpu_used_bytes); + return m_cpu_used_bytes; + } + + void print_cache_details() const { + MMSOLVER_MAYA_INFO( + "mmsolver::ImageCache::print_cache_details:" + << " GPU cache item count=" + << m_gpu_cache_map.size() + // << " GPU min item count=" << m_gpu_min_item_count + << " used MB=" + << (static_cast(m_gpu_used_bytes) * kBYTES_TO_MEGABYTES) + << " capacity MB=" + << (static_cast(m_gpu_capacity_bytes) * kBYTES_TO_MEGABYTES) + << " percent=" + << (static_cast(m_gpu_used_bytes) / + static_cast(m_gpu_capacity_bytes))); + MMSOLVER_MAYA_INFO( + "mmsolver::ImageCache::print_cache_details:" + << " CPU cache item count=" + << m_cpu_cache_map.size() + // << " CPU min item count=" << m_cpu_min_item_count + << " used MB=" + << (static_cast(m_cpu_used_bytes) * kBYTES_TO_MEGABYTES) + << " capacity MB=" + << (static_cast(m_cpu_capacity_bytes) * kBYTES_TO_MEGABYTES) + << " percent=" + << (static_cast(m_cpu_used_bytes) / + static_cast(m_cpu_capacity_bytes))); + return; + } - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity: " - << "m_cpu_capacity=" << m_cpu_capacity); + // Set the capacity of the GPU. + // + // Note: Setting a lower value than what is already used will + // cause the cached memory to be evicted until the new memory + // capacity is reached. + void set_gpu_capacity_bytes(MHWRender::MTextureManager *texture_manager, + const size_t value) { + const bool verbose = false; + m_gpu_capacity_bytes = value; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " + << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); + + // Because we must always ensure our used memory is less than + // the given capacity. + if ((m_gpu_used_bytes > m_gpu_capacity_bytes) && + m_gpu_cache_key_list.empty()) { + // If we are at capacity remove the least recently used items + // until our capacity is under 'new_used_bytes'. + while (m_gpu_used_bytes > m_gpu_capacity_bytes) { + const bool ok = ImageCache::gpu_evict_one(texture_manager); + if (!ok) { + break; + } + } + } } - // TODO: Set/Get Disk space allowed. + // Set the capacity of the CPU. // + // Note: Setting a lower value than what is already used will + // cause the cached memory to be evicted until the new memory + // capacity is reached. + void set_cpu_capacity_bytes(const size_t value) { + const bool verbose = false; + m_cpu_capacity_bytes = value; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " + << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); + + // Because we must always ensure our used memory is less than + // the given capacity. + if ((m_cpu_used_bytes > m_cpu_capacity_bytes) && + m_cpu_cache_key_list.empty()) { + // If we are at capacity remove the least recently used items + // until our capacity is under 'new_used_bytes'. + while (m_cpu_used_bytes > m_cpu_capacity_bytes) { + const bool ok = ImageCache::cpu_evict_one(); + if (!ok) { + break; + } + } + } + } + // TODO: Set/Get Disk cache location. Used to find disk-cached // files. This should be a directory on a very fast disk. + // + // This class should allow a set of threads used for writing CPU + // cache data to image files. These files should be very fast to + // read into the CPU memory. + // + // We would need methods such as: + // + // bool write_cpu_item_to_disk(const CPUCacheKey &key, const bool + // background=true); + // + // bool wait_for_disk_writes(); + // + // bool disk_find(const DiskCacheKey &key); + // + // bool disk_find_file_path(const DiskCacheKey &key); // Insert pixels into CPU cache. // @@ -293,246 +547,49 @@ struct ImageCache { // // Returns true/false, if the the data was inserted or not. bool cpu_insert(const CPUCacheKey &key, - const CPUCacheValue &image_pixel_data) { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " - << "key=" << key); - - const CPUMapIt search = m_cpu_cache_map.find(key); - - const bool found = search != m_cpu_cache_map.end(); - if (found) { - ImageCache::cpu_erase(key); - } - - // 'key' is the most-recently-used key. - CPUKeyListIt key_iterator = - m_cpu_cache_key_list.insert(m_cpu_cache_key_list.end(), key); - - const auto pair = m_cpu_cache_map.insert(std::make_pair( - key, std::make_pair(key_iterator, image_pixel_data))); - - const auto inserted_key_iterator = pair.first; - const bool ok = pair.second; - assert(ok == true); - return ok; - } + const CPUCacheValue &image_pixel_data); // Insert and upload some pixels to the GPU and cache the result. // // Returns the GPUCacheValue inserted into the GPU cache. GPUCacheValue gpu_insert(MHWRender::MTextureManager *texture_manager, const GPUCacheKey &key, - const CPUCacheValue &image_pixel_data) { - assert(texture_manager != nullptr); - assert(image_pixel_data.is_valid()); - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert: " - << "key=" << key); - - // No need for MIP-maps. - const bool generate_mip_maps = false; - - MTexture *texture = nullptr; - const GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); - if (!found) { - // If we are at capacity, make room for new entry. - if (m_gpu_cache_map.size() >= m_gpu_capacity) { - ImageCache::gpu_evict_one(texture_manager); - } - - const uint32_t width = image_pixel_data.width(); - const uint32_t height = image_pixel_data.height(); - const uint8_t number_of_channels = image_pixel_data.num_channels(); - const CachePixelDataType pixel_data_type = - image_pixel_data.pixel_data_type(); - const uint8_t bytes_per_channel = - convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); - void *pixel_data = image_pixel_data.pixel_data(); - - MHWRender::MTextureDescription texture_desc; - texture_desc.setToDefault2DTexture(); - texture_desc.fWidth = width; - texture_desc.fHeight = height; - texture_desc.fDepth = 1; - - texture_desc.fMipmaps = 1; - texture_desc.fArraySlices = 1; - texture_desc.fTextureType = MHWRender::kImage2D; - texture_desc.fFormat = - convert_pixel_data_type_to_texture_format(pixel_data_type); - - texture_desc.fBytesPerRow = - number_of_channels * bytes_per_channel * width; - texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * height; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " - << "width=" << width << " height=" << height - << " number_of_channels=" - << static_cast(number_of_channels)); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " - << "bytes_per_channel=" - << static_cast(bytes_per_channel)); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " - << "pixel_data_type=" - << static_cast(pixel_data_type)); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert: " - << "pixel_data=" << pixel_data); - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: gpu_insert:" - << " fBytesPerRow=" << texture_desc.fBytesPerRow - << " fBytesPerSlice=" - << texture_desc.fBytesPerSlice); - - // If the texture name provided is an empty string then the - // texture will not be cached as part of the internal texture - // caching system. Thus each such call to this method will - // create a new texture. - texture = texture_manager->acquireTexture( - m_empty_string, texture_desc, pixel_data, generate_mip_maps); - if (texture == nullptr) { - MMSOLVER_MAYA_ERR( - "mmsolver::ImageCache: gpu_insert: " - "Could not acquire texture!"); - return false; - } - - // Make 'key' the most-recently-used key. - GPUKeyListIt key_iterator = - m_gpu_cache_key_list.insert(m_gpu_cache_key_list.end(), key); - - // Create the key-value entry, linked to the usage record. - const auto pair = m_gpu_cache_map.insert( - std::make_pair(key, std::make_pair(key_iterator, texture))); - - const auto inserted_key_iterator = pair.first; - const bool ok = pair.second; - assert(ok == true); - - } else { - GPUKeyListIt iterator = search->second.first; - texture = search->second.second; - if (texture == nullptr) { - MMSOLVER_MAYA_ERR( - "mmsolver::ImageCache: gpu_insert: " - "Found texture is invalid!"); - return false; - } - - move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); - - // TODO: If the 'texture_desc' is different than the - // current texture, it cannot be updated, and must be - // released and a new texture created instead. - - // The default value of this argument is 0. This means to - // use the texture's "width * number of bytes per pixel". - uint32_t rowPitch = 0; - - MHWRender::MTextureUpdateRegion *region = nullptr; - void *pixel_data = image_pixel_data.pixel_data(); - MStatus status = texture->update(pixel_data, generate_mip_maps, - rowPitch, region); - CHECK_MSTATUS(status); - } - - return texture; - } + const CPUCacheValue &image_pixel_data); // Find the key in the GPU cache. // // Returns the GPUCacheValue at the key, or nullptr. - GPUCacheValue gpu_find(const GPUCacheKey &key) { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " - << "key=" << key); - - const GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); - if (found) { - GPUKeyListIt iterator = search->second.first; - GPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); - return value; - } - return nullptr; - } + GPUCacheValue gpu_find(const GPUCacheKey &key); // Find the key in the CPU cache. // // Returns the CPUCacheValue at the key, or constructs a default // value and returns it. - CPUCacheValue cpu_find(const CPUCacheKey &key) { - const bool verbose = false; + CPUCacheValue cpu_find(const CPUCacheKey &key); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " - << "key=" << key); - - const CPUMapIt search = m_cpu_cache_map.find(key); - const bool found = search != m_cpu_cache_map.end(); - if (found) { - CPUKeyListIt iterator = search->second.first; - CPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_cpu_cache_key_list, iterator); - return value; - } - return CPUCacheValue(); - } + // TODO: Add a 'gpu/cpu_prefetch()' method, used to add the images + // into a prefetching queue. + // + // For the GPU, the images are uploaded to the GPU, + // asynchronously. + // + // For the CPU, the images are read from disk cache, if they are + // not in memory. + // + // There must also be a way to wait for all prefetched images to + // be loaded before continuing - a "blocking" function call. // Evict the least recently used item from the GPU cache. // // Returns true/false, if an item was removed from the cache or // not. - bool gpu_evict_one(MHWRender::MTextureManager *texture_manager) { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); - - assert(texture_manager != nullptr); - if (m_gpu_cache_key_list.empty()) { - return false; - } - - const GPUCacheKey lru_key = m_gpu_cache_key_list.front(); - const GPUMapIt lru_key_iterator = m_gpu_cache_map.find(lru_key); - assert(lru_key_iterator != m_gpu_cache_map.end()); - - GPUCacheValue texture = lru_key_iterator->second.second; - texture_manager->releaseTexture(texture); - - m_gpu_cache_map.erase(lru_key_iterator); - m_gpu_cache_key_list.pop_front(); - return true; - } + bool gpu_evict_one(MHWRender::MTextureManager *texture_manager); // Evict the least recently used item from the CPU cache. // // Returns true/false, if an item was removed from the cache or // not. - bool cpu_evict_one() { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); - - if (m_cpu_cache_key_list.empty()) { - return false; - } - - const CPUCacheKey lru_key = m_cpu_cache_key_list.front(); - const CPUMapIt lru_key_iterator = m_cpu_cache_map.find(lru_key); - assert(lru_key_iterator != m_cpu_cache_map.end()); - - CPUCacheValue value = lru_key_iterator->second.second; - value.deallocate_pixels(); - - m_cpu_cache_map.erase(lru_key_iterator); - m_cpu_cache_key_list.pop_front(); - return true; - } + bool cpu_evict_one(); // Remove the key from the image GPU cache. // @@ -541,27 +598,7 @@ struct ImageCache { // // Returns true/false, if the key was removed or not. bool gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey &key) { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: "); - - const GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); - if (found) { - GPUCacheValue value = search->second.second; - - assert(texture_manager != nullptr); - texture_manager->releaseTexture(value); - - m_gpu_cache_map.erase(search); - - // NOTE: This is a O(n) linear operation, and can be very - // slow since the list items is spread out in memory. - m_gpu_cache_key_list.remove(key); - } - return found; - } + const GPUCacheKey &key); // Remove the key from the image CPU cache. // @@ -569,25 +606,7 @@ struct ImageCache { // slow to to remove a specific key from the cache. // // Returns true/false, if the key was removed or not. - bool cpu_erase(const CPUCacheKey &key) { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: "); - - const CPUMapIt search = m_cpu_cache_map.find(key); - const bool found = search != m_cpu_cache_map.end(); - if (found) { - CPUCacheValue value = search->second.second; - value.deallocate_pixels(); - - m_cpu_cache_map.erase(search); - - // NOTE: This is a O(n) linear operation, and can be very - // slow since the list items is spread out in memory. - m_cpu_cache_key_list.remove(key); - } - return found; - } + bool cpu_erase(const CPUCacheKey &key); // C++ 11; deleting the methods we don't want to ensure they can never be // used. @@ -600,6 +619,23 @@ struct ImageCache { void operator=(ImageCache const &) = delete; private: + bool gpu_evict_enough_for_new_entry( + MHWRender::MTextureManager *texture_manager, + const size_t new_memory_chunk_size); + bool cpu_evict_enough_for_new_entry(const size_t new_memory_chunk_size); + + // Number of items allowed in the cache. + size_t m_gpu_min_item_count; + size_t m_cpu_min_item_count; + + // Amount of memory capacity. + size_t m_gpu_capacity_bytes; + size_t m_cpu_capacity_bytes; + + // How much memory is currently stored in the cached? + size_t m_gpu_used_bytes; + size_t m_cpu_used_bytes; + // A Map of keys to values. // // An unordered hash map is used to map file path strings to CPU @@ -626,14 +662,6 @@ struct ImageCache { // 'back' of the list is the "most recently used" key. GPUKeyList m_gpu_cache_key_list; CPUKeyList m_cpu_cache_key_list; - - // Number of items allowed in the cache. - size_t m_gpu_capacity; - size_t m_cpu_capacity; - - // Pre-allocated empty string to be re-used inside the class, to - // avoid reallocations. - MString m_empty_string; }; MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index b7c1dd06f..f9ad7ce78 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -662,10 +662,15 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const bool do_texture_update = false; ImageCache &image_cache = ImageCache::getInstance(); - // TODO: Set the capacity using a command, and use sensible - // defaults. - image_cache.set_gpu_capacity(10); - image_cache.set_cpu_capacity(1000); + // // TODO: Set the capacity using a command, and use sensible + // // defaults. + // image_cache.set_gpu_min_item_count(10); + // image_cache.set_cpu_min_item_count(1000); + // image_cache.get_gpu_capacity_bytes(); + // image_cache.get_cpu_capacity_bytes(); + // image_cache.get_gpu_used_bytes(); + // image_cache.get_cpu_used_bytes(); + // image_cache.print_cache_details(); out_color_texture = read_image_file(textureManager, image_cache, m_temp_image, expanded_file_path, pixel_type, do_texture_update); From d29d6879daa3602f9ded2562150684564d074691 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 8 Jun 2024 18:59:15 +1000 Subject: [PATCH 053/295] Move ImageCache to mmsolver::image namespace. Unifies image reading of all supported image formats. Adds the ability to read EXR images (using Rust-based 'mmimage'). GitHub issue #252. --- src/CMakeLists.txt | 6 +- src/mmSolver/{shape => image}/ImageCache.cpp | 258 +++-------- src/mmSolver/{shape => image}/ImageCache.h | 277 +----------- src/mmSolver/image/ImagePixelData.cpp | 96 +++++ src/mmSolver/image/ImagePixelData.h | 100 +++++ src/mmSolver/image/PixelDataType.cpp | 26 ++ src/mmSolver/image/PixelDataType.h | 91 ++++ src/mmSolver/image/TextureData.cpp | 135 ++++++ src/mmSolver/image/TextureData.h | 100 +++++ src/mmSolver/image/image_io.cpp | 402 ++++++++++++++++++ src/mmSolver/image/image_io.h | 59 +++ .../shape/ImagePlaneGeometry2Override.cpp | 10 +- .../shape/ImagePlaneGeometry2Override.h | 2 +- src/mmSolver/shape/ImagePlaneUtils.cpp | 1 - src/mmSolver/shape/ImagePlaneUtils.h | 1 - 15 files changed, 1097 insertions(+), 467 deletions(-) rename src/mmSolver/{shape => image}/ImageCache.cpp (69%) rename src/mmSolver/{shape => image}/ImageCache.h (61%) create mode 100644 src/mmSolver/image/ImagePixelData.cpp create mode 100644 src/mmSolver/image/ImagePixelData.h create mode 100644 src/mmSolver/image/PixelDataType.cpp create mode 100644 src/mmSolver/image/PixelDataType.h create mode 100644 src/mmSolver/image/TextureData.cpp create mode 100644 src/mmSolver/image/TextureData.h create mode 100644 src/mmSolver/image/image_io.cpp create mode 100644 src/mmSolver/image/image_io.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 783eff2d1..b0c1e9e95 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -65,6 +65,11 @@ set(SOURCE_FILES mmSolver/cmd/arg_flags_solve_scene_graph.cpp mmSolver/cmd/arg_flags_solve_scene_graph.h mmSolver/core/reprojection.cpp + mmSolver/image/ImageCache.cpp + mmSolver/image/ImagePixelData.cpp + mmSolver/image/PixelDataType.cpp + mmSolver/image/TextureData.cpp + mmSolver/image/image_io.cpp mmSolver/mayahelper/maya_attr.cpp mmSolver/mayahelper/maya_bundle.cpp mmSolver/mayahelper/maya_camera.cpp @@ -96,7 +101,6 @@ set(SOURCE_FILES mmSolver/sfm/sfm_utils.cpp mmSolver/shape/BundleDrawOverride.cpp mmSolver/shape/BundleShapeNode.cpp - mmSolver/shape/ImageCache.cpp mmSolver/shape/ImagePlaneGeometry2Override.cpp mmSolver/shape/ImagePlaneGeometryOverride.cpp mmSolver/shape/ImagePlaneShape2Node.cpp diff --git a/src/mmSolver/shape/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp similarity index 69% rename from src/mmSolver/shape/ImageCache.cpp rename to src/mmSolver/image/ImageCache.cpp index 01eeb499e..797c02b61 100644 --- a/src/mmSolver/shape/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -19,37 +19,6 @@ * */ -#include "ImageCache.h" - -// Get M_PI constant -#define _USE_MATH_DEFINES -#include - -// STL -#include -#include -#include -#include - -// Maya -#include -#include - -// Maya Viewport 2.0 -#include - -// MM Solver -#include -#include - -#include "mmSolver/mayahelper/maya_utils.h" -#include "mmSolver/render/shader/shader_utils.h" -#include "mmSolver/shape/constant_texture_data.h" -#include "mmSolver/utilities/number_utils.h" -#include "mmSolver/utilities/path_utils.h" - -namespace mmsolver { - // Get GPU memory info from the Maya API: // // MRenderer::GPUtotalMemorySize() @@ -97,180 +66,72 @@ namespace mmsolver { // https://github.com/david-cattermole/cpp-utilities/blob/master/include/hashUtils.h // https://github.com/david-cattermole/cpp-utilities/blob/master/include/osMemoryUtils.h -void *get_mimage_pixel_data(const MImage &image, - const CachePixelDataType pixel_data_type, - const uint32_t width, const uint32_t height, - const uint8_t number_of_channels, - uint8_t &out_bytes_per_channel, - MHWRender::MRasterFormat &out_texture_format) { - const bool verbose = false; - - const uint32_t print_num_pixels = 8; - void *pixel_data = nullptr; - if (pixel_data_type == CachePixelDataType::kU8) { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" - << " pixel_data_type=CachePixelDataType::kU8"); - - // 8-bit unsigned integers use 1 byte. - out_bytes_per_channel = 1; - - const bool is_rgba = image.isRGBA(); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" - << " is_rgba=" << is_rgba); - if (is_rgba) { - out_texture_format = MHWRender::kR8G8B8A8_UNORM; - } else { - out_texture_format = MHWRender::kB8G8R8A8; - } - - unsigned char *pixels = image.pixels(); - - if (verbose) { - for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; - const uint32_t r = static_cast(pixels[index + 0]); - const uint32_t g = static_cast(pixels[index + 1]); - const uint32_t b = static_cast(pixels[index + 2]); - const uint32_t a = static_cast(pixels[index + 3]); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" - << " row=" << row << " pixel=" << r << ", " - << g << ", " << b << ", " << a); - } - } - - // TODO: Allow giving the explicit input and output color - // space names. - // - // TODO: Allow outputting 32-bit/16-bit float pixel data, to - // ensure the plate doesn't get quantize. - // - // mmcolorio::image_convert_srgb_to_linear_srgb_u8(pixels, width, - // height, - // number_of_channels); - - // mmcolorio::test_opencolorio(pixels, width, height, - // number_of_channels); - - pixel_data = static_cast(pixels); - } else if (pixel_data_type == CachePixelDataType::kF32) { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" - << " pixel_data_type=CachePixelDataType::kF32"); - - // 32-bit floats use 4 bytes. - out_bytes_per_channel = 4; - - out_texture_format = MHWRender::kR32G32B32A32_FLOAT; - - float *floatPixels = image.floatPixels(); - - if (verbose) { - for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; - const float r = floatPixels[index + 0]; - const float g = floatPixels[index + 1]; - const float b = floatPixels[index + 2]; - const float a = floatPixels[index + 3]; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: get_mimage_pixel_data:" - << " row=" << row << " pixel=" << r << ", " - << g << ", " << b << ", " << a); - } - } - - // mmcolorio::image_convert_srgb_to_linear_srgb_f32( - // floatPixels, width, height, number_of_channels); - - pixel_data = static_cast(floatPixels); - } else { - MMSOLVER_MAYA_ERR("mmsolver::ImageCache: get_mimage_pixel_data: " - << "Invalid pixel type is " - << static_cast(pixel_data_type)); - return nullptr; - } - - return pixel_data; -} - -CachePixelDataType convert_mpixel_type_to_pixel_data_type( - MImage::MPixelType pixel_type) { - CachePixelDataType pixel_data_type = CachePixelDataType::kUnknown; - if (pixel_type == MImage::MPixelType::kByte) { - pixel_data_type = CachePixelDataType::kU8; - } else if (pixel_type == MImage::MPixelType::kFloat) { - pixel_data_type = CachePixelDataType::kF32; - } else { - MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache: convert_mpixel_type_to_pixel_data_type: " - "Invalid MImage::MPixelType value."); - } - return pixel_data_type; -} +#include "ImageCache.h" -MStatus read_with_mimage(MImage &image, MString &file_path, - const MImage::MPixelType pixel_type, - uint32_t &out_width, uint32_t &out_height, - uint8_t &out_number_of_channels, - uint8_t &out_bytes_per_channel, - MHWRender::MRasterFormat &out_texture_format, - CachePixelDataType &out_pixel_data_type, - void *&out_pixel_data) { - const bool verbose = false; +// Get M_PI constant +#define _USE_MATH_DEFINES +#include - MStatus status = MStatus::kSuccess; +// STL +#include +#include +#include +#include - // TODO: This code can be changed to whatever reading function - // that reads the input file path. - status = image.readFromFile(file_path, pixel_type); - CHECK_MSTATUS(status); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_WRN("mmsolver::ImageCache: read_with_mimage:" - << " failed to read image \"" << file_path.asChar() - << "\"."); - return status; - } +// Maya +#include +#include - image.getSize(out_width, out_height); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_with_mimage:" - << " width=" << out_width << " height=" << out_height); +// Maya Viewport 2.0 +#include - out_pixel_data_type = convert_mpixel_type_to_pixel_data_type(pixel_type); +// MM Solver +#include - out_pixel_data = get_mimage_pixel_data( - image, out_pixel_data_type, out_width, out_height, - out_number_of_channels, out_bytes_per_channel, out_texture_format); +#include "ImagePixelData.h" +#include "PixelDataType.h" +#include "TextureData.h" +#include "image_io.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" - return status; -} +namespace mmsolver { +namespace image { -MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, - ImageCache &image_cache, MImage &temp_image, - const MString &file_path, - const MImage::MPixelType pixel_type, - const bool do_texture_update) { +MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, + ImageCache &image_cache, MImage &temp_image, + const MString &file_path, + const MImage::MPixelType pixel_type, + const bool do_texture_update) { assert(texture_manager != nullptr); const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file:" + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file_path=" << file_path.asChar()); MString resolved_file_path = file_path; MStatus status = mmpath::resolve_input_file_path(resolved_file_path); if (status != MS::kSuccess) { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file:" + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file does not exist \"" << resolved_file_path.asChar() << "\"."); return nullptr; } std::string key = std::string(resolved_file_path.asChar()); - CacheTextureData texture_data = image_cache.gpu_find(key); + TextureData texture_data = image_cache.gpu_find(key); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: findTexture: " - << texture_data.is_valid()); MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache: read_image_file: do_texture_update=" + "mmsolver::ImageCache: read_texture_image_file: findTexture: " + << texture_data.is_valid()); + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache: read_texture_image_file: do_texture_update=" << do_texture_update); if (texture_data.is_valid() && (do_texture_update == false)) { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE1:" + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file DONE1:" << " texture=" << texture_data.texture()); return texture_data.texture(); } @@ -286,14 +147,14 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, // Python user code to add the list of valid images into the // cache. - CacheImagePixelData image_pixel_data = image_cache.cpu_find(key); + ImagePixelData image_pixel_data = image_cache.cpu_find(key); uint32_t width = 0; uint32_t height = 0; uint8_t number_of_channels = 4; uint8_t bytes_per_channel = 0; MHWRender::MRasterFormat texture_format; - CachePixelDataType pixel_data_type = CachePixelDataType::kUnknown; + PixelDataType pixel_data_type = PixelDataType::kUnknown; void *maya_owned_pixel_data = nullptr; const bool image_pixel_data_valid = image_pixel_data.is_valid(); @@ -306,10 +167,10 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, bytes_per_channel = convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); - if (pixel_data_type == CachePixelDataType::kU8) { + if (pixel_data_type == PixelDataType::kU8) { // Assumes the 8-bit data is "RGBA". texture_format = MHWRender::kR8G8B8A8_UNORM; - } else if (pixel_data_type == CachePixelDataType::kF32) { + } else if (pixel_data_type == PixelDataType::kF32) { texture_format = MHWRender::kR32G32B32A32_FLOAT; } @@ -317,10 +178,10 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, // TODO: This code can be changed to whatever reading function // that reads the input file path. - status = read_with_mimage(temp_image, resolved_file_path, pixel_type, - width, height, number_of_channels, - bytes_per_channel, texture_format, - pixel_data_type, maya_owned_pixel_data); + status = read_image_file(temp_image, resolved_file_path, pixel_type, + width, height, number_of_channels, + bytes_per_channel, texture_format, + pixel_data_type, maya_owned_pixel_data); if (status != MS::kSuccess) { return nullptr; } @@ -331,28 +192,28 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, } if (!maya_owned_pixel_data) { - MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_image_file: " + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_texture_image_file: " << "Invalid pixel data!"); return nullptr; } - CacheImagePixelData gpu_image_pixel_data = - CacheImagePixelData(static_cast(maya_owned_pixel_data), width, - height, number_of_channels, pixel_data_type); + ImagePixelData gpu_image_pixel_data = + ImagePixelData(static_cast(maya_owned_pixel_data), width, + height, number_of_channels, pixel_data_type); texture_data = image_cache.gpu_insert(texture_manager, key, gpu_image_pixel_data); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "gpu_inserted=" << texture_data.texture()); // Duplicate the Maya-owned pixel data for our image cache. const size_t pixel_data_byte_count = width * height * number_of_channels * bytes_per_channel; - image_pixel_data = CacheImagePixelData(); + image_pixel_data = ImagePixelData(); const bool allocated_ok = image_pixel_data.allocate_pixels( width, height, number_of_channels, pixel_data_type); if (allocated_ok == false) { - MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_image_file: " + MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_texture_image_file: " << "Could not allocate pixel data!"); return nullptr; } @@ -362,10 +223,10 @@ MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, pixel_data_byte_count); const bool cpu_inserted = image_cache.cpu_insert(key, image_pixel_data); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "cpu_inserted=" << cpu_inserted); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_image_file DONE2:" + MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file DONE2:" << " texture=" << texture_data.texture()); return texture_data.texture(); @@ -410,8 +271,8 @@ bool ImageCache::cpu_insert(const CPUCacheKey &key, return ok; } -void update_texture(MTexture *texture, - const ImageCache::CPUCacheValue &image_pixel_data) { +static void update_texture(MTexture *texture, + const ImageCache::CPUCacheValue &image_pixel_data) { const bool verbose = false; // No need for MIP-maps. @@ -710,4 +571,5 @@ bool ImageCache::cpu_erase(const CPUCacheKey &key) { return found; } +} // namespace image } // namespace mmsolver diff --git a/src/mmSolver/shape/ImageCache.h b/src/mmSolver/image/ImageCache.h similarity index 61% rename from src/mmSolver/shape/ImageCache.h rename to src/mmSolver/image/ImageCache.h index 5faca7b0f..f0d46cb53 100644 --- a/src/mmSolver/shape/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -19,8 +19,8 @@ * */ -#ifndef MM_IMAGE_CACHE_H -#define MM_IMAGE_CACHE_H +#ifndef MM_SOLVER_IMAGE_IMAGE_CACHE_H +#define MM_SOLVER_IMAGE_IMAGE_CACHE_H // STL #include @@ -38,258 +38,13 @@ // MM Solver #include +#include "ImagePixelData.h" +#include "PixelDataType.h" +#include "TextureData.h" #include "mmSolver/utilities/debug_utils.h" namespace mmsolver { - -// Pre-allocated empty string to be re-used inside the class, to -// avoid reallocations. -const MString g_empty_string; - -enum class CachePixelDataType : uint8_t { - kU8 = 0, - kF32, - - // Always the second to last, so it's equal to the number of - // options. - kCount, - - // When the pixel data type is not initialized or invalid. - kUnknown = 255, -}; - -static uint8_t convert_pixel_data_type_to_bytes_per_channel( - CachePixelDataType pixel_data_type) { - uint8_t bytes_per_channel = 0; - if (pixel_data_type == CachePixelDataType::kU8) { - // 8-bit unsigned integers use 1 byte. - bytes_per_channel = 1; - } else if (pixel_data_type == CachePixelDataType::kF32) { - // 32-bit floats use 4 bytes. - bytes_per_channel = 4; - } else { - bytes_per_channel = 0; - MMSOLVER_MAYA_ERR("mmsolver::ImageCache: get_mimage_pixel_data: " - << "Invalid pixel type is " - << static_cast(pixel_data_type)); - } - return bytes_per_channel; -} - -static MHWRender::MRasterFormat convert_pixel_data_type_to_texture_format( - const CachePixelDataType pixel_data_type) { - if (pixel_data_type == CachePixelDataType::kU8) { - // Assumes the 8-bit data is "RGBA". - return MHWRender::kR8G8B8A8_UNORM; - } else if (pixel_data_type == CachePixelDataType::kF32) { - return MHWRender::kR32G32B32A32_FLOAT; - } - - return MHWRender::MRasterFormat(); -} - -struct CacheImagePixelData { - CacheImagePixelData() - : m_pixel_data(nullptr) - , m_width(0) - , m_height(0) - , m_num_channels(0) - , m_pixel_data_type(CachePixelDataType::kUnknown){}; - - CacheImagePixelData(void *pixel_data, const uint32_t width, - const uint32_t height, const uint8_t num_channels, - const CachePixelDataType pixel_data_type) - : m_pixel_data(pixel_data) - , m_width(width) - , m_height(height) - , m_num_channels(num_channels) - , m_pixel_data_type(pixel_data_type){}; - - ~CacheImagePixelData() = default; - - bool allocate_pixels(const uint32_t width, const uint32_t height, - const uint8_t num_channels, - const CachePixelDataType pixel_data_type) { - m_width = width; - m_height = height; - m_num_channels = num_channels; - m_pixel_data_type = pixel_data_type; - - const size_t pixel_data_byte_count = CacheImagePixelData::byte_count(); - - if (pixel_data_byte_count == 0) { - MMSOLVER_MAYA_ERR("mmsolver::CacheImagePixelData:allocate_pixels: " - << "Invalid image size for allocating pixel data!" - << " width=" << m_width << " height=" << m_height - << " num_channels=" << m_num_channels - << " pixel_data_type=" - << static_cast(m_pixel_data_type)); - return false; - } - - bool ok = false; - void *data = std::malloc(pixel_data_byte_count); - if (data) { - m_pixel_data = data; - ok = true; - } else { - ok = false; - MMSOLVER_MAYA_ERR( - "mmsolver::CacheImagePixelData:allocate_pixels: " - << "Could not allocate pixel data!" - << " requested=" << pixel_data_byte_count << "B" - << " requested=" - << (static_cast(pixel_data_byte_count) * 1e-6) << "MB"); - } - - return ok; - } - - void deallocate_pixels() { std::free(m_pixel_data); } - - bool is_valid() const { - return (m_pixel_data != nullptr) && (m_width != 0) && (m_height != 0) && - (m_num_channels != 0) && - (m_pixel_data_type != CachePixelDataType::kUnknown); - }; - - void *pixel_data() const { return m_pixel_data; }; - uint32_t width() const { return m_width; } - uint32_t height() const { return m_height; } - uint8_t num_channels() const { return m_num_channels; } - CachePixelDataType pixel_data_type() const { return m_pixel_data_type; } - - size_t byte_count() const { - const uint8_t bytes_per_channel = - convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); - return m_width * m_height * m_num_channels * bytes_per_channel; - } - -private: - void *m_pixel_data; - uint32_t m_width; - uint32_t m_height; - uint8_t m_num_channels; - CachePixelDataType m_pixel_data_type; -}; - -struct CacheTextureData { - CacheTextureData() - : m_texture(nullptr) - , m_width(0) - , m_height(0) - , m_num_channels(0) - , m_pixel_data_type(CachePixelDataType::kUnknown){}; - - CacheTextureData(MTexture *texture, const uint32_t width, - const uint32_t height, const uint8_t num_channels, - const CachePixelDataType pixel_data_type) - : m_texture(texture) - , m_width(width) - , m_height(height) - , m_num_channels(num_channels) - , m_pixel_data_type(pixel_data_type){}; - - ~CacheTextureData() = default; - - bool allocate_texture(MHWRender::MTextureManager *texture_manager, - void *pixel_data, const uint32_t width, - const uint32_t height, const uint8_t num_channels, - const CachePixelDataType pixel_data_type) { - assert(texture_manager != nullptr); - - m_width = width; - m_height = height; - m_num_channels = num_channels; - m_pixel_data_type = pixel_data_type; - - const uint8_t bytes_per_channel = - convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); - const size_t pixel_data_byte_count = CacheTextureData::byte_count(); - - if (pixel_data_byte_count == 0) { - MMSOLVER_MAYA_ERR("mmsolver::CacheTextureData:allocate_pixels: " - << "Invalid image size for allocating pixel data!" - << " width=" << m_width << " height=" << m_height - << " num_channels=" << m_num_channels - << " pixel_data_type=" - << static_cast(m_pixel_data_type)); - return false; - } - - bool ok = false; - - MHWRender::MTextureDescription texture_desc; - texture_desc.setToDefault2DTexture(); - texture_desc.fWidth = m_width; - texture_desc.fHeight = m_height; - texture_desc.fDepth = 1; - - texture_desc.fMipmaps = 1; - texture_desc.fArraySlices = 1; - texture_desc.fTextureType = MHWRender::kImage2D; - texture_desc.fFormat = - convert_pixel_data_type_to_texture_format(m_pixel_data_type); - - texture_desc.fBytesPerRow = - m_num_channels * bytes_per_channel * m_width; - texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * m_height; - - // No need for MIP-maps. - const bool generate_mip_maps = false; - - // If the texture name provided is an empty string then the - // texture will not be cached as part of the internal texture - // caching system. Thus each such call to this method will - // create a new texture. - MTexture *texture = texture_manager->acquireTexture( - g_empty_string, texture_desc, pixel_data, generate_mip_maps); - if (texture) { - m_texture = texture; - ok = true; - } else { - ok = false; - MMSOLVER_MAYA_ERR( - "mmsolver::CacheTextureData:allocate_texture: " - << "Could not acquire texture!" - << " requested=" << pixel_data_byte_count << "B" - << " requested=" - << (static_cast(pixel_data_byte_count) * 1e-6) << "MB"); - } - - return ok; - } - - void deallocate_texture(MHWRender::MTextureManager *texture_manager) { - assert(texture_manager != nullptr); - texture_manager->releaseTexture(m_texture); - } - - bool is_valid() const { - return (m_texture != nullptr) && (m_width != 0) && (m_height != 0) && - (m_num_channels != 0) && - (m_pixel_data_type != CachePixelDataType::kUnknown); - }; - - MTexture *texture() const { return m_texture; }; - uint32_t width() const { return m_width; } - uint32_t height() const { return m_height; } - uint8_t num_channels() const { return m_num_channels; } - CachePixelDataType pixel_data_type() const { return m_pixel_data_type; } - - size_t byte_count() const { - const uint8_t bytes_per_channel = - convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); - return m_width * m_height * m_num_channels * bytes_per_channel; - } - -private: - MTexture *m_texture; - uint32_t m_width; - uint32_t m_height; - uint8_t m_num_channels; - CachePixelDataType m_pixel_data_type; -}; +namespace image { // The ImageCache, used to load and cache images into GPU, CPU and // Disk. @@ -329,12 +84,14 @@ struct CacheTextureData { // tone-mapping to avoid clipping colours, so that as much detail is // retained - even if it's not colour accurate. // + +// TODO: Move to a constants header. const size_t MEGABYTES_TO_BYTES = 1e+6; const double kBYTES_TO_MEGABYTES = 1e-6; struct ImageCache { using GPUCacheKey = std::string; - using GPUCacheValue = CacheTextureData; + using GPUCacheValue = TextureData; using GPUKeyList = std::list; using GPUKeyListIt = GPUKeyList::iterator; using GPUMap = @@ -342,7 +99,7 @@ struct ImageCache { using GPUMapIt = GPUMap::iterator; using CPUCacheKey = std::string; - using CPUCacheValue = CacheImagePixelData; + using CPUCacheValue = ImagePixelData; using CPUKeyList = std::list; using CPUKeyListIt = CPUKeyList::iterator; using CPUMap = @@ -664,12 +421,12 @@ struct ImageCache { CPUKeyList m_cpu_cache_key_list; }; -MTexture *read_image_file(MHWRender::MTextureManager *texture_manager, - ImageCache &image_cache, MImage &temp_image, - const MString &file_path, - const MImage::MPixelType pixel_type, - const bool do_texture_update); - +MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, + ImageCache &image_cache, MImage &temp_image, + const MString &file_path, + const MImage::MPixelType pixel_type, + const bool do_texture_update); +} // namespace image } // namespace mmsolver -#endif // MM_IMAGE_CACHE_H +#endif // MM_SOLVER_IMAGE_IMAGE_CACHE_H diff --git a/src/mmSolver/image/ImagePixelData.cpp b/src/mmSolver/image/ImagePixelData.cpp new file mode 100644 index 000000000..e1da8cbc0 --- /dev/null +++ b/src/mmSolver/image/ImagePixelData.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "ImagePixelData.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "PixelDataType.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { +namespace image { + +bool ImagePixelData::allocate_pixels(const uint32_t width, + const uint32_t height, + const uint8_t num_channels, + const PixelDataType pixel_data_type) { + m_width = width; + m_height = height; + m_num_channels = num_channels; + m_pixel_data_type = pixel_data_type; + + const size_t pixel_data_byte_count = ImagePixelData::byte_count(); + + if (pixel_data_byte_count == 0) { + MMSOLVER_MAYA_ERR("mmsolver::ImagePixelData:allocate_pixels: " + << "Invalid image size for allocating pixel data!" + << " width=" << m_width << " height=" << m_height + << " num_channels=" << m_num_channels + << " pixel_data_type=" + << static_cast(m_pixel_data_type)); + return false; + } + + bool ok = false; + void *data = std::malloc(pixel_data_byte_count); + if (data) { + m_pixel_data = data; + ok = true; + } else { + ok = false; + MMSOLVER_MAYA_ERR("mmsolver::ImagePixelData:allocate_pixels: " + << "Could not allocate pixel data!" + << " requested=" << pixel_data_byte_count << "B" + << " requested=" + << (static_cast(pixel_data_byte_count) * 1e-6) + << "MB"); + } + + return ok; +} + +void ImagePixelData::deallocate_pixels() { std::free(m_pixel_data); } + +} // namespace image +} // namespace mmsolver diff --git a/src/mmSolver/image/ImagePixelData.h b/src/mmSolver/image/ImagePixelData.h new file mode 100644 index 000000000..ca9fbe5bb --- /dev/null +++ b/src/mmSolver/image/ImagePixelData.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_SOLVER_IMAGE_IMAGE_PIXEL_DATA_H +#define MM_SOLVER_IMAGE_IMAGE_PIXEL_DATA_H + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "PixelDataType.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { +namespace image { + +struct ImagePixelData { + ImagePixelData() + : m_pixel_data(nullptr) + , m_width(0) + , m_height(0) + , m_num_channels(0) + , m_pixel_data_type(PixelDataType::kUnknown){}; + + ImagePixelData(void *pixel_data, const uint32_t width, + const uint32_t height, const uint8_t num_channels, + const PixelDataType pixel_data_type) + : m_pixel_data(pixel_data) + , m_width(width) + , m_height(height) + , m_num_channels(num_channels) + , m_pixel_data_type(pixel_data_type){}; + + ~ImagePixelData() = default; + + bool allocate_pixels(const uint32_t width, const uint32_t height, + const uint8_t num_channels, + const PixelDataType pixel_data_type); + void deallocate_pixels(); + + bool is_valid() const { + return (m_pixel_data != nullptr) && (m_width != 0) && (m_height != 0) && + (m_num_channels != 0) && + (m_pixel_data_type != PixelDataType::kUnknown); + }; + + void *pixel_data() const { return m_pixel_data; }; + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + uint8_t num_channels() const { return m_num_channels; } + PixelDataType pixel_data_type() const { return m_pixel_data_type; } + + size_t byte_count() const { + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); + return m_width * m_height * m_num_channels * bytes_per_channel; + } + +private: + void *m_pixel_data; + uint32_t m_width; + uint32_t m_height; + uint8_t m_num_channels; + PixelDataType m_pixel_data_type; +}; + +} // namespace image +} // namespace mmsolver + +#endif // MM_SOLVER_IMAGE_IMAGE_PIXEL_DATA_H diff --git a/src/mmSolver/image/PixelDataType.cpp b/src/mmSolver/image/PixelDataType.cpp new file mode 100644 index 000000000..7eaba9636 --- /dev/null +++ b/src/mmSolver/image/PixelDataType.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "PixelDataType.h" + +namespace mmsolver { +namespace image {} // namespace image +} // namespace mmsolver diff --git a/src/mmSolver/image/PixelDataType.h b/src/mmSolver/image/PixelDataType.h new file mode 100644 index 000000000..4066a4138 --- /dev/null +++ b/src/mmSolver/image/PixelDataType.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_SOLVER_IMAGE_PIXEL_DATA_TYPE_H +#define MM_SOLVER_IMAGE_PIXEL_DATA_TYPE_H + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { +namespace image { + +enum class PixelDataType : uint8_t { + kU8 = 0, + kF32, + + // Always the second to last, so it's equal to the number of + // options. + kCount, + + // When the pixel data type is not initialized or invalid. + kUnknown = 255, +}; + +static uint8_t convert_pixel_data_type_to_bytes_per_channel( + PixelDataType pixel_data_type) { + uint8_t bytes_per_channel = 0; + if (pixel_data_type == PixelDataType::kU8) { + // 8-bit unsigned integers use 1 byte. + bytes_per_channel = 1; + } else if (pixel_data_type == PixelDataType::kF32) { + // 32-bit floats use 4 bytes. + bytes_per_channel = 4; + } else { + bytes_per_channel = 0; + MMSOLVER_MAYA_ERR("mmsolver::image_io: get_mimage_pixel_data: " + << "Invalid pixel type is " + << static_cast(pixel_data_type)); + } + return bytes_per_channel; +} + +static MHWRender::MRasterFormat convert_pixel_data_type_to_texture_format( + const PixelDataType pixel_data_type) { + if (pixel_data_type == PixelDataType::kU8) { + // Assumes the 8-bit data is "RGBA". + return MHWRender::kR8G8B8A8_UNORM; + } else if (pixel_data_type == PixelDataType::kF32) { + return MHWRender::kR32G32B32A32_FLOAT; + } + + return MHWRender::MRasterFormat(); +} + +} // namespace image +} // namespace mmsolver + +#endif // MM_SOLVER_IMAGE_PIXEL_DATA_TYPE_H diff --git a/src/mmSolver/image/TextureData.cpp b/src/mmSolver/image/TextureData.cpp new file mode 100644 index 000000000..fae747514 --- /dev/null +++ b/src/mmSolver/image/TextureData.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "TextureData.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "ImagePixelData.h" +#include "PixelDataType.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { +namespace image { + +bool TextureData::allocate_texture(MHWRender::MTextureManager *texture_manager, + void *pixel_data, const uint32_t width, + const uint32_t height, + const uint8_t num_channels, + const PixelDataType pixel_data_type) { + assert(texture_manager != nullptr); + + m_width = width; + m_height = height; + m_num_channels = num_channels; + m_pixel_data_type = pixel_data_type; + + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); + const size_t pixel_data_byte_count = TextureData::byte_count(); + + if (pixel_data_byte_count == 0) { + MMSOLVER_MAYA_ERR("mmsolver::TextureData:allocate_pixels: " + << "Invalid image size for allocating pixel data!" + << " width=" << m_width << " height=" << m_height + << " num_channels=" << m_num_channels + << " pixel_data_type=" + << static_cast(m_pixel_data_type)); + return false; + } + + bool ok = false; + + MHWRender::MTextureDescription texture_desc; + texture_desc.setToDefault2DTexture(); + texture_desc.fWidth = m_width; + texture_desc.fHeight = m_height; + texture_desc.fDepth = 1; + + texture_desc.fMipmaps = 1; + texture_desc.fArraySlices = 1; + texture_desc.fTextureType = MHWRender::kImage2D; + texture_desc.fFormat = + convert_pixel_data_type_to_texture_format(m_pixel_data_type); + + texture_desc.fBytesPerRow = m_num_channels * bytes_per_channel * m_width; + texture_desc.fBytesPerSlice = texture_desc.fBytesPerRow * m_height; + + // No need for MIP-maps. + const bool generate_mip_maps = false; + + // If the texture name provided is an empty string then the + // texture will not be cached as part of the internal texture + // caching system. Thus each such call to this method will + // create a new texture. + + // Pre-allocated empty string to be re-used inside the class, to + // avoid reallocations. + const MString g_empty_string; + + MTexture *texture = texture_manager->acquireTexture( + g_empty_string, texture_desc, pixel_data, generate_mip_maps); + if (texture) { + m_texture = texture; + ok = true; + } else { + ok = false; + MMSOLVER_MAYA_ERR("mmsolver::TextureData:allocate_texture: " + << "Could not acquire texture!" + << " requested=" << pixel_data_byte_count << "B" + << " requested=" + << (static_cast(pixel_data_byte_count) * 1e-6) + << "MB"); + } + + return ok; +} + +void TextureData::deallocate_texture( + MHWRender::MTextureManager *texture_manager) { + assert(texture_manager != nullptr); + texture_manager->releaseTexture(m_texture); +} + +} // namespace image +} // namespace mmsolver diff --git a/src/mmSolver/image/TextureData.h b/src/mmSolver/image/TextureData.h new file mode 100644 index 000000000..dc46f38d7 --- /dev/null +++ b/src/mmSolver/image/TextureData.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_SOLVER_IMAGE_TEXTURE_DATA_H +#define MM_SOLVER_IMAGE_TEXTURE_DATA_H + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "PixelDataType.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { +namespace image { + +struct TextureData { + TextureData() + : m_texture(nullptr) + , m_width(0) + , m_height(0) + , m_num_channels(0) + , m_pixel_data_type(PixelDataType::kUnknown){}; + + TextureData(MTexture *texture, const uint32_t width, const uint32_t height, + const uint8_t num_channels, const PixelDataType pixel_data_type) + : m_texture(texture) + , m_width(width) + , m_height(height) + , m_num_channels(num_channels) + , m_pixel_data_type(pixel_data_type){}; + + ~TextureData() = default; + + bool allocate_texture(MHWRender::MTextureManager *texture_manager, + void *pixel_data, const uint32_t width, + const uint32_t height, const uint8_t num_channels, + const PixelDataType pixel_data_type); + void deallocate_texture(MHWRender::MTextureManager *texture_manager); + + bool is_valid() const { + return (m_texture != nullptr) && (m_width != 0) && (m_height != 0) && + (m_num_channels != 0) && + (m_pixel_data_type != PixelDataType::kUnknown); + }; + + MTexture *texture() const { return m_texture; }; + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + uint8_t num_channels() const { return m_num_channels; } + PixelDataType pixel_data_type() const { return m_pixel_data_type; } + + size_t byte_count() const { + const uint8_t bytes_per_channel = + convert_pixel_data_type_to_bytes_per_channel(m_pixel_data_type); + return m_width * m_height * m_num_channels * bytes_per_channel; + } + +private: + MTexture *m_texture; + uint32_t m_width; + uint32_t m_height; + uint8_t m_num_channels; + PixelDataType m_pixel_data_type; +}; + +} // namespace image +} // namespace mmsolver + +#endif // MM_SOLVER_IMAGE_TEXTURE_DATA_H diff --git a/src/mmSolver/image/image_io.cpp b/src/mmSolver/image/image_io.cpp new file mode 100644 index 000000000..8922ac724 --- /dev/null +++ b/src/mmSolver/image/image_io.cpp @@ -0,0 +1,402 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "image_io.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include +#include + +#include "PixelDataType.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { +namespace image { + +void *get_mimage_pixel_data(const MImage &image, + const PixelDataType pixel_data_type, + const uint32_t width, const uint32_t height, + const uint8_t number_of_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format) { + const bool verbose = false; + + const uint32_t print_num_pixels = 8; + void *pixel_data = nullptr; + if (pixel_data_type == PixelDataType::kU8) { + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + << " pixel_data_type=PixelDataType::kU8"); + + // 8-bit unsigned integers use 1 byte. + out_bytes_per_channel = 1; + + const bool is_rgba = image.isRGBA(); + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + << " is_rgba=" << is_rgba); + if (is_rgba) { + out_texture_format = MHWRender::kR8G8B8A8_UNORM; + } else { + out_texture_format = MHWRender::kB8G8R8A8; + } + + unsigned char *pixels = image.pixels(); + + if (verbose) { + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const uint32_t r = static_cast(pixels[index + 0]); + const uint32_t g = static_cast(pixels[index + 1]); + const uint32_t b = static_cast(pixels[index + 2]); + const uint32_t a = static_cast(pixels[index + 3]); + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " + << g << ", " << b << ", " << a); + } + } + + // TODO: Allow giving the explicit input and output color + // space names. + // + // TODO: Allow outputting 32-bit/16-bit float pixel data, to + // ensure the plate doesn't get quantize. + // + // mmcolorio::image_convert_srgb_to_linear_srgb_u8(pixels, width, + // height, + // number_of_channels); + + // mmcolorio::test_opencolorio(pixels, width, height, + // number_of_channels); + + pixel_data = static_cast(pixels); + } else if (pixel_data_type == PixelDataType::kF32) { + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + << " pixel_data_type=PixelDataType::kF32"); + + // 32-bit floats use 4 bytes. + out_bytes_per_channel = 4; + + out_texture_format = MHWRender::kR32G32B32A32_FLOAT; + + float *floatPixels = image.floatPixels(); + + if (verbose) { + for (uint32_t row = 0; row <= print_num_pixels; row++) { + const uint32_t index = row * number_of_channels; + const float r = floatPixels[index + 0]; + const float g = floatPixels[index + 1]; + const float b = floatPixels[index + 2]; + const float a = floatPixels[index + 3]; + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " + << g << ", " << b << ", " << a); + } + } + + // mmcolorio::image_convert_srgb_to_linear_srgb_f32( + // floatPixels, width, height, number_of_channels); + + pixel_data = static_cast(floatPixels); + } else { + MMSOLVER_MAYA_ERR("mmsolver::image_io::get_mimage_pixel_data: " + << "Invalid pixel type is " + << static_cast(pixel_data_type)); + return nullptr; + } + + return pixel_data; +} + +PixelDataType convert_mpixel_type_to_pixel_data_type( + MImage::MPixelType pixel_type) { + PixelDataType pixel_data_type = PixelDataType::kUnknown; + if (pixel_type == MImage::MPixelType::kByte) { + pixel_data_type = PixelDataType::kU8; + } else if (pixel_type == MImage::MPixelType::kFloat) { + pixel_data_type = PixelDataType::kF32; + } else { + MMSOLVER_MAYA_WRN( + "mmsolver::image_io::convert_mpixel_type_to_pixel_data_type: " + "Invalid MImage::MPixelType value."); + } + return pixel_data_type; +} + +MStatus read_with_maya_image(MImage &image, MString &file_path, + const MImage::MPixelType pixel_type, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + PixelDataType &out_pixel_data_type, + void *&out_pixel_data) { + const bool verbose = false; + + MStatus status = MStatus::kSuccess; + + status = image.readFromFile(file_path, pixel_type); + CHECK_MSTATUS(status); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_WRN("mmsolver::image_io::read_with_maya_image:" + << " failed to read image \"" << file_path.asChar() + << "\"."); + return status; + } + + image.getSize(out_width, out_height); + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_with_maya_image:" + << " width=" << out_width << " height=" << out_height); + + out_pixel_data_type = convert_mpixel_type_to_pixel_data_type(pixel_type); + + out_pixel_data = get_mimage_pixel_data( + image, out_pixel_data_type, out_width, out_height, out_num_channels, + out_bytes_per_channel, out_texture_format); + + return status; +} + +MStatus read_exr_with_mmimage(MImage &image, MString &file_path, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + PixelDataType &out_pixel_data_type, + void *&out_pixel_data) { + const bool verbose = false; + + MStatus status = MStatus::kSuccess; + + auto pixel_buffer = mmimage::ImagePixelBuffer(); + auto meta_data = mmimage::ImageMetaData(); + + const std::string input_file_path_string = file_path.asChar(); + const auto input_file_path = rust::Str(input_file_path_string); + + bool read_ok = mmimage::image_read_pixels_exr_f32x4( + input_file_path, meta_data, pixel_buffer); + + if (!read_ok) { + MMSOLVER_MAYA_WRN("mmsolver::image_io::read_exr_with_mmimage:" + << " failed to read image \"" << file_path.asChar() + << "\"."); + return MStatus::kFailure; + } + + size_t num_channels = pixel_buffer.num_channels(); + if ((num_channels != 3) && (num_channels != 4)) { + MMSOLVER_MAYA_WRN("mmsolver::image_io::read_exr_with_mmimage:" + << " image has " << num_channels + << " which is supported; \"" << file_path.asChar() + << "\"."); + return MStatus::kFailure; + } + + if (num_channels == 3) { + out_texture_format = MHWRender::kR32G32B32_FLOAT; + } else if (num_channels == 4) { + out_texture_format = MHWRender::kR32G32B32A32_FLOAT; + } else { + MMSOLVER_MAYA_ERR("mmsolver::image_io::read_exr_with_mmimage:" + << " should never get here; \"" << file_path.asChar() + << "\"."); + return MStatus::kFailure; + } + + out_width = pixel_buffer.image_width(); + out_height = pixel_buffer.image_height(); + out_num_channels = num_channels; + + // Other data types are unsupported right now. + out_pixel_data_type = PixelDataType::kF32; + out_bytes_per_channel = 4; // f32 is 4-bytes per element. + + MMSOLVER_MAYA_VRB( + "mmsolver::image_io::read_exr_with_mmimage:" + << " width=" << out_width << " height=" << out_height + << " num_channels=" << static_cast(out_num_channels) + << " bytes_per_channel=" << static_cast(out_bytes_per_channel) + << " pixel_data_type=" << static_cast(out_pixel_data_type) + << " sizeof(mmimage::PixelF32x4)=" << sizeof(mmimage::PixelF32x4)); + + const rust::Slice slice = + pixel_buffer.as_slice_f32x4(); + + // TODO: Why does this need to be multiplied by 2? Shouldn't it be + // 4? + size_t pixel_data_byte_count = slice.size() * 2 * out_bytes_per_channel; + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " pixel_data_byte_count=" << pixel_data_byte_count); + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " slice.length()=" << slice.length()); + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " slice.size()=" << slice.size()); + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " requesting=" << pixel_data_byte_count << "B" + << " requesting=" + << (static_cast(pixel_data_byte_count) * 1e-6) + << "MB"); + + bool ok = false; + void *data = std::malloc(pixel_data_byte_count); + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " void *data=" << data); + + if (data) { + ok = true; + + const bool is_vertically_flipped = true; + if (is_vertically_flipped) { + // The underlying EXR data is actually flipped vertically + // compared to Maya's MImage output. We must flip this to + // be consistent. + // + // TODO: Try to do this somewhere else, perhaps in the + // shader, because that would probably be faster. + const int8_t *src_ptr = (int8_t *)slice.data(); + int8_t *dst_ptr = reinterpret_cast(data); + const size_t line_count = out_height; // * out_num_channels; + const size_t line_byte_count = pixel_data_byte_count / line_count; + MMSOLVER_MAYA_VRB( + "mmsolver::image_io::read_exr_with_mmimage:" + << " std::memcpy" + << " src_ptr=" << reinterpret_cast(src_ptr) + << " dst_ptr=" << reinterpret_cast(dst_ptr) + << " line_count=" << line_count + << " line_byte_count=" << line_byte_count + << " pixel_data_byte_count=" << pixel_data_byte_count); + for (size_t i = 0; i < line_count; i++) { + const size_t reverse_i = (line_count - 1) - i; + + const size_t src_offset = reverse_i * line_byte_count; + const size_t dst_offset = i * line_byte_count; + + const void *src_ptr_with_offset = src_ptr + src_offset; + void *dst_ptr_with_offset = dst_ptr + dst_offset; + + std::memcpy(dst_ptr_with_offset, src_ptr_with_offset, + line_byte_count); + } + } else { + // Make a copy of the underlying Rust allocated data. This is + // a little wasteful because we need to allocate and copy new + // memory, but it's much easier than coercing Rust-owned + // memory across the FFI barrier. + void *src_ptr = (void *)slice.data(); + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " std::memcpy(data=" << data + << ", (void *)slice.data()=" << src_ptr + << ", pixel_data_byte_count=" + << pixel_data_byte_count << ");"); + std::memcpy(data, src_ptr, pixel_data_byte_count); + } + + out_pixel_data = data; + } else { + ok = false; + out_pixel_data = nullptr; + MMSOLVER_MAYA_ERR("mmsolver::image_io::read_exr_with_mmimage: " + << "Could not allocate pixel data!" + << " requested=" << pixel_data_byte_count << "B" + << " requested=" + << (static_cast(pixel_data_byte_count) * 1e-6) + << "MB"); + } + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" + << " out_pixel_data=" << out_pixel_data); + + return status; +} + +MStatus read_image_file(MImage &image, MString &file_path, + const MImage::MPixelType pixel_type, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + PixelDataType &out_pixel_data_type, + void *&out_pixel_data) { + const bool verbose = false; + MStatus status = MStatus::kSuccess; + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" + << " file_path=" << file_path.asChar()); + + // We assume the file extension is standard and common. + // Non-standard file extensions will fail to use the expected + // image reader. + MString file_extension = ""; + int32_t dot_char_index = file_path.rindexW('.'); + if (dot_char_index >= 0) { + file_extension = + file_path.substringW(dot_char_index + 1, file_path.length()); + file_extension.toLowerCase(); + } + + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" + << " file_extension=" << file_extension.asChar()); + + if (file_extension == "exr") { + status = read_exr_with_mmimage(image, file_path, out_width, out_height, + out_num_channels, out_bytes_per_channel, + out_texture_format, out_pixel_data_type, + out_pixel_data); + CHECK_MSTATUS_AND_RETURN_IT(status); + } else { + status = read_with_maya_image(image, file_path, pixel_type, out_width, + out_height, out_num_channels, + out_bytes_per_channel, out_texture_format, + out_pixel_data_type, out_pixel_data); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + return status; +} + +} // namespace image +} // namespace mmsolver diff --git a/src/mmSolver/image/image_io.h b/src/mmSolver/image/image_io.h new file mode 100644 index 000000000..4b6cee914 --- /dev/null +++ b/src/mmSolver/image/image_io.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_SOLVER_IMAGE_IMAGE_IO_H +#define MM_SOLVER_IMAGE_IMAGE_IO_H + +// STL +#include +#include +#include +#include + +// Maya +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "PixelDataType.h" +#include "mmSolver/utilities/debug_utils.h" + +namespace mmsolver { +namespace image { + +MStatus read_image_file(MImage &image, MString &file_path, + const MImage::MPixelType pixel_type, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + PixelDataType &out_pixel_data_type, + void *&out_pixel_data); + +} // namespace image +} // namespace mmsolver + +#endif // MM_SOLVER_IMAGE_IMAGE_IO_H diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index f9ad7ce78..5818ee001 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -54,9 +54,9 @@ #include #include -#include "ImageCache.h" #include "ImagePlaneShape2Node.h" #include "ImagePlaneUtils.h" +#include "mmSolver/image/ImageCache.h" #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/render/shader/shader_utils.h" #include "mmSolver/shape/constant_texture_data.h" @@ -661,7 +661,7 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; const bool do_texture_update = false; - ImageCache &image_cache = ImageCache::getInstance(); + image::ImageCache &image_cache = image::ImageCache::getInstance(); // // TODO: Set the capacity using a command, and use sensible // // defaults. // image_cache.set_gpu_min_item_count(10); @@ -671,9 +671,9 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( // image_cache.get_gpu_used_bytes(); // image_cache.get_cpu_used_bytes(); // image_cache.print_cache_details(); - out_color_texture = - read_image_file(textureManager, image_cache, m_temp_image, - expanded_file_path, pixel_type, do_texture_update); + out_color_texture = image::read_texture_image_file( + textureManager, image_cache, m_temp_image, expanded_file_path, + pixel_type, do_texture_update); if (out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index f0a805df6..7966be947 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -41,7 +41,7 @@ // MM Solver #include -#include "ImageCache.h" +#include "mmSolver/image/ImageCache.h" #include "ImagePlaneShape2Node.h" #include "ImagePlaneUtils.h" #include "mmSolver/utilities/debug_utils.h" diff --git a/src/mmSolver/shape/ImagePlaneUtils.cpp b/src/mmSolver/shape/ImagePlaneUtils.cpp index 0589d7478..927e95db1 100644 --- a/src/mmSolver/shape/ImagePlaneUtils.cpp +++ b/src/mmSolver/shape/ImagePlaneUtils.cpp @@ -54,7 +54,6 @@ #include #include -#include "ImageCache.h" #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/render/shader/shader_utils.h" #include "mmSolver/shape/constant_texture_data.h" diff --git a/src/mmSolver/shape/ImagePlaneUtils.h b/src/mmSolver/shape/ImagePlaneUtils.h index 81b00b6f7..79c257fc8 100644 --- a/src/mmSolver/shape/ImagePlaneUtils.h +++ b/src/mmSolver/shape/ImagePlaneUtils.h @@ -41,7 +41,6 @@ // MM Solver #include -#include "ImageCache.h" #include "ImagePlaneShapeNode.h" #include "mmSolver/utilities/debug_utils.h" From a8800c30ed9327e6470180bdfac4a41a36df0778 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 8 Jun 2024 19:02:32 +1000 Subject: [PATCH 054/295] Change mmReadImage command to use mmsolver::image image reader. GitHub issue #252. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 31 +++++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 5c4c32625..3e506ae71 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -38,6 +38,7 @@ #include // MM Solver +#include "mmSolver/image/image_io.h" #include "mmSolver/utilities/debug_utils.h" namespace mmsolver { @@ -118,6 +119,7 @@ MStatus MMReadImageCmd::parseArgs(const MArgList &args) { } MStatus MMReadImageCmd::doIt(const MArgList &args) { + const bool verbose = false; MStatus status = MStatus::kSuccess; // Read all the flag arguments. @@ -140,20 +142,31 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { } MString resolved_file_path = file_object.resolvedFullName(); if (resolved_file_path.length() > 0) { - // MMSOLVER_MAYA_INFO( - // "mmReadImage: resolved file path " - // << "\"" << resolved_file_path.asChar() << "\"."); + MMSOLVER_MAYA_VRB("mmReadImage: resolved file path " + << "\"" << resolved_file_path.asChar() << "\"."); m_file_path = file_object.resolvedFullName(); } if (m_query_width_height) { auto image = MImage(); + // kUnknown attempts to load the native pixel type. auto pixel_type = MImage::kUnknown; - status = image.readFromFile( - m_file_path, - pixel_type // The desired pixel format is unknown. - ); + + // TODO: Can we read just the file header to get the image + // size? This would remove the need to read the entire image + // for this command's usage. + uint32_t image_width; + uint32_t image_height; + uint8_t num_channels; + uint8_t bytes_per_channel; + MHWRender::MRasterFormat texture_format; + image::PixelDataType pixel_data_type; + void *pixel_data = nullptr; + status = image::read_image_file(image, m_file_path, pixel_type, + image_width, image_height, num_channels, + bytes_per_channel, texture_format, + pixel_data_type, pixel_data); if (status != MS::kSuccess) { status = MS::kSuccess; MMSOLVER_MAYA_WRN("mmReadImage: " @@ -162,10 +175,6 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { return status; } - uint32_t image_width = 2; - uint32_t image_height = 2; - image.getSize(image_width, image_height); - MIntArray outResult; outResult.append(image_width); outResult.append(image_height); From accff51061edcbc4f0f0cdb0c25193894f63e550 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 8 Jun 2024 20:00:33 +1000 Subject: [PATCH 055/295] Fix image plane width/height values when changing slots. When changing the image plane shape node's "slot" to a image sequence with '#' in it, the image width/height is not set correctly. This change fixes that problem. GitHub issue #252. --- .../AEmmImagePlaneShape2Template.mel | 6 +- .../createimageplane/_lib/mmimageplane.py | 13 ++++ .../createimageplane/_lib/mmimageplane_v2.py | 1 + python/mmSolver/utils/constant.py | 6 ++ python/mmSolver/utils/imageseq.py | 61 +++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 77bbf0d9e..424500776 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -135,6 +135,8 @@ global proc AEmmImagePlaneShape2_sequenceSlotChanged( AEmmImagePlaneShape2_setSlotValue($image_plane_shp, $slot_attr_value); string $cmd = ""; + $cmd = $cmd + "import mmSolver.utils.constant as utils_const;\n"; + $cmd = $cmd + "import mmSolver.utils.imageseq as utils_imageseq;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; $cmd = $cmd + "\n"; @@ -147,9 +149,11 @@ global proc AEmmImagePlaneShape2_sequenceSlotChanged( $cmd = $cmd + "if len(image_seq) == 0:\n"; $cmd = $cmd + " image_seq = lib.get_default_image_path();\n"; $cmd = $cmd + "\n"; + $cmd = $cmd + "format_style = utils_const.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED;\n"; + $cmd = $cmd + "real_file_path = utils_imageseq.resolve_file_pattern_to_file_path(image_seq, format_style);\n"; $cmd = $cmd + "lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; - $cmd = $cmd + " image_seq,\n"; + $cmd = $cmd + " real_file_path,\n"; $cmd = $cmd + " attr_name=current_slot_attr\n"; $cmd = $cmd + ")\n"; diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index ddbac6803..2ce25abaa 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -42,6 +42,8 @@ def create_transform_node(name_tfm, cam_tfm, cam_shp, version=None): Create a default polygon image plane under camera. """ assert isinstance(name_tfm, pycompat.TEXT_TYPE) + assert maya.cmds.objExists(cam_tfm) + assert maya.cmds.objExists(cam_shp) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST tfm_node_type = lib_const.MM_IMAGE_PLANE_TRANSFORM @@ -92,6 +94,8 @@ def create_shape_node( assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST shp_node_type = lib_const.MM_IMAGE_PLANE_SHAPE_MAP[version] assert shp_node_type in lib_const.MM_IMAGE_PLANE_SHAPE_LIST + assert maya.cmds.objExists(tfm) + assert maya.cmds.objExists(cam_shp) img_plane_poly_shp = poly_plane_node_network.mesh_shape img_plane_poly_shp_original = poly_plane_node_network.mesh_shape_original @@ -210,6 +214,9 @@ def create_shape_node( def set_image_sequence(shp, image_sequence_path, attr_name, version=None): + assert isinstance(shp, str) + assert isinstance(image_sequence_path, str) + assert isinstance(attr_name, str) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: @@ -226,6 +233,8 @@ def set_image_sequence(shp, image_sequence_path, attr_name, version=None): def get_shape_node(image_plane_tfm, version=None): + assert isinstance(image_plane_tfm, str) + assert maya.cmds.objExists(image_plane_tfm) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: @@ -238,6 +247,8 @@ def get_shape_node(image_plane_tfm, version=None): def get_transform_node(image_plane_shp, version=None): + assert isinstance(image_plane_shp, str) + assert maya.cmds.objExists(image_plane_shp) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: @@ -250,6 +261,8 @@ def get_transform_node(image_plane_shp, version=None): def get_image_plane_node_pair(node, version=None): + assert isinstance(node, str) + assert maya.cmds.objExists(node) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 3fbd413dd..fb9c831c7 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -224,6 +224,7 @@ def create_image_plane_shape_attrs(image_plane_shp): def set_image_sequence(shp, image_sequence_path, attr_name): + assert isinstance(image_sequence_path, str) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 assert node_utils.attribute_exists(attr_name, shp) is True diff --git a/python/mmSolver/utils/constant.py b/python/mmSolver/utils/constant.py index 32dbea89c..1c5015a7e 100644 --- a/python/mmSolver/utils/constant.py +++ b/python/mmSolver/utils/constant.py @@ -95,6 +95,12 @@ IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED = 'hash_padded' IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME = 'first_frame' IMAGE_SEQ_FORMAT_STYLE_PRINTF = 'printf' +IMAGE_SEQ_FORMAT_STYLE_VALUES = [ + IMAGE_SEQ_FORMAT_STYLE_MAYA, + IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED, + IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME, + IMAGE_SEQ_FORMAT_STYLE_PRINTF, +] # What type of lens distortion mode is used? diff --git a/python/mmSolver/utils/imageseq.py b/python/mmSolver/utils/imageseq.py index 290888e4f..a35181140 100644 --- a/python/mmSolver/utils/imageseq.py +++ b/python/mmSolver/utils/imageseq.py @@ -104,6 +104,8 @@ def expand_image_sequence_path(image_sequence_path, format_style): :returns: Tuple of (file_pattern, start_frame, end_frame, padding_num, is_seq) """ + assert os.path.isfile(image_sequence_path) + assert format_style in const.IMAGE_SEQ_FORMAT_STYLE_VALUES image_sequence_path = os.path.abspath(image_sequence_path) ( @@ -143,3 +145,62 @@ def expand_image_sequence_path(image_sequence_path, format_style): file_pattern = os.path.join(base_dir, file_pattern) return file_pattern, start_frame, end_frame, padding_num, is_seq + + +def resolve_file_pattern_to_file_path(file_pattern, format_style): + """ + Resolve a file pattern into a valid file path. + + :param file_pattern: The pattern of the file path. + :type file_pattern: str + + :param format_style: The format style of the input file pattern + :type format_style: One of mmSolver.utils.constant.IMAGE_SEQ_FORMAT_STYLE_VALUES + + :returns: Valid file path or None. + :rtype: str or None + """ + assert format_style in const.IMAGE_SEQ_FORMAT_STYLE_VALUES + + if os.path.isfile(file_pattern): + file_pattern = os.path.abspath(file_pattern) + return file_pattern + + if format_style == const.IMAGE_SEQ_FORMAT_STYLE_MAYA: + # file..png + file_pattern = file_pattern.replace('', '') + elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED: + # file.####.png + file_pattern = file_pattern.replace('#', '') + elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_PRINTF: + # file.%04d.png + raise NotImplementedError + elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME: + # file.1001.png + + # Should have already been picked out as a valid file path, so + # it must be wrong. + return None + else: + raise NotImplementedError + + ( + base_dir, + file_name, + seq_num_int, + seq_num_str, + file_extension, + ) = _split_image_sequence_path(file_pattern) + + start_frame, end_frame, padding_num = _get_image_sequence_start_end_frames( + base_dir, file_name, file_extension + ) + + image_seq_num = str(start_frame).zfill(padding_num) + file_pattern = '{}{}{}'.format(file_name, image_seq_num, file_extension) + file_pattern = os.path.join(base_dir, file_pattern) + + file_path, _, _, _, _ = expand_image_sequence_path( + file_pattern, const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME + ) + return file_path From 1937ddd58061013f19748ee1164e3a83b9f22570 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 8 Jun 2024 21:25:10 +1000 Subject: [PATCH 056/295] Fix the copyright year for mmColorIO command --- src/mmSolver/cmd/MMColorIOCmd.cpp | 2 +- src/mmSolver/cmd/MMColorIOCmd.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mmSolver/cmd/MMColorIOCmd.cpp b/src/mmSolver/cmd/MMColorIOCmd.cpp index 065b941fb..15f4393df 100644 --- a/src/mmSolver/cmd/MMColorIOCmd.cpp +++ b/src/mmSolver/cmd/MMColorIOCmd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2024 David Cattermole. * * This file is part of mmSolver. * diff --git a/src/mmSolver/cmd/MMColorIOCmd.h b/src/mmSolver/cmd/MMColorIOCmd.h index e346d9df2..1b7395eaf 100644 --- a/src/mmSolver/cmd/MMColorIOCmd.h +++ b/src/mmSolver/cmd/MMColorIOCmd.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 David Cattermole. + * Copyright (C) 2024 David Cattermole. * * This file is part of mmSolver. * From f2edb3f6046adf959cd691361563bff0e84e18d9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 10:58:33 +1000 Subject: [PATCH 057/295] Remove unused ImagePlaneGeometry2Override code --- .../shape/ImagePlaneGeometry2Override.cpp | 185 ------------------ 1 file changed, 185 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index 5818ee001..ecdd97606 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -165,123 +165,6 @@ MHWRender::DrawAPI ImagePlaneGeometry2Override::supportedDrawAPIs() const { return MHWRender::kOpenGLCoreProfile; } -// bool getUpstreamNodeFromConnection(const MObject &this_node, -// const MString &attr_name, -// MPlugArray &out_connections) { -// MStatus status; -// MFnDependencyNode mfn_depend_node(this_node); - -// bool wantNetworkedPlug = true; -// MPlug plug = -// mfn_depend_node.findPlug(attr_name, wantNetworkedPlug, &status); -// if (status != MStatus::kSuccess) { -// CHECK_MSTATUS(status); -// return false; -// } -// if (plug.isNull()) { -// MMSOLVER_MAYA_WRN("Could not get plug for \"" -// << mfn_depend_node.name().asChar() << "." -// << attr_name.asChar() << "\" node."); -// return false; -// } - -// bool as_destination = true; -// bool as_source = false; -// // Ask for plugs connecting to this node's ".shaderNode" -// // attribute. -// plug.connectedTo(out_connections, as_destination, as_source, &status); -// if (status != MStatus::kSuccess) { -// CHECK_MSTATUS(status); -// return false; -// } -// if (out_connections.length() == 0) { -// MMSOLVER_MAYA_WRN("No connections to the \"" -// << mfn_depend_node.name().asChar() << "." -// << attr_name.asChar() << "\" attribute."); -// return false; -// } -// return true; -// } - -// void calculate_node_image_size_string(MDagPath &objPath, -// const uint32_t int_precision, -// const uint32_t double_precision, -// bool &out_draw_image_size, -// MString &out_image_size) { -// MStatus status = MS::kSuccess; - -// double width = 1.0; -// double height = 1.0; -// double pixel_aspect = 1.0; - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_draw_image_size, -// out_draw_image_size); -// CHECK_MSTATUS(status); - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_width, -// width); CHECK_MSTATUS(status); - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_height, -// height); CHECK_MSTATUS(status); - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_image_pixel_aspect, -// pixel_aspect); -// CHECK_MSTATUS(status); - -// double aspect = (width * pixel_aspect) / height; - -// MString width_string; -// MString height_string; -// MString pixel_aspect_string; -// MString aspect_string; - -// width_string.set(width, int_precision); -// height_string.set(height, int_precision); -// pixel_aspect_string.set(pixel_aspect, double_precision); -// aspect_string.set(aspect, double_precision); - -// out_image_size = MString("Image: ") + width_string + MString(" x ") + -// height_string + MString(" | PAR ") + pixel_aspect_string -// + MString(" | ") + aspect_string; -// return; -// } - -// void calculate_node_camera_size_string(MDagPath &objPath, -// const uint32_t double_precision, -// bool &out_draw_camera_size, -// MString &out_camera_size) { -// MStatus status = MS::kSuccess; - -// double width = 0.0; -// double height = 0.0; - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_draw_camera_size, -// out_draw_camera_size); -// CHECK_MSTATUS(status); - -// status = -// getNodeAttr(objPath, ImagePlaneShape2Node::m_camera_width_inch, -// width); -// CHECK_MSTATUS(status); - -// status = getNodeAttr(objPath, ImagePlaneShape2Node::m_camera_height_inch, -// height); -// CHECK_MSTATUS(status); - -// double aspect = width / height; - -// MString width_string; -// MString height_string; -// MString aspect_string; - -// width_string.set(width * INCH_TO_MM, double_precision); -// height_string.set(height * INCH_TO_MM, double_precision); -// aspect_string.set(aspect, double_precision); - -// out_camera_size = MString("Camera: ") + width_string + MString("mm x ") + -// height_string + MString("mm | ") + aspect_string; -// } - void ImagePlaneGeometry2Override::query_node_attributes( MObject &node, MDagPath &out_camera_node_path, bool &out_visible, bool &out_visible_to_camera_only, bool &out_is_under_camera, @@ -438,74 +321,6 @@ void ImagePlaneGeometry2Override::query_node_attributes( << out_output_color_space_name.asChar() << "\"."); } -// void find_geometry_node_path(MObject &node, MString &attr_name, -// MDagPath &out_geometry_node_path, -// MFn::Type &out_geometry_node_type) { -// const auto verbose = false; - -// MPlugArray connections; -// bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); -// if (!ok) { -// return; -// } - -// for (uint32_t i = 0; i < connections.length(); ++i) { -// MObject node = connections[i].node(); - -// if (node.hasFn(MFn::kMesh)) { -// MDagPath path; -// MDagPath::getAPathTo(node, path); -// out_geometry_node_path = path; -// out_geometry_node_type = path.apiType(); -// MMSOLVER_MAYA_VRB("Validated geometry node: " -// << " path=" -// << -// out_geometry_node_path.fullPathName().asChar() -// << " type=" << node.apiTypeStr()); -// break; -// } else { -// MMSOLVER_MAYA_WRN("Geometry node is not correct type:" -// << " path=" -// << -// out_geometry_node_path.fullPathName().asChar() -// << " type=" << node.apiTypeStr()); -// } -// } -// } - -// void find_camera_node_path(MObject &node, MString &attr_name, -// MDagPath &out_camera_node_path, -// MFn::Type &out_camera_node_type) { -// const auto verbose = false; - -// MPlugArray connections; -// bool ok = getUpstreamNodeFromConnection(node, attr_name, connections); -// if (!ok) { -// return; -// } - -// for (uint32_t i = 0; i < connections.length(); ++i) { -// MObject node = connections[i].node(); - -// if (node.hasFn(MFn::kCamera)) { -// MDagPath path; -// MDagPath::getAPathTo(node, path); -// out_camera_node_path = path; -// out_camera_node_type = path.apiType(); -// MMSOLVER_MAYA_VRB("Validated camera node: " -// << " path=" -// << out_camera_node_path.fullPathName().asChar() -// << " type=" << node.apiTypeStr()); -// break; -// } else { -// MMSOLVER_MAYA_WRN("Camera node is not correct type:" -// << " path=" -// << out_camera_node_path.fullPathName().asChar() -// << " type=" << node.apiTypeStr()); -// } -// } -// } - void ImagePlaneGeometry2Override::updateDG() { if (!m_geometry_node_path.isValid()) { MString attr_name = "geometryNode"; From d1e2bd1f7b3dd6c839049f40f1fcc7b28a4e331a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 10:59:03 +1000 Subject: [PATCH 058/295] Fix header ordering of ImagePlaneGeometry2Override. --- src/mmSolver/shape/ImagePlaneGeometry2Override.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index 7966be947..e569c56d4 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -41,9 +41,9 @@ // MM Solver #include -#include "mmSolver/image/ImageCache.h" #include "ImagePlaneShape2Node.h" #include "ImagePlaneUtils.h" +#include "mmSolver/image/ImageCache.h" #include "mmSolver/utilities/debug_utils.h" namespace mmsolver { From 47a6599767897f908a664610d8fbfec6650fabb4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 14:09:35 +1000 Subject: [PATCH 059/295] Add mmMemorySystem command to query RAM usage. GitHub issue #252. --- src/CMakeLists.txt | 2 + src/mmSolver/cmd/MMMemorySystemCmd.cpp | 227 ++++++++++++++++++ src/mmSolver/cmd/MMMemorySystemCmd.h | 90 +++++++ src/mmSolver/image/ImageCache.cpp | 43 ---- src/mmSolver/image/ImageCache.h | 17 +- src/mmSolver/pluginMain.cpp | 6 + .../utilities/system_memory_utils.cpp | 170 +++++++++++++ src/mmSolver/utilities/system_memory_utils.h | 35 +++ 8 files changed, 537 insertions(+), 53 deletions(-) create mode 100644 src/mmSolver/cmd/MMMemorySystemCmd.cpp create mode 100644 src/mmSolver/cmd/MMMemorySystemCmd.h create mode 100644 src/mmSolver/utilities/system_memory_utils.cpp create mode 100644 src/mmSolver/utilities/system_memory_utils.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0c1e9e95..b8dbf69e8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set(SOURCE_FILES mmSolver/cmd/MMColorIOCmd.cpp mmSolver/cmd/MMConvertImageCmd.cpp mmSolver/cmd/MMMarkerHomographyCmd.cpp + mmSolver/cmd/MMMemorySystemCmd.cpp mmSolver/cmd/MMReadImageCmd.cpp mmSolver/cmd/MMReprojectionCmd.cpp mmSolver/cmd/MMSolver2Cmd.cpp @@ -117,6 +118,7 @@ set(SOURCE_FILES mmSolver/utilities/number_utils.cpp mmSolver/utilities/path_utils.cpp mmSolver/utilities/string_utils.cpp + mmSolver/utilities/system_memory_utils.cpp ) if (MMSOLVER_BUILD_RENDERER) diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.cpp b/src/mmSolver/cmd/MMMemorySystemCmd.cpp new file mode 100644 index 000000000..1c73081d4 --- /dev/null +++ b/src/mmSolver/cmd/MMMemorySystemCmd.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "MMMemorySystemCmd.h" + +// STD +#include +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// MM Solver +#include + +#include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" +#include "mmSolver/utilities/string_utils.h" +#include "mmSolver/utilities/system_memory_utils.h" + +// Command arguments and command name: +#define SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG "-tot" +#define SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG_LONG "-systemPhysicalMemoryTotal" + +#define SYSTEM_PHYSICAL_MEMORY_FREE_FLAG "-fre" +#define SYSTEM_PHYSICAL_MEMORY_FREE_FLAG_LONG "-systemPhysicalMemoryFree" + +#define SYSTEM_PHYSICAL_MEMORY_USED_FLAG "-usd" +#define SYSTEM_PHYSICAL_MEMORY_USED_FLAG_LONG "-systemPhysicalMemoryUsed" + +#define PROCESS_MEMORY_USED_FLAG "-pud" +#define PROCESS_MEMORY_USED_FLAG_LONG "-processMemoryUsed" + +#define MEMORY_AS_KILOBYTES_FLAG "-kb" +#define MEMORY_AS_KILOBYTES_FLAG_LONG "-asKiloBytes" + +#define MEMORY_AS_MEGABYTES_FLAG "-mb" +#define MEMORY_AS_MEGABYTES_FLAG_LONG "-asMegaBytes" + +#define MEMORY_AS_GIGABYTES_FLAG "-gb" +#define MEMORY_AS_GIGABYTES_FLAG_LONG "-asGigaBytes" + +namespace mmsolver { + +MMMemorySystemCmd::~MMMemorySystemCmd() {} + +void *MMMemorySystemCmd::creator() { return new MMMemorySystemCmd(); } + +MString MMMemorySystemCmd::cmdName() { return MString("mmMemorySystem"); } + +/* + * Tell Maya we have a syntax function. + */ +bool MMMemorySystemCmd::hasSyntax() const { return true; } + +bool MMMemorySystemCmd::isUndoable() const { return false; } + +/* + * Add flags to the command syntax + */ +MSyntax MMMemorySystemCmd::newSyntax() { + MSyntax syntax; + syntax.enableQuery(true); + syntax.enableEdit(false); + + syntax.addFlag(SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG, + SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG_LONG); + syntax.addFlag(SYSTEM_PHYSICAL_MEMORY_FREE_FLAG, + SYSTEM_PHYSICAL_MEMORY_FREE_FLAG_LONG); + syntax.addFlag(SYSTEM_PHYSICAL_MEMORY_USED_FLAG, + SYSTEM_PHYSICAL_MEMORY_USED_FLAG_LONG); + + syntax.addFlag(PROCESS_MEMORY_USED_FLAG, PROCESS_MEMORY_USED_FLAG_LONG); + + syntax.addFlag(MEMORY_AS_MEGABYTES_FLAG, MEMORY_AS_MEGABYTES_FLAG_LONG); + syntax.addFlag(MEMORY_AS_GIGABYTES_FLAG, MEMORY_AS_GIGABYTES_FLAG_LONG); + + return syntax; +} + +/* + * Parse command line arguments + */ +MStatus MMMemorySystemCmd::parseArgs(const MArgList &args) { + MStatus status = MStatus::kSuccess; + + MArgDatabase argData(syntax(), args, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + if (!argData.isQuery()) { + status = MStatus::kFailure; + MMSOLVER_MAYA_ERR( + "mmsolver::MMMemorySystemCmd::parseArgs: " + "Command must be in query mode!"); + return status; + } + + m_system_physical_memory_total = + argData.isFlagSet(SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_system_physical_memory_free = + argData.isFlagSet(SYSTEM_PHYSICAL_MEMORY_FREE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_system_physical_memory_used = + argData.isFlagSet(SYSTEM_PHYSICAL_MEMORY_USED_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_process_memory_used = + argData.isFlagSet(PROCESS_MEMORY_USED_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const bool as_kilobytes = + argData.isFlagSet(MEMORY_AS_KILOBYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const bool as_megabytes = + argData.isFlagSet(MEMORY_AS_MEGABYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const bool as_gigabytes = + argData.isFlagSet(MEMORY_AS_GIGABYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_memory_unit = MemoryUnit::kBytes; + if (as_kilobytes) { + m_memory_unit = MemoryUnit::kKiloBytes; + } else if (as_megabytes) { + m_memory_unit = MemoryUnit::kMegaBytes; + } else if (as_gigabytes) { + m_memory_unit = MemoryUnit::kGigaBytes; + } + + return status; +} + +MStatus MMMemorySystemCmd::doIt(const MArgList &args) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + // Read all the flag arguments. + status = parseArgs(args); + CHECK_MSTATUS_AND_RETURN_IT(status); + + size_t bytes_value = 0; + if (m_system_physical_memory_total) { + bytes_value = mmsystemmemory::system_physical_memory_total(); + } else if (m_system_physical_memory_free) { + bytes_value = mmsystemmemory::system_physical_memory_free(); + } else if (m_system_physical_memory_used) { + bytes_value = mmsystemmemory::system_physical_memory_used(); + } else if (m_process_memory_used) { + size_t peak_resident_set_size = 0; + size_t current_resident_set_size = 0; + mmsystemmemory::process_memory_usage(peak_resident_set_size, + current_resident_set_size); + bytes_value = current_resident_set_size; + } else { + MMSOLVER_MAYA_ERR( + "mmsolver::MMMemorySystemCmd::doIt: " + "Give a flag to the command!"); + return MStatus::kFailure; + } + + if (m_memory_unit == MemoryUnit::kBytes) { + // It is only possible to return a maximum of "unsigned int" + // (unsigned 32-bit number) from a Maya command, but our + // maximum may exceed that size, so we must return the full + // number converted to a string, then have the Python (or + // MEL?) code convert that to an integer. + std::string number_string = mmstring::numberToString(bytes_value); + MString number_mstring(number_string.c_str()); + MMMemorySystemCmd::setResult(number_mstring); + } else if (m_memory_unit == MemoryUnit::kKiloBytes) { + double outResult = + static_cast(bytes_value) / BYTES_TO_KILOBYTES; + MMMemorySystemCmd::setResult(outResult); + } else if (m_memory_unit == MemoryUnit::kMegaBytes) { + double outResult = + static_cast(bytes_value) / BYTES_TO_MEGABYTES; + MMMemorySystemCmd::setResult(outResult); + } else if (m_memory_unit == MemoryUnit::kGigaBytes) { + double outResult = + static_cast(bytes_value) / BYTES_TO_GIGABYTES; + MMMemorySystemCmd::setResult(outResult); + } else { + // Should not get here. + return MStatus::kFailure; + } + + return status; +} + +} // namespace mmsolver diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.h b/src/mmSolver/cmd/MMMemorySystemCmd.h new file mode 100644 index 000000000..c2d758633 --- /dev/null +++ b/src/mmSolver/cmd/MMMemorySystemCmd.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * 'mmMemorySystem' Command is responsible for querying details about + * the operating systems's memory. + * + * MEL: + * // Return the number of used memory by the image cache. + * mmMemorySystem -query -systemPhysicalMemoryTotal -asMegaBytes; + * mmMemorySystem -query -systemPhysicalMemoryFree -asMegaBytes; + * mmMemorySystem -query -systemPhysicalMemoryUsed -asMegaBytes; + * + * // Return the amount of memory used by the current process. + * mmMemorySystem -query -processMemoryUsed -asMegaBytes; + * + */ + +#ifndef MAYA_MM_MEMORY_SYSTEM_CMD_H +#define MAYA_MM_MEMORY_SYSTEM_CMD_H + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include + +namespace mmsolver { + +enum class MemoryUnit : uint8_t { + kBytes = 0, + kKiloBytes, + kMegaBytes, + kGigaBytes, +}; + +class MMMemorySystemCmd : public MPxCommand { +public: + MMMemorySystemCmd() + : m_system_physical_memory_total(false) + , m_system_physical_memory_free(false) + , m_system_physical_memory_used(false) + , m_process_memory_used(false) + , m_memory_unit(MemoryUnit::kBytes){}; + + virtual ~MMMemorySystemCmd(); + + virtual bool hasSyntax() const; + static MSyntax newSyntax(); + + virtual MStatus doIt(const MArgList &args); + + virtual bool isUndoable() const; + + static void *creator(); + + static MString cmdName(); + +private: + MStatus parseArgs(const MArgList &args); + + bool m_system_physical_memory_total; + bool m_system_physical_memory_free; + bool m_system_physical_memory_used; + bool m_process_memory_used; + MemoryUnit m_memory_unit; +}; + +} // namespace mmsolver + +#endif // MAYA_MM_MEMORY_SYSTEM_CMD_H diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 797c02b61..9390bede7 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -19,52 +19,9 @@ * */ -// Get GPU memory info from the Maya API: -// -// MRenderer::GPUtotalMemorySize() -// MRenderer::GPUUsedMemorySize() -// -// -// These methods can be used to inform Maya's internal system of any -// GPU memory that we allocate/deallocate: -// -// MRenderer::holdGPUMemory(); -// MRenderer::releaseGPUMemory(); -// -// -// How to get the amount of (CPU) system memory? -// https://stackoverflow.com/questions/2513505/how-to-get-available-memory-c-g -// https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ -// -// -// On UNIX-like operating systems, use sysconf to get the amount of -// system memory: -// -// #include -// unsigned long long getTotalSystemMemory() -// { -// long pages = sysconf(_SC_PHYS_PAGES); -// long page_size = sysconf(_SC_PAGE_SIZE); -// return pages * page_size; -// } -// -// -// On Windows, use GlobalMemoryStatusEx: -// -// #include -// unsigned long long getTotalSystemMemory() -// { -// MEMORYSTATUSEX status; -// status.dwLength = sizeof(status); -// GlobalMemoryStatusEx(&status); -// return status.ullTotalPhys; -// } -// -// // Helpful code: // https://github.com/david-cattermole/cpp-utilities/blob/master/include/fileSystemUtils.h // https://github.com/david-cattermole/cpp-utilities/blob/master/include/hashUtils.h -// https://github.com/david-cattermole/cpp-utilities/blob/master/include/osMemoryUtils.h #include "ImageCache.h" diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index f0d46cb53..7a7097be5 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -42,6 +42,7 @@ #include "PixelDataType.h" #include "TextureData.h" #include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/number_utils.h" namespace mmsolver { namespace image { @@ -85,10 +86,6 @@ namespace image { // retained - even if it's not colour accurate. // -// TODO: Move to a constants header. -const size_t MEGABYTES_TO_BYTES = 1e+6; -const double kBYTES_TO_MEGABYTES = 1e-6; - struct ImageCache { using GPUCacheKey = std::string; using GPUCacheValue = TextureData; @@ -118,8 +115,8 @@ struct ImageCache { ImageCache() : m_gpu_min_item_count(1) , m_cpu_min_item_count(1) - , m_gpu_capacity_bytes(200 * MEGABYTES_TO_BYTES) - , m_cpu_capacity_bytes(1000 * MEGABYTES_TO_BYTES) + , m_gpu_capacity_bytes(200 * BYTES_TO_MEGABYTES) + , m_cpu_capacity_bytes(1000 * BYTES_TO_MEGABYTES) , m_gpu_used_bytes(0) , m_cpu_used_bytes(0) {} @@ -205,9 +202,9 @@ struct ImageCache { << m_gpu_cache_map.size() // << " GPU min item count=" << m_gpu_min_item_count << " used MB=" - << (static_cast(m_gpu_used_bytes) * kBYTES_TO_MEGABYTES) + << (static_cast(m_gpu_used_bytes) * BYTES_TO_MEGABYTES) << " capacity MB=" - << (static_cast(m_gpu_capacity_bytes) * kBYTES_TO_MEGABYTES) + << (static_cast(m_gpu_capacity_bytes) * BYTES_TO_MEGABYTES) << " percent=" << (static_cast(m_gpu_used_bytes) / static_cast(m_gpu_capacity_bytes))); @@ -217,9 +214,9 @@ struct ImageCache { << m_cpu_cache_map.size() // << " CPU min item count=" << m_cpu_min_item_count << " used MB=" - << (static_cast(m_cpu_used_bytes) * kBYTES_TO_MEGABYTES) + << (static_cast(m_cpu_used_bytes) * BYTES_TO_MEGABYTES) << " capacity MB=" - << (static_cast(m_cpu_capacity_bytes) * kBYTES_TO_MEGABYTES) + << (static_cast(m_cpu_capacity_bytes) * BYTES_TO_MEGABYTES) << " percent=" << (static_cast(m_cpu_used_bytes) / static_cast(m_cpu_capacity_bytes))); diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index c044f92ad..cc656b168 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -42,6 +42,7 @@ #include "mmSolver/cmd/MMColorIOCmd.h" #include "mmSolver/cmd/MMConvertImageCmd.h" #include "mmSolver/cmd/MMMarkerHomographyCmd.h" +#include "mmSolver/cmd/MMMemorySystemCmd.h" #include "mmSolver/cmd/MMReadImageCmd.h" #include "mmSolver/cmd/MMReprojectionCmd.h" #include "mmSolver/cmd/MMSolver2Cmd.h" @@ -266,6 +267,10 @@ MStatus initializePlugin(MObject obj) { mmsolver::MMMarkerHomographyCmd::creator, mmsolver::MMMarkerHomographyCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMMemorySystemCmd::cmdName(), + mmsolver::MMMemorySystemCmd::creator, + mmsolver::MMMemorySystemCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMReadImageCmd::cmdName(), mmsolver::MMReadImageCmd::creator, mmsolver::MMReadImageCmd::newSyntax, status); @@ -626,6 +631,7 @@ MStatus uninitializePlugin(MObject obj) { DEREGISTER_COMMAND(plugin, mmsolver::MMConvertImageCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMMarkerHomographyCmd::cmdName(), status); + DEREGISTER_COMMAND(plugin, mmsolver::MMMemorySystemCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMReadImageCmd::cmdName(), status); DEREGISTER_DRAW_OVERRIDE( diff --git a/src/mmSolver/utilities/system_memory_utils.cpp b/src/mmSolver/utilities/system_memory_utils.cpp new file mode 100644 index 000000000..95354b25e --- /dev/null +++ b/src/mmSolver/utilities/system_memory_utils.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "system_memory_utils.h" + +// STL +#include // ifstream +#include // ios_base +#include // cout, cerr, endl +#include // stringstream + +// OS-specific headers. +#ifdef _WIN32 // Windows MSVC +#include +// windows.h must be defined first. +#include +#else // Linux and MacOS +#include // stat (check a file exists) +#include // sysinfo +#include // uint32_t, uint64_t, etc +#include // sysconf +#endif + +namespace mmsystemmemory { + +// Get the current process's memory usage. +// +// Takes two size_t by reference, attempts to read the +// system-dependent data for a process' virtual memoryUsed size and +// resident set size, and return the results in bytes. +// +// On failure, returns 0, 0 +// +// http://stackoverflow.com/questions/669438/how-to-get-memory-usage-at-run-time-in-c +void process_memory_usage(size_t &peak_resident_set_size, + size_t ¤t_resident_set_size) { + peak_resident_set_size = 0; + current_resident_set_size = 0; + +#ifdef _WIN32 // Windows MSVC + PROCESS_MEMORY_COUNTERS info; + GetProcessMemoryInfo(GetCurrentProcess(), &info, sizeof(info)); + + // TODO: We could get the "PrivateUsage" on Windows as this is + // what is displayed in the Windows Task Manager? + // https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ + current_resident_set_size = static_cast(info.WorkingSetSize); + peak_resident_set_size = static_cast(info.PeakWorkingSetSize); +#else // Linux and MacOS + // 'file' stat seems to give the most reliable results + std::ifstream stat_stream("/proc/self/stat", std::ios_base::in); + + // dummy vars for leading entries in stat that we don't care about + std::string pid, comm, state, ppid, pgrp, session, tty_nr; + std::string tpgid, flags, minflt, cminflt, majflt, cmajflt; + std::string utime, stime, cutime, cstime, priority, nice; + std::string O, itrealvalue, starttime; + + // the two fields we want + size_t vsize; + size_t rss; + + stat_stream >> pid >> comm >> state >> ppid >> pgrp >> session >> tty_nr >> + tpgid >> flags >> minflt >> cminflt >> majflt >> cmajflt >> utime >> + stime >> cutime >> cstime >> priority >> nice >> O >> itrealvalue >> + starttime >> vsize >> rss; // don't care about the rest + stat_stream.close(); + + // In case of x86-64, page size is configured to use 2MB pages. + const size_t page_size = static_cast(sysconf(_SC_PAGE_SIZE)); + peak_resident_set_size = vsize; + current_resident_set_size = rss * page_size; +#endif + return; +} + +// Get the all the physical RAM available to the OS, in bytes. +// +// How to get the amount of (CPU) system memory? +// https://stackoverflow.com/questions/2513505/how-to-get-available-memory-c-g +// https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ +size_t system_physical_memory_total() { +#ifdef _WIN32 // Windows MSVC + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + + size_t total_physical_memory_bytes = status.ullTotalPhys; + return total_physical_memory_bytes; +#else // Linux and MacOS + // https://www.man7.org/linux/man-pages/man2/sysinfo.2.html + struct sysinfo memInfo; + sysinfo(&memInfo); + + size_t total_physical_memory_bytes = memInfo.totalram; + // Multiply to avoid int overflow on right hand side. + total_physical_memory_bytes *= memInfo.mem_unit; + + return total_physical_memory_bytes; +#endif +} + +// Returns memory bytes used. +// +// https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ +size_t system_physical_memory_used() { +#ifdef _WIN32 // Windows MSVC + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + + size_t physical_memory_used_bytes = + status.ullTotalPhys - status.ullAvailPhys; + return physical_memory_used_bytes; +#else // Linux and MacOS + // https://www.man7.org/linux/man-pages/man2/sysinfo.2.html + struct sysinfo memInfo; + sysinfo(&memInfo); + + size_t physical_memory_used_bytes = memInfo.totalram - memInfo.freeram; + // Multiply to avoid int overflow on right hand side. + physical_memory_used_bytes *= memInfo.mem_unit; + + return physical_memory_used_bytes; +#endif +} + +// Returns memory bytes free. +// +// https://stackoverflow.com/questions/63166/how-to-determine-cpu-and-memory-consumption-from-inside-a-process/ +size_t system_physical_memory_free() { +#ifdef _WIN32 // Windows MSVC + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + + size_t physical_memory_free_bytes = status.ullAvailPhys; + return physical_memory_free_bytes; +#else // Linux and MacOS + // https://www.man7.org/linux/man-pages/man2/sysinfo.2.html + struct sysinfo memInfo; + sysinfo(&memInfo); + + size_t physical_memory_free_bytes = memInfo.freeram; + // Multiply to avoid int overflow on right hand side. + physical_memory_used_bytes *= memInfo.mem_unit; + + return physical_memory_free_bytes; +#endif +} + +} // namespace mmsystemmemory diff --git a/src/mmSolver/utilities/system_memory_utils.h b/src/mmSolver/utilities/system_memory_utils.h new file mode 100644 index 000000000..cec71b039 --- /dev/null +++ b/src/mmSolver/utilities/system_memory_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Generic path helper functions. + */ + +#ifndef SYSTEM_MEMORY_UTILS_H +#define SYSTEM_MEMORY_UTILS_H + +namespace mmsystemmemory { +void process_memory_usage(size_t &peak_resident_set_size, + size_t ¤t_resident_set_size); +size_t system_physical_memory_total(); +size_t system_physical_memory_free(); +size_t system_physical_memory_used(); + +} // namespace mmsystemmemory + +#endif // SYSTEM_MEMORY_UTILS_H From 51a9a6f9cef9411e250b2d5539277c654b9d5b19 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 14:10:15 +1000 Subject: [PATCH 060/295] Move mmReadImage command flags to .cpp file. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 16 ++++++++++++++++ src/mmSolver/cmd/MMReadImageCmd.h | 7 ------- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 3e506ae71..c7e7750c5 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -41,6 +41,22 @@ #include "mmSolver/image/image_io.h" #include "mmSolver/utilities/debug_utils.h" +// Command arguments and command name: +#define FILE_PATH_FLAG "-fp" +#define FILE_PATH_FLAG_LONG "-filePath" + +#define WIDTH_HEIGHT_FLAG "-wh" +#define WIDTH_HEIGHT_FLAG_LONG "-widthHeight" + +// TODO: Add flags to: +// - Return the number of channels in the image. +// - Return the number of bytes per-channel in the image. +// - Return the number of total raw bytes in the image. +// +// NOTE: We do not want to have to call mmReadImage multiple times. We +// want to get as much data as possible in a single call, because +// subsequent calls will need to re-read the image. + namespace mmsolver { MMReadImageCmd::~MMReadImageCmd() {} diff --git a/src/mmSolver/cmd/MMReadImageCmd.h b/src/mmSolver/cmd/MMReadImageCmd.h index f148d8437..e36988b67 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.h +++ b/src/mmSolver/cmd/MMReadImageCmd.h @@ -33,13 +33,6 @@ #include #include -// Command arguments and command name: -#define FILE_PATH_FLAG "-fp" -#define FILE_PATH_FLAG_LONG "-filePath" - -#define WIDTH_HEIGHT_FLAG "-wh" -#define WIDTH_HEIGHT_FLAG_LONG "-widthHeight" - namespace mmsolver { class MMReadImageCmd : public MPxCommand { From 6af46224f0225ae7701956694da450b8c560fc8c Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 17:14:09 +1000 Subject: [PATCH 061/295] Split memory utilities to be reused. Renames 'system_memory_utils' to memory_system_utils. --- src/CMakeLists.txt | 3 +- src/mmSolver/cmd/MMMemorySystemCmd.cpp | 38 +++++-------- src/mmSolver/cmd/MMMemorySystemCmd.h | 14 ++--- ...mory_utils.cpp => memory_system_utils.cpp} | 6 +-- ...m_memory_utils.h => memory_system_utils.h} | 12 ++--- src/mmSolver/utilities/memory_utils.cpp | 54 +++++++++++++++++++ src/mmSolver/utilities/memory_utils.h | 43 +++++++++++++++ 7 files changed, 126 insertions(+), 44 deletions(-) rename src/mmSolver/utilities/{system_memory_utils.cpp => memory_system_utils.cpp} (98%) rename src/mmSolver/utilities/{system_memory_utils.h => memory_system_utils.h} (84%) create mode 100644 src/mmSolver/utilities/memory_utils.cpp create mode 100644 src/mmSolver/utilities/memory_utils.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b8dbf69e8..143f81d76 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -115,10 +115,11 @@ set(SOURCE_FILES mmSolver/shape/SkyDomeDrawOverride.cpp mmSolver/shape/SkyDomeShapeNode.cpp mmSolver/utilities/debug_utils.cpp + mmSolver/utilities/memory_system_utils.cpp + mmSolver/utilities/memory_utils.cpp mmSolver/utilities/number_utils.cpp mmSolver/utilities/path_utils.cpp mmSolver/utilities/string_utils.cpp - mmSolver/utilities/system_memory_utils.cpp ) if (MMSOLVER_BUILD_RENDERER) diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.cpp b/src/mmSolver/cmd/MMMemorySystemCmd.cpp index 1c73081d4..e916dce08 100644 --- a/src/mmSolver/cmd/MMMemorySystemCmd.cpp +++ b/src/mmSolver/cmd/MMMemorySystemCmd.cpp @@ -45,10 +45,10 @@ #include #include "mmSolver/utilities/debug_utils.h" -#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/memory_system_utils.h" +#include "mmSolver/utilities/memory_utils.h" #include "mmSolver/utilities/path_utils.h" #include "mmSolver/utilities/string_utils.h" -#include "mmSolver/utilities/system_memory_utils.h" // Command arguments and command name: #define SYSTEM_PHYSICAL_MEMORY_TOTAL_FLAG "-tot" @@ -155,13 +155,13 @@ MStatus MMMemorySystemCmd::parseArgs(const MArgList &args) { argData.isFlagSet(MEMORY_AS_GIGABYTES_FLAG, &status); CHECK_MSTATUS_AND_RETURN_IT(status); - m_memory_unit = MemoryUnit::kBytes; + m_memory_unit = mmmemory::MemoryUnit::kBytes; if (as_kilobytes) { - m_memory_unit = MemoryUnit::kKiloBytes; + m_memory_unit = mmmemory::MemoryUnit::kKiloBytes; } else if (as_megabytes) { - m_memory_unit = MemoryUnit::kMegaBytes; + m_memory_unit = mmmemory::MemoryUnit::kMegaBytes; } else if (as_gigabytes) { - m_memory_unit = MemoryUnit::kGigaBytes; + m_memory_unit = mmmemory::MemoryUnit::kGigaBytes; } return status; @@ -177,15 +177,15 @@ MStatus MMMemorySystemCmd::doIt(const MArgList &args) { size_t bytes_value = 0; if (m_system_physical_memory_total) { - bytes_value = mmsystemmemory::system_physical_memory_total(); + bytes_value = mmmemorysystem::system_physical_memory_total(); } else if (m_system_physical_memory_free) { - bytes_value = mmsystemmemory::system_physical_memory_free(); + bytes_value = mmmemorysystem::system_physical_memory_free(); } else if (m_system_physical_memory_used) { - bytes_value = mmsystemmemory::system_physical_memory_used(); + bytes_value = mmmemorysystem::system_physical_memory_used(); } else if (m_process_memory_used) { size_t peak_resident_set_size = 0; size_t current_resident_set_size = 0; - mmsystemmemory::process_memory_usage(peak_resident_set_size, + mmmemorysystem::process_memory_usage(peak_resident_set_size, current_resident_set_size); bytes_value = current_resident_set_size; } else { @@ -195,7 +195,7 @@ MStatus MMMemorySystemCmd::doIt(const MArgList &args) { return MStatus::kFailure; } - if (m_memory_unit == MemoryUnit::kBytes) { + if (m_memory_unit == mmmemory::MemoryUnit::kBytes) { // It is only possible to return a maximum of "unsigned int" // (unsigned 32-bit number) from a Maya command, but our // maximum may exceed that size, so we must return the full @@ -204,23 +204,11 @@ MStatus MMMemorySystemCmd::doIt(const MArgList &args) { std::string number_string = mmstring::numberToString(bytes_value); MString number_mstring(number_string.c_str()); MMMemorySystemCmd::setResult(number_mstring); - } else if (m_memory_unit == MemoryUnit::kKiloBytes) { - double outResult = - static_cast(bytes_value) / BYTES_TO_KILOBYTES; - MMMemorySystemCmd::setResult(outResult); - } else if (m_memory_unit == MemoryUnit::kMegaBytes) { - double outResult = - static_cast(bytes_value) / BYTES_TO_MEGABYTES; - MMMemorySystemCmd::setResult(outResult); - } else if (m_memory_unit == MemoryUnit::kGigaBytes) { + } else { double outResult = - static_cast(bytes_value) / BYTES_TO_GIGABYTES; + mmmemory::bytes_as_double(bytes_value, m_memory_unit); MMMemorySystemCmd::setResult(outResult); - } else { - // Should not get here. - return MStatus::kFailure; } - return status; } diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.h b/src/mmSolver/cmd/MMMemorySystemCmd.h index c2d758633..74d55f063 100644 --- a/src/mmSolver/cmd/MMMemorySystemCmd.h +++ b/src/mmSolver/cmd/MMMemorySystemCmd.h @@ -44,14 +44,10 @@ #include #include -namespace mmsolver { +// MM Solver +#include "mmSolver/utilities/memory_utils.h" -enum class MemoryUnit : uint8_t { - kBytes = 0, - kKiloBytes, - kMegaBytes, - kGigaBytes, -}; +namespace mmsolver { class MMMemorySystemCmd : public MPxCommand { public: @@ -60,7 +56,7 @@ class MMMemorySystemCmd : public MPxCommand { , m_system_physical_memory_free(false) , m_system_physical_memory_used(false) , m_process_memory_used(false) - , m_memory_unit(MemoryUnit::kBytes){}; + , m_memory_unit(mmmemory::MemoryUnit::kBytes){}; virtual ~MMMemorySystemCmd(); @@ -82,7 +78,7 @@ class MMMemorySystemCmd : public MPxCommand { bool m_system_physical_memory_free; bool m_system_physical_memory_used; bool m_process_memory_used; - MemoryUnit m_memory_unit; + mmmemory::MemoryUnit m_memory_unit; }; } // namespace mmsolver diff --git a/src/mmSolver/utilities/system_memory_utils.cpp b/src/mmSolver/utilities/memory_system_utils.cpp similarity index 98% rename from src/mmSolver/utilities/system_memory_utils.cpp rename to src/mmSolver/utilities/memory_system_utils.cpp index 95354b25e..522e34c61 100644 --- a/src/mmSolver/utilities/system_memory_utils.cpp +++ b/src/mmSolver/utilities/memory_system_utils.cpp @@ -19,7 +19,7 @@ * */ -#include "system_memory_utils.h" +#include "memory_system_utils.h" // STL #include // ifstream @@ -39,7 +39,7 @@ #include // sysconf #endif -namespace mmsystemmemory { +namespace mmmemorysystem { // Get the current process's memory usage. // @@ -167,4 +167,4 @@ size_t system_physical_memory_free() { #endif } -} // namespace mmsystemmemory +} // namespace mmmemorysystem diff --git a/src/mmSolver/utilities/system_memory_utils.h b/src/mmSolver/utilities/memory_system_utils.h similarity index 84% rename from src/mmSolver/utilities/system_memory_utils.h rename to src/mmSolver/utilities/memory_system_utils.h index cec71b039..c358887cc 100644 --- a/src/mmSolver/utilities/system_memory_utils.h +++ b/src/mmSolver/utilities/memory_system_utils.h @@ -17,19 +17,19 @@ * along with mmSolver. If not, see . * ==================================================================== * - * Generic path helper functions. + * Get details about the OS system memory. */ -#ifndef SYSTEM_MEMORY_UTILS_H -#define SYSTEM_MEMORY_UTILS_H +#ifndef MEMORY_SYSTEM_UTILS_H +#define MEMORY_SYSTEM_UTILS_H -namespace mmsystemmemory { +namespace mmmemorysystem { void process_memory_usage(size_t &peak_resident_set_size, size_t ¤t_resident_set_size); size_t system_physical_memory_total(); size_t system_physical_memory_free(); size_t system_physical_memory_used(); -} // namespace mmsystemmemory +} // namespace mmmemorysystem -#endif // SYSTEM_MEMORY_UTILS_H +#endif // MEMORY_SYSTEM_UTILS_H diff --git a/src/mmSolver/utilities/memory_utils.cpp b/src/mmSolver/utilities/memory_utils.cpp new file mode 100644 index 000000000..8e6476b12 --- /dev/null +++ b/src/mmSolver/utilities/memory_utils.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "memory_utils.h" + +// MM Solver +#include "debug_utils.h" +#include "number_utils.h" + +namespace mmmemory { + +double bytes_as_double(const size_t size_as_bytes, + const mmmemory::MemoryUnit memory_unit) { + // NOTE: This essentially downcasts to a a 52-bit integer here, + // so in theory we could loose precision. + double result = static_cast(size_as_bytes); + if (memory_unit == mmmemory::MemoryUnit::kBytes) { + MMSOLVER_MAYA_WRN( + "mmmemory::bytes_as_double: " + "Casting bytes to a 'double' value may not maintain precision."); + } else if (memory_unit == mmmemory::MemoryUnit::kKiloBytes) { + result /= BYTES_TO_KILOBYTES; + } else if (memory_unit == mmmemory::MemoryUnit::kMegaBytes) { + result /= BYTES_TO_MEGABYTES; + } else if (memory_unit == mmmemory::MemoryUnit::kGigaBytes) { + result /= BYTES_TO_GIGABYTES; + } else { + MMSOLVER_MAYA_ERR( + "mmmemory::bytes_as_double: " + "MemoryUnit given is not supported! value=" + << static_cast(memory_unit)); + } + return result; +} + +} // namespace mmmemory diff --git a/src/mmSolver/utilities/memory_utils.h b/src/mmSolver/utilities/memory_utils.h new file mode 100644 index 000000000..32d802434 --- /dev/null +++ b/src/mmSolver/utilities/memory_utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Memory-related utilities. + */ + +#ifndef MEMORY_UTILS_H +#define MEMORY_UTILS_H + +// STL +#include + +namespace mmmemory { + +enum class MemoryUnit : uint8_t { + kBytes = 0, + kKiloBytes, + kMegaBytes, + kGigaBytes, +}; + +double bytes_as_double(const size_t size_as_bytes, + const mmmemory::MemoryUnit memory_unit); + +} // namespace mmmemory + +#endif // MEMORY_UTILS_H From 4db3941fe7305f240668d810a8efec077c9744bb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 19:43:28 +1000 Subject: [PATCH 062/295] mmMemorySystem - Add missing -asKiloBytes flag. --- src/mmSolver/cmd/MMMemorySystemCmd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.cpp b/src/mmSolver/cmd/MMMemorySystemCmd.cpp index e916dce08..afb02d352 100644 --- a/src/mmSolver/cmd/MMMemorySystemCmd.cpp +++ b/src/mmSolver/cmd/MMMemorySystemCmd.cpp @@ -104,6 +104,7 @@ MSyntax MMMemorySystemCmd::newSyntax() { syntax.addFlag(PROCESS_MEMORY_USED_FLAG, PROCESS_MEMORY_USED_FLAG_LONG); + syntax.addFlag(MEMORY_AS_KILOBYTES_FLAG, MEMORY_AS_KILOBYTES_FLAG_LONG); syntax.addFlag(MEMORY_AS_MEGABYTES_FLAG, MEMORY_AS_MEGABYTES_FLAG_LONG); syntax.addFlag(MEMORY_AS_GIGABYTES_FLAG, MEMORY_AS_GIGABYTES_FLAG_LONG); From ea35480f9f2a97a8b7a72012a5f41122bd9d9aab Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 19:51:56 +1000 Subject: [PATCH 063/295] Add mmMemoryGPU command and GPU memory utilities Old deprecated OpenGL extensions are used to get the amount of free memory. --- src/CMakeLists.txt | 2 + src/mmSolver/cmd/MMMemoryGPUCmd.cpp | 197 ++++++++++++++++ src/mmSolver/cmd/MMMemoryGPUCmd.h | 81 +++++++ src/mmSolver/pluginMain.cpp | 6 + src/mmSolver/utilities/memory_gpu_utils.cpp | 235 ++++++++++++++++++++ src/mmSolver/utilities/memory_gpu_utils.h | 43 ++++ 6 files changed, 564 insertions(+) create mode 100644 src/mmSolver/cmd/MMMemoryGPUCmd.cpp create mode 100644 src/mmSolver/cmd/MMMemoryGPUCmd.h create mode 100644 src/mmSolver/utilities/memory_gpu_utils.cpp create mode 100644 src/mmSolver/utilities/memory_gpu_utils.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 143f81d76..a7f5f1899 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,7 @@ set(SOURCE_FILES mmSolver/cmd/MMColorIOCmd.cpp mmSolver/cmd/MMConvertImageCmd.cpp mmSolver/cmd/MMMarkerHomographyCmd.cpp + mmSolver/cmd/MMMemoryGPUCmd.cpp mmSolver/cmd/MMMemorySystemCmd.cpp mmSolver/cmd/MMReadImageCmd.cpp mmSolver/cmd/MMReprojectionCmd.cpp @@ -115,6 +116,7 @@ set(SOURCE_FILES mmSolver/shape/SkyDomeDrawOverride.cpp mmSolver/shape/SkyDomeShapeNode.cpp mmSolver/utilities/debug_utils.cpp + mmSolver/utilities/memory_gpu_utils.cpp mmSolver/utilities/memory_system_utils.cpp mmSolver/utilities/memory_utils.cpp mmSolver/utilities/number_utils.cpp diff --git a/src/mmSolver/cmd/MMMemoryGPUCmd.cpp b/src/mmSolver/cmd/MMMemoryGPUCmd.cpp new file mode 100644 index 000000000..65fbc3c62 --- /dev/null +++ b/src/mmSolver/cmd/MMMemoryGPUCmd.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "MMMemoryGPUCmd.h" + +// STD +#include +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// MM Solver +#include + +#include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/memory_gpu_utils.h" +#include "mmSolver/utilities/path_utils.h" +#include "mmSolver/utilities/string_utils.h" + +// Command arguments and command name: +#define MEMORY_TOTAL_FLAG "-tot" +#define MEMORY_TOTAL_FLAG_LONG "-total" + +#define MEMORY_FREE_FLAG "-fre" +#define MEMORY_FREE_FLAG_LONG "-free" + +#define MEMORY_USED_FLAG "-usd" +#define MEMORY_USED_FLAG_LONG "-used" + +#define MEMORY_AS_KILOBYTES_FLAG "-kb" +#define MEMORY_AS_KILOBYTES_FLAG_LONG "-asKiloBytes" + +#define MEMORY_AS_MEGABYTES_FLAG "-mb" +#define MEMORY_AS_MEGABYTES_FLAG_LONG "-asMegaBytes" + +#define MEMORY_AS_GIGABYTES_FLAG "-gb" +#define MEMORY_AS_GIGABYTES_FLAG_LONG "-asGigaBytes" + +namespace mmsolver { + +MMMemoryGPUCmd::~MMMemoryGPUCmd() {} + +void *MMMemoryGPUCmd::creator() { return new MMMemoryGPUCmd(); } + +MString MMMemoryGPUCmd::cmdName() { return MString("mmMemoryGPU"); } + +/* + * Tell Maya we have a syntax function. + */ +bool MMMemoryGPUCmd::hasSyntax() const { return true; } + +bool MMMemoryGPUCmd::isUndoable() const { return false; } + +/* + * Add flags to the command syntax + */ +MSyntax MMMemoryGPUCmd::newSyntax() { + MSyntax syntax; + syntax.enableQuery(true); + syntax.enableEdit(false); + + syntax.addFlag(MEMORY_TOTAL_FLAG, MEMORY_TOTAL_FLAG_LONG); + syntax.addFlag(MEMORY_FREE_FLAG, MEMORY_FREE_FLAG_LONG); + syntax.addFlag(MEMORY_USED_FLAG, MEMORY_USED_FLAG_LONG); + + syntax.addFlag(MEMORY_AS_KILOBYTES_FLAG, MEMORY_AS_KILOBYTES_FLAG_LONG); + syntax.addFlag(MEMORY_AS_MEGABYTES_FLAG, MEMORY_AS_MEGABYTES_FLAG_LONG); + syntax.addFlag(MEMORY_AS_GIGABYTES_FLAG, MEMORY_AS_GIGABYTES_FLAG_LONG); + + return syntax; +} + +/* + * Parse command line arguments + */ +MStatus MMMemoryGPUCmd::parseArgs(const MArgList &args) { + MStatus status = MStatus::kSuccess; + + MArgDatabase argData(syntax(), args, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + if (!argData.isQuery()) { + status = MStatus::kFailure; + MMSOLVER_MAYA_ERR( + "mmsolver::MMMemoryGPUCmd::parseArgs: " + "Command must be in query mode!"); + return status; + } + + m_memory_total = argData.isFlagSet(MEMORY_TOTAL_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_memory_free = argData.isFlagSet(MEMORY_FREE_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_memory_used = argData.isFlagSet(MEMORY_USED_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + const bool as_kilobytes = + argData.isFlagSet(MEMORY_AS_KILOBYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const bool as_megabytes = + argData.isFlagSet(MEMORY_AS_MEGABYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + const bool as_gigabytes = + argData.isFlagSet(MEMORY_AS_GIGABYTES_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_memory_unit = mmmemory::MemoryUnit::kBytes; + if (as_kilobytes) { + m_memory_unit = mmmemory::MemoryUnit::kKiloBytes; + } else if (as_megabytes) { + m_memory_unit = mmmemory::MemoryUnit::kMegaBytes; + } else if (as_gigabytes) { + m_memory_unit = mmmemory::MemoryUnit::kGigaBytes; + } + + return status; +} + +MStatus MMMemoryGPUCmd::doIt(const MArgList &args) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + // Read all the flag arguments. + status = parseArgs(args); + CHECK_MSTATUS_AND_RETURN_IT(status); + + size_t bytes_value = 0; + if (m_memory_total) { + status = mmmemorygpu::memory_total_size_in_bytes(bytes_value); + CHECK_MSTATUS_AND_RETURN_IT(status); + } else if (m_memory_free) { + status = mmmemorygpu::memory_free_size_in_bytes(bytes_value); + CHECK_MSTATUS_AND_RETURN_IT(status); + } else if (m_memory_used) { + status = mmmemorygpu::memory_used_size_in_bytes(bytes_value); + CHECK_MSTATUS_AND_RETURN_IT(status); + } else { + MMSOLVER_MAYA_ERR( + "mmsolver::MMMemoryGPUCmd::doIt: " + "Give a flag to the command!"); + return MStatus::kFailure; + } + + if (m_memory_unit == mmmemory::MemoryUnit::kBytes) { + // It is only possible to return a maximum of "unsigned int" + // (unsigned 32-bit number) from a Maya command, but our + // maximum may exceed that size, so we must return the full + // number converted to a string, then have the Python (or + // MEL?) code convert that to an integer. + std::string number_string = mmstring::numberToString(bytes_value); + MString number_mstring(number_string.c_str()); + MMMemoryGPUCmd::setResult(number_mstring); + } else { + double outResult = + mmmemory::bytes_as_double(bytes_value, m_memory_unit); + MMMemoryGPUCmd::setResult(outResult); + } + + return status; +} + +} // namespace mmsolver diff --git a/src/mmSolver/cmd/MMMemoryGPUCmd.h b/src/mmSolver/cmd/MMMemoryGPUCmd.h new file mode 100644 index 000000000..a4a34f208 --- /dev/null +++ b/src/mmSolver/cmd/MMMemoryGPUCmd.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * 'mmMemoryGPU' Command is responsible for querying details about + * the operating systems's memory. + * + * MEL: + * // Return the number of used memory by the image cache. + * mmMemoryGPU -query -total -asMegaBytes; + * mmMemoryGPU -query -free -asMegaBytes; + * mmMemoryGPU -query -used -asMegaBytes; + * + */ + +#ifndef MAYA_MM_MEMORY_GPU_CMD_H +#define MAYA_MM_MEMORY_GPU_CMD_H + +// Maya +#include +#include +#include +#include +#include +#include +#include +#include + +// MM Solver +#include "mmSolver/utilities/memory_utils.h" + +namespace mmsolver { + +class MMMemoryGPUCmd : public MPxCommand { +public: + MMMemoryGPUCmd() + : m_memory_total(false) + , m_memory_free(false) + , m_memory_used(false) + , m_memory_unit(mmmemory::MemoryUnit::kBytes){}; + + virtual ~MMMemoryGPUCmd(); + + virtual bool hasSyntax() const; + static MSyntax newSyntax(); + + virtual MStatus doIt(const MArgList &args); + + virtual bool isUndoable() const; + + static void *creator(); + + static MString cmdName(); + +private: + MStatus parseArgs(const MArgList &args); + + bool m_memory_total; + bool m_memory_free; + bool m_memory_used; + mmmemory::MemoryUnit m_memory_unit; +}; + +} // namespace mmsolver + +#endif // MAYA_MM_MEMORY_GPU_CMD_H diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index cc656b168..b57ca8e69 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -42,6 +42,7 @@ #include "mmSolver/cmd/MMColorIOCmd.h" #include "mmSolver/cmd/MMConvertImageCmd.h" #include "mmSolver/cmd/MMMarkerHomographyCmd.h" +#include "mmSolver/cmd/MMMemoryGPUCmd.h" #include "mmSolver/cmd/MMMemorySystemCmd.h" #include "mmSolver/cmd/MMReadImageCmd.h" #include "mmSolver/cmd/MMReprojectionCmd.h" @@ -267,6 +268,10 @@ MStatus initializePlugin(MObject obj) { mmsolver::MMMarkerHomographyCmd::creator, mmsolver::MMMarkerHomographyCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMMemoryGPUCmd::cmdName(), + mmsolver::MMMemoryGPUCmd::creator, + mmsolver::MMMemoryGPUCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMMemorySystemCmd::cmdName(), mmsolver::MMMemorySystemCmd::creator, mmsolver::MMMemorySystemCmd::newSyntax, status); @@ -631,6 +636,7 @@ MStatus uninitializePlugin(MObject obj) { DEREGISTER_COMMAND(plugin, mmsolver::MMConvertImageCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMMarkerHomographyCmd::cmdName(), status); + DEREGISTER_COMMAND(plugin, mmsolver::MMMemoryGPUCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMMemorySystemCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMReadImageCmd::cmdName(), status); diff --git a/src/mmSolver/utilities/memory_gpu_utils.cpp b/src/mmSolver/utilities/memory_gpu_utils.cpp new file mode 100644 index 000000000..ecbc9230e --- /dev/null +++ b/src/mmSolver/utilities/memory_gpu_utils.cpp @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "memory_gpu_utils.h" + +// STL +#include + +// Maya +#include + +// Maya Viewport 1.0 (Legacy) +#include +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include "debug_utils.h" + +namespace mmmemorygpu { + +MStatus memory_total_size_in_bytes(size_t &out_size_in_bytes) { + out_size_in_bytes = 0; + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_WRN( + "mmmemorygpu::memory_total_size_in_bytes: " + "Failed to get Maya MRenderer!"); + return MStatus::kFailure; + } + out_size_in_bytes = static_cast(renderer->GPUtotalMemorySize()); + return MStatus::kSuccess; +} + +MStatus gpu_memory_usage(size_t &total_memory, size_t &free_memory, + size_t &used_memory) { + const bool verbose = true; + MStatus status = MStatus::kSuccess; + + total_memory = 0; + free_memory = 0; + used_memory = 0; + + MGLFunctionTable *gGLFT = nullptr; + const MHardwareRenderer *hardware_renderer_ptr = + MHardwareRenderer::theRenderer(); + if (hardware_renderer_ptr) { + gGLFT = hardware_renderer_ptr->glFunctionTable(); + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "gGLFT=" + << gGLFT); + } + + if (!gGLFT) { + MMSOLVER_MAYA_ERR( + "mmmemorygpu::gpu_memory_usage: " + "Could not get OpenGL Function Table!"); + return MStatus::kFailure; + } + + const bool has_extension_nvidia = + gGLFT->extensionExists(MGLExtension::kMGLext_NVX_gpu_memory_info); + const bool has_extension_ati = + gGLFT->extensionExists(MGLExtension::kMGLext_ATI_meminfo); + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "has_extension_nvidia=" + << has_extension_nvidia); + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "has_extension_ati=" + << has_extension_ati); + + const size_t kilobytes_to_bytes = 1024; + if (has_extension_nvidia) { + // https://registry.khronos.org/OpenGL/extensions/NVX/NVX_gpu_memory_info.txt + MGLint nvidia_total_memory_as_kilobytes = 0; + MGLint nvidia_free_memory_as_kilobytes = 0; + gGLFT->glGetIntegerv(MGL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, + &nvidia_total_memory_as_kilobytes); + gGLFT->glGetIntegerv(MGL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, + &nvidia_free_memory_as_kilobytes); + total_memory = static_cast(nvidia_total_memory_as_kilobytes) * + kilobytes_to_bytes; + free_memory = static_cast(nvidia_free_memory_as_kilobytes) * + kilobytes_to_bytes; + used_memory = total_memory - free_memory; + } else if (has_extension_ati) { + // https://registry.khronos.org/OpenGL/extensions/ATI/ATI_meminfo.txt + MGLint ati_vbo_free_memory[4]; + gGLFT->glGetIntegerv(MGL_VBO_FREE_MEMORY_ATI, ati_vbo_free_memory); + + MGLint ati_free_memory_as_kilobytes = ati_vbo_free_memory[0]; + MGLint ati_free_memory_largest_block_as_kilobytes = + ati_vbo_free_memory[1]; + + // Auxiliary memory is memory that an implementation may use + // as a backup to its primary pool for a certain type of + // allocation. + MGLint ati_free_auxiliary_memory_as_kilobytes = ati_vbo_free_memory[2]; + MGLint ati_free_auxiliary_memory_largest_block_as_kilobytes = + ati_vbo_free_memory[3]; + + // ATL cards (using this extension or the extensions supported + // by Maya) do not allow getting the total amount of memory on + // the device, so we must call our function. + size_t ati_total_memory_as_bytes = 0; + status = memory_total_size_in_bytes(ati_total_memory_as_bytes); + CHECK_MSTATUS_AND_RETURN_IT(status); + + total_memory = ati_total_memory_as_bytes; + free_memory = static_cast(ati_free_memory_as_kilobytes) * + kilobytes_to_bytes; + used_memory = total_memory - free_memory; + } else { + MMSOLVER_MAYA_ERR( + "mmmemorygpu::gpu_memory_usage: " + "Neither GL_NVX_gpu_memory_info nor GL_ATI_meminfo " + "extensions are supported on this system."); + status = MStatus::kFailure; + } + + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "total_memory=" + << total_memory); + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "free_memory=" + << free_memory); + MMSOLVER_MAYA_VRB( + "mmmemorygpu::gpu_memory_usage: " + "used_memory=" + << used_memory); + + return status; +} + +MStatus memory_used_size_in_bytes(size_t &out_size_in_bytes) { + out_size_in_bytes = 0; + + size_t total_memory = 0; + size_t free_memory = 0; + size_t used_memory = 0; + + MStatus status = gpu_memory_usage(total_memory, free_memory, used_memory); + CHECK_MSTATUS_AND_RETURN_IT(status); + + out_size_in_bytes = used_memory; + + return MStatus::kSuccess; +} + +MStatus memory_free_size_in_bytes(size_t &out_size_in_bytes) { + out_size_in_bytes = 0; + + size_t total_memory = 0; + size_t free_memory = 0; + size_t used_memory = 0; + + MStatus status = gpu_memory_usage(total_memory, free_memory, used_memory); + CHECK_MSTATUS_AND_RETURN_IT(status); + + out_size_in_bytes = free_memory; + + return MStatus::kSuccess; +} + +// Gets the GPU memory used by the current Maya process. +// +// NOTE: This is not all the memory used by the GPU, only by the +// current Maya instance. +MStatus current_maya_process_memory_used_size_in_bytes( + size_t &out_size_in_bytes) { + out_size_in_bytes = 0; + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_WRN( + "mmmemorygpu::current_maya_process_memory_used_size_in_bytes: " + "Failed to get Maya MRenderer!"); + return MStatus::kFailure; + } + out_size_in_bytes = static_cast(renderer->GPUUsedMemorySize( + MHWRender::MRenderer::MGPUMemType::kMemAll)); + return MStatus::kSuccess; +} + +// These methods can be used to inform Maya's internal system of any +// GPU memory that we allocate/de-allocate. +MStatus register_allocated_memory_size_in_bytes(const size_t size_in_bytes) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_WRN( + "mmmemorygpu::register_allocated_memory_size_in_bytes: " + "Failed to get Maya MRenderer!"); + return MStatus::kFailure; + } + MInt64 *evictedGPUMemSize = nullptr; + return renderer->holdGPUMemory(size_in_bytes, evictedGPUMemSize); +} + +MStatus register_deallocated_memory_size_in_bytes(const size_t size_in_bytes) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_WRN( + "mmmemorygpu::register_deallocated_memory_size_in_bytes: " + "Failed to get Maya MRenderer!"); + return MStatus::kFailure; + } + return renderer->releaseGPUMemory(size_in_bytes); +} + +} // namespace mmmemorygpu diff --git a/src/mmSolver/utilities/memory_gpu_utils.h b/src/mmSolver/utilities/memory_gpu_utils.h new file mode 100644 index 000000000..549aa510e --- /dev/null +++ b/src/mmSolver/utilities/memory_gpu_utils.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Get details about the GPU memory. + */ + +#ifndef MEMORY_GPU_UTILS_H +#define MEMORY_GPU_UTILS_H + +// Maya +#include + +namespace mmmemorygpu { + +MStatus memory_total_size_in_bytes(size_t &out_size_in_bytes); +MStatus memory_used_size_in_bytes(size_t &out_size_in_bytes); +MStatus memory_free_size_in_bytes(size_t &out_size_in_bytes); + +MStatus current_maya_process_memory_used_size_in_bytes( + size_t &out_size_in_bytes); + +MStatus register_allocated_memory_size_in_bytes(const size_t size_in_bytes); +MStatus register_deallocated_memory_size_in_bytes(const size_t size_in_bytes); + +} // namespace mmmemorygpu + +#endif // MEMORY_GPU_UTILS_H From 14c5c81fd91df30db401e1cab33f9834b071d13c Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 19:55:12 +1000 Subject: [PATCH 064/295] Memory GPU Utilities - turn off verbose logging. --- src/mmSolver/utilities/memory_gpu_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/utilities/memory_gpu_utils.cpp b/src/mmSolver/utilities/memory_gpu_utils.cpp index ecbc9230e..9b4ca7632 100644 --- a/src/mmSolver/utilities/memory_gpu_utils.cpp +++ b/src/mmSolver/utilities/memory_gpu_utils.cpp @@ -55,7 +55,7 @@ MStatus memory_total_size_in_bytes(size_t &out_size_in_bytes) { MStatus gpu_memory_usage(size_t &total_memory, size_t &free_memory, size_t &used_memory) { - const bool verbose = true; + const bool verbose = false; MStatus status = MStatus::kSuccess; total_memory = 0; From 04bec798e2d164c925515cb35b3f24dc98f90518 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 22:05:00 +1000 Subject: [PATCH 065/295] Add mmImageCache command The mmImageCache command is used to query and set the ImageCache internal values. The command is not yet complete, but this commit represents a good starting point. GitHub issue #252. --- src/CMakeLists.txt | 1 + src/mmSolver/cmd/MMImageCacheCmd.cpp | 336 +++++++++++++++++++++++++++ src/mmSolver/cmd/MMImageCacheCmd.h | 168 ++++++++++++++ src/mmSolver/image/ImageCache.h | 2 +- src/mmSolver/pluginMain.cpp | 6 + 5 files changed, 512 insertions(+), 1 deletion(-) create mode 100644 src/mmSolver/cmd/MMImageCacheCmd.cpp create mode 100644 src/mmSolver/cmd/MMImageCacheCmd.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a7f5f1899..aaf9de90b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -43,6 +43,7 @@ set(SOURCE_FILES mmSolver/cmd/MMCameraSolveCmd.cpp mmSolver/cmd/MMColorIOCmd.cpp mmSolver/cmd/MMConvertImageCmd.cpp + mmSolver/cmd/MMImageCacheCmd.cpp mmSolver/cmd/MMMarkerHomographyCmd.cpp mmSolver/cmd/MMMemoryGPUCmd.cpp mmSolver/cmd/MMMemorySystemCmd.cpp diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp new file mode 100644 index 000000000..287c03c77 --- /dev/null +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "MMImageCacheCmd.h" + +// STD +#include +#include + +// Maya +#include +#include +#include +#include + +// Maya Viewport 2.0 +#include + +// MM Solver +#include + +#include "mmSolver/image/ImageCache.h" +#include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/path_utils.h" +#include "mmSolver/utilities/string_utils.h" + +// Command arguments and command name: +#define GPU_CAPACITY_FLAG "-gpc" +#define GPU_CAPACITY_FLAG_LONG "-gpuCapacity" + +#define GPU_USED_FLAG "-gpu" +#define GPU_USED_FLAG_LONG "-gpuUsed" + +#define CPU_CAPACITY_FLAG "-cpc" +#define CPU_CAPACITY_FLAG_LONG "-cpuCapacity" + +#define CPU_USED_FLAG "-cpu" +#define CPU_USED_FLAG_LONG "-cpuUsed" + +#define PRINT_BRIEF_FLAG "-pbf" +#define PRINT_BRIEF_FLAG_LONG "-printBrief" + +namespace mmsolver { + +MMImageCacheCmd::~MMImageCacheCmd() {} + +void *MMImageCacheCmd::creator() { return new MMImageCacheCmd(); } + +MString MMImageCacheCmd::cmdName() { return MString("mmImageCache"); } + +/* + * Tell Maya we have a syntax function. + */ +bool MMImageCacheCmd::hasSyntax() const { return true; } + +bool MMImageCacheCmd::isUndoable() const { return true; } + +/* + * Add flags to the command syntax + */ +MSyntax MMImageCacheCmd::newSyntax() { + MStatus status = MStatus::kSuccess; + + MSyntax syntax; + syntax.enableQuery(true); + syntax.enableEdit(true); + + CHECK_MSTATUS(syntax.addFlag(GPU_CAPACITY_FLAG, GPU_CAPACITY_FLAG_LONG, + MSyntax::kString)); + CHECK_MSTATUS(syntax.addFlag(CPU_CAPACITY_FLAG, CPU_CAPACITY_FLAG_LONG, + MSyntax::kString)); + + CHECK_MSTATUS(syntax.addFlag(GPU_USED_FLAG, GPU_USED_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(CPU_USED_FLAG, CPU_USED_FLAG_LONG)); + + CHECK_MSTATUS(syntax.addFlag(PRINT_BRIEF_FLAG, PRINT_BRIEF_FLAG_LONG)); + + return syntax; +} + +/* + * Parse command line arguments + */ +MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + MArgDatabase argData(syntax(), args, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + + m_is_query = argData.isQuery(); + m_is_edit = argData.isEdit(); + + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "m_is_query=" + << m_is_query); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "m_is_edit=" + << m_is_edit); + + const bool has_gpu_capacity = argData.isFlagSet(GPU_CAPACITY_FLAG, &status); + const bool has_cpu_capacity = argData.isFlagSet(CPU_CAPACITY_FLAG, &status); + const bool has_gpu_used = argData.isFlagSet(GPU_USED_FLAG, &status); + const bool has_cpu_used = argData.isFlagSet(CPU_USED_FLAG, &status); + const bool has_print_brief = argData.isFlagSet(PRINT_BRIEF_FLAG, &status); + + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "has_gpu_capacity=" + << has_gpu_capacity); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "has_cpu_capacity=" + << has_cpu_capacity); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "has_gpu_used=" + << has_gpu_used); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "has_cpu_used=" + << has_cpu_used); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "has_print_brief=" + << has_print_brief); + + if (m_is_query) { + if (has_gpu_capacity) { + m_command_flag = ImageCacheFlagMode::kGpuCapacity; + } else if (has_cpu_capacity) { + m_command_flag = ImageCacheFlagMode::kCpuCapacity; + } else if (has_gpu_used) { + m_command_flag = ImageCacheFlagMode::kGpuUsed; + } else if (has_cpu_used) { + m_command_flag = ImageCacheFlagMode::kCpuUsed; + } else if (has_print_brief) { + m_command_flag = ImageCacheFlagMode::kPrintBrief; + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::parseArgs: " + "Invalid command query flag!" + "value=" + << static_cast(m_command_flag)); + return MStatus::kFailure; + } + } else if (m_is_edit) { + image::ImageCache &image_cache = image::ImageCache::getInstance(); + + if (has_gpu_capacity) { + m_command_flag = ImageCacheFlagMode::kGpuCapacity; + MString mstring; + status = argData.getFlagArgument(GPU_CAPACITY_FLAG, 0, mstring); + CHECK_MSTATUS_AND_RETURN_IT(status); + std::string std_string = mstring.asChar(); + m_gpu_capacity_bytes = mmstring::stringToNumber(std_string); + + // Store the current value, so we can undo later. + m_previous_gpu_capacity_bytes = + image_cache.get_gpu_capacity_bytes(); + } else if (has_cpu_capacity) { + m_command_flag = ImageCacheFlagMode::kCpuCapacity; + MString mstring; + status = argData.getFlagArgument(CPU_CAPACITY_FLAG, 0, mstring); + CHECK_MSTATUS_AND_RETURN_IT(status); + std::string std_string = mstring.asChar(); + m_cpu_capacity_bytes = mmstring::stringToNumber(std_string); + + // Store the current value, so we can undo later. + m_previous_cpu_capacity_bytes = + image_cache.get_cpu_capacity_bytes(); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::parseArgs: " + "Invalid command edit flag!" + "value=" + << static_cast(m_command_flag)); + return MStatus::kFailure; + } + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::parseArgs: " + "Command is not in query or edit mode! " + "Please use query or edit mode."); + return MStatus::kFailure; + } + + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "Command flag=" + << static_cast(m_command_flag)); + + return status; +} + +inline MStatus get_texture_manager( + MHWRender::MTextureManager *&texture_manager) { + MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); + if (!renderer) { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::get_texture_manager: " + "Could not get MRenderer!"); + return MStatus::kFailure; + } + + texture_manager = renderer->getTextureManager(); + if (!texture_manager) { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::get_texture_manager: " + "Could not get MTextureManager!"); + return MStatus::kFailure; + } + + return MStatus::kSuccess; +} + +inline MStatus set_values(const ImageCacheFlagMode command_flag, + const size_t gpu_capacity_bytes, + const size_t cpu_capacity_bytes) { + MStatus status = MStatus::kSuccess; + + image::ImageCache &image_cache = image::ImageCache::getInstance(); + if (command_flag == ImageCacheFlagMode::kGpuCapacity) { + MHWRender::MTextureManager *texture_manager = nullptr; + status = get_texture_manager(texture_manager); + CHECK_MSTATUS_AND_RETURN_IT(status); + + image_cache.set_gpu_capacity_bytes(texture_manager, gpu_capacity_bytes); + } else if (command_flag == ImageCacheFlagMode::kCpuCapacity) { + image_cache.set_cpu_capacity_bytes(cpu_capacity_bytes); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::set_values: " + "Invalid command edit flag! " + "value=" + << static_cast(command_flag)); + return MStatus::kFailure; + } + + return status; +} + +MStatus MMImageCacheCmd::doIt(const MArgList &args) { + MStatus status = MStatus::kSuccess; + const bool verbose = false; + + // Read all the flag arguments. + status = parseArgs(args); + CHECK_MSTATUS_AND_RETURN_IT(status); + + image::ImageCache &image_cache = image::ImageCache::getInstance(); + + if (m_is_query) { + if (m_command_flag == ImageCacheFlagMode::kPrintBrief) { + // Prints to terminal/Output Window. + image_cache.print_brief(); + MString number_mstring = "TODO: brief text here"; + MMImageCacheCmd::setResult(number_mstring); + } else { + size_t bytes_value = 0; + if (m_command_flag == ImageCacheFlagMode::kGpuCapacity) { + bytes_value = image_cache.get_gpu_capacity_bytes(); + } else if (m_command_flag == ImageCacheFlagMode::kCpuCapacity) { + bytes_value = image_cache.get_cpu_capacity_bytes(); + } else if (m_command_flag == ImageCacheFlagMode::kGpuUsed) { + bytes_value = image_cache.get_gpu_used_bytes(); + } else if (m_command_flag == ImageCacheFlagMode::kCpuUsed) { + bytes_value = image_cache.get_cpu_used_bytes(); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(m_command_flag)); + return MStatus::kFailure; + } + + std::string number_string = mmstring::numberToString(bytes_value); + MString number_mstring(number_string.c_str()); + MMImageCacheCmd::setResult(number_mstring); + } + } else if (m_is_edit) { + set_values(m_command_flag, m_gpu_capacity_bytes, m_cpu_capacity_bytes); + CHECK_MSTATUS_AND_RETURN_IT(status); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Command is not in query or edit mode! " + "Please use query or edit mode."); + return MStatus::kFailure; + } + + return status; +} + +MStatus MMImageCacheCmd::redoIt() { + MStatus status = MStatus::kSuccess; + if (m_is_edit) { + status = set_values(m_command_flag, m_gpu_capacity_bytes, + m_cpu_capacity_bytes); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + return status; +} + +MStatus MMImageCacheCmd::undoIt() { + MStatus status = MStatus::kSuccess; + if (m_is_edit) { + status = set_values(m_command_flag, m_previous_gpu_capacity_bytes, + m_previous_cpu_capacity_bytes); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + return status; +} + +} // namespace mmsolver diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h new file mode 100644 index 000000000..dc2cc7f5a --- /dev/null +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * 'mmImageCache' Command for querying and manipulating the MM Solver + * ImageCache. + * + * The mmImageCache command is responsible for querying and setting + * details of the underlying image cache. + * + * + * Use cases: + * - Get total amount of used CPU memory, displayed in a UI. + * - Get total amount of CPU memory on the system. + * - Get amount of CPU memory used by the current process. + * - Get total GPU memory. + * - Get used GPU memory. + * - Display on an image plane node... + * - How much memory the currently loaded image sequence is expected + * to take up. + * - How much memory each frame takes up. + * - What is the currently used CPU and GPU memory by this image + * sequence/image plane. + * - UI to display overall memory usage, with... + * - Each loaded image sequence. + * - How much memory is used by each image sequence (CPU and GPU). + * - The total CPU and GPU memory on the system. + * - The used CPU and GPU memory on the system. + * - A widget to set the Image Cache memory available as a percentage + * or fixed memory amount. + * + * + * MEL: + * // Return the number of used GPU memory bytes by the image cache. + * mmImageCache -query -gpuCapacityUsedBytes; + * + * // Return the number of free GPU memory bytes by the image cache. + * mmImageCache -query -gpuCapacityFreeBytes; + * + * // Return the total number of GPU memory bytes that is allowed + * // to be to be used by the image cache. + * mmImageCache -query -gpuCapacityAllowedBytes; + * + * // Set the number of GPU memory bytes that is allowed to be used. + * mmImageCache -edit -gpuCapacityBytes 1000; + * + * // Set the minimum number of GPU images allowed to be stored in + * // the cache. + * mmImageCache -edit -gpuItemMinimum 3; + * + * // Get the amount of data that the image contains. + * // + * // This will not actually read the file into memory, just enough + * // to find the data type and dimensions. + * string $image_sequence = "/path/to/image.####.png"; + * string $image_file = "/path/to/image.1001.png"; + * int $data_size = `mmReadImage -dataSizeBytes $image_file`; + * + * // Set the number of CPU memory bytes that is allowed to be used. + * int $start_frame = 1001; + * int $end_frame = 1101; + * int $frame_count = $end_frame - $start_frame; + * int $extra_buffer = 3; // allow a few more images just in case. + * // In reality we should check if the system has enough free + * // CPU memory, before attempting to load the full image sequence + * // or not. + * int $new_capacity = ($data_size * ($frame_count + $extra_buffer)) + * mmImageCache -edit -cpuCapacityBytes $new_capacity; + * + * // Read the image sequence into RAM. + * // + * // This call will block until all the images are read and loaded. + * // Progress will be printed to std::cout. + * mmImageCache -cpuLoadImageSequence + * -startFrame $start_frame + * -endFrame $end_frame + * $image_sequence; + * + * // Loads the images (already in the CPU cache - see above), and + * // writes them out to the Disk cache. + * mmImageCache -writeDiskCache + * -startFrame $start_frame + * -endFrame $end_frame + * $image_sequence; + * + */ + +#ifndef MAYA_MM_IMAGE_CACHE_CMD_H +#define MAYA_MM_IMAGE_CACHE_CMD_H + +// Maya +#include +#include +#include +#include +#include +#include + +namespace mmsolver { + +enum class ImageCacheFlagMode : uint8_t { + kGpuCapacity = 0, + kCpuCapacity, + kGpuUsed, + kCpuUsed, + kPrintBrief, + kUnknown = 255 +}; + +class MMImageCacheCmd : public MPxCommand { +public: + MMImageCacheCmd() + : m_is_query(false) + , m_is_edit(false) + , m_command_flag(ImageCacheFlagMode::kUnknown) + , m_previous_gpu_capacity_bytes(0) + , m_previous_cpu_capacity_bytes(0) + , m_gpu_capacity_bytes(0) + , m_cpu_capacity_bytes(0){}; + + virtual ~MMImageCacheCmd(); + + virtual bool hasSyntax() const; + static MSyntax newSyntax(); + + virtual MStatus doIt(const MArgList &args); + virtual bool isUndoable() const; + virtual MStatus undoIt(); + virtual MStatus redoIt(); + + static void *creator(); + + static MString cmdName(); + +private: + MStatus parseArgs(const MArgList &args); + + bool m_is_edit; + bool m_is_query; + + ImageCacheFlagMode m_command_flag; + + // The previous values, before the command was run. + size_t m_previous_gpu_capacity_bytes; + size_t m_previous_cpu_capacity_bytes; + + size_t m_gpu_capacity_bytes; + size_t m_cpu_capacity_bytes; +}; + +} // namespace mmsolver + +#endif // MAYA_MM_IMAGE_CACHE_CMD_H diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 7a7097be5..c3475bf17 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -195,7 +195,7 @@ struct ImageCache { return m_cpu_used_bytes; } - void print_cache_details() const { + void print_brief() const { MMSOLVER_MAYA_INFO( "mmsolver::ImageCache::print_cache_details:" << " GPU cache item count=" diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index b57ca8e69..e25c27d2a 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -41,6 +41,7 @@ #include "mmSolver/cmd/MMCameraSolveCmd.h" #include "mmSolver/cmd/MMColorIOCmd.h" #include "mmSolver/cmd/MMConvertImageCmd.h" +#include "mmSolver/cmd/MMImageCacheCmd.h" #include "mmSolver/cmd/MMMarkerHomographyCmd.h" #include "mmSolver/cmd/MMMemoryGPUCmd.h" #include "mmSolver/cmd/MMMemorySystemCmd.h" @@ -264,6 +265,10 @@ MStatus initializePlugin(MObject obj) { mmsolver::MMConvertImageCmd::creator, mmsolver::MMConvertImageCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMImageCacheCmd::cmdName(), + mmsolver::MMImageCacheCmd::creator, + mmsolver::MMImageCacheCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::MMMarkerHomographyCmd::cmdName(), mmsolver::MMMarkerHomographyCmd::creator, mmsolver::MMMarkerHomographyCmd::newSyntax, status); @@ -633,6 +638,7 @@ MStatus uninitializePlugin(MObject obj) { status); DEREGISTER_COMMAND(plugin, mmsolver::MMCameraSolveCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMColorIOCmd::cmdName(), status); + DEREGISTER_COMMAND(plugin, mmsolver::MMImageCacheCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMConvertImageCmd::cmdName(), status); DEREGISTER_COMMAND(plugin, mmsolver::MMMarkerHomographyCmd::cmdName(), status); From 9cc63c78022829ada72c9dbac45199f0549336a9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 22:16:14 +1000 Subject: [PATCH 066/295] Image Cache - Change Default CPU/GPU Capacity Also removes the '*_min_item_count' from the ImageCache - it was never used. --- src/mmSolver/image/ImageCache.h | 57 +++++---------------------------- 1 file changed, 8 insertions(+), 49 deletions(-) diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index c3475bf17..ab0b5e71a 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -86,7 +86,12 @@ namespace image { // retained - even if it's not colour accurate. // +// The default ImageCache sizes. +const size_t kDEFAULT_GPU_CAPACITY_BYTES = BYTES_TO_MEGABYTES * 128; +const size_t kDEFAULT_CPU_CAPACITY_BYTES = BYTES_TO_MEGABYTES * 1024; + struct ImageCache { + // GPU data types using GPUCacheKey = std::string; using GPUCacheValue = TextureData; using GPUKeyList = std::list; @@ -95,6 +100,7 @@ struct ImageCache { std::unordered_map>; using GPUMapIt = GPUMap::iterator; + // CPU data types using CPUCacheKey = std::string; using CPUCacheValue = ImagePixelData; using CPUKeyList = std::list; @@ -113,10 +119,8 @@ struct ImageCache { private: // Constructor. The {} brackets are needed here. ImageCache() - : m_gpu_min_item_count(1) - , m_cpu_min_item_count(1) - , m_gpu_capacity_bytes(200 * BYTES_TO_MEGABYTES) - , m_cpu_capacity_bytes(1000 * BYTES_TO_MEGABYTES) + : m_gpu_capacity_bytes(kDEFAULT_GPU_CAPACITY_BYTES) + , m_cpu_capacity_bytes(kDEFAULT_CPU_CAPACITY_BYTES) , m_gpu_used_bytes(0) , m_cpu_used_bytes(0) {} @@ -128,47 +132,6 @@ struct ImageCache { } public: - // TODO: Allow a minimum number of items to be cached. For example - // even if the GPU memory capacity is zero bytes, we allow at - // least N items, "no questions asked". - size_t get_gpu_min_item_count() const { - const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_min_item_count: " - << "m_gpu_min_item_count=" << m_gpu_min_item_count); - - return m_gpu_min_item_count; - } - size_t get_cpu_min_item_count() const { - const bool verbose = false; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_min_item_count: " - << "m_cpu_min_item_count=" << m_cpu_min_item_count); - - return m_cpu_min_item_count; - } - - void set_gpu_min_item_count(const size_t value) { - const bool verbose = false; - - // TODO: Evict the contents of the cache, if the current - // number of cached items exceeds the new capacity size. - m_gpu_min_item_count = value; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_min_item_count: " - << "m_gpu_min_item_count=" << m_gpu_min_item_count); - } - - void set_cpu_min_item_count(const size_t value) { - const bool verbose = false; - - // TODO: Evict the contents of the cache, if the current - // number of cached items exceeds the new capacity size. - m_cpu_min_item_count = value; - - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_min_item_count: " - << "m_cpu_min_item_count=" << m_cpu_min_item_count); - } - // Get the capacity of the GPU and CPU. size_t get_gpu_capacity_bytes() const { const bool verbose = false; @@ -378,10 +341,6 @@ struct ImageCache { const size_t new_memory_chunk_size); bool cpu_evict_enough_for_new_entry(const size_t new_memory_chunk_size); - // Number of items allowed in the cache. - size_t m_gpu_min_item_count; - size_t m_cpu_min_item_count; - // Amount of memory capacity. size_t m_gpu_capacity_bytes; size_t m_cpu_capacity_bytes; From d42c50bf89bcd20d399bb5d1869007344eca02e5 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 22:17:07 +1000 Subject: [PATCH 067/295] Make MTextureManager variable names consistent style. --- .../shape/ImagePlaneGeometry2Override.cpp | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index ecdd97606..e6fa3c4f3 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -115,10 +115,10 @@ ImagePlaneGeometry2Override::~ImagePlaneGeometry2Override() { if (m_color_texture) { MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); - MHWRender::MTextureManager *textureMgr = + MHWRender::MTextureManager *texture_manager = renderer ? renderer->getTextureManager() : nullptr; - if (textureMgr) { - textureMgr->releaseTexture(m_color_texture); + if (texture_manager) { + texture_manager->releaseTexture(m_color_texture); m_color_texture = nullptr; } } @@ -396,7 +396,7 @@ inline MFloatMatrix create_saturation_matrix(const float saturation) { } void ImagePlaneGeometry2Override::set_shader_instance_parameters( - MShaderInstance *shader, MHWRender::MTextureManager *textureManager, + MShaderInstance *shader, MHWRender::MTextureManager *texture_manager, const MColor &color_gain, const float color_exposure, const float color_gamma, const float color_saturation, const float color_soft_clip, const float alpha_gain, @@ -477,17 +477,8 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const bool do_texture_update = false; image::ImageCache &image_cache = image::ImageCache::getInstance(); - // // TODO: Set the capacity using a command, and use sensible - // // defaults. - // image_cache.set_gpu_min_item_count(10); - // image_cache.set_cpu_min_item_count(1000); - // image_cache.get_gpu_capacity_bytes(); - // image_cache.get_cpu_capacity_bytes(); - // image_cache.get_gpu_used_bytes(); - // image_cache.get_cpu_used_bytes(); - // image_cache.print_cache_details(); out_color_texture = image::read_texture_image_file( - textureManager, image_cache, m_temp_image, expanded_file_path, + texture_manager, image_cache, m_temp_image, expanded_file_path, pixel_type, do_texture_update); if (out_color_texture) { @@ -736,16 +727,16 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, } if (m_shader) { - MHWRender::MTextureManager *textureManager = + MHWRender::MTextureManager *texture_manager = renderer->getTextureManager(); - if (!textureManager) { + if (!texture_manager) { MMSOLVER_MAYA_WRN( "mmImagePlaneShape: Could not get MTextureManager."); return; } set_shader_instance_parameters( - m_shader, textureManager, m_color_gain, m_color_exposure, + m_shader, texture_manager, m_color_gain, m_color_exposure, m_color_gamma, m_color_saturation, m_color_soft_clip, m_alpha_gain, m_default_color, m_ignore_alpha, m_flip, m_flop, m_is_transparent, m_image_display_channel, m_frame, m_file_path, From 74b7fa65eadafd5780d63f98456958e87f8fef32 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 23:20:26 +1000 Subject: [PATCH 068/295] ImageCache - Refactor generating cache debug text. Changes the "-printBrief" flag to "-briefText". --- src/mmSolver/cmd/MMImageCacheCmd.cpp | 37 +++++++++-------- src/mmSolver/cmd/MMImageCacheCmd.h | 2 +- src/mmSolver/image/ImageCache.cpp | 60 ++++++++++++++++++++++++++++ src/mmSolver/image/ImageCache.h | 32 ++------------- 4 files changed, 85 insertions(+), 46 deletions(-) diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 287c03c77..00e148338 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -55,8 +55,8 @@ #define CPU_USED_FLAG "-cpu" #define CPU_USED_FLAG_LONG "-cpuUsed" -#define PRINT_BRIEF_FLAG "-pbf" -#define PRINT_BRIEF_FLAG_LONG "-printBrief" +#define BRIEF_TEXT_FLAG "-btx" +#define BRIEF_TEXT_FLAG_LONG "-briefText" namespace mmsolver { @@ -91,7 +91,7 @@ MSyntax MMImageCacheCmd::newSyntax() { CHECK_MSTATUS(syntax.addFlag(GPU_USED_FLAG, GPU_USED_FLAG_LONG)); CHECK_MSTATUS(syntax.addFlag(CPU_USED_FLAG, CPU_USED_FLAG_LONG)); - CHECK_MSTATUS(syntax.addFlag(PRINT_BRIEF_FLAG, PRINT_BRIEF_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(BRIEF_TEXT_FLAG, BRIEF_TEXT_FLAG_LONG)); return syntax; } @@ -122,7 +122,7 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { const bool has_cpu_capacity = argData.isFlagSet(CPU_CAPACITY_FLAG, &status); const bool has_gpu_used = argData.isFlagSet(GPU_USED_FLAG, &status); const bool has_cpu_used = argData.isFlagSet(CPU_USED_FLAG, &status); - const bool has_print_brief = argData.isFlagSet(PRINT_BRIEF_FLAG, &status); + const bool has_print_brief = argData.isFlagSet(BRIEF_TEXT_FLAG, &status); MMSOLVER_MAYA_VRB( "MMImageCacheCmd::parseArgs: " @@ -155,7 +155,7 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { } else if (has_cpu_used) { m_command_flag = ImageCacheFlagMode::kCpuUsed; } else if (has_print_brief) { - m_command_flag = ImageCacheFlagMode::kPrintBrief; + m_command_flag = ImageCacheFlagMode::kGenerateBriefText; } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::parseArgs: " @@ -234,12 +234,12 @@ inline MStatus get_texture_manager( return MStatus::kSuccess; } -inline MStatus set_values(const ImageCacheFlagMode command_flag, +inline MStatus set_values(image::ImageCache &image_cache, + const ImageCacheFlagMode command_flag, const size_t gpu_capacity_bytes, const size_t cpu_capacity_bytes) { MStatus status = MStatus::kSuccess; - image::ImageCache &image_cache = image::ImageCache::getInstance(); if (command_flag == ImageCacheFlagMode::kGpuCapacity) { MHWRender::MTextureManager *texture_manager = nullptr; status = get_texture_manager(texture_manager); @@ -268,14 +268,12 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { status = parseArgs(args); CHECK_MSTATUS_AND_RETURN_IT(status); - image::ImageCache &image_cache = image::ImageCache::getInstance(); - if (m_is_query) { - if (m_command_flag == ImageCacheFlagMode::kPrintBrief) { - // Prints to terminal/Output Window. - image_cache.print_brief(); - MString number_mstring = "TODO: brief text here"; - MMImageCacheCmd::setResult(number_mstring); + image::ImageCache &image_cache = image::ImageCache::getInstance(); + + if (m_command_flag == ImageCacheFlagMode::kGenerateBriefText) { + MString mstring = image_cache.generate_cache_brief_text(); + MMImageCacheCmd::setResult(mstring); } else { size_t bytes_value = 0; if (m_command_flag == ImageCacheFlagMode::kGpuCapacity) { @@ -300,7 +298,9 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { MMImageCacheCmd::setResult(number_mstring); } } else if (m_is_edit) { - set_values(m_command_flag, m_gpu_capacity_bytes, m_cpu_capacity_bytes); + image::ImageCache &image_cache = image::ImageCache::getInstance(); + set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, + m_cpu_capacity_bytes); CHECK_MSTATUS_AND_RETURN_IT(status); } else { MMSOLVER_MAYA_ERR( @@ -316,7 +316,8 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { MStatus MMImageCacheCmd::redoIt() { MStatus status = MStatus::kSuccess; if (m_is_edit) { - status = set_values(m_command_flag, m_gpu_capacity_bytes, + image::ImageCache &image_cache = image::ImageCache::getInstance(); + status = set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, m_cpu_capacity_bytes); CHECK_MSTATUS_AND_RETURN_IT(status); } @@ -326,7 +327,9 @@ MStatus MMImageCacheCmd::redoIt() { MStatus MMImageCacheCmd::undoIt() { MStatus status = MStatus::kSuccess; if (m_is_edit) { - status = set_values(m_command_flag, m_previous_gpu_capacity_bytes, + image::ImageCache &image_cache = image::ImageCache::getInstance(); + status = set_values(image_cache, m_command_flag, + m_previous_gpu_capacity_bytes, m_previous_cpu_capacity_bytes); CHECK_MSTATUS_AND_RETURN_IT(status); } diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index dc2cc7f5a..1e2f6f99b 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -118,7 +118,7 @@ enum class ImageCacheFlagMode : uint8_t { kCpuCapacity, kGpuUsed, kCpuUsed, - kPrintBrief, + kGenerateBriefText, kUnknown = 255 }; diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 9390bede7..4822b94b7 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -54,6 +54,7 @@ #include "mmSolver/shape/constant_texture_data.h" #include "mmSolver/utilities/number_utils.h" #include "mmSolver/utilities/path_utils.h" +#include "mmSolver/utilities/string_utils.h" namespace mmsolver { namespace image { @@ -189,6 +190,65 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, return texture_data.texture(); } +inline std::string generate_cache_brief(const char *prefix_str, + const size_t item_count, + const size_t capacity_bytes, + const size_t used_bytes) { + const size_t capacity_megabytes = capacity_bytes / BYTES_TO_MEGABYTES; + const size_t used_megabytes = used_bytes / BYTES_TO_MEGABYTES; + + std::string capacity_megabytes_str = + mmstring::numberToStringWithCommas(capacity_megabytes); + + std::string used_megabytes_str = + mmstring::numberToStringWithCommas(used_megabytes); + + double used_percent = 0.0; + if (capacity_bytes > 0) { + used_percent = (static_cast(used_bytes) / + static_cast(capacity_bytes)) * + 100.0; + } + + std::stringstream ss; + ss << prefix_str << "count=" << item_count << " items " + << "| used=" << used_megabytes_str << "MB " + << "| capacity=" << used_megabytes_str << "MB " + << "| percent=" << used_percent << '%'; + return ss.str(); +} + +MString ImageCache::generate_cache_brief_text() const { + std::string gpu_cache_text = + generate_cache_brief("GPU cache | ", m_gpu_cache_map.size(), + m_gpu_capacity_bytes, m_gpu_used_bytes); + std::string cpu_cache_text = + generate_cache_brief("CPU cache | ", m_cpu_cache_map.size(), + m_cpu_capacity_bytes, m_cpu_used_bytes); + + std::stringstream ss; + ss << gpu_cache_text << std::endl << cpu_cache_text << std::endl; + + std::string string = ss.str(); + MString mstring = MString(string.c_str()); + return mstring; +} + +void ImageCache::print_cache_brief() const { + std::string gpu_cache_text = + generate_cache_brief("GPU cache | ", m_gpu_cache_map.size(), + m_gpu_capacity_bytes, m_gpu_used_bytes); + std::string cpu_cache_text = + generate_cache_brief("CPU cache | ", m_cpu_cache_map.size(), + m_cpu_capacity_bytes, m_cpu_used_bytes); + + MMSOLVER_MAYA_INFO( + "mmsolver::ImageCache::print_cache_brief: " << gpu_cache_text); + MMSOLVER_MAYA_INFO( + "mmsolver::ImageCache::print_cache_brief: " << cpu_cache_text); + return; +} + bool ImageCache::cpu_insert(const CPUCacheKey &key, const CPUCacheValue &image_pixel_data) { const bool verbose = false; diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index ab0b5e71a..8b463ee08 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -158,34 +158,6 @@ struct ImageCache { return m_cpu_used_bytes; } - void print_brief() const { - MMSOLVER_MAYA_INFO( - "mmsolver::ImageCache::print_cache_details:" - << " GPU cache item count=" - << m_gpu_cache_map.size() - // << " GPU min item count=" << m_gpu_min_item_count - << " used MB=" - << (static_cast(m_gpu_used_bytes) * BYTES_TO_MEGABYTES) - << " capacity MB=" - << (static_cast(m_gpu_capacity_bytes) * BYTES_TO_MEGABYTES) - << " percent=" - << (static_cast(m_gpu_used_bytes) / - static_cast(m_gpu_capacity_bytes))); - MMSOLVER_MAYA_INFO( - "mmsolver::ImageCache::print_cache_details:" - << " CPU cache item count=" - << m_cpu_cache_map.size() - // << " CPU min item count=" << m_cpu_min_item_count - << " used MB=" - << (static_cast(m_cpu_used_bytes) * BYTES_TO_MEGABYTES) - << " capacity MB=" - << (static_cast(m_cpu_capacity_bytes) * BYTES_TO_MEGABYTES) - << " percent=" - << (static_cast(m_cpu_used_bytes) / - static_cast(m_cpu_capacity_bytes))); - return; - } - // Set the capacity of the GPU. // // Note: Setting a lower value than what is already used will @@ -239,6 +211,10 @@ struct ImageCache { } } + // Debug functions to display internals of the cache. + MString generate_cache_brief_text() const; + void print_cache_brief() const; + // TODO: Set/Get Disk cache location. Used to find disk-cached // files. This should be a directory on a very fast disk. // From 70ca57675816a47020eedf908dc4348e8c642399 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 9 Jun 2024 23:21:50 +1000 Subject: [PATCH 069/295] ImageCache - Fix cache freeing memory. Fix cache clearing when capacity is set lower than used. GitHub issue #252. --- src/mmSolver/image/ImageCache.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 8b463ee08..ecdfb0cbc 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -173,7 +173,7 @@ struct ImageCache { // Because we must always ensure our used memory is less than // the given capacity. if ((m_gpu_used_bytes > m_gpu_capacity_bytes) && - m_gpu_cache_key_list.empty()) { + !m_gpu_cache_key_list.empty()) { // If we are at capacity remove the least recently used items // until our capacity is under 'new_used_bytes'. while (m_gpu_used_bytes > m_gpu_capacity_bytes) { @@ -199,7 +199,7 @@ struct ImageCache { // Because we must always ensure our used memory is less than // the given capacity. if ((m_cpu_used_bytes > m_cpu_capacity_bytes) && - m_cpu_cache_key_list.empty()) { + !m_cpu_cache_key_list.empty()) { // If we are at capacity remove the least recently used items // until our capacity is under 'new_used_bytes'. while (m_cpu_used_bytes > m_cpu_capacity_bytes) { From 0ef5919969a4933446e7b2edc1381d09f05d86c7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 11:36:09 +1000 Subject: [PATCH 070/295] Image Cache - Adjust item eviction logic. The ImageCache initially defaults to having a capacity of zero, but it will still allow a minimum number of items. GitHub issue #252 --- src/mmSolver/image/ImageCache.cpp | 147 +++++++++++++++++++++--------- src/mmSolver/image/ImageCache.h | 82 +++++------------ 2 files changed, 126 insertions(+), 103 deletions(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 4822b94b7..0afa8bbe4 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -133,9 +133,6 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, } } else { - // TODO: This code can be changed to whatever reading function - // that reads the input file path. - status = read_image_file(temp_image, resolved_file_path, pixel_type, width, height, number_of_channels, bytes_per_channel, texture_format, @@ -190,8 +187,55 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, return texture_data.texture(); } +void ImageCache::set_gpu_capacity_bytes( + MHWRender::MTextureManager *texture_manager, const size_t value) { + const bool verbose = false; + m_gpu_capacity_bytes = value; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " + << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); + + // Because we must always ensure our used memory is less than + // the given capacity. + // + // If we are at capacity remove the least recently used items + // until our capacity is under 'new_used_bytes' or we reach the minimum + // number of items + while (!m_gpu_cache_map.empty() && + (m_gpu_cache_map.size() > m_gpu_item_count_minumum) && + (m_gpu_used_bytes > m_gpu_capacity_bytes)) { + const CacheEvictionResult result = + ImageCache::gpu_evict_one(texture_manager); + if (result != CacheEvictionResult::kSuccess) { + break; + } + } +} + +void ImageCache::set_cpu_capacity_bytes(const size_t value) { + const bool verbose = false; + m_cpu_capacity_bytes = value; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " + << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); + + // Because we must always ensure our used memory is less than + // the given capacity. + // + // If we are at capacity remove the least recently used items + // until our capacity is under 'new_used_bytes' or we reach the minimum + // number of items + while (!m_cpu_cache_map.empty() && + (m_cpu_cache_map.size() > m_cpu_item_count_minumum) && + (m_cpu_used_bytes > m_cpu_capacity_bytes)) { + const CacheEvictionResult result = ImageCache::cpu_evict_one(); + if (result != CacheEvictionResult::kSuccess) { + break; + } + } +} + inline std::string generate_cache_brief(const char *prefix_str, const size_t item_count, + const size_t item_min_count, const size_t capacity_bytes, const size_t used_bytes) { const size_t capacity_megabytes = capacity_bytes / BYTES_TO_MEGABYTES; @@ -212,6 +256,7 @@ inline std::string generate_cache_brief(const char *prefix_str, std::stringstream ss; ss << prefix_str << "count=" << item_count << " items " + << "| minimum=" << item_min_count << " items " << "| used=" << used_megabytes_str << "MB " << "| capacity=" << used_megabytes_str << "MB " << "| percent=" << used_percent << '%'; @@ -219,12 +264,12 @@ inline std::string generate_cache_brief(const char *prefix_str, } MString ImageCache::generate_cache_brief_text() const { - std::string gpu_cache_text = - generate_cache_brief("GPU cache | ", m_gpu_cache_map.size(), - m_gpu_capacity_bytes, m_gpu_used_bytes); - std::string cpu_cache_text = - generate_cache_brief("CPU cache | ", m_cpu_cache_map.size(), - m_cpu_capacity_bytes, m_cpu_used_bytes); + std::string gpu_cache_text = generate_cache_brief( + "GPU cache | ", m_gpu_cache_map.size(), m_gpu_item_count_minumum, + m_gpu_capacity_bytes, m_gpu_used_bytes); + std::string cpu_cache_text = generate_cache_brief( + "CPU cache | ", m_cpu_cache_map.size(), m_cpu_item_count_minumum, + m_cpu_capacity_bytes, m_cpu_used_bytes); std::stringstream ss; ss << gpu_cache_text << std::endl << cpu_cache_text << std::endl; @@ -235,12 +280,12 @@ MString ImageCache::generate_cache_brief_text() const { } void ImageCache::print_cache_brief() const { - std::string gpu_cache_text = - generate_cache_brief("GPU cache | ", m_gpu_cache_map.size(), - m_gpu_capacity_bytes, m_gpu_used_bytes); - std::string cpu_cache_text = - generate_cache_brief("CPU cache | ", m_cpu_cache_map.size(), - m_cpu_capacity_bytes, m_cpu_used_bytes); + std::string gpu_cache_text = generate_cache_brief( + "GPU cache | ", m_gpu_cache_map.size(), m_gpu_item_count_minumum, + m_gpu_capacity_bytes, m_gpu_used_bytes); + std::string cpu_cache_text = generate_cache_brief( + "CPU cache | ", m_cpu_cache_map.size(), m_gpu_item_count_minumum, + m_cpu_capacity_bytes, m_cpu_used_bytes); MMSOLVER_MAYA_INFO( "mmsolver::ImageCache::print_cache_brief: " << gpu_cache_text); @@ -264,11 +309,12 @@ bool ImageCache::cpu_insert(const CPUCacheKey &key, // If we are at capacity, make room for new entry. const size_t image_data_size = image_pixel_data.byte_count(); - const bool evict_ok = + const CacheEvictionResult evict_result = ImageCache::cpu_evict_enough_for_new_entry(image_data_size); - if (!evict_ok) { + if (evict_result == CacheEvictionResult::kFailed) { MMSOLVER_MAYA_WRN( "mmsolver::ImageCache::cpu_insert: evicting memory failed!"); + ImageCache::print_cache_brief(); } m_cpu_used_bytes += image_data_size; @@ -326,11 +372,14 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( if (!found) { // If we are at capacity, make room for new entry. const size_t image_data_size = image_pixel_data.byte_count(); - const bool evict_ok = ImageCache::gpu_evict_enough_for_new_entry( - texture_manager, image_data_size); - if (!evict_ok) { + + const CacheEvictionResult evict_result = + ImageCache::gpu_evict_enough_for_new_entry(texture_manager, + image_data_size); + if (evict_result == CacheEvictionResult::kFailed) { MMSOLVER_MAYA_WRN( "mmsolver::ImageCache::gpu_insert: evicting memory failed!"); + ImageCache::print_cache_brief(); } const bool allocate_ok = texture_data.allocate_texture( @@ -415,7 +464,8 @@ ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey &key) { return CPUCacheValue(); } -bool ImageCache::gpu_evict_one(MHWRender::MTextureManager *texture_manager) { +CacheEvictionResult ImageCache::gpu_evict_one( + MHWRender::MTextureManager *texture_manager) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); @@ -426,8 +476,9 @@ bool ImageCache::gpu_evict_one(MHWRender::MTextureManager *texture_manager) { << m_gpu_used_bytes); assert(texture_manager != nullptr); - if (m_gpu_cache_key_list.empty()) { - return false; + if (m_gpu_cache_key_list.empty() || + (m_gpu_cache_map.size() <= m_gpu_item_count_minumum)) { + return CacheEvictionResult::kNotNeeded; } const GPUCacheKey lru_key = m_gpu_cache_key_list.front(); @@ -445,10 +496,10 @@ bool ImageCache::gpu_evict_one(MHWRender::MTextureManager *texture_manager) { "mmsolver::ImageCache::gpu_evict_one: " "after m_gpu_used_bytes=" << m_gpu_used_bytes); - return true; + return CacheEvictionResult::kSuccess; } -bool ImageCache::cpu_evict_one() { +CacheEvictionResult ImageCache::cpu_evict_one() { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); @@ -458,8 +509,9 @@ bool ImageCache::cpu_evict_one() { "before m_cpu_used_bytes=" << m_cpu_used_bytes); - if (m_cpu_cache_key_list.empty()) { - return false; + if (m_cpu_cache_key_list.empty() || + (m_cpu_cache_map.size() <= m_cpu_item_count_minumum)) { + return CacheEvictionResult::kNotNeeded; } const CPUCacheKey lru_key = m_cpu_cache_key_list.front(); @@ -477,20 +529,22 @@ bool ImageCache::cpu_evict_one() { "mmsolver::ImageCache::cpu_evict_one: " "after m_cpu_used_bytes=" << m_cpu_used_bytes); - return true; + return CacheEvictionResult::kSuccess; } -bool ImageCache::gpu_evict_enough_for_new_entry( +CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_entry: "); - if (m_gpu_cache_key_list.empty()) { - return false; + if (m_gpu_cache_key_list.empty() || + (m_gpu_cache_map.size() <= m_gpu_item_count_minumum)) { + return CacheEvictionResult::kNotNeeded; } + CacheEvictionResult result = CacheEvictionResult::kSuccess; // If we are at capacity remove the least recently used items // until we have enough room to store 'new_memory_chunk_size'. size_t new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; @@ -498,9 +552,13 @@ bool ImageCache::gpu_evict_enough_for_new_entry( "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " "new_used_bytes=" << new_used_bytes); - while (new_used_bytes > m_gpu_capacity_bytes) { - const bool ok = ImageCache::gpu_evict_one(texture_manager); - if (!ok) { + while (!m_gpu_cache_map.empty() && + (m_gpu_cache_map.size() > m_gpu_item_count_minumum) && + (new_used_bytes > m_gpu_capacity_bytes)) { + const CacheEvictionResult evict_result = + ImageCache::gpu_evict_one(texture_manager); + if (evict_result != CacheEvictionResult::kSuccess) { + result = evict_result; break; } new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; @@ -510,19 +568,21 @@ bool ImageCache::gpu_evict_enough_for_new_entry( << new_used_bytes); } - return true; + return result; } -bool ImageCache::cpu_evict_enough_for_new_entry( +CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( const size_t new_memory_chunk_size) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_entry: "); - if (m_cpu_cache_key_list.empty()) { - return false; + if (m_cpu_cache_key_list.empty() || + (m_cpu_cache_map.size() <= m_cpu_item_count_minumum)) { + return CacheEvictionResult::kNotNeeded; } + CacheEvictionResult result = CacheEvictionResult::kSuccess; // If we are at capacity remove the least recently used items // until we have enough room to store 'new_memory_chunk_size'. size_t new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; @@ -530,9 +590,12 @@ bool ImageCache::cpu_evict_enough_for_new_entry( "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " "new_used_bytes=" << new_used_bytes); - while (new_used_bytes > m_cpu_capacity_bytes) { - const bool ok = ImageCache::cpu_evict_one(); - if (!ok) { + while (!m_cpu_cache_map.empty() && + (m_cpu_cache_map.size() > m_cpu_item_count_minumum) && + (new_used_bytes > m_cpu_capacity_bytes)) { + const CacheEvictionResult evict_result = ImageCache::cpu_evict_one(); + if (evict_result != CacheEvictionResult::kSuccess) { + result = evict_result; break; } new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; @@ -542,7 +605,7 @@ bool ImageCache::cpu_evict_enough_for_new_entry( << new_used_bytes); } - return true; + return result; } bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index ecdfb0cbc..da35eee97 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -86,9 +86,7 @@ namespace image { // retained - even if it's not colour accurate. // -// The default ImageCache sizes. -const size_t kDEFAULT_GPU_CAPACITY_BYTES = BYTES_TO_MEGABYTES * 128; -const size_t kDEFAULT_CPU_CAPACITY_BYTES = BYTES_TO_MEGABYTES * 1024; +enum class CacheEvictionResult : uint8_t { kSuccess = 0, kNotNeeded, kFailed }; struct ImageCache { // GPU data types @@ -119,8 +117,10 @@ struct ImageCache { private: // Constructor. The {} brackets are needed here. ImageCache() - : m_gpu_capacity_bytes(kDEFAULT_GPU_CAPACITY_BYTES) - , m_cpu_capacity_bytes(kDEFAULT_CPU_CAPACITY_BYTES) + : m_gpu_capacity_bytes(0) + , m_cpu_capacity_bytes(0) + , m_gpu_item_count_minumum(1) + , m_cpu_item_count_minumum(1) , m_gpu_used_bytes(0) , m_cpu_used_bytes(0) {} @@ -132,7 +132,7 @@ struct ImageCache { } public: - // Get the capacity of the GPU and CPU. + // Get the capacity of the cache. size_t get_gpu_capacity_bytes() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity_bytes: " @@ -158,58 +158,10 @@ struct ImageCache { return m_cpu_used_bytes; } - // Set the capacity of the GPU. - // - // Note: Setting a lower value than what is already used will - // cause the cached memory to be evicted until the new memory - // capacity is reached. + // Set the capacity of the cache. void set_gpu_capacity_bytes(MHWRender::MTextureManager *texture_manager, - const size_t value) { - const bool verbose = false; - m_gpu_capacity_bytes = value; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " - << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); - - // Because we must always ensure our used memory is less than - // the given capacity. - if ((m_gpu_used_bytes > m_gpu_capacity_bytes) && - !m_gpu_cache_key_list.empty()) { - // If we are at capacity remove the least recently used items - // until our capacity is under 'new_used_bytes'. - while (m_gpu_used_bytes > m_gpu_capacity_bytes) { - const bool ok = ImageCache::gpu_evict_one(texture_manager); - if (!ok) { - break; - } - } - } - } - - // Set the capacity of the CPU. - // - // Note: Setting a lower value than what is already used will - // cause the cached memory to be evicted until the new memory - // capacity is reached. - void set_cpu_capacity_bytes(const size_t value) { - const bool verbose = false; - m_cpu_capacity_bytes = value; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " - << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); - - // Because we must always ensure our used memory is less than - // the given capacity. - if ((m_cpu_used_bytes > m_cpu_capacity_bytes) && - !m_cpu_cache_key_list.empty()) { - // If we are at capacity remove the least recently used items - // until our capacity is under 'new_used_bytes'. - while (m_cpu_used_bytes > m_cpu_capacity_bytes) { - const bool ok = ImageCache::cpu_evict_one(); - if (!ok) { - break; - } - } - } - } + const size_t value); + void set_cpu_capacity_bytes(const size_t value); // Debug functions to display internals of the cache. MString generate_cache_brief_text() const; @@ -276,13 +228,14 @@ struct ImageCache { // // Returns true/false, if an item was removed from the cache or // not. - bool gpu_evict_one(MHWRender::MTextureManager *texture_manager); + CacheEvictionResult gpu_evict_one( + MHWRender::MTextureManager *texture_manager); // Evict the least recently used item from the CPU cache. // // Returns true/false, if an item was removed from the cache or // not. - bool cpu_evict_one(); + CacheEvictionResult cpu_evict_one(); // Remove the key from the image GPU cache. // @@ -312,10 +265,11 @@ struct ImageCache { void operator=(ImageCache const &) = delete; private: - bool gpu_evict_enough_for_new_entry( + CacheEvictionResult gpu_evict_enough_for_new_entry( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size); - bool cpu_evict_enough_for_new_entry(const size_t new_memory_chunk_size); + CacheEvictionResult cpu_evict_enough_for_new_entry( + const size_t new_memory_chunk_size); // Amount of memory capacity. size_t m_gpu_capacity_bytes; @@ -325,6 +279,12 @@ struct ImageCache { size_t m_gpu_used_bytes; size_t m_cpu_used_bytes; + // The minimum number of items that are allowed in the cache. We + // want to retain a fixed number of images, to avoid invalid + // conditions in the cache. + size_t m_gpu_item_count_minumum; + size_t m_cpu_item_count_minumum; + // A Map of keys to values. // // An unordered hash map is used to map file path strings to CPU From 908265be58dce2b8969150f352db2f8c91359588 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 12:29:18 +1000 Subject: [PATCH 071/295] mmReadImage Command - Query channels and byte count This allows the user to query the total amount of memory required by an image in memory. In turn this allows us to make better decisions about the Image Cache utilisation in Python (or MEL) code. This also fixes inconsistencies with variable naming, and getting the number of channels in an image. We now make assumptions about the data type that will be read by the Maya MImage class. GitHub issue #252. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 167 ++++++++++++------ src/mmSolver/cmd/MMReadImageCmd.h | 1 + src/mmSolver/image/ImageCache.cpp | 19 +- src/mmSolver/image/PixelDataType.h | 2 +- src/mmSolver/image/image_io.cpp | 39 ++-- src/mmSolver/image/image_io.h | 3 +- .../shape/ImagePlaneGeometry2Override.cpp | 6 +- 7 files changed, 155 insertions(+), 82 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index c7e7750c5..079dd2f20 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -27,19 +27,18 @@ #include #include #include -#include #include -#include -#include #include -#include #include #include #include // MM Solver +#include "mmSolver/image/ImagePixelData.h" +#include "mmSolver/image/PixelDataType.h" #include "mmSolver/image/image_io.h" #include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/string_utils.h" // Command arguments and command name: #define FILE_PATH_FLAG "-fp" @@ -48,14 +47,8 @@ #define WIDTH_HEIGHT_FLAG "-wh" #define WIDTH_HEIGHT_FLAG_LONG "-widthHeight" -// TODO: Add flags to: -// - Return the number of channels in the image. -// - Return the number of bytes per-channel in the image. -// - Return the number of total raw bytes in the image. -// -// NOTE: We do not want to have to call mmReadImage multiple times. We -// want to get as much data as possible in a single call, because -// subsequent calls will need to re-read the image. +#define DATA_HEADER_FLAG "-dhr" +#define DATA_HEADER_FLAG_LONG "-dataHeader" namespace mmsolver { @@ -84,8 +77,9 @@ MSyntax MMReadImageCmd::newSyntax() { auto maxNumObjects = 1; syntax.setObjectType(MSyntax::kStringObjects, minNumObjects, maxNumObjects); - syntax.addFlag(WIDTH_HEIGHT_FLAG, WIDTH_HEIGHT_FLAG_LONG, - MSyntax::kBoolean); + syntax.addFlag(WIDTH_HEIGHT_FLAG, WIDTH_HEIGHT_FLAG_LONG); + syntax.addFlag(DATA_HEADER_FLAG, DATA_HEADER_FLAG_LONG); + return syntax; } @@ -98,6 +92,21 @@ MStatus MMReadImageCmd::parseArgs(const MArgList &args) { MArgDatabase argData(syntax(), args, &status); CHECK_MSTATUS_AND_RETURN_IT(status); + // Query Flag + const bool query = argData.isQuery(&status); + CHECK_MSTATUS(status); + if (status != MStatus::kSuccess) { + status.perror("mmReadImage: Could not get the query flag"); + return status; + } + + if (!query) { + status = MStatus::kFailure; + status.perror("mmReadImage command must query using the 'query' flag"); + return status; + } + + // Get the file path. MStringArray objects; argData.getObjects(objects); if (objects.length() == 0) { @@ -116,21 +125,43 @@ MStatus MMReadImageCmd::parseArgs(const MArgList &args) { } m_file_path = objects[0]; - // Query Flag - const bool query = argData.isQuery(&status); - CHECK_MSTATUS(status); - if (status != MStatus::kSuccess) { - status.perror("mmReadImage: Could not get the query flag"); - return status; - } - if (!query) { - status = MStatus::kFailure; - status.perror("mmReadImage command must query using the 'query' flag"); - return status; - } - m_query_width_height = argData.isFlagSet(WIDTH_HEIGHT_FLAG, &status); CHECK_MSTATUS_AND_RETURN_IT(status); + + m_query_data_header = argData.isFlagSet(DATA_HEADER_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + return status; +} + +MStatus read_image_header(const MString &file_path, uint32_t &out_image_width, + uint32_t &out_image_height, uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + image::PixelDataType &out_pixel_data_type) { + MStatus status = MStatus::kSuccess; + MImage image; + + out_image_width = 0; + out_image_height = 0; + out_num_channels = 0; + out_bytes_per_channel = 0; + + // We won't use the image data anyway. + void *pixel_data = nullptr; + + // TODO: Can we read just the file header to get the image size? + // This would remove the need to read the entire image for this + // command's usage. + status = image::read_image_file(image, file_path, out_image_width, + out_image_height, out_num_channels, + out_bytes_per_channel, out_texture_format, + out_pixel_data_type, pixel_data); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_WRN("mmReadImage: " + << "Image file path could not be read: " + << file_path.asChar()); + } + return status; } @@ -156,6 +187,7 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { << "\"" << resolved_file_path.asChar() << "\"."); return status; } + MString resolved_file_path = file_object.resolvedFullName(); if (resolved_file_path.length() > 0) { MMSOLVER_MAYA_VRB("mmReadImage: resolved file path " @@ -164,38 +196,71 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { } if (m_query_width_height) { - auto image = MImage(); - - // kUnknown attempts to load the native pixel type. - auto pixel_type = MImage::kUnknown; - - // TODO: Can we read just the file header to get the image - // size? This would remove the need to read the entire image - // for this command's usage. - uint32_t image_width; - uint32_t image_height; - uint8_t num_channels; - uint8_t bytes_per_channel; + uint32_t image_width = 0; + uint32_t image_height = 0; + uint8_t num_channels = 0; + uint8_t bytes_per_channel = 0; MHWRender::MRasterFormat texture_format; image::PixelDataType pixel_data_type; - void *pixel_data = nullptr; - status = image::read_image_file(image, m_file_path, pixel_type, - image_width, image_height, num_channels, - bytes_per_channel, texture_format, - pixel_data_type, pixel_data); - if (status != MS::kSuccess) { - status = MS::kSuccess; - MMSOLVER_MAYA_WRN("mmReadImage: " - << "Image file path could not be read: " - << m_file_path.asChar()); - return status; - } + + status = read_image_header(m_file_path, image_width, image_height, + num_channels, bytes_per_channel, + texture_format, pixel_data_type); + CHECK_MSTATUS_AND_RETURN_IT(status); MIntArray outResult; outResult.append(image_width); outResult.append(image_height); MMReadImageCmd::setResult(outResult); + } else if (m_query_data_header) { + // NOTE: We do not want to have to call mmReadImage multiple + // times. We want to get as much data as possible in a single + // call, because subsequent calls will need to re-read the + // image. + + uint32_t image_width = 0; + uint32_t image_height = 0; + uint8_t num_channels = 0; + uint8_t bytes_per_channel = 0; + MHWRender::MRasterFormat texture_format; + image::PixelDataType pixel_data_type; + + status = read_image_header(m_file_path, image_width, image_height, + num_channels, bytes_per_channel, + texture_format, pixel_data_type); + CHECK_MSTATUS_AND_RETURN_IT(status); + + void *pixel_data = nullptr; + image::ImagePixelData image_pixel_data(pixel_data, image_width, + image_height, num_channels, + pixel_data_type); + size_t byte_count = image_pixel_data.byte_count(); + + // Some of the numbers may be more than 'int' can hold, so we + // must return as a MString. + std::string width_string = mmstring::numberToString(image_width); + std::string height_string = mmstring::numberToString(image_height); + std::string num_channels_string = + mmstring::numberToString(static_cast(num_channels)); + std::string bytes_per_channel_string = + mmstring::numberToString(static_cast(bytes_per_channel)); + std::string byte_count_string = mmstring::numberToString(byte_count); + + MString width_mstring(width_string.c_str()); + MString height_mstring(height_string.c_str()); + MString num_channels_mstring(num_channels_string.c_str()); + MString bytes_per_channel_mstring(bytes_per_channel_string.c_str()); + MString byte_count_mstring(byte_count_string.c_str()); + + MStringArray outResult; + outResult.append(width_mstring); + outResult.append(height_mstring); + outResult.append(num_channels_mstring); + outResult.append(bytes_per_channel_mstring); + outResult.append(byte_count_mstring); + MMReadImageCmd::setResult(outResult); } + return status; } diff --git a/src/mmSolver/cmd/MMReadImageCmd.h b/src/mmSolver/cmd/MMReadImageCmd.h index e36988b67..18c08d494 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.h +++ b/src/mmSolver/cmd/MMReadImageCmd.h @@ -57,6 +57,7 @@ class MMReadImageCmd : public MPxCommand { MString m_file_path; bool m_query_width_height; + bool m_query_data_header; }; } // namespace mmsolver diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 0afa8bbe4..cf291cec0 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -62,7 +62,6 @@ namespace image { MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, const MString &file_path, - const MImage::MPixelType pixel_type, const bool do_texture_update) { assert(texture_manager != nullptr); @@ -109,7 +108,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, uint32_t width = 0; uint32_t height = 0; - uint8_t number_of_channels = 4; + uint8_t num_channels = 4; uint8_t bytes_per_channel = 0; MHWRender::MRasterFormat texture_format; PixelDataType pixel_data_type = PixelDataType::kUnknown; @@ -120,7 +119,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, maya_owned_pixel_data = image_pixel_data.pixel_data(); width = image_pixel_data.width(); height = image_pixel_data.height(); - number_of_channels = image_pixel_data.num_channels(); + num_channels = image_pixel_data.num_channels(); pixel_data_type = image_pixel_data.pixel_data_type(); bytes_per_channel = convert_pixel_data_type_to_bytes_per_channel(pixel_data_type); @@ -133,10 +132,10 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, } } else { - status = read_image_file(temp_image, resolved_file_path, pixel_type, - width, height, number_of_channels, - bytes_per_channel, texture_format, - pixel_data_type, maya_owned_pixel_data); + status = + read_image_file(temp_image, resolved_file_path, width, height, + num_channels, bytes_per_channel, texture_format, + pixel_data_type, maya_owned_pixel_data); if (status != MS::kSuccess) { return nullptr; } @@ -154,7 +153,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImagePixelData gpu_image_pixel_data = ImagePixelData(static_cast(maya_owned_pixel_data), width, - height, number_of_channels, pixel_data_type); + height, num_channels, pixel_data_type); texture_data = image_cache.gpu_insert(texture_manager, key, gpu_image_pixel_data); @@ -163,10 +162,10 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, // Duplicate the Maya-owned pixel data for our image cache. const size_t pixel_data_byte_count = - width * height * number_of_channels * bytes_per_channel; + width * height * num_channels * bytes_per_channel; image_pixel_data = ImagePixelData(); const bool allocated_ok = image_pixel_data.allocate_pixels( - width, height, number_of_channels, pixel_data_type); + width, height, num_channels, pixel_data_type); if (allocated_ok == false) { MMSOLVER_MAYA_ERR("mmsolver::ImageCache: read_texture_image_file: " << "Could not allocate pixel data!"); diff --git a/src/mmSolver/image/PixelDataType.h b/src/mmSolver/image/PixelDataType.h index 4066a4138..6fba8d6b2 100644 --- a/src/mmSolver/image/PixelDataType.h +++ b/src/mmSolver/image/PixelDataType.h @@ -66,7 +66,7 @@ static uint8_t convert_pixel_data_type_to_bytes_per_channel( bytes_per_channel = 4; } else { bytes_per_channel = 0; - MMSOLVER_MAYA_ERR("mmsolver::image_io: get_mimage_pixel_data: " + MMSOLVER_MAYA_ERR("mmsolver::image::convert_pixel_data_type_to_bytes_per_channel: " << "Invalid pixel type is " << static_cast(pixel_data_type)); } diff --git a/src/mmSolver/image/image_io.cpp b/src/mmSolver/image/image_io.cpp index 8922ac724..ebe270b3c 100644 --- a/src/mmSolver/image/image_io.cpp +++ b/src/mmSolver/image/image_io.cpp @@ -56,7 +56,7 @@ namespace image { void *get_mimage_pixel_data(const MImage &image, const PixelDataType pixel_data_type, const uint32_t width, const uint32_t height, - const uint8_t number_of_channels, + const uint8_t num_channels, uint8_t &out_bytes_per_channel, MHWRender::MRasterFormat &out_texture_format) { const bool verbose = false; @@ -83,7 +83,7 @@ void *get_mimage_pixel_data(const MImage &image, if (verbose) { for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; + const uint32_t index = row * num_channels; const uint32_t r = static_cast(pixels[index + 0]); const uint32_t g = static_cast(pixels[index + 1]); const uint32_t b = static_cast(pixels[index + 2]); @@ -102,10 +102,10 @@ void *get_mimage_pixel_data(const MImage &image, // // mmcolorio::image_convert_srgb_to_linear_srgb_u8(pixels, width, // height, - // number_of_channels); + // num_channels); // mmcolorio::test_opencolorio(pixels, width, height, - // number_of_channels); + // num_channels); pixel_data = static_cast(pixels); } else if (pixel_data_type == PixelDataType::kF32) { @@ -121,7 +121,7 @@ void *get_mimage_pixel_data(const MImage &image, if (verbose) { for (uint32_t row = 0; row <= print_num_pixels; row++) { - const uint32_t index = row * number_of_channels; + const uint32_t index = row * num_channels; const float r = floatPixels[index + 0]; const float g = floatPixels[index + 1]; const float b = floatPixels[index + 2]; @@ -133,7 +133,7 @@ void *get_mimage_pixel_data(const MImage &image, } // mmcolorio::image_convert_srgb_to_linear_srgb_f32( - // floatPixels, width, height, number_of_channels); + // floatPixels, width, height, num_channels); pixel_data = static_cast(floatPixels); } else { @@ -147,7 +147,7 @@ void *get_mimage_pixel_data(const MImage &image, } PixelDataType convert_mpixel_type_to_pixel_data_type( - MImage::MPixelType pixel_type) { + const MImage::MPixelType pixel_type) { PixelDataType pixel_data_type = PixelDataType::kUnknown; if (pixel_type == MImage::MPixelType::kByte) { pixel_data_type = PixelDataType::kU8; @@ -156,12 +156,14 @@ PixelDataType convert_mpixel_type_to_pixel_data_type( } else { MMSOLVER_MAYA_WRN( "mmsolver::image_io::convert_mpixel_type_to_pixel_data_type: " - "Invalid MImage::MPixelType value."); + "Invalid MImage::MPixelType value. " + "value=" + << static_cast(pixel_type)); } return pixel_data_type; } -MStatus read_with_maya_image(MImage &image, MString &file_path, +MStatus read_with_maya_image(MImage &image, const MString &file_path, const MImage::MPixelType pixel_type, uint32_t &out_width, uint32_t &out_height, uint8_t &out_num_channels, @@ -195,7 +197,7 @@ MStatus read_with_maya_image(MImage &image, MString &file_path, return status; } -MStatus read_exr_with_mmimage(MImage &image, MString &file_path, +MStatus read_exr_with_mmimage(MImage &image, const MString &file_path, uint32_t &out_width, uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, @@ -212,6 +214,7 @@ MStatus read_exr_with_mmimage(MImage &image, MString &file_path, const std::string input_file_path_string = file_path.asChar(); const auto input_file_path = rust::Str(input_file_path_string); + // TODO: Support 3-channel RGB EXR images. bool read_ok = mmimage::image_read_pixels_exr_f32x4( input_file_path, meta_data, pixel_buffer); @@ -282,7 +285,6 @@ MStatus read_exr_with_mmimage(MImage &image, MString &file_path, bool ok = false; void *data = std::malloc(pixel_data_byte_count); - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" << " void *data=" << data); @@ -353,8 +355,7 @@ MStatus read_exr_with_mmimage(MImage &image, MString &file_path, return status; } -MStatus read_image_file(MImage &image, MString &file_path, - const MImage::MPixelType pixel_type, +MStatus read_image_file(MImage &image, const MString &file_path, uint32_t &out_width, uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, @@ -382,12 +383,24 @@ MStatus read_image_file(MImage &image, MString &file_path, << " file_extension=" << file_extension.asChar()); if (file_extension == "exr") { + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" + << "read_exr_with_mmimage..."); status = read_exr_with_mmimage(image, file_path, out_width, out_height, out_num_channels, out_bytes_per_channel, out_texture_format, out_pixel_data_type, out_pixel_data); CHECK_MSTATUS_AND_RETURN_IT(status); } else { + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" + << "read_with_maya_image..."); + + // Maya always reads images as RGBA. + out_num_channels = 4; + + // Maya is meant to be able to load pixels as float (kFloat), + // but in my attempts it just fails, so lets hard-code to 8-bit. + const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; + status = read_with_maya_image(image, file_path, pixel_type, out_width, out_height, out_num_channels, out_bytes_per_channel, out_texture_format, diff --git a/src/mmSolver/image/image_io.h b/src/mmSolver/image/image_io.h index 4b6cee914..02093fe72 100644 --- a/src/mmSolver/image/image_io.h +++ b/src/mmSolver/image/image_io.h @@ -44,8 +44,7 @@ namespace mmsolver { namespace image { -MStatus read_image_file(MImage &image, MString &file_path, - const MImage::MPixelType pixel_type, +MStatus read_image_file(MImage &image, const MString &file_path, uint32_t &out_width, uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index e6fa3c4f3..d16e167fe 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -470,16 +470,12 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( if (!out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); - const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; - - // // TODO: using kFloat crashes. - // const MImage::MPixelType pixel_type = MImage::MPixelType::kFloat; const bool do_texture_update = false; image::ImageCache &image_cache = image::ImageCache::getInstance(); out_color_texture = image::read_texture_image_file( texture_manager, image_cache, m_temp_image, expanded_file_path, - pixel_type, do_texture_update); + do_texture_update); if (out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" From 861cef01d208463b9592935712933049eb902361 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:00:15 +1000 Subject: [PATCH 072/295] Switch to using mmSolver Maya logging macro. --- src/mmSolver/mayahelper/maya_utils.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mmSolver/mayahelper/maya_utils.cpp b/src/mmSolver/mayahelper/maya_utils.cpp index e2266796a..e7b854ffa 100644 --- a/src/mmSolver/mayahelper/maya_utils.cpp +++ b/src/mmSolver/mayahelper/maya_utils.cpp @@ -44,9 +44,9 @@ MStatus MMNodeInitUtils::attributeAffectsMulti( MObject outputAttr = outputAttrs[j]; status = MPxNode::attributeAffects(inputAttr, outputAttr); if (status != MS::kSuccess) { - MStreamUtils::stdErrorStream() - << "ERROR: attributeAffects failed at " - << "input_index=" << i << " output_index=" << j << '\n'; + MMSOLVER_MAYA_ERR( + "MMNodeInitUtils::attributeAffects: Failed at " + << "input_index=" << i << " output_index=" << j); CHECK_MSTATUS(status); } } From 86660155367e750127b716c09786e14727dec4a4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:08:18 +1000 Subject: [PATCH 073/295] Add Maya String Utilities to reduce boiler-plate code. --- src/mmSolver/cmd/MMImageCacheCmd.cpp | 10 ++- src/mmSolver/cmd/MMMemoryGPUCmd.cpp | 4 +- src/mmSolver/cmd/MMMemorySystemCmd.cpp | 4 +- src/mmSolver/cmd/MMReadImageCmd.cpp | 21 +++---- src/mmSolver/cmd/MMSolverTypeCmd.cpp | 13 ++-- src/mmSolver/mayahelper/maya_string_utils.cpp | 24 +++++++ src/mmSolver/mayahelper/maya_string_utils.h | 63 +++++++++++++++++++ 7 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 src/mmSolver/mayahelper/maya_string_utils.cpp create mode 100644 src/mmSolver/mayahelper/maya_string_utils.h diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 00e148338..084dfb20a 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -38,6 +38,7 @@ #include #include "mmSolver/image/ImageCache.h" +#include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/path_utils.h" #include "mmSolver/utilities/string_utils.h" @@ -172,8 +173,7 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MString mstring; status = argData.getFlagArgument(GPU_CAPACITY_FLAG, 0, mstring); CHECK_MSTATUS_AND_RETURN_IT(status); - std::string std_string = mstring.asChar(); - m_gpu_capacity_bytes = mmstring::stringToNumber(std_string); + m_gpu_capacity_bytes = mmmayastring::mstringToNumber(mstring); // Store the current value, so we can undo later. m_previous_gpu_capacity_bytes = @@ -183,8 +183,7 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MString mstring; status = argData.getFlagArgument(CPU_CAPACITY_FLAG, 0, mstring); CHECK_MSTATUS_AND_RETURN_IT(status); - std::string std_string = mstring.asChar(); - m_cpu_capacity_bytes = mmstring::stringToNumber(std_string); + m_cpu_capacity_bytes = mmmayastring::mstringToNumber(mstring); // Store the current value, so we can undo later. m_previous_cpu_capacity_bytes = @@ -293,8 +292,7 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { return MStatus::kFailure; } - std::string number_string = mmstring::numberToString(bytes_value); - MString number_mstring(number_string.c_str()); + MString number_mstring(mmmayastring::numberToMString(bytes_value)); MMImageCacheCmd::setResult(number_mstring); } } else if (m_is_edit) { diff --git a/src/mmSolver/cmd/MMMemoryGPUCmd.cpp b/src/mmSolver/cmd/MMMemoryGPUCmd.cpp index 65fbc3c62..efd56fd49 100644 --- a/src/mmSolver/cmd/MMMemoryGPUCmd.cpp +++ b/src/mmSolver/cmd/MMMemoryGPUCmd.cpp @@ -44,6 +44,7 @@ // MM Solver #include +#include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/memory_gpu_utils.h" #include "mmSolver/utilities/path_utils.h" @@ -182,8 +183,7 @@ MStatus MMMemoryGPUCmd::doIt(const MArgList &args) { // maximum may exceed that size, so we must return the full // number converted to a string, then have the Python (or // MEL?) code convert that to an integer. - std::string number_string = mmstring::numberToString(bytes_value); - MString number_mstring(number_string.c_str()); + MString number_mstring = mmmayastring::numberToMString(bytes_value); MMMemoryGPUCmd::setResult(number_mstring); } else { double outResult = diff --git a/src/mmSolver/cmd/MMMemorySystemCmd.cpp b/src/mmSolver/cmd/MMMemorySystemCmd.cpp index afb02d352..05ddcf616 100644 --- a/src/mmSolver/cmd/MMMemorySystemCmd.cpp +++ b/src/mmSolver/cmd/MMMemorySystemCmd.cpp @@ -44,6 +44,7 @@ // MM Solver #include +#include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/memory_system_utils.h" #include "mmSolver/utilities/memory_utils.h" @@ -202,8 +203,7 @@ MStatus MMMemorySystemCmd::doIt(const MArgList &args) { // maximum may exceed that size, so we must return the full // number converted to a string, then have the Python (or // MEL?) code convert that to an integer. - std::string number_string = mmstring::numberToString(bytes_value); - MString number_mstring(number_string.c_str()); + MString number_mstring = mmmayastring::numberToMString(bytes_value); MMMemorySystemCmd::setResult(number_mstring); } else { double outResult = diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 079dd2f20..636852d32 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -37,6 +37,7 @@ #include "mmSolver/image/ImagePixelData.h" #include "mmSolver/image/PixelDataType.h" #include "mmSolver/image/image_io.h" +#include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/string_utils.h" @@ -238,19 +239,13 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { // Some of the numbers may be more than 'int' can hold, so we // must return as a MString. - std::string width_string = mmstring::numberToString(image_width); - std::string height_string = mmstring::numberToString(image_height); - std::string num_channels_string = - mmstring::numberToString(static_cast(num_channels)); - std::string bytes_per_channel_string = - mmstring::numberToString(static_cast(bytes_per_channel)); - std::string byte_count_string = mmstring::numberToString(byte_count); - - MString width_mstring(width_string.c_str()); - MString height_mstring(height_string.c_str()); - MString num_channels_mstring(num_channels_string.c_str()); - MString bytes_per_channel_mstring(bytes_per_channel_string.c_str()); - MString byte_count_mstring(byte_count_string.c_str()); + MString width_mstring(mmmayastring::numberToMString(image_width)); + MString height_mstring(mmmayastring::numberToMString(image_height)); + MString num_channels_mstring( + mmmayastring::numberToMString(num_channels)); + MString bytes_per_channel_mstring( + mmmayastring::numberToMString(bytes_per_channel)); + MString byte_count_mstring(mmmayastring::numberToMString(byte_count)); MStringArray outResult; outResult.append(width_mstring); diff --git a/src/mmSolver/cmd/MMSolverTypeCmd.cpp b/src/mmSolver/cmd/MMSolverTypeCmd.cpp index da5e673e9..27fde6ea4 100644 --- a/src/mmSolver/cmd/MMSolverTypeCmd.cpp +++ b/src/mmSolver/cmd/MMSolverTypeCmd.cpp @@ -45,9 +45,9 @@ // MM Solver #include "mmSolver/adjust/adjust_base.h" +#include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/utilities/debug_utils.h" -#include "mmSolver/utilities/string_utils.h" namespace mmsolver { @@ -207,9 +207,9 @@ MStatus MMSolverTypeCmd::doIt(const MArgList &args) { MString item = ""; if (m_index) { - std::string index_string = - mmstring::numberToString(index); - item += MString(index_string.c_str()); + MString index_mstring = + mmmayastring::numberToMString(index); + item += index_mstring; item += "="; } item += name.c_str(); @@ -237,8 +237,9 @@ MStatus MMSolverTypeCmd::doIt(const MArgList &args) { std::string name = solverType.second; if (m_index) { - std::string index_string = mmstring::numberToString(index); - outResult += MString(index_string.c_str()); + MString index_mstring = + mmmayastring::numberToMString(index); + outResult += index_mstring; outResult += "="; } outResult += name.c_str(); diff --git a/src/mmSolver/mayahelper/maya_string_utils.cpp b/src/mmSolver/mayahelper/maya_string_utils.cpp new file mode 100644 index 000000000..be9c8bab4 --- /dev/null +++ b/src/mmSolver/mayahelper/maya_string_utils.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "maya_string_utils.h" + +namespace mmmayastring {} // namespace mmmayastring diff --git a/src/mmSolver/mayahelper/maya_string_utils.h b/src/mmSolver/mayahelper/maya_string_utils.h new file mode 100644 index 000000000..f4a3ea303 --- /dev/null +++ b/src/mmSolver/mayahelper/maya_string_utils.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Generic string helper functions for Maya. + */ + +#ifndef MAYA_STRING_UTILS_H +#define MAYA_STRING_UTILS_H + +// STL +#include // fabs +#include +#include // stringstream +#include + +// MM Solver +#include "mmSolver/utilities/string_utils.h" + +// Maya +#include + +namespace mmmayastring { + +/*! Convert a number to a Maya MString + * + * Convert 42 to "42". + */ +template +MString numberToMString(NUM_TYPE num) { + std::string string = mmstring::numberToString(num); + MString mstring(string.c_str()); + return mstring; +} + +/*! Convert a Maya MString to a number. + * + * Convert "3.14" to 3.14. + */ +template +NUM_TYPE mstringToNumber(const MString &text) { + std::string text_string = text.asChar(); + return mmstring::stringToNumber(text_string); +} + +} // namespace mmmayastring + +#endif // MAYA_STRING_UTILS_H From 9465031a40e7ba0b4e5b5b8ab986b0af6c22c2c6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:09:03 +1000 Subject: [PATCH 074/295] Fix read_texture_image_file definition. This was not removed in the previous commit. --- src/mmSolver/image/ImageCache.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index da35eee97..1a957fa2c 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -27,6 +27,7 @@ #include #include #include +// #include // Maya #include @@ -92,21 +93,39 @@ struct ImageCache { // GPU data types using GPUCacheKey = std::string; using GPUCacheValue = TextureData; + using GPUKeyList = std::list; using GPUKeyListIt = GPUKeyList::iterator; + using GPUMap = std::unordered_map>; using GPUMapIt = GPUMap::iterator; + // using GPUGroupKeySet = std::unordered_set; + // using GPUGroupKeySetIt = GPUGroupKeySet::iterator; + + // using GPUCacheGroupKey = std::string; + // using GPUGroupKeyMap = std::unordered_map; using GPUGroupKeyMapIt = GPUGroupKeyMap::iterator; + // CPU data types using CPUCacheKey = std::string; using CPUCacheValue = ImagePixelData; + using CPUKeyList = std::list; using CPUKeyListIt = CPUKeyList::iterator; + using CPUMap = std::unordered_map>; using CPUMapIt = CPUMap::iterator; + // using CPUGroupKeySet = std::unordered_set; + // using CPUGroupKeySetIt = CPUGroupKeySet::iterator; + + // using CPUCacheGroupKey = std::string; + // using CPUGroupKeyMap = std::unordered_map; using CPUGroupKeyMapIt = CPUGroupKeyMap::iterator; + public: static ImageCache &getInstance() { static ImageCache instance; // Guaranteed to be destroyed. @@ -311,12 +330,18 @@ struct ImageCache { // 'back' of the list is the "most recently used" key. GPUKeyList m_gpu_cache_key_list; CPUKeyList m_cpu_cache_key_list; + + // // A Map of groups to a Set of key values. + // // + // // This map can be used to find all the loaded values used by an + // // image sequence. + // GPUGroupKeyMap m_gpu_cache_group_map; + // CPUGroupKeyMap m_cpu_cache_group_map; }; MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, const MString &file_path, - const MImage::MPixelType pixel_type, const bool do_texture_update); } // namespace image } // namespace mmsolver From 2b9ab526760b191ebbf1f17c11a6e02cf8143a1b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:27:05 +1000 Subject: [PATCH 075/295] mmReadImage command - mmpath function to resolve file. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 636852d32..0dabc4cd3 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -39,6 +39,7 @@ #include "mmSolver/image/image_io.h" #include "mmSolver/mayahelper/maya_string_utils.h" #include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/path_utils.h" #include "mmSolver/utilities/string_utils.h" // Command arguments and command name: @@ -174,27 +175,9 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { status = parseArgs(args); CHECK_MSTATUS_AND_RETURN_IT(status); - auto file_object = MFileObject(); - file_object.setRawFullName(m_file_path); - file_object.setResolveMethod(MFileObject::kInputFile); - - bool path_exists = file_object.exists(); - if (!path_exists) { - MString resolved_file_path = file_object.resolvedFullName(); - status = MS::kFailure; - MMSOLVER_MAYA_WRN("mmReadImage: Could not find file path " - << "\"" << m_file_path.asChar() - << "\", resolved path " - << "\"" << resolved_file_path.asChar() << "\"."); - return status; - } - - MString resolved_file_path = file_object.resolvedFullName(); - if (resolved_file_path.length() > 0) { - MMSOLVER_MAYA_VRB("mmReadImage: resolved file path " - << "\"" << resolved_file_path.asChar() << "\"."); - m_file_path = file_object.resolvedFullName(); - } + // status = resolve_file_path(m_file_path); + status = mmpath::resolve_input_file_path(m_file_path); + CHECK_MSTATUS_AND_RETURN_IT(status); if (m_query_width_height) { uint32_t image_width = 0; From 659b94f423b2e2465d42b6c5d43943a327c1062e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:28:31 +1000 Subject: [PATCH 076/295] mmConvertImage command - move functions into mmsolver::image. --- src/CMakeLists.txt | 1 + src/mmSolver/cmd/MMConvertImageCmd.cpp | 259 +-------------------- src/mmSolver/image/image_convert.cpp | 304 +++++++++++++++++++++++++ src/mmSolver/image/image_convert.h | 39 ++++ 4 files changed, 347 insertions(+), 256 deletions(-) create mode 100644 src/mmSolver/image/image_convert.cpp create mode 100644 src/mmSolver/image/image_convert.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aaf9de90b..066aa3e34 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -72,6 +72,7 @@ set(SOURCE_FILES mmSolver/image/ImagePixelData.cpp mmSolver/image/PixelDataType.cpp mmSolver/image/TextureData.cpp + mmSolver/image/image_convert.cpp mmSolver/image/image_io.cpp mmSolver/mayahelper/maya_attr.cpp mmSolver/mayahelper/maya_bundle.cpp diff --git a/src/mmSolver/cmd/MMConvertImageCmd.cpp b/src/mmSolver/cmd/MMConvertImageCmd.cpp index e1be18b50..81e477b1e 100644 --- a/src/mmSolver/cmd/MMConvertImageCmd.cpp +++ b/src/mmSolver/cmd/MMConvertImageCmd.cpp @@ -54,14 +54,7 @@ // Maya #include #include -#include #include -#include -#include -#include -#include -#include -#include #include #include #include @@ -69,14 +62,12 @@ // MM Solver #include +#include "mmSolver/image/image_convert.h" #include "mmSolver/utilities/debug_utils.h" #include "mmSolver/utilities/string_utils.h" namespace mmsolver { -using mmmath::clamp; -using mmmath::fast_pow; - // For an image sequence the 'file_path' should contain at least one // character '#', which will be replaced with the 'frame_number', with // a padding width of 'frame_padding'. @@ -151,250 +142,6 @@ MStatus find_existing_file_path(MFileObject &file_object, return status; } -MStatus guess_output_format_pixel_type(const MString &in_output_format, - MImage::MPixelType &out_pixel_type) { - MStatus status = MStatus::kSuccess; - - MString output_format(in_output_format); - output_format.toLowerCase(); - - if (output_format == MString("exr")) { - out_pixel_type = MImage::kFloat; - } else if (output_format == MString("hdr")) { - out_pixel_type = MImage::kFloat; - } else { - out_pixel_type = MImage::kByte; - } - - return status; -} - -MStatus guess_file_path_pixel_type(const MString &in_file_path, - MImage::MPixelType &out_pixel_type) { - MStatus status = MStatus::kSuccess; - - MString file_path(in_file_path); - file_path.toLowerCase(); - - MStringArray splits; - file_path.split('.', splits); - MString file_extension = splits[splits.length() - 1]; - - guess_output_format_pixel_type(file_extension, out_pixel_type); - - return status; -} - -MStatus resize_image(MImage &image, const double resize_scale) { - MStatus status = MStatus::kSuccess; - - MImage::MPixelType pixel_type = image.pixelType(); - if (pixel_type != MImage::kByte) { - MMSOLVER_MAYA_WRN( - "mmConvertImage: " - << "Maya does not support resizing floating-point pixels."); - } - - uint32_t src_width = 2; - uint32_t src_height = 2; - status = image.getSize(src_width, src_height); - CHECK_MSTATUS_AND_RETURN_IT(status); - - auto dst_width_float = static_cast(src_width) * resize_scale; - auto dst_height_float = static_cast(src_height) * resize_scale; - auto dst_width = static_cast(dst_width_float); - auto dst_height = static_cast(dst_height_float); - if ((src_width != dst_width) && (src_height != dst_height)) { - const auto preserve_aspect_ratio = true; - - // TODO: Replace this with a hand-written resize function. The - // MImage.resize() method appears to have a bug whereby the - // resized image is offset by +1 pixel in X and Y. - // - // If we can use Rust, then we have these easily available, - // and they appear to do exactly what we need, very quickly: - // https://crates.io/crates/fast_image_resize - // https://crates.io/crates/resize - // - // NOTE: MImage.resize() only works on 8-bit images, not - // floating point. - status = image.resize(dst_width, dst_height, preserve_aspect_ratio); - CHECK_MSTATUS_AND_RETURN_IT(status); - } - - return status; -} - -MStatus convert_image(const MString &src_file_path, - const MString &dst_file_path, - // Common output formats include: als, bmp, cin, - // gif, jpg, rla, sgi, tga, tif, iff. "iff" is - // default. - const MString &dst_output_format, - const double resize_scale) { - MStatus status = MStatus::kSuccess; - - if (src_file_path == dst_file_path) { - status = MS::kFailure; - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Cannot have source and destination as same path: " - << src_file_path.asChar()); - CHECK_MSTATUS_AND_RETURN_IT(status); - } - - auto image = MImage(); - // kUnknown attempts to load the native pixel type. - auto src_pixel_type = MImage::kUnknown; - status = image.readFromFile( - src_file_path, - src_pixel_type // The desired pixel format is unknown. - ); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Image file path could not be read: " - << src_file_path.asChar()); - CHECK_MSTATUS_AND_RETURN_IT(status); - } - src_pixel_type = image.pixelType(); - const bool src_is_rgba = image.isRGBA(); - - // Maya always stores 4 channels (according to the - // documentation). - const auto channels = 4; - - // Guess the Pixel Type for the output image. - MImage::MPixelType dst_pixel_type; - MImage::MPixelType format_pixel_type; - status = - guess_output_format_pixel_type(dst_output_format, format_pixel_type); - CHECK_MSTATUS_AND_RETURN_IT(status); - status = guess_file_path_pixel_type(dst_file_path, dst_pixel_type); - CHECK_MSTATUS_AND_RETURN_IT(status); - if (format_pixel_type != dst_pixel_type) { - MMSOLVER_MAYA_WRN( - "mmConvertImage: " - << "The destination file extension and output format seem " - "to contradict each other. file path: " - << dst_file_path.asChar() << " output format: \"" - << dst_output_format.asChar() << "\""); - } - - if (src_pixel_type == dst_pixel_type) { - // Try to resize. We can only resize kByte - Maya is limited - // with the pixel types it can resize. - if (src_pixel_type == MImage::kByte) { - status = resize_image(image, resize_scale); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to resize image file: " - << src_file_path.asChar()); - return status; - } - } - - // No conversion is needed. We write out the 8-bit image - // directly. - status = image.writeToFile(dst_file_path, dst_output_format); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to write image file: " - << dst_file_path.asChar() << " output format: \"" - << dst_output_format.asChar() << "\""); - CHECK_MSTATUS_AND_RETURN_IT(status); - } - } else { - // Convert 32-bit to 8-bit integer. We assume the image - // has not been resized yet. - if (image.pixelType() == MImage::kByte) { - status = MS::kFailure; - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to write image file: " - << dst_file_path.asChar() << " output format: \"" - << dst_output_format.asChar() << "\""); - return status; - } - - // Do gamma correction before converting. - uint32_t image_width = 2; - uint32_t image_height = 2; - status = image.getSize(image_width, image_height); - CHECK_MSTATUS_AND_RETURN_IT(status); - - // Get exponent based on if we are converting to/from floating - // point or not. Make (linear color space) pixels brighter. - const float gamma = 2.2F; - float exponent = 1.0F / gamma; - - // TODO: Use the Autodesk 'synColor' library to change the - // color space for an 8-bit file format. This library is - // available in the Maya devkit. - // - // https://forums.autodesk.com/t5/maya-programming/how-to-export-colors-with-correct-color-management/td-p/10515406 - - // Make sure we do our color management with floating point - // numbers, to avoid loss of detail. This does not do anything - // if the pixel format is already MImage::kFloat. - float *float_pixels = image.floatPixels(); - if (float_pixels == nullptr) { - // The data pointer should be valid because we have - // already read and operated on the pixels, so it seems - // very wrong that the data wouldn't be valid. - status = MS::kFailure; - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to get floating point pixel data: " - << src_file_path.asChar()); - return status; - } - - MImage out_image; - out_image.create(image_width, image_height, channels, MImage::kByte); - unsigned char *pixels = out_image.pixels(); - - // Apply gamma correction. - for (auto y = 0; y < image_height; ++y) { - for (auto x = 0; x < image_width; ++x) { - auto index = (y * image_width * channels) + (x * channels); - auto r = - clamp(fast_pow(float_pixels[index + 0], exponent) * 255.0F, - 0.0, 255.0); - auto g = - clamp(fast_pow(float_pixels[index + 1], exponent) * 255.0F, - 0.0, 255.0); - auto b = - clamp(fast_pow(float_pixels[index + 2], exponent) * 255.0F, - 0.0, 255.0); - auto a = clamp(float_pixels[index + 3] * 255.0F, 0.0, 255.0); - pixels[index + 0] = static_cast(r); - pixels[index + 1] = static_cast(g); - pixels[index + 2] = static_cast(b); - // Alpha does not need to be gamma corrected. - pixels[index + 3] = static_cast(a); - } - } - out_image.setRGBA(true); - - // Try to resize. We can only resize kByte - Maya is - // limited with the pixel types it can resize. - status = resize_image(out_image, resize_scale); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to resize image file: " - << src_file_path.asChar()); - return status; - } - - status = out_image.writeToFile(dst_file_path, dst_output_format); - if (status != MS::kSuccess) { - MMSOLVER_MAYA_ERR("mmConvertImage: " - << "Failed to write image file: " - << dst_file_path.asChar() << " output format: \"" - << dst_output_format.asChar() << "\""); - CHECK_MSTATUS_AND_RETURN_IT(status); - } - } - return status; -} - MMConvertImageCmd::~MMConvertImageCmd() {} void *MMConvertImageCmd::creator() { return new MMConvertImageCmd(); } @@ -603,8 +350,8 @@ MStatus MMConvertImageCmd::doIt(const MArgList &args) { continue; } - status = convert_image(src_file_path, dst_file_path, - m_dst_output_format, m_resize_scale); + status = image::convert_image(src_file_path, dst_file_path, + m_dst_output_format, m_resize_scale); if (status != MS::kSuccess) { MMSOLVER_MAYA_WRN("mmConvertImage: " << "Failed to convert image: " diff --git a/src/mmSolver/image/image_convert.cpp b/src/mmSolver/image/image_convert.cpp new file mode 100644 index 000000000..beaaf62e5 --- /dev/null +++ b/src/mmSolver/image/image_convert.cpp @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include "image_convert.h" + +// Get M_PI constant +#define _USE_MATH_DEFINES +#include + +// STL +#include +#include +#include +#include +#include + +// Maya +#include +#include +#include + +// MM Solver +#include +#include + +#include "PixelDataType.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/render/shader/shader_utils.h" +#include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/number_utils.h" +#include "mmSolver/utilities/path_utils.h" + +namespace mmsolver { +namespace image { + +using mmmath::clamp; +using mmmath::fast_pow; + +MStatus guess_output_format_pixel_type(const MString &in_output_format, + MImage::MPixelType &out_pixel_type) { + MStatus status = MStatus::kSuccess; + + MString output_format(in_output_format); + output_format.toLowerCase(); + + if (output_format == MString("exr")) { + out_pixel_type = MImage::kFloat; + } else if (output_format == MString("hdr")) { + out_pixel_type = MImage::kFloat; + } else { + out_pixel_type = MImage::kByte; + } + + return status; +} + +MStatus guess_file_path_pixel_type(const MString &in_file_path, + MImage::MPixelType &out_pixel_type) { + MStatus status = MStatus::kSuccess; + + MString file_path(in_file_path); + file_path.toLowerCase(); + + MStringArray splits; + file_path.split('.', splits); + MString file_extension = splits[splits.length() - 1]; + + guess_output_format_pixel_type(file_extension, out_pixel_type); + + return status; +} + +MStatus resize_image(MImage &image, const double resize_scale) { + MStatus status = MStatus::kSuccess; + + MImage::MPixelType pixel_type = image.pixelType(); + if (pixel_type != MImage::kByte) { + MMSOLVER_MAYA_WRN( + "mmConvertImage: " + << "Maya does not support resizing floating-point pixels."); + } + + uint32_t src_width = 2; + uint32_t src_height = 2; + status = image.getSize(src_width, src_height); + CHECK_MSTATUS_AND_RETURN_IT(status); + + auto dst_width_float = static_cast(src_width) * resize_scale; + auto dst_height_float = static_cast(src_height) * resize_scale; + auto dst_width = static_cast(dst_width_float); + auto dst_height = static_cast(dst_height_float); + if ((src_width != dst_width) && (src_height != dst_height)) { + const auto preserve_aspect_ratio = true; + + // TODO: Replace this with a hand-written resize function. The + // MImage.resize() method appears to have a bug whereby the + // resized image is offset by +1 pixel in X and Y. + // + // If we can use Rust, then we have these easily available, + // and they appear to do exactly what we need, very quickly: + // https://crates.io/crates/fast_image_resize + // https://crates.io/crates/resize + // + // NOTE: MImage.resize() only works on 8-bit images, not + // floating point. + status = image.resize(dst_width, dst_height, preserve_aspect_ratio); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + return status; +} + +// This tool should be able to convert an image sequence with one +// image format to another. +// +// Mostly this tool is intended to convert non-native image formats to +// Maya IFF for increased speed. +MStatus convert_image(const MString &src_file_path, + const MString &dst_file_path, + // Common output formats include: als, bmp, cin, + // gif, jpg, rla, sgi, tga, tif, iff. "iff" is + // default. + const MString &dst_output_format, + const double resize_scale) { + MStatus status = MStatus::kSuccess; + + if (src_file_path == dst_file_path) { + status = MS::kFailure; + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Cannot have source and destination as same path: " + << src_file_path.asChar()); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + + auto image = MImage(); + // kUnknown attempts to load the native pixel type. + auto src_pixel_type = MImage::kUnknown; + status = image.readFromFile( + src_file_path, + src_pixel_type // The desired pixel format is unknown. + ); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Image file path could not be read: " + << src_file_path.asChar()); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + src_pixel_type = image.pixelType(); + const bool src_is_rgba = image.isRGBA(); + + // Maya always stores 4 channels (according to the + // documentation). + const auto channels = 4; + + // Guess the Pixel Type for the output image. + MImage::MPixelType dst_pixel_type; + MImage::MPixelType format_pixel_type; + status = + guess_output_format_pixel_type(dst_output_format, format_pixel_type); + CHECK_MSTATUS_AND_RETURN_IT(status); + status = guess_file_path_pixel_type(dst_file_path, dst_pixel_type); + CHECK_MSTATUS_AND_RETURN_IT(status); + if (format_pixel_type != dst_pixel_type) { + MMSOLVER_MAYA_WRN( + "mmConvertImage: " + << "The destination file extension and output format seem " + "to contradict each other. file path: " + << dst_file_path.asChar() << " output format: \"" + << dst_output_format.asChar() << "\""); + } + + if (src_pixel_type == dst_pixel_type) { + // Try to resize. We can only resize kByte - Maya is limited + // with the pixel types it can resize. + if (src_pixel_type == MImage::kByte) { + status = resize_image(image, resize_scale); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to resize image file: " + << src_file_path.asChar()); + return status; + } + } + + // No conversion is needed. We write out the 8-bit image + // directly. + status = image.writeToFile(dst_file_path, dst_output_format); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to write image file: " + << dst_file_path.asChar() << " output format: \"" + << dst_output_format.asChar() << "\""); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + } else { + // Convert 32-bit to 8-bit integer. We assume the image + // has not been resized yet. + if (image.pixelType() == MImage::kByte) { + status = MS::kFailure; + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to write image file: " + << dst_file_path.asChar() << " output format: \"" + << dst_output_format.asChar() << "\""); + return status; + } + + // Do gamma correction before converting. + uint32_t image_width = 2; + uint32_t image_height = 2; + status = image.getSize(image_width, image_height); + CHECK_MSTATUS_AND_RETURN_IT(status); + + // Get exponent based on if we are converting to/from floating + // point or not. Make (linear color space) pixels brighter. + const float gamma = 2.2F; + float exponent = 1.0F / gamma; + + // TODO: Use the OpenColorIO library to change the + // color space for an 8-bit file format. + + // Make sure we do our color management with floating point + // numbers, to avoid loss of detail. This does not do anything + // if the pixel format is already MImage::kFloat. + float *float_pixels = image.floatPixels(); + if (float_pixels == nullptr) { + // The data pointer should be valid because we have + // already read and operated on the pixels, so it seems + // very wrong that the data wouldn't be valid. + status = MS::kFailure; + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to get floating point pixel data: " + << src_file_path.asChar()); + return status; + } + + MImage out_image; + out_image.create(image_width, image_height, channels, MImage::kByte); + unsigned char *pixels = out_image.pixels(); + + // Apply gamma correction. + for (auto y = 0; y < image_height; ++y) { + for (auto x = 0; x < image_width; ++x) { + auto index = (y * image_width * channels) + (x * channels); + auto r = + clamp(fast_pow(float_pixels[index + 0], exponent) * 255.0F, + 0.0, 255.0); + auto g = + clamp(fast_pow(float_pixels[index + 1], exponent) * 255.0F, + 0.0, 255.0); + auto b = + clamp(fast_pow(float_pixels[index + 2], exponent) * 255.0F, + 0.0, 255.0); + auto a = clamp(float_pixels[index + 3] * 255.0F, 0.0, 255.0); + pixels[index + 0] = static_cast(r); + pixels[index + 1] = static_cast(g); + pixels[index + 2] = static_cast(b); + // Alpha does not need to be gamma corrected. + pixels[index + 3] = static_cast(a); + } + } + out_image.setRGBA(true); + + // Try to resize. We can only resize kByte - Maya is + // limited with the pixel types it can resize. + status = resize_image(out_image, resize_scale); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to resize image file: " + << src_file_path.asChar()); + return status; + } + + status = out_image.writeToFile(dst_file_path, dst_output_format); + if (status != MS::kSuccess) { + MMSOLVER_MAYA_ERR("mmConvertImage: " + << "Failed to write image file: " + << dst_file_path.asChar() << " output format: \"" + << dst_output_format.asChar() << "\""); + CHECK_MSTATUS_AND_RETURN_IT(status); + } + } + return status; +} + +} // namespace image +} // namespace mmsolver diff --git a/src/mmSolver/image/image_convert.h b/src/mmSolver/image/image_convert.h new file mode 100644 index 000000000..7885e0014 --- /dev/null +++ b/src/mmSolver/image/image_convert.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#ifndef MM_SOLVER_IMAGE_IMAGE_CONVERT_H +#define MM_SOLVER_IMAGE_IMAGE_CONVERT_H + +// Maya +#include + +namespace mmsolver { +namespace image { + +MStatus convert_image(const MString &src_file_path, + const MString &dst_file_path, + const MString &dst_output_format, + const double resize_scale); + +} // namespace image +} // namespace mmsolver + +#endif // MM_SOLVER_IMAGE_IMAGE_CONVERT_H From bcfc1343230656869ca6020d82ba0619945947e7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 14:43:29 +1000 Subject: [PATCH 077/295] mmReadImage Command - Add -resolveFilePath flag For example this command flag is used like this: mmReadImage -query -resolveFilePath "/path/to/image.png"; --- src/mmSolver/cmd/MMReadImageCmd.cpp | 44 ++++++++++++++++++++++++++--- src/mmSolver/cmd/MMReadImageCmd.h | 1 + 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 0dabc4cd3..7947f7818 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -18,6 +18,24 @@ * ==================================================================== * * Command for running mmReadImage. + * + * MEL: + * // Get the width and height of the input image. + * mmReadImage -query -widthHeight "/path/to/image.png"; + * // For example, returns [1920, 1080] + * + * // Get the image's header details and pixel data size. + * mmReadImage -query -dataHeader "/path/to/image.png"; + * // For example, returns ["1920", "1080", "4", "1", "8294400"] + * // Index 0 is image width. + * // Index 1 is image height. + * // Index 2 is image channel count. + * // Index 3 is image bytes-per-channel count. + * // Index 4 is image size in bytes. + * + * // Full file path to the image name. + * string $resolved = `mmReadImage -query -resolveFilePath "/path/to/image.png"`; + * // Returns the resolved file path, if it exists, None otherwise. */ #include "MMReadImageCmd.h" @@ -52,6 +70,9 @@ #define DATA_HEADER_FLAG "-dhr" #define DATA_HEADER_FLAG_LONG "-dataHeader" +#define RESOLVE_FILE_PATH_FLAG "-rfp" +#define RESOLVE_FILE_PATH_FLAG_LONG "-resolveFilePath" + namespace mmsolver { MMReadImageCmd::~MMReadImageCmd() {} @@ -81,6 +102,7 @@ MSyntax MMReadImageCmd::newSyntax() { syntax.addFlag(WIDTH_HEIGHT_FLAG, WIDTH_HEIGHT_FLAG_LONG); syntax.addFlag(DATA_HEADER_FLAG, DATA_HEADER_FLAG_LONG); + syntax.addFlag(RESOLVE_FILE_PATH_FLAG, RESOLVE_FILE_PATH_FLAG_LONG); return syntax; } @@ -132,6 +154,10 @@ MStatus MMReadImageCmd::parseArgs(const MArgList &args) { m_query_data_header = argData.isFlagSet(DATA_HEADER_FLAG, &status); CHECK_MSTATUS_AND_RETURN_IT(status); + + m_query_resolve_file_path = + argData.isFlagSet(RESOLVE_FILE_PATH_FLAG, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); return status; } @@ -175,11 +201,18 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { status = parseArgs(args); CHECK_MSTATUS_AND_RETURN_IT(status); - // status = resolve_file_path(m_file_path); - status = mmpath::resolve_input_file_path(m_file_path); - CHECK_MSTATUS_AND_RETURN_IT(status); + if (m_query_resolve_file_path) { + status = mmpath::resolve_input_file_path(m_file_path); + if (status == MStatus::kSuccess) { + MMReadImageCmd::setResult(m_file_path); + } else { + // Pretend everything is fine. + status = MStatus::kSuccess; + } + } else if (m_query_width_height) { + status = mmpath::resolve_input_file_path(m_file_path); + CHECK_MSTATUS_AND_RETURN_IT(status); - if (m_query_width_height) { uint32_t image_width = 0; uint32_t image_height = 0; uint8_t num_channels = 0; @@ -197,6 +230,9 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { outResult.append(image_height); MMReadImageCmd::setResult(outResult); } else if (m_query_data_header) { + status = mmpath::resolve_input_file_path(m_file_path); + CHECK_MSTATUS_AND_RETURN_IT(status); + // NOTE: We do not want to have to call mmReadImage multiple // times. We want to get as much data as possible in a single // call, because subsequent calls will need to re-read the diff --git a/src/mmSolver/cmd/MMReadImageCmd.h b/src/mmSolver/cmd/MMReadImageCmd.h index 18c08d494..aed05ac8e 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.h +++ b/src/mmSolver/cmd/MMReadImageCmd.h @@ -58,6 +58,7 @@ class MMReadImageCmd : public MPxCommand { MString m_file_path; bool m_query_width_height; bool m_query_data_header; + bool m_query_resolve_file_path; }; } // namespace mmsolver From e58b4dc55b303bdf34ef27296e231346dc742327 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 15:24:25 +1000 Subject: [PATCH 078/295] mmReadImage command - fix -dataHeader flag integer output. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 7947f7818..9fcbd1146 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -34,7 +34,8 @@ * // Index 4 is image size in bytes. * * // Full file path to the image name. - * string $resolved = `mmReadImage -query -resolveFilePath "/path/to/image.png"`; + * string $file_path = "/path/to/image.png"; + * string $resolved = `mmReadImage -query -resolveFilePath $file_path`; * // Returns the resolved file path, if it exists, None otherwise. */ @@ -254,17 +255,20 @@ MStatus MMReadImageCmd::doIt(const MArgList &args) { image::ImagePixelData image_pixel_data(pixel_data, image_width, image_height, num_channels, pixel_data_type); - size_t byte_count = image_pixel_data.byte_count(); + const size_t byte_count = image_pixel_data.byte_count(); // Some of the numbers may be more than 'int' can hold, so we // must return as a MString. - MString width_mstring(mmmayastring::numberToMString(image_width)); - MString height_mstring(mmmayastring::numberToMString(image_height)); + MString width_mstring( + mmmayastring::numberToMString(image_width)); + MString height_mstring( + mmmayastring::numberToMString(image_height)); MString num_channels_mstring( - mmmayastring::numberToMString(num_channels)); + mmmayastring::numberToMString(num_channels)); MString bytes_per_channel_mstring( - mmmayastring::numberToMString(bytes_per_channel)); - MString byte_count_mstring(mmmayastring::numberToMString(byte_count)); + mmmayastring::numberToMString(bytes_per_channel)); + MString byte_count_mstring( + mmmayastring::numberToMString(byte_count)); MStringArray outResult; outResult.append(width_mstring); From 3b2762524da306dda9a978315045f0f13ab423fd Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 15:57:24 +1000 Subject: [PATCH 079/295] mmImagePlaneShape2 node - add image details to node This will allow the ability to query the image data structure, and estimate the amount of memory that will be used by the ImageCache. GitHub issue #252. --- .../AEmmImagePlaneShape2Template.mel | 3 ++ .../createimageplane/_lib/mmimageplane_v2.py | 47 +++++++++++++------ src/mmSolver/shape/ImagePlaneShape2Node.cpp | 28 +++++++++++ src/mmSolver/shape/ImagePlaneShape2Node.h | 3 ++ 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 424500776..0b86b6c57 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -285,6 +285,9 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addSeparator; editorTemplate -addControl "imageWidth"; editorTemplate -addControl "imageHeight"; + editorTemplate -addControl "imageNumChannels"; + editorTemplate -addControl "imageBytesPerChannel"; + editorTemplate -addControl "imageSizeBytes"; editorTemplate -addControl "imagePixelAspect"; editorTemplate -addSeparator; editorTemplate -addControl "imageSequenceStartFrame"; diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index fb9c831c7..90ab54a39 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -240,27 +240,46 @@ def set_image_sequence(shp, image_sequence_path, attr_name): mmapi.load_plugin() try: - image_width_height = maya.cmds.mmReadImage( - first_frame_file_seq, query=True, widthHeight=True + image_data_header = maya.cmds.mmReadImage( + first_frame_file_seq, query=True, dataHeader=True ) except RuntimeError: - image_width_height = None + image_data_header = None LOG.warn('Failed to read file: %r', first_frame_file_seq) - if image_width_height is not None: - image_width = image_width_height[0] - image_height = image_width_height[1] + LOG.warn('image_data_header: %r', image_data_header) - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageWidth', lock=False) - maya.cmds.setAttr(shp + '.imageHeight', lock=False) + image_width = 1 + image_height = 1 + image_num_channels = 0 + image_bytes_per_channel = 0 + image_data_size_as_bytes = 0 + if image_data_header is not None: + image_width = int(image_data_header[0]) + image_height = int(image_data_header[1]) + image_num_channels = int(image_data_header[2]) + image_bytes_per_channel = int(image_data_header[3]) + image_data_size_as_bytes = image_data_header[4] - maya.cmds.setAttr(shp + '.imageWidth', image_width) - maya.cmds.setAttr(shp + '.imageHeight', image_height) + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=False) + maya.cmds.setAttr(shp + '.imageHeight', lock=False) + maya.cmds.setAttr(shp + '.imageNumChannels', lock=False) + maya.cmds.setAttr(shp + '.imageBytesPerChannel', lock=False) + maya.cmds.setAttr(shp + '.imageSizeBytes', lock=False) + + maya.cmds.setAttr(shp + '.imageWidth', image_width) + maya.cmds.setAttr(shp + '.imageHeight', image_height) + maya.cmds.setAttr(shp + '.imageNumChannels', image_num_channels) + maya.cmds.setAttr(shp + '.imageBytesPerChannel', image_bytes_per_channel) + maya.cmds.setAttr(shp + '.imageSizeBytes', image_data_size_as_bytes, type='string') - if not node_utils.node_is_referenced(shp): - maya.cmds.setAttr(shp + '.imageWidth', lock=True) - maya.cmds.setAttr(shp + '.imageHeight', lock=True) + if not node_utils.node_is_referenced(shp): + maya.cmds.setAttr(shp + '.imageWidth', lock=True) + maya.cmds.setAttr(shp + '.imageHeight', lock=True) + maya.cmds.setAttr(shp + '.imageNumChannels', lock=True) + maya.cmds.setAttr(shp + '.imageBytesPerChannel', lock=True) + maya.cmds.setAttr(shp + '.imageSizeBytes', lock=True) format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED ( diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index 1151032ea..7834c1c74 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -73,6 +73,9 @@ MObject ImagePlaneShape2Node::m_draw_image_size; MObject ImagePlaneShape2Node::m_draw_camera_size; MObject ImagePlaneShape2Node::m_image_width; MObject ImagePlaneShape2Node::m_image_height; +MObject ImagePlaneShape2Node::m_image_num_channels; +MObject ImagePlaneShape2Node::m_image_bytes_per_channel; +MObject ImagePlaneShape2Node::m_image_size_bytes; MObject ImagePlaneShape2Node::m_image_pixel_aspect; MObject ImagePlaneShape2Node::m_camera_width_inch; MObject ImagePlaneShape2Node::m_camera_height_inch; @@ -215,6 +218,31 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setMin(1)); CHECK_MSTATUS(addAttribute(m_image_height)); + m_image_num_channels = + nAttr.create("imageNumChannels", "imgnchan", MFnNumericData::kInt, 4); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(0)); + CHECK_MSTATUS(addAttribute(m_image_num_channels)); + + m_image_bytes_per_channel = nAttr.create( + "imageBytesPerChannel", "imgbtyprchan", MFnNumericData::kInt, 1); + CHECK_MSTATUS(nAttr.setStorable(true)); + CHECK_MSTATUS(nAttr.setKeyable(true)); + CHECK_MSTATUS(nAttr.setMin(0)); + CHECK_MSTATUS(addAttribute(m_image_bytes_per_channel)); + + // Create empty string data to be used as attribute default + // (string) value. + MFnStringData zero_string_data; + MObject zero_string_data_obj = zero_string_data.create("0"); + + m_image_size_bytes = tAttr.create("imageSizeBytes", "imgszbyt", + MFnData::kString, zero_string_data_obj); + CHECK_MSTATUS(tAttr.setStorable(true)); + CHECK_MSTATUS(tAttr.setUsedAsFilename(false)); + CHECK_MSTATUS(addAttribute(m_image_size_bytes)); + m_image_pixel_aspect = nAttr.create("imagePixelAspect", "imgpxasp", MFnNumericData::kDouble, 1.0); CHECK_MSTATUS(nAttr.setStorable(true)); diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.h b/src/mmSolver/shape/ImagePlaneShape2Node.h index 4c068dbec..e18676a0a 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.h +++ b/src/mmSolver/shape/ImagePlaneShape2Node.h @@ -89,6 +89,9 @@ class ImagePlaneShape2Node : public MPxLocatorNode { static MObject m_draw_camera_size; static MObject m_image_width; static MObject m_image_height; + static MObject m_image_num_channels; + static MObject m_image_bytes_per_channel; + static MObject m_image_size_bytes; static MObject m_image_pixel_aspect; static MObject m_camera_width_inch; static MObject m_camera_height_inch; From af58fe8e7261471bb4690a162a4bd18746ca6819 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 15:58:00 +1000 Subject: [PATCH 080/295] mmReadImage command - Refine command examples. --- src/mmSolver/cmd/MMReadImageCmd.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 9fcbd1146..7c94e741e 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -20,12 +20,15 @@ * Command for running mmReadImage. * * MEL: + * // Example image file path. + * string $file_path = "/path/to/image.png"; + * * // Get the width and height of the input image. - * mmReadImage -query -widthHeight "/path/to/image.png"; + * mmReadImage -query -widthHeight $file_path; * // For example, returns [1920, 1080] * * // Get the image's header details and pixel data size. - * mmReadImage -query -dataHeader "/path/to/image.png"; + * mmReadImage -query -dataHeader $file_path; * // For example, returns ["1920", "1080", "4", "1", "8294400"] * // Index 0 is image width. * // Index 1 is image height. @@ -34,7 +37,6 @@ * // Index 4 is image size in bytes. * * // Full file path to the image name. - * string $file_path = "/path/to/image.png"; * string $resolved = `mmReadImage -query -resolveFilePath $file_path`; * // Returns the resolved file path, if it exists, None otherwise. */ From 2f1fd53e38f0d3232149b39f834e3dfd2ca5aaf6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 15:58:45 +1000 Subject: [PATCH 081/295] mmImageCache command - change formatting. --- src/mmSolver/cmd/MMImageCacheCmd.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 084dfb20a..0f385c695 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -173,7 +173,8 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MString mstring; status = argData.getFlagArgument(GPU_CAPACITY_FLAG, 0, mstring); CHECK_MSTATUS_AND_RETURN_IT(status); - m_gpu_capacity_bytes = mmmayastring::mstringToNumber(mstring); + m_gpu_capacity_bytes = + mmmayastring::mstringToNumber(mstring); // Store the current value, so we can undo later. m_previous_gpu_capacity_bytes = @@ -183,7 +184,8 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MString mstring; status = argData.getFlagArgument(CPU_CAPACITY_FLAG, 0, mstring); CHECK_MSTATUS_AND_RETURN_IT(status); - m_cpu_capacity_bytes = mmmayastring::mstringToNumber(mstring); + m_cpu_capacity_bytes = + mmmayastring::mstringToNumber(mstring); // Store the current value, so we can undo later. m_previous_cpu_capacity_bytes = From ee759f38b8bba5190b20f643ab04f265bec390e4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 15:59:14 +1000 Subject: [PATCH 082/295] mmImageCache command - Refine command examples. --- src/mmSolver/cmd/MMImageCacheCmd.h | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index 1e2f6f99b..39e0751a3 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -25,17 +25,24 @@ * * * Use cases: + * * - Get total amount of used CPU memory, displayed in a UI. + * * - Get total amount of CPU memory on the system. + * * - Get amount of CPU memory used by the current process. + * * - Get total GPU memory. + * * - Get used GPU memory. + * * - Display on an image plane node... * - How much memory the currently loaded image sequence is expected * to take up. * - How much memory each frame takes up. * - What is the currently used CPU and GPU memory by this image * sequence/image plane. + * * - UI to display overall memory usage, with... * - Each loaded image sequence. * - How much memory is used by each image sequence (CPU and GPU). @@ -47,21 +54,17 @@ * * MEL: * // Return the number of used GPU memory bytes by the image cache. - * mmImageCache -query -gpuCapacityUsedBytes; + * mmImageCache -query -gpuUsed; * * // Return the number of free GPU memory bytes by the image cache. - * mmImageCache -query -gpuCapacityFreeBytes; + * mmImageCache -query -gpuFree; * * // Return the total number of GPU memory bytes that is allowed * // to be to be used by the image cache. - * mmImageCache -query -gpuCapacityAllowedBytes; + * mmImageCache -query -gpuCapacity; * * // Set the number of GPU memory bytes that is allowed to be used. - * mmImageCache -edit -gpuCapacityBytes 1000; - * - * // Set the minimum number of GPU images allowed to be stored in - * // the cache. - * mmImageCache -edit -gpuItemMinimum 3; + * mmImageCache -edit -gpuCapacity 1000; * * // Get the amount of data that the image contains. * // @@ -69,7 +72,8 @@ * // to find the data type and dimensions. * string $image_sequence = "/path/to/image.####.png"; * string $image_file = "/path/to/image.1001.png"; - * int $data_size = `mmReadImage -dataSizeBytes $image_file`; + * string $data_header[] = `mmReadImage -dataHeader $image_file`; + * string $data_size = $data_header[4]; * * // Set the number of CPU memory bytes that is allowed to be used. * int $start_frame = 1001; @@ -80,7 +84,7 @@ * // CPU memory, before attempting to load the full image sequence * // or not. * int $new_capacity = ($data_size * ($frame_count + $extra_buffer)) - * mmImageCache -edit -cpuCapacityBytes $new_capacity; + * mmImageCache -edit -cpuCapacity $new_capacity; * * // Read the image sequence into RAM. * // From 1bae926ce1334763a6a31893d1de68a7885d07c7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 19:52:24 +1000 Subject: [PATCH 083/295] Add mmImageSequenceFrameLogic node. This node controls the mmImagePlane's frame number. By default this node 'holds' the start and end frame, as well as calculating the best frame number. --- include/mmSolver/nodeTypeIds.h | 3 + .../AEmmImageSequenceFrameLogicTemplate.mel | 39 +++++ .../tools/createimageplane/_lib/main.py | 14 -- .../createimageplane/_lib/mmimageplane.py | 15 +- .../tools/createimageplane/constant.py | 23 --- src/CMakeLists.txt | 1 + .../node/MMImageSequenceFrameLogicNode.cpp | 165 ++++++++++++++++++ .../node/MMImageSequenceFrameLogicNode.h | 64 +++++++ src/mmSolver/pluginMain.cpp | 9 + 9 files changed, 294 insertions(+), 39 deletions(-) create mode 100644 mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel create mode 100644 src/mmSolver/node/MMImageSequenceFrameLogicNode.cpp create mode 100644 src/mmSolver/node/MMImageSequenceFrameLogicNode.h diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index f928f0060..c58a7fbcf 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -164,6 +164,9 @@ #define MM_IMAGE_PLANE_TRANSFORM_TYPE_NAME "mmImagePlaneTransform" #define MM_IMAGE_PLANE_TRANSFORM_DRAW_CLASSIFY "drawdb/geometry/transform" +#define MM_IMAGE_SEQUENCE_FRAME_LOGIC_TYPE_ID 0x0012F190 +#define MM_IMAGE_SEQUENCE_FRAME_LOGIC_TYPE_NAME "mmImageSequenceFrameLogic" + #define OCGM_IMAGE_PLANE_SHAPE_TYPE_ID 0x0012F18B #endif // MM_SOLVER_NODE_TYPE_IDS_H diff --git a/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel b/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel new file mode 100644 index 000000000..bd617c98f --- /dev/null +++ b/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel @@ -0,0 +1,39 @@ +// +// Copyright (C) 2024 David Cattermole. +// +// This file is part of mmSolver. +// +// mmSolver is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// mmSolver is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with mmSolver. If not, see . +// --------------------------------------------------------------------- +// +// Image Sequence Frame Logic node Template file. +// + +source "AEmmNodeTemplateCommon"; + +global proc AEmmImageSequenceFrameLogicTemplate(string $nodeName) { + AEmmNodeTemplateCommonBegin($nodeName); + + editorTemplate -beginLayout "Common" - collapse 0; + editorTemplate -addControl "inFrame"; + editorTemplate -addSeparator; + editorTemplate -addControl "firstFrame"; + editorTemplate -addControl "startFrame"; + editorTemplate -addControl "endFrame"; + editorTemplate -addSeparator; + editorTemplate -addControl "outFrame"; + editorTemplate -endLayout; + + AEmmNodeTemplateCommonEnd($nodeName); +} diff --git a/python/mmSolver/tools/createimageplane/_lib/main.py b/python/mmSolver/tools/createimageplane/_lib/main.py index 8b67e5009..32fd8bc84 100644 --- a/python/mmSolver/tools/createimageplane/_lib/main.py +++ b/python/mmSolver/tools/createimageplane/_lib/main.py @@ -28,7 +28,6 @@ import mmSolver.utils.constant as const_utils import mmSolver.utils.imageseq as imageseq_utils import mmSolver.utils.python_compat as pycompat -import mmSolver.tools.createimageplane.constant as const import mmSolver.tools.createimageplane._lib.constant as lib_const import mmSolver.tools.createimageplane._lib.mmimageplane as lib_mmimageplane import mmSolver.tools.createimageplane._lib.mmimageplane_v1 as lib_mmimageplane_v1 @@ -79,19 +78,6 @@ def create_image_plane_on_camera(cam, name=None, version=None): version=version, ) - # Logic to calculate the frame number. - # - # TODO: Move this expression into a Maya node, because expressions - # are buggy and not flexible. - frame_expr = const.FRAME_EXPRESSION.format(node=mm_ip_shp) - frame_expr = frame_expr.replace('{{', '{') - frame_expr = frame_expr.replace('}}', '}') - maya.cmds.expression(string=frame_expr) - - # Show the users the final frame number. - shp_node_attr = mm_ip_shp + '.imageSequenceFrameOutput' - maya.cmds.setAttr(shp_node_attr, lock=True) - # Image sequence. image_sequence_path = lib_utils.get_default_image_path() set_image_sequence(mm_ip_tfm, image_sequence_path) diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index 2ce25abaa..c44305675 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -28,12 +28,12 @@ import mmSolver.logger import mmSolver.api as mmapi import mmSolver.utils.python_compat as pycompat +import mmSolver.tools.createimageplane.constant as const import mmSolver.tools.createimageplane._lib.constant as lib_const import mmSolver.tools.createimageplane._lib.mmimageplane_v1 as lib_mmimageplane_v1 import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as lib_mmimageplane_v2 import mmSolver.tools.createimageplane._lib.utilities as lib_utils - LOG = mmSolver.logger.get_logger() @@ -207,9 +207,20 @@ def create_shape_node( maya.cmds.setAttr(img_plane_poly_shp + '.intermediateObject', 1) # Add extra message attributes for finding nodes during callbacks. - # maya.cmds.addAttr(shp, longName='imagePlaneShapeNode', attributeType='message') if shader_node_network is not None: maya.cmds.addAttr(shp, longName='shaderFileNode', attributeType='message') + + # Logic to calculate the frame number. + node = maya.cmds.createNode('mmImageSequenceFrameLogic') + lib_utils.force_connect_attr(shp + '.imageSequenceFrame', node + '.inFrame') + lib_utils.force_connect_attr(shp + '.imageSequenceFirstFrame', node + '.firstFrame') + lib_utils.force_connect_attr(shp + '.imageSequenceStartFrame', node + '.startFrame') + lib_utils.force_connect_attr(shp + '.imageSequenceEndFrame', node + '.endFrame') + lib_utils.force_connect_attr(node + '.outFrame', shp + '.imageSequenceFrameOutput') + + # Only show the users the final frame number, no editing. + maya.cmds.setAttr(shp + '.imageSequenceFrameOutput', lock=True) + return shp diff --git a/python/mmSolver/tools/createimageplane/constant.py b/python/mmSolver/tools/createimageplane/constant.py index ea0aa28e0..8d0834d17 100644 --- a/python/mmSolver/tools/createimageplane/constant.py +++ b/python/mmSolver/tools/createimageplane/constant.py @@ -28,26 +28,3 @@ {live_image_plane_shape}.lodVisibility = 0; }} ''' - -# NOTE: '{{' and '}}' is used in place of real '{' and '}' characters, -# to allow Python's 'str.format()' to work. -FRAME_EXPRESSION = ''' -int $start_frame = {node}.imageSequenceStartFrame; -int $end_frame = {node}.imageSequenceEndFrame; -int $first_frame = {node}.imageSequenceFirstFrame; -int $input_frame = {node}.imageSequenceFrame; - -int $result = ($start_frame - $first_frame) + $input_frame; - -// // Clamp to start and end frames. -// if ($result < $start_frame) -// {{ -// $result = $start_frame; -// }} -// else if ($result > $end_frame) -// {{ -// $result = $end_frame; -// }} - -{node}.imageSequenceFrameOutput = $result; -''' diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 066aa3e34..27865c50e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,6 +84,7 @@ set(SOURCE_FILES mmSolver/mayahelper/maya_utils.cpp mmSolver/node/MMCameraCalibrateNode.cpp mmSolver/node/MMImagePlaneTransformNode.cpp + mmSolver/node/MMImageSequenceFrameLogicNode.cpp mmSolver/node/MMLensData.cpp mmSolver/node/MMLensDeformerNode.cpp mmSolver/node/MMLensEvaluateNode.cpp diff --git a/src/mmSolver/node/MMImageSequenceFrameLogicNode.cpp b/src/mmSolver/node/MMImageSequenceFrameLogicNode.cpp new file mode 100644 index 000000000..c4fd7443c --- /dev/null +++ b/src/mmSolver/node/MMImageSequenceFrameLogicNode.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * + */ + +#include "MMImageSequenceFrameLogicNode.h" + +// STL +#include +#include + +// Maya +#include +#include +#include +#include +#include +#include +#include + +// MM Solver +#include "mmSolver/mayahelper/maya_camera.h" +#include "mmSolver/mayahelper/maya_utils.h" +#include "mmSolver/nodeTypeIds.h" +#include "mmSolver/utilities/debug_utils.h" +#include "mmSolver/utilities/number_utils.h" + +namespace mmsolver { + +MTypeId MMImageSequenceFrameLogicNode::m_id( + MM_IMAGE_SEQUENCE_FRAME_LOGIC_TYPE_ID); + +// Input Attributes +MObject MMImageSequenceFrameLogicNode::a_inFrame; +MObject MMImageSequenceFrameLogicNode::a_firstFrame; +MObject MMImageSequenceFrameLogicNode::a_startFrame; +MObject MMImageSequenceFrameLogicNode::a_endFrame; + +// Output Attributes +MObject MMImageSequenceFrameLogicNode::a_outFrame; + +MMImageSequenceFrameLogicNode::MMImageSequenceFrameLogicNode() {} + +MMImageSequenceFrameLogicNode::~MMImageSequenceFrameLogicNode() {} + +MString MMImageSequenceFrameLogicNode::nodeName() { + return MString(MM_IMAGE_SEQUENCE_FRAME_LOGIC_TYPE_NAME); +} + +MStatus MMImageSequenceFrameLogicNode::compute(const MPlug &plug, + MDataBlock &data) { + MStatus status = MS::kUnknownParameter; + + if (plug == a_outFrame) { + // Get Data Handles + MDataHandle inFrameHandle = data.inputValue(a_inFrame); + MDataHandle firstFrameHandle = data.inputValue(a_firstFrame); + MDataHandle startFrameHandle = data.inputValue(a_startFrame); + MDataHandle endFrameHandle = data.inputValue(a_endFrame); + + // Get Values + double inFrame = inFrameHandle.asDouble(); + double firstFrame = firstFrameHandle.asDouble(); + double startFrame = startFrameHandle.asDouble(); + double endFrame = endFrameHandle.asDouble(); + + // Clamp to start and end frames. + double outFrame = (startFrame - firstFrame) + inFrame; + if (outFrame < startFrame) { + outFrame = startFrame; + } else if (outFrame > endFrame) { + outFrame = endFrame; + } + + // Output Frame + MDataHandle outFrameHandle = data.outputValue(a_outFrame); + outFrameHandle.setDouble(outFrame); + outFrameHandle.setClean(); + + status = MS::kSuccess; + } + return status; +} + +void *MMImageSequenceFrameLogicNode::creator() { + return (new MMImageSequenceFrameLogicNode()); +} + +MStatus MMImageSequenceFrameLogicNode::initialize() { + MStatus status; + MFnNumericAttribute numericAttr; + MFnEnumAttribute enumAttr; + MFnCompoundAttribute compoundAttr; + + // In Frame + a_inFrame = + numericAttr.create("inFrame", "ifrm", MFnNumericData::kDouble, 0.0); + CHECK_MSTATUS(numericAttr.setStorable(true)); + CHECK_MSTATUS(numericAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(a_inFrame)); + + // Frame First + a_firstFrame = numericAttr.create("firstFrame", "fstfrm", + MFnNumericData::kDouble, 0.0); + CHECK_MSTATUS(numericAttr.setStorable(true)); + CHECK_MSTATUS(numericAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(a_firstFrame)); + + // Frame Start + a_startFrame = + numericAttr.create("startFrame", "stfrm", MFnNumericData::kDouble, 0.0); + CHECK_MSTATUS(numericAttr.setStorable(true)); + CHECK_MSTATUS(numericAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(a_startFrame)); + + // Frame End + a_endFrame = + numericAttr.create("endFrame", "edfrm", MFnNumericData::kDouble, 0.0); + CHECK_MSTATUS(numericAttr.setStorable(true)); + CHECK_MSTATUS(numericAttr.setKeyable(true)); + CHECK_MSTATUS(addAttribute(a_endFrame)); + + // Out Frame + a_outFrame = + numericAttr.create("outFrame", "ofrm", MFnNumericData::kDouble, 0.0); + CHECK_MSTATUS(numericAttr.setStorable(false)); + CHECK_MSTATUS(numericAttr.setKeyable(false)); + CHECK_MSTATUS(numericAttr.setReadable(true)); + CHECK_MSTATUS(numericAttr.setWritable(false)); + CHECK_MSTATUS(addAttribute(a_outFrame)); + + // Attribute Affects + MObjectArray inputAttrs; + inputAttrs.append(a_inFrame); + inputAttrs.append(a_firstFrame); + inputAttrs.append(a_startFrame); + inputAttrs.append(a_endFrame); + + MObjectArray outputAttrs; + outputAttrs.append(a_outFrame); + + CHECK_MSTATUS( + MMNodeInitUtils::attributeAffectsMulti(inputAttrs, outputAttrs)); + + return (MS::kSuccess); +} + +} // namespace mmsolver diff --git a/src/mmSolver/node/MMImageSequenceFrameLogicNode.h b/src/mmSolver/node/MMImageSequenceFrameLogicNode.h new file mode 100644 index 000000000..9216d78f9 --- /dev/null +++ b/src/mmSolver/node/MMImageSequenceFrameLogicNode.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Calculates the frame number of an image sequence. + */ + +#ifndef MM_IMAGE_SEQUENCE_FRAME_LOGIC_NODE_H +#define MM_IMAGE_SEQUENCE_FRAME_LOGIC_NODE_H + +// Maya +#include +#include +#include +#include +#include +#include + +namespace mmsolver { + +class MMImageSequenceFrameLogicNode : public MPxNode { +public: + MMImageSequenceFrameLogicNode(); + + virtual ~MMImageSequenceFrameLogicNode(); + + virtual MStatus compute(const MPlug &plug, MDataBlock &data); + + static void *creator(); + + static MStatus initialize(); + + static MString nodeName(); + + static MTypeId m_id; + + // Input Attributes + static MObject a_inFrame; + static MObject a_firstFrame; + static MObject a_startFrame; + static MObject a_endFrame; + + // Output Attributes + static MObject a_outFrame; +}; + +} // namespace mmsolver + +#endif // MM_IMAGE_SEQUENCE_FRAME_LOGIC_NODE_H diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index e25c27d2a..4a4839d61 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -55,6 +55,7 @@ #include "mmSolver/cmd/MMTestCameraMatrixCmd.h" #include "mmSolver/node/MMCameraCalibrateNode.h" #include "mmSolver/node/MMImagePlaneTransformNode.h" +#include "mmSolver/node/MMImageSequenceFrameLogicNode.h" #include "mmSolver/node/MMLensData.h" #include "mmSolver/node/MMLensDeformerNode.h" #include "mmSolver/node/MMLensEvaluateNode.h" @@ -290,6 +291,11 @@ MStatus initializePlugin(MObject obj) { mmsolver::MMMarkerScaleNode::creator, mmsolver::MMMarkerScaleNode::initialize, status); + REGISTER_NODE(plugin, mmsolver::MMImageSequenceFrameLogicNode::nodeName(), + mmsolver::MMImageSequenceFrameLogicNode::m_id, + mmsolver::MMImageSequenceFrameLogicNode::creator, + mmsolver::MMImageSequenceFrameLogicNode::initialize, status); + REGISTER_NODE(plugin, mmsolver::MMReprojectionNode::nodeName(), mmsolver::MMReprojectionNode::m_id, mmsolver::MMReprojectionNode::creator, @@ -694,6 +700,9 @@ MStatus uninitializePlugin(MObject obj) { DEREGISTER_NODE(plugin, mmsolver::MMMarkerGroupTransformNode::nodeName(), mmsolver::MMMarkerGroupTransformNode::m_id, status); + DEREGISTER_NODE(plugin, mmsolver::MMImageSequenceFrameLogicNode::nodeName(), + mmsolver::MMImageSequenceFrameLogicNode::m_id, status); + DEREGISTER_NODE(plugin, mmsolver::MMImagePlaneTransformNode::nodeName(), mmsolver::MMImagePlaneTransformNode::m_id, status); From 604bfd7619f0ac8cff723cb050fb525fea9a5f6c Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 21:09:46 +1000 Subject: [PATCH 084/295] mmImageSequenceFrameLogic command fix AE template --- mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel b/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel index bd617c98f..7a18f7f34 100644 --- a/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel +++ b/mel/AETemplates/AEmmImageSequenceFrameLogicTemplate.mel @@ -25,7 +25,7 @@ source "AEmmNodeTemplateCommon"; global proc AEmmImageSequenceFrameLogicTemplate(string $nodeName) { AEmmNodeTemplateCommonBegin($nodeName); - editorTemplate -beginLayout "Common" - collapse 0; + editorTemplate -beginLayout "Common" -collapse 0; editorTemplate -addControl "inFrame"; editorTemplate -addSeparator; editorTemplate -addControl "firstFrame"; From ca3d55ba76f5577570d8aefe092764b9fa8e29a3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 21:14:32 +1000 Subject: [PATCH 085/295] mmImagePlane - remove warning. --- python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 90ab54a39..db6b9dd34 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -247,8 +247,6 @@ def set_image_sequence(shp, image_sequence_path, attr_name): image_data_header = None LOG.warn('Failed to read file: %r', first_frame_file_seq) - LOG.warn('image_data_header: %r', image_data_header) - image_width = 1 image_height = 1 image_num_channels = 0 From dfc6c12e4465828ddd8701f67f03b155c1b63814 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 10 Jun 2024 22:12:06 +1000 Subject: [PATCH 086/295] AEmmImagePlaneShape2 - template hide image data header details --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 0b86b6c57..724e3b179 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -285,9 +285,6 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addSeparator; editorTemplate -addControl "imageWidth"; editorTemplate -addControl "imageHeight"; - editorTemplate -addControl "imageNumChannels"; - editorTemplate -addControl "imageBytesPerChannel"; - editorTemplate -addControl "imageSizeBytes"; editorTemplate -addControl "imagePixelAspect"; editorTemplate -addSeparator; editorTemplate -addControl "imageSequenceStartFrame"; @@ -335,6 +332,9 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -endLayout; // Internals that we don't want the user to see. + editorTemplate -suppress "imageNumChannels"; + editorTemplate -suppress "imageBytesPerChannel"; + editorTemplate -suppress "imageSizeBytes"; editorTemplate -suppress "imageSequencePadding"; editorTemplate -suppress "cameraWidthInch"; editorTemplate -suppress "cameraHeightInch"; From bafc99efd0271d08fd699a54771832d45133fb50 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 11 Jun 2024 23:03:32 +1000 Subject: [PATCH 087/295] Add Image Cache UI tool A first pass non-functional proto-type UI to control the mmSolver ImageCache. GitHub issue #252 --- .../AEmmImagePlaneShape2Template.mel | 18 +- python/CMakeLists.txt | 5 + python/mmSolver/tools/imagecache/__init__.py | 20 + python/mmSolver/tools/imagecache/constant.py | 22 + python/mmSolver/tools/imagecache/lib.py | 60 ++ python/mmSolver/tools/imagecache/tool.py | 34 ++ .../mmSolver/tools/imagecache/ui/__init__.py | 20 + .../tools/imagecache/ui/imagecache_layout.py | 51 ++ .../tools/imagecache/ui/imagecache_layout.ui | 533 ++++++++++++++++++ .../tools/imagecache/ui/imagecache_window.py | 116 ++++ share/config/functions.json | 9 + share/config/menu.json | 1 + share/config/shelf.json | 1 + share/config/shelf_minimal.json | 1 + 14 files changed, 888 insertions(+), 3 deletions(-) create mode 100644 python/mmSolver/tools/imagecache/__init__.py create mode 100644 python/mmSolver/tools/imagecache/constant.py create mode 100644 python/mmSolver/tools/imagecache/lib.py create mode 100644 python/mmSolver/tools/imagecache/tool.py create mode 100644 python/mmSolver/tools/imagecache/ui/__init__.py create mode 100644 python/mmSolver/tools/imagecache/ui/imagecache_layout.py create mode 100644 python/mmSolver/tools/imagecache/ui/imagecache_layout.ui create mode 100644 python/mmSolver/tools/imagecache/ui/imagecache_window.py diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 724e3b179..f7d359975 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -315,9 +315,21 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) // TODO: Add 'hudTextColor' - control the HUD text color. editorTemplate -endLayout; - // editorTemplate -beginLayout "Image Cache" -collapse 1; - // // TODO: Add controls to view and edit the image cache. - // editorTemplate -endLayout; + editorTemplate -beginLayout "Image Cache" -collapse 1; + // TODO: Add controls to view and edit the image cache. + // + // Add: + // - Display: + // - the current image size for one frame (MB) + // - the total memory size for the image sequence (GB / MB) + // - the used cache size. + // - Update button. + // - Clear cache button menu + // - Clear image plane. + // - Clear unused contents. + // - Clear all. + // + editorTemplate -endLayout; editorTemplate -beginLayout "Miscellaneous" -collapse 1; editorTemplate -addControl "meshResolution"; diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 01538bc25..545e60954 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -194,6 +194,11 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/surfacecluster/ui/ui_surfacecluster_layout.py ) + compile_qt_ui_to_python_file("imagecache" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/imagecache/ui/imagecache_layout.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/imagecache/ui/ui_imagecache_layout.py + ) + # Install generated Python UI files install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/" DESTINATION "${MODULE_FULL_NAME}/python" diff --git a/python/mmSolver/tools/imagecache/__init__.py b/python/mmSolver/tools/imagecache/__init__.py new file mode 100644 index 000000000..ac8160ee7 --- /dev/null +++ b/python/mmSolver/tools/imagecache/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache tool - Adjust the mmSolver Image Cache +""" diff --git a/python/mmSolver/tools/imagecache/constant.py b/python/mmSolver/tools/imagecache/constant.py new file mode 100644 index 000000000..d64b7f56e --- /dev/null +++ b/python/mmSolver/tools/imagecache/constant.py @@ -0,0 +1,22 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache constants. +""" + +WINDOW_TITLE = 'mmSolver Image Cache' diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py new file mode 100644 index 000000000..e16beb184 --- /dev/null +++ b/python/mmSolver/tools/imagecache/lib.py @@ -0,0 +1,60 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +CACHE_TYPE_ALL = 'all' +CACHE_TYPE_GPU = 'gpu' +CACHE_TYPE_CPU = 'cpu' +CACHE_TYPE_VALUES = [ + CACHE_TYPE_ALL, + CACHE_TYPE_GPU, + CACHE_TYPE_CPU, +] + + +def cache_remove_image_plane_contents(cache_type): + pass + + +def cache_remove_image_sequence(file_pattern, start_frame, end_frame, cache_type): + pass + + +def cache_remove_all(cache_type): + pass + + +def cache_remove_all_inactive(cache_type): + # Removes all the items in the cache that cannot be 'reached' by + # any of the image planes. + pass + + +def function(): + pass diff --git a/python/mmSolver/tools/imagecache/tool.py b/python/mmSolver/tools/imagecache/tool.py new file mode 100644 index 000000000..3b5594396 --- /dev/null +++ b/python/mmSolver/tools/imagecache/tool.py @@ -0,0 +1,34 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Controls the mmSolver Image Cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def main(): + import mmSolver.tools.imagecache.ui.imagecache_window as window + + window.main() diff --git a/python/mmSolver/tools/imagecache/ui/__init__.py b/python/mmSolver/tools/imagecache/ui/__init__.py new file mode 100644 index 000000000..871b8b123 --- /dev/null +++ b/python/mmSolver/tools/imagecache/ui/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache user interface. +""" diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py new file mode 100644 index 000000000..a8a5535d7 --- /dev/null +++ b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py @@ -0,0 +1,51 @@ +# Copyright (C) 2024 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The main component of the user interface for the image cache +window. +""" + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import mmSolver.logger +import mmSolver.tools.imagecache.ui.ui_imagecache_layout as ui_imagecache_layout + + +LOG = mmSolver.logger.get_logger() + + +class ImageCacheLayout(QtWidgets.QWidget, ui_imagecache_layout.Ui_Form): + def __init__(self, parent=None, *args, **kwargs): + super(ImageCacheLayout, self).__init__(*args, **kwargs) + self.setupUi(self) + + # Populate the UI with data + self.populateUi() + + def reset_options(self): + self.populateUi() + + def populateUi(self): + """ + Update the UI for the first time the class is created. + """ + return diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui b/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui new file mode 100644 index 000000000..ebc591651 --- /dev/null +++ b/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui @@ -0,0 +1,533 @@ + + + Form + + + + 0 + 0 + 608 + 481 + + + + Form + + + + + + + + Update Every + + + + + + + seconds + + + 1 + + + 300 + + + 2 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + GPU Resources + + + + + + + + + + GPU Memory Total: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">2.0 GB</span></p></body></html> + + + + + + + + + + + GPU Memory Used: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">0.125 GB (6.3%)</span></p></body></html> + + + + + + + + + + + + + + CPU Resources + + + + + + + + + + CPU Memory Total: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">128.0 GB</span></p></body></html> + + + + + + + + + + + CPU Memory Used: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">22.5 GB (17.5%)</span></p></body></html> + + + + + + + + + + + + + + + + + + GPU Image Cache Overview + + + + + + + + Items: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> + + + + + + + + + + + Cache Capacity: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">2.0 GB</span></p></body></html> + + + + + + + + + + + Cache Used: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">0.125 GB (6.3%)</span></p></body></html> + + + + + + + + + + + + CPU Image Cache Overview + + + + + + + + Items: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> + + + + + + + + + + + Capacity: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">128.0 GB</span></p></body></html> + + + + + + + + + + + Used: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">22.5 GB (17.5%)</span></p></body></html> + + + + + + + + + + + + + + Image Cache Settings + + + + + + + + GPU Capacity: + + + + + + + <html><head/><body><p><span style=" font-weight:600;">1 GB</span></p></body></html> + + + + + + + Qt::Horizontal + + + + + + + % + + + 1 + + + 100.000000000000000 + + + 5.000000000000000 + + + 5.000000000000000 + + + + + + + + + + + CPU Capacity: + + + + + + + <html><head/><body><p><span style=" font-weight:600;">5 GB</span></p></body></html> + + + + + + + Qt::Horizontal + + + + + + + % + + + 1 + + + 100.000000000000000 + + + 5.000000000000000 + + + 5.000000000000000 + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_window.py b/python/mmSolver/tools/imagecache/ui/imagecache_window.py new file mode 100644 index 000000000..fd942aa9c --- /dev/null +++ b/python/mmSolver/tools/imagecache/ui/imagecache_window.py @@ -0,0 +1,116 @@ +# Copyright (C) 2024 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Window for the Image Cache tool. + +Usage:: + + import mmSolver.tools.imagecache.ui.imagecache_window as window + window.main() + +""" + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtCore as QtCore +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import mmSolver.logger +import mmSolver.ui.uiutils as uiutils +import mmSolver.ui.helputils as helputils +import mmSolver.ui.commonmenus as commonmenus +import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.imagecache.tool as tool +import mmSolver.tools.imagecache.ui.imagecache_layout as imagecache_layout + + +LOG = mmSolver.logger.get_logger() +baseModule, BaseWindow = uiutils.getBaseWindow() + + +def _open_help(): + src = helputils.get_help_source() + page = 'tools_generaltools.html#image-cache' + helputils.open_help_in_browser(page=page, help_source=src) + return + + +class ImageCacheWindow(BaseWindow): + name = 'ImageCacheWindow' + + def __init__(self, parent=None, name=None): + super(ImageCacheWindow, self).__init__(parent, name=name) + self.setupUi(self) + self.addSubForm(imagecache_layout.ImageCacheLayout) + + self.setWindowTitle(const.WINDOW_TITLE) + self.setWindowFlags(QtCore.Qt.Tool) + + # Standard Buttons + self.baseHideStandardButtons() + self.applyBtn.show() + self.closeBtn.show() + self.applyBtn.setText('Apply') + + # self.applyBtn.clicked.connect(tool.do_cache_things) + + # Hide irrelevant stuff + self.baseHideProgressBar() + + self.add_menus(self.menubar) + self.menubar.show() + + def add_menus(self, menubar): + edit_menu = QtWidgets.QMenu('Edit', menubar) + commonmenus.create_edit_menu_items( + edit_menu, reset_settings_func=self.reset_options + ) + menubar.addMenu(edit_menu) + + help_menu = QtWidgets.QMenu('Help', menubar) + commonmenus.create_help_menu_items(help_menu, tool_help_func=_open_help) + menubar.addMenu(help_menu) + + def reset_options(self): + form = self.getSubForm() + form.reset_options() + return + + +def main(show=True, auto_raise=True, delete=False): + """ + Open the Image Cache UI window. + + :param show: Show the UI. + :type show: bool + + :param auto_raise: If the UI is open, raise it to the front? + :type auto_raise: bool + + :param delete: Delete the existing UI and rebuild it? Helpful when + developing the UI in Maya script editor. + :type delete: bool + + :returns: A new solver window, or None if the window cannot be + opened. + :rtype: SolverWindow or None. + """ + win = ImageCacheWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) + return win diff --git a/share/config/functions.json b/share/config/functions.json index e803bfd7e..fbf6fdd4c 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -866,6 +866,15 @@ "mmSolver.tools.createimageplane.tool.main_version_two();" ] }, + "image_cache_ui": { + "name": "Image Cache UI...", + "name_shelf": "ImgCch", + "tooltip": "Open the Image Cache UI.", + "command": [ + "import mmSolver.tools.imagecache.tool;", + "mmSolver.tools.imagecache.tool.main();" + ] + }, "camera_toggle_distortion": { "name": "Toggle Camera Lens Distortion", "name_shelf": "TglDst", diff --git a/share/config/menu.json b/share/config/menu.json index 6cc40bcd0..163c68733 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -116,6 +116,7 @@ "general_tools/sort_nodes_in_outliner", "general_tools/remove_all_solver_nodes", "general_tools/---Settings & Preferences", + "general_tools/image_cache_ui", "general_tools/user_preferences_window", "file_io_tools/---Marker", "file_io_tools/load_marker_ui", diff --git a/share/config/shelf.json b/share/config/shelf.json index 1f39329af..d7d28b553 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -113,6 +113,7 @@ "general_tools/gen_tools_popup/sort_nodes_in_outliner", "general_tools/gen_tools_popup/remove_all_solver_nodes", "general_tools/gen_tools_popup/---Settings & Preferences", + "general_tools/gen_tools_popup/image_cache_ui", "general_tools/gen_tools_popup/user_preferences_window", "file_io_tools/file_io_popup/---Marker", "file_io_tools/file_io_popup/load_marker_ui", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 5c631c964..ea34b1b45 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -124,6 +124,7 @@ "general_tools/gen_tools_popup/sort_nodes_in_outliner", "general_tools/gen_tools_popup/remove_all_solver_nodes", "general_tools/gen_tools_popup/---Settings & Preferences", + "general_tools/gen_tools_popup/image_cache_ui", "general_tools/gen_tools_popup/user_preferences_window", "file_io_tools/file_io_popup/---Marker", "file_io_tools/file_io_popup/load_marker_ui", From 4f1bb18078a1553e79b944af7d282ba577ad25c1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:17:23 +1000 Subject: [PATCH 088/295] mmImagePlaneShape2 node - fix draw classification string. --- src/mmSolver/pluginMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index 4a4839d61..8a3539875 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -370,7 +370,7 @@ MStatus initializePlugin(MObject obj) { mmsolver::ImagePlaneShape2Node::m_id, mmsolver::ImagePlaneShape2Node::creator, mmsolver::ImagePlaneShape2Node::initialize, - MPxNode::kLocatorNode, &imagePlaneShapeClassification, + MPxNode::kLocatorNode, &imagePlaneShape2Classification, status); REGISTER_LOCATOR_NODE( plugin, mmsolver::SkyDomeShapeNode::nodeName(), From 0d275abaf110fbc93a6ee040028880cfcc50fee2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:20:06 +1000 Subject: [PATCH 089/295] Memory utilities - Fix Linux bugs found with GCC 11 size_t is defined by in C++. Windows seems a little lax on that requirement, GCC seems more strict. --- src/mmSolver/utilities/memory_system_utils.h | 3 +++ src/mmSolver/utilities/memory_utils.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/mmSolver/utilities/memory_system_utils.h b/src/mmSolver/utilities/memory_system_utils.h index c358887cc..294081f79 100644 --- a/src/mmSolver/utilities/memory_system_utils.h +++ b/src/mmSolver/utilities/memory_system_utils.h @@ -23,6 +23,9 @@ #ifndef MEMORY_SYSTEM_UTILS_H #define MEMORY_SYSTEM_UTILS_H +// STL +#include + namespace mmmemorysystem { void process_memory_usage(size_t &peak_resident_set_size, size_t ¤t_resident_set_size); diff --git a/src/mmSolver/utilities/memory_utils.h b/src/mmSolver/utilities/memory_utils.h index 32d802434..061c8ba78 100644 --- a/src/mmSolver/utilities/memory_utils.h +++ b/src/mmSolver/utilities/memory_utils.h @@ -25,6 +25,7 @@ // STL #include +#include namespace mmmemory { From a76dd3e71ed78b80c2c899a43ccee966c5856d27 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:20:54 +1000 Subject: [PATCH 090/295] Memory System utilities - Fix bug on Linux. Fixes bug with Linux implementation of 'system_physical_memory_free()' function. --- src/mmSolver/utilities/memory_system_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/utilities/memory_system_utils.cpp b/src/mmSolver/utilities/memory_system_utils.cpp index 522e34c61..7d9a10753 100644 --- a/src/mmSolver/utilities/memory_system_utils.cpp +++ b/src/mmSolver/utilities/memory_system_utils.cpp @@ -161,7 +161,7 @@ size_t system_physical_memory_free() { size_t physical_memory_free_bytes = memInfo.freeram; // Multiply to avoid int overflow on right hand side. - physical_memory_used_bytes *= memInfo.mem_unit; + physical_memory_free_bytes *= memInfo.mem_unit; return physical_memory_free_bytes; #endif From a4afe44599ff2ba2f993215aa5b2b33c18fc5e6b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:22:27 +1000 Subject: [PATCH 091/295] mmColorIO command - fix missing include for GCC 11 std::strlen is defined in . Windows MSVC seems to be a leanient - GCC 11 is not. --- src/mmSolver/cmd/MMColorIOCmd.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mmSolver/cmd/MMColorIOCmd.cpp b/src/mmSolver/cmd/MMColorIOCmd.cpp index 15f4393df..a13a3250e 100644 --- a/src/mmSolver/cmd/MMColorIOCmd.cpp +++ b/src/mmSolver/cmd/MMColorIOCmd.cpp @@ -55,6 +55,7 @@ // STD #include +#include #include #include From cf5c0dd4c2f575a2b8c337e802fabaf9da302e8e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:23:41 +1000 Subject: [PATCH 092/295] MSharedPtr can only be used in Maya 2022+ This fixes builds on Maya 2020 and below. --- .../shape/ImagePlaneGeometry2Override.cpp | 5 ++--- .../shape/ImagePlaneGeometry2Override.h | 21 ++++++++++++++++--- src/mmSolver/shape/ImagePlaneUtils.cpp | 1 - 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index d16e167fe..88aaf4029 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -47,7 +47,6 @@ #include #include #include -#include #include // MM Solver @@ -99,10 +98,10 @@ ImagePlaneGeometry2Override::ImagePlaneGeometry2Override(const MObject &obj) m_model_editor_changed_callback_id = MEventMessage::addEventCallback( "modelEditorChanged", ImagePlaneGeometry2Override::on_model_editor_changed_func, this); -#if MAYA_API_VERSION >= 20200000 +#if MAYA_API_VERSION >= 20220000 m_shader_link_lost_user_data_ptr = ShaderLinkLostUserData2Ptr(new ShaderLinkLostUserData2()); -#elif +#else m_shader_link_lost_user_data = ShaderLinkLostUserData2(); #endif } diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index e569c56d4..c44922286 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -38,6 +38,10 @@ #include #include +#if MAYA_API_VERSION >= 20220000 +#include +#endif + // MM Solver #include @@ -51,7 +55,16 @@ namespace mmsolver { class ShaderLinkLostUserData2 : public MUserData { public: ShaderLinkLostUserData2() - : MUserData(), link_lost_count(0), set_shader_count(0) {} +#if MAYA_API_VERSION >= 20220000 + : MUserData() +#else + // MUserData(bool) constructor is deprecated in Maya 2022+ + // because 'deleteAfterUse' is no longer needed. + : MUserData(true) +#endif + , link_lost_count(0) + , set_shader_count(0) { + } // Keep track of the number of times stuff happens, just for // interest sake (maybe to help debugging?) - doesn't really mean @@ -60,7 +73,9 @@ class ShaderLinkLostUserData2 : public MUserData { uint32_t set_shader_count; }; +#if MAYA_API_VERSION >= 20220000 using ShaderLinkLostUserData2Ptr = MSharedPtr; +#endif class ImagePlaneGeometry2Override : public MPxGeometryOverride { public: @@ -178,9 +193,9 @@ class ImagePlaneGeometry2Override : public MPxGeometryOverride { MHWRender::MTexture *m_color_texture; const MHWRender::MSamplerState *m_texture_sampler; -#if MAYA_API_VERSION >= 20200000 +#if MAYA_API_VERSION >= 20220000 ShaderLinkLostUserData2Ptr m_shader_link_lost_user_data_ptr; -#elif +#else ShaderLinkLostUserData2 m_shader_link_lost_user_data; #endif }; diff --git a/src/mmSolver/shape/ImagePlaneUtils.cpp b/src/mmSolver/shape/ImagePlaneUtils.cpp index 927e95db1..826cec596 100644 --- a/src/mmSolver/shape/ImagePlaneUtils.cpp +++ b/src/mmSolver/shape/ImagePlaneUtils.cpp @@ -47,7 +47,6 @@ #include #include #include -#include #include // MM Solver From a0e1500f285b66ba28f0ef6e6f1a205c3ef66ae7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:37:21 +1000 Subject: [PATCH 093/295] mmImagePlane2 node - match image plane v1 load parity. It's important we register the geometry overrides, and deregister them, as well as selection filters. GitHub issue #252. --- include/mmSolver/nodeTypeIds.h | 2 +- src/mmSolver/pluginMain.cpp | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index c58a7fbcf..4ba8f9b8a 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -140,7 +140,7 @@ #define MM_IMAGE_PLANE_SHAPE_2_TYPE_ID 0x0012F18F #define MM_IMAGE_PLANE_SHAPE_2_TYPE_NAME "mmImagePlaneShape2" -#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane" +#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane2" #define MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID "mmImagePlaneShape2" #define MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME "mmImagePlaneShape2Selection" // Same as v1. diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index 8a3539875..64bf64bd5 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -370,8 +370,8 @@ MStatus initializePlugin(MObject obj) { mmsolver::ImagePlaneShape2Node::m_id, mmsolver::ImagePlaneShape2Node::creator, mmsolver::ImagePlaneShape2Node::initialize, - MPxNode::kLocatorNode, &imagePlaneShape2Classification, - status); + MPxNode::kLocatorNode, + &imagePlaneShape2Classification, status); REGISTER_LOCATOR_NODE( plugin, mmsolver::SkyDomeShapeNode::nodeName(), mmsolver::SkyDomeShapeNode::m_id, mmsolver::SkyDomeShapeNode::creator, @@ -530,6 +530,14 @@ MStatus initializePlugin(MObject obj) { status = MGlobal::executeCommand(mel_cmd); CHECK_MSTATUS(status); + MSelectionMask::registerSelectionType( + mmsolver::ImagePlaneShape2Node::m_selection_type_name, 2); + mel_cmd = "selectType -byName \""; + mel_cmd += mmsolver::ImagePlaneShape2Node::m_selection_type_name; + mel_cmd += "\" 1"; + status = MGlobal::executeCommand(mel_cmd); + CHECK_MSTATUS(status); + MSelectionMask::registerSelectionType( mmsolver::SkyDomeShapeNode::m_selection_type_name, 2); mel_cmd = "selectType -byName \""; @@ -561,6 +569,10 @@ MStatus initializePlugin(MObject obj) { mmsolver::ImagePlaneShapeNode::m_display_filter_name, mmsolver::ImagePlaneShapeNode::m_display_filter_label, mmsolver::ImagePlaneShapeNode::m_draw_db_classification); + plugin.registerDisplayFilter( + mmsolver::ImagePlaneShape2Node::m_display_filter_name, + mmsolver::ImagePlaneShape2Node::m_display_filter_label, + mmsolver::ImagePlaneShape2Node::m_draw_db_classification); plugin.registerDisplayFilter( mmsolver::SkyDomeShapeNode::m_display_filter_name, mmsolver::SkyDomeShapeNode::m_display_filter_label, @@ -661,6 +673,9 @@ MStatus uninitializePlugin(MObject obj) { DEREGISTER_GEOMETRY_OVERRIDE( mmsolver::ImagePlaneShapeNode::m_draw_db_classification, mmsolver::ImagePlaneShapeNode::m_draw_registrant_id, status); + DEREGISTER_GEOMETRY_OVERRIDE( + mmsolver::ImagePlaneShape2Node::m_draw_db_classification, + mmsolver::ImagePlaneShape2Node::m_draw_registrant_id, status); DEREGISTER_DRAW_OVERRIDE( mmsolver::SkyDomeShapeNode::m_draw_db_classification, mmsolver::SkyDomeShapeNode::m_draw_registrant_id, status); @@ -674,6 +689,8 @@ MStatus uninitializePlugin(MObject obj) { mmsolver::BundleShapeNode::m_id, status); DEREGISTER_LOCATOR_NODE(plugin, mmsolver::ImagePlaneShapeNode::nodeName(), mmsolver::ImagePlaneShapeNode::m_id, status); + DEREGISTER_LOCATOR_NODE(plugin, mmsolver::ImagePlaneShape2Node::nodeName(), + mmsolver::ImagePlaneShape2Node::m_id, status); DEREGISTER_LOCATOR_NODE(plugin, mmsolver::SkyDomeShapeNode::nodeName(), mmsolver::SkyDomeShapeNode::m_id, status); DEREGISTER_LOCATOR_NODE(plugin, mmsolver::LineShapeNode::nodeName(), From e81a2476fabc6d0cf6dde36c8515fd98532f0488 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 11:38:52 +1000 Subject: [PATCH 094/295] CMake 3.23 and below compatibility. The build would fail with CMake 3.17 (for example), because CMake Policy CMP0135 did not exist in CMake 3.23 and below. --- CMakeLists.txt | 12 +++++++----- lib/CMakeLists.txt | 12 +++++++----- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c2e401f8..ed7862b00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,11 +38,13 @@ cmake_policy(SET CMP0048 NEW) # https://cmake.org/cmake/help/latest/policy/CMP0063.html cmake_policy(SET CMP0063 NEW) -# Changes how timestamps of downloaded files with -# ExternalProject_Add() are set. -# -# https://cmake.org/cmake/help/latest/policy/CMP0135.html -cmake_policy(SET CMP0135 NEW) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + # Changes how timestamps of downloaded files with + # ExternalProject_Add() are set. + # + # https://cmake.org/cmake/help/latest/policy/CMP0135.html + cmake_policy(SET CMP0135 NEW) +endif() # Do not allow using GNU extensions (such as '-std=g++11'), because # it's not compatible with Maya. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 589ff0179..0e48db825 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -32,11 +32,13 @@ cmake_policy(SET CMP0048 NEW) # # https://cmake.org/cmake/help/latest/policy/CMP0063.html # cmake_policy(SET CMP0063 NEW) -# Changes how timestamps of downloaded files with -# ExternalProject_Add() are set. -# -# https://cmake.org/cmake/help/latest/policy/CMP0135.html -cmake_policy(SET CMP0135 NEW) +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0") + # Changes how timestamps of downloaded files with + # ExternalProject_Add() are set. + # + # https://cmake.org/cmake/help/latest/policy/CMP0135.html + cmake_policy(SET CMP0135 NEW) +endif() # Do not allow using GNU extensions (such as '-std=g++11'), because # it's not compatible with Maya. From c8e2e9dfcfcc1fd1fba92a52cd675fd6f77a3bac Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 14:47:25 +1000 Subject: [PATCH 095/295] Move join_path into .cpp file So that we can avoid GCC warnings about an included but not used function. --- lib/cppbind/mmlens/tests/CMakeLists.txt | 1 + lib/cppbind/mmlens/tests/common.cpp | 31 +++++++++++++++++++++++++ lib/cppbind/mmlens/tests/common.h | 7 +----- 3 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 lib/cppbind/mmlens/tests/common.cpp diff --git a/lib/cppbind/mmlens/tests/CMakeLists.txt b/lib/cppbind/mmlens/tests/CMakeLists.txt index 661ac3f26..f350feafc 100644 --- a/lib/cppbind/mmlens/tests/CMakeLists.txt +++ b/lib/cppbind/mmlens/tests/CMakeLists.txt @@ -21,6 +21,7 @@ set(target_test_exe_name "mmlens_tests") set(test_source_files + ${CMAKE_CURRENT_SOURCE_DIR}/common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_batch_3de_anamorphic_std_deg4.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_batch_3de_anamorphic_std_deg4_rescaled.cpp diff --git a/lib/cppbind/mmlens/tests/common.cpp b/lib/cppbind/mmlens/tests/common.cpp new file mode 100644 index 000000000..193a9fd97 --- /dev/null +++ b/lib/cppbind/mmlens/tests/common.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023, 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include +#include +#include + +std::string join_path(const char* arg1, const char* arg2) { + std::stringstream stream; + stream << arg1; + stream << arg2; + return stream.str(); +} diff --git a/lib/cppbind/mmlens/tests/common.h b/lib/cppbind/mmlens/tests/common.h index 12b4622e4..cf9d93baa 100644 --- a/lib/cppbind/mmlens/tests/common.h +++ b/lib/cppbind/mmlens/tests/common.h @@ -26,12 +26,7 @@ #include #include -static std::string join_path(const char* arg1, const char* arg2) { - std::stringstream stream; - stream << arg1; - stream << arg2; - return stream.str(); -} +std::string join_path(const char* arg1, const char* arg2); const int kCoordinateSystemImage = 0; const int kCoordinateSystemNDC = 1; From 1528c634ae4ed6e2c51b34670b6beb7ca6afadeb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 16 Jun 2024 14:49:40 +1000 Subject: [PATCH 096/295] Comment out lens parameter use The lens parameters are not being used, so GCC was complaining - this is now fixed. --- lib/cppbind/mmlens/tests/test_lens_file_load.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/cppbind/mmlens/tests/test_lens_file_load.cpp b/lib/cppbind/mmlens/tests/test_lens_file_load.cpp index 19a0f626b..2567994e0 100644 --- a/lib/cppbind/mmlens/tests/test_lens_file_load.cpp +++ b/lib/cppbind/mmlens/tests/test_lens_file_load.cpp @@ -71,8 +71,8 @@ int test_lens_file_load(const char* dir_path, const char* file_name) { if (!option_lens_parameters.exists) { continue; } - const mmlens::Parameters3deClassic lens_parameters = - option_lens_parameters.value; + // const mmlens::Parameters3deClassic lens_parameters = + // option_lens_parameters.value; // TODO: Do something with the lens parameters. } else if (lens_model_type == @@ -83,8 +83,8 @@ int test_lens_file_load(const char* dir_path, const char* file_name) { if (!option_lens_parameters.exists) { continue; } - const mmlens::Parameters3deRadialStdDeg4 lens_parameters = - option_lens_parameters.value; + // const mmlens::Parameters3deRadialStdDeg4 lens_parameters = + // option_lens_parameters.value; // TODO: Do something with the lens parameters. } else if (lens_model_type == @@ -97,8 +97,8 @@ int test_lens_file_load(const char* dir_path, const char* file_name) { if (!option_lens_parameters.exists) { continue; } - const mmlens::Parameters3deAnamorphicStdDeg4 lens_parameters = - option_lens_parameters.value; + // const mmlens::Parameters3deAnamorphicStdDeg4 lens_parameters = + // option_lens_parameters.value; // TODO: Do something with the lens parameters. } else if (lens_model_type == @@ -111,8 +111,8 @@ int test_lens_file_load(const char* dir_path, const char* file_name) { if (!option_lens_parameters.exists) { continue; } - const mmlens::Parameters3deAnamorphicStdDeg4Rescaled - lens_parameters = option_lens_parameters.value; + // const mmlens::Parameters3deAnamorphicStdDeg4Rescaled + // lens_parameters = option_lens_parameters.value; // TODO: Do something with the lens parameters. } else { From 1c7ef1db0c54e2a1c46542a50a9c7d320e7a7712 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 17 Jun 2024 00:21:26 +1000 Subject: [PATCH 097/295] Add OpenColorIO to mmSolver for Maya 2023 and below. This commit uses only OpenColorIO v2.2+, because that version is using the built-in configs that are only in OpenColorIO 2.2 and above. GitHub issue #252 --- CMakeLists.txt | 17 -- lib/mmsolverlibs/src/CMakeLists.txt | 66 +----- scripts/build_mmSolver_linux_maya2018.bash | 16 ++ scripts/build_mmSolver_linux_maya2019.bash | 16 ++ scripts/build_mmSolver_linux_maya2020.bash | 16 ++ scripts/build_mmSolver_linux_maya2022.bash | 16 ++ scripts/build_mmSolver_linux_maya2023.bash | 16 ++ scripts/build_mmSolver_linux_maya2024.bash | 12 +- scripts/build_mmSolver_windows64_maya2016.bat | 2 + ...mmSolver_windows64_maya2016_extension2.bat | 2 + scripts/build_mmSolver_windows64_maya2017.bat | 2 + scripts/build_mmSolver_windows64_maya2018.bat | 26 +++ scripts/build_mmSolver_windows64_maya2019.bat | 26 +++ scripts/build_mmSolver_windows64_maya2020.bat | 29 +++ scripts/build_mmSolver_windows64_maya2022.bat | 29 +++ scripts/build_mmSolver_windows64_maya2023.bat | 29 +++ scripts/build_mmSolver_windows64_maya2024.bat | 19 +- .../internal/build_mmSolverLibs_linux.bash | 51 ++++- .../internal/build_mmSolverLibs_windows64.bat | 54 ++++- scripts/internal/build_mmSolver_linux.bash | 51 ++++- scripts/internal/build_mmSolver_windows64.bat | 45 +++- scripts/internal/build_openColorIO_linux.bash | 15 +- .../internal/build_openColorIO_windows64.bat | 15 +- share/cmake/modules/MMColorIOUtils.cmake | 205 ++++++++++++++++++ share/cmake/modules/MMSolverUtils.cmake | 68 +++++- src/CMakeLists.txt | 66 ++++-- tools/lensdistortion/src/CMakeLists.txt | 10 +- 27 files changed, 760 insertions(+), 159 deletions(-) create mode 100644 share/cmake/modules/MMColorIOUtils.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index ed7862b00..7ef939e89 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,23 +50,6 @@ endif() # it's not compatible with Maya. set(CXX_EXTENSIONS OFF) -# Use the older C++11 ABI for std::string and std::list, to be -# compatible with RHEL/CentOS 7, Maya and the VFX Platform. -# -# https://vfxplatform.com/#footnote-gcc6 -# -# TODO: In VFX Platform CY2023, and the move to RHEL 8 or RHEL 9, -# the new default is to use "_GLIBCXX_USE_CXX11_ABI=1". -# -# https://vfxplatform.com/#footnote-gcc9 -# -# TODO: Add a flag for use in RHEL 8/9 and Rocky Linux, so we can -# enable/disable. -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") -if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_USE_CXX11_ABI=0") -endif() - # For Windows, allow paths of more than 250 to avoid build issues # arrising on Windows with very long file paths. if(NOT DEFINED CMAKE_OBJECT_PATH_MAX) diff --git a/lib/mmsolverlibs/src/CMakeLists.txt b/lib/mmsolverlibs/src/CMakeLists.txt index 1e072afa5..1771ee65a 100644 --- a/lib/mmsolverlibs/src/CMakeLists.txt +++ b/lib/mmsolverlibs/src/CMakeLists.txt @@ -102,67 +102,11 @@ target_link_libraries(${cpp_lib_name} # ldpk to be installed so we have the 'ldpk_INCLUDE_DIR' variable. add_dependencies(${cpp_lib_name} ldpk::ldpk) - -# OpenColorIO -message(STATUS "OpenColorIO_DIR: ${OpenColorIO_DIR}") -find_package(OpenColorIO REQUIRED) - -get_target_property(OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES OpenColorIO::OpenColorIO INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(OpenColorIO_IMPORTED_LOCATION_RELEASE OpenColorIO::OpenColorIO IMPORTED_LOCATION_RELEASE) -get_target_property(OpenColorIO_INTERFACE_LINK_LIBRARIES OpenColorIO::OpenColorIO INTERFACE_LINK_LIBRARIES) - -message(STATUS "OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES: ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES}") -message(STATUS "OpenColorIO_IMPORTED_LOCATION_RELEASE: ${OpenColorIO_IMPORTED_LOCATION_RELEASE}") -message(STATUS "OpenColorIO_INTERFACE_LINK_LIBRARIES: ${OpenColorIO_INTERFACE_LINK_LIBRARIES}") - -target_include_directories(${cpp_lib_name} - PRIVATE ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES} -) - -find_package(ZLIB REQUIRED) -message(STATUS "ZLIB: Found: ${ZLIB_FOUND}") -message(STATUS "ZLIB: Version: ${ZLIB_VERSION}") -message(STATUS "ZLIB: Libraries: ${ZLIB_LIBRARIES}") -message(STATUS "ZLIB: Include Dirs: ${ZLIB_INCLUDE_DIRS}") - -find_package(pystring REQUIRED) -message(STATUS "pystring: Found: ${pystring_FOUND}") -message(STATUS "pystring: Libraries: ${pystring_LIBRARY}") -message(STATUS "pystring: Include Dir: ${pystring_INCLUDE_DIR}") - -find_package(Imath REQUIRED) -message(STATUS "Imath: Found: ${Imath_FOUND}") -message(STATUS "Imath: Version: ${Imath_VERSION}") -message(STATUS "Imath: Libraries: ${Imath_LIBRARY}") -message(STATUS "Imath: Include Dir: ${Imath_INCLUDE_DIR}") - -find_package(expat REQUIRED) -message(STATUS "expat: Found: ${expat_FOUND}") -message(STATUS "expat: Version: ${expat_VERSION}") -message(STATUS "expat: Libraries: ${expat_LIBRARY}") -message(STATUS "expat: Include Dir: ${expat_INCLUDE_DIR}") - -find_package(minizip-ng REQUIRED) -message(STATUS "minizip-ng: Found: ${minizip-ng_FOUND}") -message(STATUS "minizip-ng: Version: ${minizip-ng_VERSION}") -message(STATUS "minizip-ng: Libraries: ${minizip-ng_LIBRARY}") -message(STATUS "minizip-ng: Include Dir: ${minizip-ng_INCLUDE_DIR}") - -find_package(yaml-cpp REQUIRED) -message(STATUS "yaml-cpp: Found: ${yaml-cpp_FOUND}") -message(STATUS "yaml-cpp: Version: ${yaml-cpp_VERSION}") -message(STATUS "yaml-cpp: Libraries: ${yaml-cpp_LIBRARY}") -message(STATUS "yaml-cpp: Include Dir: ${yaml-cpp_INCLUDE_DIR}") - -target_link_libraries(${cpp_lib_name} - PRIVATE OpenColorIO::OpenColorIO - PRIVATE ZLIB::ZLIB - PRIVATE expat::expat - PRIVATE Imath::Imath - PRIVATE pystring::pystring - PRIVATE yaml-cpp - PRIVATE MINIZIP::minizip-ng - ) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(${cpp_lib_name}) +mmcolorio_target_include_packages(${cpp_lib_name}) target_include_directories(${cpp_lib_name} PUBLIC $ diff --git a/scripts/build_mmSolver_linux_maya2018.bash b/scripts/build_mmSolver_linux_maya2018.bash index 416fccbaa..f1d82db7f 100644 --- a/scripts/build_mmSolver_linux_maya2018.bash +++ b/scripts/build_mmSolver_linux_maya2018.bash @@ -29,6 +29,21 @@ PYTHON_EXE=python CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2017 + # C++ Standard to use. CXX_STANDARD=11 @@ -40,6 +55,7 @@ CWD=`pwd` # These scripts assume 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_linux_maya2019.bash b/scripts/build_mmSolver_linux_maya2019.bash index 0794dea84..db5b1dd39 100644 --- a/scripts/build_mmSolver_linux_maya2019.bash +++ b/scripts/build_mmSolver_linux_maya2019.bash @@ -29,6 +29,21 @@ PYTHON_EXE=python CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2018 + # C++ Standard to use. CXX_STANDARD=11 @@ -40,6 +55,7 @@ CWD=`pwd` # These scripts assume 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_linux_maya2020.bash b/scripts/build_mmSolver_linux_maya2020.bash index ed7f6f950..21d89c53d 100644 --- a/scripts/build_mmSolver_linux_maya2020.bash +++ b/scripts/build_mmSolver_linux_maya2020.bash @@ -29,6 +29,21 @@ PYTHON_EXE=python CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2019 + # C++ Standard to use. CXX_STANDARD=11 @@ -40,6 +55,7 @@ CWD=`pwd` # These scripts assume 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_linux_maya2022.bash b/scripts/build_mmSolver_linux_maya2022.bash index 25ae09807..62fdf672b 100644 --- a/scripts/build_mmSolver_linux_maya2022.bash +++ b/scripts/build_mmSolver_linux_maya2022.bash @@ -29,6 +29,21 @@ PYTHON_EXE=python CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2021 + # C++ Standard to use. CXX_STANDARD=14 @@ -40,6 +55,7 @@ CWD=`pwd` # This script assumes 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_opencolorio_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_linux_maya2023.bash b/scripts/build_mmSolver_linux_maya2023.bash index 791888f15..f4b56cc03 100644 --- a/scripts/build_mmSolver_linux_maya2023.bash +++ b/scripts/build_mmSolver_linux_maya2023.bash @@ -29,6 +29,21 @@ PYTHON_EXE=python CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2022 + # C++ Standard to use. CXX_STANDARD=14 @@ -40,6 +55,7 @@ CWD=`pwd` # These scripts assume 'RUST_CARGO_EXE' has been set to the Rust # 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" source "${CWD}/scripts/internal/build_mmSolver_linux.bash" diff --git a/scripts/build_mmSolver_linux_maya2024.bash b/scripts/build_mmSolver_linux_maya2024.bash index d48715c08..814946881 100644 --- a/scripts/build_mmSolver_linux_maya2024.bash +++ b/scripts/build_mmSolver_linux_maya2024.bash @@ -32,13 +32,23 @@ RUST_CARGO_EXE=cargo # OpenColorIO specific options. OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.2.1.tar.gz" OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.2.1" -EXPAT_VERSION=2.4.1 +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a # Manually override OpenGL include headers, because CMake doesn't seem # to automatically find OpenGL headers on RockyLinux8 (which is used # in the Docker containers). OPENGL_INCLUDE_DIR=/usr/include/ +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2023 + # C++ Standard to use. CXX_STANDARD=14 diff --git a/scripts/build_mmSolver_windows64_maya2016.bat b/scripts/build_mmSolver_windows64_maya2016.bat index d5c4caf5b..d5662bb97 100644 --- a/scripts/build_mmSolver_windows64_maya2016.bat +++ b/scripts/build_mmSolver_windows64_maya2016.bat @@ -45,6 +45,8 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 :failed_to_build_mmsolverlibs diff --git a/scripts/build_mmSolver_windows64_maya2016_extension2.bat b/scripts/build_mmSolver_windows64_maya2016_extension2.bat index 22027ed76..b7165b949 100644 --- a/scripts/build_mmSolver_windows64_maya2016_extension2.bat +++ b/scripts/build_mmSolver_windows64_maya2016_extension2.bat @@ -45,6 +45,8 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 :failed_to_build_mmsolverlibs diff --git a/scripts/build_mmSolver_windows64_maya2017.bat b/scripts/build_mmSolver_windows64_maya2017.bat index a53325b38..051074361 100644 --- a/scripts/build_mmSolver_windows64_maya2017.bat +++ b/scripts/build_mmSolver_windows64_maya2017.bat @@ -45,6 +45,8 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 :failed_to_build_mmsolverlibs diff --git a/scripts/build_mmSolver_windows64_maya2018.bat b/scripts/build_mmSolver_windows64_maya2018.bat index a840fa9ca..ea224f0f2 100644 --- a/scripts/build_mmSolver_windows64_maya2018.bat +++ b/scripts/build_mmSolver_windows64_maya2018.bat @@ -33,12 +33,32 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +:: +:: Maya doesn't ship with OpenColorIO at all, so lets pick the v2.0.x. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.0.5 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.0.5.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.0.5.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 +SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ +:: yaml-cpp 0.6.3 +SET YAML_RELATIVE_CMAKE_DIR=CMake\ +SET HALF_RELATIVE_LIB_PATH=lib\Half-2_4.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2017 + :: C++ Standard to use. SET CXX_STANDARD=11 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -46,8 +66,14 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/build_mmSolver_windows64_maya2019.bat b/scripts/build_mmSolver_windows64_maya2019.bat index 81c55e387..35d9d02ee 100644 --- a/scripts/build_mmSolver_windows64_maya2019.bat +++ b/scripts/build_mmSolver_windows64_maya2019.bat @@ -33,12 +33,32 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +:: +:: Maya doesn't ship with OpenColorIO at all, so lets pick the v2.0.x. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.0.5 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.0.5.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.0.5.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 +SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ +:: yaml-cpp 0.6.3 +SET YAML_RELATIVE_CMAKE_DIR=CMake\ +SET HALF_RELATIVE_LIB_PATH=lib\Half-2_4.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2018 + :: C++ Standard to use. SET CXX_STANDARD=11 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -46,8 +66,14 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/build_mmSolver_windows64_maya2020.bat b/scripts/build_mmSolver_windows64_maya2020.bat index 426656711..f8cbac206 100644 --- a/scripts/build_mmSolver_windows64_maya2020.bat +++ b/scripts/build_mmSolver_windows64_maya2020.bat @@ -33,12 +33,35 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 +SET EXPAT_RELATIVE_CMAKE_DIR=lib\cmake\expat-2.4.1\ +SET EXPAT_RELATIVE_LIB_PATH=lib\expatMD.lib +SET ZLIB_RELATIVE_LIB_PATH=lib\zlibstatic.lib +SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng +:: yaml-cpp 0.7.0 +SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ +SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib +SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib +SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2019 + :: C++ Standard to use. SET CXX_STANDARD=11 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -46,8 +69,14 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/build_mmSolver_windows64_maya2022.bat b/scripts/build_mmSolver_windows64_maya2022.bat index 5cd4a1a99..7e9deac0a 100644 --- a/scripts/build_mmSolver_windows64_maya2022.bat +++ b/scripts/build_mmSolver_windows64_maya2022.bat @@ -33,12 +33,35 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 +SET EXPAT_RELATIVE_CMAKE_DIR=lib\cmake\expat-2.4.1\ +SET EXPAT_RELATIVE_LIB_PATH=lib\expatMD.lib +SET ZLIB_RELATIVE_LIB_PATH=lib\zlibstatic.lib +SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng +:: yaml-cpp 0.7.0 +SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ +SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib +SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib +SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2021 + :: C++ Standard to use. SET CXX_STANDARD=14 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -46,8 +69,14 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/build_mmSolver_windows64_maya2023.bat b/scripts/build_mmSolver_windows64_maya2023.bat index ddcabf684..9786a7bae 100644 --- a/scripts/build_mmSolver_windows64_maya2023.bat +++ b/scripts/build_mmSolver_windows64_maya2023.bat @@ -33,12 +33,35 @@ SET PYTHON_EXE=python SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo +:: OpenColorIO specific options. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 +SET EXPAT_RELATIVE_CMAKE_DIR=lib\cmake\expat-2.4.1\ +SET EXPAT_RELATIVE_LIB_PATH=lib\expatMD.lib +SET ZLIB_RELATIVE_LIB_PATH=lib\zlibstatic.lib +SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng +:: yaml-cpp 0.7.0 +SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ +SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib +SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib +SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2022 + :: C++ Standard to use. SET CXX_STANDARD=14 :: Setup Compiler environment. Change for your install path as needed. CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL scripts\internal\build_opencolorio_windows64.bat +if errorlevel 1 goto failed_to_build_opencolorio + :: This script assumes 'RUST_CARGO_EXE' has been set to the Rust :: 'cargo' executable. CALL scripts\internal\build_mmSolverLibs_windows64.bat @@ -46,8 +69,14 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 +:failed_to_build_opencolorio +echo Failed to build OpenColorIO dependency. +exit /b 1 + :failed_to_build_mmsolverlibs echo Failed to build MM Solver Library entry point. exit /b 1 diff --git a/scripts/build_mmSolver_windows64_maya2024.bat b/scripts/build_mmSolver_windows64_maya2024.bat index c80b3a6dc..745f22d2c 100644 --- a/scripts/build_mmSolver_windows64_maya2024.bat +++ b/scripts/build_mmSolver_windows64_maya2024.bat @@ -34,9 +34,24 @@ SET CMAKE_EXE=cmake SET RUST_CARGO_EXE=cargo :: OpenColorIO specific options. +:: +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 -SET EXPAT_VERSION=2.4.1 +SET EXPAT_RELATIVE_CMAKE_DIR=lib\cmake\expat-2.4.1\ +SET EXPAT_RELATIVE_LIB_PATH=lib\expatMD.lib +SET ZLIB_RELATIVE_LIB_PATH=lib\zlibstatic.lib +SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng +:: yaml-cpp 0.7.0 +SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ +SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib +SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib +SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib + +:: Which version of the VFX platform are we "using"? (Maya doesn't +:: currently conform to the VFX Platform.) +SET VFX_PLATFORM=2023 :: C++ Standard to use. SET CXX_STANDARD=14 @@ -54,6 +69,8 @@ if errorlevel 1 goto failed_to_build_mmsolverlibs CALL scripts\internal\build_mmSolver_windows64.bat if errorlevel 1 goto failed_to_build_mmsolver + +:: Successful return. exit /b 0 :failed_to_build_opencolorio diff --git a/scripts/internal/build_mmSolverLibs_linux.bash b/scripts/internal/build_mmSolverLibs_linux.bash index b5a7608bd..f398b55a8 100644 --- a/scripts/internal/build_mmSolverLibs_linux.bash +++ b/scripts/internal/build_mmSolverLibs_linux.bash @@ -52,6 +52,10 @@ if [ ${BUILD_TYPE}=="Release" ]; then BUILD_TYPE_DIR="release" fi +# Allows you to see the build command lines, to help debugging build +# problems. Set to ON to enable, and OFF to disable. +MMSOLVER_BUILD_VERBOSE=OFF + # Where to find the mmsolverlibs Rust libraries and headers. MMSOLVERLIBS_INSTALL_PATH="${BUILD_DIR_BASE}/build_mmsolverlibs/install/maya${MAYA_VERSION}_linux/" MMSOLVERLIBS_ROOT="${PROJECT_ROOT}/lib" @@ -64,14 +68,28 @@ MMSOLVERLIBS_INCLUDE_DIR="${MMSOLVERLIBS_ROOT}/include" EXTERNAL_BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/cmake_linux_maya${MAYA_VERSION}_${BUILD_TYPE}/ext/dist" OPENCOLORIO_INSTALL_DIR="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA_VERSION}_linux/" OPENCOLORIO_CMAKE_CONFIG_DIR="${OPENCOLORIO_INSTALL_DIR}/lib64/cmake/OpenColorIO/" -ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/lib/libz.a" -ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" -expat_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/expat-${EXPAT_VERSION}" +OPENCOLORIO_CMAKE_FIND_MODULES_DIR="${PROJECT_ROOT}/external/working/maya${MAYA_VERSION}_linux/${OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME}/share/cmake/modules" + +expat_DIR="${EXTERNAL_BUILD_DIR}/${EXPAT_RELATIVE_CMAKE_DIR}" +expat_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +expat_LIBRARY="${EXTERNAL_BUILD_DIR}/${EXPAT_RELATIVE_LIB_PATH}" + +pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/${PYSTRING_RELATIVE_LIB_PATH}" +pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" + +yaml_DIR="${EXTERNAL_BUILD_DIR}/${YAML_RELATIVE_CMAKE_DIR}" +yaml_LIBRARY="${EXTERNAL_BUILD_DIR}/${YAML_RELATIVE_LIB_PATH}" +yaml_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" + Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" + +Half_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +Half_LIBRARY="${EXTERNAL_BUILD_DIR}/${HALF_RELATIVE_LIB_PATH}" + +ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/${ZLIB_RELATIVE_LIB_PATH}" +ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" + minizip_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/minizip-ng" -yaml_DIR="${EXTERNAL_BUILD_DIR}/share/cmake/yaml-cpp" -pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/lib64/libpystring.a" -pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" MMSOLVERLIBS_BUILD_TESTS=1 @@ -119,21 +137,32 @@ ${CMAKE_EXE} \ -DCMAKE_INSTALL_PREFIX=${MMSOLVERLIBS_INSTALL_PATH} \ -DCMAKE_POSITION_INDEPENDENT_CODE=1 \ -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ + -DCMAKE_MODULE_PATH=${OPENCOLORIO_CMAKE_FIND_MODULES_DIR} \ + -DCMAKE_VERBOSE_MAKEFILE=${MMSOLVER_BUILD_VERBOSE} \ + -DMMSOLVER_VFX_PLATFORM=${VFX_PLATFORM} \ -DMMSOLVERLIBS_CXXBRIDGE_EXE=${MMSOLVERLIBS_CXXBRIDGE_EXE} \ -DMMSOLVERLIBS_BUILD_TESTS=${MMSOLVERLIBS_BUILD_TESTS} \ -DMMSOLVERLIBS_LIB_DIR=${MMSOLVERLIBS_LIB_DIR} \ -Dldpk_URL=${LDPK_URL} \ -DOpenColorIO_DIR=${OPENCOLORIO_CMAKE_CONFIG_DIR} \ -DOCIO_INSTALL_EXT_PACKAGES=NONE \ - -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ - -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ - -DZLIB_STATIC_LIBRARY=ON \ -Dexpat_DIR=${expat_DIR} \ - -DImath_DIR=${Imath_DIR} \ - -Dminizip-ng_DIR=${minizip_DIR} \ + -Dexpat_LIBRARY=${expat_LIBRARY} \ + -Dexpat_INCLUDE_DIR=${expat_INCLUDE_DIR} \ + -Dexpat_USE_STATIC_LIBS=TRUE \ -Dpystring_LIBRARY=${pystring_LIBRARY} \ -Dpystring_INCLUDE_DIR=${pystring_INCLUDE_DIR} \ -Dyaml-cpp_DIR=${yaml_DIR} \ + -Dyaml-cpp_LIBRARY=${yaml_LIBRARY} \ + -Dyaml-cpp_INCLUDE_DIR=${yaml_INCLUDE_DIR} \ + -DImath_DIR=${Imath_DIR} \ + -DHalf_STATIC_LIBRARY=ON \ + -DHalf_LIBRARY=${Half_LIBRARY} \ + -DHalf_INCLUDE_DIR=${Half_INCLUDE_DIR} \ + -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ + -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ + -DZLIB_STATIC_LIBRARY=ON \ + -Dminizip-ng_DIR=${minizip_DIR} \ ${MMSOLVERLIBS_ROOT} ${CMAKE_EXE} --build . --parallel diff --git a/scripts/internal/build_mmSolverLibs_windows64.bat b/scripts/internal/build_mmSolverLibs_windows64.bat index ea194a469..02a860107 100644 --- a/scripts/internal/build_mmSolverLibs_windows64.bat +++ b/scripts/internal/build_mmSolverLibs_windows64.bat @@ -43,6 +43,10 @@ IF "%BUILD_TYPE%"=="Release" ( SET BUILD_TYPE_DIR=release ) +:: Allows you to see the build command lines, to help debugging build +:: problems. Set to ON to enable, and OFF to disable. +SET MMSOLVER_BUILD_VERBOSE=OFF + :: Where to find the mmsolverlibs Rust libraries and headers. SET MMSOLVERLIBS_INSTALL_PATH=%BUILD_DIR_BASE%\build_mmsolverlibs\install\maya%MAYA_VERSION%_windows64\ SET MMSOLVERLIBS_ROOT=%PROJECT_ROOT%\lib @@ -57,14 +61,31 @@ SET MMSOLVERLIBS_BUILD_TESTS=1 SET EXTERNAL_BUILD_DIR=%BUILD_DIR_BASE%\build_opencolorio\cmake_win64_maya%MAYA_VERSION%_%BUILD_TYPE%\ext\dist SET OPENCOLORIO_INSTALL_DIR=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAYA_VERSION%_windows64\ SET OPENCOLORIO_CMAKE_CONFIG_DIR=%OPENCOLORIO_INSTALL_DIR%\lib\cmake\OpenColorIO\ +SET OPENCOLORIO_CMAKE_FIND_MODULES_DIR=%PROJECT_ROOT%\external\working\maya%MAYA_VERSION%_windows64\%OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME%\share\cmake\modules +:: Convert back-slashes to forward-slashes. +:: https://stackoverflow.com/questions/23542453/change-backslash-to-forward-slash-in-windows-batch-file +SET "OPENCOLORIO_CMAKE_FIND_MODULES_DIR=%OPENCOLORIO_CMAKE_FIND_MODULES_DIR:\=/%" + +SET expat_DIR=%EXTERNAL_BUILD_DIR%\%EXPAT_RELATIVE_CMAKE_DIR% +SET expat_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET expat_LIBRARY=%EXTERNAL_BUILD_DIR%\%EXPAT_RELATIVE_LIB_PATH% + +SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include +SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\%PYSTRING_RELATIVE_LIB_PATH% + +SET yaml_DIR=%EXTERNAL_BUILD_DIR%\%YAML_RELATIVE_CMAKE_DIR% +SET yaml_LIBRARY=%EXTERNAL_BUILD_DIR%\%YAML_RELATIVE_LIB_PATH% +SET yaml_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ + SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath + +SET Half_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET Half_LIBRARY=%EXTERNAL_BUILD_DIR%\%HALF_RELATIVE_LIB_PATH% + SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ -SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\zlibstatic.lib -SET expat_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\expat-%EXPAT_VERSION% -SET minizip_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\minizip-ng -SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include -SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\pystring.lib -SET yaml_DIR=%EXTERNAL_BUILD_DIR%\share\cmake\yaml-cpp +SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\%ZLIB_RELATIVE_LIB_PATH% + +SET minizip_DIR=%EXTERNAL_BUILD_DIR%\%MINIZIP_RELATIVE_CMAKE_DIR% ECHO Building mmsolverlibs... (%MMSOLVERLIBS_ROOT%) @@ -141,21 +162,32 @@ CHDIR "%BUILD_DIR%" -DCMAKE_CXX_STANDARD=%CXX_STANDARD% ^ -DCMAKE_C_COMPILER=%CMAKE_C_COMPILER% ^ -DCMAKE_CXX_COMPILER=%CMAKE_CXX_COMPILER% ^ + -DCMAKE_MODULE_PATH=%OPENCOLORIO_CMAKE_FIND_MODULES_DIR% ^ + -DCMAKE_VERBOSE_MAKEFILE=%MMSOLVER_BUILD_VERBOSE% ^ + -DMMSOLVER_VFX_PLATFORM=%VFX_PLATFORM% ^ -DMMSOLVERLIBS_CXXBRIDGE_EXE=%MMSOLVERLIBS_CXXBRIDGE_EXE% ^ -DMMSOLVERLIBS_BUILD_TESTS=%MMSOLVERLIBS_BUILD_TESTS% ^ -DMMSOLVERLIBS_LIB_DIR=%MMSOLVERLIBS_LIB_DIR% ^ -Dldpk_URL=%LDPK_URL% ^ -DOpenColorIO_DIR=%OPENCOLORIO_CMAKE_CONFIG_DIR% ^ -DOCIO_INSTALL_EXT_PACKAGES=NONE ^ - -DZLIB_LIBRARY=%ZLIB_LIBRARY% ^ - -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ - -DZLIB_STATIC_LIBRARY=ON ^ -Dexpat_DIR=%expat_DIR% ^ - -DImath_DIR=%Imath_DIR% ^ - -Dminizip-ng_DIR=%minizip_DIR% ^ + -Dexpat_LIBRARY=%expat_LIBRARY% ^ + -Dexpat_INCLUDE_DIR=%expat_INCLUDE_DIR% ^ + -Dexpat_USE_STATIC_LIBS=TRUE ^ -Dpystring_LIBRARY=%pystring_LIBRARY% ^ -Dpystring_INCLUDE_DIR=%pystring_INCLUDE_DIR% ^ -Dyaml-cpp_DIR=%yaml_DIR% ^ + -Dyaml-cpp_LIBRARY=%yaml_LIBRARY% ^ + -Dyaml-cpp_INCLUDE_DIR=%yaml_INCLUDE_DIR% ^ + -DImath_DIR=%Imath_DIR% ^ + -DHalf_STATIC_LIBRARY=ON ^ + -DHalf_LIBRARY=%Half_LIBRARY% ^ + -DHalf_INCLUDE_DIR=%Half_INCLUDE_DIR% ^ + -DZLIB_LIBRARY=%ZLIB_LIBRARY% ^ + -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ + -DZLIB_STATIC_LIBRARY=ON ^ + -Dminizip-ng_DIR=%minizip_DIR% ^ %MMSOLVERLIBS_ROOT% if errorlevel 1 goto failed_to_generate_cpp diff --git a/scripts/internal/build_mmSolver_linux.bash b/scripts/internal/build_mmSolver_linux.bash index 7aa853637..5270f3cdc 100644 --- a/scripts/internal/build_mmSolver_linux.bash +++ b/scripts/internal/build_mmSolver_linux.bash @@ -76,6 +76,10 @@ MMSOLVER_BUILD_ICONS=1 MMSOLVER_BUILD_CONFIG=1 MMSOLVER_BUILD_TESTS=0 +# Allows you to see the build command lines, to help debugging build +# problems. Set to ON to enable, and OFF to disable. +MMSOLVER_BUILD_VERBOSE=OFF + # Path to this script. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" # The root of this project. @@ -95,14 +99,28 @@ MMSOLVERLIBS_RUST_DIR="${BUILD_DIR_BASE}/build_mmsolverlibs/rust_linux_maya${MAY EXTERNAL_BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/cmake_linux_maya${MAYA_VERSION}_${BUILD_TYPE}/ext/dist" OPENCOLORIO_INSTALL_DIR="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA_VERSION}_linux/" OPENCOLORIO_CMAKE_CONFIG_DIR="${OPENCOLORIO_INSTALL_DIR}/lib64/cmake/OpenColorIO/" -ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/lib/libz.a" -ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" -expat_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/expat-${EXPAT_VERSION}" +OPENCOLORIO_CMAKE_FIND_MODULES_DIR="${PROJECT_ROOT}/external/working/maya${MAYA_VERSION}_linux/${OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME}/share/cmake/modules" + +expat_DIR="${EXTERNAL_BUILD_DIR}/${EXPAT_RELATIVE_CMAKE_DIR}" +expat_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +expat_LIBRARY="${EXTERNAL_BUILD_DIR}/${EXPAT_RELATIVE_LIB_PATH}" + +pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/${PYSTRING_RELATIVE_LIB_PATH}" +pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" + +yaml_DIR="${EXTERNAL_BUILD_DIR}/${YAML_RELATIVE_CMAKE_DIR}" +yaml_LIBRARY="${EXTERNAL_BUILD_DIR}/${YAML_RELATIVE_LIB_PATH}" +yaml_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" + Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" + +Half_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" +Half_LIBRARY="${EXTERNAL_BUILD_DIR}/${HALF_RELATIVE_LIB_PATH}" + +ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/${ZLIB_RELATIVE_LIB_PATH}" +ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" + minizip_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/minizip-ng" -yaml_DIR="${EXTERNAL_BUILD_DIR}/share/cmake/yaml-cpp" -pystring_LIBRARY="${EXTERNAL_BUILD_DIR}/lib64/libpystring.a" -pystring_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include" # We don't want to find system packages. CMAKE_IGNORE_PATH="/lib;/lib64;/usr;/usr/lib;/usr/lib64;/usr/local;/usr/local/lib;/usr/local/lib64;" @@ -121,7 +139,10 @@ ${CMAKE_EXE} \ -DCMAKE_IGNORE_PATH=${CMAKE_IGNORE_PATH} \ -DCMAKE_POSITION_INDEPENDENT_CODE=1 \ -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ + -DCMAKE_MODULE_PATH=${OPENCOLORIO_CMAKE_FIND_MODULES_DIR} \ + -DCMAKE_VERBOSE_MAKEFILE=${MMSOLVER_BUILD_VERBOSE} \ -DOPENGL_INCLUDE_DIR=${OPENGL_INCLUDE_DIR} \ + -DMMSOLVER_VFX_PLATFORM=${VFX_PLATFORM} \ -DMMSOLVER_BUILD_PLUGIN=${MMSOLVER_BUILD_PLUGIN} \ -DMMSOLVER_BUILD_TOOLS=${MMSOLVER_BUILD_TOOLS} \ -DMMSOLVER_BUILD_PYTHON=${MMSOLVER_BUILD_PYTHON} \ @@ -141,15 +162,23 @@ ${CMAKE_EXE} \ -Dmmsolverlibs_cpp_DIR=${MMSOLVERLIBS_CMAKE_CONFIG_DIR} \ -DOpenColorIO_DIR=${OPENCOLORIO_CMAKE_CONFIG_DIR} \ -DOCIO_INSTALL_EXT_PACKAGES=NONE \ - -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ - -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ - -DZLIB_STATIC_LIBRARY=ON \ -Dexpat_DIR=${expat_DIR} \ - -DImath_DIR=${Imath_DIR} \ - -Dminizip-ng_DIR=${minizip_DIR} \ + -Dexpat_LIBRARY=${expat_LIBRARY} \ + -Dexpat_INCLUDE_DIR=${expat_INCLUDE_DIR} \ + -Dexpat_USE_STATIC_LIBS=TRUE \ -Dpystring_LIBRARY=${pystring_LIBRARY} \ -Dpystring_INCLUDE_DIR=${pystring_INCLUDE_DIR} \ -Dyaml-cpp_DIR=${yaml_DIR} \ + -Dyaml-cpp_LIBRARY=${yaml_LIBRARY} \ + -Dyaml-cpp_INCLUDE_DIR=${yaml_INCLUDE_DIR} \ + -DImath_DIR=${Imath_DIR} \ + -DHalf_STATIC_LIBRARY=ON \ + -DHalf_LIBRARY=${Half_LIBRARY} \ + -DHalf_INCLUDE_DIR=${Half_INCLUDE_DIR} \ + -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ + -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ + -DZLIB_STATIC_LIBRARY=ON \ + -Dminizip-ng_DIR=${minizip_DIR} \ ${PROJECT_ROOT} ${CMAKE_EXE} --build . --parallel diff --git a/scripts/internal/build_mmSolver_windows64.bat b/scripts/internal/build_mmSolver_windows64.bat index 74e63b827..84a6a39e9 100644 --- a/scripts/internal/build_mmSolver_windows64.bat +++ b/scripts/internal/build_mmSolver_windows64.bat @@ -76,6 +76,10 @@ SET MMSOLVER_BUILD_ICONS=1 SET MMSOLVER_BUILD_CONFIG=1 SET MMSOLVER_BUILD_TESTS=0 +:: Allows you to see the build command lines, to help debugging build +:: problems. Set to ON to enable, and OFF to disable. +SET MMSOLVER_BUILD_VERBOSE=OFF + SET PYTHON_VIRTUAL_ENV_DIR_NAME=python_venv_windows64_maya%MAYA_VERSION% :: Note: There is no need to deactivate the virtual environment because @@ -88,17 +92,35 @@ SET MMSOLVERLIBS_INSTALL_DIR="%BUILD_DIR_BASE%\build_mmsolverlibs\install\maya%M SET MMSOLVERLIBS_CMAKE_CONFIG_DIR="%MMSOLVERLIBS_INSTALL_DIR%\lib\cmake\mmsolverlibs_cpp" SET MMSOLVERLIBS_RUST_DIR="%BUILD_DIR_BASE%\build_mmsolverlibs\rust_windows64_maya%MAYA_VERSION%\%BUILD_TYPE_DIR%" +:: Paths for dependencies. SET EXTERNAL_BUILD_DIR=%BUILD_DIR_BASE%\build_opencolorio\cmake_win64_maya%MAYA_VERSION%_%BUILD_TYPE%\ext\dist SET OPENCOLORIO_INSTALL_DIR=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAYA_VERSION%_windows64\ SET OPENCOLORIO_CMAKE_CONFIG_DIR=%OPENCOLORIO_INSTALL_DIR%\lib\cmake\OpenColorIO\ +SET OPENCOLORIO_CMAKE_FIND_MODULES_DIR=%PROJECT_ROOT%\external\working\maya%MAYA_VERSION%_windows64\%OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME%\share\cmake\modules +:: Convert back-slashes to forward-slashes. +:: https://stackoverflow.com/questions/23542453/change-backslash-to-forward-slash-in-windows-batch-file +SET "OPENCOLORIO_CMAKE_FIND_MODULES_DIR=%OPENCOLORIO_CMAKE_FIND_MODULES_DIR:\=/%" + +SET expat_DIR=%EXTERNAL_BUILD_DIR%\%EXPAT_RELATIVE_CMAKE_DIR% +SET expat_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET expat_LIBRARY=%EXTERNAL_BUILD_DIR%\%EXPAT_RELATIVE_LIB_PATH% + +SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include +SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\%PYSTRING_RELATIVE_LIB_PATH% + +SET yaml_DIR=%EXTERNAL_BUILD_DIR%\%YAML_RELATIVE_CMAKE_DIR% +SET yaml_LIBRARY=%EXTERNAL_BUILD_DIR%\%YAML_RELATIVE_LIB_PATH% +SET yaml_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ + SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath + +SET Half_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ +SET Half_LIBRARY=%EXTERNAL_BUILD_DIR%\%HALF_RELATIVE_LIB_PATH% + SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ -SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\zlibstatic.lib -SET expat_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\expat-%EXPAT_VERSION% -SET minizip_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\minizip-ng -SET pystring_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include -SET pystring_LIBRARY=%EXTERNAL_BUILD_DIR%\lib\pystring.lib -SET yaml_DIR=%EXTERNAL_BUILD_DIR%\share\cmake\yaml-cpp +SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\%ZLIB_RELATIVE_LIB_PATH% + +SET minizip_DIR=%EXTERNAL_BUILD_DIR%\%MINIZIP_RELATIVE_CMAKE_DIR% :: MinGW is a common install for developers on Windows and :: if installed and used it will cause build conflicts and @@ -136,6 +158,9 @@ CHDIR "%BUILD_DIR%" -DCMAKE_CXX_STANDARD=%CXX_STANDARD% ^ -DCMAKE_C_COMPILER=%CMAKE_C_COMPILER% ^ -DCMAKE_CXX_COMPILER=%CMAKE_CXX_COMPILER% ^ + -DCMAKE_MODULE_PATH=%OPENCOLORIO_CMAKE_FIND_MODULES_DIR% ^ + -DCMAKE_VERBOSE_MAKEFILE=%MMSOLVER_BUILD_VERBOSE% ^ + -DMMSOLVER_VFX_PLATFORM=%VFX_PLATFORM% ^ -DMMSOLVER_BUILD_PLUGIN=%MMSOLVER_BUILD_PLUGIN% ^ -DMMSOLVER_BUILD_TOOLS=%MMSOLVER_BUILD_TOOLS% ^ -DMMSOLVER_BUILD_PYTHON=%MMSOLVER_BUILD_PYTHON% ^ @@ -159,11 +184,19 @@ CHDIR "%BUILD_DIR%" -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ -DZLIB_STATIC_LIBRARY=ON ^ -Dexpat_DIR=%expat_DIR% ^ + -Dexpat_LIBRARY=%expat_LIBRARY% ^ + -Dexpat_INCLUDE_DIR=%expat_INCLUDE_DIR% ^ + -Dexpat_USE_STATIC_LIBS=TRUE ^ -DImath_DIR=%Imath_DIR% ^ + -DHalf_STATIC_LIBRARY=ON ^ + -DHalf_LIBRARY=%Half_LIBRARY% ^ + -DHalf_INCLUDE_DIR=%Half_INCLUDE_DIR% ^ -Dminizip-ng_DIR=%minizip_DIR% ^ -Dpystring_LIBRARY=%pystring_LIBRARY% ^ -Dpystring_INCLUDE_DIR=%pystring_INCLUDE_DIR% ^ -Dyaml-cpp_DIR=%yaml_DIR% ^ + -Dyaml-cpp_LIBRARY=%yaml_LIBRARY% ^ + -Dyaml-cpp_INCLUDE_DIR=%yaml_INCLUDE_DIR% ^ %PROJECT_ROOT% if errorlevel 1 goto failed_to_generate diff --git a/scripts/internal/build_openColorIO_linux.bash b/scripts/internal/build_openColorIO_linux.bash index 30ca89e94..f1b615e4f 100644 --- a/scripts/internal/build_openColorIO_linux.bash +++ b/scripts/internal/build_openColorIO_linux.bash @@ -56,6 +56,10 @@ OPENCOLORIO_INSTALL_PATH="${BUILD_DIR_BASE}/build_opencolorio/install/maya${MAYA # What type of build? "Release" or "Debug"? BUILD_TYPE=Release +# Allows you to see the build command lines, to help debugging build +# problems. Set to ON to enable, and OFF to disable. +MMSOLVER_BUILD_VERBOSE=OFF + # Path to this script. DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" # The root of this project. @@ -93,13 +97,20 @@ BUILD_DIR="${BUILD_DIR_BASE}/build_opencolorio/${BUILD_DIR_NAME}" mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} +# Renaming the library name and C++ namespace, is so that software +# looking for the "regular" OpenColorIO will not conflict with the +# mmSolver library. +MMSOLVER_OCIO_LIBNAME_SUFFIX="_mmSolver" +MMSOLVER_OCIO_NAMESPACE="OpenColorIO_mmSolver" + ${CMAKE_EXE} \ - -DBUILD_SHARED_LIBS=OFF \ + -DBUILD_SHARED_LIBS=ON \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_INSTALL_PREFIX=${OPENCOLORIO_INSTALL_PATH} \ -DCMAKE_IGNORE_PATH=${CMAKE_IGNORE_PATH} \ -DCMAKE_POSITION_INDEPENDENT_CODE=1 \ -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ + -DCMAKE_VERBOSE_MAKEFILE=${MMSOLVER_BUILD_VERBOSE} \ -DOCIO_INSTALL_EXT_PACKAGES=ALL \ -DOCIO_BUILD_APPS=OFF \ -DOCIO_USE_OIIO_FOR_APPS=OFF \ @@ -109,6 +120,8 @@ ${CMAKE_EXE} \ -DOCIO_BUILD_FROZEN_DOCS=OFF \ -DOCIO_BUILD_PYTHON=OFF \ -DOCIO_BUILD_OPENFX=OFF \ + -DOCIO_LIBNAME_SUFFIX=${MMSOLVER_OCIO_LIBNAME_SUFFIX} \ + -DOCIO_NAMESPACE=${MMSOLVER_OCIO_NAMESPACE} \ ${SOURCE_ROOT} ${CMAKE_EXE} --build . --parallel diff --git a/scripts/internal/build_openColorIO_windows64.bat b/scripts/internal/build_openColorIO_windows64.bat index ca0ea34a4..70614c6b8 100644 --- a/scripts/internal/build_openColorIO_windows64.bat +++ b/scripts/internal/build_openColorIO_windows64.bat @@ -56,6 +56,10 @@ SET OPENCOLORIO_INSTALL_PATH=%BUILD_DIR_BASE%\build_opencolorio\install\maya%MAY :: What type of build? "Release" or "Debug"? SET BUILD_TYPE=Release +:: Allows you to see the build command lines, to help debugging build +:: problems. Set to ON to enable, and OFF to disable. +SET MMSOLVER_BUILD_VERBOSE=OFF + :: Make sure source code archive is downloaded and exists. SET SOURCE_TARBALL=%PROJECT_ROOT%\external\archives\%OPENCOLORIO_TARBALL_NAME% IF NOT EXIST %SOURCE_TARBALL% ( @@ -109,14 +113,21 @@ CHDIR "%BUILD_DIR_BASE%\build_opencolorio\" MKDIR "%BUILD_DIR_NAME%" CHDIR "%BUILD_DIR%" +:: Renaming the library name and C++ namespace, is so that software +:: looking for the "regular" OpenColorIO will not conflict with the +:: mmSolver library. +SET MMSOLVER_OCIO_LIBNAME_SUFFIX="_mmSolver" +SET MMSOLVER_OCIO_NAMESPACE="OpenColorIO_mmSolver" + %CMAKE_EXE% -G %CMAKE_GENERATOR% ^ - -DBUILD_SHARED_LIBS=OFF ^ + -DBUILD_SHARED_LIBS=ON ^ -DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^ -DCMAKE_INSTALL_PREFIX=%OPENCOLORIO_INSTALL_PATH% ^ -DCMAKE_IGNORE_PATH=%IGNORE_INCLUDE_DIRECTORIES% ^ -DCMAKE_C_COMPILER=%CMAKE_C_COMPILER% ^ -DCMAKE_CXX_COMPILER=%CMAKE_CXX_COMPILER% ^ -DCMAKE_CXX_STANDARD=%CXX_STANDARD% ^ + -DCMAKE_VERBOSE_MAKEFILE=%MMSOLVER_BUILD_VERBOSE% ^ -DOCIO_INSTALL_EXT_PACKAGES=ALL ^ -DOCIO_BUILD_APPS=OFF ^ -DOCIO_USE_OIIO_FOR_APPS=OFF ^ @@ -127,6 +138,8 @@ CHDIR "%BUILD_DIR%" -DOCIO_BUILD_PYTHON=OFF ^ -DOCIO_BUILD_OPENFX=OFF ^ -DOCIO_USE_SSE=ON ^ + -DOCIO_LIBNAME_SUFFIX=%MMSOLVER_OCIO_LIBNAME_SUFFIX% ^ + -DOCIO_NAMESPACE=%MMSOLVER_OCIO_NAMESPACE% ^ %SOURCE_ROOT% IF errorlevel 1 GOTO failed_to_generate_cpp diff --git a/share/cmake/modules/MMColorIOUtils.cmake b/share/cmake/modules/MMColorIOUtils.cmake new file mode 100644 index 000000000..75b5f4fd5 --- /dev/null +++ b/share/cmake/modules/MMColorIOUtils.cmake @@ -0,0 +1,205 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# --------------------------------------------------------------------- +# +# CMake utilities for mmColorIO. +# + +macro(mmcolorio_find_packages) + message(STATUS "mmcolorio_find_packages") + + # OpenColorIO + find_package(OpenColorIO REQUIRED) + message(STATUS "OpenColorIO: Version: ${OpenColorIO_VERSION}") + + # OpenColorIO Dependencies + find_package(pystring REQUIRED) + message(STATUS "pystring: Found: ${pystring_FOUND}") + message(STATUS "pystring: Version: ${pystring_VERSION}") + message(STATUS "pystring: Libraries: ${pystring_LIBRARY}") + message(STATUS "pystring: Include Dir: ${pystring_INCLUDE_DIR}") + + # This is the (all-lower-case) 'expat' library namespace, which is the + # same as the 'EXPAT' library, except using ALL-UPPER-CASE name will + # use the inbuilt CMake FindEXPAT.cmake module which is not what + # OpenColorIO uses. + find_package(expat REQUIRED) + message(STATUS "expat: Found: ${expat_FOUND}") + message(STATUS "expat: Version: ${expat_VERSION}") + message(STATUS "expat: Libraries: ${expat_LIBRARY}") + message(STATUS "expat: Include Dir: ${expat_INCLUDE_DIR}") + + find_package(yaml-cpp REQUIRED) + message(STATUS "yaml-cpp: Found: ${yaml-cpp_FOUND}") + message(STATUS "yaml-cpp: Version: ${yaml-cpp_VERSION}") + message(STATUS "yaml-cpp: Libraries: ${yaml-cpp_LIBRARY}") + message(STATUS "yaml-cpp: Include Dir: ${yaml-cpp_INCLUDE_DIR}") + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.1.0") + find_package(Imath REQUIRED) + message(STATUS "Imath: Found: ${Imath_FOUND}") + message(STATUS "Imath: Version: ${Imath_VERSION}") + message(STATUS "Imath: Libraries: ${Imath_LIBRARY}") + message(STATUS "Imath: Include Dir: ${Imath_INCLUDE_DIR}") + else() + find_package(Half REQUIRED) + message(STATUS "Half: Found: ${Half_FOUND}") + message(STATUS "Half: Version: ${Half_VERSION}") + message(STATUS "Half: Libraries: ${Half_LIBRARY}") + message(STATUS "Half: Include Dir: ${Half_INCLUDE_DIR}") + endif() + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") + find_package(ZLIB REQUIRED) + message(STATUS "ZLIB: Found: ${ZLIB_FOUND}") + message(STATUS "ZLIB: Version: ${ZLIB_VERSION}") + message(STATUS "ZLIB: Libraries: ${ZLIB_LIBRARIES}") + message(STATUS "ZLIB: Include Dirs: ${ZLIB_INCLUDE_DIRS}") + + find_package(minizip-ng REQUIRED) + message(STATUS "minizip-ng: Found: ${minizip-ng_FOUND}") + message(STATUS "minizip-ng: Version: ${minizip-ng_VERSION}") + message(STATUS "minizip-ng: Libraries: ${minizip-ng_LIBRARY}") + message(STATUS "minizip-ng: Include Dir: ${minizip-ng_INCLUDE_DIR}") + endif() + +endmacro() + + +macro(mmcolorio_target_link_packages target) + message(STATUS "mmcolorio_target_link_packages: ${target}") + + get_target_property(yaml_cpp_LOCATION_RELEASE + yaml-cpp + IMPORTED_LOCATION_RELEASE) + message(STATUS + "yaml_cpp_LOCATION_RELEASE: ${yaml_cpp_LOCATION_RELEASE}") + + target_link_libraries(${target} + PRIVATE ${expat_LIBRARY} + PRIVATE ${pystring_LIBRARY} + PRIVATE ${yaml_cpp_LOCATION_RELEASE} + ) + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") + target_link_libraries(${target} + PRIVATE ZLIB::ZLIB + PRIVATE MINIZIP::minizip-ng + ) + endif() + + # The 'half' 16-bit float data type is used from Imath by default in + # OCIO 2.2+, but OpenEXR/IlmBase is used before that. + # + # The reason is that OpenEXR used to be the sole location for the + # 'half' data type (because it was supported as part of OpenEXR), + # but since many libraries wanted the 'half' data type without all + # of OpenEXR, it was refactored out of OpenEXR. + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") + target_link_libraries(${target} + PRIVATE Imath::Imath + ) + else() + target_link_libraries(${target} + PRIVATE ${Half_LIBRARY} + ) + endif() + + if (WIN32) + get_target_property(OpenColorIO_IMPLIB_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_IMPLIB_RELEASE) + message(STATUS + "OpenColorIO_IMPLIB_RELEASE: ${OpenColorIO_IMPLIB_RELEASE}") + + get_target_property(OpenColorIO_LOCATION_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_LOCATION_RELEASE) + message(STATUS + "OpenColorIO_LOCATION_RELEASE: ${OpenColorIO_LOCATION_RELEASE}") + + get_filename_component( + OpenColorIO_IMPLIB_RELEASE_ABS + ${OpenColorIO_IMPLIB_RELEASE} + REALPATH) + + target_link_libraries(${target} + # PRIVATE OpenColorIO::OpenColorIO + # PRIVATE ${OpenColorIO_LOCATION_RELEASE} + PRIVATE ${OpenColorIO_IMPLIB_RELEASE_ABS} + ) + elseif (UNIX) + get_target_property(OpenColorIO_LOCATION_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_LOCATION_RELEASE) + message(STATUS + "OpenColorIO_LOCATION_RELEASE: ${OpenColorIO_LOCATION_RELEASE}") + + target_link_libraries(${target} + # PRIVATE OpenColorIO::OpenColorIO + PRIVATE ${OpenColorIO_LOCATION_RELEASE} + # PRIVATE ${OpenColorIO_IMPLIB_RELEASE} + ) + + endif () + +endmacro() + + +macro(mmcolorio_target_include_packages target) + message(STATUS "mmcolorio_target_include_packages: ${target}") + + message(STATUS "OpenColorIO: Version: ${OpenColorIO_VERSION}") + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.1.0") + get_target_property(OpenColorIO_INCLUDE_DIR + OpenColorIO::OpenColorIO + INTERFACE_INCLUDE_DIRECTORIES) + else() + get_target_property(OpenColorIO_INCLUDE_DIR + OpenColorIO::OpenColorIOHeaders + INTERFACE_INCLUDE_DIRECTORIES) + endif() + + message(STATUS + "OpenColorIO_INCLUDE_DIR: ${OpenColorIO_INCLUDE_DIR}") + + target_include_directories(${target} + PRIVATE ${OpenColorIO_INCLUDE_DIR} + PRIVATE ${pystring_INCLUDE_DIR} + PRIVATE ${expat_INCLUDE_DIR} + PRIVATE ${yaml-cpp_INCLUDE_DIR} + ) + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.1.0") + target_include_directories(${target} + PRIVATE ${Imath_INCLUDE_DIR} + ) + else() + target_include_directories(${target} + PRIVATE ${Half_INCLUDE_DIR} + ) + endif() + + if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") + target_include_directories(${target} + PRIVATE ${ZLIB_INCLUDE_DIRS} + PRIVATE ${minizip-ng_INCLUDE_DIR} + ) + endif() + +endmacro() diff --git a/share/cmake/modules/MMSolverUtils.cmake b/share/cmake/modules/MMSolverUtils.cmake index a51e2885f..c343641bc 100644 --- a/share/cmake/modules/MMSolverUtils.cmake +++ b/share/cmake/modules/MMSolverUtils.cmake @@ -68,6 +68,8 @@ function(set_global_maya_plugin_compile_options) add_compile_definitions(USERDLL) # Use multithread-specific Run-Time Library. + # + # NOTE: This changes the ABI. set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /MD") add_compile_options("/arch:AVX2") @@ -87,6 +89,8 @@ function(set_global_maya_plugin_compile_options) set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG") # Use debug-specific Run-Time Library. + # + # NOTE: This changes the ABI. set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} /MDd") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od") # Optimize for Debug. @@ -169,14 +173,18 @@ function(set_global_maya_plugin_compile_options) # # https://vfxplatform.com/#footnote-gcc6 # - # TODO: In VFX Platform CY2023, and the move to RHEL 8 or RHEL 9, + # In VFX Platform CY2023, and the move to RHEL 8 or RHEL 9, # the new default is to use "_GLIBCXX_USE_CXX11_ABI=1". # # https://vfxplatform.com/#footnote-gcc9 # - # TODO: Expose this variable as a CMake option, so we can enable - # this for RHEL 8/9 and Rocky Linux 8. - add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) + # NOTE: This changes the ABI. + if (VFX_PLATFORM VERSION_GREATER_EQUAL 2023) + # Must be enabled for RHEL 8/9, Alma Linux and Rocky Linux 8/9. + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=1) + else () + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) + endif() # Enable warnings. add_definitions(-Wall) @@ -332,7 +340,7 @@ function(install_target_plugin_to_module target module_dir) endfunction() -# Install the Plug-In. +# Install executables. function(install_target_executable_to_module target module_dir) set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${module_dir}") @@ -344,9 +352,9 @@ endfunction() # Install shared (dynamic) library. function(install_shared_library lib_file lib_file_dll install_dir) - # message(STATUS "INSTALL FILE: ${lib_file}") - # message(STATUS "INSTALL DLL : ${lib_file_dll}") - # message(STATUS "INSTALL DIR : ${install_dir}") + # message(STATUS "install_shared_library install file: ${lib_file}") + # message(STATUS "install_shared_library install dll : ${lib_file_dll}") + # message(STATUS "install_shared_library install dir : ${install_dir}") if (WIN32) if (EXISTS ${lib_file_dll}) install(FILES ${lib_file_dll} @@ -357,12 +365,17 @@ function(install_shared_library lib_file lib_file_dll install_dir) elseif (UNIX) string(FIND ${lib_file} ".so" find_so) if(${find_so} GREATER_EQUAL 0) - # Install only the real library, with the symlink name. - get_filename_component(absolute_lib_file ${lib_file} REALPATH) + # message(STATUS "install_shared_library lib_file : ${lib_file}") + + # Install only the real library, with the original name. + get_filename_component(abs_lib_file ${lib_file} REALPATH) get_filename_component(lib_file_name ${lib_file} NAME) - install(FILES ${absolute_lib_file} + # message(STATUS "install_shared_library abs_lib_file : ${abs_lib_file}") + # message(STATUS "install_shared_library lib_file_name: ${lib_file_name}") + install(FILES ${abs_lib_file} DESTINATION ${install_dir} - RENAME ${lib_file_name}) + RENAME ${lib_file_name} + ) # # Copies all files similar to ${lib_file} to the install # # directory. @@ -375,6 +388,37 @@ function(install_shared_library lib_file lib_file_dll install_dir) endfunction() +function(install_shared_library_with_name lib_file lib_file_dll lib_name install_dir) + # message(STATUS "install_shared_library install file: ${lib_file}") + # message(STATUS "install_shared_library install dll : ${lib_file_dll}") + # message(STATUS "install_shared_library install name: ${lib_name}") + # message(STATUS "install_shared_library install dir : ${install_dir}") + if (WIN32) + if (EXISTS ${lib_file_dll}) + install(FILES ${lib_file_dll} + DESTINATION ${install_dir}) + else () + message(FATAL_ERROR "Cannot find .dll file to install: ${lib_file_dll}") + endif () + elseif (UNIX) + string(FIND ${lib_file} ".so" find_so) + if(${find_so} GREATER_EQUAL 0) + # message(STATUS "install_shared_library lib_file : ${lib_file}") + + # Install only the real library, with the original name. + get_filename_component(abs_lib_file ${lib_file} REALPATH) + get_filename_component(lib_file_name ${lib_file} NAME) + # message(STATUS "install_shared_library abs_lib_file : ${abs_lib_file}") + + install(FILES ${abs_lib_file} + DESTINATION ${install_dir} + RENAME ${lib_name} + ) + endif () + endif () +endfunction() + + # Install many shared (dynamic) libraries. function(install_shared_libraries lib_files lib_files_dll install_dir) # message(STATUS "INSTALL FILES: ${lib_files}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 27865c50e..895aeadf7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -218,11 +218,13 @@ if(MAYA_FOUND) ) endif() -find_package(ZLIB REQUIRED) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(mmSolver) # Link all libraries. target_link_libraries(mmSolver - PRIVATE ZLIB::ZLIB PRIVATE cminpack::cminpack PRIVATE glog::glog PRIVATE Eigen3::Eigen @@ -230,28 +232,48 @@ target_link_libraries(mmSolver PRIVATE openMVG ) -# OpenColorIO -find_package(OpenColorIO REQUIRED) +target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_CMINPACK) +target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_OPENMVG) -get_target_property(OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES OpenColorIO::OpenColorIO INTERFACE_INCLUDE_DIRECTORIES) -get_target_property(OpenColorIO_IMPORTED_LOCATION_RELEASE OpenColorIO::OpenColorIO IMPORTED_LOCATION_RELEASE) -get_target_property(OpenColorIO_INTERFACE_LINK_LIBRARIES OpenColorIO::OpenColorIO INTERFACE_LINK_LIBRARIES) +install_target_plugin_to_module(mmSolver "${MODULE_FULL_NAME}") -message(STATUS "OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES: ${OpenColorIO_INTERFACE_INCLUDE_DIRECTORIES}") -message(STATUS "OpenColorIO_IMPORTED_LOCATION_RELEASE: ${OpenColorIO_IMPORTED_LOCATION_RELEASE}") -message(STATUS "OpenColorIO_INTERFACE_LINK_LIBRARIES: ${OpenColorIO_INTERFACE_LINK_LIBRARIES}") -if (MSVC) - target_link_libraries(mmSolver - PRIVATE ${OpenColorIO_IMPORTED_LOCATION_RELEASE} - ) -else() - target_link_libraries(mmSolver - PRIVATE ${OpenColorIO_IMPORTED_LOCATION_RELEASE} - ) -endif() +# TODO: Extract this logic into a reusable function/macro in a CMake +# module. +if (WIN32) + get_target_property(OpenColorIO_IMPLIB_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_IMPLIB_RELEASE) + message(STATUS + "OpenColorIO_IMPLIB_RELEASE: ${OpenColorIO_IMPLIB_RELEASE}") -target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_CMINPACK) -target_compile_definitions(mmSolver PRIVATE MMSOLVER_USE_OPENMVG) + get_target_property(OpenColorIO_LOCATION_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_LOCATION_RELEASE) + message(STATUS + "OpenColorIO_LOCATION_RELEASE: ${OpenColorIO_LOCATION_RELEASE}") -install_target_plugin_to_module(mmSolver "${MODULE_FULL_NAME}") + install_shared_library( + ${OpenColorIO_IMPLIB_RELEASE} + ${OpenColorIO_LOCATION_RELEASE} + "${MODULE_FULL_NAME}/lib") +elseif (UNIX) + + get_target_property(OpenColorIO_LOCATION_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_LOCATION_RELEASE) + message(STATUS + "OpenColorIO_LOCATION_RELEASE: ${OpenColorIO_LOCATION_RELEASE}") + + get_target_property(OpenColorIO_SONAME_RELEASE + OpenColorIO::OpenColorIO + IMPORTED_SONAME_RELEASE) + message(STATUS + "OpenColorIO_SONAME_RELEASE: ${OpenColorIO_SONAME_RELEASE}") + + install_shared_library_with_name( + ${OpenColorIO_LOCATION_RELEASE} + ${OpenColorIO_LOCATION_RELEASE} + ${OpenColorIO_SONAME_RELEASE} + "${MODULE_FULL_NAME}/lib") +endif () diff --git a/tools/lensdistortion/src/CMakeLists.txt b/tools/lensdistortion/src/CMakeLists.txt index b0655ebe8..5ba3cb8c9 100644 --- a/tools/lensdistortion/src/CMakeLists.txt +++ b/tools/lensdistortion/src/CMakeLists.txt @@ -28,9 +28,6 @@ set(source_files # Add test executable using the C++ bindings. add_executable(${lensdistortion_exe_name} ${source_files}) -# OpenColorIO is needed by 'mmsolverlibs_cpp'. -find_package(OpenColorIO REQUIRED) - # MM Solver standalone libraries. find_package(mmsolverlibs_cpp REQUIRED) find_package(mmsolverlibs_rust REQUIRED) @@ -40,10 +37,15 @@ target_link_libraries(${lensdistortion_exe_name} # 'target_link_libraries()' in '${PROJECT_ROOT}/src/CMakeLists.txt'. PUBLIC mmsolverlibs_rust::mmsolverlibs_rust PUBLIC mmsolverlibs_cpp::mmsolverlibs_cpp - PRIVATE OpenColorIO::OpenColorIO + PRIVATE ${rust_depend_on_libraries} ) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(${lensdistortion_exe_name}) + target_include_directories(${lensdistortion_exe_name} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} From 2ed8efbcd49af3fb6f9036f050fa84b9a78f2cf5 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 17 Jun 2024 22:47:57 +1000 Subject: [PATCH 098/295] ImageCache - Fix incorrect variable printing. --- src/mmSolver/image/ImageCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index cf291cec0..bd9c46dd0 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -257,7 +257,7 @@ inline std::string generate_cache_brief(const char *prefix_str, ss << prefix_str << "count=" << item_count << " items " << "| minimum=" << item_min_count << " items " << "| used=" << used_megabytes_str << "MB " - << "| capacity=" << used_megabytes_str << "MB " + << "| capacity=" << capacity_megabytes_str << "MB " << "| percent=" << used_percent << '%'; return ss.str(); } From 5b048fe95c0480ac5c42c3ebe5652b66af97d8cc Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 18 Jun 2024 00:17:02 +1000 Subject: [PATCH 099/295] Maya 2025 on Linux - Initial support added for building plug-in. Uses OpenColorIO 2.3.2 (upgraded from 2.2 in previous versions). This has not been tested well and has been confirmed to build, but there are currently symbols missing at runtime (which is a problem currently affecting all Linux builds with OpenColorIO). The location of Qt's Resource Compiler executable ("rcc") and UI Compiler ("uic") has been moved in the Linux install of Maya 2025 to the '${MAYA_LOCATION}/libexec' directory. The CMake script has been updated for this. --- scripts/build_mmSolver_linux_maya2025.bash | 67 +++++++++ share/cmake/modules/MMSolverUtils.cmake | 2 + share/docker/Dockerfile_maya2025 | 161 +++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 scripts/build_mmSolver_linux_maya2025.bash create mode 100644 share/docker/Dockerfile_maya2025 diff --git a/scripts/build_mmSolver_linux_maya2025.bash b/scripts/build_mmSolver_linux_maya2025.bash new file mode 100644 index 000000000..3f8af5925 --- /dev/null +++ b/scripts/build_mmSolver_linux_maya2025.bash @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Copyright (C) 2019, 2022, 2023, 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# --------------------------------------------------------------------- +# +# Builds the Maya MatchMove Solver project. + +# Maya +MAYA_VERSION=2025 +MAYA_LOCATION=/usr/autodesk/maya2025/ + +# Executable names/paths used for build process. +PYTHON_EXE=python3.9 +CMAKE_EXE=cmake3 +RUST_CARGO_EXE=cargo + +# OpenColorIO specific options. +OPENCOLORIO_TARBALL_NAME="OpenColorIO-2.3.2.tar.gz" +OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME="OpenColorIO-2.3.2" +EXPAT_RELATIVE_CMAKE_DIR=lib64/cmake/expat-2.4.1/ +EXPAT_RELATIVE_LIB_PATH=lib64/libexpat.a +# yaml-cpp 0.7.0 +YAML_RELATIVE_CMAKE_DIR=share/cmake/yaml-cpp +YAML_RELATIVE_LIB_PATH=lib64/libyaml-cpp.a +PYSTRING_RELATIVE_LIB_PATH=lib64/libpystring.a +ZLIB_RELATIVE_LIB_PATH=lib/libz.a + +# Manually override OpenGL include headers, because CMake doesn't seem +# to automatically find OpenGL headers on RockyLinux8 (which is used +# in the Docker containers). +OPENGL_INCLUDE_DIR=/usr/include/ + +# Which version of the VFX platform are we "using"? (Maya doesn't +# currently conform to the VFX Platform.) +VFX_PLATFORM=2024 + +# C++ Standard to use. +CXX_STANDARD=14 + +# The -e flag causes the script to exit as soon as one command returns +# a non-zero exit code. +set -ev + +CWD=`pwd` + +# These scripts assume 'RUST_CARGO_EXE' has been set to the Rust +# 'cargo' executable. +source "${CWD}/scripts/internal/build_openColorIO_linux.bash" +source "${CWD}/scripts/internal/build_mmSolverLibs_linux.bash" +source "${CWD}/scripts/internal/build_mmSolver_linux.bash" + +cd ${CWD} diff --git a/share/cmake/modules/MMSolverUtils.cmake b/share/cmake/modules/MMSolverUtils.cmake index c343641bc..bd4391412 100644 --- a/share/cmake/modules/MMSolverUtils.cmake +++ b/share/cmake/modules/MMSolverUtils.cmake @@ -465,6 +465,7 @@ function(compile_qt_ui_to_python_file PATH_SUFFIXES MacOS/ bin/ + libexec/ # Maya 2025 on Linux changed the directory. DOC "Maya provided Qt 'uic' executable path" ) @@ -528,6 +529,7 @@ function(compile_qt_resources_qrc_to_rcc_file MacOS bin3/ # Use Python 3.x location, in Maya 2022. bin/ + libexec/ # Maya 2025 on Linux changed the directory. DOC "Maya's Qt resource compiler (rcc) executable path" ) diff --git a/share/docker/Dockerfile_maya2025 b/share/docker/Dockerfile_maya2025 new file mode 100644 index 000000000..9b2035f55 --- /dev/null +++ b/share/docker/Dockerfile_maya2025 @@ -0,0 +1,161 @@ +# To create and run the docker container in PowerShell or BASH: +# +# $ docker build --file share/docker/Dockerfile_maya2025 -t mmsolver-linux-maya2025-build . +# $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2025-build +# +# +# Once the docker image is built and run, you can execute the +# following... +# +# +# Install Python and run tools: +# +# $ source ./scripts/python_venv_activate_maya2025.bash +# $ ./scripts/python_linter_run_pylint.bash +# $ ./scripts/python_linter_run_flake8.bash +# $ ./scripts/python_linter_run_cpplint.bash +# $ ./scripts/python_formatter_run_black_check.bash +# $ deactivate +# +# +# Build CMake project: +# +# $ ./scripts/build_mmSolver_linux_maya2025.bash +# $ mayapy tests/runTests.py +# + +FROM rockylinux:8 + +# Maya documentation for installing on RHEL8 / Rocky8: +# https://help.autodesk.com/view/MAYAUL/2025/ENU/?guid=GUID-D2B5433C-E0D2-421B-9BD8-24FED217FD7F +# +# And this forum post: +# https://forums.autodesk.com/t5/maya-forum/install-maya-2023-update-3-on-rocky-linux-8-7-instructions/td-p/11735138 + +RUN dnf install --assumeyes https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ + && dnf install --assumeyes epel-release \ + && dnf update --assumeyes \ + && dnf makecache + +# General packages +RUN dnf install --assumeyes \ + glibc \ + libSM \ + libICE \ + zlib \ + openssl-libs \ + nss \ + libnsl \ + dbus \ + redhat-lsb-core \ + pcre-utf16 \ + pciutils \ + libXdamage + +# Multimedia Packages +RUN dnf install --assumeyes \ + mesa-libGL \ + mesa-libGL-devel \ + mesa-libGLU \ + mesa-libGLw \ + gamin \ + audiofile-devel \ + e2fsprogs-libs \ + libcap \ + libdrm \ + libmng \ + speech-dispatcher \ + cups \ + libpng15 + +# X Window – Xcb – X11 Packages +RUN dnf install --assumeyes \ + libX11 \ + libXScrnSaver \ + libXau \ + libXcomposite \ + libXcursor \ + libXext \ + libXfixes \ + libXi \ + libXinerama \ + libXmu \ + libXp \ + libXpm \ + libXrandr \ + libXrender \ + libXt \ + libXtst \ + libxcb \ + libxkbcommon \ + libxkbcommon-x11 \ + libxshmfence \ + xcb-util \ + xcb-util-image \ + xcb-util-keysyms \ + xcb-util-renderutil \ + xcb-util-wm \ + xorg-x11-server-Xorg \ + xorg-x11-server-Xvfb + +# Install fonts needed by Maya. +# This is probably only needed by the GUI (which we will not open), +# but it's good to have everything needed, just in case. +RUN dnf install --assumeyes \ + fontconfig \ + freetype \ + xorg-x11-fonts-ISO8859-1-100dpi \ + xorg-x11-fonts-ISO8859-1-75dpi \ + liberation-mono-fonts \ + liberation-fonts-common \ + liberation-sans-fonts \ + liberation-serif-fonts + +# OpenSource "mesa" OpenGL Driver. +RUN dnf install --assumeyes \ + mesa-libGLw \ + mesa-libGLU \ + mesa-libGL-devel \ + mesa-libEGL-devel \ + mesa-libGLES-devel \ + mesa-libGLU-devel \ + mesa-libGLw-devel \ + libglvnd \ + libglvnd-opengl \ + libglvnd-egl \ + libglvnd-glx \ + libglvnd-gles \ + libglvnd-core-devel \ + libglvnd-devel + +# Install latest stable Rust with 'rustup'. +# +# TODO: Define a minimum Rust version to install. +RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ + && source ${HOME}/.cargo/env + +# Development tools for Maya 2025. +RUN dnf install --assumeyes \ + git \ + python39 \ + cmake \ + clang-tools-extra \ + gcc-toolset-11 + +# Install Maya from archive. +ADD ./external/archives/Autodesk_Maya_2025_1_Update_Linux.tgz /temp +# 'rpm --force' is needed to override the dependency conflict of using +# 'tcl' (which is required by 'gcc-toolset-11', because it's a +# "software collection" using environment modules written in tcl). +RUN rpm -Uvh --force /temp/Packages/Maya2025*.rpm && rm -r /temp +ENV MAYA_LOCATION=/usr/autodesk/maya/ +ENV PATH=$MAYA_LOCATION/bin:$PATH + +# Workaround for Maya "Segmentation fault (core dumped)" issue. +# See https://forums.autodesk.com/t5/maya-general/render-crash-on-linux/m-p/5608552/highlight/true +ENV MAYA_DISABLE_CIP=1 + +WORKDIR /mmSolver + +# Maya 2025 development environment. +ENTRYPOINT [ "scl", "enable", "gcc-toolset-11", "bash" ] From d54ac96875d9dbb48933c69b25e7f08076d4b97e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 00:11:31 +1000 Subject: [PATCH 100/295] mmImagePlaneShape2 - Update values in attribute editor. This adds scaffolding to the mmImagePlaneShap2 attribute editor template so that the values of the image cache section can be controlled via a Python module. This is really just a stub and will be updated in future with proper values, etc. --- .../AEmmImagePlaneShape2Template.mel | 384 ++++++++++++++---- python/mmSolver/tools/imagecache/constant.py | 9 + python/mmSolver/tools/imagecache/lib.py | 107 ++++- .../tools/imagecache/ui/imagecache_window.py | 19 +- 4 files changed, 416 insertions(+), 103 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index f7d359975..a675306e2 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -235,112 +235,324 @@ global proc AEmmImagePlaneShape2_imageSequenceReplace(string $file_attr) } +global proc AEmmImagePlaneShape2_imageCache_updateValues( + string $image_plane_shp) +{ + string $image_seq_size_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $image_seq_size_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $image_seq_size_cmd += "lib.format_image_sequence_size(image_plane_shp);"; + + string $cache_gpu_used_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cache_gpu_used_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $cache_gpu_used_cmd += "lib.format_cache_gpu_used(image_plane_shp);"; + + string $cache_cpu_used_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cache_cpu_used_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $cache_cpu_used_cmd += "lib.format_cache_cpu_used(image_plane_shp);"; + + string $cache_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cache_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $cache_available_cmd += "lib.format_cache_available(image_plane_shp);"; + + string $image_seq_size = python($image_seq_size_cmd); + string $cache_gpu_used = python($cache_gpu_used_cmd); + string $cache_cpu_used = python($cache_cpu_used_cmd); + string $cache_available = python($cache_available_cmd); + + text -edit -label $image_seq_size imageCacheImageSequenceSizeText; + text -edit -label $cache_gpu_used imageCacheCacheGpuUsedText; + text -edit -label $cache_cpu_used imageCacheCacheCpuUsedText; + text -edit -label $cache_available imageCacheCacheAvailableText; +} + + +global proc AEmmImagePlaneShape2_imageCacheDisplaySectionNew(string $attr_name) +{ + setUITemplate -pst attributeEditorTemplate; + + symbolButton -image "TypeResetAll.png" imageCacheUpdateValuesButton; + + // The total memory size for the image sequence (GB / MB) + rowLayout -nc 2 imageCacheImageSequenceSizeLayout; + { + text -label "Image Sequence Size:"; + text -align "left" imageCacheImageSequenceSizeText; + } + setParent ..; + + rowLayout -nc 2 imageCacheCacheGpuUsedLayout; + { + text -label "GPU Cache Used:"; + text -align "left" imageCacheCacheGpuUsedText; + } + setParent ..; + + rowLayout -nc 2 imageCacheCacheCpuUsedLayout; + { + text -label "CPU Cache Used:"; + text -align "left" imageCacheCacheCpuUsedText; + } + setParent ..; + + rowLayout -nc 2 imageCacheCacheAvailableLayout; + { + text -label "Total Memory Available:"; + text -align "left" imageCacheCacheAvailableText; + } + setParent ..; + + setUITemplate -ppt; + + AEmmImagePlaneShape2_imageCacheDisplaySectionReplace $attr_name; +} + + +global proc AEmmImagePlaneShape2_imageCacheDisplaySectionReplace(string $attr_name) +{ + string $image_plane_shp[]; + tokenize($attr_name, ".", $image_plane_shp); + if(size($image_plane_shp) < 1) { + return; + } + + AEmmImagePlaneShape2_imageCache_updateValues($image_plane_shp[0]); + + string $update_cmd = "AEmmImagePlaneShape2_imageCache_updateValues " + $image_plane_shp[0]; + symbolButton -edit -command $update_cmd imageCacheUpdateValuesButton; +} + + +global proc AEmmImagePlaneShape2_imageCache_clearAllSlots( + string $image_plane_shp) +{ + string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cmd += "import mmSolver.tools.imagecache.constant as const;"; + $cmd += "lib.cache_remove_all_image_plane_slots("; + $cmd += " const.CACHE_TYPE_ALL, "; + $cmd += " \"" + $image_plane_shp + "\""; + $cmd += ");"; + python($cmd); +} + + +global proc AEmmImagePlaneShape2_imageCache_clearActiveSlot( + string $image_plane_shp) +{ + string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cmd += "import mmSolver.tools.imagecache.constant as const;"; + $cmd += "lib.cache_remove_active_image_plane_slot("; + $cmd += " const.CACHE_TYPE_ALL, "; + $cmd += " \"" + $image_plane_shp + "\""; + $cmd += ");"; + python($cmd); +} + + +global proc AEmmImagePlaneShape2_imageCache_clearUnusedSlots( + string $image_plane_shp) +{ + string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cmd += "import mmSolver.tools.imagecache.constant as const;"; + $cmd += "lib.cache_remove_unused_image_plane_slots("; + $cmd += " const.CACHE_TYPE_ALL, "; + $cmd += " \"" + $image_plane_shp + "\""; + $cmd += ");"; + python($cmd); +} + + +global proc AEmmImagePlaneShape2_imageCache_clearAllImages( + string $image_plane_shp) +{ + string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $cmd += "import mmSolver.tools.imagecache.constant as const;"; + $cmd += "lib.cache_remove_all(const.CACHE_TYPE_ALL);"; + python($cmd); +} + + +global proc AEmmImagePlaneShape2_imageCache_openPreferences( + string $image_plane_shp) +{ + string $cmd = "import mmSolver.tools.imagecache.tool as tool;"; + $cmd += "tool.main();"; + python($cmd); +} + + +global proc AEmmImagePlaneShape2_imageCacheClearSectionNew(string $attr_name) +{ + setUITemplate -pst attributeEditorTemplate; + + text -label ""; + + // Clear buttons and Image Cache preferences. + rowLayout -nc 3 imageCacheClearLayout; + { + // Spacer to avoid button on the screen-left of the Attribute + // Editor. + text -label ""; + + button -label "Clear..." imageCacheClearButton; + int $left_mouse_button = 1; + popupMenu -button $left_mouse_button; + menuItem -label "All image slots" imageCacheClearAllSlotsMenuItem; + menuItem -label "Active image slot" imageCacheClearActiveSlotMenuItem; + menuItem -label "Unused image slots" imageCacheClearUnusedImageSlotsMenuItem; + menuItem -divider true; + menuItem -label "All images in cache" imageCacheClearAllImagesMenuItem; + + button -label "Preferences..." imageCachePreferencesButton; + } + setParent ..; + + setUITemplate -ppt; + + AEmmImagePlaneShape2_imageCacheClearSectionReplace $attr_name; +} + + +global proc AEmmImagePlaneShape2_imageCacheClearSectionReplace(string $attr_name) +{ + string $image_plane_shp[]; + tokenize($attr_name, ".", $image_plane_shp); + if(size($image_plane_shp) < 1) { + return; + } + + string $clear_all_slots_cmd = "AEmmImagePlaneShape2_imageCache_clearAllSlots " + $image_plane_shp[0]; + string $clear_active_slot_cmd = "AEmmImagePlaneShape2_imageCache_clearActiveSlot " + $image_plane_shp[0]; + string $clear_unused_cmd = "AEmmImagePlaneShape2_imageCache_clearUnusedSlots " + $image_plane_shp[0]; + string $clear_all_cmd = "AEmmImagePlaneShape2_imageCache_clearAllImages " + $image_plane_shp[0]; + + string $open_preferences_cmd = "AEmmImagePlaneShape2_imageCache_openPreferences " + $image_plane_shp[0]; + + menuItem -edit -command $clear_all_slots_cmd imageCacheClearAllSlotsMenuItem; + menuItem -edit -command $clear_active_slot_cmd imageCacheClearActiveSlotMenuItem; + menuItem -edit -command $clear_unused_cmd imageCacheClearUnusedImageSlotsMenuItem; + menuItem -edit -command $clear_all_cmd imageCacheClearAllImagesMenuItem; + + button -edit -command $open_preferences_cmd imageCachePreferencesButton; +} + + global proc AEmmImagePlaneShape2Template(string $nodeName) { AEmmNodeShapeTemplateCommonBegin($nodeName); editorTemplate -beginLayout "Display" -collapse 0; - editorTemplate -addControl "visibleToCameraOnly"; - editorTemplate -addSeparator; - editorTemplate -addControl "colorGain"; - editorTemplate -addControl "colorExposure"; - editorTemplate -addControl "colorGamma"; - editorTemplate -addControl "colorSaturation"; - editorTemplate -addControl "colorSoftClip"; - editorTemplate -addControl "alphaGain"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageIgnoreAlpha"; - editorTemplate -addControl "displayChannel"; + { + editorTemplate -addControl "visibleToCameraOnly"; + editorTemplate -addSeparator; + editorTemplate -addControl "colorGain"; + editorTemplate -addControl "colorExposure"; + editorTemplate -addControl "colorGamma"; + editorTemplate -addControl "colorSaturation"; + editorTemplate -addControl "colorSoftClip"; + editorTemplate -addControl "alphaGain"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageIgnoreAlpha"; + editorTemplate -addControl "displayChannel"; + } editorTemplate -endLayout; editorTemplate -beginLayout "Image Sequence" -collapse 0; - - editorTemplate - -callCustom - "AEmmImagePlaneShape2_sequenceSlotNew" - "AEmmImagePlaneShape2_sequenceSlotReplace" - "imageSequenceSlot"; - - editorTemplate - -callCustom - "AEmmImagePlaneShape2_imageSequenceNew" - "AEmmImagePlaneShape2_imageSequenceReplace" - "imageSequenceMain"; - editorTemplate - -callCustom - "AEmmImagePlaneShape2_imageSequenceNew" - "AEmmImagePlaneShape2_imageSequenceReplace" - "imageSequenceAlternate1"; - editorTemplate - -callCustom - "AEmmImagePlaneShape2_imageSequenceNew" - "AEmmImagePlaneShape2_imageSequenceReplace" - "imageSequenceAlternate2"; - editorTemplate - -callCustom - "AEmmImagePlaneShape2_imageSequenceNew" - "AEmmImagePlaneShape2_imageSequenceReplace" - "imageSequenceAlternate3"; - - editorTemplate -addSeparator; - editorTemplate -addControl "imageWidth"; - editorTemplate -addControl "imageHeight"; - editorTemplate -addControl "imagePixelAspect"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageSequenceStartFrame"; - editorTemplate -addControl "imageSequenceEndFrame"; - editorTemplate -addSeparator; - // TODO: Use mmColorIO results to allow users to manually give the - // inputColorSpace. - editorTemplate -addControl "inputColorSpace"; - editorTemplate -addSeparator; - // TODO: Add radio button to choose what will connect to the - // 'imageSequenceFrame' value? Options are: - // - Scene Time (time1) - // - Animation Curve - // - editorTemplate -addControl "imageSequenceFrame"; - editorTemplate -addControl "imageSequenceFirstFrame"; - editorTemplate -addControl "imageSequenceFrameOutput"; - editorTemplate -addSeparator; - editorTemplate -addControl "imageFlip"; - editorTemplate -addControl "imageFlop"; + { + editorTemplate + -callCustom + "AEmmImagePlaneShape2_sequenceSlotNew" + "AEmmImagePlaneShape2_sequenceSlotReplace" + "imageSequenceSlot"; + + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceMain"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate1"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate2"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageSequenceNew" + "AEmmImagePlaneShape2_imageSequenceReplace" + "imageSequenceAlternate3"; + + editorTemplate -addSeparator; + editorTemplate -addControl "imageWidth"; + editorTemplate -addControl "imageHeight"; + editorTemplate -addControl "imagePixelAspect"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageSequenceStartFrame"; + editorTemplate -addControl "imageSequenceEndFrame"; + editorTemplate -addSeparator; + // TODO: Use mmColorIO results to allow users to manually give the + // inputColorSpace. + // + // TODO: Add a custom script with a button and pop-up menu to + // choose the color space. + editorTemplate -addControl "inputColorSpace"; + editorTemplate -addSeparator; + // TODO: Add radio button to choose what will connect to the + // 'imageSequenceFrame' value? Options are: + // - Scene Time (time1) + // - Animation Curve + // + editorTemplate -addControl "imageSequenceFrame"; + editorTemplate -addControl "imageSequenceFirstFrame"; + editorTemplate -addControl "imageSequenceFrameOutput"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageFlip"; + editorTemplate -addControl "imageFlop"; + } editorTemplate -endLayout; editorTemplate -beginLayout "HUD" -collapse 0; - editorTemplate -addControl "drawHud"; - editorTemplate -addSeparator; - editorTemplate -addControl "drawCameraSize"; - editorTemplate -addControl "drawImageSize"; - // TODO: Add 'hudTextColor' - control the HUD text color. + { + editorTemplate -addControl "drawHud"; + editorTemplate -addSeparator; + editorTemplate -addControl "drawCameraSize"; + editorTemplate -addControl "drawImageSize"; + // TODO: Add 'hudTextColor' - control the HUD text color. + } editorTemplate -endLayout; editorTemplate -beginLayout "Image Cache" -collapse 1; - // TODO: Add controls to view and edit the image cache. - // - // Add: - // - Display: - // - the current image size for one frame (MB) - // - the total memory size for the image sequence (GB / MB) - // - the used cache size. - // - Update button. - // - Clear cache button menu - // - Clear image plane. - // - Clear unused contents. - // - Clear all. - // + { + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageCacheDisplaySectionNew" + "AEmmImagePlaneShape2_imageCacheDisplaySectionReplace" + ""; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_imageCacheClearSectionNew" + "AEmmImagePlaneShape2_imageCacheClearSectionReplace" + ""; + } editorTemplate -endLayout; editorTemplate -beginLayout "Miscellaneous" -collapse 1; - editorTemplate -addControl "meshResolution"; - editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. - editorTemplate -addControl "shaderIsTransparent"; + { + editorTemplate -addControl "meshResolution"; + editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. + editorTemplate -addControl "shaderIsTransparent"; + } editorTemplate -endLayout; editorTemplate -beginLayout "Nodes" -collapse 1; - editorTemplate -addControl "geometryNode"; - editorTemplate -addControl "cameraNode"; - editorTemplate -addControl "imagePlaneShapeNode"; + { + editorTemplate -addControl "geometryNode"; + editorTemplate -addControl "cameraNode"; + editorTemplate -addControl "imagePlaneShapeNode"; + } editorTemplate -endLayout; // Internals that we don't want the user to see. diff --git a/python/mmSolver/tools/imagecache/constant.py b/python/mmSolver/tools/imagecache/constant.py index d64b7f56e..0870acf01 100644 --- a/python/mmSolver/tools/imagecache/constant.py +++ b/python/mmSolver/tools/imagecache/constant.py @@ -20,3 +20,12 @@ """ WINDOW_TITLE = 'mmSolver Image Cache' + +CACHE_TYPE_ALL = 'all' +CACHE_TYPE_GPU = 'gpu' +CACHE_TYPE_CPU = 'cpu' +CACHE_TYPE_VALUES = [ + CACHE_TYPE_ALL, + CACHE_TYPE_GPU, + CACHE_TYPE_CPU, +] diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index e16beb184..ad7305b5f 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -23,38 +23,113 @@ from __future__ import division from __future__ import print_function +import maya.cmds + import mmSolver.logger +import mmSolver.tools.imagecache.constant as const LOG = mmSolver.logger.get_logger() -CACHE_TYPE_ALL = 'all' -CACHE_TYPE_GPU = 'gpu' -CACHE_TYPE_CPU = 'cpu' -CACHE_TYPE_VALUES = [ - CACHE_TYPE_ALL, - CACHE_TYPE_GPU, - CACHE_TYPE_CPU, -] +def format_image_sequence_size(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'format_image_sequence_size: image_plane_shp=%r', + image_plane_shp, + ) + # TODO: Calculate this string. + return '2,346MB (23MB x 102 frames)' -def cache_remove_image_plane_contents(cache_type): - pass +def format_cache_gpu_used(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'format_cache_gpu_used: image_plane_shp=%r', + image_plane_shp, + ) + # TODO: Calculate this string. + return '42% (3.5GB) of 8GB' -def cache_remove_image_sequence(file_pattern, start_frame, end_frame, cache_type): - pass +def format_cache_cpu_used(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'format_cache_cpu_used: image_plane_shp=%r', + image_plane_shp, + ) + # TODO: Calculate this string. + return '23% (34GB) of 240GB' + + +def format_cache_available(image_plane_shp): + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'format_cache_available: image_plane_shp=%r', + image_plane_shp, + ) + # TODO: Calculate this string. + return 'CPU: 240GB | GPU: 8GB' + + +def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'cache_remove_all_image_plane_slots: image_plane_shp=%r, cache_type=%r', + image_plane_shp, + cache_type, + ) + return + + +def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'cache_remove_active_image_plane_slot: image_plane_shp=%r, cache_type=%r', + image_plane_shp, + cache_type, + ) + return + + +def cache_remove_unused_image_plane_slots(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + LOG.info( + 'cache_remove_unused_image_plane_slots: image_plane_shp=%r, cache_type=%r', + image_plane_shp, + cache_type, + ) + return def cache_remove_all(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES + LOG.info( + 'cache_remove_unused_image_plane_slots: cache_type=%r', + cache_type, + ) + return + + +def cache_remove_image_sequence(file_pattern, start_frame, end_frame, cache_type): + LOG.info( + 'cache_remove_image_sequence: ' + 'file_pattern=%r, start_frame=%r, end_frame=%r, cache_type=%r', + file_pattern, + start_frame, + end_frame, + cache_type, + ) pass def cache_remove_all_inactive(cache_type): # Removes all the items in the cache that cannot be 'reached' by # any of the image planes. - pass - - -def function(): + LOG.info( + 'cache_remove_all_inactive: cache_type=%r', + cache_type, + ) pass diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_window.py b/python/mmSolver/tools/imagecache/ui/imagecache_window.py index fd942aa9c..b5fb73f7a 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_window.py +++ b/python/mmSolver/tools/imagecache/ui/imagecache_window.py @@ -66,10 +66,13 @@ def __init__(self, parent=None, name=None): # Standard Buttons self.baseHideStandardButtons() self.applyBtn.show() + self.resetBtn.show() self.closeBtn.show() self.applyBtn.setText('Apply') + self.resetBtn.setText('Clear Cache') - # self.applyBtn.clicked.connect(tool.do_cache_things) + self.applyBtn.clicked.connect(self._apply) + self.resetBtn.clicked.connect(self._clear_cache) # Hide irrelevant stuff self.baseHideProgressBar() @@ -88,6 +91,20 @@ def add_menus(self, menubar): commonmenus.create_help_menu_items(help_menu, tool_help_func=_open_help) menubar.addMenu(help_menu) + def _apply(self): + # 1) Get the widget value. + # 2) Convert percentage into byte count. + # 3) Set the ImageCache capacity (CPU and GPU) + LOG.info('Set Capacity...') + return + + def _clear_cache(self): + # 1) Get the ImageCache capacities (CPU and GPU). + # 2) Set the ImageCache capacity (CPU and GPU) to zero. + # 3) Restore the ImageCache capacities (CPU and GPU). + LOG.info('Clear Cache...') + return + def reset_options(self): form = self.getSubForm() form.reset_options() From a1c0f508dbb238292f9ed632ffaa40be948d9ee9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 01:37:30 +1000 Subject: [PATCH 101/295] mmImagePlane - Compute memory values for attribute editor --- .../AEmmImagePlaneShape2Template.mel | 16 +++- .../createimageplane/_lib/mmimageplane_v2.py | 24 ++++++ python/mmSolver/tools/imagecache/lib.py | 74 +++++++++++++++++-- 3 files changed, 103 insertions(+), 11 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index a675306e2..2400524a3 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -555,10 +555,20 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) } editorTemplate -endLayout; + + editorTemplate -beginLayout "Extended Image Details" -collapse 1; + { + editorTemplate -addControl "imageNumChannels"; + editorTemplate -addControl "imageBytesPerChannel"; + editorTemplate -addControl "imageSizeBytes"; + + // editorTemplate -suppress "imageNumChannels"; + // editorTemplate -suppress "imageBytesPerChannel"; + // editorTemplate -suppress "imageSizeBytes"; + } + editorTemplate -endLayout; + // Internals that we don't want the user to see. - editorTemplate -suppress "imageNumChannels"; - editorTemplate -suppress "imageBytesPerChannel"; - editorTemplate -suppress "imageSizeBytes"; editorTemplate -suppress "imageSequencePadding"; editorTemplate -suppress "cameraWidthInch"; editorTemplate -suppress "cameraHeightInch"; diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index db6b9dd34..27cc5c52a 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -307,6 +307,30 @@ def set_image_sequence(shp, image_sequence_path, attr_name): return +def get_frame_size_bytes(shp): + assert isinstance(shp, str) + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + image_size_bytes = maya.cmds.getAttr(shp + '.imageSizeBytes') + return int(image_size_bytes) + + +def get_frame_count(shp): + assert isinstance(shp, str) + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + start_frame = maya.cmds.getAttr(shp + '.imageSequenceStartFrame') + end_frame = maya.cmds.getAttr(shp + '.imageSequenceEndFrame') + frame_count = end_frame - start_frame + return frame_count + + +def get_image_sequence_size_bytes(shp): + assert isinstance(shp, str) + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + image_size_bytes = get_frame_size_bytes(shp) + frame_count = get_frame_count(shp) + return image_size_bytes * frame_count + + def get_shape_node(image_plane_tfm): assert maya.cmds.nodeType(image_plane_tfm) == lib_const.MM_IMAGE_PLANE_TRANSFORM diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index ad7305b5f..0c5d8f7ef 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -27,18 +27,61 @@ import mmSolver.logger import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib LOG = mmSolver.logger.get_logger() +# Memory Conversion +BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) +BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) +BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) +KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) +KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) + + def format_image_sequence_size(image_plane_shp): assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' LOG.info( 'format_image_sequence_size: image_plane_shp=%r', image_plane_shp, ) - # TODO: Calculate this string. - return '2,346MB (23MB x 102 frames)' + + frame_size_bytes = imageplane_lib.get_frame_size_bytes(image_plane_shp) + frame_count = imageplane_lib.get_frame_count(image_plane_shp) + image_sequence_size_bytes = imageplane_lib.get_image_sequence_size_bytes( + image_plane_shp + ) + + seq_size_mb = int(image_sequence_size_bytes / BYTES_TO_MEGABYTES) + frame_size_mb = int(frame_size_bytes / BYTES_TO_MEGABYTES) + text = '2,346MB (23MB x 102 frames)' + text = '{seq_size_mb}MB (frame_size_mb)MB x {frame_count} frames)' + return text.format( + seq_size_mb=seq_size_mb, frame_size_mb=frame_size_mb, frame_count=frame_count + ) + + +def _format_cache_used(used_bytes, capacity_bytes): + assert isinstance(used_bytes, int) + assert isinstance(capacity_bytes, int) + + usage_percent = 0 + usage_gigabytes = 0 + capacity_gigabyte = 0 + if capacity_bytes > 0: + usage_percent = used_bytes / capacity_bytes + usage_gigabytes = used_bytes / BYTES_TO_GIGABYTES + capacity_gigabyte = capacity_bytes / BYTES_TO_GIGABYTES + + # TODO: Limit the percentage of the gigabyte float values to only + # 1 or 2 digits of precision. + text = '{usage_percent} ({usage_gigabytes}GB) of {capacity_gigabyte}GB' + return text.format( + usage_percent=usage_percent, + usage_gigabytes=usage_gigabytes, + capacity_gigabyte=capacity_gigabyte, + ) def format_cache_gpu_used(image_plane_shp): @@ -47,8 +90,11 @@ def format_cache_gpu_used(image_plane_shp): 'format_cache_gpu_used: image_plane_shp=%r', image_plane_shp, ) - # TODO: Calculate this string. - return '42% (3.5GB) of 8GB' + + text = '42% (3.5GB) of 8GB' + used_bytes = int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) + capacity_bytes = int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) + return _format_cache_used(used_bytes, capacity_bytes) def format_cache_cpu_used(image_plane_shp): @@ -57,8 +103,11 @@ def format_cache_cpu_used(image_plane_shp): 'format_cache_cpu_used: image_plane_shp=%r', image_plane_shp, ) - # TODO: Calculate this string. - return '23% (34GB) of 240GB' + + text = '23% (34GB) of 240GB' + used_bytes = int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) + capacity_bytes = int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) + return _format_cache_used(used_bytes, capacity_bytes) def format_cache_available(image_plane_shp): @@ -67,8 +116,17 @@ def format_cache_available(image_plane_shp): 'format_cache_available: image_plane_shp=%r', image_plane_shp, ) - # TODO: Calculate this string. - return 'CPU: 240GB | GPU: 8GB' + + cpu_memory_gigabytes = maya.cmds.mmMemorySystem(query=True, systemPhysicalMemoryTotal=True, asGigaBytes=True) + gpu_memory_gigabytes = maya.cmds.mmMemoryGPU(query=True, total=True, asGigaBytes=True) + + text = 'CPU: 240GB | GPU: 8GB' + # TODO: Round these values to the closest value. + text = 'CPU: {cpu_memory_gigabytes}GB | GPU: {gpu_memory_gigabytes}GB' + return text.format( + cpu_memory_gigabytes=cpu_memory_gigabytes, + gpu_memory_gigabytes=gpu_memory_gigabytes + ) def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): From a97c5e9ad759dd6e58e655ff113a3f57f1dafb01 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 20:58:19 +1000 Subject: [PATCH 102/295] Upgrade CXX to 1.0.124 CXX is used to generate Rust/C++ bindings. The same version of CXX must be used across the project. We are updating just to stay update and not fall behind. There are no expected features that are needed from this update. I am hoping this will resolve linking/building issues but initial tests seem to suggest that won't happen. This is the latest version (1.0.124) as of today (2024-06-20). --- lib/cppbind/mmcore/Cargo.toml | 4 +- lib/cppbind/mmimage/Cargo.toml | 4 +- lib/cppbind/mmlens/Cargo.toml | 4 +- lib/cppbind/mmscenegraph/Cargo.toml | 4 +- lib/mmsolverlibs/Cargo.lock | 53 ++++++++----------- .../internal/build_mmSolverLibs_linux.bash | 2 +- .../internal/build_mmSolverLibs_windows64.bat | 4 +- 7 files changed, 32 insertions(+), 43 deletions(-) diff --git a/lib/cppbind/mmcore/Cargo.toml b/lib/cppbind/mmcore/Cargo.toml index 58cc09621..fd64e6465 100644 --- a/lib/cppbind/mmcore/Cargo.toml +++ b/lib/cppbind/mmcore/Cargo.toml @@ -12,8 +12,8 @@ path = "./src/lib.rs" [dependencies] # NOTE: When changing this version number ensure to also update the # installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_rust_library_*.*' -cxx = "=1.0.75" +# here: './scripts/internal/build_mmSolverLibs_*.*' +cxx = "=1.0.124" [dependencies.mmcore_rust] path = "../../rust/mmcore" diff --git a/lib/cppbind/mmimage/Cargo.toml b/lib/cppbind/mmimage/Cargo.toml index 4900b6fb3..b7669fafc 100644 --- a/lib/cppbind/mmimage/Cargo.toml +++ b/lib/cppbind/mmimage/Cargo.toml @@ -12,8 +12,8 @@ path = "./src/lib.rs" [dependencies] # NOTE: When changing this version number ensure to also update the # installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_rust_library_*.*' -cxx = "=1.0.75" +# here: './scripts/internal/build_mmSolverLibs_*.*' +cxx = "=1.0.124" [dependencies.mmimage_rust] path = "../../rust/mmimage" diff --git a/lib/cppbind/mmlens/Cargo.toml b/lib/cppbind/mmlens/Cargo.toml index dbcf9543c..4b303e53c 100644 --- a/lib/cppbind/mmlens/Cargo.toml +++ b/lib/cppbind/mmlens/Cargo.toml @@ -13,8 +13,8 @@ path = "./src/lib.rs" anyhow = "1.0.71" # NOTE: When changing this version number ensure to also update the # installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_rust_library_*.*' -cxx = "=1.0.75" +# here: './scripts/internal/build_mmSolverLibs_*.*' +cxx = "=1.0.124" num_cpus = "1.15.0" rayon = "1.7.0" smallvec = "1.10.0" diff --git a/lib/cppbind/mmscenegraph/Cargo.toml b/lib/cppbind/mmscenegraph/Cargo.toml index c2d19524c..18a62a8ad 100644 --- a/lib/cppbind/mmscenegraph/Cargo.toml +++ b/lib/cppbind/mmscenegraph/Cargo.toml @@ -16,8 +16,8 @@ path = "./src/lib.rs" [dependencies] # NOTE: When changing this version number ensure to also update the # installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_rust_library_*.*' -cxx = "=1.0.75" +# here: './scripts/internal/build_mmSolverLibs_*.*' +cxx = "=1.0.124" [dependencies.mmscenegraph_rust] path = "../../rust/mmscenegraph/" diff --git a/lib/mmsolverlibs/Cargo.lock b/lib/mmsolverlibs/Cargo.lock index 59d883a3b..1a3688314 100644 --- a/lib/mmsolverlibs/Cargo.lock +++ b/lib/mmsolverlibs/Cargo.lock @@ -75,9 +75,9 @@ checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "cfg-if" @@ -136,9 +136,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "cxx" -version = "1.0.75" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7df2292959b7e22a5cb39d37b7e72b2c748b12f956cc409b529fddcdc8857b" +checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" dependencies = [ "cc", "cxxbridge-flags", @@ -148,19 +148,19 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.75" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2069b1573efd6e5901004e8fdca2e28bc6f47f86dc24da81182851e71cf3208" +checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" [[package]] name = "cxxbridge-macro" -version = "1.0.75" +version = "1.0.124" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d980827d1ec28ea6e0db545fceaa611eb8e43f70eff8c1c33cc2c96ffa0f0476" +checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -334,9 +334,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" dependencies = [ "cc", ] @@ -636,7 +636,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -647,18 +647,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.27" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -833,20 +833,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.16" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -870,7 +859,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", ] [[package]] @@ -918,7 +907,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.16", + "syn", "wasm-bindgen-shared", ] @@ -940,7 +929,7 @@ checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.16", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/scripts/internal/build_mmSolverLibs_linux.bash b/scripts/internal/build_mmSolverLibs_linux.bash index f398b55a8..d302a009d 100644 --- a/scripts/internal/build_mmSolverLibs_linux.bash +++ b/scripts/internal/build_mmSolverLibs_linux.bash @@ -112,7 +112,7 @@ then # './lib/cppbind/mmlens/Cargo.toml' # './lib/cppbind/mmcore/Cargo.toml' # './scripts/internal/build_mmSolverLibs_windows64.bat' - ${RUST_CARGO_EXE} install cxxbridge-cmd --version 1.0.75 + ${RUST_CARGO_EXE} install cxxbridge-cmd --version 1.0.124 fi MMSOLVERLIBS_CXXBRIDGE_EXE="${HOME}/.cargo/bin/cxxbridge" diff --git a/scripts/internal/build_mmSolverLibs_windows64.bat b/scripts/internal/build_mmSolverLibs_windows64.bat index 02a860107..a076653b1 100644 --- a/scripts/internal/build_mmSolverLibs_windows64.bat +++ b/scripts/internal/build_mmSolverLibs_windows64.bat @@ -106,8 +106,8 @@ IF ERRORLEVEL 1 ( :: './lib/cppbind/mmlens/Cargo.toml' :: './lib/cppbind/mmcore/Cargo.toml' :: './scripts/internal/build_mmSolverLibs_windows64.bat' - :: './scripts/internal/build_rust_library_linux.bash' - %RUST_CARGO_EXE% install cxxbridge-cmd --version 1.0.75 + :: './scripts/internal/build_mmSolverLibs_linux.bash' + %RUST_CARGO_EXE% install cxxbridge-cmd --version 1.0.124 ) SET MMSOLVERLIBS_CXXBRIDGE_EXE="%USERPROFILE%\.cargo\bin\cxxbridge.exe" :: Convert back-slashes to forward-slashes. From a767eedec71c43a31b5396633318b37de25912ad Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 21:12:04 +1000 Subject: [PATCH 103/295] Update rust crate dependencies pegraph from 0.5 to 0.6 nalgebra from 0.29 to 0.32.2 criterion from 0.3.6 to 0.5.1 rand from 0.7 to 0.8.5 rustc-hash from 1.1.0 to 2.0.0 approx from 0.3.2 to 0.5.1 Just to try and keep up to date with latest fixes and performance improvements - if there are any. --- lib/cppbind/mmlens/Cargo.toml | 2 +- lib/mmsolverlibs/Cargo.lock | 109 ++++++++++--------------------- lib/rust/mmscenegraph/Cargo.toml | 12 ++-- 3 files changed, 41 insertions(+), 82 deletions(-) diff --git a/lib/cppbind/mmlens/Cargo.toml b/lib/cppbind/mmlens/Cargo.toml index 4b303e53c..792bb4b83 100644 --- a/lib/cppbind/mmlens/Cargo.toml +++ b/lib/cppbind/mmlens/Cargo.toml @@ -18,7 +18,7 @@ cxx = "=1.0.124" num_cpus = "1.15.0" rayon = "1.7.0" smallvec = "1.10.0" -rustc-hash = "1.1.0" +rustc-hash = "2.0.0" [profile.release] opt-level = 3 diff --git a/lib/mmsolverlibs/Cargo.lock b/lib/mmsolverlibs/Cargo.lock index 1a3688314..81ee2011a 100644 --- a/lib/mmsolverlibs/Cargo.lock +++ b/lib/mmsolverlibs/Cargo.lock @@ -14,15 +14,6 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "approx" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" -dependencies = [ - "num-traits", -] - [[package]] name = "approx" version = "0.5.1" @@ -190,6 +181,12 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "exr" version = "1.6.3" @@ -214,9 +211,9 @@ checksum = "d0031c93f37b5d18272de2d932ebff6a7eb32d4bc3bab6751a9af42da7d1a424" [[package]] name = "fixedbitset" -version = "0.2.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flume" @@ -243,17 +240,6 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.9" @@ -263,7 +249,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -278,9 +264,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" @@ -293,11 +279,11 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "autocfg", + "equivalent", "hashbrown", ] @@ -452,7 +438,7 @@ dependencies = [ name = "mmscenegraph_rust" version = "0.1.0" dependencies = [ - "approx 0.3.2", + "approx", "fastapprox", "log", "nalgebra", @@ -474,11 +460,11 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.29.0" +version = "0.32.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d506eb7e08d6329505faa8a3a00a5dcc6de9f76e0c77e4b75763ae3c770831ff" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" dependencies = [ - "approx 0.5.1", + "approx", "matrixmultiply", "num-complex", "num-rational", @@ -493,7 +479,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" dependencies = [ - "getrandom 0.2.9", + "getrandom", ] [[package]] @@ -611,9 +597,9 @@ checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "petgraph" -version = "0.5.1" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap", @@ -665,23 +651,20 @@ dependencies = [ [[package]] name = "rand" -version = "0.7.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "getrandom 0.1.16", "libc", "rand_chacha", "rand_core", - "rand_hc", - "rand_pcg", ] [[package]] name = "rand_chacha" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -689,29 +672,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.5.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core", + "getrandom", ] [[package]] @@ -748,7 +713,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "getrandom 0.2.9", + "getrandom", "libredox", "thiserror", ] @@ -761,9 +726,9 @@ checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "safe_arch" @@ -799,11 +764,11 @@ dependencies = [ [[package]] name = "simba" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0b7840f121a46d63066ee7a99fc81dcabbc6105e437cae43528cea199b5a05f" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" dependencies = [ - "approx 0.5.1", + "approx", "num-complex", "num-traits", "paste", @@ -874,12 +839,6 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/lib/rust/mmscenegraph/Cargo.toml b/lib/rust/mmscenegraph/Cargo.toml index 9d0347c32..f82ad8367 100644 --- a/lib/rust/mmscenegraph/Cargo.toml +++ b/lib/rust/mmscenegraph/Cargo.toml @@ -15,29 +15,29 @@ name = "bench" harness = false [dependencies] -approx = "0.3.2" +approx = "0.5.1" fastapprox = "0.3.0" -rustc-hash = "1.1.0" +rustc-hash = "2.0.0" log = "0.4.0" num-traits = "0.2" [dependencies.rand] -version = "0.7" +version = "0.8.5" default-features = false features = ["std", "alloc", "small_rng"] [dependencies.petgraph] -version = "0.5" +version = "0.6" default-features = false features = ["stable_graph"] [dependencies.nalgebra] -version = "0.29" +version = "0.32.2" default-features = false features = ["std", "matrixmultiply"] [dev-dependencies.criterion] -version = "0.3.6" +version = "0.5.1" default-features = false features = ["html_reports"] From 0d019f42962fdd16ab3d170e903fd39c5c690662 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 21:22:44 +1000 Subject: [PATCH 104/295] mmimage tests - Make functions non-static. Just use normal functions, there's no real reason not too. This is also intended to get rid of GCC 11 warnings. --- lib/cppbind/mmimage/tests/CMakeLists.txt | 1 + lib/cppbind/mmimage/tests/common.cpp | 117 +++++++++++++++++++++++ lib/cppbind/mmimage/tests/common.h | 99 ++----------------- 3 files changed, 126 insertions(+), 91 deletions(-) create mode 100644 lib/cppbind/mmimage/tests/common.cpp diff --git a/lib/cppbind/mmimage/tests/CMakeLists.txt b/lib/cppbind/mmimage/tests/CMakeLists.txt index 95b964a6e..8dcee79cc 100644 --- a/lib/cppbind/mmimage/tests/CMakeLists.txt +++ b/lib/cppbind/mmimage/tests/CMakeLists.txt @@ -22,6 +22,7 @@ set(target_test_exe_name "mmimage_tests") set(test_source_files ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/common.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_a.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_b.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test_c.cpp diff --git a/lib/cppbind/mmimage/tests/common.cpp b/lib/cppbind/mmimage/tests/common.cpp new file mode 100644 index 000000000..0006c1143 --- /dev/null +++ b/lib/cppbind/mmimage/tests/common.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2023 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + */ + +#include + +#include +#include +#include +#include + +std::string join_path(const char *arg1, const char *arg2) { + std::stringstream stream; + stream << arg1; + stream << arg2; + return stream.str(); +} + +std::string join_path(const char *arg1, const char *arg2, const char *arg3) { + std::stringstream stream; + stream << arg1; + stream << arg2; + stream << arg3; + return stream.str(); +} + +std::string join_path(const char *arg1, const char *arg2, const char *arg3, + const char *arg4) { + std::stringstream stream; + stream << arg1; + stream << arg2; + stream << arg3; + stream << arg4; + return stream.str(); +} + +bool test_print_metadata_named_attributes(const char *test_name, + mmimage::ImageMetaData &meta_data) { + rust::Vec all_attr_names = + meta_data.all_named_attribute_names(); + + std::cout << test_name << " attrs count: " << all_attr_names.size() + << std::endl; + for (auto value : all_attr_names) { + std::cout << test_name << " attr_name: " << value.c_str() << std::endl; + } + return true; +} + +bool test_print_metadata_fields(const char *test_name, + mmimage::ImageMetaData &meta_data) { + float image_pixel_aspect = meta_data.get_pixel_aspect(); + mmimage::ImageRegionRectangle image_display_window = + meta_data.get_display_window(); + rust::Str image_layer_name = meta_data.get_layer_name(); + mmimage::Vec2I32 image_layer_position = meta_data.get_layer_position(); + mmimage::Vec2F32 image_screen_window_center = + meta_data.get_screen_window_center(); + float image_screen_window_width = meta_data.get_screen_window_width(); + rust::Str image_owner = meta_data.get_owner(); + rust::Str image_comments = meta_data.get_comments(); + rust::Str image_capture_date = meta_data.get_capture_date(); + mmimage::OptionF32 image_utc_offset = meta_data.get_utc_offset(); + mmimage::OptionF32 image_longitude = meta_data.get_longitude(); + mmimage::OptionF32 image_latitude = meta_data.get_latitude(); + mmimage::OptionF32 image_altitude = meta_data.get_altitude(); + mmimage::OptionF32 image_focus = meta_data.get_focus(); + mmimage::OptionF32 image_exposure = meta_data.get_exposure(); + mmimage::OptionF32 image_aperture = meta_data.get_aperture(); + mmimage::OptionF32 image_iso_speed = meta_data.get_iso_speed(); + mmimage::OptionF32 image_frames_per_second = + meta_data.get_frames_per_second(); + std::cout << test_name << " image pixel_aspect: " << image_pixel_aspect + << std::endl + << test_name + << " image display_window: " << image_display_window.position_x + << ',' << image_display_window.position_y << ',' + << image_display_window.size_x << ',' + << image_display_window.size_y << std::endl + << test_name << " image owner: " << image_owner << std::endl + << test_name << " image comments: " << image_comments << std::endl + << test_name << " image capture_date: " << image_capture_date + << std::endl + << test_name << " image utc_offset: " << image_utc_offset + << std::endl + << test_name << " image longitude: " << image_longitude + << std::endl + << test_name << " image latitude: " << image_latitude << std::endl + << test_name << " image altitude: " << image_altitude << std::endl + << test_name << " image focus: " << image_focus << std::endl + << test_name << " image exposure: " << image_exposure << std::endl + << test_name << " image aperture: " << image_aperture << std::endl + << test_name << " image iso_speed: " << image_iso_speed + << std::endl + << test_name + << " image frames_per_second: " << image_frames_per_second + << std::endl; + + return true; +} diff --git a/lib/cppbind/mmimage/tests/common.h b/lib/cppbind/mmimage/tests/common.h index ee3d7f7f1..42bd59945 100644 --- a/lib/cppbind/mmimage/tests/common.h +++ b/lib/cppbind/mmimage/tests/common.h @@ -23,98 +23,15 @@ #include -#include -#include -#include #include -static std::string join_path(const char *arg1, const char *arg2) { - std::stringstream stream; - stream << arg1; - stream << arg2; - return stream.str(); -} +std::string join_path(const char *arg1, const char *arg2); +std::string join_path(const char *arg1, const char *arg2, const char *arg3); +std::string join_path(const char *arg1, const char *arg2, const char *arg3, + const char *arg4); -static std::string join_path(const char *arg1, const char *arg2, - const char *arg3) { - std::stringstream stream; - stream << arg1; - stream << arg2; - stream << arg3; - return stream.str(); -} +bool test_print_metadata_named_attributes(const char *test_name, + mmimage::ImageMetaData &meta_data); -static std::string join_path(const char *arg1, const char *arg2, - const char *arg3, const char *arg4) { - std::stringstream stream; - stream << arg1; - stream << arg2; - stream << arg3; - stream << arg4; - return stream.str(); -} - -static bool test_print_metadata_named_attributes( - const char *test_name, mmimage::ImageMetaData &meta_data) { - rust::Vec all_attr_names = - meta_data.all_named_attribute_names(); - - std::cout << test_name << " attrs count: " << all_attr_names.size() - << std::endl; - for (auto value : all_attr_names) { - std::cout << test_name << " attr_name: " << value.c_str() << std::endl; - } - return true; -} - -static bool test_print_metadata_fields(const char *test_name, - mmimage::ImageMetaData &meta_data) { - float image_pixel_aspect = meta_data.get_pixel_aspect(); - mmimage::ImageRegionRectangle image_display_window = - meta_data.get_display_window(); - rust::Str image_layer_name = meta_data.get_layer_name(); - mmimage::Vec2I32 image_layer_position = meta_data.get_layer_position(); - mmimage::Vec2F32 image_screen_window_center = - meta_data.get_screen_window_center(); - float image_screen_window_width = meta_data.get_screen_window_width(); - rust::Str image_owner = meta_data.get_owner(); - rust::Str image_comments = meta_data.get_comments(); - rust::Str image_capture_date = meta_data.get_capture_date(); - mmimage::OptionF32 image_utc_offset = meta_data.get_utc_offset(); - mmimage::OptionF32 image_longitude = meta_data.get_longitude(); - mmimage::OptionF32 image_latitude = meta_data.get_latitude(); - mmimage::OptionF32 image_altitude = meta_data.get_altitude(); - mmimage::OptionF32 image_focus = meta_data.get_focus(); - mmimage::OptionF32 image_exposure = meta_data.get_exposure(); - mmimage::OptionF32 image_aperture = meta_data.get_aperture(); - mmimage::OptionF32 image_iso_speed = meta_data.get_iso_speed(); - mmimage::OptionF32 image_frames_per_second = - meta_data.get_frames_per_second(); - std::cout << test_name << " image pixel_aspect: " << image_pixel_aspect - << std::endl - << test_name - << " image display_window: " << image_display_window.position_x - << ',' << image_display_window.position_y << ',' - << image_display_window.size_x << ',' - << image_display_window.size_y << std::endl - << test_name << " image owner: " << image_owner << std::endl - << test_name << " image comments: " << image_comments << std::endl - << test_name << " image capture_date: " << image_capture_date - << std::endl - << test_name << " image utc_offset: " << image_utc_offset - << std::endl - << test_name << " image longitude: " << image_longitude - << std::endl - << test_name << " image latitude: " << image_latitude << std::endl - << test_name << " image altitude: " << image_altitude << std::endl - << test_name << " image focus: " << image_focus << std::endl - << test_name << " image exposure: " << image_exposure << std::endl - << test_name << " image aperture: " << image_aperture << std::endl - << test_name << " image iso_speed: " << image_iso_speed - << std::endl - << test_name - << " image frames_per_second: " << image_frames_per_second - << std::endl; - - return true; -} +bool test_print_metadata_fields(const char *test_name, + mmimage::ImageMetaData &meta_data); From f1e44e00895d81d79a31a488684235f46039b150 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 22:16:44 +1000 Subject: [PATCH 105/295] Fix Rust CXX link error on Linux For some strange reason the rust::Str constructor 'rust::Str(std::string &)' does not link correctly on Linux with GCC, but it works fine on Windows with MSVC. The workaround is to pass the 'char *' to the overloaded rust::Str constructor. In theory we might get a little more performance by using rust::Str(char *, size_t) to give the data pointer and length in one call, but this makes the code look slightly more verbose for seemingly no reason, so we avoid that. --- lib/CMakeLists.txt | 4 +--- lib/cppbind/mmimage/tests/test_a.cpp | 9 +++++---- lib/cppbind/mmimage/tests/test_b.cpp | 9 +++++---- lib/cppbind/mmimage/tests/test_c.cpp | 19 +++++++++++-------- lib/cppbind/mmimage/tests/test_d.cpp | 5 +++-- src/mmSolver/image/image_io.cpp | 2 +- tools/lensdistortion/src/main.cpp | 6 +++--- 7 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 0e48db825..b8f57b7c3 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -90,9 +90,7 @@ add_subdirectory(mmsolverlibs/src) option(MMSOLVERLIBS_BUILD_TESTS "Do you want to build the test files for mmsolverlibs?" OFF) if (MMSOLVERLIBS_BUILD_TESTS) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cppbind/mmlens/tests) - # # TODO: This does not work on Maya 2024. I suspect this is because - # # of the std::string ABI has changed in CentOS 8.x/9.x. - # add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cppbind/mmimage/tests) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cppbind/mmimage/tests) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/cppbind/mmscenegraph/tests) endif() diff --git a/lib/cppbind/mmimage/tests/test_a.cpp b/lib/cppbind/mmimage/tests/test_a.cpp index 751af2f1f..d7a92e173 100644 --- a/lib/cppbind/mmimage/tests/test_a.cpp +++ b/lib/cppbind/mmimage/tests/test_a.cpp @@ -87,11 +87,12 @@ bool test_a_image_read(const char *test_name, rust::Str file_path) { } int test_a(const char *test_name, const char *dir_path) { - auto path_string1 = + const std::string path_string1 = join_path(dir_path, "/Beachball/singlepart.0001", ".exr"); - auto path_string2 = join_path(dir_path, "/ScanLines/Tree", ".exr"); - const auto file_path1 = rust::Str(path_string1); - const auto file_path2 = rust::Str(path_string2); + const std::string path_string2 = + join_path(dir_path, "/ScanLines/Tree", ".exr"); + const rust::Str file_path1(path_string1.c_str()); + const rust::Str file_path2(path_string2.c_str()); bool ok = test_a_image_read(test_name, file_path1); if (!ok) { diff --git a/lib/cppbind/mmimage/tests/test_b.cpp b/lib/cppbind/mmimage/tests/test_b.cpp index d976378b7..8ddfe9641 100644 --- a/lib/cppbind/mmimage/tests/test_b.cpp +++ b/lib/cppbind/mmimage/tests/test_b.cpp @@ -58,11 +58,12 @@ bool test_b_load_file_metadata(const char *test_name, rust::Str file_path) { } int test_b(const char *test_name, const char *dir_path) { - auto path_string1 = + const std::string path_string1 = join_path(dir_path, "/Beachball/singlepart.0001", ".exr"); - auto path_string2 = join_path(dir_path, "/ScanLines/Tree", ".exr"); - const auto file_path1 = rust::Str(path_string1); - const auto file_path2 = rust::Str(path_string2); + const std::string path_string2 = + join_path(dir_path, "/ScanLines/Tree", ".exr"); + const rust::Str file_path1(path_string1.c_str()); + const rust::Str file_path2(path_string2.c_str()); bool ok = test_b_load_file_metadata(test_name, file_path1); if (!ok) { diff --git a/lib/cppbind/mmimage/tests/test_c.cpp b/lib/cppbind/mmimage/tests/test_c.cpp index 08629519e..fb3b76ec8 100644 --- a/lib/cppbind/mmimage/tests/test_c.cpp +++ b/lib/cppbind/mmimage/tests/test_c.cpp @@ -100,16 +100,19 @@ bool test_c_image_write(const char *test_name, rust::Str input_file_path, } int test_c(const char *test_name, const char *dir_path) { - auto path_string1 = + const std::string path_string1 = join_path(dir_path, "/Beachball/singlepart.0001", ".exr"); - auto path_string2 = join_path(dir_path, "/ScanLines/Tree", ".exr"); - auto path_out_string1 = + const std::string path_string2 = + join_path(dir_path, "/ScanLines/Tree", ".exr"); + const std::string path_out_string1 = join_path(dir_path, "/Beachball/singlepart.0001", ".out.exr"); - auto path_out_string2 = join_path(dir_path, "/ScanLines/Tree", ".out.exr"); - const auto file_path1 = rust::Str(path_string1); - const auto file_path2 = rust::Str(path_string2); - const auto file_path_out1 = rust::Str(path_out_string1); - const auto file_path_out2 = rust::Str(path_out_string2); + const std::string path_out_string2 = + join_path(dir_path, "/ScanLines/Tree", ".out.exr"); + + const rust::Str file_path1(path_string1.c_str()); + const rust::Str file_path2(path_string2.c_str()); + const rust::Str file_path_out1(path_out_string1.c_str()); + const rust::Str file_path_out2(path_out_string2.c_str()); bool ok = test_c_image_write(test_name, file_path1, file_path_out1); if (!ok) { diff --git a/lib/cppbind/mmimage/tests/test_d.cpp b/lib/cppbind/mmimage/tests/test_d.cpp index 8558c1db0..b7974adb9 100644 --- a/lib/cppbind/mmimage/tests/test_d.cpp +++ b/lib/cppbind/mmimage/tests/test_d.cpp @@ -91,8 +91,9 @@ bool test_d_image_write(const char *test_name, const size_t image_width, } int test_d(const char *test_name, const char *dir_path) { - auto out_path = join_path(dir_path, "test_identity_st_map.0001.out.exr"); - const auto out_file_path = rust::Str(out_path); + const std::string out_path = + join_path(dir_path, "test_identity_st_map.0001.out.exr"); + rust::Str out_file_path(out_path.c_str()); size_t image_width = 2048; size_t image_height = 1556; diff --git a/src/mmSolver/image/image_io.cpp b/src/mmSolver/image/image_io.cpp index ebe270b3c..661400ebb 100644 --- a/src/mmSolver/image/image_io.cpp +++ b/src/mmSolver/image/image_io.cpp @@ -212,7 +212,7 @@ MStatus read_exr_with_mmimage(MImage &image, const MString &file_path, auto meta_data = mmimage::ImageMetaData(); const std::string input_file_path_string = file_path.asChar(); - const auto input_file_path = rust::Str(input_file_path_string); + const rust::Str input_file_path(input_file_path_string.c_str()); // TODO: Support 3-channel RGB EXR images. bool read_ok = mmimage::image_read_pixels_exr_f32x4( diff --git a/tools/lensdistortion/src/main.cpp b/tools/lensdistortion/src/main.cpp index a9d0411ad..4ab81a66d 100644 --- a/tools/lensdistortion/src/main.cpp +++ b/tools/lensdistortion/src/main.cpp @@ -85,7 +85,7 @@ bool run_frame(mmlens::FrameNumber frame, const std::string output_file_path_string = compute_output_file_path(output_file_path_string, frame, verbose); - const auto output_file_path = rust::Str(output_file_path_string); + const rust::Str output_file_path(output_file_path_string.c_str()); auto meta_data = mmimage::ImageMetaData(); std::chrono::duration write_duration; @@ -172,7 +172,7 @@ bool run(const Arguments& args) { const size_t image_width = 3600; const size_t image_height = 2400; const size_t num_channels = 4; // 4 channels - RGBA - const auto input_file_path = rust::Str(args.input_file_path); + const rust::Str input_file_path(args.input_file_path.c_str()); // Read input lens distortion file. // @@ -181,7 +181,7 @@ bool run(const Arguments& args) { // they are not animated. This also includes when some frames // share lens distortion, but others do not; such as frames 1 to // 10 are the same and frames 11 to 20 are animated. - const auto lens_file_path = rust::Str(args.lens_file_path); + const rust::Str lens_file_path(args.lens_file_path.c_str()); mmlens::DistortionLayers lens_layers = mmlens::read_lens_file(lens_file_path); std::cout << "read_lens_file: " << lens_layers.as_string().c_str() From bc7092bedae5719b269b62ed2c97186e612bfdfb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 20 Jun 2024 22:26:48 +1000 Subject: [PATCH 106/295] Remove build configuration for "half" We are using OpenColorIO 2.2+, and "half" was only needed for OpenColorIO 2.0.x and 2.1.x. --- scripts/build_mmSolver_windows64_maya2018.bat | 1 - scripts/build_mmSolver_windows64_maya2019.bat | 1 - scripts/build_mmSolver_windows64_maya2020.bat | 1 - scripts/build_mmSolver_windows64_maya2022.bat | 1 - scripts/build_mmSolver_windows64_maya2023.bat | 1 - scripts/build_mmSolver_windows64_maya2024.bat | 1 - scripts/internal/build_mmSolverLibs_linux.bash | 6 ------ scripts/internal/build_mmSolverLibs_windows64.bat | 6 ------ scripts/internal/build_mmSolver_linux.bash | 6 ------ scripts/internal/build_mmSolver_windows64.bat | 6 ------ 10 files changed, 30 deletions(-) diff --git a/scripts/build_mmSolver_windows64_maya2018.bat b/scripts/build_mmSolver_windows64_maya2018.bat index ea224f0f2..e36246073 100644 --- a/scripts/build_mmSolver_windows64_maya2018.bat +++ b/scripts/build_mmSolver_windows64_maya2018.bat @@ -44,7 +44,6 @@ SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ :: yaml-cpp 0.6.3 SET YAML_RELATIVE_CMAKE_DIR=CMake\ -SET HALF_RELATIVE_LIB_PATH=lib\Half-2_4.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/build_mmSolver_windows64_maya2019.bat b/scripts/build_mmSolver_windows64_maya2019.bat index 35d9d02ee..8686045da 100644 --- a/scripts/build_mmSolver_windows64_maya2019.bat +++ b/scripts/build_mmSolver_windows64_maya2019.bat @@ -44,7 +44,6 @@ SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ :: yaml-cpp 0.6.3 SET YAML_RELATIVE_CMAKE_DIR=CMake\ -SET HALF_RELATIVE_LIB_PATH=lib\Half-2_4.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/build_mmSolver_windows64_maya2020.bat b/scripts/build_mmSolver_windows64_maya2020.bat index f8cbac206..0e6f86554 100644 --- a/scripts/build_mmSolver_windows64_maya2020.bat +++ b/scripts/build_mmSolver_windows64_maya2020.bat @@ -47,7 +47,6 @@ SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib -SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/build_mmSolver_windows64_maya2022.bat b/scripts/build_mmSolver_windows64_maya2022.bat index 7e9deac0a..12cdebbde 100644 --- a/scripts/build_mmSolver_windows64_maya2022.bat +++ b/scripts/build_mmSolver_windows64_maya2022.bat @@ -47,7 +47,6 @@ SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib -SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/build_mmSolver_windows64_maya2023.bat b/scripts/build_mmSolver_windows64_maya2023.bat index 9786a7bae..ee79c5f0c 100644 --- a/scripts/build_mmSolver_windows64_maya2023.bat +++ b/scripts/build_mmSolver_windows64_maya2023.bat @@ -47,7 +47,6 @@ SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib -SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/build_mmSolver_windows64_maya2024.bat b/scripts/build_mmSolver_windows64_maya2024.bat index 745f22d2c..d11e040e7 100644 --- a/scripts/build_mmSolver_windows64_maya2024.bat +++ b/scripts/build_mmSolver_windows64_maya2024.bat @@ -47,7 +47,6 @@ SET MINIZIP_RELATIVE_CMAKE_DIR=lib\cmake\minizip-ng SET YAML_RELATIVE_CMAKE_DIR=share\cmake\yaml-cpp\ SET YAML_RELATIVE_LIB_PATH=lib\yaml-cpp.lib SET PYSTRING_RELATIVE_LIB_PATH=lib\pystring.lib -SET HALF_RELATIVE_LIB_PATH=lib\Imath-3_1.lib :: Which version of the VFX platform are we "using"? (Maya doesn't :: currently conform to the VFX Platform.) diff --git a/scripts/internal/build_mmSolverLibs_linux.bash b/scripts/internal/build_mmSolverLibs_linux.bash index d302a009d..84c5ec3db 100644 --- a/scripts/internal/build_mmSolverLibs_linux.bash +++ b/scripts/internal/build_mmSolverLibs_linux.bash @@ -83,9 +83,6 @@ yaml_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" -Half_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" -Half_LIBRARY="${EXTERNAL_BUILD_DIR}/${HALF_RELATIVE_LIB_PATH}" - ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/${ZLIB_RELATIVE_LIB_PATH}" ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" @@ -156,9 +153,6 @@ ${CMAKE_EXE} \ -Dyaml-cpp_LIBRARY=${yaml_LIBRARY} \ -Dyaml-cpp_INCLUDE_DIR=${yaml_INCLUDE_DIR} \ -DImath_DIR=${Imath_DIR} \ - -DHalf_STATIC_LIBRARY=ON \ - -DHalf_LIBRARY=${Half_LIBRARY} \ - -DHalf_INCLUDE_DIR=${Half_INCLUDE_DIR} \ -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ -DZLIB_STATIC_LIBRARY=ON \ diff --git a/scripts/internal/build_mmSolverLibs_windows64.bat b/scripts/internal/build_mmSolverLibs_windows64.bat index a076653b1..f73c38d3e 100644 --- a/scripts/internal/build_mmSolverLibs_windows64.bat +++ b/scripts/internal/build_mmSolverLibs_windows64.bat @@ -79,9 +79,6 @@ SET yaml_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath -SET Half_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ -SET Half_LIBRARY=%EXTERNAL_BUILD_DIR%\%HALF_RELATIVE_LIB_PATH% - SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\%ZLIB_RELATIVE_LIB_PATH% @@ -181,9 +178,6 @@ CHDIR "%BUILD_DIR%" -Dyaml-cpp_LIBRARY=%yaml_LIBRARY% ^ -Dyaml-cpp_INCLUDE_DIR=%yaml_INCLUDE_DIR% ^ -DImath_DIR=%Imath_DIR% ^ - -DHalf_STATIC_LIBRARY=ON ^ - -DHalf_LIBRARY=%Half_LIBRARY% ^ - -DHalf_INCLUDE_DIR=%Half_INCLUDE_DIR% ^ -DZLIB_LIBRARY=%ZLIB_LIBRARY% ^ -DZLIB_INCLUDE_DIR=%ZLIB_INCLUDE_DIR% ^ -DZLIB_STATIC_LIBRARY=ON ^ diff --git a/scripts/internal/build_mmSolver_linux.bash b/scripts/internal/build_mmSolver_linux.bash index 5270f3cdc..b5c0df6dd 100644 --- a/scripts/internal/build_mmSolver_linux.bash +++ b/scripts/internal/build_mmSolver_linux.bash @@ -114,9 +114,6 @@ yaml_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" Imath_DIR="${EXTERNAL_BUILD_DIR}/lib64/cmake/Imath" -Half_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" -Half_LIBRARY="${EXTERNAL_BUILD_DIR}/${HALF_RELATIVE_LIB_PATH}" - ZLIB_LIBRARY="${EXTERNAL_BUILD_DIR}/${ZLIB_RELATIVE_LIB_PATH}" ZLIB_INCLUDE_DIR="${EXTERNAL_BUILD_DIR}/include/" @@ -172,9 +169,6 @@ ${CMAKE_EXE} \ -Dyaml-cpp_LIBRARY=${yaml_LIBRARY} \ -Dyaml-cpp_INCLUDE_DIR=${yaml_INCLUDE_DIR} \ -DImath_DIR=${Imath_DIR} \ - -DHalf_STATIC_LIBRARY=ON \ - -DHalf_LIBRARY=${Half_LIBRARY} \ - -DHalf_INCLUDE_DIR=${Half_INCLUDE_DIR} \ -DZLIB_LIBRARY=${ZLIB_LIBRARY} \ -DZLIB_INCLUDE_DIR=${ZLIB_INCLUDE_DIR} \ -DZLIB_STATIC_LIBRARY=ON \ diff --git a/scripts/internal/build_mmSolver_windows64.bat b/scripts/internal/build_mmSolver_windows64.bat index 84a6a39e9..e1fcfdef8 100644 --- a/scripts/internal/build_mmSolver_windows64.bat +++ b/scripts/internal/build_mmSolver_windows64.bat @@ -114,9 +114,6 @@ SET yaml_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ SET Imath_DIR=%EXTERNAL_BUILD_DIR%\lib\cmake\Imath -SET Half_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ -SET Half_LIBRARY=%EXTERNAL_BUILD_DIR%\%HALF_RELATIVE_LIB_PATH% - SET ZLIB_INCLUDE_DIR=%EXTERNAL_BUILD_DIR%\include\ SET ZLIB_LIBRARY=%EXTERNAL_BUILD_DIR%\%ZLIB_RELATIVE_LIB_PATH% @@ -188,9 +185,6 @@ CHDIR "%BUILD_DIR%" -Dexpat_INCLUDE_DIR=%expat_INCLUDE_DIR% ^ -Dexpat_USE_STATIC_LIBS=TRUE ^ -DImath_DIR=%Imath_DIR% ^ - -DHalf_STATIC_LIBRARY=ON ^ - -DHalf_LIBRARY=%Half_LIBRARY% ^ - -DHalf_INCLUDE_DIR=%Half_INCLUDE_DIR% ^ -Dminizip-ng_DIR=%minizip_DIR% ^ -Dpystring_LIBRARY=%pystring_LIBRARY% ^ -Dpystring_INCLUDE_DIR=%pystring_INCLUDE_DIR% ^ From c6e2d89289caec9f4b889ac2599e78bbad445513 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 00:39:31 +1000 Subject: [PATCH 107/295] Fix linker error on Linux GCC This is an attempt to refine the ABI breakage and ensure all libraries are linked correctly. P.S. The OpenColorIO library is also a static library now (yay!) on Linux at least - Windows requires a DLL (for now). --- lib/CMakeLists.txt | 4 +- lib/cppbind/mmimage/tests/CMakeLists.txt | 8 + lib/cppbind/mmlens/tests/CMakeLists.txt | 8 + lib/cppbind/mmscenegraph/tests/CMakeLists.txt | 8 + scripts/internal/build_openColorIO_linux.bash | 4 +- share/cmake/modules/MMColorIOUtils.cmake | 16 +- share/cmake/modules/MMCommonUtils.cmake | 245 +++++++++++++++++- share/cmake/modules/MMSolverUtils.cmake | 221 +--------------- src/CMakeLists.txt | 5 +- 9 files changed, 285 insertions(+), 234 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index b8f57b7c3..f386dbfc4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -66,8 +66,8 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} set(cpp_lib_name "mmsolverlibs_cpp") set(rust_lib_name "mmsolverlibs_rust") -include(MMSolverUtils) -set_global_maya_plugin_compile_options() +include(MMCommonUtils) +mm_common_set_global_compile_options() include(MMRustUtils) set(rust_linktime_file "NOT-FOUND") diff --git a/lib/cppbind/mmimage/tests/CMakeLists.txt b/lib/cppbind/mmimage/tests/CMakeLists.txt index 8dcee79cc..67cfdab03 100644 --- a/lib/cppbind/mmimage/tests/CMakeLists.txt +++ b/lib/cppbind/mmimage/tests/CMakeLists.txt @@ -29,6 +29,9 @@ set(test_source_files ${CMAKE_CURRENT_SOURCE_DIR}/test_d.cpp ) +include(MMCommonUtils) +mm_common_set_global_compile_options() + # Add test executable using the C++ bindings. message(STATUS "target_test_exe_name: ${target_test_exe_name}") message(STATUS "test_source_files: ${test_source_files}") @@ -47,4 +50,9 @@ target_include_directories(${target_test_exe_name} PUBLIC ${RUST_INCLUDE_DIR} ) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(${target_test_exe_name}) + install(TARGETS ${target_test_exe_name}) diff --git a/lib/cppbind/mmlens/tests/CMakeLists.txt b/lib/cppbind/mmlens/tests/CMakeLists.txt index f350feafc..249677108 100644 --- a/lib/cppbind/mmlens/tests/CMakeLists.txt +++ b/lib/cppbind/mmlens/tests/CMakeLists.txt @@ -38,6 +38,9 @@ set(test_source_files ${CMAKE_CURRENT_SOURCE_DIR}/test_once_3de_radial_std_deg4.cpp ) +include(MMCommonUtils) +mm_common_set_global_compile_options() + # Add test executable using the C++ bindings. add_executable(${target_test_exe_name} ${test_source_files}) target_link_libraries(${target_test_exe_name} @@ -55,4 +58,9 @@ target_include_directories(${target_test_exe_name} PUBLIC ${RUST_INCLUDE_DIR} ) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(${target_test_exe_name}) + install(TARGETS ${target_test_exe_name}) diff --git a/lib/cppbind/mmscenegraph/tests/CMakeLists.txt b/lib/cppbind/mmscenegraph/tests/CMakeLists.txt index 75858457e..da69034a9 100644 --- a/lib/cppbind/mmscenegraph/tests/CMakeLists.txt +++ b/lib/cppbind/mmscenegraph/tests/CMakeLists.txt @@ -27,6 +27,9 @@ set(test_source_files # ${CMAKE_CURRENT_SOURCE_DIR}/test_c.cpp ) +include(MMCommonUtils) +mm_common_set_global_compile_options() + # Add test executable using the C++ bindings. add_executable(${target_test_exe_name} ${test_source_files}) target_link_libraries(${target_test_exe_name} @@ -44,4 +47,9 @@ target_include_directories(${target_test_exe_name} PUBLIC ${RUST_INCLUDE_DIR} ) +# MM Color IO dependencies +include(MMColorIOUtils) +mmcolorio_find_packages() +mmcolorio_target_link_packages(${target_test_exe_name}) + install(TARGETS ${target_test_exe_name}) diff --git a/scripts/internal/build_openColorIO_linux.bash b/scripts/internal/build_openColorIO_linux.bash index f1b615e4f..865d1244f 100644 --- a/scripts/internal/build_openColorIO_linux.bash +++ b/scripts/internal/build_openColorIO_linux.bash @@ -104,7 +104,7 @@ MMSOLVER_OCIO_LIBNAME_SUFFIX="_mmSolver" MMSOLVER_OCIO_NAMESPACE="OpenColorIO_mmSolver" ${CMAKE_EXE} \ - -DBUILD_SHARED_LIBS=ON \ + -DBUILD_SHARED_LIBS=OFF \ -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ -DCMAKE_INSTALL_PREFIX=${OPENCOLORIO_INSTALL_PATH} \ -DCMAKE_IGNORE_PATH=${CMAKE_IGNORE_PATH} \ @@ -112,8 +112,8 @@ ${CMAKE_EXE} \ -DCMAKE_CXX_STANDARD=${CXX_STANDARD} \ -DCMAKE_VERBOSE_MAKEFILE=${MMSOLVER_BUILD_VERBOSE} \ -DOCIO_INSTALL_EXT_PACKAGES=ALL \ - -DOCIO_BUILD_APPS=OFF \ -DOCIO_USE_OIIO_FOR_APPS=OFF \ + -DOCIO_BUILD_APPS=OFF \ -DOCIO_BUILD_TESTS=OFF \ -DOCIO_BUILD_GPU_TESTS=OFF \ -DOCIO_BUILD_DOCS=OFF \ diff --git a/share/cmake/modules/MMColorIOUtils.cmake b/share/cmake/modules/MMColorIOUtils.cmake index 75b5f4fd5..2677ee2f1 100644 --- a/share/cmake/modules/MMColorIOUtils.cmake +++ b/share/cmake/modules/MMColorIOUtils.cmake @@ -83,22 +83,28 @@ endmacro() macro(mmcolorio_target_link_packages target) message(STATUS "mmcolorio_target_link_packages: ${target}") + find_package(yaml-cpp REQUIRED) get_target_property(yaml_cpp_LOCATION_RELEASE yaml-cpp IMPORTED_LOCATION_RELEASE) message(STATUS "yaml_cpp_LOCATION_RELEASE: ${yaml_cpp_LOCATION_RELEASE}") + find_package(expat REQUIRED) + find_package(pystring REQUIRED) target_link_libraries(${target} PRIVATE ${expat_LIBRARY} PRIVATE ${pystring_LIBRARY} PRIVATE ${yaml_cpp_LOCATION_RELEASE} ) + find_package(OpenColorIO REQUIRED) + find_package(ZLIB REQUIRED) + find_package(minizip-ng REQUIRED) if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") target_link_libraries(${target} PRIVATE ZLIB::ZLIB - PRIVATE MINIZIP::minizip-ng + PRIVATE ${minizip-ng_LIBRARY} ) endif() @@ -110,16 +116,19 @@ macro(mmcolorio_target_link_packages target) # but since many libraries wanted the 'half' data type without all # of OpenEXR, it was refactored out of OpenEXR. if(OpenColorIO_VERSION VERSION_GREATER_EQUAL "2.2.0") + find_package(Imath REQUIRED) target_link_libraries(${target} PRIVATE Imath::Imath ) else() + find_package(Half REQUIRED) target_link_libraries(${target} PRIVATE ${Half_LIBRARY} ) endif() if (WIN32) + find_package(OpenColorIO REQUIRED) get_target_property(OpenColorIO_IMPLIB_RELEASE OpenColorIO::OpenColorIO IMPORTED_IMPLIB_RELEASE) @@ -138,8 +147,6 @@ macro(mmcolorio_target_link_packages target) REALPATH) target_link_libraries(${target} - # PRIVATE OpenColorIO::OpenColorIO - # PRIVATE ${OpenColorIO_LOCATION_RELEASE} PRIVATE ${OpenColorIO_IMPLIB_RELEASE_ABS} ) elseif (UNIX) @@ -150,11 +157,8 @@ macro(mmcolorio_target_link_packages target) "OpenColorIO_LOCATION_RELEASE: ${OpenColorIO_LOCATION_RELEASE}") target_link_libraries(${target} - # PRIVATE OpenColorIO::OpenColorIO PRIVATE ${OpenColorIO_LOCATION_RELEASE} - # PRIVATE ${OpenColorIO_IMPLIB_RELEASE} ) - endif () endmacro() diff --git a/share/cmake/modules/MMCommonUtils.cmake b/share/cmake/modules/MMCommonUtils.cmake index ac24144a4..00deac1ba 100644 --- a/share/cmake/modules/MMCommonUtils.cmake +++ b/share/cmake/modules/MMCommonUtils.cmake @@ -1,4 +1,4 @@ -# Copyright (C) 2020, 2021, 2023 David Cattermole. +# Copyright (C) 2020, 2021, 2023, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -16,9 +16,250 @@ # along with mmSolver. If not, see . # --------------------------------------------------------------------- # -# CMake utilities for mmSolver. +# CMake common utilities for mmSolver. # + +function(mm_common_apple_clang_set_global_compile_options) + # For MacOS with Clang (which is the supported compiler for Maya + # 2018+) + set(CMAKE_CXX_FLAGS "") # Zero out the C++ flags, we have complete control. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-comment -Wno-sign-compare") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -Wno-reorder") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-35 -fno-gnu-keywords") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funsigned-char -fpascal-strings") # -pthread + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCC_GNU_") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMac_") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMacOSX_") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMac_MachO_") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LANGUAGE_C_PLUS_PLUS") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8") + + # Special MacOS linking stuff + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -headerpad_max_install_names") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework System") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework SystemConfiguration") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework CoreServices") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework Carbon") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework Cocoa") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework ApplicationServices") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework IOKit") + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -bundle") + + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") + set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fPIC -fno-strict-aliasing -m64") +endfunction() + + +function(mm_common_windows_msvc_set_global_compile_options) + # For Visual Studio 11 2012 + set(CMAKE_CXX_FLAGS "") # Zero out the C++ flags, we have complete control. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS") # Buffer Security Check + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:wchar_t") # wchar_t Is Native Type + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") # Separate .pdb debug info file. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") # Precise floating-point behavior + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:forScope") # Force Conformance in for Loop Scope + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR") # Enable Run-Time Type Information + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Gd") # __cdecl Calling Convention + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # Handle standard C++ Exceptions. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") # Warning Levels 1-4 enabled. + + add_compile_definitions(OSWin_) + add_compile_definitions(WIN32) + add_compile_definitions(_WINDOWS) + add_compile_definitions(_USRDLL) + add_compile_definitions(NT_PLUGIN) + add_compile_definitions(_HAS_ITERATOR_DEBUGGING=0) + add_compile_definitions(_SECURE_SCL=0) + add_compile_definitions(_SECURE_SCL_THROWS=0) + add_compile_definitions(_SECURE_SCL_DEPRECATE=0) + add_compile_definitions(_CRT_SECURE_NO_DEPRECATE) + add_compile_definitions(_CRT_SECURE_NO_WARNINGS) + add_compile_definitions(TBB_USE_DEBUG=0) + add_compile_definitions(__TBB_LIB_NAME=tbb.lib) + add_compile_definitions(_WINDLL) + add_compile_definitions(Bits64_) + add_compile_definitions(REQUIRE_IOSTREAM) + add_compile_definitions(NT_PLUGIN) + add_compile_definitions(USERDLL) + + # Use multithread-specific Run-Time Library. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /MD") + + add_compile_options("/arch:AVX2") + + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Gy") # Enable Function-Level Linking + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GF") # Eliminate Duplicate Strings + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") # Optimize for Maximize Speed + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi") # Generate Intrinsic Functions + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") # Whole program optimization + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:AVX2") # Use AVX2 instructions + + # Link-time code generation. + # + # /LTGC and /GL work together and therefore are both enabled. + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG") + + # Use debug-specific Run-Time Library. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} /MDd") + + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od") # Optimize for Debug. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /RTC1") # Run-time error checks + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob0") # Disables inline expansions. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GR") # Enable Run-Time Type Information + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GL") # Whole program optimization + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Oi") # Generate Intrinsic Functions + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gy") # Enable Function-Level Linking + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi") # Debug Information Format + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /EHsc") # Handle standard C++ Exceptions. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /analyze") # Code analysis + + # Ensure Google Logging does not use "ERROR" macro, because Windows + # doesn't support it. + add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES) + + # Ceres running on Windows can trigger a MSVC warning: + # + # "Compiler Warning (level 3) C4996: Your code uses a function, + # class member, variable, or typedef that's marked deprecated." + # + # To override this, we define "_CRT_NONSTDC_NO_DEPRECATE" + # + # https://docs.microsoft.com/en-us/previous-versions/ms235384(v=vs.100) + add_compile_definitions(_CRT_NONSTDC_NO_DEPRECATE) +endfunction() + + +# For Linux with GCC +function(mm_common_linux_gcc_set_cxx11_abi) + + if(NOT DEFINED MMSOLVER_VFX_PLATFORM) + message(FATAL_ERROR "Please define the VFX_PLATFORM variable (eg. with '-DVFX_PLATFORM=2020').") + endif() + + # Use the older C++11 ABI for std::string and std::list, to be + # compatible with RHEL/CentOS 7, Maya and the VFX Platform. + # + # https://vfxplatform.com/#footnote-gcc6 + # + # In VFX Platform CY2023, and the move to RHEL 8 or RHEL 9, + # the new default is to use "_GLIBCXX_USE_CXX11_ABI=1". + # + # https://vfxplatform.com/#footnote-gcc9 + if (MMSOLVER_VFX_PLATFORM VERSION_GREATER_EQUAL 2023) + # Must be enabled for RHEL 8/9, Alma Linux and Rocky Linux 8/9. + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=1) + else () + add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) + endif() + +endfunction() + + +# For Linux with GCC +function(mm_common_linux_gcc_set_global_compile_options) + + # Definitions + add_compile_definitions(Bits64_) + add_compile_definitions(UNIX) + add_compile_definitions(_BOOL) + add_compile_definitions(LINUX) + add_compile_definitions(linux) + add_compile_definitions(__linux__) + add_compile_definitions(OSLinux_) + add_compile_definitions(FUNCPROTO) + add_compile_definitions(_GNU_SOURCE) + add_compile_definitions(LINUX_64) + add_compile_definitions(REQUIRE_IOSTREAM) + + # Enable warnings. + add_compile_options(-Wall) + add_compile_options(-Wno-multichar) + add_compile_options(-Wno-comment) + add_compile_options(-Wno-sign-compare) + add_compile_options(-Wno-unused-parameter) + add_compile_options(-Wno-unused-parameter) + add_compile_options(-Wno-unused-variable) + add_compile_options(-Wno-unused-private-field) + add_compile_options(-Wno-deprecated) + add_compile_options(-Wno-reorder) + + # GCC Features + add_compile_options(-pthread) + add_compile_options(-fopenmp) + add_compile_options(-fvisibility=hidden) + add_compile_options(-funsigned-char) + add_compile_options(-fno-gnu-keywords) + add_compile_options(-fno-strict-aliasing) + + mm_common_linux_gcc_set_cxx11_abi() + + # How how many levels of 'depth' can templates use until the depth + # is too high? + # + # If this value is too high compile times will be too slow. + # + # '-ftemplate-depth-27' is required to compile under GCC 4.8.5. + # '-ftemplate-depth-35' is required to compile under GCC 5.5.x. + # + # In GCC 6.3.x, with C++11 the default depth is set to 900, but + # the C++11 standard says 1024 is the default. + add_compile_options(-ftemplate-depth-900) + + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0") # No optmizations. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g") # Include debug symbols + + # Enable AddressSanitizer. + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") + + # Nicer stack traces in error messages + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer") + + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") # Optimize for maximum performance. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -m64") # Set 64-bit machine. + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=skylake") # Use AVX2 instructions +endfunction() + +function(mm_common_set_global_compile_options) + + if(CMAKE_BUILD_TYPE EQUAL "Release") + add_compile_definitions(NDEBUG) + elseif (CMAKE_BUILD_TYPE EQUAL "Debug") + add_compile_definitions(_DEBUG) + endif () + + # Compile Flags. + # + # TODO: Make this function take a target and set the compile + # arguments per-target. + # + # Release flags come from the Autodesk Maya build scripts (and + # Visual Studio project files). + if (MSVC) + mm_common_windows_msvc_set_global_compile_options() + elseif (APPLE) + mm_common_apple_clang_set_global_compile_options() + else () + mm_common_linux_gcc_set_global_compile_options() + endif () +endfunction() + + +function(mm_common_set_global_treat_warnings_as_errors) + if(MSVC) + add_compile_options(/WX) + else() + add_compile_options(-Werror) + endif() +endfunction() + + macro(mm_common_set_relative_library_rpath target relative_path) # HACK: We must change the RPATH variable for the library so that a # binary can find the shared object, even if it's not in the diff --git a/share/cmake/modules/MMSolverUtils.cmake b/share/cmake/modules/MMSolverUtils.cmake index bd4391412..db269e0eb 100644 --- a/share/cmake/modules/MMSolverUtils.cmake +++ b/share/cmake/modules/MMSolverUtils.cmake @@ -1,4 +1,4 @@ -# Copyright (C) 2020 David Cattermole. +# Copyright (C) 2020, 2024 David Cattermole. # # This file is part of mmSolver. # @@ -20,225 +20,6 @@ # -function(set_global_maya_plugin_compile_options) - - if(CMAKE_BUILD_TYPE EQUAL "Release") - add_compile_definitions(NDEBUG) - elseif (CMAKE_BUILD_TYPE EQUAL "Debug") - add_compile_definitions(_DEBUG) - endif () - - # Compile Flags. - # - # TODO: Make this function take a target and set the compile - # arguments per-target. - # - # Release flags come from the Autodesk Maya build scripts (and - # Visual Studio project files). - if (MSVC) - # For Visual Studio 11 2012 - set(CMAKE_CXX_FLAGS "") # Zero out the C++ flags, we have complete control. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GS") # Buffer Security Check - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:wchar_t") # wchar_t Is Native Type - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") # Separate .pdb debug info file. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /fp:precise") # Precise floating-point behavior - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:forScope") # Force Conformance in for Loop Scope - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR") # Enable Run-Time Type Information - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Gd") # __cdecl Calling Convention - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc") # Handle standard C++ Exceptions. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") # Warning Levels 1-4 enabled. - - add_compile_definitions(OSWin_) - add_compile_definitions(WIN32) - add_compile_definitions(_WINDOWS) - add_compile_definitions(_USRDLL) - add_compile_definitions(NT_PLUGIN) - add_compile_definitions(_HAS_ITERATOR_DEBUGGING=0) - add_compile_definitions(_SECURE_SCL=0) - add_compile_definitions(_SECURE_SCL_THROWS=0) - add_compile_definitions(_SECURE_SCL_DEPRECATE=0) - add_compile_definitions(_CRT_SECURE_NO_DEPRECATE) - add_compile_definitions(_CRT_SECURE_NO_WARNINGS) - add_compile_definitions(TBB_USE_DEBUG=0) - add_compile_definitions(__TBB_LIB_NAME=tbb.lib) - add_compile_definitions(_WINDLL) - add_compile_definitions(Bits64_) - add_compile_definitions(REQUIRE_IOSTREAM) - add_compile_definitions(NT_PLUGIN) - add_compile_definitions(USERDLL) - - # Use multithread-specific Run-Time Library. - # - # NOTE: This changes the ABI. - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} /MD") - - add_compile_options("/arch:AVX2") - - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Gy") # Enable Function-Level Linking - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GF") # Eliminate Duplicate Strings - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /O2") # Optimize for Maximize Speed - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi") # Generate Intrinsic Functions - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") # Whole program optimization - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /arch:AVX2") # Use AVX2 instructions - - # Link-time code generation. - # - # /LTGC and /GL work together and therefore are both enabled. - set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") - set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") - set(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} /LTCG") - - # Use debug-specific Run-Time Library. - # - # NOTE: This changes the ABI. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} /MDd") - - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Od") # Optimize for Debug. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /RTC1") # Run-time error checks - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Ob0") # Disables inline expansions. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GR") # Enable Run-Time Type Information - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /GL") # Whole program optimization - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Oi") # Generate Intrinsic Functions - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Gy") # Enable Function-Level Linking - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /Zi") # Debug Information Format - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /EHsc") # Handle standard C++ Exceptions. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /analyze") # Code analysis - - # Ensure Google Logging does not use "ERROR" macro, because Windows - # doesn't support it. - add_compile_definitions(GLOG_NO_ABBREVIATED_SEVERITIES) - - # Ceres running on Windows can trigger a MSVC warning: - # - # "Compiler Warning (level 3) C4996: Your code uses a function, - # class member, variable, or typedef that's marked deprecated." - # - # To override this, we define "_CRT_NONSTDC_NO_DEPRECATE" - # - # https://docs.microsoft.com/en-us/previous-versions/ms235384(v=vs.100) - add_compile_definitions(_CRT_NONSTDC_NO_DEPRECATE) - - elseif (APPLE) - - # For MacOS with Clang (which is the supported compiler for Maya - # 2018+) - set(CMAKE_CXX_FLAGS "") # Zero out the C++ flags, we have complete control. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -arch x86_64") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -stdlib=libc++") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-comment -Wno-sign-compare") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-deprecated -Wno-reorder") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ftemplate-depth-35 -fno-gnu-keywords") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funsigned-char -fpascal-strings") # -pthread - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DCC_GNU_") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMac_") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMacOSX_") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DOSMac_MachO_") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LANGUAGE_C_PLUS_PLUS") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8") - - # Special MacOS linking stuff - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -headerpad_max_install_names") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework System") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework SystemConfiguration") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework CoreServices") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework Carbon") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework Cocoa") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework ApplicationServices") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -framework IOKit") - set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -bundle") - - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fPIC -fno-strict-aliasing -m64") - - else () - # For Linux with GCC - - # Definitions - add_compile_definitions(Bits64_) - add_compile_definitions(UNIX) - add_compile_definitions(_BOOL) - add_compile_definitions(LINUX) - add_compile_definitions(linux) - add_compile_definitions(__linux__) - add_compile_definitions(OSLinux_) - add_compile_definitions(FUNCPROTO) - add_compile_definitions(_GNU_SOURCE) - add_compile_definitions(LINUX_64) - add_compile_definitions(REQUIRE_IOSTREAM) - - # Use the older C++11 ABI for std::string and std::list, to be - # compatible with RHEL/CentOS 7, Maya and the VFX Platform. - # - # https://vfxplatform.com/#footnote-gcc6 - # - # In VFX Platform CY2023, and the move to RHEL 8 or RHEL 9, - # the new default is to use "_GLIBCXX_USE_CXX11_ABI=1". - # - # https://vfxplatform.com/#footnote-gcc9 - # - # NOTE: This changes the ABI. - if (VFX_PLATFORM VERSION_GREATER_EQUAL 2023) - # Must be enabled for RHEL 8/9, Alma Linux and Rocky Linux 8/9. - add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=1) - else () - add_compile_definitions(_GLIBCXX_USE_CXX11_ABI=0) - endif() - - # Enable warnings. - add_definitions(-Wall) - add_definitions(-Wno-multichar) - add_definitions(-Wno-comment) - add_definitions(-Wno-sign-compare) - add_definitions(-Wno-unused-parameter) - add_definitions(-Wno-unused-parameter) - add_definitions(-Wno-unused-variable) - add_definitions(-Wno-unused-private-field) - add_definitions(-Wno-deprecated) - add_definitions(-Wno-reorder) - - # GCC Features - add_definitions(-pthread) - add_definitions(-fopenmp) - add_definitions(-fvisibility=hidden) - add_definitions(-funsigned-char) - add_definitions(-fno-gnu-keywords) - add_definitions(-fno-strict-aliasing) - - # '-ftemplate-depth-27' is required to compile under GCC 4.8.5. - # '-ftemplate-depth-35' is required to compile under GCC 5.5.x. - # - # In GCC 6.3.x, with C++11 the default depth is set to 900, but - # the C++11 standard says 1024 is the default. - add_definitions(-ftemplate-depth-900) - - set(CMAKE_CXX_FLAGS_DEBUG "-O0") # No optmizations. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -g") # Include debug symbols - - # Enable AddressSanitizer. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address") - - # Nicer stack traces in error messages - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer") - - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3") # Optimize for maximum performance. - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -m64") # Set 64-bit machine. - set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -march=skylake") # Use AVX2 instructions - - endif () -endfunction() - - -function(set_global_treat_warnings_as_errors) - if(MSVC) - add_compile_options(/WX) - else() - add_compile_options(-Werror) - endif() -endfunction() - - function(add_library_maya_plugin target source_files) if (APPLE) add_library(${target} MODULE "${source_files}") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 895aeadf7..740276e7f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -172,8 +172,9 @@ endif () # Set global flags and options that need to apply to the Maya # plug-in. See 'MMSolverUtils' CMake module for more information. -set_global_treat_warnings_as_errors() -set_global_maya_plugin_compile_options() +include(MMCommonUtils) +mm_common_set_global_treat_warnings_as_errors() +mm_common_set_global_compile_options() # MM Solver standalone libraries. find_package(mmsolverlibs_cpp REQUIRED) From ebac9406f79f7b0d77a69a4a6e1e9b3aa5ee8363 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 01:30:52 +1000 Subject: [PATCH 108/295] mmImagePlaneShape2 - Show image cache values in AE GitHub issue #252 --- .../AEmmImagePlaneShape2Template.mel | 37 +++-- .../createimageplane/_lib/mmimageplane_v2.py | 4 +- python/mmSolver/tools/imagecache/lib.py | 129 ++++++++++++------ 3 files changed, 118 insertions(+), 52 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 2400524a3..eaa2d9be4 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -250,19 +250,25 @@ global proc AEmmImagePlaneShape2_imageCache_updateValues( $cache_cpu_used_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $cache_cpu_used_cmd += "lib.format_cache_cpu_used(image_plane_shp);"; - string $cache_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; - $cache_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; - $cache_available_cmd += "lib.format_cache_available(image_plane_shp);"; + string $memory_gpu_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $memory_gpu_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $memory_gpu_available_cmd += "lib.format_memory_gpu_available(image_plane_shp);"; + + string $memory_cpu_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + $memory_cpu_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; + $memory_cpu_available_cmd += "lib.format_memory_cpu_available(image_plane_shp);"; string $image_seq_size = python($image_seq_size_cmd); string $cache_gpu_used = python($cache_gpu_used_cmd); string $cache_cpu_used = python($cache_cpu_used_cmd); - string $cache_available = python($cache_available_cmd); + string $memory_gpu_available = python($memory_gpu_available_cmd); + string $memory_cpu_available = python($memory_cpu_available_cmd); text -edit -label $image_seq_size imageCacheImageSequenceSizeText; text -edit -label $cache_gpu_used imageCacheCacheGpuUsedText; text -edit -label $cache_cpu_used imageCacheCacheCpuUsedText; - text -edit -label $cache_available imageCacheCacheAvailableText; + text -edit -label $memory_gpu_available imageCacheMemoryGpuAvailableText; + text -edit -label $memory_cpu_available imageCacheMemoryCpuAvailableText; } @@ -280,6 +286,8 @@ global proc AEmmImagePlaneShape2_imageCacheDisplaySectionNew(string $attr_name) } setParent ..; + text -label ""; // spacer + rowLayout -nc 2 imageCacheCacheGpuUsedLayout; { text -label "GPU Cache Used:"; @@ -294,10 +302,19 @@ global proc AEmmImagePlaneShape2_imageCacheDisplaySectionNew(string $attr_name) } setParent ..; - rowLayout -nc 2 imageCacheCacheAvailableLayout; + text -label ""; // spacer + + rowLayout -nc 2 imageCacheMemoryGpuAvailableLayout; { text -label "Total Memory Available:"; - text -align "left" imageCacheCacheAvailableText; + text -align "left" imageCacheMemoryGpuAvailableText; + } + setParent ..; + + rowLayout -nc 2 imageCacheMemoryCpuAvailableLayout; + { + text -label ""; // empty space + text -align "left" imageCacheMemoryCpuAvailableText; } setParent ..; @@ -561,15 +578,11 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "imageNumChannels"; editorTemplate -addControl "imageBytesPerChannel"; editorTemplate -addControl "imageSizeBytes"; - - // editorTemplate -suppress "imageNumChannels"; - // editorTemplate -suppress "imageBytesPerChannel"; - // editorTemplate -suppress "imageSizeBytes"; + editorTemplate -addControl "imageSequencePadding"; } editorTemplate -endLayout; // Internals that we don't want the user to see. - editorTemplate -suppress "imageSequencePadding"; editorTemplate -suppress "cameraWidthInch"; editorTemplate -suppress "cameraHeightInch"; editorTemplate -suppress "lensHashCurrent"; diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 27cc5c52a..bc852d75c 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -320,7 +320,9 @@ def get_frame_count(shp): start_frame = maya.cmds.getAttr(shp + '.imageSequenceStartFrame') end_frame = maya.cmds.getAttr(shp + '.imageSequenceEndFrame') frame_count = end_frame - start_frame - return frame_count + # When the start and end are the same value, that's still one + # frame. + return frame_count + 1 def get_image_sequence_size_bytes(shp): diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 0c5d8f7ef..fb03f6ff0 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -27,21 +27,30 @@ import mmSolver.logger import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.createimageplane._lib.constant as imageplane_const import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib LOG = mmSolver.logger.get_logger() +# Shorter alias. +_MM_IMAGE_PLANE_SHAPE_V2 = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 # Memory Conversion -BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) -BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) -BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) -KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) -KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) +_BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) +_BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) +_BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) +_KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) +_KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) def format_image_sequence_size(image_plane_shp): - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + """ + Look up node values and format as text. + + Example output: + '2,346.1MB (23.7MB x 102 frames)' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'format_image_sequence_size: image_plane_shp=%r', image_plane_shp, @@ -53,16 +62,21 @@ def format_image_sequence_size(image_plane_shp): image_plane_shp ) - seq_size_mb = int(image_sequence_size_bytes / BYTES_TO_MEGABYTES) - frame_size_mb = int(frame_size_bytes / BYTES_TO_MEGABYTES) - text = '2,346MB (23MB x 102 frames)' - text = '{seq_size_mb}MB (frame_size_mb)MB x {frame_count} frames)' + frame_size_mb = frame_size_bytes / _BYTES_TO_MEGABYTES + seq_size_mb = image_sequence_size_bytes / _BYTES_TO_MEGABYTES + text = '{seq_size_mb:0,.1f} MB ({frame_size_mb:0,.1f} MB x {frame_count} frames)' return text.format( seq_size_mb=seq_size_mb, frame_size_mb=frame_size_mb, frame_count=frame_count ) def _format_cache_used(used_bytes, capacity_bytes): + """ + Look up node values and format as text. + + Example text: + ' 42.1% (3.53 GB) of 8.00 GB' + """ assert isinstance(used_bytes, int) assert isinstance(capacity_bytes, int) @@ -70,13 +84,11 @@ def _format_cache_used(used_bytes, capacity_bytes): usage_gigabytes = 0 capacity_gigabyte = 0 if capacity_bytes > 0: - usage_percent = used_bytes / capacity_bytes - usage_gigabytes = used_bytes / BYTES_TO_GIGABYTES - capacity_gigabyte = capacity_bytes / BYTES_TO_GIGABYTES + usage_percent = (used_bytes / capacity_bytes) * 100.0 + usage_gigabytes = used_bytes / _BYTES_TO_GIGABYTES + capacity_gigabyte = capacity_bytes / _BYTES_TO_GIGABYTES - # TODO: Limit the percentage of the gigabyte float values to only - # 1 or 2 digits of precision. - text = '{usage_percent} ({usage_gigabytes}GB) of {capacity_gigabyte}GB' + text = '{usage_percent:3.1f}% ({usage_gigabytes:0,.2f} GB) of {capacity_gigabyte:0,.2f} GB' return text.format( usage_percent=usage_percent, usage_gigabytes=usage_gigabytes, @@ -85,53 +97,92 @@ def _format_cache_used(used_bytes, capacity_bytes): def format_cache_gpu_used(image_plane_shp): - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + """ + Look up node values and format text. + + Example text: + ' 42.1% (3.53GB) of 8.00GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'format_cache_gpu_used: image_plane_shp=%r', image_plane_shp, ) - text = '42% (3.5GB) of 8GB' - used_bytes = int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) - capacity_bytes = int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) - return _format_cache_used(used_bytes, capacity_bytes) + used_bytes = maya.cmds.mmImageCache(query=True, gpuUsed=True) + capacity_bytes = maya.cmds.mmImageCache(query=True, gpuCapacity=True) + LOG.info('used_bytes: %r', used_bytes) + LOG.info('capacity_bytes: %r', capacity_bytes) + return _format_cache_used(int(used_bytes), int(capacity_bytes)) def format_cache_cpu_used(image_plane_shp): - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + """ + Look up node values and format as text. + + Example text: + ' 23.1% (34.24 GB) of 240.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'format_cache_cpu_used: image_plane_shp=%r', image_plane_shp, ) - text = '23% (34GB) of 240GB' - used_bytes = int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) - capacity_bytes = int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) - return _format_cache_used(used_bytes, capacity_bytes) + used_bytes = maya.cmds.mmImageCache(query=True, cpuUsed=True) + capacity_bytes = maya.cmds.mmImageCache(query=True, cpuCapacity=True) + LOG.info('used_bytes: %r', used_bytes) + LOG.info('capacity_bytes: %r', capacity_bytes) + return _format_cache_used(int(used_bytes), int(capacity_bytes)) + + +def format_memory_gpu_available(image_plane_shp): + """ + Look up node values and format as text. + + Example text: + 'GPU 8.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + LOG.info( + 'format_memory_available: image_plane_shp=%r', + image_plane_shp, + ) + + memory_gigabytes = maya.cmds.mmMemoryGPU(query=True, total=True, asGigaBytes=True) + + text = 'GPU: {memory_gigabytes:0,.2f} GB' + return text.format( + memory_gigabytes=memory_gigabytes, + ) + +def format_memory_cpu_available(image_plane_shp): + """ + Look up node values and format as text. -def format_cache_available(image_plane_shp): - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + Example text: + 'CPU: 240.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( - 'format_cache_available: image_plane_shp=%r', + 'format_memory_available: image_plane_shp=%r', image_plane_shp, ) - cpu_memory_gigabytes = maya.cmds.mmMemorySystem(query=True, systemPhysicalMemoryTotal=True, asGigaBytes=True) - gpu_memory_gigabytes = maya.cmds.mmMemoryGPU(query=True, total=True, asGigaBytes=True) + memory_gigabytes = maya.cmds.mmMemorySystem( + query=True, systemPhysicalMemoryTotal=True, asGigaBytes=True + ) - text = 'CPU: 240GB | GPU: 8GB' - # TODO: Round these values to the closest value. - text = 'CPU: {cpu_memory_gigabytes}GB | GPU: {gpu_memory_gigabytes}GB' + text = 'CPU: {memory_gigabytes:0,.2f} GB' return text.format( - cpu_memory_gigabytes=cpu_memory_gigabytes, - gpu_memory_gigabytes=gpu_memory_gigabytes + memory_gigabytes=memory_gigabytes, ) def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'cache_remove_all_image_plane_slots: image_plane_shp=%r, cache_type=%r', image_plane_shp, @@ -142,7 +193,7 @@ def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'cache_remove_active_image_plane_slot: image_plane_shp=%r, cache_type=%r', image_plane_shp, @@ -153,7 +204,7 @@ def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): def cache_remove_unused_image_plane_slots(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == 'mmImagePlaneShape2' + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( 'cache_remove_unused_image_plane_slots: image_plane_shp=%r, cache_type=%r', image_plane_shp, From 5cd2445fc704be90f33390de19f5048cf498f549 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 20:39:21 +1000 Subject: [PATCH 109/295] Remove deprecation warnings for MUserData --- src/mmSolver/shape/BundleDrawOverride.h | 9 ++++++++- src/mmSolver/shape/ImagePlaneGeometry2Override.h | 2 +- src/mmSolver/shape/ImagePlaneGeometryOverride.cpp | 8 +++++++- src/mmSolver/shape/LineDrawOverride.h | 9 ++++++++- src/mmSolver/shape/MarkerDrawOverride.h | 9 ++++++++- src/mmSolver/shape/SkyDomeDrawOverride.h | 9 ++++++++- 6 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/mmSolver/shape/BundleDrawOverride.h b/src/mmSolver/shape/BundleDrawOverride.h index d2ce7f11f..71fc76597 100644 --- a/src/mmSolver/shape/BundleDrawOverride.h +++ b/src/mmSolver/shape/BundleDrawOverride.h @@ -48,13 +48,20 @@ namespace mmsolver { class BundleDrawData : public MUserData { public: BundleDrawData() +#if MAYA_API_VERSION >= 20220000 + : MUserData() +#else + // MUserData(bool) constructor is deprecated in Maya 2022+ + // because 'deleteAfterUse' is no longer needed. : MUserData(/*deleteAfterUse=*/true) // let Maya clean up +#endif , m_depth_priority(0) , m_line_width(1.0) , m_point_size(1.0) , m_icon_size(1.0) , m_active(false) - , m_draw_name(false) {} + , m_draw_name(false) { + } ~BundleDrawData() override {} diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index c44922286..dc8fd263c 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -60,7 +60,7 @@ class ShaderLinkLostUserData2 : public MUserData { #else // MUserData(bool) constructor is deprecated in Maya 2022+ // because 'deleteAfterUse' is no longer needed. - : MUserData(true) + : MUserData(/*deleteAfterUse=*/true) // let Maya clean up #endif , link_lost_count(0) , set_shader_count(0) { diff --git a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp index e6e79874f..95298be7a 100644 --- a/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometryOverride.cpp @@ -271,10 +271,16 @@ void ImagePlaneGeometryOverride::updateRenderItems(const MDagPath &path, // needs to be re-compiled. auto linkLostCb = nullptr; auto linkLostUserData = nullptr; - bool nonTextured = false; + const bool nonTextured = false; +#if MAYA_API_VERSION >= 20220000 + shadedItem->setShaderFromNode2(m_shader_node, m_geometry_node_path, + linkLostCb, linkLostUserData, + nonTextured); +#else shadedItem->setShaderFromNode(m_shader_node, m_geometry_node_path, linkLostCb, linkLostUserData, nonTextured); +#endif } else { MMSOLVER_MAYA_WRN( "mmImagePlaneShape: " diff --git a/src/mmSolver/shape/LineDrawOverride.h b/src/mmSolver/shape/LineDrawOverride.h index 6486f1589..11c9fb719 100644 --- a/src/mmSolver/shape/LineDrawOverride.h +++ b/src/mmSolver/shape/LineDrawOverride.h @@ -49,7 +49,13 @@ namespace mmsolver { class LineDrawData : public MUserData { public: LineDrawData() +#if MAYA_API_VERSION >= 20220000 + : MUserData() +#else + // MUserData(bool) constructor is deprecated in Maya 2022+ + // because 'deleteAfterUse' is no longer needed. : MUserData(/*deleteAfterUse=*/true) // let Maya clean up +#endif , m_depth_priority(0) , m_inner_line_width(1.0) , m_middle_line_width(1.0) @@ -58,7 +64,8 @@ class LineDrawData : public MUserData { , m_point_list() , m_active(false) , m_draw_name(false) - , m_draw_middle(false) {} + , m_draw_middle(false) { + } ~LineDrawData() override {} diff --git a/src/mmSolver/shape/MarkerDrawOverride.h b/src/mmSolver/shape/MarkerDrawOverride.h index 8d2c29444..fc0085b8c 100644 --- a/src/mmSolver/shape/MarkerDrawOverride.h +++ b/src/mmSolver/shape/MarkerDrawOverride.h @@ -46,7 +46,13 @@ namespace mmsolver { class MarkerDrawData : public MUserData { public: MarkerDrawData() +#if MAYA_API_VERSION >= 20220000 + : MUserData() +#else + // MUserData(bool) constructor is deprecated in Maya 2022+ + // because 'deleteAfterUse' is no longer needed. : MUserData(/*deleteAfterUse=*/true) // let Maya clean up +#endif , m_depth_priority(0) , m_line_width(1.0) , m_point_size(1.0) @@ -54,7 +60,8 @@ class MarkerDrawData : public MUserData { , m_locked(false) , m_active(false) , m_draw_name(false) - , m_visible(true) {} + , m_visible(true) { + } ~MarkerDrawData() override {} diff --git a/src/mmSolver/shape/SkyDomeDrawOverride.h b/src/mmSolver/shape/SkyDomeDrawOverride.h index a829a0f07..9ebfc932e 100644 --- a/src/mmSolver/shape/SkyDomeDrawOverride.h +++ b/src/mmSolver/shape/SkyDomeDrawOverride.h @@ -46,7 +46,13 @@ namespace mmsolver { class SkyDomeDrawData : public MUserData { public: SkyDomeDrawData() +#if MAYA_API_VERSION >= 20220000 + : MUserData() +#else + // MUserData(bool) constructor is deprecated in Maya 2022+ + // because 'deleteAfterUse' is no longer needed. : MUserData(/*deleteAfterUse=*/true) // let Maya clean up +#endif , m_draw_mode(static_cast(DrawMode::kDrawOnTop)) , m_transform_mode(static_cast(TransformMode::kNoOffset)) , m_line_width(1.0) @@ -77,7 +83,8 @@ class SkyDomeDrawData : public MUserData { , m_grid_lat_line_width(1.0f) , m_grid_long_line_width(1.0f) , m_grid_lat_divisions(6) - , m_grid_long_divisions(6) {} + , m_grid_long_divisions(6) { + } ~SkyDomeDrawData() override {} From 51e043f89b767b2cd36db09ab06ec929db17525f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 20:40:02 +1000 Subject: [PATCH 110/295] mmImagePlaneShape2 - Remove 'color' from display attr names --- src/mmSolver/shape/ImagePlaneShape2Node.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index 7834c1c74..18536eb6e 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -305,6 +305,7 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setReadable(true)); CHECK_MSTATUS(nAttr.setWritable(true)); CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Gain"))); CHECK_MSTATUS(addAttribute(m_image_color_gain)); const float exposure_soft_min = -9.0f; @@ -316,6 +317,7 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setKeyable(true)); CHECK_MSTATUS(nAttr.setSoftMin(exposure_soft_min)); CHECK_MSTATUS(nAttr.setSoftMax(exposure_soft_max)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Exposure"))); CHECK_MSTATUS(addAttribute(m_image_color_exposure)); const float gamma_min = 0.0f; @@ -327,6 +329,7 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setKeyable(true)); CHECK_MSTATUS(nAttr.setMin(gamma_min)); CHECK_MSTATUS(nAttr.setSoftMax(gamma_soft_max)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Gamma"))); CHECK_MSTATUS(addAttribute(m_image_color_gamma)); const float saturation_min = 0.0f; @@ -339,6 +342,7 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setKeyable(true)); CHECK_MSTATUS(nAttr.setMin(saturation_min)); CHECK_MSTATUS(nAttr.setSoftMax(saturation_soft_max)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("Saturation"))); CHECK_MSTATUS(addAttribute(m_image_color_saturation)); const float soft_clip_min = 0.0f; @@ -351,6 +355,7 @@ MStatus ImagePlaneShape2Node::initialize() { CHECK_MSTATUS(nAttr.setKeyable(true)); CHECK_MSTATUS(nAttr.setMin(soft_clip_min)); CHECK_MSTATUS(nAttr.setMax(soft_clip_max)); + CHECK_MSTATUS(nAttr.setNiceNameOverride(MString("SoftClip"))); CHECK_MSTATUS(addAttribute(m_image_color_soft_clip)); const double alpha_min = 0.0; From 5d00ecb3a1ad6d65415cf8e8e6cdb8363001562a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 22:47:26 +1000 Subject: [PATCH 111/295] Image Cache UI - Update real values. --- python/mmSolver/tools/imagecache/lib.py | 86 ++++++++++--- .../tools/imagecache/ui/imagecache_layout.py | 121 ++++++++++++++++++ .../tools/imagecache/ui/imagecache_layout.ui | 32 ++--- 3 files changed, 206 insertions(+), 33 deletions(-) diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index fb03f6ff0..6e2e3864d 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -51,7 +51,7 @@ def format_image_sequence_size(image_plane_shp): '2,346.1MB (23.7MB x 102 frames)' """ assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( + LOG.debug( 'format_image_sequence_size: image_plane_shp=%r', image_plane_shp, ) @@ -104,15 +104,13 @@ def format_cache_gpu_used(image_plane_shp): ' 42.1% (3.53GB) of 8.00GB' """ assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( + LOG.debug( 'format_cache_gpu_used: image_plane_shp=%r', image_plane_shp, ) - used_bytes = maya.cmds.mmImageCache(query=True, gpuUsed=True) - capacity_bytes = maya.cmds.mmImageCache(query=True, gpuCapacity=True) - LOG.info('used_bytes: %r', used_bytes) - LOG.info('capacity_bytes: %r', capacity_bytes) + used_bytes = get_gpu_cache_used_bytes() + capacity_bytes = get_gpu_cache_capacity_bytes() return _format_cache_used(int(used_bytes), int(capacity_bytes)) @@ -124,15 +122,13 @@ def format_cache_cpu_used(image_plane_shp): ' 23.1% (34.24 GB) of 240.00 GB' """ assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( + LOG.debug( 'format_cache_cpu_used: image_plane_shp=%r', image_plane_shp, ) - used_bytes = maya.cmds.mmImageCache(query=True, cpuUsed=True) - capacity_bytes = maya.cmds.mmImageCache(query=True, cpuCapacity=True) - LOG.info('used_bytes: %r', used_bytes) - LOG.info('capacity_bytes: %r', capacity_bytes) + used_bytes = get_cpu_cache_used_bytes() + capacity_bytes = get_cpu_cache_capacity_bytes() return _format_cache_used(int(used_bytes), int(capacity_bytes)) @@ -144,12 +140,15 @@ def format_memory_gpu_available(image_plane_shp): 'GPU 8.00 GB' """ assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( + LOG.debug( 'format_memory_available: image_plane_shp=%r', image_plane_shp, ) - memory_gigabytes = maya.cmds.mmMemoryGPU(query=True, total=True, asGigaBytes=True) + memory_bytes = get_gpu_memory_total_bytes() + memory_gigabytes = 0.0 + if memory_bytes > 0: + memory_gigabytes = memory_bytes / _BYTES_TO_GIGABYTES text = 'GPU: {memory_gigabytes:0,.2f} GB' return text.format( @@ -165,14 +164,15 @@ def format_memory_cpu_available(image_plane_shp): 'CPU: 240.00 GB' """ assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( + LOG.debug( 'format_memory_available: image_plane_shp=%r', image_plane_shp, ) - memory_gigabytes = maya.cmds.mmMemorySystem( - query=True, systemPhysicalMemoryTotal=True, asGigaBytes=True - ) + memory_bytes = get_cpu_memory_total_bytes() + memory_gigabytes = 0.0 + if memory_bytes > 0: + memory_gigabytes = memory_bytes / _BYTES_TO_GIGABYTES text = 'CPU: {memory_gigabytes:0,.2f} GB' return text.format( @@ -180,6 +180,58 @@ def format_memory_cpu_available(image_plane_shp): ) +def get_gpu_cache_item_count(): + # TODO: Query the image cache. + return 42 + + +def get_cpu_cache_item_count(): + # TODO: Query the image cache. + return 42 + + +def get_gpu_cache_used_bytes(): + return int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) + + +def get_cpu_cache_used_bytes(): + return int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) + + +def get_gpu_cache_capacity_bytes(): + return int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) + + +def get_cpu_cache_capacity_bytes(): + return int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) + + +def get_gpu_memory_total_bytes(): + return int(maya.cmds.mmMemoryGPU(query=True, total=True)) + + +def get_gpu_memory_used_bytes(): + return int(maya.cmds.mmMemoryGPU(query=True, used=True)) + + +def get_cpu_memory_total_bytes(): + return int( + maya.cmds.mmMemorySystem( + query=True, + systemPhysicalMemoryTotal=True, + ) + ) + + +def get_cpu_memory_used_bytes(): + return int( + maya.cmds.mmMemorySystem( + query=True, + systemPhysicalMemoryUsed=True, + ) + ) + + def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py index a8a5535d7..520c48f04 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py +++ b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py @@ -25,22 +25,132 @@ qtpyutils.override_binding_order() import mmSolver.ui.Qt.QtWidgets as QtWidgets +import mmSolver.ui.Qt.QtCore as QtCore import mmSolver.logger import mmSolver.tools.imagecache.ui.ui_imagecache_layout as ui_imagecache_layout +import mmSolver.tools.imagecache.lib as lib LOG = mmSolver.logger.get_logger() +# Memory Conversion +_BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) +_BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) +_BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) +_KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) +_KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) + + +def memoryTotalUpdateUi( + label, + size_bytes, +): + gigabytes = 0.0 + if size_bytes > 0: + gigabytes = size_bytes / _BYTES_TO_GIGABYTES + text = '{:0,.2f} GB'.format(gigabytes) + label.setText(text) + + +def memoryUsedUpdateUi(label, used_size_bytes, total_size_bytes): + used_gigabytes = 0.0 + if used_size_bytes > 0: + used_gigabytes = used_size_bytes / _BYTES_TO_GIGABYTES + + used_percent = 0.0 + if used_size_bytes > 0 and total_size_bytes > 0: + used_percent = (used_size_bytes / total_size_bytes) * 100.0 + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) + label.setText(text) + + +def cacheItemCountUpdateUi( + label, + value, +): + text = '{:,}'.format(value) + label.setText(text) + + +def cacheCapacityUpdateUi( + label, + size_bytes, +): + gigabytes = 0.0 + if size_bytes > 0: + gigabytes = size_bytes / _BYTES_TO_GIGABYTES + text = '{:0,.2f} GB'.format(gigabytes) + label.setText(text) + + +def cacheUsedUpdateUi(label, used_size_bytes, capacity_size_bytes): + used_gigabytes = 0.0 + if used_size_bytes > 0: + used_gigabytes = used_size_bytes / _BYTES_TO_GIGABYTES + + used_percent = 0.0 + if used_size_bytes > 0 and capacity_size_bytes > 0: + used_percent = (used_size_bytes / capacity_size_bytes) * 100.0 + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) + label.setText(text) + + class ImageCacheLayout(QtWidgets.QWidget, ui_imagecache_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): super(ImageCacheLayout, self).__init__(*args, **kwargs) self.setupUi(self) + # Update timer. + self.update_timer = QtCore.QTimer() + seconds = 0.5 + milliseconds = int(seconds * 1000) + self.update_timer.setInterval(milliseconds) + self.update_timer.timeout.connect(self.updateUiValues) + self.update_timer.start() + # Populate the UI with data self.populateUi() + def updateUiValues(self): + LOG.debug('updateUiValues...') + + gpu_cache_item_count = lib.get_gpu_cache_item_count() + cpu_cache_item_count = lib.get_cpu_cache_item_count() + gpu_cache_used = lib.get_gpu_cache_used_bytes() + cpu_cache_used = lib.get_cpu_cache_used_bytes() + gpu_cache_capacity = lib.get_gpu_cache_capacity_bytes() + cpu_cache_capacity = lib.get_cpu_cache_capacity_bytes() + gpu_memory_total = lib.get_gpu_memory_total_bytes() + cpu_memory_total = lib.get_cpu_memory_total_bytes() + gpu_memory_used = lib.get_gpu_memory_used_bytes() + cpu_memory_used = lib.get_cpu_memory_used_bytes() + + memoryTotalUpdateUi(self.gpuMemoryTotalValue_label, gpu_memory_total) + memoryTotalUpdateUi(self.cpuMemoryTotalValue_label, cpu_memory_total) + + memoryUsedUpdateUi( + self.gpuMemoryUsedValue_label, gpu_memory_used, gpu_memory_total + ) + memoryUsedUpdateUi( + self.cpuMemoryUsedValue_label, cpu_memory_used, cpu_memory_total + ) + + cacheItemCountUpdateUi(self.gpuCacheItemCountValue_label, gpu_cache_item_count) + cacheItemCountUpdateUi(self.cpuCacheItemCountValue_label, cpu_cache_item_count) + + cacheCapacityUpdateUi(self.gpuCacheCapacityValue_label, gpu_cache_capacity) + cacheCapacityUpdateUi(self.cpuCacheCapacityValue_label, cpu_cache_capacity) + + cacheUsedUpdateUi( + self.cpuCacheUsedValue_label, cpu_cache_used, cpu_cache_capacity + ) + cacheUsedUpdateUi( + self.gpuCacheUsedValue_label, gpu_cache_used, gpu_cache_capacity + ) + return + def reset_options(self): self.populateUi() @@ -48,4 +158,15 @@ def populateUi(self): """ Update the UI for the first time the class is created. """ + self.updateUiValues() + + # name = const.CONFIG_WIDTH_KEY + # value = configmaya.get_scene_option(name, default=const.DEFAULT_WIDTH) + # LOG.debug('key=%r value=%r', name, value) + # self.width_horizontalSlider.setValue(value) + # self.width_spinBox.setValue(value) + + # self.gpuCacheCapacityGigaBytes_label + # self.gpuCacheCapacity_horizontalSlider + # self.gpuCacheCapacity_doubleSpinBox return diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui b/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui index ebc591651..e9c72d77b 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui +++ b/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui @@ -219,9 +219,9 @@ - + - Items: + Item Count: @@ -239,7 +239,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> @@ -250,9 +250,9 @@ - + - Cache Capacity: + Capacity: @@ -270,7 +270,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">2.0 GB</span></p></body></html> @@ -281,9 +281,9 @@ - + - Cache Used: + Used: @@ -301,7 +301,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">0.125 GB (6.3%)</span></p></body></html> @@ -321,9 +321,9 @@ - + - Items: + Item Count: @@ -341,7 +341,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> @@ -352,7 +352,7 @@ - + Capacity: @@ -372,7 +372,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">128.0 GB</span></p></body></html> @@ -383,7 +383,7 @@ - + Used: @@ -403,7 +403,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">22.5 GB (17.5%)</span></p></body></html> From 6cc5a0ffa64acc25b00e6f75d33e8506236354f0 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 21 Jun 2024 23:11:12 +1000 Subject: [PATCH 112/295] Fix type-hint doc-strings for opening windows. --- .../mmSolver/tools/centertwodee/ui/centertwodee_window.py | 4 ++-- python/mmSolver/tools/channelsen/ui/channelsen_window.py | 6 +++--- .../mmSolver/tools/convertmarker/ui/convertmarker_window.py | 4 ++-- python/mmSolver/tools/imagecache/ui/imagecache_window.py | 4 ++-- python/mmSolver/tools/loadmarker/ui/loadmarker_window.py | 4 ++-- .../mmSolver/tools/raycastmarker/ui/raycastmarker_window.py | 6 +++--- python/mmSolver/tools/reparent2/ui/reparent2_window.py | 4 ++-- .../tools/setcameraoriginframe/ui/originframe_window.py | 4 ++-- .../mmSolver/tools/smoothkeyframes/ui/smoothkeys_window.py | 4 ++-- .../tools/surfacecluster/ui/surfacecluster_window.py | 4 ++-- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/python/mmSolver/tools/centertwodee/ui/centertwodee_window.py b/python/mmSolver/tools/centertwodee/ui/centertwodee_window.py index ff28e0017..fd90a6958 100644 --- a/python/mmSolver/tools/centertwodee/ui/centertwodee_window.py +++ b/python/mmSolver/tools/centertwodee/ui/centertwodee_window.py @@ -188,9 +188,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new center 2D window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: CenterTwoDeeWindow or None. """ win = CenterTwoDeeWindow.open_window( show=show, auto_raise=auto_raise, delete=delete diff --git a/python/mmSolver/tools/channelsen/ui/channelsen_window.py b/python/mmSolver/tools/channelsen/ui/channelsen_window.py index 6b3f8ac42..eb6afdcbb 100644 --- a/python/mmSolver/tools/channelsen/ui/channelsen_window.py +++ b/python/mmSolver/tools/channelsen/ui/channelsen_window.py @@ -85,9 +85,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be - opened. - :rtype: SolverWindow or None. + :returns: A new channel sensitivity window, or None if the window + cannot be opened. + :rtype: ChannelSenWindow or None. """ win = ChannelSenWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/convertmarker/ui/convertmarker_window.py b/python/mmSolver/tools/convertmarker/ui/convertmarker_window.py index 7495592de..f11c0e43c 100644 --- a/python/mmSolver/tools/convertmarker/ui/convertmarker_window.py +++ b/python/mmSolver/tools/convertmarker/ui/convertmarker_window.py @@ -107,9 +107,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new convert marker window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: ConvertMarkerWindow or None. """ win = ConvertMarkerWindow.open_window( show=show, auto_raise=auto_raise, delete=delete diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_window.py b/python/mmSolver/tools/imagecache/ui/imagecache_window.py index b5fb73f7a..f74e377bf 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_window.py +++ b/python/mmSolver/tools/imagecache/ui/imagecache_window.py @@ -125,9 +125,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new image cache window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: ImageCacheWindow or None. """ win = ImageCacheWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/loadmarker/ui/loadmarker_window.py b/python/mmSolver/tools/loadmarker/ui/loadmarker_window.py index a2596e707..e0641841b 100644 --- a/python/mmSolver/tools/loadmarker/ui/loadmarker_window.py +++ b/python/mmSolver/tools/loadmarker/ui/loadmarker_window.py @@ -259,9 +259,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new loadMarker window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: LoadMarkerWindow or None. """ win = LoadMarkerWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/raycastmarker/ui/raycastmarker_window.py b/python/mmSolver/tools/raycastmarker/ui/raycastmarker_window.py index 8dd9e4e19..6aa2678ce 100644 --- a/python/mmSolver/tools/raycastmarker/ui/raycastmarker_window.py +++ b/python/mmSolver/tools/raycastmarker/ui/raycastmarker_window.py @@ -106,9 +106,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be - opened. - :rtype: SolverWindow or None. + :returns: A new ray-cast marker window, or None if the window cannot + be opened. + :rtype: RayCastMarkerWindow or None. """ win = RayCastMarkerWindow.open_window( show=show, auto_raise=auto_raise, delete=delete diff --git a/python/mmSolver/tools/reparent2/ui/reparent2_window.py b/python/mmSolver/tools/reparent2/ui/reparent2_window.py index 82f24ad6f..5283a60db 100644 --- a/python/mmSolver/tools/reparent2/ui/reparent2_window.py +++ b/python/mmSolver/tools/reparent2/ui/reparent2_window.py @@ -140,9 +140,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new reparent2 window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: Reparent2Window or None. """ win = Reparent2Window.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/setcameraoriginframe/ui/originframe_window.py b/python/mmSolver/tools/setcameraoriginframe/ui/originframe_window.py index d6c5a80ac..e0a8e62cd 100644 --- a/python/mmSolver/tools/setcameraoriginframe/ui/originframe_window.py +++ b/python/mmSolver/tools/setcameraoriginframe/ui/originframe_window.py @@ -108,9 +108,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new origin frame window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: OriginFrameWindow or None. """ win = OriginFrameWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/smoothkeyframes/ui/smoothkeys_window.py b/python/mmSolver/tools/smoothkeyframes/ui/smoothkeys_window.py index a4c8a33cf..19af36be1 100644 --- a/python/mmSolver/tools/smoothkeyframes/ui/smoothkeys_window.py +++ b/python/mmSolver/tools/smoothkeyframes/ui/smoothkeys_window.py @@ -108,9 +108,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new smooth keys window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: SmoothKeysWindow or None. """ win = SmoothKeysWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) return win diff --git a/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py index 1608bc87c..509472768 100644 --- a/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py +++ b/python/mmSolver/tools/surfacecluster/ui/surfacecluster_window.py @@ -108,9 +108,9 @@ def main(show=True, auto_raise=True, delete=False): developing the UI in Maya script editor. :type delete: bool - :returns: A new solver window, or None if the window cannot be + :returns: A new surface cluster window, or None if the window cannot be opened. - :rtype: SolverWindow or None. + :rtype: SurfaceClusterWindow or None. """ win = SurfaceClusterWindow.open_window( show=show, auto_raise=auto_raise, delete=delete From f4107ddb85efb001f4fca6e0420e36879860898b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 00:48:07 +1000 Subject: [PATCH 113/295] mmImagePlaneShape2 - Add color space button pop-up menu. --- .../AEmmImagePlaneShape2Template.mel | 123 +++++++++++++++++- 1 file changed, 117 insertions(+), 6 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index eaa2d9be4..ce3534f8a 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -235,6 +235,116 @@ global proc AEmmImagePlaneShape2_imageSequenceReplace(string $file_attr) } +proc string set_color_space_make_command(string $quoted_node_attr, string $control_name, string $value) +{ + string $cmd = ""; + // TODO: Check if the node is editable first? + $cmd += "setAttr -lock false " + $quoted_node_attr + ";"; + $cmd += "setAttr -type \"string\" " + $quoted_node_attr + " \"" + $value + "\";"; + $cmd += "setAttr -lock true " + $quoted_node_attr + ";"; + $cmd += "button -edit -label \"" + $value + "\" " + $control_name + ";"; + return $cmd; +} + +global proc AEmmImagePlaneShape2_inputColorSpaceNew(string $attr_name) +{ + string $image_plane_shp[]; + tokenize($attr_name, ".", $image_plane_shp); + if(size($image_plane_shp) < 1) { + return; + } + + string $attr_names[]; + tokenize ($attr_name, ".", $attr_names); + + string $nice_name = `attributeName -nice $attr_name`; + string $node_attr = $image_plane_shp[0] + "." + $attr_names[1]; + string $quoted_node_attr = "\"" + $image_plane_shp[0] + "." + $attr_names[1] + "\""; + + setUITemplate -pst attributeEditorTemplate; + + string $current_value = `getAttr $node_attr`; + string $role = ""; + string $cmd = ""; + + rowLayout -nc 3 inputColorSpaceLayout; + { + // TODO: Convert color space button into a Python script. + text -label $nice_name; + + string $button_name = "inputColorSpaceButton"; + button inputColorSpaceButton; + + if (size($current_value) == 0) { + button -edit -label "" inputColorSpaceButton; + } else { + button -edit -label $current_value inputColorSpaceButton; + } + + int $left_mouse_button = 1; + popupMenu -button $left_mouse_button; + + $role = `mmColorIO -roleDefault true`; + if (size($role) > 0) { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $role); + menuItem -label ("Default: " + $role) -command $cmd; + } + + $role = `mmColorIO -roleSceneLinear true`; + if (size($role) > 0) { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $role); + menuItem -label ("Scene Linear: " + $role) -command $cmd; + } + + $role = `mmColorIO -roleMattePaint true`; + if (size($role) > 0) { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $role); + menuItem -label ("Matte Paint: " + $role) -command $cmd; + } + + $role = `mmColorIO -roleData true`; + if (size($role) > 0) { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $role); + menuItem -label ("Data: " + $role) -command $cmd; + } + + $role = `mmColorIO -roleColorPicking true`; + if (size($role) > 0) { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $role); + menuItem -label ("Color Picking: " + $role) -command $cmd; + } + + menuItem -divider true; + + string $names[] = `mmColorIO -listColorSpacesActive true`; + string $name; + for($name in $names) + { + $cmd = set_color_space_make_command( + $quoted_node_attr, $button_name, $name); + menuItem -label $name -command $cmd; + } + + } + setParent ..; + + setUITemplate -ppt; + + AEmmImagePlaneShape2_inputColorSpaceReplace $attr_name; +} + + +global proc AEmmImagePlaneShape2_inputColorSpaceReplace(string $attr_name) +{ + // Nothing needed here. +} + + global proc AEmmImagePlaneShape2_imageCache_updateValues( string $image_plane_shp) { @@ -510,12 +620,11 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "imageSequenceStartFrame"; editorTemplate -addControl "imageSequenceEndFrame"; editorTemplate -addSeparator; - // TODO: Use mmColorIO results to allow users to manually give the - // inputColorSpace. - // - // TODO: Add a custom script with a button and pop-up menu to - // choose the color space. - editorTemplate -addControl "inputColorSpace"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_inputColorSpaceNew" + "AEmmImagePlaneShape2_inputColorSpaceReplace" + "inputColorSpace"; editorTemplate -addSeparator; // TODO: Add radio button to choose what will connect to the // 'imageSequenceFrame' value? Options are: @@ -579,6 +688,8 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "imageBytesPerChannel"; editorTemplate -addControl "imageSizeBytes"; editorTemplate -addControl "imageSequencePadding"; + editorTemplate -addControl "inputColorSpace"; + editorTemplate -addControl "outputColorSpace"; } editorTemplate -endLayout; From c729fc386905e112125556aba9f8b6b40f0c011b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 01:22:50 +1000 Subject: [PATCH 114/295] Image Cache - Get Item Count with Python. GitHub issue #252. --- python/mmSolver/tools/imagecache/lib.py | 6 ++-- src/mmSolver/cmd/MMImageCacheCmd.cpp | 44 +++++++++++++------------ src/mmSolver/cmd/MMImageCacheCmd.h | 2 ++ src/mmSolver/image/ImageCache.h | 14 ++++++++ 4 files changed, 41 insertions(+), 25 deletions(-) diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 6e2e3864d..e5a49247b 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -181,13 +181,11 @@ def format_memory_cpu_available(image_plane_shp): def get_gpu_cache_item_count(): - # TODO: Query the image cache. - return 42 + return int(maya.cmds.mmImageCache(query=True, gpuItemCount=True)) def get_cpu_cache_item_count(): - # TODO: Query the image cache. - return 42 + return int(maya.cmds.mmImageCache(query=True, cpuItemCount=True)) def get_gpu_cache_used_bytes(): diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 0f385c695..27574b9b0 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -56,6 +56,12 @@ #define CPU_USED_FLAG "-cpu" #define CPU_USED_FLAG_LONG "-cpuUsed" +#define GPU_ITEM_COUNT_FLAG "-gpi" +#define GPU_ITEM_COUNT_FLAG_LONG "-gpuItemCount" + +#define CPU_ITEM_COUNT_FLAG "-cpi" +#define CPU_ITEM_COUNT_FLAG_LONG "-cpuItemCount" + #define BRIEF_TEXT_FLAG "-btx" #define BRIEF_TEXT_FLAG_LONG "-briefText" @@ -92,6 +98,11 @@ MSyntax MMImageCacheCmd::newSyntax() { CHECK_MSTATUS(syntax.addFlag(GPU_USED_FLAG, GPU_USED_FLAG_LONG)); CHECK_MSTATUS(syntax.addFlag(CPU_USED_FLAG, CPU_USED_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(GPU_ITEM_COUNT_FLAG, GPU_ITEM_COUNT_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(CPU_ITEM_COUNT_FLAG, CPU_ITEM_COUNT_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(BRIEF_TEXT_FLAG, BRIEF_TEXT_FLAG_LONG)); return syntax; @@ -123,29 +134,12 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { const bool has_cpu_capacity = argData.isFlagSet(CPU_CAPACITY_FLAG, &status); const bool has_gpu_used = argData.isFlagSet(GPU_USED_FLAG, &status); const bool has_cpu_used = argData.isFlagSet(CPU_USED_FLAG, &status); + const bool has_gpu_item_count = + argData.isFlagSet(GPU_ITEM_COUNT_FLAG, &status); + const bool has_cpu_item_count = + argData.isFlagSet(CPU_ITEM_COUNT_FLAG, &status); const bool has_print_brief = argData.isFlagSet(BRIEF_TEXT_FLAG, &status); - MMSOLVER_MAYA_VRB( - "MMImageCacheCmd::parseArgs: " - "has_gpu_capacity=" - << has_gpu_capacity); - MMSOLVER_MAYA_VRB( - "MMImageCacheCmd::parseArgs: " - "has_cpu_capacity=" - << has_cpu_capacity); - MMSOLVER_MAYA_VRB( - "MMImageCacheCmd::parseArgs: " - "has_gpu_used=" - << has_gpu_used); - MMSOLVER_MAYA_VRB( - "MMImageCacheCmd::parseArgs: " - "has_cpu_used=" - << has_cpu_used); - MMSOLVER_MAYA_VRB( - "MMImageCacheCmd::parseArgs: " - "has_print_brief=" - << has_print_brief); - if (m_is_query) { if (has_gpu_capacity) { m_command_flag = ImageCacheFlagMode::kGpuCapacity; @@ -155,6 +149,10 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { m_command_flag = ImageCacheFlagMode::kGpuUsed; } else if (has_cpu_used) { m_command_flag = ImageCacheFlagMode::kCpuUsed; + } else if (has_gpu_item_count) { + m_command_flag = ImageCacheFlagMode::kGpuItemCount; + } else if (has_cpu_item_count) { + m_command_flag = ImageCacheFlagMode::kCpuItemCount; } else if (has_print_brief) { m_command_flag = ImageCacheFlagMode::kGenerateBriefText; } else { @@ -285,6 +283,10 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { bytes_value = image_cache.get_gpu_used_bytes(); } else if (m_command_flag == ImageCacheFlagMode::kCpuUsed) { bytes_value = image_cache.get_cpu_used_bytes(); + } else if (m_command_flag == ImageCacheFlagMode::kGpuItemCount) { + bytes_value = image_cache.get_gpu_item_count(); + } else if (m_command_flag == ImageCacheFlagMode::kCpuItemCount) { + bytes_value = image_cache.get_cpu_item_count(); } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::doIt: " diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index 39e0751a3..cb79685e7 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -122,6 +122,8 @@ enum class ImageCacheFlagMode : uint8_t { kCpuCapacity, kGpuUsed, kCpuUsed, + kGpuItemCount, + kCpuItemCount, kGenerateBriefText, kUnknown = 255 }; diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 1a957fa2c..55eed9b04 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -176,6 +176,20 @@ struct ImageCache { << "m_cpu_used_bytes=" << m_cpu_used_bytes); return m_cpu_used_bytes; } + size_t get_gpu_item_count() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_item_count: " + << "m_gpu_cache_map.size()=" + << m_gpu_cache_map.size()); + return m_gpu_cache_map.size(); + } + size_t get_cpu_item_count() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_item_count: " + << "m_cpu_cache_map.size()=" + << m_cpu_cache_map.size()); + return m_cpu_cache_map.size(); + } // Set the capacity of the cache. void set_gpu_capacity_bytes(MHWRender::MTextureManager *texture_manager, From d9db3f457d616c0ad7ca61711309d10b15951df8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 01:23:54 +1000 Subject: [PATCH 115/295] mmImagePlaneShape2 - Unlock/Relock color space attributes This will fix an error caused when the user changes the Image Sequence slot. --- .../tools/createimageplane/_lib/main.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/main.py b/python/mmSolver/tools/createimageplane/_lib/main.py index 32fd8bc84..633815a7b 100644 --- a/python/mmSolver/tools/createimageplane/_lib/main.py +++ b/python/mmSolver/tools/createimageplane/_lib/main.py @@ -171,6 +171,13 @@ def _set_image_sequence_v1(mm_image_plane_node, image_sequence_path, attr_name=N return +def _set_locked_string_attr(node_attr, value): + maya.cmds.setAttr(node_attr, lock=False) + maya.cmds.setAttr(node_attr, value, type='string') + maya.cmds.setAttr(node_attr, lock=True) + return + + def _set_image_sequence_v2(mm_image_plane_node, image_sequence_path, attr_name=None): if attr_name is None: attr_name = lib_const.DEFAULT_IMAGE_SEQUENCE_ATTR_NAME @@ -208,16 +215,11 @@ def _set_image_sequence_v2(mm_image_plane_node, image_sequence_path, attr_name=N input_color_space = _guess_color_space(first_frame_file_seq) output_color_space = maya.cmds.mmColorIO(roleSceneLinear=True) - maya.cmds.setAttr( - shp + '.' + lib_const.INPUT_COLOR_SPACE_ATTR_NAME, - input_color_space, - type='string', - ) - maya.cmds.setAttr( - shp + '.' + lib_const.OUTPUT_COLOR_SPACE_ATTR_NAME, - output_color_space, - type='string', - ) + node_attr = shp + '.' + lib_const.INPUT_COLOR_SPACE_ATTR_NAME + _set_locked_string_attr(node_attr, input_color_space) + + node_attr = shp + '.' + lib_const.OUTPUT_COLOR_SPACE_ATTR_NAME + _set_locked_string_attr(node_attr, output_color_space) return From 9cf17e552dd8581002b3e5a55f3a3dc851a8e9bf Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 10:56:12 +1000 Subject: [PATCH 116/295] mmImagePlaneShape2 - Move color space controls to Python --- .../AEmmImagePlaneShape2Template.mel | 119 +++--------------- .../createimageplane/_lib/mmimageplane_v2.py | 72 +++++++++++ 2 files changed, 89 insertions(+), 102 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index ce3534f8a..7d3fe345f 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -235,111 +235,22 @@ global proc AEmmImagePlaneShape2_imageSequenceReplace(string $file_attr) } -proc string set_color_space_make_command(string $quoted_node_attr, string $control_name, string $value) +global proc AEmmImagePlaneShape2_colorSpaceNew(string $node_attr) { - string $cmd = ""; - // TODO: Check if the node is editable first? - $cmd += "setAttr -lock false " + $quoted_node_attr + ";"; - $cmd += "setAttr -type \"string\" " + $quoted_node_attr + " \"" + $value + "\";"; - $cmd += "setAttr -lock true " + $quoted_node_attr + ";"; - $cmd += "button -edit -label \"" + $value + "\" " + $control_name + ";"; - return $cmd; -} - -global proc AEmmImagePlaneShape2_inputColorSpaceNew(string $attr_name) -{ - string $image_plane_shp[]; - tokenize($attr_name, ".", $image_plane_shp); - if(size($image_plane_shp) < 1) { - return; - } - - string $attr_names[]; - tokenize ($attr_name, ".", $attr_names); - - string $nice_name = `attributeName -nice $attr_name`; - string $node_attr = $image_plane_shp[0] + "." + $attr_names[1]; - string $quoted_node_attr = "\"" + $image_plane_shp[0] + "." + $attr_names[1] + "\""; - setUITemplate -pst attributeEditorTemplate; - string $current_value = `getAttr $node_attr`; - string $role = ""; - string $cmd = ""; - - rowLayout -nc 3 inputColorSpaceLayout; - { - // TODO: Convert color space button into a Python script. - text -label $nice_name; - - string $button_name = "inputColorSpaceButton"; - button inputColorSpaceButton; - - if (size($current_value) == 0) { - button -edit -label "" inputColorSpaceButton; - } else { - button -edit -label $current_value inputColorSpaceButton; - } - - int $left_mouse_button = 1; - popupMenu -button $left_mouse_button; - - $role = `mmColorIO -roleDefault true`; - if (size($role) > 0) { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $role); - menuItem -label ("Default: " + $role) -command $cmd; - } - - $role = `mmColorIO -roleSceneLinear true`; - if (size($role) > 0) { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $role); - menuItem -label ("Scene Linear: " + $role) -command $cmd; - } - - $role = `mmColorIO -roleMattePaint true`; - if (size($role) > 0) { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $role); - menuItem -label ("Matte Paint: " + $role) -command $cmd; - } - - $role = `mmColorIO -roleData true`; - if (size($role) > 0) { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $role); - menuItem -label ("Data: " + $role) -command $cmd; - } - - $role = `mmColorIO -roleColorPicking true`; - if (size($role) > 0) { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $role); - menuItem -label ("Color Picking: " + $role) -command $cmd; - } - - menuItem -divider true; - - string $names[] = `mmColorIO -listColorSpacesActive true`; - string $name; - for($name in $names) - { - $cmd = set_color_space_make_command( - $quoted_node_attr, $button_name, $name); - menuItem -label $name -command $cmd; - } - - } - setParent ..; + string $cmd = "import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as lib;"; + $cmd += "node_attr = \"" + $node_attr + "\";"; + $cmd += "lib.color_space_attribute_editor_new(node_attr);"; + python($cmd); setUITemplate -ppt; - AEmmImagePlaneShape2_inputColorSpaceReplace $attr_name; + AEmmImagePlaneShape2_colorSpaceReplace $node_attr; } -global proc AEmmImagePlaneShape2_inputColorSpaceReplace(string $attr_name) +global proc AEmmImagePlaneShape2_colorSpaceReplace(string $node_attr) { // Nothing needed here. } @@ -580,6 +491,11 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addSeparator; editorTemplate -addControl "imageIgnoreAlpha"; editorTemplate -addControl "displayChannel"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_colorSpaceNew" + "AEmmImagePlaneShape2_colorSpaceReplace" + "inputColorSpace"; } editorTemplate -endLayout; @@ -620,12 +536,6 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "imageSequenceStartFrame"; editorTemplate -addControl "imageSequenceEndFrame"; editorTemplate -addSeparator; - editorTemplate - -callCustom - "AEmmImagePlaneShape2_inputColorSpaceNew" - "AEmmImagePlaneShape2_inputColorSpaceReplace" - "inputColorSpace"; - editorTemplate -addSeparator; // TODO: Add radio button to choose what will connect to the // 'imageSequenceFrame' value? Options are: // - Scene Time (time1) @@ -670,6 +580,11 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "meshResolution"; editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. editorTemplate -addControl "shaderIsTransparent"; + editorTemplate + -callCustom + "AEmmImagePlaneShape2_colorSpaceNew" + "AEmmImagePlaneShape2_colorSpaceReplace" + "outputColorSpace"; } editorTemplate -endLayout; diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index bc852d75c..1b4342298 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -386,3 +386,75 @@ def get_image_plane_node_pair(node): tfm = get_transform_node(node) shp = node return tfm, shp + + +def _set_attribute_editor_color_space(node_attr, control_name, value): + # TODO: Check if the node is editable first? + maya.cmds.setAttr(node_attr, lock=False) + maya.cmds.setAttr(node_attr, value, type='string') + maya.cmds.setAttr(node_attr, lock=True) + maya.cmds.button(control_name, edit=True, label=value) + + +def _maybe_make_menu_item(button_name, label, node_attr, value): + if value and len(value) > 0: + func = lambda x: _set_attribute_editor_color_space( + node_attr, button_name, value + ) + maya.cmds.menuItem(label=label, command=func) + return + + +def color_space_attribute_editor_new(node_attr): + assert isinstance(node_attr, str) + split = node_attr.split('.') + if len(split) == 0: + LOG.warn('Could not get attribute name from: %r', node_attr) + return + attr = split[-1] + + current_value = maya.cmds.getAttr(node_attr) + nice_name = maya.cmds.attributeName(node_attr, nice=True) + + layout_name = '{}_colorSpaceLayout'.format(attr) + maya.cmds.rowLayout(layout_name, numberOfColumns=3) + + maya.cmds.text(label=nice_name) + + button_name = "{}_colorSpaceButton".format(attr) + maya.cmds.button(button_name) + if len(current_value) == 0: + maya.cmds.button(button_name, edit=True, label="") + else: + maya.cmds.button(button_name, edit=True, label=current_value) + + left_mouse_button = 1 + maya.cmds.popupMenu(button=left_mouse_button) + + value = maya.cmds.mmColorIO(roleDefault=True) + label = 'Default: {}'.format(value) + _maybe_make_menu_item(button_name, label, node_attr, value) + + value = maya.cmds.mmColorIO(roleSceneLinear=True) + label = 'Scene Linear: {}'.format(value) + _maybe_make_menu_item(button_name, label, node_attr, value) + + value = maya.cmds.mmColorIO(roleMattePaint=True) + label = 'Matte Paint: {}'.format(value) + _maybe_make_menu_item(button_name, label, node_attr, value) + + value = maya.cmds.mmColorIO(roleData=True) + label = 'Data: {}'.format(value) + _maybe_make_menu_item(button_name, label, node_attr, value) + + value = maya.cmds.mmColorIO(roleColorPicking=True) + label = 'Color Picking: {}'.format(value) + _maybe_make_menu_item(button_name, label, node_attr, value) + + maya.cmds.menuItem(divider=True) + + color_space_names = maya.cmds.mmColorIO(listColorSpacesActive=True) + for name in color_space_names: + _maybe_make_menu_item(button_name, name, node_attr, name) + + maya.cmds.setParent('..') From d8c69d5efc231716c1263b7189db5569031fd479 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 11:26:24 +1000 Subject: [PATCH 117/295] mmImagePlaneShape2 - Rename AE preferences button --- .../AEmmImagePlaneShape2Template.mel | 72 +++++++++++-------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 7d3fe345f..1cf7c49d8 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -399,8 +399,7 @@ global proc AEmmImagePlaneShape2_imageCache_clearUnusedSlots( } -global proc AEmmImagePlaneShape2_imageCache_clearAllImages( - string $image_plane_shp) +global proc AEmmImagePlaneShape2_imageCache_clearAllImages() { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; @@ -409,8 +408,7 @@ global proc AEmmImagePlaneShape2_imageCache_clearAllImages( } -global proc AEmmImagePlaneShape2_imageCache_openPreferences( - string $image_plane_shp) +global proc AEmmImagePlaneShape2_imageCache_openImageCacheUI() { string $cmd = "import mmSolver.tools.imagecache.tool as tool;"; $cmd += "tool.main();"; @@ -418,39 +416,50 @@ global proc AEmmImagePlaneShape2_imageCache_openPreferences( } -global proc AEmmImagePlaneShape2_imageCacheClearSectionNew(string $attr_name) +global proc AEmmImagePlaneShape2_imageCacheButtonsNew(string $attr_name) { setUITemplate -pst attributeEditorTemplate; text -label ""; - // Clear buttons and Image Cache preferences. - rowLayout -nc 3 imageCacheClearLayout; + // Clear buttons + rowLayout -nc 3 imageCacheButtons_clearLayout; { // Spacer to avoid button on the screen-left of the Attribute // Editor. text -label ""; - button -label "Clear..." imageCacheClearButton; + button -label "Clear..." imageCacheButtons_clearButton; + int $left_mouse_button = 1; popupMenu -button $left_mouse_button; - menuItem -label "All image slots" imageCacheClearAllSlotsMenuItem; - menuItem -label "Active image slot" imageCacheClearActiveSlotMenuItem; - menuItem -label "Unused image slots" imageCacheClearUnusedImageSlotsMenuItem; + + menuItem -label "All image slots" imageCacheButtons_clearAllSlotsMenuItem; + menuItem -label "Active image slot" imageCacheButtons_clearActiveSlotMenuItem; + menuItem -label "Unused image slots" imageCacheButtons_clearUnusedImageSlotsMenuItem; menuItem -divider true; - menuItem -label "All images in cache" imageCacheClearAllImagesMenuItem; + menuItem -label "All images in cache" imageCacheButtons_clearAllImagesMenuItem; + } + setParent ..; + + // Image Cache Capacity preferences. + rowLayout -nc 3 imageCacheButtons_capacityLayout; + { + // Spacer to avoid button on the screen-left of the Attribute + // Editor. + text -label ""; - button -label "Preferences..." imageCachePreferencesButton; + button -label "Cache Capacity..." imageCacheButtons_capacityButton; } setParent ..; setUITemplate -ppt; - AEmmImagePlaneShape2_imageCacheClearSectionReplace $attr_name; + AEmmImagePlaneShape2_imageCacheButtonsReplace $attr_name; } -global proc AEmmImagePlaneShape2_imageCacheClearSectionReplace(string $attr_name) +global proc AEmmImagePlaneShape2_imageCacheButtonsReplace(string $attr_name) { string $image_plane_shp[]; tokenize($attr_name, ".", $image_plane_shp); @@ -458,19 +467,22 @@ global proc AEmmImagePlaneShape2_imageCacheClearSectionReplace(string $attr_name return; } - string $clear_all_slots_cmd = "AEmmImagePlaneShape2_imageCache_clearAllSlots " + $image_plane_shp[0]; - string $clear_active_slot_cmd = "AEmmImagePlaneShape2_imageCache_clearActiveSlot " + $image_plane_shp[0]; - string $clear_unused_cmd = "AEmmImagePlaneShape2_imageCache_clearUnusedSlots " + $image_plane_shp[0]; - string $clear_all_cmd = "AEmmImagePlaneShape2_imageCache_clearAllImages " + $image_plane_shp[0]; - - string $open_preferences_cmd = "AEmmImagePlaneShape2_imageCache_openPreferences " + $image_plane_shp[0]; - - menuItem -edit -command $clear_all_slots_cmd imageCacheClearAllSlotsMenuItem; - menuItem -edit -command $clear_active_slot_cmd imageCacheClearActiveSlotMenuItem; - menuItem -edit -command $clear_unused_cmd imageCacheClearUnusedImageSlotsMenuItem; - menuItem -edit -command $clear_all_cmd imageCacheClearAllImagesMenuItem; - - button -edit -command $open_preferences_cmd imageCachePreferencesButton; + menuItem -edit + -command ("AEmmImagePlaneShape2_imageCache_clearAllSlots " + $image_plane_shp[0]) + imageCacheButtons_clearAllSlotsMenuItem; + menuItem -edit + -command ("AEmmImagePlaneShape2_imageCache_clearActiveSlot " + $image_plane_shp[0]) + imageCacheButtons_clearActiveSlotMenuItem; + menuItem -edit + -command ("AEmmImagePlaneShape2_imageCache_clearUnusedSlots " + $image_plane_shp[0]) + imageCacheButtons_clearUnusedImageSlotsMenuItem; + menuItem -edit + -command ("AEmmImagePlaneShape2_imageCache_clearAllImages;") + imageCacheButtons_clearAllImagesMenuItem; + + button -edit + -command "AEmmImagePlaneShape2_imageCache_openImageCacheUI;" + imageCacheButtons_capacityButton; } @@ -569,8 +581,8 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) ""; editorTemplate -callCustom - "AEmmImagePlaneShape2_imageCacheClearSectionNew" - "AEmmImagePlaneShape2_imageCacheClearSectionReplace" + "AEmmImagePlaneShape2_imageCacheButtonsNew" + "AEmmImagePlaneShape2_imageCacheButtonsReplace" ""; } editorTemplate -endLayout; From 3e018306b44393bacb097b61d1d9045433ea8886 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 11:26:55 +1000 Subject: [PATCH 118/295] mmImagePlaneShape2 - Move inputColorSpace attr in AE --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 1cf7c49d8..bdd715ae5 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -501,13 +501,14 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -addControl "colorSoftClip"; editorTemplate -addControl "alphaGain"; editorTemplate -addSeparator; - editorTemplate -addControl "imageIgnoreAlpha"; - editorTemplate -addControl "displayChannel"; editorTemplate -callCustom "AEmmImagePlaneShape2_colorSpaceNew" "AEmmImagePlaneShape2_colorSpaceReplace" "inputColorSpace"; + editorTemplate -addSeparator; + editorTemplate -addControl "imageIgnoreAlpha"; + editorTemplate -addControl "displayChannel"; } editorTemplate -endLayout; From 4aeebd36db3517d5916f9144ee4b2f07f7ed1a52 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 20:39:48 +1000 Subject: [PATCH 119/295] ImageCache - Link similar images into groups This also adds the ability to query group information from the image cache using the mmImageCache command in Maya. GitHub issue #252. --- .../AEmmImagePlaneShape2Template.mel | 24 +- .../createimageplane/_lib/mmimageplane_v2.py | 16 + python/mmSolver/tools/imagecache/constant.py | 2 - python/mmSolver/tools/imagecache/lib.py | 139 ++++- .../tools/imagecache/ui/imagecache_layout.py | 4 + src/mmSolver/cmd/MMImageCacheCmd.cpp | 283 +++++++++-- src/mmSolver/cmd/MMImageCacheCmd.h | 22 +- src/mmSolver/image/ImageCache.cpp | 480 ++++++++++++++---- src/mmSolver/image/ImageCache.h | 159 ++++-- .../shape/ImagePlaneGeometry2Override.cpp | 4 +- 10 files changed, 916 insertions(+), 217 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index bdd715ae5..091e34594 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -365,10 +365,9 @@ global proc AEmmImagePlaneShape2_imageCache_clearAllSlots( { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; - $cmd += "lib.cache_remove_all_image_plane_slots("; - $cmd += " const.CACHE_TYPE_ALL, "; - $cmd += " \"" + $image_plane_shp + "\""; - $cmd += ");"; + $cmd += "node = \"" + $image_plane_shp + "\";"; + $cmd += "lib.cache_remove_all_image_plane_slots(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.cache_remove_all_image_plane_slots(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -378,10 +377,9 @@ global proc AEmmImagePlaneShape2_imageCache_clearActiveSlot( { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; - $cmd += "lib.cache_remove_active_image_plane_slot("; - $cmd += " const.CACHE_TYPE_ALL, "; - $cmd += " \"" + $image_plane_shp + "\""; - $cmd += ");"; + $cmd += "node = \"" + $image_plane_shp + "\";"; + $cmd += "lib.cache_remove_active_image_plane_slot(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.cache_remove_active_image_plane_slot(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -391,10 +389,9 @@ global proc AEmmImagePlaneShape2_imageCache_clearUnusedSlots( { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; - $cmd += "lib.cache_remove_unused_image_plane_slots("; - $cmd += " const.CACHE_TYPE_ALL, "; - $cmd += " \"" + $image_plane_shp + "\""; - $cmd += ");"; + $cmd += "node = \"" + $image_plane_shp + "\";"; + $cmd += "lib.cache_remove_unused_image_plane_slots(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.cache_remove_unused_image_plane_slots(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -403,7 +400,8 @@ global proc AEmmImagePlaneShape2_imageCache_clearAllImages() { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; - $cmd += "lib.cache_remove_all(const.CACHE_TYPE_ALL);"; + $cmd += "lib.cache_remove_all(const.CACHE_TYPE_CPU);"; + $cmd += "lib.cache_remove_all(const.CACHE_TYPE_GPU);"; python($cmd); } diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 1b4342298..11c5b44eb 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -223,6 +223,22 @@ def create_image_plane_shape_attrs(image_plane_shp): return +def get_image_sequence_for_active_slot(shp): + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + raise NotImplementedError + + +def get_image_sequence_for_unused_slots(shp): + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + raise NotImplementedError + + +def get_image_sequence_for_all_slots(shp): + assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 + slots = ['slot 1', 'slot 2'] + return slots + + def set_image_sequence(shp, image_sequence_path, attr_name): assert isinstance(image_sequence_path, str) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 diff --git a/python/mmSolver/tools/imagecache/constant.py b/python/mmSolver/tools/imagecache/constant.py index 0870acf01..cf5cc3954 100644 --- a/python/mmSolver/tools/imagecache/constant.py +++ b/python/mmSolver/tools/imagecache/constant.py @@ -21,11 +21,9 @@ WINDOW_TITLE = 'mmSolver Image Cache' -CACHE_TYPE_ALL = 'all' CACHE_TYPE_GPU = 'gpu' CACHE_TYPE_CPU = 'cpu' CACHE_TYPE_VALUES = [ - CACHE_TYPE_ALL, CACHE_TYPE_GPU, CACHE_TYPE_CPU, ] diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index e5a49247b..5b1a9128e 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -230,14 +230,89 @@ def get_cpu_memory_used_bytes(): ) +def get_cache_group_names(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(query=True, gpuGroupNames=True) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(query=True, cpuGroupNames=True) + return value + + +def get_cache_group_count(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(query=True, gpuGroupCount=True) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(query=True, cpuGroupCount=True) + return value + + +def get_cache_group_item_count(cache_type, group_name): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(group_name, str) + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True) + return value + + +def get_cache_group_item_names(cache_type, group_name): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(group_name, str) + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) + return value + + +def cache_erase_group(cache_type, group_name): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(group_name, str) + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(edit=True, gpuEraseGroup=group_name) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(edit=True, cpuEraseGroup=group_name) + return value + + +def cache_erase_items(cache_type, items): + assert cache_type in const.CACHE_TYPE_VALUES + assert len(items) >= 0 + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache(edit=True, gpuEraseItems=items) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache(edit=True, cpuEraseItems=items) + return value + + def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( - 'cache_remove_all_image_plane_slots: image_plane_shp=%r, cache_type=%r', - image_plane_shp, + 'cache_remove_all_image_plane_slots: cache_type=%r, image_plane_shp=%r', cache_type, + image_plane_shp, ) + + slots = imageplane_lib.get_image_sequence_for_all_slots(image_plane_shp) + LOG.info('cache_remove_all_image_plane_slots: slots=%r', slots) + + group_names = get_cache_group_names(cache_type) + for slot in slots: + if slot not in group_names: + LOG.warn('Slot not found in groups: group_names=%r', group_names) + continue + cache_erase_group(cache_type, slot) + return @@ -245,10 +320,14 @@ def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( - 'cache_remove_active_image_plane_slot: image_plane_shp=%r, cache_type=%r', - image_plane_shp, + 'cache_remove_active_image_plane_slot: cache_type=%r, image_plane_shp=%r', cache_type, + image_plane_shp, ) + + slot = imageplane_lib.get_image_sequence_for_active_slot(image_plane_shp) + LOG.info('cache_remove_active_image_plane_slot: slot=%r', slot) + return @@ -256,10 +335,12 @@ def cache_remove_unused_image_plane_slots(cache_type, image_plane_shp): assert cache_type in const.CACHE_TYPE_VALUES assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 LOG.info( - 'cache_remove_unused_image_plane_slots: image_plane_shp=%r, cache_type=%r', - image_plane_shp, + 'cache_remove_unused_image_plane_slots: cache_type=%r, image_plane_shp=%r', cache_type, + image_plane_shp, ) + slots = imageplane_lib.get_image_sequence_for_unused_slots(image_plane_shp) + LOG.info('cache_remove_unused_image_plane_slots: slots=%r', slots) return @@ -269,26 +350,62 @@ def cache_remove_all(cache_type): 'cache_remove_unused_image_plane_slots: cache_type=%r', cache_type, ) + + # TODO: Clear image cache completely. return -def cache_remove_image_sequence(file_pattern, start_frame, end_frame, cache_type): +def cache_remove_image_sequence(cache_type, file_pattern, start_frame, end_frame): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(file_pattern, str) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) LOG.info( 'cache_remove_image_sequence: ' - 'file_pattern=%r, start_frame=%r, end_frame=%r, cache_type=%r', + 'cache_type=%r, file_pattern=%r, start_frame=%r, end_frame=%r', + cache_type, file_pattern, start_frame, end_frame, - cache_type, ) - pass + + item_count = get_cache_group_item_count(cache_type, file_pattern) + if item_count == 0: + LOG.warn('File pattern does not have any items. item_count=%r', item_count) + return + + item_names = get_cache_group_item_names(cache_type, file_pattern) + assert len(item_names) > 0 + + # TODO: + # + # 1) Evaluate the file_pattern for start_frame to end_frame. + # + # 2) If the evaluated file path is in the image cache, add it to + # the list to be removed. + # + # 3) Remove named items from the cache. + + raise NotImplementedError def cache_remove_all_inactive(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES # Removes all the items in the cache that cannot be 'reached' by # any of the image planes. LOG.info( 'cache_remove_all_inactive: cache_type=%r', cache_type, ) - pass + + # TODO: + # + # 1) Get all image planes. + # + # 2) Get all active slots on all image planes. + # + # 3) Get all groups in the image cache. + # + # 4) For any group that is not in the active slots, remove it. + + raise NotImplementedError diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py index 520c48f04..687df5a4f 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py +++ b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py @@ -50,6 +50,7 @@ def memoryTotalUpdateUi( gigabytes = 0.0 if size_bytes > 0: gigabytes = size_bytes / _BYTES_TO_GIGABYTES + text = '{:0,.2f} GB'.format(gigabytes) label.setText(text) @@ -62,6 +63,7 @@ def memoryUsedUpdateUi(label, used_size_bytes, total_size_bytes): used_percent = 0.0 if used_size_bytes > 0 and total_size_bytes > 0: used_percent = (used_size_bytes / total_size_bytes) * 100.0 + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) label.setText(text) @@ -81,6 +83,7 @@ def cacheCapacityUpdateUi( gigabytes = 0.0 if size_bytes > 0: gigabytes = size_bytes / _BYTES_TO_GIGABYTES + text = '{:0,.2f} GB'.format(gigabytes) label.setText(text) @@ -93,6 +96,7 @@ def cacheUsedUpdateUi(label, used_size_bytes, capacity_size_bytes): used_percent = 0.0 if used_size_bytes > 0 and capacity_size_bytes > 0: used_percent = (used_size_bytes / capacity_size_bytes) * 100.0 + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) label.setText(text) diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 27574b9b0..6e16407f9 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -46,22 +46,39 @@ // Command arguments and command name: #define GPU_CAPACITY_FLAG "-gpc" #define GPU_CAPACITY_FLAG_LONG "-gpuCapacity" - -#define GPU_USED_FLAG "-gpu" -#define GPU_USED_FLAG_LONG "-gpuUsed" - #define CPU_CAPACITY_FLAG "-cpc" #define CPU_CAPACITY_FLAG_LONG "-cpuCapacity" +#define GPU_USED_FLAG "-gpu" +#define GPU_USED_FLAG_LONG "-gpuUsed" #define CPU_USED_FLAG "-cpu" #define CPU_USED_FLAG_LONG "-cpuUsed" -#define GPU_ITEM_COUNT_FLAG "-gpi" +#define GPU_ITEM_COUNT_FLAG "-gic" #define GPU_ITEM_COUNT_FLAG_LONG "-gpuItemCount" - -#define CPU_ITEM_COUNT_FLAG "-cpi" +#define CPU_ITEM_COUNT_FLAG "-cic" #define CPU_ITEM_COUNT_FLAG_LONG "-cpuItemCount" +#define GPU_GROUP_COUNT_FLAG "-ggc" +#define GPU_GROUP_COUNT_FLAG_LONG "-gpuGroupCount" +#define CPU_GROUP_COUNT_FLAG "-cgc" +#define CPU_GROUP_COUNT_FLAG_LONG "-cpuGroupCount" + +#define GPU_GROUP_NAMES_FLAG "-ggn" +#define GPU_GROUP_NAMES_FLAG_LONG "-gpuGroupNames" +#define CPU_GROUP_NAMES_FLAG "-cgn" +#define CPU_GROUP_NAMES_FLAG_LONG "-cpuGroupNames" + +#define GPU_GROUP_ITEM_COUNT_FLAG "-ggt" +#define GPU_GROUP_ITEM_COUNT_FLAG_LONG "-gpuGroupItemCount" +#define CPU_GROUP_ITEM_COUNT_FLAG "-cgt" +#define CPU_GROUP_ITEM_COUNT_FLAG_LONG "-cpuGroupItemCount" + +#define GPU_GROUP_ITEM_NAMES_FLAG "-gin" +#define GPU_GROUP_ITEM_NAMES_FLAG_LONG "-gpuGroupItemNames" +#define CPU_GROUP_ITEM_NAMES_FLAG "-cin" +#define CPU_GROUP_ITEM_NAMES_FLAG_LONG "-cpuGroupItemNames" + #define BRIEF_TEXT_FLAG "-btx" #define BRIEF_TEXT_FLAG_LONG "-briefText" @@ -90,6 +107,8 @@ MSyntax MMImageCacheCmd::newSyntax() { syntax.enableQuery(true); syntax.enableEdit(true); + syntax.setObjectType(MSyntax::kStringObjects); + CHECK_MSTATUS(syntax.addFlag(GPU_CAPACITY_FLAG, GPU_CAPACITY_FLAG_LONG, MSyntax::kString)); CHECK_MSTATUS(syntax.addFlag(CPU_CAPACITY_FLAG, CPU_CAPACITY_FLAG_LONG, @@ -103,6 +122,26 @@ MSyntax MMImageCacheCmd::newSyntax() { CHECK_MSTATUS( syntax.addFlag(CPU_ITEM_COUNT_FLAG, CPU_ITEM_COUNT_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(GPU_GROUP_COUNT_FLAG, GPU_GROUP_COUNT_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(CPU_GROUP_COUNT_FLAG, CPU_GROUP_COUNT_FLAG_LONG)); + + CHECK_MSTATUS( + syntax.addFlag(GPU_GROUP_NAMES_FLAG, GPU_GROUP_NAMES_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(CPU_GROUP_NAMES_FLAG, CPU_GROUP_NAMES_FLAG_LONG)); + + CHECK_MSTATUS(syntax.addFlag(GPU_GROUP_ITEM_COUNT_FLAG, + GPU_GROUP_ITEM_COUNT_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(CPU_GROUP_ITEM_COUNT_FLAG, + CPU_GROUP_ITEM_COUNT_FLAG_LONG)); + + CHECK_MSTATUS(syntax.addFlag(GPU_GROUP_ITEM_NAMES_FLAG, + GPU_GROUP_ITEM_NAMES_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(CPU_GROUP_ITEM_NAMES_FLAG, + CPU_GROUP_ITEM_NAMES_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(BRIEF_TEXT_FLAG, BRIEF_TEXT_FLAG_LONG)); return syntax; @@ -113,7 +152,7 @@ MSyntax MMImageCacheCmd::newSyntax() { */ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MStatus status = MStatus::kSuccess; - const bool verbose = false; + const bool verbose = true; MArgDatabase argData(syntax(), args, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -130,6 +169,10 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { "m_is_edit=" << m_is_edit); + // Get the file path. + MStringArray string_objects; + argData.getObjects(string_objects); + const bool has_gpu_capacity = argData.isFlagSet(GPU_CAPACITY_FLAG, &status); const bool has_cpu_capacity = argData.isFlagSet(CPU_CAPACITY_FLAG, &status); const bool has_gpu_used = argData.isFlagSet(GPU_USED_FLAG, &status); @@ -138,23 +181,87 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { argData.isFlagSet(GPU_ITEM_COUNT_FLAG, &status); const bool has_cpu_item_count = argData.isFlagSet(CPU_ITEM_COUNT_FLAG, &status); + + const bool has_gpu_group_count = + argData.isFlagSet(GPU_GROUP_COUNT_FLAG, &status); + const bool has_cpu_group_count = + argData.isFlagSet(CPU_GROUP_COUNT_FLAG, &status); + const bool has_gpu_group_names = + argData.isFlagSet(GPU_GROUP_NAMES_FLAG, &status); + const bool has_cpu_group_names = + argData.isFlagSet(CPU_GROUP_NAMES_FLAG, &status); + + const bool has_gpu_group_item_count = + argData.isFlagSet(GPU_GROUP_ITEM_COUNT_FLAG, &status); + const bool has_cpu_group_item_count = + argData.isFlagSet(CPU_GROUP_ITEM_COUNT_FLAG, &status); + const bool has_gpu_group_item_names = + argData.isFlagSet(GPU_GROUP_ITEM_NAMES_FLAG, &status); + const bool has_cpu_group_item_names = + argData.isFlagSet(CPU_GROUP_ITEM_NAMES_FLAG, &status); + const bool has_print_brief = argData.isFlagSet(BRIEF_TEXT_FLAG, &status); if (m_is_query) { if (has_gpu_capacity) { m_command_flag = ImageCacheFlagMode::kGpuCapacity; + m_output_type = ImageCacheOutputType::kSize; } else if (has_cpu_capacity) { m_command_flag = ImageCacheFlagMode::kCpuCapacity; + m_output_type = ImageCacheOutputType::kSize; } else if (has_gpu_used) { m_command_flag = ImageCacheFlagMode::kGpuUsed; + m_output_type = ImageCacheOutputType::kSize; } else if (has_cpu_used) { m_command_flag = ImageCacheFlagMode::kCpuUsed; + m_output_type = ImageCacheOutputType::kSize; } else if (has_gpu_item_count) { m_command_flag = ImageCacheFlagMode::kGpuItemCount; + m_output_type = ImageCacheOutputType::kSize; } else if (has_cpu_item_count) { m_command_flag = ImageCacheFlagMode::kCpuItemCount; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_gpu_group_count) { + m_command_flag = ImageCacheFlagMode::kGpuGroupCount; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_cpu_group_count) { + m_command_flag = ImageCacheFlagMode::kCpuGroupCount; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_gpu_group_names) { + m_command_flag = ImageCacheFlagMode::kGpuGroupNames; + m_output_type = ImageCacheOutputType::kStringArray; + } else if (has_cpu_group_names) { + m_command_flag = ImageCacheFlagMode::kCpuGroupNames; + m_output_type = ImageCacheOutputType::kStringArray; + } else if (has_gpu_group_item_count || has_cpu_group_item_count || + has_gpu_group_item_names || has_cpu_group_item_names) { + if (string_objects.length() != 1) { + status = MStatus::kFailure; + status.perror( + "mmImageCache: " + "One group name must be given to command!"); + return status; + } + + m_string_objects.clear(); + m_string_objects.append(string_objects[0]); + + if (has_gpu_group_item_count) { + m_command_flag = ImageCacheFlagMode::kGpuGroupItemCount; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_cpu_group_item_count) { + m_command_flag = ImageCacheFlagMode::kCpuGroupItemCount; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_gpu_group_item_names) { + m_command_flag = ImageCacheFlagMode::kGpuGroupItemNames; + m_output_type = ImageCacheOutputType::kStringArray; + } else if (has_cpu_group_item_names) { + m_command_flag = ImageCacheFlagMode::kCpuGroupItemNames; + m_output_type = ImageCacheOutputType::kStringArray; + } } else if (has_print_brief) { m_command_flag = ImageCacheFlagMode::kGenerateBriefText; + m_output_type = ImageCacheOutputType::kString; } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::parseArgs: " @@ -259,9 +366,105 @@ inline MStatus set_values(image::ImageCache &image_cache, return status; } +MStatus get_value_size(image::ImageCache &image_cache, + const ImageCacheFlagMode command_flag, + const std::string &group_name, size_t &out_value) { + out_value = 0; + + if (command_flag == ImageCacheFlagMode::kGpuCapacity) { + out_value = image_cache.get_gpu_capacity_bytes(); + } else if (command_flag == ImageCacheFlagMode::kCpuCapacity) { + out_value = image_cache.get_cpu_capacity_bytes(); + } else if (command_flag == ImageCacheFlagMode::kGpuUsed) { + out_value = image_cache.get_gpu_used_bytes(); + } else if (command_flag == ImageCacheFlagMode::kCpuUsed) { + out_value = image_cache.get_cpu_used_bytes(); + } else if (command_flag == ImageCacheFlagMode::kGpuItemCount) { + out_value = image_cache.get_gpu_item_count(); + } else if (command_flag == ImageCacheFlagMode::kCpuItemCount) { + out_value = image_cache.get_cpu_item_count(); + } else if (command_flag == ImageCacheFlagMode::kGpuGroupCount) { + out_value = image_cache.get_gpu_group_count(); + } else if (command_flag == ImageCacheFlagMode::kCpuGroupCount) { + out_value = image_cache.get_cpu_group_count(); + } else if (command_flag == ImageCacheFlagMode::kGpuGroupItemCount) { + out_value = image_cache.gpu_group_item_count(group_name); + } else if (command_flag == ImageCacheFlagMode::kCpuGroupItemCount) { + out_value = image_cache.cpu_group_item_count(group_name); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(command_flag)); + return MStatus::kFailure; + } + + return MStatus::kSuccess; +} + +MStatus get_value_string(image::ImageCache &image_cache, + const ImageCacheFlagMode command_flag, + MString &out_result) { + if (command_flag == ImageCacheFlagMode::kGenerateBriefText) { + out_result = image_cache.generate_cache_brief_text(); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(command_flag)); + return MStatus::kFailure; + } + + return MStatus::kSuccess; +} + +MStatus get_value_string_array(image::ImageCache &image_cache, + const ImageCacheFlagMode command_flag, + const std::string &group_name, + MStringArray &out_results) { + bool ok = true; + std::vector outputs; + + if (command_flag == ImageCacheFlagMode::kGpuGroupNames) { + image_cache.gpu_group_names(outputs); + } else if (command_flag == ImageCacheFlagMode::kCpuGroupNames) { + image_cache.cpu_group_names(outputs); + } else if (command_flag == ImageCacheFlagMode::kGpuGroupItemNames) { + ok = image_cache.gpu_group_item_names(group_name, outputs); + } else if (command_flag == ImageCacheFlagMode::kCpuGroupItemNames) { + ok = image_cache.cpu_group_item_names(group_name, outputs); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(command_flag)); + return MStatus::kFailure; + } + + if (!ok) { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(command_flag)); + return MStatus::kFailure; + } + + out_results.clear(); + for (auto i = 0; i < outputs.size(); i++) { + const auto value = outputs[i]; + out_results.append(MString(value.c_str())); + } + + return MStatus::kSuccess; +} + MStatus MMImageCacheCmd::doIt(const MArgList &args) { MStatus status = MStatus::kSuccess; - const bool verbose = false; + const bool verbose = true; // Read all the flag arguments. status = parseArgs(args); @@ -270,35 +473,45 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { if (m_is_query) { image::ImageCache &image_cache = image::ImageCache::getInstance(); - if (m_command_flag == ImageCacheFlagMode::kGenerateBriefText) { - MString mstring = image_cache.generate_cache_brief_text(); - MMImageCacheCmd::setResult(mstring); - } else { - size_t bytes_value = 0; - if (m_command_flag == ImageCacheFlagMode::kGpuCapacity) { - bytes_value = image_cache.get_gpu_capacity_bytes(); - } else if (m_command_flag == ImageCacheFlagMode::kCpuCapacity) { - bytes_value = image_cache.get_cpu_capacity_bytes(); - } else if (m_command_flag == ImageCacheFlagMode::kGpuUsed) { - bytes_value = image_cache.get_gpu_used_bytes(); - } else if (m_command_flag == ImageCacheFlagMode::kCpuUsed) { - bytes_value = image_cache.get_cpu_used_bytes(); - } else if (m_command_flag == ImageCacheFlagMode::kGpuItemCount) { - bytes_value = image_cache.get_gpu_item_count(); - } else if (m_command_flag == ImageCacheFlagMode::kCpuItemCount) { - bytes_value = image_cache.get_cpu_item_count(); - } else { - MMSOLVER_MAYA_ERR( - "MMImageCacheCmd::doIt: " - "Invalid command query flag! " - "value=" - << static_cast(m_command_flag)); - return MStatus::kFailure; + if (m_output_type == ImageCacheOutputType::kString) { + MString result; + status = get_value_string(image_cache, m_command_flag, result); + CHECK_MSTATUS_AND_RETURN_IT(status); + MMImageCacheCmd::setResult(result); + } else if (m_output_type == ImageCacheOutputType::kSize) { + std::string group_name = ""; + if (m_string_objects.length() > 0) { + group_name = m_string_objects[0].asChar(); } - MString number_mstring(mmmayastring::numberToMString(bytes_value)); - MMImageCacheCmd::setResult(number_mstring); + size_t result = 0; + status = + get_value_size(image_cache, m_command_flag, group_name, result); + CHECK_MSTATUS_AND_RETURN_IT(status); + + MString result_string(mmmayastring::numberToMString(result)); + + MMImageCacheCmd::setResult(result_string); + } else if (m_output_type == ImageCacheOutputType::kStringArray) { + std::string group_name = ""; + if (m_string_objects.length() > 0) { + group_name = m_string_objects[0].asChar(); + } + + MStringArray results; + status = get_value_string_array(image_cache, m_command_flag, + group_name, results); + CHECK_MSTATUS_AND_RETURN_IT(status); + MMImageCacheCmd::setResult(results); + } else { + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::doIt: " + "Invalid command query flag! " + "value=" + << static_cast(m_command_flag)); + return MStatus::kFailure; } + } else if (m_is_edit) { image::ImageCache &image_cache = image::ImageCache::getInstance(); set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index cb79685e7..8502089b0 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -124,20 +124,37 @@ enum class ImageCacheFlagMode : uint8_t { kCpuUsed, kGpuItemCount, kCpuItemCount, + kGpuGroupCount, + kCpuGroupCount, + kGpuGroupNames, + kCpuGroupNames, + kGpuGroupItemCount, + kCpuGroupItemCount, + kGpuGroupItemNames, + kCpuGroupItemNames, kGenerateBriefText, kUnknown = 255 }; +enum class ImageCacheOutputType : uint8_t { + kSize = 0, + kString, + kStringArray, + kUnknown = 255 +}; + class MMImageCacheCmd : public MPxCommand { public: MMImageCacheCmd() : m_is_query(false) , m_is_edit(false) , m_command_flag(ImageCacheFlagMode::kUnknown) + , m_output_type(ImageCacheOutputType::kUnknown) , m_previous_gpu_capacity_bytes(0) , m_previous_cpu_capacity_bytes(0) , m_gpu_capacity_bytes(0) - , m_cpu_capacity_bytes(0){}; + , m_cpu_capacity_bytes(0) + , m_string_objects(){}; virtual ~MMImageCacheCmd(); @@ -160,6 +177,7 @@ class MMImageCacheCmd : public MPxCommand { bool m_is_query; ImageCacheFlagMode m_command_flag; + ImageCacheOutputType m_output_type; // The previous values, before the command was run. size_t m_previous_gpu_capacity_bytes; @@ -167,6 +185,8 @@ class MMImageCacheCmd : public MPxCommand { size_t m_gpu_capacity_bytes; size_t m_cpu_capacity_bytes; + + MStringArray m_string_objects; }; } // namespace mmsolver diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index bd9c46dd0..94f28584c 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -30,6 +30,7 @@ #include // STL +#include #include #include #include @@ -52,6 +53,7 @@ #include "mmSolver/mayahelper/maya_utils.h" #include "mmSolver/render/shader/shader_utils.h" #include "mmSolver/shape/constant_texture_data.h" +#include "mmSolver/utilities/hash_utils.h" #include "mmSolver/utilities/number_utils.h" #include "mmSolver/utilities/path_utils.h" #include "mmSolver/utilities/string_utils.h" @@ -61,11 +63,12 @@ namespace image { MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, + const MString &file_pattern, const MString &file_path, const bool do_texture_update) { assert(texture_manager != nullptr); - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file_path=" << file_path.asChar()); @@ -78,7 +81,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, return nullptr; } - std::string key = std::string(resolved_file_path.asChar()); + const std::string key = std::string(resolved_file_path.asChar()); TextureData texture_data = image_cache.gpu_find(key); MMSOLVER_MAYA_VRB( @@ -155,8 +158,11 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImagePixelData(static_cast(maya_owned_pixel_data), width, height, num_channels, pixel_data_type); - texture_data = - image_cache.gpu_insert(texture_manager, key, gpu_image_pixel_data); + const std::string group_name = std::string(file_pattern.asChar()); + const uint64_t group_hash = mmsolver::hash::make_hash(group_name); + + texture_data = image_cache.gpu_insert(texture_manager, group_name, key, + gpu_image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "gpu_inserted=" << texture_data.texture()); @@ -176,7 +182,8 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, std::memcpy(image_pixel_data.pixel_data(), maya_owned_pixel_data, pixel_data_byte_count); - const bool cpu_inserted = image_cache.cpu_insert(key, image_pixel_data); + const bool cpu_inserted = + image_cache.cpu_insert(group_name, key, image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "cpu_inserted=" << cpu_inserted); @@ -188,7 +195,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, void ImageCache::set_gpu_capacity_bytes( MHWRender::MTextureManager *texture_manager, const size_t value) { - const bool verbose = false; + const bool verbose = true; m_gpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); @@ -199,8 +206,8 @@ void ImageCache::set_gpu_capacity_bytes( // If we are at capacity remove the least recently used items // until our capacity is under 'new_used_bytes' or we reach the minimum // number of items - while (!m_gpu_cache_map.empty() && - (m_gpu_cache_map.size() > m_gpu_item_count_minumum) && + while (!m_gpu_item_map.empty() && + (m_gpu_item_map.size() > m_gpu_item_count_minumum) && (m_gpu_used_bytes > m_gpu_capacity_bytes)) { const CacheEvictionResult result = ImageCache::gpu_evict_one(texture_manager); @@ -211,7 +218,7 @@ void ImageCache::set_gpu_capacity_bytes( } void ImageCache::set_cpu_capacity_bytes(const size_t value) { - const bool verbose = false; + const bool verbose = true; m_cpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); @@ -222,8 +229,8 @@ void ImageCache::set_cpu_capacity_bytes(const size_t value) { // If we are at capacity remove the least recently used items // until our capacity is under 'new_used_bytes' or we reach the minimum // number of items - while (!m_cpu_cache_map.empty() && - (m_cpu_cache_map.size() > m_cpu_item_count_minumum) && + while (!m_cpu_item_map.empty() && + (m_cpu_item_map.size() > m_cpu_item_count_minumum) && (m_cpu_used_bytes > m_cpu_capacity_bytes)) { const CacheEvictionResult result = ImageCache::cpu_evict_one(); if (result != CacheEvictionResult::kSuccess) { @@ -254,7 +261,7 @@ inline std::string generate_cache_brief(const char *prefix_str, } std::stringstream ss; - ss << prefix_str << "count=" << item_count << " items " + ss << prefix_str << "item_count=" << item_count << " items " << "| minimum=" << item_min_count << " items " << "| used=" << used_megabytes_str << "MB " << "| capacity=" << capacity_megabytes_str << "MB " @@ -264,10 +271,10 @@ inline std::string generate_cache_brief(const char *prefix_str, MString ImageCache::generate_cache_brief_text() const { std::string gpu_cache_text = generate_cache_brief( - "GPU cache | ", m_gpu_cache_map.size(), m_gpu_item_count_minumum, + "GPU cache | ", m_gpu_item_map.size(), m_gpu_item_count_minumum, m_gpu_capacity_bytes, m_gpu_used_bytes); std::string cpu_cache_text = generate_cache_brief( - "CPU cache | ", m_cpu_cache_map.size(), m_cpu_item_count_minumum, + "CPU cache | ", m_cpu_item_map.size(), m_cpu_item_count_minumum, m_cpu_capacity_bytes, m_cpu_used_bytes); std::stringstream ss; @@ -280,10 +287,10 @@ MString ImageCache::generate_cache_brief_text() const { void ImageCache::print_cache_brief() const { std::string gpu_cache_text = generate_cache_brief( - "GPU cache | ", m_gpu_cache_map.size(), m_gpu_item_count_minumum, + "GPU cache | ", m_gpu_item_map.size(), m_gpu_item_count_minumum, m_gpu_capacity_bytes, m_gpu_used_bytes); std::string cpu_cache_text = generate_cache_brief( - "CPU cache | ", m_cpu_cache_map.size(), m_gpu_item_count_minumum, + "CPU cache | ", m_cpu_item_map.size(), m_gpu_item_count_minumum, m_cpu_capacity_bytes, m_cpu_used_bytes); MMSOLVER_MAYA_INFO( @@ -293,49 +300,199 @@ void ImageCache::print_cache_brief() const { return; } -bool ImageCache::cpu_insert(const CPUCacheKey &key, - const CPUCacheValue &image_pixel_data) { - const bool verbose = false; +void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { + const bool verbose = true; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_group_names: "); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " - << "key=" << key); + out_group_names.clear(); + out_group_names.reserve(m_gpu_group_names_set.size()); + for (auto it = m_gpu_group_names_set.begin(); + it != m_gpu_group_names_set.end(); it++) { + GPUCacheString value = *it; + out_group_names.push_back(value); + } + // The set is unordered, so lets make the output of this function + // consistent for end users. + std::sort(out_group_names.begin(), out_group_names.end()); - const CPUMapIt search = m_cpu_cache_map.find(key); - const bool found = search != m_cpu_cache_map.end(); - if (found) { - ImageCache::cpu_erase(key); + return; +} + +void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { + const bool verbose = true; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_group_names: "); + + out_group_names.clear(); + out_group_names.reserve(m_cpu_group_names_set.size()); + for (auto it = m_cpu_group_names_set.begin(); + it != m_cpu_group_names_set.end(); it++) { + CPUCacheString value = *it; + out_group_names.push_back(value); } + // The set is unordered, so lets make the output of this function + // consistent for end users. + std::sort(out_group_names.begin(), out_group_names.end()); - // If we are at capacity, make room for new entry. - const size_t image_data_size = image_pixel_data.byte_count(); - const CacheEvictionResult evict_result = - ImageCache::cpu_evict_enough_for_new_entry(image_data_size); - if (evict_result == CacheEvictionResult::kFailed) { - MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache::cpu_insert: evicting memory failed!"); - ImageCache::print_cache_brief(); + return; +} + +size_t ImageCache::gpu_group_item_count( + const GPUCacheString &group_name) const { + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + return ImageCache::gpu_group_item_count(group_key); +} + +size_t ImageCache::gpu_group_item_count(const GPUGroupKey group_key) const { + const auto group_search = m_gpu_group_map.find(group_key); + const bool group_found = group_search != m_gpu_group_map.end(); + if (!group_found) { + return 0; } - m_cpu_used_bytes += image_data_size; - assert(m_cpu_used_bytes <= m_cpu_capacity_bytes); + const GPUGroupSet &values_set = group_search->second; + return values_set.size(); +} - // Because we are inserting into the cache, the 'key' is the - // most-recently-used item. - CPUKeyListIt key_iterator = - m_cpu_cache_key_list.insert(m_cpu_cache_key_list.end(), key); +size_t ImageCache::cpu_group_item_count( + const CPUCacheString &group_name) const { + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + return ImageCache::cpu_group_item_count(group_key); +} - const auto pair = m_cpu_cache_map.insert( - std::make_pair(key, std::make_pair(key_iterator, image_pixel_data))); +size_t ImageCache::cpu_group_item_count(const CPUGroupKey group_key) const { + const auto group_search = m_cpu_group_map.find(group_key); + const bool group_found = group_search != m_cpu_group_map.end(); + if (!group_found) { + return 0; + } + + const CPUGroupSet &values_set = group_search->second; + return values_set.size(); +} + +bool ImageCache::gpu_group_item_names( + const GPUCacheString &group_name, + GPUVectorString &out_group_item_names) const { + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + return ImageCache::gpu_group_item_names(group_key, out_group_item_names); +} + +bool ImageCache::gpu_group_item_names( + const GPUGroupKey group_key, GPUVectorString &out_group_item_names) const { + out_group_item_names.clear(); + const auto group_search = m_gpu_group_map.find(group_key); + const bool group_found = group_search != m_gpu_group_map.end(); + if (!group_found) { + return false; + } + + const GPUGroupSet &values_set = group_search->second; + out_group_item_names.reserve(values_set.size()); + for (auto it = values_set.begin(); it != values_set.end(); it++) { + GPUCacheString value = *it; + out_group_item_names.push_back(value); + } + // The set is unordered, so lets make the output of this function + // consistent for end users. + std::sort(out_group_item_names.begin(), out_group_item_names.end()); + + return true; +} - const auto inserted_key_iterator = pair.first; - const bool ok = pair.second; - assert(ok == true); - return ok; +bool ImageCache::cpu_group_item_names( + const CPUCacheString &group_name, + CPUVectorString &out_group_item_names) const { + const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + return ImageCache::cpu_group_item_names(group_key, out_group_item_names); +} + +bool ImageCache::cpu_group_item_names( + const CPUGroupKey group_key, CPUVectorString &out_group_item_names) const { + out_group_item_names.clear(); + const auto group_search = m_cpu_group_map.find(group_key); + const bool group_found = group_search != m_cpu_group_map.end(); + if (!group_found) { + return false; + } + + const CPUGroupSet &values_set = group_search->second; + out_group_item_names.reserve(values_set.size()); + for (auto it = values_set.begin(); it != values_set.end(); it++) { + CPUCacheString value = *it; + out_group_item_names.push_back(value); + } + // The set is unordered, so lets make the output of this function + // consistent for end users. + std::sort(out_group_item_names.begin(), out_group_item_names.end()); + + return true; +} + +bool ImageCache::gpu_group_insert(const GPUGroupKey group_key, + const GPUCacheString &group_name, + const GPUCacheString &file_path) { + const GPUGroupMapIt group_search = m_gpu_group_map.find(group_key); + const bool group_found = group_search != m_gpu_group_map.end(); + if (!group_found) { + GPUGroupSet values_set; + values_set.insert(file_path); + + const auto group_map_pair = + m_gpu_group_map.insert(std::make_pair(group_key, values_set)); + const bool group_map_ok = group_map_pair.second; + assert(group_map_ok == true); + + // First time inserting into the set, it won't exist yet. + const auto set_pair = m_gpu_group_names_set.insert(group_name); + const bool set_ok = set_pair.second; + assert(set_ok == true); + } else { + GPUGroupSet &values_set = group_search->second; + const GPUGroupSetIt value_search = values_set.find(group_name); + const bool value_found = value_search != values_set.end(); + if (!value_found) { + const auto values_set_pair = values_set.insert(file_path); + const bool values_set_ok = values_set_pair.second; + assert(values_set_ok == true); + } + } + return true; +} + +bool ImageCache::cpu_group_insert(const CPUGroupKey group_key, + const CPUCacheString &group_name, + const CPUCacheString &file_path) { + const CPUGroupMapIt group_search = m_cpu_group_map.find(group_key); + const bool group_found = group_search != m_cpu_group_map.end(); + if (!group_found) { + CPUGroupSet values_set; + values_set.insert(file_path); + + const auto group_map_pair = + m_cpu_group_map.insert(std::make_pair(group_key, values_set)); + const bool group_map_ok = group_map_pair.second; + assert(group_map_ok == true); + + // First time inserting into the set, it won't exist yet. + const auto set_pair = m_cpu_group_names_set.insert(group_name); + const bool set_ok = set_pair.second; + assert(set_ok == true); + } else { + CPUGroupSet &values_set = group_search->second; + const CPUGroupSetIt value_search = values_set.find(group_name); + const bool value_found = value_search != values_set.end(); + if (!value_found) { + const auto values_set_pair = values_set.insert(file_path); + const bool values_set_ok = values_set_pair.second; + assert(values_set_ok == true); + } + } + return true; } static void update_texture(MTexture *texture, const ImageCache::CPUCacheValue &image_pixel_data) { - const bool verbose = false; + const bool verbose = true; // No need for MIP-maps. const bool generate_mip_maps = false; @@ -356,18 +513,23 @@ static void update_texture(MTexture *texture, } ImageCache::GPUCacheValue ImageCache::gpu_insert( - MHWRender::MTextureManager *texture_manager, const GPUCacheKey &key, + MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name, const GPUCacheString &file_path, const ImageCache::CPUCacheValue &image_pixel_data) { assert(texture_manager != nullptr); assert(image_pixel_data.is_valid()); - const bool verbose = false; + const bool verbose = true; + + const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert: " - << "key=" << key); + << "key=" << key << " group_name=" << group_name.c_str() + << " file_path=" << file_path.c_str()); GPUCacheValue texture_data = GPUCacheValue(); - const ImageCache::GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); + const ImageCache::GPUMapIt search = m_gpu_item_map.find(key); + const bool found = search != m_gpu_item_map.end(); if (!found) { // If we are at capacity, make room for new entry. const size_t image_data_size = image_pixel_data.byte_count(); @@ -402,15 +564,20 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( // Make 'key' the most-recently-used key, because when we // insert an item into the cache, it's used most recently. ImageCache::GPUKeyListIt key_iterator = - m_gpu_cache_key_list.insert(m_gpu_cache_key_list.end(), key); + m_gpu_key_list.insert(m_gpu_key_list.end(), key); // Create the key-value entry, linked to the usage record. - const auto pair = m_gpu_cache_map.insert( + const auto item_map_pair = m_gpu_item_map.insert( std::make_pair(key, std::make_pair(key_iterator, texture_data))); - const auto inserted_key_iterator = pair.first; - const bool ok = pair.second; + const auto inserted_key_iterator = item_map_pair.first; + const bool item_ok = item_map_pair.second; assert(ok == true); + + const bool group_ok = + ImageCache::gpu_group_insert(group_key, group_name, file_path); + assert(group_ok == true); + } else { ImageCache::GPUKeyListIt iterator = search->second.first; texture_data = search->second.second; @@ -421,7 +588,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( return ImageCache::GPUCacheValue(); } - move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); + move_iterator_to_back_of_key_list(m_gpu_key_list, iterator); update_texture(texture_data.texture(), image_pixel_data); } @@ -429,35 +596,107 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( return texture_data; } -ImageCache::GPUCacheValue ImageCache::gpu_find(const GPUCacheKey &key) { - const bool verbose = false; +bool ImageCache::cpu_insert(const CPUCacheString &group_name, + const CPUCacheString &file_path, + const CPUCacheValue &image_pixel_data) { + const bool verbose = true; + + const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " + << "key=" << key << " group_name=" << group_name.c_str() + << " file_path=" << file_path.c_str()); + + const CPUMapIt item_search = m_cpu_item_map.find(key); + const bool item_found = item_search != m_cpu_item_map.end(); + if (item_found) { + ImageCache::cpu_erase(key); + } + + // If we are at capacity, make room for new entry. + const size_t image_data_size = image_pixel_data.byte_count(); + const CacheEvictionResult evict_result = + ImageCache::cpu_evict_enough_for_new_entry(image_data_size); + if (evict_result == CacheEvictionResult::kFailed) { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache::cpu_insert: evicting memory failed!"); + ImageCache::print_cache_brief(); + } + + m_cpu_used_bytes += image_data_size; + assert(m_cpu_used_bytes <= m_cpu_capacity_bytes); + + // Because we are inserting into the cache, the 'key' is the + // most-recently-used item. + CPUKeyListIt key_iterator = + m_cpu_key_list.insert(m_cpu_key_list.end(), key); + + const auto item_map_pair = m_cpu_item_map.insert( + std::make_pair(key, std::make_pair(key_iterator, image_pixel_data))); + + const auto inserted_key_iterator = item_map_pair.first; + const bool item_map_ok = item_map_pair.second; + assert(item_map_ok == true); + + const bool group_ok = + ImageCache::cpu_group_insert(group_key, group_name, file_path); + assert(group_ok == true); + + return true; +} + +ImageCache::GPUCacheValue ImageCache::gpu_find( + const GPUCacheString &file_path) { + const bool verbose = true; + + const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " + << "key=" << key << "file_path=\"" << file_path.c_str() + << "\""); + return ImageCache::gpu_find(key); +} + +ImageCache::GPUCacheValue ImageCache::gpu_find(const GPUCacheKey key) { + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " << "key=" << key); - const GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); + const GPUMapIt search = m_gpu_item_map.find(key); + const bool found = search != m_gpu_item_map.end(); if (found) { GPUKeyListIt iterator = search->second.first; GPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_gpu_cache_key_list, iterator); + move_iterator_to_back_of_key_list(m_gpu_key_list, iterator); return value; } return GPUCacheValue(); } -ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey &key) { - const bool verbose = false; +ImageCache::CPUCacheValue ImageCache::cpu_find( + const CPUCacheString &file_path) { + const bool verbose = true; + + const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " + << "key=" << key << "file_path=\"" << file_path.c_str() + << "\""); + return ImageCache::cpu_find(key); +} + +ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey key) { + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " << "key=" << key); - const CPUMapIt search = m_cpu_cache_map.find(key); - const bool found = search != m_cpu_cache_map.end(); + const CPUMapIt search = m_cpu_item_map.find(key); + const bool found = search != m_cpu_item_map.end(); if (found) { CPUKeyListIt iterator = search->second.first; CPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_cpu_cache_key_list, iterator); + move_iterator_to_back_of_key_list(m_cpu_key_list, iterator); return value; } return CPUCacheValue(); @@ -465,7 +704,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey &key) { CacheEvictionResult ImageCache::gpu_evict_one( MHWRender::MTextureManager *texture_manager) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); @@ -475,21 +714,23 @@ CacheEvictionResult ImageCache::gpu_evict_one( << m_gpu_used_bytes); assert(texture_manager != nullptr); - if (m_gpu_cache_key_list.empty() || - (m_gpu_cache_map.size() <= m_gpu_item_count_minumum)) { + if (m_gpu_key_list.empty() || + (m_gpu_item_map.size() <= m_gpu_item_count_minumum)) { return CacheEvictionResult::kNotNeeded; } - const GPUCacheKey lru_key = m_gpu_cache_key_list.front(); - const GPUMapIt lru_key_iterator = m_gpu_cache_map.find(lru_key); - assert(lru_key_iterator != m_gpu_cache_map.end()); + const GPUCacheKey lru_key = m_gpu_key_list.front(); + const GPUMapIt lru_key_iterator = m_gpu_item_map.find(lru_key); + assert(lru_key_iterator != m_gpu_item_map.end()); GPUCacheValue texture_data = lru_key_iterator->second.second; m_gpu_used_bytes -= texture_data.byte_count(); texture_data.deallocate_texture(texture_manager); - m_gpu_cache_map.erase(lru_key_iterator); - m_gpu_cache_key_list.pop_front(); + m_gpu_item_map.erase(lru_key_iterator); + m_gpu_key_list.pop_front(); + + // TODO: Remove from group maps. MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::gpu_evict_one: " @@ -499,7 +740,7 @@ CacheEvictionResult ImageCache::gpu_evict_one( } CacheEvictionResult ImageCache::cpu_evict_one() { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); @@ -508,21 +749,23 @@ CacheEvictionResult ImageCache::cpu_evict_one() { "before m_cpu_used_bytes=" << m_cpu_used_bytes); - if (m_cpu_cache_key_list.empty() || - (m_cpu_cache_map.size() <= m_cpu_item_count_minumum)) { + if (m_cpu_key_list.empty() || + (m_cpu_item_map.size() <= m_cpu_item_count_minumum)) { return CacheEvictionResult::kNotNeeded; } - const CPUCacheKey lru_key = m_cpu_cache_key_list.front(); - const CPUMapIt lru_key_iterator = m_cpu_cache_map.find(lru_key); - assert(lru_key_iterator != m_cpu_cache_map.end()); + const CPUCacheKey lru_key = m_cpu_key_list.front(); + const CPUMapIt lru_key_iterator = m_cpu_item_map.find(lru_key); + assert(lru_key_iterator != m_cpu_item_map.end()); CPUCacheValue image_pixel_data = lru_key_iterator->second.second; m_cpu_used_bytes -= image_pixel_data.byte_count(); image_pixel_data.deallocate_pixels(); - m_cpu_cache_map.erase(lru_key_iterator); - m_cpu_cache_key_list.pop_front(); + m_cpu_item_map.erase(lru_key_iterator); + m_cpu_key_list.pop_front(); + + // TODO: Remove from group maps. MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::cpu_evict_one: " @@ -534,12 +777,12 @@ CacheEvictionResult ImageCache::cpu_evict_one() { CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_entry: "); - if (m_gpu_cache_key_list.empty() || - (m_gpu_cache_map.size() <= m_gpu_item_count_minumum)) { + if (m_gpu_key_list.empty() || + (m_gpu_item_map.size() <= m_gpu_item_count_minumum)) { return CacheEvictionResult::kNotNeeded; } @@ -551,8 +794,8 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " "new_used_bytes=" << new_used_bytes); - while (!m_gpu_cache_map.empty() && - (m_gpu_cache_map.size() > m_gpu_item_count_minumum) && + while (!m_gpu_item_map.empty() && + (m_gpu_item_map.size() > m_gpu_item_count_minumum) && (new_used_bytes > m_gpu_capacity_bytes)) { const CacheEvictionResult evict_result = ImageCache::gpu_evict_one(texture_manager); @@ -572,12 +815,12 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( const size_t new_memory_chunk_size) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_entry: "); - if (m_cpu_cache_key_list.empty() || - (m_cpu_cache_map.size() <= m_cpu_item_count_minumum)) { + if (m_cpu_key_list.empty() || + (m_cpu_item_map.size() <= m_cpu_item_count_minumum)) { return CacheEvictionResult::kNotNeeded; } @@ -589,8 +832,8 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " "new_used_bytes=" << new_used_bytes); - while (!m_cpu_cache_map.empty() && - (m_cpu_cache_map.size() > m_cpu_item_count_minumum) && + while (!m_cpu_item_map.empty() && + (m_cpu_item_map.size() > m_cpu_item_count_minumum) && (new_used_bytes > m_cpu_capacity_bytes)) { const CacheEvictionResult evict_result = ImageCache::cpu_evict_one(); if (evict_result != CacheEvictionResult::kSuccess) { @@ -608,44 +851,69 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( } bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey &key) { - const bool verbose = false; + const GPUCacheString &file_path) { + const bool verbose = true; + const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: " + << "key=" << key << "file_path=\"" << file_path.c_str() + << "\""); + return ImageCache::gpu_erase(texture_manager, key); +} + +bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey key) { + const bool verbose = true; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: "); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: " + << "key=" << key); - const GPUMapIt search = m_gpu_cache_map.find(key); - const bool found = search != m_gpu_cache_map.end(); + const GPUMapIt search = m_gpu_item_map.find(key); + const bool found = search != m_gpu_item_map.end(); if (found) { GPUCacheValue texture_data = search->second.second; m_gpu_used_bytes -= texture_data.byte_count(); texture_data.deallocate_texture(texture_manager); - m_gpu_cache_map.erase(search); + m_gpu_item_map.erase(search); // NOTE: This is a O(n) linear operation, and can be very // slow since the list items is spread out in memory. - m_gpu_cache_key_list.remove(key); + m_gpu_key_list.remove(key); + + // TODO: Remove from group maps. } return found; } -bool ImageCache::cpu_erase(const CPUCacheKey &key) { - const bool verbose = false; +bool ImageCache::cpu_erase(const CPUCacheString &file_path) { + const bool verbose = true; + const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: " + << "key=" << key << "file_path=\"" << file_path.c_str() + << "\""); + return ImageCache::cpu_erase(key); +} - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: "); +bool ImageCache::cpu_erase(const CPUCacheKey key) { + const bool verbose = true; - const CPUMapIt search = m_cpu_cache_map.find(key); - const bool found = search != m_cpu_cache_map.end(); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: " + << "key=" << key); + + const CPUMapIt search = m_cpu_item_map.find(key); + const bool found = search != m_cpu_item_map.end(); if (found) { CPUCacheValue image_pixel_data = search->second.second; m_cpu_used_bytes -= image_pixel_data.byte_count(); image_pixel_data.deallocate_pixels(); - m_cpu_cache_map.erase(search); + m_cpu_item_map.erase(search); // NOTE: This is a O(n) linear operation, and can be very // slow since the list items is spread out in memory. - m_cpu_cache_key_list.remove(key); + m_cpu_key_list.remove(key); + + // TODO: Remove from group maps. } return found; } diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 55eed9b04..7f3bf4297 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -27,7 +27,7 @@ #include #include #include -// #include +#include // Maya #include @@ -90,41 +90,41 @@ namespace image { enum class CacheEvictionResult : uint8_t { kSuccess = 0, kNotNeeded, kFailed }; struct ImageCache { - // GPU data types - using GPUCacheKey = std::string; + using HashValue = uint64_t; + + using GPUCacheKey = HashValue; + using CPUCacheKey = HashValue; using GPUCacheValue = TextureData; + using CPUCacheValue = ImagePixelData; + using GPUCacheString = std::string; + using CPUCacheString = std::string; + + using GPUVectorString = std::vector; + using CPUVectorString = std::vector; using GPUKeyList = std::list; + using CPUKeyList = std::list; using GPUKeyListIt = GPUKeyList::iterator; + using CPUKeyListIt = CPUKeyList::iterator; using GPUMap = std::unordered_map>; - using GPUMapIt = GPUMap::iterator; - - // using GPUGroupKeySet = std::unordered_set; - // using GPUGroupKeySetIt = GPUGroupKeySet::iterator; - - // using GPUCacheGroupKey = std::string; - // using GPUGroupKeyMap = std::unordered_map; using GPUGroupKeyMapIt = GPUGroupKeyMap::iterator; - - // CPU data types - using CPUCacheKey = std::string; - using CPUCacheValue = ImagePixelData; - - using CPUKeyList = std::list; - using CPUKeyListIt = CPUKeyList::iterator; - using CPUMap = std::unordered_map>; + using GPUMapIt = GPUMap::iterator; using CPUMapIt = CPUMap::iterator; - // using CPUGroupKeySet = std::unordered_set; - // using CPUGroupKeySetIt = CPUGroupKeySet::iterator; + using GPUGroupKey = HashValue; + using CPUGroupKey = HashValue; + using GPUGroupSet = std::unordered_set; + using CPUGroupSet = std::unordered_set; + using GPUGroupSetIt = GPUGroupSet::iterator; + using CPUGroupSetIt = CPUGroupSet::iterator; - // using CPUCacheGroupKey = std::string; - // using CPUGroupKeyMap = std::unordered_map; using CPUGroupKeyMapIt = CPUGroupKeyMap::iterator; + using GPUGroupMap = std::unordered_map; + using CPUGroupMap = std::unordered_map; + using GPUGroupMapIt = GPUGroupMap::iterator; + using CPUGroupMapIt = CPUGroupMap::iterator; public: static ImageCache &getInstance() { @@ -164,6 +164,8 @@ struct ImageCache { << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); return m_cpu_capacity_bytes; } + + // Get amount of bytes used by the cache. size_t get_gpu_used_bytes() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_used_bytes: " @@ -176,19 +178,19 @@ struct ImageCache { << "m_cpu_used_bytes=" << m_cpu_used_bytes); return m_cpu_used_bytes; } + + // Get the number of items in the cache. size_t get_gpu_item_count() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_item_count: " - << "m_gpu_cache_map.size()=" - << m_gpu_cache_map.size()); - return m_gpu_cache_map.size(); + << "m_gpu_item_map.size()=" << m_gpu_item_map.size()); + return m_gpu_item_map.size(); } size_t get_cpu_item_count() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_item_count: " - << "m_cpu_cache_map.size()=" - << m_cpu_cache_map.size()); - return m_cpu_cache_map.size(); + << "m_cpu_item_map.size()=" << m_cpu_item_map.size()); + return m_cpu_item_map.size(); } // Set the capacity of the cache. @@ -200,6 +202,42 @@ struct ImageCache { MString generate_cache_brief_text() const; void print_cache_brief() const; + // Get the number of groups in the cache. + size_t get_gpu_group_count() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_group_count: " + << "m_gpu_group_names_set.size()=" + << m_gpu_group_names_set.size()); + return m_gpu_group_names_set.size(); + } + size_t get_cpu_group_count() const { + const bool verbose = false; + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_group_count: " + << "m_cpu_group_names_set.size()=" + << m_cpu_group_names_set.size()); + return m_cpu_group_names_set.size(); + } + + // Get sorted vector of group names. + void gpu_group_names(GPUVectorString &out_group_names) const; + void cpu_group_names(CPUVectorString &out_group_names) const; + + // Get the number of items in the given group name. + size_t gpu_group_item_count(const GPUCacheString &group_name) const; + size_t gpu_group_item_count(const GPUGroupKey group_key) const; + size_t cpu_group_item_count(const CPUCacheString &group_name) const; + size_t cpu_group_item_count(const GPUGroupKey group_key) const; + + // Item names in a group. + bool gpu_group_item_names(const GPUCacheString &group_name, + GPUVectorString &out_group_item_names) const; + bool gpu_group_item_names(const GPUGroupKey group_key, + GPUVectorString &out_group_item_names) const; + bool cpu_group_item_names(const CPUCacheString &group_name, + CPUVectorString &out_group_item_names) const; + bool cpu_group_item_names(const CPUGroupKey group_key, + CPUVectorString &out_group_item_names) const; + // TODO: Set/Get Disk cache location. Used to find disk-cached // files. This should be a directory on a very fast disk. // @@ -224,26 +262,30 @@ struct ImageCache { // erased. // // Returns true/false, if the the data was inserted or not. - bool cpu_insert(const CPUCacheKey &key, + bool cpu_insert(const CPUCacheString &group_name, + const CPUCacheString &file_path, const CPUCacheValue &image_pixel_data); // Insert and upload some pixels to the GPU and cache the result. // // Returns the GPUCacheValue inserted into the GPU cache. GPUCacheValue gpu_insert(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey &key, + const GPUCacheString &group_name, + const GPUCacheString &file_path, const CPUCacheValue &image_pixel_data); // Find the key in the GPU cache. // // Returns the GPUCacheValue at the key, or nullptr. - GPUCacheValue gpu_find(const GPUCacheKey &key); + GPUCacheValue gpu_find(const GPUCacheString &file_path); + GPUCacheValue gpu_find(const GPUCacheKey key); // Find the key in the CPU cache. // // Returns the CPUCacheValue at the key, or constructs a default // value and returns it. - CPUCacheValue cpu_find(const CPUCacheKey &key); + CPUCacheValue cpu_find(const CPUCacheString &file_path); + CPUCacheValue cpu_find(const CPUCacheKey key); // TODO: Add a 'gpu/cpu_prefetch()' method, used to add the images // into a prefetching queue. @@ -277,7 +319,9 @@ struct ImageCache { // // Returns true/false, if the key was removed or not. bool gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey &key); + const GPUCacheString &file_path); + bool gpu_erase(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey key); // Remove the key from the image CPU cache. // @@ -285,7 +329,13 @@ struct ImageCache { // slow to to remove a specific key from the cache. // // Returns true/false, if the key was removed or not. - bool cpu_erase(const CPUCacheKey &key); + bool cpu_erase(const CPUCacheString &file_path); + bool cpu_erase(const CPUCacheKey key); + + // + // // get_gpu_group + // bool gpu_erase_group(MHWRender::MTextureManager *texture_manager, + // const GPUCacheString &group_name); // C++ 11; deleting the methods we don't want to ensure they can never be // used. @@ -304,6 +354,16 @@ struct ImageCache { CacheEvictionResult cpu_evict_enough_for_new_entry( const size_t new_memory_chunk_size); + // Add group name into cache, associated with the file path. + // + // This is only used internally as a helper method. + bool gpu_group_insert(const GPUGroupKey group_key, + const GPUCacheString &group_name, + const GPUCacheString &file_path); + bool cpu_group_insert(const CPUGroupKey group_key, + const CPUCacheString &group_name, + const CPUCacheString &file_path); + // Amount of memory capacity. size_t m_gpu_capacity_bytes; size_t m_cpu_capacity_bytes; @@ -326,8 +386,8 @@ struct ImageCache { // These maps also contain pointers into the 'key list' data // structures, allowing us to map from the values to the 'key // list's. - GPUMap m_gpu_cache_map; - CPUMap m_cpu_cache_map; + GPUMap m_gpu_item_map; + CPUMap m_cpu_item_map; // List of keys. // @@ -342,19 +402,24 @@ struct ImageCache { // // The 'front' of the list is the "least recently used" key. The // 'back' of the list is the "most recently used" key. - GPUKeyList m_gpu_cache_key_list; - CPUKeyList m_cpu_cache_key_list; - - // // A Map of groups to a Set of key values. - // // - // // This map can be used to find all the loaded values used by an - // // image sequence. - // GPUGroupKeyMap m_gpu_cache_group_map; - // CPUGroupKeyMap m_cpu_cache_group_map; + GPUKeyList m_gpu_key_list; + CPUKeyList m_cpu_key_list; + + // A Map of groups to a Set of key values. + // + // This map can be used to find all the loaded values used by an + // image sequence. + GPUGroupMap m_gpu_group_map; + CPUGroupMap m_cpu_group_map; + + // All of the group names in a set. + GPUGroupSet m_gpu_group_names_set; + CPUGroupSet m_cpu_group_names_set; }; MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, + const MString &file_pattern, const MString &file_path, const bool do_texture_update); } // namespace image diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index 88aaf4029..d87aec3ea 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -473,8 +473,8 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const bool do_texture_update = false; image::ImageCache &image_cache = image::ImageCache::getInstance(); out_color_texture = image::read_texture_image_file( - texture_manager, image_cache, m_temp_image, expanded_file_path, - do_texture_update); + texture_manager, image_cache, m_temp_image, file_path, + expanded_file_path, do_texture_update); if (out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" From 6f26b8f17f90d1be6c406cdf97948e6a8baf45d9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 22 Jun 2024 23:25:10 +1000 Subject: [PATCH 120/295] mmImageCache command - Erase items and groups with command. The feature is also added to the internal ImageCache C++ code. GitHub issue #252. --- python/mmSolver/tools/imagecache/lib.py | 17 +- src/mmSolver/cmd/MMImageCacheCmd.cpp | 242 +++++++++++++++++++----- src/mmSolver/cmd/MMImageCacheCmd.h | 15 +- src/mmSolver/image/ImageCache.cpp | 231 +++++++++++++--------- src/mmSolver/image/ImageCache.h | 74 +++----- 5 files changed, 392 insertions(+), 187 deletions(-) diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 5b1a9128e..098984aef 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -272,14 +272,14 @@ def get_cache_group_item_names(cache_type, group_name): return value -def cache_erase_group(cache_type, group_name): +def cache_erase_groups(cache_type, group_names): assert cache_type in const.CACHE_TYPE_VALUES - assert isinstance(group_name, str) + assert len(group_names) >= 0 value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(edit=True, gpuEraseGroup=group_name) + value = maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroups=True) elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(edit=True, cpuEraseGroup=group_name) + value = maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroups=True) return value @@ -288,9 +288,9 @@ def cache_erase_items(cache_type, items): assert len(items) >= 0 value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(edit=True, gpuEraseItems=items) + value = maya.cmds.mmImageCache(items, edit=True, gpuEraseItems=True) elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(edit=True, cpuEraseItems=items) + value = maya.cmds.mmImageCache(items, edit=True, cpuEraseItems=True) return value @@ -307,13 +307,14 @@ def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): LOG.info('cache_remove_all_image_plane_slots: slots=%r', slots) group_names = get_cache_group_names(cache_type) + slots_to_remove = [] for slot in slots: if slot not in group_names: LOG.warn('Slot not found in groups: group_names=%r', group_names) continue - cache_erase_group(cache_type, slot) + slots_to_remove.append(slot) - return + return cache_erase_groups(cache_type, slots_to_remove) def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 6e16407f9..79e0165b9 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -24,11 +24,13 @@ // STD #include #include +#include // Maya #include #include #include +#include #include // Maya Viewport 2.0 @@ -59,6 +61,16 @@ #define CPU_ITEM_COUNT_FLAG "-cic" #define CPU_ITEM_COUNT_FLAG_LONG "-cpuItemCount" +#define GPU_ERASE_ITEMS_FLAG "-gei" +#define GPU_ERASE_ITEMS_FLAG_LONG "-gpuEraseItems" +#define CPU_ERASE_ITEMS_FLAG "-cei" +#define CPU_ERASE_ITEMS_FLAG_LONG "-cpuEraseItems" + +#define GPU_ERASE_GROUPS_FLAG "-geg" +#define GPU_ERASE_GROUPS_FLAG_LONG "-gpuEraseGroups" +#define CPU_ERASE_GROUPS_FLAG "-ceg" +#define CPU_ERASE_GROUPS_FLAG_LONG "-cpuEraseGroups" + #define GPU_GROUP_COUNT_FLAG "-ggc" #define GPU_GROUP_COUNT_FLAG_LONG "-gpuGroupCount" #define CPU_GROUP_COUNT_FLAG "-cgc" @@ -122,6 +134,16 @@ MSyntax MMImageCacheCmd::newSyntax() { CHECK_MSTATUS( syntax.addFlag(CPU_ITEM_COUNT_FLAG, CPU_ITEM_COUNT_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(GPU_ERASE_ITEMS_FLAG, GPU_ERASE_ITEMS_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(CPU_ERASE_ITEMS_FLAG, CPU_ERASE_ITEMS_FLAG_LONG)); + + CHECK_MSTATUS( + syntax.addFlag(GPU_ERASE_GROUPS_FLAG, GPU_ERASE_GROUPS_FLAG_LONG)); + CHECK_MSTATUS( + syntax.addFlag(CPU_ERASE_GROUPS_FLAG, CPU_ERASE_GROUPS_FLAG_LONG)); + CHECK_MSTATUS( syntax.addFlag(GPU_GROUP_COUNT_FLAG, GPU_GROUP_COUNT_FLAG_LONG)); CHECK_MSTATUS( @@ -152,7 +174,7 @@ MSyntax MMImageCacheCmd::newSyntax() { */ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MStatus status = MStatus::kSuccess; - const bool verbose = true; + const bool verbose = false; MArgDatabase argData(syntax(), args, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -169,19 +191,49 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { "m_is_edit=" << m_is_edit); - // Get the file path. + // Get string objects given to the command. MStringArray string_objects; argData.getObjects(string_objects); + m_group_name = ""; + if (string_objects.length() > 0) { + const std::string item_name = string_objects[0].asChar(); + m_group_name = item_name; + } + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "m_group_name=\"" + << m_group_name << "\""); + + for (auto i = 0; i < string_objects.length(); i++) { + const std::string item_name = string_objects[i].asChar(); + m_item_names.push_back(item_name); + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::parseArgs: " + "m_item_names[" + << i << "]=\"" << item_name << "\""); + } + const bool has_gpu_capacity = argData.isFlagSet(GPU_CAPACITY_FLAG, &status); const bool has_cpu_capacity = argData.isFlagSet(CPU_CAPACITY_FLAG, &status); const bool has_gpu_used = argData.isFlagSet(GPU_USED_FLAG, &status); const bool has_cpu_used = argData.isFlagSet(CPU_USED_FLAG, &status); + const bool has_gpu_item_count = argData.isFlagSet(GPU_ITEM_COUNT_FLAG, &status); const bool has_cpu_item_count = argData.isFlagSet(CPU_ITEM_COUNT_FLAG, &status); + const bool has_gpu_erase_items = + argData.isFlagSet(GPU_ERASE_ITEMS_FLAG, &status); + const bool has_cpu_erase_items = + argData.isFlagSet(CPU_ERASE_ITEMS_FLAG, &status); + + const bool has_gpu_erase_groups = + argData.isFlagSet(GPU_ERASE_GROUPS_FLAG, &status); + const bool has_cpu_erase_groups = + argData.isFlagSet(CPU_ERASE_GROUPS_FLAG, &status); + const bool has_gpu_group_count = argData.isFlagSet(GPU_GROUP_COUNT_FLAG, &status); const bool has_cpu_group_count = @@ -243,9 +295,6 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { return status; } - m_string_objects.clear(); - m_string_objects.append(string_objects[0]); - if (has_gpu_group_item_count) { m_command_flag = ImageCacheFlagMode::kGpuGroupItemCount; m_output_type = ImageCacheOutputType::kSize; @@ -265,8 +314,8 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::parseArgs: " - "Invalid command query flag!" - "value=" + "Invalid command query flag! " + "m_command_flag=" << static_cast(m_command_flag)); return MStatus::kFailure; } @@ -295,11 +344,23 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { // Store the current value, so we can undo later. m_previous_cpu_capacity_bytes = image_cache.get_cpu_capacity_bytes(); + } else if (has_gpu_erase_items) { + m_command_flag = ImageCacheFlagMode::kGpuEraseItems; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_cpu_erase_items) { + m_command_flag = ImageCacheFlagMode::kCpuEraseItems; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_gpu_erase_groups) { + m_command_flag = ImageCacheFlagMode::kGpuEraseGroups; + m_output_type = ImageCacheOutputType::kSize; + } else if (has_cpu_erase_groups) { + m_command_flag = ImageCacheFlagMode::kCpuEraseGroups; + m_output_type = ImageCacheOutputType::kSize; } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::parseArgs: " - "Invalid command edit flag!" - "value=" + "Invalid command flag! " + "m_command_flag=" << static_cast(m_command_flag)); return MStatus::kFailure; } @@ -343,9 +404,13 @@ inline MStatus get_texture_manager( inline MStatus set_values(image::ImageCache &image_cache, const ImageCacheFlagMode command_flag, const size_t gpu_capacity_bytes, - const size_t cpu_capacity_bytes) { + const size_t cpu_capacity_bytes, + const std::vector &item_names, + const std::string &group_name, + size_t &out_item_count) { MStatus status = MStatus::kSuccess; + out_item_count = 0; if (command_flag == ImageCacheFlagMode::kGpuCapacity) { MHWRender::MTextureManager *texture_manager = nullptr; status = get_texture_manager(texture_manager); @@ -354,11 +419,55 @@ inline MStatus set_values(image::ImageCache &image_cache, image_cache.set_gpu_capacity_bytes(texture_manager, gpu_capacity_bytes); } else if (command_flag == ImageCacheFlagMode::kCpuCapacity) { image_cache.set_cpu_capacity_bytes(cpu_capacity_bytes); + } else if (command_flag == ImageCacheFlagMode::kGpuEraseItems) { + MHWRender::MTextureManager *texture_manager = nullptr; + status = get_texture_manager(texture_manager); + CHECK_MSTATUS_AND_RETURN_IT(status); + + for (auto it = item_names.begin(); it != item_names.end(); it++) { + const bool ok = image_cache.gpu_erase_item(texture_manager, *it); + out_item_count += static_cast(ok); + } + } else if (command_flag == ImageCacheFlagMode::kCpuEraseItems) { + for (auto it = item_names.begin(); it != item_names.end(); it++) { + const bool ok = image_cache.cpu_erase_item(*it); + out_item_count += static_cast(ok); + } + } else if (command_flag == ImageCacheFlagMode::kGpuEraseGroups) { + if (group_name.size() == 0) { + MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " + << "\"" << GPU_ERASE_GROUPS_FLAG_LONG << "\" " + << "flag needs a group name, but none given! " + "group_name.size()=" + << static_cast(group_name.size())); + return MStatus::kFailure; + } + + MHWRender::MTextureManager *texture_manager = nullptr; + status = get_texture_manager(texture_manager); + CHECK_MSTATUS_AND_RETURN_IT(status); + + for (auto it = item_names.begin(); it != item_names.end(); it++) { + out_item_count += image_cache.gpu_erase_group(texture_manager, *it); + } + } else if (command_flag == ImageCacheFlagMode::kCpuEraseGroups) { + if (group_name.size() == 0) { + MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " + << "\"" << CPU_ERASE_GROUPS_FLAG_LONG << "\" " + << "flag needs a group name, but none given! " + "group_name.size()=" + << static_cast(group_name.size())); + return MStatus::kFailure; + } + + for (auto it = item_names.begin(); it != item_names.end(); it++) { + out_item_count = image_cache.cpu_erase_group(*it); + } } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::set_values: " - "Invalid command edit flag! " - "value=" + "Invalid command flag! " + "command_flag=" << static_cast(command_flag)); return MStatus::kFailure; } @@ -393,9 +502,9 @@ MStatus get_value_size(image::ImageCache &image_cache, out_value = image_cache.cpu_group_item_count(group_name); } else { MMSOLVER_MAYA_ERR( - "MMImageCacheCmd::doIt: " - "Invalid command query flag! " - "value=" + "MMImageCacheCmd::get_value_size: " + "Invalid command flag! " + "command_flag=" << static_cast(command_flag)); return MStatus::kFailure; } @@ -410,9 +519,9 @@ MStatus get_value_string(image::ImageCache &image_cache, out_result = image_cache.generate_cache_brief_text(); } else { MMSOLVER_MAYA_ERR( - "MMImageCacheCmd::doIt: " - "Invalid command query flag! " - "value=" + "MMImageCacheCmd::get_value_string: " + "Invalid command flag! " + "command_flag=" << static_cast(command_flag)); return MStatus::kFailure; } @@ -432,24 +541,41 @@ MStatus get_value_string_array(image::ImageCache &image_cache, } else if (command_flag == ImageCacheFlagMode::kCpuGroupNames) { image_cache.cpu_group_names(outputs); } else if (command_flag == ImageCacheFlagMode::kGpuGroupItemNames) { + if (group_name.size() == 0) { + MMSOLVER_MAYA_ERR("MMImageCacheCmd::get_value_string_array: " + << "\"" << GPU_GROUP_ITEM_NAMES_FLAG_LONG << "\" " + << "flag needs a group name, but none given! " + "group_name.size()=" + << static_cast(group_name.size())); + return MStatus::kFailure; + } + ok = image_cache.gpu_group_item_names(group_name, outputs); } else if (command_flag == ImageCacheFlagMode::kCpuGroupItemNames) { + if (group_name.size() == 0) { + MMSOLVER_MAYA_ERR("MMImageCacheCmd::get_value_string_array: " + << "\"" << CPU_GROUP_ITEM_NAMES_FLAG_LONG << "\" " + << "flag needs a group name, but none given! " + "group_name.size()=" + << static_cast(group_name.size())); + return MStatus::kFailure; + } + ok = image_cache.cpu_group_item_names(group_name, outputs); } else { MMSOLVER_MAYA_ERR( - "MMImageCacheCmd::doIt: " - "Invalid command query flag! " - "value=" + "MMImageCacheCmd::get_value_string_array: " + "Invalid command flag! " + "command_flag=" << static_cast(command_flag)); return MStatus::kFailure; } if (!ok) { MMSOLVER_MAYA_ERR( - "MMImageCacheCmd::doIt: " - "Invalid command query flag! " - "value=" - << static_cast(command_flag)); + "MMImageCacheCmd::get_value_string_array: " + "failed to apply action! ok=" + << static_cast(ok)); return MStatus::kFailure; } @@ -464,7 +590,7 @@ MStatus get_value_string_array(image::ImageCache &image_cache, MStatus MMImageCacheCmd::doIt(const MArgList &args) { MStatus status = MStatus::kSuccess; - const bool verbose = true; + const bool verbose = false; // Read all the flag arguments. status = parseArgs(args); @@ -479,28 +605,17 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { CHECK_MSTATUS_AND_RETURN_IT(status); MMImageCacheCmd::setResult(result); } else if (m_output_type == ImageCacheOutputType::kSize) { - std::string group_name = ""; - if (m_string_objects.length() > 0) { - group_name = m_string_objects[0].asChar(); - } - size_t result = 0; - status = - get_value_size(image_cache, m_command_flag, group_name, result); + status = get_value_size(image_cache, m_command_flag, m_group_name, + result); CHECK_MSTATUS_AND_RETURN_IT(status); MString result_string(mmmayastring::numberToMString(result)); - MMImageCacheCmd::setResult(result_string); } else if (m_output_type == ImageCacheOutputType::kStringArray) { - std::string group_name = ""; - if (m_string_objects.length() > 0) { - group_name = m_string_objects[0].asChar(); - } - MStringArray results; status = get_value_string_array(image_cache, m_command_flag, - group_name, results); + m_group_name, results); CHECK_MSTATUS_AND_RETURN_IT(status); MMImageCacheCmd::setResult(results); } else { @@ -514,9 +629,17 @@ MStatus MMImageCacheCmd::doIt(const MArgList &args) { } else if (m_is_edit) { image::ImageCache &image_cache = image::ImageCache::getInstance(); - set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, - m_cpu_capacity_bytes); + + size_t item_count = 0; + status = set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, + m_cpu_capacity_bytes, m_item_names, m_group_name, + item_count); CHECK_MSTATUS_AND_RETURN_IT(status); + + if (m_output_type == ImageCacheOutputType::kSize) { + MString result_string(mmmayastring::numberToMString(item_count)); + MMImageCacheCmd::setResult(result_string); + } } else { MMSOLVER_MAYA_ERR( "MMImageCacheCmd::doIt: " @@ -532,9 +655,17 @@ MStatus MMImageCacheCmd::redoIt() { MStatus status = MStatus::kSuccess; if (m_is_edit) { image::ImageCache &image_cache = image::ImageCache::getInstance(); + + size_t item_count = 0; status = set_values(image_cache, m_command_flag, m_gpu_capacity_bytes, - m_cpu_capacity_bytes); + m_cpu_capacity_bytes, m_item_names, m_group_name, + item_count); CHECK_MSTATUS_AND_RETURN_IT(status); + + if (m_output_type == ImageCacheOutputType::kSize) { + MString result_string(mmmayastring::numberToMString(item_count)); + MMImageCacheCmd::setResult(result_string); + } } return status; } @@ -542,11 +673,26 @@ MStatus MMImageCacheCmd::redoIt() { MStatus MMImageCacheCmd::undoIt() { MStatus status = MStatus::kSuccess; if (m_is_edit) { - image::ImageCache &image_cache = image::ImageCache::getInstance(); - status = set_values(image_cache, m_command_flag, - m_previous_gpu_capacity_bytes, - m_previous_cpu_capacity_bytes); - CHECK_MSTATUS_AND_RETURN_IT(status); + if ((m_command_flag == ImageCacheFlagMode::kGpuEraseItems) || + (m_command_flag == ImageCacheFlagMode::kCpuEraseItems) || + (m_command_flag == ImageCacheFlagMode::kGpuEraseGroups) || + (m_command_flag == ImageCacheFlagMode::kCpuEraseGroups)) { + status = MStatus::kFailure; + MMSOLVER_MAYA_ERR( + "MMImageCacheCmd::undoIt: " + "Invalid command edit flag! " + "value=" + << static_cast(m_command_flag)); + } else { + image::ImageCache &image_cache = image::ImageCache::getInstance(); + + size_t item_count = 0; + status = set_values(image_cache, m_command_flag, + m_previous_gpu_capacity_bytes, + m_previous_cpu_capacity_bytes, m_item_names, + m_group_name, item_count); + CHECK_MSTATUS_AND_RETURN_IT(status); + } } return status; } diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index 8502089b0..c64dcf502 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -107,12 +107,17 @@ #ifndef MAYA_MM_IMAGE_CACHE_CMD_H #define MAYA_MM_IMAGE_CACHE_CMD_H +// STL +#include +#include + // Maya #include #include #include #include #include +#include #include namespace mmsolver { @@ -124,6 +129,10 @@ enum class ImageCacheFlagMode : uint8_t { kCpuUsed, kGpuItemCount, kCpuItemCount, + kGpuEraseGroups, + kCpuEraseGroups, + kGpuEraseItems, + kCpuEraseItems, kGpuGroupCount, kCpuGroupCount, kGpuGroupNames, @@ -154,7 +163,8 @@ class MMImageCacheCmd : public MPxCommand { , m_previous_cpu_capacity_bytes(0) , m_gpu_capacity_bytes(0) , m_cpu_capacity_bytes(0) - , m_string_objects(){}; + , m_item_names() + , m_group_name(){}; virtual ~MMImageCacheCmd(); @@ -186,7 +196,8 @@ class MMImageCacheCmd : public MPxCommand { size_t m_gpu_capacity_bytes; size_t m_cpu_capacity_bytes; - MStringArray m_string_objects; + std::vector m_item_names; + std::string m_group_name; }; } // namespace mmsolver diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 94f28584c..ee0f757e6 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -68,7 +68,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, const bool do_texture_update) { assert(texture_manager != nullptr); - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file_path=" << file_path.asChar()); @@ -82,7 +82,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, } const std::string key = std::string(resolved_file_path.asChar()); - TextureData texture_data = image_cache.gpu_find(key); + TextureData texture_data = image_cache.gpu_find_item(key); MMSOLVER_MAYA_VRB( "mmsolver::ImageCache: read_texture_image_file: findTexture: " @@ -107,7 +107,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, // Python user code to add the list of valid images into the // cache. - ImagePixelData image_pixel_data = image_cache.cpu_find(key); + ImagePixelData image_pixel_data = image_cache.cpu_find_item(key); uint32_t width = 0; uint32_t height = 0; @@ -161,8 +161,8 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, const std::string group_name = std::string(file_pattern.asChar()); const uint64_t group_hash = mmsolver::hash::make_hash(group_name); - texture_data = image_cache.gpu_insert(texture_manager, group_name, key, - gpu_image_pixel_data); + texture_data = image_cache.gpu_insert_item(texture_manager, group_name, key, + gpu_image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "gpu_inserted=" << texture_data.texture()); @@ -183,7 +183,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, pixel_data_byte_count); const bool cpu_inserted = - image_cache.cpu_insert(group_name, key, image_pixel_data); + image_cache.cpu_insert_item(group_name, key, image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "cpu_inserted=" << cpu_inserted); @@ -195,7 +195,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, void ImageCache::set_gpu_capacity_bytes( MHWRender::MTextureManager *texture_manager, const size_t value) { - const bool verbose = true; + const bool verbose = false; m_gpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); @@ -210,7 +210,7 @@ void ImageCache::set_gpu_capacity_bytes( (m_gpu_item_map.size() > m_gpu_item_count_minumum) && (m_gpu_used_bytes > m_gpu_capacity_bytes)) { const CacheEvictionResult result = - ImageCache::gpu_evict_one(texture_manager); + ImageCache::gpu_evict_one_item(texture_manager); if (result != CacheEvictionResult::kSuccess) { break; } @@ -218,7 +218,7 @@ void ImageCache::set_gpu_capacity_bytes( } void ImageCache::set_cpu_capacity_bytes(const size_t value) { - const bool verbose = true; + const bool verbose = false; m_cpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); @@ -232,7 +232,7 @@ void ImageCache::set_cpu_capacity_bytes(const size_t value) { while (!m_cpu_item_map.empty() && (m_cpu_item_map.size() > m_cpu_item_count_minumum) && (m_cpu_used_bytes > m_cpu_capacity_bytes)) { - const CacheEvictionResult result = ImageCache::cpu_evict_one(); + const CacheEvictionResult result = ImageCache::cpu_evict_one_item(); if (result != CacheEvictionResult::kSuccess) { break; } @@ -301,7 +301,7 @@ void ImageCache::print_cache_brief() const { } void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_group_names: "); out_group_names.clear(); @@ -319,7 +319,7 @@ void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { } void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_group_names: "); out_group_names.clear(); @@ -428,7 +428,7 @@ bool ImageCache::cpu_group_item_names( return true; } -bool ImageCache::gpu_group_insert(const GPUGroupKey group_key, +bool ImageCache::gpu_insert_group(const GPUGroupKey group_key, const GPUCacheString &group_name, const GPUCacheString &file_path) { const GPUGroupMapIt group_search = m_gpu_group_map.find(group_key); @@ -459,7 +459,7 @@ bool ImageCache::gpu_group_insert(const GPUGroupKey group_key, return true; } -bool ImageCache::cpu_group_insert(const CPUGroupKey group_key, +bool ImageCache::cpu_insert_group(const CPUGroupKey group_key, const CPUCacheString &group_name, const CPUCacheString &file_path) { const CPUGroupMapIt group_search = m_cpu_group_map.find(group_key); @@ -492,7 +492,7 @@ bool ImageCache::cpu_group_insert(const CPUGroupKey group_key, static void update_texture(MTexture *texture, const ImageCache::CPUCacheValue &image_pixel_data) { - const bool verbose = true; + const bool verbose = false; // No need for MIP-maps. const bool generate_mip_maps = false; @@ -512,18 +512,18 @@ static void update_texture(MTexture *texture, CHECK_MSTATUS(status); } -ImageCache::GPUCacheValue ImageCache::gpu_insert( +ImageCache::GPUCacheValue ImageCache::gpu_insert_item( MHWRender::MTextureManager *texture_manager, const GPUCacheString &group_name, const GPUCacheString &file_path, const ImageCache::CPUCacheValue &image_pixel_data) { assert(texture_manager != nullptr); assert(image_pixel_data.is_valid()); - const bool verbose = true; + const bool verbose = false; const GPUGroupKey key = mmsolver::hash::make_hash(file_path); const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert_item: " << "key=" << key << " group_name=" << group_name.c_str() << " file_path=" << file_path.c_str()); @@ -535,11 +535,12 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( const size_t image_data_size = image_pixel_data.byte_count(); const CacheEvictionResult evict_result = - ImageCache::gpu_evict_enough_for_new_entry(texture_manager, - image_data_size); + ImageCache::gpu_evict_enough_for_new_item(texture_manager, + image_data_size); if (evict_result == CacheEvictionResult::kFailed) { MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache::gpu_insert: evicting memory failed!"); + "mmsolver::ImageCache::gpu_insert_item: evicting memory " + "failed!"); ImageCache::print_cache_brief(); } @@ -550,7 +551,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( image_pixel_data.pixel_data_type()); if (!allocate_ok) { MMSOLVER_MAYA_ERR( - "mmsolver::ImageCache: gpu_insert: " + "mmsolver::ImageCache: gpu_insert_item: " "Could not allocate texture!"); } @@ -575,7 +576,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( assert(ok == true); const bool group_ok = - ImageCache::gpu_group_insert(group_key, group_name, file_path); + ImageCache::gpu_insert_group(group_key, group_name, file_path); assert(group_ok == true); } else { @@ -583,7 +584,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( texture_data = search->second.second; if (!texture_data.is_valid()) { MMSOLVER_MAYA_ERR( - "mmsolver::ImageCache: gpu_insert: " + "mmsolver::ImageCache: gpu_insert_item: " "Found texture is invalid!"); return ImageCache::GPUCacheValue(); } @@ -596,31 +597,31 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert( return texture_data; } -bool ImageCache::cpu_insert(const CPUCacheString &group_name, - const CPUCacheString &file_path, - const CPUCacheValue &image_pixel_data) { - const bool verbose = true; +bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, + const CPUCacheString &file_path, + const CPUCacheValue &image_pixel_data) { + const bool verbose = false; const CPUGroupKey key = mmsolver::hash::make_hash(file_path); const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert_item: " << "key=" << key << " group_name=" << group_name.c_str() << " file_path=" << file_path.c_str()); const CPUMapIt item_search = m_cpu_item_map.find(key); const bool item_found = item_search != m_cpu_item_map.end(); if (item_found) { - ImageCache::cpu_erase(key); + ImageCache::cpu_erase_item(key); } // If we are at capacity, make room for new entry. const size_t image_data_size = image_pixel_data.byte_count(); const CacheEvictionResult evict_result = - ImageCache::cpu_evict_enough_for_new_entry(image_data_size); + ImageCache::cpu_evict_enough_for_new_item(image_data_size); if (evict_result == CacheEvictionResult::kFailed) { MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache::cpu_insert: evicting memory failed!"); + "mmsolver::ImageCache::cpu_insert_item: evicting memory failed!"); ImageCache::print_cache_brief(); } @@ -640,27 +641,27 @@ bool ImageCache::cpu_insert(const CPUCacheString &group_name, assert(item_map_ok == true); const bool group_ok = - ImageCache::cpu_group_insert(group_key, group_name, file_path); + ImageCache::cpu_insert_group(group_key, group_name, file_path); assert(group_ok == true); return true; } -ImageCache::GPUCacheValue ImageCache::gpu_find( +ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const GPUGroupKey key = mmsolver::hash::make_hash(file_path); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " << "key=" << key << "file_path=\"" << file_path.c_str() << "\""); - return ImageCache::gpu_find(key); + return ImageCache::gpu_find_item(key); } -ImageCache::GPUCacheValue ImageCache::gpu_find(const GPUCacheKey key) { - const bool verbose = true; +ImageCache::GPUCacheValue ImageCache::gpu_find_item(const GPUCacheKey key) { + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " << "key=" << key); const GPUMapIt search = m_gpu_item_map.find(key); @@ -674,21 +675,21 @@ ImageCache::GPUCacheValue ImageCache::gpu_find(const GPUCacheKey key) { return GPUCacheValue(); } -ImageCache::CPUCacheValue ImageCache::cpu_find( +ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const CPUGroupKey key = mmsolver::hash::make_hash(file_path); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " << "key=" << key << "file_path=\"" << file_path.c_str() << "\""); - return ImageCache::cpu_find(key); + return ImageCache::cpu_find_item(key); } -ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey key) { - const bool verbose = true; +ImageCache::CPUCacheValue ImageCache::cpu_find_item(const CPUCacheKey key) { + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " << "key=" << key); const CPUMapIt search = m_cpu_item_map.find(key); @@ -702,14 +703,14 @@ ImageCache::CPUCacheValue ImageCache::cpu_find(const CPUCacheKey key) { return CPUCacheValue(); } -CacheEvictionResult ImageCache::gpu_evict_one( +CacheEvictionResult ImageCache::gpu_evict_one_item( MHWRender::MTextureManager *texture_manager) { - const bool verbose = true; + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one: "); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one_item: "); MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::gpu_evict_one: " + "mmsolver::ImageCache::gpu_evict_one_item: " "before m_gpu_used_bytes=" << m_gpu_used_bytes); @@ -733,19 +734,19 @@ CacheEvictionResult ImageCache::gpu_evict_one( // TODO: Remove from group maps. MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::gpu_evict_one: " + "mmsolver::ImageCache::gpu_evict_one_item: " "after m_gpu_used_bytes=" << m_gpu_used_bytes); return CacheEvictionResult::kSuccess; } -CacheEvictionResult ImageCache::cpu_evict_one() { - const bool verbose = true; +CacheEvictionResult ImageCache::cpu_evict_one_item() { + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one: "); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one_item: "); MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::cpu_evict_one: " + "mmsolver::ImageCache::cpu_evict_one_item: " "before m_cpu_used_bytes=" << m_cpu_used_bytes); @@ -768,18 +769,18 @@ CacheEvictionResult ImageCache::cpu_evict_one() { // TODO: Remove from group maps. MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::cpu_evict_one: " + "mmsolver::ImageCache::cpu_evict_one_item: " "after m_cpu_used_bytes=" << m_cpu_used_bytes); return CacheEvictionResult::kSuccess; } -CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( +CacheEvictionResult ImageCache::gpu_evict_enough_for_new_item( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size) { - const bool verbose = true; + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_entry: "); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_item: "); if (m_gpu_key_list.empty() || (m_gpu_item_map.size() <= m_gpu_item_count_minumum)) { @@ -791,21 +792,21 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( // until we have enough room to store 'new_memory_chunk_size'. size_t new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " + "mmsolver::ImageCache::gpu_evict_enough_for_new_item: " "new_used_bytes=" << new_used_bytes); while (!m_gpu_item_map.empty() && (m_gpu_item_map.size() > m_gpu_item_count_minumum) && (new_used_bytes > m_gpu_capacity_bytes)) { const CacheEvictionResult evict_result = - ImageCache::gpu_evict_one(texture_manager); + ImageCache::gpu_evict_one_item(texture_manager); if (evict_result != CacheEvictionResult::kSuccess) { result = evict_result; break; } new_used_bytes = m_gpu_used_bytes + new_memory_chunk_size; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::gpu_evict_enough_for_new_entry: " + "mmsolver::ImageCache::gpu_evict_enough_for_new_item: " "new_used_bytes=" << new_used_bytes); } @@ -813,11 +814,11 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_entry( return result; } -CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( +CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( const size_t new_memory_chunk_size) { - const bool verbose = true; + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_entry: "); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_item: "); if (m_cpu_key_list.empty() || (m_cpu_item_map.size() <= m_cpu_item_count_minumum)) { @@ -829,20 +830,21 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( // until we have enough room to store 'new_memory_chunk_size'. size_t new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " + "mmsolver::ImageCache::cpu_evict_enough_for_new_item: " "new_used_bytes=" << new_used_bytes); while (!m_cpu_item_map.empty() && (m_cpu_item_map.size() > m_cpu_item_count_minumum) && (new_used_bytes > m_cpu_capacity_bytes)) { - const CacheEvictionResult evict_result = ImageCache::cpu_evict_one(); + const CacheEvictionResult evict_result = + ImageCache::cpu_evict_one_item(); if (evict_result != CacheEvictionResult::kSuccess) { result = evict_result; break; } new_used_bytes = m_cpu_used_bytes + new_memory_chunk_size; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::cpu_evict_enough_for_new_entry: " + "mmsolver::ImageCache::cpu_evict_enough_for_new_item: " "new_used_bytes=" << new_used_bytes); } @@ -850,21 +852,21 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_entry( return result; } -bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheString &file_path) { - const bool verbose = true; +bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &file_path) { + const bool verbose = false; const GPUGroupKey key = mmsolver::hash::make_hash(file_path); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "key=" << key << "file_path=\"" << file_path.c_str() << "\""); - return ImageCache::gpu_erase(texture_manager, key); + return ImageCache::gpu_erase_item(texture_manager, key); } -bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey key) { - const bool verbose = true; +bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey key) { + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "key=" << key); const GPUMapIt search = m_gpu_item_map.find(key); @@ -885,19 +887,19 @@ bool ImageCache::gpu_erase(MHWRender::MTextureManager *texture_manager, return found; } -bool ImageCache::cpu_erase(const CPUCacheString &file_path) { - const bool verbose = true; +bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { + const bool verbose = false; const CPUGroupKey key = mmsolver::hash::make_hash(file_path); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "key=" << key << "file_path=\"" << file_path.c_str() << "\""); - return ImageCache::cpu_erase(key); + return ImageCache::cpu_erase_item(key); } -bool ImageCache::cpu_erase(const CPUCacheKey key) { - const bool verbose = true; +bool ImageCache::cpu_erase_item(const CPUCacheKey key) { + const bool verbose = false; - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "key=" << key); const CPUMapIt search = m_cpu_item_map.find(key); @@ -918,5 +920,64 @@ bool ImageCache::cpu_erase(const CPUCacheKey key) { return found; } +size_t ImageCache::gpu_erase_group(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name) { + const bool verbose = false; + + const GPUGroupKey key = mmsolver::hash::make_hash(group_name); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_group: " + << "key=" << key << "group_name=\"" << group_name.c_str() + << "\""); + + const GPUGroupMapIt group_search = m_gpu_group_map.find(key); + const bool group_found = group_search != m_gpu_group_map.end(); + if (!group_found) { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache: gpu_erase_group: " + "Group name \"" + << group_name << "\" not found!"); + return 0; + } + + size_t count = 0; + GPUGroupSet &values_set = group_search->second; + for (auto it = values_set.begin(); it != values_set.end(); it++) { + GPUCacheString value = *it; + const bool ok = ImageCache::gpu_erase_item(texture_manager, value); + count += static_cast(ok); + } + + return count; +} + +size_t ImageCache::cpu_erase_group(const CPUCacheString &group_name) { + const bool verbose = false; + + const CPUGroupKey key = mmsolver::hash::make_hash(group_name); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_group: " + << "key=" << key << "group_name=\"" << group_name.c_str() + << "\""); + + const CPUGroupMapIt group_search = m_cpu_group_map.find(key); + const bool group_found = group_search != m_cpu_group_map.end(); + if (!group_found) { + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache: cpu_erase_group: " + "Group name \"" + << group_name << "\" not found!"); + return 0; + } + + size_t count = 0; + CPUGroupSet &values_set = group_search->second; + for (auto it = values_set.begin(); it != values_set.end(); it++) { + CPUCacheString value = *it; + const bool ok = ImageCache::cpu_erase_item(value); + count += static_cast(ok); + } + + return count; +} + } // namespace image } // namespace mmsolver diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 7f3bf4297..70fb899ce 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -262,30 +262,28 @@ struct ImageCache { // erased. // // Returns true/false, if the the data was inserted or not. - bool cpu_insert(const CPUCacheString &group_name, - const CPUCacheString &file_path, - const CPUCacheValue &image_pixel_data); + bool cpu_insert_item(const CPUCacheString &group_name, + const CPUCacheString &file_path, + const CPUCacheValue &image_pixel_data); // Insert and upload some pixels to the GPU and cache the result. // // Returns the GPUCacheValue inserted into the GPU cache. - GPUCacheValue gpu_insert(MHWRender::MTextureManager *texture_manager, - const GPUCacheString &group_name, - const GPUCacheString &file_path, - const CPUCacheValue &image_pixel_data); + GPUCacheValue gpu_insert_item(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name, + const GPUCacheString &file_path, + const CPUCacheValue &image_pixel_data); - // Find the key in the GPU cache. + // Find the key in the GPU/CPU cache. // // Returns the GPUCacheValue at the key, or nullptr. - GPUCacheValue gpu_find(const GPUCacheString &file_path); - GPUCacheValue gpu_find(const GPUCacheKey key); - - // Find the key in the CPU cache. // // Returns the CPUCacheValue at the key, or constructs a default // value and returns it. - CPUCacheValue cpu_find(const CPUCacheString &file_path); - CPUCacheValue cpu_find(const CPUCacheKey key); + GPUCacheValue gpu_find_item(const GPUCacheString &file_path); + GPUCacheValue gpu_find_item(const GPUCacheKey key); + CPUCacheValue cpu_find_item(const CPUCacheString &file_path); + CPUCacheValue cpu_find_item(const CPUCacheKey key); // TODO: Add a 'gpu/cpu_prefetch()' method, used to add the images // into a prefetching queue. @@ -303,39 +301,27 @@ struct ImageCache { // // Returns true/false, if an item was removed from the cache or // not. - CacheEvictionResult gpu_evict_one( + CacheEvictionResult gpu_evict_one_item( MHWRender::MTextureManager *texture_manager); + CacheEvictionResult cpu_evict_one_item(); - // Evict the least recently used item from the CPU cache. - // - // Returns true/false, if an item was removed from the cache or - // not. - CacheEvictionResult cpu_evict_one(); - - // Remove the key from the image GPU cache. + // Remove the key from the image GPU/CPU cache. // // NOTE: Due to the way the LRU cache works, this can be quite // slow to to remove a specific key from the cache. // // Returns true/false, if the key was removed or not. - bool gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheString &file_path); - bool gpu_erase(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey key); - - // Remove the key from the image CPU cache. - // - // NOTE: Due to the way the LRU cache works, this can be quite - // slow to to remove a specific key from the cache. - // - // Returns true/false, if the key was removed or not. - bool cpu_erase(const CPUCacheString &file_path); - bool cpu_erase(const CPUCacheKey key); - - // - // // get_gpu_group - // bool gpu_erase_group(MHWRender::MTextureManager *texture_manager, - // const GPUCacheString &group_name); + bool gpu_erase_item(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &file_path); + bool gpu_erase_item(MHWRender::MTextureManager *texture_manager, + const GPUCacheKey key); + bool cpu_erase_item(const CPUCacheString &file_path); + bool cpu_erase_item(const CPUCacheKey key); + + // Erase all items in the given group_name. + size_t gpu_erase_group(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name); + size_t cpu_erase_group(const GPUCacheString &group_name); // C++ 11; deleting the methods we don't want to ensure they can never be // used. @@ -348,19 +334,19 @@ struct ImageCache { void operator=(ImageCache const &) = delete; private: - CacheEvictionResult gpu_evict_enough_for_new_entry( + CacheEvictionResult gpu_evict_enough_for_new_item( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size); - CacheEvictionResult cpu_evict_enough_for_new_entry( + CacheEvictionResult cpu_evict_enough_for_new_item( const size_t new_memory_chunk_size); // Add group name into cache, associated with the file path. // // This is only used internally as a helper method. - bool gpu_group_insert(const GPUGroupKey group_key, + bool gpu_insert_group(const GPUGroupKey group_key, const GPUCacheString &group_name, const GPUCacheString &file_path); - bool cpu_group_insert(const CPUGroupKey group_key, + bool cpu_insert_group(const CPUGroupKey group_key, const CPUCacheString &group_name, const CPUCacheString &file_path); From 57cdbc76a0ef13227e0a935fc16ec4f2736086fb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 23 Jun 2024 12:06:46 +1000 Subject: [PATCH 121/295] ImageCache - Erase items by group association Allow erasing entire groups of images, or individual items in the cache by file path name. GitHub issue #252. --- python/mmSolver/tools/imagecache/lib.py | 8 +- src/mmSolver/cmd/MMImageCacheCmd.cpp | 51 +-- src/mmSolver/cmd/MMImageCacheCmd.h | 4 +- src/mmSolver/image/ImageCache.cpp | 414 +++++++++++++----------- src/mmSolver/image/ImageCache.h | 91 +++--- 5 files changed, 304 insertions(+), 264 deletions(-) diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 098984aef..7172a500b 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -272,14 +272,14 @@ def get_cache_group_item_names(cache_type, group_name): return value -def cache_erase_groups(cache_type, group_names): +def cache_erase_group_items(cache_type, group_names): assert cache_type in const.CACHE_TYPE_VALUES assert len(group_names) >= 0 value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroups=True) + value = maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroupItems=True) elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroups=True) + value = maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroupItems=True) return value @@ -314,7 +314,7 @@ def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): continue slots_to_remove.append(slot) - return cache_erase_groups(cache_type, slots_to_remove) + return cache_erase_group_items(cache_type, slots_to_remove) def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 79e0165b9..624c973cb 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -66,10 +66,10 @@ #define CPU_ERASE_ITEMS_FLAG "-cei" #define CPU_ERASE_ITEMS_FLAG_LONG "-cpuEraseItems" -#define GPU_ERASE_GROUPS_FLAG "-geg" -#define GPU_ERASE_GROUPS_FLAG_LONG "-gpuEraseGroups" -#define CPU_ERASE_GROUPS_FLAG "-ceg" -#define CPU_ERASE_GROUPS_FLAG_LONG "-cpuEraseGroups" +#define GPU_ERASE_GROUP_ITEMS_FLAG "-geg" +#define GPU_ERASE_GROUP_ITEMS_FLAG_LONG "-gpuEraseGroupItems" +#define CPU_ERASE_GROUP_ITEMS_FLAG "-ceg" +#define CPU_ERASE_GROUP_ITEMS_FLAG_LONG "-cpuEraseGroupItems" #define GPU_GROUP_COUNT_FLAG "-ggc" #define GPU_GROUP_COUNT_FLAG_LONG "-gpuGroupCount" @@ -139,10 +139,10 @@ MSyntax MMImageCacheCmd::newSyntax() { CHECK_MSTATUS( syntax.addFlag(CPU_ERASE_ITEMS_FLAG, CPU_ERASE_ITEMS_FLAG_LONG)); - CHECK_MSTATUS( - syntax.addFlag(GPU_ERASE_GROUPS_FLAG, GPU_ERASE_GROUPS_FLAG_LONG)); - CHECK_MSTATUS( - syntax.addFlag(CPU_ERASE_GROUPS_FLAG, CPU_ERASE_GROUPS_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(GPU_ERASE_GROUP_ITEMS_FLAG, + GPU_ERASE_GROUP_ITEMS_FLAG_LONG)); + CHECK_MSTATUS(syntax.addFlag(CPU_ERASE_GROUP_ITEMS_FLAG, + CPU_ERASE_GROUP_ITEMS_FLAG_LONG)); CHECK_MSTATUS( syntax.addFlag(GPU_GROUP_COUNT_FLAG, GPU_GROUP_COUNT_FLAG_LONG)); @@ -229,10 +229,10 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { const bool has_cpu_erase_items = argData.isFlagSet(CPU_ERASE_ITEMS_FLAG, &status); - const bool has_gpu_erase_groups = - argData.isFlagSet(GPU_ERASE_GROUPS_FLAG, &status); - const bool has_cpu_erase_groups = - argData.isFlagSet(CPU_ERASE_GROUPS_FLAG, &status); + const bool has_gpu_erase_group_items = + argData.isFlagSet(GPU_ERASE_GROUP_ITEMS_FLAG, &status); + const bool has_cpu_erase_group_items = + argData.isFlagSet(CPU_ERASE_GROUP_ITEMS_FLAG, &status); const bool has_gpu_group_count = argData.isFlagSet(GPU_GROUP_COUNT_FLAG, &status); @@ -350,11 +350,11 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { } else if (has_cpu_erase_items) { m_command_flag = ImageCacheFlagMode::kCpuEraseItems; m_output_type = ImageCacheOutputType::kSize; - } else if (has_gpu_erase_groups) { - m_command_flag = ImageCacheFlagMode::kGpuEraseGroups; + } else if (has_gpu_erase_group_items) { + m_command_flag = ImageCacheFlagMode::kGpuEraseGroupItems; m_output_type = ImageCacheOutputType::kSize; - } else if (has_cpu_erase_groups) { - m_command_flag = ImageCacheFlagMode::kCpuEraseGroups; + } else if (has_cpu_erase_group_items) { + m_command_flag = ImageCacheFlagMode::kCpuEraseGroupItems; m_output_type = ImageCacheOutputType::kSize; } else { MMSOLVER_MAYA_ERR( @@ -433,10 +433,11 @@ inline MStatus set_values(image::ImageCache &image_cache, const bool ok = image_cache.cpu_erase_item(*it); out_item_count += static_cast(ok); } - } else if (command_flag == ImageCacheFlagMode::kGpuEraseGroups) { + } else if (command_flag == ImageCacheFlagMode::kGpuEraseGroupItems) { if (group_name.size() == 0) { MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " - << "\"" << GPU_ERASE_GROUPS_FLAG_LONG << "\" " + << "\"" << GPU_ERASE_GROUP_ITEMS_FLAG_LONG + << "\" " << "flag needs a group name, but none given! " "group_name.size()=" << static_cast(group_name.size())); @@ -448,12 +449,14 @@ inline MStatus set_values(image::ImageCache &image_cache, CHECK_MSTATUS_AND_RETURN_IT(status); for (auto it = item_names.begin(); it != item_names.end(); it++) { - out_item_count += image_cache.gpu_erase_group(texture_manager, *it); + out_item_count += + image_cache.gpu_erase_group_items(texture_manager, *it); } - } else if (command_flag == ImageCacheFlagMode::kCpuEraseGroups) { + } else if (command_flag == ImageCacheFlagMode::kCpuEraseGroupItems) { if (group_name.size() == 0) { MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " - << "\"" << CPU_ERASE_GROUPS_FLAG_LONG << "\" " + << "\"" << CPU_ERASE_GROUP_ITEMS_FLAG_LONG + << "\" " << "flag needs a group name, but none given! " "group_name.size()=" << static_cast(group_name.size())); @@ -461,7 +464,7 @@ inline MStatus set_values(image::ImageCache &image_cache, } for (auto it = item_names.begin(); it != item_names.end(); it++) { - out_item_count = image_cache.cpu_erase_group(*it); + out_item_count = image_cache.cpu_erase_group_items(*it); } } else { MMSOLVER_MAYA_ERR( @@ -675,8 +678,8 @@ MStatus MMImageCacheCmd::undoIt() { if (m_is_edit) { if ((m_command_flag == ImageCacheFlagMode::kGpuEraseItems) || (m_command_flag == ImageCacheFlagMode::kCpuEraseItems) || - (m_command_flag == ImageCacheFlagMode::kGpuEraseGroups) || - (m_command_flag == ImageCacheFlagMode::kCpuEraseGroups)) { + (m_command_flag == ImageCacheFlagMode::kGpuEraseGroupItems) || + (m_command_flag == ImageCacheFlagMode::kCpuEraseGroupItems)) { status = MStatus::kFailure; MMSOLVER_MAYA_ERR( "MMImageCacheCmd::undoIt: " diff --git a/src/mmSolver/cmd/MMImageCacheCmd.h b/src/mmSolver/cmd/MMImageCacheCmd.h index c64dcf502..5c1274044 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.h +++ b/src/mmSolver/cmd/MMImageCacheCmd.h @@ -129,8 +129,8 @@ enum class ImageCacheFlagMode : uint8_t { kCpuUsed, kGpuItemCount, kCpuItemCount, - kGpuEraseGroups, - kCpuEraseGroups, + kGpuEraseGroupItems, + kCpuEraseGroupItems, kGpuEraseItems, kCpuEraseItems, kGpuGroupCount, diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index ee0f757e6..f9a031210 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -19,10 +19,6 @@ * */ -// Helpful code: -// https://github.com/david-cattermole/cpp-utilities/blob/master/include/fileSystemUtils.h -// https://github.com/david-cattermole/cpp-utilities/blob/master/include/hashUtils.h - #include "ImageCache.h" // Get M_PI constant @@ -81,8 +77,8 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, return nullptr; } - const std::string key = std::string(resolved_file_path.asChar()); - TextureData texture_data = image_cache.gpu_find_item(key); + const std::string item_key = std::string(resolved_file_path.asChar()); + TextureData texture_data = image_cache.gpu_find_item(item_key); MMSOLVER_MAYA_VRB( "mmsolver::ImageCache: read_texture_image_file: findTexture: " @@ -107,7 +103,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, // Python user code to add the list of valid images into the // cache. - ImagePixelData image_pixel_data = image_cache.cpu_find_item(key); + ImagePixelData image_pixel_data = image_cache.cpu_find_item(item_key); uint32_t width = 0; uint32_t height = 0; @@ -159,10 +155,8 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, height, num_channels, pixel_data_type); const std::string group_name = std::string(file_pattern.asChar()); - const uint64_t group_hash = mmsolver::hash::make_hash(group_name); - - texture_data = image_cache.gpu_insert_item(texture_manager, group_name, key, - gpu_image_pixel_data); + texture_data = image_cache.gpu_insert_item(texture_manager, group_name, + item_key, gpu_image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "gpu_inserted=" << texture_data.texture()); @@ -183,7 +177,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, pixel_data_byte_count); const bool cpu_inserted = - image_cache.cpu_insert_item(group_name, key, image_pixel_data); + image_cache.cpu_insert_item(group_name, item_key, image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " << "cpu_inserted=" << cpu_inserted); @@ -305,10 +299,9 @@ void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_group_names: "); out_group_names.clear(); - out_group_names.reserve(m_gpu_group_names_set.size()); - for (auto it = m_gpu_group_names_set.begin(); - it != m_gpu_group_names_set.end(); it++) { - GPUCacheString value = *it; + out_group_names.reserve(m_gpu_group_map.size()); + for (auto it = m_gpu_group_map.begin(); it != m_gpu_group_map.end(); it++) { + const GPUCacheString value = it->first; out_group_names.push_back(value); } // The set is unordered, so lets make the output of this function @@ -323,10 +316,9 @@ void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_group_names: "); out_group_names.clear(); - out_group_names.reserve(m_cpu_group_names_set.size()); - for (auto it = m_cpu_group_names_set.begin(); - it != m_cpu_group_names_set.end(); it++) { - CPUCacheString value = *it; + out_group_names.reserve(m_cpu_group_map.size()); + for (auto it = m_cpu_group_map.begin(); it != m_cpu_group_map.end(); it++) { + const CPUCacheString value = it->first; out_group_names.push_back(value); } // The set is unordered, so lets make the output of this function @@ -338,12 +330,7 @@ void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { size_t ImageCache::gpu_group_item_count( const GPUCacheString &group_name) const { - const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - return ImageCache::gpu_group_item_count(group_key); -} - -size_t ImageCache::gpu_group_item_count(const GPUGroupKey group_key) const { - const auto group_search = m_gpu_group_map.find(group_key); + const auto group_search = m_gpu_group_map.find(group_name); const bool group_found = group_search != m_gpu_group_map.end(); if (!group_found) { return 0; @@ -355,12 +342,7 @@ size_t ImageCache::gpu_group_item_count(const GPUGroupKey group_key) const { size_t ImageCache::cpu_group_item_count( const CPUCacheString &group_name) const { - const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - return ImageCache::cpu_group_item_count(group_key); -} - -size_t ImageCache::cpu_group_item_count(const CPUGroupKey group_key) const { - const auto group_search = m_cpu_group_map.find(group_key); + const auto group_search = m_cpu_group_map.find(group_name); const bool group_found = group_search != m_cpu_group_map.end(); if (!group_found) { return 0; @@ -373,14 +355,8 @@ size_t ImageCache::cpu_group_item_count(const CPUGroupKey group_key) const { bool ImageCache::gpu_group_item_names( const GPUCacheString &group_name, GPUVectorString &out_group_item_names) const { - const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - return ImageCache::gpu_group_item_names(group_key, out_group_item_names); -} - -bool ImageCache::gpu_group_item_names( - const GPUGroupKey group_key, GPUVectorString &out_group_item_names) const { out_group_item_names.clear(); - const auto group_search = m_gpu_group_map.find(group_key); + const auto group_search = m_gpu_group_map.find(group_name); const bool group_found = group_search != m_gpu_group_map.end(); if (!group_found) { return false; @@ -402,14 +378,8 @@ bool ImageCache::gpu_group_item_names( bool ImageCache::cpu_group_item_names( const CPUCacheString &group_name, CPUVectorString &out_group_item_names) const { - const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); - return ImageCache::cpu_group_item_names(group_key, out_group_item_names); -} - -bool ImageCache::cpu_group_item_names( - const CPUGroupKey group_key, CPUVectorString &out_group_item_names) const { out_group_item_names.clear(); - const auto group_search = m_cpu_group_map.find(group_key); + const auto group_search = m_cpu_group_map.find(group_name); const bool group_found = group_search != m_cpu_group_map.end(); if (!group_found) { return false; @@ -428,64 +398,40 @@ bool ImageCache::cpu_group_item_names( return true; } -bool ImageCache::gpu_insert_group(const GPUGroupKey group_key, - const GPUCacheString &group_name, +bool ImageCache::gpu_insert_group(const GPUCacheString &group_name, const GPUCacheString &file_path) { - const GPUGroupMapIt group_search = m_gpu_group_map.find(group_key); + const GPUGroupMapIt group_search = m_gpu_group_map.find(group_name); const bool group_found = group_search != m_gpu_group_map.end(); if (!group_found) { GPUGroupSet values_set; values_set.insert(file_path); const auto group_map_pair = - m_gpu_group_map.insert(std::make_pair(group_key, values_set)); + m_gpu_group_map.insert(std::make_pair(group_name, values_set)); const bool group_map_ok = group_map_pair.second; assert(group_map_ok == true); - - // First time inserting into the set, it won't exist yet. - const auto set_pair = m_gpu_group_names_set.insert(group_name); - const bool set_ok = set_pair.second; - assert(set_ok == true); } else { GPUGroupSet &values_set = group_search->second; - const GPUGroupSetIt value_search = values_set.find(group_name); - const bool value_found = value_search != values_set.end(); - if (!value_found) { - const auto values_set_pair = values_set.insert(file_path); - const bool values_set_ok = values_set_pair.second; - assert(values_set_ok == true); - } + values_set.insert(file_path); } return true; } -bool ImageCache::cpu_insert_group(const CPUGroupKey group_key, - const CPUCacheString &group_name, +bool ImageCache::cpu_insert_group(const CPUCacheString &group_name, const CPUCacheString &file_path) { - const CPUGroupMapIt group_search = m_cpu_group_map.find(group_key); + const CPUGroupMapIt group_search = m_cpu_group_map.find(group_name); const bool group_found = group_search != m_cpu_group_map.end(); if (!group_found) { CPUGroupSet values_set; values_set.insert(file_path); const auto group_map_pair = - m_cpu_group_map.insert(std::make_pair(group_key, values_set)); + m_cpu_group_map.insert(std::make_pair(group_name, values_set)); const bool group_map_ok = group_map_pair.second; assert(group_map_ok == true); - - // First time inserting into the set, it won't exist yet. - const auto set_pair = m_cpu_group_names_set.insert(group_name); - const bool set_ok = set_pair.second; - assert(set_ok == true); } else { CPUGroupSet &values_set = group_search->second; - const CPUGroupSetIt value_search = values_set.find(group_name); - const bool value_found = value_search != values_set.end(); - if (!value_found) { - const auto values_set_pair = values_set.insert(file_path); - const bool values_set_ok = values_set_pair.second; - assert(values_set_ok == true); - } + values_set.insert(file_path); } return true; } @@ -520,17 +466,18 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( assert(image_pixel_data.is_valid()); const bool verbose = false; - const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_insert_item: " - << "key=" << key << " group_name=" << group_name.c_str() + << "item_key=" << item_key + << " group_name=" << group_name.c_str() << " file_path=" << file_path.c_str()); GPUCacheValue texture_data = GPUCacheValue(); - const ImageCache::GPUMapIt search = m_gpu_item_map.find(key); - const bool found = search != m_gpu_item_map.end(); - if (!found) { + const ImageCache::GPUMapIt item_search = m_gpu_item_map.find(item_key); + const bool item_found = item_search != m_gpu_item_map.end(); + if (!item_found) { // If we are at capacity, make room for new entry. const size_t image_data_size = image_pixel_data.byte_count(); @@ -562,26 +509,26 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( m_gpu_used_bytes += texture_data.byte_count(); assert(m_gpu_used_bytes <= m_gpu_capacity_bytes); - // Make 'key' the most-recently-used key, because when we + // Make 'item_key' the most-recently-used item key, because when we // insert an item into the cache, it's used most recently. - ImageCache::GPUKeyListIt key_iterator = - m_gpu_key_list.insert(m_gpu_key_list.end(), key); + ImageCache::GPUKeyListIt item_key_iterator = + m_gpu_key_list.insert(m_gpu_key_list.end(), item_key); // Create the key-value entry, linked to the usage record. - const auto item_map_pair = m_gpu_item_map.insert( - std::make_pair(key, std::make_pair(key_iterator, texture_data))); + const auto item_map_pair = m_gpu_item_map.insert(std::make_pair( + item_key, std::make_pair(item_key_iterator, texture_data))); const auto inserted_key_iterator = item_map_pair.first; const bool item_ok = item_map_pair.second; assert(ok == true); const bool group_ok = - ImageCache::gpu_insert_group(group_key, group_name, file_path); + ImageCache::gpu_insert_group(group_name, file_path); assert(group_ok == true); } else { - ImageCache::GPUKeyListIt iterator = search->second.first; - texture_data = search->second.second; + ImageCache::GPUKeyListIt item_iterator = item_search->second.first; + texture_data = item_search->second.second; if (!texture_data.is_valid()) { MMSOLVER_MAYA_ERR( "mmsolver::ImageCache: gpu_insert_item: " @@ -589,7 +536,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( return ImageCache::GPUCacheValue(); } - move_iterator_to_back_of_key_list(m_gpu_key_list, iterator); + move_iterator_to_back_of_key_list(m_gpu_key_list, item_iterator); update_texture(texture_data.texture(), image_pixel_data); } @@ -602,17 +549,18 @@ bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, const CPUCacheValue &image_pixel_data) { const bool verbose = false; - const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_insert_item: " - << "key=" << key << " group_name=" << group_name.c_str() + << "item_key=" << item_key + << " group_name=" << group_name.c_str() << " file_path=" << file_path.c_str()); - const CPUMapIt item_search = m_cpu_item_map.find(key); + const CPUMapIt item_search = m_cpu_item_map.find(item_key); const bool item_found = item_search != m_cpu_item_map.end(); if (item_found) { - ImageCache::cpu_erase_item(key); + ImageCache::cpu_erase_item(item_key); } // If we are at capacity, make room for new entry. @@ -630,18 +578,17 @@ bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, // Because we are inserting into the cache, the 'key' is the // most-recently-used item. - CPUKeyListIt key_iterator = - m_cpu_key_list.insert(m_cpu_key_list.end(), key); + CPUKeyListIt item_key_iterator = + m_cpu_key_list.insert(m_cpu_key_list.end(), item_key); - const auto item_map_pair = m_cpu_item_map.insert( - std::make_pair(key, std::make_pair(key_iterator, image_pixel_data))); + const auto item_map_pair = m_cpu_item_map.insert(std::make_pair( + item_key, std::make_pair(item_key_iterator, image_pixel_data))); const auto inserted_key_iterator = item_map_pair.first; const bool item_map_ok = item_map_pair.second; assert(item_map_ok == true); - const bool group_ok = - ImageCache::cpu_insert_group(group_key, group_name, file_path); + const bool group_ok = ImageCache::cpu_insert_group(group_name, file_path); assert(group_ok == true); return true; @@ -651,26 +598,27 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheString &file_path) { const bool verbose = false; - const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " - << "key=" << key << "file_path=\"" << file_path.c_str() - << "\""); - return ImageCache::gpu_find_item(key); + << "item_key=" << item_key << "file_path=\"" + << file_path.c_str() << "\""); + return ImageCache::gpu_find_item(item_key); } -ImageCache::GPUCacheValue ImageCache::gpu_find_item(const GPUCacheKey key) { +ImageCache::GPUCacheValue ImageCache::gpu_find_item( + const GPUCacheKey item_key) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " - << "key=" << key); + << "item_key=" << item_key); - const GPUMapIt search = m_gpu_item_map.find(key); - const bool found = search != m_gpu_item_map.end(); - if (found) { - GPUKeyListIt iterator = search->second.first; - GPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_gpu_key_list, iterator); - return value; + const GPUMapIt item_search = m_gpu_item_map.find(item_key); + const bool item_found = item_search != m_gpu_item_map.end(); + if (item_found) { + GPUKeyListIt item_iterator = item_search->second.first; + GPUCacheValue item_value = item_search->second.second; + move_iterator_to_back_of_key_list(m_gpu_key_list, item_iterator); + return item_value; } return GPUCacheValue(); } @@ -679,26 +627,27 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheString &file_path) { const bool verbose = false; - const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " - << "key=" << key << "file_path=\"" << file_path.c_str() - << "\""); - return ImageCache::cpu_find_item(key); + << "item_key=" << item_key << "file_path=\"" + << file_path.c_str() << "\""); + return ImageCache::cpu_find_item(item_key); } -ImageCache::CPUCacheValue ImageCache::cpu_find_item(const CPUCacheKey key) { +ImageCache::CPUCacheValue ImageCache::cpu_find_item( + const CPUCacheKey item_key) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " - << "key=" << key); + << "item_key=" << item_key); - const CPUMapIt search = m_cpu_item_map.find(key); - const bool found = search != m_cpu_item_map.end(); - if (found) { - CPUKeyListIt iterator = search->second.first; - CPUCacheValue value = search->second.second; - move_iterator_to_back_of_key_list(m_cpu_key_list, iterator); - return value; + const CPUMapIt item_search = m_cpu_item_map.find(item_key); + const bool item_found = item_search != m_cpu_item_map.end(); + if (item_found) { + CPUKeyListIt item_iterator = item_search->second.first; + CPUCacheValue item_value = item_search->second.second; + move_iterator_to_back_of_key_list(m_cpu_key_list, item_iterator); + return item_value; } return CPUCacheValue(); } @@ -720,18 +669,18 @@ CacheEvictionResult ImageCache::gpu_evict_one_item( return CacheEvictionResult::kNotNeeded; } - const GPUCacheKey lru_key = m_gpu_key_list.front(); - const GPUMapIt lru_key_iterator = m_gpu_item_map.find(lru_key); - assert(lru_key_iterator != m_gpu_item_map.end()); + const GPUCacheKey item_key = m_gpu_key_list.front(); + const GPUMapIt item_key_iterator = m_gpu_item_map.find(item_key); + assert(item_key_iterator != m_gpu_item_map.end()); - GPUCacheValue texture_data = lru_key_iterator->second.second; + GPUCacheValue texture_data = item_key_iterator->second.second; m_gpu_used_bytes -= texture_data.byte_count(); texture_data.deallocate_texture(texture_manager); - m_gpu_item_map.erase(lru_key_iterator); + m_gpu_item_map.erase(item_key_iterator); m_gpu_key_list.pop_front(); - // TODO: Remove from group maps. + gpu_remove_item_from_group(item_key); MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::gpu_evict_one_item: " @@ -755,18 +704,18 @@ CacheEvictionResult ImageCache::cpu_evict_one_item() { return CacheEvictionResult::kNotNeeded; } - const CPUCacheKey lru_key = m_cpu_key_list.front(); - const CPUMapIt lru_key_iterator = m_cpu_item_map.find(lru_key); - assert(lru_key_iterator != m_cpu_item_map.end()); + const CPUCacheKey item_key = m_cpu_key_list.front(); + const CPUMapIt item_key_iterator = m_cpu_item_map.find(item_key); + assert(item_key_iterator != m_cpu_item_map.end()); - CPUCacheValue image_pixel_data = lru_key_iterator->second.second; + CPUCacheValue image_pixel_data = item_key_iterator->second.second; m_cpu_used_bytes -= image_pixel_data.byte_count(); image_pixel_data.deallocate_pixels(); - m_cpu_item_map.erase(lru_key_iterator); + m_cpu_item_map.erase(item_key_iterator); m_cpu_key_list.pop_front(); - // TODO: Remove from group maps. + cpu_remove_item_from_group(item_key); MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::cpu_evict_one_item: " @@ -852,88 +801,181 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( return result; } +size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_remove_item_from_group: " + "item_key=" + << item_key); + + size_t count = 0; + std::vector group_keys; + for (auto it = m_gpu_group_map.begin(); it != m_gpu_group_map.end(); ++it) { + const GPUCacheString group_name = it->first; + GPUGroupSet &values_set = it->second; + + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + + // NOTE: This is a O(n) linear operation. + for (auto it2 = values_set.begin(); it2 != values_set.end(); ++it2) { + const GPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); + + if (item_key == item_value_hash) { + group_keys.push_back(group_key); + it2 = values_set.erase(it2); + count += 1; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_erase_item: " + << "group_key=" << group_key << " item_key=" << item_key + << " item_value_hash=" << item_value_hash << " - got it"); + } else { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " + << "item_key=" << item_key + << " item_value_hash=" << item_value_hash); + } + } + + if (values_set.size() == 0) { + it = m_gpu_group_map.erase(it); + } + } + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " + << "count=" << count); + + return count; +} + +size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { + const bool verbose = false; + + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_remove_item_from_group: " + "item_key=" + << item_key); + + size_t count = 0; + std::vector group_keys; + for (auto it = m_cpu_group_map.begin(); it != m_cpu_group_map.end(); ++it) { + const CPUCacheString group_name = it->first; + CPUGroupSet &values_set = it->second; + + const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + + // NOTE: This is a O(n) linear operation. + for (auto it2 = values_set.begin(); it2 != values_set.end(); ++it2) { + const CPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); + + if (item_key == item_value_hash) { + group_keys.push_back(group_key); + it2 = values_set.erase(it2); + count += 1; + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_erase_item: " + << "group_key=" << group_key << " item_key=" << item_key + << " item_value_hash=" << item_value_hash << " - got it"); + } else { + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " + << "item_key=" << item_key + << " item_value_hash=" << item_value_hash); + } + } + + if (values_set.size() == 0) { + it = m_cpu_group_map.erase(it); + } + } + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " + << "count=" << count); + + return count; +} + bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheString &file_path) { const bool verbose = false; - const GPUGroupKey key = mmsolver::hash::make_hash(file_path); + const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " - << "key=" << key << "file_path=\"" << file_path.c_str() - << "\""); - return ImageCache::gpu_erase_item(texture_manager, key); + << "item_key=" << item_key << "file_path=\"" + << file_path.c_str() << "\""); + return ImageCache::gpu_erase_item(texture_manager, item_key); } bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey key) { + const GPUCacheKey item_key) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " - << "key=" << key); - - const GPUMapIt search = m_gpu_item_map.find(key); - const bool found = search != m_gpu_item_map.end(); - if (found) { - GPUCacheValue texture_data = search->second.second; + << "item_key=" << item_key); + const GPUMapIt item_search = m_gpu_item_map.find(item_key); + const bool item_found = item_search != m_gpu_item_map.end(); + if (item_found) { + GPUCacheValue texture_data = item_search->second.second; m_gpu_used_bytes -= texture_data.byte_count(); texture_data.deallocate_texture(texture_manager); - m_gpu_item_map.erase(search); + m_gpu_item_map.erase(item_search); // NOTE: This is a O(n) linear operation, and can be very // slow since the list items is spread out in memory. - m_gpu_key_list.remove(key); + m_gpu_key_list.remove(item_key); - // TODO: Remove from group maps. + // NOTE: This is a O(n) linear operation. + gpu_remove_item_from_group(item_key); } - return found; + return item_found; } bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { const bool verbose = false; - const CPUGroupKey key = mmsolver::hash::make_hash(file_path); + const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " - << "key=" << key << "file_path=\"" << file_path.c_str() - << "\""); - return ImageCache::cpu_erase_item(key); + << "item_key=" << item_key << "file_path=\"" + << file_path.c_str() << "\""); + return ImageCache::cpu_erase_item(item_key); } -bool ImageCache::cpu_erase_item(const CPUCacheKey key) { +bool ImageCache::cpu_erase_item(const CPUCacheKey item_key) { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " - << "key=" << key); - - const CPUMapIt search = m_cpu_item_map.find(key); - const bool found = search != m_cpu_item_map.end(); - if (found) { - CPUCacheValue image_pixel_data = search->second.second; + << "item_key=" << item_key); + const CPUMapIt item_search = m_cpu_item_map.find(item_key); + const bool item_found = item_search != m_cpu_item_map.end(); + if (item_found) { + CPUCacheValue image_pixel_data = item_search->second.second; m_cpu_used_bytes -= image_pixel_data.byte_count(); image_pixel_data.deallocate_pixels(); - m_cpu_item_map.erase(search); + m_cpu_item_map.erase(item_search); // NOTE: This is a O(n) linear operation, and can be very // slow since the list items is spread out in memory. - m_cpu_key_list.remove(key); + m_cpu_key_list.remove(item_key); - // TODO: Remove from group maps. + // NOTE: This is a O(n) linear operation. + cpu_remove_item_from_group(item_key); } - return found; + return item_found; } -size_t ImageCache::gpu_erase_group(MHWRender::MTextureManager *texture_manager, - const GPUCacheString &group_name) { +size_t ImageCache::gpu_erase_group_items( + MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name) { const bool verbose = false; - const GPUGroupKey key = mmsolver::hash::make_hash(group_name); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_group: " - << "key=" << key << "group_name=\"" << group_name.c_str() - << "\""); + if (verbose) { + const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_group_items: " + << "group_key=" << group_key << "group_name=\"" + << group_name.c_str() << "\""); + } - const GPUGroupMapIt group_search = m_gpu_group_map.find(key); + const GPUGroupMapIt group_search = m_gpu_group_map.find(group_name); const bool group_found = group_search != m_gpu_group_map.end(); if (!group_found) { MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache: gpu_erase_group: " + "mmsolver::ImageCache: gpu_erase_group_items: " "Group name \"" << group_name << "\" not found!"); return 0; @@ -950,19 +992,21 @@ size_t ImageCache::gpu_erase_group(MHWRender::MTextureManager *texture_manager, return count; } -size_t ImageCache::cpu_erase_group(const CPUCacheString &group_name) { +size_t ImageCache::cpu_erase_group_items(const CPUCacheString &group_name) { const bool verbose = false; - const CPUGroupKey key = mmsolver::hash::make_hash(group_name); - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_group: " - << "key=" << key << "group_name=\"" << group_name.c_str() - << "\""); + if (verbose) { + const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_group_items: " + << "group_key=" << group_key << "group_name=\"" + << group_name.c_str() << "\""); + } - const CPUGroupMapIt group_search = m_cpu_group_map.find(key); + const CPUGroupMapIt group_search = m_cpu_group_map.find(group_name); const bool group_found = group_search != m_cpu_group_map.end(); if (!group_found) { MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache: cpu_erase_group: " + "mmsolver::ImageCache: cpu_erase_group_items: " "Group name \"" << group_name << "\" not found!"); return 0; diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 70fb899ce..107caf208 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -121,8 +121,8 @@ struct ImageCache { using GPUGroupSetIt = GPUGroupSet::iterator; using CPUGroupSetIt = CPUGroupSet::iterator; - using GPUGroupMap = std::unordered_map; - using CPUGroupMap = std::unordered_map; + using GPUGroupMap = std::unordered_map; + using CPUGroupMap = std::unordered_map; using GPUGroupMapIt = GPUGroupMap::iterator; using CPUGroupMapIt = CPUGroupMap::iterator; @@ -150,6 +150,25 @@ struct ImageCache { key_list.splice(key_list.end(), key_list, key_list_iterator); } + // Evict cached items until a new memory chunk can fit in. + CacheEvictionResult gpu_evict_enough_for_new_item( + MHWRender::MTextureManager *texture_manager, + const size_t new_memory_chunk_size); + CacheEvictionResult cpu_evict_enough_for_new_item( + const size_t new_memory_chunk_size); + + // Add group name into cache, associated with the file path. + // + // This is only used internally as a helper method. + bool gpu_insert_group(const GPUCacheString &group_name, + const GPUCacheString &file_path); + bool cpu_insert_group(const CPUCacheString &group_name, + const CPUCacheString &file_path); + + // Removes the given item key from the groups. + size_t gpu_remove_item_from_group(const GPUCacheKey item_key); + size_t cpu_remove_item_from_group(const CPUCacheKey item_key); + public: // Get the capacity of the cache. size_t get_gpu_capacity_bytes() const { @@ -206,16 +225,16 @@ struct ImageCache { size_t get_gpu_group_count() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_group_count: " - << "m_gpu_group_names_set.size()=" - << m_gpu_group_names_set.size()); - return m_gpu_group_names_set.size(); + << "m_gpu_group_map.size()=" + << m_gpu_group_map.size()); + return m_gpu_group_map.size(); } size_t get_cpu_group_count() const { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_group_count: " - << "m_cpu_group_names_set.size()=" - << m_cpu_group_names_set.size()); - return m_cpu_group_names_set.size(); + << "m_cpu_group_map.size()=" + << m_cpu_group_map.size()); + return m_cpu_group_map.size(); } // Get sorted vector of group names. @@ -224,19 +243,13 @@ struct ImageCache { // Get the number of items in the given group name. size_t gpu_group_item_count(const GPUCacheString &group_name) const; - size_t gpu_group_item_count(const GPUGroupKey group_key) const; size_t cpu_group_item_count(const CPUCacheString &group_name) const; - size_t cpu_group_item_count(const GPUGroupKey group_key) const; // Item names in a group. bool gpu_group_item_names(const GPUCacheString &group_name, GPUVectorString &out_group_item_names) const; - bool gpu_group_item_names(const GPUGroupKey group_key, - GPUVectorString &out_group_item_names) const; bool cpu_group_item_names(const CPUCacheString &group_name, CPUVectorString &out_group_item_names) const; - bool cpu_group_item_names(const CPUGroupKey group_key, - CPUVectorString &out_group_item_names) const; // TODO: Set/Get Disk cache location. Used to find disk-cached // files. This should be a directory on a very fast disk. @@ -258,8 +271,8 @@ struct ImageCache { // Insert pixels into CPU cache. // - // If the key is already in the image cache, the previous value is - // erased. + // If the file path is already in the image cache, the previous + // value is erased. // // Returns true/false, if the the data was inserted or not. bool cpu_insert_item(const CPUCacheString &group_name, @@ -274,16 +287,16 @@ struct ImageCache { const GPUCacheString &file_path, const CPUCacheValue &image_pixel_data); - // Find the key in the GPU/CPU cache. + // Find the item key in the GPU/CPU cache. // - // Returns the GPUCacheValue at the key, or nullptr. + // Returns the GPUCacheValue at the item key, or nullptr. // - // Returns the CPUCacheValue at the key, or constructs a default + // Returns the CPUCacheValue at the item key, or constructs a default // value and returns it. GPUCacheValue gpu_find_item(const GPUCacheString &file_path); - GPUCacheValue gpu_find_item(const GPUCacheKey key); + GPUCacheValue gpu_find_item(const GPUCacheKey item_key); CPUCacheValue cpu_find_item(const CPUCacheString &file_path); - CPUCacheValue cpu_find_item(const CPUCacheKey key); + CPUCacheValue cpu_find_item(const CPUCacheKey item_key); // TODO: Add a 'gpu/cpu_prefetch()' method, used to add the images // into a prefetching queue. @@ -305,23 +318,23 @@ struct ImageCache { MHWRender::MTextureManager *texture_manager); CacheEvictionResult cpu_evict_one_item(); - // Remove the key from the image GPU/CPU cache. + // Remove the file path from the image GPU/CPU cache. // // NOTE: Due to the way the LRU cache works, this can be quite - // slow to to remove a specific key from the cache. + // slow to to remove a specific file path from the cache. // - // Returns true/false, if the key was removed or not. + // Returns true/false, if the item was removed or not. bool gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheString &file_path); bool gpu_erase_item(MHWRender::MTextureManager *texture_manager, - const GPUCacheKey key); + const GPUCacheKey item_key); bool cpu_erase_item(const CPUCacheString &file_path); - bool cpu_erase_item(const CPUCacheKey key); + bool cpu_erase_item(const CPUCacheKey item_key); // Erase all items in the given group_name. - size_t gpu_erase_group(MHWRender::MTextureManager *texture_manager, - const GPUCacheString &group_name); - size_t cpu_erase_group(const GPUCacheString &group_name); + size_t gpu_erase_group_items(MHWRender::MTextureManager *texture_manager, + const GPUCacheString &group_name); + size_t cpu_erase_group_items(const GPUCacheString &group_name); // C++ 11; deleting the methods we don't want to ensure they can never be // used. @@ -334,22 +347,6 @@ struct ImageCache { void operator=(ImageCache const &) = delete; private: - CacheEvictionResult gpu_evict_enough_for_new_item( - MHWRender::MTextureManager *texture_manager, - const size_t new_memory_chunk_size); - CacheEvictionResult cpu_evict_enough_for_new_item( - const size_t new_memory_chunk_size); - - // Add group name into cache, associated with the file path. - // - // This is only used internally as a helper method. - bool gpu_insert_group(const GPUGroupKey group_key, - const GPUCacheString &group_name, - const GPUCacheString &file_path); - bool cpu_insert_group(const CPUGroupKey group_key, - const CPUCacheString &group_name, - const CPUCacheString &file_path); - // Amount of memory capacity. size_t m_gpu_capacity_bytes; size_t m_cpu_capacity_bytes; @@ -397,10 +394,6 @@ struct ImageCache { // image sequence. GPUGroupMap m_gpu_group_map; CPUGroupMap m_cpu_group_map; - - // All of the group names in a set. - GPUGroupSet m_gpu_group_names_set; - CPUGroupSet m_cpu_group_names_set; }; MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, From 6c41bb52a98a00e2f5c7f27358a81e19da3a4169 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 24 Jun 2024 01:15:09 +1000 Subject: [PATCH 122/295] Add Image Cache Preferences --- docs/source/mmSolver.utils.rst | 7 + python/CMakeLists.txt | 6 +- python/mmSolver/tools/centertwodee/lib.py | 48 +- python/mmSolver/tools/imagecache/__init__.py | 2 +- python/mmSolver/tools/imagecache/constant.py | 2 - python/mmSolver/tools/imagecache/lib.py | 89 ++- .../tools/imagecache/ui/imagecache_layout.py | 176 ----- .../tools/imagecacheprefs/__init__.py | 20 + .../tools/imagecacheprefs/constant.py | 33 + .../{imagecache => imagecacheprefs}/tool.py | 6 +- .../ui/__init__.py | 0 .../ui/imagecacheprefs_layout.py | 661 ++++++++++++++++++ .../ui/imagecacheprefs_layout.ui} | 227 +++++- .../ui/imagecacheprefs_window.py} | 45 +- python/mmSolver/utils/constant.py | 6 + python/mmSolver/utils/math.py | 69 ++ share/config/functions.json | 12 +- share/config/menu.json | 2 +- share/config/shelf.json | 2 +- share/config/shelf_minimal.json | 2 +- 20 files changed, 1129 insertions(+), 286 deletions(-) delete mode 100644 python/mmSolver/tools/imagecache/ui/imagecache_layout.py create mode 100644 python/mmSolver/tools/imagecacheprefs/__init__.py create mode 100644 python/mmSolver/tools/imagecacheprefs/constant.py rename python/mmSolver/tools/{imagecache => imagecacheprefs}/tool.py (86%) rename python/mmSolver/tools/{imagecache => imagecacheprefs}/ui/__init__.py (100%) create mode 100644 python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py rename python/mmSolver/tools/{imagecache/ui/imagecache_layout.ui => imagecacheprefs/ui/imagecacheprefs_layout.ui} (69%) rename python/mmSolver/tools/{imagecache/ui/imagecache_window.py => imagecacheprefs/ui/imagecacheprefs_window.py} (73%) create mode 100644 python/mmSolver/utils/math.py diff --git a/docs/source/mmSolver.utils.rst b/docs/source/mmSolver.utils.rst index bc458801c..6360e0248 100644 --- a/docs/source/mmSolver.utils.rst +++ b/docs/source/mmSolver.utils.rst @@ -145,6 +145,13 @@ Node Affects :members: :undoc-members: +Math +++++ + +.. automodule:: mmSolver.utils.math + :members: + :undoc-members: + Python Compatibility ++++++++++++++++++++ diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 545e60954..60189c64e 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -194,9 +194,9 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/surfacecluster/ui/ui_surfacecluster_layout.py ) - compile_qt_ui_to_python_file("imagecache" - ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/imagecache/ui/imagecache_layout.ui - ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/imagecache/ui/ui_imagecache_layout.py + compile_qt_ui_to_python_file("imagecacheprefs" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/imagecacheprefs/ui/ui_imagecacheprefs_layout.py ) # Install generated Python UI files diff --git a/python/mmSolver/tools/centertwodee/lib.py b/python/mmSolver/tools/centertwodee/lib.py index fa9987cdc..460fbee61 100644 --- a/python/mmSolver/tools/centertwodee/lib.py +++ b/python/mmSolver/tools/centertwodee/lib.py @@ -27,6 +27,7 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.math as math_utils import mmSolver.utils.viewport as viewport_utils import mmSolver.utils.reproject as reproject_utils import mmSolver.tools.centertwodee.constant as const @@ -34,51 +35,6 @@ LOG = mmSolver.logger.get_logger() -def _lerp(min_value, max_value, mix): - """ - Return 'min_value' to 'max_value' linearly, for a 'mix' value - between 0.0 and 1.0. - - :type min_value: float - :type max_value: float - :type mix: float - - :rtype: float - """ - return (1.0 - mix) * min_value + mix * max_value - - -def _inverse_lerp(min_value, max_value, mix): - """ - Return 0.0 to 1.0 linearly, for a 'mix' value between 'min_value' - and 'max_value'. - - :type min_value: float - :type max_value: float - :type mix: float - - :rtype: float - """ - return (mix - min_value) / (max_value - min_value) - - -def _remap(old_min, old_max, new_min, new_max, mix): - """ - Remap from the 'old_*' values to 'new_*' values, using a 'mix' - value between 0.0 and 1.0; - - :type old_min: float - :type old_max: float - :type new_min: float - :type new_max: float - :type mix: float - - :rtype: float - """ - blend = _inverse_lerp(old_min, old_max, mix) - return _lerp(new_min, new_max, blend) - - def set_offset_range(source='slider'): """ Setting in/out ranges for pan offset values. @@ -168,7 +124,7 @@ def process_value(input_value=None, source=None, zoom=None): zoom = True input_range_start, input_range_end, output_range_start, output_range_end = new_range - output = _remap( + output = math_utils.remap( input_range_start, input_range_end, float(output_range_start), diff --git a/python/mmSolver/tools/imagecache/__init__.py b/python/mmSolver/tools/imagecache/__init__.py index ac8160ee7..515b20e61 100644 --- a/python/mmSolver/tools/imagecache/__init__.py +++ b/python/mmSolver/tools/imagecache/__init__.py @@ -16,5 +16,5 @@ # along with mmSolver. If not, see . # """ -Image Cache tool - Adjust the mmSolver Image Cache +Image Cache. """ diff --git a/python/mmSolver/tools/imagecache/constant.py b/python/mmSolver/tools/imagecache/constant.py index cf5cc3954..5622ccb47 100644 --- a/python/mmSolver/tools/imagecache/constant.py +++ b/python/mmSolver/tools/imagecache/constant.py @@ -19,8 +19,6 @@ Image Cache constants. """ -WINDOW_TITLE = 'mmSolver Image Cache' - CACHE_TYPE_GPU = 'gpu' CACHE_TYPE_CPU = 'cpu' CACHE_TYPE_VALUES = [ diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 7172a500b..679d9e65e 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -188,6 +188,46 @@ def get_cpu_cache_item_count(): return int(maya.cmds.mmImageCache(query=True, cpuItemCount=True)) +def get_gpu_cache_group_names(): + return maya.cmds.mmImageCache(query=True, gpuGroupNames=True) + + +def get_cpu_cache_group_names(): + return maya.cmds.mmImageCache(query=True, cpuGroupNames=True) + + +def get_gpu_cache_group_count(): + return int(maya.cmds.mmImageCache(query=True, gpuGroupCount=True)) + + +def get_cpu_cache_group_count(): + return int(maya.cmds.mmImageCache(query=True, cpuGroupCount=True)) + + +def get_gpu_cache_group_item_count(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return int(maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True)) + + +def get_cpu_cache_group_item_count(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return int(maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True)) + + +def get_gpu_cache_group_item_names(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) + + +def get_cpu_cache_group_item_names(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) + + def get_gpu_cache_used_bytes(): return int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) @@ -204,6 +244,16 @@ def get_cpu_cache_capacity_bytes(): return int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) +def set_gpu_cache_capacity_bytes(size_bytes): + assert isinstance(size_bytes, int) + return maya.cmds.mmImageCache(edit=True, gpuCapacity=size_bytes) + + +def set_cpu_cache_capacity_bytes(size_bytes): + assert isinstance(size_bytes, int) + return maya.cmds.mmImageCache(edit=True, cpuCapacity=size_bytes) + + def get_gpu_memory_total_bytes(): return int(maya.cmds.mmMemoryGPU(query=True, total=True)) @@ -234,9 +284,9 @@ def get_cache_group_names(cache_type): assert cache_type in const.CACHE_TYPE_VALUES value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(query=True, gpuGroupNames=True) + value = get_gpu_cache_group_names() elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(query=True, cpuGroupNames=True) + value = get_cpu_cache_group_names() return value @@ -244,35 +294,49 @@ def get_cache_group_count(cache_type): assert cache_type in const.CACHE_TYPE_VALUES value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(query=True, gpuGroupCount=True) + value = get_gpu_cache_group_count() elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(query=True, cpuGroupCount=True) + value = get_cpu_cache_group_count() return value def get_cache_group_item_count(cache_type, group_name): assert cache_type in const.CACHE_TYPE_VALUES assert isinstance(group_name, str) + assert len(group_name) > 0 value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True) + value = get_gpu_cache_group_item_count(group_name) elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True) + value = get_cpu_cache_group_item_count(group_name) return value def get_cache_group_item_names(cache_type, group_name): assert cache_type in const.CACHE_TYPE_VALUES assert isinstance(group_name, str) + assert len(group_name) > 0 value = None if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) + value = get_gpu_cache_group_item_names(group_name) elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) + value = get_cpu_cache_group_item_names(group_name) return value -def cache_erase_group_items(cache_type, group_names): +def cache_erase_group_items(cache_type, group_name): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(group_name, str) + assert len(group_name) > 0 + value = None + if cache_type == const.CACHE_TYPE_GPU: + value = maya.cmds.mmImageCache([group_name], edit=True, gpuEraseGroupItems=True) + elif cache_type == const.CACHE_TYPE_CPU: + value = maya.cmds.mmImageCache([group_name], edit=True, cpuEraseGroupItems=True) + return value + + +def cache_erase_groups_items(cache_type, group_names): assert cache_type in const.CACHE_TYPE_VALUES assert len(group_names) >= 0 value = None @@ -314,7 +378,7 @@ def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): continue slots_to_remove.append(slot) - return cache_erase_group_items(cache_type, slots_to_remove) + return cache_erase_groups_items(cache_type, slots_to_remove) def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): @@ -353,6 +417,11 @@ def cache_remove_all(cache_type): ) # TODO: Clear image cache completely. + + # 1) Get the ImageCache capacities (CPU and GPU). + # 2) Set the ImageCache capacity (CPU and GPU) to zero. + # 3) Restore the ImageCache capacities (CPU and GPU). + return diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py b/python/mmSolver/tools/imagecache/ui/imagecache_layout.py deleted file mode 100644 index 687df5a4f..000000000 --- a/python/mmSolver/tools/imagecache/ui/imagecache_layout.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (C) 2024 David Cattermole -# -# This file is part of mmSolver. -# -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . -# -""" -The main component of the user interface for the image cache -window. -""" - -import mmSolver.ui.qtpyutils as qtpyutils - -qtpyutils.override_binding_order() - -import mmSolver.ui.Qt.QtWidgets as QtWidgets -import mmSolver.ui.Qt.QtCore as QtCore - -import mmSolver.logger -import mmSolver.tools.imagecache.ui.ui_imagecache_layout as ui_imagecache_layout -import mmSolver.tools.imagecache.lib as lib - - -LOG = mmSolver.logger.get_logger() - - -# Memory Conversion -_BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) -_BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) -_BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) -_KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) -_KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) - - -def memoryTotalUpdateUi( - label, - size_bytes, -): - gigabytes = 0.0 - if size_bytes > 0: - gigabytes = size_bytes / _BYTES_TO_GIGABYTES - - text = '{:0,.2f} GB'.format(gigabytes) - label.setText(text) - - -def memoryUsedUpdateUi(label, used_size_bytes, total_size_bytes): - used_gigabytes = 0.0 - if used_size_bytes > 0: - used_gigabytes = used_size_bytes / _BYTES_TO_GIGABYTES - - used_percent = 0.0 - if used_size_bytes > 0 and total_size_bytes > 0: - used_percent = (used_size_bytes / total_size_bytes) * 100.0 - - text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) - label.setText(text) - - -def cacheItemCountUpdateUi( - label, - value, -): - text = '{:,}'.format(value) - label.setText(text) - - -def cacheCapacityUpdateUi( - label, - size_bytes, -): - gigabytes = 0.0 - if size_bytes > 0: - gigabytes = size_bytes / _BYTES_TO_GIGABYTES - - text = '{:0,.2f} GB'.format(gigabytes) - label.setText(text) - - -def cacheUsedUpdateUi(label, used_size_bytes, capacity_size_bytes): - used_gigabytes = 0.0 - if used_size_bytes > 0: - used_gigabytes = used_size_bytes / _BYTES_TO_GIGABYTES - - used_percent = 0.0 - if used_size_bytes > 0 and capacity_size_bytes > 0: - used_percent = (used_size_bytes / capacity_size_bytes) * 100.0 - - text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) - label.setText(text) - - -class ImageCacheLayout(QtWidgets.QWidget, ui_imagecache_layout.Ui_Form): - def __init__(self, parent=None, *args, **kwargs): - super(ImageCacheLayout, self).__init__(*args, **kwargs) - self.setupUi(self) - - # Update timer. - self.update_timer = QtCore.QTimer() - seconds = 0.5 - milliseconds = int(seconds * 1000) - self.update_timer.setInterval(milliseconds) - self.update_timer.timeout.connect(self.updateUiValues) - self.update_timer.start() - - # Populate the UI with data - self.populateUi() - - def updateUiValues(self): - LOG.debug('updateUiValues...') - - gpu_cache_item_count = lib.get_gpu_cache_item_count() - cpu_cache_item_count = lib.get_cpu_cache_item_count() - gpu_cache_used = lib.get_gpu_cache_used_bytes() - cpu_cache_used = lib.get_cpu_cache_used_bytes() - gpu_cache_capacity = lib.get_gpu_cache_capacity_bytes() - cpu_cache_capacity = lib.get_cpu_cache_capacity_bytes() - gpu_memory_total = lib.get_gpu_memory_total_bytes() - cpu_memory_total = lib.get_cpu_memory_total_bytes() - gpu_memory_used = lib.get_gpu_memory_used_bytes() - cpu_memory_used = lib.get_cpu_memory_used_bytes() - - memoryTotalUpdateUi(self.gpuMemoryTotalValue_label, gpu_memory_total) - memoryTotalUpdateUi(self.cpuMemoryTotalValue_label, cpu_memory_total) - - memoryUsedUpdateUi( - self.gpuMemoryUsedValue_label, gpu_memory_used, gpu_memory_total - ) - memoryUsedUpdateUi( - self.cpuMemoryUsedValue_label, cpu_memory_used, cpu_memory_total - ) - - cacheItemCountUpdateUi(self.gpuCacheItemCountValue_label, gpu_cache_item_count) - cacheItemCountUpdateUi(self.cpuCacheItemCountValue_label, cpu_cache_item_count) - - cacheCapacityUpdateUi(self.gpuCacheCapacityValue_label, gpu_cache_capacity) - cacheCapacityUpdateUi(self.cpuCacheCapacityValue_label, cpu_cache_capacity) - - cacheUsedUpdateUi( - self.cpuCacheUsedValue_label, cpu_cache_used, cpu_cache_capacity - ) - cacheUsedUpdateUi( - self.gpuCacheUsedValue_label, gpu_cache_used, gpu_cache_capacity - ) - return - - def reset_options(self): - self.populateUi() - - def populateUi(self): - """ - Update the UI for the first time the class is created. - """ - self.updateUiValues() - - # name = const.CONFIG_WIDTH_KEY - # value = configmaya.get_scene_option(name, default=const.DEFAULT_WIDTH) - # LOG.debug('key=%r value=%r', name, value) - # self.width_horizontalSlider.setValue(value) - # self.width_spinBox.setValue(value) - - # self.gpuCacheCapacityGigaBytes_label - # self.gpuCacheCapacity_horizontalSlider - # self.gpuCacheCapacity_doubleSpinBox - return diff --git a/python/mmSolver/tools/imagecacheprefs/__init__.py b/python/mmSolver/tools/imagecacheprefs/__init__.py new file mode 100644 index 000000000..ac8160ee7 --- /dev/null +++ b/python/mmSolver/tools/imagecacheprefs/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache tool - Adjust the mmSolver Image Cache +""" diff --git a/python/mmSolver/tools/imagecacheprefs/constant.py b/python/mmSolver/tools/imagecacheprefs/constant.py new file mode 100644 index 000000000..6518364f0 --- /dev/null +++ b/python/mmSolver/tools/imagecacheprefs/constant.py @@ -0,0 +1,33 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache constants. +""" + +WINDOW_TITLE = 'Image Cache Preferences' + +CONFIG_FILE_NAME = "tools_imagecacheprefs.json" + +CONFIG_SCENE_CAPACITY_OVERRIDE_KEY = 'mmSolver_imagecache_capacity_override' +CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_gpu_capacity_percent' +CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_cpu_capacity_percent' + +CONFIG_UPDATE_EVERY_N_SECONDS_KEY = 'data/update_every_n_seconds' +CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE = 2 +CONFIG_GPU_CAPACITY_PERCENT_KEY = 'data/gpu_capacity_percent' +CONFIG_CPU_CAPACITY_PERCENT_KEY = 'data/cpu_capacity_percent' diff --git a/python/mmSolver/tools/imagecache/tool.py b/python/mmSolver/tools/imagecacheprefs/tool.py similarity index 86% rename from python/mmSolver/tools/imagecache/tool.py rename to python/mmSolver/tools/imagecacheprefs/tool.py index 3b5594396..880e0873a 100644 --- a/python/mmSolver/tools/imagecache/tool.py +++ b/python/mmSolver/tools/imagecacheprefs/tool.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # """ -Controls the mmSolver Image Cache. +Controls the mmSolver Image Cache Preferences. """ from __future__ import absolute_import @@ -28,7 +28,7 @@ LOG = mmSolver.logger.get_logger() -def main(): - import mmSolver.tools.imagecache.ui.imagecache_window as window +def open_window(): + import mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_window as window window.main() diff --git a/python/mmSolver/tools/imagecache/ui/__init__.py b/python/mmSolver/tools/imagecacheprefs/ui/__init__.py similarity index 100% rename from python/mmSolver/tools/imagecache/ui/__init__.py rename to python/mmSolver/tools/imagecacheprefs/ui/__init__.py diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py new file mode 100644 index 000000000..ff07c856b --- /dev/null +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -0,0 +1,661 @@ +# Copyright (C) 2024 David Cattermole +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The main component of the user interface for the image cache +window. +""" + +import os +import collections + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtWidgets as QtWidgets +import mmSolver.ui.Qt.QtCore as QtCore + +import mmSolver.logger +import mmSolver.utils.config as config_utils +import mmSolver.utils.configmaya as configmaya +import mmSolver.utils.constant as const_utils +import mmSolver.utils.math as math_utils +import mmSolver.tools.imagecache.lib as lib +import mmSolver.tools.imagecacheprefs.constant as const +import mmSolver.tools.imagecacheprefs.ui.ui_imagecacheprefs_layout as ui_imagecacheprefs_layout + +LOG = mmSolver.logger.get_logger() + + +CapacityValue = collections.namedtuple('CapacityValue', ['size_bytes', 'percent']) + + +def set_memory_total_label( + label, + size_bytes, +): + assert isinstance(label, QtWidgets.QLabel) + assert isinstance(size_bytes, int) + gigabytes = 0.0 + if size_bytes > 0: + gigabytes = size_bytes / const_utils.BYTES_TO_GIGABYTES + + text = '{:0,.2f} GB'.format(gigabytes) + label.setText(text) + + +def set_memory_used_label(label, used_size_bytes, total_size_bytes): + assert isinstance(label, QtWidgets.QLabel) + assert isinstance(used_size_bytes, int) + assert isinstance(total_size_bytes, int) + used_gigabytes = 0.0 + if used_size_bytes > 0: + used_gigabytes = used_size_bytes / const_utils.BYTES_TO_GIGABYTES + + used_percent = 0.0 + if used_size_bytes > 0 and total_size_bytes > 0: + used_percent = (used_size_bytes / total_size_bytes) * 100.0 + + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) + label.setText(text) + + +def set_count_label( + label, + value, +): + assert isinstance(label, QtWidgets.QLabel) + assert isinstance(value, int) + text = '{:,}'.format(value) + label.setText(text) + + +def set_capacity_size_label( + label, + size_bytes, +): + assert isinstance(label, QtWidgets.QLabel) + assert isinstance(size_bytes, int) + gigabytes = 0.0 + if size_bytes > 0: + gigabytes = size_bytes / const_utils.BYTES_TO_GIGABYTES + + text = '{:0,.2f} GB'.format(gigabytes) + label.setText(text) + + +def set_used_size_label(label, used_size_bytes, capacity_size_bytes): + assert isinstance(label, QtWidgets.QLabel) + assert isinstance(used_size_bytes, int) + assert isinstance(capacity_size_bytes, int) + used_gigabytes = 0.0 + if used_size_bytes > 0: + used_gigabytes = used_size_bytes / const_utils.BYTES_TO_GIGABYTES + + used_percent = 0.0 + if used_size_bytes > 0 and capacity_size_bytes > 0: + used_percent = (used_size_bytes / capacity_size_bytes) * 100.0 + + text = '{:0,.2f} GB ({:3.1f}%)'.format(used_gigabytes, used_percent) + label.setText(text) + + +def set_percent_slider( + slider, + percent, +): + assert isinstance(slider, QtWidgets.QSlider) + assert isinstance(percent, float) + + old_min = 0.0 + old_max = 100.0 + new_min = float(slider.minimum()) + new_max = float(slider.maximum()) + slider_value = math_utils.remap(old_min, old_max, new_min, new_max, percent) + slider.setValue(int(slider_value)) + + +def set_value_double_spin_box( + doubleSpinBox, + value, +): + assert isinstance(doubleSpinBox, QtWidgets.QDoubleSpinBox) + assert isinstance(value, float) + doubleSpinBox.setValue(int(value)) + + +def set_capacity_widgets( + label, slider, double_spin_box, capacity_bytes, capacity_percent +): + assert isinstance(capacity_bytes, int) + assert isinstance(capacity_percent, float) + set_capacity_size_label(label, capacity_bytes) + set_percent_slider(slider, capacity_percent) + set_value_double_spin_box(double_spin_box, capacity_percent) + return + + +def get_config(): + """Get the Image Cache config object or None.""" + file_name = const.CONFIG_FILE_NAME + config_path = config_utils.get_home_dir_path(file_name) + config = config_utils.Config(config_path) + config.set_autoread(False) + config.set_autowrite(False) + if os.path.isfile(config.file_path): + config.read() + return config + + +def get_config_value(config, key, fallback): + """Query the attribute from the user's home directory. If the user's + option is saved, use that value instead. + """ + value = fallback + if config is not None: + value = config.get_value(key, fallback) + return value + + +def get_update_every_n_seconds(config, default_value=None): + if default_value is None: + default_value = const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE + assert isinstance(default_value, int) + name = const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY + seconds = get_config_value(config, name, default_value) + assert isinstance(seconds, int) + return seconds + + +def get_gpu_cache_capacity_percent_default(config, default_value=None): + if default_value is None: + default_value = 0.0 + assert isinstance(default_value, float) + name = const.CONFIG_GPU_CAPACITY_PERCENT_KEY + percent = get_config_value(config, name, default_value) + assert isinstance(percent, float) + ratio = percent / 100.0 + capacity_bytes = lib.get_gpu_memory_total_bytes() + size_bytes = int(capacity_bytes * ratio) + return CapacityValue(size_bytes, percent) + + +def get_gpu_cache_capacity_percent_default(config, default_value=None): + if default_value is None: + default_value = 0.0 + assert isinstance(default_value, float) + name = const.CONFIG_GPU_CAPACITY_PERCENT_KEY + percent = get_config_value(config, name, default_value) + assert isinstance(percent, float) + ratio = percent / 100.0 + capacity_bytes = lib.get_gpu_memory_total_bytes() + size_bytes = int(capacity_bytes * ratio) + return CapacityValue(size_bytes, percent) + + +def get_cpu_cache_capacity_percent_default(config, default_value=None): + if default_value is None: + default_value = 0.0 + assert isinstance(default_value, float) + name = const.CONFIG_CPU_CAPACITY_PERCENT_KEY + percent = get_config_value(config, name, default_value) + assert isinstance(percent, float) + ratio = percent / 100.0 + capacity_bytes = lib.get_cpu_memory_total_bytes() + size_bytes = int(capacity_bytes * ratio) + return CapacityValue(size_bytes, percent) + + +def get_cache_scene_override(): + name = const.CONFIG_SCENE_CAPACITY_OVERRIDE_KEY + value = configmaya.get_scene_option(name, default=None) + assert value is None or isinstance(value, bool) + return value + + +def get_gpu_cache_capacity_percent_scene(default_value=None): + if default_value is None: + default_value = 0.0 + assert isinstance(default_value, float) + name = const.CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY + percent = configmaya.get_scene_option(name, default=default_value) + assert isinstance(percent, float) + ratio = percent / 100.0 + capacity_bytes = lib.get_gpu_memory_total_bytes() + size_bytes = int(capacity_bytes * ratio) + return CapacityValue(size_bytes, percent) + + +def get_cpu_cache_capacity_percent_scene(default_value=None): + if default_value is None: + default_value = 0.0 + assert isinstance(default_value, float) + name = const.CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY + percent = configmaya.get_scene_option(name, default=default_value) + assert isinstance(percent, float) + ratio = percent / 100.0 + capacity_bytes = lib.get_cpu_memory_total_bytes() + size_bytes = int(capacity_bytes * ratio) + return CapacityValue(size_bytes, percent) + + +def set_cache_scene_override(value): + assert value is None or isinstance(value, bool) + name = const.CONFIG_SCENE_CAPACITY_OVERRIDE_KEY + configmaya.set_scene_option(name, value, add_attr=True) + return + + +def set_gpu_cache_capacity_percent_scene(percent): + assert isinstance(percent, float) + name = const.CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY + percent = configmaya.set_scene_option(name, percent, add_attr=True) + return + + +def set_cpu_cache_capacity_percent_scene(percent): + assert isinstance(percent, float) + name = const.CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY + percent = configmaya.set_scene_option(name, percent, add_attr=True) + return + + +class ImageCachePrefsLayout(QtWidgets.QWidget, ui_imagecacheprefs_layout.Ui_Form): + def __init__(self, parent=None, *args, **kwargs): + super(ImageCachePrefsLayout, self).__init__(*args, **kwargs) + self.setupUi(self) + + # Update timer. + self.update_timer = QtCore.QTimer() + milliseconds = int(const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE * 1000) + self.update_timer.setInterval(milliseconds) + self.update_timer.timeout.connect(self.update_resource_values) + self.update_timer.start() + + self._connect_connections() + + # Populate the UI with data + self.reset_options() + + def _connect_connections(self): + self.updateEvery_spinBox.valueChanged[int].connect(self._update_every_changed) + + self.gpuCacheDefaultCapacity_doubleSpinBox.valueChanged[float].connect( + self._gpu_default_capacity_spin_box_changed + ) + self.cpuCacheDefaultCapacity_doubleSpinBox.valueChanged[float].connect( + self._cpu_default_capacity_spin_box_changed + ) + self.gpuCacheSceneCapacity_doubleSpinBox.valueChanged[float].connect( + self._gpu_scene_capacity_spin_box_changed + ) + self.cpuCacheSceneCapacity_doubleSpinBox.valueChanged[float].connect( + self._cpu_scene_capacity_spin_box_changed + ) + + self.gpuCacheDefaultCapacity_horizontalSlider.valueChanged[int].connect( + self._gpu_default_capacity_slider_changed + ) + self.cpuCacheDefaultCapacity_horizontalSlider.valueChanged[int].connect( + self._cpu_default_capacity_slider_changed + ) + self.gpuCacheSceneCapacity_horizontalSlider.valueChanged[int].connect( + self._gpu_scene_capacity_slider_changed + ) + self.cpuCacheSceneCapacity_horizontalSlider.valueChanged[int].connect( + self._cpu_scene_capacity_slider_changed + ) + + def _gpu_default_capacity_spin_box_changed(self, percent): + assert isinstance(percent, float) + LOG.debug('_gpu_default_capacity_spin_box_changed: percent=%r', percent) + + label = self.gpuCacheDefaultCapacityValue_label + slider = self.gpuCacheDefaultCapacity_horizontalSlider + + gpu_memory_total = lib.get_gpu_memory_total_bytes() + ratio = percent / 100.0 + size_bytes = int(ratio * gpu_memory_total) + LOG.debug('_gpu_default_capacity_spin_box_changed: ratio=%r', ratio) + LOG.debug('_gpu_default_capacity_spin_box_changed: size_bytes=%r', size_bytes) + + set_capacity_size_label(label, size_bytes) + + block = slider.blockSignals(True) + set_percent_slider(slider, percent) + slider.blockSignals(block) + + def _cpu_default_capacity_spin_box_changed(self, percent): + assert isinstance(percent, float) + LOG.debug('_cpu_default_capacity_spin_box_changed: percent=%r', percent) + + label = self.cpuCacheDefaultCapacityValue_label + slider = self.cpuCacheDefaultCapacity_horizontalSlider + + cpu_memory_total = lib.get_cpu_memory_total_bytes() + ratio = percent / 100.0 + size_bytes = int(ratio * cpu_memory_total) + LOG.debug('_cpu_default_capacity_spin_box_changed: ratio=%r', ratio) + LOG.debug('_cpu_default_capacity_spin_box_changed: size_bytes=%r', size_bytes) + + set_capacity_size_label(label, size_bytes) + + block = slider.blockSignals(True) + set_percent_slider(slider, percent) + slider.blockSignals(block) + + def _gpu_scene_capacity_spin_box_changed(self, percent): + assert isinstance(percent, float) + LOG.debug('_gpu_scene_capacity_spin_box_changed: percent=%r', percent) + + label = self.gpuCacheSceneCapacityValue_label + slider = self.gpuCacheSceneCapacity_horizontalSlider + + gpu_memory_total = lib.get_gpu_memory_total_bytes() + ratio = percent / 100.0 + size_bytes = int(ratio * gpu_memory_total) + LOG.debug('_gpu_scene_capacity_spin_box_changed: ratio=%r', ratio) + LOG.debug('_gpu_scene_capacity_spin_box_changed: size_bytes=%r', size_bytes) + + set_capacity_size_label(label, size_bytes) + + block = slider.blockSignals(True) + set_percent_slider(slider, percent) + slider.blockSignals(block) + + def _cpu_scene_capacity_spin_box_changed(self, percent): + assert isinstance(percent, float) + LOG.debug('_cpu_scene_capacity_spin_box_changed: percent=%r', percent) + + label = self.cpuCacheSceneCapacityValue_label + slider = self.cpuCacheSceneCapacity_horizontalSlider + + cpu_memory_total = lib.get_cpu_memory_total_bytes() + ratio = percent / 100.0 + size_bytes = int(ratio * cpu_memory_total) + LOG.debug('_cpu_scene_capacity_spin_box_changed: ratio=%r', ratio) + LOG.debug('_cpu_scene_capacity_spin_box_changed: size_bytes=%r', size_bytes) + + set_capacity_size_label(label, size_bytes) + + block = slider.blockSignals(True) + set_percent_slider(slider, percent) + slider.blockSignals(block) + + def _gpu_default_capacity_slider_changed(self, value): + assert isinstance(value, int) + LOG.debug('_gpu_default_capacity_slider_changed: value=%r', value) + + label = self.gpuCacheDefaultCapacityValue_label + spin_box = self.gpuCacheDefaultCapacity_doubleSpinBox + slider = self.gpuCacheDefaultCapacity_horizontalSlider + + old_min = float(slider.minimum()) + old_max = float(slider.maximum()) + new_min = 0.0 + new_max = 100.0 + percent = math_utils.remap(old_min, old_max, new_min, new_max, float(value)) + ratio = percent / new_max + LOG.debug('_gpu_default_capacity_slider_changed: percent=%r', percent) + + gpu_memory_total = lib.get_gpu_memory_total_bytes() + size_bytes = int(ratio * gpu_memory_total) + + set_capacity_size_label(label, size_bytes) + + block = spin_box.blockSignals(True) + set_value_double_spin_box(spin_box, percent) + spin_box.blockSignals(block) + + def _cpu_default_capacity_slider_changed(self, value): + assert isinstance(value, int) + LOG.debug('_cpu_default_capacity_slider_changed: value=%r', value) + + label = self.cpuCacheDefaultCapacityValue_label + spin_box = self.cpuCacheDefaultCapacity_doubleSpinBox + slider = self.cpuCacheDefaultCapacity_horizontalSlider + + old_min = float(slider.minimum()) + old_max = float(slider.maximum()) + new_min = 0.0 + new_max = 100.0 + percent = math_utils.remap(old_min, old_max, new_min, new_max, float(value)) + ratio = percent / new_max + LOG.debug('_cpu_default_capacity_slider_changed: percent=%r', percent) + + cpu_memory_total = lib.get_cpu_memory_total_bytes() + size_bytes = int(ratio * cpu_memory_total) + + set_capacity_size_label(label, size_bytes) + + block = spin_box.blockSignals(True) + set_value_double_spin_box(spin_box, percent) + spin_box.blockSignals(block) + + def _gpu_scene_capacity_slider_changed(self, value): + assert isinstance(value, int) + LOG.debug('_gpu_scene_capacity_slider_changed: value=%r', value) + + label = self.gpuCacheSceneCapacityValue_label + spin_box = self.gpuCacheSceneCapacity_doubleSpinBox + slider = self.gpuCacheSceneCapacity_horizontalSlider + + old_min = float(slider.minimum()) + old_max = float(slider.maximum()) + new_min = 0.0 + new_max = 100.0 + percent = math_utils.remap(old_min, old_max, new_min, new_max, float(value)) + ratio = percent / new_max + LOG.debug('_gpu_scene_capacity_slider_changed: percent=%r', percent) + + gpu_memory_total = lib.get_gpu_memory_total_bytes() + size_bytes = int(ratio * gpu_memory_total) + + set_capacity_size_label(label, size_bytes) + + block = spin_box.blockSignals(True) + set_value_double_spin_box(spin_box, percent) + spin_box.blockSignals(block) + + def _cpu_scene_capacity_slider_changed(self, value): + assert isinstance(value, int) + LOG.debug('_cpu_scene_capacity_slider_changed: value=%r', value) + + label = self.cpuCacheSceneCapacityValue_label + spin_box = self.cpuCacheSceneCapacity_doubleSpinBox + slider = self.cpuCacheSceneCapacity_horizontalSlider + + old_min = float(slider.minimum()) + old_max = float(slider.maximum()) + new_min = 0.0 + new_max = 100.0 + percent = math_utils.remap(old_min, old_max, new_min, new_max, float(value)) + ratio = percent / new_max + LOG.debug('_cpu_scene_capacity_slider_changed: percent=%r', percent) + + cpu_memory_total = lib.get_cpu_memory_total_bytes() + size_bytes = int(ratio * cpu_memory_total) + + set_capacity_size_label(label, size_bytes) + + block = spin_box.blockSignals(True) + set_value_double_spin_box(spin_box, percent) + spin_box.blockSignals(block) + + def _update_every_changed(self, value): + assert isinstance(value, int) + milliseconds = int(value * 1000) + self.update_timer.setInterval(milliseconds) + + config = get_config() + if config is not None: + config.set_value(const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, value) + config.write() + return + + def _get_capacity_bytes( + self, scene_override, default_spin_box, scene_spin_box, memory_total + ): + percent = default_spin_box.value() + if scene_override is True: + percent = scene_spin_box.value() + ratio = percent / 100.0 + + size_bytes = int(ratio * memory_total) + return size_bytes + + def get_gpu_capacity_bytes(self): + scene_override = self.imageCacheSceneSettings_groupBox.isChecked() + default_spin_box = self.gpuCacheDefaultCapacity_doubleSpinBox + scene_spin_box = self.gpuCacheSceneCapacity_doubleSpinBox + gpu_memory_total = lib.get_gpu_memory_total_bytes() + return self._get_capacity_bytes( + scene_override, default_spin_box, scene_spin_box, gpu_memory_total + ) + + def get_cpu_capacity_bytes(self): + scene_override = self.imageCacheSceneSettings_groupBox.isChecked() + default_spin_box = self.cpuCacheDefaultCapacity_doubleSpinBox + scene_spin_box = self.cpuCacheSceneCapacity_doubleSpinBox + cpu_memory_total = lib.get_cpu_memory_total_bytes() + return self._get_capacity_bytes( + scene_override, default_spin_box, scene_spin_box, cpu_memory_total + ) + + def update_resource_values(self): + gpu_cache_item_count = lib.get_gpu_cache_item_count() + cpu_cache_item_count = lib.get_cpu_cache_item_count() + gpu_cache_group_count = lib.get_gpu_cache_group_count() + cpu_cache_group_count = lib.get_cpu_cache_group_count() + gpu_cache_used = lib.get_gpu_cache_used_bytes() + cpu_cache_used = lib.get_cpu_cache_used_bytes() + gpu_cache_capacity = lib.get_gpu_cache_capacity_bytes() + cpu_cache_capacity = lib.get_cpu_cache_capacity_bytes() + gpu_memory_total = lib.get_gpu_memory_total_bytes() + cpu_memory_total = lib.get_cpu_memory_total_bytes() + gpu_memory_used = lib.get_gpu_memory_used_bytes() + cpu_memory_used = lib.get_cpu_memory_used_bytes() + + set_memory_total_label(self.gpuMemoryTotalValue_label, gpu_memory_total) + set_memory_total_label(self.cpuMemoryTotalValue_label, cpu_memory_total) + + set_memory_used_label( + self.gpuMemoryUsedValue_label, gpu_memory_used, gpu_memory_total + ) + set_memory_used_label( + self.cpuMemoryUsedValue_label, cpu_memory_used, cpu_memory_total + ) + + set_count_label(self.gpuCacheItemCountValue_label, gpu_cache_item_count) + set_count_label(self.cpuCacheItemCountValue_label, cpu_cache_item_count) + + set_count_label(self.gpuCacheGroupCountValue_label, gpu_cache_group_count) + set_count_label(self.cpuCacheGroupCountValue_label, cpu_cache_group_count) + + set_capacity_size_label(self.gpuCacheCapacityValue_label, gpu_cache_capacity) + set_capacity_size_label(self.cpuCacheCapacityValue_label, cpu_cache_capacity) + + set_used_size_label( + self.cpuCacheUsedValue_label, cpu_cache_used, cpu_cache_capacity + ) + set_used_size_label( + self.gpuCacheUsedValue_label, gpu_cache_used, gpu_cache_capacity + ) + return + + def reset_options(self): + """ + Reset the UI the values. + + If scene override is enabled, get the scene option values, + otherwise use the default values saved in the config file. + """ + self.update_resource_values() + + config = get_config() + update_seconds = get_update_every_n_seconds(config) + self.updateEvery_spinBox.setValue(update_seconds) + + default_gpu_capacity = get_gpu_cache_capacity_percent_default(config) + default_cpu_capacity = get_cpu_cache_capacity_percent_default(config) + + scene_override = get_cache_scene_override() + scene_gpu_capacity = default_gpu_capacity + scene_cpu_capacity = default_cpu_capacity + if scene_override is True: + scene_gpu_capacity = get_gpu_cache_capacity_percent_scene() + scene_cpu_capacity = get_cpu_cache_capacity_percent_scene() + + # Set default capacities. + set_capacity_widgets( + self.gpuCacheDefaultCapacityValue_label, + self.gpuCacheDefaultCapacity_horizontalSlider, + self.gpuCacheDefaultCapacity_doubleSpinBox, + default_gpu_capacity.size_bytes, + default_gpu_capacity.percent, + ) + set_capacity_widgets( + self.cpuCacheDefaultCapacityValue_label, + self.cpuCacheDefaultCapacity_horizontalSlider, + self.cpuCacheDefaultCapacity_doubleSpinBox, + default_cpu_capacity.size_bytes, + default_cpu_capacity.percent, + ) + + # Scene Override? + self.imageCacheSceneSettings_groupBox.setChecked(scene_override is True) + + # Set scene capacities. + set_capacity_widgets( + self.gpuCacheSceneCapacityValue_label, + self.gpuCacheSceneCapacity_horizontalSlider, + self.gpuCacheSceneCapacity_doubleSpinBox, + scene_gpu_capacity.size_bytes, + scene_gpu_capacity.percent, + ) + set_capacity_widgets( + self.cpuCacheSceneCapacityValue_label, + self.cpuCacheSceneCapacity_horizontalSlider, + self.cpuCacheSceneCapacity_doubleSpinBox, + scene_cpu_capacity.size_bytes, + scene_cpu_capacity.percent, + ) + return + + def save_options(self): + # Save config values in config file. + update_seconds = self.updateEvery_spinBox.value() + gpu_percent_default = self.gpuCacheDefaultCapacity_doubleSpinBox.value() + cpu_percent_default = self.cpuCacheDefaultCapacity_doubleSpinBox.value() + config = get_config() + if config is not None: + config.set_value(const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, update_seconds) + config.set_value(const.CONFIG_GPU_CAPACITY_PERCENT_KEY, gpu_percent_default) + config.set_value(const.CONFIG_CPU_CAPACITY_PERCENT_KEY, cpu_percent_default) + config.write() + + # Save config values in Maya Scene. + scene_override = self.imageCacheSceneSettings_groupBox.isChecked() + assert isinstance(scene_override, bool) + gpu_percent_scene = self.gpuCacheSceneCapacity_doubleSpinBox.value() + cpu_percent_scene = self.cpuCacheSceneCapacity_doubleSpinBox.value() + set_cache_scene_override(scene_override) + if scene_override is True: + set_gpu_cache_capacity_percent_scene(gpu_percent_scene) + set_cpu_cache_capacity_percent_scene(cpu_percent_scene) + return diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui similarity index 69% rename from python/mmSolver/tools/imagecache/ui/imagecache_layout.ui rename to python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui index e9c72d77b..89cc53477 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_layout.ui +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui @@ -6,8 +6,8 @@ 0 0 - 608 - 481 + 547 + 537 @@ -216,6 +216,37 @@ GPU Image Cache Overview + + + + + + Group Count: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> + + + + + @@ -318,6 +349,37 @@ CPU Image Cache Overview + + + + + + Group Count: + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> + + + + + @@ -417,36 +479,45 @@ - + - Image Cache Settings + Image Cache Defaults - + GPU Capacity: - + <html><head/><body><p><span style=" font-weight:600;">1 GB</span></p></body></html> - + + + 1000 + + + 100 + + + 500 + Qt::Horizontal - + % @@ -460,7 +531,7 @@ 5.000000000000000 - 5.000000000000000 + 50.000000000000000 @@ -469,28 +540,105 @@ - + CPU Capacity: - + <html><head/><body><p><span style=" font-weight:600;">5 GB</span></p></body></html> - + + + 1000 + + + 100 + + + 500 + + + Qt::Horizontal + + + + + + + % + + + 1 + + + 100.000000000000000 + + + 5.000000000000000 + + + 50.000000000000000 + + + + + + + + + + + + Image Cache Scene Override + + + true + + + false + + + + + + + + GPU Capacity: + + + + + + + <html><head/><body><p><span style=" font-weight:600;">1 GB</span></p></body></html> + + + + + + + 1000 + + + 100 + + + 500 + Qt::Horizontal - + % @@ -504,8 +652,61 @@ 5.000000000000000 + 50.000000000000000 + + + + + + + + + + + CPU Capacity: + + + + + + + <html><head/><body><p><span style=" font-weight:600;">5 GB</span></p></body></html> + + + + + + + 1000 + + + 100 + + + 500 + + + Qt::Horizontal + + + + + + + % + + + 1 + + + 100.000000000000000 + + 5.000000000000000 + + 50.000000000000000 + diff --git a/python/mmSolver/tools/imagecache/ui/imagecache_window.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py similarity index 73% rename from python/mmSolver/tools/imagecache/ui/imagecache_window.py rename to python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py index f74e377bf..b89a132ed 100644 --- a/python/mmSolver/tools/imagecache/ui/imagecache_window.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py @@ -36,9 +36,11 @@ import mmSolver.ui.uiutils as uiutils import mmSolver.ui.helputils as helputils import mmSolver.ui.commonmenus as commonmenus -import mmSolver.tools.imagecache.constant as const -import mmSolver.tools.imagecache.tool as tool -import mmSolver.tools.imagecache.ui.imagecache_layout as imagecache_layout + +# import mmSolver.tools.imagecache.constant as imagecache_const +import mmSolver.tools.imagecache.lib as imagecache_lib +import mmSolver.tools.imagecacheprefs.constant as const +import mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_layout as imagecacheprefs_layout LOG = mmSolver.logger.get_logger() @@ -52,13 +54,13 @@ def _open_help(): return -class ImageCacheWindow(BaseWindow): - name = 'ImageCacheWindow' +class ImageCachePrefsWindow(BaseWindow): + name = 'ImageCachePrefsWindow' def __init__(self, parent=None, name=None): - super(ImageCacheWindow, self).__init__(parent, name=name) + super(ImageCachePrefsWindow, self).__init__(parent, name=name) self.setupUi(self) - self.addSubForm(imagecache_layout.ImageCacheLayout) + self.addSubForm(imagecacheprefs_layout.ImageCachePrefsLayout) self.setWindowTitle(const.WINDOW_TITLE) self.setWindowFlags(QtCore.Qt.Tool) @@ -66,13 +68,10 @@ def __init__(self, parent=None, name=None): # Standard Buttons self.baseHideStandardButtons() self.applyBtn.show() - self.resetBtn.show() self.closeBtn.show() - self.applyBtn.setText('Apply') - self.resetBtn.setText('Clear Cache') + self.applyBtn.setText('Apply Capacity') self.applyBtn.clicked.connect(self._apply) - self.resetBtn.clicked.connect(self._clear_cache) # Hide irrelevant stuff self.baseHideProgressBar() @@ -92,17 +91,15 @@ def add_menus(self, menubar): menubar.addMenu(help_menu) def _apply(self): - # 1) Get the widget value. - # 2) Convert percentage into byte count. - # 3) Set the ImageCache capacity (CPU and GPU) - LOG.info('Set Capacity...') - return + form = self.getSubForm() + gpu_capacity_bytes = form.get_gpu_capacity_bytes() + cpu_capacity_bytes = form.get_cpu_capacity_bytes() + + imagecache_lib.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) + imagecache_lib.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) - def _clear_cache(self): - # 1) Get the ImageCache capacities (CPU and GPU). - # 2) Set the ImageCache capacity (CPU and GPU) to zero. - # 3) Restore the ImageCache capacities (CPU and GPU). - LOG.info('Clear Cache...') + form.update_resource_values() + form.save_options() return def reset_options(self): @@ -127,7 +124,9 @@ def main(show=True, auto_raise=True, delete=False): :returns: A new image cache window, or None if the window cannot be opened. - :rtype: ImageCacheWindow or None. + :rtype: ImageCachePrefsWindow or None. """ - win = ImageCacheWindow.open_window(show=show, auto_raise=auto_raise, delete=delete) + win = ImageCachePrefsWindow.open_window( + show=show, auto_raise=auto_raise, delete=delete + ) return win diff --git a/python/mmSolver/utils/constant.py b/python/mmSolver/utils/constant.py index 1c5015a7e..2f603d2b0 100644 --- a/python/mmSolver/utils/constant.py +++ b/python/mmSolver/utils/constant.py @@ -115,3 +115,9 @@ DISTORT_MODE_UNDISTORT, DISTORT_MODE_REDISTORT, ) + + +# Memory Conversion +BYTES_TO_KILOBYTES = 1024 ####### int(pow(2, 10)) +BYTES_TO_MEGABYTES = 1048576 #### int(pow(2, 20)) +BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) diff --git a/python/mmSolver/utils/math.py b/python/mmSolver/utils/math.py new file mode 100644 index 000000000..02041a49d --- /dev/null +++ b/python/mmSolver/utils/math.py @@ -0,0 +1,69 @@ +# Copyright (C) 2021 David Cattermole, Kazuma Tonegawa. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Extra mathematical functions. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + + +def lerp(min_value, max_value, mix): + """ + Return 'min_value' to 'max_value' linearly, for a 'mix' value + between 0.0 and 1.0. + + :type min_value: float + :type max_value: float + :type mix: float + + :rtype: float + """ + return (1.0 - mix) * min_value + mix * max_value + + +def inverse_lerp(min_value, max_value, mix): + """ + Return 0.0 to 1.0 linearly, for a 'mix' value between 'min_value' + and 'max_value'. + + :type min_value: float + :type max_value: float + :type mix: float + + :rtype: float + """ + return (mix - min_value) / (max_value - min_value) + + +def remap(old_min, old_max, new_min, new_max, mix): + """ + Remap from the 'old_*' values to 'new_*' values, using a 'mix' + value between 0.0 and 1.0; + + :type old_min: float + :type old_max: float + :type new_min: float + :type new_max: float + :type mix: float + + :rtype: float + """ + blend = inverse_lerp(old_min, old_max, mix) + return lerp(new_min, new_max, blend) diff --git a/share/config/functions.json b/share/config/functions.json index fbf6fdd4c..c4df719bc 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -866,13 +866,13 @@ "mmSolver.tools.createimageplane.tool.main_version_two();" ] }, - "image_cache_ui": { - "name": "Image Cache UI...", - "name_shelf": "ImgCch", - "tooltip": "Open the Image Cache UI.", + "image_cache_preferences_window": { + "name": "Image Cache Preferences...", + "name_shelf": "ICPref", + "tooltip": "Open the Image Cache Preferences window.", "command": [ - "import mmSolver.tools.imagecache.tool;", - "mmSolver.tools.imagecache.tool.main();" + "import mmSolver.tools.imagecacheprefs.tool;", + "mmSolver.tools.imagecacheprefs.tool.open_window();" ] }, "camera_toggle_distortion": { diff --git a/share/config/menu.json b/share/config/menu.json index 163c68733..8f6ea66c0 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -116,7 +116,7 @@ "general_tools/sort_nodes_in_outliner", "general_tools/remove_all_solver_nodes", "general_tools/---Settings & Preferences", - "general_tools/image_cache_ui", + "general_tools/image_cache_preferences_window", "general_tools/user_preferences_window", "file_io_tools/---Marker", "file_io_tools/load_marker_ui", diff --git a/share/config/shelf.json b/share/config/shelf.json index d7d28b553..4deb5b31c 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -113,7 +113,7 @@ "general_tools/gen_tools_popup/sort_nodes_in_outliner", "general_tools/gen_tools_popup/remove_all_solver_nodes", "general_tools/gen_tools_popup/---Settings & Preferences", - "general_tools/gen_tools_popup/image_cache_ui", + "general_tools/gen_tools_popup/image_cache_preferences_window", "general_tools/gen_tools_popup/user_preferences_window", "file_io_tools/file_io_popup/---Marker", "file_io_tools/file_io_popup/load_marker_ui", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index ea34b1b45..15f0a6cb4 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -124,7 +124,7 @@ "general_tools/gen_tools_popup/sort_nodes_in_outliner", "general_tools/gen_tools_popup/remove_all_solver_nodes", "general_tools/gen_tools_popup/---Settings & Preferences", - "general_tools/gen_tools_popup/image_cache_ui", + "general_tools/gen_tools_popup/image_cache_preferences_window", "general_tools/gen_tools_popup/user_preferences_window", "file_io_tools/file_io_popup/---Marker", "file_io_tools/file_io_popup/load_marker_ui", From 6c3b98517356a2b7af53acda0580d4f6d7e94a1f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 24 Jun 2024 01:15:41 +1000 Subject: [PATCH 123/295] Add C++ Hash Utilties --- src/mmSolver/utilities/hash_utils.h | 157 ++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/mmSolver/utilities/hash_utils.h diff --git a/src/mmSolver/utilities/hash_utils.h b/src/mmSolver/utilities/hash_utils.h new file mode 100644 index 000000000..cc0b17ee8 --- /dev/null +++ b/src/mmSolver/utilities/hash_utils.h @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2024 David Cattermole. + * + * This file is part of mmSolver. + * + * mmSolver is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * mmSolver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with mmSolver. If not, see . + * ==================================================================== + * + * Implements simple hashing on generic data. + * + * See this page for an interesting article on hash collisions: + * http://softwareengineering.stackexchange.com/questions/49550/which-hashing-algorithm-is-best-for-uniqueness-and-speed + * + */ + +#ifndef HASH_UTILS_H +#define HASH_UTILS_H + +// STL +#include // size_t +#include // string +#include // vector + +// Platform-specific functions and macros +#if defined(_MSC_VER) +// Microsoft Visual Studio +#define BIG_CONSTANT(x) (x) +#else +// Other compilers +#define BIG_CONSTANT(x) (x##LLU) +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1600) +// Microsoft Visual Studio +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef unsigned __int64 uint64_t; +#else +// Other compilers +#include +#endif + +// typedef unsigned long long int CacheKey; + +namespace mmsolver { +namespace hash { + +// MurmurHash2 hash, for 64-bit versions +// +// From: +// https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp#L96 +// +// MurmurHash2, 64-bit versions, by Austin Appleby +// +// MurmurHash2 was written by Austin Appleby, and is placed in the +// public domain. The author hereby disclaims copyright to the +// MurmurHash2 source code. +// +// The same caveats as 32-bit MurmurHash2 apply here - beware of alignment +// and endian-ness issues if used across multiple platforms. +// +// 64-bit hash for 64-bit platforms +inline uint64_t MurmurHash64A(const void *key, int len, uint64_t seed) { + const uint64_t m = BIG_CONSTANT(0xc6a4a7935bd1e995); + const int r = 47; + + uint64_t h = seed ^ (len * m); + + const uint64_t *data = (const uint64_t *)key; + const uint64_t *end = data + (len / 8); + + while (data != end) { + uint64_t k = *data++; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + + const unsigned char *data2 = (const unsigned char *)data; + + switch (len & 7) { + case 7: + h ^= uint64_t(data2[6]) << 48; + case 6: + h ^= uint64_t(data2[5]) << 40; + case 5: + h ^= uint64_t(data2[4]) << 32; + case 4: + h ^= uint64_t(data2[3]) << 24; + case 3: + h ^= uint64_t(data2[2]) << 16; + case 2: + h ^= uint64_t(data2[1]) << 8; + case 1: + h ^= uint64_t(data2[0]); + h *= m; + default: + break; + }; + + h ^= h >> r; + h *= m; + h ^= h >> r; + + return h; +} + +template +inline R hashMurmur2(const K *key, int len) { + const void *p = reinterpret_cast(key); + R h = (R)MurmurHash64A(p, len, 0); + return h; +} + +// Hashing functions, added to the class for convenience. +inline uint64_t make_hash(const std::string &value) { + return hash::hashMurmur2(value.c_str(), value.size()); +} + +// Merges multiple hashes together into a single hash. +template +inline R mergeHashes(const std::vector &hashes, R seed = 0) { + typename std::vector::const_iterator it; + R result = seed; + for (it = hashes.begin(); it != hashes.end(); ++it) { + result = 16777619 * result ^ *it; + } + return result; +} + +// Combines a hash with another hash. +// +// Similar to 'boost::combine_hash'. +template +inline void combineHash(K &seed, K key) { + seed = 16777619 * seed ^ key; +} + +} // namespace hash +} // namespace mmsolver + +#endif // HASH_UTILS_H From a624d0ff65c9f3e80cc2022a1c5db7d58f472ad9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 24 Jun 2024 22:34:06 +1000 Subject: [PATCH 124/295] Image Cache Preferences - Change UI widget labels. Slightly re-organises the widgets too. --- .../ui/imagecacheprefs_layout.ui | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui index 89cc53477..714aab784 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui @@ -69,7 +69,7 @@ - GPU Memory Total: + Memory Total: @@ -100,7 +100,7 @@ - GPU Memory Used: + Memory Used: @@ -144,7 +144,7 @@ - CPU Memory Total: + Memory Total: @@ -175,7 +175,7 @@ - CPU Memory Used: + Memory Used: @@ -217,16 +217,16 @@ - + - + - Group Count: + Images: - + Qt::Horizontal @@ -239,25 +239,25 @@ - + - <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> - + - + - Item Count: + Image Groups: - + Qt::Horizontal @@ -270,9 +270,9 @@ - + - <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> @@ -350,16 +350,16 @@ - + - + - Group Count: + Images: - + Qt::Horizontal @@ -372,25 +372,25 @@ - + - <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> - + - + - Item Count: + Image Groups: - + Qt::Horizontal @@ -403,9 +403,9 @@ - + - <html><head/><body><p><span style=" font-weight:600;">42</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> From 20e46e9717802e99b01af913ed1d01fbe72f9956 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 25 Jun 2024 23:52:40 +1000 Subject: [PATCH 125/295] Image Cache - Set capacity at mmSolver startup This commit reorganises a lot of functions and modules of the image cache. When starting mmSolver (usually at the start of Maya), the Image Cache capacity will be set from the preferences stored by the user. The capacity preferences can be overridden per-maya-scene as needed too, without changing the default for new Maya scenes. --- .../AEmmImagePlaneShape2Template.mel | 35 +- python/mmSolver/startup.py | 12 + .../tools/createimageplane/_lib/format.py | 154 +++++ python/mmSolver/tools/createimageplane/lib.py | 13 + .../mmSolver/tools/imagecache/_lib/erase.py | 192 +++++++ .../tools/imagecache/_lib/imagecache_cmd.py | 104 ++++ .../tools/imagecache/_lib/initialize.py | 99 ++++ .../tools/imagecache/_lib/query_resources.py | 64 +++ python/mmSolver/tools/imagecache/config.py | 78 +++ .../mmSolver/tools/imagecache/config_file.py | 98 ++++ .../mmSolver/tools/imagecache/config_scene.py | 78 +++ .../mmSolver/tools/imagecache/config_utils.py | 39 ++ python/mmSolver/tools/imagecache/constant.py | 18 + python/mmSolver/tools/imagecache/lib.py | 529 +++--------------- .../tools/imagecacheprefs/constant.py | 6 - .../ui/imagecacheprefs_layout.py | 171 ++---- .../ui/imagecacheprefs_window.py | 10 +- 17 files changed, 1087 insertions(+), 613 deletions(-) create mode 100644 python/mmSolver/tools/createimageplane/_lib/format.py create mode 100644 python/mmSolver/tools/imagecache/_lib/erase.py create mode 100644 python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py create mode 100644 python/mmSolver/tools/imagecache/_lib/initialize.py create mode 100644 python/mmSolver/tools/imagecache/_lib/query_resources.py create mode 100644 python/mmSolver/tools/imagecache/config.py create mode 100644 python/mmSolver/tools/imagecache/config_file.py create mode 100644 python/mmSolver/tools/imagecache/config_scene.py create mode 100644 python/mmSolver/tools/imagecache/config_utils.py diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 091e34594..cadd49d9b 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -259,23 +259,28 @@ global proc AEmmImagePlaneShape2_colorSpaceReplace(string $node_attr) global proc AEmmImagePlaneShape2_imageCache_updateValues( string $image_plane_shp) { - string $image_seq_size_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + string $image_seq_size_cmd = ""; + $image_seq_size_cmd = "import mmSolver.tools.createimageplane.lib as lib;"; $image_seq_size_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $image_seq_size_cmd += "lib.format_image_sequence_size(image_plane_shp);"; - string $cache_gpu_used_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + string $cache_gpu_used_cmd = ""; + $cache_gpu_used_cmd = "import mmSolver.tools.createimageplane.lib as lib;"; $cache_gpu_used_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $cache_gpu_used_cmd += "lib.format_cache_gpu_used(image_plane_shp);"; - string $cache_cpu_used_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + string $cache_cpu_used_cmd = ""; + $cache_cpu_used_cmd = "import mmSolver.tools.createimageplane.lib as lib;"; $cache_cpu_used_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $cache_cpu_used_cmd += "lib.format_cache_cpu_used(image_plane_shp);"; - string $memory_gpu_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + string $memory_gpu_available_cmd = ""; + $memory_gpu_available_cmd = "import mmSolver.tools.createimageplane.lib as lib;"; $memory_gpu_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $memory_gpu_available_cmd += "lib.format_memory_gpu_available(image_plane_shp);"; - string $memory_cpu_available_cmd = "import mmSolver.tools.imagecache.lib as lib;"; + string $memory_cpu_available_cmd = ""; + $memory_cpu_available_cmd = "import mmSolver.tools.createimageplane.lib as lib;"; $memory_cpu_available_cmd += "image_plane_shp = \"" + $image_plane_shp + "\";"; $memory_cpu_available_cmd += "lib.format_memory_cpu_available(image_plane_shp);"; @@ -366,8 +371,8 @@ global proc AEmmImagePlaneShape2_imageCache_clearAllSlots( string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; $cmd += "node = \"" + $image_plane_shp + "\";"; - $cmd += "lib.cache_remove_all_image_plane_slots(const.CACHE_TYPE_CPU, node);"; - $cmd += "lib.cache_remove_all_image_plane_slots(const.CACHE_TYPE_GPU, node);"; + $cmd += "lib.erase_all_images_on_image_plane_slots(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.erase_all_images_on_image_plane_slots(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -378,8 +383,8 @@ global proc AEmmImagePlaneShape2_imageCache_clearActiveSlot( string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; $cmd += "node = \"" + $image_plane_shp + "\";"; - $cmd += "lib.cache_remove_active_image_plane_slot(const.CACHE_TYPE_CPU, node);"; - $cmd += "lib.cache_remove_active_image_plane_slot(const.CACHE_TYPE_GPU, node);"; + $cmd += "lib.erase_images_in_active_image_plane_slot(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.erase_images_in_active_image_plane_slot(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -390,8 +395,8 @@ global proc AEmmImagePlaneShape2_imageCache_clearUnusedSlots( string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; $cmd += "node = \"" + $image_plane_shp + "\";"; - $cmd += "lib.cache_remove_unused_image_plane_slots(const.CACHE_TYPE_CPU, node);"; - $cmd += "lib.cache_remove_unused_image_plane_slots(const.CACHE_TYPE_GPU, node);"; + $cmd += "lib.erase_images_in_unused_image_plane_slots(const.CACHE_TYPE_CPU, node);"; + $cmd += "lib.erase_images_in_unused_image_plane_slots(const.CACHE_TYPE_GPU, node);"; python($cmd); } @@ -400,16 +405,16 @@ global proc AEmmImagePlaneShape2_imageCache_clearAllImages() { string $cmd = "import mmSolver.tools.imagecache.lib as lib;"; $cmd += "import mmSolver.tools.imagecache.constant as const;"; - $cmd += "lib.cache_remove_all(const.CACHE_TYPE_CPU);"; - $cmd += "lib.cache_remove_all(const.CACHE_TYPE_GPU);"; + $cmd += "lib.erase_all_images(const.CACHE_TYPE_CPU);"; + $cmd += "lib.erase_all_images(const.CACHE_TYPE_GPU);"; python($cmd); } global proc AEmmImagePlaneShape2_imageCache_openImageCacheUI() { - string $cmd = "import mmSolver.tools.imagecache.tool as tool;"; - $cmd += "tool.main();"; + string $cmd = "import mmSolver.tools.imagecacheprefs.tool as tool;"; + $cmd += "tool.open_window();"; python($cmd); } diff --git a/python/mmSolver/startup.py b/python/mmSolver/startup.py index e932c93f1..c5266f94a 100644 --- a/python/mmSolver/startup.py +++ b/python/mmSolver/startup.py @@ -69,6 +69,15 @@ def mmsolver_register_events(): mmSolver.tools.registerevents.tool.register_events() +def mmsolver_image_cache_initalize(): + """ + Initialise the mmSolver ImageCache. + """ + import mmSolver.tools.imagecache.lib + + mmSolver.tools.imagecache.lib.initialize() + + def mmsolver_startup(): """ Responsible for starting up mmSolver, including creating shelves, @@ -106,4 +115,7 @@ def mmsolver_startup(): # Register Events. maya.utils.executeDeferred(mmsolver_register_events) + + # Start up the image cache. + maya.utils.executeDeferred(mmsolver_image_cache_initalize) return diff --git a/python/mmSolver/tools/createimageplane/_lib/format.py b/python/mmSolver/tools/createimageplane/_lib/format.py new file mode 100644 index 000000000..7bb01f199 --- /dev/null +++ b/python/mmSolver/tools/createimageplane/_lib/format.py @@ -0,0 +1,154 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.utils.constant as const_utils +import mmSolver.tools.imagecache.lib as lib +import mmSolver.tools.createimageplane._lib.constant as imageplane_const +import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib + +LOG = mmSolver.logger.get_logger() + +# Shorter alias. +_MM_IMAGE_PLANE_SHAPE_V2 = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 + + +def format_image_sequence_size(image_plane_shp): + """ + Look up node values and format as text. + + Example output: + '2,346.1MB (23.7MB x 102 frames)' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + + frame_size_bytes = imageplane_lib.get_frame_size_bytes(image_plane_shp) + frame_count = imageplane_lib.get_frame_count(image_plane_shp) + image_sequence_size_bytes = imageplane_lib.get_image_sequence_size_bytes( + image_plane_shp + ) + + frame_size_mb = frame_size_bytes / const_utils.BYTES_TO_MEGABYTES + seq_size_mb = image_sequence_size_bytes / const_utils.BYTES_TO_MEGABYTES + text = '{seq_size_mb:0,.1f} MB ({frame_size_mb:0,.1f} MB x {frame_count} frames)' + return text.format( + seq_size_mb=seq_size_mb, frame_size_mb=frame_size_mb, frame_count=frame_count + ) + + +def _format_cache_used(used_bytes, capacity_bytes): + """ + Look up node values and format as text. + + Example text: + ' 42.1% (3.53 GB) of 8.00 GB' + """ + assert isinstance(used_bytes, int) + assert isinstance(capacity_bytes, int) + + usage_percent = 0 + usage_gigabytes = 0 + capacity_gigabyte = 0 + if capacity_bytes > 0: + usage_percent = (used_bytes / capacity_bytes) * 100.0 + usage_gigabytes = used_bytes / const_utils.BYTES_TO_GIGABYTES + capacity_gigabyte = capacity_bytes / const_utils.BYTES_TO_GIGABYTES + + text = '{usage_percent:3.1f}% ({usage_gigabytes:0,.2f} GB) of {capacity_gigabyte:0,.2f} GB' + return text.format( + usage_percent=usage_percent, + usage_gigabytes=usage_gigabytes, + capacity_gigabyte=capacity_gigabyte, + ) + + +def format_cache_gpu_used(image_plane_shp): + """ + Look up node values and format text. + + Example text: + ' 42.1% (3.53GB) of 8.00GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + + used_bytes = lib.get_gpu_cache_used_bytes() + capacity_bytes = lib.get_gpu_cache_capacity_bytes() + return _format_cache_used(int(used_bytes), int(capacity_bytes)) + + +def format_cache_cpu_used(image_plane_shp): + """ + Look up node values and format as text. + + Example text: + ' 23.1% (34.24 GB) of 240.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + + used_bytes = lib.get_cpu_cache_used_bytes() + capacity_bytes = lib.get_cpu_cache_capacity_bytes() + return _format_cache_used(int(used_bytes), int(capacity_bytes)) + + +def format_memory_gpu_available(image_plane_shp): + """ + Look up node values and format as text. + + Example text: + 'GPU 8.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + + memory_bytes = lib.get_gpu_memory_total_bytes() + memory_gigabytes = 0.0 + if memory_bytes > 0: + memory_gigabytes = memory_bytes / const_utils.BYTES_TO_GIGABYTES + + text = 'GPU: {memory_gigabytes:0,.2f} GB' + return text.format( + memory_gigabytes=memory_gigabytes, + ) + + +def format_memory_cpu_available(image_plane_shp): + """ + Look up node values and format as text. + + Example text: + 'CPU: 240.00 GB' + """ + assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 + + memory_bytes = lib.get_cpu_memory_total_bytes() + memory_gigabytes = 0.0 + if memory_bytes > 0: + memory_gigabytes = memory_bytes / const_utils.BYTES_TO_GIGABYTES + + text = 'CPU: {memory_gigabytes:0,.2f} GB' + return text.format( + memory_gigabytes=memory_gigabytes, + ) diff --git a/python/mmSolver/tools/createimageplane/lib.py b/python/mmSolver/tools/createimageplane/lib.py index f9bc3c3c5..a9ba2b453 100644 --- a/python/mmSolver/tools/createimageplane/lib.py +++ b/python/mmSolver/tools/createimageplane/lib.py @@ -79,6 +79,14 @@ from mmSolver.tools.createimageplane._lib.utilities import get_default_image_path +from mmSolver.tools.createimageplane._lib.format import ( + format_image_sequence_size, + format_cache_gpu_used, + format_cache_cpu_used, + format_memory_gpu_available, + format_memory_cpu_available, +) + # Stop users from accessing the internal functions of this sub-module. __all__ = [ @@ -87,4 +95,9 @@ 'set_image_sequence', 'get_default_image_path', 'DEFAULT_IMAGE_SEQUENCE_ATTR_NAME', + 'format_image_sequence_size', + 'format_cache_gpu_used', + 'format_cache_cpu_used', + 'format_memory_gpu_available', + 'format_memory_cpu_available', ] diff --git a/python/mmSolver/tools/imagecache/_lib/erase.py b/python/mmSolver/tools/imagecache/_lib/erase.py new file mode 100644 index 000000000..d36c9b022 --- /dev/null +++ b/python/mmSolver/tools/imagecache/_lib/erase.py @@ -0,0 +1,192 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.imagecache._lib.imagecache_cmd as imagecache_cmd +import mmSolver.tools.createimageplane._lib.constant as imageplane_const +import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib + +LOG = mmSolver.logger.get_logger() + +# Shorter alias. +_IMAGE_PLANE_SHAPE = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 + + +def erase_gpu_group_items(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache([group_name], edit=True, gpuEraseGroupItems=True) + + +def erase_cpu_group_items(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache([group_name], edit=True, cpuEraseGroupItems=True) + + +def erase_gpu_groups_items(group_names): + assert len(group_names) >= 0 + return maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroupItems=True) + + +def erase_cpu_groups_items(group_names): + assert len(group_names) >= 0 + return maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroupItems=True) + + +def erase_gpu_image_items(items): + assert len(items) >= 0 + return maya.cmds.mmImageCache(items, edit=True, gpuEraseItems=True) + + +def erase_cpu_image_items(items): + assert len(items) >= 0 + return maya.cmds.mmImageCache(items, edit=True, cpuEraseItems=True) + + +def erase_all_images_on_image_plane_slots(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE + LOG.info( + 'erase_all_images_on_image_plane_slots: cache_type=%r, image_plane_shp=%r', + cache_type, + image_plane_shp, + ) + + slots = imageplane_lib.get_image_sequence_for_all_slots(image_plane_shp) + LOG.info('erase_all_images_on_image_plane_slots: slots=%r', slots) + + group_names = imagecache_cmd.get_cache_group_names(cache_type) + slots_to_erase = [] + for slot in slots: + if slot not in group_names: + LOG.warn('Slot not found in groups: group_names=%r', group_names) + continue + slots_to_erase.append(slot) + + return imagecache_cmd.cache_erase_groups_items(cache_type, slots_to_erase) + + +def erase_images_in_active_image_plane_slot(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE + LOG.info( + 'erase_images_in_active_image_plane_slot: cache_type=%r, image_plane_shp=%r', + cache_type, + image_plane_shp, + ) + + slot = imageplane_lib.get_image_sequence_for_active_slot(image_plane_shp) + LOG.info('erase_images_in_active_image_plane_slot: slot=%r', slot) + + return + + +def erase_images_in_unused_image_plane_slots(cache_type, image_plane_shp): + assert cache_type in const.CACHE_TYPE_VALUES + assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE + LOG.info( + 'erase_images_in_unused_image_plane_slots: cache_type=%r, image_plane_shp=%r', + cache_type, + image_plane_shp, + ) + slots = imageplane_lib.get_image_sequence_for_unused_slots(image_plane_shp) + LOG.info('erase_images_in_unused_image_plane_slots: slots=%r', slots) + return + + +def erase_image_sequence(cache_type, file_pattern, start_frame, end_frame): + assert cache_type in const.CACHE_TYPE_VALUES + assert isinstance(file_pattern, str) + assert isinstance(start_frame, int) + assert isinstance(end_frame, int) + LOG.info( + 'erase_image_sequence: ' + 'cache_type=%r, file_pattern=%r, start_frame=%r, end_frame=%r', + cache_type, + file_pattern, + start_frame, + end_frame, + ) + + item_count = imagecache_cmd.get_cache_group_item_count(cache_type, file_pattern) + if item_count == 0: + LOG.warn('File pattern does not have any items. item_count=%r', item_count) + return + + item_names = imagecache_cmd.get_gpu_group_item_names(cache_type, file_pattern) + assert len(item_names) > 0 + + # TODO: + # + # 1) Evaluate the file_pattern for start_frame to end_frame. + # + # 2) If the evaluated file path is in the image cache, add it to + # the list to be removed. + # + # 3) Remove named items from the cache. + + raise NotImplementedError + + +def erase_all_inactive_images(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES + # Removes all the items in the cache that cannot be 'reached' by + # any of the image planes. + LOG.info( + 'erase_all_inactive_images: cache_type=%r', + cache_type, + ) + + # TODO: + # + # 1) Get all image planes. + # + # 2) Get all active slots on all image planes. + # + # 3) Get all groups in the image cache. + # + # 4) For any group that is not in the active slots, remove it. + + raise NotImplementedError + + +def erase_all_images(cache_type): + assert cache_type in const.CACHE_TYPE_VALUES + LOG.info( + 'erase_images_in_unused_image_plane_slots: cache_type=%r', + cache_type, + ) + + # TODO: Clear image cache completely. + + # 1) Get the ImageCache capacities (CPU and GPU). + # 2) Set the ImageCache capacity (CPU and GPU) to zero. + # 3) Restore the ImageCache capacities (CPU and GPU). + + return diff --git a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py new file mode 100644 index 000000000..7c3987943 --- /dev/null +++ b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py @@ -0,0 +1,104 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def get_gpu_cache_item_count(): + return int(maya.cmds.mmImageCache(query=True, gpuItemCount=True)) + + +def get_cpu_cache_item_count(): + return int(maya.cmds.mmImageCache(query=True, cpuItemCount=True)) + + +def get_gpu_cache_group_names(): + return maya.cmds.mmImageCache(query=True, gpuGroupNames=True) + + +def get_cpu_cache_group_names(): + return maya.cmds.mmImageCache(query=True, cpuGroupNames=True) + + +def get_gpu_cache_group_count(): + return int(maya.cmds.mmImageCache(query=True, gpuGroupCount=True)) + + +def get_cpu_cache_group_count(): + return int(maya.cmds.mmImageCache(query=True, cpuGroupCount=True)) + + +def get_gpu_cache_group_item_count(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return int(maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True)) + + +def get_cpu_cache_group_item_count(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return int(maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True)) + + +def get_gpu_cache_group_item_names(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) + + +def get_cpu_cache_group_item_names(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + return maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) + + +def get_gpu_cache_used_bytes(): + return int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) + + +def get_cpu_cache_used_bytes(): + return int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) + + +def get_gpu_cache_capacity_bytes(): + return int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) + + +def get_cpu_cache_capacity_bytes(): + return int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) + + +def set_gpu_cache_capacity_bytes(size_bytes): + assert isinstance(size_bytes, int) + return maya.cmds.mmImageCache(edit=True, gpuCapacity=size_bytes) + + +def set_cpu_cache_capacity_bytes(size_bytes): + assert isinstance(size_bytes, int) + return maya.cmds.mmImageCache(edit=True, cpuCapacity=size_bytes) diff --git a/python/mmSolver/tools/imagecache/_lib/initialize.py b/python/mmSolver/tools/imagecache/_lib/initialize.py new file mode 100644 index 000000000..3d6e7ebd5 --- /dev/null +++ b/python/mmSolver/tools/imagecache/_lib/initialize.py @@ -0,0 +1,99 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger +import mmSolver.api as mmapi +import mmSolver.utils.constant as const_utils +import mmSolver.tools.imagecache._lib.imagecache_cmd as imagecache_cmd +import mmSolver.tools.imagecache.config as config + + +LOG = mmSolver.logger.get_logger() + + +def _format_gigabytes(memory_bytes): + memory_gigabytes = 0.0 + if memory_bytes > 0: + memory_gigabytes = memory_bytes / const_utils.BYTES_TO_GIGABYTES + + text = '{:0,.2f} GB' + return text.format(memory_gigabytes) + + +def _format_percent(percent): + text = '{:0.1f}'.format(percent) + return text + '%' + + +def _ensure_plugin_loaded(): + """ + Loads all plug-ins required for the ImageCache. + + :raises: mmapi.SolverNotAvailable + """ + mmapi.load_plugin() + command_names = ('mmImageCache', 'mmMemoryGPU', 'mmMemorySystem') + for command_name in command_names: + if command_name not in dir(maya.cmds): + raise mmapi.SolverNotAvailable + return + + +def initialize(): + """ + Set Image Cache capacities from preferences. + """ + LOG.info('MM Solver Initialize Image Cache...') + + # TODO: Install a callback to be called each time a new scene is + # opened. We will look for enabled scene override values for the + # cache and set it, otherwise we use the defaults. + + _ensure_plugin_loaded() + + capacity_data = config.resolve_capacity_data() + gpu_capacity_bytes = capacity_data.gpu_resolved_capacity.size_bytes + cpu_capacity_bytes = capacity_data.cpu_resolved_capacity.size_bytes + gpu_capacity_percent = capacity_data.gpu_resolved_capacity.percent + cpu_capacity_percent = capacity_data.cpu_resolved_capacity.percent + + # Set the image cache capacities. + gpu_capacty_gigabytes = _format_gigabytes(gpu_capacity_bytes) + cpu_capacty_gigabytes = _format_gigabytes(cpu_capacity_bytes) + gpu_capacity_percent = _format_percent(gpu_capacity_percent) + cpu_capacity_percent = _format_percent(cpu_capacity_percent) + + LOG.info( + 'Image Cache GPU Capacity: %s (%s)', gpu_capacty_gigabytes, gpu_capacity_percent + ) + LOG.info( + 'Image Cache CPU Capacity: %s (%s)', cpu_capacty_gigabytes, cpu_capacity_percent + ) + + imagecache_cmd.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) + imagecache_cmd.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) + return diff --git a/python/mmSolver/tools/imagecache/_lib/query_resources.py b/python/mmSolver/tools/imagecache/_lib/query_resources.py new file mode 100644 index 000000000..df4354758 --- /dev/null +++ b/python/mmSolver/tools/imagecache/_lib/query_resources.py @@ -0,0 +1,64 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def get_gpu_memory_total_bytes(): + return int(maya.cmds.mmMemoryGPU(query=True, total=True)) + + +def get_gpu_memory_used_bytes(): + return int(maya.cmds.mmMemoryGPU(query=True, used=True)) + + +def get_cpu_memory_total_bytes(): + # The value returned from the command is a 'str', because the + # value may be more than what a 32-bit integer can support, which + # is the largest integer size returned by the Maya API. Therefore + # we simply convert the string to a integer in Python (which + # supports more than 32-bit integers in the 'int' type). + value = maya.cmds.mmMemorySystem( + query=True, + systemPhysicalMemoryTotal=True, + ) + return int(value) + + +def get_cpu_memory_used_bytes(): + # The value returned from the command is a 'str', because the + # value may be more than what a 32-bit integer can support, which + # is the largest integer size returned by the Maya API. Therefore + # we simply convert the string to a integer in Python (which + # supports more than 32-bit integers in the 'int' type). + value = maya.cmds.mmMemorySystem( + query=True, + systemPhysicalMemoryUsed=True, + ) + return int(value) diff --git a/python/mmSolver/tools/imagecache/config.py b/python/mmSolver/tools/imagecache/config.py new file mode 100644 index 000000000..9d2788d6b --- /dev/null +++ b/python/mmSolver/tools/imagecache/config.py @@ -0,0 +1,78 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import mmSolver.logger +import mmSolver.tools.imagecache.config_file as config_file +import mmSolver.tools.imagecache.config_scene as config_scene +import mmSolver.tools.createimageplane._lib.constant as imageplane_const + + +# Shorter alias. +_MM_IMAGE_PLANE_SHAPE_V2 = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 + + +LOG = mmSolver.logger.get_logger() +CapacityData = collections.namedtuple( + 'CapacityData', + [ + 'gpu_default_capacity', + 'cpu_default_capacity', + 'scene_override', + 'gpu_scene_capacity', + 'cpu_scene_capacity', + 'gpu_resolved_capacity', + 'cpu_resolved_capacity', + ], +) + + +def resolve_capacity_data(): + # Open Config file. + default_gpu_capacity = config_file.get_gpu_capacity_percent() + default_cpu_capacity = config_file.get_cpu_capacity_percent() + + # Check Maya scene options for image cache overrides. + scene_override = config_scene.get_cache_scene_override() + scene_gpu_capacity = config_scene.get_gpu_capacity_percent() + scene_cpu_capacity = config_scene.get_cpu_capacity_percent() + + # Apply override to 'resolved' values. + resolved_gpu_capacity = default_gpu_capacity + resolved_cpu_capacity = default_cpu_capacity + if scene_override is True: + resolved_gpu_capacity = scene_gpu_capacity + resolved_cpu_capacity = scene_cpu_capacity + + return CapacityData( + default_gpu_capacity, + default_cpu_capacity, + scene_override, + scene_gpu_capacity, + scene_cpu_capacity, + resolved_gpu_capacity, + resolved_cpu_capacity, + ) diff --git a/python/mmSolver/tools/imagecache/config_file.py b/python/mmSolver/tools/imagecache/config_file.py new file mode 100644 index 000000000..742343016 --- /dev/null +++ b/python/mmSolver/tools/imagecache/config_file.py @@ -0,0 +1,98 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.logger + +import mmSolver.utils.config +import mmSolver.tools.imagecache.config_utils as config_utils +import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.imagecache.lib as lib + +LOG = mmSolver.logger.get_logger() + + +# Initialised without a file path, because we only look up the file in +# the functions below. +# +# Users are not allowed to touch this variable. +# +# Note we never re-assign this variable. The Config object instance is +# always reused. +__CONFIG_PATH = mmSolver.utils.config.get_home_dir_path(const.CONFIG_FILE_NAME) +__CONFIG = mmSolver.utils.config.Config(__CONFIG_PATH) +__CONFIG.autoread = True +__CONFIG.autowrite = False + + +def _get_config(): + global __CONFIG + return __CONFIG + + +def _get_config_value(config, key, fallback): + """Query the attribute from the user's home directory. If the user's + option is saved, use that value instead. + """ + value = fallback + if config is not None: + value = config.get_value(key, fallback) + return value + + +def write(): + global __CONFIG + __CONFIG.write() + + +def get_gpu_capacity_percent(): + default_value = const.CONFIG_GPU_CAPACITY_PERCENT_DEFAULT_VALUE + assert isinstance(default_value, float) + name = const.CONFIG_GPU_CAPACITY_PERCENT_KEY + config = _get_config() + percent = _get_config_value(config, name, default_value) + total_bytes = lib.get_gpu_memory_total_bytes() + return config_utils.convert_to_capacity_value(percent, total_bytes) + + +def set_gpu_capacity_percent(value): + config_file = _get_config() + config_file.set_value(const.CONFIG_GPU_CAPACITY_PERCENT_KEY, value) + return + + +def get_cpu_capacity_percent(): + default_value = const.CONFIG_CPU_CAPACITY_PERCENT_DEFAULT_VALUE + assert isinstance(default_value, float) + name = const.CONFIG_CPU_CAPACITY_PERCENT_KEY + config = _get_config() + percent = _get_config_value(config, name, default_value) + total_bytes = lib.get_cpu_memory_total_bytes() + return config_utils.convert_to_capacity_value(percent, total_bytes) + + +def set_cpu_capacity_percent(value): + config_file = _get_config() + config_file.set_value(const.CONFIG_CPU_CAPACITY_PERCENT_KEY, value) + return diff --git a/python/mmSolver/tools/imagecache/config_scene.py b/python/mmSolver/tools/imagecache/config_scene.py new file mode 100644 index 000000000..c1eea3369 --- /dev/null +++ b/python/mmSolver/tools/imagecache/config_scene.py @@ -0,0 +1,78 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.logger +import mmSolver.utils.configmaya as configmaya +import mmSolver.tools.imagecache.config_utils as config_utils +import mmSolver.tools.imagecache.constant as const +import mmSolver.tools.imagecache._lib.query_resources as query_resources + +LOG = mmSolver.logger.get_logger() + + +def get_cache_scene_override(): + name = const.SCENE_OPTION_CAPACITY_OVERRIDE_KEY + value = configmaya.get_scene_option(name, default=None) + assert value is None or isinstance(value, bool) + return value + + +def set_cache_scene_override(value): + assert value is None or isinstance(value, bool) + name = const.SCENE_OPTION_CAPACITY_OVERRIDE_KEY + configmaya.set_scene_option(name, value, add_attr=True) + return + + +def get_gpu_capacity_percent(): + default_value = const.SCENE_OPTION_GPU_CAPACITY_PERCENT_DEFAULT_VALUE + assert isinstance(default_value, float) + name = const.SCENE_OPTION_GPU_CAPACITY_PERCENT_KEY + percent = configmaya.get_scene_option(name, default=default_value) + total_bytes = query_resources.get_gpu_memory_total_bytes() + return config_utils.convert_to_capacity_value(percent, total_bytes) + + +def set_gpu_capacity_percent(percent): + assert isinstance(percent, float) + name = const.SCENE_OPTION_GPU_CAPACITY_PERCENT_KEY + percent = configmaya.set_scene_option(name, percent, add_attr=True) + return + + +def get_cpu_capacity_percent(): + default_value = const.SCENE_OPTION_CPU_CAPACITY_PERCENT_DEFAULT_VALUE + assert isinstance(default_value, float) + name = const.SCENE_OPTION_CPU_CAPACITY_PERCENT_KEY + percent = configmaya.get_scene_option(name, default=default_value) + total_bytes = query_resources.get_cpu_memory_total_bytes() + return config_utils.convert_to_capacity_value(percent, total_bytes) + + +def set_cpu_capacity_percent(percent): + assert isinstance(percent, float) + name = const.SCENE_OPTION_CPU_CAPACITY_PERCENT_KEY + percent = configmaya.set_scene_option(name, percent, add_attr=True) + return diff --git a/python/mmSolver/tools/imagecache/config_utils.py b/python/mmSolver/tools/imagecache/config_utils.py new file mode 100644 index 000000000..769c789e8 --- /dev/null +++ b/python/mmSolver/tools/imagecache/config_utils.py @@ -0,0 +1,39 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Functions to control the image cache. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import collections + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() +CapacityValue = collections.namedtuple('CapacityValue', ['size_bytes', 'percent']) + + +def convert_to_capacity_value(percent, total_bytes): + assert isinstance(percent, float) + ratio = percent / 100.0 + size_bytes = int(total_bytes * ratio) + return CapacityValue(size_bytes, percent) + diff --git a/python/mmSolver/tools/imagecache/constant.py b/python/mmSolver/tools/imagecache/constant.py index 5622ccb47..84370a502 100644 --- a/python/mmSolver/tools/imagecache/constant.py +++ b/python/mmSolver/tools/imagecache/constant.py @@ -19,9 +19,27 @@ Image Cache constants. """ + CACHE_TYPE_GPU = 'gpu' CACHE_TYPE_CPU = 'cpu' CACHE_TYPE_VALUES = [ CACHE_TYPE_GPU, CACHE_TYPE_CPU, ] + +CONFIG_FILE_NAME = 'tools_imagecache.json' + +CONFIG_GPU_CAPACITY_PERCENT_KEY = 'data/gpu_capacity_percent' +CONFIG_GPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 + +CONFIG_CPU_CAPACITY_PERCENT_KEY = 'data/cpu_capacity_percent' +CONFIG_CPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 + +SCENE_OPTION_CAPACITY_OVERRIDE_KEY = 'mmSolver_imagecache_capacity_override' +SCENE_OPTION_CAPACITY_OVERRIDE_DEFAULT_VALUE = False + +SCENE_OPTION_GPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_gpu_capacity_percent' +SCENE_OPTION_GPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 + +SCENE_OPTION_CPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_cpu_capacity_percent' +SCENE_OPTION_CPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 679d9e65e..41d259011 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -23,459 +23,82 @@ from __future__ import division from __future__ import print_function -import maya.cmds - -import mmSolver.logger -import mmSolver.tools.imagecache.constant as const -import mmSolver.tools.createimageplane._lib.constant as imageplane_const -import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib - -LOG = mmSolver.logger.get_logger() - -# Shorter alias. -_MM_IMAGE_PLANE_SHAPE_V2 = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 - -# Memory Conversion -_BYTES_TO_KILOBYTES = 1024 # int(pow(2, 10)) -_BYTES_TO_MEGABYTES = 1048576 # int(pow(2, 20)) -_BYTES_TO_GIGABYTES = 1073741824 # int(pow(2, 30)) -_KILOBYTES_TO_MEGABYTES = 1024 # int(pow(2, 10)) -_KILOBYTES_TO_GIGABYTES = 1048576 # int(pow(2, 20)) - - -def format_image_sequence_size(image_plane_shp): - """ - Look up node values and format as text. - - Example output: - '2,346.1MB (23.7MB x 102 frames)' - """ - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.debug( - 'format_image_sequence_size: image_plane_shp=%r', - image_plane_shp, - ) - - frame_size_bytes = imageplane_lib.get_frame_size_bytes(image_plane_shp) - frame_count = imageplane_lib.get_frame_count(image_plane_shp) - image_sequence_size_bytes = imageplane_lib.get_image_sequence_size_bytes( - image_plane_shp - ) - - frame_size_mb = frame_size_bytes / _BYTES_TO_MEGABYTES - seq_size_mb = image_sequence_size_bytes / _BYTES_TO_MEGABYTES - text = '{seq_size_mb:0,.1f} MB ({frame_size_mb:0,.1f} MB x {frame_count} frames)' - return text.format( - seq_size_mb=seq_size_mb, frame_size_mb=frame_size_mb, frame_count=frame_count - ) - - -def _format_cache_used(used_bytes, capacity_bytes): - """ - Look up node values and format as text. - - Example text: - ' 42.1% (3.53 GB) of 8.00 GB' - """ - assert isinstance(used_bytes, int) - assert isinstance(capacity_bytes, int) - - usage_percent = 0 - usage_gigabytes = 0 - capacity_gigabyte = 0 - if capacity_bytes > 0: - usage_percent = (used_bytes / capacity_bytes) * 100.0 - usage_gigabytes = used_bytes / _BYTES_TO_GIGABYTES - capacity_gigabyte = capacity_bytes / _BYTES_TO_GIGABYTES - - text = '{usage_percent:3.1f}% ({usage_gigabytes:0,.2f} GB) of {capacity_gigabyte:0,.2f} GB' - return text.format( - usage_percent=usage_percent, - usage_gigabytes=usage_gigabytes, - capacity_gigabyte=capacity_gigabyte, - ) - - -def format_cache_gpu_used(image_plane_shp): - """ - Look up node values and format text. - - Example text: - ' 42.1% (3.53GB) of 8.00GB' - """ - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.debug( - 'format_cache_gpu_used: image_plane_shp=%r', - image_plane_shp, - ) - - used_bytes = get_gpu_cache_used_bytes() - capacity_bytes = get_gpu_cache_capacity_bytes() - return _format_cache_used(int(used_bytes), int(capacity_bytes)) - - -def format_cache_cpu_used(image_plane_shp): - """ - Look up node values and format as text. - - Example text: - ' 23.1% (34.24 GB) of 240.00 GB' - """ - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.debug( - 'format_cache_cpu_used: image_plane_shp=%r', - image_plane_shp, - ) - - used_bytes = get_cpu_cache_used_bytes() - capacity_bytes = get_cpu_cache_capacity_bytes() - return _format_cache_used(int(used_bytes), int(capacity_bytes)) - - -def format_memory_gpu_available(image_plane_shp): - """ - Look up node values and format as text. - - Example text: - 'GPU 8.00 GB' - """ - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.debug( - 'format_memory_available: image_plane_shp=%r', - image_plane_shp, - ) - - memory_bytes = get_gpu_memory_total_bytes() - memory_gigabytes = 0.0 - if memory_bytes > 0: - memory_gigabytes = memory_bytes / _BYTES_TO_GIGABYTES - - text = 'GPU: {memory_gigabytes:0,.2f} GB' - return text.format( - memory_gigabytes=memory_gigabytes, - ) - - -def format_memory_cpu_available(image_plane_shp): - """ - Look up node values and format as text. - - Example text: - 'CPU: 240.00 GB' - """ - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.debug( - 'format_memory_available: image_plane_shp=%r', - image_plane_shp, - ) - - memory_bytes = get_cpu_memory_total_bytes() - memory_gigabytes = 0.0 - if memory_bytes > 0: - memory_gigabytes = memory_bytes / _BYTES_TO_GIGABYTES - - text = 'CPU: {memory_gigabytes:0,.2f} GB' - return text.format( - memory_gigabytes=memory_gigabytes, - ) - - -def get_gpu_cache_item_count(): - return int(maya.cmds.mmImageCache(query=True, gpuItemCount=True)) - - -def get_cpu_cache_item_count(): - return int(maya.cmds.mmImageCache(query=True, cpuItemCount=True)) - - -def get_gpu_cache_group_names(): - return maya.cmds.mmImageCache(query=True, gpuGroupNames=True) - - -def get_cpu_cache_group_names(): - return maya.cmds.mmImageCache(query=True, cpuGroupNames=True) - - -def get_gpu_cache_group_count(): - return int(maya.cmds.mmImageCache(query=True, gpuGroupCount=True)) - - -def get_cpu_cache_group_count(): - return int(maya.cmds.mmImageCache(query=True, cpuGroupCount=True)) - - -def get_gpu_cache_group_item_count(group_name): - assert isinstance(group_name, str) - assert len(group_name) > 0 - return int(maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True)) - - -def get_cpu_cache_group_item_count(group_name): - assert isinstance(group_name, str) - assert len(group_name) > 0 - return int(maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True)) - - -def get_gpu_cache_group_item_names(group_name): - assert isinstance(group_name, str) - assert len(group_name) > 0 - return maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) - - -def get_cpu_cache_group_item_names(group_name): - assert isinstance(group_name, str) - assert len(group_name) > 0 - return maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) - - -def get_gpu_cache_used_bytes(): - return int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) - - -def get_cpu_cache_used_bytes(): - return int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) - - -def get_gpu_cache_capacity_bytes(): - return int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) - - -def get_cpu_cache_capacity_bytes(): - return int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) - - -def set_gpu_cache_capacity_bytes(size_bytes): - assert isinstance(size_bytes, int) - return maya.cmds.mmImageCache(edit=True, gpuCapacity=size_bytes) - - -def set_cpu_cache_capacity_bytes(size_bytes): - assert isinstance(size_bytes, int) - return maya.cmds.mmImageCache(edit=True, cpuCapacity=size_bytes) - - -def get_gpu_memory_total_bytes(): - return int(maya.cmds.mmMemoryGPU(query=True, total=True)) - - -def get_gpu_memory_used_bytes(): - return int(maya.cmds.mmMemoryGPU(query=True, used=True)) - - -def get_cpu_memory_total_bytes(): - return int( - maya.cmds.mmMemorySystem( - query=True, - systemPhysicalMemoryTotal=True, - ) - ) - - -def get_cpu_memory_used_bytes(): - return int( - maya.cmds.mmMemorySystem( - query=True, - systemPhysicalMemoryUsed=True, - ) - ) - - -def get_cache_group_names(cache_type): - assert cache_type in const.CACHE_TYPE_VALUES - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = get_gpu_cache_group_names() - elif cache_type == const.CACHE_TYPE_CPU: - value = get_cpu_cache_group_names() - return value - - -def get_cache_group_count(cache_type): - assert cache_type in const.CACHE_TYPE_VALUES - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = get_gpu_cache_group_count() - elif cache_type == const.CACHE_TYPE_CPU: - value = get_cpu_cache_group_count() - return value - - -def get_cache_group_item_count(cache_type, group_name): - assert cache_type in const.CACHE_TYPE_VALUES - assert isinstance(group_name, str) - assert len(group_name) > 0 - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = get_gpu_cache_group_item_count(group_name) - elif cache_type == const.CACHE_TYPE_CPU: - value = get_cpu_cache_group_item_count(group_name) - return value - - -def get_cache_group_item_names(cache_type, group_name): - assert cache_type in const.CACHE_TYPE_VALUES - assert isinstance(group_name, str) - assert len(group_name) > 0 - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = get_gpu_cache_group_item_names(group_name) - elif cache_type == const.CACHE_TYPE_CPU: - value = get_cpu_cache_group_item_names(group_name) - return value - - -def cache_erase_group_items(cache_type, group_name): - assert cache_type in const.CACHE_TYPE_VALUES - assert isinstance(group_name, str) - assert len(group_name) > 0 - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache([group_name], edit=True, gpuEraseGroupItems=True) - elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache([group_name], edit=True, cpuEraseGroupItems=True) - return value - - -def cache_erase_groups_items(cache_type, group_names): - assert cache_type in const.CACHE_TYPE_VALUES - assert len(group_names) >= 0 - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroupItems=True) - elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroupItems=True) - return value - - -def cache_erase_items(cache_type, items): - assert cache_type in const.CACHE_TYPE_VALUES - assert len(items) >= 0 - value = None - if cache_type == const.CACHE_TYPE_GPU: - value = maya.cmds.mmImageCache(items, edit=True, gpuEraseItems=True) - elif cache_type == const.CACHE_TYPE_CPU: - value = maya.cmds.mmImageCache(items, edit=True, cpuEraseItems=True) - return value - - -def cache_remove_all_image_plane_slots(cache_type, image_plane_shp): - assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( - 'cache_remove_all_image_plane_slots: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) - - slots = imageplane_lib.get_image_sequence_for_all_slots(image_plane_shp) - LOG.info('cache_remove_all_image_plane_slots: slots=%r', slots) - - group_names = get_cache_group_names(cache_type) - slots_to_remove = [] - for slot in slots: - if slot not in group_names: - LOG.warn('Slot not found in groups: group_names=%r', group_names) - continue - slots_to_remove.append(slot) - - return cache_erase_groups_items(cache_type, slots_to_remove) - - -def cache_remove_active_image_plane_slot(cache_type, image_plane_shp): - assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( - 'cache_remove_active_image_plane_slot: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) - - slot = imageplane_lib.get_image_sequence_for_active_slot(image_plane_shp) - LOG.info('cache_remove_active_image_plane_slot: slot=%r', slot) - - return - - -def cache_remove_unused_image_plane_slots(cache_type, image_plane_shp): - assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _MM_IMAGE_PLANE_SHAPE_V2 - LOG.info( - 'cache_remove_unused_image_plane_slots: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) - slots = imageplane_lib.get_image_sequence_for_unused_slots(image_plane_shp) - LOG.info('cache_remove_unused_image_plane_slots: slots=%r', slots) - return - - -def cache_remove_all(cache_type): - assert cache_type in const.CACHE_TYPE_VALUES - LOG.info( - 'cache_remove_unused_image_plane_slots: cache_type=%r', - cache_type, - ) - - # TODO: Clear image cache completely. - - # 1) Get the ImageCache capacities (CPU and GPU). - # 2) Set the ImageCache capacity (CPU and GPU) to zero. - # 3) Restore the ImageCache capacities (CPU and GPU). - - return - - -def cache_remove_image_sequence(cache_type, file_pattern, start_frame, end_frame): - assert cache_type in const.CACHE_TYPE_VALUES - assert isinstance(file_pattern, str) - assert isinstance(start_frame, int) - assert isinstance(end_frame, int) - LOG.info( - 'cache_remove_image_sequence: ' - 'cache_type=%r, file_pattern=%r, start_frame=%r, end_frame=%r', - cache_type, - file_pattern, - start_frame, - end_frame, - ) - - item_count = get_cache_group_item_count(cache_type, file_pattern) - if item_count == 0: - LOG.warn('File pattern does not have any items. item_count=%r', item_count) - return - - item_names = get_cache_group_item_names(cache_type, file_pattern) - assert len(item_names) > 0 - - # TODO: - # - # 1) Evaluate the file_pattern for start_frame to end_frame. - # - # 2) If the evaluated file path is in the image cache, add it to - # the list to be removed. +from mmSolver.tools.imagecache._lib.initialize import initialize +from mmSolver.tools.imagecache._lib.query_resources import ( + get_gpu_memory_total_bytes, + get_gpu_memory_used_bytes, + get_cpu_memory_total_bytes, + get_cpu_memory_used_bytes, +) +from mmSolver.tools.imagecache._lib.imagecache_cmd import ( + get_gpu_cache_item_count, + get_cpu_cache_item_count, + get_gpu_cache_group_names, + get_cpu_cache_group_names, + get_gpu_cache_group_count, + get_cpu_cache_group_count, + get_gpu_cache_group_item_count, + get_cpu_cache_group_item_count, + get_gpu_cache_group_item_names, + get_cpu_cache_group_item_names, + get_gpu_cache_used_bytes, + get_cpu_cache_used_bytes, + get_gpu_cache_capacity_bytes, + get_cpu_cache_capacity_bytes, + set_gpu_cache_capacity_bytes, + set_cpu_cache_capacity_bytes, +) +from mmSolver.tools.imagecache._lib.erase import ( + erase_gpu_group_items, + erase_cpu_group_items, + erase_gpu_groups_items, + erase_cpu_groups_items, + erase_gpu_image_items, + erase_cpu_image_items, + erase_all_images_on_image_plane_slots, + erase_images_in_active_image_plane_slot, + erase_images_in_unused_image_plane_slots, + erase_image_sequence, + erase_all_inactive_images, + erase_all_images, +) + +# Stop users from accessing the internal functions of this sub-module. +__all__ = [ + 'initialize', # - # 3) Remove named items from the cache. - - raise NotImplementedError - - -def cache_remove_all_inactive(cache_type): - assert cache_type in const.CACHE_TYPE_VALUES - # Removes all the items in the cache that cannot be 'reached' by - # any of the image planes. - LOG.info( - 'cache_remove_all_inactive: cache_type=%r', - cache_type, - ) - - # TODO: + 'get_gpu_memory_total_bytes', + 'get_gpu_memory_used_bytes', + 'get_cpu_memory_total_bytes', + 'get_cpu_memory_used_bytes', # - # 1) Get all image planes. + 'get_gpu_cache_item_count', + 'get_cpu_cache_item_count', + 'get_gpu_cache_group_names', + 'get_cpu_cache_group_names', + 'get_gpu_cache_group_count', + 'get_cpu_cache_group_count', + 'get_gpu_cache_group_item_count', + 'get_cpu_cache_group_item_count', + 'get_gpu_cache_group_item_names', + 'get_cpu_cache_group_item_names', + 'get_gpu_cache_used_bytes', + 'get_cpu_cache_used_bytes', + 'get_gpu_cache_capacity_bytes', + 'get_cpu_cache_capacity_bytes', + 'set_gpu_cache_capacity_bytes', + 'set_cpu_cache_capacity_bytes', # - # 2) Get all active slots on all image planes. - # - # 3) Get all groups in the image cache. - # - # 4) For any group that is not in the active slots, remove it. - - raise NotImplementedError + 'erase_gpu_group_items', + 'erase_cpu_group_items', + 'erase_gpu_groups_items', + 'erase_cpu_groups_items', + 'erase_gpu_image_items', + 'erase_cpu_image_items', + 'erase_all_images_on_image_plane_slots', + 'erase_images_in_active_image_plane_slot', + 'erase_images_in_unused_image_plane_slots', + 'erase_image_sequence', + 'erase_all_inactive_images', + 'erase_all_images', +] diff --git a/python/mmSolver/tools/imagecacheprefs/constant.py b/python/mmSolver/tools/imagecacheprefs/constant.py index 6518364f0..e38753b88 100644 --- a/python/mmSolver/tools/imagecacheprefs/constant.py +++ b/python/mmSolver/tools/imagecacheprefs/constant.py @@ -23,11 +23,5 @@ CONFIG_FILE_NAME = "tools_imagecacheprefs.json" -CONFIG_SCENE_CAPACITY_OVERRIDE_KEY = 'mmSolver_imagecache_capacity_override' -CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_gpu_capacity_percent' -CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY = 'mmSolver_imagecache_cpu_capacity_percent' - CONFIG_UPDATE_EVERY_N_SECONDS_KEY = 'data/update_every_n_seconds' CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE = 2 -CONFIG_GPU_CAPACITY_PERCENT_KEY = 'data/gpu_capacity_percent' -CONFIG_CPU_CAPACITY_PERCENT_KEY = 'data/cpu_capacity_percent' diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py index ff07c856b..59e79d208 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -21,7 +21,6 @@ """ import os -import collections import mmSolver.ui.qtpyutils as qtpyutils @@ -32,19 +31,17 @@ import mmSolver.logger import mmSolver.utils.config as config_utils -import mmSolver.utils.configmaya as configmaya import mmSolver.utils.constant as const_utils import mmSolver.utils.math as math_utils +import mmSolver.tools.imagecache.config_file as config_file +import mmSolver.tools.imagecache.config_scene as config_scene import mmSolver.tools.imagecache.lib as lib -import mmSolver.tools.imagecacheprefs.constant as const +import mmSolver.tools.imagecacheprefs.constant as tool_const import mmSolver.tools.imagecacheprefs.ui.ui_imagecacheprefs_layout as ui_imagecacheprefs_layout LOG = mmSolver.logger.get_logger() -CapacityValue = collections.namedtuple('CapacityValue', ['size_bytes', 'percent']) - - def set_memory_total_label( label, size_bytes, @@ -150,9 +147,9 @@ def set_capacity_widgets( return -def get_config(): +def get_tool_config(): """Get the Image Cache config object or None.""" - file_name = const.CONFIG_FILE_NAME + file_name = tool_const.CONFIG_FILE_NAME config_path = config_utils.get_home_dir_path(file_name) config = config_utils.Config(config_path) config.set_autoread(False) @@ -162,8 +159,9 @@ def get_config(): return config -def get_config_value(config, key, fallback): - """Query the attribute from the user's home directory. If the user's +def _get_tool_config_value(config, key, fallback): + """ + Query the attribute from the user's home directory. If the user's option is saved, use that value instead. """ value = fallback @@ -172,117 +170,27 @@ def get_config_value(config, key, fallback): return value -def get_update_every_n_seconds(config, default_value=None): - if default_value is None: - default_value = const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE +def get_update_every_n_seconds(config): + default_value = tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE assert isinstance(default_value, int) - name = const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY - seconds = get_config_value(config, name, default_value) + name = tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY + seconds = _get_tool_config_value(config, name, default_value) assert isinstance(seconds, int) return seconds -def get_gpu_cache_capacity_percent_default(config, default_value=None): - if default_value is None: - default_value = 0.0 - assert isinstance(default_value, float) - name = const.CONFIG_GPU_CAPACITY_PERCENT_KEY - percent = get_config_value(config, name, default_value) - assert isinstance(percent, float) - ratio = percent / 100.0 - capacity_bytes = lib.get_gpu_memory_total_bytes() - size_bytes = int(capacity_bytes * ratio) - return CapacityValue(size_bytes, percent) - - -def get_gpu_cache_capacity_percent_default(config, default_value=None): - if default_value is None: - default_value = 0.0 - assert isinstance(default_value, float) - name = const.CONFIG_GPU_CAPACITY_PERCENT_KEY - percent = get_config_value(config, name, default_value) - assert isinstance(percent, float) - ratio = percent / 100.0 - capacity_bytes = lib.get_gpu_memory_total_bytes() - size_bytes = int(capacity_bytes * ratio) - return CapacityValue(size_bytes, percent) - - -def get_cpu_cache_capacity_percent_default(config, default_value=None): - if default_value is None: - default_value = 0.0 - assert isinstance(default_value, float) - name = const.CONFIG_CPU_CAPACITY_PERCENT_KEY - percent = get_config_value(config, name, default_value) - assert isinstance(percent, float) - ratio = percent / 100.0 - capacity_bytes = lib.get_cpu_memory_total_bytes() - size_bytes = int(capacity_bytes * ratio) - return CapacityValue(size_bytes, percent) - - -def get_cache_scene_override(): - name = const.CONFIG_SCENE_CAPACITY_OVERRIDE_KEY - value = configmaya.get_scene_option(name, default=None) - assert value is None or isinstance(value, bool) - return value - - -def get_gpu_cache_capacity_percent_scene(default_value=None): - if default_value is None: - default_value = 0.0 - assert isinstance(default_value, float) - name = const.CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY - percent = configmaya.get_scene_option(name, default=default_value) - assert isinstance(percent, float) - ratio = percent / 100.0 - capacity_bytes = lib.get_gpu_memory_total_bytes() - size_bytes = int(capacity_bytes * ratio) - return CapacityValue(size_bytes, percent) - - -def get_cpu_cache_capacity_percent_scene(default_value=None): - if default_value is None: - default_value = 0.0 - assert isinstance(default_value, float) - name = const.CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY - percent = configmaya.get_scene_option(name, default=default_value) - assert isinstance(percent, float) - ratio = percent / 100.0 - capacity_bytes = lib.get_cpu_memory_total_bytes() - size_bytes = int(capacity_bytes * ratio) - return CapacityValue(size_bytes, percent) - - -def set_cache_scene_override(value): - assert value is None or isinstance(value, bool) - name = const.CONFIG_SCENE_CAPACITY_OVERRIDE_KEY - configmaya.set_scene_option(name, value, add_attr=True) - return - - -def set_gpu_cache_capacity_percent_scene(percent): - assert isinstance(percent, float) - name = const.CONFIG_SCENE_GPU_CAPACITY_PERCENT_KEY - percent = configmaya.set_scene_option(name, percent, add_attr=True) - return - - -def set_cpu_cache_capacity_percent_scene(percent): - assert isinstance(percent, float) - name = const.CONFIG_SCENE_CPU_CAPACITY_PERCENT_KEY - percent = configmaya.set_scene_option(name, percent, add_attr=True) - return - - class ImageCachePrefsLayout(QtWidgets.QWidget, ui_imagecacheprefs_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): super(ImageCachePrefsLayout, self).__init__(*args, **kwargs) self.setupUi(self) + self._config = get_tool_config() + # Update timer. self.update_timer = QtCore.QTimer() - milliseconds = int(const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE * 1000) + milliseconds = int( + tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_DEFAULT_VALUE * 1000 + ) self.update_timer.setInterval(milliseconds) self.update_timer.timeout.connect(self.update_resource_values) self.update_timer.start() @@ -502,10 +410,9 @@ def _update_every_changed(self, value): milliseconds = int(value * 1000) self.update_timer.setInterval(milliseconds) - config = get_config() - if config is not None: - config.set_value(const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, value) - config.write() + if self._config is not None: + self._config.set_value(tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, value) + self._config.write() return def _get_capacity_bytes( @@ -587,19 +494,15 @@ def reset_options(self): """ self.update_resource_values() - config = get_config() - update_seconds = get_update_every_n_seconds(config) + update_seconds = get_update_every_n_seconds(self._config) self.updateEvery_spinBox.setValue(update_seconds) - default_gpu_capacity = get_gpu_cache_capacity_percent_default(config) - default_cpu_capacity = get_cpu_cache_capacity_percent_default(config) - - scene_override = get_cache_scene_override() - scene_gpu_capacity = default_gpu_capacity - scene_cpu_capacity = default_cpu_capacity - if scene_override is True: - scene_gpu_capacity = get_gpu_cache_capacity_percent_scene() - scene_cpu_capacity = get_cpu_cache_capacity_percent_scene() + capacity_data = lib.resolve_capacity_data() + default_gpu_capacity = capacity_data.gpu_default_capacity + default_cpu_capacity = capacity_data.cpu_default_capacity + scene_override = capacity_data.scene_override + scene_gpu_capacity = capacity_data.gpu_scene_capacity + scene_cpu_capacity = capacity_data.cpu_scene_capacity # Set default capacities. set_capacity_widgets( @@ -640,22 +543,24 @@ def reset_options(self): def save_options(self): # Save config values in config file. update_seconds = self.updateEvery_spinBox.value() + self._config.set_value( + tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, update_seconds + ) + + # Save config values in config file. gpu_percent_default = self.gpuCacheDefaultCapacity_doubleSpinBox.value() cpu_percent_default = self.cpuCacheDefaultCapacity_doubleSpinBox.value() - config = get_config() - if config is not None: - config.set_value(const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, update_seconds) - config.set_value(const.CONFIG_GPU_CAPACITY_PERCENT_KEY, gpu_percent_default) - config.set_value(const.CONFIG_CPU_CAPACITY_PERCENT_KEY, cpu_percent_default) - config.write() + config_file.set_gpu_capacity_percent(gpu_percent_default) + config_file.set_cpu_capacity_percent(cpu_percent_default) + config_file.write() # Save config values in Maya Scene. scene_override = self.imageCacheSceneSettings_groupBox.isChecked() assert isinstance(scene_override, bool) gpu_percent_scene = self.gpuCacheSceneCapacity_doubleSpinBox.value() cpu_percent_scene = self.cpuCacheSceneCapacity_doubleSpinBox.value() - set_cache_scene_override(scene_override) + config_scene.set_cache_scene_override(scene_override) if scene_override is True: - set_gpu_cache_capacity_percent_scene(gpu_percent_scene) - set_cpu_cache_capacity_percent_scene(cpu_percent_scene) + config_scene.set_gpu_capacity_percent(gpu_percent_scene) + config_scene.set_cpu_capacity_percent(cpu_percent_scene) return diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py index b89a132ed..46620cbdd 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py @@ -20,7 +20,7 @@ Usage:: - import mmSolver.tools.imagecache.ui.imagecache_window as window + import mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_window as window window.main() """ @@ -36,9 +36,7 @@ import mmSolver.ui.uiutils as uiutils import mmSolver.ui.helputils as helputils import mmSolver.ui.commonmenus as commonmenus - -# import mmSolver.tools.imagecache.constant as imagecache_const -import mmSolver.tools.imagecache.lib as imagecache_lib +import mmSolver.tools.imagecache.lib as lib import mmSolver.tools.imagecacheprefs.constant as const import mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_layout as imagecacheprefs_layout @@ -95,8 +93,8 @@ def _apply(self): gpu_capacity_bytes = form.get_gpu_capacity_bytes() cpu_capacity_bytes = form.get_cpu_capacity_bytes() - imagecache_lib.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) - imagecache_lib.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) + lib.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) + lib.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) form.update_resource_values() form.save_options() From f43a72df4ce44c20909c1590cecf2f6fd5a8ffef Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 25 Jun 2024 23:53:48 +1000 Subject: [PATCH 126/295] Combine selection type creation MEL commands into one. --- src/mmSolver/pluginMain.cpp | 50 ++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index 64bf64bd5..a2abbd066 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -500,57 +500,49 @@ MStatus initializePlugin(MObject obj) { } #endif - // TODO: Construct a single MEL command buffer and run all - // 'selectType' MEL commands at once. - MString mel_cmd = ""; - // Register a custom selection mask with priority 2 (same as // locators by default). + MString mel_cmd = ""; + auto selection_priority = 2; MSelectionMask::registerSelectionType( - mmsolver::MarkerShapeNode::m_selection_type_name, 2); - mel_cmd = "selectType -byName \""; + mmsolver::MarkerShapeNode::m_selection_type_name, selection_priority); + mel_cmd += "selectType -byName \""; mel_cmd += mmsolver::MarkerShapeNode::m_selection_type_name; - mel_cmd += "\" 1"; - status = MGlobal::executeCommand(mel_cmd); - CHECK_MSTATUS(status); + mel_cmd += "\" 1;"; MSelectionMask::registerSelectionType( - mmsolver::BundleShapeNode::m_selection_type_name, 2); - mel_cmd = "selectType -byName \""; + mmsolver::BundleShapeNode::m_selection_type_name, selection_priority); + mel_cmd += "selectType -byName \""; mel_cmd += mmsolver::BundleShapeNode::m_selection_type_name; - mel_cmd += "\" 1"; - status = MGlobal::executeCommand(mel_cmd); - CHECK_MSTATUS(status); + mel_cmd += "\" 1;"; MSelectionMask::registerSelectionType( - mmsolver::ImagePlaneShapeNode::m_selection_type_name, 2); - mel_cmd = "selectType -byName \""; + mmsolver::ImagePlaneShapeNode::m_selection_type_name, + selection_priority); + mel_cmd += "selectType -byName \""; mel_cmd += mmsolver::ImagePlaneShapeNode::m_selection_type_name; - mel_cmd += "\" 1"; - status = MGlobal::executeCommand(mel_cmd); - CHECK_MSTATUS(status); + mel_cmd += "\" 1;"; MSelectionMask::registerSelectionType( - mmsolver::ImagePlaneShape2Node::m_selection_type_name, 2); + mmsolver::ImagePlaneShape2Node::m_selection_type_name, + selection_priority); mel_cmd = "selectType -byName \""; mel_cmd += mmsolver::ImagePlaneShape2Node::m_selection_type_name; - mel_cmd += "\" 1"; - status = MGlobal::executeCommand(mel_cmd); - CHECK_MSTATUS(status); + mel_cmd += "\" 1;"; MSelectionMask::registerSelectionType( - mmsolver::SkyDomeShapeNode::m_selection_type_name, 2); + mmsolver::SkyDomeShapeNode::m_selection_type_name, selection_priority); mel_cmd = "selectType -byName \""; mel_cmd += mmsolver::SkyDomeShapeNode::m_selection_type_name; - mel_cmd += "\" 1"; - status = MGlobal::executeCommand(mel_cmd); - CHECK_MSTATUS(status); + mel_cmd += "\" 1;"; MSelectionMask::registerSelectionType( - mmsolver::LineShapeNode::m_selection_type_name, 2); + mmsolver::LineShapeNode::m_selection_type_name, selection_priority); mel_cmd = "selectType -byName \""; mel_cmd += mmsolver::LineShapeNode::m_selection_type_name; - mel_cmd += "\" 1"; + mel_cmd += "\" 1;"; + + // Register selection types. status = MGlobal::executeCommand(mel_cmd); CHECK_MSTATUS(status); From 87b48b1536a73b3269c1c60f1bd4a5cdbcb1e666 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 26 Jun 2024 00:11:57 +1000 Subject: [PATCH 127/295] ImageCache - Fix getting/setting capacity configs --- python/mmSolver/tools/imagecache/config.py | 26 +++++++++++++++++++ .../ui/imagecacheprefs_layout.py | 22 +++++++--------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/python/mmSolver/tools/imagecache/config.py b/python/mmSolver/tools/imagecache/config.py index 9d2788d6b..1fbf9c6a9 100644 --- a/python/mmSolver/tools/imagecache/config.py +++ b/python/mmSolver/tools/imagecache/config.py @@ -76,3 +76,29 @@ def resolve_capacity_data(): resolved_gpu_capacity, resolved_cpu_capacity, ) + + +def save_capacity_values( + gpu_percent_default, + cpu_percent_default, + scene_override, + gpu_percent_scene, + cpu_percent_scene, +): + assert isinstance(gpu_percent_default, float) + assert isinstance(cpu_percent_default, float) + assert isinstance(scene_override, bool) + assert isinstance(gpu_percent_scene, float) + assert isinstance(cpu_percent_scene, float) + + # Save config values in config file. + config_file.set_gpu_capacity_percent(gpu_percent_default) + config_file.set_cpu_capacity_percent(cpu_percent_default) + config_file.write() + + # Save config values in Maya Scene. + config_scene.set_cache_scene_override(scene_override) + if scene_override is True: + config_scene.set_gpu_capacity_percent(gpu_percent_scene) + config_scene.set_cpu_capacity_percent(cpu_percent_scene) + return diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py index 59e79d208..b7add10c6 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -35,6 +35,7 @@ import mmSolver.utils.math as math_utils import mmSolver.tools.imagecache.config_file as config_file import mmSolver.tools.imagecache.config_scene as config_scene +import mmSolver.tools.imagecache.config as config import mmSolver.tools.imagecache.lib as lib import mmSolver.tools.imagecacheprefs.constant as tool_const import mmSolver.tools.imagecacheprefs.ui.ui_imagecacheprefs_layout as ui_imagecacheprefs_layout @@ -497,7 +498,7 @@ def reset_options(self): update_seconds = get_update_every_n_seconds(self._config) self.updateEvery_spinBox.setValue(update_seconds) - capacity_data = lib.resolve_capacity_data() + capacity_data = config.resolve_capacity_data() default_gpu_capacity = capacity_data.gpu_default_capacity default_cpu_capacity = capacity_data.cpu_default_capacity scene_override = capacity_data.scene_override @@ -547,20 +548,17 @@ def save_options(self): tool_const.CONFIG_UPDATE_EVERY_N_SECONDS_KEY, update_seconds ) - # Save config values in config file. gpu_percent_default = self.gpuCacheDefaultCapacity_doubleSpinBox.value() cpu_percent_default = self.cpuCacheDefaultCapacity_doubleSpinBox.value() - config_file.set_gpu_capacity_percent(gpu_percent_default) - config_file.set_cpu_capacity_percent(cpu_percent_default) - config_file.write() - - # Save config values in Maya Scene. scene_override = self.imageCacheSceneSettings_groupBox.isChecked() - assert isinstance(scene_override, bool) gpu_percent_scene = self.gpuCacheSceneCapacity_doubleSpinBox.value() cpu_percent_scene = self.cpuCacheSceneCapacity_doubleSpinBox.value() - config_scene.set_cache_scene_override(scene_override) - if scene_override is True: - config_scene.set_gpu_capacity_percent(gpu_percent_scene) - config_scene.set_cpu_capacity_percent(cpu_percent_scene) + + config.save_capacity_values( + gpu_percent_default, + cpu_percent_default, + scene_override, + gpu_percent_scene, + cpu_percent_scene, + ) return From e142b5de12f705fbce211b1423c64c439c6b692d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 26 Jun 2024 00:15:00 +1000 Subject: [PATCH 128/295] ImageCache - Fix erasing functions. --- .../mmSolver/tools/imagecache/_lib/erase.py | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/python/mmSolver/tools/imagecache/_lib/erase.py b/python/mmSolver/tools/imagecache/_lib/erase.py index d36c9b022..22c659e5f 100644 --- a/python/mmSolver/tools/imagecache/_lib/erase.py +++ b/python/mmSolver/tools/imagecache/_lib/erase.py @@ -81,7 +81,13 @@ def erase_all_images_on_image_plane_slots(cache_type, image_plane_shp): slots = imageplane_lib.get_image_sequence_for_all_slots(image_plane_shp) LOG.info('erase_all_images_on_image_plane_slots: slots=%r', slots) - group_names = imagecache_cmd.get_cache_group_names(cache_type) + group_names = None + if cache_type == const.CACHE_TYPE_GPU: + group_names = imagecache_cmd.get_gpu_cache_group_names() + elif cache_type == const.CACHE_TYPE_CPU: + group_names = imagecache_cmd.get_cpu_cache_group_names() + assert group_names is not None + slots_to_erase = [] for slot in slots: if slot not in group_names: @@ -89,7 +95,11 @@ def erase_all_images_on_image_plane_slots(cache_type, image_plane_shp): continue slots_to_erase.append(slot) - return imagecache_cmd.cache_erase_groups_items(cache_type, slots_to_erase) + if cache_type == const.CACHE_TYPE_GPU: + return erase_gpu_groups_items(slots_to_erase) + elif cache_type == const.CACHE_TYPE_CPU: + return erase_cpu_groups_items(slots_to_erase) + assert False def erase_images_in_active_image_plane_slot(cache_type, image_plane_shp): @@ -134,7 +144,13 @@ def erase_image_sequence(cache_type, file_pattern, start_frame, end_frame): end_frame, ) - item_count = imagecache_cmd.get_cache_group_item_count(cache_type, file_pattern) + item_count = None + if cache_type == const.CACHE_TYPE_GPU: + item_count = imagecache_cmd.get_gpu_cache_group_item_count(file_pattern) + elif cache_type == const.CACHE_TYPE_CPU: + item_count = imagecache_cmd.get_cpu_cache_group_item_count(file_pattern) + assert item_count is not None + if item_count == 0: LOG.warn('File pattern does not have any items. item_count=%r', item_count) return From 7e4bfdead6d64eeb95d69a1cb4e16bafed057f18 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 21:20:05 +1000 Subject: [PATCH 129/295] mmImagePlaneShape2 - Fix slot maintained in AE template. --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index cadd49d9b..1d22e8a65 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -184,6 +184,12 @@ global proc AEmmImagePlaneShape2_sequenceSlotNew(string $file_attr) global proc AEmmImagePlaneShape2_sequenceSlotReplace(string $file_attr) { + string $image_plane_shp[]; + tokenize($file_attr, ".", $image_plane_shp); + if(size($image_plane_shp) < 1) { + return; + } + string $tokens[]; tokenize($file_attr, ".", $tokens); if(size($tokens) < 1) { @@ -195,6 +201,13 @@ global proc AEmmImagePlaneShape2_sequenceSlotReplace(string $file_attr) string $cmd = "AEmmImagePlaneShape2_sequenceSlotChanged \"" + $tokens[1] + "\" " + $tokens[0] + " \"#1\";"; + // Ensure the slot is maintained when the Attribute Editor is + // opened. + int $slot = AEmmImagePlaneShape2_getSlotValue($image_plane_shp[0]); + // The slot value stored is 0-indexed. With '-1' meaning the value + // is undefined and '0' equal to the 'Main' slot. + optionMenu -edit -select ($slot + 1) imageSeqSlotField; + optionMenu -edit -changeCommand $cmd imageSeqSlotField; } @@ -629,6 +642,7 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -suppress "cameraHeightInch"; editorTemplate -suppress "lensHashCurrent"; editorTemplate -suppress "lensHashPrevious"; + editorTemplate -suppress "imageSequenceSlot"; AEmmNodeShapeTemplateCommonEnd($nodeName); } From c2174c7ed64b580f709076cd5c08f1bb2b8523f2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 22:33:54 +1000 Subject: [PATCH 130/295] ImageCache - force all paths to have consistent path separators We use UNIX-style '/' forward slashes as path separators, because it's easier to understand and that's what Maya converts down to anyway. --- python/mmSolver/tools/imagecache/_lib/erase.py | 15 +++++++++++++++ src/mmSolver/image/ImageCache.cpp | 8 +++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/python/mmSolver/tools/imagecache/_lib/erase.py b/python/mmSolver/tools/imagecache/_lib/erase.py index 22c659e5f..ed7b4f8b0 100644 --- a/python/mmSolver/tools/imagecache/_lib/erase.py +++ b/python/mmSolver/tools/imagecache/_lib/erase.py @@ -37,25 +37,40 @@ _IMAGE_PLANE_SHAPE = imageplane_const.MM_IMAGE_PLANE_SHAPE_V2 +def _make_group_name_consistent(group_name): + assert isinstance(group_name, str) + assert len(group_name) > 0 + + # The ImageCache stores all file paths with UNIX + # forward-slashes convention. + group_name = group_name.replace('\\', '/') + + return group_name + + def erase_gpu_group_items(group_name): assert isinstance(group_name, str) assert len(group_name) > 0 + group_name = _make_group_name_consistent(group_name) return maya.cmds.mmImageCache([group_name], edit=True, gpuEraseGroupItems=True) def erase_cpu_group_items(group_name): assert isinstance(group_name, str) assert len(group_name) > 0 + group_name = _make_group_name_consistent(group_name) return maya.cmds.mmImageCache([group_name], edit=True, cpuEraseGroupItems=True) def erase_gpu_groups_items(group_names): assert len(group_names) >= 0 + group_names = [_make_group_name_consistent(x) for x in group_names] return maya.cmds.mmImageCache(group_names, edit=True, gpuEraseGroupItems=True) def erase_cpu_groups_items(group_names): assert len(group_names) >= 0 + group_names = [_make_group_name_consistent(x) for x in group_names] return maya.cmds.mmImageCache(group_names, edit=True, cpuEraseGroupItems=True) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index f9a031210..85a3aad0c 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -154,7 +154,13 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImagePixelData(static_cast(maya_owned_pixel_data), width, height, num_channels, pixel_data_type); - const std::string group_name = std::string(file_pattern.asChar()); + // All group names are normalised to use UNIX-style path + // separators, so that the internal values are all consistent. + MString normalised_file_pattern(file_pattern); + normalised_file_pattern.substitute("\\", "/"); + const std::string group_name = + std::string(normalised_file_pattern.asChar()); + texture_data = image_cache.gpu_insert_item(texture_manager, group_name, item_key, gpu_image_pixel_data); MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file: " From 9f633e717f3f04d2ffd9433933e5e073cbce7074 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 22:35:39 +1000 Subject: [PATCH 131/295] Add "exact frame" image sequence format style. This is added to 'look-up' a specific file path for a specific frame number given, but formatted to the given file pattern. --- python/mmSolver/utils/constant.py | 3 +++ python/mmSolver/utils/imageseq.py | 42 ++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/python/mmSolver/utils/constant.py b/python/mmSolver/utils/constant.py index 2f603d2b0..5c18fcbd2 100644 --- a/python/mmSolver/utils/constant.py +++ b/python/mmSolver/utils/constant.py @@ -88,17 +88,20 @@ # # - Maya Style: "file..png" # - First frame style "file.1001.png" +# - Exact frame style "file.0042.png" # - Hash Padded style: "file.####.png" # - "old school" Nuke style: file.%04d.png # IMAGE_SEQ_FORMAT_STYLE_MAYA = 'maya' IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED = 'hash_padded' IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME = 'first_frame' +IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME = 'exact_frame' IMAGE_SEQ_FORMAT_STYLE_PRINTF = 'printf' IMAGE_SEQ_FORMAT_STYLE_VALUES = [ IMAGE_SEQ_FORMAT_STYLE_MAYA, IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED, IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME, + IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME, IMAGE_SEQ_FORMAT_STYLE_PRINTF, ] diff --git a/python/mmSolver/utils/imageseq.py b/python/mmSolver/utils/imageseq.py index a35181140..8b18c87c9 100644 --- a/python/mmSolver/utils/imageseq.py +++ b/python/mmSolver/utils/imageseq.py @@ -90,10 +90,23 @@ def _get_image_sequence_start_end_frames(base_dir, file_name, file_extension): return start_frame, end_frame, padding_num -def expand_image_sequence_path(image_sequence_path, format_style): +def expand_image_sequence_path(image_sequence_path, format_style, exact_frame=None): """ Expand a given image sequence path into tokens. + Converts 'file.1001.png' into 'file.####.png', when format_style + is IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED. + + :param image_sequence_path: An existing file path. + :type image_sequence_path: str + + :param format_style: What style this function should expand into? + :type format_style: One of mmSolver.utils.constant.IMAGE_SEQ_FORMAT_STYLE_VALUES + + :param exact_frame: The frame number that will be expanded to when + format_style IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME is used. + :type exact_frame: None or int + The tokens are: - file_pattern: str, the pattern of the file path. - start_frame: int, first frame of the image sequence. @@ -106,6 +119,7 @@ def expand_image_sequence_path(image_sequence_path, format_style): """ assert os.path.isfile(image_sequence_path) assert format_style in const.IMAGE_SEQ_FORMAT_STYLE_VALUES + assert exact_frame is None or isinstance(exact_frame, int) image_sequence_path = os.path.abspath(image_sequence_path) ( @@ -139,6 +153,15 @@ def expand_image_sequence_path(image_sequence_path, format_style): elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME: # file.1001.png image_seq_num = str(start_frame).zfill(padding_num) + elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME: + # file.0042.png (exact_frame == 42) + # + # Same as 'IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME', but allows + # the user to give the frame value that will be used. + frame = start_frame + if exact_frame is not None: + frame = exact_frame + image_seq_num = str(frame).zfill(padding_num) else: raise NotImplementedError file_pattern = '{}{}{}'.format(file_name, image_seq_num, file_extension) @@ -147,20 +170,28 @@ def expand_image_sequence_path(image_sequence_path, format_style): return file_pattern, start_frame, end_frame, padding_num, is_seq -def resolve_file_pattern_to_file_path(file_pattern, format_style): +def resolve_file_pattern_to_file_path(file_pattern, format_style, exact_frame=None): """ Resolve a file pattern into a valid file path. + Converts 'file.####.png' into 'file.1001.png', when format_style + is IMAGE_SEQ_FORMAT_STYLE_HASH_PADDED. + :param file_pattern: The pattern of the file path. :type file_pattern: str :param format_style: The format style of the input file pattern :type format_style: One of mmSolver.utils.constant.IMAGE_SEQ_FORMAT_STYLE_VALUES + :param exact_frame: The frame number that will be expanded to. + If None is given, the start_frame of the image sequence is used. + :type exact_frame: None or int + :returns: Valid file path or None. :rtype: str or None """ assert format_style in const.IMAGE_SEQ_FORMAT_STYLE_VALUES + assert isinstance(file_pattern, str) if os.path.isfile(file_pattern): file_pattern = os.path.abspath(file_pattern) @@ -175,7 +206,10 @@ def resolve_file_pattern_to_file_path(file_pattern, format_style): elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_PRINTF: # file.%04d.png raise NotImplementedError - elif format_style == const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME: + elif format_style in [ + const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME, + const.IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME, + ]: # file.1001.png # Should have already been picked out as a valid file path, so @@ -201,6 +235,6 @@ def resolve_file_pattern_to_file_path(file_pattern, format_style): file_pattern = os.path.join(base_dir, file_pattern) file_path, _, _, _, _ = expand_image_sequence_path( - file_pattern, const.IMAGE_SEQ_FORMAT_STYLE_FIRST_FRAME + file_pattern, const.IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME, exact_frame=exact_frame ) return file_path From c5eab544b959a501a7095c9f086257ad2d69cf23 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 22:36:37 +1000 Subject: [PATCH 132/295] mmImagePlaneShape2 - query slot attributes and numbers Used to get the file patterns on the image plane node. --- .../createimageplane/_lib/mmimageplane_v2.py | 83 +++++++++++++++++-- 1 file changed, 76 insertions(+), 7 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 11c5b44eb..a35e43b8a 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -223,20 +223,89 @@ def create_image_plane_shape_attrs(image_plane_shp): return -def get_image_sequence_for_active_slot(shp): +SLOT_NUMBER_MAIN = 0 +SLOT_NUMBER_ALT1 = 1 +SLOT_NUMBER_ALT2 = 2 +SLOT_NUMBER_ALT3 = 3 +SLOT_NUMBER_VALUES = [ + SLOT_NUMBER_MAIN, + SLOT_NUMBER_ALT1, + SLOT_NUMBER_ALT2, + SLOT_NUMBER_ALT3, +] +SLOT_ATTR_NAME_MAIN = 'imageSequenceMain' +SLOT_ATTR_NAME_ALT1 = 'imageSequenceAlternate1' +SLOT_ATTR_NAME_ALT2 = 'imageSequenceAlternate2' +SLOT_ATTR_NAME_ALT3 = 'imageSequenceAlternate3' +SLOT_ATTR_NAME_VALUES = [ + SLOT_ATTR_NAME_MAIN, + SLOT_ATTR_NAME_ALT1, + SLOT_ATTR_NAME_ALT2, + SLOT_ATTR_NAME_ALT3, +] +SLOT_NUMBER_TO_ATTR_NAME_MAP = { + SLOT_NUMBER_MAIN: SLOT_ATTR_NAME_MAIN, + SLOT_NUMBER_ALT1: SLOT_ATTR_NAME_ALT1, + SLOT_NUMBER_ALT2: SLOT_ATTR_NAME_ALT2, + SLOT_NUMBER_ALT3: SLOT_ATTR_NAME_ALT3, +} +SLOT_ATTR_NAME_TO_NUMBER_MAP = { + SLOT_ATTR_NAME_MAIN: SLOT_NUMBER_MAIN, + SLOT_ATTR_NAME_ALT1: SLOT_NUMBER_ALT1, + SLOT_ATTR_NAME_ALT2: SLOT_NUMBER_ALT2, + SLOT_ATTR_NAME_ALT3: SLOT_NUMBER_ALT2, +} + + +def _get_image_plane_image_slot_number(shp): + slot_number = maya.cmds.getAttr(shp + '.imageSequenceSlot') + return slot_number + + +def _slot_number_to_attr_name(slot_number): + assert isinstance(slot_number, int) + assert slot_number in SLOT_NUMBER_VALUES + return SLOT_NUMBER_TO_ATTR_NAME_MAP[slot_number] + + +def _slot_attr_name_to_number(attr_name): + assert isinstance(attr_name, str) + assert attr_name in SLOT_ATTR_NAME_VALUES + return SLOT_ATTR_NAME_TO_NUMBER_MAP[attr_name] + + +def get_file_pattern_for_active_slot(shp): assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 - raise NotImplementedError + slot_number = _get_image_plane_image_slot_number(shp) + slot_attr_name = _slot_number_to_attr_name(slot_number) + file_pattern = maya.cmds.getAttr('{}.{}'.format(shp, slot_attr_name)) + return file_pattern -def get_image_sequence_for_unused_slots(shp): +def get_file_pattern_for_unused_slots(shp): assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 - raise NotImplementedError + slot_number = _get_image_plane_image_slot_number(shp) + slot_attr_name = _slot_number_to_attr_name(slot_number) -def get_image_sequence_for_all_slots(shp): + all_slot_attr_names = list(SLOT_ATTR_NAME_VALUES) + all_slot_attr_names.remove(slot_attr_name) + + unused_file_patterns = [] + for attr_name in sorted(all_slot_attr_names): + file_pattern = maya.cmds.getAttr('{}.{}'.format(shp, attr_name)) + unused_file_patterns.append(file_pattern) + return unused_file_patterns + + +def get_file_pattern_for_all_slots(shp): assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 - slots = ['slot 1', 'slot 2'] - return slots + + all_file_patterns = [] + for attr_name in SLOT_ATTR_NAME_VALUES: + file_pattern = maya.cmds.getAttr('{}.{}'.format(shp, attr_name)) + all_file_patterns.append(file_pattern) + return all_file_patterns def set_image_sequence(shp, image_sequence_path, attr_name): From 8a3ed2ce0ba17386aadcf2d9dc18b56180f8854f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 22:41:21 +1000 Subject: [PATCH 133/295] ImageCache - Add erase functions This allows users to erase images based on the currently loaded 'file patterns' on the image plane. --- .../mmSolver/tools/imagecache/_lib/erase.py | 174 +++++++++++------- 1 file changed, 103 insertions(+), 71 deletions(-) diff --git a/python/mmSolver/tools/imagecache/_lib/erase.py b/python/mmSolver/tools/imagecache/_lib/erase.py index ed7b4f8b0..769060b02 100644 --- a/python/mmSolver/tools/imagecache/_lib/erase.py +++ b/python/mmSolver/tools/imagecache/_lib/erase.py @@ -26,6 +26,8 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.imageseq as imageseq_utils +import mmSolver.utils.constant as const_utils import mmSolver.tools.imagecache.constant as const import mmSolver.tools.imagecache._lib.imagecache_cmd as imagecache_cmd import mmSolver.tools.createimageplane._lib.constant as imageplane_const @@ -84,80 +86,64 @@ def erase_cpu_image_items(items): return maya.cmds.mmImageCache(items, edit=True, cpuEraseItems=True) -def erase_all_images_on_image_plane_slots(cache_type, image_plane_shp): +def erase_all_images_on_image_plane_slots(cache_type, shape_node): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE - LOG.info( - 'erase_all_images_on_image_plane_slots: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) - - slots = imageplane_lib.get_image_sequence_for_all_slots(image_plane_shp) - LOG.info('erase_all_images_on_image_plane_slots: slots=%r', slots) - - group_names = None - if cache_type == const.CACHE_TYPE_GPU: - group_names = imagecache_cmd.get_gpu_cache_group_names() - elif cache_type == const.CACHE_TYPE_CPU: - group_names = imagecache_cmd.get_cpu_cache_group_names() - assert group_names is not None + assert maya.cmds.nodeType(shape_node) == _IMAGE_PLANE_SHAPE - slots_to_erase = [] - for slot in slots: - if slot not in group_names: - LOG.warn('Slot not found in groups: group_names=%r', group_names) - continue - slots_to_erase.append(slot) + file_patterns = imageplane_lib.get_file_pattern_for_all_slots(shape_node) + file_patterns = [x for x in file_patterns if isinstance(x, str) and len(x) > 0] + if len(file_patterns) == 0: + LOG.warn('mmImagePlane unused slots are all invalid; node=%r', shape_node) + return if cache_type == const.CACHE_TYPE_GPU: - return erase_gpu_groups_items(slots_to_erase) + return erase_gpu_groups_items(file_patterns) elif cache_type == const.CACHE_TYPE_CPU: - return erase_cpu_groups_items(slots_to_erase) + return erase_cpu_groups_items(file_patterns) assert False -def erase_images_in_active_image_plane_slot(cache_type, image_plane_shp): +def erase_images_in_active_image_plane_slot(cache_type, shape_node): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE - LOG.info( - 'erase_images_in_active_image_plane_slot: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) + assert maya.cmds.nodeType(shape_node) == _IMAGE_PLANE_SHAPE - slot = imageplane_lib.get_image_sequence_for_active_slot(image_plane_shp) - LOG.info('erase_images_in_active_image_plane_slot: slot=%r', slot) + file_pattern = imageplane_lib.get_file_pattern_for_active_slot(shape_node) + if file_pattern is None or len(file_pattern) == 0: + LOG.warn('mmImagePlane active slot is invalid=%r', file_pattern) + return - return + if cache_type == const.CACHE_TYPE_GPU: + return erase_gpu_group_items(file_pattern) + elif cache_type == const.CACHE_TYPE_CPU: + return erase_cpu_group_items(file_pattern) + raise NotImplementedError -def erase_images_in_unused_image_plane_slots(cache_type, image_plane_shp): +def erase_images_in_unused_image_plane_slots(cache_type, shape_node): assert cache_type in const.CACHE_TYPE_VALUES - assert maya.cmds.nodeType(image_plane_shp) == _IMAGE_PLANE_SHAPE - LOG.info( - 'erase_images_in_unused_image_plane_slots: cache_type=%r, image_plane_shp=%r', - cache_type, - image_plane_shp, - ) - slots = imageplane_lib.get_image_sequence_for_unused_slots(image_plane_shp) - LOG.info('erase_images_in_unused_image_plane_slots: slots=%r', slots) - return + assert maya.cmds.nodeType(shape_node) == _IMAGE_PLANE_SHAPE + + file_patterns = imageplane_lib.get_file_pattern_for_unused_slots(shape_node) + file_patterns = [x for x in file_patterns if isinstance(x, str) and len(x) > 0] + if len(file_patterns) == 0: + LOG.warn('mmImagePlane unused slots are all invalid; node=%r', shape_node) + return + + if cache_type == const.CACHE_TYPE_GPU: + return erase_gpu_groups_items(file_patterns) + elif cache_type == const.CACHE_TYPE_CPU: + return erase_cpu_groups_items(file_patterns) + raise NotImplementedError -def erase_image_sequence(cache_type, file_pattern, start_frame, end_frame): +def erase_image_sequence( + cache_type, file_pattern, format_style, start_frame, end_frame +): assert cache_type in const.CACHE_TYPE_VALUES + assert format_style in const_utils.IMAGE_SEQ_FORMAT_STYLE_VALUES assert isinstance(file_pattern, str) assert isinstance(start_frame, int) assert isinstance(end_frame, int) - LOG.info( - 'erase_image_sequence: ' - 'cache_type=%r, file_pattern=%r, start_frame=%r, end_frame=%r', - cache_type, - file_pattern, - start_frame, - end_frame, - ) item_count = None if cache_type == const.CACHE_TYPE_GPU: @@ -170,19 +156,33 @@ def erase_image_sequence(cache_type, file_pattern, start_frame, end_frame): LOG.warn('File pattern does not have any items. item_count=%r', item_count) return - item_names = imagecache_cmd.get_gpu_group_item_names(cache_type, file_pattern) + item_names = set(imagecache_cmd.get_gpu_group_item_names(cache_type, file_pattern)) assert len(item_names) > 0 - # TODO: + # Evaluate the file_pattern for start_frame to end_frame. + image_file_paths = set() + for frame in range(start_frame, end_frame + 1): + format_style = const_utils.IMAGE_SEQ_FORMAT_STYLE_EXACT_FRAME + file_path = imageseq_utils.resolve_file_pattern_to_file_path( + file_pattern, format_style, exact_frame=start_frame + ) + image_file_paths.add(file_path) + + # If the evaluated file path is in the image cache, add it to the + # list to be removed. # - # 1) Evaluate the file_pattern for start_frame to end_frame. - # - # 2) If the evaluated file path is in the image cache, add it to - # the list to be removed. - # - # 3) Remove named items from the cache. + # This assumes both lists use the same representation of the same + # file; the file is absolute and both use forward-slashes (which + # is expected). + common_file_paths = item_names & image_file_paths - raise NotImplementedError + # Remove named items from the cache. + if cache_type == const.CACHE_TYPE_GPU: + return erase_gpu_image_items(list(common_file_paths)) + elif cache_type == const.CACHE_TYPE_CPU: + return erase_cpu_image_items(list(common_file_paths)) + else: + raise NotImplementedError def erase_all_inactive_images(cache_type): @@ -208,16 +208,48 @@ def erase_all_inactive_images(cache_type): def erase_all_images(cache_type): + """ + Clear image cache completely. + """ assert cache_type in const.CACHE_TYPE_VALUES - LOG.info( - 'erase_images_in_unused_image_plane_slots: cache_type=%r', - cache_type, - ) - # TODO: Clear image cache completely. + # Remove named items from the cache. + before_item_count = None + after_item_count = None + if cache_type == const.CACHE_TYPE_GPU: + before_item_count = imagecache_cmd.get_gpu_cache_item_count() + capacity_bytes = imagecache_cmd.get_gpu_cache_capacity_bytes() + + # Because setting the capacity to zero will automatically + # evict "all" images (except the minimum number allowed) from + # the cache. + imagecache_cmd.set_gpu_cache_capacity_bytes(0) + + imagecache_cmd.set_gpu_cache_capacity_bytes(capacity_bytes) + after_item_count = imagecache_cmd.get_gpu_cache_item_count() + elif cache_type == const.CACHE_TYPE_CPU: + before_item_count = imagecache_cmd.get_cpu_cache_item_count() + capacity_bytes = imagecache_cmd.get_cpu_cache_capacity_bytes() + + # Because setting the capacity to zero will automatically + # evict "all" images (except the minimum number allowed) from + # the cache. + imagecache_cmd.set_cpu_cache_capacity_bytes(0) + + imagecache_cmd.set_cpu_cache_capacity_bytes(capacity_bytes) + after_item_count = imagecache_cmd.get_cpu_cache_item_count() + else: + raise NotImplementedError - # 1) Get the ImageCache capacities (CPU and GPU). - # 2) Set the ImageCache capacity (CPU and GPU) to zero. - # 3) Restore the ImageCache capacities (CPU and GPU). + LOG.debug( + '%s ImageCache items before clearing: %r', cache_type.upper(), before_item_count + ) + LOG.debug( + '%s ImageCache items after clearing: %r', cache_type.upper(), after_item_count + ) + removed_count = before_item_count - after_item_count + LOG.info( + 'Erased %r images from the %s Image Cache', removed_count, cache_type.upper() + ) return From 09428f4895e2cb7e818d2b5af7a88740c2f802fb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 27 Jun 2024 23:48:12 +1000 Subject: [PATCH 134/295] ImageIO - do not read EXR images due to memory leak bug. Reading EXRs does not seem to ever deallocate memory, until this is fixed, lets use the (non-existent 8-bit) Maya MImage EXR reader. --- src/mmSolver/image/image_io.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mmSolver/image/image_io.cpp b/src/mmSolver/image/image_io.cpp index 661400ebb..17fac4e05 100644 --- a/src/mmSolver/image/image_io.cpp +++ b/src/mmSolver/image/image_io.cpp @@ -382,7 +382,11 @@ MStatus read_image_file(MImage &image, const MString &file_path, MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" << " file_extension=" << file_extension.asChar()); - if (file_extension == "exr") { + // BUG: The EXR reader is leaking memory and we don't know where, + // so for now, lets disable the reader. + const bool use_exr_reader = false; + + if ((file_extension == "exr") && use_exr_reader) { MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" << "read_exr_with_mmimage..."); status = read_exr_with_mmimage(image, file_path, out_width, out_height, From fd3906092a5b9506dc9f3fcb17fa5b6d2767b35b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 28 Jun 2024 00:10:53 +1000 Subject: [PATCH 135/295] ImageCache - Re-set cache capacity when opening Maya scenes. This allows the scene-override values saved in a Maya scene to automatically kick in as soon as a Maya scene is loaded. --- .../tools/imagecache/_lib/initialize.py | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/python/mmSolver/tools/imagecache/_lib/initialize.py b/python/mmSolver/tools/imagecache/_lib/initialize.py index 3d6e7ebd5..df4a8d913 100644 --- a/python/mmSolver/tools/imagecache/_lib/initialize.py +++ b/python/mmSolver/tools/imagecache/_lib/initialize.py @@ -24,6 +24,7 @@ from __future__ import print_function import maya.cmds +import maya.OpenMaya as OpenMaya import mmSolver.logger import mmSolver.api as mmapi @@ -33,6 +34,8 @@ LOG = mmSolver.logger.get_logger() +AFTER_NEW_CALLBACK_ID = None +AFTER_OPEN_CALLBACK_ID = None def _format_gigabytes(memory_bytes): @@ -63,15 +66,33 @@ def _ensure_plugin_loaded(): return +def _dummy_initialize(*args): + """ + This dummy function is used for the callbacks below, but it + contains an extra argument. + """ + initialize() + + def initialize(): """ Set Image Cache capacities from preferences. """ LOG.info('MM Solver Initialize Image Cache...') - # TODO: Install a callback to be called each time a new scene is + # Install callbacks to be called each time a new scene is # opened. We will look for enabled scene override values for the # cache and set it, otherwise we use the defaults. + global AFTER_NEW_CALLBACK_ID + global AFTER_OPEN_CALLBACK_ID + if AFTER_NEW_CALLBACK_ID is None: + AFTER_NEW_CALLBACK_ID = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterNew, _dummy_initialize + ) + if AFTER_OPEN_CALLBACK_ID is None: + AFTER_OPEN_CALLBACK_ID = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterOpen, _dummy_initialize + ) _ensure_plugin_loaded() @@ -87,13 +108,22 @@ def initialize(): gpu_capacity_percent = _format_percent(gpu_capacity_percent) cpu_capacity_percent = _format_percent(cpu_capacity_percent) - LOG.info( - 'Image Cache GPU Capacity: %s (%s)', gpu_capacty_gigabytes, gpu_capacity_percent - ) - LOG.info( - 'Image Cache CPU Capacity: %s (%s)', cpu_capacty_gigabytes, cpu_capacity_percent - ) - - imagecache_cmd.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) - imagecache_cmd.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) + gpu_current_capacity_bytes = imagecache_cmd.get_gpu_cache_capacity_bytes() + cpu_current_capacity_bytes = imagecache_cmd.get_cpu_cache_capacity_bytes() + + if gpu_capacity_bytes != gpu_current_capacity_bytes: + LOG.info( + 'Image Cache - Set GPU Capacity: %s (%s)', + gpu_capacty_gigabytes, + gpu_capacity_percent, + ) + imagecache_cmd.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) + + if cpu_capacity_bytes != cpu_current_capacity_bytes: + LOG.info( + 'Image Cache - Set CPU Capacity: %s (%s)', + cpu_capacty_gigabytes, + cpu_capacity_percent, + ) + imagecache_cmd.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) return From f02ac2de3e20aea56117b8ebb385a9eaeb937d43 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 14:07:03 +1000 Subject: [PATCH 136/295] Fix Silhouette Playblast Rendering The camera and model matrices need to be updated while playblasting, otherwise the silhouette lines do not match with the geometry. --- src/mmSolver/render/ops/SilhouetteRender.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mmSolver/render/ops/SilhouetteRender.cpp b/src/mmSolver/render/ops/SilhouetteRender.cpp index e2f1fa18f..d1f92b82e 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.cpp +++ b/src/mmSolver/render/ops/SilhouetteRender.cpp @@ -152,6 +152,10 @@ MStatus calculate_model_view_projection_matrix( M3dView view, MDagPath dag_path, MMatrix& out_model_view_projection) { const MMatrix inclusive_matrix = dag_path.inclusiveMatrix(); + // The camera and geometry matrices must be updated each frame, + // when playblasting. This is not obvious when viewing in the + view.updateViewingParameters(); + MMatrix projection_matrix; MStatus status = view.projectionMatrix(projection_matrix); CHECK_MSTATUS_AND_RETURN_IT(status); From c5efd1e0c2fd1bbfa1b6aad073ac4271d7f6e006 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 16:28:06 +1000 Subject: [PATCH 137/295] Silhouette Render - wireframe color and override color This allows users to use the Maya wireframe colour by default, with the option to override the silhouette/line color with a global color. --- .../AEmmRenderGlobalsSilhouetteTemplate.mel | 31 ++++++++++++++++--- .../render/RenderGlobalsSilhouetteNode.cpp | 13 ++++++++ .../render/RenderGlobalsSilhouetteNode.h | 1 + .../render/RenderOverrideSilhouette.cpp | 26 ++++++++++++---- .../render/RenderOverrideSilhouette.h | 1 + src/mmSolver/render/data/constants.h | 2 ++ src/mmSolver/render/ops/SilhouetteRender.cpp | 21 ++++++++++++- src/mmSolver/render/ops/SilhouetteRender.h | 2 ++ 8 files changed, 86 insertions(+), 11 deletions(-) diff --git a/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel b/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel index 93bc3e54f..bd90bf309 100644 --- a/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel +++ b/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel @@ -31,10 +31,6 @@ global proc AEmmRenderGlobalsSilhouetteTemplate(string $name) { -beginLayout "Attributes" -collapse false; - editorTemplate - -label "Enable" - -addControl "enable"; - editorTemplate -label "Depth Offset" -addControl "depthOffset"; @@ -43,6 +39,10 @@ global proc AEmmRenderGlobalsSilhouetteTemplate(string $name) { -label "Width" -addControl "width"; + editorTemplate + -label "Override Color" + -addControl "overrideColor"; + editorTemplate -label "Color" -addControl "color"; @@ -51,8 +51,27 @@ global proc AEmmRenderGlobalsSilhouetteTemplate(string $name) { -label "Alpha" -addControl "alpha"; + editorTemplate + -label "Cull Face" + -addControl "cullFace"; + + editorTemplate -endLayout; + + editorTemplate + -beginLayout "Debug" + -collapse true; + + editorTemplate + -label "Enable" + -addControl "enable"; + + editorTemplate + -label "Operation Number" + -addControl "operationNum"; + editorTemplate -endLayout; + editorTemplate -endNoOptimize; editorTemplate -addExtraControls; @@ -60,5 +79,9 @@ global proc AEmmRenderGlobalsSilhouetteTemplate(string $name) { // include/call base class/node attributes AEabstractBaseCreateTemplate $name; + // Don't need to show the enable check-box, users will always want + // it enabled, but we leave it here for testing/debugging. + editorTemplate -suppress "enable"; + editorTemplate -endScrollLayout; } diff --git a/src/mmSolver/render/RenderGlobalsSilhouetteNode.cpp b/src/mmSolver/render/RenderGlobalsSilhouetteNode.cpp index 4b7717654..67a57e012 100644 --- a/src/mmSolver/render/RenderGlobalsSilhouetteNode.cpp +++ b/src/mmSolver/render/RenderGlobalsSilhouetteNode.cpp @@ -52,6 +52,7 @@ MTypeId RenderGlobalsSilhouetteNode::m_id(MM_RENDER_GLOBALS_SILHOUETTE_TYPE_ID); // Input Attributes MObject RenderGlobalsSilhouetteNode::a_enable; +MObject RenderGlobalsSilhouetteNode::a_overrideColor; MObject RenderGlobalsSilhouetteNode::a_depthOffset; MObject RenderGlobalsSilhouetteNode::a_width; MObject RenderGlobalsSilhouetteNode::a_color; @@ -152,6 +153,18 @@ MStatus RenderGlobalsSilhouetteNode::initialize() { CHECK_MSTATUS(addAttribute(a_enable)); } + // Silhouette Override Color + { + a_overrideColor = numeric_attribute.create( + kAttrNameSilhouetteOverrideColor, "ovrdcol", + MFnNumericData::kBoolean, + static_cast(kSilhouetteOverrideColorDefault)); + CHECK_MSTATUS(numeric_attribute.setStorable(true)); + CHECK_MSTATUS(numeric_attribute.setConnectable(true)); + CHECK_MSTATUS(numeric_attribute.setKeyable(true)); + CHECK_MSTATUS(addAttribute(a_overrideColor)); + } + // Silhouette Depth Offset { auto depth_offset_max = 0.0; diff --git a/src/mmSolver/render/RenderGlobalsSilhouetteNode.h b/src/mmSolver/render/RenderGlobalsSilhouetteNode.h index 909309aea..55f9ef6cd 100644 --- a/src/mmSolver/render/RenderGlobalsSilhouetteNode.h +++ b/src/mmSolver/render/RenderGlobalsSilhouetteNode.h @@ -56,6 +56,7 @@ class RenderGlobalsSilhouetteNode : public MPxNode { static MObject a_enable; static MObject a_depthOffset; static MObject a_width; + static MObject a_overrideColor; static MObject a_color; static MObject a_alpha; static MObject a_cullFace; diff --git a/src/mmSolver/render/RenderOverrideSilhouette.cpp b/src/mmSolver/render/RenderOverrideSilhouette.cpp index 84b7dd26b..b163d206d 100644 --- a/src/mmSolver/render/RenderOverrideSilhouette.cpp +++ b/src/mmSolver/render/RenderOverrideSilhouette.cpp @@ -108,6 +108,7 @@ RenderOverrideSilhouette::RenderOverrideSilhouette(const MString &name) , m_render_override_change_callback(0) , m_globals_node() , m_enable(kSilhouetteEnableDefault) + , m_override_color(kSilhouetteOverrideColorDefault) , m_depth_offset(kSilhouetteDepthOffsetDefault) , m_width(kSilhouetteWidthDefault) , m_color{kSilhouetteColorDefault[0], kSilhouetteColorDefault[1], @@ -319,10 +320,11 @@ MHWRender::DrawAPI RenderOverrideSilhouette::supportedDrawAPIs() const { // Read node plug attributes and set the values. MStatus update_parameters_silhouette( MObjectHandle &out_globals_node, bool &out_silhouette_enable, - float &out_silhouette_depth_offset, float &out_silhouette_width, - float &out_silhouette_color_r, float &out_silhouette_color_g, - float &out_silhouette_color_b, float &out_silhouette_alpha, - CullFace &out_silhouette_cull_face, uint8_t &out_operation_num) { + bool &out_silhouette_override_color, float &out_silhouette_depth_offset, + float &out_silhouette_width, float &out_silhouette_color_r, + float &out_silhouette_color_g, float &out_silhouette_color_b, + float &out_silhouette_alpha, CullFace &out_silhouette_cull_face, + uint8_t &out_operation_num) { const bool verbose = false; MStatus status = MS::kSuccess; MMSOLVER_MAYA_VRB("RenderOverrideSilhouette::update_parameters_silhouette"); @@ -385,6 +387,16 @@ MStatus update_parameters_silhouette( MMSOLVER_MAYA_VRB("RenderOverrideSilhouette Silhouette Enable: " << static_cast(out_silhouette_enable)); + // Override Color Silhouette render. + out_silhouette_override_color = kSilhouetteOverrideColorDefault; + MPlug silhouette_override_color_plug = depends_node.findPlug( + kAttrNameSilhouetteOverrideColor, want_networked_plug, &status); + if (status == MStatus::kSuccess) { + out_silhouette_override_color = silhouette_override_color_plug.asBool(); + } + MMSOLVER_MAYA_VRB("RenderOverrideSilhouette Silhouette Override Color: " + << static_cast(out_silhouette_override_color)); + // Silhouette Depth Offset out_silhouette_depth_offset = kSilhouetteDepthOffsetDefault; MPlug silhouette_depth_offset_plug = depends_node.findPlug( @@ -562,8 +574,9 @@ MStatus RenderOverrideSilhouette::setup(const MString &destination) { // Get override values. status = update_parameters_silhouette( - m_globals_node, m_enable, m_depth_offset, m_width, m_color[0], - m_color[1], m_color[2], m_alpha, m_cull_face, m_operation_num); + m_globals_node, m_enable, m_override_color, m_depth_offset, m_width, + m_color[0], m_color[1], m_color[2], m_alpha, m_cull_face, + m_operation_num); CHECK_MSTATUS(status); MMSOLVER_MAYA_VRB( @@ -587,6 +600,7 @@ MStatus RenderOverrideSilhouette::setup(const MString &destination) { m_silhouetteOp->setPanelName(destination); m_silhouetteOp->setEnabled(m_enable); + m_silhouetteOp->setSilhouetteOverrideColor(m_override_color); m_silhouetteOp->setSilhouetteDepthOffset(m_depth_offset); m_silhouetteOp->setSilhouetteWidth(m_width); m_silhouetteOp->setSilhouetteColor(m_color[0], m_color[1], m_color[2]); diff --git a/src/mmSolver/render/RenderOverrideSilhouette.h b/src/mmSolver/render/RenderOverrideSilhouette.h index 16638df0a..050f22f21 100644 --- a/src/mmSolver/render/RenderOverrideSilhouette.h +++ b/src/mmSolver/render/RenderOverrideSilhouette.h @@ -111,6 +111,7 @@ class RenderOverrideSilhouette : public MHWRender::MRenderOverride { MSelectionList m_image_plane_nodes; bool m_enable; + bool m_override_color; float m_depth_offset; float m_width; float m_color[3]; diff --git a/src/mmSolver/render/data/constants.h b/src/mmSolver/render/data/constants.h index 8626b22f4..4131c7ebd 100644 --- a/src/mmSolver/render/data/constants.h +++ b/src/mmSolver/render/data/constants.h @@ -118,6 +118,7 @@ const MString kRendererSilhouetteCreateNodeCommand = // Silhouette Attribute Names const MString kAttrNameSilhouetteEnable = "enable"; +const MString kAttrNameSilhouetteOverrideColor = "overrideColor"; const MString kAttrNameSilhouetteDepthOffset = "depthOffset"; const MString kAttrNameSilhouetteWidth = "width"; const MString kAttrNameSilhouetteColor = "color"; @@ -130,6 +131,7 @@ const MString kAttrNameSilhouetteOperationNum = "operationNum"; // Silhouette Renderer Attribute Default Values const bool kSilhouetteEnableDefault = true; +const bool kSilhouetteOverrideColorDefault = false; const float kSilhouetteDepthOffsetDefault = -1.0f; const float kSilhouetteWidthDefault = 2.0f; const float kSilhouetteColorDefault[] = {0.0f, 1.0f, 0.0f}; diff --git a/src/mmSolver/render/ops/SilhouetteRender.cpp b/src/mmSolver/render/ops/SilhouetteRender.cpp index d1f92b82e..985d851e6 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.cpp +++ b/src/mmSolver/render/ops/SilhouetteRender.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ SilhouetteRender::SilhouetteRender(const MString& name) , m_shader_program(0) , m_output_targets(nullptr) , m_silhouette_cull_face(GL_BACK) + , m_silhouette_override_color(false) , gGLFT(nullptr) {} SilhouetteRender::~SilhouetteRender() { @@ -335,6 +337,10 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { const float default_line_width = drawContext.getGlobalLineWidth(); + float silhouette_color[3] = {m_silhouette_color[0], m_silhouette_color[1], + m_silhouette_color[2]}; + MColor wireframe_color = MColor(); + // Extract OpenGL buffers from Maya mesh nodes, then render the // buffers using our own OpenGL pipeline. MItDag dag_iter = MItDag(MItDag::kDepthFirst, MFn::kMesh); @@ -356,6 +362,19 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { continue; } + if (!m_silhouette_override_color) { + // TODO: Write our own version of + // 'MGeometryUtilities::wireframeColor', that will do the same + // logic, but will not take into account the current selection + // status of the DagPath. This would avoid the need to save + // the selection list, de-select and re-select. + wireframe_color = + MHWRender::MGeometryUtilities::wireframeColor(dag_path); + silhouette_color[0] = wireframe_color.r; + silhouette_color[1] = wireframe_color.g; + silhouette_color[2] = wireframe_color.b; + } + // TODO: Check if an attribute exists on the shape node, and // check the value of the shape node. @@ -438,7 +457,7 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { const MGLuint* triangles_index_buffer_handle = static_cast(triangles_index_buffer_handle_ptr); draw_buffers( - gGLFT, m_silhouette_color, m_silhouette_alpha, m_silhouette_width, + gGLFT, silhouette_color, m_silhouette_alpha, m_silhouette_width, m_silhouette_depth_offset, m_silhouette_cull_face, default_line_width, model_view_projection, vertex_buffer_handle, edge_index_buffer_handle, triangles_index_buffer_handle, diff --git a/src/mmSolver/render/ops/SilhouetteRender.h b/src/mmSolver/render/ops/SilhouetteRender.h index 716b84f29..66669cded 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.h +++ b/src/mmSolver/render/ops/SilhouetteRender.h @@ -62,6 +62,7 @@ class SilhouetteRender : public MHWRender::MUserRenderOperation { void setPanelName(MString value) { m_panel_name = value; } void setSilhouetteEnable(const bool value) { m_silhouette_enable = value; } + void setSilhouetteOverrideColor(const bool value) { m_silhouette_override_color = value; } void setSilhouetteDepthOffset(const float value) { m_silhouette_depth_offset = value; } @@ -98,6 +99,7 @@ class SilhouetteRender : public MHWRender::MUserRenderOperation { MGLFunctionTable *gGLFT; bool m_silhouette_enable; + bool m_silhouette_override_color; float m_silhouette_depth_offset; float m_silhouette_width; float m_silhouette_color[3]; From eea881cc684fa18ff6134022c0ff316bd599c8c6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 16:28:38 +1000 Subject: [PATCH 138/295] Silhouette Render - Show attribute editor by default with Option Box --- mel/mmRendererSilhouetteOptionBox.mel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mel/mmRendererSilhouetteOptionBox.mel b/mel/mmRendererSilhouetteOptionBox.mel index c7277042a..0f4aa48db 100644 --- a/mel/mmRendererSilhouetteOptionBox.mel +++ b/mel/mmRendererSilhouetteOptionBox.mel @@ -40,5 +40,9 @@ global proc mmRendererSilhouetteOptionBox() { lockNode -lock on $node; } select -r "mmRenderGlobalsSilhouette"; + + // Users want to be able to select and change the color swatch, so + // we need the attribute editor open. + AttributeEditor; return; } From 9be81cd30e14721ca1155923757fc750f68dd1c4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 16:31:57 +1000 Subject: [PATCH 139/295] Silhouette Render - fix matrices not updating during playblast The Projection Matrix would be squished when rendering to a playblast movie that is a different aspect ratio than the Maya Viewport window. --- src/mmSolver/render/ops/SilhouetteRender.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/mmSolver/render/ops/SilhouetteRender.cpp b/src/mmSolver/render/ops/SilhouetteRender.cpp index 985d851e6..697fcbae2 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.cpp +++ b/src/mmSolver/render/ops/SilhouetteRender.cpp @@ -151,15 +151,14 @@ GLuint build_shader_program(MGLFunctionTable* gGLFT) { } MStatus calculate_model_view_projection_matrix( - M3dView view, MDagPath dag_path, MMatrix& out_model_view_projection) { + M3dView view, MDagPath dag_path, MMatrix& projection_matrix, + MMatrix& out_model_view_projection) { const MMatrix inclusive_matrix = dag_path.inclusiveMatrix(); // The camera and geometry matrices must be updated each frame, // when playblasting. This is not obvious when viewing in the - view.updateViewingParameters(); - - MMatrix projection_matrix; - MStatus status = view.projectionMatrix(projection_matrix); + MStatus status = MStatus::kSuccess; + status = view.updateViewingParameters(); CHECK_MSTATUS_AND_RETURN_IT(status); MMatrix model_view_matrix; @@ -378,9 +377,13 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { // TODO: Check if an attribute exists on the shape node, and // check the value of the shape node. + MMatrix projection_matrix = drawContext.getMatrix( + MHWRender::MFrameContext::kProjectionMtx, &status); + CHECK_MSTATUS_AND_RETURN_IT(status); + MMatrix model_view_projection; - status = calculate_model_view_projection_matrix(view, dag_path, - model_view_projection); + status = calculate_model_view_projection_matrix( + view, dag_path, projection_matrix, model_view_projection); // Create vertex buffer desc for position const MString empty_string = ""; From 581e2d38ca0c0217d48639538d40f5e1c4af7625 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 20:56:27 +1000 Subject: [PATCH 140/295] Silhouette Render - Reorganise #includes --- src/mmSolver/render/RenderOverrideSilhouette.h | 4 +++- src/mmSolver/render/ops/SilhouetteRender.cpp | 18 +++++++++++------- src/mmSolver/render/ops/SilhouetteRender.h | 8 ++++++-- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/mmSolver/render/RenderOverrideSilhouette.h b/src/mmSolver/render/RenderOverrideSilhouette.h index 050f22f21..5a7c9bf7e 100644 --- a/src/mmSolver/render/RenderOverrideSilhouette.h +++ b/src/mmSolver/render/RenderOverrideSilhouette.h @@ -31,9 +31,11 @@ #include #include #include -#include #include #include + +// Maya Viewport 2.0 +#include #include // MM Solver diff --git a/src/mmSolver/render/ops/SilhouetteRender.cpp b/src/mmSolver/render/ops/SilhouetteRender.cpp index 697fcbae2..ab27ab00c 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.cpp +++ b/src/mmSolver/render/ops/SilhouetteRender.cpp @@ -28,21 +28,25 @@ // Maya #include #include -#include #include -#include -#include #include #include +#include +#include +#include + +// Maya Viewport 1.0 +#include +#include #include -#include #include -#include + +// Maya Viewport 2.0 +#include +#include #include -#include #include #include -#include #include #include diff --git a/src/mmSolver/render/ops/SilhouetteRender.h b/src/mmSolver/render/ops/SilhouetteRender.h index 66669cded..361377386 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.h +++ b/src/mmSolver/render/ops/SilhouetteRender.h @@ -28,11 +28,15 @@ // Maya #include +#include +#include + +// Maya Viewport 1.0 #include #include + +// Maya Viewport 2.0 #include -#include -#include #include // MM Solver From 6c35cce2b5a3e913e44dc86f43ba430ac228bd0e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 17 Aug 2024 21:00:33 +1000 Subject: [PATCH 141/295] Silhouette Render - Make variables const Plus some formatting and comment changes. --- src/mmSolver/render/ops/SilhouetteRender.cpp | 11 +++++++---- src/mmSolver/render/ops/SilhouetteRender.h | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/mmSolver/render/ops/SilhouetteRender.cpp b/src/mmSolver/render/ops/SilhouetteRender.cpp index ab27ab00c..44d21306c 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.cpp +++ b/src/mmSolver/render/ops/SilhouetteRender.cpp @@ -155,7 +155,7 @@ GLuint build_shader_program(MGLFunctionTable* gGLFT) { } MStatus calculate_model_view_projection_matrix( - M3dView view, MDagPath dag_path, MMatrix& projection_matrix, + M3dView view, const MDagPath dag_path, const MMatrix& projection_matrix, MMatrix& out_model_view_projection) { const MMatrix inclusive_matrix = dag_path.inclusiveMatrix(); @@ -342,7 +342,6 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { float silhouette_color[3] = {m_silhouette_color[0], m_silhouette_color[1], m_silhouette_color[2]}; - MColor wireframe_color = MColor(); // Extract OpenGL buffers from Maya mesh nodes, then render the // buffers using our own OpenGL pipeline. @@ -371,7 +370,7 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { // logic, but will not take into account the current selection // status of the DagPath. This would avoid the need to save // the selection list, de-select and re-select. - wireframe_color = + const MColor wireframe_color = MHWRender::MGeometryUtilities::wireframeColor(dag_path); silhouette_color[0] = wireframe_color.r; silhouette_color[1] = wireframe_color.g; @@ -381,7 +380,7 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { // TODO: Check if an attribute exists on the shape node, and // check the value of the shape node. - MMatrix projection_matrix = drawContext.getMatrix( + const MMatrix projection_matrix = drawContext.getMatrix( MHWRender::MFrameContext::kProjectionMtx, &status); CHECK_MSTATUS_AND_RETURN_IT(status); @@ -427,6 +426,8 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { vertex_buffer.commit(vertices_ptr); // Fill edge index buffer with data + // + // Edges have 2 indices for each edge. unsigned int edge_count = geometry_extractor.primitiveCount(edge_description); void* edge_indices_ptr = @@ -436,6 +437,8 @@ MStatus SilhouetteRender::execute(const MHWRender::MDrawContext& drawContext) { edge_index_buffer.commit(edge_indices_ptr); // Fill tri index buffer with data + // + // Triangles have 3 indices for each triangle. unsigned int triangles_count = geometry_extractor.primitiveCount(triangle_description); void* triangles_indices_ptr = diff --git a/src/mmSolver/render/ops/SilhouetteRender.h b/src/mmSolver/render/ops/SilhouetteRender.h index 361377386..d7e2b0a92 100644 --- a/src/mmSolver/render/ops/SilhouetteRender.h +++ b/src/mmSolver/render/ops/SilhouetteRender.h @@ -66,7 +66,9 @@ class SilhouetteRender : public MHWRender::MUserRenderOperation { void setPanelName(MString value) { m_panel_name = value; } void setSilhouetteEnable(const bool value) { m_silhouette_enable = value; } - void setSilhouetteOverrideColor(const bool value) { m_silhouette_override_color = value; } + void setSilhouetteOverrideColor(const bool value) { + m_silhouette_override_color = value; + } void setSilhouetteDepthOffset(const float value) { m_silhouette_depth_offset = value; } From ea55bce4a8fb4f97d2b1c9b9fbb9e6ecba00f708 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 26 Aug 2024 00:24:44 +1000 Subject: [PATCH 142/295] Silhouette Renderer - Fix background colours during playblast This fixes a bug where the viewport background was being set incorrectly while Maya playblasts. This seems to have been tracked down to the "MRenderer::useGradient()" method which was reporting the wrong values during a playblast. 'setOverridesColors' was also set to override colours. This combination of values appears to provide the correct output, although I'm not exactly sure why or how. --- src/mmSolver/render/ops/scene_utils.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/mmSolver/render/ops/scene_utils.cpp b/src/mmSolver/render/ops/scene_utils.cpp index 2f2597712..9df8e95a5 100644 --- a/src/mmSolver/render/ops/scene_utils.cpp +++ b/src/mmSolver/render/ops/scene_utils.cpp @@ -361,9 +361,10 @@ bool set_background_clear_operation( MHWRender::MClearOperation& out_clear_operation) { if (background_style == BackgroundStyle::kTransparentBlack) { float val[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + out_clear_operation.setOverridesColors(false); out_clear_operation.setClearColor(val); out_clear_operation.setClearColor2(val); - out_clear_operation.setClearGradient(false); + out_clear_operation.setClearGradient(true); out_clear_operation.setClearStencil(0); // A depth value of 1.0f represents the 'most distant' // object. As objects draw, they draw darker pixels on top of @@ -374,14 +375,23 @@ bool set_background_clear_operation( // preferences. MRenderer provides us a way to get these // values automatically. MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer(); - bool gradient = renderer->useGradient(); - MColor color1 = renderer->clearColor(); - MColor color2 = renderer->clearColor2(); + const bool gradient = renderer->useGradient(); + const MColor color1 = renderer->clearColor(); + const MColor color2 = renderer->clearColor2(); + float val1[4] = {color1[0], color1[1], color1[2], 1.0f}; float val2[4] = {color2[0], color2[1], color2[2], 1.0f}; + + out_clear_operation.setOverridesColors(true); out_clear_operation.setClearColor(val1); out_clear_operation.setClearColor2(val2); - out_clear_operation.setClearGradient(gradient); + // NOTE: The gradient is forced to enabled because + // MRenderer::useGradient() appears to return zero when + // playblasting, but correctly returns values when rendering + // in the interactive Maya viewport. If we force the clear + // gradient enabled, the 'clearColor's will be set to solid + // colours when a non-gradient is waned. + out_clear_operation.setClearGradient(true); out_clear_operation.setClearStencil(0); out_clear_operation.setClearDepth(1.0f); } else { From 6f988b9b6f85b9a2bd4f1e6de9c36b12620f18a4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 27 Aug 2024 23:37:39 +1000 Subject: [PATCH 143/295] Don't fail when camera node is locked. Make the code more resilient when the camera shape node is locked. Locked nodes cannot have attributes added to them, so we must check for the existence of inLens/outLens attributes. GitHub issue #259. --- python/mmSolver/_api/camera.py | 67 +++++++++++++++++-- .../tools/cameracontextmenu/lib/camera.py | 5 +- .../createimageplane/_lib/mmimageplane.py | 4 +- .../tools/createimageplane/_lib/polyplane.py | 4 +- python/mmSolver/tools/createlens/lib.py | 14 +++- .../tools/togglecameradistort/tool.py | 3 + 6 files changed, 87 insertions(+), 10 deletions(-) diff --git a/python/mmSolver/_api/camera.py b/python/mmSolver/_api/camera.py index 039cca9d2..0d5fa75f2 100644 --- a/python/mmSolver/_api/camera.py +++ b/python/mmSolver/_api/camera.py @@ -44,6 +44,14 @@ def _create_camera_attributes(cam_shp): """ assert isinstance(cam_shp, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(cam_shp) == 'camera' + + if maya.cmds.lockNode(cam_shp, query=True) is False: + LOG.warn( + 'Cannot create camera attributes, camera shape is locked; cam_shp=%r', + cam_shp, + ) + return + node_obj = node_utils.get_as_object_apione(cam_shp) dg_node_fn = OpenMaya.MFnDependencyNode(node_obj) @@ -72,9 +80,16 @@ def _create_camera_attributes(cam_shp): def _create_lens_toggle_setup(cam_tfm, cam_shp): - # When linking to a camera, if an attribute 'lens' - # does not already exist, create it. - _create_camera_attributes(cam_shp) + cam_shp_is_locked = maya.cmds.lockNode(cam_shp, query=True) + if cam_shp_is_locked is False: + _create_camera_attributes(cam_shp) + + in_lens_attr_exists = node_utils.attribute_exists('inLens', cam_shp) + if in_lens_attr_exists is False: + return None + + # When linking to a camera, if an attribute 'lens' does not + # already exist, create it. toggle_nodes = ( maya.cmds.listConnections(cam_shp + ".inLens", shapes=False, destination=True) or [] @@ -84,8 +99,13 @@ def _create_lens_toggle_setup(cam_tfm, cam_shp): toggle_node = maya.cmds.createNode( 'mmLensModelToggle', name=const.LENS_TOGGLE_NODE_NAME ) - maya.cmds.connectAttr(cam_shp + '.inLens', toggle_node + '.inLens') - maya.cmds.connectAttr(toggle_node + '.outLens', cam_shp + '.outLens') + + out_lens_attr_exists = node_utils.attribute_exists('outLens', cam_shp) + + if in_lens_attr_exists: + maya.cmds.connectAttr(cam_shp + '.inLens', toggle_node + '.inLens') + if out_lens_attr_exists: + maya.cmds.connectAttr(toggle_node + '.outLens', cam_shp + '.outLens') else: toggle_node = toggle_nodes[0] return toggle_node @@ -96,6 +116,16 @@ def _link_lens_to_camera(cam_tfm, cam_shp, lens): Assumes that no lens is already connected to the camera.""" assert isinstance(lens, lensmodule.Lens) + + in_lens_attr_exists = node_utils.attribute_exists('inLens', cam_shp) + if in_lens_attr_exists is False: + LOG.warn( + 'Cannot link lens to camera, missing "inLens" attribute; cam_shp=%r lens=%r', + cam_shp, + lens, + ) + return + lens_node = lens.get_node() src = lens_node + '.outLens' dst = cam_shp + '.inLens' @@ -106,6 +136,14 @@ def _link_lens_to_camera(cam_tfm, cam_shp, lens): def _unlink_lens_from_camera(cam_tfm, cam_shp): """Disconnect Lens(es) from the Camera attribute.""" + in_lens_attr_exists = node_utils.attribute_exists('inLens', cam_shp) + if in_lens_attr_exists is False: + LOG.warn( + 'Cannot unlink lens from camera, missing "inLens" attribute; cam_shp=%r', + cam_shp, + ) + return + lens_node_connections = ( maya.cmds.listConnections( cam_shp + ".inLens", @@ -519,7 +557,13 @@ def get_lens_enable(self): msg = "Camera object has no transform/shape node: object=%r" LOG.warn(msg, self) return + toggle_node = _create_lens_toggle_setup(cam_tfm, cam_shp) + if toggle_node is None: + msg = "Camera node toggle could not be found or created: object=%r" + LOG.warn(msg, self) + return + return maya.cmds.getAttr(toggle_node + '.enable') def set_lens_enable(self, value): @@ -533,7 +577,13 @@ def set_lens_enable(self, value): msg = "Camera object has no transform/shape node: object=%r" LOG.warn(msg, self) return + toggle_node = _create_lens_toggle_setup(cam_tfm, cam_shp) + if toggle_node is None: + msg = "Camera node toggle could not be found or created: object=%r" + LOG.warn(msg, self) + return + maya.cmds.setAttr(toggle_node + '.enable', value) return @@ -552,7 +602,14 @@ def get_lens(self): msg = "Camera object has no transform/shape node: object=%r" LOG.warn(msg, self) return lens + _create_lens_toggle_setup(cam_tfm, cam_shp) + in_lens_attr_exists = node_utils.attribute_exists('inLens', cam_shp) + if in_lens_attr_exists is not True: + msg = 'Camera node "inLens" attribute not found: object=%r' + LOG.warn(msg, self) + return + nodes = ( maya.cmds.listConnections( cam_shp + '.inLens', source=True, destination=False, shapes=False diff --git a/python/mmSolver/tools/cameracontextmenu/lib/camera.py b/python/mmSolver/tools/cameracontextmenu/lib/camera.py index 449733391..3ecc609db 100644 --- a/python/mmSolver/tools/cameracontextmenu/lib/camera.py +++ b/python/mmSolver/tools/cameracontextmenu/lib/camera.py @@ -39,12 +39,15 @@ def create_camera(): def camera_lens_distortion_enabled(camera_shape_node): assert camera_shape_node is not None cam = mmapi.Camera(shape=camera_shape_node) - return cam.get_lens_enable() + return cam.get_lens_enable() is True def toggle_camera_lens_distortion_enabled(camera_shape_node): assert camera_shape_node is not None cam = mmapi.Camera(shape=camera_shape_node) enable = cam.get_lens_enable() + if enable is None: + LOG.warn('Cannot toggle camera lens distortion.') + return cam.set_lens_enable(not enable) return diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index c44305675..868b89dbe 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -33,6 +33,7 @@ import mmSolver.tools.createimageplane._lib.mmimageplane_v1 as lib_mmimageplane_v1 import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as lib_mmimageplane_v2 import mmSolver.tools.createimageplane._lib.utilities as lib_utils +import mmSolver.utils.node as node_utils LOG = mmSolver.logger.get_logger() @@ -127,7 +128,8 @@ def create_shape_node( # Image plane hash will be used to stop updating the Viewport 2.0 # shape when the lens distortion values stay the same. lens_eval_node = maya.cmds.createNode('mmLensEvaluate') - lib_utils.force_connect_attr(cam_shp + '.outLens', lens_eval_node + '.inLens') + if node_utils.attribute_exists('outLens', cam_shp): + lib_utils.force_connect_attr(cam_shp + '.outLens', lens_eval_node + '.inLens') lib_utils.force_connect_attr(lens_eval_node + '.outHash', shp + '.lensHashCurrent') maya.cmds.reorder(shp, back=True) diff --git a/python/mmSolver/tools/createimageplane/_lib/polyplane.py b/python/mmSolver/tools/createimageplane/_lib/polyplane.py index d67b31227..b8351a9a6 100644 --- a/python/mmSolver/tools/createimageplane/_lib/polyplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/polyplane.py @@ -29,6 +29,7 @@ import mmSolver.logger import mmSolver.tools.createimageplane._lib.utilities as lib_utils +import mmSolver.utils.node as node_utils LOG = mmSolver.logger.get_logger() @@ -70,7 +71,8 @@ def create_poly_plane(name_mesh_shp, image_plane_tfm, cam_shp): # Drive the Deformer node with the camera lens. src = cam_shp + '.outLens' dst = deform_node + '.inLens' - if not maya.cmds.isConnected(src, dst): + src_attr_exists = node_utils.attribute_exists('outLens', cam_shp) + if src_attr_exists is True and (not maya.cmds.isConnected(src, dst)): lib_utils.force_connect_attr(src, dst) # Get the intermediate mesh shape, so we can re-order the nodes diff --git a/python/mmSolver/tools/createlens/lib.py b/python/mmSolver/tools/createlens/lib.py index c6b067cdf..a0c419136 100644 --- a/python/mmSolver/tools/createlens/lib.py +++ b/python/mmSolver/tools/createlens/lib.py @@ -50,6 +50,7 @@ import mmSolver.logger import mmSolver.api as mmapi +import mmSolver.utils.node as node_utils LOG = mmSolver.logger.get_logger() @@ -84,10 +85,19 @@ def create_marker_connections(cam): for src, dst in zip(src_list, dst_list): maya.cmds.disconnectAttr(src, dst) - # Ensure Marker have connections to the camera lens. cam_shp = cam.get_shape_node() + in_lens_attr_exists = node_utils.attribute_exists('inLens', cam_shp) + if in_lens_attr_exists is False: + LOG.warn( + 'Cannot create marker connections, camera is missing "inLens" attribute; ' + 'cam_shp=%r', + cam_shp, + ) + return + + # Ensure Marker have connections to the camera lens. + src = cam_shp + '.outLens' for mkr_node in mkr_nodes: - src = cam_shp + '.outLens' dst = mkr_node + '.inLens' if not maya.cmds.isConnected(src, dst): maya.cmds.connectAttr(src, dst) diff --git a/python/mmSolver/tools/togglecameradistort/tool.py b/python/mmSolver/tools/togglecameradistort/tool.py index 284c1ea11..c35928cbc 100644 --- a/python/mmSolver/tools/togglecameradistort/tool.py +++ b/python/mmSolver/tools/togglecameradistort/tool.py @@ -77,6 +77,9 @@ def main(): for cam in cams_to_toggle: # Set lens mode (enabled/disabled) enable = cam.get_lens_enable() + if enable is None: + LOG.error('Cannot toggle lens distortion on camera; cam=%r', cam) + continue cam.set_lens_enable(not enable) maya.cmds.select(sel, replace=True) return From 77ea59b4aa0f8f71331f2ab148e7379ce57c3389 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 29 Aug 2024 23:49:55 +1000 Subject: [PATCH 144/295] Image Cache - rename image "groups" to "slots" Because the name "slots" is already used to describe what this is to users and we want to be consistent with our naming. --- .../mmSolver/tools/imagecache/_lib/imagecache_cmd.py | 8 ++++++-- python/mmSolver/tools/imagecache/lib.py | 8 ++++---- .../imagecacheprefs/ui/imagecacheprefs_layout.py | 8 ++++---- .../imagecacheprefs/ui/imagecacheprefs_layout.ui | 12 ++++++------ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py index 7c3987943..7be3fa00e 100644 --- a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py +++ b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py @@ -46,11 +46,15 @@ def get_cpu_cache_group_names(): return maya.cmds.mmImageCache(query=True, cpuGroupNames=True) -def get_gpu_cache_group_count(): +def get_gpu_cache_slot_count(): + # NOTE: The internal mmImageCache name for a "slot" is actually a + # "group" - a group of images. return int(maya.cmds.mmImageCache(query=True, gpuGroupCount=True)) -def get_cpu_cache_group_count(): +def get_cpu_cache_slot_count(): + # NOTE: The internal mmImageCache name for a "slot" is actually a + # "group" - a group of images. return int(maya.cmds.mmImageCache(query=True, cpuGroupCount=True)) diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index 41d259011..e8c5e6eed 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -35,8 +35,8 @@ get_cpu_cache_item_count, get_gpu_cache_group_names, get_cpu_cache_group_names, - get_gpu_cache_group_count, - get_cpu_cache_group_count, + get_gpu_cache_slot_count, + get_cpu_cache_slot_count, get_gpu_cache_group_item_count, get_cpu_cache_group_item_count, get_gpu_cache_group_item_names, @@ -76,8 +76,8 @@ 'get_cpu_cache_item_count', 'get_gpu_cache_group_names', 'get_cpu_cache_group_names', - 'get_gpu_cache_group_count', - 'get_cpu_cache_group_count', + 'get_gpu_cache_slot_count', + 'get_cpu_cache_slot_count', 'get_gpu_cache_group_item_count', 'get_cpu_cache_group_item_count', 'get_gpu_cache_group_item_names', diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py index b7add10c6..cabd51658 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -448,8 +448,8 @@ def get_cpu_capacity_bytes(self): def update_resource_values(self): gpu_cache_item_count = lib.get_gpu_cache_item_count() cpu_cache_item_count = lib.get_cpu_cache_item_count() - gpu_cache_group_count = lib.get_gpu_cache_group_count() - cpu_cache_group_count = lib.get_cpu_cache_group_count() + gpu_cache_slot_count = lib.get_gpu_cache_slot_count() + cpu_cache_slot_count = lib.get_cpu_cache_slot_count() gpu_cache_used = lib.get_gpu_cache_used_bytes() cpu_cache_used = lib.get_cpu_cache_used_bytes() gpu_cache_capacity = lib.get_gpu_cache_capacity_bytes() @@ -472,8 +472,8 @@ def update_resource_values(self): set_count_label(self.gpuCacheItemCountValue_label, gpu_cache_item_count) set_count_label(self.cpuCacheItemCountValue_label, cpu_cache_item_count) - set_count_label(self.gpuCacheGroupCountValue_label, gpu_cache_group_count) - set_count_label(self.cpuCacheGroupCountValue_label, cpu_cache_group_count) + set_count_label(self.gpuCacheSlotCountValue_label, gpu_cache_slot_count) + set_count_label(self.cpuCacheSlotCountValue_label, cpu_cache_slot_count) set_capacity_size_label(self.gpuCacheCapacityValue_label, gpu_cache_capacity) set_capacity_size_label(self.cpuCacheCapacityValue_label, cpu_cache_capacity) diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui index 714aab784..eeeb19c80 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.ui @@ -250,9 +250,9 @@ - + - Image Groups: + Image Slots: @@ -270,7 +270,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> @@ -383,9 +383,9 @@ - + - Image Groups: + Image Slots: @@ -403,7 +403,7 @@ - + <html><head/><body><p><span style=" font-weight:600;">2</span></p></body></html> From 843b3af265e03c6c2a66ed917f6f28200fd44b12 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:30:41 +1000 Subject: [PATCH 145/295] Add Toggle Viewport tools to Display menu This only adds the tools to the menu, nothing else. GitHub issue #263. --- share/config/functions.json | 27 +++++++++++++++++++++++++++ share/config/menu.json | 4 ++++ share/config/shelf.json | 4 ++++ share/config/shelf_minimal.json | 4 ++++ 4 files changed, 39 insertions(+) diff --git a/share/config/functions.json b/share/config/functions.json index c4df719bc..6fea0bbbb 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -456,6 +456,33 @@ "mmSolver.tools.createskydome.tool.main(preset_name=const.PRESET_HORIZON_LINE_NAME);" ] }, + "toggle_viewport_geom": { + "name": "Toggle Viewport Geometry", + "name_shelf": "TglGeo", + "tooltip": "Toggle the visiblity of geometry in the active viewport.", + "command": [ + "import mmSolver.tools.toggleviewportgeom.tool;", + "mmSolver.tools.toggleviewportgeom.tool.main();" + ] + }, + "toggle_viewport_ctrls": { + "name": "Toggle Viewport Controls", + "name_shelf": "TglCtl", + "tooltip": "Toggle the visiblity of character controls in the active viewport.", + "command": [ + "import mmSolver.tools.toggleviewportctrls.tool;", + "mmSolver.tools.toggleviewportctrls.tool.main();" + ] + }, + "toggle_viewport_imgplns": { + "name": "Toggle Viewport Image Planes", + "name_shelf": "TglImPl", + "tooltip": "Toggle the visiblity of image planes in the active viewport.", + "command": [ + "import mmSolver.tools.toggleviewportimgplns.tool;", + "mmSolver.tools.toggleviewportimgplns.tool.main();" + ] + }, "general_tools": { "name": "General Tools", "name_shelf": "", diff --git a/share/config/menu.json b/share/config/menu.json index 8f6ea66c0..aff5f4150 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -79,6 +79,10 @@ "display_tools/---Viewport", "display_tools/center_twodee", "display_tools/center_twodee_remove", + "display_tools/---", + "display_tools/toggle_viewport_geom", + "display_tools/toggle_viewport_ctrls", + "display_tools/toggle_viewport_imgplns", "display_tools/---Create", "display_tools/create_horizon_line", "display_tools/create_axis_dome", diff --git a/share/config/shelf.json b/share/config/shelf.json index 4deb5b31c..cba84d8c4 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -76,6 +76,10 @@ "display_tools/display_popup/---Viewport", "display_tools/display_popup/center_twodee", "display_tools/display_popup/center_twodee_remove", + "display_tools/display_popup/---", + "display_tools/display_popup/toggle_viewport_geom", + "display_tools/display_popup/toggle_viewport_ctrls", + "display_tools/display_popup/toggle_viewport_imgplns", "display_tools/display_popup/---Create", "display_tools/display_popup/create_horizon_line", "display_tools/display_popup/create_axis_dome", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 15f0a6cb4..e46cbe550 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -87,6 +87,10 @@ "display_tools/display_popup/---Viewport", "display_tools/display_popup/center_twodee", "display_tools/display_popup/center_twodee_remove", + "display_tools/display_popup/---", + "display_tools/display_popup/toggle_viewport_geom", + "display_tools/display_popup/toggle_viewport_ctrls", + "display_tools/display_popup/toggle_viewport_imgplns", "display_tools/display_popup/---Create", "display_tools/display_popup/create_horizon_line", "display_tools/display_popup/create_axis_dome", From 8760cf2cbe0cdf9ae2d6cc954a7ed02e52c31b88 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:35:13 +1000 Subject: [PATCH 146/295] Fix MM ImagePlane VP2 display filters The display filters for the MM image plane shapes were not working because both shared the same values and it seems Maya doesn't like that. To avoid confusion the v1 MM image plane doesn't register a display filter at all. This could be enabled with a simple value change if needed, but hopefully nobody uses the old v1 image plane. GitHub issue #263. --- include/mmSolver/nodeTypeIds.h | 12 ++++++------ src/mmSolver/pluginMain.cpp | 19 ++++++++++++++----- src/mmSolver/shape/ImagePlaneShape2Node.cpp | 2 ++ src/mmSolver/shape/ImagePlaneShape2Node.h | 1 + src/mmSolver/shape/ImagePlaneShapeNode.cpp | 2 ++ src/mmSolver/shape/ImagePlaneShapeNode.h | 1 + 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index 4ba8f9b8a..6aeca05ac 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -131,20 +131,20 @@ #define MM_IMAGE_PLANE_SHAPE_TYPE_ID 0x0012F187 #define MM_IMAGE_PLANE_SHAPE_TYPE_NAME "mmImagePlaneShape" -#define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane" +#define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane/v1" #define MM_IMAGE_PLANE_SHAPE_DRAW_REGISTRANT_ID "mmImagePlaneShape" #define MM_IMAGE_PLANE_SHAPE_SELECTION_TYPE_NAME "mmImagePlaneShapeSelection" -// Same as v2. +#define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION "drawdb/geometry/mmSolver/imagePlane/v1" #define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_NAME "mmImagePlaneDisplayFilter" -#define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_LABEL "MM ImagePlane" +#define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_LABEL "MM ImagePlane (legacy)" #define MM_IMAGE_PLANE_SHAPE_2_TYPE_ID 0x0012F18F #define MM_IMAGE_PLANE_SHAPE_2_TYPE_NAME "mmImagePlaneShape2" -#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane2" +#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane/v2" #define MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID "mmImagePlaneShape2" #define MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME "mmImagePlaneShape2Selection" -// Same as v1. -#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME "mmImagePlaneDisplayFilter" +#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION "drawdb/geometry/mmSolver/imagePlane/v2" +#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME "mmImagePlane2DisplayFilter" #define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_LABEL "MM ImagePlane" #define CAMERA_INFERNO_TYPE_ID 0x0012F183 // Not used in mmSolver. diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index a2abbd066..eeecfb792 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -546,6 +546,11 @@ MStatus initializePlugin(MObject obj) { status = MGlobal::executeCommand(mel_cmd); CHECK_MSTATUS(status); + // Should we create a display filter for the 'mmImagePlaneShape'? + // We have a newer 'mmImagePlaneShape2' and it would be good to + // avoid having two almost identical display filter names. + const bool registerLegacyImagePlaneDisplayFilter = false; + // Register plugin display filter. // The filter is registered in both interactive and batch mode // (Hardware 2.0) @@ -557,14 +562,18 @@ MStatus initializePlugin(MObject obj) { mmsolver::BundleShapeNode::m_display_filter_name, mmsolver::BundleShapeNode::m_display_filter_label, mmsolver::BundleShapeNode::m_draw_db_classification); - plugin.registerDisplayFilter( - mmsolver::ImagePlaneShapeNode::m_display_filter_name, - mmsolver::ImagePlaneShapeNode::m_display_filter_label, - mmsolver::ImagePlaneShapeNode::m_draw_db_classification); + if (registerLegacyImagePlaneDisplayFilter) { + plugin.registerDisplayFilter( + mmsolver::ImagePlaneShapeNode::m_display_filter_name, + mmsolver::ImagePlaneShapeNode::m_display_filter_label, + mmsolver::ImagePlaneShapeNode:: + m_display_filter_draw_db_classification); + } plugin.registerDisplayFilter( mmsolver::ImagePlaneShape2Node::m_display_filter_name, mmsolver::ImagePlaneShape2Node::m_display_filter_label, - mmsolver::ImagePlaneShape2Node::m_draw_db_classification); + mmsolver::ImagePlaneShape2Node:: + m_display_filter_draw_db_classification); plugin.registerDisplayFilter( mmsolver::SkyDomeShapeNode::m_display_filter_name, mmsolver::SkyDomeShapeNode::m_display_filter_label, diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index 18536eb6e..c0c8ea14b 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -61,6 +61,8 @@ MString ImagePlaneShape2Node::m_draw_registrant_id( MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID); MString ImagePlaneShape2Node::m_selection_type_name( MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME); +MString ImagePlaneShape2Node::m_display_filter_draw_db_classification( + MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION); MString ImagePlaneShape2Node::m_display_filter_name( MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME); MString ImagePlaneShape2Node::m_display_filter_label( diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.h b/src/mmSolver/shape/ImagePlaneShape2Node.h index e18676a0a..0af176bd8 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.h +++ b/src/mmSolver/shape/ImagePlaneShape2Node.h @@ -79,6 +79,7 @@ class ImagePlaneShape2Node : public MPxLocatorNode { static MString m_draw_db_classification; static MString m_draw_registrant_id; static MString m_selection_type_name; + static MString m_display_filter_draw_db_classification; static MString m_display_filter_name; static MString m_display_filter_label; diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.cpp b/src/mmSolver/shape/ImagePlaneShapeNode.cpp index d1452e55b..6a439656d 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.cpp +++ b/src/mmSolver/shape/ImagePlaneShapeNode.cpp @@ -58,6 +58,8 @@ MString ImagePlaneShapeNode::m_draw_registrant_id( MM_IMAGE_PLANE_SHAPE_DRAW_REGISTRANT_ID); MString ImagePlaneShapeNode::m_selection_type_name( MM_IMAGE_PLANE_SHAPE_SELECTION_TYPE_NAME); +MString ImagePlaneShapeNode::m_display_filter_draw_db_classification( + MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION); MString ImagePlaneShapeNode::m_display_filter_name( MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_NAME); MString ImagePlaneShapeNode::m_display_filter_label( diff --git a/src/mmSolver/shape/ImagePlaneShapeNode.h b/src/mmSolver/shape/ImagePlaneShapeNode.h index d6ee10472..c253ef472 100644 --- a/src/mmSolver/shape/ImagePlaneShapeNode.h +++ b/src/mmSolver/shape/ImagePlaneShapeNode.h @@ -82,6 +82,7 @@ class ImagePlaneShapeNode : public MPxLocatorNode { static MString m_draw_db_classification; static MString m_draw_registrant_id; static MString m_selection_type_name; + static MString m_display_filter_draw_db_classification; static MString m_display_filter_name; static MString m_display_filter_label; From 5c57838e93badef813a6becfd14484a412aa621e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:36:36 +1000 Subject: [PATCH 147/295] Fix creating MM Image Plane v1 v1 is deprecated, but at least now it can be created, before this commit, the function was actually broken. GitHub issue #263. --- mel/AETemplates/AEmmImagePlaneShapeTemplate.mel | 10 ++++++++-- .../tools/createimageplane/_lib/mmimageplane.py | 1 + python/mmSolver/tools/createimageplane/tool.py | 6 +++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel index 075ba7ffe..e72940a58 100644 --- a/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel +++ b/mel/AETemplates/AEmmImagePlaneShapeTemplate.mel @@ -97,6 +97,8 @@ global proc AEmmImagePlaneShape_browser( string $cmd = ""; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "import mmSolver.tools.createimageplane._lib.constant as const;\n"; + $cmd = $cmd + "version = const.MM_IMAGE_PLANE_VERSION_ONE;\n"; $cmd = $cmd + "image_seq = tool.prompt_user_for_image_sequence();\n"; $cmd = $cmd + "if image_seq:\n"; $cmd = $cmd + " mm_ip_shp = \"" + $image_plane_shp + "\";\n"; @@ -106,7 +108,8 @@ global proc AEmmImagePlaneShape_browser( $cmd = $cmd + " lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; $cmd = $cmd + " image_seq,\n"; - $cmd = $cmd + " attr_name=attr_name\n"; + $cmd = $cmd + " attr_name=attr_name,\n"; + $cmd = $cmd + " version=version\n"; $cmd = $cmd + " )\n"; $cmd = $cmd + " else:\n"; $cmd = $cmd + " maya.cmds.setAttr(\n"; @@ -135,6 +138,8 @@ global proc AEmmImagePlaneShape_sequenceSlotChanged( $cmd = $cmd + "import os.path;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.tool as tool;\n"; $cmd = $cmd + "import mmSolver.tools.createimageplane.lib as lib;\n"; + $cmd = $cmd + "import mmSolver.tools.createimageplane._lib.constant as const;\n"; + $cmd = $cmd + "version = const.MM_IMAGE_PLANE_VERSION_ONE;\n"; $cmd = $cmd + "mm_ip_shp = \"" + $image_plane_shp + "\";\n"; $cmd = $cmd + "attr_name = \"" + $attr_name + "\";\n"; $cmd = $cmd + "current_slot_attr = \"" + $slot_attr_name + "\";\n"; @@ -145,7 +150,8 @@ global proc AEmmImagePlaneShape_sequenceSlotChanged( $cmd = $cmd + "lib.set_image_sequence(\n"; $cmd = $cmd + " mm_ip_shp,\n"; $cmd = $cmd + " image_seq,\n"; - $cmd = $cmd + " attr_name=current_slot_attr\n"; + $cmd = $cmd + " attr_name=current_slot_attr,\n"; + $cmd = $cmd + " version=version\n"; $cmd = $cmd + ")\n"; python($cmd); diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index 868b89dbe..73e10c1ef 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -211,6 +211,7 @@ def create_shape_node( # Add extra message attributes for finding nodes during callbacks. if shader_node_network is not None: maya.cmds.addAttr(shp, longName='shaderFileNode', attributeType='message') + lib_utils.force_connect_attr(file_node + '.message', shp + '.shaderFileNode') # Logic to calculate the frame number. node = maya.cmds.createNode('mmImageSequenceFrameLogic') diff --git a/python/mmSolver/tools/createimageplane/tool.py b/python/mmSolver/tools/createimageplane/tool.py index 792724120..c5a400208 100644 --- a/python/mmSolver/tools/createimageplane/tool.py +++ b/python/mmSolver/tools/createimageplane/tool.py @@ -147,11 +147,11 @@ def _run(version): # user has the transform and shape nodes selected. if cam_shp in created: continue - mm_ip_tfm, mm_ip_shp = lib.create_image_plane_on_camera(cam) + mm_ip_tfm, mm_ip_shp = lib.create_image_plane_on_camera(cam, version=version) image_seq = prompt_user_for_image_sequence() if image_seq: - lib.set_image_sequence(mm_ip_tfm, image_seq) + lib.set_image_sequence(mm_ip_tfm, image_seq, version=version) nodes.append(mm_ip_shp) created.add(cam_shp) @@ -168,7 +168,7 @@ def _run(version): def main(): - _run(const.MM_IMAGE_PLANE_VERSION_ONE) + _run(const.MM_IMAGE_PLANE_VERSION_TWO) def main_version_one(): From 3e9526ea721c1fe5a6f8f34ce5f8e2e84128cf48 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:38:38 +1000 Subject: [PATCH 148/295] Viewport Utility - update for mmImagePlaneShape2 Allows changing the visibility of mmImagePlaneShape2 nodes with the module. GitHub issue #263. --- python/mmSolver/utils/viewport.py | 74 +++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/python/mmSolver/utils/viewport.py b/python/mmSolver/utils/viewport.py index 827602775..6fa81e6e5 100644 --- a/python/mmSolver/utils/viewport.py +++ b/python/mmSolver/utils/viewport.py @@ -56,6 +56,9 @@ 'nRigid', 'texture', 'stroke', + # + # Custom mmSolver node types: + 'mmImagePlaneShape2', ] @@ -410,14 +413,16 @@ def set_selection_highlight(model_panel, value): def _get_node_type_visibility(model_panel, node_type): """ - Query the image plane visibility. + Query the node type visibility of the given model panel. :param model_panel: Model panel name to set visibility. :type model_panel: str - :return: The visibility of image planes. + :return: The visibility of the node type. :rtype: bool """ + assert isinstance(model_panel, str) + assert isinstance(node_type, str) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) kwargs = { 'query': True, @@ -434,9 +439,12 @@ def _set_node_type_visibility(model_panel, node_type, value): :param model_panel: Model panel name to set visibility. :type model_panel: str - :param value: Visibility of image planes. + :param value: Visibility of the node type. :type value: bool """ + assert isinstance(model_panel, str) + assert isinstance(node_type, str) + assert isinstance(value, bool) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) kwargs = { 'edit': True, @@ -446,6 +454,46 @@ def _set_node_type_visibility(model_panel, node_type, value): return +def _get_plugin_display_filter_visibility(model_panel, plugin_display_filter): + """ + Query the display filter visibility of the given model panel. + + :param model_panel: Model panel name to set visibility. + :type model_panel: str + + :return: The visibility of the display filter. + :rtype: bool + """ + assert isinstance(model_panel, str) + assert isinstance(plugin_display_filter, str) + model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) + value = maya.cmds.modelEditor( + model_editor, query=True, queryPluginObjects=plugin_display_filter + ) + return value + + +def _set_plugin_display_filter_visibility(model_panel, plugin_display_filter, value): + """ + Set the visibility of a plug-in display filter for the given + model panel. + + :param model_panel: Model panel name to set visibility. + :type model_panel: str + + :param value: Visibility of the node type. + :type value: bool + """ + assert isinstance(model_panel, str) + assert isinstance(plugin_display_filter, str) + assert isinstance(value, bool) + model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) + maya.cmds.modelEditor( + model_editor, edit=True, pluginObjects=[plugin_display_filter, value] + ) + return + + def get_grid_visibility(model_panel): """ Query the grids visibility in given model panel. @@ -474,6 +522,24 @@ def set_image_plane_visibility(model_panel, value): return _set_node_type_visibility(model_panel, 'imagePlane', value) +def get_mm_image_plane_visibility(model_panel): + """ + Query the MM Image Plane visibility in given model panel. + """ + return _get_plugin_display_filter_visibility( + model_panel, 'mmImagePlane2DisplayFilter' + ) + + +def set_mm_image_plane_visibility(model_panel, value): + """ + Set the visibility of MM Image Plane nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmImagePlane2DisplayFilter', value + ) + + def get_camera_visibility(model_panel): """ Query the camera visibility in given model panel. @@ -757,6 +823,7 @@ def set_stroke_visibility(model_panel, value): NODE_TYPE_TO_GET_VIS_FUNC = { 'mesh': get_mesh_visibility, 'imagePlane': get_image_plane_visibility, + 'mmImagePlane': get_mm_image_plane_visibility, 'nurbsCurve': get_nurbs_curve_visibility, 'nurbsSurface': get_nurbs_surface_visibility, 'subdiv': get_subdiv_visibility, @@ -781,6 +848,7 @@ def set_stroke_visibility(model_panel, value): NODE_TYPE_TO_SET_VIS_FUNC = { 'mesh': set_mesh_visibility, 'imagePlane': set_image_plane_visibility, + 'mmImagePlane': set_mm_image_plane_visibility, 'nurbsCurve': set_nurbs_curve_visibility, 'nurbsSurface': set_nurbs_surface_visibility, 'subdiv': set_subdiv_visibility, From 66b58f039ba1885bbe1203abcba97088523a37af Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:39:48 +1000 Subject: [PATCH 149/295] Toggle Viewport Image Planes hides mmImagePlaneShape2 MM Image Plane v2 is also toggled with Maya native image planes. Github issue #263 --- python/mmSolver/tools/toggleviewportimgplns/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/mmSolver/tools/toggleviewportimgplns/lib.py b/python/mmSolver/tools/toggleviewportimgplns/lib.py index 11f5db922..d9be7ae94 100644 --- a/python/mmSolver/tools/toggleviewportimgplns/lib.py +++ b/python/mmSolver/tools/toggleviewportimgplns/lib.py @@ -33,4 +33,5 @@ def toggle_image_plane_visibility(model_panel): value = viewport_utils.get_image_plane_visibility(model_panel) new_value = not value viewport_utils.set_image_plane_visibility(model_panel, new_value) + viewport_utils.set_mm_image_plane_visibility(model_panel, new_value) return From 4065ceca0ca0650916b82370189a503ae3a67171 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 4 Sep 2024 23:57:14 +1000 Subject: [PATCH 150/295] Toggle Viewport Node Types - Update documentation GitHub issue #263 --- docs/source/tools_displaytools.rst | 66 ++++++++++++++++++++++++++++++ docs/source/tools_hotkeys.rst | 8 ++-- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/docs/source/tools_displaytools.rst b/docs/source/tools_displaytools.rst index 6f6f73753..f54dea360 100644 --- a/docs/source/tools_displaytools.rst +++ b/docs/source/tools_displaytools.rst @@ -57,6 +57,72 @@ To run the tool, use this Python command: tool.remove() +.. _toggle-viewport-node-types-ref: + +Toggle Viewport Node Types +-------------------------- + +There are a range of `Toggle Viewport *` tools that are used to +hide/show groups of node types. + +Node type groups include: + +- Show/Hide geometry in the current viewport. + +- Show/Hide Markers, Bundles, Locators and NURBS Curves in the current viewport. + +- Show/Hide Image Planes in the current viewport. + +Usage: + +1) Activate a 3D Viewport. + +2) Run tool. + + - The node type visibility will be shown/hidden based on the + current visibility. + +Each different group can be activated with a slightly different Python +command. + + +Toggle Viewport Geometry (Mesh, NURBS, etc): + +.. code:: python + + import mmSolver.tools.toggleviewportgeom.tool as tool + tool.main() + + +Toggle Viewport Controls: (Locators, Curves, etc) + +.. code:: python + + import mmSolver.tools.toggleviewportctrls.tool as tool + tool.main() + + +Toggle Viewport Image Planes (Maya native and MM solver image planes): + +.. code:: python + + import mmSolver.tools.toggleviewportimgplns.tool as tool + tool.main() + + +Alternatively, a user can construct their own custom scripts to +control visibility like so: + +.. code:: python + + import mmSolver.utils.viewport as viewport_utils + model_panel = viewport_utils.get_active_model_panel() + if model_panel: + value = viewport_utils.get_locator_visibility(model_panel) + new_value = not value + viewport_utils.set_locator_visibility(model_panel, new_value) + + .. _create-sky-dome-tool-ref: Create Horizon / Axis Dome / Sky Dome diff --git a/docs/source/tools_hotkeys.rst b/docs/source/tools_hotkeys.rst index e48e504b5..44ff2f25e 100644 --- a/docs/source/tools_hotkeys.rst +++ b/docs/source/tools_hotkeys.rst @@ -86,16 +86,16 @@ viewport. * - Press **ALT + 1** key - Show/Hide geometry in the current viewport. - - N/A + - :ref:`Link ` * - Press **ALT + 2** key - Show/Hide Markers, Bundles, Locators and NURBS Curves in the current viewport. - - N/A + - :ref:`Link ` * - Press **ALT + 3** key - - Show/Hide Maya Image Planes in the current viewport. - - N/A + - Show/Hide Image Planes in the current viewport. + - :ref:`Link ` .. _marking-menu-heading: From 57fbd18da7c3f88a711b17a9f682f8c0ac46df6e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 5 Sep 2024 00:01:31 +1000 Subject: [PATCH 151/295] Toggle Viewport Node Types - Link docs to python module GitHub issue #263 --- docs/source/mmSolver.utils.rst | 4 ++++ docs/source/tools_displaytools.rst | 3 +++ 2 files changed, 7 insertions(+) diff --git a/docs/source/mmSolver.utils.rst b/docs/source/mmSolver.utils.rst index 6360e0248..8c31df4d2 100644 --- a/docs/source/mmSolver.utils.rst +++ b/docs/source/mmSolver.utils.rst @@ -1,3 +1,5 @@ +.. _mmsolver-utils-ref: + mmSolver.utils ============== @@ -237,6 +239,8 @@ Undo :members: :undoc-members: +.. _mmsolver-utils-viewport-ref: + Viewport ++++++++ diff --git a/docs/source/tools_displaytools.rst b/docs/source/tools_displaytools.rst index f54dea360..9e7c56001 100644 --- a/docs/source/tools_displaytools.rst +++ b/docs/source/tools_displaytools.rst @@ -123,6 +123,9 @@ control visibility like so: viewport_utils.set_locator_visibility(model_panel, new_value) +See :ref:`mmSolver.utils.viewport ` +Python module documentation for more help. + .. _create-sky-dome-tool-ref: Create Horizon / Axis Dome / Sky Dome From 4dfd141ca822edeeeabdecb9a99da264849509c1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 5 Sep 2024 00:06:41 +1000 Subject: [PATCH 152/295] Toggle Viewport Controls - Correct documentation The documentation was technically correct, but markers and bundles are now custom shapes were are not handled by the Toggle Viewport Controls tool. The documentation has been corrected to what the tool currently does, but the tool should be updated in the future. GitHub issue #263 --- docs/source/tools_displaytools.rst | 2 +- docs/source/tools_hotkeys.rst | 2 +- python/mmSolver/tools/toggleviewportctrls/lib.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/tools_displaytools.rst b/docs/source/tools_displaytools.rst index 9e7c56001..b5dad3289 100644 --- a/docs/source/tools_displaytools.rst +++ b/docs/source/tools_displaytools.rst @@ -69,7 +69,7 @@ Node type groups include: - Show/Hide geometry in the current viewport. -- Show/Hide Markers, Bundles, Locators and NURBS Curves in the current viewport. +- Show/Hide Locators and NURBS Curves in the current viewport. - Show/Hide Image Planes in the current viewport. diff --git a/docs/source/tools_hotkeys.rst b/docs/source/tools_hotkeys.rst index 44ff2f25e..b0d77772f 100644 --- a/docs/source/tools_hotkeys.rst +++ b/docs/source/tools_hotkeys.rst @@ -89,7 +89,7 @@ viewport. - :ref:`Link ` * - Press **ALT + 2** key - - Show/Hide Markers, Bundles, Locators and NURBS Curves in the + - Show/Hide Locators and NURBS Curves in the current viewport. - :ref:`Link ` diff --git a/python/mmSolver/tools/toggleviewportctrls/lib.py b/python/mmSolver/tools/toggleviewportctrls/lib.py index df7e0396c..3e44b1681 100644 --- a/python/mmSolver/tools/toggleviewportctrls/lib.py +++ b/python/mmSolver/tools/toggleviewportctrls/lib.py @@ -32,6 +32,7 @@ def toggle_ctrls_visibility(model_panel): value = viewport_utils.get_locator_visibility(model_panel) new_value = not value + # TODO: Add MM Markers, Bundles and Lines to be disabled/enabled. viewport_utils.set_nurbs_curve_visibility(model_panel, new_value) viewport_utils.set_locator_visibility(model_panel, new_value) viewport_utils.set_joint_visibility(model_panel, new_value) From 27c093d74dfcef7400f5aefed5992b0300c6d0d3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 5 Sep 2024 00:35:05 +1000 Subject: [PATCH 153/295] Show/Hide Markers, Bundles, etc with Viewport module. GitHub issue #263. --- .../tools/toggleviewportimgplns/lib.py | 2 +- python/mmSolver/utils/viewport.py | 120 +++++++++++++++--- 2 files changed, 101 insertions(+), 21 deletions(-) diff --git a/python/mmSolver/tools/toggleviewportimgplns/lib.py b/python/mmSolver/tools/toggleviewportimgplns/lib.py index d9be7ae94..622ee083b 100644 --- a/python/mmSolver/tools/toggleviewportimgplns/lib.py +++ b/python/mmSolver/tools/toggleviewportimgplns/lib.py @@ -33,5 +33,5 @@ def toggle_image_plane_visibility(model_panel): value = viewport_utils.get_image_plane_visibility(model_panel) new_value = not value viewport_utils.set_image_plane_visibility(model_panel, new_value) - viewport_utils.set_mm_image_plane_visibility(model_panel, new_value) + viewport_utils.set_mm_image_plane_v2_visibility(model_panel, new_value) return diff --git a/python/mmSolver/utils/viewport.py b/python/mmSolver/utils/viewport.py index 6fa81e6e5..039f9e48d 100644 --- a/python/mmSolver/utils/viewport.py +++ b/python/mmSolver/utils/viewport.py @@ -59,6 +59,10 @@ # # Custom mmSolver node types: 'mmImagePlaneShape2', + 'mmMarkerShape', + 'mmBundleShape', + 'mmLineShape', + 'mmSkyDomeShape', ] @@ -522,24 +526,6 @@ def set_image_plane_visibility(model_panel, value): return _set_node_type_visibility(model_panel, 'imagePlane', value) -def get_mm_image_plane_visibility(model_panel): - """ - Query the MM Image Plane visibility in given model panel. - """ - return _get_plugin_display_filter_visibility( - model_panel, 'mmImagePlane2DisplayFilter' - ) - - -def set_mm_image_plane_visibility(model_panel, value): - """ - Set the visibility of MM Image Plane nodes in the given model panel. - """ - return _set_plugin_display_filter_visibility( - model_panel, 'mmImagePlane2DisplayFilter', value - ) - - def get_camera_visibility(model_panel): """ Query the camera visibility in given model panel. @@ -820,10 +806,91 @@ def set_stroke_visibility(model_panel, value): return _set_node_type_visibility(model_panel, 'strokes', value) +def get_mm_image_plane_v2_visibility(model_panel): + """ + Query the MM Image Plane visibility in given model panel. + """ + return _get_plugin_display_filter_visibility( + model_panel, 'mmImagePlane2DisplayFilter' + ) + + +def set_mm_image_plane_v2_visibility(model_panel, value): + """ + Set the visibility of MM Image Plane nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmImagePlane2DisplayFilter', value + ) + + +def get_mm_marker_visibility(model_panel): + """ + Query the MM Marker visibility in given model panel. + """ + return _get_plugin_display_filter_visibility(model_panel, 'mmMarkerDisplayFilter') + + +def set_mm_marker_visibility(model_panel, value): + """ + Set the visibility of MM Marker nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmMarkerDisplayFilter', value + ) + + +def get_mm_bundle_visibility(model_panel): + """ + Query the MM Bundle visibility in given model panel. + """ + return _get_plugin_display_filter_visibility(model_panel, 'mmBundleDisplayFilter') + + +def set_mm_bundle_visibility(model_panel, value): + """ + Set the visibility of MM Bundle nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmBundleDisplayFilter', value + ) + + +def get_mm_line_visibility(model_panel): + """ + Query the MM Line visibility in given model panel. + """ + return _get_plugin_display_filter_visibility(model_panel, 'mmLineDisplayFilter') + + +def set_mm_line_visibility(model_panel, value): + """ + Set the visibility of MM Line nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmLineDisplayFilter', value + ) + + +def get_mm_sky_dome_visibility(model_panel): + """ + Query the MM Sky Dome visibility in given model panel. + """ + return _get_plugin_display_filter_visibility(model_panel, 'mmSkyDomeDisplayFilter') + + +def set_mm_sky_dome_visibility(model_panel, value): + """ + Set the visibility of MM Sky Dome nodes in the given model panel. + """ + return _set_plugin_display_filter_visibility( + model_panel, 'mmSkyDomeDisplayFilter', value + ) + + NODE_TYPE_TO_GET_VIS_FUNC = { 'mesh': get_mesh_visibility, 'imagePlane': get_image_plane_visibility, - 'mmImagePlane': get_mm_image_plane_visibility, 'nurbsCurve': get_nurbs_curve_visibility, 'nurbsSurface': get_nurbs_surface_visibility, 'subdiv': get_subdiv_visibility, @@ -842,13 +909,19 @@ def set_stroke_visibility(model_panel, value): 'nRigid': get_nrigid_visibility, 'texture': get_texture_visibility, 'stroke': get_stroke_visibility, + # + # Custom MM Solver node types. + 'mmImagePlaneShape2': get_mm_image_plane_v2_visibility, + 'mmMarkerShape': get_mm_marker_visibility, + 'mmBundleShape': get_mm_bundle_visibility, + 'mmLineShape': get_mm_line_visibility, + 'mmSkyDomeShape': get_mm_sky_dome_visibility, } NODE_TYPE_TO_SET_VIS_FUNC = { 'mesh': set_mesh_visibility, 'imagePlane': set_image_plane_visibility, - 'mmImagePlane': set_mm_image_plane_visibility, 'nurbsCurve': set_nurbs_curve_visibility, 'nurbsSurface': set_nurbs_surface_visibility, 'subdiv': set_subdiv_visibility, @@ -867,6 +940,13 @@ def set_stroke_visibility(model_panel, value): 'nRigid': set_nrigid_visibility, 'texture': set_texture_visibility, 'stroke': set_stroke_visibility, + # + # Custom MM Solver node types. + 'mmImagePlaneShape2': set_mm_image_plane_v2_visibility, + 'mmMarkerShape': set_mm_marker_visibility, + 'mmBundleShape': set_mm_bundle_visibility, + 'mmLineShape': set_mm_line_visibility, + 'mmSkyDomeShape': set_mm_sky_dome_visibility, } From 734cf2f7ab468d2a3282f25849831c55c138504a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 5 Sep 2024 00:36:15 +1000 Subject: [PATCH 154/295] Show/Hide Markers, Bundles and Lines GitHub issue #263. --- docs/source/tools_hotkeys.rst | 2 +- python/mmSolver/tools/toggleviewportctrls/lib.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/source/tools_hotkeys.rst b/docs/source/tools_hotkeys.rst index b0d77772f..44ff2f25e 100644 --- a/docs/source/tools_hotkeys.rst +++ b/docs/source/tools_hotkeys.rst @@ -89,7 +89,7 @@ viewport. - :ref:`Link ` * - Press **ALT + 2** key - - Show/Hide Locators and NURBS Curves in the + - Show/Hide Markers, Bundles, Locators and NURBS Curves in the current viewport. - :ref:`Link ` diff --git a/python/mmSolver/tools/toggleviewportctrls/lib.py b/python/mmSolver/tools/toggleviewportctrls/lib.py index 3e44b1681..a6c1537f8 100644 --- a/python/mmSolver/tools/toggleviewportctrls/lib.py +++ b/python/mmSolver/tools/toggleviewportctrls/lib.py @@ -32,7 +32,9 @@ def toggle_ctrls_visibility(model_panel): value = viewport_utils.get_locator_visibility(model_panel) new_value = not value - # TODO: Add MM Markers, Bundles and Lines to be disabled/enabled. + viewport_utils.set_mm_marker_visibility(model_panel, new_value) + viewport_utils.set_mm_bundle_visibility(model_panel, new_value) + viewport_utils.set_mm_line_visibility(model_panel, new_value) viewport_utils.set_nurbs_curve_visibility(model_panel, new_value) viewport_utils.set_locator_visibility(model_panel, new_value) viewport_utils.set_joint_visibility(model_panel, new_value) From 48d608ae3e882d8a24e410faf1e635e1ec375e7e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 7 Sep 2024 22:03:59 +1000 Subject: [PATCH 155/295] mmImagePlane2 - Fix Cached Playback not working The node was advertising that it should be cached but the node doesn't support Cached Playback, so we disable Cached Playback for now with the intention of adding support in the future so we can load images for other frames in the background thread, so we don't block the user. --- src/mmSolver/shape/ImagePlaneShape2Node.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index c0c8ea14b..8ec1b4ab4 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -170,7 +170,7 @@ void ImagePlaneShape2Node::getCacheSetup( monitoredAttributes); assert(!disablingInfo.getCacheDisabled()); cacheSetupInfo.setPreference(MNodeCacheSetupInfo::kWantToCacheByDefault, - true); + false); } #endif From 2eb56e253cc0b9f4cc8a0bbb70d738ad82edf082 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 7 Sep 2024 22:04:56 +1000 Subject: [PATCH 156/295] mmImagePlaneShape2 - Reorganise Viewport 2 include line. --- src/mmSolver/shape/ImagePlaneShape2Node.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index 8ec1b4ab4..42444c259 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -41,12 +41,14 @@ #include #include #include -#include #if MAYA_API_VERSION >= 20190000 #include #endif +// Maya Viewport 2.0 +#include + // MM Solver #include "ImagePlaneUtils.h" #include "mmSolver/mayahelper/maya_utils.h" From eb4cd8dd2a9a4faae23da4283cb0dff8570e7fd8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 7 Sep 2024 22:31:37 +1000 Subject: [PATCH 157/295] MM ImagePlane2 geometry override - re-order functions. --- src/mmSolver/shape/ImagePlaneGeometry2Override.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index dc8fd263c..77beb468d 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -88,19 +88,15 @@ class ImagePlaneGeometry2Override : public MPxGeometryOverride { MHWRender::DrawAPI supportedDrawAPIs() const override; bool hasUIDrawables() const override; + void addUIDrawables(const MDagPath &path, MUIDrawManager &drawManager, + const MFrameContext &frameContext) override; void updateDG() override; - void updateRenderItems(const MDagPath &path, MRenderItemList &list) override; - - void addUIDrawables(const MDagPath &path, MUIDrawManager &drawManager, - const MFrameContext &frameContext) override; - void populateGeometry(const MGeometryRequirements &requirements, const MRenderItemList &renderItems, MGeometry &data) override; - void cleanUp() override; #if MAYA_API_VERSION >= 20190000 From ab9a4ab448540d463c833152e06ca64a70dcfa30 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 7 Sep 2024 22:36:30 +1000 Subject: [PATCH 158/295] MM ImagePlane2 - change class name for print To help us debug the real location of issues. --- .../shape/ImagePlaneGeometry2Override.cpp | 179 +++++++++++------- 1 file changed, 108 insertions(+), 71 deletions(-) diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index d87aec3ea..9540c9e2a 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -152,7 +152,9 @@ void ImagePlaneGeometry2Override::shader_link_lost_func( ShaderLinkLostUserData2 *userData) { // TODO: What should this function do? Does it need to do anything? MMSOLVER_MAYA_DBG( - "mmImagePlaneShape: shader_link_lost_func: link_lost_count=" + "mmImagePlaneGeometry2Override: " + "shader_link_lost_func: " + "link_lost_count=" << (*userData).link_lost_count << " set_shader_count=" << (*userData).set_shader_count); (*userData).link_lost_count += 1; @@ -308,16 +310,19 @@ void ImagePlaneGeometry2Override::query_node_attributes( const char *file_color_space_name = mmcolorio::guess_color_space_name_from_file_path( out_file_path.asChar()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" - << " file_color_space_name=\"" << file_color_space_name - << "\"."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "query_node_attributes:" + << " file_color_space_name=\"" << file_color_space_name << "\"."); const char *output_color_space_name = mmcolorio::get_role_color_space_name( mmcolorio::ColorSpaceRole::kSceneLinear); out_output_color_space_name = MString(output_color_space_name); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: query_node_attributes:" - << " out_output_color_space_name=\"" - << out_output_color_space_name.asChar() << "\"."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "query_node_attributes:" + << " out_output_color_space_name=\"" + << out_output_color_space_name.asChar() << "\"."); } void ImagePlaneGeometry2Override::updateDG() { @@ -409,7 +414,8 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const MHWRender::MSamplerState *out_texture_sampler) { MStatus status = MStatus::kSuccess; const bool verbose = false; - MMSOLVER_MAYA_VRB("mmImagePlaneShape: set_shader_instance_parameters."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: set_shader_instance_parameters."); const float color[] = {color_gain[0], color_gain[1], color_gain[2], 1.0f}; status = shader->setParameter("gColorGain", color); @@ -450,25 +456,26 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( CHECK_MSTATUS(status); status = shader->setIsTransparent(is_transparent); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: shader->isTransparent()=" + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: shader->isTransparent()=" << shader->isTransparent()); CHECK_MSTATUS(status); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: file_path=" << file_path.asChar()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: file_path=" << file_path.asChar()); rust::Str file_path_rust_str = rust::Str(file_path.asChar()); rust::String expanded_file_path_rust_string = mmcore::expand_file_path_string(file_path_rust_str, frame); MString expanded_file_path(expanded_file_path_rust_string.data(), expanded_file_path_rust_string.length()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: expanded_file_path=" + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: expanded_file_path=" << expanded_file_path.asChar()); - MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: start out_color_texture=" << out_color_texture); + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: start out_color_texture=" + << out_color_texture); if (!out_color_texture) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: use image read"); + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: use image read"); const bool do_texture_update = false; image::ImageCache &image_cache = image::ImageCache::getInstance(); @@ -477,50 +484,64 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( expanded_file_path, do_texture_update); if (out_color_texture) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->name()=" + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: texture->name()=" << out_color_texture->name().asChar()); const void *resource_handle = out_color_texture->resourceHandle(); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->resourceHandle()=" - << resource_handle); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture->resourceHandle()=" + << resource_handle); if (resource_handle) { MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: *texture->resourceHandle()=" + "mmImagePlaneGeometry2Override: *texture->resourceHandle()=" << *(uint32_t *)resource_handle); } - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasAlpha()=" - << out_color_texture->hasAlpha()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->hasZeroAlpha()=" - << out_color_texture->hasZeroAlpha()); MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: texture->hasTransparentAlpha()=" + "mmImagePlaneGeometry2Override: texture->hasAlpha()=" + << out_color_texture->hasAlpha()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture->hasZeroAlpha()=" + << out_color_texture->hasZeroAlpha()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture->hasTransparentAlpha()=" << out_color_texture->hasTransparentAlpha()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture->bytesPerPixel()=" - << out_color_texture->bytesPerPixel()); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture->bytesPerPixel()=" + << out_color_texture->bytesPerPixel()); MTextureDescription texture_desc; out_color_texture->textureDescription(texture_desc); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fWidth=" - << texture_desc.fWidth); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fHeight=" - << texture_desc.fHeight); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fDepth=" - << texture_desc.fDepth); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerRow=" - << texture_desc.fBytesPerRow); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fBytesPerSlice=" - << texture_desc.fBytesPerSlice); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fMipmaps=" - << texture_desc.fMipmaps); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fArraySlices=" - << texture_desc.fArraySlices); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fFormat=" - << texture_desc.fFormat); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fTextureType=" - << texture_desc.fTextureType); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: texture_desc.fEnvMapType=" - << texture_desc.fEnvMapType); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fWidth=" + << texture_desc.fWidth); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fHeight=" + << texture_desc.fHeight); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fDepth=" + << texture_desc.fDepth); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fBytesPerRow=" + << texture_desc.fBytesPerRow); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fBytesPerSlice=" + << texture_desc.fBytesPerSlice); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fMipmaps=" + << texture_desc.fMipmaps); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fArraySlices=" + << texture_desc.fArraySlices); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fFormat=" + << texture_desc.fFormat); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fTextureType=" + << texture_desc.fTextureType); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: texture_desc.fEnvMapType=" + << texture_desc.fEnvMapType); } } @@ -540,8 +561,10 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( shader->setParameter("gImageTextureSampler", *out_texture_sampler); CHECK_MSTATUS(status); } else { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get texture sampler." - << " out_texture_sampler=" << out_texture_sampler); + MMSOLVER_MAYA_WRN( + "mmImagePlaneGeometry2Override: " + "Could not get texture sampler." + << " out_texture_sampler=" << out_texture_sampler); } if (out_color_texture) { @@ -553,8 +576,8 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( out_color_texture = nullptr; } else { MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: Could not get color texture; " - "did not assign texture." + "mmImagePlaneGeometry2Override: " + "Could not get color texture; did not assign texture." << " out_color_texture=" << out_color_texture); } @@ -565,26 +588,31 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, MRenderItemList &list) { const bool verbose = false; if (!m_geometry_node_path.isValid()) { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " - << "Geometry node DAG path is not valid."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "Geometry node DAG path is not valid."); return; } MHWRender::MRenderer *renderer = MRenderer::theRenderer(); if (!renderer) { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MRenderer."); + MMSOLVER_MAYA_WRN( + "mmImagePlaneGeometry2Override: " + "Could not get MRenderer."); return; } const MHWRender::MShaderManager *shaderManager = renderer->getShaderManager(); if (!shaderManager) { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: Could not get MShaderManager."); + MMSOLVER_MAYA_WRN( + "mmImagePlaneGeometry2Override: " + "Could not get MShaderManager."); return; } if (m_geometry_node_type != MFn::kMesh) { - MMSOLVER_MAYA_WRN("mmImagePlaneShape: " + MMSOLVER_MAYA_WRN("mmImagePlaneGeometry2Override: " << "Only Meshes are supported, geometry node " "given is not a mesh."); return; @@ -601,7 +629,8 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, wireframeItem = list.itemAt(index); } else { MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: Generate wireframe MRenderItem..."); + "mmImagePlaneGeometry2Override: " + "Generate wireframe MRenderItem..."); wireframeItem = MRenderItem::Create( renderItemName_imagePlaneWireframe, MRenderItem::DecorationItem, MGeometry::kLines); @@ -622,7 +651,9 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, if (index >= 0) { shadedItem = list.itemAt(index); } else { - MMSOLVER_MAYA_VRB("mmImagePlaneShape: Generate shaded MRenderItem..."); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "Generate shaded MRenderItem..."); shadedItem = MRenderItem::Create(renderItemName_imagePlaneShaded, MRenderItem::NonMaterialSceneItem, MGeometry::kTriangles); @@ -653,31 +684,31 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, if (shadedItem) { shadedItem->enable(m_visible); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->isEnabled()=" << shadedItem->isEnabled()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->isShaderFromNode()=" << shadedItem->isShaderFromNode()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->isMultiDraw()=" << shadedItem->isMultiDraw()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->isConsolidated()=" << shadedItem->isConsolidated()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->wantConsolidation()=" << shadedItem->wantConsolidation()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->castsShadows()=" << shadedItem->castsShadows()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->receivesShadows()=" << shadedItem->receivesShadows()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->excludedFromPostEffects()=" << shadedItem->excludedFromPostEffects()); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: " + MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: " << "shadedItem->supportsAdvancedTransparency()=" << shadedItem->supportsAdvancedTransparency()); @@ -688,8 +719,10 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, const MString shader_file_path = mmsolver::render::find_shader_file_path("mmImagePlane.ogsfx"); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: found shader_file_path=\"" - << shader_file_path << "\""); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "found shader_file_path=\"" + << shader_file_path << "\""); if (shader_file_path.length() > 0) { MString shader_text = @@ -699,8 +732,10 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, mmcolorio::generate_shader_text( m_input_color_space_name.asChar(), m_output_color_space_name.asChar(), ocio_shader_text); - MMSOLVER_MAYA_VRB("mmImagePlaneShape: ocio_shader_text=\"" - << ocio_shader_text << "\""); + MMSOLVER_MAYA_VRB( + "mmImagePlaneGeometry2Override: " + "ocio_shader_text=\"" + << ocio_shader_text << "\""); if (ocio_shader_text.size() > 0) { const MString ocio_function_declare_text = MString( "vec4 OCIODisplay(vec4 passthrough) { return " @@ -726,7 +761,8 @@ void ImagePlaneGeometry2Override::updateRenderItems(const MDagPath &path, renderer->getTextureManager(); if (!texture_manager) { MMSOLVER_MAYA_WRN( - "mmImagePlaneShape: Could not get MTextureManager."); + "mmImagePlaneGeometry2Override: " + "Could not get MTextureManager."); return; } @@ -749,7 +785,8 @@ void ImagePlaneGeometry2Override::populateGeometry( const bool verbose = false; if (!m_geometry_node_path.isValid()) { MMSOLVER_MAYA_VRB( - "mmImagePlaneShape: Geometry node DAG path is not valid."); + "mmImagePlaneGeometry2Override: " + "Geometry node DAG path is not valid."); return; } From 863df23325e710f1731da8ec53b9e138d63d63fd Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 7 Sep 2024 22:37:55 +1000 Subject: [PATCH 159/295] Build scripts - do not hard-code "C:\Program Files" We use %PROGRAMFILES% instead, in case a user has their "Program Files" directory on a different drive letter for some strange reason. --- scripts/build_mmSolver_windows64_maya2016.bat | 2 +- scripts/build_mmSolver_windows64_maya2016_extension2.bat | 2 +- scripts/build_mmSolver_windows64_maya2017.bat | 2 +- scripts/build_mmSolver_windows64_maya2018.bat | 2 +- scripts/build_mmSolver_windows64_maya2019.bat | 2 +- scripts/build_mmSolver_windows64_maya2020.bat | 2 +- scripts/build_mmSolver_windows64_maya2022.bat | 2 +- scripts/build_mmSolver_windows64_maya2023.bat | 2 +- scripts/build_mmSolver_windows64_maya2024.bat | 4 ++-- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scripts/build_mmSolver_windows64_maya2016.bat b/scripts/build_mmSolver_windows64_maya2016.bat index d5662bb97..d8c9bcb55 100644 --- a/scripts/build_mmSolver_windows64_maya2016.bat +++ b/scripts/build_mmSolver_windows64_maya2016.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2016 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2016" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2016" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2016_extension2.bat b/scripts/build_mmSolver_windows64_maya2016_extension2.bat index b7165b949..755e26307 100644 --- a/scripts/build_mmSolver_windows64_maya2016_extension2.bat +++ b/scripts/build_mmSolver_windows64_maya2016_extension2.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2016.5 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2016.5" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2016.5" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2017.bat b/scripts/build_mmSolver_windows64_maya2017.bat index 051074361..9aab313ab 100644 --- a/scripts/build_mmSolver_windows64_maya2017.bat +++ b/scripts/build_mmSolver_windows64_maya2017.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2017 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2017" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2017" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2018.bat b/scripts/build_mmSolver_windows64_maya2018.bat index e36246073..715687f4a 100644 --- a/scripts/build_mmSolver_windows64_maya2018.bat +++ b/scripts/build_mmSolver_windows64_maya2018.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2018 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2018" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2018" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2019.bat b/scripts/build_mmSolver_windows64_maya2019.bat index 8686045da..5e8b3b706 100644 --- a/scripts/build_mmSolver_windows64_maya2019.bat +++ b/scripts/build_mmSolver_windows64_maya2019.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2019 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2019" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2019" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2020.bat b/scripts/build_mmSolver_windows64_maya2020.bat index 0e6f86554..e0ed5f49f 100644 --- a/scripts/build_mmSolver_windows64_maya2020.bat +++ b/scripts/build_mmSolver_windows64_maya2020.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2020 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2020" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2020" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2022.bat b/scripts/build_mmSolver_windows64_maya2022.bat index 12cdebbde..82a09cebc 100644 --- a/scripts/build_mmSolver_windows64_maya2022.bat +++ b/scripts/build_mmSolver_windows64_maya2022.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2022 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2022" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2022" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2023.bat b/scripts/build_mmSolver_windows64_maya2023.bat index ee79c5f0c..38d2a0d3d 100644 --- a/scripts/build_mmSolver_windows64_maya2023.bat +++ b/scripts/build_mmSolver_windows64_maya2023.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2023 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2023" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2023" :: Executable names/paths used for build process. SET PYTHON_EXE=python diff --git a/scripts/build_mmSolver_windows64_maya2024.bat b/scripts/build_mmSolver_windows64_maya2024.bat index d11e040e7..df60950c7 100644 --- a/scripts/build_mmSolver_windows64_maya2024.bat +++ b/scripts/build_mmSolver_windows64_maya2024.bat @@ -26,7 +26,7 @@ SETLOCAL :: Note: Do not enclose the MAYA_VERSION in quotes, it will :: lead to tears. SET MAYA_VERSION=2024 -SET MAYA_LOCATION="C:\Program Files\Autodesk\Maya2024" +SET MAYA_LOCATION="%PROGRAMFILES%\Autodesk\Maya2024" :: Executable names/paths used for build process. SET PYTHON_EXE=python @@ -56,7 +56,7 @@ SET VFX_PLATFORM=2023 SET CXX_STANDARD=14 :: Setup Compiler environment. Change for your install path as needed. -CALL "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 +CALL "%PROGRAMFILES%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 CALL scripts\internal\build_opencolorio_windows64.bat if errorlevel 1 goto failed_to_build_opencolorio From b5e98b8048593100408a3f5f815d6c98363669b8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 8 Sep 2024 23:44:25 +1000 Subject: [PATCH 160/295] mmSolver - Pass specific option values, not entire structure This allows us to be more explicit and understand what is being used when reading the functions. --- python/mmSolver/_api/_execute/main.py | 20 ++++++-- python/mmSolver/_api/_execute/postsolve.py | 31 +++++++----- python/mmSolver/_api/_execute/presolve.py | 58 +++++++++++++++------- 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/python/mmSolver/_api/_execute/main.py b/python/mmSolver/_api/_execute/main.py index ea6835bc6..e3f861e0f 100644 --- a/python/mmSolver/_api/_execute/main.py +++ b/python/mmSolver/_api/_execute/main.py @@ -279,7 +279,7 @@ def execute( panels = viewport_utils.get_all_model_panels() panel_objs, panel_node_type_vis = executepresolve.preSolve_queryViewportState( - options, panels + options.refresh, options.do_isolate, options.display_node_types, panels ) # Save scene state, to revert to later on. @@ -343,8 +343,16 @@ def execute( marker_relock_nodes |= executepresolve.preSolve_unlockMarkerAttrs(mkr_list) # Prepare frame solve - executepresolve.preSolve_setIsolatedNodes(action_list, options, panels) - executepresolve.preSolve_triggerEvaluation(action_list, cur_frame, options) + executepresolve.preSolve_setIsolatedNodes( + action_list, + options.refresh, + options.do_isolate, + options.display_node_types, + panels, + ) + executepresolve.preSolve_triggerEvaluation( + action_list, cur_frame, options.pre_solve_force_eval, options.force_update + ) # Ensure prediction attributes are created and initialised. collectionutils.set_initial_prediction_attributes(col, attr_list, cur_frame) @@ -506,7 +514,9 @@ def execute( # Refresh the Viewport. if func_is_mmsolver_v1 is True: frame = kwargs.get('frame') - executepostsolve.postSolve_refreshViewport(options, frame) + executepostsolve.postSolve_refreshViewport( + options.refresh, options.force_update, frame + ) finally: # If something has gone wrong, or the user cancels the solver # without finishing, then we make sure to reconnect animcurves @@ -523,7 +533,7 @@ def execute( marker_relock_nodes = set() executepostsolve.postSolve_setViewportState( - options, panel_objs, panel_node_type_vis + options.refresh, options.do_isolate, panel_objs, panel_node_type_vis ) collectionutils.run_status_func(status_fn, 'Solve Ended') collectionutils.run_progress_func(prog_fn, 100) diff --git a/python/mmSolver/_api/_execute/postsolve.py b/python/mmSolver/_api/_execute/postsolve.py index 896ddf5ea..74fc32fd2 100644 --- a/python/mmSolver/_api/_execute/postsolve.py +++ b/python/mmSolver/_api/_execute/postsolve.py @@ -32,37 +32,39 @@ LOG = mmSolver.logger.get_logger() -def postSolve_refreshViewport(options, frame): +def postSolve_refreshViewport(refresh, force_update, frame): """ Refresh the viewport after a solve has finished. - :param options: The execution options for the current solve. - :type options: ExecuteOptions + :type refresh: bool or None + :type do_isolate: bool or None :param frame: The list of frame numbers, first item in list is used to refresh the viewport. :type frame: [int or float, ..] """ - # Refresh the Viewport. - if options.refresh is not True: + assert refresh is None or isinstance(refresh, bool) + assert force_update is None or isinstance(force_update, bool) + + if refresh is not True: return maya.cmds.currentTime( frame[0], edit=True, - update=options.force_update, + update=force_update, ) maya.cmds.refresh() return -def postSolve_setViewportState(options, panel_objs, panel_node_type_vis): +def postSolve_setViewportState(refresh, do_isolate, panel_objs, panel_node_type_vis): """ - Change the viewport state based on the ExecuteOptions given + Change the viewport state based on the values given - :param options: The execution options for the current solve. - :type options: ExecuteOptions + :type refresh: bool or None + :type do_isolate: bool or None :param panel_objs: The panels and object to isolate, in a list of tuples. @@ -72,7 +74,10 @@ def postSolve_setViewportState(options, panel_objs, panel_node_type_vis): The panels and node-type visibility options in a list of tuples. :type panel_node_type_vis: [(str, {str: int or bool or None}), ..] """ - if options.refresh is not True: + assert refresh is None or isinstance(refresh, bool) + assert do_isolate is None or isinstance(do_isolate, bool) + + if refresh is not True: return # Isolate Objects restore. @@ -80,10 +85,10 @@ def postSolve_setViewportState(options, panel_objs, panel_node_type_vis): if objs is None: # No original objects, disable 'isolate # selected' after resetting the objects. - if options.do_isolate is True: + if do_isolate is True: viewport_utils.set_isolated_nodes(panel, [], False) - elif options.do_isolate is True: + elif do_isolate is True: viewport_utils.set_isolated_nodes(panel, list(objs), True) # Show menu restore. diff --git a/python/mmSolver/_api/_execute/presolve.py b/python/mmSolver/_api/_execute/presolve.py index c2b950302..d09cf5ce1 100644 --- a/python/mmSolver/_api/_execute/presolve.py +++ b/python/mmSolver/_api/_execute/presolve.py @@ -57,26 +57,35 @@ def preSolve_updateProgress(prog_fn, status_fn): return -def preSolve_queryViewportState(options, panels): +def preSolve_queryViewportState(refresh, do_isolate, display_node_types, panels): """ + Query the viewport state before solving. + If 'refresh' is 'on' change all viewports to 'isolate selected' on only the markers and bundles being solved. This will speed up computations, especially per-frame solving as it will not re-compute any invisible nodes (such as rigs or image planes). - :param options: - :param panels: - :return: + :type refresh: bool or None + :type do_isolate: bool or None + :type display_node_types: {str: bool, ..} + + :param panels: List of panel names to query details from. + :type panels: [str, ..] + + :rtype: ({str: [str, ..]}, {str: {str: bool}}) """ + assert refresh is None or isinstance(refresh, bool) + assert do_isolate is None or isinstance(do_isolate, bool) + assert display_node_types is None or isinstance(display_node_types, dict) + panel_objs = {} panel_node_type_vis = collections.defaultdict(dict) - if options.refresh is not True: + if refresh is not True: return panel_objs, panel_node_type_vis - display_node_types = options.display_node_types if display_node_types is not None: - assert isinstance(display_node_types, dict) for panel in panels: node_types = display_node_types.keys() node_type_vis = dict() @@ -85,7 +94,7 @@ def preSolve_queryViewportState(options, panels): node_type_vis[node_type] = value panel_node_type_vis[panel] = node_type_vis - if options.do_isolate is True: + if do_isolate is True: for panel in panels: state = maya.cmds.isolateSelect(panel, query=True, state=True) nodes = None @@ -96,17 +105,28 @@ def preSolve_queryViewportState(options, panels): return panel_objs, panel_node_type_vis -def preSolve_setIsolatedNodes(actions_list, options, panels): +def preSolve_setIsolatedNodes( + actions_list, refresh, do_isolate, display_node_types, panels +): """ Prepare frame solve Isolate all nodes used in all of the kwargs to be run. Note; This assumes the isolated objects are visible, but they may actually be hidden. + + :type refresh: bool or None + :type do_isolate: bool or None + :type display_node_types: {str: bool, ..} + + :returns: None """ - if options.refresh is not True: + assert refresh is None or isinstance(refresh, bool) + assert do_isolate is None or isinstance(do_isolate, bool) + assert display_node_types is None or isinstance(display_node_types, dict) + if refresh is not True: return - if options.do_isolate is True: + if do_isolate is True: isolate_nodes = set() for action in actions_list: kwargs = action.kwargs @@ -117,9 +137,7 @@ def preSolve_setIsolatedNodes(actions_list, options, panels): for panel in panels: viewport_utils.set_isolated_nodes(panel, isolate_node_list, True) - display_node_types = options.display_node_types if display_node_types is not None: - assert isinstance(display_node_types, dict) for panel in panels: for node_type, value in display_node_types.items(): if value is None: @@ -129,7 +147,9 @@ def preSolve_setIsolatedNodes(actions_list, options, panels): return -def preSolve_triggerEvaluation(action_list, cur_frame, options): +def preSolve_triggerEvaluation( + action_list, cur_frame, pre_solve_force_eval, force_update +): """ Set the first current time to the frame before current. @@ -144,10 +164,12 @@ def preSolve_triggerEvaluation(action_list, cur_frame, options): :param cur_frame: The current frame number. :type cur_frame: int or float - :param options: The execution options for the solve. - :type options: ExecuteOptions + :type pre_solve_force_eval: None or bool + :type force_update: None or bool + + :rtype: None """ - if options.pre_solve_force_eval is not True: + if pre_solve_force_eval is not True: return frame_list = [] for action in action_list: @@ -160,7 +182,7 @@ def preSolve_triggerEvaluation(action_list, cur_frame, options): maya.cmds.currentTime( cur_frame + 1, edit=True, - update=options.force_update, + update=force_update, ) return From bb7dc77f0a246375dd04cb9dbd81a2560603d824 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 8 Sep 2024 23:44:51 +1000 Subject: [PATCH 161/295] Solver - Hide mmImagePlane2 during solve --- python/mmSolver/tools/solver/lib/collection.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/mmSolver/tools/solver/lib/collection.py b/python/mmSolver/tools/solver/lib/collection.py index f241e8af5..7ab361895 100644 --- a/python/mmSolver/tools/solver/lib/collection.py +++ b/python/mmSolver/tools/solver/lib/collection.py @@ -681,6 +681,7 @@ def gather_execute_options(): image_plane_state = lib_state.get_display_image_plane_while_solving_state() meshes_state = lib_state.get_display_meshes_while_solving_state() disp_node_types['imagePlane'] = image_plane_state + disp_node_types['mmImagePlaneShape2'] = image_plane_state disp_node_types['mesh'] = meshes_state # Minimal UI from config file. From d10cc9e3582c73753fd14f219516b2ed9996269a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 9 Sep 2024 20:58:27 +1000 Subject: [PATCH 162/295] Build System - Update CentOS Docker Image To Use Vault Since CentOS 7.x is now end-of-life (EOL) and deprecated, we must pull down packages from the CentOS Vault to continue building Docker containers. --- share/docker/Dockerfile_maya2019 | 18 ++++++++++++++++++ share/docker/Dockerfile_maya2020 | 18 ++++++++++++++++++ share/docker/Dockerfile_maya2022 | 18 ++++++++++++++++++ share/docker/Dockerfile_maya2023 | 18 ++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/share/docker/Dockerfile_maya2019 b/share/docker/Dockerfile_maya2019 index 6e4bc91f0..38886acfc 100644 --- a/share/docker/Dockerfile_maya2019 +++ b/share/docker/Dockerfile_maya2019 @@ -26,12 +26,30 @@ FROM centos:7 +# Pull CentOS 7.x Packages from the Vault, because CentOS 7.x is now +# End-Of-Life (EOL) as of June 30tb, 2024. +# +# https://vault.centos.org/centos/7/ +# https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo + +# Install Software Collections (SCL) repo for the extended devtools. +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. +# Install Yum and RPM tools to speed up installations. RUN yum install --assumeyes \ centos-release-scl \ epel-release \ deltarpm \ yum-utils +# Now new repos are installed, make sure they're all from the Vault. +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && yum clean all && yum makecache + # OpenSource "mesa" OpenGL Driver. RUN yum install --assumeyes \ mesa-libGLw \ diff --git a/share/docker/Dockerfile_maya2020 b/share/docker/Dockerfile_maya2020 index 881cd9273..0ae371673 100644 --- a/share/docker/Dockerfile_maya2020 +++ b/share/docker/Dockerfile_maya2020 @@ -26,12 +26,30 @@ FROM centos:7 +# Pull CentOS 7.x Packages from the Vault, because CentOS 7.x is now +# End-Of-Life (EOL) as of June 30tb, 2024. +# +# https://vault.centos.org/centos/7/ +# https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo + +# Install Software Collections (SCL) repo for the extended devtools. +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. +# Install Yum and RPM tools to speed up installations. RUN yum install --assumeyes \ centos-release-scl \ epel-release \ deltarpm \ yum-utils +# Now new repos are installed, make sure they're all from the Vault. +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && yum clean all && yum makecache + # OpenSource "mesa" OpenGL Driver. RUN yum install --assumeyes \ mesa-libGLw \ diff --git a/share/docker/Dockerfile_maya2022 b/share/docker/Dockerfile_maya2022 index 4cfc6856f..74e9e4ef2 100644 --- a/share/docker/Dockerfile_maya2022 +++ b/share/docker/Dockerfile_maya2022 @@ -26,12 +26,30 @@ FROM centos:7 +# Pull CentOS 7.x Packages from the Vault, because CentOS 7.x is now +# End-Of-Life (EOL) as of June 30tb, 2024. +# +# https://vault.centos.org/centos/7/ +# https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo + +# Install Software Collections (SCL) repo for the extended devtools. +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. +# Install Yum and RPM tools to speed up installations. RUN yum install --assumeyes \ centos-release-scl \ epel-release \ deltarpm \ yum-utils +# Now new repos are installed, make sure they're all from the Vault. +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && yum clean all && yum makecache + # OpenSource "mesa" OpenGL Driver. RUN yum install --assumeyes \ mesa-libGLw \ diff --git a/share/docker/Dockerfile_maya2023 b/share/docker/Dockerfile_maya2023 index c321beaa8..f6aeb7283 100644 --- a/share/docker/Dockerfile_maya2023 +++ b/share/docker/Dockerfile_maya2023 @@ -26,12 +26,30 @@ FROM centos:7 +# Pull CentOS 7.x Packages from the Vault, because CentOS 7.x is now +# End-Of-Life (EOL) as of June 30tb, 2024. +# +# https://vault.centos.org/centos/7/ +# https://serverfault.com/questions/1161816/mirrorlist-centos-org-no-longer-resolve +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo + +# Install Software Collections (SCL) repo for the extended devtools. +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. +# Install Yum and RPM tools to speed up installations. RUN yum install --assumeyes \ centos-release-scl \ epel-release \ deltarpm \ yum-utils +# Now new repos are installed, make sure they're all from the Vault. +RUN sed -i 's/mirror.centos.org/vault.centos.org/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^#.*baseurl=http/baseurl=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && sed -i 's/^mirrorlist=http/#mirrorlist=http/g' /etc/yum.repos.d/CentOS-*.repo \ + && yum clean all && yum makecache + # OpenSource "mesa" OpenGL Driver. RUN yum install --assumeyes \ mesa-libGLw \ From 0f0461d28907697e16ed2dec57e54cd1b1b5c7bf Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 9 Sep 2024 21:01:26 +1000 Subject: [PATCH 163/295] Build System - Update Docker instructions Just make it clear that you should be in the project directory before running docker commands. --- share/docker/Dockerfile_maya2019 | 1 + share/docker/Dockerfile_maya2020 | 1 + share/docker/Dockerfile_maya2022 | 1 + share/docker/Dockerfile_maya2023 | 1 + share/docker/Dockerfile_maya2024 | 1 + share/docker/Dockerfile_maya2025 | 1 + 6 files changed, 6 insertions(+) diff --git a/share/docker/Dockerfile_maya2019 b/share/docker/Dockerfile_maya2019 index 38886acfc..c376d23ab 100644 --- a/share/docker/Dockerfile_maya2019 +++ b/share/docker/Dockerfile_maya2019 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2019 -t mmsolver-linux-maya2019-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2019-build # diff --git a/share/docker/Dockerfile_maya2020 b/share/docker/Dockerfile_maya2020 index 0ae371673..716f13799 100644 --- a/share/docker/Dockerfile_maya2020 +++ b/share/docker/Dockerfile_maya2020 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2020 -t mmsolver-linux-maya2020-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2020-build # diff --git a/share/docker/Dockerfile_maya2022 b/share/docker/Dockerfile_maya2022 index 74e9e4ef2..bd308da97 100644 --- a/share/docker/Dockerfile_maya2022 +++ b/share/docker/Dockerfile_maya2022 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2022 -t mmsolver-linux-maya2022-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2022-build # diff --git a/share/docker/Dockerfile_maya2023 b/share/docker/Dockerfile_maya2023 index f6aeb7283..502d6a5c5 100644 --- a/share/docker/Dockerfile_maya2023 +++ b/share/docker/Dockerfile_maya2023 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2023 -t mmsolver-linux-maya2023-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2023-build # diff --git a/share/docker/Dockerfile_maya2024 b/share/docker/Dockerfile_maya2024 index 3da0cd0ff..02016fee1 100644 --- a/share/docker/Dockerfile_maya2024 +++ b/share/docker/Dockerfile_maya2024 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2024 -t mmsolver-linux-maya2024-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2024-build # diff --git a/share/docker/Dockerfile_maya2025 b/share/docker/Dockerfile_maya2025 index 9b2035f55..fd4b5ceb3 100644 --- a/share/docker/Dockerfile_maya2025 +++ b/share/docker/Dockerfile_maya2025 @@ -1,5 +1,6 @@ # To create and run the docker container in PowerShell or BASH: # +# $ cd /path/to/project/root/mayaMatchMoveSolver/ # $ docker build --file share/docker/Dockerfile_maya2025 -t mmsolver-linux-maya2025-build . # $ docker run --rm --interactive --volume "${PWD}:/mmSolver" --tty mmsolver-linux-maya2025-build # From 8fe55de8c2f68c9df5653e556f833d13aa15cd6d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 9 Sep 2024 21:03:49 +1000 Subject: [PATCH 164/295] Build System - Explain Docker install line. --- share/docker/Dockerfile_maya2024 | 1 + share/docker/Dockerfile_maya2025 | 1 + 2 files changed, 2 insertions(+) diff --git a/share/docker/Dockerfile_maya2024 b/share/docker/Dockerfile_maya2024 index 02016fee1..b18f3b9e8 100644 --- a/share/docker/Dockerfile_maya2024 +++ b/share/docker/Dockerfile_maya2024 @@ -33,6 +33,7 @@ FROM rockylinux:8 # And this forum post: # https://forums.autodesk.com/t5/maya-forum/install-maya-2023-update-3-on-rocky-linux-8-7-instructions/td-p/11735138 +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. RUN dnf install --assumeyes https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ && dnf install --assumeyes epel-release \ && dnf update --assumeyes \ diff --git a/share/docker/Dockerfile_maya2025 b/share/docker/Dockerfile_maya2025 index fd4b5ceb3..cbc72745b 100644 --- a/share/docker/Dockerfile_maya2025 +++ b/share/docker/Dockerfile_maya2025 @@ -33,6 +33,7 @@ FROM rockylinux:8 # And this forum post: # https://forums.autodesk.com/t5/maya-forum/install-maya-2023-update-3-on-rocky-linux-8-7-instructions/td-p/11735138 +# Install Extra Packages Enterprise Linux (EPEL) repo for extra tools. RUN dnf install --assumeyes https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm \ && dnf install --assumeyes epel-release \ && dnf update --assumeyes \ From b47e972e140e2ccc9ada2bffc97c5336823b328d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 9 Sep 2024 21:10:33 +1000 Subject: [PATCH 165/295] Build System - Docker - Only install Maya packages. Maya install archives can have multiple packages, so we can save time installing only the archive we need, not the other packages (like "Substance for Maya", etc). --- share/docker/Dockerfile_maya2019 | 2 +- share/docker/Dockerfile_maya2020 | 2 +- share/docker/Dockerfile_maya2022 | 2 +- share/docker/Dockerfile_maya2023 | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/share/docker/Dockerfile_maya2019 b/share/docker/Dockerfile_maya2019 index c376d23ab..8330f035c 100644 --- a/share/docker/Dockerfile_maya2019 +++ b/share/docker/Dockerfile_maya2019 @@ -150,7 +150,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ # Install Maya from archive. ADD ./external/archives/Autodesk_Maya_2019_Linux_64bit.tgz /temp -RUN rpm -Uvh /temp/Maya*.rpm && rm -r /temp +RUN rpm -Uvh /temp/Maya2019*.rpm && rm -r /temp ENV MAYA_LOCATION=/usr/autodesk/maya/ ENV PATH=$MAYA_LOCATION/bin:$PATH diff --git a/share/docker/Dockerfile_maya2020 b/share/docker/Dockerfile_maya2020 index 716f13799..6dce6e489 100644 --- a/share/docker/Dockerfile_maya2020 +++ b/share/docker/Dockerfile_maya2020 @@ -147,7 +147,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ # Install Maya from archive. ADD ./external/archives/Autodesk_Maya_2020_ML_Linux_64bit.tgz /temp -RUN rpm -Uvh /temp/Packages/Maya*.rpm && rm -r /temp +RUN rpm -Uvh /temp/Packages/Maya2020*.rpm && rm -r /temp ENV MAYA_LOCATION=/usr/autodesk/maya/ ENV PATH=$MAYA_LOCATION/bin:$PATH diff --git a/share/docker/Dockerfile_maya2022 b/share/docker/Dockerfile_maya2022 index bd308da97..128d2b5ee 100644 --- a/share/docker/Dockerfile_maya2022 +++ b/share/docker/Dockerfile_maya2022 @@ -154,7 +154,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ # Install Maya from archive. ADD ./external/archives/Autodesk_Maya_2022_ML_Linux_64bit.tgz /temp -RUN rpm -Uvh /temp/Packages/Maya*.rpm && rm -r /temp +RUN rpm -Uvh /temp/Packages/Maya2022*.rpm && rm -r /temp ENV MAYA_LOCATION=/usr/autodesk/maya/ ENV PATH=$MAYA_LOCATION/bin:$PATH diff --git a/share/docker/Dockerfile_maya2023 b/share/docker/Dockerfile_maya2023 index 502d6a5c5..a3e86c1c8 100644 --- a/share/docker/Dockerfile_maya2023 +++ b/share/docker/Dockerfile_maya2023 @@ -154,7 +154,7 @@ RUN curl https://sh.rustup.rs -sSf | sh -s -- -y \ # Install Maya from archive. ADD ./external/archives/Autodesk_Maya_2023_ML_Linux_64bit.tgz /temp -RUN rpm -Uvh /temp/Packages/Maya*.rpm && rm -r /temp +RUN rpm -Uvh /temp/Packages/Maya2023*.rpm && rm -r /temp ENV MAYA_LOCATION=/usr/autodesk/maya/ ENV PATH=$MAYA_LOCATION/bin:$PATH From 441a40aece165c4735b365372416bd035e382692 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Tue, 20 Aug 2024 15:45:42 -0400 Subject: [PATCH 166/295] mesh from locators tool added --- python/CMakeLists.txt | 5 + .../tools/meshfromlocators/__init__.py | 20 + .../tools/meshfromlocators/constant.py | 20 + .../tools/meshfromlocators/delaunator.py | 552 ++++++++++++++++++ python/mmSolver/tools/meshfromlocators/lib.py | 107 ++++ .../mmSolver/tools/meshfromlocators/tool.py | 37 ++ .../tools/meshfromlocators/ui/__init__.py | 3 + .../ui/meshfromlocators_layout.py | 58 ++ .../ui/meshfromlocators_layout.ui | 100 ++++ .../ui/meshfromlocators_window.py | 86 +++ .../ui/screenspacerigbake_layout.ui | 4 +- share/config/functions.json | 16 + share/config/menu.json | 6 + share/config/shelf.json | 6 + share/icons/mesh_bunny_32x32.png | Bin 0 -> 2092 bytes 15 files changed, 1018 insertions(+), 2 deletions(-) create mode 100644 python/mmSolver/tools/meshfromlocators/__init__.py create mode 100644 python/mmSolver/tools/meshfromlocators/constant.py create mode 100644 python/mmSolver/tools/meshfromlocators/delaunator.py create mode 100644 python/mmSolver/tools/meshfromlocators/lib.py create mode 100644 python/mmSolver/tools/meshfromlocators/tool.py create mode 100644 python/mmSolver/tools/meshfromlocators/ui/__init__.py create mode 100644 python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py create mode 100644 python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui create mode 100644 python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py create mode 100644 share/icons/mesh_bunny_32x32.png diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 01538bc25..fe402701b 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -194,6 +194,11 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/surfacecluster/ui/ui_surfacecluster_layout.py ) + compile_qt_ui_to_python_file("meshfromlocators" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/meshfromlocators/ui/ui_meshfromlocators_layout.py + ) + # Install generated Python UI files install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/" DESTINATION "${MODULE_FULL_NAME}/python" diff --git a/python/mmSolver/tools/meshfromlocators/__init__.py b/python/mmSolver/tools/meshfromlocators/__init__.py new file mode 100644 index 000000000..b9c3a7ad8 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Mesh From Locators tool. +""" diff --git a/python/mmSolver/tools/meshfromlocators/constant.py b/python/mmSolver/tools/meshfromlocators/constant.py new file mode 100644 index 000000000..a1cf0b9c7 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/constant.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# + +MESH_NAME = 'meshFromLocators' +WINDOW_TITLE = 'Mesh From Locators' diff --git a/python/mmSolver/tools/meshfromlocators/delaunator.py b/python/mmSolver/tools/meshfromlocators/delaunator.py new file mode 100644 index 000000000..358ba733d --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/delaunator.py @@ -0,0 +1,552 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +# Code from https://github.com/HakanSeven12/Delaunator-Python + +import math + +EPSILON = math.pow(2, -52) +EDGE_STACK = [None] * 512 + + +class Delaunator(): + + def __init__(self, points): + n = len(points) + + if (len(points) < 3): + raise ValueError("Need at least 3 points") + coords = [None] * n * 2 + + for i in range(0, n): + p = points[i] + coords[2 * i] = (p[0]) + coords[2 * i + 1] = (p[1]) + triangles = self.constructor(coords) + + def constructor(self, coords): + n = len(coords) >> 1 + + self.coords = coords + + # arrays that will store the triangulation graph + maxTriangles = max(2 * n - 5, 0) + self._triangles = [None] * maxTriangles * 3 + self._halfedges = [None] * maxTriangles * 3 + + # temporary arrays for tracking the edges of the advancing convex hull + self.hashSize = math.ceil(math.sqrt(n)) + self.hullPrev = [None] * n # edge to prev edge + self.hullNext = [None] * n # edge to next edge + self.hullTri = [None] * n # edge to adjacent triangle + self.hullHash = [-1] * self.hashSize # angular edge hash + + # temporary arrays for sorting points + self._ids = [None] * n + self._dists = [None] * n + triangles = self.update(coords) + + return triangles + + def update(self, coords): + n = len(coords) >> 1 + + # populate an array of point indices; calculate input data bbox + minX = math.inf + minY = math.inf + maxX = -math.inf + maxY = -math.inf + + for i in range(0, n): + x = coords[2 * i] + y = coords[2 * i + 1] + if (x < minX): minX = x + if (y < minY): minY = y + if (x > maxX): maxX = x + if (y > maxY): maxY = y + self._ids[i] = i + + cx = (minX + maxX) / 2 + cy = (minY + maxY) / 2 + + minDist = math.inf + i0 = 0 + i1 = 0 + i2 = 0 + + # pick a seed point close to the center + for i in range(0, n): + d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]) + + if (d < minDist): + i0 = i + minDist = d + + i0x = coords[2 * i0] + i0y = coords[2 * i0 + 1] + minDist = math.inf + + # find the point closest to the seed + for i in range(0, n): + if (i == i0): continue + d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]) + + if (d < minDist and d > 0): + i1 = i + minDist = d + + i1x = coords[2 * i1] + i1y = coords[2 * i1 + 1] + + minRadius = math.inf + + # find the third point which forms the smallest circumcircle with the first two + for i in range(0, n): + if (i == i0 or i == i1): continue + r = circumradius(i0x, i0y, i1x, i1y, coords[2 * i], + coords[2 * i + 1]) + + if (r < minRadius): + i2 = i + minRadius = r + + i2x = coords[2 * i2] + i2y = coords[2 * i2 + 1] + + if (minRadius == math.inf): + # order collinear points by dx (or dy if all x are identical) + # and return the list as a hull + for i in range(0, n): + self._dists[i] = (coords[2 * i] - coords[0]) or ( + coords[2 * i + 1] - coords[1]) + + quicksort(self._ids, self._dists, 0, n - 1) + hull = [None] * n + j = 0 + d0 = -math.inf + + for i in range(0, n): + id = self._ids[i] + + if (self._dists[id] > d0): + hull[j] = id + j += 1 + d0 = self._dists[id] + + self.hull = hull[0:j] + self.triangles = [] + self.halfedges = [] + + # swap the order of the seed points for counter-clockwise orientation + if (orient(i0x, i0y, i1x, i1y, i2x, i2y)): + i = i1 + x = i1x + y = i1y + i1 = i2 + i1x = i2x + i1y = i2y + i2 = i + i2x = x + i2y = y + + center = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y) + self._cx = center[0] + self._cy = center[1] + + for i in range(0, n): + self._dists[i] = dist(coords[2 * i], coords[2 * i + 1], center[0], + center[1]) + + # sort the points by distance from the seed triangle circumcenter + quicksort(self._ids, self._dists, 0, n - 1) + + # set up the seed triangle as the starting hull + self._hullStart = i0 + hullSize = 3 + + self.hullNext[i0] = self.hullPrev[i2] = i1 + self.hullNext[i1] = self.hullPrev[i0] = i2 + self.hullNext[i2] = self.hullPrev[i1] = i0 + + self.hullTri[i0] = 0 + self.hullTri[i1] = 1 + self.hullTri[i2] = 2 + + self.hullHash[self._hashKey(i0x, i0y)] = i0 + self.hullHash[self._hashKey(i1x, i1y)] = i1 + self.hullHash[self._hashKey(i2x, i2y)] = i2 + + self.trianglesLen = 0 + self._addTriangle(i0, i1, i2, -1, -1, -1) + + xp = 0 + yp = 0 + + for k in range(0, len(self._ids)): + i = self._ids[k] + x = coords[2 * i] + y = coords[2 * i + 1] + + # skip near-duplicate points + if (k > 0 and abs(x - xp) <= EPSILON and abs( + y - yp) <= EPSILON): continue + + xp = x + yp = y + + # skip seed triangle points + if (i == i0 or i == i1 or i == i2): continue + + # find a visible edge on the convex hull using edge hash + start = 0 + key = self._hashKey(x, y) + + for j in range(0, self.hashSize): + start = self.hullHash[(key + j) % self.hashSize] + if (start != -1 and start != self.hullNext[start]): break + + start = self.hullPrev[start] + e = start + + while True: + q = self.hullNext[e] + if orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], + coords[2 * q + 1]): break + e = q + + if (e == start): + e = -1 + break + + if (e == -1): continue # likely a near-duplicate point; skip it + + # add the first triangle from the point + t = self._addTriangle(e, i, self.hullNext[e], -1, -1, + self.hullTri[e]) + + # recursively flip triangles from the point until they satisfy the Delaunay condition + self.hullTri[i] = self._legalize(t + 2, coords) + self.hullTri[e] = t # keep track of boundary triangles on the hull + hullSize += 1 + + # walk forward through the hull, adding more triangles and flipping recursively + n = self.hullNext[e] + + while True: + q = self.hullNext[n] + if not ( + orient(x, y, coords[2 * n], coords[2 * n + 1], + coords[2 * q], + coords[2 * q + 1])): break + t = self._addTriangle(n, i, q, self.hullTri[i], -1, + self.hullTri[n]) + self.hullTri[i] = self._legalize(t + 2, coords) + self.hullNext[n] = n # mark as removed + hullSize -= 1 + n = q + + # walk backward from the other side, adding more triangles and flipping + if (e == start): + while True: + q = self.hullPrev[e] + if not (orient(x, y, coords[2 * q], coords[2 * q + 1], + coords[2 * e], coords[2 * e + 1])): break + t = self._addTriangle(q, i, e, -1, self.hullTri[e], + self.hullTri[q]) + self._legalize(t + 2, coords) + self.hullTri[q] = t + self.hullNext[e] = e # mark as removed + hullSize -= 1 + e = q + + # update the hull indices + self._hullStart = self.hullPrev[i] = e + self.hullNext[e] = self.hullPrev[n] = i + self.hullNext[i] = n + + # save the two new edges in the hash table + self.hullHash[self._hashKey(x, y)] = i + self.hullHash[self._hashKey(coords[2 * e], coords[2 * e + 1])] = e + + self.hull = [None] * hullSize + e = self._hullStart + for i in range(0, hullSize): + self.hull[i] = e + e = self.hullNext[e] + + # trim typed triangle mesh arrays + self.triangles = self._triangles[0:self.trianglesLen] + self.halfedges = self._halfedges[0:self.trianglesLen] + + return self.triangles + + def _hashKey(self, x, y): + return math.floor(pseudoAngle(x - self._cx, + y - self._cy) * self.hashSize) % self.hashSize + + def _legalize(self, a, coords): + i = 0 + ar = 0 + + # recursion eliminated with a fixed-size stack + while True: + b = self._halfedges[a] + """ + if the pair of triangles doesn't satisfy the Delaunay condition + (p1 is inside the circumcircle of [p0, pl, pr]), flip them, + then do the same check/flip recursively for the new pair of triangles + """ + + # pl pl + # /||\ / \ + # al/ || \bl al/ \a + # / || \ / \ + # / a||b \ flip /___ar___\ + # p0\ || /p1 => p0\---bl---/p1 + # \ || / \ / + # ar\ || /br b\ /br + # \||/ \ / + # pr pr + + a0 = a - a % 3 + ar = a0 + (a + 2) % 3 + + if (b == -1): # convex hull edge + if (i == 0): break + i -= 1 + a = EDGE_STACK[i] + continue + + b0 = b - b % 3 + al = a0 + (a + 1) % 3 + bl = b0 + (b + 2) % 3 + + p0 = self._triangles[ar] + pr = self._triangles[a] + pl = self._triangles[al] + p1 = self._triangles[bl] + + illegal = inCircle( + coords[2 * p0], coords[2 * p0 + 1], + coords[2 * pr], coords[2 * pr + 1], + coords[2 * pl], coords[2 * pl + 1], + coords[2 * p1], coords[2 * p1 + 1]) + + if (illegal): + self._triangles[a] = p1 + self._triangles[b] = p0 + + hbl = self._halfedges[bl] + + # edge swapped on the other side of the hull (rare); fix the halfedge reference + if (hbl == -1): + e = self._hullStart + + while True: + if (self.hullTri[e] == bl): + self.hullTri[e] = a + break + + e = self.hullPrev[e] + if (e == self._hullStart): break + + self._link(a, hbl) + self._link(b, self._halfedges[ar]) + self._link(ar, bl) + + br = b0 + (b + 1) % 3 + + # don't worry about hitting the cap: it can only happen on extremely degenerate input + if (i < len(EDGE_STACK)): + EDGE_STACK[i] = br + i += 1 + + else: + if (i == 0): break + i -= 1 + a = EDGE_STACK[i] + + return ar + + def _link(self, a, b): + self._halfedges[a] = b + if (b != -1): + self._halfedges[b] = a + + # add a new triangle given vertex indices and adjacent half-edge ids + def _addTriangle(self, i0, i1, i2, a, b, c): + t = self.trianglesLen + + self._triangles[t] = i0 + self._triangles[t + 1] = i1 + self._triangles[t + 2] = i2 + + self._link(t, a) + self._link(t + 1, b) + self._link(t + 2, c) + + self.trianglesLen += 3 + + return t + + +# monotonically increases with real angle, but doesn't need expensive trigonometry +def pseudoAngle(dx, dy): + p = dx / (abs(dx) + abs(dy)) + + if (dy > 0): + return (3 - p) / 4 # [0..1] + else: + return (1 + p) / 4 # [0..1] + + +def dist(ax, ay, bx, by): + dx = ax - bx + dy = ay - by + return dx * dx + dy * dy + + +# return 2d orientation sign if we're confident in it through J. Shewchuk's error bound check +def orientIfSure(px, py, rx, ry, qx, qy): + l = (ry - py) * (qx - px) + r = (rx - px) * (qy - py) + + if (abs(l - r) >= 3.3306690738754716e-16 * abs(l + r)): + return l - r + else: + return 0 + + +# a more robust orientation test that's stable in a given triangle (to fix robustness issues) +def orient(rx, ry, qx, qy, px, py): + return (orientIfSure(px, py, rx, ry, qx, qy) or \ + orientIfSure(rx, ry, qx, qy, px, py) or \ + orientIfSure(qx, qy, px, py, rx, ry)) < 0 + + +def inCircle(ax, ay, bx, by, cx, cy, px, py): + dx = ax - px + dy = ay - py + ex = bx - px + ey = by - py + fx = cx - px + fy = cy - py + + ap = dx * dx + dy * dy + bp = ex * ex + ey * ey + cp = fx * fx + fy * fy + + return dx * (ey * cp - bp * fy) - \ + dy * (ex * cp - bp * fx) + \ + ap * (ex * fy - ey * fx) < 0 + + +def circumradius(ax, ay, bx, by, cx, cy): + dx = bx - ax + dy = by - ay + ex = cx - ax + ey = cy - ay + + bl = dx * dx + dy * dy + cl = ex * ex + ey * ey + try: + d = 0.5 / (dx * ey - dy * ex) + except ZeroDivisionError: + d = float('inf') + + x = (ey * bl - dy * cl) * d + y = (dx * cl - ex * bl) * d + + return x * x + y * y + + +def circumcenter(ax, ay, bx, by, cx, cy): + dx = bx - ax + dy = by - ay + ex = cx - ax + ey = cy - ay + + bl = dx * dx + dy * dy + cl = ex * ex + ey * ey + try: + d = 0.5 / (dx * ey - dy * ex) + except ZeroDivisionError: + d = float('inf') + + x = ax + (ey * bl - dy * cl) * d + y = ay + (dx * cl - ex * bl) * d + + return x, y + + +def quicksort(ids, dists, left, right): + if (right - left <= 20): + for i in range(left + 1, right + 1): + temp = ids[i] + tempDist = dists[temp] + j = i - 1 + while (j >= left and dists[ids[j]] > tempDist): + ids[j + 1] = ids[j] + j -= 1 + ids[j + 1] = temp; + + else: + median = (left + right) >> 1 + i = left + 1 + j = right + swap(ids, median, i) + + if (dists[ids[left]] > dists[ids[right]]): + swap(ids, left, right) + + if (dists[ids[i]] > dists[ids[right]]): + swap(ids, i, right) + + if (dists[ids[left]] > dists[ids[i]]): + swap(ids, left, i) + + temp = ids[i] + tempDist = dists[temp] + + while True: + while True: + i += 1 + if (dists[ids[i]] >= tempDist): break + + while True: + j -= 1 + if (dists[ids[j]] <= tempDist): break + + if (j < i): break + swap(ids, i, j); + + ids[left + 1] = ids[j]; + ids[j] = temp; + + if (right - i + 1 >= j - left): + quicksort(ids, dists, i, right) + quicksort(ids, dists, left, j - 1) + + else: + quicksort(ids, dists, left, j - 1) + quicksort(ids, dists, i, right) + + +def swap(arr, i, j): + tmp = arr[i] + arr[i] = arr[j] + arr[j] = tmp diff --git a/python/mmSolver/tools/meshfromlocators/lib.py b/python/mmSolver/tools/meshfromlocators/lib.py new file mode 100644 index 000000000..7a075c13e --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/lib.py @@ -0,0 +1,107 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# + +import maya.api.OpenMaya as om +import maya.api.OpenMayaUI as omui +import maya.cmds as cmds + +import mmSolver.logger +from mmSolver.tools.meshfromlocators.delaunator import Delaunator +import mmSolver.tools.meshfromlocators.constant as const + +LOG = mmSolver.logger.get_logger() + + +def delaunator_indices(locators): + """ + Returns delaunay indices in the proper order to create mesh. + + :param locators: locatorlist + :type locators: list + """ + mesh_data = {"world_positions": [], + "vew_positions": [], + "full_mesh_indices": [], + "border_mesh_indices": []} + for locator_name in locators: + locator_pos = cmds.xform(locator_name, query=True, worldSpace=True, + translation=True) + locator_mpoint = om.MPoint(locator_pos[0], locator_pos[1], + locator_pos[2]) + mesh_data["world_positions"].append(locator_mpoint) + # Convert world position to view space + view = omui.M3dView.active3dView() + view_pos = view.worldToView(locator_mpoint) + view_x, view_y = view_pos[0], view_pos[1] + mesh_data["vew_positions"].append([view_x, view_y]) + # Indices + mesh_indices = Delaunator(mesh_data["vew_positions"]).triangles + # Reverse order + mesh_indices_reverse = [] + for i in range(0, len(mesh_indices), 3): + chunk = mesh_indices[i:i + 3] + mesh_indices_reverse.extend(chunk[::-1]) + mesh_data["full_mesh_indices"] = mesh_indices_reverse + # Hull indices + hull = Delaunator(mesh_data["vew_positions"]).hull + hull.reverse() + mesh_data["border_mesh_indices"] = hull + return mesh_data + + +def create_mesh_from_locators(mesh_type=None, offset_value=1.0): + """ + Creates mesh from locators. + + :param mesh_type: 'fullMesh' 'borderMesh' 'borderEdgeStripMesh'. + :type mesh_type: str + + :param offset_value: An offset value for borderEdgeStripMesh + :type offset_value: float + """ + locators = cmds.ls(selection=True, transforms=True) or [] + if len(locators) < 3: + LOG.warn('at least three locators must be selected.') + return + + mesh_data = delaunator_indices(locators) + positions = mesh_data["world_positions"] + + # Set face count and indices + if mesh_type == 'fullMesh': + indices = mesh_data["full_mesh_indices"] + face_counts = [3] * (len(indices) // 3) + else: + indices = mesh_data["border_mesh_indices"] + face_counts = [len(indices)] + + # Create the mesh + mesh_fn = om.MFnMesh() + mesh = mesh_fn.create(positions, face_counts, indices) + # Set mesh name + dag_node = om.MFnDagNode(mesh) + dag_node.setName(const.MESH_NAME) + mesh_name = dag_node.name() + # Set lambert + cmds.sets(mesh_name, edit=True, forceElement='initialShadingGroup') + # Create border edge strip mesh + if mesh_type == 'borderEdgeStripMesh': + cmds.polyExtrudeFacet(mesh_name, offset=offset_value) + face_0 = '{}.f[0]'.format(mesh_name) + face_1 = '{}.f[1]'.format(mesh_name) + cmds.delete(face_0, face_1) diff --git a/python/mmSolver/tools/meshfromlocators/tool.py b/python/mmSolver/tools/meshfromlocators/tool.py new file mode 100644 index 000000000..67b33bf95 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/tool.py @@ -0,0 +1,37 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Mesh From Locators main. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def main(): + """ + Open the 'Mesh From Locators' window. + """ + import mmSolver.tools.meshfromlocators.ui.meshfromlocators_window as window + + window.main() diff --git a/python/mmSolver/tools/meshfromlocators/ui/__init__.py b/python/mmSolver/tools/meshfromlocators/ui/__init__.py new file mode 100644 index 000000000..64823a0ca --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/ui/__init__.py @@ -0,0 +1,3 @@ +""" +Mesh From Points user interface. +""" diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py new file mode 100644 index 000000000..434518df1 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py @@ -0,0 +1,58 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +The main component of the user interface for the mesh from locators window. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import mmSolver.tools.meshfromlocators.ui.ui_meshfromlocators_layout as ui_meshfromlocators_layout +import mmSolver.tools.meshfromlocators.lib as lib + + +class MeshFromLocatorsLayout(QtWidgets.QWidget, + ui_meshfromlocators_layout.Ui_Form): + def __init__(self, parent=None, *args, **kwargs): + super(MeshFromLocatorsLayout, self).__init__(*args, **kwargs) + self.setupUi(self) + + self.create_connections() + + def create_connections(self): + self.createFullMeshBtn.clicked.connect(self.full_mesh_btn_clicked) + self.createBorderMeshBtn.clicked.connect(self.border_mesh_btn_clicked) + self.createEdgeStripMeshBtn.clicked.connect( + self.border_edge_strip_mesh_btn_clicked) + + def full_mesh_btn_clicked(self): + lib.create_mesh_from_locators('fullMesh') + + def border_mesh_btn_clicked(self): + lib.create_mesh_from_locators('borderMesh') + + def border_edge_strip_mesh_btn_clicked(self): + offset = self.stripWidthSpinBox.value() + lib.create_mesh_from_locators('borderEdgeStripMesh', offset) diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui new file mode 100644 index 000000000..ae5fa2362 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui @@ -0,0 +1,100 @@ + + + Form + + + + 0 + 0 + 300 + 117 + + + + Form + + + + 6 + + + 0 + + + + + + + Border Edge Strip Width + + + + + + + Qt::ClickFocus + + + QAbstractSpinBox::UpDownArrows + + + 0.010000000000000 + + + 0.100000000000000 + + + 1.000000000000000 + + + + + + + + + + + + 280 + 0 + + + + Create Full Mesh + + + + + + + + 280 + 0 + + + + Create Border Mesh + + + + + + + + 280 + 0 + + + + Create Border Edge Strip Mesh + + + + + + + + + + diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py new file mode 100644 index 000000000..af536d4a0 --- /dev/null +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py @@ -0,0 +1,86 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Window for the Mesh From Locators tool. + +Usage:: + + import mmSolver.tools.meshfromlocators.ui.meshfromlocators_window as meshfromlocators_window + meshfromlocators_window.main() + +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import mmSolver.ui.qtpyutils as qtpyutils + +qtpyutils.override_binding_order() + +import mmSolver.ui.Qt.QtCore as QtCore +import mmSolver.ui.Qt.QtWidgets as QtWidgets + +import maya.cmds + +import mmSolver.logger +import mmSolver.ui.uiutils as uiutils +import mmSolver.tools.meshfromlocators.ui.meshfromlocators_layout as meshfromlocators_layout +import mmSolver.tools.meshfromlocators.constant as const + +LOG = mmSolver.logger.get_logger() +baseModule, BaseWindow = uiutils.getBaseWindow() + + +class MeshFromLocatorsWindow(BaseWindow): + name = 'MeshFromLocatorsWindow' + + def __init__(self, parent=None, name=None): + super(MeshFromLocatorsWindow, self).__init__(parent, name=name) + self.setupUi(self) + self.addSubForm(meshfromlocators_layout.MeshFromLocatorsLayout) + + self.setWindowTitle(const.WINDOW_TITLE) + self.setWindowFlags(QtCore.Qt.Tool) + # Hide irrelevant stuff + self.baseHideStandardButtons() + self.baseHideProgressBar() + + +def main(show=True, auto_raise=True, delete=False): + """ + Open the Mesh From Locators UI. + + :param show: Show the UI. + :type show: bool + + :param auto_raise: If the UI is open, raise it to the front? + :type auto_raise: bool + + :param delete: Delete the existing UI and rebuild it? Helpful when + developing the UI in Maya script editor. + :type delete: bool + + :returns: A new ui window, or None if the window cannot be + opened. + :rtype: MeshFromLocatorsWindow or None + """ + win = MeshFromLocatorsWindow.open_window( + show=show, auto_raise=auto_raise, delete=delete + ) + return win diff --git a/python/mmSolver/tools/screenspacerigbake/ui/screenspacerigbake_layout.ui b/python/mmSolver/tools/screenspacerigbake/ui/screenspacerigbake_layout.ui index 01bb88ba5..9cd162398 100644 --- a/python/mmSolver/tools/screenspacerigbake/ui/screenspacerigbake_layout.ui +++ b/python/mmSolver/tools/screenspacerigbake/ui/screenspacerigbake_layout.ui @@ -6,8 +6,8 @@ 0 0 - 289 - 397 + 305 + 444 diff --git a/share/config/functions.json b/share/config/functions.json index 71c42796c..7bed66554 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -463,6 +463,13 @@ "tooltip": "General Tools", "command": ["pass"] }, + "mesh_tools": { + "name": "Mesh Tools", + "name_shelf": "", + "icon_shelf": "mesh_bunny.png", + "tooltip": "Mesh Tools", + "command": ["pass"] + }, "center_twodee_ui": { "name": "Offset 2D Center UI", "name_shelf": "Center", @@ -617,6 +624,15 @@ "mmSolver.tools.undoredoscene.tool.main_redo();" ] }, + "mesh_from_locators": { + "name": "Mesh From Locators", + "name_shelf": "MeshFrLoc", + "tooltip": "Creates mesh from locators", + "command": [ + "import mmSolver.tools.meshfromlocators.tool;", + "mmSolver.tools.meshfromlocators.tool.main();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index c655ab6c6..d6eb57639 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -95,6 +95,8 @@ "zdepth_tools/camera_object_scale_remove", "zdepth_tools/---", "zdepth_tools/screen_z_transform_bake", + "mesh_tools/---Mesh", + "mesh_tools/mesh_from_locators", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", @@ -156,6 +158,10 @@ "name": "Z-Depth Tools", "tearoff": true }, + "mesh_tools": { + "name": "Mesh Tools", + "tearoff": true + }, "general_tools": { "name": "General Tools", "tearoff": true diff --git a/share/config/shelf.json b/share/config/shelf.json index aa1b35c5d..c47bacd99 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -92,6 +92,8 @@ "zdepth_tools/zdepth_popup/camera_object_scale_remove", "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", + "mesh_tools/mesh_tools_popup/---Mesh", + "mesh_tools/mesh_tools_popup/mesh_from_locators", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", @@ -170,6 +172,10 @@ "popup": true, "popup_button": ["left", "right"] }, + "mesh_tools_popup": { + "popup": true, + "popup_button": ["left", "right"] + }, "gen_tools_popup": { "popup": true, "popup_button": ["left", "right"] diff --git a/share/icons/mesh_bunny_32x32.png b/share/icons/mesh_bunny_32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..28cbd00bbce57f6d86810eba59c5984791004ea9 GIT binary patch literal 2092 zcmV+{2-Ek8P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA2f;~1K~z{rwO4CQ zQ)d`{djkr^QZ6l}v|1Dd0dcYc&SYjmGTjn&KU}tDSr)T7U7X35@WZg^ek|LM#klN} zNK6)&EXz#%vBa3&_h(*0ydffBKm<#%T;x((3Wc_Np3hSI3v%GVfmgGv))sp~K{X;5d@DMWDZjS1c9ZtWvSpD2^Yimnh+M+qut*~Dg!Wpt zY}u05)YQ~mT3Y&5ZfTozpOeT|9GBRXpYD#8jXC*W>)z*6b`b)=8ocN@_zdx?2Shgq! z>gwvuIXOA67z~Ezj3!edpb(u-=hT}_`-+N+Y4*|I3!IVrqoZ>3=1tkWd9wtA!HEIC|0AUSv(u+f zN3{L_T67yXZroc{Rn=?-_JwY@tjNri1Qy~bM*qNobocZKTJ7iV_V(9)xo~0d!M6WS z^twbML$BA%%9Sg{nV&BfI1rhhmKjtxvyhRVE_Z#t-~SKe6umJeC56RhHk%~}sZRsW zj2=ZMDR`zs(~-z_eEUeO`FCO{UbE&PU?_Jwohn+&D6nT%=olSBQM&UTj)=FYsI|AZ zcTRnz$-2Bei{0ye(&O>)}6^wW9-aVC-yLa!-PK83}&<$PS1NZFAOcA=J3|TVdA$!)WESUy; zb0DaR@$rOzaPUkZ5O}|>t?im-Z;9w$*uA@>zP`SU1(*&P*n)k1eKI^eEE&i^3R)0G zWggr>=8NKjVIgDEIh)NUS-8j(a7JR-&o#>6;Ghine9^AX&i&^vUi?zMd`JvMt5;_u z@~4d`I4VZFiO=T~lF{1IQdNk2&d$!3TyQ^#N)j-TX|agg;}LdGDl!G?7Be6OpNIu( zgpL6rD+V~W6VIb4=1ZF01u;xcPF_K8zX;rIeRuB2{rmS7{uPy#N(m!lCU`gCJq{{m zh$&CUo!miZ$Q`6{?qwE0>*_fU$57`Io}S){`wxI*Q8yXD>vQE0dtP3(XVoexDJhXk z7*~9N%~V4Er!gv0bpglpNVkdv1qwHC2q|znkLKs+>**=x0M+osl?qIVj(0jkrw9vw^EABC-Y(Z$Th%$iE@3t@ zkvT#^*!aDUu;EIQ8#pC=r?2nq1M?uFD_5?>urP%KMs1)X79H_cQNRJdH^DFqkWn+m zU0q$uP)$t@Cn;IKe!Z+IE>^|LJ%oaKXq?@__k<9EZPjcm;XDurJ{O1;?kaj>%FVo( zVpcfsQAB|LmJzflvw*r0S7D*b8ZSoAI}OoM-kCW%!~J{*=e*3ivJO!L$@tjVx5SVi z4na99mluE2F_H_hWCsvSreDExXpG$)BCDU6is{Eh`keqkZ2dV*txg@voM zPZuVl1m=Ktc>gkRH}U{tBpkhXC}o4`=m>>%L9QS@^YG&QK&5IO7o%pkBrg_Ta%~j* z7znbnwyvgTRe{UZwlI=`di$~1iA}q($$*heZD#73UKSjMN*OgBNI^s_F2w^hq7^V< zVRop4?VGHrp+G>2QK%rXLQwcqfTCg1OgJ<&)R|$i{DeZQK_vN1Efs`u7C68U%!J@izOLIN^^6w30-jvjOOG)Lt{)~cXziUAu429at2^T6#f}HxLd& z#e<8T#zFVSjT={kq0s&ZrTvgJHa0#BrN_{cC777hvCFw&7HxIu@@2u_Gbt-8Q_M&> zh&{W3Sced8I$)tNQvnn!1{uM4uL>8lMjV66*SNop|C7Nb4(!;m<1w5Kzky6r>o#qQ zliesJo Date: Sun, 25 Aug 2024 14:12:17 -0400 Subject: [PATCH 167/295] Hold-Outs enable for selected meshes tool added. --- .../tools/holdoutsenableselectedmeshes/lib.py | 28 +++++++++++++ .../holdoutsenableselectedmeshes/tool.py | 41 +++++++++++++++++++ share/config/functions.json | 9 ++++ share/config/menu.json | 2 + share/config/shelf.json | 2 + 5 files changed, 82 insertions(+) create mode 100644 python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py create mode 100644 python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py diff --git a/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py b/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py new file mode 100644 index 000000000..ce31c1307 --- /dev/null +++ b/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py @@ -0,0 +1,28 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# + +import maya.cmds as cmds + +import mmSolver.logger + +LOG = mmSolver.logger.get_logger() + + +def set_holdout(mesh_list, enable=True): + for mesh in mesh_list: + cmds.setAttr(mesh + ".holdOut", enable) diff --git a/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py b/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py new file mode 100644 index 000000000..76b76f272 --- /dev/null +++ b/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py @@ -0,0 +1,41 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Enable Hold-Outs for selected meshes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds as cmds +import mmSolver.logger +import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib + +LOG = mmSolver.logger.get_logger() + + +def main(): + """ + Enable Hold-Outs for selected meshes. + """ + selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] + if not selected_meshes: + LOG.warn("Mesh selection not found.") + return + lib.set_holdout(selected_meshes, True) diff --git a/share/config/functions.json b/share/config/functions.json index 7bed66554..412e41744 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -633,6 +633,15 @@ "mmSolver.tools.meshfromlocators.tool.main();" ] }, + "enable_holdouts_for_all_meshes": { + "name": "Enable Hold-Outs For All Meshes", + "name_shelf": "HldAll", + "tooltip": "Enables Hold-Out attribute for all meshes.", + "command": [ + "import mmSolver.tools.holdoutsenableselectedmeshes.tool;", + "mmSolver.tools.holdoutsenableselectedmeshes.tool.main();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index d6eb57639..036d34c6f 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -97,6 +97,8 @@ "zdepth_tools/screen_z_transform_bake", "mesh_tools/---Mesh", "mesh_tools/mesh_from_locators", + "mesh_tools/---Hold-Outs", + "mesh_tools/enable_holdouts_for_all_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index c47bacd99..a9a3d6fab 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -94,6 +94,8 @@ "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Mesh", "mesh_tools/mesh_tools_popup/mesh_from_locators", + "mesh_tools/mesh_tools_popup/---Hold-Outs", + "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 761548e6904f0473e09c0cd4f94b5f97f2423eee Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 14:38:17 -0400 Subject: [PATCH 168/295] Hold-Outs disable for selected meshes tool added. --- .../holdoutsdisableselectedmeshes/tool.py | 41 +++++++++++++++++++ share/config/functions.json | 17 ++++++-- share/config/menu.json | 3 +- share/config/shelf.json | 3 +- 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py diff --git a/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py b/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py new file mode 100644 index 000000000..127604677 --- /dev/null +++ b/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py @@ -0,0 +1,41 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Enable Hold-Outs for selected meshes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds as cmds +import mmSolver.logger +import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib + +LOG = mmSolver.logger.get_logger() + + +def main(): + """ + Disable Hold-Outs for selected meshes. + """ + selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] + if not selected_meshes: + LOG.warn("Mesh selection not found.") + return + lib.set_holdout(selected_meshes, False) diff --git a/share/config/functions.json b/share/config/functions.json index 412e41744..1faf3768e 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -633,15 +633,24 @@ "mmSolver.tools.meshfromlocators.tool.main();" ] }, - "enable_holdouts_for_all_meshes": { - "name": "Enable Hold-Outs For All Meshes", - "name_shelf": "HldAll", - "tooltip": "Enables Hold-Out attribute for all meshes.", + "enable_holdouts_for_selected_meshes": { + "name": "Enable Hold-Outs For Selected Meshes", + "name_shelf": "", + "tooltip": "Enable Hold-Out attribute for selected meshes.", "command": [ "import mmSolver.tools.holdoutsenableselectedmeshes.tool;", "mmSolver.tools.holdoutsenableselectedmeshes.tool.main();" ] }, + "disable_holdouts_for_selected_meshes": { + "name": "Disable Hold-Outs For Selected Meshes", + "name_shelf": "", + "tooltip": "Disable Hold-Out attribute for selected meshes.", + "command": [ + "import mmSolver.tools.holdoutsdisableselectedmeshes.tool;", + "mmSolver.tools.holdoutsdisableselectedmeshes.tool.main();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index 036d34c6f..ed100d793 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -98,7 +98,8 @@ "mesh_tools/---Mesh", "mesh_tools/mesh_from_locators", "mesh_tools/---Hold-Outs", - "mesh_tools/enable_holdouts_for_all_meshes", + "mesh_tools/enable_holdouts_for_selected_meshes", + "mesh_tools/disable_holdouts_for_selected_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index a9a3d6fab..cc2e79dae 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -95,7 +95,8 @@ "mesh_tools/mesh_tools_popup/---Mesh", "mesh_tools/mesh_tools_popup/mesh_from_locators", "mesh_tools/mesh_tools_popup/---Hold-Outs", - "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", + "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", + "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From caf3b30ef54dd1583f658c67482cf150123f5251 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 14:45:51 -0400 Subject: [PATCH 169/295] Hold-Outs enable for all meshes tool added. --- .../tools/holdoutsenableallmeshes/tool.py | 41 +++++++++++++++++++ share/config/functions.json | 9 ++++ share/config/menu.json | 1 + share/config/shelf.json | 1 + 4 files changed, 52 insertions(+) create mode 100644 python/mmSolver/tools/holdoutsenableallmeshes/tool.py diff --git a/python/mmSolver/tools/holdoutsenableallmeshes/tool.py b/python/mmSolver/tools/holdoutsenableallmeshes/tool.py new file mode 100644 index 000000000..4af0fa5f1 --- /dev/null +++ b/python/mmSolver/tools/holdoutsenableallmeshes/tool.py @@ -0,0 +1,41 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Enable Hold-Outs for selected meshes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds as cmds +import mmSolver.logger +import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib + +LOG = mmSolver.logger.get_logger() + + +def main(): + """ + Enable Hold-Outs for all meshes. + """ + all_meshes = cmds.ls(dag=True, type="mesh") or [] + if not all_meshes: + LOG.warn("Mesh not found.") + return + lib.set_holdout(all_meshes, True) diff --git a/share/config/functions.json b/share/config/functions.json index 1faf3768e..a90549ee2 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -651,6 +651,15 @@ "mmSolver.tools.holdoutsdisableselectedmeshes.tool.main();" ] }, + "enable_holdouts_for_all_meshes": { + "name": "Enable Hold-Outs For All Meshes", + "name_shelf": "", + "tooltip": "Enable Hold-Out attribute for all meshes.", + "command": [ + "import mmSolver.tools.holdoutsenableallmeshes.tool;", + "mmSolver.tools.holdoutsenableallmeshes.tool.main();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index ed100d793..003e47b2a 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -100,6 +100,7 @@ "mesh_tools/---Hold-Outs", "mesh_tools/enable_holdouts_for_selected_meshes", "mesh_tools/disable_holdouts_for_selected_meshes", + "mesh_tools/enable_holdouts_for_all_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index cc2e79dae..77c7842ed 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -97,6 +97,7 @@ "mesh_tools/mesh_tools_popup/---Hold-Outs", "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", + "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From c82e87e8eccf4a0946dc0eac8f6f32843b7cc488 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 14:51:51 -0400 Subject: [PATCH 170/295] Hold-Outs disable for all meshes tool added. --- .../tools/holdoutsdisableallmeshes/tool.py | 41 +++++++++++++++++++ share/config/functions.json | 9 ++++ share/config/menu.json | 1 + share/config/shelf.json | 1 + 4 files changed, 52 insertions(+) create mode 100644 python/mmSolver/tools/holdoutsdisableallmeshes/tool.py diff --git a/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py b/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py new file mode 100644 index 000000000..1c49f102d --- /dev/null +++ b/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py @@ -0,0 +1,41 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Disable Hold-Outs for selected meshes. +""" + +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import maya.cmds as cmds +import mmSolver.logger +import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib + +LOG = mmSolver.logger.get_logger() + + +def main(): + """ + Disable Hold-Outs for all meshes. + """ + all_meshes = cmds.ls(dag=True, type="mesh") or [] + if not all_meshes: + LOG.warn("Mesh not found.") + return + lib.set_holdout(all_meshes, False) diff --git a/share/config/functions.json b/share/config/functions.json index a90549ee2..f9d664a08 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -660,6 +660,15 @@ "mmSolver.tools.holdoutsenableallmeshes.tool.main();" ] }, + "disable_holdouts_for_all_meshes": { + "name": "Disable Hold-Outs For All Meshes", + "name_shelf": "", + "tooltip": "Disable Hold-Out attribute for all meshes.", + "command": [ + "import mmSolver.tools.holdoutsdisableallmeshes.tool;", + "mmSolver.tools.holdoutsdisableallmeshes.tool.main();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index 003e47b2a..cf9d6ee1c 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -101,6 +101,7 @@ "mesh_tools/enable_holdouts_for_selected_meshes", "mesh_tools/disable_holdouts_for_selected_meshes", "mesh_tools/enable_holdouts_for_all_meshes", + "mesh_tools/disable_holdouts_for_all_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index 77c7842ed..1cb0e6a09 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -98,6 +98,7 @@ "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", + "mesh_tools/mesh_tools_popup/disable_holdouts_for_all_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 06e754c708b5f2341de4e143fd0ef2862beb7328 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 14:52:38 -0400 Subject: [PATCH 171/295] Hold-Outs disable for all selected meshes tool desc updated. --- python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py b/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py index 127604677..4803b2f88 100644 --- a/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py +++ b/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # """ -Enable Hold-Outs for selected meshes. +Disable Hold-Outs for selected meshes. """ from __future__ import absolute_import From 2f04affcf9e5f3f4d8d88e31bebd52cbc347190c Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 15:07:09 -0400 Subject: [PATCH 172/295] Hold-Out tools reorder(menu and shelf). --- share/config/menu.json | 4 ++-- share/config/shelf.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/share/config/menu.json b/share/config/menu.json index cf9d6ee1c..710db2204 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -98,10 +98,10 @@ "mesh_tools/---Mesh", "mesh_tools/mesh_from_locators", "mesh_tools/---Hold-Outs", - "mesh_tools/enable_holdouts_for_selected_meshes", - "mesh_tools/disable_holdouts_for_selected_meshes", "mesh_tools/enable_holdouts_for_all_meshes", "mesh_tools/disable_holdouts_for_all_meshes", + "mesh_tools/enable_holdouts_for_selected_meshes", + "mesh_tools/disable_holdouts_for_selected_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index 1cb0e6a09..2161890ae 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -95,10 +95,10 @@ "mesh_tools/mesh_tools_popup/---Mesh", "mesh_tools/mesh_tools_popup/mesh_from_locators", "mesh_tools/mesh_tools_popup/---Hold-Outs", - "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", - "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", "mesh_tools/mesh_tools_popup/disable_holdouts_for_all_meshes", + "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", + "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 371f3545414c0091241356184c68b7feeb961351 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Sun, 25 Aug 2024 16:07:48 -0400 Subject: [PATCH 173/295] Create world space controller tool updated to proprly disable viewport. --- python/mmSolver/tools/createcontroller2/tool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/mmSolver/tools/createcontroller2/tool.py b/python/mmSolver/tools/createcontroller2/tool.py index 415c2bd76..e13cf4a95 100644 --- a/python/mmSolver/tools/createcontroller2/tool.py +++ b/python/mmSolver/tools/createcontroller2/tool.py @@ -26,6 +26,7 @@ import mmSolver.logger import mmSolver.utils.time as time_utils +import mmSolver.utils.constant as const_utils import mmSolver.utils.tools as tools_utils import mmSolver.tools.createcontroller2.constant as const import mmSolver.tools.createcontroller2.lib as lib @@ -79,6 +80,7 @@ def create_world_controllers(): restore_current_frame=True, use_dg_evaluation_mode=True, disable_viewport=True, + disable_viewport_mode=const_utils.DISABLE_VIEWPORT_MODE_VP1_VALUE ): for node in nodes: From be1751d0656a59d3a65c40211a8d766a6f145649 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Tue, 27 Aug 2024 23:47:11 -0400 Subject: [PATCH 174/295] Category name updated, Mesh to Create. --- share/config/menu.json | 2 +- share/config/shelf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/share/config/menu.json b/share/config/menu.json index 710db2204..dba8bc3c0 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -95,7 +95,7 @@ "zdepth_tools/camera_object_scale_remove", "zdepth_tools/---", "zdepth_tools/screen_z_transform_bake", - "mesh_tools/---Mesh", + "mesh_tools/---Create", "mesh_tools/mesh_from_locators", "mesh_tools/---Hold-Outs", "mesh_tools/enable_holdouts_for_all_meshes", diff --git a/share/config/shelf.json b/share/config/shelf.json index 2161890ae..ca239c0f4 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -92,7 +92,7 @@ "zdepth_tools/zdepth_popup/camera_object_scale_remove", "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", - "mesh_tools/mesh_tools_popup/---Mesh", + "mesh_tools/mesh_tools_popup/---Create", "mesh_tools/mesh_tools_popup/mesh_from_locators", "mesh_tools/mesh_tools_popup/---Hold-Outs", "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", From 8f07a6372fd446138ca747c270060252ad8f83b8 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Tue, 10 Sep 2024 22:11:41 -0400 Subject: [PATCH 175/295] Delaunator copyright text updated. --- .../tools/meshfromlocators/delaunator.py | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/python/mmSolver/tools/meshfromlocators/delaunator.py b/python/mmSolver/tools/meshfromlocators/delaunator.py index 358ba733d..35f842c21 100644 --- a/python/mmSolver/tools/meshfromlocators/delaunator.py +++ b/python/mmSolver/tools/meshfromlocators/delaunator.py @@ -1,21 +1,21 @@ -# Copyright (C) 2024 Patcha Saheb Binginapalli. +# Delaunator-Python - Fast Delaunay triangulation of 2D points implemented in Python. +# Copyright (C) 2020 Hakan Seven (https://github.com/HakanSeven12 hakanseven12@gmail.com) # -# This file is part of mmSolver. +# Delaunator-Python is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. # -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, +# Delaunator-Python is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this Delaunator-Python; If not, see . # -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . +# Code from https://github.com/HakanSeven12/Delaunator-Python/blob/master/Delaunator.py4 # -# Code from https://github.com/HakanSeven12/Delaunator-Python import math From 9bf33a3c9b2500b2500b238e6d8e8cd7450a0781 Mon Sep 17 00:00:00 2001 From: Patcha Saheb Binginapalli Date: Tue, 10 Sep 2024 23:07:28 -0400 Subject: [PATCH 176/295] Set mesh hold-out tools moved under Display tools. Removed old set hold-out tools. --- .../tools/holdoutsdisableallmeshes/tool.py | 41 ----------- .../tools/holdoutsenableallmeshes/tool.py | 41 ----------- .../tools/holdoutsenableselectedmeshes/lib.py | 28 -------- .../holdoutsenableselectedmeshes/tool.py | 41 ----------- .../tool.py | 45 ++++++++---- share/config/functions.json | 72 +++++++++---------- share/config/menu.json | 9 +-- share/config/shelf.json | 10 +-- 8 files changed, 78 insertions(+), 209 deletions(-) delete mode 100644 python/mmSolver/tools/holdoutsdisableallmeshes/tool.py delete mode 100644 python/mmSolver/tools/holdoutsenableallmeshes/tool.py delete mode 100644 python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py delete mode 100644 python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py rename python/mmSolver/tools/{holdoutsdisableselectedmeshes => setmeshholdouts}/tool.py (55%) diff --git a/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py b/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py deleted file mode 100644 index 1c49f102d..000000000 --- a/python/mmSolver/tools/holdoutsdisableallmeshes/tool.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2024 Patcha Saheb Binginapalli. -# -# This file is part of mmSolver. -# -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . -# -""" -Disable Hold-Outs for selected meshes. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import maya.cmds as cmds -import mmSolver.logger -import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib - -LOG = mmSolver.logger.get_logger() - - -def main(): - """ - Disable Hold-Outs for all meshes. - """ - all_meshes = cmds.ls(dag=True, type="mesh") or [] - if not all_meshes: - LOG.warn("Mesh not found.") - return - lib.set_holdout(all_meshes, False) diff --git a/python/mmSolver/tools/holdoutsenableallmeshes/tool.py b/python/mmSolver/tools/holdoutsenableallmeshes/tool.py deleted file mode 100644 index 4af0fa5f1..000000000 --- a/python/mmSolver/tools/holdoutsenableallmeshes/tool.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2024 Patcha Saheb Binginapalli. -# -# This file is part of mmSolver. -# -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . -# -""" -Enable Hold-Outs for selected meshes. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import maya.cmds as cmds -import mmSolver.logger -import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib - -LOG = mmSolver.logger.get_logger() - - -def main(): - """ - Enable Hold-Outs for all meshes. - """ - all_meshes = cmds.ls(dag=True, type="mesh") or [] - if not all_meshes: - LOG.warn("Mesh not found.") - return - lib.set_holdout(all_meshes, True) diff --git a/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py b/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py deleted file mode 100644 index ce31c1307..000000000 --- a/python/mmSolver/tools/holdoutsenableselectedmeshes/lib.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2024 Patcha Saheb Binginapalli. -# -# This file is part of mmSolver. -# -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . -# - -import maya.cmds as cmds - -import mmSolver.logger - -LOG = mmSolver.logger.get_logger() - - -def set_holdout(mesh_list, enable=True): - for mesh in mesh_list: - cmds.setAttr(mesh + ".holdOut", enable) diff --git a/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py b/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py deleted file mode 100644 index 76b76f272..000000000 --- a/python/mmSolver/tools/holdoutsenableselectedmeshes/tool.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (C) 2024 Patcha Saheb Binginapalli. -# -# This file is part of mmSolver. -# -# mmSolver is free software: you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# mmSolver is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with mmSolver. If not, see . -# -""" -Enable Hold-Outs for selected meshes. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import maya.cmds as cmds -import mmSolver.logger -import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib - -LOG = mmSolver.logger.get_logger() - - -def main(): - """ - Enable Hold-Outs for selected meshes. - """ - selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] - if not selected_meshes: - LOG.warn("Mesh selection not found.") - return - lib.set_holdout(selected_meshes, True) diff --git a/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py b/python/mmSolver/tools/setmeshholdouts/tool.py similarity index 55% rename from python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py rename to python/mmSolver/tools/setmeshholdouts/tool.py index 4803b2f88..e27cefe8d 100644 --- a/python/mmSolver/tools/holdoutsdisableselectedmeshes/tool.py +++ b/python/mmSolver/tools/setmeshholdouts/tool.py @@ -15,27 +15,46 @@ # You should have received a copy of the GNU Lesser General Public License # along with mmSolver. If not, see . # -""" -Disable Hold-Outs for selected meshes. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function import maya.cmds as cmds + import mmSolver.logger -import mmSolver.tools.holdoutsenableselectedmeshes.lib as lib LOG = mmSolver.logger.get_logger() -def main(): - """ - Disable Hold-Outs for selected meshes. - """ +def set_holdout(mesh_list, enable=True): + for mesh in mesh_list: + cmds.setAttr(mesh + ".holdOut", enable) + + +def enable_all_meshes(): + all_meshes = cmds.ls(dag=True, type="mesh") or [] + if not all_meshes: + LOG.warn("Mesh not found.") + return + set_holdout(all_meshes, True) + + +def disable_all_meshes(): + all_meshes = cmds.ls(dag=True, type="mesh") or [] + if not all_meshes: + LOG.warn("Mesh not found.") + return + set_holdout(all_meshes, False) + + +def enable_selected_meshes(): + selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] + if not selected_meshes: + LOG.warn("Mesh selection not found.") + return + set_holdout(selected_meshes, True) + + +def disable_selected_meshes(): selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] if not selected_meshes: LOG.warn("Mesh selection not found.") return - lib.set_holdout(selected_meshes, False) + set_holdout(selected_meshes, False) diff --git a/share/config/functions.json b/share/config/functions.json index f9d664a08..e56e0c9d4 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -633,42 +633,6 @@ "mmSolver.tools.meshfromlocators.tool.main();" ] }, - "enable_holdouts_for_selected_meshes": { - "name": "Enable Hold-Outs For Selected Meshes", - "name_shelf": "", - "tooltip": "Enable Hold-Out attribute for selected meshes.", - "command": [ - "import mmSolver.tools.holdoutsenableselectedmeshes.tool;", - "mmSolver.tools.holdoutsenableselectedmeshes.tool.main();" - ] - }, - "disable_holdouts_for_selected_meshes": { - "name": "Disable Hold-Outs For Selected Meshes", - "name_shelf": "", - "tooltip": "Disable Hold-Out attribute for selected meshes.", - "command": [ - "import mmSolver.tools.holdoutsdisableselectedmeshes.tool;", - "mmSolver.tools.holdoutsdisableselectedmeshes.tool.main();" - ] - }, - "enable_holdouts_for_all_meshes": { - "name": "Enable Hold-Outs For All Meshes", - "name_shelf": "", - "tooltip": "Enable Hold-Out attribute for all meshes.", - "command": [ - "import mmSolver.tools.holdoutsenableallmeshes.tool;", - "mmSolver.tools.holdoutsenableallmeshes.tool.main();" - ] - }, - "disable_holdouts_for_all_meshes": { - "name": "Disable Hold-Outs For All Meshes", - "name_shelf": "", - "tooltip": "Disable Hold-Out attribute for all meshes.", - "command": [ - "import mmSolver.tools.holdoutsdisableallmeshes.tool;", - "mmSolver.tools.holdoutsdisableallmeshes.tool.main();" - ] - }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", @@ -878,6 +842,42 @@ "mmSolver.tools.toggleobjectmotiontrail.tool.main();" ] }, + "enable_holdout_all_meshes": { + "name": "Enable Hold-Out For All Meshes", + "name_shelf": "EnHOAll", + "tooltip": "Enable Hold-Out for all meshes in scene.", + "command": [ + "import mmSolver.tools.setmeshholdouts.tool;", + "mmSolver.tools.setmeshholdouts.tool.enable_all_meshes();" + ] + }, + "disable_holdout_all_meshes": { + "name": "Disable Hold-Out For All Meshes", + "name_shelf": "DsHOAll", + "tooltip": "Disable Hold-Out for all meshes in scene.", + "command": [ + "import mmSolver.tools.setmeshholdouts.tool;", + "mmSolver.tools.setmeshholdouts.tool.disable_all_meshes();" + ] + }, + "enable_holdout_selected_meshes": { + "name": "Enable Hold-Out For Selected Meshes", + "name_shelf": "EnHOSel", + "tooltip": "Enable Hold-Out for selected meshes in scene.", + "command": [ + "import mmSolver.tools.setmeshholdouts.tool;", + "mmSolver.tools.setmeshholdouts.tool.enable_selected_meshes();" + ] + }, + "disable_holdout_selected_meshes": { + "name": "Disable Hold-Out For Selected Meshes", + "name_shelf": "DsHOSel", + "tooltip": "Disable Hold-Out for selected meshes in scene.", + "command": [ + "import mmSolver.tools.setmeshholdouts.tool;", + "mmSolver.tools.setmeshholdouts.tool.disable_selected_meshes();" + ] + }, "create_camera": { "name": "Create Camera", "name_shelf": "", diff --git a/share/config/menu.json b/share/config/menu.json index dba8bc3c0..c011663cc 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -88,6 +88,11 @@ "display_tools/reset_object_colour", "display_tools/toggle_object_motion_trail", "display_tools/create_screen_space_motion_trail", + "display_tools/---Mesh Hold-Out", + "display_tools/enable_holdout_all_meshes", + "display_tools/disable_holdout_all_meshes", + "display_tools/enable_holdout_selected_meshes", + "display_tools/disable_holdout_selected_meshes", "zdepth_tools/---Z-Depth Screen-Space Tools", "zdepth_tools/screen_z_manipulator", "zdepth_tools/screen_space_rig_bake", @@ -98,10 +103,6 @@ "mesh_tools/---Create", "mesh_tools/mesh_from_locators", "mesh_tools/---Hold-Outs", - "mesh_tools/enable_holdouts_for_all_meshes", - "mesh_tools/disable_holdouts_for_all_meshes", - "mesh_tools/enable_holdouts_for_selected_meshes", - "mesh_tools/disable_holdouts_for_selected_meshes", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index ca239c0f4..db627a612 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -85,6 +85,11 @@ "display_tools/display_popup/reset_object_colour", "display_tools/display_popup/toggle_object_motion_trail", "display_tools/display_popup/create_screen_space_motion_trail", + "display_tools/display_popup/---Mesh Hold-Out", + "display_tools/display_popup/enable_holdout_all_meshes", + "display_tools/display_popup/disable_holdout_all_meshes", + "display_tools/display_popup/enable_holdout_selected_meshes", + "display_tools/display_popup/disable_holdout_selected_meshes", "zdepth_tools/zdepth_popup/---Z-Depth Screen-Space Tools", "zdepth_tools/zdepth_popup/screen_z_manipulator", "zdepth_tools/zdepth_popup/screen_space_rig_bake", @@ -94,11 +99,6 @@ "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", "mesh_tools/mesh_tools_popup/mesh_from_locators", - "mesh_tools/mesh_tools_popup/---Hold-Outs", - "mesh_tools/mesh_tools_popup/enable_holdouts_for_all_meshes", - "mesh_tools/mesh_tools_popup/disable_holdouts_for_all_meshes", - "mesh_tools/mesh_tools_popup/enable_holdouts_for_selected_meshes", - "mesh_tools/mesh_tools_popup/disable_holdouts_for_selected_meshes", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 389a32e0dbadc7d6ff9ef87f937254883816e2c8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 12 Sep 2024 00:03:55 +1000 Subject: [PATCH 177/295] Minimal shelf - Add mesh tools and holdout toggles. --- share/config/shelf_minimal.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index e46cbe550..b1668121d 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -100,6 +100,11 @@ "display_tools/display_popup/reset_object_colour", "display_tools/display_popup/toggle_object_motion_trail", "display_tools/display_popup/create_screen_space_motion_trail", + "display_tools/display_popup/---Mesh Hold-Out", + "display_tools/display_popup/enable_holdout_all_meshes", + "display_tools/display_popup/disable_holdout_all_meshes", + "display_tools/display_popup/enable_holdout_selected_meshes", + "display_tools/display_popup/disable_holdout_selected_meshes", "zdepth_tools/zdepth_popup/---Z-Depth Screen-Space Tools", "zdepth_tools/zdepth_popup/screen_z_manipulator", "zdepth_tools/zdepth_popup/screen_space_rig_bake", @@ -107,6 +112,8 @@ "zdepth_tools/zdepth_popup/camera_object_scale_remove", "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", + "mesh_tools/mesh_tools_popup/---Create", + "mesh_tools/mesh_tools_popup/mesh_from_locators", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From fdefcc05ffab1af49a96c7bdc8f94ca63fd6a4cc Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 12 Sep 2024 00:06:45 +1000 Subject: [PATCH 178/295] Auto-format code to be consistent. --- .../mmSolver/tools/createcontroller2/tool.py | 2 +- python/mmSolver/tools/meshfromlocators/lib.py | 36 +++++++++++-------- .../ui/meshfromlocators_layout.py | 6 ++-- 3 files changed, 26 insertions(+), 18 deletions(-) diff --git a/python/mmSolver/tools/createcontroller2/tool.py b/python/mmSolver/tools/createcontroller2/tool.py index e13cf4a95..187c809ff 100644 --- a/python/mmSolver/tools/createcontroller2/tool.py +++ b/python/mmSolver/tools/createcontroller2/tool.py @@ -80,7 +80,7 @@ def create_world_controllers(): restore_current_frame=True, use_dg_evaluation_mode=True, disable_viewport=True, - disable_viewport_mode=const_utils.DISABLE_VIEWPORT_MODE_VP1_VALUE + disable_viewport_mode=const_utils.DISABLE_VIEWPORT_MODE_VP1_VALUE, ): for node in nodes: diff --git a/python/mmSolver/tools/meshfromlocators/lib.py b/python/mmSolver/tools/meshfromlocators/lib.py index 7a075c13e..4d9349406 100644 --- a/python/mmSolver/tools/meshfromlocators/lib.py +++ b/python/mmSolver/tools/meshfromlocators/lib.py @@ -18,7 +18,7 @@ import maya.api.OpenMaya as om import maya.api.OpenMayaUI as omui -import maya.cmds as cmds +import maya.cmds import mmSolver.logger from mmSolver.tools.meshfromlocators.delaunator import Delaunator @@ -34,29 +34,34 @@ def delaunator_indices(locators): :param locators: locatorlist :type locators: list """ - mesh_data = {"world_positions": [], - "vew_positions": [], - "full_mesh_indices": [], - "border_mesh_indices": []} + mesh_data = { + "world_positions": [], + "vew_positions": [], + "full_mesh_indices": [], + "border_mesh_indices": [], + } for locator_name in locators: - locator_pos = cmds.xform(locator_name, query=True, worldSpace=True, - translation=True) - locator_mpoint = om.MPoint(locator_pos[0], locator_pos[1], - locator_pos[2]) + locator_pos = maya.cmds.xform( + locator_name, query=True, worldSpace=True, translation=True + ) + locator_mpoint = om.MPoint(locator_pos[0], locator_pos[1], locator_pos[2]) mesh_data["world_positions"].append(locator_mpoint) # Convert world position to view space view = omui.M3dView.active3dView() view_pos = view.worldToView(locator_mpoint) view_x, view_y = view_pos[0], view_pos[1] mesh_data["vew_positions"].append([view_x, view_y]) + # Indices mesh_indices = Delaunator(mesh_data["vew_positions"]).triangles + # Reverse order mesh_indices_reverse = [] for i in range(0, len(mesh_indices), 3): - chunk = mesh_indices[i:i + 3] + chunk = mesh_indices[i : i + 3] mesh_indices_reverse.extend(chunk[::-1]) mesh_data["full_mesh_indices"] = mesh_indices_reverse + # Hull indices hull = Delaunator(mesh_data["vew_positions"]).hull hull.reverse() @@ -74,7 +79,7 @@ def create_mesh_from_locators(mesh_type=None, offset_value=1.0): :param offset_value: An offset value for borderEdgeStripMesh :type offset_value: float """ - locators = cmds.ls(selection=True, transforms=True) or [] + locators = maya.cmds.ls(selection=True, transforms=True) or [] if len(locators) < 3: LOG.warn('at least three locators must be selected.') return @@ -93,15 +98,18 @@ def create_mesh_from_locators(mesh_type=None, offset_value=1.0): # Create the mesh mesh_fn = om.MFnMesh() mesh = mesh_fn.create(positions, face_counts, indices) + # Set mesh name dag_node = om.MFnDagNode(mesh) dag_node.setName(const.MESH_NAME) mesh_name = dag_node.name() + # Set lambert - cmds.sets(mesh_name, edit=True, forceElement='initialShadingGroup') + maya.cmds.sets(mesh_name, edit=True, forceElement='initialShadingGroup') + # Create border edge strip mesh if mesh_type == 'borderEdgeStripMesh': - cmds.polyExtrudeFacet(mesh_name, offset=offset_value) + maya.cmds.polyExtrudeFacet(mesh_name, offset=offset_value) face_0 = '{}.f[0]'.format(mesh_name) face_1 = '{}.f[1]'.format(mesh_name) - cmds.delete(face_0, face_1) + maya.cmds.delete(face_0, face_1) diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py index 434518df1..c544c1a87 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py @@ -33,8 +33,7 @@ import mmSolver.tools.meshfromlocators.lib as lib -class MeshFromLocatorsLayout(QtWidgets.QWidget, - ui_meshfromlocators_layout.Ui_Form): +class MeshFromLocatorsLayout(QtWidgets.QWidget, ui_meshfromlocators_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): super(MeshFromLocatorsLayout, self).__init__(*args, **kwargs) self.setupUi(self) @@ -45,7 +44,8 @@ def create_connections(self): self.createFullMeshBtn.clicked.connect(self.full_mesh_btn_clicked) self.createBorderMeshBtn.clicked.connect(self.border_mesh_btn_clicked) self.createEdgeStripMeshBtn.clicked.connect( - self.border_edge_strip_mesh_btn_clicked) + self.border_edge_strip_mesh_btn_clicked + ) def full_mesh_btn_clicked(self): lib.create_mesh_from_locators('fullMesh') From cf08aca3d53b8e128dba65bf02d51f8f7d97e315 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 12 Sep 2024 00:07:14 +1000 Subject: [PATCH 179/295] Remove left over hold-outs menu line. --- share/config/menu.json | 1 - 1 file changed, 1 deletion(-) diff --git a/share/config/menu.json b/share/config/menu.json index 807d71ee0..c327d1606 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -106,7 +106,6 @@ "zdepth_tools/screen_z_transform_bake", "mesh_tools/---Create", "mesh_tools/mesh_from_locators", - "mesh_tools/---Hold-Outs", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", From cf46f3085ec67b5b5b511bf665d6ae92feb15e1b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 12 Sep 2024 00:07:39 +1000 Subject: [PATCH 180/295] Delaunator - fix link name. --- python/mmSolver/tools/meshfromlocators/delaunator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/meshfromlocators/delaunator.py b/python/mmSolver/tools/meshfromlocators/delaunator.py index 35f842c21..d52a5e8c2 100644 --- a/python/mmSolver/tools/meshfromlocators/delaunator.py +++ b/python/mmSolver/tools/meshfromlocators/delaunator.py @@ -14,7 +14,7 @@ # You should have received a copy of the GNU Lesser General Public # License along with this Delaunator-Python; If not, see . # -# Code from https://github.com/HakanSeven12/Delaunator-Python/blob/master/Delaunator.py4 +# Code from https://github.com/HakanSeven12/Delaunator-Python/blob/master/Delaunator.py # import math From 2e8f2412a0abcb2ef03dec9f2d3a0d6eacae5901 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Thu, 12 Sep 2024 00:08:04 +1000 Subject: [PATCH 181/295] Do not lint delaunator --- pylintrc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pylintrc b/pylintrc index fd4697e8f..66f589c82 100644 --- a/pylintrc +++ b/pylintrc @@ -13,7 +13,8 @@ ignore=CVS,.git # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. -ignore-paths=python/mmSolver/tools/mltools/*.py +ignore-paths=^python/mmSolver/tools/mltools/*.py$, + ^python/mmSolver/tools/meshfromlocators/delaunator.py$ # Pickle collected data for later comparisons. persistent=yes From 4dba6272b7137ccd4fead8e86ab779719ad3f1f1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 14 Sep 2024 23:55:26 +1000 Subject: [PATCH 182/295] Build System - fix Maya 2018/2019 OpenColorIO version The problem is that even with this newer OpenColorIO version, it won't compile with the compiler that Maya 2018/2019 expects... so these are unsupported build configurations now. --- scripts/build_mmSolver_windows64_maya2018.bat | 10 ++++------ scripts/build_mmSolver_windows64_maya2019.bat | 10 ++++------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/scripts/build_mmSolver_windows64_maya2018.bat b/scripts/build_mmSolver_windows64_maya2018.bat index 715687f4a..9751665b6 100644 --- a/scripts/build_mmSolver_windows64_maya2018.bat +++ b/scripts/build_mmSolver_windows64_maya2018.bat @@ -35,12 +35,10 @@ SET RUST_CARGO_EXE=cargo :: OpenColorIO specific options. :: -:: Maya doesn't ship with OpenColorIO at all, so lets pick the v2.0.x. -:: -:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.0.5 -:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.0.5.tar.gz -SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.0.5.tar.gz -SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ :: yaml-cpp 0.6.3 SET YAML_RELATIVE_CMAKE_DIR=CMake\ diff --git a/scripts/build_mmSolver_windows64_maya2019.bat b/scripts/build_mmSolver_windows64_maya2019.bat index 5e8b3b706..9d48c8d60 100644 --- a/scripts/build_mmSolver_windows64_maya2019.bat +++ b/scripts/build_mmSolver_windows64_maya2019.bat @@ -35,12 +35,10 @@ SET RUST_CARGO_EXE=cargo :: OpenColorIO specific options. :: -:: Maya doesn't ship with OpenColorIO at all, so lets pick the v2.0.x. -:: -:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.0.5 -:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.0.5.tar.gz -SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.0.5.tar.gz -SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.0.5 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/releases/tag/v2.2.1 +:: https://github.com/AcademySoftwareFoundation/OpenColorIO/archive/refs/tags/v2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_NAME=OpenColorIO-2.2.1.tar.gz +SET OPENCOLORIO_TARBALL_EXTRACTED_DIR_NAME=OpenColorIO-2.2.1 SET EXPAT_RELATIVE_LIB_PATH=lib\cmake\expat-2.2.8\ :: yaml-cpp 0.6.3 SET YAML_RELATIVE_CMAKE_DIR=CMake\ From 2d87324a1e7300db2a2f21648d86b14a81c3e7fa Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 14 Sep 2024 23:57:24 +1000 Subject: [PATCH 183/295] Build System - Build packages by default. You need to explicitly disable the packages if you don't want to build them. I know that's annoying when most of the time you don't want to produce a package, you just want to install locally, test, and iterate on the code. This change makes it possible that a default build from a random user will produce a package ready for installation, which is probably what they want. --- scripts/internal/build_mmSolver_linux.bash | 2 +- scripts/internal/build_mmSolver_windows64.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/internal/build_mmSolver_linux.bash b/scripts/internal/build_mmSolver_linux.bash index b5c0df6dd..c321d3728 100644 --- a/scripts/internal/build_mmSolver_linux.bash +++ b/scripts/internal/build_mmSolver_linux.bash @@ -52,7 +52,7 @@ INSTALL_MODULE_DIR="${HOME}/maya/${MAYA_VERSION}/modules" # Build ZIP Package. # For developer use. Make ZIP packages ready to distribute to others. -BUILD_PACKAGE=0 +BUILD_PACKAGE=1 # What directory to build the project in? BUILD_DIR_BASE="${PROJECT_ROOT}/../" diff --git a/scripts/internal/build_mmSolver_windows64.bat b/scripts/internal/build_mmSolver_windows64.bat index e1fcfdef8..d364d8ac8 100644 --- a/scripts/internal/build_mmSolver_windows64.bat +++ b/scripts/internal/build_mmSolver_windows64.bat @@ -52,7 +52,7 @@ SET INSTALL_MODULE_DIR="%USERPROFILE%\My Documents\maya\%MAYA_VERSION%\modules" :: Build ZIP Package. :: For developer use. Make ZIP packages ready to distribute to others. -SET BUILD_PACKAGE=0 +SET BUILD_PACKAGE=1 :: Do not edit below, unless you know what you're doing. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: From 2a6f53fe92b467a5184eb010de2210586a3b519f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sun, 15 Sep 2024 00:44:22 +1000 Subject: [PATCH 184/295] Build System - Black to 22.8.0. Per-Maya-version Python requirements. Black was changed to v22.8.0, because this is the last version that supports Python 3.6.x. I've created per-Maya-version requirements files so we can control the individual versions that each Maya version is getting. Each Maya version may be on a different Python version. --- scripts/internal/python_venv_activate.bash | 9 ++++----- scripts/internal/python_venv_activate.bat | 11 ++++------- ...irements-dev.txt => requirements-dev-maya2018.txt} | 3 ++- share/requirements-dev-maya2019.txt | 5 +++++ share/requirements-dev-maya2020.txt | 5 +++++ share/requirements-dev-maya2022.txt | 5 +++++ share/requirements-dev-maya2023.txt | 5 +++++ share/requirements-dev-maya2024.txt | 5 +++++ ...irements-doc.txt => requirements-doc-maya2018.txt} | 1 + share/requirements-doc-maya2019.txt | 3 +++ share/requirements-doc-maya2020.txt | 3 +++ share/requirements-doc-maya2022.txt | 3 +++ share/requirements-doc-maya2023.txt | 3 +++ share/requirements-doc-maya2024.txt | 3 +++ 14 files changed, 51 insertions(+), 13 deletions(-) rename share/{requirements-dev.txt => requirements-dev-maya2018.txt} (54%) create mode 100644 share/requirements-dev-maya2019.txt create mode 100644 share/requirements-dev-maya2020.txt create mode 100644 share/requirements-dev-maya2022.txt create mode 100644 share/requirements-dev-maya2023.txt rename share/{requirements-doc.txt => requirements-doc-maya2018.txt} (56%) create mode 100644 share/requirements-doc-maya2019.txt create mode 100644 share/requirements-doc-maya2020.txt create mode 100644 share/requirements-doc-maya2022.txt create mode 100644 share/requirements-doc-maya2023.txt create mode 100644 share/requirements-doc-maya2024.txt diff --git a/scripts/internal/python_venv_activate.bash b/scripts/internal/python_venv_activate.bash index 4a7e5396f..de08e353a 100755 --- a/scripts/internal/python_venv_activate.bash +++ b/scripts/internal/python_venv_activate.bash @@ -70,13 +70,12 @@ source "${PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT}" # Install requirements if [ ${REQUIRE_PACKAGE_INSTALL} -eq 1 ]; then ${PYTHON_EXE} -m pip install --upgrade pip - ${PYTHON_EXE} -m pip install -r "${PROJECT_ROOT}/share/requirements-dev.txt" - ${PYTHON_EXE} -m pip install -r "${PROJECT_ROOT}/share/requirements-doc.txt" REQUIRE_DEV_MAYA_VERSION_FILE="${PROJECT_ROOT}/share/requirements-dev-maya${MAYA_VERSION}.txt" - if [ -f "$REQUIRE_DEV_MAYA_VERSION_FILE" ]; then - ${PYTHON_EXE} -m pip install -r $REQUIRE_DEV_MAYA_VERSION_FILE - fi + REQUIRE_DOC_MAYA_VERSION_FILE="${PROJECT_ROOT}/share/requirements-doc-maya${MAYA_VERSION}.txt" + + ${PYTHON_EXE} -m pip install -r $REQUIRE_DEV_MAYA_VERSION_FILE + ${PYTHON_EXE} -m pip install -r $REQUIRE_DOC_MAYA_VERSION_FILE fi cd ${PROJECT_ROOT} diff --git a/scripts/internal/python_venv_activate.bat b/scripts/internal/python_venv_activate.bat index 6e7deacc8..6262bad57 100644 --- a/scripts/internal/python_venv_activate.bat +++ b/scripts/internal/python_venv_activate.bat @@ -65,15 +65,12 @@ ECHO Activating Python Virtual Environment "%PYTHON_VIRTUAL_ENV_DIR_NAME%" CALL %PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT% :: Install requirements -SET MAYA_VERSION_REQUIRE_FILE=%PROJECT_ROOT%\share\requirements-dev-maya%MAYA_VERSION%.txt +SET MAYA_VERSION_REQUIRE_DEV_FILE=%PROJECT_ROOT%\share\requirements-dev-maya%MAYA_VERSION%.txt +SET MAYA_VERSION_REQUIRE_DOC_FILE=%PROJECT_ROOT%\share\requirements-doc-maya%MAYA_VERSION%.txt IF "%REQUIRE_PACKAGE_INSTALL%"=="1" ( :: %PYTHON_EXE% -m pip install --upgrade pip - %PYTHON_EXE% -m pip install -r "%PROJECT_ROOT%\share\requirements-dev.txt" - %PYTHON_EXE% -m pip install -r "%PROJECT_ROOT%\share\requirements-doc.txt" - - IF EXIST %MAYA_VERSION_REQUIRE_FILE% ( - %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_FILE% - ) + %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_DEV_FILE% + %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_DEV_FILE% ) :: Return back project root directory. diff --git a/share/requirements-dev.txt b/share/requirements-dev-maya2018.txt similarity index 54% rename from share/requirements-dev.txt rename to share/requirements-dev-maya2018.txt index 86ac84c72..500c54c32 100644 --- a/share/requirements-dev.txt +++ b/share/requirements-dev-maya2018.txt @@ -1,4 +1,5 @@ -black==24.3.0 +# Expects Python 3.6.x +black==22.8.0 pylint==2.13.9 flake8==4.0.1 cpplint==1.6.0 diff --git a/share/requirements-dev-maya2019.txt b/share/requirements-dev-maya2019.txt new file mode 100644 index 000000000..500c54c32 --- /dev/null +++ b/share/requirements-dev-maya2019.txt @@ -0,0 +1,5 @@ +# Expects Python 3.6.x +black==22.8.0 +pylint==2.13.9 +flake8==4.0.1 +cpplint==1.6.0 diff --git a/share/requirements-dev-maya2020.txt b/share/requirements-dev-maya2020.txt new file mode 100644 index 000000000..500c54c32 --- /dev/null +++ b/share/requirements-dev-maya2020.txt @@ -0,0 +1,5 @@ +# Expects Python 3.6.x +black==22.8.0 +pylint==2.13.9 +flake8==4.0.1 +cpplint==1.6.0 diff --git a/share/requirements-dev-maya2022.txt b/share/requirements-dev-maya2022.txt new file mode 100644 index 000000000..500c54c32 --- /dev/null +++ b/share/requirements-dev-maya2022.txt @@ -0,0 +1,5 @@ +# Expects Python 3.6.x +black==22.8.0 +pylint==2.13.9 +flake8==4.0.1 +cpplint==1.6.0 diff --git a/share/requirements-dev-maya2023.txt b/share/requirements-dev-maya2023.txt new file mode 100644 index 000000000..500c54c32 --- /dev/null +++ b/share/requirements-dev-maya2023.txt @@ -0,0 +1,5 @@ +# Expects Python 3.6.x +black==22.8.0 +pylint==2.13.9 +flake8==4.0.1 +cpplint==1.6.0 diff --git a/share/requirements-dev-maya2024.txt b/share/requirements-dev-maya2024.txt index 2bc0c6f01..856ef8f07 100644 --- a/share/requirements-dev-maya2024.txt +++ b/share/requirements-dev-maya2024.txt @@ -1 +1,6 @@ +# Expects Python 3.9.x +black==24.3.0 +pylint==2.13.9 +flake8==4.0.1 +cpplint==1.6.0 ruff==0.0.261 diff --git a/share/requirements-doc.txt b/share/requirements-doc-maya2018.txt similarity index 56% rename from share/requirements-doc.txt rename to share/requirements-doc-maya2018.txt index 89637b009..91e78f95a 100644 --- a/share/requirements-doc.txt +++ b/share/requirements-doc-maya2018.txt @@ -1,2 +1,3 @@ +# Expects Python 3.6.x Sphinx>=4.5.0 furo>=2022.2.23 diff --git a/share/requirements-doc-maya2019.txt b/share/requirements-doc-maya2019.txt new file mode 100644 index 000000000..91e78f95a --- /dev/null +++ b/share/requirements-doc-maya2019.txt @@ -0,0 +1,3 @@ +# Expects Python 3.6.x +Sphinx>=4.5.0 +furo>=2022.2.23 diff --git a/share/requirements-doc-maya2020.txt b/share/requirements-doc-maya2020.txt new file mode 100644 index 000000000..91e78f95a --- /dev/null +++ b/share/requirements-doc-maya2020.txt @@ -0,0 +1,3 @@ +# Expects Python 3.6.x +Sphinx>=4.5.0 +furo>=2022.2.23 diff --git a/share/requirements-doc-maya2022.txt b/share/requirements-doc-maya2022.txt new file mode 100644 index 000000000..91e78f95a --- /dev/null +++ b/share/requirements-doc-maya2022.txt @@ -0,0 +1,3 @@ +# Expects Python 3.6.x +Sphinx>=4.5.0 +furo>=2022.2.23 diff --git a/share/requirements-doc-maya2023.txt b/share/requirements-doc-maya2023.txt new file mode 100644 index 000000000..91e78f95a --- /dev/null +++ b/share/requirements-doc-maya2023.txt @@ -0,0 +1,3 @@ +# Expects Python 3.6.x +Sphinx>=4.5.0 +furo>=2022.2.23 diff --git a/share/requirements-doc-maya2024.txt b/share/requirements-doc-maya2024.txt new file mode 100644 index 000000000..2fdd0f4a6 --- /dev/null +++ b/share/requirements-doc-maya2024.txt @@ -0,0 +1,3 @@ +# Expects Python 3.9.x +Sphinx>=4.5.0 +furo>=2022.2.23 From 67bedad88cf5bfc98cec6b2537bc02e2052386b2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 16:33:35 +1000 Subject: [PATCH 185/295] Change Python package update system The versions are hard-coded but also relaxed in some cases to allow dependencies to be resolved easier. The reason we hard-code version numbers is to ensure consistent formatting/linting and documentation building. Different Maya versions will require different Python versions and so will require different package versions. The "doc" and "dev" requirement files have been merged into a single file for each Maya version because keeping all the files up to date seemed like a bit of a nightmare, and doesn't seem to provide any extra details. Instead the different needs of the packages are named in the comments of the files. The versions (where given) in these files are known to work today (2024-09-16) with the Python versions expected. If the Python vesions change in the future, these version numbers may need to be changed. --- .github/workflows/build_and_deploy_docs.yml | 2 +- .github/workflows/lint_code.yml | 2 +- scripts/build_mmSolver_linux_maya2020.bash | 2 +- scripts/build_mmSolver_linux_maya2022.bash | 2 +- scripts/build_mmSolver_linux_maya2023.bash | 2 +- scripts/build_mmSolver_linux_maya2024.bash | 3 +++ scripts/internal/python_venv_activate.bash | 9 ++++----- scripts/internal/python_venv_activate.bat | 9 +++++---- scripts/python_venv_activate_maya2020.bash | 2 +- scripts/python_venv_activate_maya2022.bash | 2 +- scripts/python_venv_activate_maya2023.bash | 2 +- share/python_requirements/README.md | 9 +++++++++ ...equirements-github-build-and-deploy-docs.txt | 10 ++++++++++ .../requirements-github-lint-code.txt | 12 ++++++++++++ .../requirements-maya2018.txt | 17 +++++++++++++++++ .../requirements-maya2019.txt | 13 +++++++++++++ .../requirements-maya2020.txt | 13 +++++++++++++ .../requirements-maya2022.txt | 13 +++++++++++++ .../requirements-maya2023.txt | 13 +++++++++++++ .../requirements-maya2024.txt | 14 ++++++++++++++ share/requirements-dev-maya2018.txt | 5 ----- share/requirements-dev-maya2019.txt | 5 ----- share/requirements-dev-maya2020.txt | 5 ----- share/requirements-dev-maya2022.txt | 5 ----- share/requirements-dev-maya2023.txt | 5 ----- share/requirements-dev-maya2024.txt | 6 ------ share/requirements-doc-maya2018.txt | 3 --- share/requirements-doc-maya2019.txt | 3 --- share/requirements-doc-maya2020.txt | 3 --- share/requirements-doc-maya2022.txt | 3 --- share/requirements-doc-maya2023.txt | 3 --- share/requirements-doc-maya2024.txt | 3 --- 32 files changed, 134 insertions(+), 66 deletions(-) create mode 100644 share/python_requirements/README.md create mode 100644 share/python_requirements/requirements-github-build-and-deploy-docs.txt create mode 100644 share/python_requirements/requirements-github-lint-code.txt create mode 100644 share/python_requirements/requirements-maya2018.txt create mode 100644 share/python_requirements/requirements-maya2019.txt create mode 100644 share/python_requirements/requirements-maya2020.txt create mode 100644 share/python_requirements/requirements-maya2022.txt create mode 100644 share/python_requirements/requirements-maya2023.txt create mode 100644 share/python_requirements/requirements-maya2024.txt delete mode 100644 share/requirements-dev-maya2018.txt delete mode 100644 share/requirements-dev-maya2019.txt delete mode 100644 share/requirements-dev-maya2020.txt delete mode 100644 share/requirements-dev-maya2022.txt delete mode 100644 share/requirements-dev-maya2023.txt delete mode 100644 share/requirements-dev-maya2024.txt delete mode 100644 share/requirements-doc-maya2018.txt delete mode 100644 share/requirements-doc-maya2019.txt delete mode 100644 share/requirements-doc-maya2020.txt delete mode 100644 share/requirements-doc-maya2022.txt delete mode 100644 share/requirements-doc-maya2023.txt delete mode 100644 share/requirements-doc-maya2024.txt diff --git a/.github/workflows/build_and_deploy_docs.yml b/.github/workflows/build_and_deploy_docs.yml index cf1459c65..7b4a8dee3 100644 --- a/.github/workflows/build_and_deploy_docs.yml +++ b/.github/workflows/build_and_deploy_docs.yml @@ -25,7 +25,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r share/requirements-doc.txt + pip install -r share/python_requirements/requirements-github-build-and-deploy-docs.txt - name: Building Sphinx Documentation run: | diff --git a/.github/workflows/lint_code.yml b/.github/workflows/lint_code.yml index 165a63671..3a7ce5c8f 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/lint_code.yml @@ -19,7 +19,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r share/requirements-dev.txt + pip install -r share/python_requirements/requirements-github-lint-code.txt - name: Analysing the Python code with flake8 (Hard Error) run: | diff --git a/scripts/build_mmSolver_linux_maya2020.bash b/scripts/build_mmSolver_linux_maya2020.bash index 21d89c53d..e42a3e5c6 100644 --- a/scripts/build_mmSolver_linux_maya2020.bash +++ b/scripts/build_mmSolver_linux_maya2020.bash @@ -25,7 +25,7 @@ MAYA_VERSION=2020 MAYA_LOCATION=/usr/autodesk/maya2020/ # Executable names/paths used for build process. -PYTHON_EXE=python +PYTHON_EXE=python3 CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo diff --git a/scripts/build_mmSolver_linux_maya2022.bash b/scripts/build_mmSolver_linux_maya2022.bash index 62fdf672b..ed565f07f 100644 --- a/scripts/build_mmSolver_linux_maya2022.bash +++ b/scripts/build_mmSolver_linux_maya2022.bash @@ -25,7 +25,7 @@ MAYA_VERSION=2022 MAYA_LOCATION=/usr/autodesk/maya2022/ # Executable names/paths used for build process. -PYTHON_EXE=python +PYTHON_EXE=python3 CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo diff --git a/scripts/build_mmSolver_linux_maya2023.bash b/scripts/build_mmSolver_linux_maya2023.bash index f4b56cc03..4194668de 100644 --- a/scripts/build_mmSolver_linux_maya2023.bash +++ b/scripts/build_mmSolver_linux_maya2023.bash @@ -25,7 +25,7 @@ MAYA_VERSION=2023 MAYA_LOCATION=/usr/autodesk/maya2023/ # Executable names/paths used for build process. -PYTHON_EXE=python +PYTHON_EXE=python3 CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo diff --git a/scripts/build_mmSolver_linux_maya2024.bash b/scripts/build_mmSolver_linux_maya2024.bash index 814946881..74e143a7b 100644 --- a/scripts/build_mmSolver_linux_maya2024.bash +++ b/scripts/build_mmSolver_linux_maya2024.bash @@ -25,6 +25,9 @@ MAYA_VERSION=2024 MAYA_LOCATION=/usr/autodesk/maya2024/ # Executable names/paths used for build process. +# +# RockyLinux8 has Python 3.6 by default, but we use Python 3.9 because +# it has better support for tools like 'ruff'. PYTHON_EXE=python3.9 CMAKE_EXE=cmake3 RUST_CARGO_EXE=cargo diff --git a/scripts/internal/python_venv_activate.bash b/scripts/internal/python_venv_activate.bash index de08e353a..5adfb427c 100755 --- a/scripts/internal/python_venv_activate.bash +++ b/scripts/internal/python_venv_activate.bash @@ -58,6 +58,7 @@ fi REQUIRE_PACKAGE_INSTALL=0 if [ ! -f "${PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT}" ]; then echo "Setting up Python Virtual Environment ${PYTHON_VIRTUAL_ENV_DIR_NAME}" + ${PYTHON_EXE} --version ${PYTHON_EXE} -m venv ${PYTHON_VIRTUAL_ENV_DIR} REQUIRE_PACKAGE_INSTALL=1 fi @@ -69,13 +70,11 @@ source "${PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT}" # Install requirements if [ ${REQUIRE_PACKAGE_INSTALL} -eq 1 ]; then + ${PYTHON_EXE} --version ${PYTHON_EXE} -m pip install --upgrade pip - REQUIRE_DEV_MAYA_VERSION_FILE="${PROJECT_ROOT}/share/requirements-dev-maya${MAYA_VERSION}.txt" - REQUIRE_DOC_MAYA_VERSION_FILE="${PROJECT_ROOT}/share/requirements-doc-maya${MAYA_VERSION}.txt" - - ${PYTHON_EXE} -m pip install -r $REQUIRE_DEV_MAYA_VERSION_FILE - ${PYTHON_EXE} -m pip install -r $REQUIRE_DOC_MAYA_VERSION_FILE + REQUIRE_MAYA_VERSION_FILE="${PROJECT_ROOT}/share/python_requirements/requirements-maya${MAYA_VERSION}.txt" + ${PYTHON_EXE} -m pip install -r $REQUIRE_MAYA_VERSION_FILE fi cd ${PROJECT_ROOT} diff --git a/scripts/internal/python_venv_activate.bat b/scripts/internal/python_venv_activate.bat index 6262bad57..eea39559e 100644 --- a/scripts/internal/python_venv_activate.bat +++ b/scripts/internal/python_venv_activate.bat @@ -56,6 +56,7 @@ IF "%FRESH_PYTHON_VIRTUAL_ENV%"=="1" ( SET REQUIRE_PACKAGE_INSTALL=0 IF NOT EXIST %PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT% ( ECHO Setting up Python Virtual Environment "%PYTHON_VIRTUAL_ENV_DIR_NAME%" + %PYTHON_EXE% --version %PYTHON_EXE% -m venv %PYTHON_VIRTUAL_ENV_DIR% SET REQUIRE_PACKAGE_INSTALL=1 ) @@ -65,12 +66,12 @@ ECHO Activating Python Virtual Environment "%PYTHON_VIRTUAL_ENV_DIR_NAME%" CALL %PYTHON_VIRTUAL_ENV_ACTIVATE_SCRIPT% :: Install requirements -SET MAYA_VERSION_REQUIRE_DEV_FILE=%PROJECT_ROOT%\share\requirements-dev-maya%MAYA_VERSION%.txt -SET MAYA_VERSION_REQUIRE_DOC_FILE=%PROJECT_ROOT%\share\requirements-doc-maya%MAYA_VERSION%.txt +SET MAYA_VERSION_REQUIRE_FILE=%PROJECT_ROOT%\share\python_requirements\requirements-maya%MAYA_VERSION%.txt IF "%REQUIRE_PACKAGE_INSTALL%"=="1" ( + %PYTHON_EXE% --version + :: TODO: Why is the PIP upgrade disabled on Windows? On Linux it's very important. :: %PYTHON_EXE% -m pip install --upgrade pip - %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_DEV_FILE% - %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_DEV_FILE% + %PYTHON_EXE% -m pip install -r %MAYA_VERSION_REQUIRE_FILE% ) :: Return back project root directory. diff --git a/scripts/python_venv_activate_maya2020.bash b/scripts/python_venv_activate_maya2020.bash index 0b3e2bcf4..9d051a6f9 100644 --- a/scripts/python_venv_activate_maya2020.bash +++ b/scripts/python_venv_activate_maya2020.bash @@ -33,7 +33,7 @@ PROJECT_ROOT=`pwd` MAYA_VERSION=2020 # Python executable - edit this to point to an explicit python executable file. -PYTHON_EXE=python +PYTHON_EXE=python3 PYTHON_VIRTUAL_ENV_DIR_NAME="python_venv_linux_maya${MAYA_VERSION}" source "${PROJECT_ROOT}/scripts/internal/python_venv_activate.bash" diff --git a/scripts/python_venv_activate_maya2022.bash b/scripts/python_venv_activate_maya2022.bash index ae802cca8..f0ae856d1 100644 --- a/scripts/python_venv_activate_maya2022.bash +++ b/scripts/python_venv_activate_maya2022.bash @@ -33,7 +33,7 @@ PROJECT_ROOT=`pwd` MAYA_VERSION=2022 # Python executable - edit this to point to an explicit python executable file. -PYTHON_EXE=python +PYTHON_EXE=python3 PYTHON_VIRTUAL_ENV_DIR_NAME="python_venv_linux_maya${MAYA_VERSION}" source "${PROJECT_ROOT}/scripts/internal/python_venv_activate.bash" diff --git a/scripts/python_venv_activate_maya2023.bash b/scripts/python_venv_activate_maya2023.bash index dc5a54251..391d6a2f7 100644 --- a/scripts/python_venv_activate_maya2023.bash +++ b/scripts/python_venv_activate_maya2023.bash @@ -33,7 +33,7 @@ PROJECT_ROOT=`pwd` MAYA_VERSION=2023 # Python executable - edit this to point to an explicit python executable file. -PYTHON_EXE=python +PYTHON_EXE=python3 PYTHON_VIRTUAL_ENV_DIR_NAME="python_venv_linux_maya${MAYA_VERSION}" source "${PROJECT_ROOT}/scripts/internal/python_venv_activate.bash" diff --git a/share/python_requirements/README.md b/share/python_requirements/README.md new file mode 100644 index 000000000..4f9363bc1 --- /dev/null +++ b/share/python_requirements/README.md @@ -0,0 +1,9 @@ +# Python Package Requirement Files + +This directory is the home to "requirements.txt" files, which are used +by Python's PIP package manager to install python packages. + +There are different files for different usages. The "github" +requirement files are used by various GitHub actions, and are reduced +to the minimum required dependencies, to improve speed of the GitHub +actions. diff --git a/share/python_requirements/requirements-github-build-and-deploy-docs.txt b/share/python_requirements/requirements-github-build-and-deploy-docs.txt new file mode 100644 index 000000000..7730af7ce --- /dev/null +++ b/share/python_requirements/requirements-github-build-and-deploy-docs.txt @@ -0,0 +1,10 @@ +# .github/workflows/build_and_deploy_docs.yml +# +# Expects Python 3.7.x + +# Required packages for building documentation on GitHub. +# +# NOTE: No versions are specified to allow the package version resolver +# to resolve correctly. +Sphinx +furo diff --git a/share/python_requirements/requirements-github-lint-code.txt b/share/python_requirements/requirements-github-lint-code.txt new file mode 100644 index 000000000..cf122dd91 --- /dev/null +++ b/share/python_requirements/requirements-github-lint-code.txt @@ -0,0 +1,12 @@ +# .github/workflows/lint_code.yml +# +# Expects Python 3.7.x + +# Required packages for linting code on GitHub. +# +# NOTE: No versions are specified to allow the package version resolver +# to resolve correctly. +black +pylint +flake8 +cpplint diff --git a/share/python_requirements/requirements-maya2018.txt b/share/python_requirements/requirements-maya2018.txt new file mode 100644 index 000000000..cbec4fc22 --- /dev/null +++ b/share/python_requirements/requirements-maya2018.txt @@ -0,0 +1,17 @@ +# Python Package Requirements. +# +# Expects Python 3.6.x + +# Tools needed for code linting and formatting. +# +# NOTE: No versions are explicitly to allow the PIP version resolver to find a match. +black +pylint +flake8 +cpplint + +# Tools needed for documentation building. +# +# NOTE: No versions are explicitly to allow the PIP version resolver to find a match. +Sphinx +furo diff --git a/share/python_requirements/requirements-maya2019.txt b/share/python_requirements/requirements-maya2019.txt new file mode 100644 index 000000000..2b0a98de2 --- /dev/null +++ b/share/python_requirements/requirements-maya2019.txt @@ -0,0 +1,13 @@ +# Python Package Requirements. +# +# Expects Python 3.6.x + +# Tools needed for code linting and formatting. +black == 22.8.0 +pylint == 2.13.9 +flake8 == 5.0.4 +cpplint == 1.6.1 + +# Tools needed for documentation building. +Sphinx == 4.3.2 +furo == 2022.4.7 diff --git a/share/python_requirements/requirements-maya2020.txt b/share/python_requirements/requirements-maya2020.txt new file mode 100644 index 000000000..2b0a98de2 --- /dev/null +++ b/share/python_requirements/requirements-maya2020.txt @@ -0,0 +1,13 @@ +# Python Package Requirements. +# +# Expects Python 3.6.x + +# Tools needed for code linting and formatting. +black == 22.8.0 +pylint == 2.13.9 +flake8 == 5.0.4 +cpplint == 1.6.1 + +# Tools needed for documentation building. +Sphinx == 4.3.2 +furo == 2022.4.7 diff --git a/share/python_requirements/requirements-maya2022.txt b/share/python_requirements/requirements-maya2022.txt new file mode 100644 index 000000000..2b0a98de2 --- /dev/null +++ b/share/python_requirements/requirements-maya2022.txt @@ -0,0 +1,13 @@ +# Python Package Requirements. +# +# Expects Python 3.6.x + +# Tools needed for code linting and formatting. +black == 22.8.0 +pylint == 2.13.9 +flake8 == 5.0.4 +cpplint == 1.6.1 + +# Tools needed for documentation building. +Sphinx == 4.3.2 +furo == 2022.4.7 diff --git a/share/python_requirements/requirements-maya2023.txt b/share/python_requirements/requirements-maya2023.txt new file mode 100644 index 000000000..2b0a98de2 --- /dev/null +++ b/share/python_requirements/requirements-maya2023.txt @@ -0,0 +1,13 @@ +# Python Package Requirements. +# +# Expects Python 3.6.x + +# Tools needed for code linting and formatting. +black == 22.8.0 +pylint == 2.13.9 +flake8 == 5.0.4 +cpplint == 1.6.1 + +# Tools needed for documentation building. +Sphinx == 4.3.2 +furo == 2022.4.7 diff --git a/share/python_requirements/requirements-maya2024.txt b/share/python_requirements/requirements-maya2024.txt new file mode 100644 index 000000000..00f68cbaf --- /dev/null +++ b/share/python_requirements/requirements-maya2024.txt @@ -0,0 +1,14 @@ +# Python Package Requirements. +# +# Expects Python 3.9.x + +# Tools needed for code linting and formatting. +black == 24.8.0 +pylint == 3.2.7 +flake8 == 7.1.1 +cpplint == 1.6.1 +ruff == 0.6.5 + +# Tools needed for documentation building. +Sphinx == 7.4.7 +furo == 2024.8.6 diff --git a/share/requirements-dev-maya2018.txt b/share/requirements-dev-maya2018.txt deleted file mode 100644 index 500c54c32..000000000 --- a/share/requirements-dev-maya2018.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Expects Python 3.6.x -black==22.8.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 diff --git a/share/requirements-dev-maya2019.txt b/share/requirements-dev-maya2019.txt deleted file mode 100644 index 500c54c32..000000000 --- a/share/requirements-dev-maya2019.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Expects Python 3.6.x -black==22.8.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 diff --git a/share/requirements-dev-maya2020.txt b/share/requirements-dev-maya2020.txt deleted file mode 100644 index 500c54c32..000000000 --- a/share/requirements-dev-maya2020.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Expects Python 3.6.x -black==22.8.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 diff --git a/share/requirements-dev-maya2022.txt b/share/requirements-dev-maya2022.txt deleted file mode 100644 index 500c54c32..000000000 --- a/share/requirements-dev-maya2022.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Expects Python 3.6.x -black==22.8.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 diff --git a/share/requirements-dev-maya2023.txt b/share/requirements-dev-maya2023.txt deleted file mode 100644 index 500c54c32..000000000 --- a/share/requirements-dev-maya2023.txt +++ /dev/null @@ -1,5 +0,0 @@ -# Expects Python 3.6.x -black==22.8.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 diff --git a/share/requirements-dev-maya2024.txt b/share/requirements-dev-maya2024.txt deleted file mode 100644 index 856ef8f07..000000000 --- a/share/requirements-dev-maya2024.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Expects Python 3.9.x -black==24.3.0 -pylint==2.13.9 -flake8==4.0.1 -cpplint==1.6.0 -ruff==0.0.261 diff --git a/share/requirements-doc-maya2018.txt b/share/requirements-doc-maya2018.txt deleted file mode 100644 index 91e78f95a..000000000 --- a/share/requirements-doc-maya2018.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.6.x -Sphinx>=4.5.0 -furo>=2022.2.23 diff --git a/share/requirements-doc-maya2019.txt b/share/requirements-doc-maya2019.txt deleted file mode 100644 index 91e78f95a..000000000 --- a/share/requirements-doc-maya2019.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.6.x -Sphinx>=4.5.0 -furo>=2022.2.23 diff --git a/share/requirements-doc-maya2020.txt b/share/requirements-doc-maya2020.txt deleted file mode 100644 index 91e78f95a..000000000 --- a/share/requirements-doc-maya2020.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.6.x -Sphinx>=4.5.0 -furo>=2022.2.23 diff --git a/share/requirements-doc-maya2022.txt b/share/requirements-doc-maya2022.txt deleted file mode 100644 index 91e78f95a..000000000 --- a/share/requirements-doc-maya2022.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.6.x -Sphinx>=4.5.0 -furo>=2022.2.23 diff --git a/share/requirements-doc-maya2023.txt b/share/requirements-doc-maya2023.txt deleted file mode 100644 index 91e78f95a..000000000 --- a/share/requirements-doc-maya2023.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.6.x -Sphinx>=4.5.0 -furo>=2022.2.23 diff --git a/share/requirements-doc-maya2024.txt b/share/requirements-doc-maya2024.txt deleted file mode 100644 index 2fdd0f4a6..000000000 --- a/share/requirements-doc-maya2024.txt +++ /dev/null @@ -1,3 +0,0 @@ -# Expects Python 3.9.x -Sphinx>=4.5.0 -furo>=2022.2.23 From 4dbb4e469831360e11633f71a68a0f936ceb3f3d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 16:44:02 +1000 Subject: [PATCH 186/295] Update copyright year for Patcha --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 787cb36bd..6bcb081de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ Copyright (C) 2018-2024 David Cattermole. Copyright (C) 2019 Anil Reddy. Copyright (C) 2020-2022 Kazuma Tonegawa. -Copyright (C) 2021-2023 Patcha Saheb Binginapalli. +Copyright (C) 2021-2024 Patcha Saheb Binginapalli. This software, mmSolver, is licensed under the GNU LGPL v3 license. From f0eb26d077f145d344601b8f21f264680c9c1701 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 16:45:24 +1000 Subject: [PATCH 187/295] Docs - Fix download links for v0.4.9. --- docs/source/download.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/source/download.rst b/docs/source/download.rst index 88c6b593c..f17f56c24 100644 --- a/docs/source/download.rst +++ b/docs/source/download.rst @@ -15,47 +15,47 @@ Download the latest release **mmSolver v0.4.9**: * - Linux - Maya 2019 - - `link `_ + - `link `_ * - Linux - Maya 2020 - - `link `_ + - `link `_ * - Linux - Maya 2022 - - `link `_ + - `link `_ * - Linux - Maya 2023 - - `link `_ + - `link `_ * - Linux - Maya 2024 - - `link `_ + - `link `_ * - Windows - Maya 2018 - - `link `_ + - `link `_ * - Windows - Maya 2019 - - `link `_ + - `link `_ * - Windows - Maya 2020 - - `link `_ + - `link `_ * - Windows - Maya 2022 - - `link `_ + - `link `_ * - Windows - Maya 2023 - - `link `_ + - `link `_ * - Windows - Maya 2024 - - `link `_ + - `link `_ Older versions and full release notes can be found on the GitHub releases_ page. From 4e5cbb5e65cbae55698827ded2b8be581a558ff5 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:28:15 +1000 Subject: [PATCH 188/295] Black formatter should support all Python versions supported. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 265b16147..712205712 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.black] line-length = 88 # The black default is a good compromise. skip-string-normalization = 1 -target-version = ['py34'] +target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311', 'py312'] include = '\.py$' extend-exclude = ''' /( From 86ec3f479a4873dd3dd2764bc0f2fc3fb63b5a2e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:29:02 +1000 Subject: [PATCH 189/295] Change Ruff to support Python 3.6+ 3.7 seems to be the default, but we're roughly supporting Python 3.6+ across all the Maya versions. --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 712205712..50382e4eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,8 @@ extend-exclude = ''' # Same as Black. line-length = 88 -# Assume Python 3.7 -target-version = "py37" +# Assume Python 3.6+ +target-version = "py36" [tool.ruff.mccabe] # Unlike Flake8, default to a complexity level of 10. From 123e6f7b3a58598235b5da2eaf5ed2b87ea00ee5 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:47:25 +1000 Subject: [PATCH 190/295] Add missingPython virtual env deactivate script for Maya 2024. --- scripts/python_venv_deactivate_maya2024.bat | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 scripts/python_venv_deactivate_maya2024.bat diff --git a/scripts/python_venv_deactivate_maya2024.bat b/scripts/python_venv_deactivate_maya2024.bat new file mode 100644 index 000000000..415ad6dee --- /dev/null +++ b/scripts/python_venv_deactivate_maya2024.bat @@ -0,0 +1,28 @@ +@ECHO OFF +:: +:: Copyright (C) 2021 David Cattermole. +:: +:: This file is part of mmSolver. +:: +:: mmSolver is free software: you can redistribute it and/or modify it +:: under the terms of the GNU Lesser General Public License as +:: published by the Free Software Foundation, either version 3 of the +:: License, or (at your option) any later version. +:: +:: mmSolver is distributed in the hope that it will be useful, +:: but WITHOUT ANY WARRANTY; without even the implied warranty of +:: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +:: GNU Lesser General Public License for more details. +:: +:: You should have received a copy of the GNU Lesser General Public License +:: along with mmSolver. If not, see . +:: --------------------------------------------------------------------- +:: +:: Deactivates the Python development environment for mmSolver maya 2023. + +SET PROJECT_ROOT=%CD% + +SET MAYA_VERSION=2024 + +SET PYTHON_VIRTUAL_ENV_DIR_NAME=python_venv_windows64_maya%MAYA_VERSION% +CALL %PROJECT_ROOT%\scripts\internal\python_venv_deactivate.bat From a6a8ca98a4d44fea49155ccaea15e7420852f04b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:48:31 +1000 Subject: [PATCH 191/295] Do not check formatting in GitHub linting action --- .github/workflows/lint_code.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/lint_code.yml b/.github/workflows/lint_code.yml index 3a7ce5c8f..6cdff8809 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/lint_code.yml @@ -25,10 +25,6 @@ jobs: run: | ./scripts/python_linter_run_flake8_errors_only.bash - - name: Check Python Formatting - run: | - ./scripts/python_formatter_run_black_check.bash - - name: Install Clang-Format run: | sudo apt-get install -y clang-format From 5c28ec14480985c964600eebbd157430144a8f63 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:52:01 +1000 Subject: [PATCH 192/295] Change code linting to use Ruff Ruff is faster and easier to configure. Also removes 'black' for formatting because I don't care so much about formatting issues - they can be fixed when they are found. --- .github/workflows/lint_code.yml | 4 +- pyproject.toml | 58 ++++++++++++++++++- .../requirements-github-lint-code.txt | 7 +-- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lint_code.yml b/.github/workflows/lint_code.yml index 6cdff8809..cc8b0262d 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/lint_code.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7"] + python-version: ["3.9"] steps: - uses: actions/checkout@v3 @@ -23,7 +23,7 @@ jobs: - name: Analysing the Python code with flake8 (Hard Error) run: | - ./scripts/python_linter_run_flake8_errors_only.bash + ./scripts/python_linter_run_ruff.bash - name: Install Clang-Format run: | diff --git a/pyproject.toml b/pyproject.toml index 50382e4eb..eae962bc8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,8 +20,62 @@ extend-exclude = ''' # Same as Black. line-length = 88 -# Assume Python 3.6+ -target-version = "py36" +# Assume Python 3.7+, because that's the minimum supported version by +# Ruff. +target-version = "py37" + +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + + # Custom directories for mmSolver. + "mltools", + "delaunator.py" +] + +[tool.ruff.lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = [ + "E4", + "E7", + "E9", + "F", + # "B" # "Bugbear" lints. Enable at a later date. +] + +ignore = [ + "E402", # Avoid line-length complaints. + "E501", # Avoid module import complaints. + "F401", # Unused module. + "F841" # Local variable `start_frame` is assigned to but never used +] [tool.ruff.mccabe] # Unlike Flake8, default to a complexity level of 10. diff --git a/share/python_requirements/requirements-github-lint-code.txt b/share/python_requirements/requirements-github-lint-code.txt index cf122dd91..09b143295 100644 --- a/share/python_requirements/requirements-github-lint-code.txt +++ b/share/python_requirements/requirements-github-lint-code.txt @@ -1,12 +1,11 @@ # .github/workflows/lint_code.yml # -# Expects Python 3.7.x +# Expects Python 3.7.x, because that's the minimum supported version +# by Ruff. # Required packages for linting code on GitHub. # # NOTE: No versions are specified to allow the package version resolver # to resolve correctly. -black -pylint -flake8 +ruff cpplint From d89019e822068176bbbc6d6093f9af76755e9676 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:52:33 +1000 Subject: [PATCH 193/295] Minor reformatting changes. --- python/mmSolver/tools/createimageplane/_lib/shader.py | 2 +- python/mmSolver/tools/imagecache/config_utils.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/shader.py b/python/mmSolver/tools/createimageplane/_lib/shader.py index 1a4c72525..eee23aecd 100644 --- a/python/mmSolver/tools/createimageplane/_lib/shader.py +++ b/python/mmSolver/tools/createimageplane/_lib/shader.py @@ -156,7 +156,7 @@ def create_network(name_shader, image_plane_tfm): ['outUV', 'uvCoord'], ['outUvFilterSize', 'uvFilterSize'], ] - for (src_attr, dst_attr) in conns: + for src_attr, dst_attr in conns: src = file_place2d + '.' + src_attr dst = file_node + '.' + dst_attr lib_utils.force_connect_attr(src, dst) diff --git a/python/mmSolver/tools/imagecache/config_utils.py b/python/mmSolver/tools/imagecache/config_utils.py index 769c789e8..7397101fa 100644 --- a/python/mmSolver/tools/imagecache/config_utils.py +++ b/python/mmSolver/tools/imagecache/config_utils.py @@ -36,4 +36,3 @@ def convert_to_capacity_value(percent, total_bytes): ratio = percent / 100.0 size_bytes = int(total_bytes * ratio) return CapacityValue(size_bytes, percent) - From bf09dccff0cc0daaa3cf9b447014e2e5aca4fe43 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:56:04 +1000 Subject: [PATCH 194/295] Python Plug-in MarkerBundleShape - Removed bare exception. --- share/maya_python_plugins/MMMarkerBundleShape.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/share/maya_python_plugins/MMMarkerBundleShape.py b/share/maya_python_plugins/MMMarkerBundleShape.py index 654d73728..1328f6709 100644 --- a/share/maya_python_plugins/MMMarkerBundleShape.py +++ b/share/maya_python_plugins/MMMarkerBundleShape.py @@ -497,7 +497,7 @@ def initializePlugin(obj): MMMarkerBundleShape.initialize, om.MPxNode.kLocatorNode, MMMarkerBundleShape.drawDbClassification) - except: + except RuntimeError: sys.stderr.write("Failed to register node\n") raise try: @@ -505,7 +505,7 @@ def initializePlugin(obj): MMMarkerBundleShape.drawDbClassification, MMMarkerBundleShape.drawRegistrantId, MarkerBundleShapeDrawOverride.creator) - except: + except RuntimeError: sys.stderr.write("Failed to register override\n") raise @@ -514,15 +514,13 @@ def uninitializePlugin(obj): plugin = om.MFnPlugin(obj) try: plugin.deregisterNode(MMMarkerBundleShape.id) - except: + except RuntimeError: sys.stderr.write("Failed to deregister node\n") pass try: omr.MDrawRegistry.deregisterDrawOverrideCreator( MMMarkerBundleShape.drawDbClassification, MMMarkerBundleShape.drawRegistrantId) - except: + except RuntimeError: sys.stderr.write("Failed to deregister override\n") pass - - From 5ee623c183deeacb8c6e2f07ff3c46bf48641757 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:56:33 +1000 Subject: [PATCH 195/295] mmImagePlane v2 - Don't assign lambda to variable. --- .../mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index a35e43b8a..1f3158be3 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -483,9 +483,9 @@ def _set_attribute_editor_color_space(node_attr, control_name, value): def _maybe_make_menu_item(button_name, label, node_attr, value): if value and len(value) > 0: - func = lambda x: _set_attribute_editor_color_space( - node_attr, button_name, value - ) + def func(x): + _set_attribute_editor_color_space(node_attr, button_name, value) + maya.cmds.menuItem(label=label, command=func) return From 041d2c609d08b4708fd0c2ca7ac5b769603a55c4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:56:53 +1000 Subject: [PATCH 196/295] Ruff - Update old configuration type --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index eae962bc8..837cfb2bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,6 +77,6 @@ ignore = [ "F841" # Local variable `start_frame` is assigned to but never used ] -[tool.ruff.mccabe] +[tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 From e7fb61325bbe86b4823cef99aaf18bb4d42109f1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 17:59:02 +1000 Subject: [PATCH 197/295] Make all bash scripts executable. --- scripts/build_mmSolver_linux_maya2016.bash | 0 scripts/build_mmSolver_linux_maya2017.bash | 0 scripts/build_mmSolver_linux_maya2018.bash | 0 scripts/build_mmSolver_linux_maya2019.bash | 0 scripts/build_mmSolver_linux_maya2020.bash | 0 scripts/build_mmSolver_linux_maya2022.bash | 0 scripts/build_mmSolver_linux_maya2023.bash | 0 scripts/build_mmSolver_linux_maya2024.bash | 0 scripts/build_mmSolver_linux_maya2025.bash | 0 scripts/build_mmSolver_mac_maya2018.bash | 0 scripts/build_mmSolver_mac_maya2019.bash | 0 scripts/python_formatter_run_black_edit.bash | 0 scripts/python_linter_run_cpplint.bash | 0 scripts/python_linter_run_flake8.bash | 0 scripts/python_linter_run_pylint.bash | 0 scripts/python_linter_run_ruff.bash | 0 scripts/python_linter_run_ruff_fix.bash | 0 scripts/python_venv_activate_maya2016.bash | 0 scripts/python_venv_activate_maya2017.bash | 0 scripts/python_venv_activate_maya2018.bash | 0 scripts/python_venv_activate_maya2019.bash | 0 scripts/python_venv_activate_maya2020.bash | 0 scripts/python_venv_activate_maya2022.bash | 0 scripts/python_venv_activate_maya2023.bash | 0 scripts/python_venv_activate_maya2024.bash | 0 25 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2016.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2017.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2018.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2019.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2020.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2022.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2023.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2024.bash mode change 100644 => 100755 scripts/build_mmSolver_linux_maya2025.bash mode change 100644 => 100755 scripts/build_mmSolver_mac_maya2018.bash mode change 100644 => 100755 scripts/build_mmSolver_mac_maya2019.bash mode change 100644 => 100755 scripts/python_formatter_run_black_edit.bash mode change 100644 => 100755 scripts/python_linter_run_cpplint.bash mode change 100644 => 100755 scripts/python_linter_run_flake8.bash mode change 100644 => 100755 scripts/python_linter_run_pylint.bash mode change 100644 => 100755 scripts/python_linter_run_ruff.bash mode change 100644 => 100755 scripts/python_linter_run_ruff_fix.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2016.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2017.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2018.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2019.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2020.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2022.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2023.bash mode change 100644 => 100755 scripts/python_venv_activate_maya2024.bash diff --git a/scripts/build_mmSolver_linux_maya2016.bash b/scripts/build_mmSolver_linux_maya2016.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2017.bash b/scripts/build_mmSolver_linux_maya2017.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2018.bash b/scripts/build_mmSolver_linux_maya2018.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2019.bash b/scripts/build_mmSolver_linux_maya2019.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2020.bash b/scripts/build_mmSolver_linux_maya2020.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2022.bash b/scripts/build_mmSolver_linux_maya2022.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2023.bash b/scripts/build_mmSolver_linux_maya2023.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2024.bash b/scripts/build_mmSolver_linux_maya2024.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_linux_maya2025.bash b/scripts/build_mmSolver_linux_maya2025.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_mac_maya2018.bash b/scripts/build_mmSolver_mac_maya2018.bash old mode 100644 new mode 100755 diff --git a/scripts/build_mmSolver_mac_maya2019.bash b/scripts/build_mmSolver_mac_maya2019.bash old mode 100644 new mode 100755 diff --git a/scripts/python_formatter_run_black_edit.bash b/scripts/python_formatter_run_black_edit.bash old mode 100644 new mode 100755 diff --git a/scripts/python_linter_run_cpplint.bash b/scripts/python_linter_run_cpplint.bash old mode 100644 new mode 100755 diff --git a/scripts/python_linter_run_flake8.bash b/scripts/python_linter_run_flake8.bash old mode 100644 new mode 100755 diff --git a/scripts/python_linter_run_pylint.bash b/scripts/python_linter_run_pylint.bash old mode 100644 new mode 100755 diff --git a/scripts/python_linter_run_ruff.bash b/scripts/python_linter_run_ruff.bash old mode 100644 new mode 100755 diff --git a/scripts/python_linter_run_ruff_fix.bash b/scripts/python_linter_run_ruff_fix.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2016.bash b/scripts/python_venv_activate_maya2016.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2017.bash b/scripts/python_venv_activate_maya2017.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2018.bash b/scripts/python_venv_activate_maya2018.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2019.bash b/scripts/python_venv_activate_maya2019.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2020.bash b/scripts/python_venv_activate_maya2020.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2022.bash b/scripts/python_venv_activate_maya2022.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2023.bash b/scripts/python_venv_activate_maya2023.bash old mode 100644 new mode 100755 diff --git a/scripts/python_venv_activate_maya2024.bash b/scripts/python_venv_activate_maya2024.bash old mode 100644 new mode 100755 From f2200bdad7922d38b96688a3cddc4ca9d5d7e370 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 18:00:00 +1000 Subject: [PATCH 198/295] GitHub action - update lint name We are not using flake8 anymore, we are using ruff. --- .github/workflows/lint_code.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint_code.yml b/.github/workflows/lint_code.yml index cc8b0262d..768ec35e3 100644 --- a/.github/workflows/lint_code.yml +++ b/.github/workflows/lint_code.yml @@ -21,7 +21,7 @@ jobs: python -m pip install --upgrade pip pip install -r share/python_requirements/requirements-github-lint-code.txt - - name: Analysing the Python code with flake8 (Hard Error) + - name: Analysing the Python code with ruff (Hard Error) run: | ./scripts/python_linter_run_ruff.bash From f73ca77e628418a98e418d84e9f36376a2e46394 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 18:02:08 +1000 Subject: [PATCH 199/295] Fix C++ formatting. --- include/mmSolver/nodeTypeIds.h | 12 ++++++++---- src/mmSolver/image/PixelDataType.h | 6 +++--- src/mmSolver/utilities/memory_utils.h | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index 6aeca05ac..e3afcd769 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -131,19 +131,23 @@ #define MM_IMAGE_PLANE_SHAPE_TYPE_ID 0x0012F187 #define MM_IMAGE_PLANE_SHAPE_TYPE_NAME "mmImagePlaneShape" -#define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane/v1" +#define MM_IMAGE_PLANE_SHAPE_DRAW_CLASSIFY \ + "drawdb/geometry/mmSolver/imagePlane/v1" #define MM_IMAGE_PLANE_SHAPE_DRAW_REGISTRANT_ID "mmImagePlaneShape" #define MM_IMAGE_PLANE_SHAPE_SELECTION_TYPE_NAME "mmImagePlaneShapeSelection" -#define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION "drawdb/geometry/mmSolver/imagePlane/v1" +#define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION \ + "drawdb/geometry/mmSolver/imagePlane/v1" #define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_NAME "mmImagePlaneDisplayFilter" #define MM_IMAGE_PLANE_SHAPE_DISPLAY_FILTER_LABEL "MM ImagePlane (legacy)" #define MM_IMAGE_PLANE_SHAPE_2_TYPE_ID 0x0012F18F #define MM_IMAGE_PLANE_SHAPE_2_TYPE_NAME "mmImagePlaneShape2" -#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY "drawdb/geometry/mmSolver/imagePlane/v2" +#define MM_IMAGE_PLANE_SHAPE_2_DRAW_CLASSIFY \ + "drawdb/geometry/mmSolver/imagePlane/v2" #define MM_IMAGE_PLANE_SHAPE_2_DRAW_REGISTRANT_ID "mmImagePlaneShape2" #define MM_IMAGE_PLANE_SHAPE_2_SELECTION_TYPE_NAME "mmImagePlaneShape2Selection" -#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION "drawdb/geometry/mmSolver/imagePlane/v2" +#define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_DRAW_DB_CLASSIFICATION \ + "drawdb/geometry/mmSolver/imagePlane/v2" #define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_NAME "mmImagePlane2DisplayFilter" #define MM_IMAGE_PLANE_SHAPE_2_DISPLAY_FILTER_LABEL "MM ImagePlane" diff --git a/src/mmSolver/image/PixelDataType.h b/src/mmSolver/image/PixelDataType.h index 6fba8d6b2..995d624df 100644 --- a/src/mmSolver/image/PixelDataType.h +++ b/src/mmSolver/image/PixelDataType.h @@ -66,9 +66,9 @@ static uint8_t convert_pixel_data_type_to_bytes_per_channel( bytes_per_channel = 4; } else { bytes_per_channel = 0; - MMSOLVER_MAYA_ERR("mmsolver::image::convert_pixel_data_type_to_bytes_per_channel: " - << "Invalid pixel type is " - << static_cast(pixel_data_type)); + MMSOLVER_MAYA_ERR( + "mmsolver::image::convert_pixel_data_type_to_bytes_per_channel: " + << "Invalid pixel type is " << static_cast(pixel_data_type)); } return bytes_per_channel; } diff --git a/src/mmSolver/utilities/memory_utils.h b/src/mmSolver/utilities/memory_utils.h index 061c8ba78..313c09ef4 100644 --- a/src/mmSolver/utilities/memory_utils.h +++ b/src/mmSolver/utilities/memory_utils.h @@ -24,8 +24,8 @@ #define MEMORY_UTILS_H // STL -#include #include +#include namespace mmmemory { From 138f9a2e666b13487323e5906ea0699277d0c8d2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 22:47:01 +1000 Subject: [PATCH 200/295] Fix mesh tools shelf button icon. --- share/config/functions.json | 2 +- .../{mesh_bunny_32x32.png => meshTools_32x32.png} | Bin 2 files changed, 1 insertion(+), 1 deletion(-) rename share/icons/{mesh_bunny_32x32.png => meshTools_32x32.png} (100%) diff --git a/share/config/functions.json b/share/config/functions.json index 41d0ed9bb..a12fbc942 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -493,7 +493,7 @@ "mesh_tools": { "name": "Mesh Tools", "name_shelf": "", - "icon_shelf": "mesh_bunny.png", + "icon_shelf": "meshTools_32x32.png", "tooltip": "Mesh Tools", "command": ["pass"] }, diff --git a/share/icons/mesh_bunny_32x32.png b/share/icons/meshTools_32x32.png similarity index 100% rename from share/icons/mesh_bunny_32x32.png rename to share/icons/meshTools_32x32.png From 217afb1d7f7b3a2914da1ea912221f69b8d837b9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 23:31:15 +1000 Subject: [PATCH 201/295] Mesh From Locators - Add menus and refactor lib. --- .../tools/meshfromlocators/constant.py | 21 ++- python/mmSolver/tools/meshfromlocators/lib.py | 125 ++++++++++++------ .../tools/meshfromlocators/ui/__init__.py | 17 +++ .../ui/meshfromlocators_layout.py | 66 ++++++++- .../ui/meshfromlocators_window.py | 31 ++++- 5 files changed, 209 insertions(+), 51 deletions(-) diff --git a/python/mmSolver/tools/meshfromlocators/constant.py b/python/mmSolver/tools/meshfromlocators/constant.py index a1cf0b9c7..27e6cff41 100644 --- a/python/mmSolver/tools/meshfromlocators/constant.py +++ b/python/mmSolver/tools/meshfromlocators/constant.py @@ -16,5 +16,24 @@ # along with mmSolver. If not, see . # -MESH_NAME = 'meshFromLocators' WINDOW_TITLE = 'Mesh From Locators' + +# We must have at least 3 points to make a mesh. +MINIMUM_NUMBER_OF_POINTS = 3 + +# The different types of meshes that can be created. +MESH_TYPE_FULL_MESH_VALUE = 'full_mesh' +MESH_TYPE_BORDER_MESH_VALUE = 'border_mesh' +MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE = 'border_edge_strip_mesh' +MESH_TYPE_VALUES = [ + MESH_TYPE_FULL_MESH_VALUE, + MESH_TYPE_BORDER_MESH_VALUE, + MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE, +] + +# Default values. +DEFAULT_MESH_NAME = 'meshFromLocators' +DEFAULT_STRIP_WIDTH = 1.0 + +# Config value keys. +CONFIG_STRIP_WIDTH_KEY = 'mmSolver_meshfromlocators_stripWidth' diff --git a/python/mmSolver/tools/meshfromlocators/lib.py b/python/mmSolver/tools/meshfromlocators/lib.py index 4d9349406..6ac0ab9e4 100644 --- a/python/mmSolver/tools/meshfromlocators/lib.py +++ b/python/mmSolver/tools/meshfromlocators/lib.py @@ -16,6 +16,8 @@ # along with mmSolver. If not, see . # +import collections + import maya.api.OpenMaya as om import maya.api.OpenMayaUI as omui import maya.cmds @@ -24,92 +26,127 @@ from mmSolver.tools.meshfromlocators.delaunator import Delaunator import mmSolver.tools.meshfromlocators.constant as const + LOG = mmSolver.logger.get_logger() -def delaunator_indices(locators): +MeshData = collections.namedtuple( + 'MeshData', + ['world_positions', 'full_mesh_indices', 'border_mesh_indices'], +) + + +def _delaunator_indices(transform_nodes, viewport): """ - Returns delaunay indices in the proper order to create mesh. + Uses transforms and active camera view to compute delaunay + indices in the proper order to create mesh. + + NOTE: This function uses the active view camera! This is hidden + + :param transform_nodes: + :type transform_nodes: list - :param locators: locatorlist - :type locators: list """ - mesh_data = { - "world_positions": [], - "vew_positions": [], - "full_mesh_indices": [], - "border_mesh_indices": [], - } - for locator_name in locators: - locator_pos = maya.cmds.xform( - locator_name, query=True, worldSpace=True, translation=True + assert isinstance(viewport, omui.M3dView) + assert len(transform_nodes) > const.MINIMUM_NUMBER_OF_POINTS + + world_positions = [] + view_positions = [] + for transform_node in transform_nodes: + transform_pos = maya.cmds.xform( + transform_node, query=True, worldSpace=True, translation=True + ) + transform_mpoint = om.MPoint( + transform_pos[0], transform_pos[1], transform_pos[2] ) - locator_mpoint = om.MPoint(locator_pos[0], locator_pos[1], locator_pos[2]) - mesh_data["world_positions"].append(locator_mpoint) - # Convert world position to view space - view = omui.M3dView.active3dView() - view_pos = view.worldToView(locator_mpoint) + world_positions.append(transform_mpoint) + + # Convert world position to view space. + view_pos = viewport.worldToView(transform_mpoint) view_x, view_y = view_pos[0], view_pos[1] - mesh_data["vew_positions"].append([view_x, view_y]) + view_positions.append([view_x, view_y]) - # Indices - mesh_indices = Delaunator(mesh_data["vew_positions"]).triangles + # Compute once, and reuse the result. + computation = Delaunator(view_positions) + mesh_indices = computation.triangles # Reverse order mesh_indices_reverse = [] for i in range(0, len(mesh_indices), 3): chunk = mesh_indices[i : i + 3] mesh_indices_reverse.extend(chunk[::-1]) - mesh_data["full_mesh_indices"] = mesh_indices_reverse + full_mesh_indices = mesh_indices_reverse # Hull indices - hull = Delaunator(mesh_data["vew_positions"]).hull - hull.reverse() - mesh_data["border_mesh_indices"] = hull + border_mesh_indices = computation.hull + border_mesh_indices.reverse() + + assert len(world_positions) == len(view_positions) + + mesh_data = MeshData( + world_positions=world_positions, + full_mesh_indices=full_mesh_indices, + border_mesh_indices=border_mesh_indices, + ) return mesh_data -def create_mesh_from_locators(mesh_type=None, offset_value=1.0): +def create_mesh_from_transform_nodes( + mesh_type, transform_nodes, offset_value=None, mesh_name=None +): """ - Creates mesh from locators. + Creates mesh from transform nodes. - :param mesh_type: 'fullMesh' 'borderMesh' 'borderEdgeStripMesh'. - :type mesh_type: str + :param mesh_type: The type of mesh to create. + :type mesh_type: mmSolver.tools.meshfromlocators.constant.MESH_TYPE_*_VALUE. - :param offset_value: An offset value for borderEdgeStripMesh + :param transform_nodes: List of transform nodes to get 3D positions from. + :type transform_nodes: [str, ..] + + :param offset_value: An offset value for MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE. :type offset_value: float - """ - locators = maya.cmds.ls(selection=True, transforms=True) or [] - if len(locators) < 3: - LOG.warn('at least three locators must be selected.') - return - mesh_data = delaunator_indices(locators) - positions = mesh_data["world_positions"] + :param mesh_name: The name of the mesh node created. + :type mesh_name: str + """ + if offset_value is None: + offset_value = const.DEFAULT_STRIP_WIDTH + if mesh_name is None: + mesh_name = const.DEFAULT_MESH_NAME + assert mesh_type in const.MESH_TYPE_VALUES + assert isinstance(mesh_name, str) + assert len(mesh_name) > 0 + assert isinstance(offset_value, float) + assert len(transform_nodes) > const.MINIMUM_NUMBER_OF_POINTS + + active_viewport = omui.M3dView.active3dView() + mesh_data = _delaunator_indices(transform_nodes, active_viewport) + positions = mesh_data.world_positions # Set face count and indices - if mesh_type == 'fullMesh': - indices = mesh_data["full_mesh_indices"] + if mesh_type == const.MESH_TYPE_FULL_MESH_VALUE: + indices = mesh_data.full_mesh_indices face_counts = [3] * (len(indices) // 3) else: - indices = mesh_data["border_mesh_indices"] + indices = mesh_data.border_mesh_indices face_counts = [len(indices)] - # Create the mesh + # Create the mesh. mesh_fn = om.MFnMesh() mesh = mesh_fn.create(positions, face_counts, indices) - # Set mesh name + # Set mesh name. dag_node = om.MFnDagNode(mesh) - dag_node.setName(const.MESH_NAME) + dag_node.setName(mesh_name) mesh_name = dag_node.name() # Set lambert maya.cmds.sets(mesh_name, edit=True, forceElement='initialShadingGroup') # Create border edge strip mesh - if mesh_type == 'borderEdgeStripMesh': + if mesh_type == const.MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE: maya.cmds.polyExtrudeFacet(mesh_name, offset=offset_value) face_0 = '{}.f[0]'.format(mesh_name) face_1 = '{}.f[1]'.format(mesh_name) maya.cmds.delete(face_0, face_1) + return diff --git a/python/mmSolver/tools/meshfromlocators/ui/__init__.py b/python/mmSolver/tools/meshfromlocators/ui/__init__.py index 64823a0ca..0a51d7170 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/__init__.py +++ b/python/mmSolver/tools/meshfromlocators/ui/__init__.py @@ -1,3 +1,20 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# """ Mesh From Points user interface. """ diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py index c544c1a87..aca59eced 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py @@ -23,23 +23,43 @@ from __future__ import division from __future__ import print_function +import maya.cmds + import mmSolver.ui.qtpyutils as qtpyutils qtpyutils.override_binding_order() import mmSolver.ui.Qt.QtWidgets as QtWidgets +import mmSolver.logger +import mmSolver.utils.configmaya as configmaya import mmSolver.tools.meshfromlocators.ui.ui_meshfromlocators_layout as ui_meshfromlocators_layout import mmSolver.tools.meshfromlocators.lib as lib +import mmSolver.tools.meshfromlocators.constant as const + + +LOG = mmSolver.logger.get_logger() + + +def _get_selection(): + transform_nodes = maya.cmds.ls(selection=True, transforms=True) or [] + if len(transform_nodes) < 3: + LOG.warn('Please select least three transform nodes.') + return None + return transform_nodes class MeshFromLocatorsLayout(QtWidgets.QWidget, ui_meshfromlocators_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): super(MeshFromLocatorsLayout, self).__init__(*args, **kwargs) self.setupUi(self) - self.create_connections() + # TODO: Should the mesh node name be exposed to the user to set? + + # Populate the UI with data + self.populateUi() + def create_connections(self): self.createFullMeshBtn.clicked.connect(self.full_mesh_btn_clicked) self.createBorderMeshBtn.clicked.connect(self.border_mesh_btn_clicked) @@ -47,12 +67,50 @@ def create_connections(self): self.border_edge_strip_mesh_btn_clicked ) + def reset_options(self): + name = const.CONFIG_STRIP_WIDTH_KEY + value = const.DEFAULT_STRIP_WIDTH + configmaya.set_scene_option(name, value) + LOG.debug('key=%r value=%r', name, value) + + self.populateUi() + return + + def populateUi(self): + """ + Update the UI for the first time the class is created. + """ + name = const.CONFIG_STRIP_WIDTH_KEY + value = configmaya.get_scene_option(name, default=const.DEFAULT_STRIP_WIDTH) + LOG.debug('key=%r value=%r', name, value) + self.stripWidthSpinBox.setValue(value) + def full_mesh_btn_clicked(self): - lib.create_mesh_from_locators('fullMesh') + transform_nodes = _get_selection() + if transform_nodes is None: + return + + lib.create_mesh_from_transform_nodes( + const.MESH_TYPE_FULL_MESH_VALUE, transform_nodes + ) def border_mesh_btn_clicked(self): - lib.create_mesh_from_locators('borderMesh') + transform_nodes = _get_selection() + if transform_nodes is None: + return + + lib.create_mesh_from_transform_nodes( + const.MESH_TYPE_BORDER_MESH_VALUE, transform_nodes + ) def border_edge_strip_mesh_btn_clicked(self): + transform_nodes = _get_selection() + if transform_nodes is None: + return + offset = self.stripWidthSpinBox.value() - lib.create_mesh_from_locators('borderEdgeStripMesh', offset) + lib.create_mesh_from_transform_nodes( + const.MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE, + transform_nodes, + offset_value=offset, + ) diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py index af536d4a0..be4d0fe69 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py +++ b/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py @@ -36,10 +36,10 @@ import mmSolver.ui.Qt.QtCore as QtCore import mmSolver.ui.Qt.QtWidgets as QtWidgets -import maya.cmds - import mmSolver.logger import mmSolver.ui.uiutils as uiutils +import mmSolver.ui.commonmenus as commonmenus +import mmSolver.ui.helputils as helputils import mmSolver.tools.meshfromlocators.ui.meshfromlocators_layout as meshfromlocators_layout import mmSolver.tools.meshfromlocators.constant as const @@ -47,6 +47,13 @@ baseModule, BaseWindow = uiutils.getBaseWindow() +def _open_help(): + src = helputils.get_help_source() + page = 'tools_meshtools.html#mesh-from-locators' + helputils.open_help_in_browser(page=page, help_source=src) + return + + class MeshFromLocatorsWindow(BaseWindow): name = 'MeshFromLocatorsWindow' @@ -57,10 +64,30 @@ def __init__(self, parent=None, name=None): self.setWindowTitle(const.WINDOW_TITLE) self.setWindowFlags(QtCore.Qt.Tool) + # Hide irrelevant stuff self.baseHideStandardButtons() self.baseHideProgressBar() + self.add_menus(self.menubar) + self.menubar.show() + + def add_menus(self, menubar): + edit_menu = QtWidgets.QMenu('Edit', menubar) + commonmenus.create_edit_menu_items( + edit_menu, reset_settings_func=self.reset_options + ) + menubar.addMenu(edit_menu) + + help_menu = QtWidgets.QMenu('Help', menubar) + commonmenus.create_help_menu_items(help_menu, tool_help_func=_open_help) + menubar.addMenu(help_menu) + + def reset_options(self): + form = self.getSubForm() + form.reset_options() + return + def main(show=True, auto_raise=True, delete=False): """ From 36a3594ee66fb5d41999e29e783374271992c990 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 23:32:11 +1000 Subject: [PATCH 202/295] Mesh From Locators - Change tool name in menus. The '...' indicates the tool is a UI that will open. --- share/config/functions.json | 6 +++--- share/config/menu.json | 2 +- share/config/shelf.json | 2 +- share/config/shelf_minimal.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/share/config/functions.json b/share/config/functions.json index a12fbc942..12914303c 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -651,10 +651,10 @@ "mmSolver.tools.undoredoscene.tool.main_redo();" ] }, - "mesh_from_locators": { - "name": "Mesh From Locators", + "mesh_from_locators_ui": { + "name": "Mesh From Locators...", "name_shelf": "MeshFrLoc", - "tooltip": "Creates mesh from locators", + "tooltip": "Open Mesh from Locators UI, to create triangulated meshes.", "command": [ "import mmSolver.tools.meshfromlocators.tool;", "mmSolver.tools.meshfromlocators.tool.main();" diff --git a/share/config/menu.json b/share/config/menu.json index c327d1606..194cdaf54 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -105,7 +105,7 @@ "zdepth_tools/---", "zdepth_tools/screen_z_transform_bake", "mesh_tools/---Create", - "mesh_tools/mesh_from_locators", + "mesh_tools/mesh_from_locators_ui", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index b067553ee..cb77d9e2c 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -102,7 +102,7 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", - "mesh_tools/mesh_tools_popup/mesh_from_locators", + "mesh_tools/mesh_tools_popup/mesh_from_locators_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index b1668121d..6e8f68c7f 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -113,7 +113,7 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", - "mesh_tools/mesh_tools_popup/mesh_from_locators", + "mesh_tools/mesh_tools_popup/mesh_from_locators_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 28b7291cea32a46b65c9705518ce51674e053e1b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 23:33:19 +1000 Subject: [PATCH 203/295] Do not format for Python 3.11 and 3.12, yet. If the installed 'black' version is not compatible, you will get an error giving versions that are too new. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 837cfb2bc..53bf1194f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.black] line-length = 88 # The black default is a good compromise. skip-string-normalization = 1 -target-version = ['py36', 'py37', 'py38', 'py39', 'py310', 'py311', 'py312'] +target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] include = '\.py$' extend-exclude = ''' /( From 39d6e4341255ccec346aa7034095cd256b2f54ce Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 16 Sep 2024 23:33:53 +1000 Subject: [PATCH 204/295] Change Project version to v0.5.0 We are getting ready for a full release soon. --- CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ef939e89..87a7b6a8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,8 +61,7 @@ project(mayaMatchMoveSolver) set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 5) set(PROJECT_VERSION_PATCH 0) -set(PROJECT_VERSION_TWEAK beta4) -set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${PROJECT_VERSION_TWEAK}") +set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "Bundle Adjustment solver for MatchMove tasks in Autodesk Maya.") set(PROJECT_AUTHOR "David Cattermole and others (see AUTHORS.txt file)") From ef80fa56db9dcdc9d76c45285fed2b9ff6606769 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 00:05:06 +1000 Subject: [PATCH 205/295] Mesh from Locators - Rename to Mesh From Points Because this tool doesn't use locators, it uses 3D points. In the future we could extend this tool to use 3D particles, or selected vertices for example - anything that gives us a 3D point. --- pylintrc | 2 +- python/CMakeLists.txt | 6 ++--- .../__init__.py | 2 +- .../constant.py | 6 ++--- .../delaunator.py | 0 .../lib.py | 6 ++--- .../tool.py | 6 ++--- .../ui/__init__.py | 0 .../ui/meshfrompoints_layout.py} | 12 ++++----- .../ui/meshfrompoints_layout.ui} | 0 .../ui/meshfrompoints_window.py} | 26 +++++++++---------- share/config/functions.json | 12 ++++----- share/config/menu.json | 2 +- share/config/shelf.json | 2 +- share/config/shelf_minimal.json | 2 +- 15 files changed, 42 insertions(+), 42 deletions(-) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/__init__.py (96%) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/constant.py (89%) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/delaunator.py (100%) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/lib.py (95%) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/tool.py (86%) rename python/mmSolver/tools/{meshfromlocators => meshfrompoints}/ui/__init__.py (100%) rename python/mmSolver/tools/{meshfromlocators/ui/meshfromlocators_layout.py => meshfrompoints/ui/meshfrompoints_layout.py} (88%) rename python/mmSolver/tools/{meshfromlocators/ui/meshfromlocators_layout.ui => meshfrompoints/ui/meshfrompoints_layout.ui} (100%) rename python/mmSolver/tools/{meshfromlocators/ui/meshfromlocators_window.py => meshfrompoints/ui/meshfrompoints_window.py} (79%) diff --git a/pylintrc b/pylintrc index 66f589c82..7e645132b 100644 --- a/pylintrc +++ b/pylintrc @@ -14,7 +14,7 @@ ignore=CVS,.git # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-paths=^python/mmSolver/tools/mltools/*.py$, - ^python/mmSolver/tools/meshfromlocators/delaunator.py$ + ^python/mmSolver/tools/meshfrompoints/delaunator.py$ # Pickle collected data for later comparisons. persistent=yes diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 7e61a0876..fa38110ad 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -194,9 +194,9 @@ if (MMSOLVER_BUILD_QT_UI) ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/surfacecluster/ui/ui_surfacecluster_layout.py ) - compile_qt_ui_to_python_file("meshfromlocators" - ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui - ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/meshfromlocators/ui/ui_meshfromlocators_layout.py + compile_qt_ui_to_python_file("meshfrompoints" + ${CMAKE_CURRENT_SOURCE_DIR}/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.ui + ${CMAKE_CURRENT_BINARY_DIR}/mmSolver/tools/meshfrompoints/ui/ui_meshfrompoints_layout.py ) compile_qt_ui_to_python_file("imagecacheprefs" diff --git a/python/mmSolver/tools/meshfromlocators/__init__.py b/python/mmSolver/tools/meshfrompoints/__init__.py similarity index 96% rename from python/mmSolver/tools/meshfromlocators/__init__.py rename to python/mmSolver/tools/meshfrompoints/__init__.py index b9c3a7ad8..541b768c3 100644 --- a/python/mmSolver/tools/meshfromlocators/__init__.py +++ b/python/mmSolver/tools/meshfrompoints/__init__.py @@ -16,5 +16,5 @@ # along with mmSolver. If not, see . # """ -Mesh From Locators tool. +Mesh From Points tool. """ diff --git a/python/mmSolver/tools/meshfromlocators/constant.py b/python/mmSolver/tools/meshfrompoints/constant.py similarity index 89% rename from python/mmSolver/tools/meshfromlocators/constant.py rename to python/mmSolver/tools/meshfrompoints/constant.py index 27e6cff41..c200ebe15 100644 --- a/python/mmSolver/tools/meshfromlocators/constant.py +++ b/python/mmSolver/tools/meshfrompoints/constant.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # -WINDOW_TITLE = 'Mesh From Locators' +WINDOW_TITLE = 'Mesh From Points' # We must have at least 3 points to make a mesh. MINIMUM_NUMBER_OF_POINTS = 3 @@ -32,8 +32,8 @@ ] # Default values. -DEFAULT_MESH_NAME = 'meshFromLocators' +DEFAULT_MESH_NAME = 'meshFromPoints' DEFAULT_STRIP_WIDTH = 1.0 # Config value keys. -CONFIG_STRIP_WIDTH_KEY = 'mmSolver_meshfromlocators_stripWidth' +CONFIG_STRIP_WIDTH_KEY = 'mmSolver_meshfrompoints_stripWidth' diff --git a/python/mmSolver/tools/meshfromlocators/delaunator.py b/python/mmSolver/tools/meshfrompoints/delaunator.py similarity index 100% rename from python/mmSolver/tools/meshfromlocators/delaunator.py rename to python/mmSolver/tools/meshfrompoints/delaunator.py diff --git a/python/mmSolver/tools/meshfromlocators/lib.py b/python/mmSolver/tools/meshfrompoints/lib.py similarity index 95% rename from python/mmSolver/tools/meshfromlocators/lib.py rename to python/mmSolver/tools/meshfrompoints/lib.py index 6ac0ab9e4..1b73ebb40 100644 --- a/python/mmSolver/tools/meshfromlocators/lib.py +++ b/python/mmSolver/tools/meshfrompoints/lib.py @@ -23,8 +23,8 @@ import maya.cmds import mmSolver.logger -from mmSolver.tools.meshfromlocators.delaunator import Delaunator -import mmSolver.tools.meshfromlocators.constant as const +from mmSolver.tools.meshfrompoints.delaunator import Delaunator +import mmSolver.tools.meshfrompoints.constant as const LOG = mmSolver.logger.get_logger() @@ -98,7 +98,7 @@ def create_mesh_from_transform_nodes( Creates mesh from transform nodes. :param mesh_type: The type of mesh to create. - :type mesh_type: mmSolver.tools.meshfromlocators.constant.MESH_TYPE_*_VALUE. + :type mesh_type: mmSolver.tools.meshfrompoints.constant.MESH_TYPE_*_VALUE. :param transform_nodes: List of transform nodes to get 3D positions from. :type transform_nodes: [str, ..] diff --git a/python/mmSolver/tools/meshfromlocators/tool.py b/python/mmSolver/tools/meshfrompoints/tool.py similarity index 86% rename from python/mmSolver/tools/meshfromlocators/tool.py rename to python/mmSolver/tools/meshfrompoints/tool.py index 67b33bf95..cdc0b4ac4 100644 --- a/python/mmSolver/tools/meshfromlocators/tool.py +++ b/python/mmSolver/tools/meshfrompoints/tool.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # """ -Mesh From Locators main. +Mesh From Points main. """ from __future__ import absolute_import @@ -30,8 +30,8 @@ def main(): """ - Open the 'Mesh From Locators' window. + Open the 'Mesh From Points' window. """ - import mmSolver.tools.meshfromlocators.ui.meshfromlocators_window as window + import mmSolver.tools.meshfrompoints.ui.meshfrompoints_window as window window.main() diff --git a/python/mmSolver/tools/meshfromlocators/ui/__init__.py b/python/mmSolver/tools/meshfrompoints/ui/__init__.py similarity index 100% rename from python/mmSolver/tools/meshfromlocators/ui/__init__.py rename to python/mmSolver/tools/meshfrompoints/ui/__init__.py diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py b/python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.py similarity index 88% rename from python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py rename to python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.py index aca59eced..2660de62c 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.py +++ b/python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # """ -The main component of the user interface for the mesh from locators window. +The main component of the user interface for the mesh from points window. """ from __future__ import absolute_import @@ -33,9 +33,9 @@ import mmSolver.logger import mmSolver.utils.configmaya as configmaya -import mmSolver.tools.meshfromlocators.ui.ui_meshfromlocators_layout as ui_meshfromlocators_layout -import mmSolver.tools.meshfromlocators.lib as lib -import mmSolver.tools.meshfromlocators.constant as const +import mmSolver.tools.meshfrompoints.ui.ui_meshfrompoints_layout as ui_meshfrompoints_layout +import mmSolver.tools.meshfrompoints.lib as lib +import mmSolver.tools.meshfrompoints.constant as const LOG = mmSolver.logger.get_logger() @@ -49,9 +49,9 @@ def _get_selection(): return transform_nodes -class MeshFromLocatorsLayout(QtWidgets.QWidget, ui_meshfromlocators_layout.Ui_Form): +class MeshFromPointsLayout(QtWidgets.QWidget, ui_meshfrompoints_layout.Ui_Form): def __init__(self, parent=None, *args, **kwargs): - super(MeshFromLocatorsLayout, self).__init__(*args, **kwargs) + super(MeshFromPointsLayout, self).__init__(*args, **kwargs) self.setupUi(self) self.create_connections() diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui b/python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.ui similarity index 100% rename from python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_layout.ui rename to python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_layout.ui diff --git a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py b/python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_window.py similarity index 79% rename from python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py rename to python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_window.py index be4d0fe69..7514c9a6d 100644 --- a/python/mmSolver/tools/meshfromlocators/ui/meshfromlocators_window.py +++ b/python/mmSolver/tools/meshfrompoints/ui/meshfrompoints_window.py @@ -16,12 +16,12 @@ # along with mmSolver. If not, see . # """ -Window for the Mesh From Locators tool. +Window for the Mesh From Points tool. Usage:: - import mmSolver.tools.meshfromlocators.ui.meshfromlocators_window as meshfromlocators_window - meshfromlocators_window.main() + import mmSolver.tools.meshfrompoints.ui.meshfrompoints_window as meshfrompoints_window + meshfrompoints_window.main() """ @@ -40,8 +40,8 @@ import mmSolver.ui.uiutils as uiutils import mmSolver.ui.commonmenus as commonmenus import mmSolver.ui.helputils as helputils -import mmSolver.tools.meshfromlocators.ui.meshfromlocators_layout as meshfromlocators_layout -import mmSolver.tools.meshfromlocators.constant as const +import mmSolver.tools.meshfrompoints.ui.meshfrompoints_layout as meshfrompoints_layout +import mmSolver.tools.meshfrompoints.constant as const LOG = mmSolver.logger.get_logger() baseModule, BaseWindow = uiutils.getBaseWindow() @@ -49,18 +49,18 @@ def _open_help(): src = helputils.get_help_source() - page = 'tools_meshtools.html#mesh-from-locators' + page = 'tools_meshtools.html#mesh-from-points' helputils.open_help_in_browser(page=page, help_source=src) return -class MeshFromLocatorsWindow(BaseWindow): - name = 'MeshFromLocatorsWindow' +class MeshFromPointsWindow(BaseWindow): + name = 'MeshFromPointsWindow' def __init__(self, parent=None, name=None): - super(MeshFromLocatorsWindow, self).__init__(parent, name=name) + super(MeshFromPointsWindow, self).__init__(parent, name=name) self.setupUi(self) - self.addSubForm(meshfromlocators_layout.MeshFromLocatorsLayout) + self.addSubForm(meshfrompoints_layout.MeshFromPointsLayout) self.setWindowTitle(const.WINDOW_TITLE) self.setWindowFlags(QtCore.Qt.Tool) @@ -91,7 +91,7 @@ def reset_options(self): def main(show=True, auto_raise=True, delete=False): """ - Open the Mesh From Locators UI. + Open the Mesh From Points UI. :param show: Show the UI. :type show: bool @@ -105,9 +105,9 @@ def main(show=True, auto_raise=True, delete=False): :returns: A new ui window, or None if the window cannot be opened. - :rtype: MeshFromLocatorsWindow or None + :rtype: MeshFromPointsWindow or None """ - win = MeshFromLocatorsWindow.open_window( + win = MeshFromPointsWindow.open_window( show=show, auto_raise=auto_raise, delete=delete ) return win diff --git a/share/config/functions.json b/share/config/functions.json index 12914303c..a67aba452 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -651,13 +651,13 @@ "mmSolver.tools.undoredoscene.tool.main_redo();" ] }, - "mesh_from_locators_ui": { - "name": "Mesh From Locators...", - "name_shelf": "MeshFrLoc", - "tooltip": "Open Mesh from Locators UI, to create triangulated meshes.", + "mesh_from_points_ui": { + "name": "Mesh From Points...", + "name_shelf": "MeshFrPts", + "tooltip": "Open Mesh from Points UI, to create triangulated meshes.", "command": [ - "import mmSolver.tools.meshfromlocators.tool;", - "mmSolver.tools.meshfromlocators.tool.main();" + "import mmSolver.tools.meshfrompoints.tool;", + "mmSolver.tools.meshfrompoints.tool.main();" ] }, "reparent_under_node": { diff --git a/share/config/menu.json b/share/config/menu.json index 194cdaf54..7cf9d8c7d 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -105,7 +105,7 @@ "zdepth_tools/---", "zdepth_tools/screen_z_transform_bake", "mesh_tools/---Create", - "mesh_tools/mesh_from_locators_ui", + "mesh_tools/mesh_from_points_ui", "general_tools/---Parenting", "general_tools/reparent_under_node2", "general_tools/unparent_to_world2", diff --git a/share/config/shelf.json b/share/config/shelf.json index cb77d9e2c..8efa30ca2 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -102,7 +102,7 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", - "mesh_tools/mesh_tools_popup/mesh_from_locators_ui", + "mesh_tools/mesh_tools_popup/mesh_from_points_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 6e8f68c7f..89a88c6b7 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -113,7 +113,7 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", - "mesh_tools/mesh_tools_popup/mesh_from_locators_ui", + "mesh_tools/mesh_tools_popup/mesh_from_points_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", "general_tools/gen_tools_popup/unparent_to_world2", From 375758ed5d9a2c3a41cec7f6bc0b753b561e39ad Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 00:07:45 +1000 Subject: [PATCH 206/295] MM Silhouette Renderer - Override color by default This is because when a user has wireframe on shaded enabled by default, they will not see the silhouettes by default. The default green color should be visible by default, and users can disable the override color option to use the colours from the underlying mesh. --- src/mmSolver/render/data/constants.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mmSolver/render/data/constants.h b/src/mmSolver/render/data/constants.h index 4131c7ebd..94bdb887c 100644 --- a/src/mmSolver/render/data/constants.h +++ b/src/mmSolver/render/data/constants.h @@ -131,7 +131,9 @@ const MString kAttrNameSilhouetteOperationNum = "operationNum"; // Silhouette Renderer Attribute Default Values const bool kSilhouetteEnableDefault = true; -const bool kSilhouetteOverrideColorDefault = false; +// We enable 'override color' by default, so that users with wireframe +// on shaded can see the obvious effect right away. +const bool kSilhouetteOverrideColorDefault = true; const float kSilhouetteDepthOffsetDefault = -1.0f; const float kSilhouetteWidthDefault = 2.0f; const float kSilhouetteColorDefault[] = {0.0f, 1.0f, 0.0f}; From 086b41dfe57b02f1b6ed20dc8ff8f7263ba7af10 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 00:14:36 +1000 Subject: [PATCH 207/295] Mesh from Points - Add documentation page stub. --- docs/source/tools.rst | 1 + docs/source/tools_meshtools.rst | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 docs/source/tools_meshtools.rst diff --git a/docs/source/tools.rst b/docs/source/tools.rst index ca1fb6f36..de69dd183 100644 --- a/docs/source/tools.rst +++ b/docs/source/tools.rst @@ -28,6 +28,7 @@ Pages dedicated to specific tools: tools_linetools tools_displaytools tools_zdepthtools + tools_meshtools tools_generaltools tools_inputoutput tools_hotkeys diff --git a/docs/source/tools_meshtools.rst b/docs/source/tools_meshtools.rst new file mode 100644 index 000000000..2db48ec74 --- /dev/null +++ b/docs/source/tools_meshtools.rst @@ -0,0 +1,18 @@ +Mesh Tools +========== + +*Write section description here.* + +.. _mesh-from-points-ref: + +Mesh From Points +---------------- + +*Write mesh from points tool description here* + +To open the window, use this Python command: + +.. code:: python + + import mmSolver.tools.meshfrompoints.tool as tool + tool.main() From 870751e89b88b9a5f7e45652aa80ebe058095286 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 00:22:52 +1000 Subject: [PATCH 208/295] Docs - Mesh From Points - Add placeholder image. This is not the actual image, just an example for how to add an image. --- docs/source/tools_meshtools.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/source/tools_meshtools.rst b/docs/source/tools_meshtools.rst index 2db48ec74..f943af5ce 100644 --- a/docs/source/tools_meshtools.rst +++ b/docs/source/tools_meshtools.rst @@ -10,6 +10,11 @@ Mesh From Points *Write mesh from points tool description here* +.. figure:: images/tools_mesh_from_points_window.png + :alt: Mesh from Points Window. + :align: right + :width: 100% + To open the window, use this Python command: .. code:: python From f646baf28116b52cf66afc8099895563d8f44e26 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 00:27:37 +1000 Subject: [PATCH 209/295] README.md - Add v0.4.9 release link. It's been missing for months :( --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f687e1cb4..d15a2748a 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ changes. | Releases | Description | | --------------------------------------------------------------------------------------- | -------------------------------------------------- | +| [v0.4.9](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.9) | Bug fix for Surface Cluster. | | [v0.4.8](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.8) | Add Create Rivet and Surface Cluster tools. | | [v0.4.7](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.7) | Bug fix for "Convert to Marker" tool. | | [v0.4.6](https://github.com/david-cattermole/mayaMatchMoveSolver/releases/tag/v0.4.6) | Bug fix for solver and minor features. | From 6c7f856c6d1d6d401111c91094ca98ef96eb741d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 17 Sep 2024 23:12:24 +1000 Subject: [PATCH 210/295] Python 2/3 - Remove use of 'str' for backwards compatibility. --- python/mmSolver/_api/sethelper.py | 2 +- python/mmSolver/_api/solveresult.py | 2 +- .../tools/createimageplane/_lib/main.py | 2 +- .../tools/createimageplane/_lib/mmimageplane.py | 12 ++++++------ .../createimageplane/_lib/mmimageplane_v2.py | 14 ++++++++------ python/mmSolver/tools/imagecache/_lib/erase.py | 17 +++++++++++------ .../tools/imagecache/_lib/imagecache_cmd.py | 9 +++++---- python/mmSolver/ui/nodes.py | 4 +++- python/mmSolver/ui/uiutils.py | 2 +- python/mmSolver/utils/tools.py | 3 ++- python/mmSolver/utils/viewport.py | 17 +++++++++-------- 11 files changed, 48 insertions(+), 36 deletions(-) diff --git a/python/mmSolver/_api/sethelper.py b/python/mmSolver/_api/sethelper.py index 336ea0e66..d4b94a3a7 100644 --- a/python/mmSolver/_api/sethelper.py +++ b/python/mmSolver/_api/sethelper.py @@ -116,7 +116,7 @@ def get_annotation(self): return ret def set_annotation(self, value): - assert isinstance(value, str) + assert isinstance(value, pycompat.TEXT_TYPE) set_node = self.get_node() maya.cmds.sets(set_node, edit=True, text=value) return diff --git a/python/mmSolver/_api/solveresult.py b/python/mmSolver/_api/solveresult.py index 593037388..5eb59aee9 100644 --- a/python/mmSolver/_api/solveresult.py +++ b/python/mmSolver/_api/solveresult.py @@ -715,5 +715,5 @@ def format_timestamp(value): # Remove microseconds from the datetime object. stamp = ts.replace(ts.year, ts.month, ts.day, ts.hour, ts.minute, ts.second, 0) stamp = stamp.isoformat(' ') - assert isinstance(stamp, str) + assert isinstance(stamp, pycompat.TEXT_TYPE) return stamp diff --git a/python/mmSolver/tools/createimageplane/_lib/main.py b/python/mmSolver/tools/createimageplane/_lib/main.py index 633815a7b..a2a6424aa 100644 --- a/python/mmSolver/tools/createimageplane/_lib/main.py +++ b/python/mmSolver/tools/createimageplane/_lib/main.py @@ -181,7 +181,7 @@ def _set_locked_string_attr(node_attr, value): def _set_image_sequence_v2(mm_image_plane_node, image_sequence_path, attr_name=None): if attr_name is None: attr_name = lib_const.DEFAULT_IMAGE_SEQUENCE_ATTR_NAME - assert isinstance(attr_name, str) + assert isinstance(attr_name, pycompat.TEXT_TYPE) assert attr_name in lib_const.VALID_INPUT_IMAGE_SEQUENCE_ATTR_NAMES version = lib_const.MM_IMAGE_PLANE_VERSION_TWO diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py index 73e10c1ef..42c9e5a9b 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane.py @@ -228,9 +228,9 @@ def create_shape_node( def set_image_sequence(shp, image_sequence_path, attr_name, version=None): - assert isinstance(shp, str) - assert isinstance(image_sequence_path, str) - assert isinstance(attr_name, str) + assert isinstance(shp, pycompat.TEXT_TYPE) + assert isinstance(image_sequence_path, pycompat.TEXT_TYPE) + assert isinstance(attr_name, pycompat.TEXT_TYPE) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None if version == lib_const.MM_IMAGE_PLANE_VERSION_ONE: @@ -247,7 +247,7 @@ def set_image_sequence(shp, image_sequence_path, attr_name, version=None): def get_shape_node(image_plane_tfm, version=None): - assert isinstance(image_plane_tfm, str) + assert isinstance(image_plane_tfm, pycompat.TEXT_TYPE) assert maya.cmds.objExists(image_plane_tfm) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None @@ -261,7 +261,7 @@ def get_shape_node(image_plane_tfm, version=None): def get_transform_node(image_plane_shp, version=None): - assert isinstance(image_plane_shp, str) + assert isinstance(image_plane_shp, pycompat.TEXT_TYPE) assert maya.cmds.objExists(image_plane_shp) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None @@ -275,7 +275,7 @@ def get_transform_node(image_plane_shp, version=None): def get_image_plane_node_pair(node, version=None): - assert isinstance(node, str) + assert isinstance(node, pycompat.TEXT_TYPE) assert maya.cmds.objExists(node) assert version in lib_const.MM_IMAGE_PLANE_VERSION_LIST result = None diff --git a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py index 1f3158be3..04f9e5f68 100644 --- a/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py +++ b/python/mmSolver/tools/createimageplane/_lib/mmimageplane_v2.py @@ -30,6 +30,7 @@ import mmSolver.utils.constant as const_utils import mmSolver.utils.imageseq as imageseq_utils import mmSolver.utils.node as node_utils +import mmSolver.utils.python_compat as pycompat import mmSolver.tools.createimageplane._lib.constant as lib_const import mmSolver.tools.createimageplane._lib.utilities as lib_utils @@ -269,7 +270,7 @@ def _slot_number_to_attr_name(slot_number): def _slot_attr_name_to_number(attr_name): - assert isinstance(attr_name, str) + assert isinstance(attr_name, pycompat.TEXT_TYPE) assert attr_name in SLOT_ATTR_NAME_VALUES return SLOT_ATTR_NAME_TO_NUMBER_MAP[attr_name] @@ -309,7 +310,7 @@ def get_file_pattern_for_all_slots(shp): def set_image_sequence(shp, image_sequence_path, attr_name): - assert isinstance(image_sequence_path, str) + assert isinstance(image_sequence_path, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 assert node_utils.attribute_exists(attr_name, shp) is True @@ -393,14 +394,14 @@ def set_image_sequence(shp, image_sequence_path, attr_name): def get_frame_size_bytes(shp): - assert isinstance(shp, str) + assert isinstance(shp, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 image_size_bytes = maya.cmds.getAttr(shp + '.imageSizeBytes') return int(image_size_bytes) def get_frame_count(shp): - assert isinstance(shp, str) + assert isinstance(shp, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 start_frame = maya.cmds.getAttr(shp + '.imageSequenceStartFrame') end_frame = maya.cmds.getAttr(shp + '.imageSequenceEndFrame') @@ -411,7 +412,7 @@ def get_frame_count(shp): def get_image_sequence_size_bytes(shp): - assert isinstance(shp, str) + assert isinstance(shp, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(shp) == lib_const.MM_IMAGE_PLANE_SHAPE_V2 image_size_bytes = get_frame_size_bytes(shp) frame_count = get_frame_count(shp) @@ -483,6 +484,7 @@ def _set_attribute_editor_color_space(node_attr, control_name, value): def _maybe_make_menu_item(button_name, label, node_attr, value): if value and len(value) > 0: + def func(x): _set_attribute_editor_color_space(node_attr, button_name, value) @@ -491,7 +493,7 @@ def func(x): def color_space_attribute_editor_new(node_attr): - assert isinstance(node_attr, str) + assert isinstance(node_attr, pycompat.TEXT_TYPE) split = node_attr.split('.') if len(split) == 0: LOG.warn('Could not get attribute name from: %r', node_attr) diff --git a/python/mmSolver/tools/imagecache/_lib/erase.py b/python/mmSolver/tools/imagecache/_lib/erase.py index 769060b02..007b68506 100644 --- a/python/mmSolver/tools/imagecache/_lib/erase.py +++ b/python/mmSolver/tools/imagecache/_lib/erase.py @@ -28,6 +28,7 @@ import mmSolver.logger import mmSolver.utils.imageseq as imageseq_utils import mmSolver.utils.constant as const_utils +import mmSolver.utils.python_compat as pycompat import mmSolver.tools.imagecache.constant as const import mmSolver.tools.imagecache._lib.imagecache_cmd as imagecache_cmd import mmSolver.tools.createimageplane._lib.constant as imageplane_const @@ -40,7 +41,7 @@ def _make_group_name_consistent(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 # The ImageCache stores all file paths with UNIX @@ -51,14 +52,14 @@ def _make_group_name_consistent(group_name): def erase_gpu_group_items(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 group_name = _make_group_name_consistent(group_name) return maya.cmds.mmImageCache([group_name], edit=True, gpuEraseGroupItems=True) def erase_cpu_group_items(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 group_name = _make_group_name_consistent(group_name) return maya.cmds.mmImageCache([group_name], edit=True, cpuEraseGroupItems=True) @@ -91,7 +92,9 @@ def erase_all_images_on_image_plane_slots(cache_type, shape_node): assert maya.cmds.nodeType(shape_node) == _IMAGE_PLANE_SHAPE file_patterns = imageplane_lib.get_file_pattern_for_all_slots(shape_node) - file_patterns = [x for x in file_patterns if isinstance(x, str) and len(x) > 0] + file_patterns = [ + x for x in file_patterns if isinstance(x, pycompat.TEXT_TYPE) and len(x) > 0 + ] if len(file_patterns) == 0: LOG.warn('mmImagePlane unused slots are all invalid; node=%r', shape_node) return @@ -124,7 +127,9 @@ def erase_images_in_unused_image_plane_slots(cache_type, shape_node): assert maya.cmds.nodeType(shape_node) == _IMAGE_PLANE_SHAPE file_patterns = imageplane_lib.get_file_pattern_for_unused_slots(shape_node) - file_patterns = [x for x in file_patterns if isinstance(x, str) and len(x) > 0] + file_patterns = [ + x for x in file_patterns if isinstance(x, pycompat.TEXT_TYPE) and len(x) > 0 + ] if len(file_patterns) == 0: LOG.warn('mmImagePlane unused slots are all invalid; node=%r', shape_node) return @@ -141,7 +146,7 @@ def erase_image_sequence( ): assert cache_type in const.CACHE_TYPE_VALUES assert format_style in const_utils.IMAGE_SEQ_FORMAT_STYLE_VALUES - assert isinstance(file_pattern, str) + assert isinstance(file_pattern, pycompat.TEXT_TYPE) assert isinstance(start_frame, int) assert isinstance(end_frame, int) diff --git a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py index 7be3fa00e..506c64319 100644 --- a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py +++ b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py @@ -26,6 +26,7 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.python_compat as pycompat LOG = mmSolver.logger.get_logger() @@ -59,25 +60,25 @@ def get_cpu_cache_slot_count(): def get_gpu_cache_group_item_count(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 return int(maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemCount=True)) def get_cpu_cache_group_item_count(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 return int(maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemCount=True)) def get_gpu_cache_group_item_names(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 return maya.cmds.mmImageCache(group_name, query=True, gpuGroupItemNames=True) def get_cpu_cache_group_item_names(group_name): - assert isinstance(group_name, str) + assert isinstance(group_name, pycompat.TEXT_TYPE) assert len(group_name) > 0 return maya.cmds.mmImageCache(group_name, query=True, cpuGroupItemNames=True) diff --git a/python/mmSolver/ui/nodes.py b/python/mmSolver/ui/nodes.py index 262ca146b..48882f5a1 100644 --- a/python/mmSolver/ui/nodes.py +++ b/python/mmSolver/ui/nodes.py @@ -23,6 +23,8 @@ from __future__ import division from __future__ import print_function +import mmSolver.utils.python_compat as pycompat + def get_nodes_recursively(top_node): nodes = [] @@ -151,7 +153,7 @@ def setNeverHasChildren(self, value): self._neverHasChildren = value def icon(self): - assert isinstance(self._iconPath, str) + assert isinstance(self._iconPath, pycompat.TEXT_TYPE) if self._icon is None: import mmSolver.ui.uiutils as uiutils diff --git a/python/mmSolver/ui/uiutils.py b/python/mmSolver/ui/uiutils.py index 44a99cced..688d164fd 100644 --- a/python/mmSolver/ui/uiutils.py +++ b/python/mmSolver/ui/uiutils.py @@ -230,7 +230,7 @@ def getIcon(path): :return: Qt Icon object. :rtype: QIcon """ - assert isinstance(path, str) + assert isinstance(path, pycompat.TEXT_TYPE) icon = QtGui.QIcon(QtGui.QPixmap(path)) return icon diff --git a/python/mmSolver/utils/tools.py b/python/mmSolver/utils/tools.py index c6677df61..01efc657e 100644 --- a/python/mmSolver/utils/tools.py +++ b/python/mmSolver/utils/tools.py @@ -29,6 +29,7 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.python_compat as pycompat import mmSolver.utils.viewport as viewport_utils LOG = mmSolver.logger.get_logger() @@ -113,7 +114,7 @@ def tool_context( use_undo_chunk = True if undo_chunk_name is None: undo_chunk_name = str(uuid.uuid4()) - assert isinstance(undo_chunk_name, str) + assert isinstance(undo_chunk_name, pycompat.TEXT_TYPE) if pre_update_frame is None: pre_update_frame = False if post_update_frame is None: diff --git a/python/mmSolver/utils/viewport.py b/python/mmSolver/utils/viewport.py index 039f9e48d..ef779e6b6 100644 --- a/python/mmSolver/utils/viewport.py +++ b/python/mmSolver/utils/viewport.py @@ -30,6 +30,7 @@ import mmSolver.utils.camera as camera_utils import mmSolver.utils.node as node_utils import mmSolver.utils.constant as const +import mmSolver.utils.python_compat as pycompat LOG = mmSolver.logger.get_logger() @@ -425,8 +426,8 @@ def _get_node_type_visibility(model_panel, node_type): :return: The visibility of the node type. :rtype: bool """ - assert isinstance(model_panel, str) - assert isinstance(node_type, str) + assert isinstance(model_panel, pycompat.TEXT_TYPE) + assert isinstance(node_type, pycompat.TEXT_TYPE) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) kwargs = { 'query': True, @@ -446,8 +447,8 @@ def _set_node_type_visibility(model_panel, node_type, value): :param value: Visibility of the node type. :type value: bool """ - assert isinstance(model_panel, str) - assert isinstance(node_type, str) + assert isinstance(model_panel, pycompat.TEXT_TYPE) + assert isinstance(node_type, pycompat.TEXT_TYPE) assert isinstance(value, bool) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) kwargs = { @@ -468,8 +469,8 @@ def _get_plugin_display_filter_visibility(model_panel, plugin_display_filter): :return: The visibility of the display filter. :rtype: bool """ - assert isinstance(model_panel, str) - assert isinstance(plugin_display_filter, str) + assert isinstance(model_panel, pycompat.TEXT_TYPE) + assert isinstance(plugin_display_filter, pycompat.TEXT_TYPE) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) value = maya.cmds.modelEditor( model_editor, query=True, queryPluginObjects=plugin_display_filter @@ -488,8 +489,8 @@ def _set_plugin_display_filter_visibility(model_panel, plugin_display_filter, va :param value: Visibility of the node type. :type value: bool """ - assert isinstance(model_panel, str) - assert isinstance(plugin_display_filter, str) + assert isinstance(model_panel, pycompat.TEXT_TYPE) + assert isinstance(plugin_display_filter, pycompat.TEXT_TYPE) assert isinstance(value, bool) model_editor = maya.cmds.modelPanel(model_panel, query=True, modelEditor=True) maya.cmds.modelEditor( From 90fef323ed382595e7882ba193c65ca03d059455 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 18 Sep 2024 00:19:47 +1000 Subject: [PATCH 211/295] Set Mesh HoldOuts - Add __init__.py ... and a description to the file. --- .../tools/setmeshholdouts/__init__.py | 20 +++++++++++++++++++ python/mmSolver/tools/setmeshholdouts/tool.py | 5 +++++ 2 files changed, 25 insertions(+) create mode 100644 python/mmSolver/tools/setmeshholdouts/__init__.py diff --git a/python/mmSolver/tools/setmeshholdouts/__init__.py b/python/mmSolver/tools/setmeshholdouts/__init__.py new file mode 100644 index 000000000..32de14f1a --- /dev/null +++ b/python/mmSolver/tools/setmeshholdouts/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 Patcha Saheb Binginapalli. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Tools to set hold-out attributes on meshes. +""" diff --git a/python/mmSolver/tools/setmeshholdouts/tool.py b/python/mmSolver/tools/setmeshholdouts/tool.py index e27cefe8d..6509e52fa 100644 --- a/python/mmSolver/tools/setmeshholdouts/tool.py +++ b/python/mmSolver/tools/setmeshholdouts/tool.py @@ -15,6 +15,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with mmSolver. If not, see . # +""" +Tools to set hold-out attributes on meshes. + +These tools can enable or disable the hold-out attribute on mesh nodes. +""" import maya.cmds as cmds From 199a95668381f2da1a64a252995bd9523007d7a9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 18 Sep 2024 00:29:43 +1000 Subject: [PATCH 212/295] mmSolver.utils.node.set_attr() return bool based on success. --- python/mmSolver/utils/node.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/python/mmSolver/utils/node.py b/python/mmSolver/utils/node.py index 8b5b8d899..f785ff2a2 100644 --- a/python/mmSolver/utils/node.py +++ b/python/mmSolver/utils/node.py @@ -60,11 +60,12 @@ def set_attr(plug, value, relock=False): Optionally unlocks and re-locks the plugs. :param plug: Node.Attr to set. - :param value: The ne value to set. + :param value: The new value to set. :param relock: If the plug was already locked, should we set the new value, then re-lock afterward? - :return: + :return: True or False, depending if the value was set or not. + :rtype: bool """ node = plug.partition('.')[0] is_referenced = node_is_referenced(node) @@ -72,13 +73,14 @@ def set_attr(plug, value, relock=False): if is_referenced is True and locked is True: msg = 'Cannot set attr %r, it is locked and the node is referenced.' LOG.warning(msg, plug) + return False if is_referenced is False: # Make sure the plug is unlocked. maya.cmds.setAttr(plug, lock=False) maya.cmds.setAttr(plug, value) if is_referenced is False and relock is True: maya.cmds.setAttr(plug, lock=locked) - return + return True def get_long_name(node): From 3e4f2fa97471f21ba8166a1dfd9a9a3995b39354 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 18 Sep 2024 00:38:20 +1000 Subject: [PATCH 213/295] Set Mesh Holdouts - Add more resilience to setting attributes If attributes are locked and on a reference node, the holdout is not changed. Also added more descriptive warning messages for users. --- python/mmSolver/tools/setmeshholdouts/tool.py | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/python/mmSolver/tools/setmeshholdouts/tool.py b/python/mmSolver/tools/setmeshholdouts/tool.py index 6509e52fa..aff78bf0d 100644 --- a/python/mmSolver/tools/setmeshholdouts/tool.py +++ b/python/mmSolver/tools/setmeshholdouts/tool.py @@ -21,45 +21,57 @@ These tools can enable or disable the hold-out attribute on mesh nodes. """ -import maya.cmds as cmds +import maya.cmds import mmSolver.logger +import mmSolver.utils.node as node_utils + LOG = mmSolver.logger.get_logger() +NO_SCENE_MESHES_WARNING_MESSAGE = "No mesh nodes found in the Maya scene." +SELECTED_MESHES_WARNING_MESSAGE = ( + "Please select mesh objects, no mesh nodes were found from the selection." +) + -def set_holdout(mesh_list, enable=True): - for mesh in mesh_list: - cmds.setAttr(mesh + ".holdOut", enable) +def set_holdout(mesh_nodes, value): + assert isinstance(value, bool) + attr_name = 'holdOut' + for mesh_node in mesh_nodes: + if node_utils.attribute_exists(attr_name, mesh_node): + node_attr = "{}.holdOut".format(mesh_node) + node_utils.set_attr(node_attr, value, relock=True) + return def enable_all_meshes(): - all_meshes = cmds.ls(dag=True, type="mesh") or [] - if not all_meshes: - LOG.warn("Mesh not found.") + all_meshes = maya.cmds.ls(dag=True, type="mesh") or [] + if len(all_meshes) == 0: + LOG.warn(NO_SCENE_MESHES_WARNING_MESSAGE) return set_holdout(all_meshes, True) def disable_all_meshes(): - all_meshes = cmds.ls(dag=True, type="mesh") or [] - if not all_meshes: - LOG.warn("Mesh not found.") + all_meshes = maya.cmds.ls(dag=True, type="mesh") or [] + if len(all_meshes) == 0: + LOG.warn(NO_SCENE_MESHES_WARNING_MESSAGE) return set_holdout(all_meshes, False) def enable_selected_meshes(): - selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] - if not selected_meshes: - LOG.warn("Mesh selection not found.") + selected_meshes = maya.cmds.ls(selection=True, dag=True, type="mesh") or [] + if len(selected_meshes) == 0: + LOG.warn(SELECTED_MESHES_WARNING_MESSAGE) return set_holdout(selected_meshes, True) def disable_selected_meshes(): - selected_meshes = cmds.ls(selection=True, dag=True, type="mesh") or [] - if not selected_meshes: - LOG.warn("Mesh selection not found.") + selected_meshes = maya.cmds.ls(selection=True, dag=True, type="mesh") or [] + if len(selected_meshes) == 0: + LOG.warn(SELECTED_MESHES_WARNING_MESSAGE) return set_holdout(selected_meshes, False) From ed10fcab0c08514ae084888d6a4b9dbb8a170c7d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 18 Sep 2024 01:07:05 +1000 Subject: [PATCH 214/295] Include Patcha's name in author's string --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87a7b6a8c..8b19b6170 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,7 +64,7 @@ set(PROJECT_VERSION_PATCH 0) set(PROJECT_VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") set(PROJECT_HOMEPAGE_URL "https://github.com/david-cattermole/mayaMatchMoveSolver") set(PROJECT_DESCRIPTION "Bundle Adjustment solver for MatchMove tasks in Autodesk Maya.") -set(PROJECT_AUTHOR "David Cattermole and others (see AUTHORS.txt file)") +set(PROJECT_AUTHOR "David Cattermole, Patcha Saheb Binginapalli, and others (see AUTHORS.txt file)") set(PROJECT_COPYRIGHT "2018-2024, David Cattermole, Anil Reddy, Kazuma Tonegawa, Patcha Saheb Binginapalli.") enable_testing() From 4d9b2664f5835e266bcdc08048e8e3e18d4753ad Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 20 Sep 2024 00:11:36 +1000 Subject: [PATCH 215/295] MM ImageCache - Update Python code for Python 2.7 We must make sure that byte size values are stored as 'long's in 2.7 and as 'int' in 3.x. We must also be careful of division, as Python 2.x does not convert '1/2' to a floating point number (0.5), but Python 3.x does. Finally we fix the circular dependencies of the initalization of the image cache, so it's moved to a different module to avoid those problems... and because it really is an entry point of sorts. --- python/mmSolver/startup.py | 8 +++- .../tools/imagecache/_lib/__init__.py | 20 ++++++++++ .../tools/imagecache/_lib/imagecache_cmd.py | 12 +++--- .../mmSolver/tools/imagecache/config_utils.py | 4 +- .../tools/imagecache/{_lib => }/initialize.py | 14 +++---- python/mmSolver/tools/imagecache/lib.py | 3 -- .../ui/imagecacheprefs_layout.py | 37 +++++++++++-------- .../ui/imagecacheprefs_window.py | 4 ++ 8 files changed, 67 insertions(+), 35 deletions(-) create mode 100644 python/mmSolver/tools/imagecache/_lib/__init__.py rename python/mmSolver/tools/imagecache/{_lib => }/initialize.py (89%) diff --git a/python/mmSolver/startup.py b/python/mmSolver/startup.py index c5266f94a..82c1f501f 100644 --- a/python/mmSolver/startup.py +++ b/python/mmSolver/startup.py @@ -23,6 +23,10 @@ components of mmSolver. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import os import maya.cmds import maya.utils @@ -73,9 +77,9 @@ def mmsolver_image_cache_initalize(): """ Initialise the mmSolver ImageCache. """ - import mmSolver.tools.imagecache.lib + import mmSolver.tools.imagecache.initialize - mmSolver.tools.imagecache.lib.initialize() + mmSolver.tools.imagecache.initialize.main() def mmsolver_startup(): diff --git a/python/mmSolver/tools/imagecache/_lib/__init__.py b/python/mmSolver/tools/imagecache/_lib/__init__.py new file mode 100644 index 000000000..de016c510 --- /dev/null +++ b/python/mmSolver/tools/imagecache/_lib/__init__.py @@ -0,0 +1,20 @@ +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# +""" +Image Cache library functions. +""" diff --git a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py index 506c64319..899de5980 100644 --- a/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py +++ b/python/mmSolver/tools/imagecache/_lib/imagecache_cmd.py @@ -84,26 +84,26 @@ def get_cpu_cache_group_item_names(group_name): def get_gpu_cache_used_bytes(): - return int(maya.cmds.mmImageCache(query=True, gpuUsed=True)) + return pycompat.LONG_TYPE(maya.cmds.mmImageCache(query=True, gpuUsed=True)) def get_cpu_cache_used_bytes(): - return int(maya.cmds.mmImageCache(query=True, cpuUsed=True)) + return pycompat.LONG_TYPE(maya.cmds.mmImageCache(query=True, cpuUsed=True)) def get_gpu_cache_capacity_bytes(): - return int(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) + return pycompat.LONG_TYPE(maya.cmds.mmImageCache(query=True, gpuCapacity=True)) def get_cpu_cache_capacity_bytes(): - return int(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) + return pycompat.LONG_TYPE(maya.cmds.mmImageCache(query=True, cpuCapacity=True)) def set_gpu_cache_capacity_bytes(size_bytes): - assert isinstance(size_bytes, int) + assert isinstance(size_bytes, pycompat.INT_TYPES) return maya.cmds.mmImageCache(edit=True, gpuCapacity=size_bytes) def set_cpu_cache_capacity_bytes(size_bytes): - assert isinstance(size_bytes, int) + assert isinstance(size_bytes, pycompat.INT_TYPES) return maya.cmds.mmImageCache(edit=True, cpuCapacity=size_bytes) diff --git a/python/mmSolver/tools/imagecache/config_utils.py b/python/mmSolver/tools/imagecache/config_utils.py index 7397101fa..b65e18d37 100644 --- a/python/mmSolver/tools/imagecache/config_utils.py +++ b/python/mmSolver/tools/imagecache/config_utils.py @@ -26,6 +26,7 @@ import collections import mmSolver.logger +import mmSolver.utils.python_compat as pycompat LOG = mmSolver.logger.get_logger() CapacityValue = collections.namedtuple('CapacityValue', ['size_bytes', 'percent']) @@ -33,6 +34,7 @@ def convert_to_capacity_value(percent, total_bytes): assert isinstance(percent, float) + assert isinstance(total_bytes, pycompat.INT_TYPES) ratio = percent / 100.0 - size_bytes = int(total_bytes * ratio) + size_bytes = pycompat.LONG_TYPE(total_bytes * ratio) return CapacityValue(size_bytes, percent) diff --git a/python/mmSolver/tools/imagecache/_lib/initialize.py b/python/mmSolver/tools/imagecache/initialize.py similarity index 89% rename from python/mmSolver/tools/imagecache/_lib/initialize.py rename to python/mmSolver/tools/imagecache/initialize.py index df4a8d913..f381e5d2f 100644 --- a/python/mmSolver/tools/imagecache/_lib/initialize.py +++ b/python/mmSolver/tools/imagecache/initialize.py @@ -16,7 +16,7 @@ # along with mmSolver. If not, see . # """ -Functions to control the image cache. +Start up and initialize the image cache. """ from __future__ import absolute_import @@ -29,7 +29,7 @@ import mmSolver.logger import mmSolver.api as mmapi import mmSolver.utils.constant as const_utils -import mmSolver.tools.imagecache._lib.imagecache_cmd as imagecache_cmd +import mmSolver.tools.imagecache.lib as lib import mmSolver.tools.imagecache.config as config @@ -74,7 +74,7 @@ def _dummy_initialize(*args): initialize() -def initialize(): +def main(): """ Set Image Cache capacities from preferences. """ @@ -108,8 +108,8 @@ def initialize(): gpu_capacity_percent = _format_percent(gpu_capacity_percent) cpu_capacity_percent = _format_percent(cpu_capacity_percent) - gpu_current_capacity_bytes = imagecache_cmd.get_gpu_cache_capacity_bytes() - cpu_current_capacity_bytes = imagecache_cmd.get_cpu_cache_capacity_bytes() + gpu_current_capacity_bytes = lib.get_gpu_cache_capacity_bytes() + cpu_current_capacity_bytes = lib.get_cpu_cache_capacity_bytes() if gpu_capacity_bytes != gpu_current_capacity_bytes: LOG.info( @@ -117,7 +117,7 @@ def initialize(): gpu_capacty_gigabytes, gpu_capacity_percent, ) - imagecache_cmd.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) + lib.set_gpu_cache_capacity_bytes(gpu_capacity_bytes) if cpu_capacity_bytes != cpu_current_capacity_bytes: LOG.info( @@ -125,5 +125,5 @@ def initialize(): cpu_capacty_gigabytes, cpu_capacity_percent, ) - imagecache_cmd.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) + lib.set_cpu_cache_capacity_bytes(cpu_capacity_bytes) return diff --git a/python/mmSolver/tools/imagecache/lib.py b/python/mmSolver/tools/imagecache/lib.py index e8c5e6eed..70182fa4d 100644 --- a/python/mmSolver/tools/imagecache/lib.py +++ b/python/mmSolver/tools/imagecache/lib.py @@ -23,7 +23,6 @@ from __future__ import division from __future__ import print_function -from mmSolver.tools.imagecache._lib.initialize import initialize from mmSolver.tools.imagecache._lib.query_resources import ( get_gpu_memory_total_bytes, get_gpu_memory_used_bytes, @@ -65,8 +64,6 @@ # Stop users from accessing the internal functions of this sub-module. __all__ = [ - 'initialize', - # 'get_gpu_memory_total_bytes', 'get_gpu_memory_used_bytes', 'get_cpu_memory_total_bytes', diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py index cabd51658..a612f7a8d 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -20,6 +20,10 @@ window. """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import os import mmSolver.ui.qtpyutils as qtpyutils @@ -33,6 +37,7 @@ import mmSolver.utils.config as config_utils import mmSolver.utils.constant as const_utils import mmSolver.utils.math as math_utils +import mmSolver.utils.python_compat as pycompat import mmSolver.tools.imagecache.config_file as config_file import mmSolver.tools.imagecache.config_scene as config_scene import mmSolver.tools.imagecache.config as config @@ -48,7 +53,7 @@ def set_memory_total_label( size_bytes, ): assert isinstance(label, QtWidgets.QLabel) - assert isinstance(size_bytes, int) + assert isinstance(size_bytes, pycompat.INT_TYPES) gigabytes = 0.0 if size_bytes > 0: gigabytes = size_bytes / const_utils.BYTES_TO_GIGABYTES @@ -59,8 +64,8 @@ def set_memory_total_label( def set_memory_used_label(label, used_size_bytes, total_size_bytes): assert isinstance(label, QtWidgets.QLabel) - assert isinstance(used_size_bytes, int) - assert isinstance(total_size_bytes, int) + assert isinstance(used_size_bytes, pycompat.INT_TYPES) + assert isinstance(total_size_bytes, pycompat.INT_TYPES) used_gigabytes = 0.0 if used_size_bytes > 0: used_gigabytes = used_size_bytes / const_utils.BYTES_TO_GIGABYTES @@ -88,7 +93,7 @@ def set_capacity_size_label( size_bytes, ): assert isinstance(label, QtWidgets.QLabel) - assert isinstance(size_bytes, int) + assert isinstance(size_bytes, pycompat.INT_TYPES) gigabytes = 0.0 if size_bytes > 0: gigabytes = size_bytes / const_utils.BYTES_TO_GIGABYTES @@ -99,8 +104,8 @@ def set_capacity_size_label( def set_used_size_label(label, used_size_bytes, capacity_size_bytes): assert isinstance(label, QtWidgets.QLabel) - assert isinstance(used_size_bytes, int) - assert isinstance(capacity_size_bytes, int) + assert isinstance(used_size_bytes, pycompat.INT_TYPES) + assert isinstance(capacity_size_bytes, pycompat.INT_TYPES) used_gigabytes = 0.0 if used_size_bytes > 0: used_gigabytes = used_size_bytes / const_utils.BYTES_TO_GIGABYTES @@ -140,7 +145,7 @@ def set_value_double_spin_box( def set_capacity_widgets( label, slider, double_spin_box, capacity_bytes, capacity_percent ): - assert isinstance(capacity_bytes, int) + assert isinstance(capacity_bytes, pycompat.INT_TYPES) assert isinstance(capacity_percent, float) set_capacity_size_label(label, capacity_bytes) set_percent_slider(slider, capacity_percent) @@ -239,7 +244,7 @@ def _gpu_default_capacity_spin_box_changed(self, percent): gpu_memory_total = lib.get_gpu_memory_total_bytes() ratio = percent / 100.0 - size_bytes = int(ratio * gpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * gpu_memory_total) LOG.debug('_gpu_default_capacity_spin_box_changed: ratio=%r', ratio) LOG.debug('_gpu_default_capacity_spin_box_changed: size_bytes=%r', size_bytes) @@ -258,7 +263,7 @@ def _cpu_default_capacity_spin_box_changed(self, percent): cpu_memory_total = lib.get_cpu_memory_total_bytes() ratio = percent / 100.0 - size_bytes = int(ratio * cpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * cpu_memory_total) LOG.debug('_cpu_default_capacity_spin_box_changed: ratio=%r', ratio) LOG.debug('_cpu_default_capacity_spin_box_changed: size_bytes=%r', size_bytes) @@ -277,7 +282,7 @@ def _gpu_scene_capacity_spin_box_changed(self, percent): gpu_memory_total = lib.get_gpu_memory_total_bytes() ratio = percent / 100.0 - size_bytes = int(ratio * gpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * gpu_memory_total) LOG.debug('_gpu_scene_capacity_spin_box_changed: ratio=%r', ratio) LOG.debug('_gpu_scene_capacity_spin_box_changed: size_bytes=%r', size_bytes) @@ -296,7 +301,7 @@ def _cpu_scene_capacity_spin_box_changed(self, percent): cpu_memory_total = lib.get_cpu_memory_total_bytes() ratio = percent / 100.0 - size_bytes = int(ratio * cpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * cpu_memory_total) LOG.debug('_cpu_scene_capacity_spin_box_changed: ratio=%r', ratio) LOG.debug('_cpu_scene_capacity_spin_box_changed: size_bytes=%r', size_bytes) @@ -323,7 +328,7 @@ def _gpu_default_capacity_slider_changed(self, value): LOG.debug('_gpu_default_capacity_slider_changed: percent=%r', percent) gpu_memory_total = lib.get_gpu_memory_total_bytes() - size_bytes = int(ratio * gpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * gpu_memory_total) set_capacity_size_label(label, size_bytes) @@ -348,7 +353,7 @@ def _cpu_default_capacity_slider_changed(self, value): LOG.debug('_cpu_default_capacity_slider_changed: percent=%r', percent) cpu_memory_total = lib.get_cpu_memory_total_bytes() - size_bytes = int(ratio * cpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * cpu_memory_total) set_capacity_size_label(label, size_bytes) @@ -373,7 +378,7 @@ def _gpu_scene_capacity_slider_changed(self, value): LOG.debug('_gpu_scene_capacity_slider_changed: percent=%r', percent) gpu_memory_total = lib.get_gpu_memory_total_bytes() - size_bytes = int(ratio * gpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * gpu_memory_total) set_capacity_size_label(label, size_bytes) @@ -398,7 +403,7 @@ def _cpu_scene_capacity_slider_changed(self, value): LOG.debug('_cpu_scene_capacity_slider_changed: percent=%r', percent) cpu_memory_total = lib.get_cpu_memory_total_bytes() - size_bytes = int(ratio * cpu_memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * cpu_memory_total) set_capacity_size_label(label, size_bytes) @@ -424,7 +429,7 @@ def _get_capacity_bytes( percent = scene_spin_box.value() ratio = percent / 100.0 - size_bytes = int(ratio * memory_total) + size_bytes = pycompat.LONG_TYPE(ratio * memory_total) return size_bytes def get_gpu_capacity_bytes(self): diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py index 46620cbdd..d9fee6563 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py @@ -25,6 +25,10 @@ """ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import mmSolver.ui.qtpyutils as qtpyutils qtpyutils.override_binding_order() From 2ce215123e0b487d3c5ea6b80d4410a8f365cdf1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 20 Sep 2024 00:17:46 +1000 Subject: [PATCH 216/295] Mesh from Points - Fix minimum number of poinst needed Only 3 points are needed to create a triangle. This is a polygon with the lowest number of points. --- python/mmSolver/tools/meshfrompoints/lib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/tools/meshfrompoints/lib.py b/python/mmSolver/tools/meshfrompoints/lib.py index 1b73ebb40..66548510f 100644 --- a/python/mmSolver/tools/meshfrompoints/lib.py +++ b/python/mmSolver/tools/meshfrompoints/lib.py @@ -48,7 +48,7 @@ def _delaunator_indices(transform_nodes, viewport): """ assert isinstance(viewport, omui.M3dView) - assert len(transform_nodes) > const.MINIMUM_NUMBER_OF_POINTS + assert len(transform_nodes) >= const.MINIMUM_NUMBER_OF_POINTS world_positions = [] view_positions = [] @@ -117,7 +117,7 @@ def create_mesh_from_transform_nodes( assert isinstance(mesh_name, str) assert len(mesh_name) > 0 assert isinstance(offset_value, float) - assert len(transform_nodes) > const.MINIMUM_NUMBER_OF_POINTS + assert len(transform_nodes) >= const.MINIMUM_NUMBER_OF_POINTS active_viewport = omui.M3dView.active3dView() mesh_data = _delaunator_indices(transform_nodes, active_viewport) From ab8b0fc4764d26215d6b33e053cd5517db758680 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 20 Sep 2024 00:27:04 +1000 Subject: [PATCH 217/295] delaunator.py - Fix Python 2/3 compatibility. This allows Maya 2020 and Maya 2024 to use this module as expected. --- .../tools/meshfrompoints/delaunator.py | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/python/mmSolver/tools/meshfrompoints/delaunator.py b/python/mmSolver/tools/meshfrompoints/delaunator.py index d52a5e8c2..71492d7b0 100644 --- a/python/mmSolver/tools/meshfrompoints/delaunator.py +++ b/python/mmSolver/tools/meshfrompoints/delaunator.py @@ -17,8 +17,15 @@ # Code from https://github.com/HakanSeven12/Delaunator-Python/blob/master/Delaunator.py # +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + import math +import mmSolver.utils.python_compat as pycompat + + EPSILON = math.pow(2, -52) EDGE_STACK = [None] * 512 @@ -49,7 +56,7 @@ def constructor(self, coords): self._halfedges = [None] * maxTriangles * 3 # temporary arrays for tracking the edges of the advancing convex hull - self.hashSize = math.ceil(math.sqrt(n)) + self.hashSize = pycompat.LONG_TYPE(math.ceil(math.sqrt(n))) self.hullPrev = [None] * n # edge to prev edge self.hullNext = [None] * n # edge to next edge self.hullTri = [None] * n # edge to adjacent triangle @@ -66,10 +73,10 @@ def update(self, coords): n = len(coords) >> 1 # populate an array of point indices; calculate input data bbox - minX = math.inf - minY = math.inf - maxX = -math.inf - maxY = -math.inf + minX = float('inf') + minY = float('inf') + maxX = float('-inf') + maxY = float('-inf') for i in range(0, n): x = coords[2 * i] @@ -83,7 +90,7 @@ def update(self, coords): cx = (minX + maxX) / 2 cy = (minY + maxY) / 2 - minDist = math.inf + minDist = float('inf') i0 = 0 i1 = 0 i2 = 0 @@ -98,7 +105,7 @@ def update(self, coords): i0x = coords[2 * i0] i0y = coords[2 * i0 + 1] - minDist = math.inf + minDist = float('inf') # find the point closest to the seed for i in range(0, n): @@ -112,7 +119,7 @@ def update(self, coords): i1x = coords[2 * i1] i1y = coords[2 * i1 + 1] - minRadius = math.inf + minRadius = float('inf') # find the third point which forms the smallest circumcircle with the first two for i in range(0, n): @@ -127,7 +134,7 @@ def update(self, coords): i2x = coords[2 * i2] i2y = coords[2 * i2 + 1] - if (minRadius == math.inf): + if (minRadius == float('inf')): # order collinear points by dx (or dy if all x are identical) # and return the list as a hull for i in range(0, n): @@ -137,7 +144,7 @@ def update(self, coords): quicksort(self._ids, self._dists, 0, n - 1) hull = [None] * n j = 0 - d0 = -math.inf + d0 = float('-inf') for i in range(0, n): id = self._ids[i] @@ -295,8 +302,10 @@ def update(self, coords): return self.triangles def _hashKey(self, x, y): - return math.floor(pseudoAngle(x - self._cx, - y - self._cy) * self.hashSize) % self.hashSize + angle = pseudoAngle(x - self._cx, + y - self._cy) + key = math.floor(angle * self.hashSize) % self.hashSize + return pycompat.LONG_TYPE(key) def _legalize(self, a, coords): i = 0 From 9070e84c05c5624a311bb9a7ec43170105c9f0f8 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 20 Sep 2024 00:27:52 +1000 Subject: [PATCH 218/295] MM ImageCache - fix new scene callback initialization. --- python/mmSolver/tools/imagecache/initialize.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/imagecache/initialize.py b/python/mmSolver/tools/imagecache/initialize.py index f381e5d2f..8da4deab7 100644 --- a/python/mmSolver/tools/imagecache/initialize.py +++ b/python/mmSolver/tools/imagecache/initialize.py @@ -71,7 +71,7 @@ def _dummy_initialize(*args): This dummy function is used for the callbacks below, but it contains an extra argument. """ - initialize() + main() def main(): From 9c8f1cd45edfc738aafceefa6aad5d82aa88edb1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 20 Sep 2024 23:09:20 +1000 Subject: [PATCH 219/295] Attribute Bake - Preserve Outside Keys by default. Users can enable/disable the option at any time. By default the option is enabled. This means that if the frame range 10-20 is baked, and this option is enabled, the keyframes before frame 10 and after frame 20 are kept. --- .../mmSolver/tools/attributebake/constant.py | 2 ++ python/mmSolver/tools/attributebake/lib.py | 9 ++++++++- python/mmSolver/tools/attributebake/tool.py | 18 ++++++++++++++++-- .../tools/attributebake/ui/attrbake_layout.py | 17 +++++++++++++++++ .../tools/attributebake/ui/attrbake_layout.ui | 10 ++++++++++ 5 files changed, 53 insertions(+), 3 deletions(-) diff --git a/python/mmSolver/tools/attributebake/constant.py b/python/mmSolver/tools/attributebake/constant.py index 49199a7f3..58cc8a3f5 100644 --- a/python/mmSolver/tools/attributebake/constant.py +++ b/python/mmSolver/tools/attributebake/constant.py @@ -53,6 +53,7 @@ DEFAULT_FRAME_RANGE_MODE = FRAME_RANGE_MODE_TIMELINE_INNER_VALUE DEFAULT_SMART_BAKE_STATE = False DEFAULT_FROM_CHANNELBOX_STATE = True +DEFAULT_PRESERVE_OUTSIDE_KEYS_STATE = True # Config files CONFIG_FRAME_RANGE_MODE_KEY = 'mmSolver_attrbake_frame_range_mode' @@ -60,3 +61,4 @@ CONFIG_FRAME_END_KEY = 'mmSolver_attrbake_frame_end' CONFIG_SMART_BAKE_KEY = 'mmSolver_attrbake_smart_bake' CONFIG_FROM_CHANNELBOX_KEY = 'mmSolver_attrbake_from_channel_box' +CONFIG_PRESERVE_OUTSIDE_KEYS_KEY = 'mmSolver_attrbake_preserve_outside_keys' diff --git a/python/mmSolver/tools/attributebake/lib.py b/python/mmSolver/tools/attributebake/lib.py index 2ffaf3d8a..4e512a91c 100644 --- a/python/mmSolver/tools/attributebake/lib.py +++ b/python/mmSolver/tools/attributebake/lib.py @@ -48,7 +48,9 @@ def get_bake_frame_range(frame_range_mode, custom_start_frame, custom_end_frame) return frame_range -def bake_attributes(nodes, attrs, start_frame, end_frame, smart_bake=False): +def bake_attributes( + nodes, attrs, start_frame, end_frame, smart_bake=False, preserve_outside_keys=False +): """ Bake the attributes on nodes. @@ -60,11 +62,14 @@ def bake_attributes(nodes, attrs, start_frame, end_frame, smart_bake=False): :param start_frame: Start frame to bake. :param end_frame: End frame to bake. :param smart_bake: Perform a "smart" bake - do not bake per-frame. + :param preserve_outside_keys: + Delete the keyframes outside the given frame range, or not. """ assert isinstance(nodes, list) assert isinstance(start_frame, pycompat.INT_TYPES) assert isinstance(end_frame, pycompat.INT_TYPES) assert isinstance(smart_bake, bool) + assert isinstance(preserve_outside_keys, bool) assert isinstance(attrs, list) if smart_bake is True: maya.cmds.bakeResults( @@ -72,6 +77,7 @@ def bake_attributes(nodes, attrs, start_frame, end_frame, smart_bake=False): time=(start_frame, end_frame), attribute=attrs, smart=int(smart_bake), + preserveOutsideKeys=preserve_outside_keys, simulation=True, sparseAnimCurveBake=False, minimizeRotation=True, @@ -81,6 +87,7 @@ def bake_attributes(nodes, attrs, start_frame, end_frame, smart_bake=False): nodes, time=(start_frame, end_frame), attribute=attrs, + preserveOutsideKeys=preserve_outside_keys, simulation=True, sparseAnimCurveBake=False, minimizeRotation=True, diff --git a/python/mmSolver/tools/attributebake/tool.py b/python/mmSolver/tools/attributebake/tool.py index 4a4d99eb1..3f779449e 100644 --- a/python/mmSolver/tools/attributebake/tool.py +++ b/python/mmSolver/tools/attributebake/tool.py @@ -66,6 +66,10 @@ def main(): from_channelbox_state = configmaya.get_scene_option( const.CONFIG_FROM_CHANNELBOX_KEY, const.DEFAULT_FROM_CHANNELBOX_STATE ) + preserve_outside_keys_state = configmaya.get_scene_option( + const.CONFIG_PRESERVE_OUTSIDE_KEYS_KEY, + const.DEFAULT_PRESERVE_OUTSIDE_KEYS_STATE, + ) frame_range = lib.get_bake_frame_range( frame_range_mode, custom_start_frame, custom_end_frame @@ -86,15 +90,25 @@ def main(): disable_viewport_mode=const_utils.DISABLE_VIEWPORT_MODE_VP1_VALUE, ) with ctx: + bake_success = False try: lib.bake_attributes( - nodes, attrs, frame_range.start, frame_range.end, smart_bake_state + nodes, + attrs, + frame_range.start, + frame_range.end, + smart_bake=smart_bake_state, + preserve_outside_keys=preserve_outside_keys_state, ) + bake_success = True except Exception: LOG.exception('Bake attributes failed.') finally: e = time.time() - LOG.warn('Bake attribute success. Time elapsed: %r secs', e - s) + if bake_success is True: + LOG.info('Bake attribute success. Time elapsed: %r secs', e - s) + else: + LOG.error('Bake attribute failed. Time elapsed: %r secs', e - s) return diff --git a/python/mmSolver/tools/attributebake/ui/attrbake_layout.py b/python/mmSolver/tools/attributebake/ui/attrbake_layout.py index 2e92c9c7b..30db4f19d 100644 --- a/python/mmSolver/tools/attributebake/ui/attrbake_layout.py +++ b/python/mmSolver/tools/attributebake/ui/attrbake_layout.py @@ -57,6 +57,9 @@ def __init__(self, parent=None, *args, **kwargs): self.end_frame_spinbox.valueChanged.connect(self.endFrameValueChanged) self.smart_bake_cbox.stateChanged.connect(self.smartBakeValueChanged) self.channel_box_cbox.stateChanged.connect(self.fromChannelBoxValueChanged) + self.preserve_outside_keys_cbox.stateChanged.connect( + self.preserveOutsideKeysValueChanged + ) self.populateUi() @@ -127,6 +130,11 @@ def fromChannelBoxValueChanged(self): value = self.channel_box_cbox.isChecked() configmaya.set_scene_option(name, value, add_attr=True) + def preserveOutsideKeysValueChanged(self): + name = const.CONFIG_PRESERVE_OUTSIDE_KEYS_KEY + value = self.preserve_outside_keys_cbox.isChecked() + configmaya.set_scene_option(name, value, add_attr=True) + def reset_options(self): name = const.CONFIG_FRAME_RANGE_MODE_KEY value = const.DEFAULT_FRAME_RANGE_MODE @@ -148,6 +156,10 @@ def reset_options(self): value = const.DEFAULT_FROM_CHANNELBOX_STATE configmaya.set_scene_option(name, value) + name = const.CONFIG_PRESERVE_OUTSIDE_KEYS_KEY + value = const.DEFAULT_PRESERVE_OUTSIDE_KEYS_STATE + configmaya.set_scene_option(name, value) + self.populateUi() return @@ -168,6 +180,10 @@ def populateUi(self): const.CONFIG_FROM_CHANNELBOX_KEY, default=const.DEFAULT_FROM_CHANNELBOX_STATE, ) + preserve_outside_keys_state = configmaya.get_scene_option( + const.CONFIG_PRESERVE_OUTSIDE_KEYS_KEY, + default=const.DEFAULT_PRESERVE_OUTSIDE_KEYS_STATE, + ) label = const.FRAME_RANGE_MODE_VALUE_LABEL_MAP[frame_range_mode] self.frame_range_combo.setCurrentText(label) @@ -180,4 +196,5 @@ def populateUi(self): self.smart_bake_cbox.setChecked(bool(smart_bake_state)) self.channel_box_cbox.setChecked(bool(from_channelbox_state)) + self.preserve_outside_keys_cbox.setChecked(bool(preserve_outside_keys_state)) return diff --git a/python/mmSolver/tools/attributebake/ui/attrbake_layout.ui b/python/mmSolver/tools/attributebake/ui/attrbake_layout.ui index e7b3d33f5..77a5982b2 100644 --- a/python/mmSolver/tools/attributebake/ui/attrbake_layout.ui +++ b/python/mmSolver/tools/attributebake/ui/attrbake_layout.ui @@ -94,6 +94,16 @@ + + + + Preserve Outside Keys + + + false + + + From 305a5c94a8e830650a4b8a95bacf5d062bc70d82 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:14:45 +1000 Subject: [PATCH 220/295] Toggle Viewport - Add Docs. --- .../mmSolver.tools.toggleviewportctrls.rst | 21 +++++++++++++++++++ .../mmSolver.tools.toggleviewportgeom.rst | 21 +++++++++++++++++++ .../mmSolver.tools.toggleviewportimgplns.rst | 21 +++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 docs/source/mmSolver.tools.toggleviewportctrls.rst create mode 100644 docs/source/mmSolver.tools.toggleviewportgeom.rst create mode 100644 docs/source/mmSolver.tools.toggleviewportimgplns.rst diff --git a/docs/source/mmSolver.tools.toggleviewportctrls.rst b/docs/source/mmSolver.tools.toggleviewportctrls.rst new file mode 100644 index 000000000..cee254f67 --- /dev/null +++ b/docs/source/mmSolver.tools.toggleviewportctrls.rst @@ -0,0 +1,21 @@ +================================== +mmSolver.tools.toggleviewportctrls +================================== + +.. automodule:: mmSolver.tools.toggleviewportctrls + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.toggleviewportctrls.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.toggleviewportctrls.lib + :members: + :undoc-members: diff --git a/docs/source/mmSolver.tools.toggleviewportgeom.rst b/docs/source/mmSolver.tools.toggleviewportgeom.rst new file mode 100644 index 000000000..15d5d9f08 --- /dev/null +++ b/docs/source/mmSolver.tools.toggleviewportgeom.rst @@ -0,0 +1,21 @@ +================================= +mmSolver.tools.toggleviewportgeom +================================= + +.. automodule:: mmSolver.tools.toggleviewportgeom + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.toggleviewportgeom.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.toggleviewportgeom.lib + :members: + :undoc-members: diff --git a/docs/source/mmSolver.tools.toggleviewportimgplns.rst b/docs/source/mmSolver.tools.toggleviewportimgplns.rst new file mode 100644 index 000000000..261b3c95c --- /dev/null +++ b/docs/source/mmSolver.tools.toggleviewportimgplns.rst @@ -0,0 +1,21 @@ +================================== +mmSolver.tools.toggleviewportimgplns +================================== + +.. automodule:: mmSolver.tools.toggleviewportimgplns + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.toggleviewportimgplns.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.toggleviewportimgplns.lib + :members: + :undoc-members: From 2f6668e6a12329b6b2779043621577824f135b53 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:15:27 +1000 Subject: [PATCH 221/295] Add Docs - Toggle Object Motion Trail. --- ...mmSolver.tools.toggleobjectmotiontrail.rst | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/source/mmSolver.tools.toggleobjectmotiontrail.rst diff --git a/docs/source/mmSolver.tools.toggleobjectmotiontrail.rst b/docs/source/mmSolver.tools.toggleobjectmotiontrail.rst new file mode 100644 index 000000000..9c2908f95 --- /dev/null +++ b/docs/source/mmSolver.tools.toggleobjectmotiontrail.rst @@ -0,0 +1,21 @@ +====================================== +mmSolver.tools.toggleobjectmotiontrail +====================================== + +.. automodule:: mmSolver.tools.toggleobjectmotiontrail + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.toggleobjectmotiontrail.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.toggleobjectmotiontrail.lib + :members: + :undoc-members: From 021d94d9d444f04299f19ab6941fe149536597b3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:17:27 +1000 Subject: [PATCH 222/295] Set Mesh Hold-Outs - Hide internal function We don't want the _set_holdout function to be public, in case people assume it's always there, and then we need to move it. --- python/mmSolver/tools/setmeshholdouts/tool.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/python/mmSolver/tools/setmeshholdouts/tool.py b/python/mmSolver/tools/setmeshholdouts/tool.py index aff78bf0d..1b5c08cbe 100644 --- a/python/mmSolver/tools/setmeshholdouts/tool.py +++ b/python/mmSolver/tools/setmeshholdouts/tool.py @@ -35,7 +35,7 @@ ) -def set_holdout(mesh_nodes, value): +def _set_holdout(mesh_nodes, value): assert isinstance(value, bool) attr_name = 'holdOut' for mesh_node in mesh_nodes: @@ -50,7 +50,7 @@ def enable_all_meshes(): if len(all_meshes) == 0: LOG.warn(NO_SCENE_MESHES_WARNING_MESSAGE) return - set_holdout(all_meshes, True) + _set_holdout(all_meshes, True) def disable_all_meshes(): @@ -58,7 +58,7 @@ def disable_all_meshes(): if len(all_meshes) == 0: LOG.warn(NO_SCENE_MESHES_WARNING_MESSAGE) return - set_holdout(all_meshes, False) + _set_holdout(all_meshes, False) def enable_selected_meshes(): @@ -66,7 +66,7 @@ def enable_selected_meshes(): if len(selected_meshes) == 0: LOG.warn(SELECTED_MESHES_WARNING_MESSAGE) return - set_holdout(selected_meshes, True) + _set_holdout(selected_meshes, True) def disable_selected_meshes(): @@ -74,4 +74,4 @@ def disable_selected_meshes(): if len(selected_meshes) == 0: LOG.warn(SELECTED_MESHES_WARNING_MESSAGE) return - set_holdout(selected_meshes, False) + _set_holdout(selected_meshes, False) From d5d95e83ec7ba8e267a0da5411125cb8ae115b68 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:18:44 +1000 Subject: [PATCH 223/295] Docs - Add Image Cache tools --- docs/source/mmSolver.tools.imagecache.rst | 54 +++++++++++++++++++ .../source/mmSolver.tools.imagecacheprefs.rst | 35 ++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 docs/source/mmSolver.tools.imagecache.rst create mode 100644 docs/source/mmSolver.tools.imagecacheprefs.rst diff --git a/docs/source/mmSolver.tools.imagecache.rst b/docs/source/mmSolver.tools.imagecache.rst new file mode 100644 index 000000000..a5fc63f5c --- /dev/null +++ b/docs/source/mmSolver.tools.imagecache.rst @@ -0,0 +1,54 @@ +========================= +mmSolver.tools.imagecache +========================= + +.. automodule:: mmSolver.tools.imagecache + :members: + :undoc-members: + +Initialize +++++++++++ + +.. automodule:: mmSolver.tools.imagecache.initialize + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.imagecache.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.imagecache.lib + :members: + :undoc-members: + +Configuration ++++++++++++++ + +.. automodule:: mmSolver.tools.imagecache.config_file + :members: + :undoc-members: + +.. automodule:: mmSolver.tools.imagecache.config + :members: + :undoc-members: + +.. automodule:: mmSolver.tools.imagecache.config_scene + :members: + :undoc-members: + +.. automodule:: mmSolver.tools.imagecache.config_utils + :members: + :undoc-members: + +Constants ++++++++++ + +.. automodule:: mmSolver.tools.imagecache.constant + :members: + :undoc-members: diff --git a/docs/source/mmSolver.tools.imagecacheprefs.rst b/docs/source/mmSolver.tools.imagecacheprefs.rst new file mode 100644 index 000000000..998f5f19f --- /dev/null +++ b/docs/source/mmSolver.tools.imagecacheprefs.rst @@ -0,0 +1,35 @@ +============================== +mmSolver.tools.imagecacheprefs +============================== + +.. automodule:: mmSolver.tools.imagecacheprefs + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.imagecacheprefs.tool + :members: + :undoc-members: + +UI - Layout ++++++++++++ + +.. automodule:: mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_layout + :members: + :undoc-members: + +UI - Window ++++++++++++ + +.. automodule:: mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_window + :members: + :undoc-members: + +Constants ++++++++++ + +.. automodule:: mmSolver.tools.imagecacheprefs.constant + :members: + :undoc-members: From d0181dca05dd84d099439dbb72f6cbaff5101ae7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:19:01 +1000 Subject: [PATCH 224/295] Docs - Mesh From Points --- docs/source/mmSolver.tools.meshfrompoints.rst | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/source/mmSolver.tools.meshfrompoints.rst diff --git a/docs/source/mmSolver.tools.meshfrompoints.rst b/docs/source/mmSolver.tools.meshfrompoints.rst new file mode 100644 index 000000000..92387f7c0 --- /dev/null +++ b/docs/source/mmSolver.tools.meshfrompoints.rst @@ -0,0 +1,49 @@ +============================= +mmSolver.tools.meshfrompoints +============================= + +.. automodule:: mmSolver.tools.meshfrompoints + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.lib + :members: + :undoc-members: + +Delaunator +++++++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.delaunator + :members: + :undoc-members: + +UI - Layout ++++++++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.ui.meshfrompoints_layout + :members: + :undoc-members: + +UI - Window ++++++++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.ui.meshfrompoints_window + :members: + :undoc-members: + +Constants ++++++++++ + +.. automodule:: mmSolver.tools.meshfrompoints.constant + :members: + :undoc-members: From 859a751ffabf785b69dcdc2860be6c5dfd9b76d4 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:19:33 +1000 Subject: [PATCH 225/295] Docs - Add Set Mesh Hold-Outs --- docs/source/mmSolver.tools.setmeshholdouts.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/source/mmSolver.tools.setmeshholdouts.rst diff --git a/docs/source/mmSolver.tools.setmeshholdouts.rst b/docs/source/mmSolver.tools.setmeshholdouts.rst new file mode 100644 index 000000000..4f274efdb --- /dev/null +++ b/docs/source/mmSolver.tools.setmeshholdouts.rst @@ -0,0 +1,15 @@ +============================== +mmSolver.tools.setmeshholdouts +============================== + +.. automodule:: mmSolver.tools.setmeshholdouts + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.setmeshholdouts.tool + :members: + :undoc-members: + From c9812d13638e414dcff296de2b47c79954124eb3 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:20:07 +1000 Subject: [PATCH 226/295] Docs - Add Set Mesh Hold-outs Tools --- docs/source/tools_displaytools.rst | 63 ++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/docs/source/tools_displaytools.rst b/docs/source/tools_displaytools.rst index b5dad3289..706e34185 100644 --- a/docs/source/tools_displaytools.rst +++ b/docs/source/tools_displaytools.rst @@ -126,6 +126,69 @@ control visibility like so: See :ref:`mmSolver.utils.viewport ` Python module documentation for more help. +.. _set-mesh-hold-outs-ref: + +Set Mesh Hold-Outs +------------------ + +This tool is used to force mesh nodes to be rendered as hold-out in +the viewport, or not. + +This is similar to assigning a UseBackground shader, to geometry, +however this tool avoids the need to create a shader, and manage +assignments. + +The tool is split into different individual features which are fairly +self-explanatory: + +- Enable / Disable Hold-Outs on *selected* meshes. + +- Enable / Disable Hold-Outs on *all* Meshes in the scene. + +Usage: + +1) Select meshes (optional) + +2) Run tool. + + - Meshes will be have the Hold-Out attribute enabled / disabled. + + +Each different feature can be activated with a slightly different Python +command. + +Enable Hold-outs on Selected Meshes: + +.. code:: python + + import mmSolver.tools.setmesholdouts.tool as tool + tool.enable_selected_meshes() + + +Disable Hold-outs on Selected Meshes: + +.. code:: python + + import mmSolver.tools.setmesholdouts.tool as tool + tool.disable_selected_meshes() + + +Enable Hold-outs on All Meshes: + +.. code:: python + + import mmSolver.tools.setmesholdouts.tool as tool + tool.enable_all_meshes() + + +Disable Hold-outs on All Meshes: + +.. code:: python + + import mmSolver.tools.setmesholdouts.tool as tool + tool.disable_all_meshes() + + .. _create-sky-dome-tool-ref: Create Horizon / Axis Dome / Sky Dome From 44e6a9c7463421172b9a3550d9e60c2433b9fbfc Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:35:31 +1000 Subject: [PATCH 227/295] Docs - Add missing tools --- docs/source/mmSolver.tools.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/mmSolver.tools.rst b/docs/source/mmSolver.tools.rst index 9dbd951c6..61d3f0760 100644 --- a/docs/source/mmSolver.tools.rst +++ b/docs/source/mmSolver.tools.rst @@ -11,6 +11,7 @@ mmSolver.tools mmSolver.tools.attachbundletocurve mmSolver.tools.attributebake mmSolver.tools.averagemarker + mmSolver.tools.calibratecamera mmSolver.tools.cameraaim mmSolver.tools.cameracontextmenu mmSolver.tools.cameraobjectscaleadjust @@ -39,6 +40,7 @@ mmSolver.tools mmSolver.tools.loadmarker mmSolver.tools.markerbundlerename mmSolver.tools.markerbundlerenamewithmetadata + mmSolver.tools.meshfrompoints mmSolver.tools.mmhotkeyset mmSolver.tools.mmmenu mmSolver.tools.mmshelf @@ -59,6 +61,7 @@ mmSolver.tools mmSolver.tools.selection mmSolver.tools.setattributedetails mmSolver.tools.setcameraoriginframe + mmSolver.tools.setmeshholdouts mmSolver.tools.setobjectcolour mmSolver.tools.showdeviationcurves mmSolver.tools.smoothkeyframes @@ -71,6 +74,10 @@ mmSolver.tools mmSolver.tools.togglecameradistort mmSolver.tools.togglelinelock mmSolver.tools.togglemarkerlock + mmSolver.tools.toggleobjectmotiontrail + mmSolver.tools.toggleviewportctrls + mmSolver.tools.toggleviewportgeom + mmSolver.tools.toggleviewportimgplns mmSolver.tools.triangulatebundle mmSolver.tools.undoredoscene mmSolver.tools.userpreferences From 10ed3f21512ee995249e12915153b0e28b331a3b Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:37:33 +1000 Subject: [PATCH 228/295] Docs - Add missing stubs for ImagePlane nodes. --- docs/source/nodes_imageplane.rst | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/source/nodes_imageplane.rst b/docs/source/nodes_imageplane.rst index b624f8758..9399f4d4a 100644 --- a/docs/source/nodes_imageplane.rst +++ b/docs/source/nodes_imageplane.rst @@ -9,6 +9,16 @@ Image Plane Nodes *To be written.* ``mmImagePlaneShape`` Node -+++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++ + +*This node type is deprecated.* + +``mmImagePlaneShape2`` Node ++++++++++++++++++++++++++++ + +*To be written.* + +``mmImageSequenceFrameLogic`` Node +++++++++++++++++++++++++++++++++++ *To be written.* From daf21405dccef6ce2e0a054465d74f70fe97a85f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 01:46:24 +1000 Subject: [PATCH 229/295] Add stub for Image Cache Preferences. --- docs/source/tools_generaltools.rst | 19 +++++++++++++++++++ .../ui/imagecacheprefs_window.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/docs/source/tools_generaltools.rst b/docs/source/tools_generaltools.rst index 3d6a5993f..7c3d23179 100644 --- a/docs/source/tools_generaltools.rst +++ b/docs/source/tools_generaltools.rst @@ -516,6 +516,25 @@ To run the tool, use this Python command: import mmSolver.tools.removesolvernodes.tool as tool tool.main() +.. _image-cache-preferences-ref: + +Image Cache Preferences +----------------------- + +.. figure:: images/tools_image_cache_preferences_ui.png + :alt: Image Cache Preferences window + :align: center + :width: 60% + +*To be written.* + +To run the tool, use this Python command: + +.. code:: python + + import mmSolver.tools.imagecacheprefs.tool as tool + tool.open_window() + .. _user-preferences-tool-ref: User Preferences diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py index d9fee6563..770c095b3 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_window.py @@ -51,7 +51,7 @@ def _open_help(): src = helputils.get_help_source() - page = 'tools_generaltools.html#image-cache' + page = 'tools_generaltools.html#image-cache-preferences' helputils.open_help_in_browser(page=page, help_source=src) return From e8c42f8f515697c276e77bcd38df074883267319 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 14:22:28 +1000 Subject: [PATCH 230/295] Fix spelling of module. --- python/mmSolver/tools/imagecache/{initialize.py => initialise.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename python/mmSolver/tools/imagecache/{initialize.py => initialise.py} (100%) diff --git a/python/mmSolver/tools/imagecache/initialize.py b/python/mmSolver/tools/imagecache/initialise.py similarity index 100% rename from python/mmSolver/tools/imagecache/initialize.py rename to python/mmSolver/tools/imagecache/initialise.py From e8452601cb6fb3de21d8e6512356c6732f3a6316 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 14:28:39 +1000 Subject: [PATCH 231/295] Image Cache - Make initalisation only happen at plug-in load This is to make sure the plug-in does not load until it's absolutely needed - to avoid non-users of mmSolver from having the plug-in loaded. --- python/mmSolver/startup.py | 9 +++------ src/mmSolver/pluginMain.cpp | 10 +++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/python/mmSolver/startup.py b/python/mmSolver/startup.py index 82c1f501f..9be823eb9 100644 --- a/python/mmSolver/startup.py +++ b/python/mmSolver/startup.py @@ -73,13 +73,13 @@ def mmsolver_register_events(): mmSolver.tools.registerevents.tool.register_events() -def mmsolver_image_cache_initalize(): +def mmsolver_image_cache_initialise(): """ Initialise the mmSolver ImageCache. """ - import mmSolver.tools.imagecache.initialize + import mmSolver.tools.imagecache.initialise - mmSolver.tools.imagecache.initialize.main() + mmSolver.tools.imagecache.initialise.main() def mmsolver_startup(): @@ -119,7 +119,4 @@ def mmsolver_startup(): # Register Events. maya.utils.executeDeferred(mmsolver_register_events) - - # Start up the image cache. - maya.utils.executeDeferred(mmsolver_image_cache_initalize) return diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index eeecfb792..157662ef5 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -583,15 +583,19 @@ MStatus initializePlugin(MObject obj) { mmsolver::LineShapeNode::m_display_filter_label, mmsolver::LineShapeNode::m_draw_db_classification); - // Run the Python startup function when the plug-in loads. + // Run the Python startup and image cache initialisation function + // when the plug-in loads. bool displayEnabled = true; bool undoEnabled = false; MString startup_cmd; startup_cmd += "global proc mmsolver_startup() "; startup_cmd += "{ "; + startup_cmd += "python(\""; startup_cmd += - "python(\"import mmSolver.startup; " - "mmSolver.startup.mmsolver_startup()\"); "; + "import mmSolver.startup; " + "mmSolver.startup.mmsolver_startup();" + "mmSolver.startup.mmsolver_image_cache_initialise();"; + startup_cmd += "\"); "; startup_cmd += "} "; startup_cmd += "evalDeferred(\"mmsolver_startup\");"; status = MGlobal::executeCommand(startup_cmd, displayEnabled, undoEnabled); From 8aa16c5cc50e69504938691e0fd3be77b1dc191e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 14:28:57 +1000 Subject: [PATCH 232/295] Docs - Add tool Calibrate camera --- .../source/mmSolver.tools.calibratecamera.rst | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 docs/source/mmSolver.tools.calibratecamera.rst diff --git a/docs/source/mmSolver.tools.calibratecamera.rst b/docs/source/mmSolver.tools.calibratecamera.rst new file mode 100644 index 000000000..99a77d0c8 --- /dev/null +++ b/docs/source/mmSolver.tools.calibratecamera.rst @@ -0,0 +1,27 @@ +mmSolver.tools.calibratecamera +============================== + +.. automodule:: mmSolver.tools.calibratecamera + :members: + :undoc-members: + +Tools ++++++ + +.. automodule:: mmSolver.tools.calibratecamera.tool + :members: + :undoc-members: + +Library ++++++++ + +.. automodule:: mmSolver.tools.calibratecamera.lib + :members: + :undoc-members: + +Constants ++++++++++ + +.. automodule:: mmSolver.tools.calibratecamera.constant + :members: + :undoc-members: From 713e5bccfddc86d1ebe3e7f5c05ac2f77c20e6d6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 18:44:25 +1000 Subject: [PATCH 233/295] Update Mesh Tools icon. The mesh is just a simple triangulated mesh. The text 'Mesh' is important for users to clearly see these are mesh tools. --- share/icons/edit/meshTools.svg | 648 ++++++++++++++++++++++++++++++++ share/icons/meshTools_32x32.png | Bin 2092 -> 2221 bytes 2 files changed, 648 insertions(+) create mode 100644 share/icons/edit/meshTools.svg diff --git a/share/icons/edit/meshTools.svg b/share/icons/edit/meshTools.svg new file mode 100644 index 000000000..ba879e92c --- /dev/null +++ b/share/icons/edit/meshTools.svg @@ -0,0 +1,648 @@ + + + +image/svg+xmlMeshMesh diff --git a/share/icons/meshTools_32x32.png b/share/icons/meshTools_32x32.png index 28cbd00bbce57f6d86810eba59c5984791004ea9..48c154cdedf9081c37f817817ce28b9a51e2b113 100644 GIT binary patch literal 2221 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 literal 2092 zcmV+{2-Ek8P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGf6951U69E94oEQKA2f;~1K~z{rwO4CQ zQ)d`{djkr^QZ6l}v|1Dd0dcYc&SYjmGTjn&KU}tDSr)T7U7X35@WZg^ek|LM#klN} zNK6)&EXz#%vBa3&_h(*0ydffBKm<#%T;x((3Wc_Np3hSI3v%GVfmgGv))sp~K{X;5d@DMWDZjS1c9ZtWvSpD2^Yimnh+M+qut*~Dg!Wpt zY}u05)YQ~mT3Y&5ZfTozpOeT|9GBRXpYD#8jXC*W>)z*6b`b)=8ocN@_zdx?2Shgq! z>gwvuIXOA67z~Ezj3!edpb(u-=hT}_`-+N+Y4*|I3!IVrqoZ>3=1tkWd9wtA!HEIC|0AUSv(u+f zN3{L_T67yXZroc{Rn=?-_JwY@tjNri1Qy~bM*qNobocZKTJ7iV_V(9)xo~0d!M6WS z^twbML$BA%%9Sg{nV&BfI1rhhmKjtxvyhRVE_Z#t-~SKe6umJeC56RhHk%~}sZRsW zj2=ZMDR`zs(~-z_eEUeO`FCO{UbE&PU?_Jwohn+&D6nT%=olSBQM&UTj)=FYsI|AZ zcTRnz$-2Bei{0ye(&O>)}6^wW9-aVC-yLa!-PK83}&<$PS1NZFAOcA=J3|TVdA$!)WESUy; zb0DaR@$rOzaPUkZ5O}|>t?im-Z;9w$*uA@>zP`SU1(*&P*n)k1eKI^eEE&i^3R)0G zWggr>=8NKjVIgDEIh)NUS-8j(a7JR-&o#>6;Ghine9^AX&i&^vUi?zMd`JvMt5;_u z@~4d`I4VZFiO=T~lF{1IQdNk2&d$!3TyQ^#N)j-TX|agg;}LdGDl!G?7Be6OpNIu( zgpL6rD+V~W6VIb4=1ZF01u;xcPF_K8zX;rIeRuB2{rmS7{uPy#N(m!lCU`gCJq{{m zh$&CUo!miZ$Q`6{?qwE0>*_fU$57`Io}S){`wxI*Q8yXD>vQE0dtP3(XVoexDJhXk z7*~9N%~V4Er!gv0bpglpNVkdv1qwHC2q|znkLKs+>**=x0M+osl?qIVj(0jkrw9vw^EABC-Y(Z$Th%$iE@3t@ zkvT#^*!aDUu;EIQ8#pC=r?2nq1M?uFD_5?>urP%KMs1)X79H_cQNRJdH^DFqkWn+m zU0q$uP)$t@Cn;IKe!Z+IE>^|LJ%oaKXq?@__k<9EZPjcm;XDurJ{O1;?kaj>%FVo( zVpcfsQAB|LmJzflvw*r0S7D*b8ZSoAI}OoM-kCW%!~J{*=e*3ivJO!L$@tjVx5SVi z4na99mluE2F_H_hWCsvSreDExXpG$)BCDU6is{Eh`keqkZ2dV*txg@voM zPZuVl1m=Ktc>gkRH}U{tBpkhXC}o4`=m>>%L9QS@^YG&QK&5IO7o%pkBrg_Ta%~j* z7znbnwyvgTRe{UZwlI=`di$~1iA}q($$*heZD#73UKSjMN*OgBNI^s_F2w^hq7^V< zVRop4?VGHrp+G>2QK%rXLQwcqfTCg1OgJ<&)R|$i{DeZQK_vN1Efs`u7C68U%!J@izOLIN^^6w30-jvjOOG)Lt{)~cXziUAu429at2^T6#f}HxLd& z#e<8T#zFVSjT={kq0s&ZrTvgJHa0#BrN_{cC777hvCFw&7HxIu@@2u_Gbt-8Q_M&> zh&{W3Sced8I$)tNQvnn!1{uM4uL>8lMjV66*SNop|C7Nb4(!;m<1w5Kzky6r>o#qQ zliesJo Date: Sat, 21 Sep 2024 19:11:54 +1000 Subject: [PATCH 234/295] Docs - Add Image Cache Preferences UI screenshot --- .../images/tools_image_cache_preferences_ui.png | Bin 0 -> 20992 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/tools_image_cache_preferences_ui.png diff --git a/docs/source/images/tools_image_cache_preferences_ui.png b/docs/source/images/tools_image_cache_preferences_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..3ade0c81d8468ad7685f336647c8028fd0cdfbb2 GIT binary patch literal 20992 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 nI8vs&0-VfbXe- Date: Sat, 21 Sep 2024 19:12:40 +1000 Subject: [PATCH 235/295] Docs - Add Mesh From Points Window screenshot --- .../images/tools_mesh_from_points_window.png | Bin 0 -> 6521 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/source/images/tools_mesh_from_points_window.png diff --git a/docs/source/images/tools_mesh_from_points_window.png b/docs/source/images/tools_mesh_from_points_window.png new file mode 100644 index 0000000000000000000000000000000000000000..9659d94db43696a42df6def6129f705f8f18bdf5 GIT binary patch literal 6521 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 n@E>-Sx&jOvEErEG9UUKNhWLg;uooF;5P Date: Sat, 21 Sep 2024 19:13:52 +1000 Subject: [PATCH 236/295] Image Cache Prefs - Autoload plug-in when loading UI If we don't do this, the UI will fail to open. ... but we want to delay loading the plug-in until we really need it. --- python/mmSolver/tools/imagecacheprefs/tool.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/mmSolver/tools/imagecacheprefs/tool.py b/python/mmSolver/tools/imagecacheprefs/tool.py index 880e0873a..f4e86b127 100644 --- a/python/mmSolver/tools/imagecacheprefs/tool.py +++ b/python/mmSolver/tools/imagecacheprefs/tool.py @@ -24,6 +24,7 @@ from __future__ import print_function import mmSolver.logger +import mmSolver.api as mmapi LOG = mmSolver.logger.get_logger() @@ -31,4 +32,5 @@ def open_window(): import mmSolver.tools.imagecacheprefs.ui.imagecacheprefs_window as window + mmapi.load_plugin() window.main() From 9b3a877abd2b27c177ce5f787aea2dfc56b4d743 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 22:36:18 +1000 Subject: [PATCH 237/295] Mesh From Points - Return mesh node name. --- python/mmSolver/tools/meshfrompoints/lib.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/python/mmSolver/tools/meshfrompoints/lib.py b/python/mmSolver/tools/meshfrompoints/lib.py index 66548510f..f39cadc31 100644 --- a/python/mmSolver/tools/meshfrompoints/lib.py +++ b/python/mmSolver/tools/meshfrompoints/lib.py @@ -23,6 +23,7 @@ import maya.cmds import mmSolver.logger +import mmSolver.utils.node as node_utils from mmSolver.tools.meshfrompoints.delaunator import Delaunator import mmSolver.tools.meshfrompoints.constant as const @@ -108,6 +109,9 @@ def create_mesh_from_transform_nodes( :param mesh_name: The name of the mesh node created. :type mesh_name: str + + :returns: The name of the created mesh node. + :rtype: str """ if offset_value is None: offset_value = const.DEFAULT_STRIP_WIDTH @@ -138,15 +142,16 @@ def create_mesh_from_transform_nodes( # Set mesh name. dag_node = om.MFnDagNode(mesh) dag_node.setName(mesh_name) - mesh_name = dag_node.name() + mesh_node = dag_node.name() # Set lambert - maya.cmds.sets(mesh_name, edit=True, forceElement='initialShadingGroup') + maya.cmds.sets(mesh_node, edit=True, forceElement='initialShadingGroup') # Create border edge strip mesh if mesh_type == const.MESH_TYPE_BORDER_EDGE_STRIP_MESH_VALUE: - maya.cmds.polyExtrudeFacet(mesh_name, offset=offset_value) - face_0 = '{}.f[0]'.format(mesh_name) - face_1 = '{}.f[1]'.format(mesh_name) + maya.cmds.polyExtrudeFacet(mesh_node, offset=offset_value) + face_0 = '{}.f[0]'.format(mesh_node) + face_1 = '{}.f[1]'.format(mesh_node) maya.cmds.delete(face_0, face_1) - return + + return node_utils.get_long_name(mesh_node) From e3655f54c34523689e1a75823eff0a0d25003566 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 22:37:24 +1000 Subject: [PATCH 238/295] Mesh From Points - Add tools for common operations. These were added into the menus to quickly create the meshes, rather than needing the UI. --- python/mmSolver/tools/meshfrompoints/tool.py | 41 ++++++++++++++++++++ share/config/functions.json | 18 +++++++++ share/config/menu.json | 2 + share/config/shelf.json | 2 + share/config/shelf_minimal.json | 2 + 5 files changed, 65 insertions(+) diff --git a/python/mmSolver/tools/meshfrompoints/tool.py b/python/mmSolver/tools/meshfrompoints/tool.py index cdc0b4ac4..8de8332dd 100644 --- a/python/mmSolver/tools/meshfrompoints/tool.py +++ b/python/mmSolver/tools/meshfrompoints/tool.py @@ -23,11 +23,52 @@ from __future__ import division from __future__ import print_function +import maya.cmds + import mmSolver.logger +import mmSolver.tools.meshfrompoints.constant as const +import mmSolver.tools.meshfrompoints.lib as lib + LOG = mmSolver.logger.get_logger() +def _get_selection(): + transform_nodes = maya.cmds.ls(selection=True, transforms=True) or [] + if len(transform_nodes) < 3: + LOG.warn('Please select least three transform nodes.') + return None + return transform_nodes + + +def create_full_mesh(): + transform_nodes = _get_selection() + if transform_nodes is None: + return + + node = lib.create_mesh_from_transform_nodes( + const.MESH_TYPE_FULL_MESH_VALUE, transform_nodes + ) + + if node is not None and maya.cmds.objExists(node): + maya.cmds.select(node, replace=True) + return + + +def create_border_mesh(): + transform_nodes = _get_selection() + if transform_nodes is None: + return + + node = lib.create_mesh_from_transform_nodes( + const.MESH_TYPE_BORDER_MESH_VALUE, transform_nodes + ) + + if node is not None and maya.cmds.objExists(node): + maya.cmds.select(node, replace=True) + return + + def main(): """ Open the 'Mesh From Points' window. diff --git a/share/config/functions.json b/share/config/functions.json index a67aba452..e7e201598 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -660,6 +660,24 @@ "mmSolver.tools.meshfrompoints.tool.main();" ] }, + "mesh_from_points_full_mesh": { + "name": "Create Full Mesh From Points", + "name_shelf": "FlMshPts", + "tooltip": "Create a triangulated full mesh from the selected nodes.", + "command": [ + "import mmSolver.tools.meshfrompoints.tool;", + "mmSolver.tools.meshfrompoints.tool.create_full_mesh();" + ] + }, + "mesh_from_points_border_mesh": { + "name": "Create Border Mesh From Points", + "name_shelf": "FlMshPts", + "tooltip": "Create a triangulated border mesh from the selected nodes.", + "command": [ + "import mmSolver.tools.meshfrompoints.tool;", + "mmSolver.tools.meshfrompoints.tool.create_border_mesh();" + ] + }, "reparent_under_node": { "name": "Reparent under Node (v1)", "name_shelf": "RePar", diff --git a/share/config/menu.json b/share/config/menu.json index 7cf9d8c7d..f24525721 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -105,6 +105,8 @@ "zdepth_tools/---", "zdepth_tools/screen_z_transform_bake", "mesh_tools/---Create", + "mesh_tools/mesh_from_points_full_mesh", + "mesh_tools/mesh_from_points_border_mesh", "mesh_tools/mesh_from_points_ui", "general_tools/---Parenting", "general_tools/reparent_under_node2", diff --git a/share/config/shelf.json b/share/config/shelf.json index 8efa30ca2..65bc08045 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -102,6 +102,8 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", + "mesh_tools/mesh_tools_popup/mesh_from_points_full_mesh", + "mesh_tools/mesh_tools_popup/mesh_from_points_border_mesh", "mesh_tools/mesh_tools_popup/mesh_from_points_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 89a88c6b7..5739472d5 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -113,6 +113,8 @@ "zdepth_tools/zdepth_popup/---", "zdepth_tools/zdepth_popup/screen_z_transform_bake", "mesh_tools/mesh_tools_popup/---Create", + "mesh_tools/mesh_tools_popup/mesh_from_points_full_mesh", + "mesh_tools/mesh_tools_popup/mesh_from_points_border_mesh", "mesh_tools/mesh_tools_popup/mesh_from_points_ui", "general_tools/gen_tools_popup/---Parenting", "general_tools/gen_tools_popup/reparent_under_node2", From 50c3ed0842103ffc61c1296e481a0a13ab4fcd36 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 21 Sep 2024 23:07:03 +1000 Subject: [PATCH 239/295] Docs - Add Mesh From Points --- docs/source/tools_meshtools.rst | 54 +++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/source/tools_meshtools.rst b/docs/source/tools_meshtools.rst index f943af5ce..af6488b1e 100644 --- a/docs/source/tools_meshtools.rst +++ b/docs/source/tools_meshtools.rst @@ -1,20 +1,70 @@ Mesh Tools ========== -*Write section description here.* +Mesh tools will create, modify or operate on meshes. These tools are +intended to augment Autodesk Maya's existing modeling tool-set and be +specific to the common use cases for MatchMove tasks. .. _mesh-from-points-ref: Mesh From Points ---------------- -*Write mesh from points tool description here* +The `Mesh From Points` tool is used to generate a triangulated mesh +from a set of 3D points. The generated mesh is extremely helpful to +create a 3D visualisation, or as a starting point for a refined +hand-generated model. + +`Mesh From Points` uses a type of `Delaunay Triangulation +`_ named +`Delaunator `_, which is +optimised to generate meshes for landscape meshes. As a result, the +camera viewing position of the points is used to triangulate the +points in camera-space. .. figure:: images/tools_mesh_from_points_window.png :alt: Mesh from Points Window. :align: right :width: 100% +.. note:: September, 2024; The Mesh From Points tools cannot be undone. + This is intended to be fixed in a future release. + +Usage: + +1) Select a transform nodes. + + - At least 3 transform nodes are required. + +2) Activate a Maya viewport, and adjust the camera view to see the + points. + + - If your points represent a landscape, adjust the camera to view + all nodes from the "sky" point of view (from Y+, looking + downwards). + +3) Run `Create Full Mesh From Points` tool. + + - Optionally, use the `Create Border Mesh From Points` or open the + `Mesh From Points` UI. + + - The `Create Border Edge Strip Mesh` button in the `Mesh From + Points` can be used create a mesh with extra geometry at the + edges. This is intended to be used to create a "patch" of + geometry that can be adjusted further using Maya's modelling + tools. Adjust the `Border Edge Strip Width` slider to adjust the + position of the edges. + +To create a mesh from the selected nodes, use this Python command: + +.. code:: python + + import mmSolver.tools.meshfrompoints.tool as tool + tool.create_full_mesh() + + # Or create the border mesh only. + tool.create_border_mesh() + To open the window, use this Python command: .. code:: python From 739df4534dee99bce0cfd7401f90c9a5897d9caa Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 30 Sep 2024 22:18:37 +1000 Subject: [PATCH 240/295] Fix creation of in/outLens attributes. --- python/mmSolver/_api/camera.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/_api/camera.py b/python/mmSolver/_api/camera.py index 0d5fa75f2..6706c66a4 100644 --- a/python/mmSolver/_api/camera.py +++ b/python/mmSolver/_api/camera.py @@ -45,7 +45,8 @@ def _create_camera_attributes(cam_shp): assert isinstance(cam_shp, pycompat.TEXT_TYPE) assert maya.cmds.nodeType(cam_shp) == 'camera' - if maya.cmds.lockNode(cam_shp, query=True) is False: + cam_shp_is_locked = maya.cmds.lockNode(cam_shp, query=True)[0] + if cam_shp_is_locked is True: LOG.warn( 'Cannot create camera attributes, camera shape is locked; cam_shp=%r', cam_shp, @@ -80,7 +81,7 @@ def _create_camera_attributes(cam_shp): def _create_lens_toggle_setup(cam_tfm, cam_shp): - cam_shp_is_locked = maya.cmds.lockNode(cam_shp, query=True) + cam_shp_is_locked = maya.cmds.lockNode(cam_shp, query=True)[0] if cam_shp_is_locked is False: _create_camera_attributes(cam_shp) From 7ab46ee8c18e4109a0069543e7ebfe6e5e4b15d5 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 30 Sep 2024 22:26:19 +1000 Subject: [PATCH 241/295] Image Cache - Add checks and clean up code. Attempt to blindly fix a crash in ImageCache reported on Linux. This change removes redundant code, adds some explicit checks, and fixes some debug print message formatting. --- src/mmSolver/image/ImageCache.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 85a3aad0c..4fac27aaa 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -606,7 +606,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " - << "item_key=" << item_key << "file_path=\"" + << "item_key=" << item_key << " file_path=\"" << file_path.c_str() << "\""); return ImageCache::gpu_find_item(item_key); } @@ -635,7 +635,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " - << "item_key=" << item_key << "file_path=\"" + << "item_key=" << item_key << " file_path=\"" << file_path.c_str() << "\""); return ImageCache::cpu_find_item(item_key); } @@ -663,7 +663,6 @@ CacheEvictionResult ImageCache::gpu_evict_one_item( const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one_item: "); - MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::gpu_evict_one_item: " "before m_gpu_used_bytes=" @@ -675,7 +674,13 @@ CacheEvictionResult ImageCache::gpu_evict_one_item( return CacheEvictionResult::kNotNeeded; } + if (m_gpu_key_list.empty()) { + return CacheEvictionResult::kNotNeeded; + } const GPUCacheKey item_key = m_gpu_key_list.front(); + if (item_key == 0) { + return CacheEvictionResult::kFailed; + } const GPUMapIt item_key_iterator = m_gpu_item_map.find(item_key); assert(item_key_iterator != m_gpu_item_map.end()); @@ -699,7 +704,6 @@ CacheEvictionResult ImageCache::cpu_evict_one_item() { const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one_item: "); - MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::cpu_evict_one_item: " "before m_cpu_used_bytes=" @@ -710,7 +714,13 @@ CacheEvictionResult ImageCache::cpu_evict_one_item() { return CacheEvictionResult::kNotNeeded; } + if (m_cpu_key_list.empty()) { + return CacheEvictionResult::kNotNeeded; + } const CPUCacheKey item_key = m_cpu_key_list.front(); + if (item_key == 0) { + return CacheEvictionResult::kFailed; + } const CPUMapIt item_key_iterator = m_cpu_item_map.find(item_key); assert(item_key_iterator != m_cpu_item_map.end()); @@ -816,7 +826,6 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { << item_key); size_t count = 0; - std::vector group_keys; for (auto it = m_gpu_group_map.begin(); it != m_gpu_group_map.end(); ++it) { const GPUCacheString group_name = it->first; GPUGroupSet &values_set = it->second; @@ -828,7 +837,6 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { const GPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); if (item_key == item_value_hash) { - group_keys.push_back(group_key); it2 = values_set.erase(it2); count += 1; MMSOLVER_MAYA_VRB( @@ -861,7 +869,6 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { << item_key); size_t count = 0; - std::vector group_keys; for (auto it = m_cpu_group_map.begin(); it != m_cpu_group_map.end(); ++it) { const CPUCacheString group_name = it->first; CPUGroupSet &values_set = it->second; @@ -873,7 +880,6 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { const CPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); if (item_key == item_value_hash) { - group_keys.push_back(group_key); it2 = values_set.erase(it2); count += 1; MMSOLVER_MAYA_VRB( From 41f15b72d225406c0c265edc523f627edb51c5fa Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 30 Sep 2024 22:38:16 +1000 Subject: [PATCH 242/295] MM Image Plane - enable verbose logging. This is to help us debug the reported crash on Linux with the MM ImagePlane. --- src/mmSolver/image/ImageCache.cpp | 48 +++++++++++++++---------------- src/mmSolver/image/ImageCache.h | 16 +++++------ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 4fac27aaa..91c5aa416 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -64,7 +64,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, const bool do_texture_update) { assert(texture_manager != nullptr); - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file_path=" << file_path.asChar()); @@ -195,7 +195,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, void ImageCache::set_gpu_capacity_bytes( MHWRender::MTextureManager *texture_manager, const size_t value) { - const bool verbose = false; + const bool verbose = true; m_gpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); @@ -218,7 +218,7 @@ void ImageCache::set_gpu_capacity_bytes( } void ImageCache::set_cpu_capacity_bytes(const size_t value) { - const bool verbose = false; + const bool verbose = true; m_cpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); @@ -301,7 +301,7 @@ void ImageCache::print_cache_brief() const { } void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_group_names: "); out_group_names.clear(); @@ -318,7 +318,7 @@ void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { } void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_group_names: "); out_group_names.clear(); @@ -444,7 +444,7 @@ bool ImageCache::cpu_insert_group(const CPUCacheString &group_name, static void update_texture(MTexture *texture, const ImageCache::CPUCacheValue &image_pixel_data) { - const bool verbose = false; + const bool verbose = true; // No need for MIP-maps. const bool generate_mip_maps = false; @@ -470,7 +470,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( const ImageCache::CPUCacheValue &image_pixel_data) { assert(texture_manager != nullptr); assert(image_pixel_data.is_valid()); - const bool verbose = false; + const bool verbose = true; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -553,7 +553,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, const CPUCacheString &file_path, const CPUCacheValue &image_pixel_data) { - const bool verbose = false; + const bool verbose = true; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -602,7 +602,7 @@ bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheString &file_path) { - const bool verbose = false; + const bool verbose = true; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " @@ -613,7 +613,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " << "item_key=" << item_key); @@ -631,7 +631,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheString &file_path) { - const bool verbose = false; + const bool verbose = true; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " @@ -642,7 +642,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " << "item_key=" << item_key); @@ -660,7 +660,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( CacheEvictionResult ImageCache::gpu_evict_one_item( MHWRender::MTextureManager *texture_manager) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one_item: "); MMSOLVER_MAYA_VRB( @@ -701,7 +701,7 @@ CacheEvictionResult ImageCache::gpu_evict_one_item( } CacheEvictionResult ImageCache::cpu_evict_one_item() { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one_item: "); MMSOLVER_MAYA_VRB( @@ -743,7 +743,7 @@ CacheEvictionResult ImageCache::cpu_evict_one_item() { CacheEvictionResult ImageCache::gpu_evict_enough_for_new_item( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_item: "); @@ -781,7 +781,7 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_item( CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( const size_t new_memory_chunk_size) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_item: "); @@ -818,7 +818,7 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( } size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::gpu_remove_item_from_group: " @@ -861,7 +861,7 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { } size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::cpu_remove_item_from_group: " @@ -905,7 +905,7 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheString &file_path) { - const bool verbose = false; + const bool verbose = true; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "item_key=" << item_key << "file_path=\"" @@ -915,7 +915,7 @@ bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "item_key=" << item_key); @@ -939,7 +939,7 @@ bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, } bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { - const bool verbose = false; + const bool verbose = true; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "item_key=" << item_key << "file_path=\"" @@ -948,7 +948,7 @@ bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { } bool ImageCache::cpu_erase_item(const CPUCacheKey item_key) { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "item_key=" << item_key); @@ -974,7 +974,7 @@ bool ImageCache::cpu_erase_item(const CPUCacheKey item_key) { size_t ImageCache::gpu_erase_group_items( MHWRender::MTextureManager *texture_manager, const GPUCacheString &group_name) { - const bool verbose = false; + const bool verbose = true; if (verbose) { const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -1005,7 +1005,7 @@ size_t ImageCache::gpu_erase_group_items( } size_t ImageCache::cpu_erase_group_items(const CPUCacheString &group_name) { - const bool verbose = false; + const bool verbose = true; if (verbose) { const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 107caf208..b44d517ca 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -172,13 +172,13 @@ struct ImageCache { public: // Get the capacity of the cache. size_t get_gpu_capacity_bytes() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); return m_gpu_capacity_bytes; } size_t get_cpu_capacity_bytes() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); return m_cpu_capacity_bytes; @@ -186,13 +186,13 @@ struct ImageCache { // Get amount of bytes used by the cache. size_t get_gpu_used_bytes() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_used_bytes: " << "m_gpu_used_bytes=" << m_gpu_used_bytes); return m_gpu_used_bytes; } size_t get_cpu_used_bytes() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_used_bytes: " << "m_cpu_used_bytes=" << m_cpu_used_bytes); return m_cpu_used_bytes; @@ -200,13 +200,13 @@ struct ImageCache { // Get the number of items in the cache. size_t get_gpu_item_count() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_item_count: " << "m_gpu_item_map.size()=" << m_gpu_item_map.size()); return m_gpu_item_map.size(); } size_t get_cpu_item_count() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_item_count: " << "m_cpu_item_map.size()=" << m_cpu_item_map.size()); return m_cpu_item_map.size(); @@ -223,14 +223,14 @@ struct ImageCache { // Get the number of groups in the cache. size_t get_gpu_group_count() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_group_count: " << "m_gpu_group_map.size()=" << m_gpu_group_map.size()); return m_gpu_group_map.size(); } size_t get_cpu_group_count() const { - const bool verbose = false; + const bool verbose = true; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_group_count: " << "m_cpu_group_map.size()=" << m_cpu_group_map.size()); From 3a61f4fc8593d560bf99cc48c3fffb19d04213be Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Mon, 30 Sep 2024 23:28:07 +1000 Subject: [PATCH 243/295] MM ImagePlane - 'sourceImages' file rule may not exist. --- python/mmSolver/tools/createimageplane/tool.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/tool.py b/python/mmSolver/tools/createimageplane/tool.py index c5a400208..609029627 100644 --- a/python/mmSolver/tools/createimageplane/tool.py +++ b/python/mmSolver/tools/createimageplane/tool.py @@ -40,18 +40,22 @@ def _get_start_directory(): + fallback_path = os.getcwd() + workspace_path = maya.cmds.workspace(query=True, fullName=True) if workspace_path is None: - return os.getcwd() + return fallback_path workspace_path = os.path.abspath(workspace_path) file_rules = maya.cmds.workspace(query=True, fileRule=True) if file_rules is None: - return os.getcwd() + return fallback_path file_rule_names = file_rules[0::2] file_rule_values = file_rules[1::2] file_rule = 'sourceImages' + if file_rule not in file_rule_names: + return fallback_path file_rule_index = file_rule_names.index(file_rule) dir_name = file_rule_values[file_rule_index] From 29fd63b023d8061b47528b5f5ce0919aa471a8e9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 1 Oct 2024 00:28:55 +1000 Subject: [PATCH 244/295] Image Cache - Fix image erasure crash on Linux. --- src/mmSolver/image/ImageCache.cpp | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 91c5aa416..8f1c09938 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -826,14 +826,16 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { << item_key); size_t count = 0; - for (auto it = m_gpu_group_map.begin(); it != m_gpu_group_map.end(); ++it) { + for (auto it = m_gpu_group_map.begin(); it != m_gpu_group_map.end(); + /* no increment */) { const GPUCacheString group_name = it->first; GPUGroupSet &values_set = it->second; const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); // NOTE: This is a O(n) linear operation. - for (auto it2 = values_set.begin(); it2 != values_set.end(); ++it2) { + for (auto it2 = values_set.begin(); it2 != values_set.end(); + /* no increment */) { const GPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); if (item_key == item_value_hash) { @@ -847,11 +849,17 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "item_key=" << item_key << " item_value_hash=" << item_value_hash); + ++it2; } } + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " + << "values_set.size()=" << values_set.size()); + if (values_set.size() == 0) { it = m_gpu_group_map.erase(it); + } else { + ++it; } } MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " @@ -869,14 +877,16 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { << item_key); size_t count = 0; - for (auto it = m_cpu_group_map.begin(); it != m_cpu_group_map.end(); ++it) { + for (auto it = m_cpu_group_map.begin(); it != m_cpu_group_map.end(); + /* no increment */) { const CPUCacheString group_name = it->first; CPUGroupSet &values_set = it->second; const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); // NOTE: This is a O(n) linear operation. - for (auto it2 = values_set.begin(); it2 != values_set.end(); ++it2) { + for (auto it2 = values_set.begin(); it2 != values_set.end(); + /* no increment */) { const CPUGroupKey item_value_hash = mmsolver::hash::make_hash(*it2); if (item_key == item_value_hash) { @@ -890,11 +900,17 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "item_key=" << item_key << " item_value_hash=" << item_value_hash); + ++it2; } } + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " + << "values_set.size()=" << values_set.size()); + if (values_set.size() == 0) { it = m_cpu_group_map.erase(it); + } else { + ++it; } } MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " From 66889b5154ea9c8f9be220c96144a00147c2c2e9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 1 Oct 2024 00:31:17 +1000 Subject: [PATCH 245/295] Image Cache - disable verbose logging The bug has been fixed that required looking into the details of how the image cache worked. --- src/mmSolver/image/ImageCache.cpp | 48 +++++++++++++++---------------- src/mmSolver/image/ImageCache.h | 16 +++++------ 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 8f1c09938..b86c2905b 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -64,7 +64,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, const bool do_texture_update) { assert(texture_manager != nullptr); - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache: read_texture_image_file:" << " file_path=" << file_path.asChar()); @@ -195,7 +195,7 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, void ImageCache::set_gpu_capacity_bytes( MHWRender::MTextureManager *texture_manager, const size_t value) { - const bool verbose = true; + const bool verbose = false; m_gpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); @@ -218,7 +218,7 @@ void ImageCache::set_gpu_capacity_bytes( } void ImageCache::set_cpu_capacity_bytes(const size_t value) { - const bool verbose = true; + const bool verbose = false; m_cpu_capacity_bytes = value; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::set_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); @@ -301,7 +301,7 @@ void ImageCache::print_cache_brief() const { } void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_group_names: "); out_group_names.clear(); @@ -318,7 +318,7 @@ void ImageCache::gpu_group_names(GPUVectorString &out_group_names) const { } void ImageCache::cpu_group_names(CPUVectorString &out_group_names) const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_group_names: "); out_group_names.clear(); @@ -444,7 +444,7 @@ bool ImageCache::cpu_insert_group(const CPUCacheString &group_name, static void update_texture(MTexture *texture, const ImageCache::CPUCacheValue &image_pixel_data) { - const bool verbose = true; + const bool verbose = false; // No need for MIP-maps. const bool generate_mip_maps = false; @@ -470,7 +470,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( const ImageCache::CPUCacheValue &image_pixel_data) { assert(texture_manager != nullptr); assert(image_pixel_data.is_valid()); - const bool verbose = true; + const bool verbose = false; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -553,7 +553,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_insert_item( bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, const CPUCacheString &file_path, const CPUCacheValue &image_pixel_data) { - const bool verbose = true; + const bool verbose = false; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -602,7 +602,7 @@ bool ImageCache::cpu_insert_item(const CPUCacheString &group_name, ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " @@ -613,7 +613,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( ImageCache::GPUCacheValue ImageCache::gpu_find_item( const GPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_find_item: " << "item_key=" << item_key); @@ -631,7 +631,7 @@ ImageCache::GPUCacheValue ImageCache::gpu_find_item( ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " @@ -642,7 +642,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( ImageCache::CPUCacheValue ImageCache::cpu_find_item( const CPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_find_item: " << "item_key=" << item_key); @@ -660,7 +660,7 @@ ImageCache::CPUCacheValue ImageCache::cpu_find_item( CacheEvictionResult ImageCache::gpu_evict_one_item( MHWRender::MTextureManager *texture_manager) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_one_item: "); MMSOLVER_MAYA_VRB( @@ -701,7 +701,7 @@ CacheEvictionResult ImageCache::gpu_evict_one_item( } CacheEvictionResult ImageCache::cpu_evict_one_item() { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_one_item: "); MMSOLVER_MAYA_VRB( @@ -743,7 +743,7 @@ CacheEvictionResult ImageCache::cpu_evict_one_item() { CacheEvictionResult ImageCache::gpu_evict_enough_for_new_item( MHWRender::MTextureManager *texture_manager, const size_t new_memory_chunk_size) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_evict_enough_for_new_item: "); @@ -781,7 +781,7 @@ CacheEvictionResult ImageCache::gpu_evict_enough_for_new_item( CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( const size_t new_memory_chunk_size) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_evict_enough_for_new_item: "); @@ -818,7 +818,7 @@ CacheEvictionResult ImageCache::cpu_evict_enough_for_new_item( } size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::gpu_remove_item_from_group: " @@ -869,7 +869,7 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { } size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB( "mmsolver::ImageCache::cpu_remove_item_from_group: " @@ -921,7 +921,7 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "item_key=" << item_key << "file_path=\"" @@ -931,7 +931,7 @@ bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const GPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " << "item_key=" << item_key); @@ -955,7 +955,7 @@ bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, } bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { - const bool verbose = true; + const bool verbose = false; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "item_key=" << item_key << "file_path=\"" @@ -964,7 +964,7 @@ bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { } bool ImageCache::cpu_erase_item(const CPUCacheKey item_key) { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " << "item_key=" << item_key); @@ -990,7 +990,7 @@ bool ImageCache::cpu_erase_item(const CPUCacheKey item_key) { size_t ImageCache::gpu_erase_group_items( MHWRender::MTextureManager *texture_manager, const GPUCacheString &group_name) { - const bool verbose = true; + const bool verbose = false; if (verbose) { const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); @@ -1021,7 +1021,7 @@ size_t ImageCache::gpu_erase_group_items( } size_t ImageCache::cpu_erase_group_items(const CPUCacheString &group_name) { - const bool verbose = true; + const bool verbose = false; if (verbose) { const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index b44d517ca..107caf208 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -172,13 +172,13 @@ struct ImageCache { public: // Get the capacity of the cache. size_t get_gpu_capacity_bytes() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_capacity_bytes: " << "m_gpu_capacity_bytes=" << m_gpu_capacity_bytes); return m_gpu_capacity_bytes; } size_t get_cpu_capacity_bytes() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_capacity_bytes: " << "m_cpu_capacity_bytes=" << m_cpu_capacity_bytes); return m_cpu_capacity_bytes; @@ -186,13 +186,13 @@ struct ImageCache { // Get amount of bytes used by the cache. size_t get_gpu_used_bytes() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_used_bytes: " << "m_gpu_used_bytes=" << m_gpu_used_bytes); return m_gpu_used_bytes; } size_t get_cpu_used_bytes() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_used_bytes: " << "m_cpu_used_bytes=" << m_cpu_used_bytes); return m_cpu_used_bytes; @@ -200,13 +200,13 @@ struct ImageCache { // Get the number of items in the cache. size_t get_gpu_item_count() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_item_count: " << "m_gpu_item_map.size()=" << m_gpu_item_map.size()); return m_gpu_item_map.size(); } size_t get_cpu_item_count() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_item_count: " << "m_cpu_item_map.size()=" << m_cpu_item_map.size()); return m_cpu_item_map.size(); @@ -223,14 +223,14 @@ struct ImageCache { // Get the number of groups in the cache. size_t get_gpu_group_count() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_gpu_group_count: " << "m_gpu_group_map.size()=" << m_gpu_group_map.size()); return m_gpu_group_map.size(); } size_t get_cpu_group_count() const { - const bool verbose = true; + const bool verbose = false; MMSOLVER_MAYA_VRB("mmsolver::ImageCache::get_cpu_group_count: " << "m_cpu_group_map.size()=" << m_cpu_group_map.size()); From e52cde3e338ba8d2566a1f4d3a8e48d35a048a0d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 2 Oct 2024 01:57:34 +1000 Subject: [PATCH 246/295] Add lens save file to shelf menu. --- share/config/menu.json | 1 + share/config/shelf.json | 1 + share/config/shelf_minimal.json | 1 + 3 files changed, 3 insertions(+) diff --git a/share/config/menu.json b/share/config/menu.json index f24525721..6ee326983 100644 --- a/share/config/menu.json +++ b/share/config/menu.json @@ -46,6 +46,7 @@ "camera_tools/---Input Output", "camera_tools/copy_camera_to_clipboard", "camera_tools/load_lens", + "camera_tools/save_lens_file", "marker_tools/---Edit Marker", "marker_tools/toggle_marker_lock", "marker_tools/place_marker_manipulator", diff --git a/share/config/shelf.json b/share/config/shelf.json index 65bc08045..5554df292 100644 --- a/share/config/shelf.json +++ b/share/config/shelf.json @@ -40,6 +40,7 @@ "camera_tools/camera_popup/---Input Output", "camera_tools/camera_popup/copy_camera_to_clipboard", "camera_tools/camera_popup/load_lens", + "camera_tools/camera_popup/save_lens_file", "marker_tools/marker_popup/---Edit Marker", "marker_tools/marker_popup/toggle_marker_lock", "marker_tools/marker_popup/place_marker_manipulator", diff --git a/share/config/shelf_minimal.json b/share/config/shelf_minimal.json index 5739472d5..c9485f36c 100644 --- a/share/config/shelf_minimal.json +++ b/share/config/shelf_minimal.json @@ -52,6 +52,7 @@ "camera_tools/camera_popup/---Input Output", "camera_tools/camera_popup/copy_camera_to_clipboard", "camera_tools/camera_popup/load_lens", + "camera_tools/camera_popup/save_lens_file", "marker_tools/marker_popup/---Edit Marker", "marker_tools/marker_popup/toggle_marker_lock", "marker_tools/marker_popup/place_marker_manipulator", From eeadb61ffacb9f1643b322326cd000866b4c26a1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 2 Oct 2024 02:16:03 +1000 Subject: [PATCH 247/295] MM Image Plane - Fix integer byte type --- python/mmSolver/tools/createimageplane/_lib/format.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/mmSolver/tools/createimageplane/_lib/format.py b/python/mmSolver/tools/createimageplane/_lib/format.py index 7bb01f199..6a2e40681 100644 --- a/python/mmSolver/tools/createimageplane/_lib/format.py +++ b/python/mmSolver/tools/createimageplane/_lib/format.py @@ -27,6 +27,7 @@ import mmSolver.logger import mmSolver.utils.constant as const_utils +import mmSolver.utils.python_compat as pycompat import mmSolver.tools.imagecache.lib as lib import mmSolver.tools.createimageplane._lib.constant as imageplane_const import mmSolver.tools.createimageplane._lib.mmimageplane_v2 as imageplane_lib @@ -67,8 +68,8 @@ def _format_cache_used(used_bytes, capacity_bytes): Example text: ' 42.1% (3.53 GB) of 8.00 GB' """ - assert isinstance(used_bytes, int) - assert isinstance(capacity_bytes, int) + assert isinstance(used_bytes, pycompat.INT_TYPES) + assert isinstance(capacity_bytes, pycompat.INT_TYPES) usage_percent = 0 usage_gigabytes = 0 From 4d259fe1cbdfe61a2adbfb714869143f0be28af1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Wed, 2 Oct 2024 02:38:41 +1000 Subject: [PATCH 248/295] Rename ImagePixelBuffer::new() to ::empty() Because the method creates an empty ImagePixelBuffer struct object. --- lib/cppbind/mmimage/src/imagepixelbuffer.rs | 6 +++--- lib/rust/mmimage/src/pixelbuffer.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/cppbind/mmimage/src/imagepixelbuffer.rs b/lib/cppbind/mmimage/src/imagepixelbuffer.rs index 96de7add2..45224d29c 100644 --- a/lib/cppbind/mmimage/src/imagepixelbuffer.rs +++ b/lib/cppbind/mmimage/src/imagepixelbuffer.rs @@ -50,9 +50,9 @@ pub struct ShimImagePixelBuffer { } impl ShimImagePixelBuffer { - pub fn new() -> Self { + pub fn empty() -> Self { Self { - inner: CoreImagePixelBuffer::new(), + inner: CoreImagePixelBuffer::empty(), } } @@ -129,5 +129,5 @@ impl ShimImagePixelBuffer { } pub fn shim_create_image_pixel_buffer_box() -> Box { - Box::new(ShimImagePixelBuffer::new()) + Box::new(ShimImagePixelBuffer::empty()) } diff --git a/lib/rust/mmimage/src/pixelbuffer.rs b/lib/rust/mmimage/src/pixelbuffer.rs index ca1979d5f..bdc4aae3c 100644 --- a/lib/rust/mmimage/src/pixelbuffer.rs +++ b/lib/rust/mmimage/src/pixelbuffer.rs @@ -46,7 +46,7 @@ pub struct ImagePixelBuffer { } impl ImagePixelBuffer { - pub fn new() -> ImagePixelBuffer { + pub fn empty() -> ImagePixelBuffer { ImagePixelBuffer { data_type: BufferDataType::None, image_width: 0, From b4d851570772cb8bd9eb3ebddced2b5b28031a03 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 08:15:25 +1000 Subject: [PATCH 249/295] Camera Context Menu - Users can view with created nodes Select nodes and view in the Attribute Editor. This change is intended to allow users to view the created nodes and interact with them faster - before this change the nodes were hidden and annoying to work with. --- .../tools/cameracontextmenu/constant.py | 24 +++++++++++++++---- .../tools/cameracontextmenu/lib/camera.py | 2 -- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/python/mmSolver/tools/cameracontextmenu/constant.py b/python/mmSolver/tools/cameracontextmenu/constant.py index 7c748d23a..ed0667553 100644 --- a/python/mmSolver/tools/cameracontextmenu/constant.py +++ b/python/mmSolver/tools/cameracontextmenu/constant.py @@ -107,7 +107,13 @@ CREATE_CAMERA_TOOLTIP = 'Create a camera.' CREATE_CAMERA_CMD_LANG = 'python' CREATE_CAMERA_CMD = ( - 'import mmSolver.tools.cameracontextmenu.lib as lib;' 'lib.create_camera();' + 'import maya.cmds;' + 'import mmSolver.tools.cameracontextmenu.lib as lib;' + 'cam = lib.create_camera();' + 'cam_tfm = cam.get_transform_node();' + 'cam_shp = cam.get_shape_node();' + 'maya.cmds.select(cam_tfm, replace=True);' + 'lib.open_node_in_attribute_editor(node=cam_shp);' ) @@ -115,8 +121,11 @@ CREATE_IMAGE_PLANE_TOOLTIP = 'Create MM Image Plane on {camera_shape_name}.' CREATE_IMAGE_PLANE_CMD_LANG = 'python' CREATE_IMAGE_PLANE_CMD = ( + 'import maya.cmds;' 'import mmSolver.tools.cameracontextmenu.lib as lib;' - 'lib.create_image_plane(camera_shape_node="{camera_shape_node}");' + 'tfm, shp = lib.create_image_plane(camera_shape_node="{camera_shape_node}");' + 'maya.cmds.select(shp, replace=True);' + 'lib.open_node_in_attribute_editor(node=shp);' ) @@ -124,6 +133,13 @@ CREATE_LENS_TOOLTIP = 'Create Lens Layer on {camera_shape_name}.' CREATE_LENS_CMD_LANG = 'python' CREATE_LENS_CMD = ( - 'import mmSolver.tools.cameracontextmenu.lib as lib;' - 'lib.create_lens(camera_shape_node="{camera_shape_node}");' + 'import maya.cmds\n' + 'import mmSolver.tools.cameracontextmenu.lib as lib\n' + 'lens = lib.create_lens(camera_shape_node="{camera_shape_node}")\n' + 'node = None\n' + 'if lens is not None:\n' + ' node = lens.get_node()\n' + 'if node is not None:\n' + ' maya.cmds.select(node, replace=True)\n' + ' lib.open_node_in_attribute_editor(node=node)\n' ) diff --git a/python/mmSolver/tools/cameracontextmenu/lib/camera.py b/python/mmSolver/tools/cameracontextmenu/lib/camera.py index 3ecc609db..70eee0853 100644 --- a/python/mmSolver/tools/cameracontextmenu/lib/camera.py +++ b/python/mmSolver/tools/cameracontextmenu/lib/camera.py @@ -31,8 +31,6 @@ def create_camera(): cam = createcamera_lib.create_camera() - cam_shp = cam.get_shape_node() - maya.cmds.select(cam_shp, replace=True) return cam From 650c426307757001c06c089bb38a1514f326b68f Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 08:16:22 +1000 Subject: [PATCH 250/295] MM Image Plane - Open image plane in attribute editor. --- python/mmSolver/tools/createimageplane/tool.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/python/mmSolver/tools/createimageplane/tool.py b/python/mmSolver/tools/createimageplane/tool.py index 609029627..2632e5c62 100644 --- a/python/mmSolver/tools/createimageplane/tool.py +++ b/python/mmSolver/tools/createimageplane/tool.py @@ -39,6 +39,14 @@ LOG = mmSolver.logger.get_logger() +# Function from +# ./python/mmSolver/tools/cameracontextmenu/lib/utilities.py +def _open_node_in_attribute_editor(node): + mel_cmd = 'showEditorExact "{node}";'.format(node=node) + maya.mel.eval(mel_cmd) + return + + def _get_start_directory(): fallback_path = os.getcwd() @@ -165,7 +173,7 @@ def _run(version): # Show the last node in the attribute editor. node = nodes[-1] - maya.mel.eval('updateAE("{}");'.format(node)) + _open_node_in_attribute_editor(node) else: maya.cmds.select(sel, replace=True) return From 625ae74f820d457c3d171be2c4d4644c48c748ee Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 08:22:15 +1000 Subject: [PATCH 251/295] Solver Window - Remove unused menu code --- .../mmSolver/tools/solver/ui/solver_window.py | 148 ------------------ 1 file changed, 148 deletions(-) diff --git a/python/mmSolver/tools/solver/ui/solver_window.py b/python/mmSolver/tools/solver/ui/solver_window.py index b6645055f..9ebfa5d02 100644 --- a/python/mmSolver/tools/solver/ui/solver_window.py +++ b/python/mmSolver/tools/solver/ui/solver_window.py @@ -180,83 +180,6 @@ def addMenuBarContents(self, menubar): action.triggered.connect(self.solverPrefWindowTriggeredCB) edit_menu.addAction(action) - # if Qt.IsPySide2 or Qt.IsPyQt5: - # edit_menu.addSection('Undo / Redo') - - # # Undo - # label = 'Undo last command (with disabled viewport)' - # tooltip = ( - # 'Undo the Maya scene state, without updating the viewport or solver UI' - # ) - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.triggered.connect(self.undoTriggeredCB) - # edit_menu.addAction(action) - - # # Redo - # label = 'Redo last command (with disabled viewport)' - # tooltip = ( - # 'Redo the Maya scene state, without updating the viewport or solver UI' - # ) - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.triggered.connect(self.redoTriggeredCB) - # edit_menu.addAction(action) - - # if Qt.IsPySide2 or Qt.IsPyQt5: - # edit_menu.addSection('Window Update') - - # # Auto Update Solver Validation - # label = 'Auto-Update Solver Validation' - # tooltip = 'Auto-update details of the solver parameter/error numbers.' - # value = lib_state.get_auto_update_solver_validation_state() - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(value) - # action.toggled.connect( - # self.subForm.solver_settings.autoUpdateSolverValidationChanged - # ) - # edit_menu.addAction(action) - - # if Qt.IsPySide2 or Qt.IsPyQt5: - # edit_menu.addSection('Solver Execution') - - # # Pre-Solve Force Evaluation - # label = 'Pre-Solve Force Evaluation' - # tooltip = 'Before starting a solve, update the scene to force an evaluation.' - # pre_solve_force_eval = lib_state.get_pre_solve_force_eval_state() - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(pre_solve_force_eval) - # action.toggled.connect(type(self).preSolveForceEvalActionToggledCB) - # edit_menu.addAction(action) - - # # Refresh Viewport During Solve - # label = 'Refresh Viewport' - # tooltip = 'Refresh the viewport while Solving.' - # refresh_value = lib_state.get_refresh_viewport_state() - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(refresh_value) - # action.toggled.connect(type(self).refreshActionToggledCB) - # edit_menu.addAction(action) - - # # Force DG evaluation. - # label = 'Force DG Update' - # tooltip = 'Force Maya DG Evaluation while solving.' - # force_dg_update_value = lib_state.get_force_dg_update_state() - # action = QtWidgets.QAction(label, edit_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(force_dg_update_value) - # action.toggled.connect(type(self).forceDgUpdateActionToggledCB) - # edit_menu.addAction(action) - - # menubar.addMenu(edit_menu) - # View Menu view_menu = QtWidgets.QMenu('View', menubar) view_menu.setTearOffEnabled(True) @@ -341,45 +264,6 @@ def addMenuBarContents(self, menubar): ) view_menu.addAction(action) - if Qt.IsPySide2 or Qt.IsPyQt5: - view_menu.addSection('During Solve') - - # # Display the Image Planes while solving. - # # - # # TODO: Add other object types to show/hide while solving, - # # such as camera, nurbsCurves, nurbsSurfaces, and locators. - # label = 'Display Image Planes' - # tooltip = 'Display Image Planes while solving.' - # value = lib_state.get_display_image_plane_while_solving_state() - # action = QtWidgets.QAction(label, view_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(value) - # action.toggled.connect(type(self).displayImagePlaneWhileSolvingActionToggledCB) - # view_menu.addAction(action) - - # # Display the Meshes while solving. - # label = 'Display Meshes' - # tooltip = 'Display Meshes while solving.' - # value = lib_state.get_display_meshes_while_solving_state() - # action = QtWidgets.QAction(label, view_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(value) - # action.toggled.connect(type(self).displayMeshesWhileSolvingActionToggledCB) - # view_menu.addAction(action) - - # # Isolate Objects while solving - # label = 'Isolate Objects' - # tooltip = 'Isolate visibility of all Markers and Bundles while solving.' - # isolate_value = lib_state.get_isolate_object_while_solving_state() - # action = QtWidgets.QAction(label, view_menu) - # action.setStatusTip(tooltip) - # action.setCheckable(True) - # action.setChecked(isolate_value) - # action.toggled.connect(type(self).isolateObjectWhileSolvingActionToggledCB) - # view_menu.addAction(action) - menubar.addMenu(view_menu) # Log Menu @@ -508,38 +392,6 @@ def triggerOutputAttributesUpdate(self): self.subForm.attribute_browser.dataChanged.emit() return - # def undoTriggeredCB(self): - # LOG.debug('undoTriggeredCB') - # import mmSolver.tools.undoredoscene.tool as undoredoscene_tool - - # validation = lib_state.get_auto_update_solver_validation_state() - # with undo_utils.no_undo_context(): - # lib_state.set_auto_update_solver_validation_state(False) - # block = self.blockSignals(True) - # try: - # undoredoscene_tool.main_undo() - # finally: - # with undo_utils.no_undo_context(): - # lib_state.set_auto_update_solver_validation_state(validation) - # self.blockSignals(block) - # return - - # def redoTriggeredCB(self): - # LOG.debug('redoTriggeredCB') - # import mmSolver.tools.undoredoscene.tool as undoredoscene_tool - - # validation = lib_state.get_auto_update_solver_validation_state() - # with undo_utils.no_undo_context(): - # lib_state.set_auto_update_solver_validation_state(False) - # block = self.blockSignals(True) - # try: - # undoredoscene_tool.main_redo() - # finally: - # with undo_utils.no_undo_context(): - # lib_state.set_auto_update_solver_validation_state(validation) - # self.blockSignals(block) - # return - def solverPrefWindowTriggeredCB(self): LOG.debug('solverPrefWindowTriggeredCB') return From 91c1de1a10cc87dda79fa45b83904e8c2788bfbf Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 15:14:32 +1000 Subject: [PATCH 252/295] MM Image Cache - Fix erasing group items When a user clears all the memory in a shot, the underlying ImageCache::m_cpu_group_map would be changed, but the code is iteratiting over it, so we must manually check the group exists each time, and remove the first value each time. This probably has a performance impact, but it's required for stability and safety. --- src/mmSolver/cmd/MMImageCacheCmd.cpp | 66 ++++++++++++++- src/mmSolver/image/ImageCache.cpp | 116 +++++++++++++++++---------- 2 files changed, 136 insertions(+), 46 deletions(-) diff --git a/src/mmSolver/cmd/MMImageCacheCmd.cpp b/src/mmSolver/cmd/MMImageCacheCmd.cpp index 624c973cb..dc8224b7c 100644 --- a/src/mmSolver/cmd/MMImageCacheCmd.cpp +++ b/src/mmSolver/cmd/MMImageCacheCmd.cpp @@ -195,6 +195,7 @@ MStatus MMImageCacheCmd::parseArgs(const MArgList &args) { MStringArray string_objects; argData.getObjects(string_objects); + // TODO: Do we actually use the group name? Or can it be removed? m_group_name = ""; if (string_objects.length() > 0) { const std::string item_name = string_objects[0].asChar(); @@ -408,32 +409,73 @@ inline MStatus set_values(image::ImageCache &image_cache, const std::vector &item_names, const std::string &group_name, size_t &out_item_count) { + const bool verbose = false; MStatus status = MStatus::kSuccess; + const auto command_flag_int = static_cast(command_flag); + if (verbose) { + std::stringstream item_names_ss; + for (auto it = item_names.begin(); it != item_names.end(); it++) { + auto item_name = *it; + item_names_ss << item_name << ";"; + } + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "command_flag=" << command_flag_int + << " gpu_capacity_bytes=" << gpu_capacity_bytes + << " cpu_capacity_bytes=" << cpu_capacity_bytes + << " item_names=" << item_names_ss.str() + << " item_names.size()=" << item_names.size() + << " group_name=" << group_name.c_str() + << " group_name.size()=" << group_name.size()); + } + out_item_count = 0; if (command_flag == ImageCacheFlagMode::kGpuCapacity) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << GPU_CAPACITY_FLAG_LONG << "\""); + MHWRender::MTextureManager *texture_manager = nullptr; status = get_texture_manager(texture_manager); CHECK_MSTATUS_AND_RETURN_IT(status); image_cache.set_gpu_capacity_bytes(texture_manager, gpu_capacity_bytes); } else if (command_flag == ImageCacheFlagMode::kCpuCapacity) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << CPU_CAPACITY_FLAG_LONG << "\""); + image_cache.set_cpu_capacity_bytes(cpu_capacity_bytes); } else if (command_flag == ImageCacheFlagMode::kGpuEraseItems) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << GPU_ERASE_ITEMS_FLAG_LONG << "\""); + MHWRender::MTextureManager *texture_manager = nullptr; status = get_texture_manager(texture_manager); CHECK_MSTATUS_AND_RETURN_IT(status); for (auto it = item_names.begin(); it != item_names.end(); it++) { - const bool ok = image_cache.gpu_erase_item(texture_manager, *it); + auto item_name = *it; + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "item_name=\"" << item_name.c_str() << "\""); + const bool ok = + image_cache.gpu_erase_item(texture_manager, item_name); out_item_count += static_cast(ok); } } else if (command_flag == ImageCacheFlagMode::kCpuEraseItems) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << CPU_ERASE_ITEMS_FLAG_LONG << "\""); + for (auto it = item_names.begin(); it != item_names.end(); it++) { - const bool ok = image_cache.cpu_erase_item(*it); + auto item_name = *it; + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "item_name=\"" << item_name.c_str() << "\""); + const bool ok = image_cache.cpu_erase_item(item_name); out_item_count += static_cast(ok); } } else if (command_flag == ImageCacheFlagMode::kGpuEraseGroupItems) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << CPU_ERASE_GROUP_ITEMS_FLAG_LONG + << "\""); + if (group_name.size() == 0) { MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " << "\"" << GPU_ERASE_GROUP_ITEMS_FLAG_LONG @@ -449,10 +491,17 @@ inline MStatus set_values(image::ImageCache &image_cache, CHECK_MSTATUS_AND_RETURN_IT(status); for (auto it = item_names.begin(); it != item_names.end(); it++) { + auto item_name = *it; + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "item_name=\"" << item_name.c_str() << "\""); out_item_count += - image_cache.gpu_erase_group_items(texture_manager, *it); + image_cache.gpu_erase_group_items(texture_manager, item_name); } } else if (command_flag == ImageCacheFlagMode::kCpuEraseGroupItems) { + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "flag=\"" << CPU_ERASE_GROUP_ITEMS_FLAG_LONG + << "\""); + if (group_name.size() == 0) { MMSOLVER_MAYA_ERR("MMImageCacheCmd::set_values: " << "\"" << CPU_ERASE_GROUP_ITEMS_FLAG_LONG @@ -463,8 +512,14 @@ inline MStatus set_values(image::ImageCache &image_cache, return MStatus::kFailure; } + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "group_name=\"" << group_name.c_str() << "\""); + for (auto it = item_names.begin(); it != item_names.end(); it++) { - out_item_count = image_cache.cpu_erase_group_items(*it); + auto item_name = *it; + MMSOLVER_MAYA_VRB("MMImageCacheCmd::set_values: " + << "item_name=\"" << item_name.c_str() << "\""); + out_item_count += image_cache.cpu_erase_group_items(item_name); } } else { MMSOLVER_MAYA_ERR( @@ -475,6 +530,9 @@ inline MStatus set_values(image::ImageCache &image_cache, return MStatus::kFailure; } + MMSOLVER_MAYA_VRB( + "MMImageCacheCmd::set_values: out_item_count=" << out_item_count); + return status; } diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index b86c2905b..1c0375e56 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -842,18 +842,19 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { it2 = values_set.erase(it2); count += 1; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::gpu_erase_item: " + "mmsolver::ImageCache::gpu_remove_item_from_group: " << "group_key=" << group_key << " item_key=" << item_key << " item_value_hash=" << item_value_hash << " - got it"); } else { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " - << "item_key=" << item_key - << " item_value_hash=" << item_value_hash); + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::gpu_remove_item_from_group: " + << "item_key=" << item_key + << " item_value_hash=" << item_value_hash); ++it2; } } - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_remove_item_from_group: " << "values_set.size()=" << values_set.size()); if (values_set.size() == 0) { @@ -862,7 +863,7 @@ size_t ImageCache::gpu_remove_item_from_group(const GPUCacheKey item_key) { ++it; } } - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_remove_item_from_group: " << "count=" << count); return count; @@ -893,18 +894,19 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { it2 = values_set.erase(it2); count += 1; MMSOLVER_MAYA_VRB( - "mmsolver::ImageCache::cpu_erase_item: " + "mmsolver::ImageCache::cpu_remove_item_from_group: " << "group_key=" << group_key << " item_key=" << item_key << " item_value_hash=" << item_value_hash << " - got it"); } else { - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " - << "item_key=" << item_key - << " item_value_hash=" << item_value_hash); + MMSOLVER_MAYA_VRB( + "mmsolver::ImageCache::cpu_remove_item_from_group: " + << "item_key=" << item_key + << " item_value_hash=" << item_value_hash); ++it2; } } - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_remove_item_from_group: " << "values_set.size()=" << values_set.size()); if (values_set.size() == 0) { @@ -913,7 +915,7 @@ size_t ImageCache::cpu_remove_item_from_group(const CPUCacheKey item_key) { ++it; } } - MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " + MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_remove_item_from_group: " << "count=" << count); return count; @@ -924,7 +926,7 @@ bool ImageCache::gpu_erase_item(MHWRender::MTextureManager *texture_manager, const bool verbose = false; const GPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_item: " - << "item_key=" << item_key << "file_path=\"" + << "item_key=" << item_key << " file_path=\"" << file_path.c_str() << "\""); return ImageCache::gpu_erase_item(texture_manager, item_key); } @@ -958,7 +960,7 @@ bool ImageCache::cpu_erase_item(const CPUCacheString &file_path) { const bool verbose = false; const CPUGroupKey item_key = mmsolver::hash::make_hash(file_path); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_item: " - << "item_key=" << item_key << "file_path=\"" + << "item_key=" << item_key << " file_path=\"" << file_path.c_str() << "\""); return ImageCache::cpu_erase_item(item_key); } @@ -995,26 +997,41 @@ size_t ImageCache::gpu_erase_group_items( if (verbose) { const GPUGroupKey group_key = mmsolver::hash::make_hash(group_name); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::gpu_erase_group_items: " - << "group_key=" << group_key << "group_name=\"" + << "group_key=" << group_key << " group_name=\"" << group_name.c_str() << "\""); } - const GPUGroupMapIt group_search = m_gpu_group_map.find(group_name); - const bool group_found = group_search != m_gpu_group_map.end(); - if (!group_found) { - MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache: gpu_erase_group_items: " - "Group name \"" - << group_name << "\" not found!"); - return 0; - } - + // Loop until all items in the group are removed. + size_t iteration = 0; size_t count = 0; - GPUGroupSet &values_set = group_search->second; - for (auto it = values_set.begin(); it != values_set.end(); it++) { - GPUCacheString value = *it; + while (true) { + const GPUGroupMapIt group_search = m_gpu_group_map.find(group_name); + const bool group_found = group_search != m_gpu_group_map.end(); + if (!group_found) { + if (iteration == 0) { + // When a user gives the wrong group name, we will + // print the warning, but if the group name is not + // valid after a removal (iteration > 0), then the + // user shouldn't be notified of that the group is not + // available. + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache: gpu_erase_group_items: " + "Group name \"" + << group_name << "\" not found!"); + } + break; + } + + GPUGroupSet &values_set = group_search->second; + GPUCacheString value = *values_set.begin(); + // NOTE: This call will invalidate the 'values_set' value, + // because 'ImageCache::m_gpu_group_map' is changed by + // 'ImageCache::gpu_remove_item_from_group()' via + // 'ImageCache::gpu_erase_item()'. const bool ok = ImageCache::gpu_erase_item(texture_manager, value); count += static_cast(ok); + + iteration++; } return count; @@ -1026,26 +1043,41 @@ size_t ImageCache::cpu_erase_group_items(const CPUCacheString &group_name) { if (verbose) { const CPUGroupKey group_key = mmsolver::hash::make_hash(group_name); MMSOLVER_MAYA_VRB("mmsolver::ImageCache::cpu_erase_group_items: " - << "group_key=" << group_key << "group_name=\"" + << "group_key=" << group_key << " group_name=\"" << group_name.c_str() << "\""); } - const CPUGroupMapIt group_search = m_cpu_group_map.find(group_name); - const bool group_found = group_search != m_cpu_group_map.end(); - if (!group_found) { - MMSOLVER_MAYA_WRN( - "mmsolver::ImageCache: cpu_erase_group_items: " - "Group name \"" - << group_name << "\" not found!"); - return 0; - } - + // Loop until all items in the group are removed. + size_t iteration = 0; size_t count = 0; - CPUGroupSet &values_set = group_search->second; - for (auto it = values_set.begin(); it != values_set.end(); it++) { - CPUCacheString value = *it; + while (true) { + const CPUGroupMapIt group_search = m_cpu_group_map.find(group_name); + const bool group_found = group_search != m_cpu_group_map.end(); + if (!group_found) { + if (iteration == 0) { + // When a user gives the wrong group name, we will + // print the warning, but if the group name is not + // valid after a removal (iteration > 0), then the + // user shouldn't be notified of that the group is not + // available. + MMSOLVER_MAYA_WRN( + "mmsolver::ImageCache: cpu_erase_group_items: " + "Group name \"" + << group_name << "\" not found!"); + } + break; + } + + CPUGroupSet &values_set = group_search->second; + CPUCacheString value = *values_set.begin(); + // NOTE: This call will invalidate the 'values_set' value, + // because 'ImageCache::m_cpu_group_map' is changed by + // 'ImageCache::cpu_remove_item_from_group()' via + // 'ImageCache::cpu_erase_item()'. const bool ok = ImageCache::cpu_erase_item(value); count += static_cast(ok); + + iteration++; } return count; From fc1d85be6b75d3b221fa0cafb5368616403b6f96 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 18:47:52 +1000 Subject: [PATCH 253/295] Move shelf icon SVG files to sub-directory This will allow us to understand what the files are used for more easily. --- share/icons/edit/{ => shelf}/attributeTools.svg | 0 share/icons/edit/{ => shelf}/bundleTools.svg | 0 share/icons/edit/{ => shelf}/cameraContext.svg | 0 share/icons/edit/{ => shelf}/cameraTools.svg | 0 share/icons/edit/{ => shelf}/convertToMarker.svg | 0 share/icons/edit/{ => shelf}/createBundle.svg | 0 share/icons/edit/{ => shelf}/createCamera.svg | 0 share/icons/edit/{ => shelf}/createImagePlane.svg | 0 share/icons/edit/{ => shelf}/createLens.svg | 0 share/icons/edit/{ => shelf}/createMarker.svg | 0 share/icons/edit/{ => shelf}/displayTools.svg | 0 share/icons/edit/{ => shelf}/fileInputOutput.svg | 0 share/icons/edit/{ => shelf}/generalTools.svg | 0 share/icons/edit/{ => shelf}/hotkeySwitcher.svg | 0 share/icons/edit/{ => shelf}/keyFrameAdd.svg | 0 share/icons/edit/{ => shelf}/keyFrameNext.svg | 0 share/icons/edit/{ => shelf}/keyFramePrevious.svg | 0 share/icons/edit/{ => shelf}/keyFrameRemove.svg | 0 share/icons/edit/{ => shelf}/lensTools.svg | 0 share/icons/edit/{ => shelf}/lineTools.svg | 0 share/icons/edit/{ => shelf}/loadMarker.svg | 0 share/icons/edit/{ => shelf}/markerBundleCombineSelection.svg | 0 share/icons/edit/{ => shelf}/markerBundleLinkTools.svg | 0 share/icons/edit/{ => shelf}/markerBundleSwapSelection.svg | 0 share/icons/edit/{ => shelf}/markerTools.svg | 0 share/icons/edit/{ => shelf}/meshTools.svg | 0 share/icons/edit/{ => shelf}/mmSolverRun.svg | 0 share/icons/edit/{ => shelf}/mmSolverRunFrame.svg | 0 share/icons/edit/{ => shelf}/mmSolverWindow.svg | 0 share/icons/edit/{ => shelf}/selectionTools.svg | 0 share/icons/edit/{ => shelf}/zDepthTools.svg | 0 31 files changed, 0 insertions(+), 0 deletions(-) rename share/icons/edit/{ => shelf}/attributeTools.svg (100%) rename share/icons/edit/{ => shelf}/bundleTools.svg (100%) rename share/icons/edit/{ => shelf}/cameraContext.svg (100%) rename share/icons/edit/{ => shelf}/cameraTools.svg (100%) rename share/icons/edit/{ => shelf}/convertToMarker.svg (100%) rename share/icons/edit/{ => shelf}/createBundle.svg (100%) rename share/icons/edit/{ => shelf}/createCamera.svg (100%) rename share/icons/edit/{ => shelf}/createImagePlane.svg (100%) rename share/icons/edit/{ => shelf}/createLens.svg (100%) rename share/icons/edit/{ => shelf}/createMarker.svg (100%) rename share/icons/edit/{ => shelf}/displayTools.svg (100%) rename share/icons/edit/{ => shelf}/fileInputOutput.svg (100%) rename share/icons/edit/{ => shelf}/generalTools.svg (100%) rename share/icons/edit/{ => shelf}/hotkeySwitcher.svg (100%) rename share/icons/edit/{ => shelf}/keyFrameAdd.svg (100%) rename share/icons/edit/{ => shelf}/keyFrameNext.svg (100%) rename share/icons/edit/{ => shelf}/keyFramePrevious.svg (100%) rename share/icons/edit/{ => shelf}/keyFrameRemove.svg (100%) rename share/icons/edit/{ => shelf}/lensTools.svg (100%) rename share/icons/edit/{ => shelf}/lineTools.svg (100%) rename share/icons/edit/{ => shelf}/loadMarker.svg (100%) rename share/icons/edit/{ => shelf}/markerBundleCombineSelection.svg (100%) rename share/icons/edit/{ => shelf}/markerBundleLinkTools.svg (100%) rename share/icons/edit/{ => shelf}/markerBundleSwapSelection.svg (100%) rename share/icons/edit/{ => shelf}/markerTools.svg (100%) rename share/icons/edit/{ => shelf}/meshTools.svg (100%) rename share/icons/edit/{ => shelf}/mmSolverRun.svg (100%) rename share/icons/edit/{ => shelf}/mmSolverRunFrame.svg (100%) rename share/icons/edit/{ => shelf}/mmSolverWindow.svg (100%) rename share/icons/edit/{ => shelf}/selectionTools.svg (100%) rename share/icons/edit/{ => shelf}/zDepthTools.svg (100%) diff --git a/share/icons/edit/attributeTools.svg b/share/icons/edit/shelf/attributeTools.svg similarity index 100% rename from share/icons/edit/attributeTools.svg rename to share/icons/edit/shelf/attributeTools.svg diff --git a/share/icons/edit/bundleTools.svg b/share/icons/edit/shelf/bundleTools.svg similarity index 100% rename from share/icons/edit/bundleTools.svg rename to share/icons/edit/shelf/bundleTools.svg diff --git a/share/icons/edit/cameraContext.svg b/share/icons/edit/shelf/cameraContext.svg similarity index 100% rename from share/icons/edit/cameraContext.svg rename to share/icons/edit/shelf/cameraContext.svg diff --git a/share/icons/edit/cameraTools.svg b/share/icons/edit/shelf/cameraTools.svg similarity index 100% rename from share/icons/edit/cameraTools.svg rename to share/icons/edit/shelf/cameraTools.svg diff --git a/share/icons/edit/convertToMarker.svg b/share/icons/edit/shelf/convertToMarker.svg similarity index 100% rename from share/icons/edit/convertToMarker.svg rename to share/icons/edit/shelf/convertToMarker.svg diff --git a/share/icons/edit/createBundle.svg b/share/icons/edit/shelf/createBundle.svg similarity index 100% rename from share/icons/edit/createBundle.svg rename to share/icons/edit/shelf/createBundle.svg diff --git a/share/icons/edit/createCamera.svg b/share/icons/edit/shelf/createCamera.svg similarity index 100% rename from share/icons/edit/createCamera.svg rename to share/icons/edit/shelf/createCamera.svg diff --git a/share/icons/edit/createImagePlane.svg b/share/icons/edit/shelf/createImagePlane.svg similarity index 100% rename from share/icons/edit/createImagePlane.svg rename to share/icons/edit/shelf/createImagePlane.svg diff --git a/share/icons/edit/createLens.svg b/share/icons/edit/shelf/createLens.svg similarity index 100% rename from share/icons/edit/createLens.svg rename to share/icons/edit/shelf/createLens.svg diff --git a/share/icons/edit/createMarker.svg b/share/icons/edit/shelf/createMarker.svg similarity index 100% rename from share/icons/edit/createMarker.svg rename to share/icons/edit/shelf/createMarker.svg diff --git a/share/icons/edit/displayTools.svg b/share/icons/edit/shelf/displayTools.svg similarity index 100% rename from share/icons/edit/displayTools.svg rename to share/icons/edit/shelf/displayTools.svg diff --git a/share/icons/edit/fileInputOutput.svg b/share/icons/edit/shelf/fileInputOutput.svg similarity index 100% rename from share/icons/edit/fileInputOutput.svg rename to share/icons/edit/shelf/fileInputOutput.svg diff --git a/share/icons/edit/generalTools.svg b/share/icons/edit/shelf/generalTools.svg similarity index 100% rename from share/icons/edit/generalTools.svg rename to share/icons/edit/shelf/generalTools.svg diff --git a/share/icons/edit/hotkeySwitcher.svg b/share/icons/edit/shelf/hotkeySwitcher.svg similarity index 100% rename from share/icons/edit/hotkeySwitcher.svg rename to share/icons/edit/shelf/hotkeySwitcher.svg diff --git a/share/icons/edit/keyFrameAdd.svg b/share/icons/edit/shelf/keyFrameAdd.svg similarity index 100% rename from share/icons/edit/keyFrameAdd.svg rename to share/icons/edit/shelf/keyFrameAdd.svg diff --git a/share/icons/edit/keyFrameNext.svg b/share/icons/edit/shelf/keyFrameNext.svg similarity index 100% rename from share/icons/edit/keyFrameNext.svg rename to share/icons/edit/shelf/keyFrameNext.svg diff --git a/share/icons/edit/keyFramePrevious.svg b/share/icons/edit/shelf/keyFramePrevious.svg similarity index 100% rename from share/icons/edit/keyFramePrevious.svg rename to share/icons/edit/shelf/keyFramePrevious.svg diff --git a/share/icons/edit/keyFrameRemove.svg b/share/icons/edit/shelf/keyFrameRemove.svg similarity index 100% rename from share/icons/edit/keyFrameRemove.svg rename to share/icons/edit/shelf/keyFrameRemove.svg diff --git a/share/icons/edit/lensTools.svg b/share/icons/edit/shelf/lensTools.svg similarity index 100% rename from share/icons/edit/lensTools.svg rename to share/icons/edit/shelf/lensTools.svg diff --git a/share/icons/edit/lineTools.svg b/share/icons/edit/shelf/lineTools.svg similarity index 100% rename from share/icons/edit/lineTools.svg rename to share/icons/edit/shelf/lineTools.svg diff --git a/share/icons/edit/loadMarker.svg b/share/icons/edit/shelf/loadMarker.svg similarity index 100% rename from share/icons/edit/loadMarker.svg rename to share/icons/edit/shelf/loadMarker.svg diff --git a/share/icons/edit/markerBundleCombineSelection.svg b/share/icons/edit/shelf/markerBundleCombineSelection.svg similarity index 100% rename from share/icons/edit/markerBundleCombineSelection.svg rename to share/icons/edit/shelf/markerBundleCombineSelection.svg diff --git a/share/icons/edit/markerBundleLinkTools.svg b/share/icons/edit/shelf/markerBundleLinkTools.svg similarity index 100% rename from share/icons/edit/markerBundleLinkTools.svg rename to share/icons/edit/shelf/markerBundleLinkTools.svg diff --git a/share/icons/edit/markerBundleSwapSelection.svg b/share/icons/edit/shelf/markerBundleSwapSelection.svg similarity index 100% rename from share/icons/edit/markerBundleSwapSelection.svg rename to share/icons/edit/shelf/markerBundleSwapSelection.svg diff --git a/share/icons/edit/markerTools.svg b/share/icons/edit/shelf/markerTools.svg similarity index 100% rename from share/icons/edit/markerTools.svg rename to share/icons/edit/shelf/markerTools.svg diff --git a/share/icons/edit/meshTools.svg b/share/icons/edit/shelf/meshTools.svg similarity index 100% rename from share/icons/edit/meshTools.svg rename to share/icons/edit/shelf/meshTools.svg diff --git a/share/icons/edit/mmSolverRun.svg b/share/icons/edit/shelf/mmSolverRun.svg similarity index 100% rename from share/icons/edit/mmSolverRun.svg rename to share/icons/edit/shelf/mmSolverRun.svg diff --git a/share/icons/edit/mmSolverRunFrame.svg b/share/icons/edit/shelf/mmSolverRunFrame.svg similarity index 100% rename from share/icons/edit/mmSolverRunFrame.svg rename to share/icons/edit/shelf/mmSolverRunFrame.svg diff --git a/share/icons/edit/mmSolverWindow.svg b/share/icons/edit/shelf/mmSolverWindow.svg similarity index 100% rename from share/icons/edit/mmSolverWindow.svg rename to share/icons/edit/shelf/mmSolverWindow.svg diff --git a/share/icons/edit/selectionTools.svg b/share/icons/edit/shelf/selectionTools.svg similarity index 100% rename from share/icons/edit/selectionTools.svg rename to share/icons/edit/shelf/selectionTools.svg diff --git a/share/icons/edit/zDepthTools.svg b/share/icons/edit/shelf/zDepthTools.svg similarity index 100% rename from share/icons/edit/zDepthTools.svg rename to share/icons/edit/shelf/zDepthTools.svg From b885325c5a089225107f2e4f5ce39ad9257e091d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 18:49:01 +1000 Subject: [PATCH 254/295] Move node SVG icon files into specific directory This allows us to know what these files are used for more easily. --- share/icons/edit/{ => node}/mmBundleShape.svg | 0 share/icons/edit/{ => node}/mmImagePlaneShape.svg | 0 share/icons/edit/{ => node}/mmLineShape.svg | 0 share/icons/edit/{ => node}/mmMarkerGroupTransform.svg | 0 share/icons/edit/{ => node}/mmMarkerShape.svg | 0 share/icons/edit/{ => node}/mmMarkerTransform.svg | 0 share/icons/edit/{ => node}/mmSkyDomeShape.svg | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename share/icons/edit/{ => node}/mmBundleShape.svg (100%) rename share/icons/edit/{ => node}/mmImagePlaneShape.svg (100%) rename share/icons/edit/{ => node}/mmLineShape.svg (100%) rename share/icons/edit/{ => node}/mmMarkerGroupTransform.svg (100%) rename share/icons/edit/{ => node}/mmMarkerShape.svg (100%) rename share/icons/edit/{ => node}/mmMarkerTransform.svg (100%) rename share/icons/edit/{ => node}/mmSkyDomeShape.svg (100%) diff --git a/share/icons/edit/mmBundleShape.svg b/share/icons/edit/node/mmBundleShape.svg similarity index 100% rename from share/icons/edit/mmBundleShape.svg rename to share/icons/edit/node/mmBundleShape.svg diff --git a/share/icons/edit/mmImagePlaneShape.svg b/share/icons/edit/node/mmImagePlaneShape.svg similarity index 100% rename from share/icons/edit/mmImagePlaneShape.svg rename to share/icons/edit/node/mmImagePlaneShape.svg diff --git a/share/icons/edit/mmLineShape.svg b/share/icons/edit/node/mmLineShape.svg similarity index 100% rename from share/icons/edit/mmLineShape.svg rename to share/icons/edit/node/mmLineShape.svg diff --git a/share/icons/edit/mmMarkerGroupTransform.svg b/share/icons/edit/node/mmMarkerGroupTransform.svg similarity index 100% rename from share/icons/edit/mmMarkerGroupTransform.svg rename to share/icons/edit/node/mmMarkerGroupTransform.svg diff --git a/share/icons/edit/mmMarkerShape.svg b/share/icons/edit/node/mmMarkerShape.svg similarity index 100% rename from share/icons/edit/mmMarkerShape.svg rename to share/icons/edit/node/mmMarkerShape.svg diff --git a/share/icons/edit/mmMarkerTransform.svg b/share/icons/edit/node/mmMarkerTransform.svg similarity index 100% rename from share/icons/edit/mmMarkerTransform.svg rename to share/icons/edit/node/mmMarkerTransform.svg diff --git a/share/icons/edit/mmSkyDomeShape.svg b/share/icons/edit/node/mmSkyDomeShape.svg similarity index 100% rename from share/icons/edit/mmSkyDomeShape.svg rename to share/icons/edit/node/mmSkyDomeShape.svg From 2413d5d16f088894db8294b4201a39cd293166d7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 20:15:43 +1000 Subject: [PATCH 255/295] Rename default 32x32 icons for Hi-DPI screens Follows the "Use a high resolution custom icon for a shelf item" Maya documentation advice, and names the icons with the icon name, without resolution embedded. https://help.autodesk.com/view/MAYAUL/2025/ENU/?guid=GUID-F2900709-59D3-4E67-A217-4FECC84053BE The higher-resolution icons for Hi-DPI screens will be added later. NOTE: Some of the text in these icons have changed very slightly, probably due to different fonts and different Inkscape version being used when re-rendering the .png files. --- share/config/functions.json | 76 +++++++++--------- share/icons/attributeTools.png | Bin 0 -> 1926 bytes share/icons/attributeTools_32x32.png | Bin 1941 -> 0 bytes ...{bundleTools_32x32.png => bundleTools.png} | Bin share/icons/cameraContext.png | Bin 0 -> 2192 bytes share/icons/cameraContext_32x32.png | Bin 2178 -> 0 bytes ...{cameraTools_32x32.png => cameraTools.png} | Bin share/icons/convertToMarker.png | Bin 0 -> 1298 bytes share/icons/convertToMarker_32x32.png | Bin 1287 -> 0 bytes share/icons/createBundle.png | Bin 0 -> 1671 bytes share/icons/createBundle_32x32.png | Bin 1688 -> 0 bytes share/icons/createCamera.png | Bin 0 -> 1437 bytes share/icons/createCamera_32x32.png | Bin 1431 -> 0 bytes share/icons/createImagePlane.png | Bin 0 -> 1092 bytes share/icons/createImagePlane_32x32.png | Bin 1817 -> 0 bytes share/icons/createLens.png | Bin 0 -> 2062 bytes share/icons/createLens_32x32.png | Bin 2053 -> 0 bytes share/icons/createMarker.png | Bin 0 -> 1335 bytes share/icons/createMarker_32x32.png | Bin 1343 -> 0 bytes ...isplayTools_32x32.png => displayTools.png} | Bin share/icons/fileInputOutput.png | Bin 0 -> 1390 bytes share/icons/fileInputOutput_32x32.png | Bin 1541 -> 0 bytes share/icons/generalTools.png | Bin 0 -> 2408 bytes share/icons/generalTools_32x32.png | Bin 2424 -> 0 bytes share/icons/hotkeySwitcher.png | Bin 0 -> 1402 bytes share/icons/hotkeySwitcher_32x32.png | Bin 1604 -> 0 bytes ...{keyFrameAdd_32x32.png => keyFrameAdd.png} | Bin ...eyFrameNext_32x32.png => keyFrameNext.png} | Bin share/icons/keyFramePrevious.png | Bin 0 -> 1211 bytes share/icons/keyFramePrevious_32x32.png | Bin 1212 -> 0 bytes ...ameRemove_32x32.png => keyFrameRemove.png} | Bin share/icons/lensTools.png | Bin 0 -> 1950 bytes share/icons/lensTools_32x32.png | Bin 1938 -> 0 bytes .../{lineTools_32x32.png => lineTools.png} | Bin share/icons/loadMarker.png | Bin 0 -> 1860 bytes share/icons/loadMarker_32x32.png | Bin 1887 -> 0 bytes share/icons/markerBundleCombineSelection.png | Bin 0 -> 1168 bytes .../markerBundleCombineSelection_32x32.png | Bin 1155 -> 0 bytes share/icons/markerBundleLinkTools.png | Bin 0 -> 1848 bytes share/icons/markerBundleLinkTools_32x32.png | Bin 1832 -> 0 bytes share/icons/markerBundleSwapSelection.png | Bin 0 -> 1622 bytes .../icons/markerBundleSwapSelection_32x32.png | Bin 1629 -> 0 bytes ...{markerTools_32x32.png => markerTools.png} | Bin share/icons/meshTools.png | Bin 0 -> 2185 bytes share/icons/meshTools_32x32.png | Bin 2221 -> 0 bytes ...{mmSolverRun_32x32.png => mmSolverRun.png} | Bin ...unFrame_32x32.png => mmSolverRunFrame.png} | Bin ...verWindow_32x32.png => mmSolverWindow.png} | Bin share/icons/selectionTools.png | Bin 0 -> 1829 bytes share/icons/selectionTools_32x32.png | Bin 1806 -> 0 bytes share/icons/zDepthTools.png | Bin 0 -> 1291 bytes share/icons/zDepthTools_32x32.png | Bin 1258 -> 0 bytes 52 files changed, 38 insertions(+), 38 deletions(-) create mode 100644 share/icons/attributeTools.png delete mode 100644 share/icons/attributeTools_32x32.png rename share/icons/{bundleTools_32x32.png => bundleTools.png} (100%) create mode 100644 share/icons/cameraContext.png delete mode 100644 share/icons/cameraContext_32x32.png rename share/icons/{cameraTools_32x32.png => cameraTools.png} (100%) create mode 100644 share/icons/convertToMarker.png delete mode 100644 share/icons/convertToMarker_32x32.png create mode 100644 share/icons/createBundle.png delete mode 100644 share/icons/createBundle_32x32.png create mode 100644 share/icons/createCamera.png delete mode 100644 share/icons/createCamera_32x32.png create mode 100644 share/icons/createImagePlane.png delete mode 100644 share/icons/createImagePlane_32x32.png create mode 100644 share/icons/createLens.png delete mode 100644 share/icons/createLens_32x32.png create mode 100644 share/icons/createMarker.png delete mode 100644 share/icons/createMarker_32x32.png rename share/icons/{displayTools_32x32.png => displayTools.png} (100%) create mode 100644 share/icons/fileInputOutput.png delete mode 100644 share/icons/fileInputOutput_32x32.png create mode 100644 share/icons/generalTools.png delete mode 100644 share/icons/generalTools_32x32.png create mode 100644 share/icons/hotkeySwitcher.png delete mode 100644 share/icons/hotkeySwitcher_32x32.png rename share/icons/{keyFrameAdd_32x32.png => keyFrameAdd.png} (100%) rename share/icons/{keyFrameNext_32x32.png => keyFrameNext.png} (100%) create mode 100644 share/icons/keyFramePrevious.png delete mode 100644 share/icons/keyFramePrevious_32x32.png rename share/icons/{keyFrameRemove_32x32.png => keyFrameRemove.png} (100%) create mode 100644 share/icons/lensTools.png delete mode 100644 share/icons/lensTools_32x32.png rename share/icons/{lineTools_32x32.png => lineTools.png} (100%) create mode 100644 share/icons/loadMarker.png delete mode 100644 share/icons/loadMarker_32x32.png create mode 100644 share/icons/markerBundleCombineSelection.png delete mode 100644 share/icons/markerBundleCombineSelection_32x32.png create mode 100644 share/icons/markerBundleLinkTools.png delete mode 100644 share/icons/markerBundleLinkTools_32x32.png create mode 100644 share/icons/markerBundleSwapSelection.png delete mode 100644 share/icons/markerBundleSwapSelection_32x32.png rename share/icons/{markerTools_32x32.png => markerTools.png} (100%) create mode 100644 share/icons/meshTools.png delete mode 100644 share/icons/meshTools_32x32.png rename share/icons/{mmSolverRun_32x32.png => mmSolverRun.png} (100%) rename share/icons/{mmSolverRunFrame_32x32.png => mmSolverRunFrame.png} (100%) rename share/icons/{mmSolverWindow_32x32.png => mmSolverWindow.png} (100%) create mode 100644 share/icons/selectionTools.png delete mode 100644 share/icons/selectionTools_32x32.png create mode 100644 share/icons/zDepthTools.png delete mode 100644 share/icons/zDepthTools_32x32.png diff --git a/share/config/functions.json b/share/config/functions.json index e7e201598..f962ec368 100644 --- a/share/config/functions.json +++ b/share/config/functions.json @@ -5,7 +5,7 @@ "name": "Solver", "name_shelf": "", "tooltip": "Open the MM Solver window.", - "icon_shelf": "mmSolverWindow_32x32.png", + "icon_shelf": "mmSolverWindow.png", "command": [ "import mmSolver.tools.solver.tool;", "mmSolver.tools.solver.tool.open_window();" @@ -15,7 +15,7 @@ "name": "Open Solver...", "name_shelf": "", "tooltip": "Open the MM Solver window.", - "icon_shelf": "mmSolverWindow_32x32.png", + "icon_shelf": "mmSolverWindow.png", "command": [ "import mmSolver.tools.solver.tool;", "mmSolver.tools.solver.tool.open_window();" @@ -24,7 +24,7 @@ "solver_run": { "name": "Run Solver", "name_shelf": "", - "icon_shelf": "mmSolverRun_32x32.png", + "icon_shelf": "mmSolverRun.png", "tooltip": "Run solver on currently active Collection.", "command": [ "import mmSolver.tools.solver.tool as tool;", @@ -34,7 +34,7 @@ "solver_run_current_frame": { "name": "Run Solver (Current Frame)", "name_shelf": "", - "icon_shelf": "mmSolverRunFrame_32x32.png", + "icon_shelf": "mmSolverRunFrame.png", "tooltip": "Run solver on currently active Collection on the current frame.", "command": [ "import mmSolver.tools.solver.tool as tool;", @@ -44,7 +44,7 @@ "navigate_root_frame_next": { "name": "Next User Frame", "name_shelf": "", - "icon_shelf": "keyFrameNext_32x32.png", + "icon_shelf": "keyFrameNext.png", "tooltip": "Jump to the next user frame.", "command": [ "import mmSolver.tools.navigaterootframes.tool as tool;", @@ -54,7 +54,7 @@ "navigate_root_frame_prev": { "name": "Previous User Frame", "name_shelf": "", - "icon_shelf": "keyFramePrevious_32x32.png", + "icon_shelf": "keyFramePrevious.png", "tooltip": "Jump to the previous user frame.", "command": [ "import mmSolver.tools.navigaterootframes.tool as tool;", @@ -64,7 +64,7 @@ "edit_root_frame_add": { "name": "Add User Frame", "name_shelf": "", - "icon_shelf": "keyFrameAdd_32x32.png", + "icon_shelf": "keyFrameAdd.png", "tooltip": "Add the current frame to the user frame list.", "command": [ "import mmSolver.tools.editrootframes.tool as tool;", @@ -74,7 +74,7 @@ "edit_root_frame_remove": { "name": "Remove User Frame", "name_shelf": "", - "icon_shelf": "keyFrameRemove_32x32.png", + "icon_shelf": "keyFrameRemove.png", "tooltip": "Remove the current frame from the user frame list.", "command": [ "import mmSolver.tools.editrootframes.tool as tool;", @@ -84,7 +84,7 @@ "file_io_tools": { "name": "File Input/Output Tools", "name_shelf": "", - "icon_shelf": "fileInputOutput_32x32.png", + "icon_shelf": "fileInputOutput.png", "tooltip": "File input and output Tools.", "command": ["pass"] }, @@ -98,7 +98,7 @@ "name": "Create Marker", "name_shelf": "", "tooltip": "Create a new Marker.", - "icon_shelf": "createMarker_32x32.png", + "icon_shelf": "createMarker.png", "command": [ "import mmSolver.tools.createmarker.tool;", "mmSolver.tools.createmarker.tool.main();" @@ -108,7 +108,7 @@ "name": "Marker Tools", "name_shelf": "", "tooltip": "Marker Tools", - "icon_shelf": "markerTools_32x32.png", + "icon_shelf": "markerTools.png", "command": ["pass"] }, "toggle_marker_lock": { @@ -123,7 +123,7 @@ "name": "Bundle Tools", "name_shelf": "", "tooltip": "Bundle Tools", - "icon_shelf": "bundleTools_32x32.png", + "icon_shelf": "bundleTools.png", "command": ["pass"] }, "toggle_bundle_lock": { @@ -249,21 +249,21 @@ "attr_tools": { "name": "Attribute Tools", "name_shelf": "", - "icon_shelf": "attributeTools_32x32.png", + "icon_shelf": "attributeTools.png", "tooltip": "Tools to adjust Attributes.", "command": ["pass"] }, "mb_tools": { "name": "Marker-Bundle Tools", "name_shelf": "", - "icon_shelf": "markerBundleLinkTools_32x32.png", + "icon_shelf": "markerBundleLinkTools.png", "tooltip": "Marker bundle link and unlink Tools.", "command": ["pass"] }, "mb_link_tools": { "name": "Marker-Bundle Link Tools", "name_shelf": "", - "icon_shelf": "markerBundleLinkTools_32x32.png", + "icon_shelf": "markerBundleLinkTools.png", "tooltip": "Marker bundle link and unlink Tools.", "command": ["pass"] }, @@ -287,7 +287,7 @@ "name": "Convert to Marker", "name_shelf": "", "tooltip": "Convert 3D Transform to Marker.", - "icon_shelf": "convertToMarker_32x32.png", + "icon_shelf": "convertToMarker.png", "command": [ "import mmSolver.tools.convertmarker.tool;", "mmSolver.tools.convertmarker.tool.main();" @@ -302,7 +302,7 @@ "name": "Convert to Marker...", "name_shelf": "", "tooltip": "Convert 3D Transform to Marker.", - "icon_shelf": "convertToMarker_32x32.png", + "icon_shelf": "convertToMarker.png", "command": [ "import mmSolver.tools.convertmarker.tool;", "mmSolver.tools.convertmarker.tool.open_window();" @@ -312,7 +312,7 @@ "name": "Load Marker...", "name_shelf": "", "tooltip": "Load Marker.", - "icon_shelf": "loadMarker_32x32.png", + "icon_shelf": "loadMarker.png", "command": [ "import mmSolver.tools.loadmarker.tool;", "mmSolver.tools.loadmarker.tool.open_window();" @@ -352,7 +352,7 @@ "name": "Create Bundle", "name_shelf": "", "tooltip": "Create Bundle.", - "icon_shelf": "createBundle_32x32.png", + "icon_shelf": "createBundle.png", "command": [ "import mmSolver.tools.createbundle.tool;", "mmSolver.tools.createbundle.tool.main();" @@ -373,7 +373,7 @@ "swap_marker_bundles": { "name": "Swap Markers / Bundles", "name_shelf": "", - "icon_shelf": "markerBundleSwapSelection_32x32.png", + "icon_shelf": "markerBundleSwapSelection.png", "tooltip": "Toggle Markers Bundles.", "command": [ "import mmSolver.tools.selection.tools as selection_tool;", @@ -383,7 +383,7 @@ "select_marker_bundles": { "name": "Select Markers and Bundles", "name_shelf": "", - "icon_shelf": "markerBundleCombineSelection_32x32.png", + "icon_shelf": "markerBundleCombineSelection.png", "tooltip": "Select Markers and Bundles.", "command": [ "import mmSolver.tools.selection.tools as selection_tool;", @@ -419,7 +419,7 @@ "display_tools": { "name": "Display Tools", "name_shelf": "", - "icon_shelf": "displayTools_32x32.png", + "icon_shelf": "displayTools.png", "tooltip": "Display Tools", "command": ["pass"] }, @@ -427,7 +427,7 @@ "name": "Create Sky Dome", "name_shelf": "", "tooltip": "Create a new Sky Dome.", - "icon_shelf": "createSkyDome_32x32.png", + "icon_shelf": "createSkyDome.png", "command": [ "import mmSolver.tools.createskydome.tool;", "import mmSolver.tools.createskydome.constant as const;", @@ -438,7 +438,7 @@ "name": "Create Axis Dome", "name_shelf": "", "tooltip": "Create a new Axis Dome.", - "icon_shelf": "createSkyDome_32x32.png", + "icon_shelf": "createSkyDome.png", "command": [ "import mmSolver.tools.createskydome.tool;", "import mmSolver.tools.createskydome.constant as const;", @@ -449,7 +449,7 @@ "name": "Create Horizon Line", "name_shelf": "", "tooltip": "Create a new Horizon Line.", - "icon_shelf": "createSkyDome_32x32.png", + "icon_shelf": "createSkyDome.png", "command": [ "import mmSolver.tools.createskydome.tool;", "import mmSolver.tools.createskydome.constant as const;", @@ -486,14 +486,14 @@ "general_tools": { "name": "General Tools", "name_shelf": "", - "icon_shelf": "generalTools_32x32.png", + "icon_shelf": "generalTools.png", "tooltip": "General Tools", "command": ["pass"] }, "mesh_tools": { "name": "Mesh Tools", "name_shelf": "", - "icon_shelf": "meshTools_32x32.png", + "icon_shelf": "meshTools.png", "tooltip": "Mesh Tools", "command": ["pass"] }, @@ -582,7 +582,7 @@ "zdepth_tools": { "name": "Z-Depth Tools", "name_shelf": "", - "icon_shelf": "zDepthTools_32x32.png", + "icon_shelf": "zDepthTools.png", "tooltip": "Screen-Space Z-Depth Tools", "command": ["pass"] }, @@ -927,7 +927,7 @@ "name": "Create Camera", "name_shelf": "", "tooltip": "Create a new Camera.", - "icon_shelf": "createCamera_32x32.png", + "icon_shelf": "createCamera.png", "command": [ "import mmSolver.tools.createcamera.tool;", "mmSolver.tools.createcamera.tool.main();" @@ -937,7 +937,7 @@ "name": "Create Lens", "name_shelf": "", "tooltip": "Create a new Lens.", - "icon_shelf": "createLens_32x32.png", + "icon_shelf": "createLens.png", "command": [ "import mmSolver.tools.createlens.tool;", "mmSolver.tools.createlens.tool.main();" @@ -947,7 +947,7 @@ "name": "Create Image Plane... (version 1)", "name_shelf": "", "tooltip": "Create a new Image Plane.", - "icon_shelf": "createImagePlane_32x32.png", + "icon_shelf": "createImagePlane.png", "command": [ "import mmSolver.tools.createimageplane.tool;", "mmSolver.tools.createimageplane.tool.main_version_one();" @@ -957,7 +957,7 @@ "name": "Create Image Plane...", "name_shelf": "", "tooltip": "Create a new Image Plane.", - "icon_shelf": "createImagePlane_32x32.png", + "icon_shelf": "createImagePlane.png", "command": [ "import mmSolver.tools.createimageplane.tool;", "mmSolver.tools.createimageplane.tool.main_version_two();" @@ -1018,7 +1018,7 @@ "name": "Camera Context", "name_shelf": "", "tooltip": "Display camera nodes.", - "icon_shelf": "cameraContext_32x32.png", + "icon_shelf": "cameraContext.png", "command": [ "pass" ] @@ -1035,7 +1035,7 @@ "name": "Deform Marker", "name_shelf": "MkrOff", "tooltip": "Deform Marker", - "icon_shelf": "createMarker_32x32.png", + "icon_shelf": "createMarker.png", "command": ["pass"] }, "deform_marker_create_offset": { @@ -1066,7 +1066,7 @@ "name": "Hotkey", "name_shelf": "", "tooltip": "Switch Hotkey Sets.", - "icon_shelf": "hotkeySwitcher_32x32.png", + "icon_shelf": "hotkeySwitcher.png", "command": [ "pass" ] @@ -1112,14 +1112,14 @@ "name": "Camera Tools", "name_shelf": "", "tooltip": "Camera Tools", - "icon_shelf": "cameraTools_32x32.png", + "icon_shelf": "cameraTools.png", "command": ["pass"] }, "create_line": { "name": "Create Line", "name_shelf": "", "tooltip": "Create a new Line.", - "icon_shelf": "createLine_32x32.png", + "icon_shelf": "createLine.png", "command": [ "import mmSolver.tools.createline.tool;", "mmSolver.tools.createline.tool.main();" @@ -1129,7 +1129,7 @@ "name": "Line Tools", "name_shelf": "", "tooltip": "Line Tools", - "icon_shelf": "lineTools_32x32.png", + "icon_shelf": "lineTools.png", "command": ["pass"] }, "toggle_line_lock": { diff --git a/share/icons/attributeTools.png b/share/icons/attributeTools.png new file mode 100644 index 0000000000000000000000000000000000000000..dfad6f0decad2bbe9614c37bb4886707aecf0ed6 GIT binary patch literal 1926 zcmV;12YL93P)Vh_Pkb&X|m2lWA(A(;dFG2w zZ0xRA@%>dlWhE-iw~ucJKpvQY5=+%nByr{{6yzWXN%Zy1RY&`LVFGwd&Fd88EGY68 zNJ113OhB*cqvMYEhTV6N zzu1QZ5sb$7C?U0;Q0Qa=%RZmo)#U%dQhev43%{m^~mEje98}wE&=1D7JnU3=Wx%#!UbaV(@Da zWQk*5$bk@;c;#lQBtp^6En5H}_vSWMmPrc;QO^H;0jQbG#FFafj&Cns@qW~PlL?yGBN)1A)M4RUB8BvTf~e(;C-l8O`UfY;4bU`kzzdKL`ecEw^v=9F zyrr5xeslv>sZ<{!s>z4S%GOv*O1^(L4H}h7bx5n#rU5{iP@cmNl)H*9KheHQuWkIX z!Qk&T8mWcn>9ykGnSP%CdN_{JBx<(nf0Xl?Hhq;_tyX78HlzS!;rSf{JnuN2pKswf z?#nT+AR49Sr;=q7=mH^GuJd!^r!NazNs#pAsnuIMfJ-kF*TNR%Q;sjT(-e8c@b|FqBN8;>L(G#b~6Kzk%P!goR`Wh3il z1rV*~Z4k0ias)B*6RS^h2e>i7AeWte0J_^`XEsAa%p ziU)wKD_2I#Z{GalOfb06gv#5)aAg5T^M;>+A@y=V#>h)nS)2&`P?DsJ(SG4-Jo!DF zZC9ik2@1sypU*cG>5Yr|`IZ!wiZ~<*Ewi(bDFvlewQ$hrO^eOtl?;PUP$)+C4AS|| z*zB7|J4Z!yO8>Y8j@2Ate2mc02y!aJ;5dD84ou48AT& z(m$36C=$w36d`H@A&Jw!x6@m1kN-^+#qR)+R!Y7IOuSm%P?wVO=8J3AsM+whXCx3n zwIFnP{r)F$dnPCW1Y>PL*1nW^HtXPendR7S015Y)IJz3?d{WYd_M9AQ;d#30p@(Fp zQn}$?V1NVSF2zAc3PU3W!ZMz<;h3me{x<49LHsF9G9`2eaZiTAP5Ek zwE)EI?CgU!o9$6b>9a9nCn%+>2qAGXY)1in7vbk7%km`4vU3?38S{4)5DJCr7=~#E zfX$mXXHrT(q?G<9+OMal2br0ffrNwvb+oOnu8u!)+o6U6JzI~Hndb`nReAQ~T(*67QQveMB>H$;&sPE|L_|$AR zQvhvYzPzumFL3PGF{)H5UukV^6|7b(<$3ePN%B}uol3-0Q@1sYqeT5H#grZC@65v&CNj&1b?)EJ0_tl%i93p z!i5Xnk|do-Pftf>W##S_OSS@Xb8}HsQ`5q6Tyb-A^O)1=TvCBM1bDsP9h6cWIB+04 zEiJ7U07^?szjeO?Zrr$mhK7cZold9R*x2}RVPWBt(Z4_p3=Cw%#l@}f?d`>-OP6$= zot+O)Pfx>SGJOfaea!^F-;Z#h;cHKnVGU+wGPQ9Xd4F)YPPF zYHHHi?e@0h;F zl}1{V0ZrQ_(Hgo0R=TzuT9t`KLd_~|RkSKvLs*tnsX`Q0+XB^!GBFylY!hQHgr(xc z*ybygKnb{jV{E_AyFaiqV@ygTHT(T{f9ITge)rsS&wIcl!Z(M?;-0wmbV4Z4eAS7~ z-Bmh%p!BD#Oda!069)m1MKyNhctr$6o5;Xue>VVJvUXd{<`z(IMuAz-%9vJ`nOD3>i__XOKQ?Y=*GdI zMxGO4FaO6Z=9MQr9?xXBgRT(Z$j**cG@4NYm`+bk;o{896_$|BV{vhBAB&6A>j;5I zRcE^w7mvR(Hg;DKgjSuAI}-cs=65#Mq?#DR|6bXrwYTW!EfYVKBih4S!9&QcQVU z8BMvklj?N3j}X%2Bl-F9_T1bTzes~Yr_=q`WHN08fP6<$8b4U%%I@0H^jLza=EsSN zzFxDL+IXJ+CnslSfajm|$1#Q|!@-1?)9#tJJm%Kx^;-iQQh>4X{0oCT?`+M?v~e8w z^{`hEiahI{4Jr{VASBs!V@|yNy5j(mNMD9tf2B4dVMm71c)vHIOs4A8jt;lMU`S9^ zHAfJH768;?Hs`2>Gyup4fR7g!addb%J2YCKFUaxl|kbKx|-Yf!5f#mSt38j<`tXCC4 zOon<8vP*VK82gFcE4!rwUaxoDZ&uxcaPHj9%=U_*Ac2%4dek99sXQ-dQ8U;TX8hRyA zjYy5=1+UjT66lT2%uHLXPDh-wj7zh#P$>naRCoWNp_}Gdi=JW_Tmyw>ympw*Uz`1v zD2jCeR*cbLa2%JEq1V5EA~7*PFgM(a(r99sVup|{Z{E3sKRTUt>ouC)vy$|NEX#ji zAt2jP6su9_5D1C7`};fev-*im2ATSYnef5#p*tcJbkJq#QZ_k(@pv>X0 zcznJBEX@Qh04dxCWb)#E2s}Bh#La%@6`HdSrEXy|G3!Z!^zLfKWz;Rp>$8kw(PBb($6h|q2ky7g2 zwr$%x0N`*qasWIT>Pso@CxoPj@~WzKGYs=NfD->&B81$QB&j7mJzWg;UGYPrD9Uq$ zkhcI}_wLgwvURaNa4MKSvL@#A*^G_NYa z?RFmlfU{@MrX(dLJr4jocI;SIvM-A>XU^Q~>+5qzL`0CRtgOobX2J@v*=#W;lPRyO zs|yz{T#)MP>y^2=Ia*Lqum!-zhZQhBK5lh7ox4O)q!kqvHT(AMOARZ4VVI}fZZ~ag zY`ojh&|qw6XfRGsPq%K`w29>9*xIY#fKB{ bf5CqNM2s0|?|J@100000NkvXXu0mjfl82&N diff --git a/share/icons/bundleTools_32x32.png b/share/icons/bundleTools.png similarity index 100% rename from share/icons/bundleTools_32x32.png rename to share/icons/bundleTools.png diff --git a/share/icons/cameraContext.png b/share/icons/cameraContext.png new file mode 100644 index 0000000000000000000000000000000000000000..4707c119d2096923d9c07a24f9cfb0991895453d GIT binary patch literal 2192 zcmV;B2ygd^P)UpD(nxf4LLn^ZB4Cino0E@`CX2@DDa^+9Vnp8k%kF zQJ=4Bpx5gcZrHG4dqhOU%Vg~ez#D)LKsNx*YZLJG^>v7dh?ogN1P6!iT)jFfGca)3 ze3hyP?(QBK85v>a^5)jc%8FkN3=A~Rn!wKpm6ViHyK!UMDw?KYHk;Ad*npKQ-vj_M zARutr>ebN=XV0F>{NabjqX0m@YJ|SNzLDPEUiL-0xVZQ$TeqgKqG=icP+ffuZ@m>i z1>@ndfY`C)qwK)I!0o^+PQYTZs0s=S`qR?VR)&U#(&PJ!7Wuy?6bjc71OWgT8X81X z(~k;-QDxA3WcJG&$6t^WHLb@5WE}#7V{aipzhp-iwpYt`n)0| zBL$3JAMWojiAqfT1#aA^MO0J_ii^K}(9lpH+|W?ZjVGU(nbQ*z62=1nmSr7#dV03= zH5$!da&vRN1(O(X97oO(*m~GIx;$6uZ>RpwY1P_|I{=`%>@>b9x{QeEWQo1N%-Y*K zK(E*1?%h_dqM~dw0B(|LUrEX3Q$az?KIHLuAW0HSmIR~_RuXl~%Kz87H`lI@wU|vk zb<#^+AuHB6I6Aw9O$R<`YXmKv2W9mhF_~X_kJ$Jo-GMkLO({a85D^oj7Z3yp07%*Pop|r=W3{0x*9#pzZ5L01nE&xbX4L|Z z#pK%fU&e8ile)P0vJaon8;vHkUmF|y&Lxs0O#m=3@YE|hI))|)0!x<$p`xP9($doO zck699)=su|!oW@GIf2u(=8In*xyl|^yFbwMYT1iybyVCIO8U*G!Yy02j<*^CAO!%r zyF0-!48~j3$Hx~ZPo5|Mpt3%Q#?fQKXf-Vb$3LnCf#C~hv#%Mm9(oEKZNlsp3M0GT zuM(Wd`&eI8^!0D9Tqz#pIBtq7ne2N61dQgO(`ivqkpEqGcjqU-43m%;&0r$hJ~Yp0Ulp&G4sg7lDXcHm=t5TV!<#B zYHBJC+1Uq=)Yo5+0bl`Un1sX_H%6;z$*o9!9FQHj1^_?i`?v?~r`x(EJACwQL zaV$ykd?h=BJnw%luaxWzjsd_Mv*4z=P^;B_g`=Nc*1}jP&@8JSDegXI2s*wj<^?!` zOt|LeW;>du6U=7wy?}rK`S||C6)2U;P_0&54}h+#s{=t02nh*^2T+R{sz{|$ zou(+t6Tl(>Dr-23qULiP_s`PO(l!7~pCi!U-+#i*&5ho*Ygd<4D%}8}Un~~C2f$QU zS9gJ?={T)cd&$w!ahpb?d7@UUBb}U__PV&Zc#$MYHa0dkb#!#pibNuvv$ONMnwpw~ z#Kgo!48s(bm6e^*>-A|gO(QNYP7)g%`=q$I_~Y#C?28iwY;0_Tv|25sQt3_rKLUVS ztv&?+&z?OCaCLQcDJdyQGa8M<>FMcSRaI5zR4UcCsi~=~qoX64o13eB{P?khlatfI z-Me?c9~2Z+X)>8?Zr;2}XJuto>2$gk85tR#GMS96tgIYvZEc-5<%nu)YX=ny1?S@8 z^4yzWT3UMN{{8!$gM-7b6$*u)QmN!}a&lAvAeBmc>+0&bu`2)o?BBmXOQ}?H&CSi6 zhlfWV0NlHG&qt|La)%Eeo^nkSqqwK1r+vm!nFqu9Ox-8 zFTWQR6_u8spPw=~IM_chFknhbN^-w*=gz?9&70o=uAWB_$=72L}fo0E`%o#wW**AHUPt*{K46ef#!p2@MTBD-wwob$53=w70iY zEiEldsZ_erU@-i2`}Xacp`oFzd-m+P92^|{fw#B!pDBuRxN+l#>-1#)FY;ef`ce2F SSi8mm0000)N&57kAI$ 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 Ef(3Z~t^fc4 diff --git a/share/icons/cameraTools_32x32.png b/share/icons/cameraTools.png similarity index 100% rename from share/icons/cameraTools_32x32.png rename to share/icons/cameraTools.png diff --git a/share/icons/convertToMarker.png b/share/icons/convertToMarker.png new file mode 100644 index 0000000000000000000000000000000000000000..dbc739f28564547b21ac92f74d9e1629ccbaff71 GIT binary patch literal 1298 zcmV+t1?~EYP)Ao;Fi6ZxtAt$TH}kbEsNkN4F;5d-3)(RsI|atyCN+G zp?`p0xV^pS>_v<*H6hn6Jk0OQIXS=k{pOQ%a(*`l_`i#2HQw8?W2Y(vrhz1yCq0*tlxdwjzV!6^YWrT5~i5JR-Kz0Gjm_V8sD+T8)$tmwkGAN~Tok zipMAaO{irnVm3b;+PU+&C&@_Lh=LZ&o{%iprmIPc5-q=V>!XRXvZHZT{f+=aXFT5d zfyeX4@b>Lr%u>2Foi89s&i%J)cJ9lCEIQAJ6u69fRjjG_<#8+byvxT>a$ z1OaHe^|SzY=I6gT>G$WdH1%0kr2zi+EG|}!$#SPkY1Y|bum?cn*MUGNEX(hw%M8hK z@G)T86t1XPO(p52a55S6A1!4u``AfapX~EK8+QO>VcFT)cR3AUiu-HX4lw z030739bE-rFQwGT^L&rVWD+|%I&|yTuYZS9I>d2YK7eeK$@JUs@NhoQ^RG&h)KXen znh-_N%otk-;5vZ4jIn+~NC^NI4u{{<>2x_Rm#bCh^?FYg78a7zr%(Uj@pz8khs^Z* z{m(?B(IG`qHUDHO6AGL?V&kCnqP9TefV;;y4a|zu(3wijuFYDuTga z!$Z3?ABjXNGcz+y_4V~b2M-=BnV6VJ8jZ&Kr4QQL+Q`0r``#WOA72mzVNY#s?Jc+4 zeQ)#T&HF`BG_P5+=7pA)mj2@6VhI46nwq#>yLKHpbm-7qfk0r~>-D0ls;Zounws+R zJde7%y2_>SP$-lkiefGR7>&kW0C#wvpJj}#?(Xi^13)5?@B`>zjLi{3^Z?-XdM}ig zmS+2WzO!RvW3b!p4}hc5=$`<70dPYQ1exPF02tsL4#%fHpHHo>u0Gk`-rnVOI{$Dw zolZ%Tex074W;Hc6H8*eGY{<#UdA_Z!ZPH?~C;&hRq4&oJkCLpctgq(h=V?JfL0NNi z^Gqt0YG0}mLX@R>EEbzFnM}BL?OLm@p`pRs)6-K{QBiSW?b@}k&CSiBx3{--aBwhe zu~@3jX7l%Yz5ZR7%jIotZ9RPA#0lAEvyBW442%GP-EQyo`Fz^|;MlQaQe9o$C642& z91h1B0E|*PWwlx-hK7blmjG-w+lTr2`9Ei5WW2oWtMgy!A34({AiBIYxc~qF07*qo IM6N<$f|;^%LjV8( literal 0 HcmV?d00001 diff --git a/share/icons/convertToMarker_32x32.png b/share/icons/convertToMarker_32x32.png deleted file mode 100644 index 1cad89268359694382764bd30c37d6308319fc44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1287 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 RB@>ipaY6+fesOd6H=Y48wE1i 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 diff --git a/share/icons/createImagePlane.png b/share/icons/createImagePlane.png new file mode 100644 index 0000000000000000000000000000000000000000..5a2b9eb0a7f7d9bf98a5ecdd91df21ad7558dccd GIT binary patch literal 1092 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 Hu0mjfJJ5Je diff --git a/share/icons/createLens.png b/share/icons/createLens.png new file mode 100644 index 0000000000000000000000000000000000000000..6ca9460ca666ba01e3510970ee85b72d927093ca GIT binary patch literal 2062 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 diff --git a/share/icons/createMarker.png b/share/icons/createMarker.png new file mode 100644 index 0000000000000000000000000000000000000000..32864eeb7c3b0d2dfe99d3292a25c9b66231e54b GIT binary patch literal 1335 zcmV-71<3k|P)DV4$;h*5{Yz#+Uq zOXAcw0vXw|Wt+H2<`T?&w-+Q!7MUhWLN=rD!p)dPB7w$`7KkM#z15D2rB^^_da$CU z|0q4~?s@h`6JzS)&Mmx{pI4Xr-S?YMp3D6X;QtO%8eFKVDw|ne?oMD#yQ9&;lYaj< zN_r@z0wTtFxfvO|%nY+5f$=e=J&FZhfULHFV%`X>x&W;-h!OG(K7Je&FjkF4B7gpy zQm;&C=gtj$P0gm)=}5VV0+p5fc|rIz)hr>3i`94Uo|xOa_v45xe}@26qmjtyCth#e z)Sf*ZQH;w{S(H&QxE_EdU#PhwN<5B2LNDZnD+so zGAN2I1q@S^DmNa9><55X6F>;bQCTJ=#R)wPphZ!XwyIPB0PsW<5dh?*Aw)%FIaMVf z0G#T6N`d?Fc<1TaSv^THkIFIzfCt`W((;EOjLI0VKNkqx27q6_efW@{7le;eb@GA` zc!`)Y=IiU%;zXiweq}{vP^<6j?Dnvd9>sfrN+cR6LY!IY>5d;CJsMHmqcr$D0>N&+_&*5-5F5bL(^ZegEL;L;yy^EhzzF+#{%j^jpYnjSM44D(Y{QT25UcK5Zkw0wBu#tn9Qdb&uf)uvZe zRHO|K4xZ9#wZ&y+Wv!wpBA(}kzP`ST?d|PawOZXgGBSc|YHHG*PUi(m6vek>S%yF$ z@bhyg6MuHWvaG7As_L`u?(X9njpq2w%nUZ0%|ApUk;jFFg)IQE!Q=6KUs_t)o12?E zAW0G-gq&<`Z9NqX1|x2_Te4Uz6`@e*f=;JvV%c-}@Zl?VyB*nV zHvgG3XMm!p^#Bk`OG}d|iXs5Oc6D`aLJo0^(V z*VWa%$1qI#`1m+eUS3`f0DusZ0044wa+Z60dmj}P6l~4U&p*#F470GXusbs|(}OWi z+p=ZLla7uK2Tjuj&*Vd)P`IR|WRuNi8@zVyTD#e74w}v8jYUO8XV5X`}=nsIB;Npc6Rnp85tRCZ{51po=hh1 t)YjJinwgntip63Q%d+f0`mFv7{sMDRDAe>z<1_#O002ovPDHLkV1lFsa>M`t literal 0 HcmV?d00001 diff --git a/share/icons/createMarker_32x32.png b/share/icons/createMarker_32x32.png deleted file mode 100644 index 97acc5c1bd48eaccc8cedac584bc01733244cf3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1343 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 diff --git a/share/icons/generalTools.png b/share/icons/generalTools.png new file mode 100644 index 0000000000000000000000000000000000000000..29555e820a66f77af2a330d8b18c89e79517d862 GIT binary patch literal 2408 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𝔧_vjuetBB1At)|PG@Gcd%a#XO;Z7CYHHAR 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 literal 0 HcmV?d00001 diff --git a/share/icons/keyFramePrevious_32x32.png b/share/icons/keyFramePrevious_32x32.png deleted file mode 100644 index 7ef8608daae71eebc6cc5754ac710dfc9a55bf50..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1212 zcmV;t1Vj6YP)OuTjL*v+%y82M|3otln-8WyiGLPW2xEaz2)m{C z^bftgaC`6Zg)+BJXlcdo<2k=`zMuTgIltcl_@9Ly9|(uT7ZF0A7=|%1Ha7NVJ z^b$haM@L70DUF%Rh!E-_gfthF{2N}^@we(_9twqCCxrZ=s_JJv&-+W`^gn~fF94Ou z-*?fZ2ssVVpER7R)p%fFpuuLdjUj{v91aIPKR@pWfYSglR|en!*La69_mY`KpdkX@ zlje)7C%DLax6vkXlOv^?HmP7 zO(tlzQQ&gWmZ)sLb{O;F;o(~!G$TT)KeoP1%cDvnFYisMi@$|m406+*(2gQ^milWnGGTopk$}b24WwlxlTyr2m zr3#0`=KF^~9T@-sfG$bWUo$f^ zU8&8@tHolmFO4zs`TcO1mvPuiNHE5kS&aLYLgC$>o}Np2MWMR8yP?|JP-`>N;_>)s zCX=}x2n25Gy3V`ZZfI+3tN5wbqaEw(>+8Sgayd^jDP7Zb{S3w!34#EX^oNS-Km{VX zTrL%h&Gt=BPQJggvZBvSPeV4FJ#b;&J|w%+;_>)sDwT=`0)g*~x^D02=z#Y2_J2K+ zhXC^0MxO0Z>^fH0;VqCX>HyZf@RU zSoT$lqRfh-ki~ls0m|+G&-2rSkh4Zn1HOs*c0QT9e@OF^^h6@@E5^7hnM{6XGMRz^ zV3a1y0PJ@Ahd)hRe=V0uKMMd4YBVp&$mjFY)YR1TJkP&l7)EU0=+4egv&VV-nL3&8 atG@xcp|-Q+McIo000002+Ua!umU~V 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 ifB~Qj7%EV%!rH%(Yn**+)Cf2L00001tU literal 0 HcmV?d00001 diff --git a/share/icons/markerBundleCombineSelection_32x32.png b/share/icons/markerBundleCombineSelection_32x32.png deleted file mode 100644 index f7aa7dfe5a045cb3709822564a893afe46714689..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1155 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 m4@fs)0v`kCo)F%Xlm7zY8_OVVxT?|s0000aKk literal 0 HcmV?d00001 diff --git a/share/icons/markerBundleLinkTools_32x32.png b/share/icons/markerBundleLinkTools_32x32.png deleted file mode 100644 index d23077971e3d3d892eb8c9ac288271697fbe1a9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1832 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 LNkvXXu0mjf%We%m literal 0 HcmV?d00001 diff --git a/share/icons/meshTools_32x32.png b/share/icons/meshTools_32x32.png deleted file mode 100644 index 48c154cdedf9081c37f817817ce28b9a51e2b113..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2221 zcmV;e2vYZnP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H12s%kb zK~z|U#aC-kl-C)4&iCzomt|M(_o%zb&1+E%h-m#UB*GOOPgR+TVQlK$5O%31ge1qPArn7<|NNQ-)n6L1H+K;t)iV6` zQmcnj+V@BT2q6rB%s`oFt6Cv`S}b5^rNxH|6QfjYU27jv%7jRd55sF4lKj^8kGN3u z*>_knCm8Q-PX{4@b-;mf58n*{GYXJPh5OY~(I&A-5V2%VkSIBJ7LzeAlwA-Pgb<|! zjU9t{uc8@${MllBW4?#g^CSH*DsNhGw!Q;zxUBf83(2QT`G)SKp1_0=@IT02p0lCj{rKJa-)S9q6 zdp;7PRMS@XPQSxTDQ%xoK!8lRJWU(o`}T4?-v8$<^xIu{YwI#7B*IDA+=VVV0;4Vu z4xDL3SfCU~UtKXJ>w%GR+_Da##X1aq=O7&8-u{U>KLLc0XSQU{6*2cyGIR~2xV#Bl zR?dYkDZ)=|ov9DmOXE;@vJQpMrXWx%LbbjdE!H7)^o@e!DS`sT2v^B){(2{Z0>l^` z89(pid$ zgXkbRl44XyidN%^Pz4x5P_6IA>923&^^FVhm#P-7>{>@UrSu!W3J^k=q!`s)mLVwk z@ZZ>!J_iL)Ct}>gp|N8SE!JVQ+!=yx%#D^iL$D5x;gxj>crjxRBGgmQ3jp7?M5v_r z^NtL#41uY~&P|Z}RX`*nzll^S4l@iP8y3f)ci0K1+Y6C^!MsQnl44ZQge&oJtqE=~ z58Id`B#xkdzoT zl48_Ihzf*M^dO|Ea|nO<^jmCSJr8NxQ2hGD*M8(K4~IkLdaw+GBiqv<7P7v%o9+D= zaeBX+9PdLXgeX!q!69#MS?ZS!$5WiBHp19#!`oZa5GWHt-#Li+vFa&thOS|}d%g*I ztK*TT4fVsha;t~$w>ytdD?lj~?tgyS6YRr_%sqA-IMaX^Gv?xzbqPLyjlLV}7spHy zXz3flJHK2Gxn%kVd$WCj2T(DsfZzbhwhc>Sd_OP8Q+!loL|e~@FYC$Hde@HMJ`)IY zj~#EFX~dS5Qw2_#ZOjcr*D#tohv?AgxPwya^%Fn{nXO9>S4=b*%e_$?_`Crdm&IYn zT7S^qVJAYA5`0wC3S;*$4sKlrsYrkp>ktfGHZ)iUsJX|^Ib2?khvP>_-QMfn{f=?~ zpFNBv0D#~?Y4PS28kSPvZ&%Ft&N6`gn^UJf-IM|sZgk>YeFs965*j^A&ToJJ3g>e3 zE{^BTmcG&RUY@U@l-5nN3w}l@rO-sE#*XG^NDrQE#M%W>*tjeX05I%u<5t%&nmPw* zgJpp4wK+M5)9dzd^zOij>(YqJQvslgQabi1WYY=&kRG9yeI*kM$aAT&=pA;_wq9TP zM#fy;dh4L$Jb(+7(mRh!G?NLW0w!H7U~O(MXDs=msetV3Awzkl=wl-e}ve|6P`@{7H zgCWRlHv64w=3$3iE+?y2tt#<&JUcj!o3g-cHZL(6jb^9Qd0QY5+`D@9YApb^t*tG^ zXfzslp6}&(zPGWlv6m3yyMtZ4c(H(G+5YkI@xijPvX$Q}pt7=ZOsCUjx!vyF=gys* za+mUWJf9j2hG?Bm*Lv*OF@11wux|J6-A9~G=Nk;e%quJ`v}a~!UMeao5&_sWv96?~ zB(}J?*ew!?WX;XZr@mJ}RaMog*|TR0IF1Xcs;c^S!oP3dK7~Lah}UQ|;+mQoZC+lU z#%{OM(b3VY`uchU$8k7x=#a9uw)V+gyLMfW%jLdHM|pX9(axPa;{kx@`A9;DFEPKS zefjd`=F-yAhQ`Lmg^`hwp8!}20LPCXcjV;cjJLP9i#KoHT;p^)ol#Ly;^5%mk%EGP zFS4_pWX$1hl-o1OD1CVy(#tqYi z&uX>Ova+&Mxw*N&s;jG8!?LVZC=|{QAlS7v;Urq%A!M^}RPhbLg*<><_v$M0m)Mzv|0L%o(W4{dv vAxZ!aN+~zVrvTugl#V|#&i~_iKH2^QQW2ee;&Pz^00000NkvXXu0mjfAkQsb diff --git a/share/icons/mmSolverRun_32x32.png b/share/icons/mmSolverRun.png similarity index 100% rename from share/icons/mmSolverRun_32x32.png rename to share/icons/mmSolverRun.png diff --git a/share/icons/mmSolverRunFrame_32x32.png b/share/icons/mmSolverRunFrame.png similarity index 100% rename from share/icons/mmSolverRunFrame_32x32.png rename to share/icons/mmSolverRunFrame.png diff --git a/share/icons/mmSolverWindow_32x32.png b/share/icons/mmSolverWindow.png similarity index 100% rename from share/icons/mmSolverWindow_32x32.png rename to share/icons/mmSolverWindow.png diff --git a/share/icons/selectionTools.png b/share/icons/selectionTools.png new file mode 100644 index 0000000000000000000000000000000000000000..6d7e85a57b89e1f9dcd88b457bff07232fb3321c GIT binary patch literal 1829 zcmV+=2io|FP)0X+AJX z7rsKWWg8mW79mZ_lq?!+w?$Kjs&-S=leeQIlpt>bI&=zqk~u>|7?PyC_Be-?*NGZ zzW^`{b3P^}Cd@F*B1w`z0YE)#hRmp_sHOAgFUgxWnHE(lb`n7G(-=s(LjE2_(d8yn zc3fH6VLClsA8oe}d>v5i6UE@)0r;La0K(xAcs`K3bElbJx9&?QDk`BFhS^}Z+m4c? za(y@)ZU-PdH2_(bonG(EiK3!|Bu&#G2m*S22J!OC`Q*Za&+y@g|N4<6Nei+p_W+RB z8~{K-rBdzCXtZf*X?n$Sl4V)sSTp+IWt zbBf~PgOntN)16L7g+ifFNs`n7AiNd;vQnvBM-a*vOs4D=wIoTxXxyyaxpOx)G2wdQ z{{8z$L!rPeSJOTg9i^*)1j`emanV3G%W~14uH1Ryd!HWAgEOAi_y{1 z&+GLYSKK#Ou3VO`U2Ad)g0Ka^R3yd`-}`?5;)Rx$_ZAY|LpXA~3 z!TkJu(Cr@oujIf$FD52j0Dy#qb$IQyH#vr3t^m+SVj5`+06-=PLIWTtJ9{e$08LF- zLp3$OS!{2=70%7wOR+46`1k~9wOVRmp!XZ0P^b<7{{&;K8mTPH`RnT&WwUv2fMr=H zfG^WD{rdX#8y4&9|0Fjyw#bc*E%N^TUlTZv>i|HmB>>dws2;60Hpp>Y3jpFU+L$c>FH^1JW8FK^mpTH-ja_Hlks(gFa0CO!MI>BXrG>*)_A>M!eB5+XV0G9{>fPl z4GpD=qS)thxhPQ-l{%e{KX>lj#sdcqBm@G14zJfsy4`MgJRXa|V9*$iM(FkWq@kgq z-|yM8=ib8urRk0F@$pi#*_@+Nsd9Lp51l!4rg&y%=0%-OmoPdy+MAV?)uvXfUkQam zmjJxJ3@|x4`HSS_v%j34p6=1GG!MY~@g8tGohMJ7I#ntN!aq5VOOYfA-QC^O6h&P&7z{tm%ggg5Cnvun z2*S3`&d#1swqRgjpuuP~78e#4+EPxs4_7zu|gC@qPDhn%SwORGmC!%*R#d* TSPqvU00000NkvXXu0mjfE96~T literal 0 HcmV?d00001 diff --git a/share/icons/selectionTools_32x32.png b/share/icons/selectionTools_32x32.png deleted file mode 100644 index a17549a5d3c3d9e70cda8674e06b9be2f0e81e12..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1806 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 BTWbIS literal 0 HcmV?d00001 diff --git a/share/icons/zDepthTools_32x32.png b/share/icons/zDepthTools_32x32.png deleted file mode 100644 index e7718d3e76f7a377bf3f138e17a8f0c49d4f8937..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1258 zcmV-9cLm4ovPI2?|=U@$ltFYfpI zL9JFlJ&ia(iWVe_<>lpchGFz#u~_&b&M*vgbaXr?2;$Sz=HgTVmSvxc#p1W(@t&TZ zzo(|AZXtvsugt*8%1XOPB)Sui>vTG%zrX)|gpl_v`OY+iHk<8TgwUU9X=yRWO(qkx zw6uKU^?IM2MI7J^6|h(=`N3fDw|H@iqM)<0^G7b?r#9elI5H0o4*s}s;X=-fI8D=_ zR;!mR7Rx;@;-@y?@pyg_i^Xro<6T`{yVKLt)d(Txl^HM^jbBP6l8@r?;o;%v;Naj5 zgwP&Waex%(fX!yRnVp^eFdlb0ov^jF^enFOOPy#Mx(Q~wze=CCus|!(P#!&aR7kB!^7NEod5TIU~zHLvb3~xgKKh^%N1yB zY)r~I*XQ_U#A|Osi~+OREZExG65DLHoJ0%G5>~5Kv9`8$;tWY>fTF0Ga5(J4aeSX3 zh@e)h{o$;l27|#gFfcF<06-9gil*sbF$}w$h#xZp6B85bdwYBGrlzLm_V)JAo12@j zjE;_eRaaN{?at0l4^7k6p-^Zq5D45A3WYf|O@Ghh@oeeo>F;cBZx4t>qGvLhOy~Fe z8H%EQXBcLoqN3t!yWKu2l}fKT9FE5-mFl~M75FbuBocX|R4S{&;V|O!`FA%rH^156 z-~UXb(aeOy;mfxhkbp0jL+v|S65eAjYbno@>_)oN8)tyaEHr|XMEBC8Dz4c$dWMctE=lS6rVc@0LRF$4e% zK@fk<%*+G Date: Sat, 5 Oct 2024 20:16:45 +1000 Subject: [PATCH 256/295] Add missing icon files for nodes. --- share/icons/mmImagePlaneShape.svg | 109 ++++++++++++++++++++++++++ share/icons/mmLineShape.svg | 103 ++++++++++++++++++++++++ share/icons/out_mmMarkerTransform.png | Bin 0 -> 496 bytes 3 files changed, 212 insertions(+) create mode 100644 share/icons/mmImagePlaneShape.svg create mode 100644 share/icons/mmLineShape.svg create mode 100644 share/icons/out_mmMarkerTransform.png diff --git a/share/icons/mmImagePlaneShape.svg b/share/icons/mmImagePlaneShape.svg new file mode 100644 index 000000000..e00192f85 --- /dev/null +++ b/share/icons/mmImagePlaneShape.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/mmLineShape.svg b/share/icons/mmLineShape.svg new file mode 100644 index 000000000..d67ba3219 --- /dev/null +++ b/share/icons/mmLineShape.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/out_mmMarkerTransform.png b/share/icons/out_mmMarkerTransform.png new file mode 100644 index 0000000000000000000000000000000000000000..00ec7632158a22c390925c5af69faaeb54a09785 GIT binary patch literal 496 zcmV#z)H^z_Jl7g+!tdQBhDx z2(?y0uO_0QRcIw7+aQ8aZ!8fuk{up1Z|1$HQ7vXhCw}ccIXAf<76~aA3dI0Mm1Whs zOQm%{#qpUWVNI#Y)@*kE=Iu=ZCEzfQZ%7gvumH3%b{akr_-+1yP#NQwecuFHrxIEz z7#A4-WcL9tR#+Jr=(lZqu)bi!W9!k;R=KZlwyM<1V;H{d)@sGI_Vx}9!(U9sh_>V7 zjZ8(##V*&KsIOlL5doK;F{^pk&E;K}6VE$%3W8G3ahB@&au6t>no(L)k@8DJX-o#N zpTjU}aU6u!TnZ75<#OsyN@MEA6)0r`Sqo?erhgd8=5jvY)6vwlbN2e`Z9hEB0N21A z-!F78aVfXO;>f-4Px(rTq4V Date: Sat, 5 Oct 2024 20:17:48 +1000 Subject: [PATCH 257/295] MM Image Plane 2 - Add missing node icon --- share/icons/edit/node/mmImagePlaneShape2.svg | 227 +++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 share/icons/edit/node/mmImagePlaneShape2.svg diff --git a/share/icons/edit/node/mmImagePlaneShape2.svg b/share/icons/edit/node/mmImagePlaneShape2.svg new file mode 100644 index 000000000..99c326e88 --- /dev/null +++ b/share/icons/edit/node/mmImagePlaneShape2.svg @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + From 542c1a371e381db0b1dbdf2115774ea88bd43822 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 20:24:00 +1000 Subject: [PATCH 258/295] Docs - Fix build version for Visual Studio/MSVC --- BUILD.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/BUILD.md b/BUILD.md index e3b5c1e8a..a85d42145 100644 --- a/BUILD.md +++ b/BUILD.md @@ -87,7 +87,8 @@ mmSolver v0.4.0 a C++ compiler with at least C++11 is required. - [Visual Studio 2015 update 3 (MSVC 14.0)](https://visualstudio.microsoft.com/downloads/) (Maya 2018 and 2019) - [Visual Studio 2017 (MSVC 15.0)](https://visualstudio.microsoft.com/downloads/) (Maya 2020) - [Visual Studio 2019 (MSVC 16.0)](https://visualstudio.microsoft.com/downloads/) (Maya 2022 and 2023) - - [Visual Studio 2019 (MSVC 19.0)](https://visualstudio.microsoft.com/downloads/) (Maya 2024) + - [Visual Studio 2022 (MSVC 17.0)](https://visualstudio.microsoft.com/downloads/) (Maya 2024) + - [Visual Studio 2022 (MSVC 17.8.3+)](https://visualstudio.microsoft.com/downloads/) (Maya 2025) ## Rust From 3a867affab1c7e73d2a6d9691af1437c6939e4e2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 20:27:32 +1000 Subject: [PATCH 259/295] Update node type icons --- share/icons/mmBundleShape.svg | 109 +++++++------ share/icons/mmMarkerGroupTransform.svg | 168 +++++++++++---------- share/icons/mmMarkerShape.svg | 101 +++++++------ share/icons/mmSkyDomeShape.svg | 2 +- share/icons/out_mmBundleShape.png | Bin 774 -> 729 bytes share/icons/out_mmImagePlaneShape.png | Bin 841 -> 857 bytes share/icons/out_mmImagePlaneShape2.png | Bin 841 -> 857 bytes share/icons/out_mmLineShape.png | Bin 487 -> 503 bytes share/icons/out_mmMarkerGroupTransform.png | Bin 642 -> 574 bytes share/icons/out_mmMarkerShape.png | Bin 501 -> 513 bytes 10 files changed, 203 insertions(+), 177 deletions(-) diff --git a/share/icons/mmBundleShape.svg b/share/icons/mmBundleShape.svg index 22cc6c7f5..1f144644f 100644 --- a/share/icons/mmBundleShape.svg +++ b/share/icons/mmBundleShape.svg @@ -1,53 +1,52 @@ - - + height="64px" + width="64px"> - - - - + refX="0.0" + refY="0.0" + orient="auto"> + transform="scale(0.8) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path5670" /> + mode="lighten" /> + + + + image/svg+xml + + + + + + style="filter:url(#filter6313)" + id="layer1"> + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,30.399608,-11.249227)"> + d="m 35,29 c 0,2.761424 -2.238576,5 -5,5 -2.761424,0 -5,-2.238576 -5,-5 0,-2.761424 2.238576,-5 5,-5 2.761424,0 5,2.238576 5,5 z" + style="fill:#000000;stroke:#000000;stroke-width:1.09090912;stroke-linejoin:miter;stroke-miterlimit:4;marker-start:none" + transform="matrix(0.91666667,0,0,0.91666668,4.4999999,5.4166663)" /> + d="m 35,29 c 0,2.761424 -2.238576,5 -5,5 -2.761424,0 -5,-2.238576 -5,-5 0,-2.761424 2.238576,-5 5,-5 2.761424,0 5,2.238576 5,5 z" + style="fill:#36f047;fill-opacity:1;stroke:#36f047;stroke-width:1.09090912;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;marker-start:none" + transform="matrix(0.64818122,0.64818122,-0.64818123,0.64818123,31.309353,-5.5075849)" /> + d="m 42.766029,-26.450687 5.97307,0 0,18.0005184 -5.97307,0 0,-18.0005184 z" + style="fill:#36f047;fill-opacity:1;stroke:#36f047;stroke-width:0.99999964;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /> + d="m 42.744606,9.0500851 5.97307,0 0,18.0005189 -5.97307,0 0,-18.0005189 z" + style="fill:#36f047;fill-opacity:1;stroke:#36f047;stroke-width:0.99999964;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /> + d="m 54.744576,-2.4501736 18.000001,0 0,6 -18.000001,0 0,-6 z" + style="fill:#36f047;fill-opacity:1;stroke:#36f047;stroke-width:0.99999964;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /> + d="m 18.744608,-2.4501736 18.000002,0 0,6 -18.000002,0 0,-6 z" + style="fill:#36f047;fill-opacity:1;stroke:#36f047;stroke-width:0.99999964;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1" + transform="matrix(0.70710678,0.70710678,-0.70710678,0.70710678,0,0)" /> diff --git a/share/icons/mmMarkerGroupTransform.svg b/share/icons/mmMarkerGroupTransform.svg index 1bff2d87d..03cba7595 100644 --- a/share/icons/mmMarkerGroupTransform.svg +++ b/share/icons/mmMarkerGroupTransform.svg @@ -1,177 +1,191 @@ - - + height="64px" + width="64px"> + refX="0.0" + refY="0.0" + orient="auto"> + transform="scale(0.8) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path5670" /> + id="filter6313-0" + color-interpolation-filters="sRGB"> + mode="lighten" /> + + + + image/svg+xml + + + + + + style="display:inline" + id="layer1"> + id="g3746" + transform="matrix(0.25692303,0,0,0.25692303,-0.6043351,8.4859998)"> + id="g3874" + transform="matrix(0.64606019,0,0,0.64558621,10.629909,10.845696)"> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:1.09090912000000007;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> diff --git a/share/icons/mmMarkerShape.svg b/share/icons/mmMarkerShape.svg index 3b121f6a3..c946188ae 100644 --- a/share/icons/mmMarkerShape.svg +++ b/share/icons/mmMarkerShape.svg @@ -1,29 +1,42 @@ - - + height="64px" + width="64px"> + refX="0.0" + refY="0.0" + orient="auto"> + transform="scale(0.8) translate(12.5,0)" + style="fill-rule:evenodd;stroke:#000000;stroke-width:1.0pt;marker-start:none" + d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z " + id="path5670" /> + + + + image/svg+xml + + + + + + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:1.09090912000000007;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + style="fill:#e83b3b;fill-opacity:1;stroke:#e83b3b;stroke-width:0.99999963999999997;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> diff --git a/share/icons/mmSkyDomeShape.svg b/share/icons/mmSkyDomeShape.svg index 263b275be..8cc3a5dda 100644 --- a/share/icons/mmSkyDomeShape.svg +++ b/share/icons/mmSkyDomeShape.svg @@ -88,7 +88,7 @@ image/svg+xml - + diff --git a/share/icons/out_mmBundleShape.png b/share/icons/out_mmBundleShape.png index 17a6d702351cba7b9d200d7f7410df01c961714f..fb230f545f7a01b1ce9a5079d9dc0f301e079e4f 100644 GIT binary patch delta 644 zcmV-~0(w7S%VMbMr2Komi!_-I{N@e$CCq9BNmO53=o zZIVeEOOrHBO)`@(iH#czN!m1ZC;0Eqz5n^R=iDFo&s6|rpLJccJ+wKiS=*y-(-!}K z&bugp6rZb#i;lSK3RDXx^3A+Qf2*~7B^ae7JeqoP$UG#B72lQ63B@p~^~B8@ct%xA zyVafbqp2s6@5P>$d8d&`Sr5_DW;vL_)CF%kC+6+}$SeUS-^a!ox*)1HYW8pjo*^vR z&u-ItlTTygi!gN>FNl`5$YKV;GsB{P^hW9d5L$L*00`d<-%kz1j>`VD!fn#9bPe4P zU>yKZ)A_4}r>(M>LFm-iN$IuJ1>jqy)s(2Q*L2wONRYtexou|G+3k_tedvYG{8)(a z4n6?Dx++kaT7((rIeWrYuqRvv!=1J-0O|l3D>W<`xlG=C;@6tRLWyUM0Dc00@cs4E z+M{k$tIsinmt76|>&+ATOJ*6u+5sWxc-wSitE*BURS8BZ2@m0EQ~cdrII#EgW&oTJ zbb3fFy+ih=v+_r2pLlHKRe3KdKPbtWn89@4SUe}@1_8_wN?9N}uA4Jz8Pwo_wxi+G z{IiI!B5;MA1ppxRAvMC*uwj*NPTkEJvfrAA^isNr{x7Av_gge0000z)L%$bQ5*;G-`Sn>I)^Sb z#UQ3_cRnNondXv$N?m4IJ^2;{NwAPGA9{{J2qJ@${tyxBF#t*Z@+SgYwR?18A{Cq)`AVG}ih0Q7T`3L}sJ0$cJC&=Ppl=?)b?z(at2|(g>wf8)Y^MKf1j#02Co& zZ0P~}MDj%A*=5=st!>W`^QiKFWJa(y++f}a?RqrCap5!~{saK{e#_m*JN*vHvlc0B zUBRpE=oU~}T)CP21K4XbUU6JFgNU=6Yy5XSPx*eoL-IzH>{6PJ3yEGrhIPE) delta 10 Rcmcb~c9Ly^%0?4aW&jrL18@KU diff --git a/share/icons/out_mmImagePlaneShape2.png b/share/icons/out_mmImagePlaneShape2.png index 2c0d8526aec09a1842808d2d340b6edaa1e82d6d..ba9e09d4e5aa6e83f4d5756819acaff7eb7424f9 100644 GIT binary patch delta 24 ecmX@fc9U&_3NK5slV=DA5Y%v_bZk`AUIPE) delta 10 Rcmcb~c9Ly^%0?4aW&jrL18@KU diff --git a/share/icons/out_mmLineShape.png b/share/icons/out_mmLineShape.png index c1f5a38483403c7427b4f9a59ef68467021d1321..7559084042fbc28e4ec05a83b5cba8087fcf11db 100644 GIT binary patch delta 24 ecmaFP{GEA%3NK5slV=DA5Y%v_bZk^S#s~mi1P3Jm delta 10 Rcmey){G54$%0`pJi~t%F1VR7+ diff --git a/share/icons/out_mmMarkerGroupTransform.png b/share/icons/out_mmMarkerGroupTransform.png index 6e746397dbbfc70bc19b4bdff349bca2f4a51402..5ee041ae77097865b9a2e3f4f820b2171c3d7125 100644 GIT binary patch delta 488 zcmVzlQBzMQ5c4wBocZXIfj6x zpn?{zKfu%}_e=%NkmMJLA*)*kH-A7~9I`b8q@%@@)WOxQx=GWtwMd7{jZg*a(p(iu zzYa}8Zq3b2JNb_1yu9Z*?{^M7bcKd(#$vIji9{kjYMA3VZ^woilF8(&N~MyEL?VHI zVZ~zco8ve;zJW?9Gn2_YH%*h{l@)$WP4QxLbD-C@?O)Aia|gKgJ<+nPJ=b+bMC60k zvc0@4e#+lFO~RY7|dA~w+9D3@9xdb)yu=f>Ha_u zp;F5HJ~8pg6#)^#z^q|#A;Rh19Z%wMPDB7`x7(bYot+NtE|p5v`n&7uczs>omr9Avo^>_b8Cn8eUTDH^aK)N1bXR-t7{?^46bKeu^=K&~}%i#{)Pd{r2hyv3AH|>`V ewEw~X!1NnV);~ygX#frY0000T)W2&QVH^hV=b}SSxKl7L zDM(8i+u5YFh<8p|>e2roi24s?2of_JD9K`03k^u=()@zDbjZ>rq)_hKp-q3h=a$l; z&7?uN_;iU@y*n+`FTBIuy*%&d{k|_Jk;F7>W~ylbg5y5MVzK#XH2UOUf*i-W7DCj2 z#|9=6iD$K1?QtLwm>5_pm6}3`7a-|56h*mTsZ@TF3N%#h) zQmIC()gnn+n#SiVE53onV(}9InWXojsH%F;FpLMGP-x;-o6ROo(>x-11;AUFPN!e@ zbR_vv*SY5-EEEcT9ZB~otE!qPm&-$cy5Fm-h|kTrbhd5NvaG*40Mh8i-d|d}yrU=) zNs<6bLSStT%FYfx%Q9~3IyxsO{kg9?oy(`Er?&jUwSiO4<#L|7zAzGrEEtCI$V)d` zLWr(qSqGlFo-mWi9Qx@<@_2KTFSoZR>PR9eNz%No>#2IZ{>zsYj{^Xz)hfp707w7= v02U|i^=3l=zLUK64II7L|65ZS|6~6FUFiFQh7$nr00000NkvXXu0mjf30M%= diff --git a/share/icons/out_mmMarkerShape.png b/share/icons/out_mmMarkerShape.png index b854c0ac1a358a6f8f2e89642df44f5949706806..78fe139e78fd12829ed2e474ba656b73cd4392eb 100644 GIT binary patch delta 427 zcmV;c0aX6=1AzpPJPMrv00f-@d^BsJkwz$gen~_@R5*?0l08eqKp4lLq(@EP)Hae* zv3>yUBJENqMd;>75JbdTM;$s!b@Mxj;OYxFxG0r^gQAm4hql@mC`S_-lN`<6IanPc zG%bn<{-%Tb@#pUOKLEc?Ba;bSr&8w^Gc#8Uk;s&rFS;h`dO$&FZbFh~>x3+QKX5>Q z{$U_;6R@VKr-bAUN=K`fb?fE}007VFw2yf_lJj!XH1(pQh<2;BU9qfaO;uNyWtmod zzM#_;DYfE-f{6eCs)QUxC28I%?W*eKB0_f&gx14>06ZE-qU`nVbH;K`H?L`R0Ei<1 z0E00<<=o?x3`$2TgeVd8KAAS9V+=rl;GB!R`@0V;N`nC18EzDdYuL8GEN#2fxtWf| zjt`s7gifiPE0wb2lpbUojWBl<`fA(wbB4XBC6oRdz@EwZ&`!C$d4h4Zm+#s#3WbJc zLRJ`K;*)8<0Kir+zpp(M1c7&tyr`-d VpczsUUf}=$002ovPDHLkV1leh#ialM delta 415 zcmV;Q0bu@t1oZ=uJPK0)00dJ30C$A zgh8@ab9ev%&9W|4gz{ljvF*mTs@4(9meZ01XQsKnrK(o}#;0L>*Xigx04Wv#z#(Lb zP|AgmLrCIjI9#1v>W{~30i{epDI2zbE+I0&*Bc&d+QGD;)jh9yqA1l$yPf^;{p5M8 zb(D+6t`7}Ep#aQxYx#a}fAWU2(%RS%d%l0+5rWE^R;+fr`Ya#$%Lt+PGQ;c=p68xj z_Z0w2v;4es7({`;zbU`JdhepxuNlU}kdU~6@ne*a9GiLb3F;WOMM^<6r2qg5002ov JPDHLkV1lXi#|Hoa From 9d942dfcc110e5459a8678051dad598b6e8e1134 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 20:28:42 +1000 Subject: [PATCH 260/295] Add missing node icon plain SVG files --- share/icons/mmImagePlaneShape2.svg | 109 +++++++++++++++++++++++++ share/icons/mmMarkerTransform.svg | 127 +++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 share/icons/mmImagePlaneShape2.svg create mode 100644 share/icons/mmMarkerTransform.svg diff --git a/share/icons/mmImagePlaneShape2.svg b/share/icons/mmImagePlaneShape2.svg new file mode 100644 index 000000000..e00192f85 --- /dev/null +++ b/share/icons/mmImagePlaneShape2.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/mmMarkerTransform.svg b/share/icons/mmMarkerTransform.svg new file mode 100644 index 000000000..2e6701430 --- /dev/null +++ b/share/icons/mmMarkerTransform.svg @@ -0,0 +1,127 @@ + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + From be5b8de3900da018d3119043e3d708d8450827f2 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 20:32:30 +1000 Subject: [PATCH 261/295] Add Hi-DPI icons for mmSolver These Hi-DPI icons follow the naming convention defined by Maya; https://help.autodesk.com/view/MAYAUL/2025/ENU/?guid=GUID-F2900709-59D3-4E67-A217-4FECC84053BE with the page name "Use a high resolution custom icon for a shelf item". This includes a MS Windows Batch script to auto-generate all the icons from the .svg files. The README.md file is intended as documentation to help me remind myself of the details if of the icons... and new people on the project. --- share/icons/README.md | 61 ++++++++++ share/icons/attributeTools_150.png | Bin 0 -> 3485 bytes share/icons/attributeTools_200.png | Bin 0 -> 4765 bytes share/icons/build_icons.bash | 33 ++++++ share/icons/build_icons.bat | 111 ++++++++++++++++++ share/icons/bundleTools_150.png | Bin 0 -> 2787 bytes share/icons/bundleTools_200.png | Bin 0 -> 3673 bytes share/icons/cameraContext_150.png | Bin 0 -> 3557 bytes share/icons/cameraContext_200.png | Bin 0 -> 4814 bytes share/icons/cameraTools_150.png | Bin 0 -> 2058 bytes share/icons/cameraTools_200.png | Bin 0 -> 2636 bytes share/icons/convertToMarker_150.png | Bin 0 -> 1996 bytes share/icons/convertToMarker_200.png | Bin 0 -> 2564 bytes share/icons/createBundle_150.png | Bin 0 -> 2692 bytes share/icons/createBundle_200.png | Bin 0 -> 3606 bytes share/icons/createCamera_150.png | Bin 0 -> 2385 bytes share/icons/createCamera_200.png | Bin 0 -> 3112 bytes share/icons/createImagePlane_150.png | Bin 0 -> 1624 bytes share/icons/createImagePlane_200.png | Bin 0 -> 2149 bytes share/icons/createLens_150.png | Bin 0 -> 3471 bytes share/icons/createLens_200.png | Bin 0 -> 4647 bytes share/icons/createMarker_150.png | Bin 0 -> 2181 bytes share/icons/createMarker_200.png | Bin 0 -> 2800 bytes share/icons/displayTools_150.png | Bin 0 -> 2831 bytes share/icons/displayTools_200.png | Bin 0 -> 3850 bytes share/icons/fileInputOutput_150.png | Bin 0 -> 2183 bytes share/icons/fileInputOutput_200.png | Bin 0 -> 3087 bytes share/icons/generalTools_150.png | Bin 0 -> 4389 bytes share/icons/generalTools_200.png | Bin 0 -> 6693 bytes share/icons/hotkeySwitcher_150.png | Bin 0 -> 2157 bytes share/icons/hotkeySwitcher_200.png | Bin 0 -> 2721 bytes share/icons/keyFrameAdd_150.png | Bin 0 -> 2061 bytes share/icons/keyFrameAdd_200.png | Bin 0 -> 2768 bytes share/icons/keyFrameNext_150.png | Bin 0 -> 2065 bytes share/icons/keyFrameNext_200.png | Bin 0 -> 2880 bytes share/icons/keyFramePrevious_150.png | Bin 0 -> 1918 bytes share/icons/keyFramePrevious_200.png | Bin 0 -> 2654 bytes share/icons/keyFrameRemove_150.png | Bin 0 -> 2321 bytes share/icons/keyFrameRemove_200.png | Bin 0 -> 3223 bytes share/icons/lensTools_150.png | Bin 0 -> 3152 bytes share/icons/lensTools_200.png | Bin 0 -> 4192 bytes share/icons/lineTools_150.png | Bin 0 -> 2415 bytes share/icons/lineTools_200.png | Bin 0 -> 3154 bytes share/icons/loadMarker_150.png | Bin 0 -> 3003 bytes share/icons/loadMarker_200.png | Bin 0 -> 4156 bytes .../markerBundleCombineSelection_150.png | Bin 0 -> 1671 bytes .../markerBundleCombineSelection_200.png | Bin 0 -> 2312 bytes share/icons/markerBundleLinkTools_150.png | Bin 0 -> 2968 bytes share/icons/markerBundleLinkTools_200.png | Bin 0 -> 4441 bytes share/icons/markerBundleSwapSelection_150.png | Bin 0 -> 2590 bytes share/icons/markerBundleSwapSelection_200.png | Bin 0 -> 3806 bytes share/icons/markerTools_150.png | Bin 0 -> 2265 bytes share/icons/markerTools_200.png | Bin 0 -> 2847 bytes share/icons/meshTools_150.png | Bin 0 -> 3825 bytes share/icons/meshTools_200.png | Bin 0 -> 5499 bytes share/icons/mmSolverRunFrame_150.png | Bin 0 -> 1441 bytes share/icons/mmSolverRunFrame_200.png | Bin 0 -> 1999 bytes share/icons/mmSolverRun_150.png | Bin 0 -> 1971 bytes share/icons/mmSolverRun_200.png | Bin 0 -> 2835 bytes share/icons/mmSolverWindow_150.png | Bin 0 -> 1711 bytes share/icons/mmSolverWindow_200.png | Bin 0 -> 2553 bytes share/icons/selectionTools_150.png | Bin 0 -> 2988 bytes share/icons/selectionTools_200.png | Bin 0 -> 3946 bytes share/icons/zDepthTools_150.png | Bin 0 -> 1982 bytes share/icons/zDepthTools_200.png | Bin 0 -> 2612 bytes 65 files changed, 205 insertions(+) create mode 100644 share/icons/README.md create mode 100644 share/icons/attributeTools_150.png create mode 100644 share/icons/attributeTools_200.png create mode 100644 share/icons/build_icons.bash create mode 100644 share/icons/build_icons.bat create mode 100644 share/icons/bundleTools_150.png create mode 100644 share/icons/bundleTools_200.png create mode 100644 share/icons/cameraContext_150.png create mode 100644 share/icons/cameraContext_200.png create mode 100644 share/icons/cameraTools_150.png create mode 100644 share/icons/cameraTools_200.png create mode 100644 share/icons/convertToMarker_150.png create mode 100644 share/icons/convertToMarker_200.png create mode 100644 share/icons/createBundle_150.png create mode 100644 share/icons/createBundle_200.png create mode 100644 share/icons/createCamera_150.png create mode 100644 share/icons/createCamera_200.png create mode 100644 share/icons/createImagePlane_150.png create mode 100644 share/icons/createImagePlane_200.png create mode 100644 share/icons/createLens_150.png create mode 100644 share/icons/createLens_200.png create mode 100644 share/icons/createMarker_150.png create mode 100644 share/icons/createMarker_200.png create mode 100644 share/icons/displayTools_150.png create mode 100644 share/icons/displayTools_200.png create mode 100644 share/icons/fileInputOutput_150.png create mode 100644 share/icons/fileInputOutput_200.png create mode 100644 share/icons/generalTools_150.png create mode 100644 share/icons/generalTools_200.png create mode 100644 share/icons/hotkeySwitcher_150.png create mode 100644 share/icons/hotkeySwitcher_200.png create mode 100644 share/icons/keyFrameAdd_150.png create mode 100644 share/icons/keyFrameAdd_200.png create mode 100644 share/icons/keyFrameNext_150.png create mode 100644 share/icons/keyFrameNext_200.png create mode 100644 share/icons/keyFramePrevious_150.png create mode 100644 share/icons/keyFramePrevious_200.png create mode 100644 share/icons/keyFrameRemove_150.png create mode 100644 share/icons/keyFrameRemove_200.png create mode 100644 share/icons/lensTools_150.png create mode 100644 share/icons/lensTools_200.png create mode 100644 share/icons/lineTools_150.png create mode 100644 share/icons/lineTools_200.png create mode 100644 share/icons/loadMarker_150.png create mode 100644 share/icons/loadMarker_200.png create mode 100644 share/icons/markerBundleCombineSelection_150.png create mode 100644 share/icons/markerBundleCombineSelection_200.png create mode 100644 share/icons/markerBundleLinkTools_150.png create mode 100644 share/icons/markerBundleLinkTools_200.png create mode 100644 share/icons/markerBundleSwapSelection_150.png create mode 100644 share/icons/markerBundleSwapSelection_200.png create mode 100644 share/icons/markerTools_150.png create mode 100644 share/icons/markerTools_200.png create mode 100644 share/icons/meshTools_150.png create mode 100644 share/icons/meshTools_200.png create mode 100644 share/icons/mmSolverRunFrame_150.png create mode 100644 share/icons/mmSolverRunFrame_200.png create mode 100644 share/icons/mmSolverRun_150.png create mode 100644 share/icons/mmSolverRun_200.png create mode 100644 share/icons/mmSolverWindow_150.png create mode 100644 share/icons/mmSolverWindow_200.png create mode 100644 share/icons/selectionTools_150.png create mode 100644 share/icons/selectionTools_200.png create mode 100644 share/icons/zDepthTools_150.png create mode 100644 share/icons/zDepthTools_200.png diff --git a/share/icons/README.md b/share/icons/README.md new file mode 100644 index 000000000..e17a76c32 --- /dev/null +++ b/share/icons/README.md @@ -0,0 +1,61 @@ +# Icons + +The icons used in mmSolver are contained in this directory. + +The "edit/" sub-directory contain the editable files, used to create the icons. + +## Guidelines + +The icons in mmSolver should: +- be readable and obvious to users. +- be consistent and share a common language with all other icons in + mmSolver (and in Autodesk Maya). +- be defined in vector formats whenever possible (to allow rendering + bitmaps at higher resolutions for Hi-DPI screens). +- use open standard formats (like SVG format) where possible to avoid + proprietary formats and avoid vendor lock-in. + +Note: [Inkscape](https://inkscape.org/) is a really good Free Software +for creating vector artwork icons - it is recommended to use it for +creating the icons for mmSolver. + +## Shelf Icon Naming Conventions + +Icons for Maya shelf buttons require the following naming convention: +``` +name.png # The default 32x32 icon. +name_150.png # The 150% 48x48 icon. +name_200.png # The 200% 64x64 icon. +``` + +Please see [High resolution shelf +icons](https://help.autodesk.com/view/MAYAUL/2025/ENU/?guid=GUID-F2900709-59D3-4E67-A217-4FECC84053BE) +for more details. + +## Node Icon Naming Conventions + +Icons for Maya nodes in the Autodesk Maya Outliner require the +following naming convention: +``` +out_nodeType.png +``` + +Additionally the icons must have a resolution of 20x20. + +## Building Icons + +To speed up the process of outputting the bitmap icons with the +correct naming convention and resolution, there is a script (currently +only on Windows) to automate the process. + +This uses [Inkscape](https://inkscape.org/) to render the SVG icons +into PNG files. + +On Windows: +```cmd +:: Go to root of the icons directory. +> CD \share/icons/ + +:: Runs Inkscape for all files and exports all PNG/SVG files. +> build_icons.bat +``` diff --git a/share/icons/attributeTools_150.png b/share/icons/attributeTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb2b920ae4a9a3f6ad50de375b9bdde217c7edc GIT binary patch literal 3485 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 literal 0 HcmV?d00001 diff --git a/share/icons/attributeTools_200.png b/share/icons/attributeTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..d75e0e986e905f7c68491a5d203b19abe370125c GIT binary patch literal 4765 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 literal 0 HcmV?d00001 diff --git a/share/icons/build_icons.bash b/share/icons/build_icons.bash new file mode 100644 index 000000000..09a92637a --- /dev/null +++ b/share/icons/build_icons.bash @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright (C) 2024 David Cattermole. +# +# This file is part of mmSolver. +# +# mmSolver is free software: you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# mmSolver is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with mmSolver. If not, see . +# --------------------------------------------------------------------- +# +# Runs 'inkscape' to export PNG files from .svg files. +# +# This file exports multiple resolutions using a naming convention. + +# Any subsequent commands which fail will cause the shell script to +# exit immediately. +set -e + +THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +# TODO: Write this script to match the Windows .bat equal. + +cd ${THIS_DIR} diff --git a/share/icons/build_icons.bat b/share/icons/build_icons.bat new file mode 100644 index 000000000..220416828 --- /dev/null +++ b/share/icons/build_icons.bat @@ -0,0 +1,111 @@ +@ECHO OFF +SETLOCAL +:: +:: Copyright (C) 2024 David Cattermole. +:: +:: This file is part of mmSolver. +:: +:: mmSolver is free software: you can redistribute it and/or modify it +:: under the terms of the GNU Lesser General Public License as +:: published by the Free Software Foundation, either version 3 of the +:: License, or (at your option) any later version. +:: +:: mmSolver is distributed in the hope that it will be useful, +:: but WITHOUT ANY WARRANTY; without even the implied warranty of +:: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +:: GNU Lesser General Public License for more details. +:: +:: You should have received a copy of the GNU Lesser General Public License +:: along with mmSolver. If not, see . +:: --------------------------------------------------------------------- +:: +:: Runs 'inkscape' to export PNG files from .svg files. +:: +:: This file exports multiple resolutions using a naming convention. + +SET THIS_DIR=%~dp0 + +SET INKSCAPE_EXE="%PROGRAMFILES%\Inkscape\inkscape.com" + +SET OUTPUT_DIR=%THIS_DIR% + +:: Export node PNG icons at 20 x 20 resolution, for use in the Outliner. +:: +:: 20 x 20 is the resolution expected by Maya's Outliner. +SET OUT_RESOLUTION=20 +for /r %%i in (edit\node\*.svg) do ( + echo ------------------------------------------------------------- + echo Input file: %%i + echo Output file: %OUTPUT_DIR%\out_%%~ni.png + + %INKSCAPE_EXE% ^ + --without-gui ^ + --export-area-page ^ + --export-width=%OUT_RESOLUTION% ^ + --export-height=%OUT_RESOLUTION% ^ + --file="%%i" ^ + --export-png="%OUTPUT_DIR%\out_%%~ni.png" +) + +:: Export node plain SVG icons. +SET OUT_RESOLUTION=20 +for /r %%i in (edit\node\*.svg) do ( + echo ------------------------------------------------------------- + echo Input file: %%i + echo Output file: %OUTPUT_DIR%\%%~ni.svg + + %INKSCAPE_EXE% ^ + --without-gui ^ + --export-area-page ^ + --file="%%i" ^ + --export-plain-svg="%OUTPUT_DIR%\%%~ni.svg" +) + +:: Export shelf PNG icons at 32 x 32 resolution. +SET OUT_RESOLUTION=32 +for /r %%i in (edit\shelf\*.svg) do ( + echo ------------------------------------------------------------- + echo Input file: %%i + echo Output file: %OUTPUT_DIR%\%%~ni_%OUT_RESOLUTION%x%OUT_RESOLUTION%.png + + %INKSCAPE_EXE% ^ + --without-gui ^ + --export-area-page ^ + --export-width=%OUT_RESOLUTION% ^ + --export-height=%OUT_RESOLUTION% ^ + --file="%%i" ^ + --export-png="%OUTPUT_DIR%\%%~ni.png" +) + +:: Export shelf PNG icons at 48 x 48 resolution (150% scale). +SET OUT_RESOLUTION=48 +for /r %%i in (edit\shelf\*.svg) do ( + echo ------------------------------------------------------------- + echo Input file: %%i + echo Output file: %OUTPUT_DIR%\%%~ni_150.png + + %INKSCAPE_EXE% ^ + --without-gui ^ + --export-area-page ^ + --export-width=%OUT_RESOLUTION% ^ + --export-height=%OUT_RESOLUTION% ^ + --file="%%i" ^ + --export-png="%OUTPUT_DIR%\%%~ni_150.png" +) + + +:: Export shelf PNG icons at 64 x 64 resolution (200% scale). +SET OUT_RESOLUTION=64 +for /r %%i in (edit\shelf\*.svg) do ( + echo ------------------------------------------------------------- + echo Input file: %%i + echo Output file: %OUTPUT_DIR%\%%~ni_200.png + + %INKSCAPE_EXE% ^ + --without-gui ^ + --export-area-page ^ + --export-width=%OUT_RESOLUTION% ^ + --export-height=%OUT_RESOLUTION% ^ + --file="%%i" ^ + --export-png="%OUTPUT_DIR%\%%~ni_200.png" +) diff --git a/share/icons/bundleTools_150.png b/share/icons/bundleTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..f6f789638907c745f356b7d17cacb8e59a03fbba GIT binary patch literal 2787 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 literal 0 HcmV?d00001 diff --git a/share/icons/bundleTools_200.png b/share/icons/bundleTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..46717ed4c99bad2e0ecbce571bfbdf8de01a599f GIT binary patch literal 3673 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 literal 0 HcmV?d00001 diff --git a/share/icons/cameraContext_150.png b/share/icons/cameraContext_150.png new file mode 100644 index 0000000000000000000000000000000000000000..216951cb327e43c5fe2b6197772ca3a76b8a77bb GIT binary patch literal 3557 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 literal 0 HcmV?d00001 diff --git a/share/icons/cameraTools_150.png b/share/icons/cameraTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..33872b12fdffd78f5a951df5cbb18bf9ac0d6c87 GIT binary patch literal 2058 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 literal 0 HcmV?d00001 diff --git a/share/icons/createCamera_150.png b/share/icons/createCamera_150.png new file mode 100644 index 0000000000000000000000000000000000000000..c2720c5623cd7507f6ba9c89b2fe028d9c574fe3 GIT binary patch literal 2385 zcmV-X39j~uP)5OCyAQvUu@gtX#PgGMQ|~0lK@pQCeE69UB`f0kH7p@zBuF z08tdLGRBs!Sg`_GSy`l}rsjp3=KzF|UlKyFbLURT$&n*R#J0A!Y58Ui0AuV^tJOMk^ypFX;lo$%ZCrZ@^Tav6+sY$H+p6h00V%|&Q7z#;rNI#R&ndrEu1`g5~HJ| zuQg2tXcI+|w70j0emHuO675JarFNE z`(TWv@H}7nf8D_UHwp;}`H(SI!59l9gp5*37qqvx_xxx803aYBV7^o;O=pb#gXj5% z_nl`>z?7#@b8~Y7fKT0S_eWBx)Z6KFTAWVjx1OG!e+&-~|JO_hX=-ZHavZlA03;I0 z=L;4rc>TkFiX;Hg+}xZ=2)T%f*X^yXtxzhJK3c8zSC=kb3IPx^Q^6-iDGg?f9Rq-M z>(-e86u*s6Vl*24ola*FA%tseY{afzyZElIt}g-DV`F1i`uO;S0zgw!(<+YR;xWG9 z)e}N~&lu}oxNzZTj4|@`>C-iWAS6*reHddcgpiVmh=@J_sH>|}Din%L##jV^pAbT9 zjIjon%XKLxCdS#)(h@}o**y71{J?<&zfDd~)&lr@L`1|x#uzmi45^HI+I~x+sdw;c!5yR1&-0K4LbTm#3tpJZ@=ei2~5*cDvc|@Gv|*JqgEg zuvjcz7K`Pl!NI}l9v&XwJLBihox7Kwp8gA!N_CY|x<(X5Haa>Ag+f8BR_l<%;fSR@ zJv|8kAP55L>+4Uv6dnM&0Kn;V$^byC)ym7t%A{MjZnYH_7P^Xxic%=0>2|vvsi~>` zadB~HDk>@ll}e?;Zns|t0GG?vyKURH#^~tizb;$0>{M!MYWc{>2)w<$gRWh>cC@v% z_3OO6y!Ocnv$L~%H*MPVT}eraQKQlPj#9eDVzFTL>eb!x@$qMF-@a}4^z_sijmE!8 z>~^~cF#fSYrBaz3j@K@T<1}4e$j!|y6a--p0EtGUIWYl_uPN%aCz=xrs zp#cEG9S(=-?Afzl5<*f%QCz|plSrjf5dih@@K}+SmbR~@r6m)fY#gF8-x%T42D$(gW-jGl}fcprBY>4O8+o0Fo4q1QVb6dWBc~)&}cLe zMbZ0B^I5G{e_vl;5JJ${*tmA;*x1+@B$U#E!NI}9US3`_Gc$AhjvYHL$Hc_kGZ+jq z0Bh`ayXTT6OD;~DCi-*BM9xLhtAI&|nT&+|P1pwsE(YuB#T+ibS_XV0D; z_Ve>YWo4!N`0?YZyLay%R;$&w0Z_)qztahVFf!RdE?Tt6+h{Zb00svKzXPyhWMl;U z_U+qiu~Q_|M}B^O$c-B} zE{BDMW!$-Q2ivx7(*UpnKyGfXZ(?F%PgqzOXS3PtGMVffN~tfUbV*%ZU0+5zeGaJ${M>({Su z?d|P-l#-Its?}*B4XCN9IR;=10II92M`~+p|2;fB{A&R7L{W@1 znM}Is>T1dP^XJuOv-wXJi{+lpX4|e*Dy1fqX+d6Io}5zJ-`3WqtgWr>Zf$M-;`s67 zuAZJA@BaS&M4sniG#X_kB_;MNSFW6Y^ypEi&1Ophpb!LMVS9Uf(9N4Sx#Hqt*~`tG zKY+CWLIKzTGy(V;fD?cZfK?N9ybJgM zz_W?ENyPw&#P}lH0-$c<_kfA{!vPEfXai7%AG`ho$_T;5+7$KV00000NkvXXu0mjf DlW=H} literal 0 HcmV?d00001 diff --git a/share/icons/createCamera_200.png b/share/icons/createCamera_200.png new file mode 100644 index 0000000000000000000000000000000000000000..b66aaa6f6fd267d7baf7553f8cc049dc691263d5 GIT binary patch literal 3112 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 literal 0 HcmV?d00001 diff --git a/share/icons/createLens_200.png b/share/icons/createLens_200.png new file mode 100644 index 0000000000000000000000000000000000000000..9c05e0762e742a377cb74668b3fc98b61c73390d GIT binary patch literal 4647 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 Hu0mjfAnsM> literal 0 HcmV?d00001 diff --git a/share/icons/createMarker_200.png b/share/icons/createMarker_200.png new file mode 100644 index 0000000000000000000000000000000000000000..65a4eff17572252538f53be8694817f4c1fbeb7f GIT binary patch literal 2800 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 literal 0 HcmV?d00001 diff --git a/share/icons/displayTools_150.png b/share/icons/displayTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..792d6cc4da0b0529b459753fee0f8468d6d37a43 GIT binary patch literal 2831 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 literal 0 HcmV?d00001 diff --git a/share/icons/displayTools_200.png b/share/icons/displayTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..4dfd6bce51bb86b1964df170151edfafc3eefb09 GIT binary patch literal 3850 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 literal 0 HcmV?d00001 diff --git a/share/icons/fileInputOutput_150.png b/share/icons/fileInputOutput_150.png new file mode 100644 index 0000000000000000000000000000000000000000..ae51f813181c767ee9ea3147245066236c4a24e9 GIT binary patch literal 2183 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 literal 0 HcmV?d00001 diff --git a/share/icons/generalTools_150.png b/share/icons/generalTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..415af6642cd37038e4f9aa482fc478948e801162 GIT binary patch literal 4389 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 literal 0 HcmV?d00001 diff --git a/share/icons/hotkeySwitcher_200.png b/share/icons/hotkeySwitcher_200.png new file mode 100644 index 0000000000000000000000000000000000000000..95c9bbe6fa882193a32e800bcab8b38c51ec39dd GIT binary patch literal 2721 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 b^xXde`C|J=>38m}00000NkvXXu0mjfhOQ8W literal 0 HcmV?d00001 diff --git a/share/icons/keyFrameAdd_150.png b/share/icons/keyFrameAdd_150.png new file mode 100644 index 0000000000000000000000000000000000000000..2c398b47c717d465c287f9a7917c037011661881 GIT binary patch literal 2061 zcmV+o2=e!dP)XAQXO@n)J2GbGigm&cdr~7x<$!9i+pU{zdC1| z&P8d-quJZWAFsVTQWi7~LUmryeWGPi%M@BV1Of`SBZGql{V=c4!k}5$y6ayp;Ol$?n(ffCh%#ji$T;sXTHYVFL7WhSm<_5i(r-nnzh1rQZ29ax}uE&g)! zA5!qFZOfV`6;Cd-e(%cFnl6sIkq1EF07F1OFd#s>J>m;#(Zw4Vym!ecHao7Zr^8i1 z$L@}R9?*{n!NMB)m4I{a@C1L89{p3Zwj-6d-4#!PtAOSO`cH+zx*GbNHRRG@)0;+- zoaI?}bpcO&e^r;NDf{NktOoQh3(}vmus!-xRRKa1KSH3_g_%dFVU6FXEevyx0=l1T z2@74_RX1ie;8u&D;Ry@dgNBL%f=eS`6$+tx67vdek)IQH3z$Xsu~1Qfsw+>`l~s+n z;7IaCcN0_+pdiE->r1INB>W17>9|g*l~!U6g^uz`dcctRq-Ek^QOb_wQhXYq$;{2{ zv0TF3BAw|`>P%SJ9rEiDy|fyKbw-RLZD9>fsk}f}6riZeeg9AO8|irO_^ZIU8wnr} z3{r~y&8vrp_5!T`djFqSg09nh-Gm6ZetiB!bBAWw4IZtTqod;Gs|-HDaY zH+*kprme@8-Nw}`t~7f50L?iDT~F88tNaf3^AZ9;&s-8^eWP)Q^@oI7NwnV3JMkL40llfa(J5!FOXv zvZMKd`q6Vo@vEKCSk9s)Uro+ewBa^$O7KP0w;iUjog9Sv(pktSQFNtZyg^!H+nw3ySXR)VzevN+Rz%%s@Y!Jl8K?Llk79H z@yp}=vMApK4g+_90wARnmaO2RQZnNve>UlL3FLtO{J8nE*xAHyV`mdveGz>}P52_kM2HqiE-oW?>?hi#ewAFG^AESxV9BBvZ0?ijb%C1zt8S2j7%w)UqUn zg2R=REqP#9IF|y417s)_8-~n`^|qO@fIVq6m{bA>{IT$yfgEZFN{W`%eQ@D`GxJ> zOIrL+p1k7c%=HAPF5h%4TVA(q`V7)W^0T=nJ*|s(kHw z_8gAnT%l0-u3;F{N`3RO%<8nYwcVL|A>`NW=AizDVHnqVz>@P(0GUkYT+y;_17wW} zQt{D;25%Yj^1l**=vlU8qvH3Sv@Fw)6sA;o{_2h4V?1Di`6>WFUAwGpi&W$$9H+PM r_UH*_D$RZl#A<;;iDS9q^AP?Ay^iWGiR3|K00000NkvXXu0mjfG1lpM literal 0 HcmV?d00001 diff --git a/share/icons/keyFrameAdd_200.png b/share/icons/keyFrameAdd_200.png new file mode 100644 index 0000000000000000000000000000000000000000..a25159dc70b48b088133690f3f56d5a0478d0384 GIT binary patch literal 2768 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 Eg8oT#j{pDw literal 0 HcmV?d00001 diff --git a/share/icons/keyFramePrevious_200.png b/share/icons/keyFramePrevious_200.png new file mode 100644 index 0000000000000000000000000000000000000000..d0bed8303b36c3e2c8dfc2f72c81a5be93d8437c GIT binary patch literal 2654 zcmV-k3ZeChP)V@DVsDQZDSE)+Ey4t6e!!!7<<8n1VTtOP1CJK9UARAJKd7h zUD_r%e%sfNYhOR^iLF94t=8{G6-{CToobW7Qmo!_h_vc^AMQ02ZIm z_f4zSx(R@x_}c{qm*KpIJBY=yCrm016uBwIF?ceH_p8j{$fb07D4D`t|G4)YOEjeE-XQ zWjgBwc6?`Z8-OjfX7-xo0f6HOY-_oxjwv3mudnY~ZX(OU;czs^-Fg9=s<1l%EM|Q08qlcfYa%`6Tlk)8i1>3c`eXN0L_`L2q8ZNz!E~x+S-bimX?yntq1`ge)!>fp66dBgxm#8&GPN-?XXxZC6B%80`&Iw z-XTfS9{{WafHiB@z;3rguh*9){%Q(9oKB~cQu-4BIzkAVnwqeF{rVMcY?jKZ2(WkW z-nA^tz5?Kj0HD+9V7J@LQu4)=U4Y)+-cM3W-vY1!D1XTpQ%(V>KQAR0VBfxdw=oR!CV(3Opr)n< z9UUDo7_K>>T@^|yfYa&R4d5UusN{<&i2x3Vqm~fzB7m;~Q?vZdH{VEyHPo<`;F2OoSeD$6oe6a^lS2j|b9hpMV22)%SlwG&kRet#B#$6~RhMn*<_kx1m*+qP}{ zd~I#5E*uU+6vc|zAC&TO0|ih>BodR7Bpr*zVm}=i7UxmT(9Q zQ;ydiiXaGamSs<6v)LC;oH+5LGiT16Qxt_Nih^_J&S7k943w_UQ=@X72dJS?NCa@s zVzDGgM@RjML}FKWclQpn*~|rlL8Q~^Rk1`l-}lV4#9%NOe5Sv@|HaA4$yK$)YCLdK z1VM;%9QQ9#6rVqS{Ps=} zdznn;H*XIPKK#H!y6T}F*WA{hW9Hk&Qwa=CoraQHht zJw2Z`nM~Z|FwGT9 zWV6{{9y@yUsk3L#UQ$&Rk|bexcz8vt#Hi7s9Pvdgkv%&1>OtG08kDU zu2@2nq{CjX_s9MH{jY^Wp$sr(3Afw5WJ}CB3F@fs-tlbV0)b1D6P{=Zc zLMdMUSjI?PQ7DCfWw|X0Ig`nRtE#Hr6-6<6^vID5pV+eHfzHm()EHADe0*j{^Dcv` z-uTC%{F>I9x^0ilY!8KkxRCgmsz3lBFB3w3Rf;SZfC9)zB9U>E$s|lnOaytJ|JwHL z+wZm6Y^HcTe$|I_G4Cqi^ZEE&Zn@=e#q9(H1|2JMgg%ka=YJlJMuW>vW93MhOeRbS zIho014h{|uKI`##{4?jR`2<*KBSfuMYr6OZmtz34fe~mGcPbR+d6Z`ooDY@fc>%!L z`uh6h>C>kt!{P7)Teoh#&0sJv`Fws(4Hn!9ilTVlU@!y-A&t{*e6+I8_Af&L=iCB~k2YnQPm(!EG zKKeM|!Rl8)F^tt}H3QfL;BEk02_Y^*$iZkd8kqUvA}{L_xu`#)<0GM)GHC$-O#sXZ zIY<5+6NHS`qt^hM&1MVPY&K>xnI=?K9b*{g{b)4m1#s2&Zn1aQq_erXPSQ)88JW3} zP!dtWp7MCTK~&yve@QIm@}6{dcIu4rIG3!hR>sE03aHTk8(s?k0V%_Xy56;#P5=M^ M07*qoM6N<$g0ME|zW@LL literal 0 HcmV?d00001 diff --git a/share/icons/keyFrameRemove_150.png b/share/icons/keyFrameRemove_150.png new file mode 100644 index 0000000000000000000000000000000000000000..e2db7c3d53225d6a7c4e9a1fbc3ed3f4e65b62b7 GIT binary patch literal 2321 zcmV+s3GViZP)7ATY;R46U&c5mJLb#`ZV z=l0IbdyXGwXPuqSzVl9LaQ!En?40ZKf1m$(&U4Oz|8t1{wE+uZ_wL=Fky1VZER$0H zanGJTPXVg1o%ZRQW)D)z2Y{P^%|eJLue*BV6%_g4i=%ANaP2qD6unS<-sudgU< zw`d6f2qn>#34lV@DHRJAwsCywR@p#XYtwDfiMB8MWdfljN!^2gCHjfD=001{cF`^3 z!*@2_EfmiL1G?~f1)30~(u&BmNhofH z;+GeVz=JK9eNid7Q`0gxI?$X2=70cw*3oWV=bAc4- zkhTWMi%xI>QRp$y9%yNP)TN8JHN>`Z`_Dn31ze6D8-xCnt?L@T4meBp$@ZIvvIzWW z?OJ!VW&K`Vh{w1{>&K~Ufu?}}&v!OA{0i_ET^JUyZA5D;y0c4rRtRn@bB>Y_0?`=o zKlHV=D?LU=l@%Fnd!Vsv{o}e2bH+=hI5Rab5=GEYec)T*p9sf7jqY&rD8_&KV{`Z z-BW4oawtjQ{%ab%y3&3}-tsxvGn{G<&l~}Ew)KyF^~3S-sdA0)k0xFQb|2^&zNL32 zK9Z~V=(=b-s|Y8|;u}S|1TcK}YJ#Txq76$clOBiTH^4_g`>TDU+mDY;ePqY@{au4O z<9`91`QB$&=?xWLUw*rQ?&DamK`K+4LMZ~8hS*tHcxk0SxMSVQ$Nhe)$ZkZPZT(}d zAC8AlS{eUb$3VPyI(}%@_>Ntho3{j=u0y&I^O7`82#)tDrVUxr3zh`#+r0LAO|Yt1 zUA@n(4uXmO_o!-E!k4H7@{5$a-P~7`}f~bG=LGhwaEF9}Zn3oG`Jle_;Y+AtP1J zWRP93$A%{wfUC)7mkLl%et0%?ZaV%-x#O8qVVKG?lrBi%gK*f8N+s-0mJ5~Lk=VPy zY4VPJKx$vdz|T%hL~QTa07)~0OBa^sUaLhcQ&JAunW!Mn{~Q?DwYhog16!J(uC1kl ztS1Hz^^R;mF%kLLE`f-VA?Vi7EErf3$RthqZ#%{>6z6qKeD^DLtN$^(uuY)zQ18ec zcG3=jNh8VffD_$jPS;Y&6vqO+1#7DnFv|V%LyyA?ZPl*WFS~PCb!WZ+^R;=@^wh z$)@EVoL2g=K|sAdINshBiSElUyn1)xADAt{RspaZgLtc?70sOx_qyb8a#|cd=#XOsosWO^d8 zH`@kAe-Jj(MWfOMV$Q}>p@@97f3)MS%Y!rPs$G_`KOf~>Y$dzHv3)Og58c-vjYrG1 zbRj4OM~5cNm(PuyQHn>mE%OACyq*D8UKLA}oN(j=$($8E`M$Vt+Lhd&eU;ynABmKycf$_a>4u!sPY0!5|W58cq z?{hn=+)mB!%<8~YikVb~sboqH#NwImNGv`UH%5TNz~6w6fe5qL>_Vv{(U|!yuU8=Q zZoQ*3CdmwRP0!nsF3Jmy6YvAIz*WFjU>&e>wj8ov8w5Il_kn)_r2YX$;r9!e=(n!G{6P8$$Hk2H@+f!KFE;uUJ8r% zfW~FDJ6bU98WnnLr>gm|sxsbEd1SJx>AiLil1O0H(Q5N|^!} z8SEpSE`CH`bkD3gw1R$mZNno{iHBq+tt%x)@CGk$Z*QN;Z?|X(0IXPnFX*bjRiM?G rqz;`u7iwpL=h?*!jcnFj^ke@AdK~!lD8A&D00000NkvXXu0mjfT}y94 literal 0 HcmV?d00001 diff --git a/share/icons/keyFrameRemove_200.png b/share/icons/keyFrameRemove_200.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5738957c281575c51a3ec14b64ab64be66920d GIT binary patch literal 3223 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 JPDHLkV1l8-Fev~4 literal 0 HcmV?d00001 diff --git a/share/icons/lensTools_150.png b/share/icons/lensTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..64d6e4ded7f395fad1e0d949d83d0992fd03249c GIT binary patch literal 3152 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 literal 0 HcmV?d00001 diff --git a/share/icons/lineTools_200.png b/share/icons/lineTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..11a6f21e7e3d590e0e2e3ebaca6414a299079242 GIT binary patch literal 3154 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 O0000xL9Y6|DN*6+q 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 AbpQYW literal 0 HcmV?d00001 diff --git a/share/icons/markerBundleSwapSelection_200.png b/share/icons/markerBundleSwapSelection_200.png new file mode 100644 index 0000000000000000000000000000000000000000..9e255aab4c52534f821e3c6e859431178dec3107 GIT binary patch literal 3806 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 UJvPW5q5uE@07*qoM6N<$g7{G^S^xk5 literal 0 HcmV?d00001 diff --git a/share/icons/markerTools_150.png b/share/icons/markerTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..93f54358d1a80713b2f3da481002eb70d14f6578 GIT binary patch literal 2265 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 literal 0 HcmV?d00001 diff --git a/share/icons/meshTools_150.png b/share/icons/meshTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..e1704fbbc45eaff2ce724bf6eca2bb446c5eb7ef GIT binary patch literal 3825 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 literal 0 HcmV?d00001 diff --git a/share/icons/meshTools_200.png b/share/icons/meshTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..1112b813830d7907212061a4157fa173f7964c7a GIT binary patch literal 5499 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 literal 0 HcmV?d00001 diff --git a/share/icons/mmSolverRunFrame_150.png b/share/icons/mmSolverRunFrame_150.png new file mode 100644 index 0000000000000000000000000000000000000000..bafa948cb0509ad835d5886599da05b3d85b5ce6 GIT binary patch literal 1441 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 literal 0 HcmV?d00001 diff --git a/share/icons/mmSolverRun_150.png b/share/icons/mmSolverRun_150.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8d388d4649891579a118fca4ed6d116a7b10ec GIT binary patch literal 1971 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 literal 0 HcmV?d00001 diff --git a/share/icons/mmSolverWindow_150.png b/share/icons/mmSolverWindow_150.png new file mode 100644 index 0000000000000000000000000000000000000000..7603d3a6d2f8b792c08876791cb136b647dedb0b GIT binary patch literal 1711 zcmV;g22lBlP)T>EKu$MUJVN}o*PZX);C^0GH$f$GK z`+M-4{W;B;jVBw0ez0M$z4lt`|KIDr*aCmBckkW?Ns@j8wy=Q_1R<$u+JE}``u+zv;UEPrzJKb;a?I-rnB73xPnOR@1cq0oGD0Di%Qy{wnLb{u_UYR}=+@!%<>c zxoDaOfGCP+nnos*VPawe0A1H_$bdKdvejy(rlzJOLRVsBWQ2G;j@@o&a&nS=`}P?I z0Ebvgao>a8ZYPyW5s5@l6a}+6r<)2J-he2I=(?VFZ^V~ER55({@`bXpGODVoh{xlk zQmLGHS=aT2Y#<(wGc`5!(?xy@RaH?G1yK}nyWK>i(dE(evLFb0wptb!7kU5weMy9_ zk?4kRLaPvwmxls@0Lf%BucK@vBuQd$aFBF54M2_om&-*(MMX)36~mi1Z;boR-h_S` zC^?(nfV|oZHk%EH!$Em@IjK~NckkZi&4yX5Rx2Kl2b;}CG#X`ebadTEv=t3BG&E3K zTg%q1TXUYz&d&1T!v{OM zeVQ9LZji}jfTg*judgrX-7Q0EkAT)YsPo(9+Vvu3ftr8yn-+ty^?-bQt2lrmU=tBS(&4 zwXXf#AQTF5>eQ*c=S3O#{P{C;b91PwN=r)%q9}6q>{(h{TdA$BT``@_W@B)0kVlUm z5s$}n;w%=6F;bb$W-Jy_nCe7rEN1 zb)}D!mzy96c)ea?u^0;r3vAoA4X@XW$z-Cvy&Z?c!T$aGiAJN0kB{SWxo|q2_==WCgDfsC zl1`^l6a}l*nzI345Ar(k*Iz={b-KE`xO3+Y=gytW`I6Jq(}creBiz)~gx~Kc7z`pw z5?x(gJbU(R9px9Ofk-66^XJcTI-ShS%y9qyefs;ceC#kEeTd_aJz2!cQ$5Fii;{4inP%ceh-_+_Bf{4!8#HoXB^ z*Y!`?!0_-e)z#Id)UzsyL}K{~#z!F#2zWG28)kF;Cjo#U2!D|xk;u%>ojYfNRyI+S z5(MF2;cz%)tlXNLoBg6F9v1{*WBqH8PYie!4u}5@@H^OxCO%Z_Inw|D002ovPDHLk FV1n_eH*Ej_ literal 0 HcmV?d00001 diff --git a/share/icons/mmSolverWindow_200.png b/share/icons/mmSolverWindow_200.png new file mode 100644 index 0000000000000000000000000000000000000000..a7034aec7a07e433e89dc2e4b673c8d221350621 GIT binary patch literal 2553 zcmVf!aFrF@uJD`sL@E12BV%FW7KLnwG!JJDAa`l zR#*zH3xuy-c4p26hskdFSXv5$wf|(3o#&mM`M>{ppXYtv=baTKp`oEc?&;~-s%hG0 zZa_$qG#HD;T7Lfd=kZyanEkSS`}V&IA^r(?OkuZrm znzpj+RslAfjqL1fva+(+uwet|&Yi>IaLjkAHb;P1EJkN%CyFws=?{ghteKpyudffM z(+R*Yzx-m%0RP9xyuG}5@#0GJTPqm2A!EQ*Ao6DyU~Uk+@4oxcb)BiHDKq%2C~P(x z-+%u-u~^Ko%@M$6v*GdFxGUo0$B(BR|LI-erT}IWFdH`oFq?qc_|prposgPn~loKNbar;8wEO*j`uqFm%^W33qO!6wrSr2;6or9-0WMs)aODbZRgrcC zoH%iUk3RZ{bLY;Ho12TpV&R!*o}sR;&N!#Kx|;XidyhNsyffu#-hTV-i&mH;kqD!s zqZAhxo8}5>1(0Q##~ynOi^XEtCMPF}Mx(4-w+@);=+Mv*s;U|S6ciK~b-3flkMq`B zZ_(4!gU{#Vx#yn4>2&hhXP>cm@7@*Y*3z-0Aefn%QaDCY6uP^+4FLeEs;cPi?IjQh zkdcu=Sy>qX@4ovkuf6sfZnvA=yLaRDdO3RZC_n!ABUY=G?c2AT8VIks>=TJZP*v3^ zHa`6D!@Tgq3;6whHgDdH&1U1vFTdo_p+kg1A)b8lNwTuC5JI5qI=9_+8>6G6csi$x_97czP5ZLW@GBYz%Y@0T1qNu2d?Ck6%j|xrGP*pX>Cd;znil2V^DTzd4 z!Rsw+{^{@UPdTossiCyA6j_#e@x>R3$KyQy_~R*^Pe!tBGMv#iSJz`y{pSj@-|s;jH9SS*}6b&9XP`ii=`ItmL5m((Gr z(@8iS=9_Q6;qv9njE;_?X&Nq<%Q#1tWuw?BNs{p$z~OKh?wCj()oIZV;rluy^+uO;@%fsPt zV6|HL_19lhO7-!0+!*rlc$`Ec0nE5$vMUH7$jr>-!3Q6tsHkYchp_D0cd`HgXU?3V zt*wpy`}dQPk+J0KNRmWtZ7sF6wJC8SU6PcpU6Q0lAHuQ)xOC|f!^6YmkQH(sIt*ve4TtEmxSy>sHrqR>W zW5j}u8#f}$GKoZD$#EbzHjEtnz@UIrfjvb@1v6127;p?9VxZQ3-p%9~^qrCFU zE0mU&GBGiM-EIe9&z?QJ|Ni^ja?34C3T|CpUA+DF+Z;J^WYKwhHZKrDke!`v*o6>A zUNAmB&dA6JzyJO_Znqn|-F|(i!OYA|cJADXBuTvb>Z`=#acXL6=3l|6mgNDC7^w2}R^wLWd6cnT#1(GDO zW5*6)?t4nf+2CSG9_Gop#9~;GRZdP0p-_m67cVkCK8~U&tY5#L;^JZ?N#fF_OB_6S z5UIDVZvSkbF*RP*< zpD-4S@$}PAGdVfQ{{8!ndVvsvuC6Wufq?PEJsuDB_4RagbTBzNiK?nttybJ_H;SSV z4u?&x2`(tW0a*!ueVc;bmC`1adxdEkKuxZ{pH<`5S`(B0ilOH0eV*Bu-j zjigu~&rSrlz;ZRO<2lXyHHG)<$qxtWhY{+L7}L0w%PRaI3B1~wrC zot>S0{`u$ld_FWyqrbnO6DLm4+S-Z`f>0>LBab{nAP`va6}Htt+F9U(4?dv1y&Y9m z*}8QrcinXtg@uJ_hpf$JBNz~=e~ zwYAjL)BrP2R+6M8leSu|?B2bbs;VkvT=I|9x_YtQRpd;CG^5jWMOH0>Grgl9MjYhBB zVLmGw8XDyD=g)To#U}AG3p!9C04gdf_UXFbXfmNyg|6#|fBNaCze@luEiFfc5Pt*4 zOy;#J&~^P^LWqNq^zU(i^78T=MNxKXns!6FgCt3#s;d6?)TvXwv$p>MNQy5;6f?LR P00000NkvXXu0mjf#B&1e literal 0 HcmV?d00001 diff --git a/share/icons/selectionTools_150.png b/share/icons/selectionTools_150.png new file mode 100644 index 0000000000000000000000000000000000000000..5795e1595a5199c1a71652eea3eb73c5bd08e398 GIT binary patch literal 2988 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 Q82|tP07*qoM6N<$f-_^LVgLXD literal 0 HcmV?d00001 diff --git a/share/icons/zDepthTools_200.png b/share/icons/zDepthTools_200.png new file mode 100644 index 0000000000000000000000000000000000000000..3f8968f2037b9de7f0dc075c37f6b4a46db33de4 GIT binary patch literal 2612 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 Date: Sat, 5 Oct 2024 21:41:15 +1000 Subject: [PATCH 262/295] Combine Rust workspace dependencies into single file. This allows us to place the list of dependencies in the main workspace file and then reference the individual packages in the respective sub-crate. The advantage is to allow us to avoid duplicating and using different versions of the same package in different sub-crates of the workspace, which can increase compilation times and bloat the final binaries. --- lib/rust/Cargo.toml | 58 +++++++++++++++++++++++++++++++- lib/rust/mmcore/Cargo.toml | 30 ++++------------- lib/rust/mmimage/Cargo.toml | 31 +++++------------ lib/rust/mmscenegraph/Cargo.toml | 56 +++++++++--------------------- 4 files changed, 86 insertions(+), 89 deletions(-) diff --git a/lib/rust/Cargo.toml b/lib/rust/Cargo.toml index 67c9d2c9b..6d7868a09 100644 --- a/lib/rust/Cargo.toml +++ b/lib/rust/Cargo.toml @@ -1,7 +1,63 @@ [workspace] - +resolver = "2" members = [ "mmcore", "mmimage", "mmscenegraph", ] + +[workspace.package] +version = "0.1.0" +authors = ["david-cattermole "] +edition = "2018" +publish = false + +[workspace.dependencies] +anyhow = "1.0.71" +approx = "0.5.1" +exr = "1.6.3" +fastapprox = "0.3.0" +log = "0.4.0" +num = "0.4.0" +num-traits = "0.2" +rustc-hash = "2.0.0" + +[workspace.dependencies.shellexpand] +version = "3.1" +default-features = false +features = ["full"] + +[workspace.dependencies.rand] +version = "0.8.5" +default-features = false +features = ["std", "alloc", "small_rng"] + +[workspace.dependencies.petgraph] +version = "0.6" +default-features = false +features = ["stable_graph"] + +[workspace.dependencies.nalgebra] +version = "0.32.2" +default-features = false +features = ["std", "matrixmultiply"] + +[workspace.dependencies.criterion] +# This is used as a dev-dependency, not at runtime. +version = "0.5.1" +default-features = false +features = ["html_reports"] + +[profile.release] +opt-level = 3 +rpath = false +codegen-units = 1 + +# 'lto = true' Performs "fat" LTO which attempts to perform +# optimizations across all crates within the dependency graph. +lto = true + +# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. +# # https://github.com/rust-lang/cargo/issues/6313 +# +# panic = "abort" diff --git a/lib/rust/mmcore/Cargo.toml b/lib/rust/mmcore/Cargo.toml index 73a21a2b4..5d9bb461d 100644 --- a/lib/rust/mmcore/Cargo.toml +++ b/lib/rust/mmcore/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "mmcore_rust" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmcore_rust" @@ -11,23 +11,5 @@ path = "./src/lib.rs" crate_type = ["lib"] [dependencies] -log = "0.4.0" - -[dependencies.shellexpand] -version = "3.1" -default-features = false -features = ["full"] - -[profile.release] -opt-level = 3 -rpath = false -codegen-units = 1 - -# 'lto = true' Performs "fat" LTO which attempts to perform -# optimizations across all crates within the dependency graph. -lto = true - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" +log = { workspace = true } +shellexpand = { workspace = true } diff --git a/lib/rust/mmimage/Cargo.toml b/lib/rust/mmimage/Cargo.toml index 545450a45..5216b51b6 100644 --- a/lib/rust/mmimage/Cargo.toml +++ b/lib/rust/mmimage/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "mmimage_rust" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmimage_rust" @@ -15,22 +15,7 @@ name = "bench" harness = false [dependencies] -log = "0.4.0" -exr = "1.6.3" -anyhow = "1.0.71" -num = "0.4.0" - -[profile.release] -opt-level = 3 -rpath = false -codegen-units = 1 - -# 'lto = true' Performs "fat" LTO which attempts to perform -# optimizations across all crates within the dependency graph. -lto = true - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" - +log = { workspace = true } +exr = { workspace = true } +anyhow = { workspace = true } +num = { workspace = true } diff --git a/lib/rust/mmscenegraph/Cargo.toml b/lib/rust/mmscenegraph/Cargo.toml index f82ad8367..8acd87d6c 100644 --- a/lib/rust/mmscenegraph/Cargo.toml +++ b/lib/rust/mmscenegraph/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "mmscenegraph_rust" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmscenegraph_rust" @@ -15,40 +15,14 @@ name = "bench" harness = false [dependencies] -approx = "0.5.1" -fastapprox = "0.3.0" -rustc-hash = "2.0.0" -log = "0.4.0" -num-traits = "0.2" - -[dependencies.rand] -version = "0.8.5" -default-features = false -features = ["std", "alloc", "small_rng"] - -[dependencies.petgraph] -version = "0.6" -default-features = false -features = ["stable_graph"] - -[dependencies.nalgebra] -version = "0.32.2" -default-features = false -features = ["std", "matrixmultiply"] - -[dev-dependencies.criterion] -version = "0.5.1" -default-features = false -features = ["html_reports"] - -[profile.release] -opt-level = 3 -rpath = false -# 'lto = true' Performs "fat" LTO which attempts to perform -# optimizations across all crates within the dependency graph. -lto = true -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" -codegen-units = 1 +approx = { workspace = true } +fastapprox = { workspace = true } +rustc-hash = { workspace = true } +log = { workspace = true } +num-traits = { workspace = true } +rand = { workspace = true } +petgraph = { workspace = true } +nalgebra = { workspace = true } + +[dev-dependencies] +criterion = { workspace = true } From 3a7d1eac93dcf481b4a19534da6f7827505bca90 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:19:45 +1000 Subject: [PATCH 263/295] Add all Rust Crates into single workspace. This top-level Cargo.toml workspace allows us to control all package/crate versions used across all Rust dependencies in mmSolver. This should have no functional difference in the crates, just a new organisation to help maintainence of the build dependencies. --- Cargo.toml | 67 ++ lib/cppbind/mmcore/Cargo.toml | 28 +- lib/cppbind/mmimage/Cargo.toml | 28 +- lib/cppbind/mmlens/Cargo.toml | 34 +- lib/cppbind/mmscenegraph/Cargo.toml | 28 +- lib/mmsolverlibs/Cargo.lock | 985 ---------------------------- lib/mmsolverlibs/Cargo.toml | 30 +- lib/rust/Cargo.toml | 63 -- lib/rust/mmimage/Cargo.toml | 4 - 9 files changed, 104 insertions(+), 1163 deletions(-) create mode 100644 Cargo.toml delete mode 100644 lib/mmsolverlibs/Cargo.lock delete mode 100644 lib/rust/Cargo.toml diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..782f4b400 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,67 @@ +[workspace] +resolver = "2" +members = [ + "lib/cppbind/mmcore", + "lib/cppbind/mmimage", + "lib/cppbind/mmlens", + "lib/cppbind/mmscenegraph", + "lib/mmsolverlibs", + "lib/rust/mmcore", + "lib/rust/mmimage", + "lib/rust/mmscenegraph", +] + +[workspace.package] +version = "0.1.0" +authors = ["david-cattermole "] +edition = "2018" +publish = false + +[workspace.dependencies] +anyhow = "1.0.71" +approx = "0.5.1" +criterion = { version = "0.5.1", default-features = false, features = ["html_reports"] } +exr = "1.6.3" +fastapprox = "0.3.0" +log = "0.4.0" +nalgebra = { version = "0.32.2", default-features = false, features = ["std", "matrixmultiply"] } +num = "0.4.0" +num-traits = "0.2" +num_cpus = "1.15.0" +petgraph = { version = "0.6", default-features = false, features = ["stable_graph"] } +rand = { version = "0.8.5", default-features = false, features = ["std", "alloc", "small_rng"] } +rayon = "1.7.0" +rustc-hash = "2.0.0" +shellexpand = { version = "3.1", default-features = false, features = ["full"] } +smallvec = "1.10.0" + +# CXX is used to build C++ bindings. +# +# NOTE: When changing this version number ensure to also update the +# installed 'cxxbridge-cmd' version so it stays in sync; Update it +# here: './scripts/internal/build_mmSolverLibs_*.*' +cxx = "=1.0.124" + +# Crates defined inside this project. +mmcore_cppbind = { path = "./lib/cppbind/mmcore" } +mmcore_rust = { path = "./lib/rust/mmcore" } +mmimage_cppbind = { path = "./lib/cppbind/mmimage" } +mmimage_rust = { path = "./lib/rust/mmimage" } +mmlens_cppbind = { path = "./lib/cppbind/mmlens" } +mmlens_rust = { path = "./lib/rust/mmlens" } +mmscenegraph_cppbind = { path = "./lib/cppbind/mmscenegraph" } +mmscenegraph_rust = { path = "./lib/rust/mmscenegraph/" } + +[profile.release] +opt-level = 3 +rpath = false +codegen-units = 1 + +# 'lto = true' Performs "fat" LTO which attempts to perform +# optimizations across all crates within the dependency graph. +lto = true + +# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. +# # https://github.com/rust-lang/cargo/issues/6313 +# +# panic = "abort" diff --git a/lib/cppbind/mmcore/Cargo.toml b/lib/cppbind/mmcore/Cargo.toml index fd64e6465..847f16068 100644 --- a/lib/cppbind/mmcore/Cargo.toml +++ b/lib/cppbind/mmcore/Cargo.toml @@ -1,30 +1,14 @@ [package] name = "mmcore_cppbind" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmcore" path = "./src/lib.rs" [dependencies] -# NOTE: When changing this version number ensure to also update the -# installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_mmSolverLibs_*.*' -cxx = "=1.0.124" - -[dependencies.mmcore_rust] -path = "../../rust/mmcore" - -[profile.release] -opt-level = 3 -rpath = false -lto = true -codegen-units = 1 - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" +cxx = { workspace = true } +mmcore_rust = { workspace = true } diff --git a/lib/cppbind/mmimage/Cargo.toml b/lib/cppbind/mmimage/Cargo.toml index b7669fafc..35aef1a05 100644 --- a/lib/cppbind/mmimage/Cargo.toml +++ b/lib/cppbind/mmimage/Cargo.toml @@ -1,30 +1,14 @@ [package] name = "mmimage_cppbind" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmimage" path = "./src/lib.rs" [dependencies] -# NOTE: When changing this version number ensure to also update the -# installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_mmSolverLibs_*.*' -cxx = "=1.0.124" - -[dependencies.mmimage_rust] -path = "../../rust/mmimage" - -[profile.release] -opt-level = 3 -rpath = false -lto = true -codegen-units = 1 - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" +cxx = { workspace = true } +mmimage_rust = { workspace = true } diff --git a/lib/cppbind/mmlens/Cargo.toml b/lib/cppbind/mmlens/Cargo.toml index 792bb4b83..a5cd2bd0c 100644 --- a/lib/cppbind/mmlens/Cargo.toml +++ b/lib/cppbind/mmlens/Cargo.toml @@ -1,32 +1,18 @@ [package] name = "mmlens_cppbind" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmlens" path = "./src/lib.rs" [dependencies] -anyhow = "1.0.71" -# NOTE: When changing this version number ensure to also update the -# installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_mmSolverLibs_*.*' -cxx = "=1.0.124" -num_cpus = "1.15.0" -rayon = "1.7.0" -smallvec = "1.10.0" -rustc-hash = "2.0.0" - -[profile.release] -opt-level = 3 -rpath = false -lto = true -codegen-units = 1 - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" +anyhow = { workspace = true } +cxx = { workspace = true } +num_cpus = { workspace = true } +rayon = { workspace = true } +smallvec = { workspace = true } +rustc-hash = { workspace = true } diff --git a/lib/cppbind/mmscenegraph/Cargo.toml b/lib/cppbind/mmscenegraph/Cargo.toml index 18a62a8ad..c89eaa5f6 100644 --- a/lib/cppbind/mmscenegraph/Cargo.toml +++ b/lib/cppbind/mmscenegraph/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "mmscenegraph_cppbind" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmscenegraph" @@ -14,21 +14,5 @@ path = "./src/lib.rs" # crate_type = ["staticlib"] [dependencies] -# NOTE: When changing this version number ensure to also update the -# installed 'cxxbridge-cmd' version so it stays in sync; Update it -# here: './scripts/internal/build_mmSolverLibs_*.*' -cxx = "=1.0.124" - -[dependencies.mmscenegraph_rust] -path = "../../rust/mmscenegraph/" - -[profile.release] -opt-level = 3 -rpath = false -lto = true -codegen-units = 1 - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" +cxx = { workspace = true } +mmscenegraph_rust = { workspace = true } diff --git a/lib/mmsolverlibs/Cargo.lock b/lib/mmsolverlibs/Cargo.lock deleted file mode 100644 index 81ee2011a..000000000 --- a/lib/mmsolverlibs/Cargo.lock +++ /dev/null @@ -1,985 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "anyhow" -version = "1.0.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bit_field" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - -[[package]] -name = "bstr" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" -dependencies = [ - "memchr", - "regex-automata", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "cc" -version = "1.0.99" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "cxx" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" -dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", -] - -[[package]] -name = "cxxbridge-flags" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.124" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "exr" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd2162b720141a91a054640662d3edce3d50a944a50ffca5313cd951abb35b4" -dependencies = [ - "bit_field", - "flume", - "half", - "lebe", - "miniz_oxide", - "rayon-core", - "smallvec", - "zune-inflate", -] - -[[package]] -name = "fastapprox" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0031c93f37b5d18272de2d932ebff6a7eb32d4bc3bab6751a9af42da7d1a424" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "nanorand", - "pin-project", - "spin", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "getrandom" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "half" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" -dependencies = [ - "crunchy", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "js-sys" -version = "0.3.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lebe" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" - -[[package]] -name = "libc" -version = "0.2.144" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags", - "libc", -] - -[[package]] -name = "link-cplusplus" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" -dependencies = [ - "cc", -] - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matrixmultiply" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" -dependencies = [ - "autocfg", - "rawpointer", -] - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "mmcore_cppbind" -version = "0.1.0" -dependencies = [ - "cxx", - "mmcore_rust", -] - -[[package]] -name = "mmcore_rust" -version = "0.1.0" -dependencies = [ - "log", - "shellexpand", -] - -[[package]] -name = "mmimage_cppbind" -version = "0.1.0" -dependencies = [ - "cxx", - "mmimage_rust", -] - -[[package]] -name = "mmimage_rust" -version = "0.1.0" -dependencies = [ - "anyhow", - "exr", - "log", - "num", -] - -[[package]] -name = "mmlens_cppbind" -version = "0.1.0" -dependencies = [ - "anyhow", - "cxx", - "num_cpus", - "rayon", - "rustc-hash", - "smallvec", -] - -[[package]] -name = "mmscenegraph_cppbind" -version = "0.1.0" -dependencies = [ - "cxx", - "mmscenegraph_rust", -] - -[[package]] -name = "mmscenegraph_rust" -version = "0.1.0" -dependencies = [ - "approx", - "fastapprox", - "log", - "nalgebra", - "num-traits", - "petgraph", - "rand", - "rustc-hash", -] - -[[package]] -name = "mmsolverlibs_cppbind" -version = "0.1.0" -dependencies = [ - "mmcore_cppbind", - "mmimage_cppbind", - "mmlens_cppbind", - "mmscenegraph_cppbind", -] - -[[package]] -name = "nalgebra" -version = "0.32.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational", - "num-traits", - "simba", - "typenum", -] - -[[package]] -name = "nanorand" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" -dependencies = [ - "getrandom", -] - -[[package]] -name = "num" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" -dependencies = [ - "memchr", -] - -[[package]] -name = "paste" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pin-project" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", -] - -[[package]] -name = "redox_users" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" -dependencies = [ - "getrandom", - "libredox", - "thiserror", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" - -[[package]] -name = "rustc-hash" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" - -[[package]] -name = "safe_arch" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62a7484307bd40f8f7ccbacccac730108f2cae119a3b11c74485b48aa9ea650f" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "serde" -version = "1.0.185" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" - -[[package]] -name = "shellexpand" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" -dependencies = [ - "bstr", - "dirs", - "os_str_bytes", -] - -[[package]] -name = "simba" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", - "wide", -] - -[[package]] -name = "simd-adler32" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] - -[[package]] -name = "syn" -version = "2.0.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "unicode-ident" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" - -[[package]] -name = "wide" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40018623e2dba2602a9790faba8d33f2ebdebf4b86561b83928db735f8784728" -dependencies = [ - "bytemuck", - "safe_arch", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "zune-inflate" -version = "0.2.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" -dependencies = [ - "simd-adler32", -] diff --git a/lib/mmsolverlibs/Cargo.toml b/lib/mmsolverlibs/Cargo.toml index 0ab91245f..33efd6932 100644 --- a/lib/mmsolverlibs/Cargo.toml +++ b/lib/mmsolverlibs/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "mmsolverlibs_cppbind" -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false +version.workspace = true +authors.workspace = true +edition.workspace = true +publish.workspace = true [lib] name = "mmsolverlibs_rust" @@ -12,20 +12,8 @@ path = "./src/lib.rs" # link with C++. crate_type = ["staticlib"] -[dependencies.mmcore_cppbind] -path = "../cppbind/mmcore" - -[dependencies.mmimage_cppbind] -path = "../cppbind/mmimage" - -[dependencies.mmlens_cppbind] -path = "../cppbind/mmlens" - -[dependencies.mmscenegraph_cppbind] -path = "../cppbind/mmscenegraph" - -[profile.release] -opt-level = 3 -rpath = false -lto = true -codegen-units = 1 +[dependencies] +mmcore_cppbind = { workspace = true } +mmimage_cppbind = { workspace = true } +mmlens_cppbind = { workspace = true } +mmscenegraph_cppbind = { workspace = true } diff --git a/lib/rust/Cargo.toml b/lib/rust/Cargo.toml deleted file mode 100644 index 6d7868a09..000000000 --- a/lib/rust/Cargo.toml +++ /dev/null @@ -1,63 +0,0 @@ -[workspace] -resolver = "2" -members = [ - "mmcore", - "mmimage", - "mmscenegraph", -] - -[workspace.package] -version = "0.1.0" -authors = ["david-cattermole "] -edition = "2018" -publish = false - -[workspace.dependencies] -anyhow = "1.0.71" -approx = "0.5.1" -exr = "1.6.3" -fastapprox = "0.3.0" -log = "0.4.0" -num = "0.4.0" -num-traits = "0.2" -rustc-hash = "2.0.0" - -[workspace.dependencies.shellexpand] -version = "3.1" -default-features = false -features = ["full"] - -[workspace.dependencies.rand] -version = "0.8.5" -default-features = false -features = ["std", "alloc", "small_rng"] - -[workspace.dependencies.petgraph] -version = "0.6" -default-features = false -features = ["stable_graph"] - -[workspace.dependencies.nalgebra] -version = "0.32.2" -default-features = false -features = ["std", "matrixmultiply"] - -[workspace.dependencies.criterion] -# This is used as a dev-dependency, not at runtime. -version = "0.5.1" -default-features = false -features = ["html_reports"] - -[profile.release] -opt-level = 3 -rpath = false -codegen-units = 1 - -# 'lto = true' Performs "fat" LTO which attempts to perform -# optimizations across all crates within the dependency graph. -lto = true - -# NOTE: If we use 'panic = "abort"' then we are unable to produce tests. -# # https://github.com/rust-lang/cargo/issues/6313 -# -# panic = "abort" diff --git a/lib/rust/mmimage/Cargo.toml b/lib/rust/mmimage/Cargo.toml index 5216b51b6..00f5fd376 100644 --- a/lib/rust/mmimage/Cargo.toml +++ b/lib/rust/mmimage/Cargo.toml @@ -10,10 +10,6 @@ name = "mmimage_rust" path = "./src/lib.rs" crate_type = ["lib"] -[[bench]] -name = "bench" -harness = false - [dependencies] log = { workspace = true } exr = { workspace = true } From 3cde0d539fb9e5bf0ae979f6f5ddce9005148f13 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:21:16 +1000 Subject: [PATCH 264/295] Add rust Cargo lock-file - to ensure consistent builds --- Cargo.lock | 1207 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1207 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 000000000..253ec96f5 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1207 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cc" +version = "1.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d9e0b4957f635b8d3da819d0db5603620467ecf1f692d22a8c2717ce27e6d8" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "cxx" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.124" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "exr" +version = "1.72.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fastapprox" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dfa3c0fd35278e839805680f4c2f673ca71eb91068115b4a611e71429bc0c46" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "spin", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi 0.4.0", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d240c6f7e1ba3a28b0249f774e6a9dd0175054b52dfbb61b16eb8505c3785c9" +dependencies = [ + "cc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mmcore_cppbind" +version = "0.1.0" +dependencies = [ + "cxx", + "mmcore_rust", +] + +[[package]] +name = "mmcore_rust" +version = "0.1.0" +dependencies = [ + "log", + "shellexpand", +] + +[[package]] +name = "mmimage_cppbind" +version = "0.1.0" +dependencies = [ + "cxx", + "mmimage_rust", +] + +[[package]] +name = "mmimage_rust" +version = "0.1.0" +dependencies = [ + "anyhow", + "exr", + "log", + "num", +] + +[[package]] +name = "mmlens_cppbind" +version = "0.1.0" +dependencies = [ + "anyhow", + "cxx", + "num_cpus", + "rayon", + "rustc-hash", + "smallvec", +] + +[[package]] +name = "mmscenegraph_cppbind" +version = "0.1.0" +dependencies = [ + "cxx", + "mmscenegraph_rust", +] + +[[package]] +name = "mmscenegraph_rust" +version = "0.1.0" +dependencies = [ + "approx", + "criterion", + "fastapprox", + "log", + "nalgebra", + "num-traits", + "petgraph", + "rand", + "rustc-hash", +] + +[[package]] +name = "mmsolverlibs_cppbind" +version = "0.1.0" +dependencies = [ + "mmcore_cppbind", + "mmimage_cppbind", + "mmlens_cppbind", + "mmscenegraph_cppbind", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +dependencies = [ + "memchr", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shellexpand" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +dependencies = [ + "bstr", + "dirs", + "os_str_bytes", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] From 01325fca764169eeac8b3757aac7a49bb4fe1e40 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:28:12 +1000 Subject: [PATCH 265/295] Upgrade CXX from 1.0.124 to 1.0.128 To be on the latest version, with the most bug-fixes and support. --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- scripts/internal/build_mmSolverLibs_linux.bash | 2 +- scripts/internal/build_mmSolverLibs_windows64.bat | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 253ec96f5..89164d0e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -225,9 +225,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "cxx" -version = "1.0.124" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273dcfd3acd4e1e276af13ed2a43eea7001318823e7a726a6b3ed39b4acc0b82" +checksum = "54ccead7d199d584d139148b04b4a368d1ec7556a1d9ea2548febb1b9d49f9a4" dependencies = [ "cc", "cxxbridge-flags", @@ -237,15 +237,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.124" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839fcd5e43464614ffaa989eaf1c139ef1f0c51672a1ed08023307fa1b909ccd" +checksum = "65777e06cc48f0cb0152024c77d6cf9e4bdb4408e7b48bea993d42fa0f5b02b6" [[package]] name = "cxxbridge-macro" -version = "1.0.124" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2c1c1776b986979be68bb2285da855f8d8a35851a769fca8740df7c3d07877" +checksum = "98532a60dedaebc4848cb2cba5023337cc9ea3af16a5b062633fabfd9f18fb60" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 782f4b400..3b3315fd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ smallvec = "1.10.0" # NOTE: When changing this version number ensure to also update the # installed 'cxxbridge-cmd' version so it stays in sync; Update it # here: './scripts/internal/build_mmSolverLibs_*.*' -cxx = "=1.0.124" +cxx = "=1.0.128" # Crates defined inside this project. mmcore_cppbind = { path = "./lib/cppbind/mmcore" } diff --git a/scripts/internal/build_mmSolverLibs_linux.bash b/scripts/internal/build_mmSolverLibs_linux.bash index 84c5ec3db..615120c1a 100644 --- a/scripts/internal/build_mmSolverLibs_linux.bash +++ b/scripts/internal/build_mmSolverLibs_linux.bash @@ -109,7 +109,7 @@ then # './lib/cppbind/mmlens/Cargo.toml' # './lib/cppbind/mmcore/Cargo.toml' # './scripts/internal/build_mmSolverLibs_windows64.bat' - ${RUST_CARGO_EXE} install cxxbridge-cmd --version 1.0.124 + ${RUST_CARGO_EXE} install cxxbridge-cmd --version 1.0.128 fi MMSOLVERLIBS_CXXBRIDGE_EXE="${HOME}/.cargo/bin/cxxbridge" diff --git a/scripts/internal/build_mmSolverLibs_windows64.bat b/scripts/internal/build_mmSolverLibs_windows64.bat index f73c38d3e..267b3cdb8 100644 --- a/scripts/internal/build_mmSolverLibs_windows64.bat +++ b/scripts/internal/build_mmSolverLibs_windows64.bat @@ -104,7 +104,7 @@ IF ERRORLEVEL 1 ( :: './lib/cppbind/mmcore/Cargo.toml' :: './scripts/internal/build_mmSolverLibs_windows64.bat' :: './scripts/internal/build_mmSolverLibs_linux.bash' - %RUST_CARGO_EXE% install cxxbridge-cmd --version 1.0.124 + %RUST_CARGO_EXE% install cxxbridge-cmd --version 1.0.128 ) SET MMSOLVERLIBS_CXXBRIDGE_EXE="%USERPROFILE%\.cargo\bin\cxxbridge.exe" :: Convert back-slashes to forward-slashes. From 528c2b60d0d8dff2359fd7e0250390635e1b6ed9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:38:32 +1000 Subject: [PATCH 266/295] Upgrade Rust nalgebra crate from 0.32.x to 0.33.x This change provides small possible bug fixes (see nalgebra changelog https://github.com/dimforge/nalgebra/blob/main/CHANGELOG.md), and slighly newer dependencies. No expected change in behaviour. --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 89164d0e4..18c709cbd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -550,9 +550,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.32.6" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +checksum = "3c4b5f057b303842cf3262c27e465f4c303572e7f6b0648f60e16248ac3397f4" dependencies = [ "approx", "matrixmultiply", @@ -908,9 +908,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simba" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", diff --git a/Cargo.toml b/Cargo.toml index 3b3315fd4..d804c9e77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ criterion = { version = "0.5.1", default-features = false, features = ["html_rep exr = "1.6.3" fastapprox = "0.3.0" log = "0.4.0" -nalgebra = { version = "0.32.2", default-features = false, features = ["std", "matrixmultiply"] } +nalgebra = { version = "0.33.0", default-features = false, features = ["std", "matrixmultiply"] } num = "0.4.0" num-traits = "0.2" num_cpus = "1.15.0" From a9491b449a77dc9f0e922390122eafb81c122d98 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:49:33 +1000 Subject: [PATCH 267/295] Consolidate Rust auxiliary files. Don't duplicate data in multiple directories. The rustfmt.toml is not in the project root, just to avoid poluting the main directory tree. --- lib/{rust => }/.gitignore | 3 ++- lib/cppbind/.gitignore | 4 ---- lib/rust/mmcore/.gitignore | 5 ----- lib/rust/mmimage/.gitignore | 5 ----- lib/rust/rustfmt.toml | 1 - lib/{cppbind => }/rustfmt.toml | 0 6 files changed, 2 insertions(+), 16 deletions(-) rename lib/{rust => }/.gitignore (83%) delete mode 100644 lib/cppbind/.gitignore delete mode 100644 lib/rust/mmcore/.gitignore delete mode 100644 lib/rust/mmimage/.gitignore delete mode 100644 lib/rust/rustfmt.toml rename lib/{cppbind => }/rustfmt.toml (100%) diff --git a/lib/rust/.gitignore b/lib/.gitignore similarity index 83% rename from lib/rust/.gitignore rename to lib/.gitignore index 06f52c05a..dfe81cdea 100644 --- a/lib/rust/.gitignore +++ b/lib/.gitignore @@ -1,5 +1,6 @@ -/target* **/*.rs.bk Cargo.lock *~ +/target* +target/ *.org diff --git a/lib/cppbind/.gitignore b/lib/cppbind/.gitignore deleted file mode 100644 index c5ca39258..000000000 --- a/lib/cppbind/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -**/*.rs.bk -Cargo.lock -*~ -target/ diff --git a/lib/rust/mmcore/.gitignore b/lib/rust/mmcore/.gitignore deleted file mode 100644 index 06f52c05a..000000000 --- a/lib/rust/mmcore/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target* -**/*.rs.bk -Cargo.lock -*~ -*.org diff --git a/lib/rust/mmimage/.gitignore b/lib/rust/mmimage/.gitignore deleted file mode 100644 index 06f52c05a..000000000 --- a/lib/rust/mmimage/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/target* -**/*.rs.bk -Cargo.lock -*~ -*.org diff --git a/lib/rust/rustfmt.toml b/lib/rust/rustfmt.toml deleted file mode 100644 index df99c6919..000000000 --- a/lib/rust/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -max_width = 80 diff --git a/lib/cppbind/rustfmt.toml b/lib/rustfmt.toml similarity index 100% rename from lib/cppbind/rustfmt.toml rename to lib/rustfmt.toml From eba0477d6d6373b71b2af72a0cbc5b13e41d8480 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:50:26 +1000 Subject: [PATCH 268/295] Upgrade Rust package versions --- Cargo.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d804c9e77..ce6635552 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,22 +18,22 @@ edition = "2018" publish = false [workspace.dependencies] -anyhow = "1.0.71" +anyhow = "1.0.89" approx = "0.5.1" criterion = { version = "0.5.1", default-features = false, features = ["html_reports"] } -exr = "1.6.3" -fastapprox = "0.3.0" -log = "0.4.0" +exr = "1.72.0" +fastapprox = "0.3.1" +log = "0.4.22" nalgebra = { version = "0.33.0", default-features = false, features = ["std", "matrixmultiply"] } -num = "0.4.0" +num = "0.4.3" num-traits = "0.2" -num_cpus = "1.15.0" +num_cpus = "1.16.0" petgraph = { version = "0.6", default-features = false, features = ["stable_graph"] } rand = { version = "0.8.5", default-features = false, features = ["std", "alloc", "small_rng"] } -rayon = "1.7.0" +rayon = "1.10.0" rustc-hash = "2.0.0" shellexpand = { version = "3.1", default-features = false, features = ["full"] } -smallvec = "1.10.0" +smallvec = "1.13.2" # CXX is used to build C++ bindings. # From fbf0036233fd465d4f503ec4c94c1e83c77bb9f6 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 22:55:04 +1000 Subject: [PATCH 269/295] Make sure all Rust code uses SSE and AVX --- {lib/mmsolverlibs/.cargo => .cargo}/config.toml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {lib/mmsolverlibs/.cargo => .cargo}/config.toml (100%) diff --git a/lib/mmsolverlibs/.cargo/config.toml b/.cargo/config.toml similarity index 100% rename from lib/mmsolverlibs/.cargo/config.toml rename to .cargo/config.toml From 244c418ca1151a0e840d9966d9657191c7b37f9e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 23:26:32 +1000 Subject: [PATCH 270/295] mmimage - read EXR - Reduce number of allocations --- lib/cppbind/mmimage/src/imagemetadata.rs | 2 +- lib/cppbind/mmimage/src/imagepixelbuffer.rs | 2 +- lib/rust/mmimage/src/lib.rs | 65 +++++++-------------- lib/rust/mmimage/src/metadata.rs | 4 +- lib/rust/mmimage/src/pixelbuffer.rs | 40 ++++++++++++- lib/rust/mmimage/src/pixeldata.rs | 4 +- 6 files changed, 67 insertions(+), 50 deletions(-) diff --git a/lib/cppbind/mmimage/src/imagemetadata.rs b/lib/cppbind/mmimage/src/imagemetadata.rs index ad59db583..d4d1ac412 100644 --- a/lib/cppbind/mmimage/src/imagemetadata.rs +++ b/lib/cppbind/mmimage/src/imagemetadata.rs @@ -25,7 +25,7 @@ use crate::cxxbridge::ffi::Vec2I32 as BindVec2I32; use mmimage_rust::metadata::ImageMetaData as CoreImageMetaData; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ShimImageMetaData { inner: CoreImageMetaData, } diff --git a/lib/cppbind/mmimage/src/imagepixelbuffer.rs b/lib/cppbind/mmimage/src/imagepixelbuffer.rs index 45224d29c..8f10b242c 100644 --- a/lib/cppbind/mmimage/src/imagepixelbuffer.rs +++ b/lib/cppbind/mmimage/src/imagepixelbuffer.rs @@ -44,7 +44,7 @@ fn bind_to_core_buffer_data_type( } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ShimImagePixelBuffer { inner: CoreImagePixelBuffer, } diff --git a/lib/rust/mmimage/src/lib.rs b/lib/rust/mmimage/src/lib.rs index 35628c6e5..a68983ae7 100644 --- a/lib/rust/mmimage/src/lib.rs +++ b/lib/rust/mmimage/src/lib.rs @@ -20,12 +20,12 @@ use crate::encoder::ImageExrEncoder; use crate::metadata::ImageMetaData; -use crate::pixelbuffer::BufferDataType; use crate::pixelbuffer::ImagePixelBuffer; use crate::pixeldata::ImagePixelDataF32x4; use anyhow::bail; use anyhow::Result; use exr::prelude::traits::*; +use log::debug; pub mod datatype; pub mod encoder; @@ -64,25 +64,31 @@ pub fn image_read_metadata_exr(file_path: &str) -> Result { pub fn image_read_pixels_exr_f32x4( file_path: &str, ) -> Result<(ImageMetaData, ImagePixelBuffer)> { + debug!("Opening file: {}", file_path); + let image = exr::image::read::read() .no_deep_data() .largest_resolution_level() .rgba_channels( |resolution, _channels: &exr::image::RgbaChannels| { - // instantiate your image type with the size of the image in - // file. - let default_pixel = (0.0, 0.0, 0.0, 0.0); - let empty_line = vec![default_pixel; resolution.width()]; - let empty_image = vec![empty_line; resolution.height()]; - empty_image + let pixel_buffer = ImagePixelBuffer::new_f32x4( + resolution.width(), + resolution.height(), + ); + pixel_buffer }, - |pixel_vector, position, (r, g, b, a): (f32, f32, f32, f32)| { + |pixel_buffer, position, (r, g, b, a): (f32, f32, f32, f32)| { // transfer the colors from the file to your image type, // requesting all values to be converted to f32 numbers (you // can also directly use f16 instead) and you could also use // `Sample` instead of `f32` to keep the original data type // from the file - pixel_vector[position.y()][position.x()] = (r, g, b, a) + // + // TODO: We can vertically flip the image here if needed. + let index = (position.y() * (pixel_buffer.image_width())) + + position.x(); + let pixel_buffer_slice = pixel_buffer.as_slice_f32x4_mut(); + pixel_buffer_slice[index] = (r, g, b, a) }, ) .first_valid_layer() @@ -91,43 +97,16 @@ pub fn image_read_pixels_exr_f32x4( // printing all pixels might kill the console, so only print some // meta data about the image. - let _data_window = image.layer_data.absolute_bounds(); + let data_window = image.layer_data.absolute_bounds(); let layer_attributes = image.layer_data.attributes; - let image_width = image.layer_data.size.width(); - let image_height = image.layer_data.size.height(); - - // println!("Opened file: {}", file_path); - // println!("Layer Attributes: {:#?}", layer_attributes); - // println!("Data Window: {:?}", data_window); - - let pixel_count = image_width * image_height; - let default_pixel = 0.0; - let mut pixel_data: Vec = vec![default_pixel; pixel_count * 2]; - let pixel_data_slice = unsafe { - std::mem::transmute::<&mut [f64], &mut [(f32, f32, f32, f32)]>( - pixel_data.as_mut_slice(), - ) - }; + let _image_width = image.layer_data.size.width(); + let _image_height = image.layer_data.size.height(); - for y in 0..image_height { - for x in 0..image_width { - let position = exr::math::Vec2(x, y); - let pixel = image.layer_data.channel_data.pixels[position.y()] - [position.x()]; - let pixel_index = (image_width * y) + x; - pixel_data_slice[pixel_index] = pixel; - } - } - - let num_channels = 4; - let image_data = ImagePixelBuffer::from_data( - BufferDataType::F32, - image_width, - image_height, - num_channels, - pixel_data, - ); + debug!("Opened file: {}", file_path); + debug!("Layer Attributes: {:#?}", layer_attributes); + debug!("Data Window: {:?}", data_window); + let image_data = image.layer_data.channel_data.pixels; let image_metadata = ImageMetaData::with_attributes(&image.attributes, &layer_attributes); diff --git a/lib/rust/mmimage/src/metadata.rs b/lib/rust/mmimage/src/metadata.rs index 4b0865bc3..44150d226 100644 --- a/lib/rust/mmimage/src/metadata.rs +++ b/lib/rust/mmimage/src/metadata.rs @@ -139,7 +139,7 @@ fn convert_option_f32_to_option_rational( } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum AttributeValue { None, String(String), @@ -226,7 +226,7 @@ fn generate_named_attributes( named_attributes } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ImageMetaData { // ImageAttributes // https://docs.rs/exr/latest/exr/meta/header/struct.ImageAttributes.html diff --git a/lib/rust/mmimage/src/pixelbuffer.rs b/lib/rust/mmimage/src/pixelbuffer.rs index bdc4aae3c..3203b033b 100644 --- a/lib/rust/mmimage/src/pixelbuffer.rs +++ b/lib/rust/mmimage/src/pixelbuffer.rs @@ -18,6 +18,9 @@ // ==================================================================== // +use log::debug; +use std::fmt; + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum BufferDataType { None = 0, @@ -31,7 +34,6 @@ pub enum BufferDataType { /// /// The stored data is 64-bit aligned, to allow SSE and AVX /// instructions can be used by the compiler. -#[derive(Clone, Debug)] pub struct ImagePixelBuffer { data_type: BufferDataType, image_width: usize, @@ -45,6 +47,23 @@ pub struct ImagePixelBuffer { data: Vec, } +impl fmt::Debug for ImagePixelBuffer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ImagePixelBuffer") + .field("data_type", &self.data_type) + .field("image_width", &self.image_width) + .field("image_height", &self.image_height) + .field("num_channels", &self.num_channels) + .finish() + } +} + +impl Drop for ImagePixelBuffer { + fn drop(&mut self) { + debug!("Drop {:?}", self) + } +} + impl ImagePixelBuffer { pub fn empty() -> ImagePixelBuffer { ImagePixelBuffer { @@ -56,6 +75,25 @@ impl ImagePixelBuffer { } } + pub fn new_f32x4( + image_width: usize, + image_height: usize, + ) -> ImagePixelBuffer { + let pixel_count = image_width * image_height; + let default_pixel = 0.0; + // 'f64' is used to allocate the Vec, so we can ensure the + // underlying memory is 64-bit aligned. + let pixel_data: Vec = vec![default_pixel; pixel_count * 2]; + + ImagePixelBuffer { + data_type: BufferDataType::F32, + image_width, + image_height, + num_channels: 4, + data: pixel_data, + } + } + pub fn from_data( data_type: BufferDataType, image_width: usize, diff --git a/lib/rust/mmimage/src/pixeldata.rs b/lib/rust/mmimage/src/pixeldata.rs index c3654637d..4861ae0cf 100644 --- a/lib/rust/mmimage/src/pixeldata.rs +++ b/lib/rust/mmimage/src/pixeldata.rs @@ -21,7 +21,7 @@ use crate::pixelbuffer::ImagePixelBuffer; use exr::prelude::*; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ImagePixelDataF32x4<'a> { buffer_ref: &'a ImagePixelBuffer, } @@ -46,7 +46,7 @@ impl GetPixel for ImagePixelDataF32x4<'_> { } } -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ImagePixelDataF64x2 { pub width: usize, pub height: usize, From 3b202690426ed3e05b12a498816223f5d0e049be Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 5 Oct 2024 23:27:22 +1000 Subject: [PATCH 271/295] mmimage - Use logging features rather than println --- .../mmimage/tests/01_read_image_pixels.rs | 7 ++++--- .../mmimage/tests/02_read_image_metadata.rs | 21 ++++++++++--------- lib/rust/mmimage/tests/common/mod.rs | 3 ++- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/rust/mmimage/tests/01_read_image_pixels.rs b/lib/rust/mmimage/tests/01_read_image_pixels.rs index c7be28450..99f3b951d 100644 --- a/lib/rust/mmimage/tests/01_read_image_pixels.rs +++ b/lib/rust/mmimage/tests/01_read_image_pixels.rs @@ -19,6 +19,7 @@ // use anyhow::Result; +use log::{debug, info}; use mmimage_rust::image_read_rgba_pixels_exr_f32; mod common; @@ -50,11 +51,11 @@ fn main() -> Result<()> { let file_path_str = file_path.as_path().to_str(); match file_path_str { Some(value) => { - println!("Reading: {}", value); + info!("Reading: {}", value); let (_pixel_data, _meta_data) = image_read_rgba_pixels_exr_f32(value)?; - // println!("Metadata: {:?}", meta_data); - // println!("Pixel Data: {:?}", pixel_data); + debug!("Metadata: {:?}", meta_data); + debug!("Pixel Data: {:?}", pixel_data); } _ => (), } diff --git a/lib/rust/mmimage/tests/02_read_image_metadata.rs b/lib/rust/mmimage/tests/02_read_image_metadata.rs index f77a26cc1..9f8860f28 100644 --- a/lib/rust/mmimage/tests/02_read_image_metadata.rs +++ b/lib/rust/mmimage/tests/02_read_image_metadata.rs @@ -19,6 +19,7 @@ // use anyhow::Result; +use log::info; use mmimage_rust::image_read_metadata_exr; mod common; @@ -60,21 +61,21 @@ fn main() -> Result<()> { let file_path_str = file_path.as_path().to_str(); match file_path_str { Some(value) => { - println!("Reading: {}", value); + info!("Reading: {}", value); let metadata = image_read_metadata_exr(value)?; - println!("{:#?}", metadata); + info!("{:#?}", metadata); let attr_names = metadata.all_named_attribute_names(); - println!("Attr Name Count: {}", attr_names.len()); + info!("Attr Name Count: {}", attr_names.len()); for attr_name in attr_names { - println!("Attr Name: {:#?}", attr_name); + info!("Attr Name: {:#?}", attr_name); let has_attr = metadata.has_named_attribute(&attr_name); let attr_type = metadata.get_named_attribute_type_index(&attr_name); let value = metadata.get_named_attribute_as_f32(&attr_name); - println!("Attr Name Exists: {:#?}", has_attr); - println!("Attr Type: {:#?}", attr_type); - println!("Attr Value: {:#?}", value); + info!("Attr Name Exists: {:#?}", has_attr); + info!("Attr Type: {:#?}", attr_type); + info!("Attr Value: {:#?}", value); } let attr_name = "focal_length"; @@ -83,9 +84,9 @@ fn main() -> Result<()> { metadata.get_named_attribute_type_index(&attr_name); let focal_length = metadata.get_named_attribute_as_f32(&attr_name); - println!("Focal Length Name Exists: {:#?}", has_focal_length); - println!("Focal Length Type: {:#?}", focal_length_type); - println!("Focal Length Value: {:#?}", focal_length); + info!("Focal Length Name Exists: {:#?}", has_focal_length); + info!("Focal Length Type: {:#?}", focal_length_type); + info!("Focal Length Value: {:#?}", focal_length); } _ => (), } diff --git a/lib/rust/mmimage/tests/common/mod.rs b/lib/rust/mmimage/tests/common/mod.rs index 6222203ae..fe70c5dc4 100644 --- a/lib/rust/mmimage/tests/common/mod.rs +++ b/lib/rust/mmimage/tests/common/mod.rs @@ -1,5 +1,6 @@ use anyhow::bail; use anyhow::Result; +use log::warn; use std::path::Path; use std::path::PathBuf; @@ -15,7 +16,7 @@ pub fn construct_image_file_paths( if file_path.is_file() { file_paths.push(file_path); } else { - println!("Could not find file name {:?}", file_path); + warn!("Could not find file name {:?}", file_path); } } if file_paths.len() == 0 { From ee2cf3ef9b8f80644dddfc2b82d99ed3596617bb Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 8 Oct 2024 00:40:17 +1100 Subject: [PATCH 272/295] Fix EXR reader memory leak This change fixes a memory leak when reading EXR images. The image reader also improves the speed of reading EXRs and can optionally vertically flip the image data being read. We also reuse allocated memory when reading an EXR image sequence. This avoids memory fragmentation, but does waste a little bit of memory - in the future we could clean this up and clear the temporary memory used - if needed. --- lib/cppbind/mmimage/include/mmimage/lib.h | 1 + lib/cppbind/mmimage/src/lib.cpp | 5 +- lib/cppbind/mmimage/src/lib.rs | 3 +- lib/cppbind/mmimage/tests/test_a.cpp | 5 +- lib/cppbind/mmimage/tests/test_c.cpp | 7 +- lib/cppbind/mmimage/tests/test_d.cpp | 3 +- lib/rust/mmimage/src/lib.rs | 25 ++- src/mmSolver/cmd/MMReadImageCmd.cpp | 10 +- src/mmSolver/image/ImageCache.cpp | 10 +- src/mmSolver/image/ImageCache.h | 3 + src/mmSolver/image/image_io.cpp | 212 ++++++------------ src/mmSolver/image/image_io.h | 9 +- .../shape/ImagePlaneGeometry2Override.cpp | 4 +- .../shape/ImagePlaneGeometry2Override.h | 3 + tools/lensdistortion/src/main.cpp | 3 +- 15 files changed, 133 insertions(+), 170 deletions(-) diff --git a/lib/cppbind/mmimage/include/mmimage/lib.h b/lib/cppbind/mmimage/include/mmimage/lib.h index 211c472d5..cdc999c5f 100644 --- a/lib/cppbind/mmimage/include/mmimage/lib.h +++ b/lib/cppbind/mmimage/include/mmimage/lib.h @@ -42,6 +42,7 @@ bool image_read_metadata_exr(const rust::Str& file_path, ImageMetaData& out_meta_data); bool image_read_pixels_exr_f32x4(const rust::Str& file_path, + const bool vertical_flip, ImageMetaData& out_meta_data, ImagePixelBuffer& out_pixel_data); diff --git a/lib/cppbind/mmimage/src/lib.cpp b/lib/cppbind/mmimage/src/lib.cpp index 766012560..fca1d2f51 100644 --- a/lib/cppbind/mmimage/src/lib.cpp +++ b/lib/cppbind/mmimage/src/lib.cpp @@ -42,13 +42,14 @@ bool image_read_metadata_exr(const rust::Str& file_path, } bool image_read_pixels_exr_f32x4(const rust::Str& file_path, + const bool vertical_flip, ImageMetaData& out_meta_data, ImagePixelBuffer& out_pixel_data) { auto pixel_data = out_pixel_data.get_inner(); auto meta_data = out_meta_data.get_inner(); - bool result = - shim_image_read_pixels_exr_f32x4(file_path, meta_data, pixel_data); + bool result = shim_image_read_pixels_exr_f32x4(file_path, vertical_flip, + meta_data, pixel_data); out_pixel_data.set_inner(pixel_data); out_meta_data.set_inner(meta_data); diff --git a/lib/cppbind/mmimage/src/lib.rs b/lib/cppbind/mmimage/src/lib.rs index 414e020eb..c9834787a 100644 --- a/lib/cppbind/mmimage/src/lib.rs +++ b/lib/cppbind/mmimage/src/lib.rs @@ -47,11 +47,12 @@ pub fn shim_image_read_metadata_exr( pub fn shim_image_read_pixels_exr_f32x4( file_path: &str, + vertical_flip: bool, out_meta_data: &mut Box, out_pixel_buffer: &mut Box, ) -> bool { // TODO: How to return errors? An enum perhaps? - let image = core_image_read_pixels_exr_f32x4(file_path); + let image = core_image_read_pixels_exr_f32x4(file_path, vertical_flip); if let Err(_err) = image { return false; } diff --git a/lib/cppbind/mmimage/tests/test_a.cpp b/lib/cppbind/mmimage/tests/test_a.cpp index d7a92e173..bc3908eb7 100644 --- a/lib/cppbind/mmimage/tests/test_a.cpp +++ b/lib/cppbind/mmimage/tests/test_a.cpp @@ -34,8 +34,9 @@ bool test_a_image_read(const char *test_name, rust::Str file_path) { auto meta_data = mmimg::ImageMetaData(); auto pixel_buffer = mmimg::ImagePixelBuffer(); - bool result = - mmimg::image_read_pixels_exr_f32x4(file_path, meta_data, pixel_buffer); + const bool vertical_flip = false; + bool result = mmimg::image_read_pixels_exr_f32x4(file_path, vertical_flip, + meta_data, pixel_buffer); std::cout << test_name << " image file path: " << file_path << " image read result: " << static_cast(result) << std::endl diff --git a/lib/cppbind/mmimage/tests/test_c.cpp b/lib/cppbind/mmimage/tests/test_c.cpp index fb3b76ec8..3d14036a5 100644 --- a/lib/cppbind/mmimage/tests/test_c.cpp +++ b/lib/cppbind/mmimage/tests/test_c.cpp @@ -41,8 +41,9 @@ bool test_c_image_write(const char *test_name, rust::Str input_file_path, }; // TODO: Get the EXR Encoding when reading the file. - bool result = mmimg::image_read_pixels_exr_f32x4(input_file_path, meta_data, - pixel_buffer); + const bool vertical_flip = false; + bool result = mmimg::image_read_pixels_exr_f32x4( + input_file_path, vertical_flip, meta_data, pixel_buffer); std::cout << test_name << " image file path: " << input_file_path << " image read result: " << static_cast(result) << std::endl @@ -82,7 +83,7 @@ bool test_c_image_write(const char *test_name, rust::Str input_file_path, meta_data, pixel_buffer); bool reread_result = mmimg::image_read_pixels_exr_f32x4( - output_file_path, meta_data, pixel_buffer); + output_file_path, vertical_flip, meta_data, pixel_buffer); std::cout << test_name << " image file path: " << output_file_path << " image read result: " << static_cast(reread_result) << std::endl diff --git a/lib/cppbind/mmimage/tests/test_d.cpp b/lib/cppbind/mmimage/tests/test_d.cpp index b7974adb9..384bc43e1 100644 --- a/lib/cppbind/mmimage/tests/test_d.cpp +++ b/lib/cppbind/mmimage/tests/test_d.cpp @@ -72,8 +72,9 @@ bool test_d_image_write(const char *test_name, const size_t image_width, return false; } + const bool vertical_flip = false; bool reread_result = mmimg::image_read_pixels_exr_f32x4( - output_file_path, meta_data, pixel_buffer); + output_file_path, vertical_flip, meta_data, pixel_buffer); std::cout << test_name << " image file path: " << output_file_path << " image read result: " << static_cast(reread_result) << std::endl diff --git a/lib/rust/mmimage/src/lib.rs b/lib/rust/mmimage/src/lib.rs index a68983ae7..6ffb089fe 100644 --- a/lib/rust/mmimage/src/lib.rs +++ b/lib/rust/mmimage/src/lib.rs @@ -58,11 +58,15 @@ pub fn image_read_metadata_exr(file_path: &str) -> Result { } /// Read an EXR image from a file path. +/// +/// Allows vertically flipping the exported pixel data as we read the +/// data. // // https://github.com/johannesvollmer/exrs/blob/master/GUIDE.md // https://github.com/johannesvollmer/exrs/blob/master/examples/0c_read_rgba.rs pub fn image_read_pixels_exr_f32x4( file_path: &str, + vertical_flip: bool, ) -> Result<(ImageMetaData, ImagePixelBuffer)> { debug!("Opening file: {}", file_path); @@ -77,16 +81,25 @@ pub fn image_read_pixels_exr_f32x4( ); pixel_buffer }, - |pixel_buffer, position, (r, g, b, a): (f32, f32, f32, f32)| { + move |pixel_buffer, + position, + (r, g, b, a): (f32, f32, f32, f32)| { // transfer the colors from the file to your image type, // requesting all values to be converted to f32 numbers (you // can also directly use f16 instead) and you could also use // `Sample` instead of `f32` to keep the original data type - // from the file - // - // TODO: We can vertically flip the image here if needed. - let index = (position.y() * (pixel_buffer.image_width())) - + position.x(); + // from the file. + + let image_width = pixel_buffer.image_width(); + let image_height = pixel_buffer.image_height(); + + let position_x = position.x(); + let position_y = match vertical_flip { + false => position.y(), + true => (image_height - 1) - position.y(), + }; + let index = (position_y * image_width) + position_x; + let pixel_buffer_slice = pixel_buffer.as_slice_f32x4_mut(); pixel_buffer_slice[index] = (r, g, b, a) }, diff --git a/src/mmSolver/cmd/MMReadImageCmd.cpp b/src/mmSolver/cmd/MMReadImageCmd.cpp index 7c94e741e..ee1e2f40f 100644 --- a/src/mmSolver/cmd/MMReadImageCmd.cpp +++ b/src/mmSolver/cmd/MMReadImageCmd.cpp @@ -55,6 +55,9 @@ #include // MM Solver +#include +#include + #include "mmSolver/image/ImagePixelData.h" #include "mmSolver/image/PixelDataType.h" #include "mmSolver/image/image_io.h" @@ -170,7 +173,9 @@ MStatus read_image_header(const MString &file_path, uint32_t &out_image_width, MHWRender::MRasterFormat &out_texture_format, image::PixelDataType &out_pixel_data_type) { MStatus status = MStatus::kSuccess; - MImage image; + MImage temp_mimage; + mmimage::ImagePixelBuffer temp_pixel_buffer; + mmimage::ImageMetaData temp_meta_data; out_image_width = 0; out_image_height = 0; @@ -183,7 +188,8 @@ MStatus read_image_header(const MString &file_path, uint32_t &out_image_width, // TODO: Can we read just the file header to get the image size? // This would remove the need to read the entire image for this // command's usage. - status = image::read_image_file(image, file_path, out_image_width, + status = image::read_image_file(temp_mimage, temp_pixel_buffer, + temp_meta_data, file_path, out_image_width, out_image_height, out_num_channels, out_bytes_per_channel, out_texture_format, out_pixel_data_type, pixel_data); diff --git a/src/mmSolver/image/ImageCache.cpp b/src/mmSolver/image/ImageCache.cpp index 1c0375e56..d5a4415c8 100644 --- a/src/mmSolver/image/ImageCache.cpp +++ b/src/mmSolver/image/ImageCache.cpp @@ -59,6 +59,8 @@ namespace image { MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, + mmimage::ImagePixelBuffer &temp_pixel_buffer, + mmimage::ImageMetaData &temp_meta_data, const MString &file_pattern, const MString &file_path, const bool do_texture_update) { @@ -131,10 +133,10 @@ MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, } } else { - status = - read_image_file(temp_image, resolved_file_path, width, height, - num_channels, bytes_per_channel, texture_format, - pixel_data_type, maya_owned_pixel_data); + status = read_image_file( + temp_image, temp_pixel_buffer, temp_meta_data, resolved_file_path, + width, height, num_channels, bytes_per_channel, texture_format, + pixel_data_type, maya_owned_pixel_data); if (status != MS::kSuccess) { return nullptr; } diff --git a/src/mmSolver/image/ImageCache.h b/src/mmSolver/image/ImageCache.h index 107caf208..225f26c21 100644 --- a/src/mmSolver/image/ImageCache.h +++ b/src/mmSolver/image/ImageCache.h @@ -38,6 +38,7 @@ // MM Solver #include +#include #include "ImagePixelData.h" #include "PixelDataType.h" @@ -398,6 +399,8 @@ struct ImageCache { MTexture *read_texture_image_file(MHWRender::MTextureManager *texture_manager, ImageCache &image_cache, MImage &temp_image, + mmimage::ImagePixelBuffer &temp_pixel_buffer, + mmimage::ImageMetaData &temp_meta_data, const MString &file_pattern, const MString &file_path, const bool do_texture_update); diff --git a/src/mmSolver/image/image_io.cpp b/src/mmSolver/image/image_io.cpp index 17fac4e05..00492dd80 100644 --- a/src/mmSolver/image/image_io.cpp +++ b/src/mmSolver/image/image_io.cpp @@ -53,25 +53,25 @@ namespace mmsolver { namespace image { -void *get_mimage_pixel_data(const MImage &image, - const PixelDataType pixel_data_type, - const uint32_t width, const uint32_t height, - const uint8_t num_channels, - uint8_t &out_bytes_per_channel, - MHWRender::MRasterFormat &out_texture_format) { +void *get_maya_mimage_pixel_data(const MImage &maya_mimage, + const PixelDataType pixel_data_type, + const uint32_t width, const uint32_t height, + const uint8_t num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format) { const bool verbose = false; const uint32_t print_num_pixels = 8; void *pixel_data = nullptr; if (pixel_data_type == PixelDataType::kU8) { - MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_maya_mimage_pixel_data:" << " pixel_data_type=PixelDataType::kU8"); // 8-bit unsigned integers use 1 byte. out_bytes_per_channel = 1; - const bool is_rgba = image.isRGBA(); - MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + const bool is_rgba = maya_mimage.isRGBA(); + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_maya_mimage_pixel_data:" << " is_rgba=" << is_rgba); if (is_rgba) { out_texture_format = MHWRender::kR8G8B8A8_UNORM; @@ -79,7 +79,7 @@ void *get_mimage_pixel_data(const MImage &image, out_texture_format = MHWRender::kB8G8R8A8; } - unsigned char *pixels = image.pixels(); + unsigned char *pixels = maya_mimage.pixels(); if (verbose) { for (uint32_t row = 0; row <= print_num_pixels; row++) { @@ -88,9 +88,10 @@ void *get_mimage_pixel_data(const MImage &image, const uint32_t g = static_cast(pixels[index + 1]); const uint32_t b = static_cast(pixels[index + 2]); const uint32_t a = static_cast(pixels[index + 3]); - MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" - << " row=" << row << " pixel=" << r << ", " - << g << ", " << b << ", " << a); + MMSOLVER_MAYA_VRB( + "mmsolver::image_io::get_maya_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " << g << ", " + << b << ", " << a); } } @@ -109,7 +110,7 @@ void *get_mimage_pixel_data(const MImage &image, pixel_data = static_cast(pixels); } else if (pixel_data_type == PixelDataType::kF32) { - MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" + MMSOLVER_MAYA_VRB("mmsolver::image_io::get_maya_mimage_pixel_data:" << " pixel_data_type=PixelDataType::kF32"); // 32-bit floats use 4 bytes. @@ -117,7 +118,7 @@ void *get_mimage_pixel_data(const MImage &image, out_texture_format = MHWRender::kR32G32B32A32_FLOAT; - float *floatPixels = image.floatPixels(); + float *floatPixels = maya_mimage.floatPixels(); if (verbose) { for (uint32_t row = 0; row <= print_num_pixels; row++) { @@ -126,9 +127,10 @@ void *get_mimage_pixel_data(const MImage &image, const float g = floatPixels[index + 1]; const float b = floatPixels[index + 2]; const float a = floatPixels[index + 3]; - MMSOLVER_MAYA_VRB("mmsolver::image_io::get_mimage_pixel_data:" - << " row=" << row << " pixel=" << r << ", " - << g << ", " << b << ", " << a); + MMSOLVER_MAYA_VRB( + "mmsolver::image_io::get_maya_mimage_pixel_data:" + << " row=" << row << " pixel=" << r << ", " << g << ", " + << b << ", " << a); } } @@ -137,7 +139,7 @@ void *get_mimage_pixel_data(const MImage &image, pixel_data = static_cast(floatPixels); } else { - MMSOLVER_MAYA_ERR("mmsolver::image_io::get_mimage_pixel_data: " + MMSOLVER_MAYA_ERR("mmsolver::image_io::get_maya_mimage_pixel_data: " << "Invalid pixel type is " << static_cast(pixel_data_type)); return nullptr; @@ -163,43 +165,51 @@ PixelDataType convert_mpixel_type_to_pixel_data_type( return pixel_data_type; } -MStatus read_with_maya_image(MImage &image, const MString &file_path, - const MImage::MPixelType pixel_type, - uint32_t &out_width, uint32_t &out_height, - uint8_t &out_num_channels, - uint8_t &out_bytes_per_channel, - MHWRender::MRasterFormat &out_texture_format, - PixelDataType &out_pixel_data_type, - void *&out_pixel_data) { +// NOTE: The pointer 'out_pixel_data' returned is actually a pointer +// into the 'MImage maya_mimage' data. When that object goes out of +// scope, accessing that data will be come undefined! +MStatus read_with_maya_mimage(MImage &maya_mimage, const MString &file_path, + const MImage::MPixelType pixel_type, + uint32_t &out_width, uint32_t &out_height, + uint8_t &out_num_channels, + uint8_t &out_bytes_per_channel, + MHWRender::MRasterFormat &out_texture_format, + PixelDataType &out_pixel_data_type, + void *&out_pixel_data) { const bool verbose = false; MStatus status = MStatus::kSuccess; - status = image.readFromFile(file_path, pixel_type); + status = maya_mimage.readFromFile(file_path, pixel_type); CHECK_MSTATUS(status); if (status != MS::kSuccess) { - MMSOLVER_MAYA_WRN("mmsolver::image_io::read_with_maya_image:" + MMSOLVER_MAYA_WRN("mmsolver::image_io::read_with_maya_mimage:" << " failed to read image \"" << file_path.asChar() << "\"."); return status; } - image.getSize(out_width, out_height); - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_with_maya_image:" + maya_mimage.getSize(out_width, out_height); + MMSOLVER_MAYA_VRB("mmsolver::image_io::read_with_maya_mimage:" << " width=" << out_width << " height=" << out_height); out_pixel_data_type = convert_mpixel_type_to_pixel_data_type(pixel_type); - out_pixel_data = get_mimage_pixel_data( - image, out_pixel_data_type, out_width, out_height, out_num_channels, - out_bytes_per_channel, out_texture_format); + out_pixel_data = get_maya_mimage_pixel_data( + maya_mimage, out_pixel_data_type, out_width, out_height, + out_num_channels, out_bytes_per_channel, out_texture_format); return status; } -MStatus read_exr_with_mmimage(MImage &image, const MString &file_path, - uint32_t &out_width, uint32_t &out_height, - uint8_t &out_num_channels, +// NOTE: The pointer 'out_pixel_data' returned is actually a pointer +// into the 'mmimage::ImagePixelBuffer pixel_buffer' data. When that +// object goes out of scope, accessing that data will be come +// undefined! +MStatus read_exr_with_mmimage(mmimage::ImagePixelBuffer &pixel_buffer, + mmimage::ImageMetaData &meta_data, + const MString &file_path, uint32_t &out_width, + uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, MHWRender::MRasterFormat &out_texture_format, PixelDataType &out_pixel_data_type, @@ -208,15 +218,13 @@ MStatus read_exr_with_mmimage(MImage &image, const MString &file_path, MStatus status = MStatus::kSuccess; - auto pixel_buffer = mmimage::ImagePixelBuffer(); - auto meta_data = mmimage::ImageMetaData(); - const std::string input_file_path_string = file_path.asChar(); const rust::Str input_file_path(input_file_path_string.c_str()); // TODO: Support 3-channel RGB EXR images. + const bool vertical_flip = true; bool read_ok = mmimage::image_read_pixels_exr_f32x4( - input_file_path, meta_data, pixel_buffer); + input_file_path, vertical_flip, meta_data, pixel_buffer); if (!read_ok) { MMSOLVER_MAYA_WRN("mmsolver::image_io::read_exr_with_mmimage:" @@ -264,100 +272,22 @@ MStatus read_exr_with_mmimage(MImage &image, const MString &file_path, const rust::Slice slice = pixel_buffer.as_slice_f32x4(); - // TODO: Why does this need to be multiplied by 2? Shouldn't it be - // 4? - size_t pixel_data_byte_count = slice.size() * 2 * out_bytes_per_channel; - - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " pixel_data_byte_count=" << pixel_data_byte_count); - - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " slice.length()=" << slice.length()); - - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " slice.size()=" << slice.size()); - - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " requesting=" << pixel_data_byte_count << "B" - << " requesting=" - << (static_cast(pixel_data_byte_count) * 1e-6) - << "MB"); - - bool ok = false; - void *data = std::malloc(pixel_data_byte_count); - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " void *data=" << data); - - if (data) { - ok = true; - - const bool is_vertically_flipped = true; - if (is_vertically_flipped) { - // The underlying EXR data is actually flipped vertically - // compared to Maya's MImage output. We must flip this to - // be consistent. - // - // TODO: Try to do this somewhere else, perhaps in the - // shader, because that would probably be faster. - const int8_t *src_ptr = (int8_t *)slice.data(); - int8_t *dst_ptr = reinterpret_cast(data); - const size_t line_count = out_height; // * out_num_channels; - const size_t line_byte_count = pixel_data_byte_count / line_count; - MMSOLVER_MAYA_VRB( - "mmsolver::image_io::read_exr_with_mmimage:" - << " std::memcpy" - << " src_ptr=" << reinterpret_cast(src_ptr) - << " dst_ptr=" << reinterpret_cast(dst_ptr) - << " line_count=" << line_count - << " line_byte_count=" << line_byte_count - << " pixel_data_byte_count=" << pixel_data_byte_count); - for (size_t i = 0; i < line_count; i++) { - const size_t reverse_i = (line_count - 1) - i; - - const size_t src_offset = reverse_i * line_byte_count; - const size_t dst_offset = i * line_byte_count; - - const void *src_ptr_with_offset = src_ptr + src_offset; - void *dst_ptr_with_offset = dst_ptr + dst_offset; - - std::memcpy(dst_ptr_with_offset, src_ptr_with_offset, - line_byte_count); - } - } else { - // Make a copy of the underlying Rust allocated data. This is - // a little wasteful because we need to allocate and copy new - // memory, but it's much easier than coercing Rust-owned - // memory across the FFI barrier. - void *src_ptr = (void *)slice.data(); - MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" - << " std::memcpy(data=" << data - << ", (void *)slice.data()=" << src_ptr - << ", pixel_data_byte_count=" - << pixel_data_byte_count << ");"); - std::memcpy(data, src_ptr, pixel_data_byte_count); - } - - out_pixel_data = data; - } else { - ok = false; - out_pixel_data = nullptr; - MMSOLVER_MAYA_ERR("mmsolver::image_io::read_exr_with_mmimage: " - << "Could not allocate pixel data!" - << " requested=" << pixel_data_byte_count << "B" - << " requested=" - << (static_cast(pixel_data_byte_count) * 1e-6) - << "MB"); - } - + out_pixel_data = (void *)slice.data(); MMSOLVER_MAYA_VRB("mmsolver::image_io::read_exr_with_mmimage:" << " out_pixel_data=" << out_pixel_data); return status; } -MStatus read_image_file(MImage &image, const MString &file_path, - uint32_t &out_width, uint32_t &out_height, - uint8_t &out_num_channels, +// NOTE: The pointer 'out_pixel_data' returned is actually a pointer +// into the 'MImage maya_mimage' or 'mmimage::ImagePixelBuffer +// pixel_buffer' data (depending on the format). When that object goes +// out of scope, accessing that data will be come undefined! +MStatus read_image_file(MImage &maya_mimage, + mmimage::ImagePixelBuffer &pixel_buffer, + mmimage::ImageMetaData &meta_data, + const MString &file_path, uint32_t &out_width, + uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, MHWRender::MRasterFormat &out_texture_format, PixelDataType &out_pixel_data_type, @@ -382,21 +312,17 @@ MStatus read_image_file(MImage &image, const MString &file_path, MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" << " file_extension=" << file_extension.asChar()); - // BUG: The EXR reader is leaking memory and we don't know where, - // so for now, lets disable the reader. - const bool use_exr_reader = false; - - if ((file_extension == "exr") && use_exr_reader) { + if (file_extension == "exr") { MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" << "read_exr_with_mmimage..."); - status = read_exr_with_mmimage(image, file_path, out_width, out_height, - out_num_channels, out_bytes_per_channel, - out_texture_format, out_pixel_data_type, - out_pixel_data); + status = read_exr_with_mmimage( + pixel_buffer, meta_data, file_path, out_width, out_height, + out_num_channels, out_bytes_per_channel, out_texture_format, + out_pixel_data_type, out_pixel_data); CHECK_MSTATUS_AND_RETURN_IT(status); } else { MMSOLVER_MAYA_VRB("mmsolver::image_io::read_image_file:" - << "read_with_maya_image..."); + << "read_with_maya_mimage..."); // Maya always reads images as RGBA. out_num_channels = 4; @@ -405,10 +331,10 @@ MStatus read_image_file(MImage &image, const MString &file_path, // but in my attempts it just fails, so lets hard-code to 8-bit. const MImage::MPixelType pixel_type = MImage::MPixelType::kByte; - status = read_with_maya_image(image, file_path, pixel_type, out_width, - out_height, out_num_channels, - out_bytes_per_channel, out_texture_format, - out_pixel_data_type, out_pixel_data); + status = read_with_maya_mimage( + maya_mimage, file_path, pixel_type, out_width, out_height, + out_num_channels, out_bytes_per_channel, out_texture_format, + out_pixel_data_type, out_pixel_data); CHECK_MSTATUS_AND_RETURN_IT(status); } diff --git a/src/mmSolver/image/image_io.h b/src/mmSolver/image/image_io.h index 02093fe72..10250b075 100644 --- a/src/mmSolver/image/image_io.h +++ b/src/mmSolver/image/image_io.h @@ -37,6 +37,7 @@ // MM Solver #include +#include #include "PixelDataType.h" #include "mmSolver/utilities/debug_utils.h" @@ -44,9 +45,11 @@ namespace mmsolver { namespace image { -MStatus read_image_file(MImage &image, const MString &file_path, - uint32_t &out_width, uint32_t &out_height, - uint8_t &out_num_channels, +MStatus read_image_file(MImage &maya_mimage, + mmimage::ImagePixelBuffer &pixel_buffer, + mmimage::ImageMetaData &meta_data, + const MString &file_path, uint32_t &out_width, + uint32_t &out_height, uint8_t &out_num_channels, uint8_t &out_bytes_per_channel, MHWRender::MRasterFormat &out_texture_format, PixelDataType &out_pixel_data_type, diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp index 9540c9e2a..6bafe440e 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.cpp @@ -480,8 +480,8 @@ void ImagePlaneGeometry2Override::set_shader_instance_parameters( const bool do_texture_update = false; image::ImageCache &image_cache = image::ImageCache::getInstance(); out_color_texture = image::read_texture_image_file( - texture_manager, image_cache, m_temp_image, file_path, - expanded_file_path, do_texture_update); + texture_manager, image_cache, m_temp_image, m_temp_pixel_buffer, + m_temp_meta_data, file_path, expanded_file_path, do_texture_update); if (out_color_texture) { MMSOLVER_MAYA_VRB("mmImagePlaneGeometry2Override: texture->name()=" diff --git a/src/mmSolver/shape/ImagePlaneGeometry2Override.h b/src/mmSolver/shape/ImagePlaneGeometry2Override.h index 77beb468d..3173405f0 100644 --- a/src/mmSolver/shape/ImagePlaneGeometry2Override.h +++ b/src/mmSolver/shape/ImagePlaneGeometry2Override.h @@ -44,6 +44,7 @@ // MM Solver #include +#include #include "ImagePlaneShape2Node.h" #include "ImagePlaneUtils.h" @@ -186,6 +187,8 @@ class ImagePlaneGeometry2Override : public MPxGeometryOverride { // Texture caching MImage m_temp_image; + mmimage::ImagePixelBuffer m_temp_pixel_buffer; + mmimage::ImageMetaData m_temp_meta_data; MHWRender::MTexture *m_color_texture; const MHWRender::MSamplerState *m_texture_sampler; diff --git a/tools/lensdistortion/src/main.cpp b/tools/lensdistortion/src/main.cpp index 4ab81a66d..72df46e4c 100644 --- a/tools/lensdistortion/src/main.cpp +++ b/tools/lensdistortion/src/main.cpp @@ -102,8 +102,9 @@ bool run_frame(mmlens::FrameNumber frame, bool reread_result = true; if (reread_image) { auto reread_start = std::chrono::high_resolution_clock::now(); + const bool vertical_flip = false; reread_result = mmimage::image_read_pixels_exr_f32x4( - output_file_path, meta_data, pixel_buffer); + output_file_path, vertical_flip, meta_data, pixel_buffer); auto reread_end = std::chrono::high_resolution_clock::now(); reread_duration = reread_end - reread_start; std::cout << "Re-read image file path: " << output_file_path << '\n' From 04a15459fe67359445f9ae1f916fc2b02ab5a8bc Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Tue, 8 Oct 2024 23:25:49 +1100 Subject: [PATCH 273/295] Update shelf screenshot. --- docs/source/images/tools_shelf_icons_all.png | Bin 21252 -> 22473 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/images/tools_shelf_icons_all.png b/docs/source/images/tools_shelf_icons_all.png index 0b44ff468338b6e3ffbf12396f5643663600db27..77471dae9e13ddd781d6452bc4e4fffa2b5e6409 100644 GIT binary patch literal 22473 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 literal 21252 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
  • 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 H<`Mrdg3A*# From 5517104e39270334bc7c510cf0d6bf6b34f90b6d Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 11 Oct 2024 01:43:00 +1030 Subject: [PATCH 274/295] Rename basic "mmRenderer" to "MM Standard Renderer" --- docs/source/nodes_display.rst | 7 ++- include/mmSolver/nodeTypeIds.h | 6 +-- .../AEmmRenderGlobalsSilhouetteTemplate.mel | 2 +- ... => AEmmRenderGlobalsStandardTemplate.mel} | 4 +- mel/mmRendererSilhouetteOptionBox.mel | 11 +++-- ...ox.mel => mmRendererStandardOptionBox.mel} | 13 ++--- src/CMakeLists.txt | 6 +-- src/mmSolver/pluginMain.cpp | 38 ++++++++------- ...BasicCmd.cpp => MMRendererStandardCmd.cpp} | 25 +++++----- ...ererBasicCmd.h => MMRendererStandardCmd.h} | 18 +++---- ...Node.cpp => RenderGlobalsStandardNode.cpp} | 37 ++++++++------- ...asicNode.h => RenderGlobalsStandardNode.h} | 12 ++--- .../render/RenderOverrideSilhouette.h | 4 +- ...deBasic.cpp => RenderOverrideStandard.cpp} | 47 ++++++++++--------- ...errideBasic.h => RenderOverrideStandard.h} | 16 +++---- src/mmSolver/render/data/constants.h | 20 ++++---- 16 files changed, 140 insertions(+), 126 deletions(-) rename mel/AETemplates/{AEmmRenderGlobalsTemplate.mel => AEmmRenderGlobalsStandardTemplate.mel} (92%) rename mel/{mmRendererOptionBox.mel => mmRendererStandardOptionBox.mel} (83%) rename src/mmSolver/render/{MMRendererBasicCmd.cpp => MMRendererStandardCmd.cpp} (73%) rename src/mmSolver/render/{MMRendererBasicCmd.h => MMRendererStandardCmd.h} (74%) rename src/mmSolver/render/{RenderGlobalsBasicNode.cpp => RenderGlobalsStandardNode.cpp} (74%) rename src/mmSolver/render/{RenderGlobalsBasicNode.h => RenderGlobalsStandardNode.h} (85%) rename src/mmSolver/render/{RenderOverrideBasic.cpp => RenderOverrideStandard.cpp} (82%) rename src/mmSolver/render/{RenderOverrideBasic.h => RenderOverrideStandard.h} (86%) diff --git a/docs/source/nodes_display.rst b/docs/source/nodes_display.rst index 31bfd0a5d..3c75712aa 100644 --- a/docs/source/nodes_display.rst +++ b/docs/source/nodes_display.rst @@ -3,7 +3,12 @@ Display Nodes *To be written.* -``mmRenderGlobals`` Nodes +``mmRenderGlobalsStandard`` Nodes ++++++++++++++++++++++++++ + +*To be written.* + +``mmRenderGlobalsSilhouette`` Nodes +++++++++++++++++++++++++ *To be written.* diff --git a/include/mmSolver/nodeTypeIds.h b/include/mmSolver/nodeTypeIds.h index e3afcd769..b8aa0b0b3 100644 --- a/include/mmSolver/nodeTypeIds.h +++ b/include/mmSolver/nodeTypeIds.h @@ -86,9 +86,9 @@ #define MM_MARKER_GROUP_TRANSFORM_TYPE_NAME "mmMarkerGroupTransform" #define MM_MARKER_GROUP_DRAW_CLASSIFY "drawdb/geometry/transform" -#define MM_RENDERER_BASIC_NAME "mmRenderer" -#define MM_RENDER_GLOBALS_BASIC_TYPE_ID 0x0012F194 -#define MM_RENDER_GLOBALS_BASIC_TYPE_NAME "mmRenderGlobals" +#define MM_RENDERER_STANDARD_NAME "mmRendererStandard" +#define MM_RENDER_GLOBALS_STANDARD_TYPE_ID 0x0012F194 +#define MM_RENDER_GLOBALS_STANDARD_TYPE_NAME "mmRenderGlobalsStandard" #define MM_RENDERER_SILHOUETTE_NAME "mmRendererSilhouette" #define MM_RENDER_GLOBALS_SILHOUETTE_TYPE_ID 0x0012F18E diff --git a/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel b/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel index bd90bf309..1b03b899b 100644 --- a/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel +++ b/mel/AETemplates/AEmmRenderGlobalsSilhouetteTemplate.mel @@ -17,7 +17,7 @@ // along with mmSolver. If not, see . // --------------------------------------------------------------------- // -// mmRenderer Render Globals AE Template +// Silhouette Renderer Render Globals AE Template // global proc AEmmRenderGlobalsSilhouetteTemplate(string $name) { diff --git a/mel/AETemplates/AEmmRenderGlobalsTemplate.mel b/mel/AETemplates/AEmmRenderGlobalsStandardTemplate.mel similarity index 92% rename from mel/AETemplates/AEmmRenderGlobalsTemplate.mel rename to mel/AETemplates/AEmmRenderGlobalsStandardTemplate.mel index 04776493b..29c6021fc 100644 --- a/mel/AETemplates/AEmmRenderGlobalsTemplate.mel +++ b/mel/AETemplates/AEmmRenderGlobalsStandardTemplate.mel @@ -17,10 +17,10 @@ // along with mmSolver. If not, see . // --------------------------------------------------------------------- // -// mmRenderer Render Globals AE Template +// Standard Renderer Render Globals AE Template // -global proc AEmmRenderGlobalsTemplate(string $name) { +global proc AEmmRenderGlobalsStandardTemplate(string $name) { string $parent = `setParent -q`; editorTemplate -beginScrollLayout; diff --git a/mel/mmRendererSilhouetteOptionBox.mel b/mel/mmRendererSilhouetteOptionBox.mel index 0f4aa48db..42c884b7c 100644 --- a/mel/mmRendererSilhouetteOptionBox.mel +++ b/mel/mmRendererSilhouetteOptionBox.mel @@ -32,14 +32,15 @@ // global proc mmRendererSilhouetteOptionBox() { - print("mmRendererSilhouetteOptionBox"); - if (!`objExists "mmRenderGlobalsSilhouette"`) { - string $node = `createNode "mmRenderGlobalsSilhouette" - -name "mmRenderGlobalsSilhouette" + // print("mmRendererSilhouetteOptionBox"); + string $node_type = "mmRenderGlobalsSilhouette"; + if (!`objExists $node_type`) { + string $node = `createNode $node_type + -name $node_type -shared -skipSelect`; lockNode -lock on $node; } - select -r "mmRenderGlobalsSilhouette"; + select -r $node_type; // Users want to be able to select and change the color swatch, so // we need the attribute editor open. diff --git a/mel/mmRendererOptionBox.mel b/mel/mmRendererStandardOptionBox.mel similarity index 83% rename from mel/mmRendererOptionBox.mel rename to mel/mmRendererStandardOptionBox.mel index 7290d35b2..4473ec2db 100644 --- a/mel/mmRendererOptionBox.mel +++ b/mel/mmRendererStandardOptionBox.mel @@ -31,14 +31,15 @@ // // -global proc mmRendererOptionBox() { - print("mmRendererOptionBox"); - if (!`objExists "mmRenderGlobals"`) { - string $node = `createNode "mmRenderGlobals" - -name "mmRenderGlobals" +global proc mmRendererStandardOptionBox() { + // print("mmRendererStandardOptionBox"); + string $node_type = "mmRenderGlobalsStandard"; + if (!`objExists $node_type`) { + string $node = `createNode $node_type + -name $node_type -shared -skipSelect`; lockNode -lock on $node; } - select -r "mmRenderGlobals"; + select -r $node_type; return; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 740276e7f..5603c7bca 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,10 +130,10 @@ set(SOURCE_FILES if (MMSOLVER_BUILD_RENDERER) set(SOURCE_FILES ${SOURCE_FILES} - mmSolver/render/MMRendererBasicCmd.cpp + mmSolver/render/MMRendererStandardCmd.cpp mmSolver/render/MMRendererSilhouetteCmd.cpp - mmSolver/render/RenderOverrideBasic.cpp - mmSolver/render/RenderGlobalsBasicNode.cpp + mmSolver/render/RenderOverrideStandard.cpp + mmSolver/render/RenderGlobalsStandardNode.cpp mmSolver/render/RenderGlobalsSilhouetteNode.cpp mmSolver/render/RenderOverrideSilhouette.cpp mmSolver/render/ops/ClearOperation.cpp diff --git a/src/mmSolver/pluginMain.cpp b/src/mmSolver/pluginMain.cpp index 157662ef5..fbe0c62de 100644 --- a/src/mmSolver/pluginMain.cpp +++ b/src/mmSolver/pluginMain.cpp @@ -85,12 +85,12 @@ // MM Renderer #if MMSOLVER_BUILD_RENDERER == 1 -#include "mmSolver/render/MMRendererBasicCmd.h" #include "mmSolver/render/MMRendererSilhouetteCmd.h" -#include "mmSolver/render/RenderGlobalsBasicNode.h" +#include "mmSolver/render/MMRendererStandardCmd.h" #include "mmSolver/render/RenderGlobalsSilhouetteNode.h" -#include "mmSolver/render/RenderOverrideBasic.h" +#include "mmSolver/render/RenderGlobalsStandardNode.h" #include "mmSolver/render/RenderOverrideSilhouette.h" +#include "mmSolver/render/RenderOverrideStandard.h" #endif #define REGISTER_COMMAND(plugin, name, creator, syntax, stat) \ @@ -436,18 +436,20 @@ MStatus initializePlugin(MObject obj) { markerTfmClassification, status); #if MMSOLVER_BUILD_RENDERER == 1 - REGISTER_COMMAND(plugin, mmsolver::render::MMRendererBasicCmd::cmdName(), - mmsolver::render::MMRendererBasicCmd::creator, - mmsolver::render::MMRendererBasicCmd::newSyntax, status); + REGISTER_COMMAND(plugin, mmsolver::render::MMRendererStandardCmd::cmdName(), + mmsolver::render::MMRendererStandardCmd::creator, + mmsolver::render::MMRendererStandardCmd::newSyntax, + status); REGISTER_COMMAND( plugin, mmsolver::render::MMRendererSilhouetteCmd::cmdName(), mmsolver::render::MMRendererSilhouetteCmd::creator, mmsolver::render::MMRendererSilhouetteCmd::newSyntax, status); - REGISTER_NODE(plugin, mmsolver::render::RenderGlobalsBasicNode::nodeName(), - mmsolver::render::RenderGlobalsBasicNode::m_id, - mmsolver::render::RenderGlobalsBasicNode::creator, - mmsolver::render::RenderGlobalsBasicNode::initialize, status); + REGISTER_NODE( + plugin, mmsolver::render::RenderGlobalsStandardNode::nodeName(), + mmsolver::render::RenderGlobalsStandardNode::m_id, + mmsolver::render::RenderGlobalsStandardNode::creator, + mmsolver::render::RenderGlobalsStandardNode::initialize, status); REGISTER_NODE( plugin, mmsolver::render::RenderGlobalsSilhouetteNode::nodeName(), mmsolver::render::RenderGlobalsSilhouetteNode::m_id, @@ -486,9 +488,9 @@ MStatus initializePlugin(MObject obj) { shader_location += MString("/shader"); shader_manager->addShaderPath(shader_location); - mmsolver::render::RenderOverrideBasic* default_renderer_ptr = - new mmsolver::render::RenderOverrideBasic( - MM_RENDERER_BASIC_NAME); + mmsolver::render::RenderOverrideStandard* default_renderer_ptr = + new mmsolver::render::RenderOverrideStandard( + MM_RENDERER_STANDARD_NAME); renderer->registerOverride(default_renderer_ptr); mmsolver::render::RenderOverrideSilhouette* @@ -619,7 +621,7 @@ MStatus uninitializePlugin(MObject obj) { if (renderer) { // Find override with the given name and deregister const MHWRender::MRenderOverride* default_renderer_ptr = - renderer->findRenderOverride(MM_RENDERER_BASIC_NAME); + renderer->findRenderOverride(MM_RENDERER_STANDARD_NAME); if (default_renderer_ptr) { renderer->deregisterOverride(default_renderer_ptr); delete default_renderer_ptr; @@ -633,14 +635,14 @@ MStatus uninitializePlugin(MObject obj) { } } - DEREGISTER_COMMAND(plugin, mmsolver::render::MMRendererBasicCmd::cmdName(), - status); + DEREGISTER_COMMAND( + plugin, mmsolver::render::MMRendererStandardCmd::cmdName(), status); DEREGISTER_COMMAND( plugin, mmsolver::render::MMRendererSilhouetteCmd::cmdName(), status); DEREGISTER_NODE(plugin, - mmsolver::render::RenderGlobalsBasicNode::nodeName(), - mmsolver::render::RenderGlobalsBasicNode::m_id, status); + mmsolver::render::RenderGlobalsStandardNode::nodeName(), + mmsolver::render::RenderGlobalsStandardNode::m_id, status); DEREGISTER_NODE( plugin, mmsolver::render::RenderGlobalsSilhouetteNode::nodeName(), mmsolver::render::RenderGlobalsSilhouetteNode::m_id, status); diff --git a/src/mmSolver/render/MMRendererBasicCmd.cpp b/src/mmSolver/render/MMRendererStandardCmd.cpp similarity index 73% rename from src/mmSolver/render/MMRendererBasicCmd.cpp rename to src/mmSolver/render/MMRendererStandardCmd.cpp index 86688b561..d6868c1b1 100644 --- a/src/mmSolver/render/MMRendererBasicCmd.cpp +++ b/src/mmSolver/render/MMRendererStandardCmd.cpp @@ -19,7 +19,7 @@ * */ -#include "MMRendererBasicCmd.h" +#include "MMRendererStandardCmd.h" // Maya #include @@ -29,26 +29,28 @@ #include // MM Solver -#include "RenderOverrideBasic.h" +#include "RenderOverrideStandard.h" namespace mmsolver { namespace render { -MMRendererBasicCmd::MMRendererBasicCmd() {} +MMRendererStandardCmd::MMRendererStandardCmd() {} -MMRendererBasicCmd::~MMRendererBasicCmd() {} +MMRendererStandardCmd::~MMRendererStandardCmd() {} -void *MMRendererBasicCmd::creator() { return (void *)(new MMRendererBasicCmd); } +void *MMRendererStandardCmd::creator() { + return (void *)(new MMRendererStandardCmd); +} -MString MMRendererBasicCmd::cmdName() { return kRendererBasicCmdName; } +MString MMRendererStandardCmd::cmdName() { return kRendererStandardCmdName; } -MSyntax MMRendererBasicCmd::newSyntax() { +MSyntax MMRendererStandardCmd::newSyntax() { MSyntax syntax; syntax.enableQuery(true); return syntax; } -MStatus MMRendererBasicCmd::doIt(const MArgList &args) { +MStatus MMRendererStandardCmd::doIt(const MArgList &args) { MStatus status = MStatus::kFailure; MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); @@ -57,10 +59,11 @@ MStatus MMRendererBasicCmd::doIt(const MArgList &args) { return status; } - RenderOverrideBasic *override_ptr = - (RenderOverrideBasic *)renderer->findRenderOverride(kRendererBasicName); + RenderOverrideStandard *override_ptr = + (RenderOverrideStandard *)renderer->findRenderOverride( + kRendererStandardName); if (override_ptr == nullptr) { - MGlobal::displayError(kRendererBasicCmdName + " is not registered."); + MGlobal::displayError(kRendererStandardCmdName + " is not registered."); return status; } diff --git a/src/mmSolver/render/MMRendererBasicCmd.h b/src/mmSolver/render/MMRendererStandardCmd.h similarity index 74% rename from src/mmSolver/render/MMRendererBasicCmd.h rename to src/mmSolver/render/MMRendererStandardCmd.h index 92401ab1a..1ff06bb43 100644 --- a/src/mmSolver/render/MMRendererBasicCmd.h +++ b/src/mmSolver/render/MMRendererStandardCmd.h @@ -17,17 +17,17 @@ * along with mmSolver. If not, see . * ==================================================================== * - * mmSolver Basic Renderer command. + * mmSolver Standard Renderer command. * * This command is used to query and edit values for the mmSolver * Renderer. * - * Create a 'mmRendererBasic' node used to store the attributes for the + * Create a 'mmRendererStandard' node used to store the attributes for the * renderer in the scene. */ -#ifndef MM_SOLVER_RENDER_MM_RENDERER_BASIC_CMD_H -#define MM_SOLVER_RENDER_MM_RENDERER_BASIC_CMD_H +#ifndef MM_SOLVER_RENDER_MM_RENDERER_STANDARD_CMD_H +#define MM_SOLVER_RENDER_MM_RENDERER_STANDARD_CMD_H // Maya #include @@ -36,13 +36,13 @@ namespace mmsolver { namespace render { /* - * MM Basic Renderer command. + * MM Standard Renderer command. */ -class MMRendererBasicCmd : public MPxCommand { +class MMRendererStandardCmd : public MPxCommand { public: - MMRendererBasicCmd(); + MMRendererStandardCmd(); - ~MMRendererBasicCmd() override; + ~MMRendererStandardCmd() override; MStatus doIt(const MArgList &args) override; @@ -56,4 +56,4 @@ class MMRendererBasicCmd : public MPxCommand { } // namespace render } // namespace mmsolver -#endif // MAYA_MM_SOLVER_RENDER_MM_RENDERER_BASIC_CMD_H +#endif // MAYA_MM_SOLVER_RENDER_MM_RENDERER_STANDARD_CMD_H diff --git a/src/mmSolver/render/RenderGlobalsBasicNode.cpp b/src/mmSolver/render/RenderGlobalsStandardNode.cpp similarity index 74% rename from src/mmSolver/render/RenderGlobalsBasicNode.cpp rename to src/mmSolver/render/RenderGlobalsStandardNode.cpp index b2456aa93..a97a3c678 100644 --- a/src/mmSolver/render/RenderGlobalsBasicNode.cpp +++ b/src/mmSolver/render/RenderGlobalsStandardNode.cpp @@ -20,7 +20,7 @@ * Stores global values for the mmSolver viewport renderer. */ -#include "RenderGlobalsBasicNode.h" +#include "RenderGlobalsStandardNode.h" // Maya #include @@ -37,7 +37,7 @@ #include // MM Solver -#include "RenderOverrideBasic.h" +#include "RenderOverrideStandard.h" #include "mmSolver/nodeTypeIds.h" #include "mmSolver/render/data/RenderColorFormat.h" #include "mmSolver/utilities/debug_utils.h" @@ -45,21 +45,22 @@ namespace mmsolver { namespace render { -MTypeId RenderGlobalsBasicNode::m_id(MM_RENDER_GLOBALS_BASIC_TYPE_ID); +MTypeId RenderGlobalsStandardNode::m_id(MM_RENDER_GLOBALS_STANDARD_TYPE_ID); -RenderGlobalsBasicNode::RenderGlobalsBasicNode() : m_attr_change_callback(0) {} +RenderGlobalsStandardNode::RenderGlobalsStandardNode() + : m_attr_change_callback(0) {} -RenderGlobalsBasicNode::~RenderGlobalsBasicNode() { +RenderGlobalsStandardNode::~RenderGlobalsStandardNode() { if (m_attr_change_callback) { MMessage::removeCallback(m_attr_change_callback); } } -MString RenderGlobalsBasicNode::nodeName() { - return MString(MM_RENDER_GLOBALS_BASIC_TYPE_NAME); +MString RenderGlobalsStandardNode::nodeName() { + return MString(MM_RENDER_GLOBALS_STANDARD_TYPE_NAME); } -void RenderGlobalsBasicNode::postConstructor() { +void RenderGlobalsStandardNode::postConstructor() { MObject obj = thisMObject(); if ((m_attr_change_callback == 0) && (!obj.isNull())) { m_attr_change_callback = @@ -67,7 +68,7 @@ void RenderGlobalsBasicNode::postConstructor() { } } -void RenderGlobalsBasicNode::attr_change_func( +void RenderGlobalsStandardNode::attr_change_func( MNodeMessage::AttributeMessage msg, MPlug &plug, MPlug & /*other_plug*/, void * /*client_data*/) { const bool verbose = false; @@ -95,11 +96,11 @@ void RenderGlobalsBasicNode::attr_change_func( return; } - RenderOverrideBasic *override_ptr = - (RenderOverrideBasic *)renderer->findRenderOverride( - MM_RENDERER_BASIC_NAME); + RenderOverrideStandard *override_ptr = + (RenderOverrideStandard *)renderer->findRenderOverride( + MM_RENDERER_STANDARD_NAME); if (override_ptr == nullptr) { - MGlobal::displayError(kRendererBasicCmdName + " is not registered."); + MGlobal::displayError(kRendererStandardCmdName + " is not registered."); return; } @@ -112,17 +113,17 @@ void RenderGlobalsBasicNode::attr_change_func( view.refresh(/*all=*/false, /*force=*/true); } -MStatus RenderGlobalsBasicNode::compute(const MPlug & /*plug*/, - MDataBlock & /*data*/) { +MStatus RenderGlobalsStandardNode::compute(const MPlug & /*plug*/, + MDataBlock & /*data*/) { // This node does not compute any values. return MS::kUnknownParameter; } -void *RenderGlobalsBasicNode::creator() { - return (new RenderGlobalsBasicNode()); +void *RenderGlobalsStandardNode::creator() { + return (new RenderGlobalsStandardNode()); } -MStatus RenderGlobalsBasicNode::initialize() { return MS::kSuccess; } +MStatus RenderGlobalsStandardNode::initialize() { return MS::kSuccess; } } // namespace render } // namespace mmsolver diff --git a/src/mmSolver/render/RenderGlobalsBasicNode.h b/src/mmSolver/render/RenderGlobalsStandardNode.h similarity index 85% rename from src/mmSolver/render/RenderGlobalsBasicNode.h rename to src/mmSolver/render/RenderGlobalsStandardNode.h index 0057876e1..099ad01b2 100644 --- a/src/mmSolver/render/RenderGlobalsBasicNode.h +++ b/src/mmSolver/render/RenderGlobalsStandardNode.h @@ -20,8 +20,8 @@ * Stores global values for the mmSolver viewport renderer. */ -#ifndef MM_SOLVER_RENDER_RENDER_GLOBALS_BASIC_NODE_H -#define MM_SOLVER_RENDER_RENDER_GLOBALS_BASIC_NODE_H +#ifndef MM_SOLVER_RENDER_RENDER_GLOBALS_STANDARD_NODE_H +#define MM_SOLVER_RENDER_RENDER_GLOBALS_STANDARD_NODE_H // Maya #include @@ -34,11 +34,11 @@ namespace mmsolver { namespace render { -class RenderGlobalsBasicNode : public MPxNode { +class RenderGlobalsStandardNode : public MPxNode { public: - RenderGlobalsBasicNode(); + RenderGlobalsStandardNode(); - ~RenderGlobalsBasicNode() override; + ~RenderGlobalsStandardNode() override; MStatus compute(const MPlug &plug, MDataBlock &data) override; @@ -63,4 +63,4 @@ class RenderGlobalsBasicNode : public MPxNode { } // namespace render } // namespace mmsolver -#endif // MM_SOLVER_RENDER_RENDER_GLOBALS_BASIC_NODE_H +#endif // MM_SOLVER_RENDER_RENDER_GLOBALS_STANDARD_NODE_H diff --git a/src/mmSolver/render/RenderOverrideSilhouette.h b/src/mmSolver/render/RenderOverrideSilhouette.h index 5a7c9bf7e..e79b32c1a 100644 --- a/src/mmSolver/render/RenderOverrideSilhouette.h +++ b/src/mmSolver/render/RenderOverrideSilhouette.h @@ -39,7 +39,7 @@ #include // MM Solver -#include "RenderGlobalsBasicNode.h" +#include "RenderGlobalsStandardNode.h" #include "mmSolver/render/data/RenderMode.h" #include "mmSolver/render/data/constants.h" #include "mmSolver/render/ops/ClearOperation.h" @@ -107,7 +107,7 @@ class RenderOverrideSilhouette : public MHWRender::MRenderOverride { HudRender* m_hudOp; PresentTarget* m_presentOp; - // A handle to the 'mmRenderGlobals' node. + // A handle to the 'mmRenderGlobalsSilhouette' node. MObjectHandle m_globals_node; MSelectionList m_image_plane_nodes; diff --git a/src/mmSolver/render/RenderOverrideBasic.cpp b/src/mmSolver/render/RenderOverrideStandard.cpp similarity index 82% rename from src/mmSolver/render/RenderOverrideBasic.cpp rename to src/mmSolver/render/RenderOverrideStandard.cpp index 5ff368616..afecf6def 100644 --- a/src/mmSolver/render/RenderOverrideBasic.cpp +++ b/src/mmSolver/render/RenderOverrideStandard.cpp @@ -21,7 +21,7 @@ * */ -#include "RenderOverrideBasic.h" +#include "RenderOverrideStandard.h" // Maya #include @@ -49,30 +49,30 @@ namespace mmsolver { namespace render { static MStatus create_render_globals_node() { - return MGlobal::executeCommand(kRendererBasicCreateNodeCommand, + return MGlobal::executeCommand(kRendererStandardCreateNodeCommand, /*displayEnabled*/ true, /*undoEnabled*/ true); } // Callback for tracking renderer changes -void RenderOverrideBasic::renderer_change_func(const MString &panel_name, - const MString &old_renderer, - const MString &new_renderer, - void * /*client_data*/) { +void RenderOverrideStandard::renderer_change_func(const MString &panel_name, + const MString &old_renderer, + const MString &new_renderer, + void * /*client_data*/) { const bool verbose = false; MMSOLVER_MAYA_VRB("Renderer changed for panel '" << panel_name.asChar() << "'. " << "New renderer is '" << new_renderer.asChar() << "', " << "old was '" << old_renderer.asChar() << "'."); - if (new_renderer == MM_RENDERER_BASIC_NAME) { + if (new_renderer == MM_RENDERER_STANDARD_NAME) { MStatus status = create_render_globals_node(); CHECK_MSTATUS(status); } } // Callback for tracking render override changes -void RenderOverrideBasic::render_override_change_func( +void RenderOverrideStandard::render_override_change_func( const MString &panel_name, const MString &old_renderer, const MString &new_renderer, void * /*client_data*/) { const bool verbose = false; @@ -81,27 +81,28 @@ void RenderOverrideBasic::render_override_change_func( << "New override is '" << new_renderer.asChar() << "', " << "old was '" << old_renderer.asChar() << "'."); - if (new_renderer == MM_RENDERER_BASIC_NAME) { + if (new_renderer == MM_RENDERER_STANDARD_NAME) { MStatus status = create_render_globals_node(); CHECK_MSTATUS(status); } } // Set up operations -RenderOverrideBasic::RenderOverrideBasic(const MString &name) +RenderOverrideStandard::RenderOverrideStandard(const MString &name) : MRenderOverride(name) - , m_ui_name(kRendererBasicUiName) + , m_ui_name(kRendererStandardUiName) , m_renderer_change_callback(0) , m_render_override_change_callback(0) , m_globals_node() { MHWRender::MRenderer *renderer = MHWRender::MRenderer::theRenderer(); if (!renderer) { MMSOLVER_MAYA_ERR( - "MM Renderer Basic Render Override: " + "MM Renderer Standard Render Override: " "Failed to get renderer."); } - const MString kBackgroundOpName = MString("mmRendererBasic_backgroundPass"); + const MString kBackgroundOpName = + MString("mmRendererStandard_backgroundPass"); m_backgroundOp = new SceneRender(kBackgroundOpName); m_backgroundOp->setEnabled(true); @@ -143,10 +144,10 @@ RenderOverrideBasic::RenderOverrideBasic(const MString &name) MHWRender::MRenderOperation::kStandardBackgroundName, m_backgroundOp); } -RenderOverrideBasic::~RenderOverrideBasic() { +RenderOverrideStandard::~RenderOverrideStandard() { m_backgroundOp = nullptr; - RenderOverrideBasic::cleanup(); + RenderOverrideStandard::cleanup(); // Clean up callbacks if (m_renderer_change_callback) { @@ -157,16 +158,16 @@ RenderOverrideBasic::~RenderOverrideBasic() { } } -MHWRender::DrawAPI RenderOverrideBasic::supportedDrawAPIs() const { +MHWRender::DrawAPI RenderOverrideStandard::supportedDrawAPIs() const { // The SilhouetteRender only works on OpenGL, so we cannot support // DirectX on Windows or Metal on Apple. return MHWRender::kOpenGLCoreProfile; } // Perform any setup required before render operations are to be executed. -MStatus RenderOverrideBasic::setup(const MString &destination) { +MStatus RenderOverrideStandard::setup(const MString &destination) { const bool verbose = false; - MMSOLVER_MAYA_VRB("RenderOverrideBasic::setup: start " + MMSOLVER_MAYA_VRB("RenderOverrideStandard::setup: start " << destination.asChar()); MStatus status = MS::kSuccess; @@ -187,19 +188,19 @@ MStatus RenderOverrideBasic::setup(const MString &destination) { } MMSOLVER_MAYA_VRB( - "RenderOverrideBasic::setup: m_backgroundOp=" << m_backgroundOp); + "RenderOverrideStandard::setup: m_backgroundOp=" << m_backgroundOp); if (m_backgroundOp) { m_image_plane_nodes.clear(); status = add_all_image_planes(m_image_plane_nodes); MMSOLVER_MAYA_VRB( - "RenderOverrideBasic::setup: m_image_plane_nodes.length()=" + "RenderOverrideStandard::setup: m_image_plane_nodes.length()=" << m_image_plane_nodes.length()); CHECK_MSTATUS_AND_RETURN_IT(status); m_backgroundOp->setObjectSetOverride(&m_image_plane_nodes); } - MMSOLVER_MAYA_VRB("RenderOverrideBasic::setup: end " + MMSOLVER_MAYA_VRB("RenderOverrideStandard::setup: end " << destination.asChar()); return MRenderOverride::setup(destination); } @@ -209,9 +210,9 @@ MStatus RenderOverrideBasic::setup(const MString &destination) { // // End of frame cleanup. Clears out any data on operations which may // change from frame to frame (render target, output panel name etc). -MStatus RenderOverrideBasic::cleanup() { +MStatus RenderOverrideStandard::cleanup() { const bool verbose = false; - MMSOLVER_MAYA_VRB("RenderOverrideBasic::cleanup: "); + MMSOLVER_MAYA_VRB("RenderOverrideStandard::cleanup: "); return MRenderOverride::cleanup(); } diff --git a/src/mmSolver/render/RenderOverrideBasic.h b/src/mmSolver/render/RenderOverrideStandard.h similarity index 86% rename from src/mmSolver/render/RenderOverrideBasic.h rename to src/mmSolver/render/RenderOverrideStandard.h index 081d5ccb1..dac16f4b6 100644 --- a/src/mmSolver/render/RenderOverrideBasic.h +++ b/src/mmSolver/render/RenderOverrideStandard.h @@ -21,8 +21,8 @@ * */ -#ifndef MM_SOLVER_RENDER_BASIC_RENDER_OVERRIDE_H -#define MM_SOLVER_RENDER_BASIC_RENDER_OVERRIDE_H +#ifndef MM_SOLVER_RENDER_STANDARD_RENDER_OVERRIDE_H +#define MM_SOLVER_RENDER_STANDARD_RENDER_OVERRIDE_H // STL #include @@ -37,7 +37,7 @@ #include // MM Solver -#include "RenderGlobalsBasicNode.h" +#include "RenderGlobalsStandardNode.h" #include "mmSolver/render/data/RenderMode.h" #include "mmSolver/render/data/constants.h" #include "mmSolver/render/ops/SceneRender.h" @@ -46,10 +46,10 @@ namespace mmsolver { namespace render { -class RenderOverrideBasic : public MHWRender::MRenderOverride { +class RenderOverrideStandard : public MHWRender::MRenderOverride { public: - RenderOverrideBasic(const MString& name); - ~RenderOverrideBasic() override; + RenderOverrideStandard(const MString& name); + ~RenderOverrideStandard() override; MHWRender::DrawAPI supportedDrawAPIs() const override; @@ -81,7 +81,7 @@ class RenderOverrideBasic : public MHWRender::MRenderOverride { private: SceneRender* m_backgroundOp; - // A handle to the 'mmRenderGlobals' node. + // A handle to the 'mmRenderGlobalsStandard' node. MObjectHandle m_globals_node; MSelectionList m_image_plane_nodes; @@ -90,4 +90,4 @@ class RenderOverrideBasic : public MHWRender::MRenderOverride { } // namespace render } // namespace mmsolver -#endif // MAYA_MM_SOLVER_RENDER_BASIC_RENDER_OVERRIDE_H +#endif // MAYA_MM_SOLVER_RENDER_STANDARD_RENDER_OVERRIDE_H diff --git a/src/mmSolver/render/data/constants.h b/src/mmSolver/render/data/constants.h index 94bdb887c..05a54b037 100644 --- a/src/mmSolver/render/data/constants.h +++ b/src/mmSolver/render/data/constants.h @@ -92,23 +92,23 @@ const BackgroundStyle kBackgroundStyleDefault = BackgroundStyle::kMayaDefault; const RenderColorFormat kRenderColorFormatDefault = RenderColorFormat::kRGBA16BitFloat; -// Basic Constants -const MString kRenderGlobalsBasicNodeName = "mmRenderGlobals"; -const MString kRendererBasicCmdName = "mmRenderer"; -const char kRendererBasicName[] = "mmRenderer"; -const char kRendererBasicUiName[] = "mmRenderer"; -const MString kRendererBasicCreateNodeCommand = +// Standard Renderer Constants +const MString kRenderGlobalsStandardNodeName = "mmRenderGlobalsStandard"; +const MString kRendererStandardCmdName = "mmRendererStandard"; +const char kRendererStandardName[] = "mmRendererStandard"; +const char kRendererStandardUiName[] = "MM Standard Renderer"; +const MString kRendererStandardCreateNodeCommand = "string $mm_globals_node = `createNode \"" + - MString(MM_RENDER_GLOBALS_BASIC_TYPE_NAME) + "\" -name \"" + - MString(kRenderGlobalsBasicNodeName) + "\" -shared -skipSelect`;\n" + + MString(MM_RENDER_GLOBALS_STANDARD_TYPE_NAME) + "\" -name \"" + + MString(kRenderGlobalsStandardNodeName) + "\" -shared -skipSelect`;\n" + "if (size($mm_globals_node) > 0) {\n" + " lockNode -lock on $mm_globals_node;\n" + "}\n"; -// Silhouette Constants +// Silhouette Renderer Constants const MString kRenderGlobalsSilhouetteNodeName = "mmRenderGlobalsSilhouette"; const MString kRendererSilhouetteCmdName = "mmRendererSilhouette"; const char kRendererSilhouetteName[] = "mmRendererSilhouette"; -const char kRendererSilhouetteUiName[] = "mmRenderer (silhouette)"; +const char kRendererSilhouetteUiName[] = "MM Silhouette Renderer"; const MString kRendererSilhouetteCreateNodeCommand = "string $mm_globals_node = `createNode \"" + MString(MM_RENDER_GLOBALS_SILHOUETTE_TYPE_NAME) + "\" -name \"" + From ab727654eab559ed88a3e20b0625331b005aa715 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 11 Oct 2024 01:54:04 +1030 Subject: [PATCH 275/295] Docs - MM Renderer documentation improved for v0.5.0 --- .../images/tools_renderer_display_layers.png | Bin 25055 -> 0 bytes .../tools_renderer_globals_silhouette.png | Bin 0 -> 12637 bytes docs/source/images/tools_renderer_menu.png | Bin 5284 -> 6210 bytes .../images/tools_renderer_menu_silhouette.png | Bin 0 -> 6326 bytes .../images/tools_renderer_menu_standard.png | Bin 0 -> 6348 bytes .../tools_renderer_silhouette_viewport.png | Bin 0 -> 146161 bytes docs/source/tools_displaytools.rst | 2 +- docs/source/tools_renderer.rst | 172 +++++++++++++++--- 8 files changed, 151 insertions(+), 23 deletions(-) delete mode 100644 docs/source/images/tools_renderer_display_layers.png create mode 100644 docs/source/images/tools_renderer_globals_silhouette.png create mode 100644 docs/source/images/tools_renderer_menu_silhouette.png create mode 100644 docs/source/images/tools_renderer_menu_standard.png create mode 100644 docs/source/images/tools_renderer_silhouette_viewport.png diff --git a/docs/source/images/tools_renderer_display_layers.png b/docs/source/images/tools_renderer_display_layers.png deleted file mode 100644 index 7bd977c00becb13446e6df530b07e073649301d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25055 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 CZf&Cg diff --git a/docs/source/images/tools_renderer_globals_silhouette.png b/docs/source/images/tools_renderer_globals_silhouette.png new file mode 100644 index 0000000000000000000000000000000000000000..aece5949ccd89f935dd017f9a16132683e880147 GIT binary patch literal 12637 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 LbgmVtSU&ncN{GH5 literal 0 HcmV?d00001 diff --git a/docs/source/images/tools_renderer_menu.png b/docs/source/images/tools_renderer_menu.png index 659f4cffe24076e31e353fd23ce3632dbe30bae2..bfc705cb891dfebcdd7dc97a3e484cb0c4b3868b 100644 GIT binary patch literal 6210 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

    +2p0r^-_nI5eVYU^Le_L!JZaKxsl!ekUuv$7 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 literal 0 HcmV?d00001 diff --git a/docs/source/images/tools_image_plane_attributes_misc.png b/docs/source/images/tools_image_plane_attributes_misc.png new file mode 100644 index 0000000000000000000000000000000000000000..8e5910410c54521226fae1c1e36f620243de7a85 GIT binary patch literal 4843 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 literal 0 HcmV?d00001 diff --git a/docs/source/images/tools_image_plane_attributes_nodes.png b/docs/source/images/tools_image_plane_attributes_nodes.png new file mode 100644 index 0000000000000000000000000000000000000000..1364b8a4648dd73c3712cb283445258fb2e0ef3f GIT binary patch literal 2908 zcmcJRc|6w?sTTNT^DQ#z_NNuH6qLrvxO08vTEQt_HiLI*AsWCBaRV}4j zOYPK>1R>?Ap(<5dkyvWNpkl2cBJak`n|bqjpZ9rxy+4wB@A=(x?>Xn*`#tA-JA{>_ z*cW0D2t*QYecm1d5g>uSy@(L_Z%ruv2pTBX-pT?}*{d=K27-R(w&oB>4MBXzdp{V9 zVys=T5QtnD{|6nvp?(tr5nF+uH+Q({u}E)4e_5l@xx)GGT7ko-8demr=CS}z1f5S#1FtLA$%wOwFNG8iB`X#>9JiIJKZtaDCgV9umNxr&Zy zY+~S@QEGrR5airX_vy%})Xp)Zn>J=F4xxxuEdQYlvzAX8lBaOdTnE0^!_MxSk8dI| zG)1(f2&sNp%OLQLC_kvxq2-2Mgq~h`^c^uEFU~wMAx=Sn4p@ zHvO8tK1!6v0%x8c2Nr3Gl4kpb=oy{b0~eq~$r6pb7mDjHyNgtYxXUMVq70e9PbFcU z&U<}=bk^4AE|ZdkCPiyMB&%Ms-v7xi-S~~6jmbv5BdnMRj9|VDEmyistuV847Hd(! zNRLmWI+xdy$ovQ$0E>j|r@P8uax_%MSmu+Hy*w}fitt~s;}o!1^YYWn)B1b|+q-M7 zhe3%t{l!&%UFe;|wWcz|-W!8w`cgR^-v&s5DF-^bYo0A9h=cA25JM5Z`$|W_842it z-i5Bc1B{G}oH+!BXGM=xxx$Ck*kn zyW$I|qN2C18gN#RLU;o&J9r6c`FeP*u$*2hiwTh(Y6Ed#zULT16;ECj2P-_hxHK*3 zM)lHF`AtP01HE030XCD@O%c@n5`QX0eku|tj-we3SIFc+)~A`u(MrbkfI&$O?gG1N z20r5VdS||gf6tVZDFk^;XOM^c$#+EvSc87Y+_ujrW=0rTiu4e*|NZTP#^+%FTwX`IrWsuN8QQF^qhnKYF5ym~~? z+;i9?cA3k?#@h!G0`Yw(X@-^x)Lb(D!-v$&g{eo}zN^JFhp_-p%@SR)jBQ zi)VJJw4m%Csq&tmS%{UcRX~VfOb52A=o5?prwl94R%sOW5>8j@o?P;|pL32MJ;I)H zoZq=>TAds^H$hy9)xT%}@5XVKM(+%>)`|XsG{t``NkXV4bFneMidH7k8R*&Z+{JAb z0oF)CzMgLFe8s9;o(9d;H8&1@8x^+SJY~$FxH)Ct+B~guK+;Dy$518ivkU$;VRSg= zaS+l9i`v-6Z=`C*6fuWz0|@;*^hH3V9Kv`fUWh)qg>n{V7_fr|TtrBIzaUZ1t6#L| z2-19#-;RJ$9FdP$P@jAp&|{)e4C3vv@F#oGe$ZS_s5VnE%lJ+k5@X13%R6Y7B5| zJg2t1yzC{^f90o@e%NUpVt^nYKq}1L-Eq7f%-^J5CbuGL-@oKP+)KZ9`ZuC=?4bqB zwGw6fR_F+M9-Zz!C=xhM{S7%j?$-fKm~i$o2D#z(e`gUpt4D{(KpQP|44@-rvCdsM z$F+i>MTIL2^j+G#=EEcy*G8r`nzG z28qf>@2~|-TV7mG%cW84D1)LL>#|n=D!n-(??*gX^b{cF8Ham=<-7Fco`#^>oEH(w zL|0Vf>WB^dO=`wE<2d7p5nk;!e^2y(AmdshK>!_Q;EW&8pyhkEixq9b>U`aJEpu*- z6Q{z5jMQ%Es!W{=IAC{L^_%R;`SusB7RxNB+D2nC;?VR~sQPsSGz`8S;Unzyn13^$ zvRCr8NOGZ(O;2#Rh6St$$vDYI ziC4iIo^uyBCD``dV`T>=u(Y>~QEr(C{6$Z1exZ5YRyp86>$9|Gt8c7Ws(j|@-`oxq z^yk*lTzT25NbeaZ*sl=f3|t` tool. Key features: @@ -188,49 +189,154 @@ Key features: Display Attributes ~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_display.png + :alt: MM ImagePlane Display attributes. + :align: center + :width: 80% + *To be written* +.. list-table:: Display Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-image-sequence-attributes-ref: Image Sequence Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_image_sequence.png + :alt: MM ImagePlane Image Sequence attributes. + :align: center + :width: 80% + *To be written* +.. list-table:: Image Sequence Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-hud-attributes-ref: HUD Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_hud.png + :alt: MM ImagePlane HUD attributes. + :align: center + :width: 80% + *To be written* +.. list-table:: HUD Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-image-cache-attributes-ref: Image Cache Attributes ~~~~~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_image_cache.png + :alt: MM ImagePlane Image Cache attributes. + :align: center + :width: 80% + *To be written* See :ref:`Image Cache Preferences ` for details on how to control the MM Image Plane hardware resources used. +.. list-table:: Image Cache Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-misc-attributes-ref: Miscellaneous Attributes ~~~~~~~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_misc.png + :alt: MM ImagePlane Miscellaneous attributes. + :align: center + :width: 80% + *To be written* +.. list-table:: Miscellaneous Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-nodes-attributes-ref: Nodes Attributes ~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_nodes.png + :alt: MM ImagePlane Nodes attributes. + :align: center + :width: 80% + *To be written* +.. list-table:: Nodes Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description + .. _imageplane-extended-image-details-attributes-ref: Extended Image Details Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. figure:: images/tools_image_plane_attributes_extended_image_details.png + :alt: MM ImagePlane Extended Image Details attributes. + :align: center + :width: 80% + *To be written* + +.. list-table:: Extended Image Details Attributes + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Name + - Description From bc1a13dd7f7232784b134a29aad3deedef17d4fc Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 09:50:55 +1030 Subject: [PATCH 285/295] Docs - Create ImagePlane - Remove old note. The note is no longer correct with the v2 MM imagePlane node. --- docs/source/tools_createnode.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index 14fe1a77a..201210e71 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -125,10 +125,6 @@ Create a :ref:`MM ImagePlane ` node, with the chosen image file (sequence) used to display a flat plane with an image texture in the Maya 3D scene. -.. note:: The image plane supports any image format supported by - Maya's ``file`` node, but can be buggy when reading image - sequences. - .. figure:: images/tools_create_mm_image_plane.png :alt: MM Image Plane :align: center From 03c240de3ebfd724def5a48afaba113772c79b7c Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 09:52:44 +1030 Subject: [PATCH 286/295] Docs - MM ImagePlane - Refine lens distortion key feature --- docs/source/tools_createnode.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index 201210e71..4607641cf 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -170,7 +170,7 @@ Key features: - Real-Time Lens Distortion; Lenses added to the camera (with :ref:`Create Lens ` tool) will distort the `MM - ImagePlane` in real-time, as the Lens attributes are updated. + ImagePlane` in real-time as attributes update. - Frame Range controls and details; Override the first frame of an image sequence to any other frame, and see the output frame number From b01c7dc93607de593d0e996a71b3ab2a2da7efd9 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:01:12 +1030 Subject: [PATCH 287/295] Docs - MM ImagePlane - bold key features list. To make the key features easier to read at a glance. --- docs/source/tools_createnode.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index 4607641cf..bc5ca69aa 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -160,25 +160,25 @@ MatchMove tasks, and can be created with the :ref:`Create ImagePlane Key features: -- Multiple Image Slots; Switch seamlessly between 4 different image - sequences loaded onto the `MM ImagePlane`. +- **Multiple Image Slots**; Switch seamlessly between 4 different + image sequences loaded onto the `MM ImagePlane`. -- Memory resource control; The :ref:`Image Cache +- **Memory resource control**; The :ref:`Image Cache ` is used to limit and detail memory usage for the `MM ImagePlane` allowing greater control than the native Maya ImagePlane. -- Real-Time Lens Distortion; Lenses added to the camera (with +- **Real-Time Lens Distortion**; Lenses added to the camera (with :ref:`Create Lens ` tool) will distort the `MM ImagePlane` in real-time as attributes update. -- Frame Range controls and details; Override the first frame of an +- **Frame Range controls and details**; Override the first frame of an image sequence to any other frame, and see the output frame number easily for debugging. -- Enhanced Display Controls; Adjust the exposure, gamma, saturation - and soft-clip of the input image data, and display individual colour - channels. +- **Enhanced Display controls**; Adjust the exposure, gamma, + saturation and soft-clip of the input image data, and display + individual colour channels. .. _imageplane-display-attributes-ref: From 728e5d7955e333ae336b9b587732ae9fac0ca081 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:26:40 +1030 Subject: [PATCH 288/295] MM ImagePlane - Remove imageDefaultColor attribute The attribute is a legacy of the v1 mmImagePlane and is not used. --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 1 - src/mmSolver/shape/ImagePlaneShape2Node.cpp | 8 -------- 2 files changed, 9 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 74be7215a..11c70eca1 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -607,7 +607,6 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -beginLayout "Miscellaneous" -collapse 1; { editorTemplate -addControl "meshResolution"; - editorTemplate -addControl "imageDefaultColor"; // Cannot be textured. editorTemplate -addControl "shaderIsTransparent"; editorTemplate -callCustom diff --git a/src/mmSolver/shape/ImagePlaneShape2Node.cpp b/src/mmSolver/shape/ImagePlaneShape2Node.cpp index 42444c259..a4111cd2b 100644 --- a/src/mmSolver/shape/ImagePlaneShape2Node.cpp +++ b/src/mmSolver/shape/ImagePlaneShape2Node.cpp @@ -430,14 +430,6 @@ MStatus ImagePlaneShape2Node::initialize() { nAttr.setNiceNameOverride(MString("Shader Is Transparent (Debug)"))); CHECK_MSTATUS(addAttribute(m_shader_is_transparent)); - m_image_default_color = nAttr.createColor("imageDefaultColor", "imgdefcol"); - CHECK_MSTATUS(nAttr.setKeyable(true)); - CHECK_MSTATUS(nAttr.setStorable(true)); - CHECK_MSTATUS(nAttr.setReadable(true)); - CHECK_MSTATUS(nAttr.setWritable(true)); - CHECK_MSTATUS(nAttr.setDefault(1.0f, 1.0f, 1.0f)); - CHECK_MSTATUS(addAttribute(m_image_default_color)); - m_image_frame_number = nAttr.create("imageFrameNumber", "imgfrmnmb", MFnNumericData::kInt, 1); CHECK_MSTATUS(nAttr.setStorable(true)); From 3b0373852cf2228671eee40f492ec27a2cc58da0 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:28:28 +1030 Subject: [PATCH 289/295] MM ImagePlane - Hide shaderIsTransparent attr in AE The shaderIsTransparent attribute is only needed for debugging and should be hidden from the main areas that users will look. --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 1 - 1 file changed, 1 deletion(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 11c70eca1..e52efcc5a 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -607,7 +607,6 @@ global proc AEmmImagePlaneShape2Template(string $nodeName) editorTemplate -beginLayout "Miscellaneous" -collapse 1; { editorTemplate -addControl "meshResolution"; - editorTemplate -addControl "shaderIsTransparent"; editorTemplate -callCustom "AEmmImagePlaneShape2_colorSpaceNew" From 7258e3b37a991b8542c539e68e1842b5c64461a1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:32:44 +1030 Subject: [PATCH 290/295] MM ImagePlane - replace refresh icon with button The button is more obvious of what it does - it says exactly what it will do. --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index e52efcc5a..09d1277d9 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -315,7 +315,16 @@ global proc AEmmImagePlaneShape2_imageCacheDisplaySectionNew(string $attr_name) { setUITemplate -pst attributeEditorTemplate; - symbolButton -image "TypeResetAll.png" imageCacheUpdateValuesButton; + // Refresh button + rowLayout -nc 3 imageCacheDisplay_refreshLayout; + { + // Spacer to avoid button on the screen-left of the Attribute + // Editor. + text -label ""; + + button -label "Refresh Attribute Editor" imageCacheDisplay_refreshButton; + } + setParent ..; // The total memory size for the image sequence (GB / MB) rowLayout -nc 2 imageCacheImageSequenceSizeLayout; @@ -374,7 +383,7 @@ global proc AEmmImagePlaneShape2_imageCacheDisplaySectionReplace(string $attr_na AEmmImagePlaneShape2_imageCache_updateValues($image_plane_shp[0]); string $update_cmd = "AEmmImagePlaneShape2_imageCache_updateValues " + $image_plane_shp[0]; - symbolButton -edit -command $update_cmd imageCacheUpdateValuesButton; + button -edit -command $update_cmd imageCacheDisplay_refreshButton; } From 3aeb4fe3cedaaf912cf35929144905e8be142b73 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:33:21 +1030 Subject: [PATCH 291/295] Docs - MM ImagePlane - Update Image Cache screenshot --- ...ols_image_plane_attributes_image_cache.png | Bin 8502 -> 8593 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/images/tools_image_plane_attributes_image_cache.png b/docs/source/images/tools_image_plane_attributes_image_cache.png index 531c68742e4429baf74e5fb103945551ab33d4c5..afb5dcfb37c953599c927d8a5875c0c3af02ff29 100644 GIT binary patch literal 8593 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

    +2p0r^-_nI5eVYU^Le_L!JZaKxsl!ekUuv$7 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 Date: Sat, 12 Oct 2024 10:34:12 +1030 Subject: [PATCH 292/295] Docs - MM ImagePlane - update Misc screenshot Some unneeded attributes were removed from the Attribute Editor, so the screenshot must be updated. --- .../tools_image_plane_attributes_misc.png | Bin 4843 -> 3056 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/source/images/tools_image_plane_attributes_misc.png b/docs/source/images/tools_image_plane_attributes_misc.png index 8e5910410c54521226fae1c1e36f620243de7a85..a830e0f81b223160c983ea1c492da6ccdef3a254 100644 GIT binary patch literal 3056 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 KOSyCK<$nX>gVu8Z literal 4843 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 From c501af7978888c4d5fb962100bdc1a3d47daa158 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 10:40:55 +1030 Subject: [PATCH 293/295] Docs - MM ImagePlane - add attribute names. --- docs/source/tools_createnode.rst | 133 +++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 8 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index bc5ca69aa..68f71ba90 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -190,7 +190,8 @@ Display Attributes :align: center :width: 80% -*To be written* +`Display Attributes` control how the `MM ImagePlane` looks in the +viewport; use these attributes to make the image easier to see. .. list-table:: Display Attributes :widths: auto @@ -199,7 +200,34 @@ Display Attributes * - Name - Description - * - Name + * - Visible To Camera Only + - Description + + * - Gain + - Description + + * - Exposure + - Description + + * - Gamma + - Description + + * - Saturation + - Description + + * - SoftClip + - Description + + * - Alpha Gain + - Description + + * - Input Color Space + - Description + + * - Image Ignore Alpha + - Description + + * - Display Channel - Description .. _imageplane-image-sequence-attributes-ref: @@ -221,7 +249,49 @@ Image Sequence Attributes * - Name - Description - * - Name + * - Image Sequence Slot + - Description + + * - Image Sequence (Main) + - Description + + * - Image Sequence (Alt 1) + - Description + + * - Image Sequence (Alt 2) + - Description + + * - Image Sequence (Alt 3) + - Description + + * - Image Width + - Description + + * - Image Height + - Description + + * - Image Pixel Aspect + - Description + + * - Start Frame + - Description + + * - End Frame + - Description + + * - Image Sequence Frame + - Description + + * - First Frame + - Description + + * - Frame Output + - Description + + * - Image Flip (Vertical) + - Description + + * - Image Flip (Horizontal) - Description .. _imageplane-hud-attributes-ref: @@ -243,7 +313,13 @@ HUD Attributes * - Name - Description - * - Name + * - Draw Hud + - Description + + * - Draw Camera Size + - Description + + * - Draw Image Size - Description .. _imageplane-image-cache-attributes-ref: @@ -268,9 +344,27 @@ details on how to control the MM Image Plane hardware resources used. * - Name - Description - * - Name + * - Refresh Attribute Editor (button) + - Description + + * - Image Sequence Size - Description + * - GPU Cache Used + - Description + + * - CPU Cache Used + - Description + + * - Total Memory Available + - Description + + * - Clear... (button) + - Description + + * - Image Cache Preferences... (button) + - Open the :ref:`Image Cache Preferences `. + .. _imageplane-misc-attributes-ref: Miscellaneous Attributes @@ -290,7 +384,10 @@ Miscellaneous Attributes * - Name - Description - * - Name + * - Mesh Resolution + - Description + + * - Output Color Space - Description .. _imageplane-nodes-attributes-ref: @@ -312,7 +409,12 @@ Nodes Attributes * - Name - Description - * - Name + * - Geometry Node + - Mostly used for debugging. The connected node used to extract + mesh geometry for the `MM ImagePlane` to draw on the + screen. This geometry node may be deformed by lens distortion. + + * - Camera Node - Description .. _imageplane-extended-image-details-attributes-ref: @@ -334,5 +436,20 @@ Extended Image Details Attributes * - Name - Description - * - Name + * - Padding + - Description + + * - Image Num Channels + - Description + + * - Image Bytes Per Channel + - Description + + * - Image Size Bytes + - Description + + * - Input Color Space + - Description + + * - Output Color Space - Description From 278ff0a2daa8784a7d88172776776b64bfb6ee6e Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 11:46:14 +1030 Subject: [PATCH 294/295] Docs - MM ImagePlane - Add attributes descriptions --- docs/source/tools_createnode.rst | 190 ++++++++++++++++++++----------- 1 file changed, 125 insertions(+), 65 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index 68f71ba90..515f2b0ad 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -185,14 +185,14 @@ Key features: Display Attributes ~~~~~~~~~~~~~~~~~~ +`Display Attributes` control how the `MM ImagePlane` looks in the +viewport; use these attributes to make the image easier to see. + .. figure:: images/tools_image_plane_attributes_display.png :alt: MM ImagePlane Display attributes. :align: center :width: 80% -`Display Attributes` control how the `MM ImagePlane` looks in the -viewport; use these attributes to make the image easier to see. - .. list-table:: Display Attributes :widths: auto :header-rows: 1 @@ -201,47 +201,69 @@ viewport; use these attributes to make the image easier to see. - Description * - Visible To Camera Only - - Description + - When enabled, this imagePlane node is not visible when viewed + by any other camera (including ``persp`` camera). * - Gain - - Description + - The color of multiplier of the displayed image. * - Exposure - - Description + - Relative exposure brightness (measured in `Stops`) applied to + the image. Negative numbers darken the image, positive values + increase the brightness. * - Gamma - - Description + - Brighten or darken the mid-tones of the image, without + affecting the shadows or highlights as much. Do not use this + slider to approximate a Color Space of 2.2 - use the `Input + Color Space` attribute. * - Saturation - - Description + - Adjust the color saturation of the image; 0.0 makes the image + black and white, 1.0+ numbers make the image colors very + vibrant. * - SoftClip - - Description + - Reduce the image highlights to flatten the bright areas of an image. * - Alpha Gain - - Description + - Alpha channel multiplier. Controls the opacity/transparency of + the image. * - Input Color Space - - Description + - The color space saved into the image. Left-click to choose + color space from menu. * - Image Ignore Alpha - - Description + - If the image contains an alpha channel, you can ignore it here, + and treat the image as fully opaque. * - Display Channel - - Description + - Which channel the image would you like to display? ``RGBA`` + (full), ``RGB`` (color only, disables alpha), ``Red`` only, + ``Green`` only, ``Blue`` only, ``Alpha`` only, or ``Luminance`` + only? .. _imageplane-image-sequence-attributes-ref: Image Sequence Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~ +`Image Sequence` attributes define the image that is loaded and the +frame number used for image look-up. + +`MM ImagePlane` is designed to contain multiple image sequence +`Slots`, so that users can easily swap between different images. For +example, you may have an EXR image sequence, a lower-resolution JPEG +image sequence and a color adjusted image sequence to better see +low-contrast image details - you can easily swap between all of these +image sequences by changing the `Image Sequence Slot` attribute. + .. figure:: images/tools_image_plane_attributes_image_sequence.png :alt: MM ImagePlane Image Sequence attributes. :align: center :width: 80% -*To be written* - .. list-table:: Image Sequence Attributes :widths: auto :header-rows: 1 @@ -250,62 +272,77 @@ Image Sequence Attributes - Description * - Image Sequence Slot - - Description + - Change the `Image Sequence Slot`, between the 4 image sequences + available; ``Main``, ``Alternate 1``, etc. * - Image Sequence (Main) - - Description + - The "Main" image sequence, and the default. * - Image Sequence (Alt 1) - - Description + - First alternate image sequence. * - Image Sequence (Alt 2) - - Description + - Second alternate image sequence. * - Image Sequence (Alt 3) - - Description + - Third alternate image sequence. * - Image Width - - Description + - Resolution width of loaded image. * - Image Height - - Description + - Resolution height of loaded image. * - Image Pixel Aspect - - Description + - Pixel-aspect ratio loaded image. This changes the way the + physical image plane size is calculated. For example, when + loading raw un-squeezed Anamorphic images, set this value to + ``2.0``. * - Start Frame - - Description + - The start frame of the loaded image sequence. Will be ``0`` if + an image sequence is not loaded. * - End Frame - - Description + - The end frame of the loaded image sequence. Will be ``0`` if an + image sequence is not loaded. * - Image Sequence Frame - - Description + - The current **input** image sequence frame number. * - First Frame - - Description + - What frame is the first frame in the image sequence? `MM + ImagePlane` Offsets the `Start Frame` of the image sequence so + it is displayed when the Maya timeline is at `First Frame`. * - Frame Output - - Description + - The current **output** image sequence frame number. * - Image Flip (Vertical) - - Description + - Flips the image vertically; does *not* flip any asymmetric lens + distortion values. - * - Image Flip (Horizontal) - - Description + * - Image Flop (Horizontal) + - Flops the image horizontally; does *not* flop any asymmetric + lens distortion values. .. _imageplane-hud-attributes-ref: -HUD Attributes -~~~~~~~~~~~~~~~~~~~~~~~~~ +HUD (Heads-Up Display) Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +HUD (Heads-Up Display) attributes control the way text is drawn in the +viewport when the `MM ImagePlane` is viewed from it's connected +camera. + +The HUD displays useful information intended to help understanding of +the loaded image and help spot common issues. .. figure:: images/tools_image_plane_attributes_hud.png :alt: MM ImagePlane HUD attributes. :align: center :width: 80% -*To be written* - .. list-table:: HUD Attributes :widths: auto :header-rows: 1 @@ -314,29 +351,31 @@ HUD Attributes - Description * - Draw Hud - - Description + - Enable or disable all HUD features. * - Draw Camera Size - - Description + - Enable or disable the camera size display. * - Draw Image Size - - Description + - Enable or disable the image size display. .. _imageplane-image-cache-attributes-ref: Image Cache Attributes ~~~~~~~~~~~~~~~~~~~~~~ +Provides quick access to see details and control the `Image Cache`, +and clear memory resources. + +See :ref:`Image Cache Preferences ` for +details on how to control the `MM Image Plane` hardware resources +used. + .. figure:: images/tools_image_plane_attributes_image_cache.png :alt: MM ImagePlane Image Cache attributes. :align: center :width: 80% -*To be written* - -See :ref:`Image Cache Preferences ` for -details on how to control the MM Image Plane hardware resources used. - .. list-table:: Image Cache Attributes :widths: auto :header-rows: 1 @@ -345,38 +384,42 @@ details on how to control the MM Image Plane hardware resources used. - Description * - Refresh Attribute Editor (button) - - Description + - Update the text fields in the attribute editor. * - Image Sequence Size - - Description + - Breaks down the amount of memory required for the full image + sequence currently loaded; the size of a single frame + multiplied by the image sequence frame count. * - GPU Cache Used - - Description + - Amount of GPU cache capacity is currently used. * - CPU Cache Used - - Description + - Amount of CPU cache capacity is currently used. * - Total Memory Available - - Description + - Amount of memory resources available on the computer. * - Clear... (button) - - Description + - Left-click to open a menu; clearing all images, clearing memory + from image sequence slots on the current `MM ImagePlane` node. * - Image Cache Preferences... (button) - - Open the :ref:`Image Cache Preferences `. + - Left-click to open the :ref:`Image Cache Preferences `. .. _imageplane-misc-attributes-ref: Miscellaneous Attributes ~~~~~~~~~~~~~~~~~~~~~~~~ +`Miscellaneous` attributes do not fit into the other categories and +are not commonly adjusted, but provided for rare use cases. + .. figure:: images/tools_image_plane_attributes_misc.png :alt: MM ImagePlane Miscellaneous attributes. :align: center :width: 80% -*To be written* - .. list-table:: Miscellaneous Attributes :widths: auto :header-rows: 1 @@ -385,23 +428,33 @@ Miscellaneous Attributes - Description * - Mesh Resolution - - Description + - Used to adjust the real-time lens distortion quality. You + should adjust the resolution with more polygons when lens + distortion is so extreme that the lens distortion is displayed + is not accurate. Increasing the number also slows-down the + real-time performance. * - Output Color Space - - Description + - This is the color space that Maya should work with + internally. Left-click to choose color space from menu. .. _imageplane-nodes-attributes-ref: Nodes Attributes ~~~~~~~~~~~~~~~~ +These attributes can be used to quickly navigate to the connected +nodes of this `MM ImagePlane` node. + +The node connections cannot be adjusted using this UI. Only adjust +these attributes if you know what you are doing, or want to +experiment. + .. figure:: images/tools_image_plane_attributes_nodes.png :alt: MM ImagePlane Nodes attributes. :align: center :width: 80% -*To be written* - .. list-table:: Nodes Attributes :widths: auto :header-rows: 1 @@ -415,20 +468,21 @@ Nodes Attributes screen. This geometry node may be deformed by lens distortion. * - Camera Node - - Description + - The image plane is attached to this camera node. .. _imageplane-extended-image-details-attributes-ref: Extended Image Details Attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +`Extended Image Details` explain the internal and low-level details of +the loaded images, and they are used to debug and for scripting tools. + .. figure:: images/tools_image_plane_attributes_extended_image_details.png :alt: MM ImagePlane Extended Image Details attributes. :align: center :width: 80% -*To be written* - .. list-table:: Extended Image Details Attributes :widths: auto :header-rows: 1 @@ -437,19 +491,25 @@ Extended Image Details Attributes - Description * - Padding - - Description + - Image sequence file path frame number padding; For example + ``file.1001.jpg`` (``file.####.jpg``) has a padding of 4. * - Image Num Channels - - Description + - The number of channels saved in the loaded image; 3 for RGB, 4 + for RGBA. * - Image Bytes Per Channel - - Description + - The number of bytes that is used for each image channel. 8-bit + is 1-byte; 32-bit is 4-bytes. * - Image Size Bytes - - Description + - The calculated number of bytes for a single image. This value + is used to make calculations for the :ref:`Image Cache + Attributes ` attributes, + and is mostly intended for debugging and scripting. * - Input Color Space - - Description + - The string value of the `Input Color Space` of the loaded image. Used for debugging. * - Output Color Space - - Description + - The string value of the `Output Color Space` of the loaded image. Used for debugging. From 82502710fc446a32521693462f4c621d681012e7 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 11:58:08 +1030 Subject: [PATCH 295/295] MM Image - Fix add "vertical_flip" to rust bindings Generated files also contain the latest changes for CXX v1.0.128 --- lib/cppbind/mmimage/include/mmimage/_cxx.h | 3 +- .../mmimage/include/mmimage/_cxxbridge.h | 119 ++--- lib/cppbind/mmimage/src/_cxxbridge.cpp | 421 +++++++++--------- lib/cppbind/mmimage/src/cxxbridge.rs | 1 + 4 files changed, 274 insertions(+), 270 deletions(-) diff --git a/lib/cppbind/mmimage/include/mmimage/_cxx.h b/lib/cppbind/mmimage/include/mmimage/_cxx.h index 907ee829f..002282551 100644 --- a/lib/cppbind/mmimage/include/mmimage/_cxx.h +++ b/lib/cppbind/mmimage/include/mmimage/_cxx.h @@ -659,7 +659,8 @@ typename Slice::iterator::difference_type Slice::iterator::operator-(const iterator &other) const noexcept { auto diff = std::distance(static_cast(other.pos), static_cast(this->pos)); - return diff / this->stride; + return diff / static_cast::iterator::difference_type>( + this->stride); } template diff --git a/lib/cppbind/mmimage/include/mmimage/_cxxbridge.h b/lib/cppbind/mmimage/include/mmimage/_cxxbridge.h index 2703ab630..67e1b05ad 100644 --- a/lib/cppbind/mmimage/include/mmimage/_cxxbridge.h +++ b/lib/cppbind/mmimage/include/mmimage/_cxxbridge.h @@ -386,7 +386,8 @@ typename Slice::iterator::difference_type Slice::iterator::operator-(const iterator &other) const noexcept { auto diff = std::distance(static_cast(other.pos), static_cast(this->pos)); - return diff / this->stride; + return diff / static_cast::iterator::difference_type>( + this->stride); } template @@ -989,12 +990,12 @@ struct ExrPixelLayout final { ::std::size_t tile_size_x; ::std::size_t tile_size_y; - bool operator==(const ExrPixelLayout &) const noexcept; - bool operator!=(const ExrPixelLayout &) const noexcept; - bool operator<(const ExrPixelLayout &) const noexcept; - bool operator<=(const ExrPixelLayout &) const noexcept; - bool operator>(const ExrPixelLayout &) const noexcept; - bool operator>=(const ExrPixelLayout &) const noexcept; + bool operator==(ExrPixelLayout const &) const noexcept; + bool operator!=(ExrPixelLayout const &) const noexcept; + bool operator<(ExrPixelLayout const &) const noexcept; + bool operator<=(ExrPixelLayout const &) const noexcept; + bool operator>(ExrPixelLayout const &) const noexcept; + bool operator>=(ExrPixelLayout const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ExrPixelLayout @@ -1016,12 +1017,12 @@ struct ImageExrEncoder final { ::mmimage::ExrPixelLayout pixel_layout; ::mmimage::ExrLineOrder line_order; - bool operator==(const ImageExrEncoder &) const noexcept; - bool operator!=(const ImageExrEncoder &) const noexcept; - bool operator<(const ImageExrEncoder &) const noexcept; - bool operator<=(const ImageExrEncoder &) const noexcept; - bool operator>(const ImageExrEncoder &) const noexcept; - bool operator>=(const ImageExrEncoder &) const noexcept; + bool operator==(ImageExrEncoder const &) const noexcept; + bool operator!=(ImageExrEncoder const &) const noexcept; + bool operator<(ImageExrEncoder const &) const noexcept; + bool operator<=(ImageExrEncoder const &) const noexcept; + bool operator>(ImageExrEncoder const &) const noexcept; + bool operator>=(ImageExrEncoder const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ImageExrEncoder @@ -1032,12 +1033,12 @@ struct OptionF32 final { bool exists; float value; - bool operator==(const OptionF32 &) const noexcept; - bool operator!=(const OptionF32 &) const noexcept; - bool operator<(const OptionF32 &) const noexcept; - bool operator<=(const OptionF32 &) const noexcept; - bool operator>(const OptionF32 &) const noexcept; - bool operator>=(const OptionF32 &) const noexcept; + bool operator==(OptionF32 const &) const noexcept; + bool operator!=(OptionF32 const &) const noexcept; + bool operator<(OptionF32 const &) const noexcept; + bool operator<=(OptionF32 const &) const noexcept; + bool operator>(OptionF32 const &) const noexcept; + bool operator>=(OptionF32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$OptionF32 @@ -1048,12 +1049,12 @@ struct Vec2F32 final { float x; float y; - bool operator==(const Vec2F32 &) const noexcept; - bool operator!=(const Vec2F32 &) const noexcept; - bool operator<(const Vec2F32 &) const noexcept; - bool operator<=(const Vec2F32 &) const noexcept; - bool operator>(const Vec2F32 &) const noexcept; - bool operator>=(const Vec2F32 &) const noexcept; + bool operator==(Vec2F32 const &) const noexcept; + bool operator!=(Vec2F32 const &) const noexcept; + bool operator<(Vec2F32 const &) const noexcept; + bool operator<=(Vec2F32 const &) const noexcept; + bool operator>(Vec2F32 const &) const noexcept; + bool operator>=(Vec2F32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Vec2F32 @@ -1064,12 +1065,12 @@ struct Vec2I32 final { ::std::int32_t x; ::std::int32_t y; - bool operator==(const Vec2I32 &) const noexcept; - bool operator!=(const Vec2I32 &) const noexcept; - bool operator<(const Vec2I32 &) const noexcept; - bool operator<=(const Vec2I32 &) const noexcept; - bool operator>(const Vec2I32 &) const noexcept; - bool operator>=(const Vec2I32 &) const noexcept; + bool operator==(Vec2I32 const &) const noexcept; + bool operator!=(Vec2I32 const &) const noexcept; + bool operator<(Vec2I32 const &) const noexcept; + bool operator<=(Vec2I32 const &) const noexcept; + bool operator>(Vec2I32 const &) const noexcept; + bool operator>=(Vec2I32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Vec2I32 @@ -1082,12 +1083,12 @@ struct Box2F32 final { float max_x; float max_y; - bool operator==(const Box2F32 &) const noexcept; - bool operator!=(const Box2F32 &) const noexcept; - bool operator<(const Box2F32 &) const noexcept; - bool operator<=(const Box2F32 &) const noexcept; - bool operator>(const Box2F32 &) const noexcept; - bool operator>=(const Box2F32 &) const noexcept; + bool operator==(Box2F32 const &) const noexcept; + bool operator!=(Box2F32 const &) const noexcept; + bool operator<(Box2F32 const &) const noexcept; + bool operator<=(Box2F32 const &) const noexcept; + bool operator>(Box2F32 const &) const noexcept; + bool operator>=(Box2F32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Box2F32 @@ -1100,12 +1101,12 @@ struct ImageRegionRectangle final { ::std::size_t size_x; ::std::size_t size_y; - bool operator==(const ImageRegionRectangle &) const noexcept; - bool operator!=(const ImageRegionRectangle &) const noexcept; - bool operator<(const ImageRegionRectangle &) const noexcept; - bool operator<=(const ImageRegionRectangle &) const noexcept; - bool operator>(const ImageRegionRectangle &) const noexcept; - bool operator>=(const ImageRegionRectangle &) const noexcept; + bool operator==(ImageRegionRectangle const &) const noexcept; + bool operator!=(ImageRegionRectangle const &) const noexcept; + bool operator<(ImageRegionRectangle const &) const noexcept; + bool operator<=(ImageRegionRectangle const &) const noexcept; + bool operator>(ImageRegionRectangle const &) const noexcept; + bool operator>=(ImageRegionRectangle const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ImageRegionRectangle @@ -1118,12 +1119,12 @@ struct PixelF32x4 final { float b; float a; - bool operator==(const PixelF32x4 &) const noexcept; - bool operator!=(const PixelF32x4 &) const noexcept; - bool operator<(const PixelF32x4 &) const noexcept; - bool operator<=(const PixelF32x4 &) const noexcept; - bool operator>(const PixelF32x4 &) const noexcept; - bool operator>=(const PixelF32x4 &) const noexcept; + bool operator==(PixelF32x4 const &) const noexcept; + bool operator!=(PixelF32x4 const &) const noexcept; + bool operator<(PixelF32x4 const &) const noexcept; + bool operator<=(PixelF32x4 const &) const noexcept; + bool operator>(PixelF32x4 const &) const noexcept; + bool operator>=(PixelF32x4 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$PixelF32x4 @@ -1134,12 +1135,12 @@ struct PixelF64x2 final { double x; double y; - bool operator==(const PixelF64x2 &) const noexcept; - bool operator!=(const PixelF64x2 &) const noexcept; - bool operator<(const PixelF64x2 &) const noexcept; - bool operator<=(const PixelF64x2 &) const noexcept; - bool operator>(const PixelF64x2 &) const noexcept; - bool operator>=(const PixelF64x2 &) const noexcept; + bool operator==(PixelF64x2 const &) const noexcept; + bool operator!=(PixelF64x2 const &) const noexcept; + bool operator<(PixelF64x2 const &) const noexcept; + bool operator<=(PixelF64x2 const &) const noexcept; + bool operator>(PixelF64x2 const &) const noexcept; + bool operator>=(PixelF64x2 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$PixelF64x2 @@ -1162,8 +1163,8 @@ struct ShimImagePixelBuffer final : public ::rust::Opaque { MMIMAGE_API_EXPORT ::std::size_t num_channels() const noexcept; MMIMAGE_API_EXPORT ::std::size_t pixel_count() const noexcept; MMIMAGE_API_EXPORT ::std::size_t element_count() const noexcept; - MMIMAGE_API_EXPORT ::rust::Slice as_slice_f32x4() const noexcept; - MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4> as_slice_f32x4_mut() noexcept; + MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 const> as_slice_f32x4() const noexcept; + MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 > as_slice_f32x4_mut() noexcept; MMIMAGE_API_EXPORT void resize(::mmimage::BufferDataType data_type, ::std::size_t image_width, ::std::size_t image_height, ::std::size_t num_channels) noexcept; ~ShimImagePixelBuffer() = delete; @@ -1230,9 +1231,9 @@ MMIMAGE_API_EXPORT ::rust::Box<::mmimage::ShimImagePixelBuffer> shim_create_imag MMIMAGE_API_EXPORT ::rust::Box<::mmimage::ShimImageMetaData> shim_create_image_meta_data_box() noexcept; -MMIMAGE_API_EXPORT bool shim_image_read_pixels_exr_f32x4(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept; +MMIMAGE_API_EXPORT bool shim_image_read_pixels_exr_f32x4(::rust::Str file_path, bool vertical_flip, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept; MMIMAGE_API_EXPORT bool shim_image_read_metadata_exr(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data) noexcept; -MMIMAGE_API_EXPORT bool shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, const ::rust::Box<::mmimage::ShimImageMetaData> &in_meta_data, const ::rust::Box<::mmimage::ShimImagePixelBuffer> &in_pixel_buffer) noexcept; +MMIMAGE_API_EXPORT bool shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, ::rust::Box<::mmimage::ShimImageMetaData> const &in_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> const &in_pixel_buffer) noexcept; } // namespace mmimage diff --git a/lib/cppbind/mmimage/src/_cxxbridge.cpp b/lib/cppbind/mmimage/src/_cxxbridge.cpp index 2ce0346d5..745a44c3f 100644 --- a/lib/cppbind/mmimage/src/_cxxbridge.cpp +++ b/lib/cppbind/mmimage/src/_cxxbridge.cpp @@ -386,7 +386,8 @@ typename Slice::iterator::difference_type Slice::iterator::operator-(const iterator &other) const noexcept { auto diff = std::distance(static_cast(other.pos), static_cast(this->pos)); - return diff / this->stride; + return diff / static_cast::iterator::difference_type>( + this->stride); } template @@ -927,6 +928,10 @@ class Slice::uninit {}; template inline Slice::Slice(uninit) noexcept {} +namespace repr { +using Fat = ::std::array<::std::uintptr_t, 2>; +} // namespace repr + namespace detail { template struct operator_new { @@ -948,10 +953,6 @@ union MaybeUninit { }; namespace { -namespace repr { -using Fat = ::std::array<::std::uintptr_t, 2>; -} // namespace repr - template <> class impl final { public: @@ -1043,12 +1044,12 @@ struct ExrPixelLayout final { ::std::size_t tile_size_x; ::std::size_t tile_size_y; - bool operator==(const ExrPixelLayout &) const noexcept; - bool operator!=(const ExrPixelLayout &) const noexcept; - bool operator<(const ExrPixelLayout &) const noexcept; - bool operator<=(const ExrPixelLayout &) const noexcept; - bool operator>(const ExrPixelLayout &) const noexcept; - bool operator>=(const ExrPixelLayout &) const noexcept; + bool operator==(ExrPixelLayout const &) const noexcept; + bool operator!=(ExrPixelLayout const &) const noexcept; + bool operator<(ExrPixelLayout const &) const noexcept; + bool operator<=(ExrPixelLayout const &) const noexcept; + bool operator>(ExrPixelLayout const &) const noexcept; + bool operator>=(ExrPixelLayout const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ExrPixelLayout @@ -1070,12 +1071,12 @@ struct ImageExrEncoder final { ::mmimage::ExrPixelLayout pixel_layout; ::mmimage::ExrLineOrder line_order; - bool operator==(const ImageExrEncoder &) const noexcept; - bool operator!=(const ImageExrEncoder &) const noexcept; - bool operator<(const ImageExrEncoder &) const noexcept; - bool operator<=(const ImageExrEncoder &) const noexcept; - bool operator>(const ImageExrEncoder &) const noexcept; - bool operator>=(const ImageExrEncoder &) const noexcept; + bool operator==(ImageExrEncoder const &) const noexcept; + bool operator!=(ImageExrEncoder const &) const noexcept; + bool operator<(ImageExrEncoder const &) const noexcept; + bool operator<=(ImageExrEncoder const &) const noexcept; + bool operator>(ImageExrEncoder const &) const noexcept; + bool operator>=(ImageExrEncoder const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ImageExrEncoder @@ -1086,12 +1087,12 @@ struct OptionF32 final { bool exists; float value; - bool operator==(const OptionF32 &) const noexcept; - bool operator!=(const OptionF32 &) const noexcept; - bool operator<(const OptionF32 &) const noexcept; - bool operator<=(const OptionF32 &) const noexcept; - bool operator>(const OptionF32 &) const noexcept; - bool operator>=(const OptionF32 &) const noexcept; + bool operator==(OptionF32 const &) const noexcept; + bool operator!=(OptionF32 const &) const noexcept; + bool operator<(OptionF32 const &) const noexcept; + bool operator<=(OptionF32 const &) const noexcept; + bool operator>(OptionF32 const &) const noexcept; + bool operator>=(OptionF32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$OptionF32 @@ -1102,12 +1103,12 @@ struct Vec2F32 final { float x; float y; - bool operator==(const Vec2F32 &) const noexcept; - bool operator!=(const Vec2F32 &) const noexcept; - bool operator<(const Vec2F32 &) const noexcept; - bool operator<=(const Vec2F32 &) const noexcept; - bool operator>(const Vec2F32 &) const noexcept; - bool operator>=(const Vec2F32 &) const noexcept; + bool operator==(Vec2F32 const &) const noexcept; + bool operator!=(Vec2F32 const &) const noexcept; + bool operator<(Vec2F32 const &) const noexcept; + bool operator<=(Vec2F32 const &) const noexcept; + bool operator>(Vec2F32 const &) const noexcept; + bool operator>=(Vec2F32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Vec2F32 @@ -1118,12 +1119,12 @@ struct Vec2I32 final { ::std::int32_t x; ::std::int32_t y; - bool operator==(const Vec2I32 &) const noexcept; - bool operator!=(const Vec2I32 &) const noexcept; - bool operator<(const Vec2I32 &) const noexcept; - bool operator<=(const Vec2I32 &) const noexcept; - bool operator>(const Vec2I32 &) const noexcept; - bool operator>=(const Vec2I32 &) const noexcept; + bool operator==(Vec2I32 const &) const noexcept; + bool operator!=(Vec2I32 const &) const noexcept; + bool operator<(Vec2I32 const &) const noexcept; + bool operator<=(Vec2I32 const &) const noexcept; + bool operator>(Vec2I32 const &) const noexcept; + bool operator>=(Vec2I32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Vec2I32 @@ -1136,12 +1137,12 @@ struct Box2F32 final { float max_x; float max_y; - bool operator==(const Box2F32 &) const noexcept; - bool operator!=(const Box2F32 &) const noexcept; - bool operator<(const Box2F32 &) const noexcept; - bool operator<=(const Box2F32 &) const noexcept; - bool operator>(const Box2F32 &) const noexcept; - bool operator>=(const Box2F32 &) const noexcept; + bool operator==(Box2F32 const &) const noexcept; + bool operator!=(Box2F32 const &) const noexcept; + bool operator<(Box2F32 const &) const noexcept; + bool operator<=(Box2F32 const &) const noexcept; + bool operator>(Box2F32 const &) const noexcept; + bool operator>=(Box2F32 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$Box2F32 @@ -1154,12 +1155,12 @@ struct ImageRegionRectangle final { ::std::size_t size_x; ::std::size_t size_y; - bool operator==(const ImageRegionRectangle &) const noexcept; - bool operator!=(const ImageRegionRectangle &) const noexcept; - bool operator<(const ImageRegionRectangle &) const noexcept; - bool operator<=(const ImageRegionRectangle &) const noexcept; - bool operator>(const ImageRegionRectangle &) const noexcept; - bool operator>=(const ImageRegionRectangle &) const noexcept; + bool operator==(ImageRegionRectangle const &) const noexcept; + bool operator!=(ImageRegionRectangle const &) const noexcept; + bool operator<(ImageRegionRectangle const &) const noexcept; + bool operator<=(ImageRegionRectangle const &) const noexcept; + bool operator>(ImageRegionRectangle const &) const noexcept; + bool operator>=(ImageRegionRectangle const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$ImageRegionRectangle @@ -1172,12 +1173,12 @@ struct PixelF32x4 final { float b; float a; - bool operator==(const PixelF32x4 &) const noexcept; - bool operator!=(const PixelF32x4 &) const noexcept; - bool operator<(const PixelF32x4 &) const noexcept; - bool operator<=(const PixelF32x4 &) const noexcept; - bool operator>(const PixelF32x4 &) const noexcept; - bool operator>=(const PixelF32x4 &) const noexcept; + bool operator==(PixelF32x4 const &) const noexcept; + bool operator!=(PixelF32x4 const &) const noexcept; + bool operator<(PixelF32x4 const &) const noexcept; + bool operator<=(PixelF32x4 const &) const noexcept; + bool operator>(PixelF32x4 const &) const noexcept; + bool operator>=(PixelF32x4 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$PixelF32x4 @@ -1188,12 +1189,12 @@ struct PixelF64x2 final { double x; double y; - bool operator==(const PixelF64x2 &) const noexcept; - bool operator!=(const PixelF64x2 &) const noexcept; - bool operator<(const PixelF64x2 &) const noexcept; - bool operator<=(const PixelF64x2 &) const noexcept; - bool operator>(const PixelF64x2 &) const noexcept; - bool operator>=(const PixelF64x2 &) const noexcept; + bool operator==(PixelF64x2 const &) const noexcept; + bool operator!=(PixelF64x2 const &) const noexcept; + bool operator<(PixelF64x2 const &) const noexcept; + bool operator<=(PixelF64x2 const &) const noexcept; + bool operator>(PixelF64x2 const &) const noexcept; + bool operator>=(PixelF64x2 const &) const noexcept; using IsRelocatable = ::std::true_type; }; #endif // CXXBRIDGE1_STRUCT_mmimage$PixelF64x2 @@ -1216,8 +1217,8 @@ struct ShimImagePixelBuffer final : public ::rust::Opaque { MMIMAGE_API_EXPORT ::std::size_t num_channels() const noexcept; MMIMAGE_API_EXPORT ::std::size_t pixel_count() const noexcept; MMIMAGE_API_EXPORT ::std::size_t element_count() const noexcept; - MMIMAGE_API_EXPORT ::rust::Slice as_slice_f32x4() const noexcept; - MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4> as_slice_f32x4_mut() noexcept; + MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 const> as_slice_f32x4() const noexcept; + MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 > as_slice_f32x4_mut() noexcept; MMIMAGE_API_EXPORT void resize(::mmimage::BufferDataType data_type, ::std::size_t image_width, ::std::size_t image_height, ::std::size_t num_channels) noexcept; ~ShimImagePixelBuffer() = delete; @@ -1281,72 +1282,72 @@ struct ShimImageMetaData final : public ::rust::Opaque { #endif // CXXBRIDGE1_STRUCT_mmimage$ShimImageMetaData extern "C" { -bool mmimage$cxxbridge1$ExrPixelLayout$operator$eq(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ExrPixelLayout$operator$ne(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ExrPixelLayout$operator$lt(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ExrPixelLayout$operator$le(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ExrPixelLayout$operator$gt(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ExrPixelLayout$operator$ge(const ExrPixelLayout &, const ExrPixelLayout &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$eq(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$ne(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$lt(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$le(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$gt(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$ImageExrEncoder$operator$ge(const ImageExrEncoder &, const ImageExrEncoder &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$eq(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$ne(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$lt(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$le(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$gt(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$OptionF32$operator$ge(const OptionF32 &, const OptionF32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$eq(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$ne(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$lt(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$le(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$gt(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2F32$operator$ge(const Vec2F32 &, const Vec2F32 &) noexcept; -bool mmimage$cxxbridge1$Vec2I32$operator$eq(const Vec2I32 &, const Vec2I32 &) noexcept; -bool mmimage$cxxbridge1$Vec2I32$operator$lt(const Vec2I32 &, const Vec2I32 &) noexcept; -bool mmimage$cxxbridge1$Vec2I32$operator$le(const Vec2I32 &, const Vec2I32 &) noexcept; -::std::size_t mmimage$cxxbridge1$Vec2I32$operator$hash(const Vec2I32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$eq(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$ne(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$lt(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$le(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$gt(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$Box2F32$operator$ge(const Box2F32 &, const Box2F32 &) noexcept; -bool mmimage$cxxbridge1$ImageRegionRectangle$operator$eq(const ImageRegionRectangle &, const ImageRegionRectangle &) noexcept; -bool mmimage$cxxbridge1$ImageRegionRectangle$operator$lt(const ImageRegionRectangle &, const ImageRegionRectangle &) noexcept; -bool mmimage$cxxbridge1$ImageRegionRectangle$operator$le(const ImageRegionRectangle &, const ImageRegionRectangle &) noexcept; -::std::size_t mmimage$cxxbridge1$ImageRegionRectangle$operator$hash(const ImageRegionRectangle &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$eq(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$ne(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$lt(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$le(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$gt(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF32x4$operator$ge(const PixelF32x4 &, const PixelF32x4 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$eq(const PixelF64x2 &, const PixelF64x2 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$ne(const PixelF64x2 &, const PixelF64x2 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$lt(const PixelF64x2 &, const PixelF64x2 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$le(const PixelF64x2 &, const PixelF64x2 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$gt(const PixelF64x2 &, const PixelF64x2 &) noexcept; -bool mmimage$cxxbridge1$PixelF64x2$operator$ge(const PixelF64x2 &, const PixelF64x2 &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$eq(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$ne(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$lt(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$le(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$gt(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ExrPixelLayout$operator$ge(ExrPixelLayout const &, ExrPixelLayout const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$eq(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$ne(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$lt(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$le(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$gt(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$ImageExrEncoder$operator$ge(ImageExrEncoder const &, ImageExrEncoder const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$eq(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$ne(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$lt(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$le(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$gt(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$OptionF32$operator$ge(OptionF32 const &, OptionF32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$eq(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$ne(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$lt(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$le(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$gt(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2F32$operator$ge(Vec2F32 const &, Vec2F32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2I32$operator$eq(Vec2I32 const &, Vec2I32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2I32$operator$lt(Vec2I32 const &, Vec2I32 const &) noexcept; +bool mmimage$cxxbridge1$Vec2I32$operator$le(Vec2I32 const &, Vec2I32 const &) noexcept; +::std::size_t mmimage$cxxbridge1$Vec2I32$operator$hash(Vec2I32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$eq(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$ne(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$lt(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$le(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$gt(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$Box2F32$operator$ge(Box2F32 const &, Box2F32 const &) noexcept; +bool mmimage$cxxbridge1$ImageRegionRectangle$operator$eq(ImageRegionRectangle const &, ImageRegionRectangle const &) noexcept; +bool mmimage$cxxbridge1$ImageRegionRectangle$operator$lt(ImageRegionRectangle const &, ImageRegionRectangle const &) noexcept; +bool mmimage$cxxbridge1$ImageRegionRectangle$operator$le(ImageRegionRectangle const &, ImageRegionRectangle const &) noexcept; +::std::size_t mmimage$cxxbridge1$ImageRegionRectangle$operator$hash(ImageRegionRectangle const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$eq(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$ne(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$lt(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$le(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$gt(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF32x4$operator$ge(PixelF32x4 const &, PixelF32x4 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$eq(PixelF64x2 const &, PixelF64x2 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$ne(PixelF64x2 const &, PixelF64x2 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$lt(PixelF64x2 const &, PixelF64x2 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$le(PixelF64x2 const &, PixelF64x2 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$gt(PixelF64x2 const &, PixelF64x2 const &) noexcept; +bool mmimage$cxxbridge1$PixelF64x2$operator$ge(PixelF64x2 const &, PixelF64x2 const &) noexcept; ::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$operator$sizeof() noexcept; ::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$operator$alignof() noexcept; -::mmimage::BufferDataType mmimage$cxxbridge1$ShimImagePixelBuffer$data_type(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::mmimage::BufferDataType mmimage$cxxbridge1$ShimImagePixelBuffer$data_type(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$image_width(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$image_width(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$image_height(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$image_height(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$num_channels(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$num_channels(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$pixel_count(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$pixel_count(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$element_count(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::std::size_t mmimage$cxxbridge1$ShimImagePixelBuffer$element_count(::mmimage::ShimImagePixelBuffer const &self) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4(const ::mmimage::ShimImagePixelBuffer &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4(::mmimage::ShimImagePixelBuffer const &self) noexcept; ::rust::repr::Fat mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4_mut(::mmimage::ShimImagePixelBuffer &self) noexcept; @@ -1356,316 +1357,316 @@ ::mmimage::ShimImagePixelBuffer *mmimage$cxxbridge1$shim_create_image_pixel_buff ::std::size_t mmimage$cxxbridge1$ShimImageMetaData$operator$sizeof() noexcept; ::std::size_t mmimage$cxxbridge1$ShimImageMetaData$operator$alignof() noexcept; -::mmimage::ImageRegionRectangle mmimage$cxxbridge1$ShimImageMetaData$get_display_window(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::ImageRegionRectangle mmimage$cxxbridge1$ShimImageMetaData$get_display_window(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_display_window(::mmimage::ShimImageMetaData &self, ::mmimage::ImageRegionRectangle value) noexcept; -float mmimage$cxxbridge1$ShimImageMetaData$get_pixel_aspect(const ::mmimage::ShimImageMetaData &self) noexcept; +float mmimage$cxxbridge1$ShimImageMetaData$get_pixel_aspect(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_pixel_aspect(::mmimage::ShimImageMetaData &self, float value) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_layer_name(const ::mmimage::ShimImageMetaData &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_layer_name(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_layer_name(::mmimage::ShimImageMetaData &self, ::rust::Str value) noexcept; -::mmimage::Vec2I32 mmimage$cxxbridge1$ShimImageMetaData$get_layer_position(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::Vec2I32 mmimage$cxxbridge1$ShimImageMetaData$get_layer_position(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_layer_position(::mmimage::ShimImageMetaData &self, ::mmimage::Vec2I32 value) noexcept; -::mmimage::Vec2F32 mmimage$cxxbridge1$ShimImageMetaData$get_screen_window_center(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::Vec2F32 mmimage$cxxbridge1$ShimImageMetaData$get_screen_window_center(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_screen_window_center(::mmimage::ShimImageMetaData &self, ::mmimage::Vec2F32 value) noexcept; -float mmimage$cxxbridge1$ShimImageMetaData$get_screen_window_width(const ::mmimage::ShimImageMetaData &self) noexcept; +float mmimage$cxxbridge1$ShimImageMetaData$get_screen_window_width(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_screen_window_width(::mmimage::ShimImageMetaData &self, float value) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_owner(const ::mmimage::ShimImageMetaData &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_owner(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_owner(::mmimage::ShimImageMetaData &self, ::rust::Str value) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_comments(const ::mmimage::ShimImageMetaData &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_comments(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_comments(::mmimage::ShimImageMetaData &self, ::rust::Str value) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_capture_date(const ::mmimage::ShimImageMetaData &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_capture_date(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_utc_offset(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_utc_offset(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_longitude(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_longitude(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_latitude(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_latitude(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_altitude(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_altitude(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_focus(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_focus(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_exposure(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_exposure(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_aperture(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_aperture(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_iso_speed(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_iso_speed(::mmimage::ShimImageMetaData const &self) noexcept; -::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_frames_per_second(const ::mmimage::ShimImageMetaData &self) noexcept; +::mmimage::OptionF32 mmimage$cxxbridge1$ShimImageMetaData$get_frames_per_second(::mmimage::ShimImageMetaData const &self) noexcept; -::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_software_name(const ::mmimage::ShimImageMetaData &self) noexcept; +::rust::repr::Fat mmimage$cxxbridge1$ShimImageMetaData$get_software_name(::mmimage::ShimImageMetaData const &self) noexcept; void mmimage$cxxbridge1$ShimImageMetaData$set_software_name(::mmimage::ShimImageMetaData &self, ::rust::Str value) noexcept; -void mmimage$cxxbridge1$ShimImageMetaData$all_named_attribute_names(const ::mmimage::ShimImageMetaData &self, ::rust::Vec<::rust::String> *return$) noexcept; +void mmimage$cxxbridge1$ShimImageMetaData$all_named_attribute_names(::mmimage::ShimImageMetaData const &self, ::rust::Vec<::rust::String> *return$) noexcept; -bool mmimage$cxxbridge1$ShimImageMetaData$has_named_attribute(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name) noexcept; +bool mmimage$cxxbridge1$ShimImageMetaData$has_named_attribute(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name) noexcept; -::std::uint8_t mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_type_index(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name) noexcept; +::std::uint8_t mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_type_index(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name) noexcept; -::std::int32_t mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_i32(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name) noexcept; +::std::int32_t mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_i32(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name) noexcept; -float mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_f32(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name) noexcept; +float mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_f32(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name) noexcept; -double mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_f64(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name) noexcept; +double mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_f64(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name) noexcept; -void mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_string(const ::mmimage::ShimImageMetaData &self, ::rust::Str attribute_name, ::rust::String *return$) noexcept; +void mmimage$cxxbridge1$ShimImageMetaData$get_named_attribute_as_string(::mmimage::ShimImageMetaData const &self, ::rust::Str attribute_name, ::rust::String *return$) noexcept; -void mmimage$cxxbridge1$ShimImageMetaData$as_string(const ::mmimage::ShimImageMetaData &self, ::rust::String *return$) noexcept; +void mmimage$cxxbridge1$ShimImageMetaData$as_string(::mmimage::ShimImageMetaData const &self, ::rust::String *return$) noexcept; ::mmimage::ShimImageMetaData *mmimage$cxxbridge1$shim_create_image_meta_data_box() noexcept; -bool mmimage$cxxbridge1$shim_image_read_pixels_exr_f32x4(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept; +bool mmimage$cxxbridge1$shim_image_read_pixels_exr_f32x4(::rust::Str file_path, bool vertical_flip, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept; bool mmimage$cxxbridge1$shim_image_read_metadata_exr(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data) noexcept; -bool mmimage$cxxbridge1$shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, const ::rust::Box<::mmimage::ShimImageMetaData> &in_meta_data, const ::rust::Box<::mmimage::ShimImagePixelBuffer> &in_pixel_buffer) noexcept; +bool mmimage$cxxbridge1$shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, ::rust::Box<::mmimage::ShimImageMetaData> const &in_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> const &in_pixel_buffer) noexcept; } // extern "C" } // namespace mmimage namespace std { template <> struct hash<::mmimage::Vec2I32> { - ::std::size_t operator()(const ::mmimage::Vec2I32 &self) const noexcept { + ::std::size_t operator()(::mmimage::Vec2I32 const &self) const noexcept { return ::mmimage::mmimage$cxxbridge1$Vec2I32$operator$hash(self); } }; template <> struct hash<::mmimage::ImageRegionRectangle> { - ::std::size_t operator()(const ::mmimage::ImageRegionRectangle &self) const noexcept { + ::std::size_t operator()(::mmimage::ImageRegionRectangle const &self) const noexcept { return ::mmimage::mmimage$cxxbridge1$ImageRegionRectangle$operator$hash(self); } }; } // namespace std namespace mmimage { -bool ExrPixelLayout::operator==(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator==(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$eq(*this, rhs); } -bool ExrPixelLayout::operator!=(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator!=(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$ne(*this, rhs); } -bool ExrPixelLayout::operator<(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator<(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$lt(*this, rhs); } -bool ExrPixelLayout::operator<=(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator<=(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$le(*this, rhs); } -bool ExrPixelLayout::operator>(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator>(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$gt(*this, rhs); } -bool ExrPixelLayout::operator>=(const ExrPixelLayout &rhs) const noexcept { +bool ExrPixelLayout::operator>=(ExrPixelLayout const &rhs) const noexcept { return mmimage$cxxbridge1$ExrPixelLayout$operator$ge(*this, rhs); } -bool ImageExrEncoder::operator==(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator==(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$eq(*this, rhs); } -bool ImageExrEncoder::operator!=(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator!=(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$ne(*this, rhs); } -bool ImageExrEncoder::operator<(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator<(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$lt(*this, rhs); } -bool ImageExrEncoder::operator<=(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator<=(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$le(*this, rhs); } -bool ImageExrEncoder::operator>(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator>(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$gt(*this, rhs); } -bool ImageExrEncoder::operator>=(const ImageExrEncoder &rhs) const noexcept { +bool ImageExrEncoder::operator>=(ImageExrEncoder const &rhs) const noexcept { return mmimage$cxxbridge1$ImageExrEncoder$operator$ge(*this, rhs); } -bool OptionF32::operator==(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator==(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$eq(*this, rhs); } -bool OptionF32::operator!=(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator!=(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$ne(*this, rhs); } -bool OptionF32::operator<(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator<(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$lt(*this, rhs); } -bool OptionF32::operator<=(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator<=(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$le(*this, rhs); } -bool OptionF32::operator>(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator>(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$gt(*this, rhs); } -bool OptionF32::operator>=(const OptionF32 &rhs) const noexcept { +bool OptionF32::operator>=(OptionF32 const &rhs) const noexcept { return mmimage$cxxbridge1$OptionF32$operator$ge(*this, rhs); } -bool Vec2F32::operator==(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator==(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$eq(*this, rhs); } -bool Vec2F32::operator!=(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator!=(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$ne(*this, rhs); } -bool Vec2F32::operator<(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator<(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$lt(*this, rhs); } -bool Vec2F32::operator<=(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator<=(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$le(*this, rhs); } -bool Vec2F32::operator>(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator>(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$gt(*this, rhs); } -bool Vec2F32::operator>=(const Vec2F32 &rhs) const noexcept { +bool Vec2F32::operator>=(Vec2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2F32$operator$ge(*this, rhs); } -bool Vec2I32::operator==(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator==(Vec2I32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2I32$operator$eq(*this, rhs); } -bool Vec2I32::operator!=(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator!=(Vec2I32 const &rhs) const noexcept { return !(*this == rhs); } -bool Vec2I32::operator<(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator<(Vec2I32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2I32$operator$lt(*this, rhs); } -bool Vec2I32::operator<=(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator<=(Vec2I32 const &rhs) const noexcept { return mmimage$cxxbridge1$Vec2I32$operator$le(*this, rhs); } -bool Vec2I32::operator>(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator>(Vec2I32 const &rhs) const noexcept { return !(*this <= rhs); } -bool Vec2I32::operator>=(const Vec2I32 &rhs) const noexcept { +bool Vec2I32::operator>=(Vec2I32 const &rhs) const noexcept { return !(*this < rhs); } -bool Box2F32::operator==(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator==(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$eq(*this, rhs); } -bool Box2F32::operator!=(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator!=(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$ne(*this, rhs); } -bool Box2F32::operator<(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator<(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$lt(*this, rhs); } -bool Box2F32::operator<=(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator<=(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$le(*this, rhs); } -bool Box2F32::operator>(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator>(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$gt(*this, rhs); } -bool Box2F32::operator>=(const Box2F32 &rhs) const noexcept { +bool Box2F32::operator>=(Box2F32 const &rhs) const noexcept { return mmimage$cxxbridge1$Box2F32$operator$ge(*this, rhs); } -bool ImageRegionRectangle::operator==(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator==(ImageRegionRectangle const &rhs) const noexcept { return mmimage$cxxbridge1$ImageRegionRectangle$operator$eq(*this, rhs); } -bool ImageRegionRectangle::operator!=(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator!=(ImageRegionRectangle const &rhs) const noexcept { return !(*this == rhs); } -bool ImageRegionRectangle::operator<(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator<(ImageRegionRectangle const &rhs) const noexcept { return mmimage$cxxbridge1$ImageRegionRectangle$operator$lt(*this, rhs); } -bool ImageRegionRectangle::operator<=(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator<=(ImageRegionRectangle const &rhs) const noexcept { return mmimage$cxxbridge1$ImageRegionRectangle$operator$le(*this, rhs); } -bool ImageRegionRectangle::operator>(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator>(ImageRegionRectangle const &rhs) const noexcept { return !(*this <= rhs); } -bool ImageRegionRectangle::operator>=(const ImageRegionRectangle &rhs) const noexcept { +bool ImageRegionRectangle::operator>=(ImageRegionRectangle const &rhs) const noexcept { return !(*this < rhs); } -bool PixelF32x4::operator==(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator==(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$eq(*this, rhs); } -bool PixelF32x4::operator!=(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator!=(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$ne(*this, rhs); } -bool PixelF32x4::operator<(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator<(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$lt(*this, rhs); } -bool PixelF32x4::operator<=(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator<=(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$le(*this, rhs); } -bool PixelF32x4::operator>(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator>(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$gt(*this, rhs); } -bool PixelF32x4::operator>=(const PixelF32x4 &rhs) const noexcept { +bool PixelF32x4::operator>=(PixelF32x4 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF32x4$operator$ge(*this, rhs); } -bool PixelF64x2::operator==(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator==(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$eq(*this, rhs); } -bool PixelF64x2::operator!=(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator!=(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$ne(*this, rhs); } -bool PixelF64x2::operator<(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator<(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$lt(*this, rhs); } -bool PixelF64x2::operator<=(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator<=(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$le(*this, rhs); } -bool PixelF64x2::operator>(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator>(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$gt(*this, rhs); } -bool PixelF64x2::operator>=(const PixelF64x2 &rhs) const noexcept { +bool PixelF64x2::operator>=(PixelF64x2 const &rhs) const noexcept { return mmimage$cxxbridge1$PixelF64x2$operator$ge(*this, rhs); } @@ -1701,12 +1702,12 @@ MMIMAGE_API_EXPORT ::std::size_t ShimImagePixelBuffer::element_count() const noe return mmimage$cxxbridge1$ShimImagePixelBuffer$element_count(*this); } -MMIMAGE_API_EXPORT ::rust::Slice ShimImagePixelBuffer::as_slice_f32x4() const noexcept { - return ::rust::impl<::rust::Slice>::slice(mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4(*this)); +MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 const> ShimImagePixelBuffer::as_slice_f32x4() const noexcept { + return ::rust::impl<::rust::Slice<::mmimage::PixelF32x4 const>>::slice(mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4(*this)); } -MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4> ShimImagePixelBuffer::as_slice_f32x4_mut() noexcept { - return ::rust::impl<::rust::Slice<::mmimage::PixelF32x4>>::slice(mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4_mut(*this)); +MMIMAGE_API_EXPORT ::rust::Slice<::mmimage::PixelF32x4 > ShimImagePixelBuffer::as_slice_f32x4_mut() noexcept { + return ::rust::impl<::rust::Slice<::mmimage::PixelF32x4 >>::slice(mmimage$cxxbridge1$ShimImagePixelBuffer$as_slice_f32x4_mut(*this)); } MMIMAGE_API_EXPORT void ShimImagePixelBuffer::resize(::mmimage::BufferDataType data_type, ::std::size_t image_width, ::std::size_t image_height, ::std::size_t num_channels) noexcept { @@ -1879,15 +1880,15 @@ MMIMAGE_API_EXPORT ::rust::Box<::mmimage::ShimImageMetaData> shim_create_image_m return ::rust::Box<::mmimage::ShimImageMetaData>::from_raw(mmimage$cxxbridge1$shim_create_image_meta_data_box()); } -MMIMAGE_API_EXPORT bool shim_image_read_pixels_exr_f32x4(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept { - return mmimage$cxxbridge1$shim_image_read_pixels_exr_f32x4(file_path, out_meta_data, out_pixel_buffer); +MMIMAGE_API_EXPORT bool shim_image_read_pixels_exr_f32x4(::rust::Str file_path, bool vertical_flip, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> &out_pixel_buffer) noexcept { + return mmimage$cxxbridge1$shim_image_read_pixels_exr_f32x4(file_path, vertical_flip, out_meta_data, out_pixel_buffer); } MMIMAGE_API_EXPORT bool shim_image_read_metadata_exr(::rust::Str file_path, ::rust::Box<::mmimage::ShimImageMetaData> &out_meta_data) noexcept { return mmimage$cxxbridge1$shim_image_read_metadata_exr(file_path, out_meta_data); } -MMIMAGE_API_EXPORT bool shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, const ::rust::Box<::mmimage::ShimImageMetaData> &in_meta_data, const ::rust::Box<::mmimage::ShimImagePixelBuffer> &in_pixel_buffer) noexcept { +MMIMAGE_API_EXPORT bool shim_image_write_pixels_exr_f32x4(::rust::Str file_path, ::mmimage::ImageExrEncoder exr_encoder, ::rust::Box<::mmimage::ShimImageMetaData> const &in_meta_data, ::rust::Box<::mmimage::ShimImagePixelBuffer> const &in_pixel_buffer) noexcept { return mmimage$cxxbridge1$shim_image_write_pixels_exr_f32x4(file_path, exr_encoder, in_meta_data, in_pixel_buffer); } } // namespace mmimage diff --git a/lib/cppbind/mmimage/src/cxxbridge.rs b/lib/cppbind/mmimage/src/cxxbridge.rs index 1e4cdd179..b7731600d 100644 --- a/lib/cppbind/mmimage/src/cxxbridge.rs +++ b/lib/cppbind/mmimage/src/cxxbridge.rs @@ -281,6 +281,7 @@ pub mod ffi { extern "Rust" { fn shim_image_read_pixels_exr_f32x4( file_path: &str, + vertical_flip: bool, out_meta_data: &mut Box, out_pixel_buffer: &mut Box, ) -> bool;

    ZB!VM)f*L{abGU@fUp`T{w<^MKagpRg}OS3&Z7ii<}xKaOVYJS&1hO$i=F5RfWD3gf|x|Oj;U9w2l#t8B(QYD(Xm11dsA$9Ak6Y`Xr zLhxPDAQ)0i^=cHEu&=*z@c$?3-C-u;E|tHycSXb)Fh*3tBQN~Jax<;`#t+5}{}OvU z$&NjGN+$`TLC`J>#vCes_bxYMKhV!?Dfv-y#UE`@M9kKmJIgY^^^+Lkbh?Iw-?lNt zQ$bI57om+*XSCEOW-fpA<0F>k!eo@yEVFz%^iXLEAfvydl4oD>&UigHn}V!*0FR znqPHAk*w!2U2at;di145`TXvMScF?_xXDk{?#5GJs8YHo7!waDc1}sXuGwln=Jhl& zS<6aOL9$p8Qg;}L3y?kgD*hlrI7vk+DQ4p1@%%JzBm5RcTc61W700}7mEw~V?;W04 zF;E;Y({DK7^B`Dxtw2RL99E*-uq!kXHK4Z>tH88{^c=xrb4(MY{wsMGBB|yK%sXvj zLGj*5L_9@J9nk$|sCgacSr97^PJ`y1;rR6 zE*DFEkj*1C`NEL9PMi316on)&5xAC~x1t?JofS@e#wkcYSBEi2(fP`yN7Yt0|jO3~%-tkQJNvLTpptw@n*=G294QVgcSxD&xto<|W znRPkdW(A(vR*2<$khOnaqZGyAJ|XxgYN*6qQ?Yr!3DJA&`1WB8Daj^=Po*^> zgmM`V-fpQoP4gjkLd$CHJhzyL5^#953ZEWSZl9m?5O{1eh%4v&a2R{J`=KCWD~N_- z)qgMdm8CN>~_pKFEJYOi&Z+jv5?-MFLC%3b9)D4?mlTb9DE!1w#*;m#bG-0S!irKfGJ z=eqJc8;8mHFTRu))N|}$;^^U+=vZduQ^80k7e%|HCeODkcabZZUBRj%D^mI9wiFvB xq}boRj(WZB@U8wPxkNra|DSGRYV#yPrOaSX%;Lux^0_~ifv$;88OR~>KLCi9QSkr( literal 5284 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 diff --git a/docs/source/images/tools_renderer_menu_silhouette.png b/docs/source/images/tools_renderer_menu_silhouette.png new file mode 100644 index 0000000000000000000000000000000000000000..6a289f832d71a38c771f8d8e18b5716005f3988d GIT binary patch literal 6326 zcmai3c{r5szqTY>Wf@!9B1%5$eMktl&HSSK7_2p*rKv# z$zU4$Zmh%1;LKFt^SjP-ZIuX$##Z~j*jl6o-V|U zj*h;GdhN<`ochgsVkD6IL+@>-1EwqO5n7}UjyY-?Y0}Z5lGrKsOw=)}r>>PZ9UZq1 z?L$BLNHl4YuF8zV+_*(_j)gD2F1we(+Qd)oG&Ar zuZUFY0N*-Mgvxq$`Aw<8`N&0j7E2`HF0@i(P)7eve zx_ARHzq1?25lQ9F6Tce-nA^* zXxsg`au?}Vq|t+no#-W5GNf!~TpwC@>He6OX=B5AN^x5(bU{|J=w1qZqJ=Lk(_E4jT0kXbZ%?(sGCD`ed< z3n_&?H4M%ZCQezX6D0Z4`Cjgr^jq1thB47khRfQ^?$}Iu9~XaK{R-rL*@Zpi@|lg* zfC9dwxyv?`9u2kBi7Uf<(X6HK<))i>zn5^W*&e-~=|vUPWbfbYWft>-pgXgy@w2Jg zhb=;*c)ZE-LF>ic*manh90weDVMs>`63Y9+NW#}W7}M;2D|b1Kn6j~m%IqALA80yR zY8tXRGFB)9^sqv-ey+PWr}EKjxIR~l_}!!35tVK-@!QushLCI#jWqs@)>xKz*17ZJ z8}YEL*i;s?a($$UElUXYY^asG>}!-CAJx+?kjkN&0U|JP1pQT%jCv6_6jLAGD=4Ey?rj^9Uym zRC5krkpZ1p^~|9X?6EgCR-b*|T5G-B25_71!bojB{?tLH7u{=PGQ9=W!};YOjrKeanWLE z9@POd4EU}HRj*NM-C1-GhOgqL51XnOn^+V(4)W%iC%# z>cpVfEhFY{+{kOD-0yj5m&AC9Rqg&zwDq@9)+l*s#8PEayjRETng%rI$-Q^6IyD(& zqlYw@z$QM;~qKEY7{4#BdFB8TydaxAZs%1^yMhF{KUo zWCcXsu2WhL%7rvwAU8{je3Fse zc{ZOo=2Eu*8AKsyrSClxhu7g8nxSNU^&jyu?IOz+Yl7WRF0<2e9KK(@G3r$_+jgO5 z=T%_MY_n#K{gzvkQ7<<6E{0iJbHv4SsxTwNw&kI4dbp*G%;;0S9tJ-X;Ql-EA$i@~ zH!Y6WjIX}ChQ(D6QEJT=M5ElV41kR&{`(7{UiCToBx(PIeZT3vx9&Dlp*6XO@h?Nj@A%Pm z`#K*a;ha^yprdi=SaBeEMbX2essem^heKmK+2q47e6(m!J2$APC$8$#rx|o0Vtukv z#Pul8nB0Xb9YldIiRN{WFsKZZ8!1rRZCI2E@Jqgh-vEfxel^R5^`VPOrg*>B=^bqj z;SWKfp8^(vokA|QvC%Bq6#;pla_L96+AR+`h+?&=y`B*LaTuSE< zR+|}?j)+I1R~O>lvECYp1hr4?9c(B#@}e#Ko-5+$j`21O$&t^DPa$sG$^BBBUY`RU z_J0rEPQivAHXOw(+()LfW|njJGn)4%@yTtQ=e|8L>P+>Sq{oIEZtdM!{}*AT7M+Qc zy+U4swxARi+`RkR%7{C;D=EZy#M|OSHwe=f)mu1-dbq>Tg5BmgT2NjkNvqTlo|my7 z6heM@?28RA%8jqM98LS>?00{EwLkD(1Tiv_cTxtOZ7KGcADc{b_;CxM5QeYIx#opg z>8$JpE-OnH&934?mmj5)>r}0@r{%gi(S;c}eQl0A8lbxkGcTq*zM*zA0zTxvBiO%} zCLRZPNm{GW5HOu2k1Hi*>5T6%pQ?Y>iCbnD-3+?sA5};>S6oLBJN?#Orvhn{%QEux z-AYng-8r4a08!VX?o8ooBhdZ~K#>pf1I?|AJY^jt=#E=ccFi?N#2GG4Zn^FGk@oz~LLC}VW0GoCj$#?@!!>nIs6w#Va4a)HZ7QQ3q&u37tZU%^OK4@ zZ}}F7NQx}IYbz{i&) z9{Qf=ZhDTmJ~|SaOik7gZlFY$3FyG*ysA^*sp~%Ft`1QZ#*`b~c_nW^{U4T1EhORM zJYdse&TXaxv?o3y_;7z-<63pco#XBcw@oxM1XZ#_X5Ef19ybVSYl~+pHf}v+vqyWa zkW)(bDEe}zgNNcc4lNzXTR5+R;LE7?{i=oViF$u>uvOJ&5sgJ%SKD#f4_0M1Zh`dh z%ZSgj+x%30J=4bB^mD|dc<=VBFOF464pvS*rL40IknJ`Rn!xC9c9Mf%t~C|d-h5eI z!oUcw*tLG8Cyw(Z=P+Ymk1iounmaa27(z77;B(I%NzWjnYBS|cE7y@k1PVV}4s64;gt+TNN9B!EIdbBAzcF7&Iz zNbL{N8dH;Aox*Eh<BVBcfiS=IDtz!~ATw!iU2NgQqj|Z#e zM@8KOoCj+8$w;46R7w&ddO-oqXvVUgf9XQBf?=oCe)EYrK*;;(Ov zabfF!8MvWcb5OrNUhO~WRaszCzlxKP>6(#)aO8V5>SNf`YiBaj?|EAVh7wws-RFB= z-CuyLH0e#XCseKM_fJJxD$HH{gBR_#V>YE~!y#H$Jr0`qbMI6saqERe>MG-&kw<)4 zp7@8R?`^+iJ!7@y0&eg%bUM0yHxHLMxe7_un9Gd(I?(Kj?u1%9hc}=Y!0_8r{S$rB zS+=37SrGY~l)*#)Sj$CHL2l?6dJHwigYQiu?s9KLE<96!28wbXg#YRi@LFB3ofjdl z)vwoipbv%-yyJ`<>YTnNuqY2nUG;-+KQxKookt8|?T$tPVBL#wi~E7^>(}a*Db5^Q z?-emBvcK}Jcit4AjA60vQj&OdK{R5km7n2JthWs(JB-C%d!^8e&4|r5?baTPQ(_0T z2ha-_O{qe<@(>UEj+A>;CEcvNT_e@NA8~`uP~MeE5>w;RBD+n#_po4@SQ|B4otT!uK=n4! zj#U)c28+#Ma@9h(USDfud$$Cj_|b#x1)(fvKf9XNgfsDKUO??5uQz zYF<~H0e*E?_VcV^TToZGYhG4*1q zT$wqK(_@o4N%=V}V#iRTGTXB8f>>ok_Z=rL7U3GTdipsJFyp73=dblb^y=Tkn{xmhlpO)Y0<8jW`XJ*_sy zFjq949AlRM;`2LR9#+w2=Tp|tIaT2?OuqzthxJX$rQnJu*agI<86x=NXE~^?`=>Ak zd{O*W{7dSI!5FQ};Omp6dNM)7G(D|@j-+Ahw@Ok+cj>4z=4c}Z>RKgc+WZ?dnA0rL z(V9?N7)td_4PM-ZdH_*^9CTGXjf$mRSRBS#7(K6u51pT&j3E%2mYPfxD z)KUiIf&nP3rnA1FE&Pf?a#%ml?6dj}IrtHK{xe%;UG$z+lk=tF%Qr$U_yC7LqcN^R z^6{{T4=heJg#`;4P6ZVUQ=RcSX0Qi?1~dFdA4h5(iHXsKqzKW%SU442L2S?)iuWg- zdt{pMW->l7Kp2XVDD51hotaQU&LIjzL3$Xf^UzoEl8aN)kxxK_X%fWQD5i|$2dg}_ zI5dD$+U9DwQf5p+DF7?6hOryUKDr>r>*A!hlc)arDDd65$Dx(Hk`;)wbAH)tQJ_M+ z2Yz!cIIv-=DeI@NAPrS{PIz`iNMn|SvF(V-7nEP%jQ{4Qb!7Xl)5{pPL~}C^=v^(4 zolA#MVgb{6V8@$DlGua~oYd*IOl-V#f0QUtUrLh~8m4PYdX$`Y0T-aoK>KB+sEcEY z)bx}s)P&FlvThWZ7ix=jG7wOGZ9criEt7|?QyAkmlfM{mGsGt-*HKEQ)_IX7lVG&g zDr=Sh4S7Q51zzHeiv^_a;PIH&J44eG(>JS!;v6$df;1A#jc)S=|9#u`=l@HNF3R;t z*K67;Ezu74hi^oPa%Mm9XINyGeu3qn_zBJcy z{NajdbK4~V=u=C_W|b7>R=!~>;q3FJYRl_U{47<%1=TDlo47iqHHesWvKt!xGHhE3?1aDuBbT34|17xl;Q#ZL$6hgRL~rxO;)8& zmH}Xnhm%)pa20aWB4KJ=pwXv?kHD(C7W3%7{Q~rR;Eipi1Rp6#X#XL#it9hprb49A)e4lma#GCW=A-YIkn&Zh75c7c^4YYA1?nbWz%a zOWeL+mJ$~vt1s^t2LRR0cDI$J1*gRV)h%9mGXw9>v@Tb2zi1GV?c|jC50k<_*t-mJ zYY?b?Sb*Sf|GH9_2TaiQv+VJaB#Br#+dWgsNm{*u1}r{cl|<^65L@fL?{~Tgn44&N zr-wp`Gw*CsJ(N5DRaFZVTQL1}zJI!kqL`a4f4E)Ag-eVkKiMABTNfUjEzD$E{5@lb3GO+c)*ZI}R zMgD!KO<-tot;=nnNv;lotBDN(9b-nkPc2z#$cm7Jr(u^2(;k5AVejVvmBuU76E>%T zs;I(+fE>4dm-IOBbCc(}Qss+fr3ux@r7dVxdq+I#rNRjm_g_hpBH}YIi9G^8&G_nB zkx(VWP{bj_$}enE_`M?bg7srTjV3C`{9c=mT@oz1XAY7SfJy!(=pw=~tl8c-y2L2# z0hh*T+Yyy@F^j{anB3G#O&w^Wkx2STt$fWM}}{|o;V^EeewQe*j_ z6qE&uAO4EVa3b!Td|3FA#}8+fHV9SHOsk z|Nha4b&dP0`o(`EoK6}p+5M_!}7UteX8SN7OUG-zo`Z;Q15Yqhk2+EU=ed+qxykNzRDDp i#0ma#W{RG8^j`g$Z1WuRd+L7;bb8vxkW#Q+#D4*D1X~ge-}#;Ey3Y0eUe|TbB;7I5`-|}+BNY|ZUk3U* z=2TSFX!37wz*+J)aiC3)+)xLY>w&1M2l$rB9a;}&qZ7&4)Wl6i89$cBtS)!7%LQe5hw(Wp3~E(lX>DPJwjl(&%_P{NjWF!*de-2 z_3d5~E|^mricTae!ax(CdICSVdDiMY?H;|=RQz!IBr>GE${y@T|8VkA)QNr5Rp;Tr z{StwQtofFo+^ZT^Uh0SlsTNL`$wwIqE`J}?Y3Ascg)f+#J1X#>YjU^d!kj)=Bfc3{ z;)mliVvkCCG}6NAPzNvSvk1=wS-woT*ac&?GzyaUVoU4 zv;DF(SlO@U>lmrGKi(pbuHC7Mwmq*U(-9V9m&k4)l&Jq4U9o<51+PL^ zVl+8Wq^jV_6fwYyUk{P6I$gZ-;&edcZm$oY7ipe$^joeUZhO%x{^&-V#CX8kD*SwS z)lzJxvQ)%kuFbML>#hACeB&-OWqT*l97mX7#fsbn&F6V5B3Z(%7x|6E1AW52wfo#H zUd}prf?q~(_KaNnhDxh8i>Mu)c-If~HH++OYO(+4MwQ=4Ycb^Dvu{7M>*eB>!D2Di z%M~vZl{FqpNz0f@fHHkzi~RHkQ>L}@bh2ZHg~K1a{+zspPno+@V9s;|HEl5Fg(k!o34!WjAzSt^Y2JcLLOC5dNN^{$jp+a95Zz!nx!9BZF zaNxR`&7?kAlC-q+S348@T^97GeZv86d8n$R_F5;mmyo&#m}~N&#vTc^o&QdjR`vq9 zp0q7m1T+*ir_m1GQ40=giqB8}K~RD`0n=&bLkGt~5WlQs*zBRQ4-4?I49R&6UW%&n za|8)j$iViITQLbklOIR01SS0fOzO7n#}^j9DeT5dd?_DJ%nf7Gt@q=(6oNoLsSPiQ zYxAxz5KFQfa&c!g?{9yFVqq3M9#uz|-SJGsYtc)CtV!8^+P&c$OV~R^u~M)oEZUS1 zpIaWnv38D3J~qcl2cWWVyvNlroLI0YFTZsW1L&QF14&=DrteE-<{M99+p6Ngn;M)#hHy-c#$%i9Q}F@FH&4RU9zAM$A7#BsAvxy zbRrYZS)?WbE?{JsD<>LubV0Td5FNk&s(kCzeyIZvXbnS3j16|fZ|Zo zDYTAN`%;+hi6%dYP>!kPCC5Nl?X54T2e?Qa+QgPhDEzq+bo;n&;${ImdQ0=J?!o83 zN6GQ=e)>p?;MwNToqG!70R6TbxbU4X0&OS6RY+x-K{sRc=Hbqg@t1pt7#crqG*Sar z<1drQud__R&!gl*beoMnL9}r!o6oEr{g{EI2ia8J$;DGy;QIHNo5!P0kq%6R6;uh| zjNM<@jLgOM@Ydxw&!-;$#Y>vfNlzb-E>OYWly9^G>l2;A@$vcIaS9#-=X6)o)ixfy zH7(xXbJ;F%gJ+gvz>9XY)%3)=t+#Kds|QgPd`zReuX7#ycIg-7uVrY*Z8nTL?)XI# zek4dz!-ZijINf%uquc7=Q!=pvtF;@veg0RN$UgWSeb{&y|H@|)!RB+MCharXanomL z^&1L#H(yIucE97hOIC^M|iK zgtPSPN$xbXf8K-1p%vLtV0@Mw@+U{pF7x}QMSt07@15?a65*1sG&eiWxDRGAIGU0AHI zC5e3J1RAiqw^NjJBmR7q(NcA=7Hk-K@;!E5PaxDyCg+M_VS4 zice1+e`Km^J+_+>5gsS+#6(yNKh?QKaK$ip(&L{D5}f5`Rp$d&1y6r+0*N2j)x$>6 zrKvZBbxXzIx&*E9omX_{B8HbFX&NW2=SpA&bI9XmkA#C?9CAiEgyL>)Oh_=dets`# zh}jH4J|ub}cL+V2Z&#b8vlj$QF5v4{9?IBYwRRgx^T1}=u#<7wK;TYbiBosV%K&E^|C(0FS;e?ul z4$ew%oD0)lmZ|17h_?Q*T8z$GO<~h=PNC1x!0UeqpUXBhaF2auSJubcz(St6*W$7d zZQJ!yitcb$dM*S{LXn5hW;*KzA0c97yOZTtc4lOw4FH0Y6ug!UK04>#L)loA0X{3q z0FZwrbPl-_n-SqS^hYc2FkzupT9KC~teF78H9TWx_QK8;>IMGOOsjd4Bql*S9789V5ZFGjIUxOXUd~;j+Fe4lpO=3$D}*H@lz0 zd==`A`^pE|fcDJwKc2sMWG@{4GZrKLYVGhV&122g9S&+c3QB>-`1wW&q1Bs31X`4-LbKrLq>}{o(;jW?Hd15?&oQ9 zz|vH}pvF=MLh+680}H`O68=+D$DFk9QLqAdUqpQ7s`Imp`__P?Gzc!Eqi^s%;VB}w ziZq)s+z33aLN5}lh62*M!OS2uZU2gXeGm<0xU;N76?`PeTOzQZ7`9r4 zY>`8gzA?SxxM{IZ@}%wp%RcE>8$q!$@3GXXwp$+<7x%|da+5Ra_!vDkUww$Y=`;T% zcPeM$%*Gp02_oP7#i}1T2lM~z1PeaE=-0Sa8a<9p?wdKNo zh_TE_y_6$>l2BX9w;YUs2u3?w+ihWC22O}QVaUbl6yGCw^oWZ0JJv^Wt^h>fbPWHx zPTEjI&KCwx&GlyaEZyi$mJf~J(k4^|8-`@nX1Be1>UBl>3$rGneSPM`WJ)1m=7vFA z9)eQ?Xxq+W*lmB-n?kt{fc%&J?maEOlKssxzxbsIulIZCeby^QGJU&D zhqY$-{w@jJi$M|`+9ID>E)>NgerQ228u0yDm2I-t_RYxpD8jInd4dv@Hzpd$}>0lD1WdTVZcY2x0M5eKI*r^_Jp=mIjlEi3^jqy5<>5`iX zq`u7#9j%2?Ke%#%)SNXP1<=ewm1n{Oad*z^TF3S_c?=4G^v{q3Sz!# zJD3Uc{jQP3fo`~j-&L@npT1G)ppf>gSaF#_sDj`CJ6zaUseHd*srf0_DhbyX#pF92 zTdH&kO7VuC13u-~$?R80WG-RQA0UKfPZHE*qEtiEilg@P)h#Ph%;{zcO8xZUtRj}D z$%5^A+JrqR62q9r!4zcR^E=8xPpgRF;t%?HvXubLd-<1A&U>=b`N3OgAOu08CYK*P z9jrk3N^+cOR9XW~y|{+Zj>(hq`(&}7sB;J|c0;UcOk%dH!=!#U{-bJ%QH@OV8Q!aHWa7U<0@Z%rwrF60ofa3bxmH4a=^ z5~D6BDriR&WP=@o$ClL99MEem$2R;D6*OdBI9-wiQDd$Qg>>FGBfY-8jd^Ju+`^X( z=o`0Wi}SDVV_Zw95onXNKXv*Sl+h|k0Ps9r;SvjR!3QmR zKA)a!Lw({RMh?8}PAe>ZPo(upla|)w{Ul#t1(!At!@Sm1_Oz~86A!iSbDY1f-O3Ht za_>n0UJUm&@t}Of)6jS_#Zzi9zEYD#T1i^B01B=OELY=^b{j1xS|3$=8LK7@`W!=c zSESjiuKE-<^7+Rx-yuyf{I&yk9kumT81yXB@Ro9NoIW7J%ml+ys+`I}dbm_GXRSvwuiG_wxK(r7zEAG!E9T&ieC|Rbw2pJ}3+%bv<|9Xl2v9mGjuT`HA zhEGI-r7KwEng2Z@bS|Nnp`;EraqbtK^X~%k>Cbhr!kzqkyMAj&Ye=ej$M$0q7162| zu$X$ZH|nIJ><9*PGrnw~xt_%+v=b!+2K^_>+2+nZIa){JOb%aiGiMu1(D`@ICLs@oHmf6j0`C@! z5>A>0EzQ##EJE;1x-@>bj*THnQ7ftl z!LCQydrc3|+3+xjOC5Yrc7)+l)@;c;@{56A>j{AwV(+DFf}Z}fy!zNcW#o!si`YdU zw#CV}P+=KmlWwsj4X|Gz%sggp5}r4YuLodzxQCCfxmQQ0GM7pR?HxI?%dLf_I!OPo zAr4SHf4ttU4b&bFUXG zi@D3*3YS%KOm#5)&vm#Q&-VIXx(6OlmG}m3RsU9{KO_KhXM}LysI9V6uk;>X^^1cR zl#W?W$naL$TLG82W5oVMp=DrT)JKBPw0~7YCr2{xngK!4ip`xZI{+K3p+z1v7Gax##Ns?BlsSY>l_F6OX`uk z=0Sg|MI@>`!u3d?exbY4dGst9!N1~_I{plJ*u{^qkD|C?ME7l4p&Y#<8J&i$L6|Anx3L}3l5AE)zn<5(#2 z$TcM;hro-K&UnVAEqEC}0xj~d=Gt%9{J&BNuI}wTDMRZ(6$0&_Mzw|@He32F*JxBf zbTG@E;jdg$ojKdnqA4+eVHmVrn%a7Tc)hEdq4Oj$$Gla{?VMofNS2U*V)eovd<@NCFa$@Mi|t#hq}{PT<+23vSHKg-t> zQ~N?;H!DQdCqzKQu|IN_O`+RyFwwWCE24R9vt{9S(CyoG+2b>gqg$!pIe7I0&xuB3QjjsbBn!cL{Wt z(5YnnHS}G*jgnO}9Cq?64anf14-~8ZEX(uB+6!}+t`}}qACZD}=gdA3;{ll7YrP8P z#cfDyTBC}^w|c|7ZI;@iO{@Gc6AkqLCWnn^)Nim55oLLzQ)YADp@<+xxwC&W`83Jq zaotFTJQH(#pee`hUo@K{oDXvYI^8Tx6_HswOamNog>QHeb5qTZbkZPg%wL0KDaBCA zM>b%TC4Zx$&_jYNQ~R|QI7hMbFDxe7%H#maQBz)oJ;})dZeR+aBo+k<^UqTNOxelB zU(f$N6k~J?q#r=;=4)RS@ zi`wP@=9|$S~T@#&ZkaNuc0E2;%)&Kwi literal 0 HcmV?d00001 diff --git a/docs/source/images/tools_renderer_silhouette_viewport.png b/docs/source/images/tools_renderer_silhouette_viewport.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7eb431f67a6ec80a7a88c751209776cf87c51c GIT binary patch literal 146161 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 {953r>1D5|MJ!n^3#BfwG?is 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 z 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 Vo!=EfRqg-)002ovPDHLkV1lB{cqsq? literal 0 HcmV?d00001 diff --git a/docs/source/tools_displaytools.rst b/docs/source/tools_displaytools.rst index 706e34185..e4e8ada91 100644 --- a/docs/source/tools_displaytools.rst +++ b/docs/source/tools_displaytools.rst @@ -134,7 +134,7 @@ Set Mesh Hold-Outs This tool is used to force mesh nodes to be rendered as hold-out in the viewport, or not. -This is similar to assigning a UseBackground shader, to geometry, +This is similar to assigning a `useBackground` shader, to geometry, however this tool avoids the need to create a shader, and manage assignments. diff --git a/docs/source/tools_renderer.rst b/docs/source/tools_renderer.rst index bdfc6fae8..efce8b9b8 100644 --- a/docs/source/tools_renderer.rst +++ b/docs/source/tools_renderer.rst @@ -1,37 +1,165 @@ .. _renderer-ref: -MM Renderer -=========== +Viewport Renderers +================== -`MM Renderer` is a Viewport 2.0 renderer designed to add helpful -features for use with MatchMove workflows and reviews. - -This tool is currently only in beta, and is not enabled in stable `MM -Solver` releases. +`MM Solver` comes with specialised Viewport 2.0 renderers designed to +add helpful features for use with MatchMove workflows and reviews. .. figure:: images/tools_renderer_menu.png :alt: Viewport Renderer menu :align: center :scale: 80% - Enable `MM Renderer` in the "Renderer" menu on each Viewport. + Viewport 2.0 "Renderer" menu. + +Currently the following Viewport 2.0 renderers are available. + +.. list-table:: MM Solver Renderers + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - :ref:`MM Standard Renderer ` + - Identical to default "Viewport 2.0", but correctly works with + `MM ImagePlane` nodes in from hold-outs, either using Hold Out + attributes or `Use Background` shaders. + + * - :ref:`MM Silhouette Renderer ` + - Identical to `MM Standard Renderer`, but adds a silhouette + outline edge to all rendered meshes in the viewport. + +.. _renderer-standard-ref: + +MM Standard Renderer +==================== + +`MM Standard Renderer` is a Viewport 2.0 renderer designed to add +helpful features for use with MatchMove workflows and reviews. + +`MM Standard Renderer` is different from regular Viewport 2.0 because +it can display `MM Image Plane` nodes with native `useBackground` +shaders (or Hold-Out geometry). + +Usage +~~~~~ + +1) To use `MM Standard Renderer`, load the ``mmSolver`` plug-in (for +example open the Solver UI to ensure the plug-in is loaded), then use +the Viewport's "Renderer" menu to switch to ``MM Standard Renderer``. + +.. figure:: images/tools_renderer_menu_standard.png + :alt: Viewport Renderer menu for `MM Standard Renderer`. + :align: center + :scale: 80% + + Enable `MM Standard Renderer` in the Viewport "Renderer" menu. + +2) Use the Maya viewport and playblast as normal. + +.. _renderer-silhouette-ref: + +MM Silhouette Renderer +====================== + +The `MM Silhouette Renderer` is used to add silhouette outline edge to +rendered geometry, allowing the artist to clearly see the edges of a +mesh against an image sequence. + +This rendering effect is particularly effective when the geometry +contains a Hold-Out effect using a `useBackground` shader or hold-out +attributes (see :ref:`Set Mesh Hold-Out ` +tool). + +.. figure:: images/tools_renderer_silhouette_viewport.png + :alt: Example green silhouette effect on shaded objects. + :align: center + :scale: 80% + + Example green silhouette effect on shaded objects. + +.. note:: `MM Silhouette Renderer` requires the use of the OpenGL + graphics API backend; DirectX is not supported. + +Usage +~~~~~ + +1) To use `MM Silhouette Renderer`, load the ``mmSolver`` plug-in (for +example open the Solver UI to ensure the plug-in is loaded), then use +the Viewport's "Renderer" menu to switch to ``MM Silhouette +Renderer``. + +.. figure:: images/tools_renderer_menu_silhouette.png + :alt: Viewport Renderer menu for `MM Silhouette Renderer`. + :align: center + :scale: 80% + + Enable `MM Silhouette Renderer` in the Viewport "Renderer" menu. + +2) Click viewport panel menu "Renderer > MM Silhouette Renderer + [Option Box]" to open the :ref:`renderer settings + ` in the Attribute Editor. + +3) Adjust Silhouette settings as needed for desired effect. + +4) Use the Maya viewport and playblast as normal. + +.. _renderer-silhouette-settings-ref: + +Settings +~~~~~~~~ + +The settings for the `MM Silhouette Renderer` affect all open viewport +panels using the same renderer and allow adjusting the silhouette +effect, including the color and opacity. + +.. figure:: images/tools_renderer_globals_silhouette.png + :alt: The global settings for the `MM Silhouette Renderer` in the + Attribute Editor. + :align: center + :scale: 80% + + The global settings for the `MM Silhouette Renderer` in the + Attribute Editor. + + +.. list-table:: MM Silhouette Renderer Settings + :widths: auto + :header-rows: 1 + + * - Name + - Description + + * - Depth Offset + - The separation between the invisible solid geometry and the + wireframe mesh. Adjust to lower values if small-mesh artifacts + are visible. Set to ``0.0`` to create a shaded wireframe + effect. + + * - Width + - The width of the silhouette lines. -`MM Renderer` is different from regular Viewport 2.0 because it has -the following features: + * - Override Color + - When enabled, all objects will use the silhouette color + below. When disabled, the wireframe color of the object is used + for each object's lines. -- Display of MM Image Plane nodes with native useBackground shaders. + * - Color + - The override color for silhouette lines. -Getting Started -~~~~~~~~~~~~~~~ + * - Alpha + - The global opacity of the silhouette lines. -To use `MM Renderer`, simply load the ``mmSolver`` plug-in (for -example open the Solver UI to ensure the plug-in is loaded), then -use the Viewport's "Renderer" menu to switch to ``MM Renderer``. + * - Cull Face + - Backface culling for solid invisible mesh surfaces. For meshes + with inverted normals values other than ``Back``; Options are + ``Back``, ``Front`` or ``FrontAndBack``. -Known Issues -~~~~~~~~~~~~ + * - (debug) Enable + - Toggle the silhouette effect on/off. -`MM Renderer` is *beta* software and is only released in ``beta`` -versions of `MM Solver`. There are numerous bugs and issues that are -not yet resolved and the viewport renderer is available as a preview -only. + * - (debug) Operation Number + - Used internally to draw only the first N number of rendering + operations inside the renderer. From 38435db3ec49c6b5dc55828e07944247a5423be1 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 11 Oct 2024 22:33:40 +1030 Subject: [PATCH 276/295] Docs - Fix renderer heading style Need to make these renderers "heading 2", not "heading 1", because it's affecting the way the text is displayed to users in the navigation bar. --- docs/source/tools_renderer.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/tools_renderer.rst b/docs/source/tools_renderer.rst index efce8b9b8..21162e6a8 100644 --- a/docs/source/tools_renderer.rst +++ b/docs/source/tools_renderer.rst @@ -34,7 +34,7 @@ Currently the following Viewport 2.0 renderers are available. .. _renderer-standard-ref: MM Standard Renderer -==================== +-------------------- `MM Standard Renderer` is a Viewport 2.0 renderer designed to add helpful features for use with MatchMove workflows and reviews. @@ -62,7 +62,7 @@ the Viewport's "Renderer" menu to switch to ``MM Standard Renderer``. .. _renderer-silhouette-ref: MM Silhouette Renderer -====================== +---------------------- The `MM Silhouette Renderer` is used to add silhouette outline edge to rendered geometry, allowing the artist to clearly see the edges of a From c041047326df3a768af4d8485fc97a0049ad2145 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Fri, 11 Oct 2024 22:58:49 +1030 Subject: [PATCH 277/295] Docs - Add ImagePlane link to "Create ImagePlane" tool --- docs/source/tools_createnode.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index c4d9503b4..fade84f49 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -121,8 +121,9 @@ Run this Python command: Create ImagePlane ----------------- -Create a MM Solver ImagePlane node, with the chosen image file -(sequence). +Create a :ref:`MM ImagePlane ` node, with the chosen +image file (sequence) used to display a flat plane with an image +texture in the Maya 3D scene. .. note:: The image plane supports any image format supported by Maya's ``file`` node, but can be buggy when reading image @@ -145,7 +146,7 @@ Usage: ``file.#.ext``), it will be detected and the full image sequence will be loaded. -Run this Python command: +To create an image plane, you can run this Python command: .. code:: python From 8d56627b498602f0ee9d819eea98ba99107cf156 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 00:00:30 +1030 Subject: [PATCH 278/295] Docs - Update ImageCache Preferences More detail is added. --- .../tools_image_cache_preferences_monitor.png | Bin 0 -> 11304 bytes docs/source/tools_generaltools.rst | 114 +++++++++++++++++- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 docs/source/images/tools_image_cache_preferences_monitor.png diff --git a/docs/source/images/tools_image_cache_preferences_monitor.png b/docs/source/images/tools_image_cache_preferences_monitor.png new file mode 100644 index 0000000000000000000000000000000000000000..bf5bea1798da2de7a14c33cd6169ace2ea5dd48e GIT binary patch literal 11304 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 uses the Image Cache to +store all image data. + +The `Image Cache Preferences` have *defaults* and *scene override* +options. The *default* options are used when Maya is started, and new +Maya scenes are created. The *scene override* options are used when +the current Maya scene is opened; these only apply to the current Maya +scene and are useful when you wish to adjust a specific scene with +exceptional requirements without adjusting your regular *default* +options. + +.. note:: The `Image Cache Preferences` *only* control the capacity of + images loaded by `MM Solver`. Other types of data in Maya + like geometry and material/surface textures are not + controlled or detailed. To run the tool, use this Python command: @@ -535,6 +554,99 @@ To run the tool, use this Python command: import mmSolver.tools.imagecacheprefs.tool as tool tool.open_window() +What Image Cache Values Should I Use? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Image Cache Capacity values are depending on two things; hardware +memory capacity and frame count / image resolution. This section lists +some common wisdom and logic to optimise the use of the `Image Cache`. + +The default `Image Cache Preferences` are intended for a user with a +~200 frame 1080p HD shot, 4 GB of GPU memory and 32 GB CPU memory. The +hardware specifications for a professional workstation in 2024 is +expected to surpass these values, but this assumption is intended to +ensure that lower-spec hardware still performs okay. + +If you don't care about MM ImagePlane playback speed; set the `Image +Cache` capacity to 0%. + +If you want the maximum performance and you have a lot of GPU and CPU +memory, and you only work with one Maya Scene open at once; set the +`Image Cache` capacity to 100% + +If you want to fit the image sequence of an `MM ImagePlane`, look at +the `Image Cache` tab in the Attribute Editor, and set your GPU and +CPU `Image Cache` capacity to just above that number. + +If you are often running out of GPU memory, decrease your GPU memory +capacity to 0% or the lowest possible - you may get a limited playback +speed (depending on the image resolution), however the Image Cache +will only keep 1 image in GPU memory at any one time - at the cost of +slower playback. + +Hardware Resource Monitor +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. figure:: images/tools_image_cache_preferences_monitor.png + :alt: Image Cache Preferences window + :align: center + :width: 60% + +The `Image Cache Preferences` window allows users to monitor the +computer's memory resources similar to the `Microsoft Windows` Task +Manager or System Monitors on `Linux`. Additionally the window +provides details of the `Image Cache` memory usage, detailing how many +individual *Images* are stored and how many *Image Slots* (similar to +image sequences) are stored. + +.. note:: The `Image Cache` *Capacity* and *Used* values only display + statistics of memory of the Image Cache; geometry and + material/shader textures are not counted. + +.. list-table:: GPU/CPU Resource Fields + :widths: auto + :header-rows: 1 + + * - Field + - Description + - Measurement Unit + + * - Memory Total + - Total amount of memory of the hardware resource. + - Gigabytes (GB) + + * - Memory Used + - Amount of memory available for the hardware resource. + - Gigabytes (GB) + + +.. list-table:: GPU/CPU Image Cache Overview Fields + :widths: auto + :header-rows: 1 + + * - Field + - Description + - Measurement Unit + + * - Images + - Number of individual images stored in the `Image Cache` on the + hardware. + - Count + + * - Image Slots + - Number of unique image sequences stored in the `Image Cache` on + the hardware. + - Count + + * - Capacity + - Amount of hardware memory allowed to be used for the `Image + Cache`. + - Gigabytes (GB) + + * - Used + - Amount of `Image Cache` capacity used. + - Gigabytes (GB) / Percentage of Capacity + .. _user-preferences-tool-ref: User Preferences From 434d0ec3d4936ab8ab5a96a5d7a37e5babb002ea Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 00:02:30 +1030 Subject: [PATCH 279/295] Fix ImageCache Preferences UI - value box not using floating point number --- .../mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py index a612f7a8d..f8e124902 100644 --- a/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py +++ b/python/mmSolver/tools/imagecacheprefs/ui/imagecacheprefs_layout.py @@ -139,7 +139,7 @@ def set_value_double_spin_box( ): assert isinstance(doubleSpinBox, QtWidgets.QDoubleSpinBox) assert isinstance(value, float) - doubleSpinBox.setValue(int(value)) + doubleSpinBox.setValue(value) def set_capacity_widgets( From 7417080e077c3a7ed9c50e118df0c0164e6f01ae Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 00:04:46 +1030 Subject: [PATCH 280/295] MM Image Plane 2 - make AE button name consistent We want the same name for the tool and the button, to avoid confusion and ensure the artist understands what the button does before they click the button. --- mel/AETemplates/AEmmImagePlaneShape2Template.mel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mel/AETemplates/AEmmImagePlaneShape2Template.mel b/mel/AETemplates/AEmmImagePlaneShape2Template.mel index 1d22e8a65..74be7215a 100644 --- a/mel/AETemplates/AEmmImagePlaneShape2Template.mel +++ b/mel/AETemplates/AEmmImagePlaneShape2Template.mel @@ -465,7 +465,7 @@ global proc AEmmImagePlaneShape2_imageCacheButtonsNew(string $attr_name) // Editor. text -label ""; - button -label "Cache Capacity..." imageCacheButtons_capacityButton; + button -label "Image Cache Preferences..." imageCacheButtons_capacityButton; } setParent ..; From 63c22b6541407d54eadd15622dc079d73e6217cf Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 00:06:36 +1030 Subject: [PATCH 281/295] Image Cache - change default capacity values The logic is written in the comments, but the general idea is to pick a low value of the capacity for some users with low-end machines working with HD footage. --- .../tools_image_cache_preferences_ui.png | Bin 20992 -> 20869 bytes python/mmSolver/tools/imagecache/constant.py | 29 ++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/docs/source/images/tools_image_cache_preferences_ui.png b/docs/source/images/tools_image_cache_preferences_ui.png index 3ade0c81d8468ad7685f336647c8028fd0cdfbb2..c2a1fb13e1e4689e7d8607880372fa0a45801df4 100644 GIT binary patch literal 20869 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 literal 20992 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 nI8vs&0-VfbXe->> width = 1920 +# >>> height = 1080 +# >>> num_channels = 3 +# >>> image_byte_size = width * height * num_channels # 6220800 Bytes +# A image is at least 6.2208 MB each frame. +# +# Image Sequence size: +# >>> frame_count = 200 +# >>> image_byte_size * frame_count # 1244160000 Bytes +# The average image sequence will take up about 1.24416 GB of memory. +# +# We also guess that the user has at least 4GB of GPU memory, and 32GB +# of CPU memory, and we assume the user wants to have 3 different Maya +# sessions running at once, each with an image sequence loaded. +# +# 100 MB (2.5% of 4GB) of GPU memory allows ~16 1080p HD images stored +# in memory at once. For large (4K) images this means perhaps only one +# image will be stored in the Image Cache. CONFIG_GPU_CAPACITY_PERCENT_KEY = 'data/gpu_capacity_percent' -CONFIG_GPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 +CONFIG_GPU_CAPACITY_PERCENT_DEFAULT_VALUE = 2.5 +# 3.2 GB (10%) of CPU memory allows ~514 1080p HD images stored in +# memory at once. This is a reasonable amount of CPU memory, but can +# easily be consumed for larger images or longer frame ranges. CONFIG_CPU_CAPACITY_PERCENT_KEY = 'data/cpu_capacity_percent' -CONFIG_CPU_CAPACITY_PERCENT_DEFAULT_VALUE = 0.0 +CONFIG_CPU_CAPACITY_PERCENT_DEFAULT_VALUE = 10.0 SCENE_OPTION_CAPACITY_OVERRIDE_KEY = 'mmSolver_imagecache_capacity_override' SCENE_OPTION_CAPACITY_OVERRIDE_DEFAULT_VALUE = False From a460b21eeb90aec2cea67277ce075672ca64c18a Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 00:08:44 +1030 Subject: [PATCH 282/295] Docs - Add stub for ImagePlane node/tool. Other tools reference this link, so now the links should be consistent. --- docs/source/tools_createnode.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index fade84f49..641db532a 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -152,3 +152,13 @@ To create an image plane, you can run this Python command: import mmSolver.tools.createimageplane.tool as tool tool.main() + +.. _imageplane-ref: + +ImagePlane +---------- + +*To be written* + +See :ref:`Image Cache Preferences ` for +details on how to control the MM Image Plane hardware resources used. From 8eb73edd7bb5cbc859a1262f9945801d05cfa087 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 09:29:20 +1030 Subject: [PATCH 283/295] Docs - MM ImagePlane - Write key features --- docs/source/tools_createnode.rst | 76 +++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/docs/source/tools_createnode.rst b/docs/source/tools_createnode.rst index 641db532a..40228bb9b 100644 --- a/docs/source/tools_createnode.rst +++ b/docs/source/tools_createnode.rst @@ -155,10 +155,82 @@ To create an image plane, you can run this Python command: .. _imageplane-ref: -ImagePlane ----------- +MM ImagePlane +------------- + +The `MM ImagePlane` node is an improved Image Plane, designed for +MatchMove tasks. + +Key features: + +- Multiple Image Slots; Switch seamlessly between 4 different image + sequences loaded onto the `MM ImagePlane`. + +- Memory resource control; The :ref:`Image Cache + ` is used to limit and detail memory + usage for the `MM ImagePlane` allowing greater control than the + native Maya ImagePlane. + +- Real-Time Lens Distortion; Lenses added to the camera (with + :ref:`Create Lens ` tool) will distort the `MM + ImagePlane` in real-time, as the Lens attributes are updated. + +- Frame Range controls and details; Override the first frame of an + image sequence to any other frame, and see the output frame number + easily for debugging. + +- Enhanced Display Controls; Adjust the exposure, gamma, saturation + and soft-clip of the input image data, and display individual colour + channels. + +.. _imageplane-display-attributes-ref: + +Display Attributes +~~~~~~~~~~~~~~~~~~ + +*To be written* + +.. _imageplane-image-sequence-attributes-ref: + +Image Sequence Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~ + +*To be written* + +.. _imageplane-hud-attributes-ref: + +HUD Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~ + +*To be written* + +.. _imageplane-image-cache-attributes-ref: + +Image Cache Attributes +~~~~~~~~~~~~~~~~~~~~~~ *To be written* See :ref:`Image Cache Preferences ` for details on how to control the MM Image Plane hardware resources used. + +.. _imageplane-misc-attributes-ref: + +Miscellaneous Attributes +~~~~~~~~~~~~~~~~~~~~~~~~ + +*To be written* + +.. _imageplane-nodes-attributes-ref: + +Nodes Attributes +~~~~~~~~~~~~~~~~ + +*To be written* + +.. _imageplane-extended-image-details-attributes-ref: + +Extended Image Details Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +*To be written* From 1e9b608ee5ab242e5f90c64184b8d96951aed829 Mon Sep 17 00:00:00 2001 From: David Cattermole Date: Sat, 12 Oct 2024 09:50:04 +1030 Subject: [PATCH 284/295] Docs - MM ImagePlane - Add images and list tables --- .../tools_image_plane_attributes_display.png | Bin 0 -> 9039 bytes ...lane_attributes_extended_image_details.png | Bin 0 -> 6790 bytes .../tools_image_plane_attributes_hud.png | Bin 0 -> 2087 bytes ...ols_image_plane_attributes_image_cache.png | Bin 0 -> 8502 bytes ..._image_plane_attributes_image_sequence.png | Bin 0 -> 15105 bytes .../tools_image_plane_attributes_misc.png | Bin 0 -> 4843 bytes .../tools_image_plane_attributes_nodes.png | Bin 0 -> 2908 bytes docs/source/tools_createnode.rst | 108 +++++++++++++++++- 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 docs/source/images/tools_image_plane_attributes_display.png create mode 100644 docs/source/images/tools_image_plane_attributes_extended_image_details.png create mode 100644 docs/source/images/tools_image_plane_attributes_hud.png create mode 100644 docs/source/images/tools_image_plane_attributes_image_cache.png create mode 100644 docs/source/images/tools_image_plane_attributes_image_sequence.png create mode 100644 docs/source/images/tools_image_plane_attributes_misc.png create mode 100644 docs/source/images/tools_image_plane_attributes_nodes.png diff --git a/docs/source/images/tools_image_plane_attributes_display.png b/docs/source/images/tools_image_plane_attributes_display.png new file mode 100644 index 0000000000000000000000000000000000000000..6381ff9aff61b3d16fa9cefd5e3afdd34993037f GIT binary patch literal 9039 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