- 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
+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."""