morphopt 4.0.4__tar.gz → 4.0.5__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 (211) hide show
  1. {morphopt-4.0.4 → morphopt-4.0.5}/PKG-INFO +2 -2
  2. {morphopt-4.0.4 → morphopt-4.0.5}/pyproject.toml +2 -2
  3. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/constants.py +0 -7
  4. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/parts/box_part.py +2 -2
  5. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/editing.py +29 -3
  6. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/fields.py +5 -1
  7. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/part_sets.py +8 -3
  8. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/quickstart.py +2 -4
  9. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/updaters.py +14 -0
  10. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/codegen/generator.py +16 -6
  11. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/locales/en-US.json +2 -1
  12. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/locales/zh-CN.json +2 -1
  13. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/base_nodes.py +1 -1
  14. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/configuration.py +12 -4
  15. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/fields.py +1 -0
  16. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/__init__.py +0 -2
  17. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/cad.py +0 -27
  18. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/parts.py +3 -0
  19. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/problem.py +94 -36
  20. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/registry.py +0 -2
  21. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/visualization/geometry.py +1 -0
  22. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/visualization/scene.py +13 -3
  23. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/definition_actions.py +42 -19
  24. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/observation_pages.py +28 -1
  25. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/param_form.py +1 -1
  26. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/plot_style.py +5 -11
  27. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/preview_renderer.py +22 -4
  28. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/quickstart.py +3 -2
  29. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/PKG-INFO +2 -2
  30. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/requires.txt +1 -1
  31. {morphopt-4.0.4 → morphopt-4.0.5}/README.md +0 -0
  32. {morphopt-4.0.4 → morphopt-4.0.5}/setup.cfg +0 -0
  33. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/__init__.py +0 -0
  34. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/codesign/__init__.py +0 -0
  35. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/codesign/constraints.py +0 -0
  36. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/codesign/offset_boundary_part.py +0 -0
  37. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/codesign/update_offset_boundary_part.py +0 -0
  38. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/opt_runner.py +0 -0
  39. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/__init__.py +0 -0
  40. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/controller.py +0 -0
  41. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/history.py +0 -0
  42. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/__init__.py +0 -0
  43. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/base_model_component.py +0 -0
  44. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/baseparam.py +0 -0
  45. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/__init__.py +0 -0
  46. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/base_component.py +0 -0
  47. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/body_force.py +0 -0
  48. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/boundary_condition.py +0 -0
  49. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/contact.py +0 -0
  50. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/couple.py +0 -0
  51. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/point_load.py +0 -0
  52. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/pressure.py +0 -0
  53. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/reference_point.py +0 -0
  54. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/fea/spring.py +0 -0
  55. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/feaparams.py +0 -0
  56. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/geometry.py +0 -0
  57. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/materials/__init__.py +0 -0
  58. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/materials/base_material.py +0 -0
  59. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/materials/homogeneous_material.py +0 -0
  60. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/materials/models.py +0 -0
  61. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/materialsparams.py +0 -0
  62. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/params.py +0 -0
  63. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/parts/__init__.py +0 -0
  64. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/parts/base_part.py +0 -0
  65. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/parts/cad_part.py +0 -0
  66. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/modelparams/parts/inp_part.py +0 -0
  67. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/objfunc.py +0 -0
  68. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/protocal.py +0 -0
  69. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/quadrature.py +0 -0
  70. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/solver.py +0 -0
  71. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/optcore/updaters.py +0 -0
  72. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/postprocess/__init__.py +0 -0
  73. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/postprocess/exporters.py +0 -0
  74. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/postprocess/fields.py +0 -0
  75. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/postprocess/native.py +0 -0
  76. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/__init__.py +0 -0
  77. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/boundary_part.py +0 -0
  78. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
  79. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +0 -0
  80. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +0 -0
  81. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
  82. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +0 -0
  83. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +0 -0
  84. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -0
  85. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/__init__.py +0 -0
  86. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/base_constraints.py +0 -0
  87. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/base_surface.py +0 -0
  88. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/brep_surface.py +0 -0
  89. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/bsp_constraints.py +0 -0
  90. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/bsp_surface.py +0 -0
  91. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/cpgeo_constraints.py +0 -0
  92. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/cpgeo_surface.py +0 -0
  93. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/mesh_surface.py +0 -0
  94. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/surfaces/nurbs_face.py +0 -0
  95. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/shapeopt/update_boundary_part.py +0 -0
  96. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/__init__.py +0 -0
  97. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
  98. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
  99. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
  100. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
  101. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
  102. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/objectivefuncs/volfrac.py +0 -0
  103. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/simpmaterial.py +0 -0
  104. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/simp/update_simpmaterial.py +0 -0
  105. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/taskoptmization.py +0 -0
  106. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/__init__.py +0 -0
  107. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/__main__.py +0 -0
  108. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/app.py +0 -0
  109. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/__init__.py +0 -0
  110. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/cad.py +0 -0
  111. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/completion.py +0 -0
  112. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/definitions.py +0 -0
  113. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/devices.py +0 -0
  114. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/edit_history.py +0 -0
  115. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/editor_routing.py +0 -0
  116. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/expressions.py +0 -0
  117. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/finite_element.py +0 -0
  118. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/load_cases.py +0 -0
  119. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/result_fields.py +0 -0
  120. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/results.py +0 -0
  121. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/runs.py +0 -0
  122. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/surface_export.py +0 -0
  123. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/validation.py +0 -0
  124. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application/values.py +0 -0
  125. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/application_icon.py +0 -0
  126. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/assets/morphopt.svg +0 -0
  127. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/cad_worker.py +0 -0
  128. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/codegen/__init__.py +0 -0
  129. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/codegen/assets.py +0 -0
  130. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/codegen/custom_classes.py +0 -0
  131. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/codegen/workspace.py +0 -0
  132. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/fea_worker.py +0 -0
  133. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/i18n.py +0 -0
  134. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/launcher.py +0 -0
  135. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/mainwindow.py +0 -0
  136. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/__init__.py +0 -0
  137. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/chamberlayout.py +0 -0
  138. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/loaders.py +0 -0
  139. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/material_models.py +0 -0
  140. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/custom_code.py +0 -0
  141. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/loads.py +0 -0
  142. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/materials.py +0 -0
  143. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/objectives.py +0 -0
  144. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/penalties.py +0 -0
  145. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/structural.py +0 -0
  146. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/surfaces.py +0 -0
  147. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/model/node_types/symmetry.py +0 -0
  148. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/observe_panel.py +0 -0
  149. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/presentation/__init__.py +0 -0
  150. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/presentation/edit_history.py +0 -0
  151. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/presentation/history.py +0 -0
  152. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/presentation/labels.py +0 -0
  153. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/presentation/navigation.py +0 -0
  154. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/refresh.py +0 -0
  155. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/schemes/__init__.py +0 -0
  156. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/schemes/base.py +0 -0
  157. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/schemes/snippets.py +0 -0
  158. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/surface_export_worker.py +0 -0
  159. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/__init__.py +0 -0
  160. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/manager.py +0 -0
  161. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/palette.py +0 -0
  162. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/styles/__init__.py +0 -0
  163. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/styles/base.qss +0 -0
  164. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/styles/controls.qss +0 -0
  165. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/styles/inputs.qss +0 -0
  166. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/stylesheet.py +0 -0
  167. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/themes/__init__.py +0 -0
  168. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/themes/dark.py +0 -0
  169. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/theme/themes/light.py +0 -0
  170. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/visualization/__init__.py +0 -0
  171. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/visualization/assets.py +0 -0
  172. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/visualization/loads.py +0 -0
  173. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/__init__.py +0 -0
  174. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/asset_browser.py +0 -0
  175. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/case_deletion.py +0 -0
  176. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/codeeditor.py +0 -0
  177. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/collection_controls.py +0 -0
  178. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/controller_editor.py +0 -0
  179. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/custom_classes.py +0 -0
  180. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/definition_manager.py +0 -0
  181. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/edit_history.py +0 -0
  182. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/editor.py +0 -0
  183. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/editor_pages.py +0 -0
  184. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/elided_label.py +0 -0
  185. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/file_dialogs.py +0 -0
  186. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/finite_element.py +0 -0
  187. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/helper_editor.py +0 -0
  188. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/history_dialogs.py +0 -0
  189. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/language_selector.py +0 -0
  190. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/load_cases.py +0 -0
  191. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/model_tree.py +0 -0
  192. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/objective_editor.py +0 -0
  193. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/result_path.py +0 -0
  194. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/run_dialogs.py +0 -0
  195. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/solver_editor.py +0 -0
  196. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/source_view.py +0 -0
  197. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/surface_export.py +0 -0
  198. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/template_insert_dialog.py +0 -0
  199. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/theme_selector.py +0 -0
  200. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/updater_editor.py +0 -0
  201. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/values.py +0 -0
  202. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/viewer.py +0 -0
  203. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/widgets/workspace_objective.py +0 -0
  204. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/ui/workbench.py +0 -0
  205. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/utils/__init__.py +0 -0
  206. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/utils/gradient_check.py +0 -0
  207. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt/utils/history_read.py +0 -0
  208. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/SOURCES.txt +0 -0
  209. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/dependency_links.txt +0 -0
  210. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/entry_points.txt +0 -0
  211. {morphopt-4.0.4 → morphopt-4.0.5}/src/morphopt.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: morphopt
3
- Version: 4.0.4
3
+ Version: 4.0.5
4
4
  Summary: A morphing optimization framework using PyTorch and torchfea.
5
5
  Requires-Python: >=3.12
6
6
  Description-Content-Type: text/markdown
@@ -17,7 +17,7 @@ Requires-Dist: cadquery-ocp>=8.0.1.1.0
17
17
  Requires-Dist: tabulate>=0.9.0
18
18
  Requires-Dist: imageio>=2.37.3
19
19
  Requires-Dist: bspmap>=1.0.3
20
- Requires-Dist: torchfea>=1.1.2
20
+ Requires-Dist: torchfea>=1.1.3
21
21
  Requires-Dist: trimesh>=4.11.5
22
22
  Requires-Dist: networkx>=3.6.1
23
23
  Requires-Dist: scipy>=1.17.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "morphopt"
3
- version = "4.0.4"
3
+ version = "4.0.5"
4
4
  description = "A morphing optimization framework using PyTorch and torchfea."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -18,7 +18,7 @@ dependencies = [
18
18
  "tabulate>=0.9.0",
19
19
  "imageio>=2.37.3",
20
20
  "bspmap>=1.0.3",
21
- "torchfea>=1.1.2",
21
+ "torchfea>=1.1.3",
22
22
  "trimesh>=4.11.5",
23
23
  "networkx>=3.6.1",
24
24
  "scipy>=1.17.1",
@@ -181,13 +181,6 @@ class _Constants:
181
181
  class TOOL_ID:
182
182
  DEFAULT = ""
183
183
 
184
- class FRAGMENT:
185
- class OBJECT_ID:
186
- DEFAULT = ""
187
-
188
- class TOOL_ID:
189
- DEFAULT = ""
190
-
191
184
  class TRANSLATE:
192
185
  class INPUT_ID:
193
186
  DEFAULT = ""
@@ -80,10 +80,10 @@ class BoxPart(BasePart):
80
80
  elems_index=torch.arange(len(cells), device=part.nodes.device),
81
81
  elems=torch.as_tensor(cells, device=part.nodes.device),
82
82
  ),
83
- name="design",
83
+ name="C3D8",
84
84
  )
85
85
  if self.mesh_order == 2:
86
- part.convert_linear_to_quadratic_elements(["design"], ["design"])
86
+ part.convert_linear_to_quadratic_elements(["C3D8"], ["C3D8"])
87
87
 
88
88
  node_coordinates = part.nodes.detach().cpu().numpy()
89
89
  bounds = np.array(
@@ -152,7 +152,20 @@ def set_node_field(node: Node, key: str, value, problem: ProblemDefinition | Non
152
152
  if isinstance(node, FEAComponentNode) and key in {*INSTANCE_REFERENCE_FIELDS, *REFERENCE_POINT_FIELDS}:
153
153
  return problem.set_component_reference(node, key, value)
154
154
  if key == "part_name" and isinstance(node, MaterialNode):
155
- return problem.set_material_part(node, value)
155
+ previous_part = node.part
156
+ if not problem.set_material_part(node, value):
157
+ return False
158
+ if node.is_updatable and node.part is not previous_part:
159
+ from ..visualization.geometry import PreviewGeometry
160
+ from ..visualization.scene import MessageItem
161
+
162
+ scene = PreviewGeometry().build(problem, "part", node.part)
163
+ bounds = (
164
+ None if any(isinstance(item, MessageItem) for item in scene.items)
165
+ else scene.bounds
166
+ )
167
+ node.bounding_box = list(bounds) if bounds is not None else [0.0] * 6
168
+ return True
156
169
  node.set_field(key, value)
157
170
  return True
158
171
 
@@ -266,13 +279,26 @@ class CustomClassSession:
266
279
  self._replace_uses(old, name)
267
280
  return True
268
281
 
282
+ def dependent_classes(self, node: CustomClassNode) -> list[CustomClassNode]:
283
+ """Classes whose inheritance chain includes the selected custom class."""
284
+ names = {node.name}
285
+ while True:
286
+ children = {custom.name for custom in self.problem.custom_classes
287
+ if custom.base_class in names}
288
+ if children <= names:
289
+ break
290
+ names.update(children)
291
+ return [custom for custom in self.problem.custom_classes
292
+ if custom is not node and custom.name in names]
293
+
269
294
  def remove(
270
295
  self, node: CustomClassNode | MethodOverrideNode | CustomFunctionNode
271
296
  ) -> CustomClassNode | None:
272
297
  from ..model.problem import CustomClassNode
273
298
  if isinstance(node, CustomClassNode):
274
- self.problem.custom_classes.remove(node)
275
- self._replace_uses(node.name, "")
299
+ for custom in [node, *self.dependent_classes(node)]:
300
+ self.problem.custom_classes.remove(custom)
301
+ self._replace_uses(custom.name, "")
276
302
  return None
277
303
  owner = next(custom for custom in self.problem.custom_classes if node in custom.members)
278
304
  owner.remove_child(node)
@@ -119,11 +119,15 @@ class SnippetInputs:
119
119
  self.problem = problem
120
120
 
121
121
  def values(self, source: str) -> list:
122
+ catalog = PartSetCatalog(self.problem)
122
123
  providers = {
123
124
  "load_steps": lambda: list(self.problem.steps.step_values) if self.problem.steps else [],
124
125
  "instances": self.problem.instance_names,
125
126
  "reference_points": self.problem.reference_point_names,
126
- "elements": self.problem.element_names,
127
+ "elements": lambda: list(dict.fromkeys(
128
+ name for part in self.problem.parts()
129
+ for name in catalog.for_part(part).elements
130
+ )),
127
131
  "force_loads": lambda: [
128
132
  node.name
129
133
  for node in self.problem.fea_components()
@@ -91,8 +91,11 @@ class PartSetCatalog:
91
91
  *metadata.get("node_sets", {}))
92
92
  )
93
93
  return PartSets(
94
- nodes=tuple(names) + end_nodes + tuple(f"{name}_nodes" for name in faces) + tuple(node_sets),
95
- surfaces=tuple(names) + tuple(faces) + tuple(surface_sets),
94
+ nodes=("all", "surface_all") + tuple(names) + end_nodes + tuple(f"{name}_nodes" for name in faces) + tuple(node_sets),
95
+ surfaces=tuple(dict.fromkeys([
96
+ "extern", part.exterior_surface or "extern", *names, *faces, *surface_sets,
97
+ ])),
98
+ elements=("C3D4", "C3D6") if isinstance(part, OffsetBoundaryPartNode) else ("C3D4",),
96
99
  )
97
100
 
98
101
  @staticmethod
@@ -100,7 +103,7 @@ class PartSetCatalog:
100
103
  return PartSets(
101
104
  nodes=("all",) + tuple(f"nodes_{axis}{side}" for axis in "xyz" for side in ("min", "max")),
102
105
  surfaces=(part.exterior_surface or "extern",) + tuple(f"surface_{axis}{side}" for axis in "xyz" for side in ("min", "max")),
103
- elements=("design",),
106
+ elements=("C3D8",),
104
107
  )
105
108
 
106
109
  @staticmethod
@@ -109,6 +112,8 @@ class PartSetCatalog:
109
112
  return PartSets(
110
113
  nodes=("all", "surface_all") + tuple(f"{name}_nodes" for name in names),
111
114
  surfaces=tuple(dict.fromkeys([part.exterior_surface or "extern", *names])),
115
+ # Quadratic conversion preserves the element block's name.
116
+ elements=("C3D4",),
112
117
  )
113
118
 
114
119
  @staticmethod
@@ -578,7 +578,7 @@ def _topology(options, geometry, loads, materials):
578
578
  loads.add_component(FEAComponentNode.create("ConcentratedForce", name="tip_force", rp_name="RP_tip"))
579
579
  materials.add_material(MaterialNode.create(
580
580
  "SIMP_BSPFieldMaterials", name="density", part_name="body",
581
- elementname="design", material_model="NeoHookeanLnJ", mu=options.mu,
581
+ elementname="C3D8", material_model="NeoHookeanLnJ", mu=options.mu,
582
582
  kappa=options.kappa, mumax=options.mu, kappamax=options.kappa,
583
583
  bounding_box=[0., options.total_length, 0., options.height, 0., options.depth],
584
584
  degree=CONSTANT.UI.QUICKSTART.GENERATION.SIMP_DEGREE,
@@ -604,7 +604,7 @@ def _topology(options, geometry, loads, materials):
604
604
  {"type": "VolFrac", "params": {
605
605
  "volfrac_min": options.volume_fraction, "volfrac_max": options.volume_fraction,
606
606
  "penalty": CONSTANT.UI.QUICKSTART.GENERATION.SIMP_VOLUME_FRACTION_PENALTY,
607
- "elementname": "design",
607
+ "elementname": "C3D8",
608
608
  }},
609
609
  ],
610
610
  }],
@@ -693,8 +693,6 @@ class ObjectiveDraft:
693
693
  return len(self.objectives) - 1
694
694
 
695
695
  def remove(self, index: int) -> None:
696
- if len(self.objectives) <= CONSTANT.UI.QUICKSTART.LIMITS.OBJECTIVE_MIN_COUNT:
697
- raise ValueError(localized_message("ui.errors.quickstart.keep_at_least_one_objective_term"))
698
696
  self.objectives.pop(index)
699
697
 
700
698
  def set_method(self, method: str) -> None:
@@ -135,6 +135,18 @@ class UpdaterItems:
135
135
  ) if custom.name not in taken)
136
136
  return choices
137
137
 
138
+ def default_element_name(self) -> str:
139
+ """Use the material's block, or the sole block of its owning Part."""
140
+ from .part_sets import PartSetCatalog
141
+
142
+ material = self.problem.config_material_node(self.config)
143
+ if material is None:
144
+ return ""
145
+ if material.elementname:
146
+ return material.elementname
147
+ elements = PartSetCatalog(self.problem).for_part(material.part).elements
148
+ return elements[0] if len(elements) == 1 else ""
149
+
138
150
  def add(self, item_type: str, custom_class: str = "") -> bool:
139
151
  item = symmetry_item(item_type, self.surfaces) if self.category == "geometry_symmetry" else updater_item(
140
152
  item_type,
@@ -143,6 +155,8 @@ class UpdaterItems:
143
155
  )
144
156
  if item is None:
145
157
  return False
158
+ if self.group == "materials" and item_type == "VolFrac":
159
+ item["params"]["elementname"] = self.default_element_name()
146
160
  self.config.setdefault(self.key, []).append(item)
147
161
  return True
148
162
 
@@ -562,8 +562,8 @@ def generate_base_source(
562
562
 
563
563
  Code-launched runs never open an observer window; results are watched by
564
564
  the MorphOpt UI's in-window observer (disk polling per iteration).
565
- ``for_class_catalog`` emits inert objective methods so editor class discovery
566
- also works while the user is still defining objective and metric terms.
565
+ ``for_class_catalog`` emits inert objective methods and updater classes so
566
+ editor class discovery also works while terms are still being defined.
567
567
  """
568
568
  if asset_paths is None:
569
569
  asset_paths, _ = collect_python_assets(problem)
@@ -672,7 +672,7 @@ def generate_base_source(
672
672
  a("")
673
673
 
674
674
  # -------- Updater -----------------------------------------------------
675
- _emit_updater(a, problem, template, upd_device)
675
+ _emit_updater(a, problem, template, upd_device, for_class_catalog=for_class_catalog)
676
676
 
677
677
  # -------- main --------------------------------------------------------
678
678
  a("")
@@ -800,7 +800,7 @@ def _emit_part_classes(
800
800
  a(" def define_instance(self) -> None:")
801
801
  instances = part.instances
802
802
  if not instances:
803
- a(" super().define_instance()")
803
+ a(" pass")
804
804
  for instance in instances:
805
805
  a("")
806
806
  pose = (
@@ -1155,6 +1155,8 @@ def _emit_updater(
1155
1155
  problem: ProblemDefinition,
1156
1156
  template: SchemeTemplate,
1157
1157
  device: str,
1158
+ *,
1159
+ for_class_catalog: bool = False,
1158
1160
  ) -> None:
1159
1161
  from .custom_classes import updater_class_name
1160
1162
 
@@ -1203,10 +1205,10 @@ def _emit_updater(
1203
1205
  if target.type == "OffsetBoundaryPart"
1204
1206
  else GEOMETRY_UPDATER_BASE
1205
1207
  )
1206
- _emit_nested_updater(a, class_name, base, config, problem)
1208
+ _emit_nested_updater(a, class_name, base, config, problem, for_class_catalog=for_class_catalog)
1207
1209
  a("")
1208
1210
  for class_name, config in zip(material_classes, mat_cfgs):
1209
- _emit_nested_updater(a, class_name, MATERIAL_UPDATER_BASE, config, problem)
1211
+ _emit_nested_updater(a, class_name, MATERIAL_UPDATER_BASE, config, problem, for_class_catalog=for_class_catalog)
1210
1212
  a("")
1211
1213
 
1212
1214
 
@@ -1272,6 +1274,8 @@ def _emit_nested_updater(
1272
1274
  base: str,
1273
1275
  cfg,
1274
1276
  problem: ProblemDefinition,
1277
+ *,
1278
+ for_class_catalog: bool = False,
1275
1279
  ) -> None:
1276
1280
  """Emit one sub-updater class.
1277
1281
 
@@ -1299,6 +1303,12 @@ def _emit_nested_updater(
1299
1303
  f"max_step_length={_render_value(cfg.get('max_step_length', defaults.MAX_STEP_LENGTH), problem)})"
1300
1304
  )
1301
1305
 
1306
+ if for_class_catalog:
1307
+ a("")
1308
+ a(" def define_objective(self) -> None:")
1309
+ a(" pass")
1310
+ return
1311
+
1302
1312
  body: list[str] = []
1303
1313
  # NOTE: the model stores the lists under ``objective_functions`` / ``constraints``
1304
1314
  # while the catalogue (UPDATER_CATALOG) is keyed ``objectives`` / ``constraints``.
@@ -1,4 +1,6 @@
1
1
  {
2
+ "ui.component.delete_dependencies_title": "Delete bound objects",
3
+ "ui.component.delete_dependencies_message": "Deleting “{component}” will also delete these bound objects:\n{dependencies}\nContinue?",
2
4
  "ui.schema.fields.control_point_spacing.label": "Target control point spacing",
3
5
  "ui.schema.fields.design_faces.label": "Design faces",
4
6
  "ui.assets.brep_metadata": "BREP topology and sets",
@@ -762,7 +764,6 @@
762
764
  "ui.schema.catalog.cad_operation.fuse.label": "Fuse",
763
765
  "ui.schema.catalog.cad_operation.cut.label": "Cut",
764
766
  "ui.schema.catalog.cad_operation.intersect.label": "Intersect",
765
- "ui.schema.catalog.cad_operation.fragment.label": "Fragment",
766
767
  "ui.schema.catalog.cad_operation.translate.label": "Translate",
767
768
  "ui.schema.catalog.cad_operation.rotate.label": "Rotate",
768
769
  "ui.schema.catalog.cad_operation.dilate.label": "Scale",
@@ -1,4 +1,6 @@
1
1
  {
2
+ "ui.component.delete_dependencies_title": "删除绑定对象",
3
+ "ui.component.delete_dependencies_message": "删除“{component}”将同时删除以下绑定对象:\n{dependencies}\n是否继续?",
2
4
  "ui.schema.fields.control_point_spacing.label": "控制点目标间距",
3
5
  "ui.schema.fields.design_faces.label": "参与更新的面",
4
6
  "ui.assets.brep_metadata": "BREP 拓扑与集合",
@@ -762,7 +764,6 @@
762
764
  "ui.schema.catalog.cad_operation.fuse.label": "融合",
763
765
  "ui.schema.catalog.cad_operation.cut.label": "切除",
764
766
  "ui.schema.catalog.cad_operation.intersect.label": "相交",
765
- "ui.schema.catalog.cad_operation.fragment.label": "碎片分割",
766
767
  "ui.schema.catalog.cad_operation.translate.label": "平移",
767
768
  "ui.schema.catalog.cad_operation.rotate.label": "旋转",
768
769
  "ui.schema.catalog.cad_operation.dilate.label": "缩放",
@@ -427,7 +427,7 @@ class PartNode(TypedNode):
427
427
  super().__init__(
428
428
  kind=kind, name=name, params=data, children=children
429
429
  )
430
- if not self.instances:
430
+ if not self.instances and children is None:
431
431
  from .node_types.structural import InstanceNode
432
432
 
433
433
  self.add_instance(InstanceNode(name=f"{self.assembly_name or 'part'}-1"))
@@ -84,21 +84,29 @@ def materials_updater_config(
84
84
  max_step_iter: int = 50,
85
85
  max_step_length: float = CONSTANT.KERNEL.SIMP_UPDATE.MAX_STEP_LENGTH,
86
86
  if_update: bool | list[bool] = True,
87
- objective_functions: tuple = (),
88
- constraints: tuple = (),
87
+ objective_functions: tuple | None = None,
88
+ constraints: tuple | None = None,
89
89
  ) -> dict:
90
90
  """Config of one material sub-optimizer.
91
91
 
92
92
  ``material_name`` is the material definition it updates; an empty name
93
93
  means "the only design-carrying material in the model".
94
+ New configs include sensitivity, variable bounds and volume fraction.
95
+ Explicitly empty collections remain empty.
94
96
  """
95
97
  return {
96
98
  "material_name": str(material_name or ""),
97
99
  "max_step_iter": int(max_step_iter),
98
100
  "max_step_length": float(max_step_length),
99
101
  "if_update": if_update,
100
- "objective_functions": list(objective_functions),
101
- "constraints": list(constraints),
102
+ "objective_functions": list(objective_functions) if objective_functions is not None else [
103
+ updater_objective("Sensitivity"),
104
+ ],
105
+ "constraints": list(constraints) if constraints is not None else [
106
+ updater_constraint("MinValue"),
107
+ updater_constraint("MaxValue"),
108
+ updater_constraint("VolFrac"),
109
+ ],
102
110
  }
103
111
 
104
112
 
@@ -29,6 +29,7 @@ class FieldSpec:
29
29
  ints: bool = False
30
30
  read_only: bool = False
31
31
  hidden: bool = False
32
+ editable: bool = True
32
33
 
33
34
  def new_default(self) -> Any:
34
35
  return _mutable_value(self.default)
@@ -6,7 +6,6 @@ from .cad import (
6
6
  CutOperationNode,
7
7
  CylinderOperationNode,
8
8
  DilateOperationNode,
9
- FragmentOperationNode,
10
9
  FuseOperationNode,
11
10
  IntersectOperationNode,
12
11
  MirrorOperationNode,
@@ -127,7 +126,6 @@ __all__ = (
127
126
  "FairnessPenaltyNode",
128
127
  "MeshSurfaceNode",
129
128
  "BREPSurfaceNode",
130
- "FragmentOperationNode",
131
129
  "FuseOperationNode",
132
130
  "GeometryNode",
133
131
  "HelperFunctionNode",
@@ -418,33 +418,6 @@ class IntersectOperationNode(CADOperationNode):
418
418
  )
419
419
 
420
420
 
421
- class FragmentOperationNode(CADOperationNode):
422
- """UI definition of Fragment."""
423
-
424
- type_name: ClassVar[str] = "Fragment"
425
- backend_class: ClassVar[str] = "Fragment"
426
- label_key: ClassVar[str] = "ui.schema.catalog.cad_operation.fragment.label"
427
- group: ClassVar[str] = "boolean"
428
- operation_kind: ClassVar[str] = "fragment"
429
- Defaults = CONSTANT.UI.SCHEMA_FIELDS.CAD_OPERATIONS.FRAGMENT
430
- object_id: str
431
- tool_id: str
432
- fields: ClassVar[tuple[FieldSpec, ...]] = (
433
- FieldSpec(
434
- "object_id",
435
- "ui.schema.fields.object_id.label.object_id",
436
- "combo",
437
- Defaults.OBJECT_ID.DEFAULT,
438
- ),
439
- FieldSpec(
440
- "tool_id",
441
- "ui.schema.fields.tool_id.label.tool_id",
442
- "combo",
443
- Defaults.TOOL_ID.DEFAULT,
444
- ),
445
- )
446
-
447
-
448
421
  class TranslateOperationNode(CADOperationNode):
449
422
  """UI definition of Translate."""
450
423
 
@@ -87,6 +87,7 @@ class BoundaryPartNode(PartNode):
87
87
  Defaults.MESH_ORDER.DEFAULT,
88
88
  doc_key="ui.schema.fields.mesh_order.help.element_order_of_the_generated_mesh",
89
89
  choices=Defaults.MESH_ORDER.CHOICES,
90
+ editable=False,
90
91
  ),
91
92
  FieldSpec(
92
93
  "exterior_surface",
@@ -210,6 +211,7 @@ class BoxPartNode(PartNode):
210
211
  Defaults.MESH_ORDER.DEFAULT,
211
212
  doc_key="ui.schema.fields.mesh_order.help.text",
212
213
  choices=Defaults.MESH_ORDER.CHOICES,
214
+ editable=False,
213
215
  ),
214
216
  FieldSpec(
215
217
  "exterior_surface",
@@ -308,6 +310,7 @@ class CADPartNode(PartNode):
308
310
  Defaults.MESH_ORDER.DEFAULT,
309
311
  doc_key="ui.schema.fields.mesh_order.help.text",
310
312
  choices=Defaults.MESH_ORDER.CHOICES,
313
+ editable=False,
311
314
  ),
312
315
  FieldSpec(
313
316
  "exterior_surface",
@@ -8,6 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  from collections.abc import Iterator
10
10
  from copy import deepcopy
11
+ import re
11
12
 
12
13
  from ...constants import CONSTANT
13
14
  from ..i18n import localized_message
@@ -715,6 +716,26 @@ class ProblemDefinition:
715
716
  )
716
717
  )
717
718
  position = owner.surfaces.index(surface)
719
+ for component in self.fea_components():
720
+ for field, attr in INSTANCE_REFERENCE_FIELDS.items():
721
+ if getattr(component, attr) not in owner.instances:
722
+ continue
723
+ suffix = field.removeprefix("instance_name")
724
+ for key in ("surface_name" + suffix, "set_nodes_name" + suffix):
725
+ if not component.has_field(key):
726
+ continue
727
+ name = component.get_field(key) or ""
728
+ if re.fullmatch(r"face_\d+(_nodes)?", name):
729
+ component.set_field(key, "")
730
+ continue
731
+ match = re.fullmatch(r"surface_(\d+)_(.+)", name)
732
+ if match is None:
733
+ continue
734
+ index = int(match[1])
735
+ if index == position:
736
+ component.set_field(key, "")
737
+ elif index > position:
738
+ component.set_field(key, f"surface_{index - 1}_{match[2]}")
718
739
  self._remove_surface_state(owner, position)
719
740
  owner.remove_child(surface)
720
741
  self.align_surface_dependent_state()
@@ -799,7 +820,7 @@ class ProblemDefinition:
799
820
  return added
800
821
 
801
822
  def remove_instance(self, instance: InstanceNode) -> None:
802
- """Remove one Instance, keeping a Part's first identity Instance."""
823
+ """Remove an Instance and clear its optional references."""
803
824
  owner = self.owner_of_instance(instance)
804
825
  if owner is None:
805
826
  raise ValueError(
@@ -807,17 +828,20 @@ class ProblemDefinition:
807
828
  "ui.errors.problem.the_instance_is_not_part_of_this_problem"
808
829
  )
809
830
  )
810
- if len(owner.instances) <= 1:
811
- raise ValueError(
812
- localized_message(
813
- "ui.errors.problem.a_part_must_keep_at_least_one_instance"
814
- )
815
- )
816
831
  owner.remove_child(instance)
817
832
  for component in self.fea_components():
833
+ if (component.has_field("obj_name") and component.obj_name == instance.name
834
+ and component.obj_type in ("auto", "instance")):
835
+ component.obj_name = ""
818
836
  for field, attr in INSTANCE_REFERENCE_FIELDS.items():
819
837
  if getattr(component, attr) is instance:
820
838
  component.set_reference(field, None)
839
+ suffix = field.removeprefix("instance_name")
840
+ for key in ("surface_name" + suffix, "set_nodes_name" + suffix,
841
+ "element_name" + suffix):
842
+ if component.has_field(key):
843
+ component.set_field(key, "")
844
+ self._clear_objective_reference("instance_name", instance.name)
821
845
 
822
846
  def move_instance(self, instance: InstanceNode, offset: int) -> bool:
823
847
  """Move an Instance in its Part's declaration order."""
@@ -850,8 +874,8 @@ class ProblemDefinition:
850
874
  )
851
875
  return part.add_cad_operation(operation)
852
876
 
853
- def remove_cad_operation(self, operation: CADOperationNode) -> None:
854
- """Remove one CAD feature from its owning Part."""
877
+ def cad_operation_dependents(self, operation: CADOperationNode) -> list[CADOperationNode]:
878
+ """Return all features transitively dependent on this operation."""
855
879
  owner = self.owner_of_cad_operation(operation)
856
880
  if owner is None:
857
881
  raise ValueError(
@@ -859,22 +883,34 @@ class ProblemDefinition:
859
883
  "ui.errors.problem.the_cad_operation_is_not_part_of_this_problem"
860
884
  )
861
885
  )
862
- dependents = [
863
- item
864
- for item in owner.operations
865
- if item is not operation
866
- and operation.operation_id in (item.object_id, item.tool_id, item.input_id)
867
- ]
868
- if dependents:
869
- names = ", ".join(item.operation_id for item in dependents)
870
- raise ValueError(
871
- localized_message(
872
- "ui.errors.problem.cannot_remove_value_it_is_used_by_value",
873
- value_0=operation.operation_id,
874
- value_1=names,
875
- )
876
- )
877
- owner.remove_child(operation)
886
+ removed = {operation.operation_id}
887
+ while True:
888
+ dependent_ids = {
889
+ item.operation_id for item in owner.operations
890
+ if removed.intersection((item.object_id, item.tool_id, item.input_id))
891
+ }
892
+ if dependent_ids <= removed:
893
+ break
894
+ removed.update(dependent_ids)
895
+ return [item for item in owner.operations
896
+ if item is not operation and item.operation_id in removed]
897
+
898
+ def remove_cad_operation(self, operation: CADOperationNode) -> None:
899
+ """Remove a CAD feature and its dependent features after UI confirmation."""
900
+ dependents = self.cad_operation_dependents(operation)
901
+ owner = self.owner_of_cad_operation(operation)
902
+ for item in [operation, *dependents]:
903
+ owner.remove_child(item)
904
+ for component in self.fea_components():
905
+ for field, attr in INSTANCE_REFERENCE_FIELDS.items():
906
+ if getattr(component, attr) not in owner.instances:
907
+ continue
908
+ suffix = field.removeprefix("instance_name")
909
+ for key in ("surface_name" + suffix, "set_nodes_name" + suffix):
910
+ if component.has_field(key) and re.fullmatch(
911
+ r"face_\d+(_nodes)?", component.get_field(key) or ""
912
+ ):
913
+ component.set_field(key, "")
878
914
 
879
915
  def move_cad_operation(self, operation: CADOperationNode, offset: int) -> bool:
880
916
  """Move one CAD feature without disturbing other Part children."""
@@ -1007,17 +1043,21 @@ class ProblemDefinition:
1007
1043
  if material.part is part:
1008
1044
  material.part = None
1009
1045
  material._part_name = ""
1046
+ material.elementname = ""
1047
+ if self.updater is not None:
1048
+ for config in self.updater.materials:
1049
+ if config.get("target") is material:
1050
+ for item in config.get("constraints", []):
1051
+ if "elementname" in item.get("params", {}):
1052
+ item["params"]["elementname"] = ""
1010
1053
  if self.updater is not None:
1011
1054
  self.updater.geometry[:] = [
1012
1055
  config
1013
1056
  for config in self.updater.geometry
1014
1057
  if config.get("target") is not part
1015
1058
  ]
1016
- for instance in part.instances:
1017
- for load in self.fea_components():
1018
- for field, attr in INSTANCE_REFERENCE_FIELDS.items():
1019
- if getattr(load, attr) is instance:
1020
- load.set_reference(field, None)
1059
+ for instance in list(part.instances):
1060
+ self.remove_instance(instance)
1021
1061
  geometry.remove_child(part)
1022
1062
  self.align_surface_dependent_state()
1023
1063
  self._refresh_surface_flags()
@@ -1088,12 +1128,6 @@ class ProblemDefinition:
1088
1128
  "ui.errors.problem.material_is_not_part_of_this_problem"
1089
1129
  )
1090
1130
  )
1091
- if len(section.assignments) <= 1:
1092
- raise ValueError(
1093
- localized_message(
1094
- "ui.errors.problem.a_problem_must_keep_at_least_one_material"
1095
- )
1096
- )
1097
1131
  section.remove_child(material)
1098
1132
  if self.updater is not None:
1099
1133
  self.updater.materials[:] = [
@@ -1155,9 +1189,33 @@ class ProblemDefinition:
1155
1189
  item for item in self.objective.jacobian_needed if item is not component
1156
1190
  ]
1157
1191
  for load in self.fea_components():
1192
+ if load.has_field("obj_name") and load.obj_name == component.name:
1193
+ load.obj_name = ""
1158
1194
  for field, attr in REFERENCE_POINT_FIELDS.items():
1159
1195
  if getattr(load, attr) is component:
1160
1196
  load.set_reference(field, None)
1197
+ if component.type == "ReferencePoint":
1198
+ self._clear_objective_reference("reference_point_name", component.name)
1199
+ else:
1200
+ for key in ("force_load_name", "moment_load_name"):
1201
+ self._clear_objective_reference(key, component.name)
1202
+ if self.objective is not None:
1203
+ for term in self.objective.objective_terms:
1204
+ if term.get("type") != "workspace":
1205
+ continue
1206
+ for entry in term.get("inputs", []):
1207
+ entry["parameters"] = [pair for pair in entry["parameters"]
1208
+ if pair[0] != component.name]
1209
+ term["inputs"] = [entry for entry in term.get("inputs", [])
1210
+ if entry["parameters"]]
1211
+
1212
+ def _clear_objective_reference(self, key: str, name: str) -> None:
1213
+ """Clear explicit objective and metric references to a deleted object."""
1214
+ if self.objective is None:
1215
+ return
1216
+ for term in [*self.objective.objective_terms, *self.objective.metrics_terms]:
1217
+ if term.get(key) == name:
1218
+ term[key] = ""
1161
1219
 
1162
1220
  def rename_definition(
1163
1221
  self,