| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """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. |
| | """ |
| | |
| | |
| | |
| |
|
| | |
| | |
| | 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) |
| | |
| | |
| | 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) |
| |
|
| | 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) |
| |
|
| | |
| | |
| | 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) |
| |
|
| | 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) |
| |
|
| | |
| | |
| | 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) |
| | |
| | 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 |
| |
|
| |
|
| | |
| | _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.""" |
| | |
| | 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 |
| |
|
| |
|
| | |
| |
|