diff --git a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui
index 625a2c8c92..81cf7362a9 100644
--- a/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui
+++ b/src/Mod/Draft/Resources/ui/TaskPanel_OrthoArray.ui
@@ -39,10 +39,59 @@
-
-
-
- (Placeholder for the icon)
+
+
+ Toggle between Orthogonal mode and Linear mode. In Linear mode, you can select which axis to use.
+
+ Axis mode
+
+
+
-
+
+
+ Switch to linear mode
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ X Axis
+
+
+ true
+
+
+
+ -
+
+
+ Y Axis
+
+
+ false
+
+
+
+ -
+
+
+ Z Axis
+
+
+ false
+
+
+
+
+
+
-
@@ -122,6 +171,18 @@ The number must be at least 1 in each direction.
+ -
+
+
+ Currently selected axis
+
+
+
+
+
+
+
+
-
diff --git a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py
index cd1f6a612b..93076d35e7 100644
--- a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py
+++ b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py
@@ -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.
diff --git a/src/Mod/Draft/draftutils/params.py b/src/Mod/Draft/draftutils/params.py
index e4046a8a90..a960935f93 100644
--- a/src/Mod/Draft/draftutils/params.py
+++ b/src/Mod/Draft/draftutils/params.py
@@ -506,6 +506,18 @@ def _get_param_dictionary():
"Wall": ("bool", False),
}
+ start_val = App.Units.Quantity(100.0, App.Units.Length).Value
+ param_dict["Mod/Draft/OrthoArrayLinearMode"] = {
+ "LinearModeOn": ("bool", True),
+ "AxisSelected": ("string", "X"),
+ "XInterval": ("float", start_val),
+ "YInterval": ("float", start_val),
+ "ZInterval": ("float", start_val),
+ "XNumOfElements": ("int", 2),
+ "YNumOfElements": ("int", 2),
+ "ZNumOfElements": ("int", 2)
+ }
+
# Arch parameters that are not in the preferences:
param_dict["Mod/Arch"] = {
"applyConstructionStyle": ("bool", True),