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),