Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ab3078d39 | |||
| 7588e2f303 |
-76
@@ -1,76 +0,0 @@
|
|||||||
*.xml
|
|
||||||
*.iml
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Python
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
*.so
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
|
|
||||||
# Virtual environments
|
|
||||||
.venv/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
|
|
||||||
# Lock files
|
|
||||||
uv.lock
|
|
||||||
|
|
||||||
# IDE
|
|
||||||
.vscode/
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
/src/fluency/Tesfiles/bordo_adapter3.fluency
|
|
||||||
/CONSTRAINT_STATUS_FINAL.md
|
|
||||||
/CONSTRAINT_STATUS_IMPLEMENTATION.md
|
|
||||||
/src/fluency/rendering/first.png
|
|
||||||
/littlebrother.md
|
|
||||||
/src/fluency/Tesfiles/multiboidy.fluency
|
|
||||||
/src/fluency/rendering/nromal_test.png
|
|
||||||
/package.json
|
|
||||||
/package-lock.json
|
|
||||||
/src/fluency/Screenshot 2026-06-28 at 17.57.52.png
|
|
||||||
/src/fluency/rendering/Screenshot 2026-07-12 at 16.54.14.png
|
|
||||||
/src/fluency/Screenshot 2026-07-26 at 20.23.42.png
|
|
||||||
/Screenshot 2026-08-05 at 10.14.15.png
|
|
||||||
/Screenshot 2026-08-05 at 11.15.33.png
|
|
||||||
/Screenshot 2026-08-05 at 11.22.42.png
|
|
||||||
/SURFACE_MODIFIER_PLAN.md
|
|
||||||
/test.step
|
|
||||||
/src/fluency/test333.step
|
|
||||||
/src/fluency/tests/test_arc_attached_to_rectangle.py
|
|
||||||
/src/fluency/tests/test_array_pattern.py
|
|
||||||
/src/fluency/tests/test_chamfer.py
|
|
||||||
/src/fluency/tests/test_circle_diameter_constraint.py
|
|
||||||
/tests/test_distance_constraint_picking.py
|
|
||||||
/tests/test_extrude_geometry.py
|
|
||||||
/src/fluency/tests/test_feature_replay.py
|
|
||||||
/src/fluency/tests/test_fillet.py
|
|
||||||
/tests/test_mirror.py
|
|
||||||
/test_modifier.py
|
|
||||||
/src/fluency/tests/test_projection_constraints.py
|
|
||||||
/tests/test_re_extrude.py
|
|
||||||
/scripts/test_render_zoom.py
|
|
||||||
/test_thread.py
|
|
||||||
/test_thread_hole.step
|
|
||||||
/test_thread_m3.step
|
|
||||||
/test_thread_m5_hole.step
|
|
||||||
/test_thread_shaft.step
|
|
||||||
/test_thread_tilted.step
|
|
||||||
/src/fluency/testpart.step
|
|
||||||
-23
@@ -1,23 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<profile version="1.0">
|
|
||||||
<option name="myName" value="Project Default" />
|
|
||||||
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
|
||||||
<option name="ignoredPackages">
|
|
||||||
<value>
|
|
||||||
<list size="10">
|
|
||||||
<item index="0" class="java.lang.String" itemvalue="python-rtmidi" />
|
|
||||||
<item index="1" class="java.lang.String" itemvalue="PyAudio" />
|
|
||||||
<item index="2" class="java.lang.String" itemvalue="rtmidi" />
|
|
||||||
<item index="3" class="java.lang.String" itemvalue="scikit-image" />
|
|
||||||
<item index="4" class="java.lang.String" itemvalue="python" />
|
|
||||||
<item index="5" class="java.lang.String" itemvalue="PySide6" />
|
|
||||||
<item index="6" class="java.lang.String" itemvalue="PySide6-Essentials" />
|
|
||||||
<item index="7" class="java.lang.String" itemvalue="PySide6-Addons" />
|
|
||||||
<item index="8" class="java.lang.String" itemvalue="lazy_loader" />
|
|
||||||
<item index="9" class="java.lang.String" itemvalue="typing_extensions" />
|
|
||||||
</list>
|
|
||||||
</value>
|
|
||||||
</option>
|
|
||||||
</inspection_tool>
|
|
||||||
</profile>
|
|
||||||
</component>
|
|
||||||
-6
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
Generated
-10
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Black">
|
|
||||||
<option name="sdkName" value="Python 3.11 (fluency)" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (fluency)" project-jdk-type="Python SDK" />
|
|
||||||
<component name="PyCharmProfessionalAdvertiser">
|
|
||||||
<option name="shown" value="true" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-8
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/fluency-cad.iml" filepath="$PROJECT_DIR$/.idea/fluency-cad.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-6
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
Generated
-569
@@ -1,569 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="AutoImportSettings">
|
|
||||||
<option name="autoReloadType" value="SELECTIVE" />
|
|
||||||
</component>
|
|
||||||
<component name="ChangeListManager">
|
|
||||||
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="Fiexed highlighting of operations">
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/geometry_occ/sketch.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/io/project_io.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/io/project_io.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/models/data_model.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/models/data_model.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/rendering/occ_renderer.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/main_window.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/main_window.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/render_window.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/render_window.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/sketch_widget.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/sketch_widget.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/technical_drawing_widget.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/technical_drawing_widget.py" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/src/fluency/ui/viewer_widget.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/ui/viewer_widget.py" afterDir="false" />
|
|
||||||
</list>
|
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
||||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
||||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
||||||
</component>
|
|
||||||
<component name="FileTemplateManagerImpl">
|
|
||||||
<option name="RECENT_TEMPLATES">
|
|
||||||
<list>
|
|
||||||
<option value="Python Script" />
|
|
||||||
</list>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="Git.Settings">
|
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
|
||||||
<map>
|
|
||||||
<entry key="$PROJECT_DIR$" value="feature/surface-modifier-pyramid-patterns" />
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
|
||||||
<option name="ROOT_SYNC" value="DONT_SYNC" />
|
|
||||||
</component>
|
|
||||||
<component name="MarkdownSettingsMigration">
|
|
||||||
<option name="stateVersion" value="1" />
|
|
||||||
</component>
|
|
||||||
<component name="ProblemsViewState">
|
|
||||||
<option name="selectedTabId" value="QODANA_PROBLEMS_VIEW_TAB" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectColorInfo">{
|
|
||||||
"associatedIndex": 6
|
|
||||||
}</component>
|
|
||||||
<component name="ProjectId" id="2aDywQvESFCKbJK4JUVHIhkN4S6" />
|
|
||||||
<component name="ProjectViewState">
|
|
||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
|
||||||
<option name="showLibraryContents" value="true" />
|
|
||||||
</component>
|
|
||||||
<component name="PropertiesComponent">{
|
|
||||||
"keyToString": {
|
|
||||||
"Python.2dtest.executor": "Run",
|
|
||||||
"Python.3d_windows.executor": "Run",
|
|
||||||
"Python.Unnamed.executor": "Run",
|
|
||||||
"Python.base.executor": "Run",
|
|
||||||
"Python.data_model.executor": "Run",
|
|
||||||
"Python.debug_dragging.executor": "Run",
|
|
||||||
"Python.draw_widget2d.executor": "Run",
|
|
||||||
"Python.draw_widget_solve.executor": "Run",
|
|
||||||
"Python.fluency.executor": "Run",
|
|
||||||
"Python.fluencyb.executor": "Run",
|
|
||||||
"Python.gl_widget.executor": "Run",
|
|
||||||
"Python.gui_ui.executor": "Run",
|
|
||||||
"Python.kernel.executor": "Run",
|
|
||||||
"Python.main.executor": "Run",
|
|
||||||
"Python.main_window.executor": "Run",
|
|
||||||
"Python.meshtest.executor": "Run",
|
|
||||||
"Python.occ_renderer.executor": "Run",
|
|
||||||
"Python.occ_to_mesh.executor": "Run",
|
|
||||||
"Python.render_backend.executor": "Run",
|
|
||||||
"Python.side_fluency.executor": "Run",
|
|
||||||
"Python.simple_mesh.executor": "Run",
|
|
||||||
"Python.sketch.executor": "Run",
|
|
||||||
"Python.technical_drawing_widget.executor": "Run",
|
|
||||||
"Python.vtk_widget.executor": "Run",
|
|
||||||
"Python.vulkan.executor": "Run",
|
|
||||||
"RunOnceActivity.OpenProjectViewOnStart": "true",
|
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
||||||
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
|
||||||
"RunOnceActivity.typescript.service.memoryLimit.init": "true",
|
|
||||||
"codeWithMe.voiceChat.enabledByDefault": "false",
|
|
||||||
"git-widget-placeholder": "feature/occ-migration",
|
|
||||||
"last_opened_file_path": "/Volumes/Data_drive/Programming/fluency/src/fluency",
|
|
||||||
"node.js.detected.package.eslint": "true",
|
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
|
||||||
"nodejs_package_manager_path": "npm",
|
|
||||||
"settings.editor.selected.configurable": "project.propVCSSupport.DirectoryMappings"
|
|
||||||
}
|
|
||||||
}</component>
|
|
||||||
<component name="QodanaReportsService">
|
|
||||||
<option name="descriptions">
|
|
||||||
<ReportDescription localRun="true" path="/private/var/folders/kg/zm48w_r96yb68mlbzvb9gtq40000gn/T/qodana_output/qodana.sarif.json" reportGuid="5f5b823c-c594-48c5-ae1f-062e30303918" reportId="fluency/qodana/2024-02-04" />
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="RecentsManager">
|
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
|
||||||
<recent name="$PROJECT_DIR$/src/fluency" />
|
|
||||||
<recent name="$PROJECT_DIR$" />
|
|
||||||
<recent name="$PROJECT_DIR$/src/fluency/Tesfiles" />
|
|
||||||
<recent name="$PROJECT_DIR$/src/fluency/rendering" />
|
|
||||||
<recent name="$PROJECT_DIR$/drawing_modules" />
|
|
||||||
</key>
|
|
||||||
<key name="MoveFile.RECENT_KEYS">
|
|
||||||
<recent name="$PROJECT_DIR$" />
|
|
||||||
</key>
|
|
||||||
</component>
|
|
||||||
<component name="SharedIndexes">
|
|
||||||
<attachedChunks>
|
|
||||||
<set>
|
|
||||||
<option value="bundled-python-sdk-c59985aa861c-c2ffad84badb-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-261.24374.152" />
|
|
||||||
</set>
|
|
||||||
</attachedChunks>
|
|
||||||
</component>
|
|
||||||
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
|
||||||
<component name="TaskManager">
|
|
||||||
<task active="true" id="Default" summary="Default task">
|
|
||||||
<changelist id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="" />
|
|
||||||
<created>1703867682707</created>
|
|
||||||
<option name="number" value="Default" />
|
|
||||||
<option name="presentableId" value="Default" />
|
|
||||||
<updated>1703867682707</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00007" summary="- changing compos for sketches works">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735652081552</created>
|
|
||||||
<option name="number" value="00007" />
|
|
||||||
<option name="presentableId" value="LOCAL-00007" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735652081552</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00008" summary="- changing compos including sketches and bodies">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735662119176</created>
|
|
||||||
<option name="number" value="00008" />
|
|
||||||
<option name="presentableId" value="LOCAL-00008" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735662119176</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00009" summary="- Drawing bodys depending on the selected compo - Cut working - Edit sketch working">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735685300102</created>
|
|
||||||
<option name="number" value="00009" />
|
|
||||||
<option name="presentableId" value="LOCAL-00009" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735685300102</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00010" summary="- delete sketch working - added mid point snap - added hovering line with distance">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735763743346</created>
|
|
||||||
<option name="number" value="00010" />
|
|
||||||
<option name="presentableId" value="LOCAL-00010" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735763743346</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00011" summary="- Added new buttons and settings">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735825176611</created>
|
|
||||||
<option name="number" value="00011" />
|
|
||||||
<option name="presentableId" value="LOCAL-00011" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735825176611</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00012" summary="- Added new buttons and settings">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1735842523870</created>
|
|
||||||
<option name="number" value="00012" />
|
|
||||||
<option name="presentableId" value="LOCAL-00012" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1735842523870</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00013" summary="- Added construction lines switching - Moved callbacks into sketchwidget from main. - Changed reset on right click">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1739739664763</created>
|
|
||||||
<option name="number" value="00013" />
|
|
||||||
<option name="presentableId" value="LOCAL-00013" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1739739664763</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00014" summary="- Added contrain displayed next to line - Slight change to point check from solver.">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1743193041868</created>
|
|
||||||
<option name="number" value="00014" />
|
|
||||||
<option name="presentableId" value="LOCAL-00014" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1743193041868</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00015" summary="- Added enabling of midpsnap and prepared others - Show dimesnion on hover">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1743284173326</created>
|
|
||||||
<option name="number" value="00015" />
|
|
||||||
<option name="presentableId" value="LOCAL-00015" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1743284173326</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00016" summary="- Fixed redraw when component changed">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1744555255868</created>
|
|
||||||
<option name="number" value="00016" />
|
|
||||||
<option name="presentableId" value="LOCAL-00016" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1744555255868</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00017" summary="- added MIT license">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1748764814845</created>
|
|
||||||
<option name="number" value="00017" />
|
|
||||||
<option name="presentableId" value="LOCAL-00017" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1748764814845</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00018" summary="- added screenshot">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1748765318267</created>
|
|
||||||
<option name="number" value="00018" />
|
|
||||||
<option name="presentableId" value="LOCAL-00018" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1748765318267</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00019" summary="- added sdf folder ( doesnt work via pip or git=)">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1755369224187</created>
|
|
||||||
<option name="number" value="00019" />
|
|
||||||
<option name="presentableId" value="LOCAL-00019" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1755369224187</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00020" summary="- Tons of addtions">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1782673954850</created>
|
|
||||||
<option name="number" value="00020" />
|
|
||||||
<option name="presentableId" value="LOCAL-00020" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1782673954850</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00021" summary="- Tons of addtions">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1782679912834</created>
|
|
||||||
<option name="number" value="00021" />
|
|
||||||
<option name="presentableId" value="LOCAL-00021" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1782679912834</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00022" summary="- Basic operations">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1782768610475</created>
|
|
||||||
<option name="number" value="00022" />
|
|
||||||
<option name="presentableId" value="LOCAL-00022" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1782768610475</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00023" summary="- removed cadquery deoendency">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1782928990792</created>
|
|
||||||
<option name="number" value="00023" />
|
|
||||||
<option name="presentableId" value="LOCAL-00023" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1782928990792</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00024" summary="- sketch enhacements">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783108151675</created>
|
|
||||||
<option name="number" value="00024" />
|
|
||||||
<option name="presentableId" value="LOCAL-00024" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783108151676</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00025" summary="- sketch enhacements">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783159860774</created>
|
|
||||||
<option name="number" value="00025" />
|
|
||||||
<option name="presentableId" value="LOCAL-00025" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783159860774</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00026" summary="- UI refinement, button position ui file as source no dirty drafting anymore">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783174566362</created>
|
|
||||||
<option name="number" value="00026" />
|
|
||||||
<option name="presentableId" value="LOCAL-00026" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783174566362</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00027" summary="- assembly draft">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783239410744</created>
|
|
||||||
<option name="number" value="00027" />
|
|
||||||
<option name="presentableId" value="LOCAL-00027" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783239410744</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00028" summary="- assembly draft">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783272988957</created>
|
|
||||||
<option name="number" value="00028" />
|
|
||||||
<option name="presentableId" value="LOCAL-00028" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783272988957</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00029" summary="- Added save file foramt - Split main.py refactor">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783282570014</created>
|
|
||||||
<option name="number" value="00029" />
|
|
||||||
<option name="presentableId" value="LOCAL-00029" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783282570014</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00030" summary="- Added save file foramt - Split main.py refactor">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783453889199</created>
|
|
||||||
<option name="number" value="00030" />
|
|
||||||
<option name="presentableId" value="LOCAL-00030" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783453889199</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00031" summary="- Added save file foramt - Split main.py refactor">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783456842297</created>
|
|
||||||
<option name="number" value="00031" />
|
|
||||||
<option name="presentableId" value="LOCAL-00031" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783456842297</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00032" summary="- Added save file foramt - Split main.py refactor">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783755278516</created>
|
|
||||||
<option name="number" value="00032" />
|
|
||||||
<option name="presentableId" value="LOCAL-00032" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783755278516</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00033" summary="- Added save file foramt - Split main.py refactor">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783777171864</created>
|
|
||||||
<option name="number" value="00033" />
|
|
||||||
<option name="presentableId" value="LOCAL-00033" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783777171864</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00034" summary="- Working assembly multi :)">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783798201133</created>
|
|
||||||
<option name="number" value="00034" />
|
|
||||||
<option name="presentableId" value="LOCAL-00034" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783798201133</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00035" summary="- Working assembly multi :)">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783798929531</created>
|
|
||||||
<option name="number" value="00035" />
|
|
||||||
<option name="presentableId" value="LOCAL-00035" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783798929531</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00036" summary="- added renderer">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783887682052</created>
|
|
||||||
<option name="number" value="00036" />
|
|
||||||
<option name="presentableId" value="LOCAL-00036" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783887682052</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00037" summary="- added renderer - Added undo">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783887704766</created>
|
|
||||||
<option name="number" value="00037" />
|
|
||||||
<option name="presentableId" value="LOCAL-00037" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783887704766</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00038" summary="- added renderer - Added undo">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783891561116</created>
|
|
||||||
<option name="number" value="00038" />
|
|
||||||
<option name="presentableId" value="LOCAL-00038" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783891561116</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00039" summary="- added renderer - Added undo">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783918463034</created>
|
|
||||||
<option name="number" value="00039" />
|
|
||||||
<option name="presentableId" value="LOCAL-00039" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783918463034</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00040" summary="- Render improvements, camera plane, update">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1783976498520</created>
|
|
||||||
<option name="number" value="00040" />
|
|
||||||
<option name="presentableId" value="LOCAL-00040" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1783976498520</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00041" summary="- added "measurement lines"">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785094789247</created>
|
|
||||||
<option name="number" value="00041" />
|
|
||||||
<option name="presentableId" value="LOCAL-00041" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785094789248</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00042" summary="- added "measurement lines"">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785697123545</created>
|
|
||||||
<option name="number" value="00042" />
|
|
||||||
<option name="presentableId" value="LOCAL-00042" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785697123545</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00043" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785701099363</created>
|
|
||||||
<option name="number" value="00043" />
|
|
||||||
<option name="presentableId" value="LOCAL-00043" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785701099363</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00044" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785915859137</created>
|
|
||||||
<option name="number" value="00044" />
|
|
||||||
<option name="presentableId" value="LOCAL-00044" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785915859137</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00045" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785941246695</created>
|
|
||||||
<option name="number" value="00045" />
|
|
||||||
<option name="presentableId" value="LOCAL-00045" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785941246695</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00046" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1785948828454</created>
|
|
||||||
<option name="number" value="00046" />
|
|
||||||
<option name="presentableId" value="LOCAL-00046" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1785948828454</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00047" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786110295218</created>
|
|
||||||
<option name="number" value="00047" />
|
|
||||||
<option name="presentableId" value="LOCAL-00047" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786110295218</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00048" summary="- arc improvements, fillets, operations, bodys">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786125317688</created>
|
|
||||||
<option name="number" value="00048" />
|
|
||||||
<option name="presentableId" value="LOCAL-00048" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786125317688</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00049" summary="- Operation highlighting, body highlighting">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786180549405</created>
|
|
||||||
<option name="number" value="00049" />
|
|
||||||
<option name="presentableId" value="LOCAL-00049" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786180549405</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00050" summary="- tech draw draft">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786888483687</created>
|
|
||||||
<option name="number" value="00050" />
|
|
||||||
<option name="presentableId" value="LOCAL-00050" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786888483688</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00051" summary="- tech draw draft v2">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786910497589</created>
|
|
||||||
<option name="number" value="00051" />
|
|
||||||
<option name="presentableId" value="LOCAL-00051" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786910497589</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00052" summary="- tech draw draft v2">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786984519780</created>
|
|
||||||
<option name="number" value="00052" />
|
|
||||||
<option name="presentableId" value="LOCAL-00052" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786984519781</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00053" summary="- tech draw draft v2">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1786995561375</created>
|
|
||||||
<option name="number" value="00053" />
|
|
||||||
<option name="presentableId" value="LOCAL-00053" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1786995561376</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00054" summary="- tech drawing and render improv">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1787058413911</created>
|
|
||||||
<option name="number" value="00054" />
|
|
||||||
<option name="presentableId" value="LOCAL-00054" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1787058413911</updated>
|
|
||||||
</task>
|
|
||||||
<task id="LOCAL-00055" summary="Fiexed highlighting of operations">
|
|
||||||
<option name="closed" value="true" />
|
|
||||||
<created>1787091089061</created>
|
|
||||||
<option name="number" value="00055" />
|
|
||||||
<option name="presentableId" value="LOCAL-00055" />
|
|
||||||
<option name="project" value="LOCAL" />
|
|
||||||
<updated>1787091089061</updated>
|
|
||||||
</task>
|
|
||||||
<option name="localTasksCounter" value="56" />
|
|
||||||
<servers />
|
|
||||||
</component>
|
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
|
||||||
<option name="version" value="3" />
|
|
||||||
</component>
|
|
||||||
<component name="Vcs.Log.Tabs.Properties">
|
|
||||||
<option name="TAB_STATES">
|
|
||||||
<map>
|
|
||||||
<entry key="MAIN">
|
|
||||||
<value>
|
|
||||||
<State />
|
|
||||||
</value>
|
|
||||||
</entry>
|
|
||||||
</map>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
<component name="VcsManagerConfiguration">
|
|
||||||
<ignored-roots>
|
|
||||||
<path value="$PROJECT_DIR$/pythonProject" />
|
|
||||||
</ignored-roots>
|
|
||||||
<MESSAGE value="- Added construction lines switching - Moved callbacks into sketchwidget from main. - Changed reset on right click" />
|
|
||||||
<MESSAGE value="- Added contrain displayed next to line - Slight change to point check from solver." />
|
|
||||||
<MESSAGE value="- Added enabling of midpsnap and prepared others - Show dimesnion on hover" />
|
|
||||||
<MESSAGE value="- Fixed redraw when component changed" />
|
|
||||||
<MESSAGE value="- added MIT license" />
|
|
||||||
<MESSAGE value="- added screenshot" />
|
|
||||||
<MESSAGE value="- added sdf folder ( doesnt work via pip or git=)" />
|
|
||||||
<MESSAGE value="- Tons of addtions" />
|
|
||||||
<MESSAGE value="- Basic operations" />
|
|
||||||
<MESSAGE value="- removed cadquery deoendency" />
|
|
||||||
<MESSAGE value="- sketch enhacements" />
|
|
||||||
<MESSAGE value="- UI refinement, button position ui file as source no dirty drafting anymore" />
|
|
||||||
<MESSAGE value="- assembly draft" />
|
|
||||||
<MESSAGE value="- Added save file foramt - Split main.py refactor" />
|
|
||||||
<MESSAGE value="- Working assembly multi :)" />
|
|
||||||
<MESSAGE value="- added renderer" />
|
|
||||||
<MESSAGE value="- added renderer - Added undo" />
|
|
||||||
<MESSAGE value="- Render improvements, camera plane, update" />
|
|
||||||
<MESSAGE value="- added "measurement lines"" />
|
|
||||||
<MESSAGE value="- arc improvements, fillets, operations, bodys" />
|
|
||||||
<MESSAGE value="- Operation highlighting, body highlighting" />
|
|
||||||
<MESSAGE value="- tech draw draft" />
|
|
||||||
<MESSAGE value="- tech draw draft v2" />
|
|
||||||
<MESSAGE value="- tech drawing and render improv" />
|
|
||||||
<MESSAGE value="Fiexed highlighting of operations" />
|
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Fiexed highlighting of operations" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
-376
@@ -1,376 +0,0 @@
|
|||||||
# ── Sheet layout regions ────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
_LAYOUT_MARGIN_MM = 10.0
|
|
||||||
_VIEW_GAP_MM = 12.0
|
|
||||||
|
|
||||||
# Title block box (see _title_block_primitives): 180 × 52 at the bottom-right
|
|
||||||
# corner with a 5 mm sheet margin. Views must clear it (plus clearance).
|
|
||||||
_TB_LEFT_MM = _A3_WIDTH_MM - 180.0 - 5.0
|
|
||||||
_TB_TOP_MM = 5.0 + 52.0
|
|
||||||
_TB_CLEARANCE_MM = 5.0
|
|
||||||
|
|
||||||
# Sheet interior (border margin) as (x0, y0, x1, y1) in sheet mm.
|
|
||||||
_SHEET_INNER = (
|
|
||||||
_LAYOUT_MARGIN_MM,
|
|
||||||
_LAYOUT_MARGIN_MM,
|
|
||||||
_A3_WIDTH_MM - _LAYOUT_MARGIN_MM,
|
|
||||||
_A3_HEIGHT_MM - _LAYOUT_MARGIN_MM,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Regions the orthographic layout may occupy, as (x0, y0, w, h):
|
|
||||||
# - UPPER: full sheet width above the title block
|
|
||||||
# - LEFT: full sheet height in the left strip beside the title block
|
|
||||||
_REGION_UPPER = (
|
|
||||||
_LAYOUT_MARGIN_MM,
|
|
||||||
_TB_TOP_MM + _TB_CLEARANCE_MM,
|
|
||||||
_A3_WIDTH_MM - 2 * _LAYOUT_MARGIN_MM,
|
|
||||||
_A3_HEIGHT_MM - _LAYOUT_MARGIN_MM - (_TB_TOP_MM + _TB_CLEARANCE_MM),
|
|
||||||
)
|
|
||||||
_REGION_LEFT = (
|
|
||||||
_LAYOUT_MARGIN_MM,
|
|
||||||
_LAYOUT_MARGIN_MM,
|
|
||||||
_TB_LEFT_MM - _TB_CLEARANCE_MM - _LAYOUT_MARGIN_MM,
|
|
||||||
_A3_HEIGHT_MM - 2 * _LAYOUT_MARGIN_MM,
|
|
||||||
)
|
|
||||||
|
|
||||||
_MID_ORDER = ("left", "front", "right", "back")
|
|
||||||
_COL_ORDER = ("top", "front", "bottom") # sheet top → bottom
|
|
||||||
_GRID_ORDER = ("front", "right", "back", "top", "left", "bottom")
|
|
||||||
|
|
||||||
_RectList = List[Tuple[str, float, float, float, float]]
|
|
||||||
|
|
||||||
|
|
||||||
def _place_cross(
|
|
||||||
dims: Dict[str, Tuple[float, float]], gap: float
|
|
||||||
) -> _RectList:
|
|
||||||
"""Classic third-angle cross: mid views run left→right (left, front,
|
|
||||||
right, back), col views stack top→bottom (top, front, bottom), with the
|
|
||||||
anchor view (front, or the first present one) at the intersection.
|
|
||||||
|
|
||||||
*dims* maps view id → ``(w, h)`` in sheet units. Returns local
|
|
||||||
``(vid, x, y, w, h)`` rects (origin arbitrary — the caller centres the
|
|
||||||
union on the sheet).
|
|
||||||
"""
|
|
||||||
mid = [k for k in _MID_ORDER if k in dims]
|
|
||||||
col = [k for k in _COL_ORDER if k in dims]
|
|
||||||
if not mid and not col:
|
|
||||||
return []
|
|
||||||
rects: Dict[str, Tuple[float, float, float, float]] = {}
|
|
||||||
|
|
||||||
if col:
|
|
||||||
# Stack bottom → top.
|
|
||||||
y = 0.0
|
|
||||||
for k in reversed(col):
|
|
||||||
w, h = dims[k]
|
|
||||||
rects[k] = (0.0, y, w, h)
|
|
||||||
y += h + gap
|
|
||||||
col_h = y - gap
|
|
||||||
cx = max(dims[k][0] for k in col) / 2.0
|
|
||||||
for k in col:
|
|
||||||
_x, yy, w, h = rects[k]
|
|
||||||
rects[k] = (cx - w / 2.0, yy, w, h)
|
|
||||||
else:
|
|
||||||
col_h = 0.0
|
|
||||||
cx = 0.0
|
|
||||||
|
|
||||||
anchor = "front" if "front" in dims else (mid[0] if mid else col[0])
|
|
||||||
if anchor in rects:
|
|
||||||
ax, ay, aw, _ah = rects[anchor]
|
|
||||||
anchor_cy = ay + _ah / 2.0
|
|
||||||
else:
|
|
||||||
aw, ah = dims[anchor]
|
|
||||||
ax = cx - aw / 2.0
|
|
||||||
ay = col_h / 2.0 - ah / 2.0
|
|
||||||
rects[anchor] = (ax, ay, aw, ah)
|
|
||||||
anchor_cy = col_h / 2.0
|
|
||||||
|
|
||||||
ia = mid.index(anchor) if anchor in mid else -1
|
|
||||||
x = ax
|
|
||||||
for k in reversed(mid[:ia]):
|
|
||||||
w, h = dims[k]
|
|
||||||
x -= w + gap
|
|
||||||
rects[k] = (x, anchor_cy - h / 2.0, w, h)
|
|
||||||
x = ax + aw
|
|
||||||
for k in mid[ia + 1 :]:
|
|
||||||
w, h = dims[k]
|
|
||||||
x += gap
|
|
||||||
rects[k] = (x, anchor_cy - h / 2.0, w, h)
|
|
||||||
x += w
|
|
||||||
return [(k, *r) for k, r in rects.items()]
|
|
||||||
|
|
||||||
|
|
||||||
def _place_swapped(dims: Dict[str, Tuple[float, float]], gap: float) -> _RectList:
|
|
||||||
"""Cross with the view families swapped: the mid views stack vertically
|
|
||||||
(left, front, right, back from the top) and the col views run
|
|
||||||
horizontally (bottom, front, top from the left) — the classic cross
|
|
||||||
turned a quarter turn, for sheets where that orientation fits more.
|
|
||||||
"""
|
|
||||||
mid = [k for k in _MID_ORDER if k in dims]
|
|
||||||
col = [k for k in _COL_ORDER if k in dims]
|
|
||||||
if not mid or not col:
|
|
||||||
return []
|
|
||||||
rects: Dict[str, Tuple[float, float, float, float]] = {}
|
|
||||||
cx = max(dims[k][0] for k in mid) / 2.0
|
|
||||||
y = 0.0
|
|
||||||
for k in mid: # top → bottom
|
|
||||||
w, h = dims[k]
|
|
||||||
rects[k] = (cx - w / 2.0, y, w, h)
|
|
||||||
y += h + gap
|
|
||||||
anchor = "front" if "front" in dims else mid[0]
|
|
||||||
anchor_cy = rects[anchor][1] + dims[anchor][1] / 2.0
|
|
||||||
|
|
||||||
row: Dict[str, Tuple[float, float, float, float]] = {}
|
|
||||||
x = 0.0
|
|
||||||
x_anchor = 0.0
|
|
||||||
for k in reversed(col): # bottom, front, top → left to right
|
|
||||||
w, h = dims[k]
|
|
||||||
if k == anchor:
|
|
||||||
x_anchor = x
|
|
||||||
row[k] = (x, anchor_cy - h / 2.0, w, h)
|
|
||||||
x += w + gap
|
|
||||||
shift = rects[anchor][0] - x_anchor
|
|
||||||
for k, r in row.items():
|
|
||||||
if k == anchor:
|
|
||||||
continue
|
|
||||||
x0, y0, w, h = r
|
|
||||||
rects[k] = (x0 + shift, y0, w, h)
|
|
||||||
return [(k, *r) for k, r in rects.items()]
|
|
||||||
|
|
||||||
|
|
||||||
def _place_grid(
|
|
||||||
dims: Dict[str, Tuple[float, float]], gap: float, rows: int
|
|
||||||
) -> _RectList:
|
|
||||||
"""Wrap the present standard views into a grid of *rows* rows, filled
|
|
||||||
bottom→top and left→right (so the primary views sit near the bottom,
|
|
||||||
like in the cross)."""
|
|
||||||
order = [k for k in _GRID_ORDER if k in dims]
|
|
||||||
if not order:
|
|
||||||
return []
|
|
||||||
cols = max(1, -(-len(order) // rows))
|
|
||||||
rects: _RectList = []
|
|
||||||
y = 0.0
|
|
||||||
for r in range(rows):
|
|
||||||
chunk = order[r * cols : (r + 1) * cols]
|
|
||||||
if not chunk:
|
|
||||||
break
|
|
||||||
x = 0.0
|
|
||||||
row_h = 0.0
|
|
||||||
for k in chunk:
|
|
||||||
w, h = dims[k]
|
|
||||||
rects.append((k, x, y, w, h))
|
|
||||||
x += w + gap
|
|
||||||
row_h = max(row_h, h)
|
|
||||||
y += row_h + gap
|
|
||||||
return rects
|
|
||||||
|
|
||||||
|
|
||||||
def _fit_scale(
|
|
||||||
place: Callable[
|
|
||||||
[Dict[str, Tuple[float, float]], float], _RectList
|
|
||||||
],
|
|
||||||
dims_m: Dict[str, Tuple[float, float]],
|
|
||||||
region: Tuple[float, float, float, float],
|
|
||||||
) -> float:
|
|
||||||
"""Largest shared scale at which *dims_m* (model units) laid out by
|
|
||||||
*place* fits the ``(x0, y0, w, h)`` *region* of the sheet.
|
|
||||||
|
|
||||||
The union size grows monotonically with the scale, so a bisection
|
|
||||||
converges to the tight fit.
|
|
||||||
"""
|
|
||||||
_rx0, _ry0, rw, rh = region
|
|
||||||
|
|
||||||
def fits(s: float) -> bool:
|
|
||||||
rects = place(
|
|
||||||
{k: (w * s, h * s) for k, (w, h) in dims_m.items()}, _VIEW_GAP_MM
|
|
||||||
)
|
|
||||||
if not rects:
|
|
||||||
return True
|
|
||||||
minx = min(r[1] for r in rects)
|
|
||||||
miny = min(r[2] for r in rects)
|
|
||||||
maxx = max(r[1] + r[3] for r in rects)
|
|
||||||
maxy = max(r[2] + r[4] for r in rects)
|
|
||||||
return (maxx - minx) <= rw + 1e-9 and (maxy - miny) <= rh + 1e-9
|
|
||||||
|
|
||||||
s_lo, s_hi = 0.0, 1.0
|
|
||||||
if fits(s_hi):
|
|
||||||
s_lo = s_hi
|
|
||||||
while s_hi < 1.0e6 and fits(s_hi * 2.0):
|
|
||||||
s_hi *= 2.0
|
|
||||||
for _ in range(60):
|
|
||||||
mid = 0.5 * (s_lo + s_hi)
|
|
||||||
if fits(mid):
|
|
||||||
s_lo = mid
|
|
||||||
else:
|
|
||||||
s_hi = mid
|
|
||||||
return s_lo
|
|
||||||
|
|
||||||
|
|
||||||
def _free_rects(
|
|
||||||
used_rects: Sequence[Tuple[float, float, float, float]]
|
|
||||||
) -> List[Tuple[float, float, float, float]]:
|
|
||||||
"""Axis-aligned free rects ``(x0, y0, w, h)`` around *used_rects*,
|
|
||||||
clearing the sheet border and the title block zone."""
|
|
||||||
ix0, iy0, ix1, iy1 = _SHEET_INNER
|
|
||||||
if used_rects:
|
|
||||||
ux0 = min(r[0] for r in used_rects)
|
|
||||||
uy0 = min(r[1] for r in used_rects)
|
|
||||||
ux1 = max(r[0] + r[2] for r in used_rects)
|
|
||||||
uy1 = max(r[1] + r[3] for r in used_rects)
|
|
||||||
cands = [
|
|
||||||
(ux1 + _VIEW_GAP_MM, iy0, ix1, iy1), # right of the used block
|
|
||||||
(ix0, iy0, ux0 - _VIEW_GAP_MM, iy1), # left
|
|
||||||
(ix0, uy1 + _VIEW_GAP_MM, ix1, iy1), # above
|
|
||||||
(ix0, iy0, ix1, uy0 - _VIEW_GAP_MM), # below
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
cands = [(ix0, iy0, ix1, iy1)]
|
|
||||||
tb = (
|
|
||||||
_TB_LEFT_MM - _TB_CLEARANCE_MM,
|
|
||||||
0.0,
|
|
||||||
_A3_WIDTH_MM - (_TB_LEFT_MM - _TB_CLEARANCE_MM),
|
|
||||||
_TB_TOP_MM + _TB_CLEARANCE_MM,
|
|
||||||
)
|
|
||||||
out: List[Tuple[float, float, float, float]] = []
|
|
||||||
for x0, y0, x1, y1 in cands:
|
|
||||||
x0, y0 = max(x0, ix0), max(y0, iy0)
|
|
||||||
x1, y1 = min(x1, ix1), min(y1, iy1)
|
|
||||||
if x1 - x0 < 1.0 or y1 - y0 < 1.0:
|
|
||||||
continue
|
|
||||||
if not (x1 <= tb[0] or tb[2] <= x0 or y1 <= tb[1] or tb[3] <= y0):
|
|
||||||
# Overlaps the title block zone — keep the parts above/left of it.
|
|
||||||
subs = [
|
|
||||||
(x0, max(y0, tb[3]), x1, y1),
|
|
||||||
(x0, y0, min(x1, tb[0]), y1),
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
subs = [(x0, y0, x1, y1)]
|
|
||||||
for sx0, sy0, sx1, sy1 in subs:
|
|
||||||
if sx1 - sx0 > 1.0 and sy1 - sy0 > 1.0:
|
|
||||||
out.append((sx0, sy0, sx1 - sx0, sy1 - sy0))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def _layout_views_on_sheet(
|
|
||||||
views: Sequence[DrawingView],
|
|
||||||
bboxes: Dict[str, Tuple[float, float, float, float]],
|
|
||||||
) -> Tuple[
|
|
||||||
Dict[str, Tuple[float, float, float, float]],
|
|
||||||
Optional[float],
|
|
||||||
Dict[str, float],
|
|
||||||
]:
|
|
||||||
"""Compute a slot rectangle and sheet rotation for each view.
|
|
||||||
|
|
||||||
*bboxes* maps view_id → ``(min_x, min_y, max_x, max_y)`` in model
|
|
||||||
units (from :func:`_edges_bounds`). Returns ``(slots, common_scale,
|
|
||||||
rotations)``: slots are ``(left, bottom, width, height)`` in sheet mm
|
|
||||||
(origin at the sheet's bottom-left corner, +y up), common_scale is the
|
|
||||||
shared model→sheet scale of the standard orthographic views, and
|
|
||||||
rotations maps view_id → sheet rotation in degrees (0 or 90).
|
|
||||||
|
|
||||||
The sheet is filled, not just used: every candidate arrangement
|
|
||||||
(classic third-angle cross, the cross with the view families swapped,
|
|
||||||
and 1/2/3-row grids) is combined with every per-view 90° rotation
|
|
||||||
assignment, and the candidate giving the largest shared scale is used.
|
|
||||||
Candidates within 0.5% of the best scale prefer the one with fewer
|
|
||||||
rotated views, then the more conventional arrangement, so layouts stay
|
|
||||||
stable and standard whenever they are already the best fit. All
|
|
||||||
orthographic views share one scale so the projections stay mutually
|
|
||||||
consistent. Isometric and custom views take the largest remaining
|
|
||||||
free rect (clearing the title block).
|
|
||||||
"""
|
|
||||||
slots: Dict[str, Tuple[float, float, float, float]] = {}
|
|
||||||
rotations: Dict[str, float] = {}
|
|
||||||
common_scale: Optional[float] = None
|
|
||||||
|
|
||||||
ortho = [
|
|
||||||
v for v in views if v.kind in _STANDARD_VIEWS and v.kind != "isometric"
|
|
||||||
]
|
|
||||||
|
|
||||||
used_rects: List[Tuple[float, float, float, float]] = []
|
|
||||||
if ortho:
|
|
||||||
dims0: Dict[str, Tuple[float, float]] = {}
|
|
||||||
for v in ortho:
|
|
||||||
b = bboxes.get(v.kind)
|
|
||||||
vs = max(v.scale, 1e-9)
|
|
||||||
if b is None:
|
|
||||||
dims0[v.kind] = (1.0 * vs, 1.0 * vs)
|
|
||||||
else:
|
|
||||||
dims0[v.kind] = (
|
|
||||||
max(b[2] - b[0], 1e-6) * vs,
|
|
||||||
max(b[3] - b[1], 1e-6) * vs,
|
|
||||||
)
|
|
||||||
keys = list(dims0)
|
|
||||||
arrangements: Tuple[
|
|
||||||
Tuple[str, int, Callable[[Dict[str, Tuple[float, float]], float], _RectList]]
|
|
||||||
] = (
|
|
||||||
("cross", 0, _place_cross),
|
|
||||||
("swapped", 1, _place_swapped),
|
|
||||||
("grid1", 2, lambda d, g: _place_grid(d, g, 1)),
|
|
||||||
("grid2", 3, lambda d, g: _place_grid(d, g, 2)),
|
|
||||||
("grid3", 4, lambda d, g: _place_grid(d, g, 3)),
|
|
||||||
)
|
|
||||||
cands: List[
|
|
||||||
Tuple[float, int, int, Tuple[float, float, float, float],
|
|
||||||
Dict[str, Tuple[float, float]],
|
|
||||||
Callable[[Dict[str, Tuple[float, float]], float], _RectList],
|
|
||||||
List[bool]]
|
|
||||||
] = []
|
|
||||||
for mask in range(1 << len(keys)):
|
|
||||||
rotated = [bool(mask & (1 << i)) for i in range(len(keys))]
|
|
||||||
dims_m = {
|
|
||||||
k: (
|
|
||||||
dims0[k][1] if rotated[i] else dims0[k][0],
|
|
||||||
dims0[k][0] if rotated[i] else dims0[k][1],
|
|
||||||
)
|
|
||||||
for i, k in enumerate(keys)
|
|
||||||
}
|
|
||||||
for _name, rank, place in arrangements:
|
|
||||||
s_up = _fit_scale(place, dims_m, _REGION_UPPER)
|
|
||||||
s_left = _fit_scale(place, dims_m, _REGION_LEFT)
|
|
||||||
if s_up >= s_left:
|
|
||||||
s, region = s_up, _REGION_UPPER
|
|
||||||
else:
|
|
||||||
s, region = s_left, _REGION_LEFT
|
|
||||||
if s <= 0.0:
|
|
||||||
continue
|
|
||||||
cands.append((s, sum(rotated), rank, region, dims_m, place, rotated))
|
|
||||||
if cands:
|
|
||||||
best_s = max(c[0] for c in cands)
|
|
||||||
s, _nrot, _rank, region, dims_m, place, rotated = min(
|
|
||||||
(c for c in cands if c[0] >= best_s * 0.995),
|
|
||||||
key=lambda c: (c[1], c[2], -c[0]),
|
|
||||||
)
|
|
||||||
rx0, _ry0, rw, rh = region
|
|
||||||
ds = {k: (w * s, h * s) for k, (w, h) in dims_m.items()}
|
|
||||||
rects = place(ds, _VIEW_GAP_MM)
|
|
||||||
minx = min(r[1] for r in rects)
|
|
||||||
miny = min(r[2] for r in rects)
|
|
||||||
maxx = max(r[1] + r[3] for r in rects)
|
|
||||||
maxy = max(r[2] + r[4] for r in rects)
|
|
||||||
ox = rx0 + (rw - (maxx - minx)) / 2.0
|
|
||||||
oy = _ry0 + (rh - (maxy - miny)) / 2.0
|
|
||||||
for vid, x, y, w, h in rects:
|
|
||||||
slots[vid] = (x - minx + ox, y - miny + oy, w, h)
|
|
||||||
rotations[vid] = 90.0 if rotated[keys.index(vid)] else 0.0
|
|
||||||
common_scale = s
|
|
||||||
used_rects = [(ox, oy, maxx - minx, maxy - miny)]
|
|
||||||
|
|
||||||
# Isometric and custom views: the largest remaining free rect each,
|
|
||||||
# clearing the title block.
|
|
||||||
extra = [
|
|
||||||
v for v in views
|
|
||||||
if v.kind == "isometric" or v.kind not in _STANDARD_VIEWS
|
|
||||||
]
|
|
||||||
assigned = list(used_rects)
|
|
||||||
for i, v in enumerate(extra):
|
|
||||||
vid = v.kind if v.kind in _STANDARD_VIEWS else (v.name or v.id)
|
|
||||||
free = _free_rects(assigned)
|
|
||||||
if free:
|
|
||||||
slot = max(free, key=lambda r: r[2] * r[3])
|
|
||||||
else:
|
|
||||||
# No free rect left — park in the bottom-left corner stack.
|
|
||||||
slot = (_LAYOUT_MARGIN_MM, _LAYOUT_MARGIN_MM + i * 60.0, 120.0, 50.0)
|
|
||||||
slots[vid] = slot
|
|
||||||
assigned.append(slot)
|
|
||||||
|
|
||||||
return slots, common_scale, rotations
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import os, sys
|
|
||||||
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
|
||||||
sys.path.insert(0, "/Volumes/Data_drive/Programming/fluency/src")
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
from PySide6.QtGui import QPixmap, QPainter, QColor
|
|
||||||
from PySide6.QtCore import QRectF
|
|
||||||
import math
|
|
||||||
|
|
||||||
app = QApplication.instance() or QApplication([])
|
|
||||||
|
|
||||||
from fluency.geometry.base import Point2D
|
|
||||||
from fluency.geometry_occ.kernel import OCGeometryKernel
|
|
||||||
from fluency.models.data_model import Body, Component, Project, DrawingView, TechnicalDrawing
|
|
||||||
from fluency.technical_drawing import generate_drawing, render_drawing, _A3_WIDTH_MM, _A3_HEIGHT_MM
|
|
||||||
|
|
||||||
kernel = OCGeometryKernel()
|
|
||||||
# Long thin bar: 120 x 25 x 30 (matches the "wide bar" screenshot case).
|
|
||||||
points = [Point2D(0, 0), Point2D(120, 0), Point2D(120, 25), Point2D(0, 25)]
|
|
||||||
box = kernel.extrude(kernel.create_polygon(points), 30.0)
|
|
||||||
body = Body(name="Bar", geometry=box)
|
|
||||||
comp = Component(name="BarComp")
|
|
||||||
comp.bodies[body.id] = body
|
|
||||||
project = Project()
|
|
||||||
project.components[comp.id] = comp
|
|
||||||
project.active_component = comp.id
|
|
||||||
|
|
||||||
W = 2400
|
|
||||||
H = int(W * _A3_HEIGHT_MM / _A3_WIDTH_MM)
|
|
||||||
pm = QPixmap(W, H)
|
|
||||||
pm.fill(QColor(255, 255, 255))
|
|
||||||
|
|
||||||
for name, kinds in [
|
|
||||||
("four", ["front", "top", "right", "back"]),
|
|
||||||
("six", ["front", "top", "right", "left", "back", "bottom"]),
|
|
||||||
("sixiso", ["front", "top", "right", "left", "back", "bottom", "isometric"]),
|
|
||||||
]:
|
|
||||||
drawing = TechnicalDrawing(
|
|
||||||
source_kind="component", source_id=comp.id,
|
|
||||||
views=[DrawingView(kind=k) for k in kinds],
|
|
||||||
auto_dimensions=True, title=name,
|
|
||||||
)
|
|
||||||
result = generate_drawing(drawing, project, kernel)
|
|
||||||
p = QPainter(pm)
|
|
||||||
render_drawing(p, result, QRectF(0, 0, W, H))
|
|
||||||
p.end()
|
|
||||||
out = f"/tmp/drawing_{name}.png"
|
|
||||||
pm.save(out)
|
|
||||||
print(name, "saved", out, "prims", len(result.primitives), "scale",
|
|
||||||
round(result.view_transforms.get("front", (None,))[0] or 0, 3))
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
"""Smoke test: layout optimizer fills the page, no overlaps, title block clear."""
|
|
||||||
import math
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
||||||
sys.path.insert(0, "/Volumes/Data_drive/Programming/fluency/src")
|
|
||||||
|
|
||||||
from fluency.models.data_model import DrawingView
|
|
||||||
from fluency.technical_drawing import (
|
|
||||||
_layout_views_on_sheet,
|
|
||||||
build_manual_candidates,
|
|
||||||
)
|
|
||||||
|
|
||||||
A3W, A3H = 420.0, 297.0
|
|
||||||
TB = (235.0, 0.0, 420.0, 62.0) # title block zone incl. clearance
|
|
||||||
|
|
||||||
|
|
||||||
def b(x, y, w, h):
|
|
||||||
return (x, y, x + w, y + h)
|
|
||||||
|
|
||||||
|
|
||||||
def overlaps(r1, r2, clear=0.0):
|
|
||||||
x0, y0, w, h = r1
|
|
||||||
x1, y1, w2, h2 = r2
|
|
||||||
return not (x0 + w <= x1 + clear or x1 + w2 <= x0 + clear
|
|
||||||
or y0 + h <= y1 + clear or y1 + h2 <= y0 + clear)
|
|
||||||
|
|
||||||
|
|
||||||
def tb_overlap(r, clear=5.0):
|
|
||||||
x0, y0, w, h = r
|
|
||||||
x1, y1, w2, h2 = TB
|
|
||||||
return not (x0 + w <= x1 + clear or x1 + w2 <= x0 + clear
|
|
||||||
or y0 + h <= y1 + clear or y1 + h2 <= y0 + clear)
|
|
||||||
|
|
||||||
|
|
||||||
def union_rect(rects):
|
|
||||||
x0 = min(r[0] for r in rects)
|
|
||||||
y0 = min(r[1] for r in rects)
|
|
||||||
x1 = max(r[0] + r[2] for r in rects)
|
|
||||||
y1 = max(r[1] + r[3] for r in rects)
|
|
||||||
return (x0, y0, x1 - x0, y1 - y0)
|
|
||||||
|
|
||||||
|
|
||||||
def check(name, kinds, boxes, expect_rot=None):
|
|
||||||
views = [DrawingView(kind=k) for k in kinds]
|
|
||||||
bboxes = {k: boxes[k] for k in kinds}
|
|
||||||
slots, scale, rots = _layout_views_on_sheet(views, bboxes)
|
|
||||||
print(f"--- {name}: scale={scale:.4f} rots={rots}")
|
|
||||||
# all slots within sheet
|
|
||||||
for k, s in slots.items():
|
|
||||||
assert 10 - 1e-6 <= s[0] and 10 - 1e-6 <= s[1], f"{k} outside sheet {s}"
|
|
||||||
assert s[0] + s[2] <= A3W - 10 + 1e-6, f"{k} beyond right {s}"
|
|
||||||
assert s[1] + s[3] <= A3H - 10 + 1e-6, f"{k} beyond top {s}"
|
|
||||||
# no overlaps between slots
|
|
||||||
ks = list(slots)
|
|
||||||
for i in range(len(ks)):
|
|
||||||
for j in range(i + 1, len(ks)):
|
|
||||||
assert not overlaps(slots[ks[i]], slots[ks[j]], 11.9), \
|
|
||||||
f"{ks[i]} overlaps {ks[j]}: {slots[ks[i]]} / {slots[ks[j]]}"
|
|
||||||
# title block clear
|
|
||||||
for k, s in slots.items():
|
|
||||||
assert not tb_overlap(s), f"{k} intrudes title block {s}"
|
|
||||||
# fill report
|
|
||||||
u = union_rect(list(slots.values()))
|
|
||||||
area = u[2] * u[3]
|
|
||||||
print(f" union: x0={u[0]:.1f} y0={u[1]:.1f} w={u[2]:.1f} h={u[3]:.1f} "
|
|
||||||
f"area={area:.0f}mm^2 ({100*area/(A3W*A3H):.0f}% of sheet)")
|
|
||||||
for k in ks:
|
|
||||||
print(f" {k}: {tuple(round(v,1) for v in slots[k])}")
|
|
||||||
if expect_rot is not None:
|
|
||||||
assert rots == expect_rot, f"expected {expect_rot}, got {rots}"
|
|
||||||
return slots, scale, rots
|
|
||||||
|
|
||||||
|
|
||||||
# 1. Two square views (front+top): should fill the page, no rotation.
|
|
||||||
check("two square", ["front", "top"],
|
|
||||||
{"front": b(0, 0, 40, 40), "top": b(0, 0, 40, 20)})
|
|
||||||
|
|
||||||
# 2. Wide bar, 4 views (old screenshot case): front+back wide, top+right.
|
|
||||||
check("wide bar 4", ["front", "top", "right", "back"],
|
|
||||||
{"front": b(0, 0, 120, 25), "top": b(0, 0, 25, 40),
|
|
||||||
"right": b(0, 0, 25, 40), "back": b(0, 0, 120, 25)})
|
|
||||||
|
|
||||||
# 3. Six views of a long thin part: rotation should kick in.
|
|
||||||
check("thin part 6", ["front", "top", "right", "left", "back", "bottom"],
|
|
||||||
{"front": b(0, 0, 200, 30), "top": b(0, 0, 30, 50),
|
|
||||||
"right": b(0, 0, 50, 30), "left": b(0, 0, 50, 30),
|
|
||||||
"back": b(0, 0, 200, 30), "bottom": b(0, 0, 30, 50)})
|
|
||||||
|
|
||||||
# 4. Single front view: fills the whole page.
|
|
||||||
check("single", ["front"], {"front": b(0, 0, 10, 20)})
|
|
||||||
|
|
||||||
# 5. All 6 + isometric + custom.
|
|
||||||
check("everything",
|
|
||||||
["front", "top", "right", "left", "back", "bottom", "isometric"],
|
|
||||||
{"front": b(0, 0, 80, 40), "top": b(0, 0, 80, 30),
|
|
||||||
"right": b(0, 0, 30, 40), "left": b(0, 0, 30, 40),
|
|
||||||
"back": b(0, 0, 80, 40), "bottom": b(0, 0, 80, 30),
|
|
||||||
"isometric": b(0, 0, 60, 60)})
|
|
||||||
|
|
||||||
# ── Inverse-transform roundtrip ─────────────────────────────────────────
|
|
||||||
# A 90°-rotated view: forward via _assemble_view's recorded 6-tuple,
|
|
||||||
# inverse via the widget's formula.
|
|
||||||
t = (2.0, 150.0, 80.0, 90.0, 10.0, 5.0) # s, o_x, o_y, deg, cx, cy
|
|
||||||
scale, ox, oy, deg, cx, cy = t
|
|
||||||
th = math.radians(deg)
|
|
||||||
cos_t, sin_t = math.cos(th), math.sin(th)
|
|
||||||
|
|
||||||
|
|
||||||
def fwd(p):
|
|
||||||
dx, dy = p[0] - cx, p[1] - cy
|
|
||||||
return ((dx * cos_t - dy * sin_t) * scale + ox,
|
|
||||||
(dx * sin_t + dy * cos_t) * scale + oy)
|
|
||||||
|
|
||||||
|
|
||||||
def inv(p):
|
|
||||||
sx, sy = (p[0] - ox) / scale, (p[1] - oy) / scale
|
|
||||||
return (sx * cos_t + sy * sin_t + cx, -sx * sin_t + sy * cos_t + cy)
|
|
||||||
|
|
||||||
|
|
||||||
for p in [(0, 0), (10, 5), (3, -7), (42.5, 11.25)]:
|
|
||||||
rt = inv(fwd(p))
|
|
||||||
assert abs(rt[0] - p[0]) < 1e-9 and abs(rt[1] - p[1]) < 1e-9, (p, rt)
|
|
||||||
print("inverse roundtrip OK")
|
|
||||||
|
|
||||||
# Legacy 3-tuple still works through build_manual_candidates.
|
|
||||||
from fluency.models.data_model import DrawingAnnotation, TechnicalDrawing
|
|
||||||
d = TechnicalDrawing(source_kind="component", source_id="c")
|
|
||||||
ann = DrawingAnnotation(kind="dimension", dimension_kind="length",
|
|
||||||
view_id="front", anchors=[(0.0, 0.0), (0.0, 12.5)],
|
|
||||||
direction=(0.0, 1.0))
|
|
||||||
d.annotations.append(ann)
|
|
||||||
cands, res, unres = build_manual_candidates(d, {"front": (2.0, 10.0, 20.0)})
|
|
||||||
assert unres == [] and cands[0].anchor_points[1] == (10.0, 45.0)
|
|
||||||
print("legacy 3-tuple OK")
|
|
||||||
|
|
||||||
# 6-tuple manual: rotated length direction must rotate too.
|
|
||||||
ann2 = DrawingAnnotation(kind="dimension", dimension_kind="length",
|
|
||||||
view_id="front",
|
|
||||||
anchors=[(0.0, 0.0), (0.0, 10.0)],
|
|
||||||
direction=(0.0, 1.0))
|
|
||||||
d2 = TechnicalDrawing(source_kind="component", source_id="c")
|
|
||||||
d2.annotations.append(ann2)
|
|
||||||
# 90° rotation about centre c=(5,5), scale 2, o=(100,80)
|
|
||||||
cands, res, unres = build_manual_candidates(
|
|
||||||
d2, {"front": (2.0, 100.0, 80.0, 90.0, 5.0, 5.0)}
|
|
||||||
)
|
|
||||||
c = cands[0]
|
|
||||||
# anchors: (0,0)->rot90 about (5,5) = (5-(0-5)*0 - ... compute: dx=-5,dy=-5
|
|
||||||
# fwd: (dx*cos - dy*sin)*2+100 = (0 - (-5))*2+100 = 110 ; (dx*sin+dy*cos)*2+80 = (-5)*2+80=70
|
|
||||||
# (0,10): dx=-5, dy=5 -> (0-5)*2+100=90 ; (−5*1+0)*2+80=70
|
|
||||||
assert c.anchor_points[0] == (110.0, 70.0), c.anchor_points
|
|
||||||
assert c.anchor_points[1] == (90.0, 70.0), c.anchor_points
|
|
||||||
# direction (0,1) rotated 90° CCW -> (-1, 0)
|
|
||||||
assert c.direction[0] == -1.0 and abs(c.direction[1]) < 1e-9, c.direction
|
|
||||||
print("6-tuple manual (rotated) OK")
|
|
||||||
|
|
||||||
print("ALL SMOKE CHECKS PASSED")
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
"""Headless test: connectors follow moved features across ALL assemblies.
|
|
||||||
|
|
||||||
Simulates the test-file scenario: a plate with a hole, mated hole-to-face
|
|
||||||
in two different assemblies. The hole is moved (rebuilt geometry) and the
|
|
||||||
body-update connector recalculation must:
|
|
||||||
1. re-locate the hole connector on every instance of the component
|
|
||||||
(both the active and a non-active assembly),
|
|
||||||
2. re-solve each mated pair so the partner parts follow,
|
|
||||||
3. never snap a planar connector onto the cylindrical hole (type match),
|
|
||||||
4. mark a connector invalid when its feature disappears.
|
|
||||||
5. auto-follow a FAR move when the candidate is unambiguous (single
|
|
||||||
feature of its class) — the demo case,
|
|
||||||
6. NOT auto-apply an ambiguous far candidate (another same-class feature
|
|
||||||
is nearer) — that needs a manual pick, simulated here.
|
|
||||||
7. backfill legacy connectors' entity_type from their auto-generated name.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2
|
|
||||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
|
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from fluency.ui.main_window import MainWindow
|
|
||||||
from fluency.models.data_model import Assembly, Body
|
|
||||||
from fluency.geometry_occ.kernel import OCCGeometryObject
|
|
||||||
|
|
||||||
app = QApplication.instance() or QApplication([])
|
|
||||||
w = MainWindow()
|
|
||||||
|
|
||||||
comp = w._current_component
|
|
||||||
|
|
||||||
|
|
||||||
def make_plate(hole_xy):
|
|
||||||
box = BRepPrimAPI_MakeBox(60.0, 40.0, 5.0).Shape()
|
|
||||||
ax = gp_Ax2(gp_Pnt(float(hole_xy[0]), float(hole_xy[1]), 0.0), gp_Dir(0, 0, 1))
|
|
||||||
cyl = BRepPrimAPI_MakeCylinder(ax, 3.0, 6.0).Shape()
|
|
||||||
return BRepAlgoAPI_Cut(box, cyl).Shape()
|
|
||||||
|
|
||||||
|
|
||||||
body = Body(name="plate")
|
|
||||||
comp.bodies[body.id] = body
|
|
||||||
body.geometry = OCCGeometryObject(make_plate((10.0, 5.0)))
|
|
||||||
|
|
||||||
partner = w._project.add_component()
|
|
||||||
pbody = Body(name="partner")
|
|
||||||
partner.bodies[pbody.id] = pbody
|
|
||||||
pbody.geometry = OCCGeometryObject(BRepPrimAPI_MakeBox(30.0, 30.0, 10.0).Shape())
|
|
||||||
|
|
||||||
|
|
||||||
def make_pair(asm):
|
|
||||||
ac1 = asm.add_component_instance(comp.id, name="A")
|
|
||||||
ac2 = asm.add_component_instance(partner.id, name="B")
|
|
||||||
ac1.position = np.zeros(3)
|
|
||||||
ac1.rotation = np.eye(3)
|
|
||||||
ac2.position = np.array([0.0, 0.0, 15.0])
|
|
||||||
ac2.rotation = np.eye(3)
|
|
||||||
ac1.geom_cache[body.id] = body.geometry
|
|
||||||
|
|
||||||
c1 = ac1.add_connector(
|
|
||||||
position=(10.0, 5.0, 2.5),
|
|
||||||
normal=(0.0, 0.0, 1.0),
|
|
||||||
x_dir=(1.0, 0.0, 0.0),
|
|
||||||
source_obj_id=f"asm_{ac1.id}_{body.id}",
|
|
||||||
name="Conn hole A",
|
|
||||||
entity_type="cylindrical_face",
|
|
||||||
)
|
|
||||||
c2 = ac2.add_connector(
|
|
||||||
position=(15.0, 15.0, 0.0),
|
|
||||||
normal=(0.0, 0.0, -1.0),
|
|
||||||
x_dir=(1.0, 0.0, 0.0),
|
|
||||||
source_obj_id=f"asm_{ac2.id}_{pbody.id}",
|
|
||||||
name="Conn face B",
|
|
||||||
entity_type="planar_face",
|
|
||||||
)
|
|
||||||
c1.is_grounded = True
|
|
||||||
c1.partner_ac_id = ac2.id
|
|
||||||
c1.partner_connector_id = c2.id
|
|
||||||
c2.partner_ac_id = ac1.id
|
|
||||||
c2.partner_connector_id = c1.id
|
|
||||||
aconn = asm.add_connection(ac1.id, ac2.id)
|
|
||||||
aconn.first_connector_id = c1.id
|
|
||||||
aconn.second_connector_id = c2.id
|
|
||||||
return ac1, ac2, c1, c2, aconn
|
|
||||||
|
|
||||||
|
|
||||||
asm1 = w._project.get_active_assembly()
|
|
||||||
asm2 = w._project.add_assembly(Assembly(name="second"))
|
|
||||||
pair1 = make_pair(asm1)
|
|
||||||
pair2 = make_pair(asm2)
|
|
||||||
assert w._project.active_assembly == asm1.id # asm2 is the NON-active one
|
|
||||||
|
|
||||||
# ── Move the hole: rebuild the plate with the hole at (25, 12) ──────────
|
|
||||||
new_geom = OCCGeometryObject(make_plate((25.0, 12.0)))
|
|
||||||
body.geometry = new_geom
|
|
||||||
for asm in (asm1, asm2):
|
|
||||||
for ac in asm.components.values():
|
|
||||||
if ac.component_id == comp.id:
|
|
||||||
ac.geom_cache[body.id] = new_geom
|
|
||||||
|
|
||||||
# ── The body-update auto path ───────────────────────────────────────────
|
|
||||||
w._recalculate_connectors()
|
|
||||||
|
|
||||||
for (ac1, ac2, c1, c2, aconn), label in ((pair1, "asm1"), (pair2, "asm2")):
|
|
||||||
# The hole connector followed the hole on every instance.
|
|
||||||
assert np.allclose(c1.position, (25.0, 12.0, 2.5), atol=1e-6), (label, c1.position)
|
|
||||||
# The mated pair re-aligned: both world connectors coincide.
|
|
||||||
w1 = ac1.position + ac1.rotation @ np.asarray(c1.position)
|
|
||||||
w2 = ac2.position + ac2.rotation @ np.asarray(c2.position)
|
|
||||||
assert np.allclose(w1, w2, atol=1e-6), (label, w1, w2)
|
|
||||||
print(f"{label}: connector at {np.round(c1.position, 3)}, "
|
|
||||||
f"partner moved to {np.round(ac2.position, 3)}")
|
|
||||||
|
|
||||||
# ── The 'Upd' button path: move the hole again, re-run the handler ─────
|
|
||||||
w._refresh_connection_list()
|
|
||||||
w._connection_list.setCurrentRow(0) # active assembly = asm1
|
|
||||||
geom3 = OCCGeometryObject(make_plate((35.0, 20.0)))
|
|
||||||
body.geometry = geom3
|
|
||||||
for ac in asm1.components.values():
|
|
||||||
if ac.component_id == comp.id:
|
|
||||||
ac.geom_cache[body.id] = geom3
|
|
||||||
w._on_update_connection_from_list()
|
|
||||||
ac1, ac2, c1, c2, aconn = pair1
|
|
||||||
assert np.allclose(c1.position, (35.0, 20.0, 2.5), atol=1e-6), c1.position
|
|
||||||
w1 = ac1.position + ac1.rotation @ np.asarray(c1.position)
|
|
||||||
w2 = ac2.position + ac2.rotation @ np.asarray(c2.position)
|
|
||||||
assert np.allclose(w1, w2, atol=1e-6), (w1, w2)
|
|
||||||
print("Upd button: connector at", np.round(c1.position, 3),
|
|
||||||
"partner at", np.round(ac2.position, 3))
|
|
||||||
|
|
||||||
# ── Type matching: a planar connector must not snap onto the hole ───────
|
|
||||||
ac1 = pair1[0]
|
|
||||||
c3 = ac1.add_connector(
|
|
||||||
position=(30.0, 20.0, 5.0),
|
|
||||||
normal=(0.0, 0.0, 1.0),
|
|
||||||
x_dir=(1.0, 0.0, 0.0),
|
|
||||||
source_obj_id=f"asm_{ac1.id}_{body.id}",
|
|
||||||
name="Conn face",
|
|
||||||
entity_type="planar_face",
|
|
||||||
)
|
|
||||||
res = w._redetect_connector_on_geometry(c3, ac1, comp)
|
|
||||||
assert res is not None, "planar connector candidate missing"
|
|
||||||
assert not c3.is_invalid, "pure relocator must not mutate the connector"
|
|
||||||
assert np.allclose(c3.position, (30.0, 20.0, 5.0), atol=1e-6), c3.position
|
|
||||||
assert np.allclose(res[1], (30.0, 20.0, 5.0), atol=1e-6), res[1]
|
|
||||||
print("planar connector stayed on the face:", np.round(res[1], 3))
|
|
||||||
|
|
||||||
# ── Feature removed: relocator finds nothing; auto path marks invalid ──
|
|
||||||
plain = OCCGeometryObject(BRepPrimAPI_MakeBox(60.0, 40.0, 5.0).Shape())
|
|
||||||
body.geometry = plain
|
|
||||||
ac1.geom_cache[body.id] = plain
|
|
||||||
res = w._redetect_connector_on_geometry(pair1[2], ac1, comp)
|
|
||||||
assert res is None, "hole connector should find no candidate on a plain box"
|
|
||||||
w._recalculate_connectors()
|
|
||||||
assert pair1[2].is_invalid, "auto path must mark the connector invalid"
|
|
||||||
print("removed feature -> connector marked invalid")
|
|
||||||
|
|
||||||
# ── Far move with a decoy: ambiguous candidate is NOT auto-applied ─────
|
|
||||||
# The real hole moved to (25, 12) — ~12.8mm from the stored (35, 20) — but
|
|
||||||
# a SECOND hole now sits at (28, 16), only ~8mm away. The nearest
|
|
||||||
# candidate is ambiguous (different feature), so the auto path must leave
|
|
||||||
# the connector alone and queue it for a manual pick.
|
|
||||||
def make_plate2(holes):
|
|
||||||
box = BRepPrimAPI_MakeBox(60.0, 40.0, 5.0).Shape()
|
|
||||||
for hx, hy in holes:
|
|
||||||
ax = gp_Ax2(gp_Pnt(hx, hy, 0.0), gp_Dir(0, 0, 1))
|
|
||||||
box = BRepAlgoAPI_Cut(box, BRepPrimAPI_MakeCylinder(ax, 3.0, 6.0).Shape()).Shape()
|
|
||||||
return box
|
|
||||||
# ── Stage 1: the user's demo — a SINGLE hole moved far away ─────────────
|
|
||||||
# 12.8mm from the stored position, but the only cylindrical face on the
|
|
||||||
# body → unambiguous → must be auto-applied (and the mate re-solved).
|
|
||||||
geom4 = OCCGeometryObject(make_plate2([(25.0, 12.0)]))
|
|
||||||
body.geometry = geom4
|
|
||||||
for asm in (asm1, asm2):
|
|
||||||
for ac in asm.components.values():
|
|
||||||
if ac.component_id == comp.id:
|
|
||||||
ac.geom_cache[body.id] = geom4
|
|
||||||
w._recalculate_connectors()
|
|
||||||
assert np.allclose(pair1[2].position, (25.0, 12.0, 2.5), atol=1e-6), \
|
|
||||||
"unique far candidate must be auto-applied (the demo case)"
|
|
||||||
assert not pair1[2].is_invalid
|
|
||||||
w1 = ac1.position + ac1.rotation @ np.asarray(pair1[2].position)
|
|
||||||
w2 = pair1[1].position + pair1[1].rotation @ np.asarray(pair1[3].position)
|
|
||||||
assert np.allclose(w1, w2, atol=1e-6), (w1, w2)
|
|
||||||
print("single far hole: auto-followed to", np.round(pair1[2].position, 3))
|
|
||||||
|
|
||||||
# ── Stage 2: far move with a decoy — ambiguous, NOT auto-applied ───────
|
|
||||||
# The real hole now sits at (32, 20) — 10.6mm from the stored (25, 12) —
|
|
||||||
# while a decoy hole at (21, 7) is only 6.4mm away. The nearest
|
|
||||||
# candidate is likely a DIFFERENT feature, so the auto path must leave
|
|
||||||
# the connector alone and queue it for a manual pick.
|
|
||||||
geom5 = OCCGeometryObject(make_plate2([(32.0, 20.0), (21.0, 7.0)]))
|
|
||||||
body.geometry = geom5
|
|
||||||
for asm in (asm1, asm2):
|
|
||||||
for ac in asm.components.values():
|
|
||||||
if ac.component_id == comp.id:
|
|
||||||
ac.geom_cache[body.id] = geom5
|
|
||||||
w._recalculate_connectors()
|
|
||||||
assert np.allclose(pair1[2].position, (25.0, 12.0, 2.5), atol=1e-6), \
|
|
||||||
"ambiguous far candidate must not be auto-applied"
|
|
||||||
assert not pair1[2].is_invalid, "ambiguous candidate is not a missing feature"
|
|
||||||
|
|
||||||
# Simulate the user clicking the REAL hole in the relocate pick flow:
|
|
||||||
w._relocate_pending = [(asm1, ac1, pair1[2])]
|
|
||||||
w._on_relocate_picked(
|
|
||||||
(32.0, 20.0, 2.5), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0),
|
|
||||||
"cylindrical_face", f"asm_{ac1.id}_{body.id}",
|
|
||||||
)
|
|
||||||
assert np.allclose(pair1[2].position, (32.0, 20.0, 2.5), atol=1e-6), pair1[2].position
|
|
||||||
assert not pair1[2].is_invalid, "manual pick must re-validate the connector"
|
|
||||||
assert w._relocate_pending is None, "pending queue must drain after the pick"
|
|
||||||
w1 = ac1.position + ac1.rotation @ np.asarray(pair1[2].position)
|
|
||||||
w2 = pair1[1].position + pair1[1].rotation @ np.asarray(pair1[3].position)
|
|
||||||
assert np.allclose(w1, w2, atol=1e-6), (w1, w2)
|
|
||||||
print("far move: manual pick re-homed connector at", np.round(pair1[2].position, 3),
|
|
||||||
"partner at", np.round(pair1[1].position, 3))
|
|
||||||
|
|
||||||
# ── Legacy backfill: empty entity_type recovered from the auto name ────
|
|
||||||
from fluency.models.data_model import Connector
|
|
||||||
legacy = Connector(
|
|
||||||
name="Conn cylindrical_face anchor",
|
|
||||||
position=(32.0, 20.0, 2.5),
|
|
||||||
source_obj_id=f"asm_{ac1.id}_{body.id}",
|
|
||||||
)
|
|
||||||
assert legacy.entity_type == "cylindrical_face", legacy.entity_type
|
|
||||||
res = w._redetect_connector_on_geometry(legacy, ac1, comp)
|
|
||||||
assert res is not None and res[0] < 1e-3, res
|
|
||||||
print("legacy name backfill: entity_type =", legacy.entity_type)
|
|
||||||
|
|
||||||
print("CONNECTOR_RELOCATE_OK")
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
"""Headless repro: load assemblytest.fluency, compare sketch circle centers
|
|
||||||
vs. saved body hole axes vs. connector positions, then run the real
|
|
||||||
body-update path and check where connectors land.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
|
||||||
from OCP.GeomAbs import GeomAbs_Cylinder
|
|
||||||
from OCP.TopAbs import TopAbs_FACE
|
|
||||||
from OCP.TopExp import TopExp_Explorer
|
|
||||||
from OCP.TopoDS import TopoDS
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from fluency.ui.main_window import MainWindow
|
|
||||||
|
|
||||||
def etype(e):
|
|
||||||
if isinstance(e, dict):
|
|
||||||
return e.get("type")
|
|
||||||
return getattr(e, "entity_type", None) or getattr(e, "type", None)
|
|
||||||
|
|
||||||
def egeom(e):
|
|
||||||
if isinstance(e, dict):
|
|
||||||
return e.get("geometry")
|
|
||||||
return getattr(e, "geometry", None)
|
|
||||||
|
|
||||||
app = QApplication.instance() or QApplication([])
|
|
||||||
w = MainWindow()
|
|
||||||
|
|
||||||
path = os.path.join(os.path.dirname(__file__), "assemblytest.fluency")
|
|
||||||
ok = w._open_project_file(path)
|
|
||||||
assert ok, "failed to open demo file"
|
|
||||||
|
|
||||||
proj = w._project
|
|
||||||
|
|
||||||
for comp in proj.components.values():
|
|
||||||
print(f"=== {comp.name} ({comp.id})")
|
|
||||||
for sk in comp.sketches.values():
|
|
||||||
occ = sk.occ_sketch
|
|
||||||
if occ is None:
|
|
||||||
print(" sketch with no occ_sketch:", sk.id)
|
|
||||||
continue
|
|
||||||
for e in occ._entities.values():
|
|
||||||
t = etype(e)
|
|
||||||
if t == "circle":
|
|
||||||
g = egeom(e)
|
|
||||||
cid = e.get("id") if isinstance(e, dict) else e.id
|
|
||||||
print(f" circle id={cid} center=({g[0][0]!r}, {g[0][1]!r}) r={g[1]!r}")
|
|
||||||
for body in comp.bodies.values():
|
|
||||||
if not body.geometry:
|
|
||||||
print(f" body {body.name}: no geometry")
|
|
||||||
continue
|
|
||||||
shape = w._kernel._get_shape(body.geometry)
|
|
||||||
print(f" body {body.name}: extrude len={body.extrude_length}")
|
|
||||||
expl = TopExp_Explorer(shape, TopAbs_FACE)
|
|
||||||
while expl.More():
|
|
||||||
face = TopoDS.Face_s(expl.Current())
|
|
||||||
try:
|
|
||||||
adaptor = BRepAdaptor_Surface(face)
|
|
||||||
if adaptor.GetType() == GeomAbs_Cylinder:
|
|
||||||
cyl = adaptor.Cylinder()
|
|
||||||
loc = cyl.Location()
|
|
||||||
d = cyl.Axis().Direction()
|
|
||||||
print(
|
|
||||||
f" cyl axis at ({loc.X()!r}, {loc.Y()!r}) "
|
|
||||||
f"dir=({d.X():.4f},{d.Y():.4f},{d.Z():.4f})"
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
expl.Next()
|
|
||||||
|
|
||||||
asm = proj.get_active_assembly()
|
|
||||||
print("=== active assembly:", asm.name)
|
|
||||||
for ac in asm.components.values():
|
|
||||||
comp = proj.get_component_by_id(ac.component_id)
|
|
||||||
print(f" instance '{ac.name}' -> {comp.name}, pos={np.round(ac.position, 4)}")
|
|
||||||
for conn in ac.connectors.values():
|
|
||||||
print(
|
|
||||||
f" conn '{conn.name}' pos={np.round(conn.position, 6)} "
|
|
||||||
f"normal={np.round(conn.normal, 3)} et={conn.entity_type!r} "
|
|
||||||
f"invalid={conn.is_invalid}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# ── Run the real update path: rebuild bodies from sketch, recalc connectors ──
|
|
||||||
# Activate the component with the holes (Component 1).
|
|
||||||
comp1 = None
|
|
||||||
for comp in proj.components.values():
|
|
||||||
if any(
|
|
||||||
etype(e) == "circle"
|
|
||||||
for sk in comp.sketches.values()
|
|
||||||
for e in (sk.occ_sketch._entities.values() if sk.occ_sketch else [])
|
|
||||||
):
|
|
||||||
comp1 = comp
|
|
||||||
break
|
|
||||||
assert comp1 is not None
|
|
||||||
w._current_component = comp1
|
|
||||||
print("=== running _update_bodies_from_sketch()")
|
|
||||||
w._update_bodies_from_sketch()
|
|
||||||
print("=== running _recalculate_connectors()")
|
|
||||||
w._recalculate_connectors()
|
|
||||||
|
|
||||||
print("=== after update")
|
|
||||||
for comp in proj.components.values():
|
|
||||||
for body in comp.bodies.values():
|
|
||||||
if not body.geometry:
|
|
||||||
continue
|
|
||||||
shape = w._kernel._get_shape(body.geometry)
|
|
||||||
expl = TopExp_Explorer(shape, TopAbs_FACE)
|
|
||||||
axes = []
|
|
||||||
while expl.More():
|
|
||||||
face = TopoDS.Face_s(expl.Current())
|
|
||||||
try:
|
|
||||||
adaptor = BRepAdaptor_Surface(face)
|
|
||||||
if adaptor.GetType() == GeomAbs_Cylinder:
|
|
||||||
loc = adaptor.Cylinder().Location()
|
|
||||||
axes.append((round(loc.X(), 9), round(loc.Y(), 9)))
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
expl.Next()
|
|
||||||
print(f" {comp.name} body axes: {axes}")
|
|
||||||
for sk in comp.sketches.values():
|
|
||||||
for e in sk.occ_sketch._entities.values():
|
|
||||||
t = etype(e)
|
|
||||||
if t == "circle":
|
|
||||||
g = egeom(e)
|
|
||||||
print(f" {comp.name} circle: ({g[0][0]!r}, {g[0][1]!r})")
|
|
||||||
|
|
||||||
asm = proj.get_active_assembly()
|
|
||||||
for ac in asm.components.values():
|
|
||||||
for conn in ac.connectors.values():
|
|
||||||
print(
|
|
||||||
f" conn '{conn.name}' pos={np.round(conn.position, 6)} "
|
|
||||||
f"invalid={conn.is_invalid}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Partner alignment check: for each connection, world positions of the pair.
|
|
||||||
for aconn in asm.connections:
|
|
||||||
a1 = asm.components.get(aconn.first_ac_id)
|
|
||||||
a2 = asm.components.get(aconn.second_ac_id)
|
|
||||||
c1 = a1.connectors.get(aconn.first_connector_id)
|
|
||||||
c2 = a2.connectors.get(aconn.second_connector_id)
|
|
||||||
if c1 is None or c2 is None:
|
|
||||||
continue
|
|
||||||
w1 = a1.position + a1.rotation @ np.asarray(c1.position, dtype=float)
|
|
||||||
w2 = a2.position + a2.rotation @ np.asarray(c2.position, dtype=float)
|
|
||||||
print(
|
|
||||||
f" conn {aconn.id[:8]}: w1={np.round(w1, 6)} w2={np.round(w2, 6)} "
|
|
||||||
f"gap={float(np.linalg.norm(w1 - w2))!r}"
|
|
||||||
)
|
|
||||||
|
|
||||||
print("DEMO_REPRO_DONE")
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
"""Round-trip smoke test: instance-local sketches + modifiers survive save/load.
|
|
||||||
|
|
||||||
Builds a minimal project (component with a body, assembly with two instances,
|
|
||||||
one carrying an instance sketch + cut modifier + fillet modifier), saves to a
|
|
||||||
temp .fluency, reloads, and asserts:
|
|
||||||
1. the shared component is untouched by instance work,
|
|
||||||
2. the instance sketch + modifiers round-trip with sketch refs intact,
|
|
||||||
3. the plain instance has none.
|
|
||||||
"""
|
|
||||||
import os
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from fluency.models.data_model import (
|
|
||||||
Project, Component, Body, Sketch, Feature, Assembly, AssemblyComponent,
|
|
||||||
)
|
|
||||||
from fluency.io.project_io import save_project, load_project
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
project = Project(name="inst test")
|
|
||||||
comp = project.add_component()
|
|
||||||
comp.name = "BasePart"
|
|
||||||
body = comp.add_body(Body(name="MainBody"))
|
|
||||||
base_sketch = comp.add_sketch(Sketch(name="BaseSketch"))
|
|
||||||
body.features.append(
|
|
||||||
Feature(operation="extrude", sketch=base_sketch, length=10.0)
|
|
||||||
)
|
|
||||||
|
|
||||||
asm = project.add_assembly(Assembly(name="TestAsm"))
|
|
||||||
ac1 = asm.add_component_instance(comp.id, name="Instance A")
|
|
||||||
ac1.position = np.array([0.0, 0.0, 0.0])
|
|
||||||
ac2 = asm.add_component_instance(comp.id, name="Instance B")
|
|
||||||
ac2.position = np.array([50.0, 0.0, 0.0])
|
|
||||||
|
|
||||||
# Instance A: local sketch + cut modifier referencing it + fillet.
|
|
||||||
inst_sketch = ac1.add_instance_sketch()
|
|
||||||
inst_sketch.name = "InstCutSketch"
|
|
||||||
ac1.add_modifier(body.id, Feature(operation="cut", sketch=inst_sketch,
|
|
||||||
length=5.0, through_all=True))
|
|
||||||
ac1.add_modifier(body.id, Feature(operation="fillet", radius=1.0))
|
|
||||||
|
|
||||||
# ---- save / load ----
|
|
||||||
fd, path = tempfile.mkstemp(suffix=".fluency")
|
|
||||||
os.close(fd)
|
|
||||||
try:
|
|
||||||
save_project(project, path)
|
|
||||||
loaded, _view = load_project(path)
|
|
||||||
|
|
||||||
lcomp = loaded.components[comp.id]
|
|
||||||
lac1 = None
|
|
||||||
lac2 = None
|
|
||||||
for lasm in loaded.assemblies.values():
|
|
||||||
for ac in lasm.components.values():
|
|
||||||
if ac.name == "Instance A":
|
|
||||||
lac1 = ac
|
|
||||||
elif ac.name == "Instance B":
|
|
||||||
lac2 = ac
|
|
||||||
assert lac1 is not None and lac2 is not None, "instances missing"
|
|
||||||
|
|
||||||
# 1. component untouched
|
|
||||||
assert len(lcomp.sketches) == 1, "component sketch count changed"
|
|
||||||
assert len(lcomp.bodies[body.id].features) == 1, "feature chain changed"
|
|
||||||
assert not getattr(lcomp.bodies[body.id], "modifiers", None)
|
|
||||||
|
|
||||||
# 2. instance A round-trip
|
|
||||||
assert len(lac1.sketches) == 1, "instance sketch missing"
|
|
||||||
lsk_id, lsk = next(iter(lac1.sketches.items()))
|
|
||||||
assert lsk.name == "InstCutSketch"
|
|
||||||
mods = lac1.modifiers
|
|
||||||
lbody_id = next(iter(lcomp.bodies))
|
|
||||||
assert len(mods.get(lbody_id, [])) == 2, f"modifiers missing: {mods}"
|
|
||||||
cut, fil = mods[lbody_id][0], mods[lbody_id][1]
|
|
||||||
assert cut.operation == "cut" and fil.operation == "fillet"
|
|
||||||
assert cut.sketch is not None and cut.sketch.id == lsk_id, \
|
|
||||||
"cut sketch ref did not resolve to instance sketch"
|
|
||||||
assert cut.length == 5.0 and cut.through_all
|
|
||||||
|
|
||||||
# 3. plain instance clean
|
|
||||||
assert not lac2.sketches and not lac2.modifiers
|
|
||||||
|
|
||||||
print("ROUND_TRIP_OK")
|
|
||||||
finally:
|
|
||||||
os.unlink(path)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
"""Headless smoke: the manual re-pick fallback path (prompt -> frame -> pick mode)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from OCP.gp import gp_Pnt, gp_Dir, gp_Ax2
|
|
||||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeCylinder
|
|
||||||
from OCP.BRepAlgoAPI import BRepAlgoAPI_Cut
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication, QMessageBox
|
|
||||||
|
|
||||||
from fluency.ui.main_window import MainWindow
|
|
||||||
from fluency.models.data_model import Body
|
|
||||||
from fluency.geometry_occ.kernel import OCCGeometryObject
|
|
||||||
|
|
||||||
app = QApplication.instance() or QApplication([])
|
|
||||||
w = MainWindow()
|
|
||||||
|
|
||||||
comp = w._current_component
|
|
||||||
body = Body(name="plate")
|
|
||||||
comp.bodies[body.id] = body
|
|
||||||
box = BRepPrimAPI_MakeBox(60.0, 40.0, 5.0).Shape()
|
|
||||||
ax = gp_Ax2(gp_Pnt(10.0, 5.0, 0.0), gp_Dir(0, 0, 1))
|
|
||||||
box = BRepAlgoAPI_Cut(box, BRepPrimAPI_MakeCylinder(ax, 3.0, 6.0).Shape()).Shape()
|
|
||||||
body.geometry = OCCGeometryObject(box)
|
|
||||||
|
|
||||||
partner = w._project.add_component()
|
|
||||||
pbody = Body(name="partner")
|
|
||||||
partner.bodies[pbody.id] = pbody
|
|
||||||
pbody.geometry = OCCGeometryObject(BRepPrimAPI_MakeBox(30.0, 30.0, 10.0).Shape())
|
|
||||||
|
|
||||||
asm = w._project.get_active_assembly()
|
|
||||||
ac1 = asm.add_component_instance(comp.id, name="A")
|
|
||||||
ac2 = asm.add_component_instance(partner.id, name="B")
|
|
||||||
ac1.geom_cache[body.id] = body.geometry
|
|
||||||
c1 = ac1.add_connector(
|
|
||||||
position=(10.0, 5.0, 2.5), normal=(0, 0, 1), x_dir=(1, 0, 0),
|
|
||||||
source_obj_id=f"asm_{ac1.id}_{body.id}",
|
|
||||||
name="Conn hole", entity_type="cylindrical_face",
|
|
||||||
)
|
|
||||||
c1.partner_ac_id = ac2.id
|
|
||||||
|
|
||||||
# Activate the assembly view so the prompt path is taken.
|
|
||||||
w._assembly_view_active = True
|
|
||||||
w._selected_assembly_component_id = ac1.id
|
|
||||||
|
|
||||||
# Force a Yes from the question dialog, and record that it actually fired.
|
|
||||||
asked = {}
|
|
||||||
def fake_question(parent, title, text, buttons, default):
|
|
||||||
asked["title"] = title
|
|
||||||
return QMessageBox.StandardButton.Yes
|
|
||||||
QMessageBox.question = staticmethod(fake_question)
|
|
||||||
def fake_warning(*a, **k):
|
|
||||||
return QMessageBox.StandardButton.Ok
|
|
||||||
QMessageBox.warning = staticmethod(fake_warning)
|
|
||||||
|
|
||||||
w._prompt_relocate_unresolved([(asm, ac1, c1)])
|
|
||||||
assert asked.get("title") == "Connector Position Needed", asked
|
|
||||||
assert w._relocate_pending == [(asm, ac1, c1)], w._relocate_pending
|
|
||||||
assert w._viewer_3d._connector_pick_mode, "pick mode must be active"
|
|
||||||
|
|
||||||
# A wrong-part click must not consume the pending entry.
|
|
||||||
w._on_relocate_picked((0, 0, 0), (0, 0, 1), (1, 0, 0), "planar_face", f"asm_{ac2.id}_{pbody.id}")
|
|
||||||
assert w._relocate_pending == [(asm, ac1, c1)], "wrong part must not consume the pick"
|
|
||||||
|
|
||||||
# The right click re-homes and drains.
|
|
||||||
w._on_relocate_picked((40.0, 30.0, 2.5), (0, 0, 1), (1, 0, 0), "cylindrical_face", f"asm_{ac1.id}_{body.id}")
|
|
||||||
assert w._relocate_pending is None
|
|
||||||
assert np.allclose(c1.position, (40.0, 30.0, 2.5)), c1.position
|
|
||||||
assert not w._viewer_3d._connector_pick_mode, "pick mode must be off after completion"
|
|
||||||
|
|
||||||
# Esc cancel mid-flight clears the state.
|
|
||||||
w._relocate_pending = [(asm, ac1, c1)]
|
|
||||||
w._start_relocate_pick_next()
|
|
||||||
w._on_connector_pick_cancelled()
|
|
||||||
assert w._relocate_pending is None
|
|
||||||
assert not w._viewer_3d._connector_pick_mode
|
|
||||||
print("RELOCATE_PICK_FALLBACK_OK")
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
"""Headless test: update_external_entities handles circle/arc dict entries.
|
|
||||||
|
|
||||||
Reproduces the crash where a re-projected face contains a circular edge
|
|
||||||
(e.g. an instance cut hole): _project_face_to_uv returns a mixed list of
|
|
||||||
polylines + curve dicts, and update_external_entities used to unpack the
|
|
||||||
dict entries as (u, v) tuples.
|
|
||||||
|
|
||||||
Covers:
|
|
||||||
1. mixed projection (polylines + circle dict) -> rebuild + rebind path,
|
|
||||||
no crash, no duplicate/orphan curve entities, user geometry re-anchored.
|
|
||||||
2. repeated update with the same mixed projection -> stable (idempotent).
|
|
||||||
3. polylines-only same-topology projection -> in-place path still works
|
|
||||||
(external ids preserved).
|
|
||||||
"""
|
|
||||||
import math
|
|
||||||
|
|
||||||
from fluency.geometry_occ.sketch import OCCSketch
|
|
||||||
|
|
||||||
RECT = [
|
|
||||||
[(0.0, 0.0), (10.0, 0.0)],
|
|
||||||
[(10.0, 0.0), (10.0, 10.0)],
|
|
||||||
[(10.0, 10.0), (0.0, 10.0)],
|
|
||||||
[(0.0, 10.0), (0.0, 0.0)],
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _counts(sk):
|
|
||||||
ents = list(sk._entities.values())
|
|
||||||
return {
|
|
||||||
"ext_points": sum(
|
|
||||||
1
|
|
||||||
for e in ents
|
|
||||||
if e.entity_type == "point" and getattr(e, "is_external", False)
|
|
||||||
),
|
|
||||||
"ext_lines": sum(
|
|
||||||
1
|
|
||||||
for e in ents
|
|
||||||
if e.entity_type == "line" and getattr(e, "is_external", False)
|
|
||||||
),
|
|
||||||
"circles": sum(1 for e in ents if e.entity_type == "circle"),
|
|
||||||
"arcs": sum(1 for e in ents if e.entity_type == "arc"),
|
|
||||||
"user_points": sum(
|
|
||||||
1
|
|
||||||
for e in ents
|
|
||||||
if e.entity_type == "point" and not getattr(e, "is_external", False)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_mixed_projection_rebuild():
|
|
||||||
sk = OCCSketch()
|
|
||||||
sk.add_external_polylines([list(p) for p in RECT])
|
|
||||||
center = sk.add_external_point(5.0, 5.0)
|
|
||||||
sk.add_circle(center, 2.0)
|
|
||||||
user = sk.add_point(5.0, 5.0)
|
|
||||||
assert sk.constrain_coincident(user, center)
|
|
||||||
assert sk.solve()
|
|
||||||
|
|
||||||
# Re-projection: same rectangle, circle moved + resized -> mixed list.
|
|
||||||
new_proj = [
|
|
||||||
list(p) for p in RECT
|
|
||||||
] + [
|
|
||||||
{"type": "circle", "center": [6.0, 6.0], "radius": 1.5},
|
|
||||||
]
|
|
||||||
old_ext_ids = set(sk._external_entity_ids)
|
|
||||||
assert sk.update_external_entities(new_proj), "rebuild + rebind solve failed"
|
|
||||||
|
|
||||||
c = _counts(sk)
|
|
||||||
assert c["circles"] == 1, f"duplicate circle entities: {c}"
|
|
||||||
assert c["ext_points"] == 5, f"ext point count wrong (expect 4 corners + 1 centre): {c}"
|
|
||||||
assert c["ext_lines"] == 4, f"ext line count wrong (expect 4): {c}"
|
|
||||||
assert c["user_points"] == 1
|
|
||||||
# The coincident rebind must anchor the user point to the NEW centre.
|
|
||||||
ux, uy = user.geometry
|
|
||||||
assert math.hypot(ux - 6.0, uy - 6.0) < 1e-6, f"user point at {(ux, uy)}"
|
|
||||||
# Rebuild path: fresh external ids.
|
|
||||||
assert not (old_ext_ids & sk._external_entity_ids)
|
|
||||||
|
|
||||||
# Idempotent second pass with the same projection.
|
|
||||||
assert sk.update_external_entities(list(new_proj)), "second pass failed"
|
|
||||||
c2 = _counts(sk)
|
|
||||||
assert c2 == c, f"counts changed on second pass: {c} -> {c2}"
|
|
||||||
ux, uy = user.geometry
|
|
||||||
assert math.hypot(ux - 6.0, uy - 6.0) < 1e-6
|
|
||||||
print("test_mixed_projection_rebuild OK")
|
|
||||||
|
|
||||||
|
|
||||||
def test_polylines_only_inplace():
|
|
||||||
sk = OCCSketch()
|
|
||||||
sk.add_external_polylines([list(p) for p in RECT])
|
|
||||||
corner = None
|
|
||||||
for eid in sk._external_entity_ids:
|
|
||||||
ent = sk._entities[eid]
|
|
||||||
if ent.entity_type == "point" and ent.geometry == (0.0, 0.0):
|
|
||||||
corner = ent
|
|
||||||
break
|
|
||||||
assert corner is not None
|
|
||||||
user = sk.add_point(0.0, 0.0)
|
|
||||||
assert sk.constrain_coincident(user, corner)
|
|
||||||
assert sk.solve()
|
|
||||||
|
|
||||||
# Same topology, slightly shifted rectangle -> in-place move.
|
|
||||||
moved = [[(u + 1.0, v + 2.0) for (u, v) in poly] for poly in RECT]
|
|
||||||
old_ext_ids = set(sk._external_entity_ids)
|
|
||||||
assert sk.update_external_entities(moved), "in-place solve failed"
|
|
||||||
assert sk._external_entity_ids == old_ext_ids, "in-place path must keep ids"
|
|
||||||
ux, uy = user.geometry
|
|
||||||
assert math.hypot(ux - 1.0, uy - 2.0) < 1e-6, f"user point at {(ux, uy)}"
|
|
||||||
print("test_polylines_only_inplace OK")
|
|
||||||
|
|
||||||
|
|
||||||
def test_arc_import_shares_corners():
|
|
||||||
"""_import_external_curves must merge arc endpoints with existing
|
|
||||||
polyline corner points (no floating duplicate endpoints)."""
|
|
||||||
sk = OCCSketch()
|
|
||||||
# Rectangle with the top-right corner filleted: the arc endpoints must
|
|
||||||
# land on the truncated-edge corner points, not create new ones.
|
|
||||||
r = 2.0
|
|
||||||
sk.add_external_polylines([
|
|
||||||
[(0.0, 0.0), (10.0, 0.0)],
|
|
||||||
[(10.0, 0.0), (10.0, 10.0 - r)],
|
|
||||||
[(10.0 - r, 10.0), (0.0, 10.0)],
|
|
||||||
[(0.0, 10.0), (0.0, 0.0)],
|
|
||||||
])
|
|
||||||
sk._import_external_curves(
|
|
||||||
[],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"type": "arc",
|
|
||||||
"center": [10.0 - r, 10.0 - r],
|
|
||||||
"start": [10.0, 10.0 - r],
|
|
||||||
"end": [10.0 - r, 10.0],
|
|
||||||
"radius": r,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
)
|
|
||||||
c = _counts(sk)
|
|
||||||
# 5 corners + 1 arc centre, NO extra endpoint entities.
|
|
||||||
assert c["ext_points"] == 6, f"expected 6 ext points, got {c}"
|
|
||||||
assert c["arcs"] == 1, f"expected 1 arc, got {c}"
|
|
||||||
assert sk.solve()
|
|
||||||
print("test_arc_import_shares_corners OK")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
test_mixed_projection_rebuild()
|
|
||||||
test_polylines_only_inplace()
|
|
||||||
test_arc_import_shares_corners()
|
|
||||||
print("UNDERLAY_CURVES_OK")
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Thomas Herrmann
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -1,111 +1,4 @@
|
|||||||
# Fluency CAD 2.0
|
# fluencyCAD
|
||||||
|
|
||||||
A parametric CAD application built on OpenCASCADE Technology (OCCT) with a modern pygfx-based 3D renderer.
|
A CAD program based on QT - sdfCAD - Solvespace and VTK meant to deliver a fluent distraction free CAD experience with alot of freedom thanks to sdf based meshes.
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- **OpenCASCADE Geometry Kernel**: Industry-standard BRep geometry with exact precision
|
|
||||||
- **STEP/IGES Import/Export**: Full support for industry-standard CAD file formats
|
|
||||||
- **Parametric Sketching**: 2D sketching with constraint solving using SolveSpace
|
|
||||||
- **Boolean Operations**: Union, difference, and intersection
|
|
||||||
- **Fillet & Chamfer**: Apply edge treatments to solid bodies
|
|
||||||
- **Modern Renderer**: WebGPU-based rendering with pygfx (smaller footprint than VTK)
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
fluency/
|
|
||||||
├── src/fluency/
|
|
||||||
│ ├── geometry/ # Geometry abstraction layer
|
|
||||||
│ │ └── base.py # Abstract interfaces
|
|
||||||
│ ├── geometry_occ/ # OpenCASCADE implementation
|
|
||||||
│ │ ├── kernel.py # OCGeometryKernel
|
|
||||||
│ │ └── sketch.py # OCCSketch with constraints
|
|
||||||
│ ├── rendering/ # Rendering abstraction
|
|
||||||
│ │ ├── base.py # Abstract renderer
|
|
||||||
│ │ └── pygfx_renderer.py
|
|
||||||
│ ├── models/ # Data models
|
|
||||||
│ │ └── data_model.py # Project, Component, Sketch, Body
|
|
||||||
│ └── main.py # Application entry point
|
|
||||||
├── tests/
|
|
||||||
│ └── test_geometry.py
|
|
||||||
└── pyproject.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create virtual environment
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate # On Windows: .venv\Scripts\activate
|
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
pip install -e ".[dev]"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
| Package | Purpose |
|
|
||||||
|---------|---------|
|
|
||||||
| cadquery-ocp | OpenCASCADE Python bindings (OCP) |
|
|
||||||
| pygfx | WebGPU-based 3D renderer |
|
|
||||||
| wgpu | WebGPU Python bindings |
|
|
||||||
| PySide6 | Qt GUI framework |
|
|
||||||
| numpy | Numerical computing |
|
|
||||||
| scipy | Scientific computing |
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run the application
|
|
||||||
fluency-cad
|
|
||||||
|
|
||||||
# Or directly
|
|
||||||
python -m fluency.main
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Example
|
|
||||||
|
|
||||||
```python
|
|
||||||
from fluency.geometry_occ.kernel import OCGeometryKernel
|
|
||||||
from fluency.geometry.base import Point2D
|
|
||||||
|
|
||||||
# Create kernel
|
|
||||||
kernel = OCGeometryKernel()
|
|
||||||
|
|
||||||
# Create a sketch
|
|
||||||
points = [
|
|
||||||
Point2D(0, 0),
|
|
||||||
Point2D(10, 0),
|
|
||||||
Point2D(10, 10),
|
|
||||||
Point2D(0, 10),
|
|
||||||
]
|
|
||||||
polygon = kernel.create_polygon(points)
|
|
||||||
|
|
||||||
# Extrude to 3D
|
|
||||||
body = kernel.extrude(polygon, height=20.0)
|
|
||||||
|
|
||||||
# Apply fillet
|
|
||||||
body = kernel.fillet(body, radius=2.0)
|
|
||||||
|
|
||||||
# Export to STEP
|
|
||||||
kernel.export_step(body, "part.step")
|
|
||||||
|
|
||||||
# Export to STL
|
|
||||||
kernel.export_stl(body, "part.stl")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Comparison: Before vs After
|
|
||||||
|
|
||||||
| Aspect | Before (SDF + VTK) | After (OCC + pygfx) |
|
|
||||||
|--------|-------------------|---------------------|
|
|
||||||
| Geometry Precision | Approximate (mesh) | Exact (BRep) |
|
|
||||||
| Export Formats | STL only | STEP, IGES, STL, BREP |
|
|
||||||
| File Size | Large (mesh) | Small (BRep) |
|
|
||||||
| Fillet/Chamfer | Approximate | Exact |
|
|
||||||
| Dependency Size | ~200MB (VTK) | ~30MB (pygfx) |
|
|
||||||
| Constraint Solver | SolveSpace (separate) | SolveSpace (integrated) |
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT License
|
|
||||||
|
|||||||
@@ -1,219 +0,0 @@
|
|||||||
# Fluency CAD — Agent Guide
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
**Fluency CAD 2.0** is a parametric CAD application built on **OpenCASCADE Technology (OCCT)** with a modern **pygfx-based 3D renderer**. It provides 2D sketching with SolveSpace constraint solving, boolean operations, STEP/IGES/STL import/export, and exact BRep geometry.
|
|
||||||
|
|
||||||
- Language: **Python 3.10+**
|
|
||||||
- GUI: **PySide6** (Qt6)
|
|
||||||
- Geometry Kernel: **OCP** (cadquery-ocp — OpenCASCADE Python bindings)
|
|
||||||
- Constraint Solver: **python_solvespace**
|
|
||||||
- Renderer: **pygfx** (WebGPU) + **OCCRenderer** (native OCC AIS display)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
src/fluency/
|
|
||||||
├── __init__.py # Package entry (version, imports)
|
|
||||||
├── main.py # Application entry point, MainWindow, Sketch2DWidget (~5000 lines)
|
|
||||||
├── sketch_solver.py # SolveSpace constraint solver wrapper (legacy)
|
|
||||||
├── geometry/
|
|
||||||
│ └── base.py # Abstract interfaces: GeometryKernel, SketchInterface, data classes
|
|
||||||
├── geometry_occ/
|
|
||||||
│ ├── kernel.py # OCGeometryKernel (extrude, boolean, fillet, import/export, mesh)
|
|
||||||
│ └── sketch.py # OCCSketch with SolveSpace integration (face detection, constraints)
|
|
||||||
├── models/
|
|
||||||
│ └── data_model.py # Project, Component, Sketch, Body dataclasses
|
|
||||||
├── rendering/
|
|
||||||
│ ├── base.py # Abstract Renderer interface
|
|
||||||
│ ├── occ_renderer.py # OCC AIS renderer (preferred — smooth BRep display)
|
|
||||||
│ └── pygfx_renderer.py # Legacy pygfx renderer
|
|
||||||
├── utils/ # Utility modules
|
|
||||||
└── widgets/ # Custom widgets
|
|
||||||
tests/
|
|
||||||
└── test_geometry.py # Comprehensive test suite (52+ tests)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Classes & Responsibilities
|
|
||||||
|
|
||||||
| Class | File | Purpose |
|
|
||||||
|-------|------|---------|
|
|
||||||
| `OCGeometryKernel` | `kernel.py` | OCC shape ops: extrude, boolean, fillet, mesh, import/export |
|
|
||||||
| `OCCSketch` | `sketch.py` | 2D sketch with SolveSpace solver, face detection, workplane |
|
|
||||||
| `OCCSketchEntity` | `sketch.py` | Entity (point/line/circle/arc) with solver handle, is_construction, is_external |
|
|
||||||
| `Sketch2DWidget` | `main.py` | Qt widget for interactive 2D sketching (draw, snap, constrain) |
|
|
||||||
| `MainWindow` | `main.py` | Main application window, toolbars, 3D viewer, operations |
|
|
||||||
| `OCCRenderer` | `occ_renderer.py` | Native OCC AIS display (shaded + edges, face pick) |
|
|
||||||
| `Sketch` | `data_model.py` | Data model: workplane, occ_sketch ref, source_body_id |
|
|
||||||
| `Body` | `data_model.py` | 3D solid with geometry, visibility, render object |
|
|
||||||
| `Component` | `data_model.py` | Container for sketches and bodies |
|
|
||||||
| `Project` | `data_model.py` | Top-level container with kernel |
|
|
||||||
|
|
||||||
### Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
User draws in Sketch2DWidget
|
|
||||||
→ OCCSketch entities created in solver
|
|
||||||
→ Constraint solving (python_solvespace)
|
|
||||||
→ OCCSketch.get_geometry() → detect_faces() → build_face_geometry()
|
|
||||||
→ OCGeometryKernel.extrude() → BRepPrimAPI_MakePrism
|
|
||||||
→ Boolean operations → Body added to Component
|
|
||||||
→ OCCRenderer.add_shape() → AIS display
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Install editable
|
|
||||||
pip install -e ".[dev]"
|
|
||||||
|
|
||||||
# Run app
|
|
||||||
python -m fluency.main
|
|
||||||
|
|
||||||
# Run tests (52 tests)
|
|
||||||
python -m pytest tests/test_geometry.py -v
|
|
||||||
|
|
||||||
# Run single test
|
|
||||||
python -m pytest tests/test_geometry.py::TestOCCSketch::test_workplane_extrude_with_hole -xvs
|
|
||||||
|
|
||||||
# Quck geometry test (raw OCC, no Qt)
|
|
||||||
python -c "from fluency.geometry_occ.sketch import OCCSketch; ..."
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Conventions
|
|
||||||
|
|
||||||
### General
|
|
||||||
- Line length: **100 chars** (black/ruff config)
|
|
||||||
- Target Python: **3.10+** (uses `from __future__ import annotations`, walrus, pattern matching)
|
|
||||||
- Docstrings: Google/NumPy style preferred
|
|
||||||
- Logging: `logger = logging.getLogger(__name__)` with `logging.DEBUG` level
|
|
||||||
|
|
||||||
### OCC / OCP
|
|
||||||
- Always use `is not None` for OCP objects — `TopoDS_Shape.__bool__` can be falsy even for valid shapes
|
|
||||||
- `BRepBuilderAPI_MakeFace.Add(wire)` expects a `TopoDS_Wire`. `wire.Reversed()` returns `TopoDS_Shape` → cast via `_TopoDS.Wire_s(wire.Reversed())`
|
|
||||||
- Face normal direction: check `face.Orientation()` vs `TopAbs_REVERSED` — a REVERSED face's outward normal is the NEGATION of the surface axis
|
|
||||||
- `TopoDS_Wire_s(shape)`, `TopoDS_Face_s(shape)` — use `_s` suffix from OCP for downcasts
|
|
||||||
- Mesh: `BRepMesh_IncrementalMesh(shape, tol, False, 0.15, True)` — default deflection 0.15 rad for smooth curves
|
|
||||||
|
|
||||||
### Extrude / Cut Workflow
|
|
||||||
- Snapshot `list(self._current_component.bodies.items())` **BEFORE** `add_body()` — the new body must not be in the target set
|
|
||||||
- Cut targets the **source body** (`sketch._source_body_id` from face pick), not `bodies[0]`
|
|
||||||
- The fix: apply boolean to **target** geometry, then remove tool body
|
|
||||||
- Plain extrude with holes: inner wires must have **OPPOSITE** geometric winding to the outer wire (see `build_face_geometry` and `_loop_signed_area`)
|
|
||||||
|
|
||||||
### Sketch / Solvers
|
|
||||||
- `python_solvespace` has NO remove API for entities/constraints. Deleting requires: drop from `_points`/`_lines`, prune `_constraint_log`, `_rebuild_solver()` (recreates entire system), `_rebuild_labels()`, re-solve
|
|
||||||
- `_constraint_log`: each entry is `{"type": str, "ids": tuple[int,...], "params": tuple, "labels": set[str]}`
|
|
||||||
- Constraint labels: stored on **point** entities for paintEvent rendering; rebuilt via `_rebuild_labels()`
|
|
||||||
- Line constraints (`horizontal`/`vertical`/`parallel`/`perpendicular`) need the **line's** solver handle, not a point's. Use `_find_line_sketch_entity()` to get the correct handle
|
|
||||||
- External entities (underlay): `is_external=True`, `is_construction=True`, fixed in solver (always `dragged`). Stored in `_external_entity_ids`, excluded from `_line_segments()`, `get_polygon_points()`, `get_closed_loops()`, `detect_faces()`, `get_geometry()`
|
|
||||||
|
|
||||||
### Face Detection
|
|
||||||
- `get_closed_loops()`: uses snapped-coordinate graph (`_SNAP_TOL = 1e-4`) from line endpoint adjacency. Only accepts simple cycles (all nodes degree 2)
|
|
||||||
- `detect_faces()`: even-odd nesting rule via `_loop_contains`. Even depth = outer boundary, odd = hole
|
|
||||||
- `_loop_rep_point`: midpoint between centroid and first vertex. **Fragile** — can land inside a nested shape for certain geometries (e.g., a small hole near the centroid's direction from the first vertex)
|
|
||||||
- `_loop_signed_area`: shoelace formula for polygons, `πr²` (positive = CCW) for circles
|
|
||||||
|
|
||||||
### Rendering
|
|
||||||
- **OCCRenderer** is the main renderer (not pygfx). Uses `AIS_Shape`, `V3d_Viewer`, `AIS_InteractiveContext`
|
|
||||||
- Face pick: `pick_planar_face(x, y)` → `MoveTo` → `DetectedShape` → `TopoDS_Face_s` → `BRepAdaptor_Surface` plane check
|
|
||||||
- Highlight: `highlight_face(face)` creates a transparent AIS overlay; `clear_face_highlight()` removes it
|
|
||||||
- Preview: `preview_shape(shape)` for live transparent extrude preview
|
|
||||||
- Navigation: Left=orbit, Middle=pan, Wheel=zoom. **Right is RESERVED** — check user before reassigning
|
|
||||||
|
|
||||||
### Paint-Event Safety
|
|
||||||
- Every constraint-tag rendering loop wraps each entry in `try/except` so a bad entry (dangling id, corrupted geometry) doesn't crash the entire paint event
|
|
||||||
- `_point_world()` and `_entity_anchor()` return `None` (not raise) for malformed input
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Known Bugs & Fix Patterns
|
|
||||||
|
|
||||||
### 1. Hole Orientation in Extrusion (FIXED 2026-07-03)
|
|
||||||
**Symptom**: Inner shapes (circle/triangle/slot) inside a rectangle become solid islands instead of holes when extruding, depending on drag direction.
|
|
||||||
|
|
||||||
**Root Cause**: `wire_loop` in `build_face_geometry` unconditionally reversed hole wires (`w.Reversed()`). When the outer polygon was CW-winding (e.g., dragging from top-left to bottom-right), the reversed inner had the SAME effective direction as the outer, making OCC treat it as solid.
|
|
||||||
|
|
||||||
**Fix**: Added `_loop_signed_area()` to compute geometric winding. Hole wires are only reversed when their natural winding matches the outer's (ensuring opposite winding for holes).
|
|
||||||
|
|
||||||
**Relevant code**: `sketch.py`, `build_face_geometry()` and `_loop_signed_area()`
|
|
||||||
|
|
||||||
### 2. _loop_rep_point Fragility (KNOWN)
|
|
||||||
**Symptom**: Face detection fails when a nested shape contains the outer loop's representative point (midpoint between centroid and first vertex).
|
|
||||||
|
|
||||||
**Would-be fix**: Use a guaranteed-interior point (maximum inscribed circle center or perturbed centroid) instead of the centroid-first-vertex midpoint.
|
|
||||||
|
|
||||||
### 3. Extrude Cut / Target Selection (FIXED 2026-06-29)
|
|
||||||
**Symptom**: Cut created a separate "cavity-shaped" body next to the original instead of modifying the target.
|
|
||||||
|
|
||||||
**Fix**: Boolean result stored on TARGET body geometry; tool body removed from component. Auto-target via `sketch._source_body_id`.
|
|
||||||
|
|
||||||
### 4. Workplane Preservation (FIXED 2026-06-29)
|
|
||||||
**Symptom**: Sketch placed on a face lost its workplane after being added to component.
|
|
||||||
|
|
||||||
**Fix**: Copy `occ_sketch` workplane fields into `Sketch` dataclass BEFORE `apply_workplane()`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## API Quirks
|
|
||||||
|
|
||||||
- **`QPoint(0,0)`**: falsy via `isNull()` in PySide6 → always use `is not None` for `Optional[QPoint]`
|
|
||||||
- **`QMouseEvent`/`QPainterPath`**: live in `PySide6.QtGui` (NOT `QtCore`)
|
|
||||||
- **`BRepBuilderAPI_MakeFace.Add()`**: needs `TopoDS_Wire`. `wire.Reversed()` returns `TopoDS_Shape` — cast via `TopoDS_Wire_s()`
|
|
||||||
- **python_solvespace**: NO entity/constraint remove API — workaround via `_rebuild_solver()`. Parameters read via `solver.params(handle.params)` → returns `(x, y)` tuple
|
|
||||||
- **OCGeometryKernel.extrude**: unwraps `OCCGeometryObject`, raw `TopoDS_Shape`, or cadquery `Workplane`. Always use `is not None` for the shape (not truthiness)
|
|
||||||
- **Sketch._source_body_id**: dynamic attribute set on `Sketch` dataclass, set during face-pick flow
|
|
||||||
- **`_get_shape(obj)`**: returns `obj.shape.wrapped` for `OCCGeometryObject`, `obj.shape` for raw shapes, `None` for empty. Use `is not None` guards everywhere
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Memory / Agent Context
|
|
||||||
|
|
||||||
This project has extensive Pi memory (hermes-memory) for:
|
|
||||||
- `project="fluency"` with `target="failure"`: bugs, fixes, corrections, insights
|
|
||||||
- `project="fluency"` with `target="memory"`: conventions, decisions, workflow patterns
|
|
||||||
- Available skills: `fix-cad-app-pipeline`, `refactor-from-cadquery-to-ocp`
|
|
||||||
|
|
||||||
Key memory queries for debugging:
|
|
||||||
- "hole orientation" → `_loop_signed_area` / `build_face_geometry` fix
|
|
||||||
- "extrude cut auto-target" → cut/target body fix
|
|
||||||
- "workplane preservation" → _add_sketch_to_component fix
|
|
||||||
- "_loop_rep_point" → face detection fragility
|
|
||||||
- "paint-event safety" → try/except per entry pattern
|
|
||||||
- "solver rebuild" → delete workflow via _rebuild_solver
|
|
||||||
- "face pick origin" → pick_planar_face face bbox centre
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing Patterns
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Direct OCC test (no Qt)
|
|
||||||
from OCP.BRepBuilderAPI import BRepBuilderAPI_MakePolygon, BRepBuilderAPI_MakeFace
|
|
||||||
from OCP.gp import gp_Pnt
|
|
||||||
from OCP.BRepPrimAPI import BRepPrimAPI_MakePrism
|
|
||||||
from OCP.GProp import GProp_GProps; from OCP.BRepGProp import BRepGProp
|
|
||||||
|
|
||||||
# Build test shape, extrude, verify volume
|
|
||||||
mp = BRepBuilderAPI_MakePolygon(); ...
|
|
||||||
g = GProp_GProps(); BRepGProp.VolumeProperties_s(shape, g)
|
|
||||||
assert abs(g.Mass() - expected) < 0.1
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Sketch-based test
|
|
||||||
from fluency.geometry_occ.sketch import OCCSketch
|
|
||||||
from fluency.geometry_occ.kernel import OCGeometryKernel
|
|
||||||
|
|
||||||
sk = OCCSketch()
|
|
||||||
# ... add points, lines, circles ...
|
|
||||||
sk.solve()
|
|
||||||
geom = sk.get_geometry()
|
|
||||||
solid = OCGeometryKernel().extrude(geom, 10.0)
|
|
||||||
```
|
|
||||||
Binary file not shown.
@@ -1,108 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Debug script to test point dragging functionality in ImprovedSketchWidget
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
sys.path.append('/Volumes/Data_drive/Programming/fluency')
|
|
||||||
|
|
||||||
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QHBoxLayout
|
|
||||||
from PySide6.QtCore import Qt
|
|
||||||
from drawing_modules.improved_sketcher import ImprovedSketchWidget, SketchMode, Point2D
|
|
||||||
import logging
|
|
||||||
|
|
||||||
# Set up logging to see debug messages
|
|
||||||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
class DebugMainWindow(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.setWindowTitle("Debug Point Dragging")
|
|
||||||
self.resize(1000, 700)
|
|
||||||
|
|
||||||
# Create central widget
|
|
||||||
central_widget = QWidget()
|
|
||||||
self.setCentralWidget(central_widget)
|
|
||||||
layout = QVBoxLayout(central_widget)
|
|
||||||
|
|
||||||
# Create button layout
|
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
|
|
||||||
# Add test points button
|
|
||||||
add_points_btn = QPushButton("Add Test Points")
|
|
||||||
add_points_btn.clicked.connect(self.add_test_points)
|
|
||||||
button_layout.addWidget(add_points_btn)
|
|
||||||
|
|
||||||
# Check mode button
|
|
||||||
check_mode_btn = QPushButton("Check Mode")
|
|
||||||
check_mode_btn.clicked.connect(self.check_mode)
|
|
||||||
button_layout.addWidget(check_mode_btn)
|
|
||||||
|
|
||||||
# Reset mode button
|
|
||||||
reset_mode_btn = QPushButton("Reset to NONE Mode")
|
|
||||||
reset_mode_btn.clicked.connect(self.reset_mode)
|
|
||||||
button_layout.addWidget(reset_mode_btn)
|
|
||||||
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
# Create the sketcher widget
|
|
||||||
self.sketcher = ImprovedSketchWidget()
|
|
||||||
layout.addWidget(self.sketcher)
|
|
||||||
|
|
||||||
print("Debug window created. Current mode:", self.sketcher.current_mode)
|
|
||||||
|
|
||||||
def add_test_points(self):
|
|
||||||
"""Add some test points to the sketch"""
|
|
||||||
print("Adding test points...")
|
|
||||||
|
|
||||||
# Add a few points at different locations
|
|
||||||
points = [
|
|
||||||
Point2D(100, 100),
|
|
||||||
Point2D(200, 150),
|
|
||||||
Point2D(150, 200),
|
|
||||||
Point2D(50, 250)
|
|
||||||
]
|
|
||||||
|
|
||||||
for point in points:
|
|
||||||
self.sketcher.sketch.add_point(point)
|
|
||||||
print(f"Added point at ({point.x}, {point.y})")
|
|
||||||
|
|
||||||
self.sketcher.update()
|
|
||||||
print(f"Total points in sketch: {len(self.sketcher.sketch.points)}")
|
|
||||||
|
|
||||||
def check_mode(self):
|
|
||||||
"""Check current mode and dragging state"""
|
|
||||||
print(f"Current mode: {self.sketcher.current_mode}")
|
|
||||||
print(f"Dragging point: {self.sketcher.dragging_point}")
|
|
||||||
print(f"Drag start pos: {self.sketcher.drag_start_pos}")
|
|
||||||
print(f"Hovered point: {self.sketcher.hovered_point}")
|
|
||||||
print(f"Number of points: {len(self.sketcher.sketch.points)}")
|
|
||||||
|
|
||||||
# Check if points have solver handles
|
|
||||||
for i, point in enumerate(self.sketcher.sketch.points):
|
|
||||||
print(f"Point {i}: ({point.x}, {point.y}), handle: {point.handle}")
|
|
||||||
|
|
||||||
def reset_mode(self):
|
|
||||||
"""Reset to NONE mode to enable dragging"""
|
|
||||||
print("Resetting mode to NONE")
|
|
||||||
self.sketcher.set_mode(SketchMode.NONE)
|
|
||||||
print(f"Mode after reset: {self.sketcher.current_mode}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
|
|
||||||
window = DebugMainWindow()
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
print("\n" + "="*50)
|
|
||||||
print("DEBUG INSTRUCTIONS:")
|
|
||||||
print("1. Click 'Add Test Points' to create some points")
|
|
||||||
print("2. Click 'Check Mode' to verify the current state")
|
|
||||||
print("3. Click 'Reset to NONE Mode' to ensure dragging is enabled")
|
|
||||||
print("4. Try to drag points by clicking and dragging them")
|
|
||||||
print("5. Watch the console for debug messages")
|
|
||||||
print("="*50 + "\n")
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
|
||||||
@@ -1,253 +0,0 @@
|
|||||||
# Realistic Render View — Implementation Plan
|
|
||||||
|
|
||||||
## Context
|
|
||||||
|
|
||||||
Add a **"Render"** feature to Fluency CAD that opens a separate window for photorealistic rendering of the selected component or assembly (like KeyShot/Cacles).
|
|
||||||
|
|
||||||
**Constraints:**
|
|
||||||
- Open in a **new window** — don't clutter the workspace
|
|
||||||
- **Keep existing OCCRenderer** for the interactive 3D viewport — untouched
|
|
||||||
- Render backend must be a **separate, swappable module** so we can change the renderer later
|
|
||||||
- Use **Mitsuba 3** as the initial backend (`pip install mitsuba`, ~50MB)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────┐
|
|
||||||
│ Main Fluency Window (existing OCCRenderer — untouched) │
|
|
||||||
│ │
|
|
||||||
│ [Select body/assembly] → [Click "Render"] │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ ┌─────────────────────────────────────┐ │
|
|
||||||
│ │ RenderWindow (separate QMainWindow)│ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ ┌───────────────────────────────┐ │ │
|
|
||||||
│ │ │ RenderBackend (ABC) │ │ │
|
|
||||||
│ │ │ ├─ MitsubaBackend ← current │ │ │
|
|
||||||
│ │ │ ├─ (future: BlenderBackend) │ │ │
|
|
||||||
│ │ │ └─ (future: CyclesBackend) │ │ │
|
|
||||||
│ │ └───────────────────────────────┘ │ │
|
|
||||||
│ │ │ │\n│ │ [Image preview] [Progress bar] │ │
|
|
||||||
│ │ [Material ▾] [Quality ▾] [Render] │ │
|
|
||||||
│ │ [Export PNG] │ │
|
|
||||||
│ └─────────────────────────────────────┘ │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Swappable Backend Interface
|
|
||||||
|
|
||||||
```python
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
from dataclasses import dataclass
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RenderMaterial:
|
|
||||||
name: str
|
|
||||||
color: tuple[float, float, float] = (0.7, 0.7, 0.7)
|
|
||||||
metallic: float = 0.0 # 0.0–1.0
|
|
||||||
roughness: float = 0.5 # 0.0–1.0
|
|
||||||
bsdf_type: str = "diffuse" # diffuse | roughconductor | roughdielectric | plastic
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RenderCamera:
|
|
||||||
origin: tuple[float, float, float] = (100, 100, 100)
|
|
||||||
target: tuple[float, float, float] = (0, 0, 0)
|
|
||||||
up: tuple[float, float, float] = (0, 0, 1)
|
|
||||||
fov: float = 45.0
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class RenderSettings:
|
|
||||||
width: int = 1920
|
|
||||||
height: int = 1080
|
|
||||||
spp: int = 256 # samples per pixel
|
|
||||||
max_depth: int = 8 # path tracer bounces
|
|
||||||
|
|
||||||
class RenderBackend(ABC):
|
|
||||||
"""Swap this to change the rendering engine."""
|
|
||||||
@abstractmethod
|
|
||||||
def render(self, obj_path: str, material: RenderMaterial,
|
|
||||||
camera: RenderCamera, settings: RenderSettings) -> np.ndarray: ...
|
|
||||||
@abstractmethod
|
|
||||||
def render_preview(self, obj_path: str, material: RenderMaterial,
|
|
||||||
camera: RenderCamera, settings: RenderSettings) -> np.ndarray: ...
|
|
||||||
@abstractmethod
|
|
||||||
def name(self) -> str: ...
|
|
||||||
```
|
|
||||||
|
|
||||||
Switching backends later = write a new class implementing `RenderBackend`. One import change.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Mitsuba 3 Backend
|
|
||||||
|
|
||||||
### Why Mitsuba
|
|
||||||
|
|
||||||
| Feature | Status |
|
|
||||||
|---------|--------|
|
|
||||||
| `pip install mitsuba` | Single install, no system deps |
|
|
||||||
| True path tracing | GI, caustics, spectral rendering |
|
|
||||||
| PBR materials | `roughconductor`, `roughdielectric`, `diffuse`, `plastic` |
|
|
||||||
| Python dict API | Build scenes programmatically, no XML |
|
|
||||||
| CPU + GPU backends | `scalar_rgb` (CPU), `cuda_rgb` (NVIDIA) |
|
|
||||||
| Output formats | PNG, EXR (HDR) with tonemapping |
|
|
||||||
|
|
||||||
### OCC → OBJ Conversion Path
|
|
||||||
|
|
||||||
```python
|
|
||||||
from OCP.BRepMesh import BRepMesh_IncrementalMesh
|
|
||||||
from OCP.StlAPI import StlAPI_Writer
|
|
||||||
from OCP.BRep import BRep_Builder
|
|
||||||
import tempfile, os
|
|
||||||
|
|
||||||
def occ_shape_to_obj(shape, obj_path: str, linear_deflection: float = 0.1):
|
|
||||||
"""Tessellate OCC shape and write as OBJ for Mitsuba."""
|
|
||||||
tess = BRepMesh_IncrementalMesh(shape, linear_deflection, False, 0.5, True)
|
|
||||||
tess.Perform()
|
|
||||||
# Write STL (reliable), then convert to OBJ via trimesh or direct
|
|
||||||
writer = StlAPI_Writer()
|
|
||||||
writer.SetASCIIMode(False)
|
|
||||||
stl_path = obj_path.replace(".obj", ".stl")
|
|
||||||
writer.Write(shape, stl_path)
|
|
||||||
# Mitsuba can read STL directly, or we convert to OBJ
|
|
||||||
return stl_path
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mitsuba Scene Construction
|
|
||||||
|
|
||||||
```python
|
|
||||||
import mitsuba as mi
|
|
||||||
mi.set_variant("scalar_rgb")
|
|
||||||
|
|
||||||
def build_scene(mesh_path: str, material: RenderMaterial,
|
|
||||||
camera: RenderCamera, settings: RenderSettings) -> mi.Scene:
|
|
||||||
# Map our material to Mitsuba BSDF
|
|
||||||
bsdf_map = {
|
|
||||||
"diffuse": {"type": "diffuse", "reflectance": {"type": "rgb", "value": material.color}},
|
|
||||||
"roughconductor": {
|
|
||||||
"type": "roughconductor",
|
|
||||||
"material": "copper", # or铝, 钢, etc.
|
|
||||||
"alpha": material.roughness,
|
|
||||||
},
|
|
||||||
"roughdielectric": {
|
|
||||||
"type": "roughdielectric",
|
|
||||||
"int_ior": 1.5,
|
|
||||||
"alpha": material.roughness,
|
|
||||||
},
|
|
||||||
"plastic": {
|
|
||||||
"type": "plastic",
|
|
||||||
"diffuse_reflectance": {"type": "rgb", "value": material.color},
|
|
||||||
"int_ior": 1.5,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return mi.load_dict({
|
|
||||||
"type": "scene",
|
|
||||||
"integrator": {"type": "path", "max_depth": settings.max_depth},
|
|
||||||
"sensor": {
|
|
||||||
"type": "perspective",
|
|
||||||
"fov": camera.fov,
|
|
||||||
"to_world": mi.ScalarTransform4f.look_at(
|
|
||||||
origin=camera.origin, target=camera.target, up=camera.up
|
|
||||||
),
|
|
||||||
"film": {"type": "hdrfilm", "width": settings.width, "height": settings.height},
|
|
||||||
"sampler": {"type": "independent", "sample_count": settings.spp},
|
|
||||||
},
|
|
||||||
"emitter": {"type": "constant"},
|
|
||||||
"shape": {
|
|
||||||
"type": "stl", # or "obj"
|
|
||||||
"filename": mesh_path,
|
|
||||||
"bsdf": bsdf_map.get(material.bsdf_type, bsdf_map["diffuse"]),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
|
||||||
|
|
||||||
| File | Action | Description |
|
|
||||||
|------|--------|-------------|
|
|
||||||
| `src/fluency/rendering/render_backend.py` | **NEW** | Abstract `RenderBackend`, `RenderMaterial`, `RenderCamera`, `RenderSettings` |
|
|
||||||
| `src/fluency/rendering/mitsuba_backend.py` | **NEW** | `MitsubaBackend(RenderBackend)` implementation |
|
|
||||||
| `src/fluency/rendering/occ_to_mesh.py` | **NEW** | OCC `TopoDS_Shape` → STL/OBJ tessellation |
|
|
||||||
| `src/fluency/rendering/material_presets.py` | **NEW** | Preset library: Steel, Aluminum, Brass, Chrome, Plastic, Rubber, Wood |
|
|
||||||
| `src/fluency/ui/render_window.py` | **NEW** | `RenderWindow(QMainWindow)` — image preview, material/quality controls, render/export |
|
|
||||||
| `src/fluency/ui/main_window.py` | MODIFY | Add "Render" button → get selected shapes → open `RenderWindow` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## UI: RenderWindow
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────────────────┐
|
|
||||||
│ Render — [Part Name] [─][□][×] │
|
|
||||||
├──────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ ┌──────────────────────────────────┐ │
|
|
||||||
│ │ │ │
|
|
||||||
│ │ Rendered Image Preview │ │
|
|
||||||
│ │ (QLabel with QPixmap) │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ └──────────────────────────────────┘ │
|
|
||||||
│ │
|
|
||||||
│ Material: [Steel ▾] │
|
|
||||||
│ Quality: [256 SPP ▾] │
|
|
||||||
│ Resolution: [1920×1080 ▾] │
|
|
||||||
│ │
|
|
||||||
│ [▶ Render] [⏹ Cancel] [💾 Export PNG] │
|
|
||||||
│ │
|
|
||||||
│ ████████████████░░░░░░ 65% (23s left) │
|
|
||||||
└──────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Preview**: progressive refinement (low SPP first, then ramp)
|
|
||||||
- **Cancel**: kill Mitsuba render thread
|
|
||||||
- **Export**: save to PNG/EXR
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Material Presets
|
|
||||||
|
|
||||||
| Preset | Color | Metallic | Roughness | BSDF |
|
|
||||||
|--------|-------|----------|-----------|------|
|
|
||||||
| Brushed Steel | (0.65, 0.67, 0.72) | 0.9 | 0.35 | roughconductor |
|
|
||||||
| Polished Chrome | (0.8, 0.8, 0.8) | 1.0 | 0.05 | roughconductor |
|
|
||||||
| Brushed Aluminum | (0.75, 0.75, 0.75) | 0.85 | 0.25 | roughconductor |
|
|
||||||
| Copper | (0.95, 0.64, 0.54) | 0.95 | 0.15 | roughconductor |
|
|
||||||
| Gold | (1.0, 0.76, 0.33) | 1.0 | 0.1 | roughconductor |
|
|
||||||
| Blackened Steel | (0.15, 0.15, 0.17) | 0.8 | 0.4 | roughconductor |
|
|
||||||
| Matte Plastic | (0.2, 0.5, 0.8) | 0.0 | 0.6 | plastic |
|
|
||||||
| Glossy Plastic | (0.2, 0.5, 0.8) | 0.0 | 0.1 | plastic |
|
|
||||||
| White Nylon | (0.85, 0.85, 0.83) | 0.0 | 0.45 | plastic |
|
|
||||||
| Black ABS | (0.05, 0.05, 0.05) | 0.0 | 0.35 | plastic |
|
|
||||||
| Red PA12 | (0.75, 0.08, 0.08) | 0.0 | 0.4 | plastic |
|
|
||||||
| Rubber | (0.1, 0.1, 0.1) | 0.0 | 0.9 | diffuse |
|
|
||||||
| Ceramic White | (0.92, 0.91, 0.88) | 0.0 | 0.15 | dielectric |
|
|
||||||
| Glass | (0.95, 0.95, 0.95) | 0.0 | 0.0 | dielectric |
|
|
||||||
| Wood | (0.6, 0.4, 0.2) | 0.0 | 0.7 | diffuse |
|
|
||||||
|
|
||||||
**Note:** Mitsuba pip installs don't include spectral metal data files (iron.spd, copper.spd, etc.), so metal presets use `material="none"` with `specular_reflectance` set to the metal color instead.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Risks & Mitigations
|
|
||||||
|
|
||||||
| Risk | Mitigation |
|
|
||||||
|------|-----------|
|
|
||||||
| Mitsuba not installed | Graceful error: "pip install mitsuba" shown in UI |
|
|
||||||
| Slow CPU rendering | Default to low SPP (64) for preview; offer GPU variant if CUDA available |
|
|
||||||
| Large meshes slow to tessellate | Progress indicator; optional mesh decimation |
|
|
||||||
| Mitsuba STL/OCC compatibility | Test tessellation quality; tune `linear_deflection` |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Estimated Effort
|
|
||||||
|
|
||||||
- **Phase 1** (abstract backend + OCC→mesh + Mitsuba impl): ~4-6 hours
|
|
||||||
- **Phase 2** (render window UI + material presets): ~3-4 hours
|
|
||||||
- **Phase 3** (polish, export, swap test): ~2-3 hours
|
|
||||||
- **Total**: ~9-13 hours
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 319 KiB |
@@ -1,948 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
## Form generated from reading UI file 'gui.ui'
|
|
||||||
##
|
|
||||||
## Created by: Qt User Interface Compiler version 6.10.2
|
|
||||||
##
|
|
||||||
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
||||||
################################################################################
|
|
||||||
|
|
||||||
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
|
|
||||||
QMetaObject, QObject, QPoint, QRect,
|
|
||||||
QSize, QTime, QUrl, Qt)
|
|
||||||
from PySide6.QtGui import (QAction, QBrush, QColor, QConicalGradient,
|
|
||||||
QCursor, QFont, QFontDatabase, QGradient,
|
|
||||||
QIcon, QImage, QKeySequence, QLinearGradient,
|
|
||||||
QPainter, QPalette, QPixmap, QRadialGradient,
|
|
||||||
QTransform)
|
|
||||||
from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QGroupBox,
|
|
||||||
QHBoxLayout, QLabel, QListWidget, QListWidgetItem,
|
|
||||||
QMainWindow, QMenu, QMenuBar, QPushButton,
|
|
||||||
QSizePolicy, QSpinBox, QStatusBar, QTabWidget,
|
|
||||||
QTextEdit, QVBoxLayout, QWidget)
|
|
||||||
|
|
||||||
class Ui_fluencyCAD(object):
|
|
||||||
def setupUi(self, fluencyCAD):
|
|
||||||
if not fluencyCAD.objectName():
|
|
||||||
fluencyCAD.setObjectName(u"fluencyCAD")
|
|
||||||
fluencyCAD.resize(2551, 1265)
|
|
||||||
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
|
|
||||||
sizePolicy.setHorizontalStretch(0)
|
|
||||||
sizePolicy.setVerticalStretch(0)
|
|
||||||
sizePolicy.setHeightForWidth(fluencyCAD.sizePolicy().hasHeightForWidth())
|
|
||||||
fluencyCAD.setSizePolicy(sizePolicy)
|
|
||||||
self.actionNew_Project = QAction(fluencyCAD)
|
|
||||||
self.actionNew_Project.setObjectName(u"actionNew_Project")
|
|
||||||
self.actionOpen_Project = QAction(fluencyCAD)
|
|
||||||
self.actionOpen_Project.setObjectName(u"actionOpen_Project")
|
|
||||||
self.actionSave_Project = QAction(fluencyCAD)
|
|
||||||
self.actionSave_Project.setObjectName(u"actionSave_Project")
|
|
||||||
self.actionSave_Project_As = QAction(fluencyCAD)
|
|
||||||
self.actionSave_Project_As.setObjectName(u"actionSave_Project_As")
|
|
||||||
self.actionImport_File = QAction(fluencyCAD)
|
|
||||||
self.actionImport_File.setObjectName(u"actionImport_File")
|
|
||||||
self.actionExport_Step = QAction(fluencyCAD)
|
|
||||||
self.actionExport_Step.setObjectName(u"actionExport_Step")
|
|
||||||
self.actionExport_Iges = QAction(fluencyCAD)
|
|
||||||
self.actionExport_Iges.setObjectName(u"actionExport_Iges")
|
|
||||||
self.actionExport_Stl = QAction(fluencyCAD)
|
|
||||||
self.actionExport_Stl.setObjectName(u"actionExport_Stl")
|
|
||||||
self.actionExit = QAction(fluencyCAD)
|
|
||||||
self.actionExit.setObjectName(u"actionExit")
|
|
||||||
self.centralwidget = QWidget(fluencyCAD)
|
|
||||||
self.centralwidget.setObjectName(u"centralwidget")
|
|
||||||
self.gridLayout = QGridLayout(self.centralwidget)
|
|
||||||
self.gridLayout.setObjectName(u"gridLayout")
|
|
||||||
self.groupBox_5 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_5.setObjectName(u"groupBox_5")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_5.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_5.setSizePolicy(sizePolicy)
|
|
||||||
self.gridLayout_11 = QGridLayout(self.groupBox_5)
|
|
||||||
self.gridLayout_11.setObjectName(u"gridLayout_11")
|
|
||||||
self.gridLayout_11.setContentsMargins(12, 12, 12, 12)
|
|
||||||
self.label = QLabel(self.groupBox_5)
|
|
||||||
self.label.setObjectName(u"label")
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.label, 5, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_snap_vert = QPushButton(self.groupBox_5)
|
|
||||||
self.pb_snap_vert.setObjectName(u"pb_snap_vert")
|
|
||||||
self.pb_snap_vert.setCheckable(True)
|
|
||||||
self.pb_snap_vert.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pb_snap_vert, 2, 1, 1, 1)
|
|
||||||
|
|
||||||
self.line_2 = QFrame(self.groupBox_5)
|
|
||||||
self.line_2.setObjectName(u"line_2")
|
|
||||||
self.line_2.setFrameShape(QFrame.Shape.HLine)
|
|
||||||
self.line_2.setFrameShadow(QFrame.Shadow.Sunken)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.line_2, 4, 0, 1, 2)
|
|
||||||
|
|
||||||
self.label_2 = QLabel(self.groupBox_5)
|
|
||||||
self.label_2.setObjectName(u"label_2")
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.label_2, 5, 1, 1, 1)
|
|
||||||
|
|
||||||
self.spinbox_snap_distance = QSpinBox(self.groupBox_5)
|
|
||||||
self.spinbox_snap_distance.setObjectName(u"spinbox_snap_distance")
|
|
||||||
self.spinbox_snap_distance.setMaximum(30)
|
|
||||||
self.spinbox_snap_distance.setValue(10)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.spinbox_snap_distance, 6, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pushButton_7 = QPushButton(self.groupBox_5)
|
|
||||||
self.pushButton_7.setObjectName(u"pushButton_7")
|
|
||||||
self.pushButton_7.setCheckable(True)
|
|
||||||
self.pushButton_7.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pushButton_7, 3, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_snap_horiz = QPushButton(self.groupBox_5)
|
|
||||||
self.pb_snap_horiz.setObjectName(u"pb_snap_horiz")
|
|
||||||
self.pb_snap_horiz.setCheckable(True)
|
|
||||||
self.pb_snap_horiz.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pb_snap_horiz, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.spinbox_angle_steps = QSpinBox(self.groupBox_5)
|
|
||||||
self.spinbox_angle_steps.setObjectName(u"spinbox_angle_steps")
|
|
||||||
self.spinbox_angle_steps.setMaximum(180)
|
|
||||||
self.spinbox_angle_steps.setValue(15)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.spinbox_angle_steps, 6, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pushButton_8 = QPushButton(self.groupBox_5)
|
|
||||||
self.pushButton_8.setObjectName(u"pushButton_8")
|
|
||||||
self.pushButton_8.setCheckable(True)
|
|
||||||
self.pushButton_8.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pushButton_8, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_snap_midp = QPushButton(self.groupBox_5)
|
|
||||||
self.pb_snap_midp.setObjectName(u"pb_snap_midp")
|
|
||||||
self.pb_snap_midp.setCheckable(True)
|
|
||||||
self.pb_snap_midp.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pb_snap_midp, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_snap_angle = QPushButton(self.groupBox_5)
|
|
||||||
self.pb_snap_angle.setObjectName(u"pb_snap_angle")
|
|
||||||
self.pb_snap_angle.setCheckable(True)
|
|
||||||
self.pb_snap_angle.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_11.addWidget(self.pb_snap_angle, 3, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_5, 3, 0, 1, 1)
|
|
||||||
|
|
||||||
self.groupBox_4 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_4.setObjectName(u"groupBox_4")
|
|
||||||
self.groupBox_4.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_9 = QGridLayout(self.groupBox_4)
|
|
||||||
self.gridLayout_9.setObjectName(u"gridLayout_9")
|
|
||||||
self.pushButton_2 = QPushButton(self.groupBox_4)
|
|
||||||
self.pushButton_2.setObjectName(u"pushButton_2")
|
|
||||||
|
|
||||||
self.gridLayout_9.addWidget(self.pushButton_2, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_export_iges = QPushButton(self.groupBox_4)
|
|
||||||
self.pb_export_iges.setObjectName(u"pb_export_iges")
|
|
||||||
|
|
||||||
self.gridLayout_9.addWidget(self.pb_export_iges, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_export_step = QPushButton(self.groupBox_4)
|
|
||||||
self.pb_export_step.setObjectName(u"pb_export_step")
|
|
||||||
|
|
||||||
self.gridLayout_9.addWidget(self.pb_export_step, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_4, 1, 3, 1, 1)
|
|
||||||
|
|
||||||
self.InputTab = QTabWidget(self.centralwidget)
|
|
||||||
self.InputTab.setObjectName(u"InputTab")
|
|
||||||
sizePolicy.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth())
|
|
||||||
self.InputTab.setSizePolicy(sizePolicy)
|
|
||||||
self.sketch_tab = QWidget()
|
|
||||||
self.sketch_tab.setObjectName(u"sketch_tab")
|
|
||||||
self.verticalLayout_4 = QVBoxLayout(self.sketch_tab)
|
|
||||||
self.verticalLayout_4.setObjectName(u"verticalLayout_4")
|
|
||||||
self.InputTab.addTab(self.sketch_tab, "")
|
|
||||||
self.code_tab = QWidget()
|
|
||||||
self.code_tab.setObjectName(u"code_tab")
|
|
||||||
self.verticalLayout = QVBoxLayout(self.code_tab)
|
|
||||||
self.verticalLayout.setObjectName(u"verticalLayout")
|
|
||||||
self.textEdit = QTextEdit(self.code_tab)
|
|
||||||
self.textEdit.setObjectName(u"textEdit")
|
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.textEdit)
|
|
||||||
|
|
||||||
self.groupBox_7 = QGroupBox(self.code_tab)
|
|
||||||
self.groupBox_7.setObjectName(u"groupBox_7")
|
|
||||||
self.gridLayout_5 = QGridLayout(self.groupBox_7)
|
|
||||||
self.gridLayout_5.setObjectName(u"gridLayout_5")
|
|
||||||
self.pushButton_5 = QPushButton(self.groupBox_7)
|
|
||||||
self.pushButton_5.setObjectName(u"pushButton_5")
|
|
||||||
|
|
||||||
self.gridLayout_5.addWidget(self.pushButton_5, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pushButton_4 = QPushButton(self.groupBox_7)
|
|
||||||
self.pushButton_4.setObjectName(u"pushButton_4")
|
|
||||||
|
|
||||||
self.gridLayout_5.addWidget(self.pushButton_4, 2, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_apply_code = QPushButton(self.groupBox_7)
|
|
||||||
self.pb_apply_code.setObjectName(u"pb_apply_code")
|
|
||||||
|
|
||||||
self.gridLayout_5.addWidget(self.pb_apply_code, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pushButton = QPushButton(self.groupBox_7)
|
|
||||||
self.pushButton.setObjectName(u"pushButton")
|
|
||||||
|
|
||||||
self.gridLayout_5.addWidget(self.pushButton, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout.addWidget(self.groupBox_7)
|
|
||||||
|
|
||||||
self.InputTab.addTab(self.code_tab, "")
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.InputTab, 0, 1, 5, 1)
|
|
||||||
|
|
||||||
self.compo_tool_box = QGroupBox(self.centralwidget)
|
|
||||||
self.compo_tool_box.setObjectName(u"compo_tool_box")
|
|
||||||
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Preferred)
|
|
||||||
sizePolicy1.setHorizontalStretch(0)
|
|
||||||
sizePolicy1.setVerticalStretch(0)
|
|
||||||
sizePolicy1.setHeightForWidth(self.compo_tool_box.sizePolicy().hasHeightForWidth())
|
|
||||||
self.compo_tool_box.setSizePolicy(sizePolicy1)
|
|
||||||
self.compo_tool_box.setMinimumSize(QSize(0, 50))
|
|
||||||
self.horizontalLayout = QHBoxLayout(self.compo_tool_box)
|
|
||||||
self.horizontalLayout.setObjectName(u"horizontalLayout")
|
|
||||||
self.pb_new_compo = QPushButton(self.compo_tool_box)
|
|
||||||
self.pb_new_compo.setObjectName(u"pb_new_compo")
|
|
||||||
self.pb_new_compo.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_new_compo.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.pb_new_compo)
|
|
||||||
|
|
||||||
self.pb_del_compo = QPushButton(self.compo_tool_box)
|
|
||||||
self.pb_del_compo.setObjectName(u"pb_del_compo")
|
|
||||||
self.pb_del_compo.setEnabled(True)
|
|
||||||
sizePolicy.setHeightForWidth(self.pb_del_compo.sizePolicy().hasHeightForWidth())
|
|
||||||
self.pb_del_compo.setSizePolicy(sizePolicy)
|
|
||||||
self.pb_del_compo.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_del_compo.setMaximumSize(QSize(50, 50))
|
|
||||||
self.pb_del_compo.setLayoutDirection(Qt.LeftToRight)
|
|
||||||
|
|
||||||
self.horizontalLayout.addWidget(self.pb_del_compo)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.compo_tool_box, 7, 0, 1, 1)
|
|
||||||
|
|
||||||
self.groupBox_3 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_3.setObjectName(u"groupBox_3")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_3.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_3.setSizePolicy(sizePolicy)
|
|
||||||
self.groupBox_3.setMaximumSize(QSize(200, 16777213))
|
|
||||||
self.gridLayout_4 = QGridLayout(self.groupBox_3)
|
|
||||||
self.gridLayout_4.setObjectName(u"gridLayout_4")
|
|
||||||
self.pb_con_ptpt = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_ptpt.setObjectName(u"pb_con_ptpt")
|
|
||||||
icon = QIcon()
|
|
||||||
icon.addFile(u"icons/pt_pt.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
|
|
||||||
self.pb_con_ptpt.setIcon(icon)
|
|
||||||
self.pb_con_ptpt.setCheckable(True)
|
|
||||||
self.pb_con_ptpt.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_ptpt, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_vert = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_vert.setObjectName(u"pb_con_vert")
|
|
||||||
self.pb_con_vert.setCheckable(True)
|
|
||||||
self.pb_con_vert.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_vert, 3, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_sym = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_sym.setObjectName(u"pb_con_sym")
|
|
||||||
self.pb_con_sym.setCheckable(True)
|
|
||||||
self.pb_con_sym.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_sym, 4, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_mid = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_mid.setObjectName(u"pb_con_mid")
|
|
||||||
self.pb_con_mid.setCheckable(True)
|
|
||||||
self.pb_con_mid.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_mid, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_line = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_line.setObjectName(u"pb_con_line")
|
|
||||||
self.pb_con_line.setCheckable(True)
|
|
||||||
self.pb_con_line.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_line, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_horiz = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_horiz.setObjectName(u"pb_con_horiz")
|
|
||||||
self.pb_con_horiz.setCheckable(True)
|
|
||||||
self.pb_con_horiz.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_horiz, 3, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_dist = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_dist.setObjectName(u"pb_con_dist")
|
|
||||||
self.pb_con_dist.setCheckable(True)
|
|
||||||
self.pb_con_dist.setAutoExclusive(False)
|
|
||||||
self.pb_con_dist.setAutoRepeatDelay(297)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_dist, 4, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_perp = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_perp.setObjectName(u"pb_con_perp")
|
|
||||||
self.pb_con_perp.setCheckable(True)
|
|
||||||
self.pb_con_perp.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_perp, 2, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_con_diameter = QPushButton(self.groupBox_3)
|
|
||||||
self.pb_con_diameter.setObjectName(u"pb_con_diameter")
|
|
||||||
|
|
||||||
self.gridLayout_4.addWidget(self.pb_con_diameter, 5, 0, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_3, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.assembly_tools = QGroupBox(self.centralwidget)
|
|
||||||
self.assembly_tools.setObjectName(u"assembly_tools")
|
|
||||||
sizePolicy1.setHeightForWidth(self.assembly_tools.sizePolicy().hasHeightForWidth())
|
|
||||||
self.assembly_tools.setSizePolicy(sizePolicy1)
|
|
||||||
self.assembly_tools.setMinimumSize(QSize(113, 50))
|
|
||||||
self.horizontalLayout_2 = QHBoxLayout(self.assembly_tools)
|
|
||||||
self.horizontalLayout_2.setObjectName(u"horizontalLayout_2")
|
|
||||||
self.pb_compo_to_assembly = QPushButton(self.assembly_tools)
|
|
||||||
self.pb_compo_to_assembly.setObjectName(u"pb_compo_to_assembly")
|
|
||||||
self.pb_compo_to_assembly.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_compo_to_assembly.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.horizontalLayout_2.addWidget(self.pb_compo_to_assembly)
|
|
||||||
|
|
||||||
self.pb_remove_compo_from_assembly = QPushButton(self.assembly_tools)
|
|
||||||
self.pb_remove_compo_from_assembly.setObjectName(u"pb_remove_compo_from_assembly")
|
|
||||||
self.pb_remove_compo_from_assembly.setEnabled(True)
|
|
||||||
sizePolicy.setHeightForWidth(self.pb_remove_compo_from_assembly.sizePolicy().hasHeightForWidth())
|
|
||||||
self.pb_remove_compo_from_assembly.setSizePolicy(sizePolicy)
|
|
||||||
self.pb_remove_compo_from_assembly.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_remove_compo_from_assembly.setMaximumSize(QSize(50, 50))
|
|
||||||
self.pb_remove_compo_from_assembly.setLayoutDirection(Qt.LeftToRight)
|
|
||||||
|
|
||||||
self.horizontalLayout_2.addWidget(self.pb_remove_compo_from_assembly)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.assembly_tools, 8, 0, 1, 1)
|
|
||||||
|
|
||||||
self.groupBox_9 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_9.setObjectName(u"groupBox_9")
|
|
||||||
self.groupBox_9.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_7 = QGridLayout(self.groupBox_9)
|
|
||||||
self.gridLayout_7.setObjectName(u"gridLayout_7")
|
|
||||||
self.pb_origin_wp = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_origin_wp.setObjectName(u"pb_origin_wp")
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_origin_wp, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_origin_face = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_origin_face.setObjectName(u"pb_origin_face")
|
|
||||||
self.pb_origin_face.setCheckable(True)
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_origin_face, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_flip_face = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_flip_face.setObjectName(u"pb_flip_face")
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_flip_face, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_underlay = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_underlay.setObjectName(u"pb_underlay")
|
|
||||||
self.pb_underlay.setEnabled(False)
|
|
||||||
self.pb_underlay.setCheckable(True)
|
|
||||||
self.pb_underlay.setChecked(True)
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_underlay, 3, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_clr_face = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_clr_face.setObjectName(u"pb_clr_face")
|
|
||||||
self.pb_clr_face.setEnabled(False)
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_clr_face, 3, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_to_sketch = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_to_sketch.setObjectName(u"pb_to_sketch")
|
|
||||||
self.pb_to_sketch.setEnabled(False)
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_to_sketch, 4, 0, 1, 2)
|
|
||||||
|
|
||||||
self.pb_wp_new = QPushButton(self.groupBox_9)
|
|
||||||
self.pb_wp_new.setObjectName(u"pb_wp_new")
|
|
||||||
|
|
||||||
self.gridLayout_7.addWidget(self.pb_wp_new, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_9, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.groupBox_12 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_12.setObjectName(u"groupBox_12")
|
|
||||||
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
|
|
||||||
sizePolicy2.setHorizontalStretch(0)
|
|
||||||
sizePolicy2.setVerticalStretch(0)
|
|
||||||
sizePolicy2.setHeightForWidth(self.groupBox_12.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_12.setSizePolicy(sizePolicy2)
|
|
||||||
self.groupBox_12.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.verticalLayout_8 = QVBoxLayout(self.groupBox_12)
|
|
||||||
self.verticalLayout_8.setObjectName(u"verticalLayout_8")
|
|
||||||
self.verticalLayout_8.setContentsMargins(5, 5, 5, 5)
|
|
||||||
self.connection_list = QListWidget(self.groupBox_12)
|
|
||||||
self.connection_list.setObjectName(u"connection_list")
|
|
||||||
self.connection_list.setSelectionRectVisible(True)
|
|
||||||
|
|
||||||
self.verticalLayout_8.addWidget(self.connection_list)
|
|
||||||
|
|
||||||
self.groupBox_13 = QGroupBox(self.groupBox_12)
|
|
||||||
self.groupBox_13.setObjectName(u"groupBox_13")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_13.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_13.setSizePolicy(sizePolicy)
|
|
||||||
self.groupBox_13.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_13 = QGridLayout(self.groupBox_13)
|
|
||||||
self.gridLayout_13.setObjectName(u"gridLayout_13")
|
|
||||||
self.gridLayout_13.setContentsMargins(2, 2, 2, 2)
|
|
||||||
self.pb_del_connection = QPushButton(self.groupBox_13)
|
|
||||||
self.pb_del_connection.setObjectName(u"pb_del_connection")
|
|
||||||
|
|
||||||
self.gridLayout_13.addWidget(self.pb_del_connection, 0, 2, 1, 1)
|
|
||||||
|
|
||||||
self.pb_update_connection = QPushButton(self.groupBox_13)
|
|
||||||
self.pb_update_connection.setObjectName(u"pb_update_connection")
|
|
||||||
|
|
||||||
self.gridLayout_13.addWidget(self.pb_update_connection, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_edt_sktch_4 = QPushButton(self.groupBox_13)
|
|
||||||
self.pb_edt_sktch_4.setObjectName(u"pb_edt_sktch_4")
|
|
||||||
|
|
||||||
self.gridLayout_13.addWidget(self.pb_edt_sktch_4, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout_8.addWidget(self.groupBox_13)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_12, 4, 3, 1, 1)
|
|
||||||
|
|
||||||
self.joint_tools = QGroupBox(self.centralwidget)
|
|
||||||
self.joint_tools.setObjectName(u"joint_tools")
|
|
||||||
self.joint_tools.setMinimumSize(QSize(0, 50))
|
|
||||||
self.gridLayout_10 = QGridLayout(self.joint_tools)
|
|
||||||
self.gridLayout_10.setObjectName(u"gridLayout_10")
|
|
||||||
self.pb_add_connector = QPushButton(self.joint_tools)
|
|
||||||
self.pb_add_connector.setObjectName(u"pb_add_connector")
|
|
||||||
self.pb_add_connector.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_add_connector.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.gridLayout_10.addWidget(self.pb_add_connector, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_remove_connector = QPushButton(self.joint_tools)
|
|
||||||
self.pb_remove_connector.setObjectName(u"pb_remove_connector")
|
|
||||||
self.pb_remove_connector.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_remove_connector.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.gridLayout_10.addWidget(self.pb_remove_connector, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_add_connector_3 = QPushButton(self.joint_tools)
|
|
||||||
self.pb_add_connector_3.setObjectName(u"pb_add_connector_3")
|
|
||||||
self.pb_add_connector_3.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_add_connector_3.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.gridLayout_10.addWidget(self.pb_add_connector_3, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_add_connector_2 = QPushButton(self.joint_tools)
|
|
||||||
self.pb_add_connector_2.setObjectName(u"pb_add_connector_2")
|
|
||||||
self.pb_add_connector_2.setMinimumSize(QSize(50, 50))
|
|
||||||
self.pb_add_connector_2.setMaximumSize(QSize(50, 50))
|
|
||||||
|
|
||||||
self.gridLayout_10.addWidget(self.pb_add_connector_2, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.joint_tools, 7, 3, 2, 1)
|
|
||||||
|
|
||||||
self.gl_box = QGroupBox(self.centralwidget)
|
|
||||||
self.gl_box.setObjectName(u"gl_box")
|
|
||||||
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
|
|
||||||
sizePolicy3.setHorizontalStretch(0)
|
|
||||||
sizePolicy3.setVerticalStretch(4)
|
|
||||||
sizePolicy3.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth())
|
|
||||||
self.gl_box.setSizePolicy(sizePolicy3)
|
|
||||||
font = QFont()
|
|
||||||
font.setPointSize(12)
|
|
||||||
self.gl_box.setFont(font)
|
|
||||||
self.horizontalLayout_4 = QHBoxLayout(self.gl_box)
|
|
||||||
#ifndef Q_OS_MAC
|
|
||||||
self.horizontalLayout_4.setSpacing(-1)
|
|
||||||
#endif
|
|
||||||
self.horizontalLayout_4.setObjectName(u"horizontalLayout_4")
|
|
||||||
self.horizontalLayout_4.setContentsMargins(12, -1, -1, -1)
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.gl_box, 0, 2, 5, 1)
|
|
||||||
|
|
||||||
self.compo_box = QGroupBox(self.centralwidget)
|
|
||||||
self.compo_box.setObjectName(u"compo_box")
|
|
||||||
self.compo_box.setMinimumSize(QSize(0, 120))
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.compo_box, 7, 1, 1, 2)
|
|
||||||
|
|
||||||
self.groupBox_11 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_11.setObjectName(u"groupBox_11")
|
|
||||||
sizePolicy2.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_11.setSizePolicy(sizePolicy2)
|
|
||||||
self.groupBox_11.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.verticalLayout_7 = QVBoxLayout(self.groupBox_11)
|
|
||||||
self.verticalLayout_7.setObjectName(u"verticalLayout_7")
|
|
||||||
self.verticalLayout_7.setContentsMargins(5, 5, 5, 5)
|
|
||||||
self.sketch_list = QListWidget(self.groupBox_11)
|
|
||||||
self.sketch_list.setObjectName(u"sketch_list")
|
|
||||||
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
||||||
sizePolicy4.setHorizontalStretch(0)
|
|
||||||
sizePolicy4.setVerticalStretch(0)
|
|
||||||
sizePolicy4.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth())
|
|
||||||
self.sketch_list.setSizePolicy(sizePolicy4)
|
|
||||||
self.sketch_list.setSelectionRectVisible(True)
|
|
||||||
|
|
||||||
self.verticalLayout_7.addWidget(self.sketch_list)
|
|
||||||
|
|
||||||
self.groupBox_6 = QGroupBox(self.groupBox_11)
|
|
||||||
self.groupBox_6.setObjectName(u"groupBox_6")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_6.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_6.setSizePolicy(sizePolicy)
|
|
||||||
self.gridLayout_6 = QGridLayout(self.groupBox_6)
|
|
||||||
self.gridLayout_6.setObjectName(u"gridLayout_6")
|
|
||||||
self.gridLayout_6.setContentsMargins(2, 2, 2, 2)
|
|
||||||
self.pb_edt_sktch = QPushButton(self.groupBox_6)
|
|
||||||
self.pb_edt_sktch.setObjectName(u"pb_edt_sktch")
|
|
||||||
|
|
||||||
self.gridLayout_6.addWidget(self.pb_edt_sktch, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_nw_sktch = QPushButton(self.groupBox_6)
|
|
||||||
self.pb_nw_sktch.setObjectName(u"pb_nw_sktch")
|
|
||||||
|
|
||||||
self.gridLayout_6.addWidget(self.pb_nw_sktch, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_del_sketch = QPushButton(self.groupBox_6)
|
|
||||||
self.pb_del_sketch.setObjectName(u"pb_del_sketch")
|
|
||||||
|
|
||||||
self.gridLayout_6.addWidget(self.pb_del_sketch, 1, 2, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout_7.addWidget(self.groupBox_6)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_11, 4, 0, 1, 1)
|
|
||||||
|
|
||||||
self.groupBox_2 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_2.setObjectName(u"groupBox_2")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_2.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_2.setSizePolicy(sizePolicy)
|
|
||||||
self.groupBox_2.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_2 = QGridLayout(self.groupBox_2)
|
|
||||||
self.gridLayout_2.setObjectName(u"gridLayout_2")
|
|
||||||
self.gridLayout_2.setContentsMargins(10, -1, -1, -1)
|
|
||||||
self.pb_arc_tool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_arc_tool.setObjectName(u"pb_arc_tool")
|
|
||||||
self.pb_arc_tool.setCheckable(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_arc_tool, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_rectool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_rectool.setObjectName(u"pb_rectool")
|
|
||||||
self.pb_rectool.setCheckable(True)
|
|
||||||
self.pb_rectool.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_rectool, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_circtool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_circtool.setObjectName(u"pb_circtool")
|
|
||||||
self.pb_circtool.setCheckable(True)
|
|
||||||
self.pb_circtool.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_circtool, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_enable_construct = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_enable_construct.setObjectName(u"pb_enable_construct")
|
|
||||||
self.pb_enable_construct.setCheckable(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_enable_construct, 4, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_enable_snap = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_enable_snap.setObjectName(u"pb_enable_snap")
|
|
||||||
self.pb_enable_snap.setIconSize(QSize(13, 16))
|
|
||||||
self.pb_enable_snap.setCheckable(True)
|
|
||||||
self.pb_enable_snap.setChecked(True)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_enable_snap, 4, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_linetool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_linetool.setObjectName(u"pb_linetool")
|
|
||||||
self.pb_linetool.setCheckable(True)
|
|
||||||
self.pb_linetool.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_linetool, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_slotool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_slotool.setObjectName(u"pb_slotool")
|
|
||||||
self.pb_slotool.setCheckable(True)
|
|
||||||
self.pb_slotool.setAutoExclusive(False)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_slotool, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.line = QFrame(self.groupBox_2)
|
|
||||||
self.line.setObjectName(u"line")
|
|
||||||
self.line.setFrameShape(QFrame.Shape.HLine)
|
|
||||||
self.line.setFrameShadow(QFrame.Shadow.Sunken)
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.line, 3, 0, 1, 2)
|
|
||||||
|
|
||||||
self.pb_offset_tool = QPushButton(self.groupBox_2)
|
|
||||||
self.pb_offset_tool.setObjectName(u"pb_offset_tool")
|
|
||||||
|
|
||||||
self.gridLayout_2.addWidget(self.pb_offset_tool, 2, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_2, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.assembly_box = QGroupBox(self.centralwidget)
|
|
||||||
self.assembly_box.setObjectName(u"assembly_box")
|
|
||||||
self.assembly_box.setMinimumSize(QSize(0, 120))
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.assembly_box, 8, 1, 1, 2)
|
|
||||||
|
|
||||||
self.groupBox = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox.setObjectName(u"groupBox")
|
|
||||||
self.groupBox.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_3 = QGridLayout(self.groupBox)
|
|
||||||
self.gridLayout_3.setObjectName(u"gridLayout_3")
|
|
||||||
self.pb_chamfer_op = QPushButton(self.groupBox)
|
|
||||||
self.pb_chamfer_op.setObjectName(u"pb_chamfer_op")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_chamfer_op, 2, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_fillet_op = QPushButton(self.groupBox)
|
|
||||||
self.pb_fillet_op.setObjectName(u"pb_fillet_op")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_fillet_op, 2, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_thread = QPushButton(self.groupBox)
|
|
||||||
self.pb_thread.setObjectName(u"pb_thread")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_thread, 6, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_extrdop = QPushButton(self.groupBox)
|
|
||||||
self.pb_extrdop.setObjectName(u"pb_extrdop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_extrdop, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_revop = QPushButton(self.groupBox)
|
|
||||||
self.pb_revop.setObjectName(u"pb_revop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_revop, 4, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_cutop = QPushButton(self.groupBox)
|
|
||||||
self.pb_cutop.setObjectName(u"pb_cutop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_cutop, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_arrayop = QPushButton(self.groupBox)
|
|
||||||
self.pb_arrayop.setObjectName(u"pb_arrayop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_arrayop, 4, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_combop = QPushButton(self.groupBox)
|
|
||||||
self.pb_combop.setObjectName(u"pb_combop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_combop, 1, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_moveop = QPushButton(self.groupBox)
|
|
||||||
self.pb_moveop.setObjectName(u"pb_moveop")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_moveop, 1, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_mirror_op = QPushButton(self.groupBox)
|
|
||||||
self.pb_mirror_op.setObjectName(u"pb_mirror_op")
|
|
||||||
|
|
||||||
self.gridLayout_3.addWidget(self.pb_mirror_op, 6, 1, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox, 0, 3, 1, 1)
|
|
||||||
|
|
||||||
self.line_3 = QFrame(self.centralwidget)
|
|
||||||
self.line_3.setObjectName(u"line_3")
|
|
||||||
self.line_3.setFrameShape(QFrame.Shape.HLine)
|
|
||||||
self.line_3.setFrameShadow(QFrame.Shadow.Sunken)
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.line_3, 5, 0, 1, 4)
|
|
||||||
|
|
||||||
self.groupBox_10 = QGroupBox(self.centralwidget)
|
|
||||||
self.groupBox_10.setObjectName(u"groupBox_10")
|
|
||||||
sizePolicy2.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_10.setSizePolicy(sizePolicy2)
|
|
||||||
self.groupBox_10.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.verticalLayout_6 = QVBoxLayout(self.groupBox_10)
|
|
||||||
self.verticalLayout_6.setObjectName(u"verticalLayout_6")
|
|
||||||
self.verticalLayout_6.setContentsMargins(5, 5, 5, 5)
|
|
||||||
self.body_list = QListWidget(self.groupBox_10)
|
|
||||||
self.body_list.setObjectName(u"body_list")
|
|
||||||
self.body_list.setSelectionRectVisible(True)
|
|
||||||
|
|
||||||
self.verticalLayout_6.addWidget(self.body_list)
|
|
||||||
|
|
||||||
self.groupBox_8 = QGroupBox(self.groupBox_10)
|
|
||||||
self.groupBox_8.setObjectName(u"groupBox_8")
|
|
||||||
sizePolicy.setHeightForWidth(self.groupBox_8.sizePolicy().hasHeightForWidth())
|
|
||||||
self.groupBox_8.setSizePolicy(sizePolicy)
|
|
||||||
self.groupBox_8.setMaximumSize(QSize(200, 16777215))
|
|
||||||
self.gridLayout_8 = QGridLayout(self.groupBox_8)
|
|
||||||
self.gridLayout_8.setObjectName(u"gridLayout_8")
|
|
||||||
self.gridLayout_8.setContentsMargins(2, 2, 2, 2)
|
|
||||||
self.pb_body_hide = QPushButton(self.groupBox_8)
|
|
||||||
self.pb_body_hide.setObjectName(u"pb_body_hide")
|
|
||||||
|
|
||||||
self.gridLayout_8.addWidget(self.pb_body_hide, 0, 1, 1, 1)
|
|
||||||
|
|
||||||
self.pb_update_body = QPushButton(self.groupBox_8)
|
|
||||||
self.pb_update_body.setObjectName(u"pb_update_body")
|
|
||||||
|
|
||||||
self.gridLayout_8.addWidget(self.pb_update_body, 0, 0, 1, 1)
|
|
||||||
|
|
||||||
self.pb_del_body = QPushButton(self.groupBox_8)
|
|
||||||
self.pb_del_body.setObjectName(u"pb_del_body")
|
|
||||||
|
|
||||||
self.gridLayout_8.addWidget(self.pb_del_body, 0, 2, 1, 1)
|
|
||||||
|
|
||||||
|
|
||||||
self.verticalLayout_6.addWidget(self.groupBox_8)
|
|
||||||
|
|
||||||
|
|
||||||
self.gridLayout.addWidget(self.groupBox_10, 2, 3, 2, 1)
|
|
||||||
|
|
||||||
fluencyCAD.setCentralWidget(self.centralwidget)
|
|
||||||
self.menubar = QMenuBar(fluencyCAD)
|
|
||||||
self.menubar.setObjectName(u"menubar")
|
|
||||||
self.menubar.setGeometry(QRect(0, 0, 2551, 24))
|
|
||||||
self.menuFile = QMenu(self.menubar)
|
|
||||||
self.menuFile.setObjectName(u"menuFile")
|
|
||||||
self.menuSettings = QMenu(self.menubar)
|
|
||||||
self.menuSettings.setObjectName(u"menuSettings")
|
|
||||||
fluencyCAD.setMenuBar(self.menubar)
|
|
||||||
self.statusbar = QStatusBar(fluencyCAD)
|
|
||||||
self.statusbar.setObjectName(u"statusbar")
|
|
||||||
fluencyCAD.setStatusBar(self.statusbar)
|
|
||||||
|
|
||||||
self.menubar.addAction(self.menuFile.menuAction())
|
|
||||||
self.menubar.addAction(self.menuSettings.menuAction())
|
|
||||||
self.menuFile.addAction(self.actionNew_Project)
|
|
||||||
self.menuFile.addAction(self.actionOpen_Project)
|
|
||||||
self.menuFile.addAction(self.actionSave_Project)
|
|
||||||
self.menuFile.addAction(self.actionSave_Project_As)
|
|
||||||
self.menuFile.addSeparator()
|
|
||||||
self.menuFile.addAction(self.actionImport_File)
|
|
||||||
self.menuFile.addSeparator()
|
|
||||||
self.menuFile.addAction(self.actionExport_Step)
|
|
||||||
self.menuFile.addAction(self.actionExport_Iges)
|
|
||||||
self.menuFile.addAction(self.actionExport_Stl)
|
|
||||||
self.menuFile.addSeparator()
|
|
||||||
self.menuFile.addAction(self.actionExit)
|
|
||||||
|
|
||||||
self.retranslateUi(fluencyCAD)
|
|
||||||
|
|
||||||
self.InputTab.setCurrentIndex(0)
|
|
||||||
|
|
||||||
|
|
||||||
QMetaObject.connectSlotsByName(fluencyCAD)
|
|
||||||
# setupUi
|
|
||||||
|
|
||||||
def retranslateUi(self, fluencyCAD):
|
|
||||||
fluencyCAD.setWindowTitle(QCoreApplication.translate("fluencyCAD", u"fluencyCAD", None))
|
|
||||||
self.actionNew_Project.setText(QCoreApplication.translate("fluencyCAD", u"New Project", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.actionNew_Project.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+N", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.actionOpen_Project.setText(QCoreApplication.translate("fluencyCAD", u"Open Project...", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.actionOpen_Project.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+O", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.actionSave_Project.setText(QCoreApplication.translate("fluencyCAD", u"Save Project", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.actionSave_Project.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+S", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.actionSave_Project_As.setText(QCoreApplication.translate("fluencyCAD", u"Save Project As...", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.actionSave_Project_As.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+Shift+S", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.actionImport_File.setText(QCoreApplication.translate("fluencyCAD", u"Import STEP/IGES...", None))
|
|
||||||
self.actionExport_Step.setText(QCoreApplication.translate("fluencyCAD", u"Export STEP...", None))
|
|
||||||
self.actionExport_Iges.setText(QCoreApplication.translate("fluencyCAD", u"Export IGES...", None))
|
|
||||||
self.actionExport_Stl.setText(QCoreApplication.translate("fluencyCAD", u"Export STL...", None))
|
|
||||||
self.actionExit.setText(QCoreApplication.translate("fluencyCAD", u"Exit", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.actionExit.setShortcut(QCoreApplication.translate("fluencyCAD", u"Ctrl+Q", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.groupBox_5.setTitle(QCoreApplication.translate("fluencyCAD", u"Snapping Points", None))
|
|
||||||
self.label.setText(QCoreApplication.translate("fluencyCAD", u"Snp Dst", None))
|
|
||||||
self.pb_snap_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None))
|
|
||||||
self.label_2.setText(QCoreApplication.translate("fluencyCAD", u"Angl Stps", None))
|
|
||||||
self.spinbox_snap_distance.setSuffix(QCoreApplication.translate("fluencyCAD", u"mm", None))
|
|
||||||
self.pushButton_7.setText(QCoreApplication.translate("fluencyCAD", u"Grid", None))
|
|
||||||
self.pb_snap_horiz.setText(QCoreApplication.translate("fluencyCAD", u"Horiz", None))
|
|
||||||
self.spinbox_angle_steps.setSuffix(QCoreApplication.translate("fluencyCAD", u"\u00b0", None))
|
|
||||||
self.pushButton_8.setText(QCoreApplication.translate("fluencyCAD", u"Pnt", None))
|
|
||||||
self.pb_snap_midp.setText(QCoreApplication.translate("fluencyCAD", u"MidP", None))
|
|
||||||
self.pb_snap_angle.setText(QCoreApplication.translate("fluencyCAD", u"Angles", None))
|
|
||||||
self.groupBox_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None))
|
|
||||||
self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None))
|
|
||||||
self.pb_export_iges.setText(QCoreApplication.translate("fluencyCAD", u"IGES", None))
|
|
||||||
self.pb_export_step.setText(QCoreApplication.translate("fluencyCAD", u"STEP", None))
|
|
||||||
self.InputTab.setTabText(self.InputTab.indexOf(self.sketch_tab), QCoreApplication.translate("fluencyCAD", u"Sketch", None))
|
|
||||||
self.groupBox_7.setTitle(QCoreApplication.translate("fluencyCAD", u"Executive", None))
|
|
||||||
self.pushButton_5.setText(QCoreApplication.translate("fluencyCAD", u"Load Code", None))
|
|
||||||
self.pushButton_4.setText(QCoreApplication.translate("fluencyCAD", u"Save code", None))
|
|
||||||
self.pb_apply_code.setText(QCoreApplication.translate("fluencyCAD", u"Apply Code", None))
|
|
||||||
self.pushButton.setText(QCoreApplication.translate("fluencyCAD", u"Delete Code", None))
|
|
||||||
self.InputTab.setTabText(self.InputTab.indexOf(self.code_tab), QCoreApplication.translate("fluencyCAD", u"Code", None))
|
|
||||||
self.compo_tool_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Tools", None))
|
|
||||||
self.pb_new_compo.setText(QCoreApplication.translate("fluencyCAD", u"New", None))
|
|
||||||
self.pb_del_compo.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
|
|
||||||
self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_ptpt.setToolTip(QCoreApplication.translate("fluencyCAD", u"Poin to Point Constrain", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_ptpt.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Pt", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_vert.setToolTip(QCoreApplication.translate("fluencyCAD", u"Vertical Constrain", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_vert.setText(QCoreApplication.translate("fluencyCAD", u"Vert", None))
|
|
||||||
self.pb_con_sym.setText(QCoreApplication.translate("fluencyCAD", u"Symetrc", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_mid.setToolTip(QCoreApplication.translate("fluencyCAD", u"Point to Middle Point Constrain", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_mid.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Mid_L", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_line.setToolTip(QCoreApplication.translate("fluencyCAD", u"Point to Line Constrain", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_line.setText(QCoreApplication.translate("fluencyCAD", u"Pt_Lne", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_horiz.setToolTip(QCoreApplication.translate("fluencyCAD", u"Horizontal Constrain ", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_horiz.setText(QCoreApplication.translate("fluencyCAD", u"Horiz", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_dist.setToolTip(QCoreApplication.translate("fluencyCAD", u"Dimension of Line of Distance from Point to Line", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_dist.setText(QCoreApplication.translate("fluencyCAD", u"Distnce", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_perp.setToolTip(QCoreApplication.translate("fluencyCAD", u"Constrain Line perpendicular to another line.", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_con_perp.setText(QCoreApplication.translate("fluencyCAD", u"Perp_Lne", None))
|
|
||||||
self.pb_con_diameter.setText(QCoreApplication.translate("fluencyCAD", u"Diameter", None))
|
|
||||||
self.assembly_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly Tools", None))
|
|
||||||
self.pb_compo_to_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
|
|
||||||
self.pb_remove_compo_from_assembly.setText(QCoreApplication.translate("fluencyCAD", u"Rem", None))
|
|
||||||
self.groupBox_9.setTitle(QCoreApplication.translate("fluencyCAD", u"Workplanes", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_origin_wp.setToolTip(QCoreApplication.translate("fluencyCAD", u"<W>orking Plane at 0, 0, 0", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_origin_wp.setText(QCoreApplication.translate("fluencyCAD", u"WP Origin", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.pb_origin_wp.setShortcut(QCoreApplication.translate("fluencyCAD", u"W", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_origin_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Working Plane >P<rojection at selected edges face", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_origin_face.setText(QCoreApplication.translate("fluencyCAD", u" WP Face", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.pb_origin_face.setShortcut(QCoreApplication.translate("fluencyCAD", u"P", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_flip_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Flip >N<ormal of projected mesh.", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_flip_face.setText(QCoreApplication.translate("fluencyCAD", u"WP Flip", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.pb_flip_face.setShortcut(QCoreApplication.translate("fluencyCAD", u"N", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_underlay.setToolTip(QCoreApplication.translate("fluencyCAD", u"Show / hide the construction lines projected from the source face", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_underlay.setText(QCoreApplication.translate("fluencyCAD", u"Underlay", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_clr_face.setToolTip(QCoreApplication.translate("fluencyCAD", u"Forget the picked source face (keep the workplane)", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_clr_face.setText(QCoreApplication.translate("fluencyCAD", u"ClrFace", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_to_sketch.setToolTip(QCoreApplication.translate("fluencyCAD", u"Convert projected construction lines into real sketch geometry", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_to_sketch.setText(QCoreApplication.translate("fluencyCAD", u"ToSketch", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_wp_new.setToolTip(QCoreApplication.translate("fluencyCAD", u"Create a new independent workplane (datum plane)", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_wp_new.setText(QCoreApplication.translate("fluencyCAD", u"WP New", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.pb_wp_new.setShortcut(QCoreApplication.translate("fluencyCAD", u"Shift+W", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.groupBox_12.setTitle(QCoreApplication.translate("fluencyCAD", u"Component Connections", None))
|
|
||||||
self.groupBox_13.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
|
|
||||||
self.pb_del_connection.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
|
|
||||||
self.pb_update_connection.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
|
|
||||||
self.pb_edt_sktch_4.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", None))
|
|
||||||
self.joint_tools.setTitle(QCoreApplication.translate("fluencyCAD", u"Joint Tools", None))
|
|
||||||
self.pb_add_connector.setText(QCoreApplication.translate("fluencyCAD", u"+ Cnct", None))
|
|
||||||
self.pb_remove_connector.setText(QCoreApplication.translate("fluencyCAD", u"- Cnct", None))
|
|
||||||
self.pb_add_connector_3.setText(QCoreApplication.translate("fluencyCAD", u"-Jnt", None))
|
|
||||||
self.pb_add_connector_2.setText(QCoreApplication.translate("fluencyCAD", u"+Jnt", None))
|
|
||||||
self.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None))
|
|
||||||
self.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", None))
|
|
||||||
self.groupBox_11.setTitle(QCoreApplication.translate("fluencyCAD", u"Sketch", None))
|
|
||||||
self.groupBox_6.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
|
|
||||||
self.pb_edt_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Edt", None))
|
|
||||||
self.pb_nw_sktch.setText(QCoreApplication.translate("fluencyCAD", u"Add", None))
|
|
||||||
self.pb_del_sketch.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
|
|
||||||
self.groupBox_2.setTitle(QCoreApplication.translate("fluencyCAD", u"Drawing", None))
|
|
||||||
self.pb_arc_tool.setText(QCoreApplication.translate("fluencyCAD", u"Arc", None))
|
|
||||||
self.pb_rectool.setText(QCoreApplication.translate("fluencyCAD", u"Rctgl", None))
|
|
||||||
self.pb_circtool.setText(QCoreApplication.translate("fluencyCAD", u"Circle", None))
|
|
||||||
self.pb_enable_construct.setText(QCoreApplication.translate("fluencyCAD", u"Cstrct", None))
|
|
||||||
self.pb_enable_snap.setText(QCoreApplication.translate("fluencyCAD", u"Snap", None))
|
|
||||||
self.pb_linetool.setText(QCoreApplication.translate("fluencyCAD", u"Line", None))
|
|
||||||
#if QT_CONFIG(shortcut)
|
|
||||||
self.pb_linetool.setShortcut(QCoreApplication.translate("fluencyCAD", u"S", None))
|
|
||||||
#endif // QT_CONFIG(shortcut)
|
|
||||||
self.pb_slotool.setText(QCoreApplication.translate("fluencyCAD", u"Slot", None))
|
|
||||||
#if QT_CONFIG(tooltip)
|
|
||||||
self.pb_offset_tool.setToolTip(QCoreApplication.translate("fluencyCAD", u"Offset selected sketch face (duplicate + offset boundary)", None))
|
|
||||||
#endif // QT_CONFIG(tooltip)
|
|
||||||
self.pb_offset_tool.setText(QCoreApplication.translate("fluencyCAD", u"Offst", None))
|
|
||||||
self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", None))
|
|
||||||
self.groupBox.setTitle(QCoreApplication.translate("fluencyCAD", u"Modify", None))
|
|
||||||
self.pb_chamfer_op.setText(QCoreApplication.translate("fluencyCAD", u"Chamfer", None))
|
|
||||||
self.pb_fillet_op.setText(QCoreApplication.translate("fluencyCAD", u"Fillet", None))
|
|
||||||
self.pb_thread.setText(QCoreApplication.translate("fluencyCAD", u"Thread", None))
|
|
||||||
self.pb_extrdop.setText(QCoreApplication.translate("fluencyCAD", u"Extrd", None))
|
|
||||||
self.pb_revop.setText(QCoreApplication.translate("fluencyCAD", u"Rev", None))
|
|
||||||
self.pb_cutop.setText(QCoreApplication.translate("fluencyCAD", u"Cut", None))
|
|
||||||
self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None))
|
|
||||||
self.pb_combop.setText(QCoreApplication.translate("fluencyCAD", u"Comb", None))
|
|
||||||
self.pb_moveop.setText(QCoreApplication.translate("fluencyCAD", u"Mve", None))
|
|
||||||
self.pb_mirror_op.setText(QCoreApplication.translate("fluencyCAD", u"Mirror", None))
|
|
||||||
self.groupBox_10.setTitle(QCoreApplication.translate("fluencyCAD", u"Bodys / Operations", None))
|
|
||||||
self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
|
|
||||||
self.pb_body_hide.setText(QCoreApplication.translate("fluencyCAD", u"Hide", None))
|
|
||||||
self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
|
|
||||||
self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
|
|
||||||
self.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None))
|
|
||||||
self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None))
|
|
||||||
# retranslateUi
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 267 B |
-16
@@ -1,16 +0,0 @@
|
|||||||
Copyright (C) 2025 Thomas Herrmann
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>main</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>main</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>main</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>main</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>1.0</string>
|
|
||||||
<key>NSHighResolutionCapable</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user