class PolarArray(APlm.AttachableFeature): """The Lattice Polar Array of placements""" def derivedInit(self, selfobj): super(PolarArray, self).derivedInit(selfobj) selfobj.addProperty( 'App::PropertyLength', 'Radius', "Polar Array", "Radius of the array (set to zero for just rotation).") selfobj.Radius = 3 selfobj.addProperty( 'App::PropertyEnumeration', 'UseArcRange', "Polar Array", "If attachment mode is concentric, supporting arc's range can be used as array's Span or Step." ) selfobj.UseArcRange = ['ignore', 'as Span', 'as Step'] selfobj.addProperty( 'App::PropertyBool', 'UseArcRadius', "Polar Array", "If True, and attachment mode is concentric, supporting arc's radius is used as array radius." ) selfobj.addProperty( 'App::PropertyEnumeration', 'OrientMode', "Polar Array", "Orientation of placements. Zero - aligns with origin. Static - aligns with self placement." ) selfobj.OrientMode = [ 'Zero', 'Static', 'Radial', 'Vortex', 'Centrifuge', 'Launchpad', 'Dominoes' ] selfobj.OrientMode = 'Radial' selfobj.addProperty('App::PropertyBool', 'Reverse', "Polar Array", "Reverses array direction.") selfobj.addProperty('App::PropertyBool', 'FlipX', "Polar Array", "Reverses x axis of every placement.") selfobj.addProperty('App::PropertyBool', 'FlipZ', "Polar Array", "Reverses z axis of every placement.") self.assureGenerator(selfobj) selfobj.ValuesSource = 'Generator' selfobj.SpanStart = 0 selfobj.SpanEnd = 360 selfobj.EndInclusive = False selfobj.Step = 55 selfobj.Count = 7 def assureGenerator(self, selfobj): '''Adds an instance of value series generator, if one doesn't exist yet.''' if hasattr(self, 'generator'): return self.generator = ValueSeriesGenerator(selfobj) self.generator.addProperties(groupname="Polar Array", groupname_gen="Lattice Series Generator", valuesdoc="List of angles, in degrees.", valuestype='App::PropertyFloat') self.updateReadonlyness(selfobj) def updateReadonlyness(self, selfobj): self.generator.updateReadonlyness() arc = self.fetchArc(selfobj) if self.isOnArc(selfobj) else None selfobj.setEditorMode('Radius', 1 if arc and selfobj.UseArcRadius else 0) self.generator.setPropertyWritable( 'SpanEnd', False if arc and selfobj.UseArcRange == 'as Span' else True) self.generator.setPropertyWritable( 'SpanStart', False if arc and selfobj.UseArcRange == 'as Span' else True) self.generator.setPropertyWritable( 'Step', False if arc and selfobj.UseArcRange == 'as Step' else True) def fetchArc(self, selfobj): """returns None, or tuple (arc_span, arc_radius)""" if selfobj.Support: lnkobj, sub = selfobj.Support[0] sub = sub[0] #resolve the link return fetchArc(lnkobj, sub) def derivedExecute(self, selfobj): self.assureGenerator(selfobj) self.updateReadonlyness(selfobj) selfobj.positionBySupport() # Apply links if (selfobj.UseArcRange != 'ignore' or selfobj.UseArcRadius) and self.isOnArc(selfobj): range, radius = self.fetchArc(selfobj) if selfobj.UseArcRange == 'as Span': selfobj.SpanStart = 0.0 selfobj.SpanEnd = range / turn * 360 elif selfobj.UseArcRange == 'as Step': selfobj.Step = range / turn * 360 if selfobj.UseArcRadius: selfobj.Radius = radius self.generator.execute() # cache properties into variables radius = float(selfobj.Radius) values = [float(strv) for strv in selfobj.Values] irot = selfobj.Placement.inverse().Rotation # compute internam placement, one behind OrientMode property baseplm = App.Placement() is_zero = selfobj.OrientMode == 'Zero' is_static = selfobj.OrientMode == 'Static' if is_zero or is_static: pass elif selfobj.OrientMode == 'Radial': baseplm = App.Placement() elif selfobj.OrientMode == 'Vortex': baseplm = App.Placement(V(), App.Rotation(V(0, 1, 0), V(), V(0, 0, 1))) elif selfobj.OrientMode == 'Centrifuge': baseplm = App.Placement(V(), App.Rotation(V(0, 1, 0), V(), V(-1, 0, 0))) elif selfobj.OrientMode == 'Launchpad': baseplm = App.Placement(V(), App.Rotation(V(0, 0, 1), V(), V(1, 0, 0))) elif selfobj.OrientMode == 'Dominoes': baseplm = App.Placement(V(), App.Rotation(V(0, 0, 1), V(), V(0, -1, 0))) else: raise NotImplementedError() flipX = selfobj.FlipX flipZ = selfobj.FlipZ flipY = flipX ^ flipZ flipplm = App.Placement( V(), App.Rotation(V(-1 if flipX else 1, 0, 0), V(0, -1 if flipY else 1, 0), V(0, 0, -1 if flipZ else 1))) baseplm = baseplm.multiply(flipplm) # Make the array on_arc = self.isOnArc(selfobj) angleplus = -90.0 if on_arc else 0.0 mm = -1.0 if selfobj.Reverse else +1.0 output = [] # list of placements for ang in values: localrot = App.Rotation(App.Vector(0, 0, 1), ang * mm + angleplus) localtransl = localrot.multVec(App.Vector(radius, 0, 0)) localplm = App.Placement(localtransl, localrot) resultplm = localplm.multiply(baseplm) if is_zero: resultplm.Rotation = irot resultplm = resultplm.multiply(flipplm) elif is_static: resultplm.Rotation = App.Rotation() resultplm = resultplm.multiply(flipplm) output.append(resultplm) return output def isOnArc(self, selfobj): return selfobj.MapMode == 'Concentric' and len( linkSubList_convertToOldStyle(selfobj.Support)) == 1 def onChanged(self, selfobj, propname): super(PolarArray, self).onChanged(selfobj, propname) if 'Restore' in selfobj.State: return if propname == 'Reverse' and self.isOnArc(selfobj): if selfobj.Reverse == True and abs(selfobj.MapPathParameter - 0.0) < ParaConfusion: selfobj.MapPathParameter = 1.0 elif selfobj.Reverse == False and abs(selfobj.MapPathParameter - 1.0) < ParaConfusion: selfobj.MapPathParameter = 0.0