Draft: Introduce 1-axis mode for ortho array (#21602)
* Draft: Introduce 1-axis mode for ortho array As the title says - the 1 axis mode allows to switch between all of the axises mode and allows to modify only 1 axis at the time that user can select with the checkbox. * Draft: Rename to Linear Mode and remove redundant comments * Draft: Display only one interval for the selected axis * Draft: Cache selected variables in user.cfg - axis mode, intervals, etc... * Draft: Make sure the checkboxes in OrthoArray are exclusively selected * Draft: Apply review comments Changed a couple of things according to review: * linear mode is now being used as default during first startup (it wasn't before) * applied Roy's comments about coding style, etc. * grouped everything into separate QGroupBox which is dedicated for Linear Mode and has it's own labels, although spinboxes are shared so during mode switch we reparent them now * removed Orthogonal Array's icon * in the QGroup applied naming suggested in the review, X Intervals -> interval, etc. * changed to radio buttons since we want exclusivity in selection
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
# @{
|
||||
import PySide.QtGui as QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
from PySide import QtWidgets
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
import Draft_rc # include resources, icons, ui files
|
||||
@@ -93,19 +93,18 @@ class TaskPanelOrthoArray:
|
||||
self.form.setWindowIcon(icon)
|
||||
self.form.setWindowTitle(translate("draft","Orthogonal array"))
|
||||
|
||||
self.form.label_icon.setPixmap(pix.scaled(32, 32))
|
||||
|
||||
# -------------------------------------------------------------------
|
||||
# Default values for the internal function,
|
||||
# and for the task panel interface
|
||||
start_x = U.Quantity(100.0, App.Units.Length)
|
||||
start_y = start_x
|
||||
start_z = start_x
|
||||
length_unit = start_x.getUserPreferred()[2]
|
||||
start_x = params.get_param("XInterval", "Mod/Draft/OrthoArrayLinearMode")
|
||||
start_y = params.get_param("YInterval", "Mod/Draft/OrthoArrayLinearMode")
|
||||
start_z = params.get_param("ZInterval", "Mod/Draft/OrthoArrayLinearMode")
|
||||
|
||||
self.v_x = App.Vector(start_x.Value, 0, 0)
|
||||
self.v_y = App.Vector(0, start_y.Value, 0)
|
||||
self.v_z = App.Vector(0, 0, start_z.Value)
|
||||
length_unit = App.Units.Quantity(0.0, App.Units.Length).getUserPreferred()[2]
|
||||
|
||||
self.v_x = App.Vector(start_x, 0, 0)
|
||||
self.v_y = App.Vector(0, start_y, 0)
|
||||
self.v_z = App.Vector(0, 0, start_z)
|
||||
|
||||
self.form.input_X_x.setProperty('rawValue', self.v_x.x)
|
||||
self.form.input_X_x.setProperty('unit', length_unit)
|
||||
@@ -128,9 +127,9 @@ class TaskPanelOrthoArray:
|
||||
self.form.input_Z_z.setProperty('rawValue', self.v_z.z)
|
||||
self.form.input_Z_z.setProperty('unit', length_unit)
|
||||
|
||||
self.n_x = 2
|
||||
self.n_y = 2
|
||||
self.n_z = 1
|
||||
self.n_x = params.get_param("XNumOfElements", "Mod/Draft/OrthoArrayLinearMode")
|
||||
self.n_y = params.get_param("YNumOfElements", "Mod/Draft/OrthoArrayLinearMode")
|
||||
self.n_z = params.get_param("ZNumOfElements", "Mod/Draft/OrthoArrayLinearMode")
|
||||
|
||||
self.form.spinbox_n_X.setValue(self.n_x)
|
||||
self.form.spinbox_n_Y.setValue(self.n_y)
|
||||
@@ -143,6 +142,19 @@ class TaskPanelOrthoArray:
|
||||
self.form.checkbox_link.setChecked(self.use_link)
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Initialize Linear mode variables
|
||||
self.linear_mode = params.get_param("LinearModeOn", "Mod/Draft/OrthoArrayLinearMode")
|
||||
self.active_axis = params.get_param("AxisSelected", "Mod/Draft/OrthoArrayLinearMode")
|
||||
|
||||
# Hide the axis radiobuttons initially (they're only visible in Linear mode)
|
||||
self.toggle_axis_radiobuttons(False)
|
||||
if self.linear_mode:
|
||||
self.form.button_linear_mode.setChecked(True)
|
||||
self.toggle_linear_mode()
|
||||
else:
|
||||
self.form.group_linearmode.hide()
|
||||
# -------------------------------------------------------------------
|
||||
|
||||
# Some objects need to be selected before we can execute the function.
|
||||
self.selection = None
|
||||
|
||||
@@ -166,6 +178,12 @@ class TaskPanelOrthoArray:
|
||||
self.form.checkbox_fuse.stateChanged.connect(self.set_fuse)
|
||||
self.form.checkbox_link.stateChanged.connect(self.set_link)
|
||||
|
||||
# Linear mode callbacks - only set up if the UI elements exist
|
||||
self.form.button_linear_mode.clicked.connect(self.toggle_linear_mode)
|
||||
self.form.radiobutton_x_axis.clicked.connect(lambda: self.set_active_axis("X"))
|
||||
self.form.radiobutton_y_axis.clicked.connect(lambda: self.set_active_axis("Y"))
|
||||
self.form.radiobutton_z_axis.clicked.connect(lambda: self.set_active_axis("Z"))
|
||||
|
||||
|
||||
def accept(self):
|
||||
"""Execute when clicking the OK button or Enter key."""
|
||||
@@ -183,6 +201,20 @@ class TaskPanelOrthoArray:
|
||||
self.v_x, self.v_y, self.v_z,
|
||||
self.n_x, self.n_y, self.n_z)
|
||||
if self.valid_input:
|
||||
axis_values = {
|
||||
'X': (self.v_x.x, self.n_x),
|
||||
'Y': (self.v_y.y, self.n_y),
|
||||
'Z': (self.v_z.z, self.n_z),
|
||||
}
|
||||
|
||||
values = axis_values.get(self.active_axis)
|
||||
if values is not None:
|
||||
interval, num_elements = values
|
||||
# Set interval
|
||||
params.set_param(f"{self.active_axis}Interval", interval, "Mod/Draft/OrthoArrayLinearMode")
|
||||
# Set number of elements
|
||||
params.set_param(f"{self.active_axis}NumOfElements", num_elements, "Mod/Draft/OrthoArrayLinearMode")
|
||||
|
||||
self.create_object()
|
||||
# The internal function already displays messages
|
||||
self.finish()
|
||||
@@ -211,6 +243,14 @@ class TaskPanelOrthoArray:
|
||||
_err(translate("draft","Object:") + " {0} ({1})".format(obj.Label, obj.TypeId))
|
||||
return False
|
||||
|
||||
# we should not ever do this but maybe a sanity check here?
|
||||
if self.linear_mode:
|
||||
if not (self.form.radiobutton_x_axis.isChecked() or
|
||||
self.form.radiobutton_y_axis.isChecked() or
|
||||
self.form.radiobutton_z_axis.isChecked()):
|
||||
_err(translate("draft","In Linear mode, at least one axis must be selected."))
|
||||
return False
|
||||
|
||||
# The other arguments are not tested but they should be present.
|
||||
if v_x and v_y and v_z:
|
||||
pass
|
||||
@@ -234,6 +274,20 @@ class TaskPanelOrthoArray:
|
||||
# make a compound and then use it as input for the array function.
|
||||
sel_obj = self.selection[0]
|
||||
|
||||
# clear this stuff out to be sure we don't preserve something
|
||||
# from 3-axis mode
|
||||
if self.linear_mode:
|
||||
start_val = App.Units.Quantity(100.0, App.Units.Length).Value
|
||||
if not self.form.radiobutton_x_axis.isChecked():
|
||||
self.n_x = 1
|
||||
self.v_x = App.Vector(start_val, 0, 0)
|
||||
if not self.form.radiobutton_y_axis.isChecked():
|
||||
self.n_y = 1
|
||||
self.v_y = App.Vector(0, start_val, 0)
|
||||
if not self.form.radiobutton_z_axis.isChecked():
|
||||
self.n_z = 1
|
||||
self.v_z = App.Vector(0, 0, start_val)
|
||||
|
||||
# This creates the object immediately
|
||||
# obj = Draft.make_ortho_array(sel_obj,
|
||||
# self.v_x, self.v_y, self.v_z,
|
||||
@@ -357,6 +411,7 @@ class TaskPanelOrthoArray:
|
||||
# make a compound and then use it as input for the array function.
|
||||
sel_obj = self.selection[0]
|
||||
_msg(translate("draft","Object:") + " {}".format(sel_obj.Label))
|
||||
|
||||
_msg(translate("draft","Number of X elements:") + " {}".format(self.n_x))
|
||||
_msg(translate("draft","Interval X:")
|
||||
+ " ({0}, {1}, {2})".format(self.v_x.x,
|
||||
@@ -379,6 +434,155 @@ class TaskPanelOrthoArray:
|
||||
"""Execute when clicking the Cancel button or pressing Escape."""
|
||||
self.finish()
|
||||
|
||||
def toggle_linear_mode(self):
|
||||
"""Toggle between Linear mode and Orthogonal mode."""
|
||||
self.linear_mode = self.form.button_linear_mode.isChecked()
|
||||
self.toggle_axis_radiobuttons(self.linear_mode)
|
||||
params.set_param("LinearModeOn" , self.linear_mode, "Mod/Draft/OrthoArrayLinearMode")
|
||||
|
||||
if self.linear_mode:
|
||||
self.form.button_linear_mode.setText(translate("draft", "Switch to ortho mode"))
|
||||
|
||||
# check radiobutton based on current cfg
|
||||
self.update_axis_ui()
|
||||
|
||||
# For linear mode we're hiding all group boxes for X, Y, Z axis and the one
|
||||
# with number of elements as we will reparent those spinboxes under newly
|
||||
# created group
|
||||
self._set_orthomode_groups_visibility(hide=True)
|
||||
self._reparent_groups(mode="Linear")
|
||||
|
||||
# Set the appropriate title for the group (we flip it back and forth after changing mode)
|
||||
# and show the group
|
||||
self.form.group_linearmode.show()
|
||||
self.form.group_linearmode.setTitle(f"{self.active_axis} Axis")
|
||||
else: # ortho mode
|
||||
self.form.button_linear_mode.setText(translate("draft", "Switch to linear mode"))
|
||||
|
||||
# For ortho mode we're showing back default groupboxes and we reparent everything
|
||||
# back to them, as we reuse spinboxes in both modes
|
||||
self._set_orthomode_groups_visibility(hide=False)
|
||||
self._reparent_groups(mode="Ortho")
|
||||
|
||||
self.form.group_linearmode.hide()
|
||||
|
||||
def toggle_axis_radiobuttons(self, show):
|
||||
"""Show or hide the axis radio buttons."""
|
||||
for radiobutton in [self.form.radiobutton_x_axis,
|
||||
self.form.radiobutton_y_axis,
|
||||
self.form.radiobutton_z_axis]:
|
||||
radiobutton.setVisible(show)
|
||||
|
||||
def set_active_axis(self, axis):
|
||||
"""Set the active axis when a radio button is changed."""
|
||||
if self.linear_mode:
|
||||
# get current radiobutton that was supposed to have state changed
|
||||
radiobutton = getattr(self.form, f"radiobutton_{axis.lower()}_axis")
|
||||
|
||||
# If we're checking a different radio button than the current active axis
|
||||
if radiobutton.isChecked() and axis != self.active_axis:
|
||||
self.active_axis = axis
|
||||
params.set_param("AxisSelected", self.active_axis, "Mod/Draft/OrthoArrayLinearMode")
|
||||
self._setup_linear_mode_layout()
|
||||
self.form.group_linearmode.setTitle(f"{self.active_axis} Axis")
|
||||
|
||||
|
||||
def update_axis_ui(self):
|
||||
"""Update the UI to reflect the current axis selection."""
|
||||
# Make sure only one axis is selected
|
||||
self.form.radiobutton_x_axis.setChecked(self.active_axis == "X")
|
||||
self.form.radiobutton_y_axis.setChecked(self.active_axis == "Y")
|
||||
self.form.radiobutton_z_axis.setChecked(self.active_axis == "Z")
|
||||
|
||||
def _get_axis_widgets(self, axis):
|
||||
"""Get all widgets for a specific axis."""
|
||||
return {
|
||||
'spinbox_elements': getattr(self.form, f"spinbox_n_{axis}", None),
|
||||
'spinbox_interval': getattr(self.form, f"input_{axis}_{axis.lower()}", None),
|
||||
'button_reset': getattr(self.form, f"button_reset_{axis}", None)
|
||||
}
|
||||
|
||||
def _clear_linear_mode_layout(self):
|
||||
"""Clear all widgets from the linear mode layout."""
|
||||
group_layout = self.form.group_linearmode.layout()
|
||||
|
||||
# Remove all items from the layout
|
||||
while group_layout.count():
|
||||
item = group_layout.takeAt(0)
|
||||
if item.widget():
|
||||
item.widget().setParent(None)
|
||||
|
||||
def _setup_linear_mode_layout(self):
|
||||
"""Set up the Linear mode layout with widgets for the active axis."""
|
||||
# Clear existing layout first
|
||||
self._clear_linear_mode_layout()
|
||||
|
||||
group_layout = self.form.group_linearmode.layout()
|
||||
|
||||
# Create labels
|
||||
label_elements = QtWidgets.QLabel(translate("draft", "Number of elements"))
|
||||
label_interval = QtWidgets.QLabel(translate("draft", "Interval"))
|
||||
|
||||
# Get widgets for active axis
|
||||
widgets = self._get_axis_widgets(self.active_axis)
|
||||
|
||||
# Add widgets to layout
|
||||
widget_pairs = [
|
||||
(label_elements, widgets['spinbox_elements']),
|
||||
(label_interval, widgets['spinbox_interval'])
|
||||
]
|
||||
|
||||
for row_index, (label, widget) in enumerate(widget_pairs):
|
||||
label.setParent(self.form.group_linearmode)
|
||||
widget.setParent(self.form.group_linearmode)
|
||||
group_layout.addWidget(label, row_index, 0)
|
||||
group_layout.addWidget(widget, row_index, 1)
|
||||
|
||||
# Add reset button spanning both columns
|
||||
widgets['button_reset'].setParent(self.form.group_linearmode)
|
||||
group_layout.addWidget(widgets['button_reset'], 2, 0, 1, 2)
|
||||
|
||||
def _restore_axis_to_ortho_layout(self, axis, row_index):
|
||||
"""Restore widgets for a specific axis back to their original Ortho layout positions."""
|
||||
widgets = self._get_axis_widgets(axis)
|
||||
|
||||
# Restore spinbox elements to grid layout
|
||||
grid_number_layout = self.form.findChild(QtWidgets.QGridLayout, "grid_number")
|
||||
grid_number_layout.addWidget(widgets['spinbox_elements'], row_index, 1)
|
||||
|
||||
# Restore interval input to its axis-specific inner grid layout
|
||||
inner_grid_layout = self.form.findChild(QtWidgets.QGridLayout, f"grid_{axis}")
|
||||
inner_grid_layout.addWidget(widgets['spinbox_interval'], row_index, 1)
|
||||
|
||||
# Restore reset button to its axis group
|
||||
group_box = self.form.findChild(QtWidgets.QGroupBox, f"group_{axis}")
|
||||
group_axis_layout = group_box.layout()
|
||||
group_axis_layout.addWidget(widgets['button_reset'], 1, 0)
|
||||
|
||||
def _setup_ortho_mode_layout(self):
|
||||
"""Restore all widgets back to their original Ortho mode layout positions."""
|
||||
for row_index, axis in enumerate(["X", "Y", "Z"]):
|
||||
self._restore_axis_to_ortho_layout(axis, row_index)
|
||||
|
||||
def _reparent_groups(self, mode="Linear"):
|
||||
"""Reparent widgets between Linear and Ortho mode layouts."""
|
||||
if mode == "Linear":
|
||||
self._setup_linear_mode_layout()
|
||||
else:
|
||||
self._setup_ortho_mode_layout()
|
||||
|
||||
def _set_orthomode_groups_visibility(self, hide=True):
|
||||
"""Change visibility of ortho mode groups"""
|
||||
for axis in ["X", "Y", "Z"]:
|
||||
group_name = f"group_{axis}"
|
||||
group_box = self.form.findChild(QtWidgets.QGroupBox, group_name)
|
||||
if hide:
|
||||
group_box.hide()
|
||||
self.form.group_copies.hide()
|
||||
else:
|
||||
group_box.show()
|
||||
self.form.group_copies.show()
|
||||
|
||||
def finish(self):
|
||||
"""Finish the command, after accept or reject.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user