diff --git a/src/Mod/Draft/draftguitools/gui_orthoarray.py b/src/Mod/Draft/draftguitools/gui_orthoarray.py index e7789838f1..e6ae8d824f 100644 --- a/src/Mod/Draft/draftguitools/gui_orthoarray.py +++ b/src/Mod/Draft/draftguitools/gui_orthoarray.py @@ -32,11 +32,12 @@ import FreeCAD as App import FreeCADGui as Gui import Draft import Draft_rc # include resources, icons, ui files +import draftutils.todo as todo + from draftutils.messages import _msg, _log from draftutils.translate import _tr from draftguitools import gui_base from drafttaskpanels import task_orthoarray -import draftutils.todo as todo # The module is used to prevent complaints from code checkers (flake8) bool(Draft_rc.__name__) @@ -58,11 +59,13 @@ class OrthoArray(gui_base.GuiCommandBase): def GetResources(self): """Set icon, menu and tooltip.""" - _tip = ("Creates copies of a selected object, " - "and places the copies in an orthogonal pattern.\n" - "The properties of the array can be further modified after " - "the new object is created, including turning it into " - "a different type of array.") + _tip = ("Creates copies of the selected object, " + "and places the copies in an orthogonal pattern,\n" + "meaning the copies follow the specified direction " + "in the X, Y, Z axes.\n" + "\n" + "The array can be turned into a polar or a circular array " + "by changing its type.") d = {'Pixmap': 'Draft_Array', 'MenuText': QT_TRANSLATE_NOOP("Draft", "Array"), diff --git a/src/Mod/Draft/draftmake/make_orthoarray.py b/src/Mod/Draft/draftmake/make_orthoarray.py index 987cc852da..7715b6f643 100644 --- a/src/Mod/Draft/draftmake/make_orthoarray.py +++ b/src/Mod/Draft/draftmake/make_orthoarray.py @@ -26,14 +26,155 @@ # \brief Provides functions for creating orthogonal arrays in 2D and 3D. import FreeCAD as App -import Draft -# import draftmake.make_array as make_array + +import draftmake.make_array as make_array import draftutils.utils as utils + from draftutils.messages import _msg, _wrn, _err from draftutils.translate import _tr -def make_ortho_array(obj, +def _make_ortho_array(base_object, + v_x=App.Vector(10, 0, 0), + v_y=App.Vector(0, 10, 0), + v_z=App.Vector(0, 0, 10), + n_x=2, + n_y=2, + n_z=1, + use_link=True): + """Create an orthogonal array from the given object. + + This is a simple wrapper of the `draftmake.make_array.make_array` + function to be used by the different orthogonal arrays. + + - `make_ortho_array` + - `make_ortho_array2d`, no Z direction + - `make_rect_array`, strictly rectangular + - `make_rect_array2d`, strictly rectangular, no Z direction + + This function has no error checking, nor does it display messages. + This should be handled by the subfunctions that use this one. + """ + _name = "_make_ortho_array" + utils.print_header(_name, _tr("Internal orthogonal array"), debug=False) + + new_obj = make_array.make_array(base_object, + arg1=v_x, arg2=v_y, arg3=v_z, + arg4=n_x, arg5=n_y, arg6=n_z, + use_link=use_link) + return new_obj + + +def _are_vectors(v_x, v_y, v_z=None, name="Unknown"): + """Check that the vectors are numbers.""" + _msg("v_x: {}".format(v_x)) + _msg("v_y: {}".format(v_y)) + if v_z: + _msg("v_z: {}".format(v_z)) + + try: + if v_z: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector)), + (v_z, (int, float, App.Vector))], + name=name) + else: + utils.type_check([(v_x, (int, float, App.Vector)), + (v_y, (int, float, App.Vector))], + name=name) + except TypeError: + _err(_tr("Wrong input: must be a number or vector.")) + return False, v_x, v_y, v_z + + _text = "Input: single value expanded to vector." + if not isinstance(v_x, App.Vector): + v_x = App.Vector(v_x, 0, 0) + _wrn(_tr(_text)) + if not isinstance(v_y, App.Vector): + v_y = App.Vector(0, v_y, 0) + _wrn(_tr(_text)) + if v_z and not isinstance(v_z, App.Vector): + v_z = App.Vector(0, 0, v_z) + _wrn(_tr(_text)) + + return True, v_x, v_y, v_z + + +def _are_integers(n_x, n_y, n_z=None, name="Unknown"): + """Check that the numbers are integers, with minimum value of 1.""" + _msg("n_x: {}".format(n_x)) + _msg("n_y: {}".format(n_y)) + if n_z: + _msg("n_z: {}".format(n_z)) + + try: + if n_z: + utils.type_check([(n_x, int), + (n_y, int), + (n_z, int)], name=name) + else: + utils.type_check([(n_x, int), + (n_y, int)], name=name) + except TypeError: + _err(_tr("Wrong input: must be an integer number.")) + return False, n_x, n_y, n_z + + _text = ("Input: number of elements must be at least 1. " + "It is set to 1.") + if n_x < 1: + _wrn(_tr(_text)) + n_x = 1 + if n_y < 1: + _wrn(_tr(_text)) + n_y = 1 + if n_z and n_z < 1: + _wrn(_tr(_text)) + n_z = 1 + + return True, n_x, n_y, n_z + + +def _are_numbers(d_x, d_y, d_z=None, name="Unknown"): + """Check that the numbers are numbers.""" + _msg("d_x: {}".format(d_x)) + _msg("d_y: {}".format(d_y)) + if d_z: + _msg("d_z: {}".format(d_z)) + + try: + if d_z: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float)), + (d_z, (int, float))], name=name) + else: + utils.type_check([(d_x, (int, float)), + (d_y, (int, float))], name=name) + except TypeError: + _err(_tr("Wrong input: must be a number.")) + return False, d_x, d_y, d_z + + return True, d_x, d_y, d_z + + +def _find_object_in_doc(base_object, doc=None): + """Check that a document is available and the object exists.""" + FOUND = True + if isinstance(base_object, str): + base_object_str = base_object + + found, base_object = utils.find_object(base_object, + doc=doc) + if not found: + _msg("base_object: {}".format(base_object_str)) + _err(_tr("Wrong input: object not in document.")) + return not FOUND, base_object + + _msg("base_object: {}".format(base_object.Label)) + + return FOUND, base_object + + +def make_ortho_array(base_object, v_x=App.Vector(10, 0, 0), v_y=App.Vector(0, 10, 0), v_z=App.Vector(0, 0, 10), @@ -45,11 +186,12 @@ def make_ortho_array(obj, Parameters ---------- - obj: Part::Feature - Any type of object that has a `Part::TopoShape` - that can be duplicated. - This means most 2D and 3D objects produced - with any workbench. + base_object: Part::Feature or str + Any of object that has a `Part::TopoShape` that can be duplicated. + This means most 2D and 3D objects produced with any workbench. + If it is a string, it must be the `Label` of that object. + Since a label is not guaranteed to be unique in a document, + it will use the first object found with this label. v_x, v_y, v_z: Base::Vector3, optional The vector indicating the vector displacement between two elements @@ -99,13 +241,13 @@ def make_ortho_array(obj, The values of `n_x` and `n_y` default to 2, while `n_z` defaults to 1. - This means the array by default is a planar array. + This means the array is a planar array by default. use_link: bool, optional It defaults to `True`. If it is `True` the produced copies are not `Part::TopoShape` copies, but rather `App::Link` objects. - The Links repeat the shape of the original `obj` exactly, + The Links repeat the shape of the original `base_object` exactly, and therefore the resulting array is more memory efficient. Also, when `use_link` is `True`, the `Fuse` property @@ -120,75 +262,44 @@ def make_ortho_array(obj, Returns ------- Part::FeaturePython - A scripted object with `Proxy.Type='Array'`. + A scripted object of type `'Array'`. Its `Shape` is a compound of the copies of the original object. + None + If there is a problem it will return `None`. + See Also -------- - make_ortho_array2d, make_rect_array, make_rect_array2d + make_ortho_array2d, make_rect_array, make_rect_array2d, make_polar_array, + make_circular_array, make_path_array, make_point_array """ _name = "make_ortho_array" utils.print_header(_name, _tr("Orthogonal array")) - _msg("v_x: {}".format(v_x)) - _msg("v_y: {}".format(v_y)) - _msg("v_z: {}".format(v_z)) - - try: - utils.type_check([(v_x, (int, float, App.Vector)), - (v_y, (int, float, App.Vector)), - (v_z, (int, float, App.Vector))], - name=_name) - except TypeError: - _err(_tr("Wrong input: must be a number or vector.")) + found, base_object = _find_object_in_doc(base_object, + doc=App.activeDocument()) + if not found: return None - _text = "Input: single value expanded to vector." - if not isinstance(v_x, App.Vector): - v_x = App.Vector(v_x, 0, 0) - _wrn(_tr(_text)) - if not isinstance(v_y, App.Vector): - v_y = App.Vector(0, v_y, 0) - _wrn(_tr(_text)) - if not isinstance(v_z, App.Vector): - v_z = App.Vector(0, 0, v_z) - _wrn(_tr(_text)) - - _msg("n_x: {}".format(n_x)) - _msg("n_y: {}".format(n_y)) - _msg("n_z: {}".format(n_z)) - - try: - utils.type_check([(n_x, int), - (n_y, int), - (n_z, int)], name=_name) - except TypeError: - _err(_tr("Wrong input: must be an integer number.")) + ok, v_x, v_y, v_z = _are_vectors(v_x, v_y, v_z, name=_name) + if not ok: return None - _text = ("Input: number of elements must be at least 1. " - "It is set to 1.") - if n_x < 1: - _wrn(_tr(_text)) - n_x = 1 - if n_y < 1: - _wrn(_tr(_text)) - n_y = 1 - if n_z < 1: - _wrn(_tr(_text)) - n_z = 1 + ok, n_x, n_y, n_z = _are_integers(n_x, n_y, n_z, name=_name) + if not ok: + return None - _msg("use_link: {}".format(bool(use_link))) + use_link = bool(use_link) + _msg("use_link: {}".format(use_link)) - # new_obj = make_array.make_array() - new_obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, arg3=v_z, - arg4=n_x, arg5=n_y, arg6=n_z, - use_link=use_link) + new_obj = _make_ortho_array(base_object, + v_x=v_x, v_y=v_y, v_z=v_z, + n_x=n_x, n_y=n_y, n_z=n_z, + use_link=use_link) return new_obj -def make_ortho_array2d(obj, +def make_ortho_array2d(base_object, v_x=App.Vector(10, 0, 0), v_y=App.Vector(0, 10, 0), n_x=2, @@ -202,11 +313,12 @@ def make_ortho_array2d(obj, Parameters ---------- - obj: Part::Feature - Any type of object that has a `Part::TopoShape` - that can be duplicated. - This means most 2D and 3D objects produced - with any workbench. + base_object: Part::Feature or str + Any of object that has a `Part::TopoShape` that can be duplicated. + This means most 2D and 3D objects produced with any workbench. + If it is a string, it must be the `Label` of that object. + Since a label is not guaranteed to be unique in a document, + it will use the first object found with this label. v_x, v_y: Base::Vector3, optional Vectorial displacement of elements @@ -225,65 +337,44 @@ def make_ortho_array2d(obj, Returns ------- Part::FeaturePython - A scripted object with `Proxy.Type='Array'`. + A scripted object of type `'Array'`. Its `Shape` is a compound of the copies of the original object. + None + If there is a problem it will return `None`. + See Also -------- - make_ortho_array, make_rect_array, make_rect_array2d + make_ortho_array, make_rect_array, make_rect_array2d, make_polar_array, + make_circular_array, make_path_array, make_point_array """ _name = "make_ortho_array2d" utils.print_header(_name, _tr("Orthogonal array 2D")) - _msg("v_x: {}".format(v_x)) - _msg("v_y: {}".format(v_y)) - - try: - utils.type_check([(v_x, (int, float, App.Vector)), - (v_y, (int, float, App.Vector))], - name=_name) - except TypeError: - _err(_tr("Wrong input: must be a number or vector.")) + found, base_object = _find_object_in_doc(base_object, + doc=App.activeDocument()) + if not found: return None - _text = "Input: single value expanded to vector." - if not isinstance(v_x, App.Vector): - v_x = App.Vector(v_x, 0, 0) - _wrn(_tr(_text)) - if not isinstance(v_y, App.Vector): - v_y = App.Vector(0, v_y, 0) - _wrn(_tr(_text)) - - _msg("n_x: {}".format(n_x)) - _msg("n_y: {}".format(n_y)) - - try: - utils.type_check([(n_x, int), - (n_y, int)], name=_name) - except TypeError: - _err(_tr("Wrong input: must be an integer number.")) + ok, v_x, v_y, __ = _are_vectors(v_x, v_y, v_z=None, name=_name) + if not ok: return None - _text = ("Input: number of elements must be at least 1. " - "It is set to 1.") - if n_x < 1: - _wrn(_tr(_text)) - n_x = 1 - if n_y < 1: - _wrn(_tr(_text)) - n_y = 1 + ok, n_x, n_y, __ = _are_integers(n_x, n_y, n_z=None, name=_name) + if not ok: + return None - _msg("use_link: {}".format(bool(use_link))) + use_link = bool(use_link) + _msg("use_link: {}".format(use_link)) - # new_obj = make_array.make_array() - new_obj = Draft.makeArray(obj, - arg1=v_x, arg2=v_y, - arg3=n_x, arg4=n_y, - use_link=use_link) + new_obj = _make_ortho_array(base_object, + v_x=v_x, v_y=v_y, + n_x=n_x, n_y=n_y, + use_link=use_link) return new_obj -def make_rect_array(obj, +def make_rect_array(base_object, d_x=10, d_y=10, d_z=10, @@ -300,11 +391,12 @@ def make_rect_array(obj, Parameters ---------- - obj: Part::Feature - Any type of object that has a `Part::TopoShape` - that can be duplicated. - This means most 2D and 3D objects produced - with any workbench. + base_object: Part::Feature or str + Any of object that has a `Part::TopoShape` that can be duplicated. + This means most 2D and 3D objects produced with any workbench. + If it is a string, it must be the `Label` of that object. + Since a label is not guaranteed to be unique in a document, + it will use the first object found with this label. d_x, d_y, d_z: Base::Vector3, optional Displacement of elements in the corresponding X, Y, and Z directions. @@ -319,41 +411,48 @@ def make_rect_array(obj, Returns ------- Part::FeaturePython - A scripted object with `Proxy.Type='Array'`. + A scripted object of type `'Array'`. Its `Shape` is a compound of the copies of the original object. + None + If there is a problem it will return `None`. + See Also -------- - make_ortho_array, make_ortho_array2d, make_rect_array2d + make_ortho_array, make_ortho_array2d, make_rect_array2d, make_polar_array, + make_circular_array, make_path_array, make_point_array """ _name = "make_rect_array" utils.print_header(_name, _tr("Rectangular array")) - _msg("d_x: {}".format(d_x)) - _msg("d_y: {}".format(d_y)) - _msg("d_z: {}".format(d_z)) - - try: - utils.type_check([(d_x, (int, float)), - (d_y, (int, float)), - (d_z, (int, float))], - name=_name) - except TypeError: - _err(_tr("Wrong input: must be a number.")) + found, base_object = _find_object_in_doc(base_object, + doc=App.activeDocument()) + if not found: return None - new_obj = make_ortho_array(obj, - v_x=App.Vector(d_x, 0, 0), - v_y=App.Vector(0, d_y, 0), - v_z=App.Vector(0, 0, d_z), - n_x=n_x, - n_y=n_y, - n_z=n_z, - use_link=use_link) + ok, d_x, d_y, d_z = _are_numbers(d_x, d_y, d_z, name=_name) + if not ok: + return None + + ok, n_x, n_y, n_z = _are_integers(n_x, n_y, n_z, _name) + if not ok: + return None + + use_link = bool(use_link) + _msg("use_link: {}".format(use_link)) + + new_obj = _make_ortho_array(base_object, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + v_z=App.Vector(0, 0, d_z), + n_x=n_x, + n_y=n_y, + n_z=n_z, + use_link=use_link) return new_obj -def make_rect_array2d(obj, +def make_rect_array2d(base_object, d_x=10, d_y=10, n_x=2, @@ -361,18 +460,20 @@ def make_rect_array2d(obj, use_link=True): """Create a 2D rectangular array from the given object. - This function wraps around `make_ortho_array2d` + This function wraps around `make_ortho_array`, to produce strictly rectangular arrays, in which the displacement vectors `v_x` and `v_y` only have their respective components in X and Y. + The Z component is ignored. Parameters ---------- - obj: Part::Feature - Any type of object that has a `Part::TopoShape` - that can be duplicated. - This means most 2D and 3D objects produced - with any workbench. + base_object: Part::Feature or str + Any of object that has a `Part::TopoShape` that can be duplicated. + This means most 2D and 3D objects produced with any workbench. + If it is a string, it must be the `Label` of that object. + Since a label is not guaranteed to be unique in a document, + it will use the first object found with this label. d_x, d_y: Base::Vector3, optional Displacement of elements in the corresponding X and Y directions. @@ -387,31 +488,40 @@ def make_rect_array2d(obj, Returns ------- Part::FeaturePython - A scripted object with `Proxy.Type='Array'`. + A scripted object of type `'Array'`. Its `Shape` is a compound of the copies of the original object. + None + If there is a problem it will return `None`. + See Also -------- - make_ortho_array, make_ortho_array2d, make_rect_array + make_ortho_array, make_ortho_array2d, make_rect_array, make_polar_array, + make_circular_array, make_path_array, make_point_array """ _name = "make_rect_array2d" utils.print_header(_name, _tr("Rectangular array 2D")) - _msg("d_x: {}".format(d_x)) - _msg("d_y: {}".format(d_y)) - - try: - utils.type_check([(d_x, (int, float)), - (d_y, (int, float))], - name=_name) - except TypeError: - _err(_tr("Wrong input: must be a number.")) + found, base_object = _find_object_in_doc(base_object, + doc=App.activeDocument()) + if not found: return None - new_obj = make_ortho_array2d(obj, - v_x=App.Vector(d_x, 0, 0), - v_y=App.Vector(0, d_y, 0), - n_x=n_x, - n_y=n_y, - use_link=use_link) + ok, d_x, d_y, __ = _are_numbers(d_x, d_y, d_z=None, name=_name) + if not ok: + return None + + ok, n_x, n_y, __ = _are_integers(n_x, n_y, n_z=None, name=_name) + if not ok: + return None + + use_link = bool(use_link) + _msg("use_link: {}".format(use_link)) + + new_obj = _make_ortho_array(base_object, + v_x=App.Vector(d_x, 0, 0), + v_y=App.Vector(0, d_y, 0), + n_x=n_x, + n_y=n_y, + use_link=use_link) return new_obj diff --git a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py index 9f8c65df5c..ab55c24720 100644 --- a/src/Mod/Draft/drafttaskpanels/task_orthoarray.py +++ b/src/Mod/Draft/drafttaskpanels/task_orthoarray.py @@ -33,9 +33,10 @@ import FreeCADGui as Gui import Draft_rc # include resources, icons, ui files import DraftVecUtils import draftutils.utils as utils + +from FreeCAD import Units as U from draftutils.messages import _msg, _err, _log from draftutils.translate import _tr -from FreeCAD import Units as U # The module is used to prevent complaints from code checkers (flake8) bool(Draft_rc.__name__) @@ -186,7 +187,8 @@ class TaskPanelOrthoArray: self.n_x, self.n_y, self.n_z) if self.valid_input: self.create_object() - self.print_messages() + # The internal function already displays messages + # self.print_messages() self.finish() def validate_input(self, selection, @@ -237,14 +239,12 @@ class TaskPanelOrthoArray: sel_obj = self.selection[0] # This creates the object immediately - # obj = Draft.makeArray(sel_obj, - # self.v_x, self.v_y, self.v_z, - # self.n_x, self.n_y, self.n_z, - # self.use_link) - # if obj: - # obj.Fuse = self.fuse + # obj = Draft.make_ortho_array(sel_obj, + # self.v_x, self.v_y, self.v_z, + # self.n_x, self.n_y, self.n_z, + # self.use_link) - # Instead, we build the commands to execute through the parent + # 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.