- UI refinement, button position ui file as source no dirty drafting anymore

This commit is contained in:
bklronin
2026-07-04 16:16:04 +02:00
parent 6ba742ddf5
commit 9f10a5c5e5
6 changed files with 1847 additions and 935 deletions
+10 -3
View File
@@ -6,9 +6,8 @@
<component name="ChangeListManager">
<list default="true" id="8f0bafd6-58a0-4b20-aa2b-ddc3ba278873" name="Changes" comment="- sketch enhacements">
<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$/gui.ui" beforeDir="false" afterPath="$PROJECT_DIR$/gui.ui" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/fluency/main.py" beforeDir="false" afterPath="$PROJECT_DIR$/src/fluency/main.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" />
</list>
<option name="SHOW_DIALOG" value="false" />
@@ -306,7 +305,15 @@
<option name="project" value="LOCAL" />
<updated>1783108151676</updated>
</task>
<option name="localTasksCounter" value="25" />
<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>
<option name="localTasksCounter" value="26" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
+219
View File
@@ -0,0 +1,219 @@
# 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)
```
+699 -560
View File
File diff suppressed because it is too large Load Diff
+802
View File
@@ -0,0 +1,802 @@
# -*- 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(1941, 1155)
self.actionNew_Project = QAction(fluencyCAD)
self.actionNew_Project.setObjectName(u"actionNew_Project")
self.actionLoad_Project = QAction(fluencyCAD)
self.actionLoad_Project.setObjectName(u"actionLoad_Project")
self.actionRecent = QAction(fluencyCAD)
self.actionRecent.setObjectName(u"actionRecent")
self.centralwidget = QWidget(fluencyCAD)
self.centralwidget.setObjectName(u"centralwidget")
self.gridLayout = QGridLayout(self.centralwidget)
self.gridLayout.setObjectName(u"gridLayout")
self.compos_in_assembly_box = QGroupBox(self.centralwidget)
self.compos_in_assembly_box.setObjectName(u"compos_in_assembly_box")
self.compos_in_assembly_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.compos_in_assembly_box, 12, 2, 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.gridLayout.addWidget(self.joint_tools, 12, 3, 1, 1)
self.groupBox_5 = QGroupBox(self.centralwidget)
self.groupBox_5.setObjectName(u"groupBox_5")
sizePolicy = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
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 = QGroupBox(self.centralwidget)
self.groupBox.setObjectName(u"groupBox")
self.gridLayout_3 = QGridLayout(self.groupBox)
self.gridLayout_3.setObjectName(u"gridLayout_3")
self.pb_revop = QPushButton(self.groupBox)
self.pb_revop.setObjectName(u"pb_revop")
self.gridLayout_3.addWidget(self.pb_revop, 2, 1, 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_arrayop = QPushButton(self.groupBox)
self.pb_arrayop.setObjectName(u"pb_arrayop")
self.gridLayout_3.addWidget(self.pb_arrayop, 2, 0, 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_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.gridLayout.addWidget(self.groupBox, 0, 3, 1, 1, Qt.AlignTop)
self.compo_tool_box = QGroupBox(self.centralwidget)
self.compo_tool_box.setObjectName(u"compo_tool_box")
self.compo_tool_box.setMinimumSize(QSize(0, 50))
self.gridLayout_9 = QGridLayout(self.compo_tool_box)
self.gridLayout_9.setObjectName(u"gridLayout_9")
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.gridLayout_9.addWidget(self.pb_new_compo, 0, 0, 1, 1)
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.gridLayout_9.addWidget(self.pb_del_compo, 0, 1, 1, 1)
self.gridLayout.addWidget(self.compo_tool_box, 11, 0, 1, 1)
self.compo_box = QGroupBox(self.centralwidget)
self.compo_box.setObjectName(u"compo_box")
self.compo_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.compo_box, 11, 1, 1, 2)
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_10 = QGroupBox(self.centralwidget)
self.groupBox_10.setObjectName(u"groupBox_10")
sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding)
sizePolicy1.setHorizontalStretch(0)
sizePolicy1.setVerticalStretch(0)
sizePolicy1.setHeightForWidth(self.groupBox_10.sizePolicy().hasHeightForWidth())
self.groupBox_10.setSizePolicy(sizePolicy1)
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_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.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_edt_sktch_3 = QPushButton(self.groupBox_8)
self.pb_edt_sktch_3.setObjectName(u"pb_edt_sktch_3")
self.gridLayout_8.addWidget(self.pb_edt_sktch_3, 0, 1, 1, 1)
self.verticalLayout_6.addWidget(self.groupBox_8)
self.gridLayout.addWidget(self.groupBox_10, 8, 3, 3, 1)
self.groupBox_11 = QGroupBox(self.centralwidget)
self.groupBox_11.setObjectName(u"groupBox_11")
sizePolicy1.setHeightForWidth(self.groupBox_11.sizePolicy().hasHeightForWidth())
self.groupBox_11.setSizePolicy(sizePolicy1)
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")
sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy2.setHorizontalStretch(0)
sizePolicy2.setVerticalStretch(0)
sizePolicy2.setHeightForWidth(self.sketch_list.sizePolicy().hasHeightForWidth())
self.sketch_list.setSizePolicy(sizePolicy2)
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, 8, 0, 3, 1)
self.groupBox_4 = QGroupBox(self.centralwidget)
self.groupBox_4.setObjectName(u"groupBox_4")
self.verticalLayout_2 = QVBoxLayout(self.groupBox_4)
self.verticalLayout_2.setObjectName(u"verticalLayout_2")
self.pushButton_2 = QPushButton(self.groupBox_4)
self.pushButton_2.setObjectName(u"pushButton_2")
self.verticalLayout_2.addWidget(self.pushButton_2)
self.pb_export_step = QPushButton(self.groupBox_4)
self.pb_export_step.setObjectName(u"pb_export_step")
self.verticalLayout_2.addWidget(self.pb_export_step)
self.pb_export_iges = QPushButton(self.groupBox_4)
self.pb_export_iges.setObjectName(u"pb_export_iges")
self.verticalLayout_2.addWidget(self.pb_export_iges)
self.gridLayout.addWidget(self.groupBox_4, 2, 3, 1, 1)
self.assembly_box = QGroupBox(self.centralwidget)
self.assembly_box.setObjectName(u"assembly_box")
self.assembly_box.setMinimumSize(QSize(0, 50))
self.gridLayout.addWidget(self.assembly_box, 12, 1, 1, 1)
self.InputTab = QTabWidget(self.centralwidget)
self.InputTab.setObjectName(u"InputTab")
sizePolicy3 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
sizePolicy3.setHorizontalStretch(0)
sizePolicy3.setVerticalStretch(0)
sizePolicy3.setHeightForWidth(self.InputTab.sizePolicy().hasHeightForWidth())
self.InputTab.setSizePolicy(sizePolicy3)
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, 11, 1)
self.assembly_tools = QGroupBox(self.centralwidget)
self.assembly_tools.setObjectName(u"assembly_tools")
self.assembly_tools.setMinimumSize(QSize(0, 50))
self.gridLayout_12 = QGridLayout(self.assembly_tools)
self.gridLayout_12.setObjectName(u"gridLayout_12")
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.gridLayout_12.addWidget(self.pb_compo_to_assembly, 0, 0, 1, 1)
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.gridLayout_12.addWidget(self.pb_remove_compo_from_assembly, 0, 1, 1, 1)
self.gridLayout.addWidget(self.assembly_tools, 12, 0, 1, 1)
self.gl_box = QGroupBox(self.centralwidget)
self.gl_box.setObjectName(u"gl_box")
sizePolicy4 = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
sizePolicy4.setHorizontalStretch(0)
sizePolicy4.setVerticalStretch(4)
sizePolicy4.setHeightForWidth(self.gl_box.sizePolicy().hasHeightForWidth())
self.gl_box.setSizePolicy(sizePolicy4)
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, 11, 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_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_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_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_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_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_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_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_ptpt = QPushButton(self.groupBox_3)
self.pb_con_ptpt.setObjectName(u"pb_con_ptpt")
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.gridLayout.addWidget(self.groupBox_3, 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, Qt.AlignTop)
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, Qt.AlignTop)
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, 2, 0, 1, 1)
fluencyCAD.setCentralWidget(self.centralwidget)
self.menubar = QMenuBar(fluencyCAD)
self.menubar.setObjectName(u"menubar")
self.menubar.setGeometry(QRect(0, 0, 1941, 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.actionLoad_Project)
self.menuFile.addAction(self.actionRecent)
self.menuFile.addSeparator()
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", None))
self.actionLoad_Project.setText(QCoreApplication.translate("fluencyCAD", u"Load", None))
self.actionRecent.setText(QCoreApplication.translate("fluencyCAD", u"Recent", None))
self.compos_in_assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components in Assembly", 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.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.setTitle(QCoreApplication.translate("fluencyCAD", u"Modify", None))
self.pb_revop.setText(QCoreApplication.translate("fluencyCAD", u"Rev", None))
self.pb_extrdop.setText(QCoreApplication.translate("fluencyCAD", u"Extrd", None))
self.pb_arrayop.setText(QCoreApplication.translate("fluencyCAD", u"Arry", None))
self.pb_cutop.setText(QCoreApplication.translate("fluencyCAD", u"Cut", None))
self.pb_combop.setText(QCoreApplication.translate("fluencyCAD", u"Comb", None))
self.pb_moveop.setText(QCoreApplication.translate("fluencyCAD", u"Mve", 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.compo_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Components", 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_10.setTitle(QCoreApplication.translate("fluencyCAD", u"Bodys / Operations", None))
self.groupBox_8.setTitle(QCoreApplication.translate("fluencyCAD", u"Tools", None))
self.pb_del_body.setText(QCoreApplication.translate("fluencyCAD", u"Del", None))
self.pb_update_body.setText(QCoreApplication.translate("fluencyCAD", u"Upd", None))
self.pb_edt_sktch_3.setText(QCoreApplication.translate("fluencyCAD", u"Nothing", 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_4.setTitle(QCoreApplication.translate("fluencyCAD", u"Export", None))
self.pushButton_2.setText(QCoreApplication.translate("fluencyCAD", u"STL", None))
self.pb_export_step.setText(QCoreApplication.translate("fluencyCAD", u"STEP", None))
self.pb_export_iges.setText(QCoreApplication.translate("fluencyCAD", u"IGES", None))
self.assembly_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Assembly", 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.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.gl_box.setTitle(QCoreApplication.translate("fluencyCAD", u"Model Viewer", None))
self.groupBox_3.setTitle(QCoreApplication.translate("fluencyCAD", u"Constrain", 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))
#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_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_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_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_sym.setText(QCoreApplication.translate("fluencyCAD", u"Symetrc", 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_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))
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.menuFile.setTitle(QCoreApplication.translate("fluencyCAD", u"File", None))
self.menuSettings.setTitle(QCoreApplication.translate("fluencyCAD", u"Settings", None))
# retranslateUi
+108 -362
View File
@@ -73,6 +73,7 @@ from fluency.geometry_occ.sketch import OCCSketch, OCCSketchEntity
from fluency.geometry.base import Point2D, Point3D
from fluency.rendering.occ_renderer import OCCRenderer
from fluency.models.data_model import Project, Component, Sketch, Body
from gui_ui import Ui_fluencyCAD
def _project_face_to_uv(
@@ -689,6 +690,7 @@ class WorkplaneOrientationDialog(QDialog):
x = np.array([1.0, 0.0, 0.0])
self._normal = tuple(float(v) for v in n)
self._x_dir = tuple(float(v) for v in x)
else:
btn = self._preset_group.checkedButton()
if btn is not None:
@@ -3992,11 +3994,15 @@ class MainWindow(QMainWindow):
self._create_menus()
self._create_central_widget()
self._create_dock_widgets()
self._setup_ui_aliases()
logger.info("Ready")
def _create_menus(self):
# Remove UI-built menu actions to avoid duplication
menubar = self.menuBar()
for action in menubar.actions():
menubar.removeAction(action)
file_menu = menubar.addMenu("&File")
@@ -4047,377 +4053,123 @@ class MainWindow(QMainWindow):
help_menu.addAction("About", self._show_about)
def _create_central_widget(self):
"""
Recreates the classic grid-based layout from the original fluencyCAD.
"""Load the compiled UI file and add programmatic custom widgets."""
self._ui = Ui_fluencyCAD()
self._ui.setupUi(self)
Grid layout:
Col 0 (200px) Col 1 (expand) Col 2 (expand) Col 3 (200px)
r0: Workplanes InputTab Model Viewer Modify
r1: Drawing
r2: Constrain
r3: Snaps (tab) r6: Export
r4: (Snaps cont) r7-8: Bodies
r5: (Snaps cont)
r6-8: Sketch List
r9: Comp Tools Comp Buttons (span 2) Assembly Tools
"""
central = QWidget()
self.setCentralWidget(central)
# Keep a reference to the grid for panel-focus management.
self._grid = self._ui.gridLayout
grid = QGridLayout(central)
grid.setContentsMargins(5, 5, 5, 5)
grid.setSpacing(4)
grid.setColumnStretch(0, 0) # left column fixed
# Sketch (col 1) and 3D viewer (col 2) share the remaining width.
# The split is hover-driven: entering the sketch enlarges it, entering
# the 3D viewer grows it to 2/3 (sketch shrinks to 1/3). Resting = 1:1.
grid.setColumnStretch(1, 1)
grid.setColumnStretch(2, 1)
grid.setColumnStretch(3, 0) # right column fixed
self._grid = grid
# -- Add programmatic custom widgets to their placeholder locations --
# ---- Row 0, Col 0: Workplanes ----
wp_group = QGroupBox("Workplanes")
wp_group.setMaximumWidth(200)
wp_layout = QGridLayout(wp_group)
self._btn_wp_origin = QPushButton("WP Origin")
self._btn_wp_origin.setToolTip("Working Plane at 0, 0, 0")
self._btn_wp_origin.setShortcut("W")
wp_layout.addWidget(self._btn_wp_origin, 0, 0)
self._btn_wp_face = QPushButton("WP Face")
self._btn_wp_face.setToolTip("Working Plane from selected face")
self._btn_wp_face.setShortcut("P")
self._btn_wp_face.setCheckable(True)
wp_layout.addWidget(self._btn_wp_face, 0, 1)
self._btn_wp_flip = QPushButton("WP Flip")
self._btn_wp_flip.setToolTip("Flip normal direction")
self._btn_wp_flip.setShortcut("N")
wp_layout.addWidget(self._btn_wp_flip, 1, 0)
self._btn_wp_move = QPushButton("WP Mve")
self._btn_wp_move.setToolTip("Move workplane")
self._btn_wp_move.setShortcut("M")
wp_layout.addWidget(self._btn_wp_move, 1, 1)
# ── New Workplane button ──
self._btn_wp_new = QPushButton("WP New")
self._btn_wp_new.setToolTip("Create a new independent workplane (datum plane)")
self._btn_wp_new.setShortcut("Shift+W")
wp_layout.addWidget(self._btn_wp_new, 2, 0, 1, 2)
# Underlay: show / hide the construction lines projected from the
# source face. Off by default is meaningless (no face picked yet)
# so it auto-unchecks when the user clears the source face.
self._btn_underlay = QPushButton("Underlay")
self._btn_underlay.setToolTip(
"Show / hide the construction lines projected from the source face"
)
self._btn_underlay.setCheckable(True)
self._btn_underlay.setChecked(True)
self._btn_underlay.setEnabled(False) # becomes enabled after WP Face
wp_layout.addWidget(self._btn_underlay, 3, 0)
# ClrFace: forget the picked source face. Removes the underlay
# construction-line entities and leaves the workplane + sketch
# intact, so the user can keep drawing on a free-standing sketch.
self._btn_clr_face = QPushButton("ClrFace")
self._btn_clr_face.setToolTip("Forget the picked source face (keep the workplane)")
self._btn_clr_face.setEnabled(False) # becomes enabled after WP Face
wp_layout.addWidget(self._btn_clr_face, 3, 1)
# ToSketch: convert the underlay (projected construction lines)
# into regular non-construction sketch geometry so the user can
# offset, modify, or extrude the projected shape directly without
# having to redraw it manually.
self._btn_to_sketch = QPushButton("ToSketch")
self._btn_to_sketch.setToolTip(
"Convert projected construction lines into real sketch geometry"
)
self._btn_to_sketch.setEnabled(False)
wp_layout.addWidget(self._btn_to_sketch, 4, 0, 1, 2)
grid.addWidget(wp_group, 0, 0)
# ---- Row 1, Col 0: Drawing ----
draw_group = QGroupBox("Drawing")
draw_group.setMaximumWidth(200)
draw_layout = QGridLayout(draw_group)
self._btn_line = QPushButton("Line")
self._btn_line.setCheckable(True)
self._btn_line.setShortcut("S")
draw_layout.addWidget(self._btn_line, 0, 0)
self._btn_rect = QPushButton("Rctgl")
self._btn_rect.setCheckable(True)
draw_layout.addWidget(self._btn_rect, 0, 1)
self._btn_circle = QPushButton("Circle")
self._btn_circle.setCheckable(True)
draw_layout.addWidget(self._btn_circle, 1, 0)
self._btn_slot = QPushButton("Slot")
self._btn_slot.setCheckable(True)
draw_layout.addWidget(self._btn_slot, 1, 1)
self._btn_move_sketch = QPushButton("Move")
self._btn_move_sketch.setCheckable(True)
self._btn_move_sketch.setToolTip("Move a drawn element (drag a point/line/rectangle/circle).")
self._btn_move_sketch.setShortcut("Q")
draw_layout.addWidget(self._btn_move_sketch, 1, 2, 1, 1)
self._btn_arc = QPushButton("Arc")
self._btn_arc.setCheckable(True)
draw_layout.addWidget(self._btn_arc, 2, 0)
# separator (shifted right to leave column 0 for Arc)
sep = QFrame()
sep.setFrameShape(QFrame.HLine)
sep.setFrameShadow(QFrame.Sunken)
draw_layout.addWidget(sep, 2, 1, 1, 2)
self._btn_construct = QPushButton("Cstrct")
self._btn_construct.setCheckable(True)
draw_layout.addWidget(self._btn_construct, 3, 0)
self._btn_snap = QPushButton("Snap")
self._btn_snap.setCheckable(True)
self._btn_snap.setChecked(True)
draw_layout.addWidget(self._btn_snap, 3, 1)
self._btn_offset = QPushButton("Offst")
self._btn_offset.setToolTip("Offset selected sketch face (duplicate + offset boundary)")
draw_layout.addWidget(self._btn_offset, 4, 0, 1, 2)
grid.addWidget(draw_group, 1, 0)
# ---- Row 2, Col 0: Constrain ----
con_group = QGroupBox("Constrain")
con_group.setMaximumWidth(200)
con_layout = QGridLayout(con_group)
self._btn_con_ptpt = QPushButton("Pt_Pt")
self._btn_con_ptpt.setCheckable(True)
self._btn_con_ptpt.setToolTip("Point to Point Coincident")
con_layout.addWidget(self._btn_con_ptpt, 0, 0)
self._btn_con_ptline = QPushButton("Pt_Lne")
self._btn_con_ptline.setCheckable(True)
self._btn_con_ptline.setToolTip("Point to Line Distance")
con_layout.addWidget(self._btn_con_ptline, 0, 1)
self._btn_con_mid = QPushButton("Pt_Mid_L")
self._btn_con_mid.setCheckable(True)
self._btn_con_mid.setToolTip("Point to Midpoint")
con_layout.addWidget(self._btn_con_mid, 1, 0)
self._btn_con_perp = QPushButton("Perp_Lne")
self._btn_con_perp.setCheckable(True)
self._btn_con_perp.setToolTip("Perpendicular Constraint")
con_layout.addWidget(self._btn_con_perp, 1, 1)
self._btn_con_horiz = QPushButton("Horiz")
self._btn_con_horiz.setCheckable(True)
self._btn_con_horiz.setToolTip("Horizontal Constraint")
con_layout.addWidget(self._btn_con_horiz, 2, 0)
self._btn_con_vert = QPushButton("Vert")
self._btn_con_vert.setCheckable(True)
self._btn_con_vert.setToolTip("Vertical Constraint")
con_layout.addWidget(self._btn_con_vert, 2, 1)
self._btn_con_dist = QPushButton("Distnce")
self._btn_con_dist.setCheckable(True)
self._btn_con_dist.setToolTip("Distance Constraint")
con_layout.addWidget(self._btn_con_dist, 3, 0)
self._btn_con_sym = QPushButton("Symetrc")
self._btn_con_sym.setCheckable(True)
con_layout.addWidget(self._btn_con_sym, 3, 1)
grid.addWidget(con_group, 2, 0)
# ---- Row 3-5, Col 0: Snaps tab ----
self._snaps_tab = QTabWidget()
self._snaps_tab.setMaximumWidth(200)
self._snaps_tab.setTabPosition(QTabWidget.TabPosition.South)
snaps_tab1 = QWidget()
snaps_layout = QVBoxLayout(snaps_tab1)
snap_group = QGroupBox("Snapping Points")
snap_grid = QGridLayout(snap_group)
snap_grid.setContentsMargins(2, 2, 2, 2)
self._btn_snap_point = QPushButton("Pnt")
self._btn_snap_point.setCheckable(True)
self._btn_snap_point.setChecked(True)
snap_grid.addWidget(self._btn_snap_point, 0, 0)
self._btn_snap_mid = QPushButton("MidP")
self._btn_snap_mid.setCheckable(True)
snap_grid.addWidget(self._btn_snap_mid, 0, 1)
self._btn_snap_horiz = QPushButton("Horiz")
self._btn_snap_horiz.setCheckable(True)
snap_grid.addWidget(self._btn_snap_horiz, 1, 0)
self._btn_snap_vert = QPushButton("Vert")
self._btn_snap_vert.setCheckable(True)
snap_grid.addWidget(self._btn_snap_vert, 1, 1)
self._btn_snap_angle = QPushButton("Angles")
self._btn_snap_angle.setCheckable(True)
snap_grid.addWidget(self._btn_snap_angle, 2, 0)
self._btn_snap_grid = QPushButton("Grid")
self._btn_snap_grid.setCheckable(True)
snap_grid.addWidget(self._btn_snap_grid, 2, 1)
# separator
sep2 = QFrame()
sep2.setFrameShape(QFrame.HLine)
sep2.setFrameShadow(QFrame.Sunken)
snap_grid.addWidget(sep2, 3, 0, 1, 2)
snap_grid.addWidget(QLabel("Snp Dst"), 4, 0)
snap_grid.addWidget(QLabel("Angl Stps"), 4, 1)
self._spin_snap_dist = QSpinBox()
self._spin_snap_dist.setRange(1, 30)
self._spin_snap_dist.setValue(10)
self._spin_snap_dist.setSuffix("px")
snap_grid.addWidget(self._spin_snap_dist, 5, 0)
self._spin_angle = QSpinBox()
self._spin_angle.setRange(1, 180)
self._spin_angle.setValue(15)
self._spin_angle.setSuffix("°")
snap_grid.addWidget(self._spin_angle, 5, 1)
snaps_layout.addWidget(snap_group)
self._snaps_tab.addTab(snaps_tab1, "Setg 1")
self._snaps_tab.addTab(QWidget(), "Setg 2")
grid.addWidget(self._snaps_tab, 3, 0)
# snaps tab spans rows 3-5
grid.setRowStretch(3, 0)
grid.setRowStretch(4, 1)
grid.setRowStretch(5, 1)
# Build row 6-8 spacer for left column
# Reserve space so the sketch list reaches the bottom
grid.setRowMinimumHeight(6, 0)
grid.setRowMinimumHeight(7, 0)
grid.setRowMinimumHeight(8, 0)
# ---- Row 6-8, Col 0: Sketch List ----
sk_list_group = QGroupBox("Sketch")
sk_list_group.setMaximumWidth(200)
sk_list_layout = QVBoxLayout(sk_list_group)
self._sketch_list = QListWidget()
self._sketch_list.setSelectionRectVisible(True)
sk_list_layout.addWidget(self._sketch_list)
sk_tools = QGroupBox("Tools")
sk_tools_grid = QGridLayout(sk_tools)
sk_tools_grid.setContentsMargins(2, 2, 2, 2)
self._btn_add_sketch = QPushButton("Add")
sk_tools_grid.addWidget(self._btn_add_sketch, 0, 0)
self._btn_edit_sketch = QPushButton("Edt")
sk_tools_grid.addWidget(self._btn_edit_sketch, 0, 1)
self._btn_del_sketch = QPushButton("Del")
sk_tools_grid.addWidget(self._btn_del_sketch, 0, 2)
sk_list_layout.addWidget(sk_tools)
grid.addWidget(sk_list_group, 6, 0, 3, 1)
# ---- Row 0-8, Col 1: Input Tabs ----
self._input_tabs = QTabWidget()
sketch_tab = QWidget()
sketch_tab_layout = QVBoxLayout(sketch_tab)
sketch_tab_layout.setContentsMargins(0, 0, 0, 0)
# Sketch2DWidget goes in the sketch tabs QVBoxLayout.
self._sketch_widget = Sketch2DWidget()
sketch_tab_layout.addWidget(self._sketch_widget)
self._input_tabs.addTab(sketch_tab, "Sketch")
code_tab = QWidget()
code_tab_layout = QVBoxLayout(code_tab)
self._code_edit = QTextEdit()
self._ui.sketch_tab.layout().addWidget(self._sketch_widget)
# Viewer3DWidget goes in the gl_box QHBoxLayout.
self._viewer_3d = Viewer3DWidget()
self._ui.gl_box.layout().addWidget(self._viewer_3d)
# Code editor — use the UIs textEdit with our custom font.
self._code_edit = self._ui.textEdit
self._code_edit.setFont(QFont("Monaco", 10))
self._code_edit.setPlaceholderText("# Enter Python code here...")
code_tab_layout.addWidget(self._code_edit)
code_tools = QHBoxLayout()
self._btn_apply_code = QPushButton("Apply Code")
self._btn_load_code = QPushButton("Load Code")
self._btn_save_code = QPushButton("Save Code")
self._btn_del_code = QPushButton("Delete Code")
code_tools.addWidget(self._btn_apply_code)
code_tools.addWidget(self._btn_load_code)
code_tools.addWidget(self._btn_save_code)
code_tools.addWidget(self._btn_del_code)
code_tab_layout.addLayout(code_tools)
self._input_tabs.addTab(code_tab, "Code")
grid.addWidget(self._input_tabs, 0, 1, 9, 1)
# ---- Row 0-8, Col 2: Model Viewer ----
viewer_group = QGroupBox("Model Viewer")
viewer_layout = QVBoxLayout(viewer_group)
viewer_layout.setContentsMargins(5, 5, 5, 5)
self._viewer_3d = Viewer3DWidget()
viewer_layout.addWidget(self._viewer_3d)
grid.addWidget(viewer_group, 0, 2, 9, 1)
# Focus split is toggled by Spacebar / the Layout button
# (see _toggle_panel_focus).
self._panel_focus: str = "equal" # "equal" | "sketch" | "viewer"
# ---- Row 0, Col 3: Modify ----
modify_group = QGroupBox("Modify")
modify_group.setMaximumWidth(200)
modify_layout = QGridLayout(modify_group)
self._btn_extrude = QPushButton("Extrd")
self._btn_extrude.setToolTip("Extrude sketch")
modify_layout.addWidget(self._btn_extrude, 0, 0)
self._btn_cut = QPushButton("Cut")
self._btn_cut.setToolTip("Boolean cut")
modify_layout.addWidget(self._btn_cut, 0, 1)
self._btn_combine = QPushButton("Comb")
self._btn_combine.setToolTip("Boolean union")
modify_layout.addWidget(self._btn_combine, 1, 0)
self._btn_move = QPushButton("Mve")
self._btn_move.setToolTip("Move body")
modify_layout.addWidget(self._btn_move, 1, 1)
self._btn_revolve = QPushButton("Rev")
self._btn_revolve.setToolTip("Revolve sketch")
modify_layout.addWidget(self._btn_revolve, 2, 0)
self._btn_array = QPushButton("Arry")
self._btn_array.setToolTip("Pattern array")
modify_layout.addWidget(self._btn_array, 2, 1)
grid.addWidget(modify_group, 0, 3, 1, 1, Qt.AlignTop)
# ---- Row 6, Col 3: Export ----
export_group = QGroupBox("Export")
export_group.setMaximumWidth(200)
export_layout = QVBoxLayout(export_group)
self._btn_export_stl = QPushButton("STL")
export_layout.addWidget(self._btn_export_stl)
self._btn_export_step = QPushButton("STEP")
export_layout.addWidget(self._btn_export_step)
self._btn_export_iges = QPushButton("IGES")
export_layout.addWidget(self._btn_export_iges)
grid.addWidget(export_group, 6, 3)
# ---- Row 7-8, Col 3: Bodies / Operations ----
body_group = QGroupBox("Bodys / Operations")
body_group.setMaximumWidth(200)
body_layout = QVBoxLayout(body_group)
self._body_list = QListWidget()
self._body_list.setSelectionRectVisible(True)
body_layout.addWidget(self._body_list)
body_tools_grid = QGridLayout()
body_tools_grid.setContentsMargins(2, 2, 2, 2)
self._btn_update_body = QPushButton("Upd")
body_tools_grid.addWidget(self._btn_update_body, 0, 0)
self._btn_edit_sketch_3 = QPushButton("Nothing")
body_tools_grid.addWidget(self._btn_edit_sketch_3, 0, 1)
self._btn_del_body = QPushButton("Del")
body_tools_grid.addWidget(self._btn_del_body, 0, 2)
body_layout.addLayout(body_tools_grid)
grid.addWidget(body_group, 7, 3, 2, 1)
# ---- Row 9, Col 0: Component Tools ----
comp_tool_group = QGroupBox("Component Tools")
comp_tool_group.setMinimumHeight(50)
comp_tool_layout = QHBoxLayout(comp_tool_group)
self._btn_new_compo = QPushButton("New")
self._btn_new_compo.setFixedSize(QSize(50, 50))
comp_tool_layout.addWidget(self._btn_new_compo)
self._btn_del_compo = QPushButton("Del")
self._btn_del_compo.setFixedSize(QSize(50, 50))
comp_tool_layout.addWidget(self._btn_del_compo)
grid.addWidget(comp_tool_group, 9, 0)
# ---- Row 9, Col 1-2: Components (button box) ----
compo_group = QGroupBox("Components")
compo_group.setMinimumHeight(50)
compo_layout = QHBoxLayout(compo_group)
# Component buttons (dynamically generated per component, not in UI).
self._component_box = QWidget()
self._component_box_layout = QHBoxLayout(self._component_box)
self._component_box_layout.setAlignment(Qt.AlignLeft)
self._component_group = QButtonGroup(self)
self._component_group.setExclusive(True)
# Add to the Components group box from the UI.
compo_layout = self._ui.compo_box.layout()
if compo_layout is None:
compo_layout = QHBoxLayout(self._ui.compo_box)
compo_layout.setContentsMargins(0, 0, 0, 0)
compo_layout.addWidget(self._component_box)
compo_layout.addStretch()
grid.addWidget(compo_group, 9, 1, 1, 2)
# ---- Row 9, Col 3: Assembly Tools ----
assy_group = QGroupBox("Assembly Tools")
assy_group.setMinimumHeight(50)
assy_layout = QHBoxLayout(assy_group)
self._btn_add_connector = QPushButton("+ Cnct")
self._btn_add_connector.setFixedSize(QSize(50, 50))
assy_layout.addWidget(self._btn_add_connector)
self._btn_del_connector = QPushButton("- Cnct")
self._btn_del_connector.setFixedSize(QSize(50, 50))
assy_layout.addWidget(self._btn_del_connector)
grid.addWidget(assy_group, 9, 3)
# Panel-focus mode (equal | sketch | viewer).
self._panel_focus: str = "equal"
def _setup_ui_aliases(self):
"""Create _btn_* aliases pointing to the UI-loaded widgets.
The rest of the application references widgets via ``self._btn_*``
names. This method maps those to the ``pb_*`` / ``pushButton_*``
names created by the compiled UI file so existing signal connections
and mode-switching code continues to work unchanged.
"""
ui = self._ui
# ── Workplanes ──
self._btn_wp_origin = ui.pb_origin_wp
self._btn_wp_face = ui.pb_origin_face
self._btn_wp_flip = ui.pb_flip_face
self._btn_wp_new = ui.pb_wp_new
self._btn_underlay = ui.pb_underlay
self._btn_clr_face = ui.pb_clr_face
self._btn_to_sketch = ui.pb_to_sketch
# ── Drawing ──
self._btn_line = ui.pb_linetool
self._btn_rect = ui.pb_rectool
self._btn_circle = ui.pb_circtool
self._btn_slot = ui.pb_slotool
self._btn_arc = ui.pb_arc_tool
self._btn_construct = ui.pb_enable_construct
self._btn_snap = ui.pb_enable_snap
self._btn_offset = ui.pb_offset_tool
# ── Constrain ──
self._btn_con_ptpt = ui.pb_con_ptpt
self._btn_con_ptline = ui.pb_con_line
self._btn_con_mid = ui.pb_con_mid
self._btn_con_perp = ui.pb_con_perp
self._btn_con_horiz = ui.pb_con_horiz
self._btn_con_vert = ui.pb_con_vert
self._btn_con_dist = ui.pb_con_dist
self._btn_con_sym = ui.pb_con_sym
# ── Snaps ──
self._btn_snap_point = ui.pushButton_8
self._btn_snap_mid = ui.pb_snap_midp
self._btn_snap_horiz = ui.pb_snap_horiz
self._btn_snap_vert = ui.pb_snap_vert
self._btn_snap_angle = ui.pb_snap_angle
self._btn_snap_grid = ui.pushButton_7
self._spin_snap_dist = ui.spinbox_snap_distance
self._spin_angle = ui.spinbox_angle_steps
# ── Modify ──
self._btn_extrude = ui.pb_extrdop
self._btn_cut = ui.pb_cutop
self._btn_combine = ui.pb_combop
self._btn_move = ui.pb_moveop
self._btn_revolve = ui.pb_revop
self._btn_array = ui.pb_arrayop
# ── Export ──
self._btn_export_stl = ui.pushButton_2
self._btn_export_step = ui.pb_export_step
self._btn_export_iges = ui.pb_export_iges
# ── Sketch list tools ──
self._btn_add_sketch = ui.pb_nw_sktch
self._btn_edit_sketch = ui.pb_edt_sktch
self._btn_del_sketch = ui.pb_del_sketch
# ── Body tools ──
self._btn_update_body = ui.pb_update_body
self._btn_edit_sketch_3 = ui.pb_edt_sktch_3
self._btn_del_body = ui.pb_del_body
# ── Component tools ──
self._btn_new_compo = ui.pb_new_compo
self._btn_del_compo = ui.pb_del_compo
# ── Assembly / Connector ──
self._btn_add_connector = ui.pb_add_connector
self._btn_del_connector = ui.pb_remove_connector
# ── Code tab ──
self._btn_apply_code = ui.pb_apply_code
self._btn_load_code = ui.pushButton_5
self._btn_save_code = ui.pushButton_4
self._btn_del_code = ui.pushButton
# ── List views & tabs ──
self._sketch_list = ui.sketch_list
self._body_list = ui.body_list
self._input_tabs = ui.InputTab
def _toggle_panel_focus(self):
"""Cycle the sketch/viewer split: equal → sketch → viewer → equal.
@@ -4468,7 +4220,6 @@ class MainWindow(QMainWindow):
self._btn_circle.clicked.connect(lambda: self._set_sketch_mode("circle"))
self._btn_arc.clicked.connect(lambda: self._set_sketch_mode("arc"))
self._btn_slot.clicked.connect(lambda: self._set_sketch_mode("slot"))
self._btn_move_sketch.clicked.connect(lambda: self._set_sketch_mode("select"))
self._btn_construct.clicked.connect(self._on_construct_change)
self._btn_con_ptpt.clicked.connect(lambda: self._set_sketch_mode("constrain_coincident"))
@@ -4535,7 +4286,6 @@ class MainWindow(QMainWindow):
self._btn_wp_origin.clicked.connect(self._new_sketch_origin)
self._btn_wp_new.clicked.connect(self._new_workplane)
self._btn_wp_flip.clicked.connect(self._flip_workplane)
self._btn_wp_move.clicked.connect(self._move_workplane)
# Underlay show/hide, ClrFace, and ToSketch — all stay in sync
# with the source face state managed by set_source_face /
# clear_source_face / _project_body_to_active_wp.
@@ -4564,7 +4314,6 @@ class MainWindow(QMainWindow):
self._btn_circle,
self._btn_arc,
self._btn_slot,
self._btn_move_sketch,
self._btn_con_ptpt,
self._btn_con_ptline,
self._btn_con_horiz,
@@ -4587,8 +4336,6 @@ class MainWindow(QMainWindow):
self._btn_arc.setChecked(True)
elif mode == "slot":
self._btn_slot.setChecked(True)
elif mode == "select":
self._btn_move_sketch.setChecked(True)
elif mode.startswith("constrain_"):
if mode == "constrain_coincident":
self._btn_con_ptpt.setChecked(True)
@@ -4607,7 +4354,6 @@ class MainWindow(QMainWindow):
self._btn_circle,
self._btn_arc,
self._btn_slot,
self._btn_move_sketch,
self._btn_con_ptpt,
self._btn_con_ptline,
self._btn_con_horiz,
+9 -10
View File
@@ -1013,7 +1013,7 @@ class OCCRenderer(Renderer):
# • Left button drag → orbit (rotate around target)
# • Middle button drag → pan
# • Right button → (reserved for future use)
# • Wheel → zoom toward cursor
# • Wheel → zoom centered on viewport
# • Double-click left → fit all (handled by the widget)
def _qt_buttons(self, event) -> Any:
@@ -1079,20 +1079,19 @@ class OCCRenderer(Renderer):
self._nav_mode = None
def handle_wheel(self, event) -> None:
"""Zoom toward the cursor on scroll."""
"""Zoom centered on the viewport (body midpoint) on scroll.
Uses the view's scale factor so the zoom is always centered
on the viewport centre the body never drifts to an edge.
"""
if self._view is None:
return
# Qt6: QWheelEvent has no .x()/.y(); use position().toPoint().
pos = event.position().toPoint() if hasattr(event, "position") else event.pos()
x, y = pos.x(), pos.y()
delta = event.angleDelta().y()
if delta == 0:
return
# ZoomAtPoint(startX, startY, endX, endY): move the cursor anchor
# by a few pixels to drive a smooth zoom centered on the pointer.
step = 30 if delta > 0 else -30
self._view.ZoomAtPoint(x, y, x, y - step)
self._view.ZFitAll()
factor = 1.15 if delta > 0 else 1.0 / 1.15
self._view.SetScale(self._view.Scale() * factor)
def handle_resize(self, w: int, h: int) -> None:
"""Resize the OCC view when the widget is resized."""