Download src/Mod/Draft/drafttaskpanels/task_circulararray.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 19.4 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/drafttaskpanels/task_circulararray.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/drafttaskpanels/task_circulararray.py
-
curl -L -o task_circulararray.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/drafttaskpanels/task_circulararray.py
19.4 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * (c) 2019 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> * | |
| # * * | |
| # * This file is part of the FreeCAD CAx development system. * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENCE text file. * | |
| # * * | |
| # * FreeCAD is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with FreeCAD; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| """Provides the task panel code for the Draft CircularArray tool.""" | |
| ## @package task_circulararray | |
| # \ingroup drafttaskpanels | |
| # \brief Provides the task panel code for the Draft CircularArray tool. | |
| ## \addtogroup drafttaskpanels | |
| # @{ | |
| import PySide.QtGui as QtGui | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| import FreeCAD as App | |
| import FreeCADGui as Gui | |
| import Draft_rc # include resources, icons, ui files | |
| import DraftVecUtils | |
| from FreeCAD import Units as U | |
| from draftutils import params | |
| from draftutils.messages import _err, _log, _msg, _wrn | |
| from draftutils.translate import translate | |
| # The module is used to prevent complaints from code checkers (flake8) | |
| bool(Draft_rc.__name__) | |
| def _quantity(st): | |
| # workaround for improper handling of plus sign | |
| # in Building US unit system | |
| # https://github.com/FreeCAD/FreeCAD/issues/11345 | |
| return U.Quantity(st.replace("+", "--")).Value | |
| class TaskPanelCircularArray: | |
| """TaskPanel code for the CircularArray 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_CircularArray.ui") | |
| self.form.setWindowTitle(translate("draft", "Circular Array")) | |
| self.form.setWindowIcon(QtGui.QIcon(":/icons/Draft_CircularArray.svg")) | |
| # ------------------------------------------------------------------- | |
| # Default values for the internal function, and for the task panel interface | |
| self.center = App.Vector() | |
| # TODO: the axis is currently fixed, it should be editable | |
| # or selectable from the task panel | |
| self.axis = App.Vector(0, 0, 1) | |
| self.r_distance = 100 | |
| self.tan_distance = 50 | |
| self.number = 3 | |
| self.symmetry = 1 | |
| self.fuse = params.get_param("Draft_array_fuse") | |
| self.use_link = params.get_param("Draft_array_Link") | |
| self.form.input_c_x.setProperty("rawValue", self.center.x) | |
| self.form.input_c_y.setProperty("rawValue", self.center.y) | |
| self.form.input_c_z.setProperty("rawValue", self.center.z) | |
| self.form.spinbox_r_distance.setProperty("rawValue", self.r_distance) | |
| self.form.spinbox_tan_distance.setProperty("rawValue", self.tan_distance) | |
| self.form.spinbox_number.setValue(self.number) | |
| self.form.spinbox_symmetry.setValue(self.symmetry) | |
| self.form.checkbox_fuse.setChecked(self.fuse) | |
| self.form.checkbox_link.setChecked(self.use_link) | |
| # ------------------------------------------------------------------- | |
| # Some objects need to be selected before we can execute the function. | |
| self.selection = None | |
| # This is used to test the input of the internal function. | |
| # It should be changed to True before we can execute the function. | |
| self.valid_input = False | |
| self.set_widget_callbacks() | |
| self.tr_true = QT_TRANSLATE_NOOP("Draft", "True") | |
| self.tr_false = QT_TRANSLATE_NOOP("Draft", "False") | |
| # The mask is not used at the moment, but could be used in the future | |
| # by a callback to restrict the coordinates of the pointer. | |
| self.mask = "" | |
| def set_widget_callbacks(self): | |
| """Set up the callbacks (slots) for the widget signals.""" | |
| # New style for Qt5 | |
| self.form.button_reset.clicked.connect(self.reset_point) | |
| # When the checkbox changes, change the internal value | |
| if hasattr(self.form.checkbox_fuse, "checkStateChanged"): # Qt version >= 6.7.0 | |
| self.form.checkbox_fuse.checkStateChanged.connect(self.set_fuse) | |
| self.form.checkbox_link.checkStateChanged.connect(self.set_link) | |
| else: # Qt version < 6.7.0 | |
| self.form.checkbox_fuse.stateChanged.connect(self.set_fuse) | |
| self.form.checkbox_link.stateChanged.connect(self.set_link) | |
| def accept(self): | |
| """Execute when clicking the OK button or Enter key.""" | |
| self.selection = Gui.Selection.getSelection() | |
| (self.r_distance, self.tan_distance) = self.get_distances() | |
| (self.number, self.symmetry) = self.get_number_symmetry() | |
| self.axis = self.get_axis() | |
| self.center = self.get_center() | |
| self.valid_input = self.validate_input( | |
| self.selection, | |
| self.r_distance, | |
| self.tan_distance, | |
| self.number, | |
| self.symmetry, | |
| self.axis, | |
| self.center, | |
| ) | |
| if self.valid_input: | |
| self.create_object() | |
| # The internal function already displays messages | |
| self.finish() | |
| def validate_input(self, selection, r_distance, tan_distance, number, symmetry, axis, center): | |
| """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 number < 2: | |
| _err(translate("draft", "Number of layers must be at least 2")) | |
| return False | |
| # TODO: this should handle multiple objects. | |
| # Each of the elements of the selection should be tested. | |
| obj = selection[0] | |
| if obj.isDerivedFrom("App::FeaturePython"): | |
| _err(translate("draft", "Selection is not suitable for array")) | |
| _err(translate("draft", "Object:") + " {}".format(selection[0].Label)) | |
| return False | |
| if r_distance == 0: | |
| _wrn( | |
| translate("draft", "Radial distance is zero. Resulting array may not look correct.") | |
| ) | |
| elif r_distance < 0: | |
| _wrn(translate("draft", "Radial distance is negative. It is made positive to proceed.")) | |
| self.r_distance = abs(r_distance) | |
| if tan_distance == 0: | |
| _err(translate("draft", "Tangential distance cannot be 0")) | |
| return False | |
| elif tan_distance < 0: | |
| _wrn( | |
| translate( | |
| "draft", "Tangential distance is negative. It is made positive to proceed." | |
| ) | |
| ) | |
| self.tan_distance = abs(tan_distance) | |
| # The other arguments are not tested but they should be present. | |
| if symmetry and axis and center: | |
| 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: | |
| # TODO: this should handle multiple objects. | |
| # For example, it could take the shapes of all objects, | |
| # make a compound and then use it as input for the array function. | |
| sel_obj = self.selection[0] | |
| # This creates the object immediately | |
| # obj = Draft.make_circular_array(sel_obj, | |
| # self.r_distance, self.tan_distance, | |
| # self.number, self.symmetry, | |
| # self.axis, self.center, | |
| # self.use_link) | |
| # Instead, we build the commands to execute through the caller | |
| # of this class, the GuiCommand. | |
| # This is needed to schedule geometry manipulation | |
| # that would crash Coin3D if done in the event callback. | |
| _cmd = "Draft.make_circular_array" | |
| _cmd += "(" | |
| _cmd += "App.ActiveDocument." + sel_obj.Name + ", " | |
| _cmd += "r_distance=" + str(self.r_distance) + ", " | |
| _cmd += "tan_distance=" + str(self.tan_distance) + ", " | |
| _cmd += "number=" + str(self.number) + ", " | |
| _cmd += "symmetry=" + str(self.symmetry) + ", " | |
| _cmd += "axis=" + DraftVecUtils.toString(self.axis) + ", " | |
| _cmd += "center=" + DraftVecUtils.toString(self.center) + ", " | |
| _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()", | |
| ] | |
| # We commit the command list through the parent command | |
| self.source_command.commit(translate("draft", "Create Circular Array"), _cmd_list) | |
| def get_distances(self): | |
| """Get the distance parameters from the widgets.""" | |
| r_d_str = self.form.spinbox_r_distance.text() | |
| tan_d_str = self.form.spinbox_tan_distance.text() | |
| return _quantity(r_d_str), _quantity(tan_d_str) | |
| def get_number_symmetry(self): | |
| """Get the number and symmetry parameters from the widgets.""" | |
| number = self.form.spinbox_number.value() | |
| symmetry = self.form.spinbox_symmetry.value() | |
| return number, symmetry | |
| def get_center(self): | |
| """Get the value of the center from the widgets.""" | |
| c_x_str = self.form.input_c_x.text() | |
| c_y_str = self.form.input_c_y.text() | |
| c_z_str = self.form.input_c_z.text() | |
| center = App.Vector(_quantity(c_x_str), _quantity(c_y_str), _quantity(c_z_str)) | |
| return center | |
| def get_axis(self): | |
| """Get the axis that will be used for the array. NOT IMPLEMENTED. | |
| It should consider a second selection of an edge or wire to use | |
| as an axis. | |
| """ | |
| return self.axis | |
| def reset_point(self): | |
| """Reset the center point to the original distance.""" | |
| self.form.input_c_x.setProperty("rawValue", 0) | |
| self.form.input_c_y.setProperty("rawValue", 0) | |
| self.form.input_c_z.setProperty("rawValue", 0) | |
| self.center = self.get_center() | |
| 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: | |
| # TODO: this should handle multiple objects. | |
| # For example, it could take the shapes of all objects, | |
| # 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", "Radial distance:") + " {}".format(self.r_distance)) | |
| _msg(translate("draft", "Tangential distance:") + " {}".format(self.tan_distance)) | |
| _msg(translate("draft", "Number of concentric circles:") + " {}".format(self.number)) | |
| _msg(translate("draft", "Symmetry parameter:") + " {}".format(self.symmetry)) | |
| _msg( | |
| translate("draft", "Center of rotation:") | |
| + " ({0}, {1}, {2})".format(self.center.x, self.center.y, self.center.z) | |
| ) | |
| self.print_fuse_state(self.fuse) | |
| self.print_link_state(self.use_link) | |
| def display_point(self, point=None, plane=None, mask=None): | |
| """Display the coordinates in the x, y, and z widgets. | |
| This function should be used in a Coin callback so that | |
| the coordinate values are automatically updated when the | |
| mouse pointer moves. | |
| This was copied from `DraftGui.py` but needs to be improved | |
| for this particular command. | |
| point: Base::Vector3 | |
| is a vector that arrives by the callback. | |
| plane: WorkingPlane.PlaneGui | |
| is a working plane instance. Not used at the moment. | |
| mask: str | |
| is a string that specifies which coordinate is being | |
| edited. It is used to restrict edition of a single coordinate. | |
| It is not used at the moment but could be used with a callback. | |
| """ | |
| # Get the coordinates to display | |
| d_p = None | |
| if point: | |
| d_p = point | |
| # Set the widgets to the value of the mouse pointer. | |
| # | |
| # setProperty() is used if the widget is a FreeCAD widget like | |
| # Gui::InputField or Gui::QuantitySpinBox, which are based on | |
| # QLineEdit and QAbstractSpinBox. | |
| # | |
| # setText() is used to set the text inside the widget, this may be | |
| # useful in some circumstances. | |
| # | |
| # The mask allows editing only one field, that is, only one coordinate. | |
| # sbx = self.form.spinbox_c_x | |
| # sby = self.form.spinbox_c_y | |
| # sbz = self.form.spinbox_c_z | |
| if d_p: | |
| if self.mask in ("y", "z"): | |
| # sbx.setText(displayExternal(d_p.x, None, 'Length')) | |
| self.form.input_c_x.setProperty("rawValue", d_p.x) | |
| else: | |
| # sbx.setText(displayExternal(d_p.x, None, 'Length')) | |
| self.form.input_c_x.setProperty("rawValue", d_p.x) | |
| if self.mask in ("x", "z"): | |
| # sby.setText(displayExternal(d_p.y, None, 'Length')) | |
| self.form.input_c_y.setProperty("rawValue", d_p.y) | |
| else: | |
| # sby.setText(displayExternal(d_p.y, None, 'Length')) | |
| self.form.input_c_y.setProperty("rawValue", d_p.y) | |
| if self.mask in ("x", "y"): | |
| # sbz.setText(displayExternal(d_p.z, None, 'Length')) | |
| self.form.input_c_z.setProperty("rawValue", d_p.z) | |
| else: | |
| # sbz.setText(displayExternal(d_p.z, None, 'Length')) | |
| self.form.input_c_z.setProperty("rawValue", d_p.z) | |
| if plane: | |
| pass | |
| # Set masks | |
| if (mask == "x") or (self.mask == "x"): | |
| self.form.input_c_x.setEnabled(True) | |
| self.form.input_c_y.setEnabled(False) | |
| self.form.input_c_z.setEnabled(False) | |
| self.set_focus("x") | |
| elif (mask == "y") or (self.mask == "y"): | |
| self.form.input_c_x.setEnabled(False) | |
| self.form.input_c_y.setEnabled(True) | |
| self.form.input_c_z.setEnabled(False) | |
| self.set_focus("y") | |
| elif (mask == "z") or (self.mask == "z"): | |
| self.form.input_c_x.setEnabled(False) | |
| self.form.input_c_y.setEnabled(False) | |
| self.form.input_c_z.setEnabled(True) | |
| self.set_focus("z") | |
| else: | |
| self.form.input_c_x.setEnabled(True) | |
| self.form.input_c_y.setEnabled(True) | |
| self.form.input_c_z.setEnabled(True) | |
| self.set_focus() | |
| def set_focus(self, key=None): | |
| """Set the focus on the widget that receives the key signal.""" | |
| if key is None or key == "x": | |
| self.form.input_c_x.setFocus() | |
| self.form.input_c_x.selectAll() | |
| elif key == "y": | |
| self.form.input_c_y.setFocus() | |
| self.form.input_c_y.selectAll() | |
| elif key == "z": | |
| self.form.input_c_z.setFocus() | |
| self.form.input_c_z.selectAll() | |
| def reject(self): | |
| """Execute when clicking the Cancel button or pressing Escape.""" | |
| self.finish() | |
| def finish(self): | |
| """Finish the command, after accept or reject. | |
| It finally calls the parent class to execute | |
| the delayed functions, and perform cleanup. | |
| """ | |
| # App.ActiveDocument.commitTransaction() | |
| if Gui.ActiveDocument is not None: | |
| Gui.ActiveDocument.resetEdit() | |
| # Runs the parent command to complete the call | |
| self.source_command.completed() | |
| ## @} | |