Download src/Mod/Draft/draftobjects/array.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 21.5 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftobjects/array.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftobjects/array.py
-
curl -L -o array.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftobjects/array.py
21.5 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> * | |
| # * Copyright (c) 2019, M G Berberich (berberic2, rynn) * | |
| # * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> * | |
| # * * | |
| # * 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. * | |
| # * * | |
| # * This program 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 this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| """Provides the object code for the Array object. | |
| The `Array` class currently handles three types of arrays, | |
| orthogonal, polar, and circular. In the future, probably they should be | |
| split in separate classes so that they are easier to manage. | |
| """ | |
| ## @package array | |
| # \ingroup draftobjects | |
| # \brief Provides the object code for the Array object. | |
| ## \addtogroup draftobjects | |
| # @{ | |
| import math | |
| from PySide.QtCore import QT_TRANSLATE_NOOP | |
| import FreeCAD as App | |
| import DraftVecUtils | |
| from draftutils.messages import _log | |
| from draftobjects.draftlink import DraftLink | |
| class Array(DraftLink): | |
| """The Draft Array object. | |
| To Do | |
| ----- | |
| The `Array` class currently handles three types of arrays, | |
| orthogonal, polar, and circular. In the future, probably they should be | |
| split in separate classes so that they are easier to manage. | |
| """ | |
| def __init__(self, obj): | |
| super(Array, self).__init__(obj, "Array") | |
| def attach(self, obj): | |
| """Set up the properties when the object is attached.""" | |
| self.set_properties(obj) | |
| super(Array, self).attach(obj) | |
| def onDocumentRestored(self, obj): | |
| super(Array, self).onDocumentRestored(obj) | |
| # Count property was added in v0.21 and PlacementList property was added | |
| # for non-link arrays in v1.1, obj should be OK if both are present: | |
| if hasattr(obj, "Count") and hasattr(obj, "PlacementList"): | |
| return | |
| if not hasattr(obj, "Count"): | |
| _log("v0.21, " + obj.Name + ", added property 'Count'") | |
| if not hasattr(obj, "PlacementList"): | |
| _log("v1.1, " + obj.Name + ", added hidden property 'PlacementList'") | |
| self.set_general_properties(obj) | |
| self.execute(obj) # Required to update Count and/or PlacementList. | |
| def set_properties(self, obj): | |
| """Set properties only if they don't exist.""" | |
| self.set_ortho_properties(obj) | |
| self.set_polar_circular_properties(obj) | |
| self.set_polar_properties(obj) | |
| self.set_circular_properties(obj) | |
| # The ArrayType property (general) must be attached | |
| # after the other array properties so that onChanged works properly | |
| self.set_general_properties(obj) | |
| self.set_link_properties(obj) | |
| def set_general_properties(self, obj): | |
| """Set general properties only if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if "Base" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "The base object that will be duplicated") | |
| obj.addProperty("App::PropertyLink", "Base", "Objects", _tip, locked=True) | |
| obj.Base = None | |
| if "ArrayType" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The type of array to create.\n" | |
| "- Ortho: places the copies " | |
| "in the direction of the global X, " | |
| "Y, Z axes.\n" | |
| "- Polar: places the copies along " | |
| "a circular arc, up to a specified " | |
| "angle, and with certain orientation " | |
| "defined by a center and an axis.\n" | |
| "- Circular: places the copies " | |
| "in concentric circles " | |
| "around the base object.", | |
| ) | |
| obj.addProperty("App::PropertyEnumeration", "ArrayType", "Objects", _tip, locked=True) | |
| obj.ArrayType = ["ortho", "polar", "circular"] | |
| if "Fuse" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Specifies if the copies " | |
| "should be fused together " | |
| "if they touch each other (slower)", | |
| ) | |
| obj.addProperty("App::PropertyBool", "Fuse", "Objects", _tip, locked=True) | |
| obj.Fuse = False | |
| if "Count" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Total number of elements " | |
| "in the array.\n" | |
| "This property is read-only, " | |
| "as the number depends " | |
| "on the parameters of the array.", | |
| ) | |
| obj.addProperty("App::PropertyInteger", "Count", "Objects", _tip, locked=True) | |
| obj.Count = 0 | |
| obj.setEditorMode("Count", 1) # Read only | |
| if not self.use_link: | |
| if "PlacementList" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "The placement for each array element") | |
| obj.addProperty( | |
| "App::PropertyPlacementList", "PlacementList", "Objects", _tip, locked=True | |
| ) | |
| obj.PlacementList = [] | |
| def set_ortho_properties(self, obj): | |
| """Set orthogonal properties only if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if "NumberX" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Number of copies in X-direction") | |
| obj.addProperty( | |
| "App::PropertyInteger", "NumberX", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.NumberX = 2 | |
| if "NumberY" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Number of copies in Y-direction") | |
| obj.addProperty( | |
| "App::PropertyInteger", "NumberY", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.NumberY = 2 | |
| if "NumberZ" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Number of copies in Z-direction") | |
| obj.addProperty( | |
| "App::PropertyInteger", "NumberZ", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.NumberZ = 1 | |
| if "IntervalX" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Distance and orientation " "of intervals in X-direction" | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVectorDistance", "IntervalX", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.IntervalX = App.Vector(50, 0, 0) | |
| if "IntervalY" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Distance and orientation " "of intervals in Y-direction" | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVectorDistance", "IntervalY", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.IntervalY = App.Vector(0, 50, 0) | |
| if "IntervalZ" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Distance and orientation " "of intervals in Z-direction" | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVectorDistance", "IntervalZ", "Orthogonal array", _tip, locked=True | |
| ) | |
| obj.IntervalZ = App.Vector(0, 0, 50) | |
| def set_polar_circular_properties(self, obj): | |
| """Set general polar and circular properties if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if "Axis" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The axis direction around which " | |
| "the elements in a polar or " | |
| "a circular array will be created", | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVector", "Axis", "Polar/circular array", _tip, locked=True | |
| ) | |
| obj.Axis = App.Vector(0, 0, 1) | |
| if "Center" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Center point for polar and " | |
| "circular arrays.\n" | |
| "The 'Axis' passes through this point.", | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVectorDistance", "Center", "Polar/circular array", _tip, locked=True | |
| ) | |
| obj.Center = App.Vector(0, 0, 0) | |
| # The AxisReference property must be attached after Axis and Center | |
| # so that onChanged works properly | |
| if "AxisReference" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "The axis object that overrides " | |
| "the value of 'Axis' and 'Center', " | |
| "for example, a datum line.\n" | |
| "Its placement, position and rotation, " | |
| "will be used when creating polar " | |
| "and circular arrays.\n" | |
| "Leave this property empty " | |
| "to be able to set 'Axis' and 'Center' " | |
| "manually.", | |
| ) | |
| obj.addProperty( | |
| "App::PropertyLinkGlobal", "AxisReference", "Objects", _tip, locked=True | |
| ) | |
| obj.AxisReference = None | |
| def set_polar_properties(self, obj): | |
| """Set polar properties only if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if "NumberPolar" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Number of copies in the polar direction") | |
| obj.addProperty("App::PropertyInteger", "NumberPolar", "Polar array", _tip, locked=True) | |
| obj.NumberPolar = 5 | |
| if "IntervalAxis" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Distance and orientation " "of intervals in 'Axis' direction" | |
| ) | |
| obj.addProperty( | |
| "App::PropertyVectorDistance", "IntervalAxis", "Polar array", _tip, locked=True | |
| ) | |
| obj.IntervalAxis = App.Vector(0, 0, 0) | |
| if "Angle" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Angle to cover with copies") | |
| obj.addProperty("App::PropertyAngle", "Angle", "Polar array", _tip, locked=True) | |
| obj.Angle = 360 | |
| def set_circular_properties(self, obj): | |
| """Set circular properties only if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if "RadialDistance" not in properties: | |
| _tip = QT_TRANSLATE_NOOP("App::Property", "Distance between concentric circles") | |
| obj.addProperty( | |
| "App::PropertyDistance", "RadialDistance", "Circular array", _tip, locked=True | |
| ) | |
| obj.RadialDistance = 50 | |
| if "TangentialDistance" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Distance between copies " "in the same circle" | |
| ) | |
| obj.addProperty( | |
| "App::PropertyDistance", "TangentialDistance", "Circular array", _tip, locked=True | |
| ) | |
| obj.TangentialDistance = 25 | |
| if "NumberCircles" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "Number of concentric circle. " "The 'Base' object counts as one circle.", | |
| ) | |
| obj.addProperty( | |
| "App::PropertyInteger", "NumberCircles", "Circular array", _tip, locked=True | |
| ) | |
| obj.NumberCircles = 3 | |
| if "Symmetry" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", | |
| "A parameter that determines " | |
| "how many symmetry planes " | |
| "the circular array will have", | |
| ) | |
| obj.addProperty("App::PropertyInteger", "Symmetry", "Circular array", _tip, locked=True) | |
| obj.Symmetry = 1 | |
| def set_link_properties(self, obj): | |
| """Set link properties only if they don't exist.""" | |
| properties = obj.PropertiesList | |
| if self.use_link: | |
| if "ExpandArray" not in properties: | |
| _tip = QT_TRANSLATE_NOOP( | |
| "App::Property", "Show the individual array elements " "(only for Link arrays)" | |
| ) | |
| obj.addProperty("App::PropertyBool", "ExpandArray", "Objects", _tip, locked=True) | |
| obj.ExpandArray = False | |
| def linkSetup(self, obj): | |
| """Set up the object as a link object.""" | |
| super(Array, self).linkSetup(obj) | |
| obj.configLinkProperty(ElementCount="Count") | |
| obj.setPropertyStatus("Count", "Hidden") | |
| def onChanged(self, obj, prop): | |
| """Execute when a property is changed.""" | |
| super(Array, self).onChanged(obj, prop) | |
| # print(prop, ": ", getattr(obj, prop)) | |
| self.show_and_hide(obj, prop) | |
| def show_and_hide(self, obj, prop): | |
| """Show and hide the properties depending on the touched property.""" | |
| if prop == "AxisReference": | |
| if obj.AxisReference: | |
| obj.setEditorMode("Center", 1) | |
| obj.setEditorMode("Axis", 1) | |
| else: | |
| obj.setEditorMode("Center", 0) | |
| obj.setEditorMode("Axis", 0) | |
| if prop == "ArrayType": | |
| if obj.ArrayType == "ortho": | |
| for pr in ("NumberX", "NumberY", "NumberZ", "IntervalX", "IntervalY", "IntervalZ"): | |
| obj.setPropertyStatus(pr, "-Hidden") | |
| for pr in ( | |
| "Axis", | |
| "Center", | |
| "NumberPolar", | |
| "Angle", | |
| "IntervalAxis", | |
| "NumberCircles", | |
| "RadialDistance", | |
| "TangentialDistance", | |
| "Symmetry", | |
| ): | |
| obj.setPropertyStatus(pr, "Hidden") | |
| if obj.ArrayType == "polar": | |
| for pr in ("Axis", "Center", "NumberPolar", "Angle", "IntervalAxis"): | |
| obj.setPropertyStatus(pr, "-Hidden") | |
| for pr in ( | |
| "NumberX", | |
| "NumberY", | |
| "NumberZ", | |
| "IntervalX", | |
| "IntervalY", | |
| "IntervalZ", | |
| "NumberCircles", | |
| "RadialDistance", | |
| "TangentialDistance", | |
| "Symmetry", | |
| ): | |
| obj.setPropertyStatus(pr, "Hidden") | |
| if obj.ArrayType == "circular": | |
| for pr in ( | |
| "Axis", | |
| "Center", | |
| "NumberCircles", | |
| "RadialDistance", | |
| "TangentialDistance", | |
| "Symmetry", | |
| ): | |
| obj.setPropertyStatus(pr, "-Hidden") | |
| for pr in ( | |
| "NumberX", | |
| "NumberY", | |
| "NumberZ", | |
| "IntervalX", | |
| "IntervalY", | |
| "IntervalZ", | |
| "NumberPolar", | |
| "Angle", | |
| "IntervalAxis", | |
| ): | |
| obj.setPropertyStatus(pr, "Hidden") | |
| def execute(self, obj): | |
| """Execute when the object is created or recomputed.""" | |
| if self.props_changed_placement_only(obj) or not obj.Base: | |
| self.props_changed_clear() | |
| return | |
| pl = obj.Placement | |
| axis = obj.Axis | |
| center = obj.Center | |
| if hasattr(obj, "AxisReference") and obj.AxisReference: | |
| if hasattr(obj.AxisReference, "Placement"): | |
| reference = obj.AxisReference.Placement | |
| axis = reference.Rotation * App.Vector(0, 0, 1) | |
| center = reference.Base | |
| else: | |
| _info = ( | |
| "'AxisReference' has no 'Placement' property. " | |
| "Please select a different object to use as " | |
| "reference." | |
| ) | |
| raise TypeError(_info) | |
| if obj.ArrayType == "ortho": | |
| pls = rect_placements( | |
| obj.Base.Placement, | |
| obj.IntervalX, | |
| obj.IntervalY, | |
| obj.IntervalZ, | |
| obj.NumberX, | |
| obj.NumberY, | |
| obj.NumberZ, | |
| ) | |
| elif obj.ArrayType == "polar": | |
| av = obj.IntervalAxis if hasattr(obj, "IntervalAxis") else None | |
| pls = polar_placements( | |
| obj.Base.Placement, center, obj.Angle.Value, obj.NumberPolar, axis, av | |
| ) | |
| elif obj.ArrayType == "circular": | |
| pls = circ_placements( | |
| obj.Base.Placement, | |
| obj.RadialDistance, | |
| obj.TangentialDistance, | |
| axis, | |
| center, | |
| obj.NumberCircles, | |
| obj.Symmetry, | |
| ) | |
| self.buildShape(obj, pl, pls) | |
| self.props_changed_clear() | |
| return not self.use_link | |
| # Alias for compatibility with v0.18 and earlier | |
| _Array = Array | |
| def rect_placements(base_placement, xvector, yvector, zvector, xnum, ynum, znum): | |
| """Determine the placements where the rectangular copies will be.""" | |
| pl = base_placement | |
| placements = [pl.copy()] | |
| for xcount in range(xnum): | |
| currentxvector = App.Vector(xvector).multiply(xcount) | |
| if xcount != 0: | |
| npl = pl.copy() | |
| npl.translate(currentxvector) | |
| placements.append(npl) | |
| for ycount in range(ynum): | |
| currentyvector = App.Vector(currentxvector) | |
| _y_shift = App.Vector(yvector).multiply(ycount) | |
| currentyvector = currentyvector.add(_y_shift) | |
| if ycount != 0: | |
| npl = pl.copy() | |
| npl.translate(currentyvector) | |
| placements.append(npl) | |
| for zcount in range(znum): | |
| currentzvector = App.Vector(currentyvector) | |
| _z_shift = App.Vector(zvector).multiply(zcount) | |
| currentzvector = currentzvector.add(_z_shift) | |
| if zcount != 0: | |
| npl = pl.copy() | |
| npl.translate(currentzvector) | |
| placements.append(npl) | |
| return placements | |
| def polar_placements(base_placement, center, angle, number, axis, axisvector): | |
| """Determine the placements where the polar copies will be.""" | |
| # print("angle ",angle," num ",num) | |
| placements = [base_placement.copy()] | |
| if number <= 1: | |
| return placements | |
| if angle == 360: | |
| fraction = float(angle) / number | |
| else: | |
| fraction = float(angle) / (number - 1) | |
| for i in range(number - 1): | |
| currangle = fraction + (i * fraction) | |
| npl = base_placement.copy() | |
| npl.rotate(center, axis, currangle, comp=True) | |
| if axisvector: | |
| if not DraftVecUtils.isNull(axisvector): | |
| npl.translate(App.Vector(axisvector).multiply(i + 1)) | |
| placements.append(npl) | |
| return placements | |
| def circ_placements( | |
| base_placement, r_distance, tan_distance, axis, center, circle_number, symmetry | |
| ): | |
| """Determine the placements where the circular copies will be.""" | |
| symmetry = max(1, symmetry) | |
| lead = (0, 1, 0) | |
| if axis.x == 0 and axis.z == 0: | |
| lead = (1, 0, 0) | |
| direction = axis.cross(App.Vector(lead)).normalize() | |
| placements = [base_placement.copy()] | |
| for xcount in range(1, circle_number): | |
| rc = xcount * r_distance | |
| trans = App.Vector(direction).multiply(rc) | |
| c = 2 * rc * math.pi | |
| n = math.floor(c / tan_distance) | |
| n = int(math.floor(n / symmetry) * symmetry) | |
| if n == 0: | |
| continue | |
| angle = 360.0 / n | |
| for ycount in range(0, n): | |
| npl = base_placement.copy() | |
| npl.translate(trans) | |
| npl.rotate(center, axis, ycount * angle, comp=True) | |
| placements.append(npl) | |
| return placements | |
| ## @} | |