Skip to content

feat: PyOpticL Layout export and STEP component preview#94

Open
beneaze wants to merge 12 commits into
mainfrom
feature/pyopticl-integration
Open

feat: PyOpticL Layout export and STEP component preview#94
beneaze wants to merge 12 commits into
mainfrom
feature/pyopticl-integration

Conversation

@beneaze

@beneaze beneaze commented May 11, 2026

Copy link
Copy Markdown
Member

Summary

  • PyOpticL Layout export (File -> Export PyOpticL Layout...): produces a self-contained folder shaped like the real PyOpticL/Designs/* folders: <name>.py + models/<stem>/<stem>.step + <stem>.json (identity transforms). Drops in directly to FreeCAD with the PyOpticL workbench. The script's only path reference is import_model("<stem>", directory="models"), resolved relative to the script file, so the folder is portable.
  • STEP attachment on import: when a STEP file is imported into a component (Component Editor), the path is stored on the ComponentRecord and persisted to the library so it can be re-emitted at export time. A warning dialog lists any components without an attached STEP before export.
  • STEP preview / renderer: new cad/ module with a STEP preview dialog and renderer used by the Component Editor.
  • Merge-readiness fixes added during review prep: deterministic component-library ordering, safer STEP file-picker cancellation, visible file-dialog tool buttons in both themes, a component-editor double-click scroll-jump guard, and a moved-folder PyOpticL script execution test.

Addresses #85, #70, and #38.

Test plan

  • pytest tests/export tests/core - 225 tests pass (28 in pyopticl exporter, 3 new for the folder layout)
  • uv run --extra dev pytest tests/core/test_component_registry.py tests/services/test_storage_service.py -q
  • uv run --extra dev pytest tests/export tests/core tests/services/test_storage_service.py tests/ui/test_component_editor.py::test_cancel_step_import_keeps_component_editor_open tests/ui/test_theme_stylesheets.py tests/ui/test_interface_tree_widget.py -q
  • uv run --extra dev pytest tests/export/test_pyopticl_exporter.py -q
  • uv run --extra dev ruff check src/optiverse/objects/definitions_loader.py src/optiverse/services/storage_service.py src/optiverse/ui/views/component_editor_dialog.py src/optiverse/ui/widgets/interface_widgets.py src/optiverse/ui/styles/light_theme.qss src/optiverse/ui/styles/dark_theme.qss src/optiverse/ui/theme_manager.py tests/core/test_component_registry.py tests/services/test_storage_service.py tests/ui/test_component_editor.py tests/ui/test_theme_stylesheets.py tests/ui/test_interface_tree_widget.py
  • Move/copy the exported folder elsewhere and re-run the exported script with stub PyOpticL modules - confirms import_model(..., directory="models") resolves the relocated models/<stem>/<stem>.step and .json files, with no absolute source/export paths embedded in the script
  • In FreeCAD with PyOpticL workbench: open the exported <name>.py and run it; verify each model loads from models/<stem>/<stem>.step (no "Model info file not found" or "Model not found in directory" errors)
  • Component Editor: import a STEP, save the component, re-open it, and verify the STEP is still attached (covered by storage round-trip test)
  • Export with one component missing a STEP - verify warning/skip behavior is represented in the generated script tests

Note: A FreeCAD.app is present locally, but its headless CLI/Python probes did not return reliably in this Codex environment, and PyOpticL workbench availability could not be confirmed. The FreeCAD runtime smoke therefore remains a manual reviewer check. The moved-folder behavior now has automated script-execution coverage without FreeCAD.

Generated with Claude Code and Codex.

beneaze and others added 12 commits May 6, 2026 13:58
When a component is created via STEP import in the Component Editor, the
original STEP file is now copied into a step/ subfolder within the
component's library folder and referenced in component.json. The path
propagates through definitions_loader, ComponentFactory, canvas sync,
type_registry serialization, and scene export so the PyOpticL exporter
can locate the 3-D model.

Also wires up the Export PyOpticL menu action in the main window.

Co-authored-by: Cursor <cursoragent@cursor.com>
# Conflicts:
#	src/optiverse/ui/views/component_editor_dialog.py
Previously the export wrote a loose .py file plus STEP files but omitted the
per-model <name>.json descriptor that PyOpticL.utils.import_model requires,
so models failed to load in FreeCAD. The exporter now writes a folder shaped
like real PyOpticL Designs:

    <export>/<export>.py
    <export>/models/<stem>/<stem>.step
    <export>/models/<stem>/<stem>.json   (identity transforms)

Stems are sanitised so the script's import_model("<stem>") always matches
the on-disk filename. The file dialog now picks a directory, and the menu
label changes from "Export PyOpticL Script" to "Export PyOpticL Layout".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add act_export_pyopticl to MainWindow typed-attribute list
- Add OCP.* and pyqtgraph.* to mypy ignore_missing_imports overrides
- cast() the np.ndarray return in _rotation_from_gl_view to satisfy
  warn_return_any
- Drop unused btn_export / btn_cancel locals in MissingStepWarningDialog
- Wrap a few over-100-char lines in step_renderer / step_preview_dialog /
  pyopticl_exporter
- Auto-fixes from `ruff --fix`: drop extraneous f-prefixes on literals,
  unused test imports, quote-style consistency

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VIEW_FRONT is intentionally a rotation matrix that maps a Z-up model
into pyqtgraph's camera frame for the "front" view (looking along -Y),
per the inline comment in step_renderer. It is not the identity, and
asserting it should be is wrong. Orthogonality and |det| = 1 are
already covered by the remaining tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cripts

- Resolve portable step_file_path (@component/..., @library/..., relative)
  back to absolute paths before export; fall back to scanning library roots
  by filename when the default resolution produces a non-existent path
- Use mesh class variable instead of shape() method for STEP-imported
  components, matching PyOpticL v2's import_model convention
- Wire metric option through to set_measurement_system("metric")
- Add fix_relative_imports() call for robust FreeCAD macro execution
- Add beam_block -> Stop() interface mapping
- Generate primitive-geometry definitions for no-STEP components with
  interfaces so beam path simulation still works
- Pre-compute beam splitter diagonal inside dim() instead of dim() * 1.414
- Add orientation note in generated JSON and script comments
- Show success dialog after export (consistent with other export formats)
- Change file dialog from directory picker to save-as for better UX
- Add diagnostic logging to export pipeline
- Update and expand test suite (36 tests)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Codex <noreply@openai.com>
Co-Authored-By: Codex <noreply@openai.com>
Execute the exported layout script from a moved folder with stub PyOpticL modules and assert import_model resolves the copied STEP and metadata under the relocated models directory.

Co-Authored-By: Codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants