- arc improvements, fillets, operations, bodys
This commit is contained in:
+230
-84
@@ -4,11 +4,12 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import math
|
||||
from typing import Tuple
|
||||
from typing import Any, Callable, Dict, Optional, Tuple
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QButtonGroup,
|
||||
QCheckBox,
|
||||
QComboBox,
|
||||
QDialog,
|
||||
QDoubleSpinBox,
|
||||
QFrame,
|
||||
@@ -19,11 +20,20 @@ from PySide6.QtWidgets import (
|
||||
QPushButton,
|
||||
QRadioButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _vec3(value: Any) -> Tuple[float, float, float]:
|
||||
"""Coerce a 3-vector to a typed float triple (defensive fallback)."""
|
||||
try:
|
||||
return (float(value[0]), float(value[1]), float(value[2]))
|
||||
except (TypeError, ValueError, IndexError):
|
||||
return (0.0, 0.0, 0.0)
|
||||
|
||||
|
||||
class ExtrudeDialog(QDialog):
|
||||
"""Dialog for extrude options.
|
||||
|
||||
@@ -33,7 +43,7 @@ class ExtrudeDialog(QDialog):
|
||||
*None*) to the callback tells the host to clear the preview.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Extrude Options")
|
||||
self.setMinimumWidth(320)
|
||||
@@ -82,8 +92,8 @@ class ExtrudeDialog(QDialog):
|
||||
layout.addWidget(self.rounded_checkbox)
|
||||
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
line.setFrameShape(QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
@@ -95,9 +105,9 @@ class ExtrudeDialog(QDialog):
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# Live preview: recompute on every option change. Use a light-
|
||||
# weight guard so we don't emit before the host has wired up the
|
||||
# callback.
|
||||
# Live preview: recompute on every option change. Wire each widget
|
||||
# to its own signal by type — spinboxes emit ``valueChanged``,
|
||||
# checkboxes emit ``stateChanged``.
|
||||
for w in (
|
||||
self.length_input,
|
||||
self.symmetric_checkbox,
|
||||
@@ -108,27 +118,18 @@ class ExtrudeDialog(QDialog):
|
||||
self.cut_all_bodies_checkbox,
|
||||
self.rounded_checkbox,
|
||||
):
|
||||
# The spinbox has valueChanged; the checkboxes have stateChanged.
|
||||
# Each must be wired in its own try/except so that a missing
|
||||
# signal on one widget type doesn't skip the OTHER signal's
|
||||
# connection (the prior single-try version accidentally
|
||||
# left checkboxes un-connected when valueChanged raised first).
|
||||
try:
|
||||
if isinstance(w, QDoubleSpinBox):
|
||||
w.valueChanged.connect(self._emit_preview)
|
||||
except AttributeError:
|
||||
pass
|
||||
try:
|
||||
else:
|
||||
w.stateChanged.connect(self._emit_preview)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def set_preview_callback(self, callback) -> None:
|
||||
def set_preview_callback(self, callback: Optional[Callable[[Any], None]]) -> None:
|
||||
"""Install the live-preview callback (or *None* to disable)."""
|
||||
self._preview_callback = callback
|
||||
# Emit once so the initial state shows a preview right away.
|
||||
self._emit_preview()
|
||||
|
||||
def _emit_preview(self, *args) -> None:
|
||||
def _emit_preview(self, *args: Any) -> None:
|
||||
if self._preview_callback is None:
|
||||
return
|
||||
try:
|
||||
@@ -136,7 +137,7 @@ class ExtrudeDialog(QDialog):
|
||||
except Exception as exc: # preview must never break the dialog
|
||||
logger.debug("extrude preview callback raised: %s", exc)
|
||||
|
||||
def hideEvent(self, event):
|
||||
def hideEvent(self, event: Any) -> None:
|
||||
# Tell the host to clear the preview when the dialog goes away
|
||||
# (accept, reject, or close). The host is responsible for the
|
||||
# actual viewer cleanup.
|
||||
@@ -163,7 +164,7 @@ class ExtrudeDialog(QDialog):
|
||||
class RevolveDialog(QDialog):
|
||||
"""Dialog for revolve options."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Revolve Options")
|
||||
self.setMinimumWidth(300)
|
||||
@@ -181,8 +182,8 @@ class RevolveDialog(QDialog):
|
||||
layout.addLayout(angle_layout)
|
||||
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
line.setFrameShape(QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
@@ -203,7 +204,7 @@ class OffsetDialog(QDialog):
|
||||
time. On accept the caller retrieves ``get_values()`` → distance.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Offset Sketch")
|
||||
self.setMinimumWidth(300)
|
||||
@@ -227,8 +228,8 @@ class OffsetDialog(QDialog):
|
||||
layout.addWidget(self.inward_checkbox)
|
||||
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
line.setFrameShape(QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
@@ -244,12 +245,12 @@ class OffsetDialog(QDialog):
|
||||
self.distance_input.valueChanged.connect(self._emit_preview)
|
||||
self.inward_checkbox.stateChanged.connect(self._emit_preview)
|
||||
|
||||
def set_preview_callback(self, callback) -> None:
|
||||
def set_preview_callback(self, callback: Optional[Callable[[Any], None]]) -> None:
|
||||
"""Install the live-preview callback (or *None* to disable)."""
|
||||
self._preview_callback = callback
|
||||
self._emit_preview()
|
||||
|
||||
def _emit_preview(self, *args) -> None:
|
||||
def _emit_preview(self, *args: Any) -> None:
|
||||
if self._preview_callback is None:
|
||||
return
|
||||
try:
|
||||
@@ -257,7 +258,7 @@ class OffsetDialog(QDialog):
|
||||
except Exception as exc:
|
||||
logger.debug("offset preview callback raised: %s", exc)
|
||||
|
||||
def hideEvent(self, event):
|
||||
def hideEvent(self, event: Any) -> None:
|
||||
if self._preview_callback is not None:
|
||||
try:
|
||||
self._preview_callback(None)
|
||||
@@ -277,7 +278,7 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
returns (normal, x_dir) pair (both as 3-tuples).
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("New Workplane Orientation")
|
||||
self.setMinimumWidth(320)
|
||||
@@ -297,6 +298,12 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
layout.addWidget(lbl)
|
||||
|
||||
self._preset_group = QButtonGroup(self)
|
||||
# normal/x_dir per preset, keyed by the QButtonGroup id — QRadioButton
|
||||
# has no data slot of its own, so stash the vectors here instead of
|
||||
# duck-typing extra attributes onto the widget.
|
||||
self._preset_vectors: Dict[
|
||||
int, Tuple[Tuple[float, float, float], Tuple[float, float, float]]
|
||||
] = {}
|
||||
preset_layout = QGridLayout()
|
||||
presets = [
|
||||
("XY (Top)", (0, 0, 1), (1, 0, 0)),
|
||||
@@ -310,15 +317,14 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
btn = QRadioButton(label)
|
||||
btn.setChecked(idx == 0)
|
||||
self._preset_group.addButton(btn, idx)
|
||||
btn.normal = normal
|
||||
btn.x_dir = x_dir
|
||||
self._preset_vectors[idx] = (_vec3(normal), _vec3(x_dir))
|
||||
preset_layout.addWidget(btn, idx // 2, idx % 2)
|
||||
layout.addLayout(preset_layout)
|
||||
|
||||
# ── Custom angle (offset from XY) ──
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.HLine)
|
||||
line.setFrameShadow(QFrame.Sunken)
|
||||
line.setFrameShape(QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
self._custom_radio = QRadioButton("Custom (angle from XY):")
|
||||
@@ -351,8 +357,8 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
|
||||
# ── Buttons ──
|
||||
line2 = QFrame()
|
||||
line2.setFrameShape(QFrame.HLine)
|
||||
line2.setFrameShadow(QFrame.Sunken)
|
||||
line2.setFrameShape(QFrame.Shape.HLine)
|
||||
line2.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line2)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
@@ -371,7 +377,7 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
self._angle_x.valueChanged.connect(self._emit_preview)
|
||||
self._angle_y.valueChanged.connect(self._emit_preview)
|
||||
|
||||
def set_preview_callback(self, callback) -> None:
|
||||
def set_preview_callback(self, callback: Optional[Callable[[Any], None]]) -> None:
|
||||
"""Install a callback for live 3D preview of the workplane orientation.
|
||||
|
||||
*callback* is called with ``(normal, x_dir)`` whenever the user
|
||||
@@ -381,7 +387,7 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
# Emit once so the initial state shows a preview right away.
|
||||
self._emit_preview()
|
||||
|
||||
def _emit_preview(self, *args) -> None:
|
||||
def _emit_preview(self, *args: Any) -> None:
|
||||
"""Call the preview callback with the current orientation, if installed."""
|
||||
if self._preview_callback is None:
|
||||
return
|
||||
@@ -391,7 +397,7 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
except Exception as exc:
|
||||
logger.debug("workplane preview callback raised: %s", exc)
|
||||
|
||||
def hideEvent(self, event):
|
||||
def hideEvent(self, event: Any) -> None:
|
||||
"""Clear the live preview when the dialog closes."""
|
||||
if self._preview_callback is not None:
|
||||
try:
|
||||
@@ -400,17 +406,23 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
pass
|
||||
super().hideEvent(event)
|
||||
|
||||
def _on_preset_changed(self, btn):
|
||||
def _on_preset_changed(self, btn: Any) -> None:
|
||||
"""When a preset is selected, deselect the custom radio and emit preview."""
|
||||
self._custom_radio.setChecked(False)
|
||||
self._emit_preview()
|
||||
|
||||
def _on_ok(self):
|
||||
"""Compute the final orientation and accept."""
|
||||
def _compute_custom_orientation(
|
||||
self,
|
||||
) -> Optional[Tuple[Tuple[float, float, float], Tuple[float, float, float]]]:
|
||||
"""Compute ``(normal, x_dir)`` from the custom angle spinboxes.
|
||||
|
||||
Starts from the +Z normal and rotates by the two angle values.
|
||||
Returns *None* if the math fails (defensive — the dialog then falls
|
||||
back to the default orientation instead of crashing).
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
if self._custom_radio.isChecked():
|
||||
# Custom: start from XY normal and rotate by the two angles.
|
||||
try:
|
||||
ax = math.radians(self._angle_x.value())
|
||||
ay = math.radians(self._angle_y.value())
|
||||
# Start from +Z normal, rotate around X then Y
|
||||
@@ -433,10 +445,13 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
]
|
||||
)
|
||||
n = ry @ n
|
||||
n = n / np.linalg.norm(n)
|
||||
n_norm = np.linalg.norm(n)
|
||||
if n_norm < 1e-12:
|
||||
return None
|
||||
n = n / n_norm
|
||||
# x_dir: cross product of normal with world Y, or world Z if normal ~ Y
|
||||
world_y = np.array([0.0, 1.0, 0.0])
|
||||
if abs(np.dot(n, world_y)) > 0.99:
|
||||
if abs(float(np.dot(n, world_y))) > 0.99:
|
||||
world_y = np.array([0.0, 0.0, 1.0])
|
||||
x = np.cross(world_y, n)
|
||||
x_norm = np.linalg.norm(x)
|
||||
@@ -444,14 +459,24 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
x = x / x_norm
|
||||
else:
|
||||
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)
|
||||
return (
|
||||
(float(n[0]), float(n[1]), float(n[2])),
|
||||
(float(x[0]), float(x[1]), float(x[2])),
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug("custom workplane orientation math failed: %s", exc)
|
||||
return None
|
||||
|
||||
def _on_ok(self) -> None:
|
||||
"""Compute the final orientation and accept."""
|
||||
if self._custom_radio.isChecked():
|
||||
orientation = self._compute_custom_orientation()
|
||||
if orientation is not None:
|
||||
self._normal, self._x_dir = orientation
|
||||
else:
|
||||
btn = self._preset_group.checkedButton()
|
||||
if btn is not None:
|
||||
self._normal = btn.normal
|
||||
self._x_dir = btn.x_dir
|
||||
self._normal, self._x_dir = self._preset_vectors[self._preset_group.id(btn)]
|
||||
self.accept()
|
||||
|
||||
def get_orientation(self) -> Tuple[Tuple[float, float, float], Tuple[float, float, float], str]:
|
||||
@@ -460,50 +485,171 @@ class WorkplaneOrientationDialog(QDialog):
|
||||
Computes the current selection from the UI state so it works
|
||||
whether called before or after ``_on_ok``.
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
if self._custom_radio.isChecked():
|
||||
ax = math.radians(self._angle_x.value())
|
||||
ay = math.radians(self._angle_y.value())
|
||||
n = np.array([0.0, 0.0, 1.0])
|
||||
rx = np.array(
|
||||
[
|
||||
[1, 0, 0],
|
||||
[0, math.cos(ax), -math.sin(ax)],
|
||||
[0, math.sin(ax), math.cos(ax)],
|
||||
]
|
||||
)
|
||||
n = rx @ n
|
||||
ry = np.array(
|
||||
[
|
||||
[math.cos(ay), 0, math.sin(ay)],
|
||||
[0, 1, 0],
|
||||
[-math.sin(ay), 0, math.cos(ay)],
|
||||
]
|
||||
)
|
||||
n = ry @ n
|
||||
n = n / np.linalg.norm(n)
|
||||
world_y = np.array([0.0, 1.0, 0.0])
|
||||
if abs(np.dot(n, world_y)) > 0.99:
|
||||
world_y = np.array([0.0, 0.0, 1.0])
|
||||
x = np.cross(world_y, n)
|
||||
x_norm = np.linalg.norm(x)
|
||||
if x_norm > 1e-9:
|
||||
x = x / x_norm
|
||||
else:
|
||||
x = np.array([1.0, 0.0, 0.0])
|
||||
orientation = self._compute_custom_orientation()
|
||||
if orientation is None:
|
||||
orientation = ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0))
|
||||
normal, x_dir = orientation
|
||||
return (
|
||||
tuple(float(v) for v in n),
|
||||
tuple(float(v) for v in x),
|
||||
normal,
|
||||
x_dir,
|
||||
self._name_input.text().strip() or "Workplane",
|
||||
)
|
||||
else:
|
||||
btn = self._preset_group.checkedButton()
|
||||
if btn is not None:
|
||||
return (btn.normal, btn.x_dir, self._name_input.text().strip() or "Workplane")
|
||||
normal, x_dir = self._preset_vectors[self._preset_group.id(btn)]
|
||||
return (normal, x_dir, self._name_input.text().strip() or "Workplane")
|
||||
# Fallback: XY default.
|
||||
return (
|
||||
(0.0, 0.0, 1.0),
|
||||
(1.0, 0.0, 0.0),
|
||||
self._name_input.text().strip() or "Workplane",
|
||||
)
|
||||
|
||||
|
||||
class FilletDialog(QDialog):
|
||||
"""Dialog for fillet options — the common settings from CAD fillet tools.
|
||||
|
||||
Shown AFTER the user has picked the two faces whose shared edges will
|
||||
be rounded. Offers:
|
||||
|
||||
- size, entered as **diameter** or **radius** (the user asked for a
|
||||
diameter box; the unit toggle covers the radius crowd),
|
||||
- **tangent propagation** (extend the round along tangent-connected
|
||||
edges, like FreeCAD/SolidWorks "tangent chain"),
|
||||
- edge **scope** (only the edges between the two picked faces vs
|
||||
every edge of the body),
|
||||
- a live 3D preview (``set_preview_callback``), so dragging the size
|
||||
spinner shows the fillet in real time before committing.
|
||||
|
||||
``get_values()`` returns ``(size, size_is_diameter, tangent_propagation,
|
||||
scope)`` where *scope* is ``"selected"`` or ``"all"``. The host
|
||||
converts *size* to a radius (``size / 2`` for diameter).
|
||||
"""
|
||||
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Fillet Options")
|
||||
self.setMinimumWidth(360)
|
||||
|
||||
self._preview_callback: Optional[Callable[[Any], None]] = None
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
# ── Size: value + Diameter/Radius unit ──
|
||||
size_row = QHBoxLayout()
|
||||
self.size_unit_combo = QComboBox()
|
||||
self.size_unit_combo.addItems(["Diameter", "Radius"])
|
||||
self.size_unit_combo.setToolTip(
|
||||
"Enter the fillet size as a diameter or a radius (radius = diameter / 2)."
|
||||
)
|
||||
size_row.addWidget(self.size_unit_combo)
|
||||
|
||||
self.size_value_label = QLabel("Diameter (mm):")
|
||||
size_row.addWidget(self.size_value_label)
|
||||
|
||||
self.size_input = QDoubleSpinBox()
|
||||
self.size_input.setDecimals(2)
|
||||
self.size_input.setRange(0.01, 100000.0)
|
||||
self.size_input.setValue(2.0)
|
||||
self.size_input.setSingleStep(0.5)
|
||||
self.size_input.setSuffix(" mm")
|
||||
self.size_input.setToolTip("Fillet size along the rounded edge.")
|
||||
size_row.addWidget(self.size_input)
|
||||
layout.addLayout(size_row)
|
||||
|
||||
# ── Edge scope ──
|
||||
self.scope_group = QButtonGroup(self)
|
||||
scope_layout = QGridLayout()
|
||||
self.scope_selected_radio = QRadioButton("Edges between faces")
|
||||
self.scope_selected_radio.setChecked(True)
|
||||
self.scope_selected_radio.setToolTip("Round only the edges shared by the two picked faces.")
|
||||
self.scope_all_radio = QRadioButton("All edges of body")
|
||||
self.scope_all_radio.setToolTip(
|
||||
"Round every edge of the body (the picked faces only choose which body is modified)."
|
||||
)
|
||||
self.scope_group.addButton(self.scope_selected_radio)
|
||||
self.scope_group.addButton(self.scope_all_radio)
|
||||
scope_layout.addWidget(self.scope_selected_radio, 0, 0)
|
||||
scope_layout.addWidget(self.scope_all_radio, 1, 0)
|
||||
layout.addLayout(scope_layout)
|
||||
|
||||
# ── Tangent propagation ──
|
||||
self.tangent_checkbox = QCheckBox("Tangent propagation")
|
||||
self.tangent_checkbox.setChecked(True)
|
||||
self.tangent_checkbox.setToolTip(
|
||||
"Extend the fillet along edges that are tangent to the picked "
|
||||
"ones (e.g. a smooth chain of lines and arcs). Off = only the "
|
||||
"exact edges between the two faces."
|
||||
)
|
||||
layout.addWidget(self.tangent_checkbox)
|
||||
|
||||
# ── Edge count feedback ──
|
||||
self.edge_label = QLabel("")
|
||||
self.edge_label.setStyleSheet("color: #8a8a8a;")
|
||||
layout.addWidget(self.edge_label)
|
||||
|
||||
line = QFrame()
|
||||
line.setFrameShape(QFrame.Shape.HLine)
|
||||
line.setFrameShadow(QFrame.Shadow.Sunken)
|
||||
layout.addWidget(line)
|
||||
|
||||
button_layout = QHBoxLayout()
|
||||
ok_button = QPushButton("Apply Fillet")
|
||||
ok_button.clicked.connect(self.accept)
|
||||
cancel_button = QPushButton("Cancel")
|
||||
cancel_button.clicked.connect(self.reject)
|
||||
button_layout.addWidget(ok_button)
|
||||
button_layout.addWidget(cancel_button)
|
||||
layout.addLayout(button_layout)
|
||||
|
||||
# ── Live preview on every change ──
|
||||
self.size_unit_combo.currentIndexChanged.connect(self._on_unit_changed)
|
||||
self.size_input.valueChanged.connect(self._emit_preview)
|
||||
self.scope_selected_radio.toggled.connect(self._emit_preview)
|
||||
self.tangent_checkbox.stateChanged.connect(self._emit_preview)
|
||||
|
||||
def _on_unit_changed(self) -> None:
|
||||
"""Swap the size label between Diameter and Radius."""
|
||||
self.size_value_label.setText(
|
||||
"Radius (mm):" if self.size_unit_combo.currentText() == "Radius" else "Diameter (mm):"
|
||||
)
|
||||
self._emit_preview()
|
||||
|
||||
def set_edge_count(self, count: int) -> None:
|
||||
"""Show how many edges the current scope will round."""
|
||||
self.edge_label.setText(
|
||||
f"Fillets {count} edge{'s' if count != 1 else ''} between the picked faces."
|
||||
)
|
||||
|
||||
def set_preview_callback(self, callback: Optional[Callable[[Any], None]]) -> None:
|
||||
"""Install a live-preview callback; fires immediately with defaults."""
|
||||
self._preview_callback = callback
|
||||
self._emit_preview()
|
||||
|
||||
def _emit_preview(self, *args: Any) -> None:
|
||||
if self._preview_callback is None:
|
||||
return
|
||||
try:
|
||||
self._preview_callback(self.get_values())
|
||||
except Exception as exc: # preview must never break the dialog
|
||||
logger.debug("fillet preview callback raised: %s", exc)
|
||||
|
||||
def hideEvent(self, event: Any) -> None:
|
||||
if self._preview_callback is not None:
|
||||
try:
|
||||
self._preview_callback(None)
|
||||
except Exception:
|
||||
pass
|
||||
super().hideEvent(event)
|
||||
|
||||
def get_values(self) -> Tuple[float, bool, bool, str]:
|
||||
"""Return ``(size, size_is_diameter, tangent_propagation, scope)``."""
|
||||
return (
|
||||
self.size_input.value(),
|
||||
self.size_unit_combo.currentText() == "Diameter",
|
||||
self.tangent_checkbox.isChecked(),
|
||||
"all" if self.scope_all_radio.isChecked() else "selected",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user