blenderwright 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. {blenderwright-2.2.0 → blenderwright-2.3.0}/PKG-INFO +5 -4
  2. {blenderwright-2.2.0 → blenderwright-2.3.0}/README.md +4 -3
  3. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/__init__.py +1 -1
  4. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/blender_manifest.toml +1 -1
  5. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/materials.py +12 -2
  6. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/mesh_quality.py +6 -4
  7. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/objects.py +21 -1
  8. {blenderwright-2.2.0 → blenderwright-2.3.0}/pyproject.toml +1 -1
  9. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/prompts/workflows.py +51 -0
  10. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_materials_handler.py +64 -0
  11. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_mesh_quality_handler.py +46 -0
  12. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_objects_handler.py +62 -0
  13. blenderwright-2.3.0/tests/test_tools/test_imported_mesh_cleanup_prompt.py +71 -0
  14. {blenderwright-2.2.0 → blenderwright-2.3.0}/uv.lock +4 -4
  15. {blenderwright-2.2.0 → blenderwright-2.3.0}/.github/FUNDING.yml +0 -0
  16. {blenderwright-2.2.0 → blenderwright-2.3.0}/.github/workflows/ci.yml +0 -0
  17. {blenderwright-2.2.0 → blenderwright-2.3.0}/.github/workflows/pylint.yml +0 -0
  18. {blenderwright-2.2.0 → blenderwright-2.3.0}/.github/workflows/release.yml +0 -0
  19. {blenderwright-2.2.0 → blenderwright-2.3.0}/.github/workflows/test-bpy51.yml +0 -0
  20. {blenderwright-2.2.0 → blenderwright-2.3.0}/.gitignore +0 -0
  21. {blenderwright-2.2.0 → blenderwright-2.3.0}/LICENSE.md +0 -0
  22. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/dispatcher.py +0 -0
  23. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/__init__.py +0 -0
  24. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/animation.py +0 -0
  25. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/armature.py +0 -0
  26. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/booltool.py +0 -0
  27. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/camera.py +0 -0
  28. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/code_exec.py +0 -0
  29. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/collections.py +0 -0
  30. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/curves.py +0 -0
  31. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/file_ops.py +0 -0
  32. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/geometry_nodes.py +0 -0
  33. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/gpencil.py +0 -0
  34. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/lighting.py +0 -0
  35. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/mesh_editing.py +0 -0
  36. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/modeling.py +0 -0
  37. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/physics.py +0 -0
  38. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/print3d.py +0 -0
  39. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/procedural.py +0 -0
  40. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/raster.py +0 -0
  41. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/rendering.py +0 -0
  42. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/scene.py +0 -0
  43. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/sculpting.py +0 -0
  44. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/selection.py +0 -0
  45. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/sequencer.py +0 -0
  46. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/sweep.py +0 -0
  47. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/transforms.py +0 -0
  48. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/uv.py +0 -0
  49. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/handlers/viewport.py +0 -0
  50. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/render_guard.py +0 -0
  51. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/server.py +0 -0
  52. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/thread_safety.py +0 -0
  53. {blenderwright-2.2.0 → blenderwright-2.3.0}/addon/ui_panel.py +0 -0
  54. {blenderwright-2.2.0 → blenderwright-2.3.0}/blend-ai-v1.0.0.zip +0 -0
  55. {blenderwright-2.2.0 → blenderwright-2.3.0}/build.ps1 +0 -0
  56. {blenderwright-2.2.0 → blenderwright-2.3.0}/build.sh +0 -0
  57. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/.nojekyll +0 -0
  58. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/CNAME +0 -0
  59. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/googled2f6c71abc2d6a7b.html +0 -0
  60. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/index.html +0 -0
  61. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/robots.txt +0 -0
  62. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/shuttle-launch.mp4 +0 -0
  63. {blenderwright-2.2.0 → blenderwright-2.3.0}/docs/sitemap.xml +0 -0
  64. {blenderwright-2.2.0 → blenderwright-2.3.0}/glama.json +0 -0
  65. {blenderwright-2.2.0 → blenderwright-2.3.0}/install-addon.sh +0 -0
  66. {blenderwright-2.2.0 → blenderwright-2.3.0}/install_addon.py +0 -0
  67. {blenderwright-2.2.0 → blenderwright-2.3.0}/launch_ollama.sh +0 -0
  68. {blenderwright-2.2.0 → blenderwright-2.3.0}/saloon.png +0 -0
  69. {blenderwright-2.2.0 → blenderwright-2.3.0}/screenshot.png +0 -0
  70. {blenderwright-2.2.0 → blenderwright-2.3.0}/scripts/generate_tool_docs.py +0 -0
  71. {blenderwright-2.2.0 → blenderwright-2.3.0}/shader-preview.png +0 -0
  72. {blenderwright-2.2.0 → blenderwright-2.3.0}/shuttle-launch.gif +0 -0
  73. {blenderwright-2.2.0 → blenderwright-2.3.0}/shuttle-launch.png +0 -0
  74. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/__init__.py +0 -0
  75. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/__main__.py +0 -0
  76. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/aliases.py +0 -0
  77. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/connection.py +0 -0
  78. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/enum_hints.py +0 -0
  79. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/ollama_chat.py +0 -0
  80. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/prompts/__init__.py +0 -0
  81. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/resources/__init__.py +0 -0
  82. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/resources/scene_info.py +0 -0
  83. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/server.py +0 -0
  84. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/strict.py +0 -0
  85. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tool_registry.py +0 -0
  86. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/__init__.py +0 -0
  87. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/animation.py +0 -0
  88. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/armature.py +0 -0
  89. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/booltool.py +0 -0
  90. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/camera.py +0 -0
  91. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/code_exec.py +0 -0
  92. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/collections.py +0 -0
  93. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/curves.py +0 -0
  94. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/file_ops.py +0 -0
  95. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/geometry_nodes.py +0 -0
  96. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/gpencil.py +0 -0
  97. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/lighting.py +0 -0
  98. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/materials.py +0 -0
  99. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/mesh_editing.py +0 -0
  100. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/mesh_quality.py +0 -0
  101. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/modeling.py +0 -0
  102. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/objects.py +0 -0
  103. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/physics.py +0 -0
  104. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/print3d.py +0 -0
  105. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/rendering.py +0 -0
  106. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/scene.py +0 -0
  107. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/screenshot.py +0 -0
  108. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/sculpting.py +0 -0
  109. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/selection.py +0 -0
  110. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/sequencer.py +0 -0
  111. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/sweep.py +0 -0
  112. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/transforms.py +0 -0
  113. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/uv.py +0 -0
  114. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/tools/viewport.py +0 -0
  115. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/toolsets.py +0 -0
  116. {blenderwright-2.2.0 → blenderwright-2.3.0}/src/blenderwright/validators.py +0 -0
  117. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/__init__.py +0 -0
  118. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/conftest.py +0 -0
  119. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/__init__.py +0 -0
  120. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/conftest.py +0 -0
  121. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_dispatcher.py +0 -0
  122. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handler_registration.py +0 -0
  123. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/__init__.py +0 -0
  124. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_camera_handler.py +0 -0
  125. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_code_exec_handler.py +0 -0
  126. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_geometry_nodes_handler.py +0 -0
  127. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_gpencil_handler.py +0 -0
  128. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_modeling_handler.py +0 -0
  129. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_print3d_handler.py +0 -0
  130. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_procedural_handler.py +0 -0
  131. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_raster_handler.py +0 -0
  132. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_rendering_handler.py +0 -0
  133. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_scene_handler.py +0 -0
  134. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_sculpting_handler.py +0 -0
  135. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_selection_handler.py +0 -0
  136. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_sequencer_handler.py +0 -0
  137. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_handlers/test_sweep_handler.py +0 -0
  138. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_layer_consistency.py +0 -0
  139. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_module_cache_purge.py +0 -0
  140. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_render_guard.py +0 -0
  141. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_server.py +0 -0
  142. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_thread_safety.py +0 -0
  143. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_addon/test_ui_panel.py +0 -0
  144. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_command_registry.py +0 -0
  145. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_connection.py +0 -0
  146. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_enum_hints.py +0 -0
  147. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_installer.py +0 -0
  148. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_long_names.py +0 -0
  149. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_object_param_naming.py +0 -0
  150. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_ollama_budget.py +0 -0
  151. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_ollama_chat.py +0 -0
  152. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_ollama_toolsets.py +0 -0
  153. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_ollama_vision.py +0 -0
  154. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_project_metadata.py +0 -0
  155. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_round_wasters.py +0 -0
  156. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_strict_parameters.py +0 -0
  157. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tool_disambiguation.py +0 -0
  158. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/__init__.py +0 -0
  159. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/conftest.py +0 -0
  160. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_animation.py +0 -0
  161. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_armature.py +0 -0
  162. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_auto_critique_prompt.py +0 -0
  163. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_booltool.py +0 -0
  164. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_camera.py +0 -0
  165. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_code_exec.py +0 -0
  166. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_collections.py +0 -0
  167. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_curves.py +0 -0
  168. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_eevee_light_path.py +0 -0
  169. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_extensions.py +0 -0
  170. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_file_ops.py +0 -0
  171. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_geometry_nodes.py +0 -0
  172. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_gpencil.py +0 -0
  173. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_issue_31_validation.py +0 -0
  174. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_lighting.py +0 -0
  175. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_materials.py +0 -0
  176. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_mesh_editing.py +0 -0
  177. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_mesh_quality.py +0 -0
  178. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_mesh_repair.py +0 -0
  179. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_modeling.py +0 -0
  180. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_negative_scale_primitives.py +0 -0
  181. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_new_nodes.py +0 -0
  182. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_numeric_text_values.py +0 -0
  183. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_objects.py +0 -0
  184. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_physics.py +0 -0
  185. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_point_camera_at.py +0 -0
  186. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_print3d.py +0 -0
  187. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_procedural.py +0 -0
  188. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_prompts.py +0 -0
  189. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_raster.py +0 -0
  190. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_rendering.py +0 -0
  191. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_scene.py +0 -0
  192. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_screenshot.py +0 -0
  193. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_sculpting.py +0 -0
  194. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_selection.py +0 -0
  195. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_selection_respected.py +0 -0
  196. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_sequencer.py +0 -0
  197. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_shader_nodes.py +0 -0
  198. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_sweep.py +0 -0
  199. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_transforms.py +0 -0
  200. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_uv.py +0 -0
  201. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_uv_slim.py +0 -0
  202. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_tools/test_viewport.py +0 -0
  203. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_toolsets.py +0 -0
  204. {blenderwright-2.2.0 → blenderwright-2.3.0}/tests/test_validators.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: blenderwright
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: The most intuitive and efficient MCP Server for Blender
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE.md
@@ -45,7 +45,7 @@ The most intuitive and efficient MCP Server for Blender. Control Blender entirel
45
45
  ## Key Features
46
46
 
47
47
  - **[191 tools](https://blenderwright.holdmybeer.gg/)** across 28 modules covering every major Blender domain: modeling, mesh editing, materials, shader nodes, lighting, camera, animation, rendering, sculpting, UV mapping, physics, geometry nodes, rigging, curves, sweeps along a path, 3D-print checking, annotations, collections, file I/O, Bool Tool, viewport control, mesh quality analysis, and extension suggestions
48
- - **12 expert prompts**: topology best practices, real-world scale references, lighting principles, studio setup, character basemesh workflow, PBR material guide, auto-critique feedback loop, and more
48
+ - **13 expert prompts**: topology best practices, real-world scale references, lighting principles, studio setup, character basemesh workflow, PBR material guide, auto-critique feedback loop, and more
49
49
  - **Visual feedback loop**: fast viewport screenshots via OpenGL render (~ms, not seconds) with auto-critique prompts that guide the LLM to check its own work
50
50
  - **Mesh quality analysis**: structured reports covering non-manifold edges, loose vertices, zero-area faces, duplicate vertices, and wire edges
51
51
  - **Extension suggestions**: proactively recommends Bool Tool, LoopTools, and Node Wrangler when a task would benefit from them (skips already-installed extensions)
@@ -236,7 +236,7 @@ you have; it reports symlinks distinctly.
236
236
 
237
237
  ## Expert Guidance
238
238
 
239
- blenderwright includes 12 MCP prompts that guide the LLM toward professional-quality results:
239
+ blenderwright includes 13 MCP prompts that guide the LLM toward professional-quality results:
240
240
 
241
241
  | Prompt | What It Teaches |
242
242
  |--------|----------------|
@@ -252,6 +252,7 @@ blenderwright includes 12 MCP prompts that guide the LLM toward professional-qua
252
252
  | `character_base_mesh` | Character modeling guide |
253
253
  | `scene_cleanup` | Scene organization workflow |
254
254
  | `animation_turntable` | Turntable animation setup |
255
+ | `imported_mesh_cleanup` | Import, analyze, repair, decimate, normals, scale, UVs for meshes from scans, downloads, or generators |
255
256
 
256
257
  ## Tool Domains
257
258
 
@@ -404,7 +405,7 @@ blenderwright/
404
405
  │ ├── validators.py # Input validation
405
406
  │ ├── tools/ # 28 tool modules (191 tools)
406
407
  │ ├── resources/ # MCP resources (scene, objects, materials)
407
- │ └── prompts/ # 12 expert prompt templates
408
+ │ └── prompts/ # 13 expert prompt templates
408
409
  ├── addon/ # Blender addon (zero external deps)
409
410
  │ ├── blender_manifest.toml # Blender 4.2+ Extension manifest
410
411
  │ ├── __init__.py # bl_info (legacy fallback) + register/unregister
@@ -27,7 +27,7 @@ The most intuitive and efficient MCP Server for Blender. Control Blender entirel
27
27
  ## Key Features
28
28
 
29
29
  - **[191 tools](https://blenderwright.holdmybeer.gg/)** across 28 modules covering every major Blender domain: modeling, mesh editing, materials, shader nodes, lighting, camera, animation, rendering, sculpting, UV mapping, physics, geometry nodes, rigging, curves, sweeps along a path, 3D-print checking, annotations, collections, file I/O, Bool Tool, viewport control, mesh quality analysis, and extension suggestions
30
- - **12 expert prompts**: topology best practices, real-world scale references, lighting principles, studio setup, character basemesh workflow, PBR material guide, auto-critique feedback loop, and more
30
+ - **13 expert prompts**: topology best practices, real-world scale references, lighting principles, studio setup, character basemesh workflow, PBR material guide, auto-critique feedback loop, and more
31
31
  - **Visual feedback loop**: fast viewport screenshots via OpenGL render (~ms, not seconds) with auto-critique prompts that guide the LLM to check its own work
32
32
  - **Mesh quality analysis**: structured reports covering non-manifold edges, loose vertices, zero-area faces, duplicate vertices, and wire edges
33
33
  - **Extension suggestions**: proactively recommends Bool Tool, LoopTools, and Node Wrangler when a task would benefit from them (skips already-installed extensions)
@@ -218,7 +218,7 @@ you have; it reports symlinks distinctly.
218
218
 
219
219
  ## Expert Guidance
220
220
 
221
- blenderwright includes 12 MCP prompts that guide the LLM toward professional-quality results:
221
+ blenderwright includes 13 MCP prompts that guide the LLM toward professional-quality results:
222
222
 
223
223
  | Prompt | What It Teaches |
224
224
  |--------|----------------|
@@ -234,6 +234,7 @@ blenderwright includes 12 MCP prompts that guide the LLM toward professional-qua
234
234
  | `character_base_mesh` | Character modeling guide |
235
235
  | `scene_cleanup` | Scene organization workflow |
236
236
  | `animation_turntable` | Turntable animation setup |
237
+ | `imported_mesh_cleanup` | Import, analyze, repair, decimate, normals, scale, UVs for meshes from scans, downloads, or generators |
237
238
 
238
239
  ## Tool Domains
239
240
 
@@ -386,7 +387,7 @@ blenderwright/
386
387
  │ ├── validators.py # Input validation
387
388
  │ ├── tools/ # 28 tool modules (191 tools)
388
389
  │ ├── resources/ # MCP resources (scene, objects, materials)
389
- │ └── prompts/ # 12 expert prompt templates
390
+ │ └── prompts/ # 13 expert prompt templates
390
391
  ├── addon/ # Blender addon (zero external deps)
391
392
  │ ├── blender_manifest.toml # Blender 4.2+ Extension manifest
392
393
  │ ├── __init__.py # bl_info (legacy fallback) + register/unregister
@@ -8,7 +8,7 @@ Blender's Python API.
8
8
  bl_info = {
9
9
  "name": "blenderwright",
10
10
  "author": "blenderwright",
11
- "version": (2, 2, 0),
11
+ "version": (2, 3, 0),
12
12
  "blender": (4, 2, 0),
13
13
  "location": "View3D > Sidebar > blenderwright",
14
14
  "description": "MCP Server integration for AI-assisted 3D workflows",
@@ -1,7 +1,7 @@
1
1
  schema_version = "1.0.0"
2
2
 
3
3
  id = "blenderwright"
4
- version = "2.2.0"
4
+ version = "2.3.0"
5
5
  name = "blenderwright"
6
6
  tagline = "MCP server integration for AI-assisted 3D workflows"
7
7
  maintainer = "jabberwock <88205+jabberwock@users.noreply.github.com>"
@@ -55,9 +55,19 @@ def handle_assign_material(params: dict) -> dict:
55
55
  obj.active_material_index = i
56
56
  return {"object": obj.name, "material": mat.name, "action": "already_assigned", "slot": i}
57
57
 
58
+ # Fill the first empty slot before adding one. A boolean modifier
59
+ # leaves an empty slot 0 that every face points at; appending puts
60
+ # the material in slot 1 and the object still renders grey.
61
+ for i, slot_mat in enumerate(obj.data.materials):
62
+ if slot_mat is None:
63
+ obj.data.materials[i] = mat
64
+ obj.active_material_index = i
65
+ return {"object": obj.name, "material": mat.name, "action": "assigned", "slot": i}
66
+
58
67
  obj.data.materials.append(mat)
59
- obj.active_material_index = len(obj.data.materials) - 1
60
- return {"object": obj.name, "material": mat.name, "action": "assigned", "slot": len(obj.data.materials) - 1}
68
+ slot = len(obj.data.materials) - 1
69
+ obj.active_material_index = slot
70
+ return {"object": obj.name, "material": mat.name, "action": "assigned", "slot": slot}
61
71
  except Exception as e:
62
72
  raise RuntimeError(f"Failed to assign material: {e}")
63
73
 
@@ -97,10 +97,8 @@ def handle_repair_mesh(params: dict) -> dict:
97
97
 
98
98
  bpy.ops.object.mode_set(mode="EDIT")
99
99
  try:
100
- bpy.ops.mesh.select_all(action="SELECT")
101
- if params.get("remove_loose", True):
102
- bpy.ops.mesh.delete_loose()
103
- applied.append("remove_loose")
100
+ # Dissolving degenerate faces and filling holes can both leave
101
+ # vertices with no faces behind, so loose removal runs last.
104
102
  if params.get("dissolve_degenerate", True):
105
103
  bpy.ops.mesh.select_all(action="SELECT")
106
104
  bpy.ops.mesh.dissolve_degenerate()
@@ -109,6 +107,10 @@ def handle_repair_mesh(params: dict) -> dict:
109
107
  bpy.ops.mesh.select_all(action="SELECT")
110
108
  bpy.ops.mesh.fill_holes(sides=0)
111
109
  applied.append("fill_holes")
110
+ if params.get("remove_loose", True):
111
+ bpy.ops.mesh.select_all(action="SELECT")
112
+ bpy.ops.mesh.delete_loose()
113
+ applied.append("remove_loose")
112
114
  finally:
113
115
  bpy.ops.object.mode_set(mode="OBJECT")
114
116
 
@@ -39,6 +39,21 @@ def _get_primitive_op(ptype: str):
39
39
  return ops_map.get(ptype)
40
40
 
41
41
 
42
+ def _bake_scale(obj, scale) -> None:
43
+ """Apply a creation scale the way primitive_cube_add does: into the mesh.
44
+
45
+ Mesh data is transformed so the object keeps unit scale and modifiers
46
+ see the real size. An empty has no mesh, so it gets object scale.
47
+ """
48
+ sx, sy, sz = (float(v) for v in scale)
49
+ if (sx, sy, sz) == (1.0, 1.0, 1.0):
50
+ return
51
+ if obj.data is not None and hasattr(obj.data, "transform"):
52
+ obj.data.transform(mathutils.Matrix.Diagonal((sx, sy, sz, 1)))
53
+ else:
54
+ obj.scale = (sx, sy, sz)
55
+
56
+
42
57
  def _extent(obj) -> dict:
43
58
  """The object's size and where it ends, in world space.
44
59
 
@@ -77,9 +92,14 @@ def handle_create_object(params: dict) -> dict:
77
92
  op_func = _get_primitive_op(obj_type)
78
93
  if op_func is None:
79
94
  raise ValueError(f"Unknown object type: {obj_type}")
80
- op_func(location=location, rotation=rotation, scale=scale)
95
+ # Created at unit scale on purpose: the scale is baked below.
96
+ # Blender 5.1's primitive_plane_add and primitive_circle_add
97
+ # silently ignore their scale argument while primitive_cube_add
98
+ # bakes it into the mesh, so the op cannot be trusted with it.
99
+ op_func(location=location, rotation=rotation)
81
100
 
82
101
  obj = bpy.context.active_object
102
+ _bake_scale(obj, scale)
83
103
  if name:
84
104
  obj.name = name
85
105
  if obj.data:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "blenderwright"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "The most intuitive and efficient MCP Server for Blender"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -328,3 +328,54 @@ def auto_critique_workflow() -> str:
328
328
  "end of the sequence, not after every individual step.\n"
329
329
  "- If the user asks you to skip screenshots, respect that preference.\n"
330
330
  )
331
+
332
+
333
+ @mcp.prompt()
334
+ def imported_mesh_cleanup() -> str:
335
+ """Take a mesh that did not originate in Blender from import to clean, usable geometry."""
336
+ return (
337
+ "## Imported Mesh Cleanup Workflow\n\n"
338
+ "Meshes that come from outside Blender (photogrammetry scans, marketplace "
339
+ "downloads, sculpt exports, CAD conversions, AI-generated models) usually "
340
+ "carry defects: split vertices along every seam, loose vertices, zero-area "
341
+ "faces, open boundaries, inverted normals, far too many triangles, and no "
342
+ "usable UVs. Measure before you fix, and fix only what the report calls for.\n\n"
343
+ "1. Import with `import_file(filepath)`. Use the names in `imported_objects` "
344
+ "for every step below.\n"
345
+ "2. Run `analyze_mesh_quality(object_name)` and read the report before touching "
346
+ "anything. `issues_found` says whether steps 3 to 5 are needed at all.\n"
347
+ "3. If `duplicate_vertex_count` is non-zero, run `merge_vertices(object_name)` "
348
+ "with the default threshold FIRST. GLB, FBX and OBJ exporters split vertices "
349
+ "wherever UVs or normals change, so a clean mesh can arrive with thousands of "
350
+ "duplicates and a `non_manifold_edge_count` that is mostly false. Merging "
351
+ "collapses them and makes the non-manifold count truthful.\n"
352
+ "4. Run `repair_mesh(object_name)` with the defaults (remove_loose and "
353
+ "dissolve_degenerate). These cannot damage a well-formed mesh. Leave "
354
+ "`fill_holes` off unless `non_manifold_edge_count` is still high after merging "
355
+ "AND the model is meant to be watertight (3D print, boolean operand, fluid "
356
+ "domain). Filling holes invents geometry, so check the result visually.\n"
357
+ "5. Run `analyze_mesh_quality` again and compare the counts. If "
358
+ "`non_manifold_edge_count` is still non-zero on a model that only needs to "
359
+ "render, stop here; open boundaries are fine for rendering.\n"
360
+ "6. Check density from `face_count`. Scans and generated meshes are often "
361
+ "500k+ triangles. If the face count is far beyond what the use needs, run "
362
+ "`decimate_mesh(object_name, ratio)` (0.1 to 0.3 for a scan, 0.5 for a mild "
363
+ "reduction). For a clean quad surface instead of collapsed triangles, use "
364
+ "`remesh` with a voxel size appropriate to the model's scale.\n"
365
+ "7. Run `recalculate_normals(object_name)`. Imported meshes frequently have "
366
+ "inconsistent winding, which shows as black patches in the viewport.\n"
367
+ "8. Check scale and transforms with `get_object_info`. Many exporters land a "
368
+ "mesh with a 100x or 0.01x object scale; compare `dimensions` against "
369
+ "`scale_reference_guide`, fix with `set_scale`, then `apply_transforms` so the "
370
+ "mesh data carries the real size. Use `set_origin` if the pivot is off.\n"
371
+ "9. Run `shade_auto_smooth(object_name)` so hard edges stay crisp and curved "
372
+ "surfaces look smooth.\n"
373
+ "10. If the model has no UVs or they are unusable, run "
374
+ "`smart_uv_project(object_name)` before building materials.\n"
375
+ "11. Run `focus_on_object(object_name)` so the viewport actually frames it "
376
+ "(the view may be left far away after a scale fix), then capture "
377
+ "`get_viewport_screenshot(mode='fast')` and critique the result (see "
378
+ "auto_critique_workflow). Look for holes, shading artifacts, and proportions.\n\n"
379
+ "Only after this pass should you retopologize, add modifiers, or build "
380
+ "materials on the mesh. Working on dirty geometry compounds every later problem."
381
+ )
@@ -481,3 +481,67 @@ class TestRegistration:
481
481
  "set_color_ramp_interpolation",
482
482
  "get_color_ramp",
483
483
  } <= registered
484
+
485
+
486
+ # ---------------------------------------------------------------------------
487
+ # assign_material fills an empty slot before adding one
488
+ # ---------------------------------------------------------------------------
489
+
490
+
491
+ class TestAssignMaterialSlots:
492
+ """An object that has been through a boolean modifier carries an empty
493
+ material slot 0. Appending put the material in slot 1, every face still
494
+ pointed at slot 0, and the wall rendered grey. The first empty slot is
495
+ filled instead; a new slot is only added when none is empty."""
496
+
497
+ def _obj(self, slots):
498
+ obj = MagicMock()
499
+ obj.name = "Wall"
500
+ obj.data.materials = list(slots)
501
+ return obj
502
+
503
+ def _mat(self, name):
504
+ mat = MagicMock()
505
+ mat.name = name
506
+ return mat
507
+
508
+ def _patch(self, monkeypatch, obj, mat):
509
+ import bpy
510
+ monkeypatch.setattr(bpy.data.objects, "get", lambda n: obj)
511
+ monkeypatch.setattr(bpy.data.materials, "get", lambda n: mat)
512
+
513
+ def test_fills_the_empty_slot(self, mh, monkeypatch):
514
+ sage = self._mat("Wall_Sage")
515
+ obj = self._obj([None])
516
+ self._patch(monkeypatch, obj, sage)
517
+ result = mh.handle_assign_material({"object_name": "Wall", "material_name": "Wall_Sage"})
518
+ assert obj.data.materials == [sage]
519
+ assert result["slot"] == 0
520
+ assert result["action"] == "assigned"
521
+
522
+ def test_fills_the_first_empty_slot_only(self, mh, monkeypatch):
523
+ sage = self._mat("Wall_Sage")
524
+ other = self._mat("Other")
525
+ obj = self._obj([other, None, None])
526
+ self._patch(monkeypatch, obj, sage)
527
+ result = mh.handle_assign_material({"object_name": "Wall", "material_name": "Wall_Sage"})
528
+ assert obj.data.materials == [other, sage, None]
529
+ assert result["slot"] == 1
530
+
531
+ def test_appends_when_no_slot_is_empty(self, mh, monkeypatch):
532
+ sage = self._mat("Wall_Sage")
533
+ other = self._mat("Other")
534
+ obj = self._obj([other])
535
+ self._patch(monkeypatch, obj, sage)
536
+ result = mh.handle_assign_material({"object_name": "Wall", "material_name": "Wall_Sage"})
537
+ assert obj.data.materials == [other, sage]
538
+ assert result["slot"] == 1
539
+
540
+ def test_already_assigned_is_reported(self, mh, monkeypatch):
541
+ sage = self._mat("Wall_Sage")
542
+ obj = self._obj([None, sage])
543
+ self._patch(monkeypatch, obj, sage)
544
+ result = mh.handle_assign_material({"object_name": "Wall", "material_name": "Wall_Sage"})
545
+ assert result["action"] == "already_assigned"
546
+ assert result["slot"] == 1
547
+ assert obj.data.materials == [None, sage]
@@ -437,3 +437,49 @@ class TestAnalyzeMeshQualityErrors:
437
437
 
438
438
  with pytest.raises(ValueError, match="not a mesh"):
439
439
  mod.handle_analyze_mesh_quality({"object_name": "Camera"})
440
+
441
+
442
+ class TestRepairMeshOrder:
443
+ """Dissolving degenerate faces can orphan vertices, so loose removal must run after it."""
444
+
445
+ def _run_repair(self, mod, params):
446
+ import bpy
447
+ mock_obj = MagicMock()
448
+ mock_obj.name = "Cube"
449
+ mock_obj.type = "MESH"
450
+ mock_obj.mode = "OBJECT"
451
+ mock_obj.data.vertices = []
452
+ mock_obj.data.edges = []
453
+ mock_obj.data.polygons = []
454
+ bpy.data.objects.get.return_value = mock_obj
455
+ bpy.ops.mesh.reset_mock()
456
+ return mod.handle_repair_mesh(params)
457
+
458
+ def test_dissolve_degenerate_runs_before_delete_loose(self, handler_and_bmesh):
459
+ mod, _ = handler_and_bmesh
460
+ import bpy
461
+
462
+ self._run_repair(mod, {"object_name": "Cube"})
463
+
464
+ names = [c[0] for c in bpy.ops.mesh.mock_calls]
465
+ assert "dissolve_degenerate" in names
466
+ assert "delete_loose" in names
467
+ assert names.index("dissolve_degenerate") < names.index("delete_loose")
468
+
469
+ def test_fill_holes_runs_before_delete_loose(self, handler_and_bmesh):
470
+ mod, _ = handler_and_bmesh
471
+ import bpy
472
+
473
+ self._run_repair(mod, {"object_name": "Cube", "fill_holes": True})
474
+
475
+ names = [c[0] for c in bpy.ops.mesh.mock_calls]
476
+ assert names.index("fill_holes") < names.index("delete_loose")
477
+
478
+ def test_repairs_applied_reports_every_enabled_step(self, handler_and_bmesh):
479
+ mod, _ = handler_and_bmesh
480
+
481
+ result = self._run_repair(mod, {"object_name": "Cube", "fill_holes": True})
482
+
483
+ assert set(result["repairs_applied"]) == {
484
+ "remove_loose", "dissolve_degenerate", "fill_holes"
485
+ }
@@ -544,3 +544,65 @@ class TestCreateReturnsExtent:
544
544
  result = objects_handler.handle_create_object({"type": "CUBE"})
545
545
  assert result["name"] == "Fuselage"
546
546
  assert "bounds" not in result
547
+
548
+
549
+ # ---------------------------------------------------------------------------
550
+ # create_object bakes the scale itself
551
+ # ---------------------------------------------------------------------------
552
+
553
+
554
+ class TestCreateBakesScale:
555
+ """Blender 5.1's primitive_plane_add and primitive_circle_add silently
556
+ ignore their scale argument, while primitive_cube_add bakes it into the
557
+ mesh. A hallway floor asked for at scale (3, 4.5, 1) came back 2m square.
558
+ The handler now creates every primitive at unit scale and bakes the scale
559
+ into the mesh itself, so every type behaves the way the cube always did."""
560
+
561
+ def _obj(self, bpy):
562
+ obj = MagicMock()
563
+ obj.name = "Floor"
564
+ obj.type = "MESH"
565
+ obj.location = [0, 0, 0]
566
+ obj.dimensions = [6.0, 9.0, 0.0]
567
+ obj.bound_box = None
568
+ for op in ("primitive_plane_add", "primitive_cube_add", "primitive_circle_add"):
569
+ getattr(bpy.ops.mesh, op).side_effect = None
570
+ bpy.context.active_object = obj
571
+ return obj
572
+
573
+ def test_plane_scale_is_baked_into_the_mesh(self, objects_handler):
574
+ import bpy
575
+ import mathutils
576
+ bpy.reset_mock()
577
+ mathutils.Matrix.reset_mock()
578
+ obj = self._obj(bpy)
579
+ objects_handler.handle_create_object({
580
+ "type": "PLANE", "location": (0, 4.5, 0), "scale": (3, 4.5, 1),
581
+ })
582
+ mathutils.Matrix.Diagonal.assert_called_once_with((3, 4.5, 1, 1))
583
+ obj.data.transform.assert_called_once_with(mathutils.Matrix.Diagonal.return_value)
584
+
585
+ def test_the_op_is_not_asked_to_scale(self, objects_handler):
586
+ """Passing scale to the op as well would double it on the cube."""
587
+ import bpy
588
+ bpy.reset_mock()
589
+ self._obj(bpy)
590
+ objects_handler.handle_create_object({"type": "CUBE", "scale": (2, 2, 2)})
591
+ assert "scale" not in bpy.ops.mesh.primitive_cube_add.call_args.kwargs
592
+
593
+ def test_unit_scale_does_not_touch_the_mesh(self, objects_handler):
594
+ import bpy
595
+ bpy.reset_mock()
596
+ obj = self._obj(bpy)
597
+ objects_handler.handle_create_object({"type": "CIRCLE", "scale": (1, 1, 1)})
598
+ obj.data.transform.assert_not_called()
599
+
600
+ def test_empty_gets_object_scale(self, objects_handler):
601
+ """An empty has no mesh to bake into, so its object scale is set."""
602
+ import bpy
603
+ bpy.reset_mock()
604
+ obj = self._obj(bpy)
605
+ obj.type = "EMPTY"
606
+ obj.data = None
607
+ objects_handler.handle_create_object({"type": "EMPTY", "scale": (2, 3, 4)})
608
+ assert tuple(obj.scale) == (2, 3, 4)
@@ -0,0 +1,71 @@
1
+ """Unit tests for the imported mesh cleanup workflow prompt."""
2
+
3
+ from blenderwright.prompts.workflows import imported_mesh_cleanup
4
+
5
+
6
+ class TestImportedMeshCleanupPrompt:
7
+ def test_returns_non_empty_string(self):
8
+ result = imported_mesh_cleanup()
9
+ assert isinstance(result, str)
10
+ assert len(result) > 0
11
+
12
+ def test_is_vendor_neutral(self):
13
+ result = imported_mesh_cleanup().lower()
14
+ for vendor in ("tripo", "meshy", "sketchfab", "luma", "rodin"):
15
+ assert vendor not in result
16
+
17
+ def test_chains_import_analyze_repair(self):
18
+ result = imported_mesh_cleanup()
19
+ assert "import_file" in result
20
+ assert "analyze_mesh_quality" in result
21
+ assert "repair_mesh" in result
22
+ assert result.index("import_file") < result.index("analyze_mesh_quality")
23
+ assert result.index("analyze_mesh_quality") < result.index("repair_mesh")
24
+
25
+ def test_merges_duplicates_before_repair(self):
26
+ result = imported_mesh_cleanup()
27
+ assert "merge_vertices" in result
28
+ assert "duplicate_vertex_count" in result
29
+ assert result.index("merge_vertices") < result.index("repair_mesh")
30
+
31
+ def test_fill_holes_is_called_out_as_opt_in(self):
32
+ result = imported_mesh_cleanup()
33
+ assert "fill_holes" in result
34
+ assert "non_manifold_edge_count" in result
35
+
36
+ def test_covers_density_and_normals(self):
37
+ result = imported_mesh_cleanup()
38
+ assert "decimate_mesh" in result
39
+ assert "recalculate_normals" in result
40
+
41
+ def test_covers_transforms_and_scale(self):
42
+ result = imported_mesh_cleanup()
43
+ assert "apply_transforms" in result
44
+ assert "scale_reference_guide" in result
45
+
46
+ def test_covers_shading_and_uvs(self):
47
+ result = imported_mesh_cleanup()
48
+ assert "shade_auto_smooth" in result
49
+ assert "smart_uv_project" in result
50
+
51
+ def test_names_common_sources(self):
52
+ result = imported_mesh_cleanup().lower()
53
+ assert "scan" in result
54
+ assert "generated" in result
55
+
56
+ def test_reanalyzes_after_repair(self):
57
+ result = imported_mesh_cleanup()
58
+ assert result.count("analyze_mesh_quality") >= 2
59
+
60
+ def test_ends_with_visual_check(self):
61
+ result = imported_mesh_cleanup()
62
+ assert "get_viewport_screenshot" in result
63
+
64
+ def test_frames_object_before_screenshot(self):
65
+ result = imported_mesh_cleanup()
66
+ assert "focus_on_object" in result
67
+ assert result.index("focus_on_object") < result.index("get_viewport_screenshot")
68
+
69
+ def test_uses_import_result_names(self):
70
+ result = imported_mesh_cleanup()
71
+ assert "imported_objects" in result
@@ -51,7 +51,7 @@ wheels = [
51
51
 
52
52
  [[package]]
53
53
  name = "blenderwright"
54
- version = "2.2.0"
54
+ version = "2.3.0"
55
55
  source = { editable = "." }
56
56
  dependencies = [
57
57
  { name = "mcp" },
@@ -99,11 +99,11 @@ dev = [
99
99
 
100
100
  [[package]]
101
101
  name = "certifi"
102
- version = "2026.7.22"
102
+ version = "2026.10.19"
103
103
  source = { registry = "https://pypi.org/simple" }
104
- sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" }
104
+ sdist = { url = "https://files.pythonhosted.org/packages/a6/bd/1436c50cc46699de0230fbaacc1d1de7bfec413176099949e9d5112e88f2/certifi-2026.10.19.tar.gz", hash = "sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae", size = 137598, upload-time = "2026-10-20T02:24:16.604Z" }
105
105
  wheels = [
106
- { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" },
106
+ { url = "https://files.pythonhosted.org/packages/c4/51/29046e48f693c54e5b7412216d447a1d8362c9653148d98c02cdf65fa733/certifi-2026.10.19-py3-none-any.whl", hash = "sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b", size = 136464, upload-time = "2026-10-20T02:24:15.072Z" },
107
107
  ]
108
108
 
109
109
  [[package]]
File without changes
File without changes
File without changes