morphopt 4.0.2__tar.gz → 4.0.3__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.
- {morphopt-4.0.2 → morphopt-4.0.3}/PKG-INFO +1 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/pyproject.toml +1 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/constants.py +2 -4
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/controller.py +3 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/feaparams.py +21 -46
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/parts/base_part.py +2 -3
- morphopt-4.0.3/src/morphopt/optcore/modelparams/parts/cad_part.py +370 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/objfunc.py +19 -13
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/solver.py +39 -34
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/boundary_part.py +138 -138
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/basefuncs.py +25 -13
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/boundarys.py +2 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/shapederivative.py +3 -6
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/surfacefairness.py +1 -25
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/volumemaximization.py +0 -20
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/__init__.py +5 -7
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/base_constraints.py +1 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/base_surface.py +421 -292
- morphopt-4.0.3/src/morphopt/shapeopt/surfaces/brep_surface.py +155 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/bsp_constraints.py +1 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/bsp_surface.py +354 -218
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/cpgeo_constraints.py +4 -12
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/surfaces/cpgeo_surface.py +212 -126
- morphopt-4.0.3/src/morphopt/shapeopt/surfaces/mesh_surface.py +86 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/update_boundary_part.py +10 -25
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/__init__.py +2 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/cad.py +45 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/completion.py +8 -9
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/definitions.py +3 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/edit_history.py +25 -33
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/editing.py +55 -58
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/fields.py +63 -13
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/finite_element.py +6 -5
- morphopt-4.0.3/src/morphopt/ui/application/load_cases.py +243 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/part_sets.py +13 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/quickstart.py +58 -77
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/results.py +8 -9
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/updaters.py +0 -13
- morphopt-4.0.3/src/morphopt/ui/cad_worker.py +28 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/codegen/assets.py +27 -6
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/codegen/custom_classes.py +4 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/codegen/generator.py +61 -60
- morphopt-4.0.3/src/morphopt/ui/codegen/workspace.py +65 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/locales/en-US.json +53 -17
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/locales/zh-CN.json +53 -17
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/mainwindow.py +33 -8
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/base_nodes.py +4 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/configuration.py +5 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/__init__.py +4 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/structural.py +22 -32
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/surfaces.py +31 -6
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/problem.py +38 -35
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/registry.py +4 -2
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/observe_panel.py +6 -4
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/presentation/labels.py +1 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/refresh.py +1 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/schemes/base.py +13 -16
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/schemes/snippets.py +11 -11
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/palette.py +0 -9
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/visualization/geometry.py +47 -7
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/visualization/loads.py +3 -4
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/asset_browser.py +16 -1
- morphopt-4.0.3/src/morphopt/ui/widgets/case_deletion.py +25 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/definition_actions.py +93 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/definition_manager.py +7 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/edit_history.py +8 -18
- morphopt-4.0.3/src/morphopt/ui/widgets/history_dialogs.py +63 -0
- morphopt-4.0.3/src/morphopt/ui/widgets/load_cases.py +153 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/model_tree.py +5 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/objective_editor.py +55 -25
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/observation_pages.py +3 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/solver_editor.py +4 -3
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/updater_editor.py +26 -129
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/viewer.py +5 -7
- morphopt-4.0.3/src/morphopt/ui/widgets/workspace_objective.py +92 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/workbench.py +69 -5
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/PKG-INFO +1 -1
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/SOURCES.txt +9 -2
- morphopt-4.0.2/src/morphopt/optcore/modelparams/parts/cad_part.py +0 -128
- morphopt-4.0.2/src/morphopt/shapeopt/meshgenerator.py +0 -312
- morphopt-4.0.2/src/morphopt/ui/widgets/stepmatrix.py +0 -207
- {morphopt-4.0.2 → morphopt-4.0.3}/README.md +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/setup.cfg +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/codesign/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/codesign/constraints.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/codesign/offset_boundary_part.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/codesign/update_offset_boundary_part.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/opt_runner.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/history.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/base_model_component.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/baseparam.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/base_component.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/body_force.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/boundary_condition.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/contact.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/couple.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/point_load.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/pressure.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/reference_point.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/fea/spring.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/geometry.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/materials/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/materials/base_material.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/materials/homogeneous_material.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/materials/models.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/materialsparams.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/params.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/parts/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/parts/box_part.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/modelparams/parts/inp_part.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/protocal.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/quadrature.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/optcore/updaters.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/postprocess/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/postprocess/exporters.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/postprocess/fields.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/postprocess/native.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/shapeopt/objectivefuncs/distancesurface.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/basefuncs.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/boundarys.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/densityfield.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/sensitivity.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/objectivefuncs/volfrac.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/simpmaterial.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/simp/update_simpmaterial.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/taskoptmization.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/__main__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/app.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/devices.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/editor_routing.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/expressions.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/result_fields.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/runs.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application/values.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/application_icon.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/assets/morphopt.svg +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/codegen/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/fea_worker.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/i18n.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/launcher.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/chamberlayout.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/fields.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/loaders.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/material_models.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/cad.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/custom_code.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/loads.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/materials.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/objectives.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/parts.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/penalties.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/model/node_types/symmetry.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/presentation/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/presentation/edit_history.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/presentation/history.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/presentation/navigation.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/schemes/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/manager.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/styles/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/styles/base.qss +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/styles/controls.qss +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/styles/inputs.qss +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/stylesheet.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/themes/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/themes/dark.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/theme/themes/light.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/visualization/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/visualization/assets.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/visualization/scene.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/codeeditor.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/collection_controls.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/controller_editor.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/custom_classes.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/editor.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/editor_pages.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/elided_label.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/file_dialogs.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/finite_element.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/helper_editor.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/language_selector.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/param_form.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/plot_style.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/preview_renderer.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/quickstart.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/result_path.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/run_dialogs.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/source_view.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/template_insert_dialog.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/theme_selector.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/ui/widgets/values.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/utils/__init__.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/utils/gradient_check.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt/utils/history_read.py +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/dependency_links.txt +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/entry_points.txt +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/requires.txt +0 -0
- {morphopt-4.0.2 → morphopt-4.0.3}/src/morphopt.egg-info/top_level.txt +0 -0
|
@@ -18,7 +18,6 @@ class _Constants:
|
|
|
18
18
|
|
|
19
19
|
class HISTORY:
|
|
20
20
|
MAX_NODES = 100
|
|
21
|
-
MAX_SNAPSHOT_BYTES = 67108864
|
|
22
21
|
|
|
23
22
|
class OBSERVER:
|
|
24
23
|
OUTPUT_MAXIMUM_BLOCKS = 10000
|
|
@@ -29,8 +28,6 @@ class _Constants:
|
|
|
29
28
|
DEFAULT_INTER_SURFACE_DISTANCE = 2.5
|
|
30
29
|
|
|
31
30
|
class WIDGETS:
|
|
32
|
-
MINIMUM_LOAD_STEPS = 1
|
|
33
|
-
MAXIMUM_LOAD_STEPS = 200
|
|
34
31
|
MAXIMUM_QUICKSTART_OBJECTIVES = 8
|
|
35
32
|
MODEL_SCAN_DEBOUNCE_MS = 500
|
|
36
33
|
|
|
@@ -487,7 +484,7 @@ class _Constants:
|
|
|
487
484
|
DEFAULT = 1.0
|
|
488
485
|
MIN = 0.0
|
|
489
486
|
|
|
490
|
-
class
|
|
487
|
+
class MESH_SURFACE:
|
|
491
488
|
class PATH_STL:
|
|
492
489
|
DEFAULT = ""
|
|
493
490
|
|
|
@@ -985,6 +982,7 @@ class _Constants:
|
|
|
985
982
|
class OBJECTIVE:
|
|
986
983
|
TYPE_DISPLACEMENT = "displacement"
|
|
987
984
|
TYPE_CUSTOM_LOSS = "custom_loss"
|
|
985
|
+
TYPE_WORKSPACE = "workspace"
|
|
988
986
|
DEFAULT_TYPE = TYPE_DISPLACEMENT
|
|
989
987
|
DIRECTION_TARGET = "target"
|
|
990
988
|
DIRECTION_MAXIMIZE = "maximize"
|
|
@@ -425,10 +425,11 @@ class Controller:
|
|
|
425
425
|
self.updater.save(
|
|
426
426
|
foldpath=self.path_result + "/log/", iteration=self.history.iteration
|
|
427
427
|
)
|
|
428
|
-
self.
|
|
428
|
+
self.objfun.save(
|
|
429
429
|
foldpath=self.path_result + "/log/", iteration=self.history.iteration
|
|
430
430
|
)
|
|
431
|
-
|
|
431
|
+
# Publish a completed iteration only after its model and results exist.
|
|
432
|
+
self.history.save(
|
|
432
433
|
foldpath=self.path_result + "/log/", iteration=self.history.iteration
|
|
433
434
|
)
|
|
434
435
|
|
|
@@ -35,8 +35,8 @@ class FEAParams(BaseParams):
|
|
|
35
35
|
"""
|
|
36
36
|
super().__init__()
|
|
37
37
|
|
|
38
|
-
self.fea_steps_params:
|
|
39
|
-
"""
|
|
38
|
+
self.fea_steps_params: dict[str, dict[str, list[float]]] = {}
|
|
39
|
+
"""Named load cases in registration order."""
|
|
40
40
|
|
|
41
41
|
def define_steps(self):
|
|
42
42
|
"""Configure the amplitude of every load in every load step."""
|
|
@@ -47,48 +47,23 @@ class FEAParams(BaseParams):
|
|
|
47
47
|
if not self.fea_steps_params:
|
|
48
48
|
self.define_steps()
|
|
49
49
|
|
|
50
|
-
def
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def set_step_params(
|
|
65
|
-
self, step_index: int, load_name: str, values: list[float]
|
|
66
|
-
) -> None:
|
|
67
|
-
"""
|
|
68
|
-
Set the load parameters for a specific load step.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
step_index (int): The index of the load step.
|
|
72
|
-
load_name (str): The name of the load interface.
|
|
73
|
-
values (list[float]): The load parameter values.
|
|
74
|
-
"""
|
|
75
|
-
if step_index < 0 or step_index >= len(self.fea_steps_params):
|
|
76
|
-
raise IndexError("step_index out of range.")
|
|
50
|
+
def add_step(self, name: str, values: dict[str, list[float]] | None = None) -> None:
|
|
51
|
+
"""Register a named load case in execution order."""
|
|
52
|
+
if not isinstance(name, str) or not name.strip() or any(char in name for char in ("/", "\\", "\0")):
|
|
53
|
+
raise ValueError("A load case requires a nonempty string name.")
|
|
54
|
+
if name in self.fea_steps_params:
|
|
55
|
+
raise ValueError(f"Duplicate load case: {name!r}")
|
|
56
|
+
self.fea_steps_params[name] = {
|
|
57
|
+
key: interface._values.copy() for key, interface in self.interfaces.items()
|
|
58
|
+
}
|
|
59
|
+
for load, amplitudes in (values or {}).items():
|
|
60
|
+
self.set_step_params(name, load, amplitudes)
|
|
61
|
+
|
|
62
|
+
def set_step_params(self, step_name: str, load_name: str, values: list[float]) -> None:
|
|
63
|
+
"""Assign load parameters to a registered named case."""
|
|
77
64
|
if load_name not in self.interfaces:
|
|
78
|
-
raise KeyError(f"
|
|
79
|
-
self.fea_steps_params[
|
|
80
|
-
|
|
81
|
-
def reinitialize(self, iteration, *args, **kwargs):
|
|
82
|
-
# Sort the load interfaces and load steps parameters by their keys
|
|
83
|
-
sorted_fea_interfaces = dict(sorted(self.interfaces.items()))
|
|
84
|
-
sorted_fea_steps_params = [
|
|
85
|
-
dict(sorted(step.items())) for step in self.fea_steps_params
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
# Update the dictionaries with the sorted versions
|
|
89
|
-
self.interfaces = sorted_fea_interfaces
|
|
90
|
-
self.fea_steps_params = sorted_fea_steps_params
|
|
91
|
-
super().reinitialize(iteration, *args, **kwargs)
|
|
65
|
+
raise KeyError(f"Unknown load: {load_name!r}")
|
|
66
|
+
self.fea_steps_params[step_name][load_name] = list(values)
|
|
92
67
|
|
|
93
68
|
@property
|
|
94
69
|
def num_load_steps(self) -> int:
|
|
@@ -123,15 +98,15 @@ class FEAParams(BaseParams):
|
|
|
123
98
|
|
|
124
99
|
return fe
|
|
125
100
|
|
|
126
|
-
def process_fea(self, fe: FEAController,
|
|
101
|
+
def process_fea(self, fe: FEAController, step_name: str) -> None:
|
|
127
102
|
"""
|
|
128
103
|
Process the FEA controller to update load-related information.
|
|
129
104
|
|
|
130
105
|
Args:
|
|
131
106
|
fe (FEAController): The FEA controller instance.
|
|
132
|
-
|
|
107
|
+
step_name (str): The registered load-case name.
|
|
133
108
|
"""
|
|
134
|
-
load_step_now = self.fea_steps_params[
|
|
109
|
+
load_step_now = self.fea_steps_params[step_name]
|
|
135
110
|
for name, load_interface in self.interfaces.items():
|
|
136
111
|
load_interface._values = load_step_now[name]
|
|
137
112
|
load_interface.apply_fea_value(fe, name)
|
|
@@ -139,10 +139,9 @@ class BasePart(BaseModelComponent):
|
|
|
139
139
|
)
|
|
140
140
|
|
|
141
141
|
def initialize(self, *args: object, **kwargs: object) -> None:
|
|
142
|
-
"""Declare
|
|
142
|
+
"""Declare Instances when absent, preserving existing declarations."""
|
|
143
143
|
super().initialize(*args, **kwargs)
|
|
144
|
-
self.
|
|
145
|
-
self.define_instance()
|
|
144
|
+
self._ensure_instance_definitions()
|
|
146
145
|
|
|
147
146
|
def build_part(
|
|
148
147
|
self, path_result: str | None = None, pools: Pool | None = None
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Shared CAD and closed-boundary Part construction."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
import json
|
|
7
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
8
|
+
from copy import deepcopy
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from functools import partial
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
13
|
+
|
|
14
|
+
import gmsh
|
|
15
|
+
import torchfea
|
|
16
|
+
from torchfea import cad as torchfea_cad
|
|
17
|
+
from torchfea.cad.builder import OCCBuilder
|
|
18
|
+
|
|
19
|
+
from ....constants import CONSTANT
|
|
20
|
+
from .base_part import BasePart
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from multiprocessing.pool import Pool
|
|
24
|
+
|
|
25
|
+
from ....shapeopt.boundary_part import BoundaryPart
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CADPart(BasePart):
|
|
29
|
+
"""Build a Part from an ordered sequence of TorchFEA CAD operations.
|
|
30
|
+
|
|
31
|
+
Subclasses override :meth:`define_operations` and add native TorchFEA CAD
|
|
32
|
+
objects with :meth:`add_operation`::
|
|
33
|
+
|
|
34
|
+
class Housing(morphopt.CADPart):
|
|
35
|
+
def define_operations(self) -> None:
|
|
36
|
+
body = torchfea.cad.Box(
|
|
37
|
+
id="body", x=0, y=0, z=0, dx=20, dy=12, dz=8
|
|
38
|
+
)
|
|
39
|
+
bore = torchfea.cad.Cylinder(
|
|
40
|
+
id="bore", x=10, y=6, z=0, dz=8, radius=2
|
|
41
|
+
)
|
|
42
|
+
self.add_operation(body)
|
|
43
|
+
self.add_operation(bore)
|
|
44
|
+
self.add_operation(
|
|
45
|
+
torchfea.cad.Cut(
|
|
46
|
+
id="housing", object_id="body", tool_id="bore"
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
Boolean and transform operations refer to earlier operations by their
|
|
51
|
+
IDs. The order in which :meth:`add_operation` is called is the CAD
|
|
52
|
+
history order. Every OCC face and its nodes are named automatically as
|
|
53
|
+
``face_N`` and ``face_N_nodes`` after meshing.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
cache_part = True
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class _Operation(torchfea_cad.CADOperation):
|
|
60
|
+
"""Bridge a Part method into TorchFEA's managed Gmsh execution session."""
|
|
61
|
+
|
|
62
|
+
action: Callable[[OCCBuilder], list[tuple[int, int]]]
|
|
63
|
+
kind: ClassVar[str] = "morphopt_geometry"
|
|
64
|
+
non_parameter_fields: ClassVar[tuple[str, ...]] = ("action",)
|
|
65
|
+
|
|
66
|
+
def apply_occ(self, context: OCCBuilder) -> list[tuple[int, int]]:
|
|
67
|
+
"""Execute the callback and return Gmsh (dimension, entity tag) pairs."""
|
|
68
|
+
return self.action(context)
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
mesh_size: float = CONSTANT.KERNEL.MESH.DEFAULT_CAD_MESH_SIZE,
|
|
73
|
+
mesh_order: int = CONSTANT.KERNEL.MESH.DEFAULT_ELEMENT_ORDER,
|
|
74
|
+
**kwargs: Any,
|
|
75
|
+
) -> None:
|
|
76
|
+
super().__init__(**kwargs)
|
|
77
|
+
if not math.isfinite(mesh_size) or mesh_size <= 0:
|
|
78
|
+
raise ValueError("mesh_size must be finite and positive.")
|
|
79
|
+
if mesh_order not in (1, 2):
|
|
80
|
+
raise ValueError("mesh_order must be 1 or 2.")
|
|
81
|
+
self.mesh_size = float(mesh_size)
|
|
82
|
+
self.mesh_order = mesh_order
|
|
83
|
+
self.operations: list[torchfea_cad.CADOperation] = []
|
|
84
|
+
self._face_names: dict[int, str] = {}
|
|
85
|
+
|
|
86
|
+
def define_operations(self) -> None:
|
|
87
|
+
"""Override to declare CAD operations in their execution order."""
|
|
88
|
+
|
|
89
|
+
def add_operation(self, operation: torchfea_cad.CADOperation) -> CADPart:
|
|
90
|
+
"""Append one native TorchFEA CAD operation to the history."""
|
|
91
|
+
if not isinstance(operation, torchfea_cad.CADOperation):
|
|
92
|
+
raise TypeError("operation must be a TorchFEA CADOperation.")
|
|
93
|
+
self.operations.append(operation)
|
|
94
|
+
return self
|
|
95
|
+
|
|
96
|
+
def initialize(self, *args: object, **kwargs: object) -> None:
|
|
97
|
+
"""Declare Instances and the CAD history once during initialization."""
|
|
98
|
+
super().initialize(*args, **kwargs)
|
|
99
|
+
self.operations.clear()
|
|
100
|
+
self.define_operations()
|
|
101
|
+
|
|
102
|
+
def prepare_geometry(
|
|
103
|
+
self, path_result: str | None = None, pools: Pool | None = None,
|
|
104
|
+
) -> list[torchfea_cad.CADOperation]:
|
|
105
|
+
"""Provide the declared CAD operations; subclasses prepare their inputs."""
|
|
106
|
+
self._face_names.clear()
|
|
107
|
+
return self.operations
|
|
108
|
+
|
|
109
|
+
def assemble_geometry(
|
|
110
|
+
self, geometry: Sequence[torchfea_cad.CADOperation],
|
|
111
|
+
) -> torchfea_cad.CADHistory:
|
|
112
|
+
"""Construct the native CAD history used by the shared mesher."""
|
|
113
|
+
return torchfea_cad.CADHistory(
|
|
114
|
+
operations=geometry,
|
|
115
|
+
mesh_size=self.mesh_size,
|
|
116
|
+
exterior_surface=self.exterior_surface or "extern",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def mesh_geometry(self, geometry: torchfea_cad.CADHistory) -> torchfea.Part:
|
|
120
|
+
"""Use TorchFEA's shared Gmsh mesh extraction for either geometry kernel."""
|
|
121
|
+
return geometry.mesh(self.mesh_size)
|
|
122
|
+
|
|
123
|
+
def define_mesh_sets(self, part: torchfea.Part) -> None:
|
|
124
|
+
"""Map geometric face identities onto the FE mesh."""
|
|
125
|
+
self._name_geometry_faces(part)
|
|
126
|
+
|
|
127
|
+
def _postprocess_part(self, part: torchfea.Part) -> torchfea.Part:
|
|
128
|
+
"""Extend the linear mesh, for example with offset-layer elements."""
|
|
129
|
+
return part
|
|
130
|
+
|
|
131
|
+
def postprocess_mesh(self, part: torchfea.Part) -> torchfea.Part:
|
|
132
|
+
"""Apply element extensions before the shared quadratic conversion."""
|
|
133
|
+
part = self._postprocess_part(part)
|
|
134
|
+
if self.mesh_order == 2:
|
|
135
|
+
element_names = list(part.elems)
|
|
136
|
+
part.convert_linear_to_quadratic_elements(element_names, element_names)
|
|
137
|
+
return part
|
|
138
|
+
|
|
139
|
+
def build_part(
|
|
140
|
+
self, path_result: str | None = None, pools: Pool | None = None,
|
|
141
|
+
) -> torchfea.Part:
|
|
142
|
+
"""Prepare, assemble, mesh, name and postprocess one Part."""
|
|
143
|
+
geometry = self.assemble_geometry(self.prepare_geometry(path_result, pools))
|
|
144
|
+
part = self.mesh_geometry(geometry)
|
|
145
|
+
self.define_mesh_sets(part)
|
|
146
|
+
return self.postprocess_mesh(part)
|
|
147
|
+
|
|
148
|
+
def _complete_face_names(self, tags: Iterable[int]) -> None:
|
|
149
|
+
"""Assign unused face names while retaining identities from source CAD."""
|
|
150
|
+
names = set(self._face_names.values())
|
|
151
|
+
index = 0
|
|
152
|
+
for tag in sorted(tags):
|
|
153
|
+
if tag in self._face_names:
|
|
154
|
+
continue
|
|
155
|
+
while f"face_{index}" in names:
|
|
156
|
+
index += 1
|
|
157
|
+
self._face_names[tag] = f"face_{index}"
|
|
158
|
+
names.add(f"face_{index}")
|
|
159
|
+
|
|
160
|
+
def _name_geometry_faces(self, part: torchfea.Part) -> None:
|
|
161
|
+
"""Name geometric boundary faces using source identities or tag order."""
|
|
162
|
+
face_names = [
|
|
163
|
+
name for name in part.surfaces.keys() if name.startswith("gmsh_surface_")
|
|
164
|
+
]
|
|
165
|
+
face_names.sort(key=lambda name: int(name.rsplit("_", 1)[1]))
|
|
166
|
+
|
|
167
|
+
tags = [int(name.rsplit("_", 1)[1]) for name in face_names]
|
|
168
|
+
self._complete_face_names(tags)
|
|
169
|
+
identities = {tag: self._face_names[tag] for tag in tags}
|
|
170
|
+
reserved_surfaces = set(identities.values())
|
|
171
|
+
reserved_nodes = {f"{name}_nodes" for name in reserved_surfaces}
|
|
172
|
+
surface_conflicts = reserved_surfaces.intersection(part.surfaces.keys())
|
|
173
|
+
node_conflicts = reserved_nodes.intersection(part.set_nodes)
|
|
174
|
+
if surface_conflicts or node_conflicts:
|
|
175
|
+
names = ", ".join(sorted(surface_conflicts | node_conflicts))
|
|
176
|
+
raise ValueError(
|
|
177
|
+
f"CAD set names {names} are reserved for OCC faces and nodes."
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
renamed_surfaces = type(part.surfaces)()
|
|
181
|
+
renamed_names = {
|
|
182
|
+
old: identities[int(old.rsplit("_", 1)[1])] for old in face_names
|
|
183
|
+
}
|
|
184
|
+
for old_name in part.surfaces.keys():
|
|
185
|
+
renamed_surfaces[renamed_names.get(old_name, old_name)] = part.surfaces[old_name]
|
|
186
|
+
part.surfaces = renamed_surfaces
|
|
187
|
+
|
|
188
|
+
for old_name in face_names:
|
|
189
|
+
tag = old_name.rsplit("_", 1)[1]
|
|
190
|
+
old_node_name = f"gmsh_nodes_{tag}"
|
|
191
|
+
name = identities[int(tag)]
|
|
192
|
+
nodes = part.set_nodes[old_node_name]
|
|
193
|
+
|
|
194
|
+
part.delete_node_set(old_node_name)
|
|
195
|
+
part.add_node_set(f"{name}_nodes", nodes)
|
|
196
|
+
|
|
197
|
+
def get_face_names(self) -> list[str]:
|
|
198
|
+
"""Return geometric face identities in the preview's face order."""
|
|
199
|
+
history = self.assemble_geometry(self.prepare_geometry())
|
|
200
|
+
if not history.operations:
|
|
201
|
+
return []
|
|
202
|
+
records = history.preview()
|
|
203
|
+
self._complete_face_names(record["surface_tag"] for record in records)
|
|
204
|
+
return [self._face_names[record["surface_tag"]] for record in records]
|
|
205
|
+
|
|
206
|
+
@staticmethod
|
|
207
|
+
def _single_solid(
|
|
208
|
+
operations: Sequence[torchfea_cad.CADOperation],
|
|
209
|
+
) -> tuple[int, list[int]]:
|
|
210
|
+
"""Replay operations and return the single volume tag and sorted face tags."""
|
|
211
|
+
volumes = OCCBuilder(gmsh).replay(operations)
|
|
212
|
+
if len(volumes) != 1:
|
|
213
|
+
raise ValueError(
|
|
214
|
+
"CAD-to-boundary conversion requires one final solid. "
|
|
215
|
+
"Define disconnected solids as separate CADParts."
|
|
216
|
+
)
|
|
217
|
+
volume = volumes[0]
|
|
218
|
+
faces = sorted(tag for _, tag in gmsh.model.getBoundary(
|
|
219
|
+
[(3, volume)], oriented=False, recursive=False,
|
|
220
|
+
))
|
|
221
|
+
return volume, faces
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
def load_step_boundary(path: str | Path, context: OCCBuilder) -> list[int]:
|
|
225
|
+
"""Import a STEP boundary while retaining its geometric faces."""
|
|
226
|
+
before = set(gmsh.model.getEntities(2))
|
|
227
|
+
imported = context.occ.importShapes(str(path))
|
|
228
|
+
context.occ.synchronize()
|
|
229
|
+
volumes = [(dim, tag) for dim, tag in imported if dim == 3]
|
|
230
|
+
if volumes:
|
|
231
|
+
context.occ.remove(volumes, recursive=False)
|
|
232
|
+
context.occ.synchronize()
|
|
233
|
+
tags = sorted(tag for _, tag in set(gmsh.model.getEntities(2)) - before)
|
|
234
|
+
if not tags:
|
|
235
|
+
raise ValueError(f"No boundary faces in {path}")
|
|
236
|
+
return tags
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def load_mesh_boundary(path: str | Path) -> list[int]:
|
|
240
|
+
"""Import a closed STL boundary into the active geometry session."""
|
|
241
|
+
before = set(gmsh.model.getEntities(2))
|
|
242
|
+
gmsh.merge(str(path))
|
|
243
|
+
tags = sorted(tag for _, tag in set(gmsh.model.getEntities(2)) - before)
|
|
244
|
+
if not tags:
|
|
245
|
+
raise ValueError(f"No boundary faces in {path}")
|
|
246
|
+
return tags
|
|
247
|
+
|
|
248
|
+
@staticmethod
|
|
249
|
+
def get_face_signature(tag: int) -> list[float]:
|
|
250
|
+
"""Describe a face by its area, centroid and inertia in local coordinates."""
|
|
251
|
+
return [
|
|
252
|
+
float(gmsh.model.occ.getMass(2, tag)),
|
|
253
|
+
*map(float, gmsh.model.occ.getCenterOfMass(2, tag)),
|
|
254
|
+
*map(float, gmsh.model.occ.getMatrixOfInertia(2, tag)),
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def _inspect_shells(
|
|
259
|
+
context: OCCBuilder,
|
|
260
|
+
*,
|
|
261
|
+
operations: tuple[torchfea_cad.CADOperation, ...],
|
|
262
|
+
shells: list[tuple[int, ...]],
|
|
263
|
+
) -> list[tuple[int, int]]:
|
|
264
|
+
"""Fill shell face indices in exterior-first order inside a CAD session."""
|
|
265
|
+
volume, faces = CADPart._single_solid(operations)
|
|
266
|
+
indices = {tag: index for index, tag in enumerate(faces)}
|
|
267
|
+
loops, shell_faces = context.occ.getSurfaceLoops(volume)
|
|
268
|
+
ordered = []
|
|
269
|
+
for loop, tags in zip(loops, shell_faces):
|
|
270
|
+
solid = context.occ.addVolume([int(loop)])
|
|
271
|
+
mass = abs(context.occ.getMass(3, solid))
|
|
272
|
+
context.occ.remove([(3, solid)], recursive=False)
|
|
273
|
+
ordered.append((mass, tuple(sorted(indices[abs(int(tag))] for tag in tags))))
|
|
274
|
+
context.occ.synchronize()
|
|
275
|
+
ordered.sort(key=lambda item: (-item[0], item[1]))
|
|
276
|
+
shells[:] = [indices for _, indices in ordered]
|
|
277
|
+
if not shells:
|
|
278
|
+
raise ValueError("The CAD solid has no closed boundary shell.")
|
|
279
|
+
return [(3, volume)]
|
|
280
|
+
|
|
281
|
+
def split_shells(self) -> tuple[
|
|
282
|
+
tuple[torchfea_cad.CADOperation, ...],
|
|
283
|
+
list[tuple[int, ...]],
|
|
284
|
+
]:
|
|
285
|
+
"""Return copied operations and exterior-first closed-shell face indices.
|
|
286
|
+
|
|
287
|
+
Each shell contains zero-based indices into the source CAD face order.
|
|
288
|
+
"""
|
|
289
|
+
operations = tuple(deepcopy(self.operations))
|
|
290
|
+
if not operations:
|
|
291
|
+
raise ValueError("The CAD history is empty; initialize the CADPart before conversion.")
|
|
292
|
+
shells: list[tuple[int, ...]] = []
|
|
293
|
+
action = partial(self._inspect_shells, operations=operations, shells=shells)
|
|
294
|
+
torchfea_cad.preview_brep([self._Operation("inspect", action)])
|
|
295
|
+
return operations, shells
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _build_shell(
|
|
299
|
+
context: OCCBuilder,
|
|
300
|
+
*,
|
|
301
|
+
operations: tuple[torchfea_cad.CADOperation, ...],
|
|
302
|
+
face_indices: tuple[int, ...],
|
|
303
|
+
face_map: dict[str, list[float]],
|
|
304
|
+
) -> list[tuple[int, int]]:
|
|
305
|
+
"""Reconstruct one shell as a solid for standalone STEP export."""
|
|
306
|
+
volume, faces = CADPart._single_solid(operations)
|
|
307
|
+
context.occ.remove([(3, volume)], recursive=False)
|
|
308
|
+
selected = [faces[index] for index in face_indices]
|
|
309
|
+
loop = context.occ.addSurfaceLoop(selected)
|
|
310
|
+
solid = context.occ.addVolume([loop])
|
|
311
|
+
context.occ.synchronize()
|
|
312
|
+
unused = [(2, tag) for tag in faces if tag not in selected]
|
|
313
|
+
if unused:
|
|
314
|
+
context.occ.remove(unused, recursive=True)
|
|
315
|
+
context.occ.synchronize()
|
|
316
|
+
face_map.update({
|
|
317
|
+
f"face_{index}": CADPart.get_face_signature(tag)
|
|
318
|
+
for index, tag in zip(face_indices, selected)
|
|
319
|
+
})
|
|
320
|
+
return [(3, solid)]
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def export_shell_step(
|
|
324
|
+
operations: tuple[torchfea_cad.CADOperation, ...],
|
|
325
|
+
face_indices: tuple[int, ...],
|
|
326
|
+
path: str | Path,
|
|
327
|
+
) -> None:
|
|
328
|
+
"""Export a closed shell and its original face identities as STEP assets."""
|
|
329
|
+
path = Path(path)
|
|
330
|
+
face_map: dict[str, list[float]] = {}
|
|
331
|
+
action = partial(
|
|
332
|
+
CADPart._build_shell, operations=operations,
|
|
333
|
+
face_indices=face_indices, face_map=face_map,
|
|
334
|
+
)
|
|
335
|
+
torchfea_cad.export_step([CADPart._Operation("shell", action)], str(path))
|
|
336
|
+
path.with_suffix(".faces.json").write_text(
|
|
337
|
+
json.dumps(face_map, indent=2), encoding="utf-8",
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
def to_boundary_part(
|
|
341
|
+
self, asset_directory: str | Path = "morphopt_assets",
|
|
342
|
+
) -> BoundaryPart:
|
|
343
|
+
"""Export an initialized single-solid CAD Part as fixed STEP boundaries.
|
|
344
|
+
|
|
345
|
+
Write one STEP and face map per shell under asset_directory/part_name.
|
|
346
|
+
Preserve original face identities, mesh settings and Instance poses;
|
|
347
|
+
the resulting surfaces retain file references, not source CAD history.
|
|
348
|
+
"""
|
|
349
|
+
from ....shapeopt.boundary_part import BoundaryPart
|
|
350
|
+
from ....shapeopt.surfaces.brep_surface import BREPSurface
|
|
351
|
+
|
|
352
|
+
operations, shells = self.split_shells()
|
|
353
|
+
directory = Path(asset_directory) / self.part_name
|
|
354
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
355
|
+
boundary = BoundaryPart(
|
|
356
|
+
fea_seed_size=self.mesh_size,
|
|
357
|
+
mesh_order=self.mesh_order,
|
|
358
|
+
part_name=self.part_name,
|
|
359
|
+
exterior_surface=self.exterior_surface or "extern",
|
|
360
|
+
)
|
|
361
|
+
boundary._name = self.name
|
|
362
|
+
boundary._instance_definitions = deepcopy(self._instance_definitions)
|
|
363
|
+
for index, face_indices in enumerate(shells):
|
|
364
|
+
path = directory / f"surface_{index}.stp"
|
|
365
|
+
self.export_shell_step(operations, face_indices, path)
|
|
366
|
+
surface = BREPSurface(path)
|
|
367
|
+
surface.flip = index != 0
|
|
368
|
+
boundary.add_surface_interface(surface)
|
|
369
|
+
boundary.initialize()
|
|
370
|
+
return boundary
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from copy import deepcopy
|
|
2
2
|
import math
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
3
5
|
import torchfea
|
|
4
6
|
import torch
|
|
5
7
|
import morphopt
|
|
@@ -19,7 +21,7 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
19
21
|
The FEA solver instance.
|
|
20
22
|
"""
|
|
21
23
|
|
|
22
|
-
self.fe_results:
|
|
24
|
+
self.fe_results: dict[str, torchfea.solver.StaticResult] = {}
|
|
23
25
|
"""
|
|
24
26
|
The FEA results for each step.
|
|
25
27
|
"""
|
|
@@ -47,12 +49,12 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
47
49
|
"""
|
|
48
50
|
raise NotImplementedError("The objective_function method should be implemented in subclass.")
|
|
49
51
|
|
|
50
|
-
def set_step(self, step:
|
|
52
|
+
def set_step(self, step: str) -> None:
|
|
51
53
|
"""
|
|
52
54
|
Set the current step for the objective function. This can be used to update any step-specific parameters or states.
|
|
53
55
|
|
|
54
56
|
Args:
|
|
55
|
-
step (
|
|
57
|
+
step (str): The registered load-case name.
|
|
56
58
|
"""
|
|
57
59
|
self.fe.assembly.set_work_conditions(self.fe_results[step].work_conditions)
|
|
58
60
|
|
|
@@ -60,7 +62,7 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
60
62
|
"""
|
|
61
63
|
Compute the total objective from a multistep FE result list.
|
|
62
64
|
"""
|
|
63
|
-
self.fe_results = fe_results
|
|
65
|
+
self.fe_results = dict(zip(self.fe_results, fe_results, strict=True))
|
|
64
66
|
return self.objective_function()
|
|
65
67
|
|
|
66
68
|
def sensitivity_analysis(self, params: Params) -> dict[str, torch.Tensor]:
|
|
@@ -91,7 +93,7 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
91
93
|
solver: torchfea.solver.StaticImplicitSolver = self.fe.solver
|
|
92
94
|
|
|
93
95
|
design_gradients = solver.get_jacobian_sensitivity_multistep(
|
|
94
|
-
fe_results=self.fe_results,
|
|
96
|
+
fe_results=list(self.fe_results.values()),
|
|
95
97
|
design_vars=design_sensitivity_vars,
|
|
96
98
|
load_names=self.jacobian_needed,
|
|
97
99
|
apply_func=apply_func,
|
|
@@ -136,14 +138,14 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
136
138
|
except AttributeError:
|
|
137
139
|
raise KeyError(f"'{key}' not found in FE_result") from None
|
|
138
140
|
|
|
139
|
-
def get_mesh_case(self, case:
|
|
141
|
+
def get_mesh_case(self, case: str):
|
|
140
142
|
"""Return the meshes carrying the result of one load case."""
|
|
141
143
|
# Create pyvista mesh
|
|
142
144
|
mesh = list(self.fe.assembly.get_meshes(GC=self.fe_results[case].GC).values())
|
|
143
145
|
|
|
144
146
|
return mesh
|
|
145
147
|
|
|
146
|
-
def plot(self, case:
|
|
148
|
+
def plot(self, case: str) -> None:
|
|
147
149
|
"""
|
|
148
150
|
Plot the FEA results for a specific case.
|
|
149
151
|
|
|
@@ -186,7 +188,7 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
186
188
|
|
|
187
189
|
color_list = deepcopy(CONSTANT.KERNEL.VISUALIZATION.INSTANCE_COLORS)
|
|
188
190
|
|
|
189
|
-
for
|
|
191
|
+
for case_name in self.fe_results:
|
|
190
192
|
|
|
191
193
|
plotter = pv.Plotter(
|
|
192
194
|
off_screen=True,
|
|
@@ -195,7 +197,7 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
195
197
|
|
|
196
198
|
# Create faces list for pyvista
|
|
197
199
|
# Create pyvista mesh
|
|
198
|
-
meshes = self.get_mesh_case(
|
|
200
|
+
meshes = self.get_mesh_case(case_name)
|
|
199
201
|
|
|
200
202
|
for meshidx, mesh in enumerate(meshes):
|
|
201
203
|
plotter.add_mesh(mesh, color=color_list[meshidx % len(color_list)], opacity=1.0)
|
|
@@ -210,10 +212,10 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
210
212
|
math.sin(math.radians(elevation))))
|
|
211
213
|
|
|
212
214
|
# Save the figure as a PNG file
|
|
213
|
-
plotter.screenshot(f"{foldpath}/{self.pathlog_required()[0]}/task_{
|
|
215
|
+
plotter.screenshot(f"{foldpath}/{self.pathlog_required()[0]}/task_{case_name}_iter_{iteration}.png")
|
|
214
216
|
|
|
215
217
|
# # Save the deformed mesh as an STL file
|
|
216
|
-
save_filepath = f"{foldpath}/{self.pathlog_required()[0]}/task_{
|
|
218
|
+
save_filepath = f"{foldpath}/{self.pathlog_required()[0]}/task_{case_name}_iter_{iteration}.stl"
|
|
217
219
|
meshall = meshes[0]
|
|
218
220
|
for meshidx in range(1, len(meshes)):
|
|
219
221
|
meshall = meshall.merge(meshes[meshidx])
|
|
@@ -230,5 +232,9 @@ class ObjectiveFunction(ProtocalInitializable, ProtocalSavable):
|
|
|
230
232
|
f"{foldpath}/{self.pathlog_required()[1]}/femodel_{iteration}"
|
|
231
233
|
)
|
|
232
234
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
+
mapping = Path(f"{foldpath}/{self.pathlog_required()[1]}/load_cases.json")
|
|
236
|
+
temporary = mapping.with_suffix(".json.tmp")
|
|
237
|
+
temporary.write_text(json.dumps(list(self.fe_results), ensure_ascii=False), encoding="utf-8")
|
|
238
|
+
temporary.replace(mapping)
|
|
239
|
+
for case_name in self.fe_results:
|
|
240
|
+
self.fe_results[case_name].save(f"{foldpath}/{self.pathlog_required()[1]}/result_{case_name}_iter_{iteration}.npz")
|