| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides the task panel code for the Draft OrthoArray tool.""" |
| | |
| | |
| | |
| |
|
| | |
| | |
| | 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 |
| | import DraftVecUtils |
| | from FreeCAD import Units as U |
| | from draftutils import params |
| | from draftutils.messages import _err, _log, _msg |
| | from draftutils.translate import translate |
| |
|
| | |
| | bool(Draft_rc.__name__) |
| |
|
| |
|
| | def _quantity(st): |
| | |
| | |
| | |
| | return U.Quantity(st.replace("+", "--")).Value |
| |
|
| |
|
| | class TaskPanelOrthoArray: |
| | """TaskPanel code for the OrthoArray command. |
| | |
| | The names of the widgets are defined in the `.ui` file. |
| | This `.ui` file `must` be loaded into an attribute |
| | called `self.form` so that it is loaded into the task panel correctly. |
| | |
| | In this class all widgets are automatically created |
| | as `self.form.<widget_name>`. |
| | |
| | The `.ui` file may use special FreeCAD widgets such as |
| | `Gui::InputField` (based on `QLineEdit`) and |
| | `Gui::QuantitySpinBox` (based on `QAbstractSpinBox`). |
| | See the Doxygen documentation of the corresponding files in `src/Gui/`, |
| | for example, `InputField.h` and `QuantitySpinBox.h`. |
| | |
| | Attributes |
| | ---------- |
| | source_command: gui_base.GuiCommandBase |
| | This attribute holds a reference to the calling class |
| | of this task panel. |
| | This parent class, which is derived from `gui_base.GuiCommandBase`, |
| | is responsible for calling this task panel, for installing |
| | certain callbacks, and for removing them. |
| | |
| | It also delays the execution of the internal creation commands |
| | by using the `draftutils.todo.ToDo` class. |
| | |
| | See Also |
| | -------- |
| | * https://forum.freecad.org/viewtopic.php?f=10&t=40007 |
| | * https://forum.freecad.org/viewtopic.php?t=5374#p43038 |
| | """ |
| |
|
| | def __init__(self): |
| |
|
| | self.form = Gui.PySideUic.loadUi(":/ui/TaskPanel_OrthoArray.ui") |
| | self.form.setWindowTitle(translate("draft", "Orthogonal Array")) |
| | self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_Array.svg")) |
| |
|
| | |
| | |
| | |
| | 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, 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_y.setProperty("rawValue", self.v_x.y) |
| | self.form.input_X_z.setProperty("rawValue", self.v_x.z) |
| |
|
| | self.form.input_Y_x.setProperty("rawValue", self.v_y.x) |
| | self.form.input_Y_y.setProperty("rawValue", self.v_y.y) |
| | self.form.input_Y_z.setProperty("rawValue", self.v_y.z) |
| |
|
| | self.form.input_Z_x.setProperty("rawValue", self.v_z.x) |
| | self.form.input_Z_y.setProperty("rawValue", self.v_z.y) |
| | self.form.input_Z_z.setProperty("rawValue", self.v_z.z) |
| |
|
| | 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) |
| | self.form.spinbox_n_Z.setValue(self.n_z) |
| |
|
| | self.fuse = params.get_param("Draft_array_fuse") |
| | self.use_link = params.get_param("Draft_array_Link") |
| |
|
| | self.form.checkbox_fuse.setChecked(self.fuse) |
| | self.form.checkbox_link.setChecked(self.use_link) |
| | |
| |
|
| | |
| | self.linear_mode = params.get_param("LinearModeOn", "Mod/Draft/OrthoArrayLinearMode") |
| | self.active_axis = params.get_param("AxisSelected", "Mod/Draft/OrthoArrayLinearMode") |
| |
|
| | |
| | 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() |
| | |
| |
|
| | |
| | self.selection = None |
| |
|
| | |
| | |
| | self.valid_input = False |
| |
|
| | self.set_widget_callbacks() |
| |
|
| | self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") |
| | self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") |
| |
|
| | def set_widget_callbacks(self): |
| | """Set up the callbacks (slots) for the widget signals.""" |
| | |
| | self.form.button_reset_X.clicked.connect(lambda: self.reset_v("X")) |
| | self.form.button_reset_Y.clicked.connect(lambda: self.reset_v("Y")) |
| | self.form.button_reset_Z.clicked.connect(lambda: self.reset_v("Z")) |
| |
|
| | |
| | if hasattr(self.form.checkbox_fuse, "checkStateChanged"): |
| | self.form.checkbox_fuse.checkStateChanged.connect(self.set_fuse) |
| | self.form.checkbox_link.checkStateChanged.connect(self.set_link) |
| | else: |
| | self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) |
| | self.form.checkbox_link.stateChanged.connect(self.set_link) |
| |
|
| | |
| | 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.""" |
| | self.selection = Gui.Selection.getSelection() |
| |
|
| | (self.v_x, self.v_y, self.v_z) = self.get_intervals() |
| |
|
| | (self.n_x, self.n_y, self.n_z) = self.get_numbers() |
| |
|
| | self.valid_input = self.validate_input( |
| | self.selection, 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 |
| | |
| | params.set_param( |
| | f"{self.active_axis}Interval", interval, "Mod/Draft/OrthoArrayLinearMode" |
| | ) |
| | |
| | params.set_param( |
| | f"{self.active_axis}NumOfElements", |
| | num_elements, |
| | "Mod/Draft/OrthoArrayLinearMode", |
| | ) |
| |
|
| | self.create_object() |
| | |
| | self.finish() |
| |
|
| | def validate_input(self, selection, v_x, v_y, v_z, n_x, n_y, n_z): |
| | """Check that the input is valid. |
| | |
| | Some values may not need to be checked because |
| | the interface may not allow one to input wrong data. |
| | """ |
| | if not selection: |
| | _err(translate("draft", "At least 1 element must be selected")) |
| | return False |
| |
|
| | if n_x < 1 or n_y < 1 or n_z < 1: |
| | _err(translate("draft", "Number of elements must be at least 1")) |
| | return False |
| |
|
| | |
| | |
| | obj = selection[0] |
| | if obj.isDerivedFrom("App::FeaturePython"): |
| | _err(translate("draft", "Selection is not suitable for array")) |
| | _err(translate("draft", "Object:") + " {0} ({1})".format(obj.Label, obj.TypeId)) |
| | return False |
| |
|
| | |
| | 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 1 axis must be selected")) |
| | return False |
| |
|
| | |
| | if v_x and v_y and v_z: |
| | pass |
| |
|
| | self.fuse = self.form.checkbox_fuse.isChecked() |
| | self.use_link = self.form.checkbox_link.isChecked() |
| | return True |
| |
|
| | def create_object(self): |
| | """Create the new object. |
| | |
| | At this stage we already tested that the input is correct |
| | so the necessary attributes are already set. |
| | Then we proceed with the internal function to create the new object. |
| | """ |
| | if len(self.selection) == 1: |
| | sel_obj = self.selection[0] |
| | else: |
| | |
| | |
| | |
| | sel_obj = self.selection[0] |
| |
|
| | |
| | |
| | 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) |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | _cmd = "Draft.make_ortho_array" |
| | _cmd += "(" |
| | _cmd += "App.ActiveDocument." + sel_obj.Name + ", " |
| | _cmd += "v_x=" + DraftVecUtils.toString(self.v_x) + ", " |
| | _cmd += "v_y=" + DraftVecUtils.toString(self.v_y) + ", " |
| | _cmd += "v_z=" + DraftVecUtils.toString(self.v_z) + ", " |
| | _cmd += "n_x=" + str(self.n_x) + ", " |
| | _cmd += "n_y=" + str(self.n_y) + ", " |
| | _cmd += "n_z=" + str(self.n_z) + ", " |
| | _cmd += "use_link=" + str(self.use_link) |
| | _cmd += ")" |
| |
|
| | Gui.addModule("Draft") |
| |
|
| | _cmd_list = [ |
| | "_obj_ = " + _cmd, |
| | "_obj_.Fuse = " + str(self.fuse), |
| | "Draft.autogroup(_obj_)", |
| | "App.ActiveDocument.recompute()", |
| | ] |
| |
|
| | |
| | self.source_command.commit(translate("draft", "Create Orthogonal Array"), _cmd_list) |
| |
|
| | def get_numbers(self): |
| | """Get the number of elements from the widgets.""" |
| | return ( |
| | self.form.spinbox_n_X.value(), |
| | self.form.spinbox_n_Y.value(), |
| | self.form.spinbox_n_Z.value(), |
| | ) |
| |
|
| | def get_intervals(self): |
| | """Get the interval vectors from the widgets.""" |
| | v_x_x_str = self.form.input_X_x.text() |
| | v_x_y_str = self.form.input_X_y.text() |
| | v_x_z_str = self.form.input_X_z.text() |
| | v_x = App.Vector(_quantity(v_x_x_str), _quantity(v_x_y_str), _quantity(v_x_z_str)) |
| |
|
| | v_y_x_str = self.form.input_Y_x.text() |
| | v_y_y_str = self.form.input_Y_y.text() |
| | v_y_z_str = self.form.input_Y_z.text() |
| | v_y = App.Vector(_quantity(v_y_x_str), _quantity(v_y_y_str), _quantity(v_y_z_str)) |
| |
|
| | v_z_x_str = self.form.input_Z_x.text() |
| | v_z_y_str = self.form.input_Z_y.text() |
| | v_z_z_str = self.form.input_Z_z.text() |
| | v_z = App.Vector(_quantity(v_z_x_str), _quantity(v_z_y_str), _quantity(v_z_z_str)) |
| |
|
| | return v_x, v_y, v_z |
| |
|
| | def reset_v(self, interval): |
| | """Reset the interval to zero distance. |
| | |
| | Parameters |
| | ---------- |
| | interval: str |
| | Either "X", "Y", "Z", to reset the interval vector |
| | for that direction. |
| | """ |
| | if interval == "X": |
| | self.form.input_X_x.setProperty("rawValue", 100) |
| | self.form.input_X_y.setProperty("rawValue", 0) |
| | self.form.input_X_z.setProperty("rawValue", 0) |
| | self.v_x, self.v_y, self.v_z = self.get_intervals() |
| | elif interval == "Y": |
| | self.form.input_Y_x.setProperty("rawValue", 0) |
| | self.form.input_Y_y.setProperty("rawValue", 100) |
| | self.form.input_Y_z.setProperty("rawValue", 0) |
| | self.v_x, self.v_y, self.v_z = self.get_intervals() |
| | elif interval == "Z": |
| | self.form.input_Z_x.setProperty("rawValue", 0) |
| | self.form.input_Z_y.setProperty("rawValue", 0) |
| | self.form.input_Z_z.setProperty("rawValue", 100) |
| | self.v_x, self.v_y, self.v_z = self.get_intervals() |
| |
|
| | def print_fuse_state(self, fuse): |
| | """Print the fuse state translated.""" |
| | if fuse: |
| | state = self.tr_true |
| | else: |
| | state = self.tr_false |
| | _msg(translate("draft", "Fuse:") + " {}".format(state)) |
| |
|
| | def set_fuse(self): |
| | """Execute as a callback when the fuse checkbox changes.""" |
| | self.fuse = self.form.checkbox_fuse.isChecked() |
| | params.set_param("Draft_array_fuse", self.fuse) |
| |
|
| | def print_link_state(self, use_link): |
| | """Print the link state translated.""" |
| | if use_link: |
| | state = self.tr_true |
| | else: |
| | state = self.tr_false |
| | _msg(translate("draft", "Create link array:") + " {}".format(state)) |
| |
|
| | def set_link(self): |
| | """Execute as a callback when the link checkbox changes.""" |
| | self.use_link = self.form.checkbox_link.isChecked() |
| | params.set_param("Draft_array_Link", self.use_link) |
| |
|
| | def print_messages(self): |
| | """Print messages about the operation.""" |
| | if len(self.selection) == 1: |
| | sel_obj = self.selection[0] |
| | else: |
| | |
| | |
| | |
| | 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, self.v_x.y, self.v_x.z) |
| | ) |
| | _msg(translate("draft", "Number of Y elements:") + " {}".format(self.n_y)) |
| | _msg( |
| | translate("draft", "Interval Y:") |
| | + " ({0}, {1}, {2})".format(self.v_y.x, self.v_y.y, self.v_y.z) |
| | ) |
| | _msg(translate("draft", "Number of Z elements:") + " {}".format(self.n_z)) |
| | _msg( |
| | translate("draft", "Interval Z:") |
| | + " ({0}, {1}, {2})".format(self.v_z.x, self.v_z.y, self.v_z.z) |
| | ) |
| | self.print_fuse_state(self.fuse) |
| | self.print_link_state(self.use_link) |
| |
|
| | def reject(self): |
| | """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")) |
| |
|
| | |
| | self.update_axis_ui() |
| |
|
| | |
| | |
| | |
| | self._set_orthomode_groups_visibility(hide=True) |
| | self._reparent_groups(mode="Linear") |
| |
|
| | |
| | |
| | self.form.group_linearmode.show() |
| | match self.active_axis: |
| | case "X": |
| | title = translate("draft", "X-Axis") |
| | case "Y": |
| | title = translate("draft", "Y-Axis") |
| | case "Z": |
| | title = translate("draft", "Z-Axis") |
| | self.form.group_linearmode.setTitle(title) |
| | else: |
| | self.form.button_linear_mode.setText(translate("draft", "Switch to Linear Mode")) |
| |
|
| | |
| | |
| | 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: |
| | |
| | radiobutton = getattr(self.form, f"radiobutton_{axis.lower()}_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() |
| | match self.active_axis: |
| | case "X": |
| | title = translate("draft", "X-Axis") |
| | case "Y": |
| | title = translate("draft", "Y-Axis") |
| | case "Z": |
| | title = translate("draft", "Z-Axis") |
| | self.form.group_linearmode.setTitle(title) |
| |
|
| | def update_axis_ui(self): |
| | """Update the UI to reflect the current axis selection.""" |
| | |
| | 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() |
| |
|
| | |
| | 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.""" |
| | |
| | self._clear_linear_mode_layout() |
| |
|
| | group_layout = self.form.group_linearmode.layout() |
| |
|
| | |
| | label_elements = QtWidgets.QLabel(translate("draft", "Number of elements")) |
| | label_interval = QtWidgets.QLabel(translate("draft", "Interval")) |
| |
|
| | |
| | widgets = self._get_axis_widgets(self.active_axis) |
| |
|
| | |
| | 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) |
| |
|
| | |
| | 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) |
| |
|
| | |
| | grid_number_layout = self.form.findChild(QtWidgets.QGridLayout, "grid_number") |
| | grid_number_layout.addWidget(widgets["spinbox_elements"], row_index, 1) |
| |
|
| | |
| | inner_grid_layout = self.form.findChild(QtWidgets.QGridLayout, f"grid_{axis}") |
| | inner_grid_layout.addWidget(widgets["spinbox_interval"], row_index, 1) |
| |
|
| | |
| | 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. |
| | |
| | It finally calls the parent class to execute |
| | the delayed functions, and perform cleanup. |
| | """ |
| | |
| | if Gui.ActiveDocument is not None: |
| | Gui.ActiveDocument.resetEdit() |
| | |
| | self.source_command.completed() |
| |
|
| |
|
| | |
| |
|