Exemplo n.º 1
0
 def assureGenerator(self, obj):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self,"generator"):
         return
     self.generator = ValueSeriesGenerator(obj)
     self.generator.addProperties(groupname= "Lattice ParaSeries", 
                                  groupname_gen= "Lattice ParaSeries Generator", 
                                  valuesdoc= "List of parameter values to compute object for.")
     self.generator.updateReadonlyness()
Exemplo n.º 2
0
 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)
Exemplo n.º 3
0
 def assureGenerator(self, host):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self, "generator"):
         return
     self.generator = ValueSeriesGenerator(host)
     self.generator.addProperties(
         groupname="Lattice Array",
         groupname_gen="Lattice Series Generator",
         valuesdoc=
         "List of parameter values. Values should be in range 0..1 for interpolation, and can be outside for extrapolation.",
         valuestype="App::PropertyFloat")
Exemplo n.º 4
0
 def assureGenerator(self, obj):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self, "generator"):
         return
     self.generator = ValueSeriesGenerator(obj)
     self.generator.addProperties(
         groupname="Lattice Array",
         groupname_gen="Lattice Series Generator",
         valuesdoc=
         "List of distances. Distance is measured from Point, along Dir, in millimeters.",
         valuestype="App::PropertyDistance")
     self.updateReadonlyness(obj)
Exemplo n.º 5
0
 def assureGenerator(self, obj):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self,"generator"):
         return
     self.generator = ValueSeriesGenerator(obj)
     self.generator.addProperties(groupname= "Lattice ParaSeries", 
                                  groupname_gen= "Lattice ParaSeries Generator", 
                                  valuesdoc= "List of parameter values to compute object for.")
     self.generator.updateReadonlyness()
Exemplo n.º 6
0
 def assureGenerator(self, obj):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self,"generator"):
         return
     self.generator = ValueSeriesGenerator(obj)
     self.generator.addProperties(groupname= "Lattice Array", 
                                  groupname_gen= "Lattice Series Generator", 
                                  valuesdoc= "List of angles, in degrees.",
                                  valuestype= "App::PropertyFloat")
     self.updateReadonlyness(obj)
Exemplo n.º 7
0
 def assureGenerator(self, obj):
     '''Adds an instance of value series generator, if one doesn't exist yet.'''
     if hasattr(self,"generator"):
         return
     self.generator = ValueSeriesGenerator(obj)
     self.generator.addProperties(groupname= "Lattice Array", 
                                  groupname_gen= "Lattice Series Generator", 
                                  valuesdoc= "List of distances. Distance is measured from Point, along Dir, in millimeters.",
                                  valuestype= "App::PropertyDistance")
     self.updateReadonlyness(obj)
Exemplo n.º 8
0
class LatticeScLERP(lattice2BaseFeature.LatticeFeature):
    "The Lattice ScLERP object"

    def derivedInit(self, host):
        host.addProperty(
            "App::PropertyLink", "Placement1Ref", "Lattice ScLERP",
            "First placement, or an array of two placements to interpolate between."
        )
        host.addProperty("App::PropertyLink", "Placement2Ref",
                         "Lattice ScLERP",
                         "Second placement to interpolate between.")
        host.addProperty(
            "App::PropertyBool", "Shorten", "Lattice ScLERP",
            "Use shortest path. (if not, and angle difference of two placement exceeds 180 degrees, longer path will be taken)"
        )
        host.Shorten = True

        self.assureGenerator(host)
        host.ValuesSource = "Generator"
        host.GeneratorMode = "SpanN"
        host.EndInclusive = True
        host.SpanStart = 0.0
        host.SpanEnd = 1.0
        host.Step = 1.0 / 11
        host.Count = 11

    def assureGenerator(self, host):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self, "generator"):
            return
        self.generator = ValueSeriesGenerator(host)
        self.generator.addProperties(
            groupname="Lattice Array",
            groupname_gen="Lattice Series Generator",
            valuesdoc=
            "List of parameter values. Values should be in range 0..1 for interpolation, and can be outside for extrapolation.",
            valuestype="App::PropertyFloat")

    def updateReadonlyness(self, host):
        super(LatticeScLERP, self).updateReadonlyness(host)

        self.assureGenerator(host)
        self.generator.updateReadonlyness()

        #host.setEditorMode('ReferenceValue', 0 if host.ReferencePlacementOption == 'at custom value' else 2)

    def derivedExecute(self, host):
        self.assureGenerator(host)

        self.generator.execute()
        values = [float(strv) for strv in host.Values]

        input = lattice2BaseFeature.getPlacementsList(host.Placement1Ref)
        if host.Placement2Ref is not None:
            input.extend(
                lattice2BaseFeature.getPlacementsList(host.Placement2Ref))

        if len(input) != 2:
            raise ValueError("Need exactly 2 placements. {n} provided.".format(
                n=len(input)))

        plm1, plm2 = input

        # construct interpolation functions
        #  prepare lists of input samples

        def plmByVal(val):
            return plm1.sclerp(plm2, val, host.Shorten)

        output = [plmByVal(val) for val in values]

        ## update reference placement
        #ref = host.ReferencePlacementOption
        #if ref == 'external':
        #    pass
        #elif ref == 'origin':
        #    self.setReferencePlm(host, None)
        #elif ref == 'inherit':
        #    self.setReferencePlm(host, lattice2BaseFeature.getReferencePlm(host.Base))
        #elif ref == 'SpanStart':
        #    self.setReferencePlm(host, plmByVal(float(host.SpanStart)))
        #elif ref == 'SpanEnd':
        #    self.setReferencePlm(host, plmByVal(float(host.SpanEnd)))
        #elif ref == 'at custom value':
        #    self.setReferencePlm(host, plmByVal(float(host.ReferenceValue)))
        #elif ref == 'first placement':
        #    self.setReferencePlm(host, output[0])
        #elif ref == 'last placement':
        #    self.setReferencePlm(host, output[-1])
        #else:
        #    raise NotImplementedError("Reference option not implemented: " + ref)
        #
        return output
Exemplo n.º 9
0
class LinearArray(lattice2BaseFeature.LatticeFeature):
    "The Lattice LinearArray object"

    def derivedInit(self, obj):
        self.Type = "LatticeLinearArray"

        obj.addProperty("App::PropertyVector", "Dir", "Lattice Array",
                        "Vector that defines axis direction")
        obj.Dir = App.Vector(1, 0, 0)

        obj.addProperty(
            "App::PropertyVector", "Point", "Lattice Array",
            "Position of base (the point through which the axis passes, and from which positions of elements are measured)"
        )

        obj.addProperty("App::PropertyLink", "Link", "Lattice Array",
                        "Link to the axis (Edge1 is used for the axis).")
        obj.addProperty("App::PropertyString", "LinkSubelement",
                        "Lattice Array",
                        "subelement to take from axis link shape")

        obj.addProperty("App::PropertyBool", "Reverse", "Lattice Array",
                        "Set to true to reverse direction")

        obj.addProperty("App::PropertyBool", "DirIsDriven", "Lattice Array",
                        "If True, Dir property is driven by link.")
        obj.DirIsDriven = True

        obj.addProperty(
            "App::PropertyBool", "PointIsDriven", "Lattice Array",
            "If True, AxisPoint is not updated based on the link.")
        obj.PointIsDriven = True

        obj.addProperty(
            "App::PropertyEnumeration", "DrivenProperty", "Lattice Array",
            "Select, which property is to be driven by length of axis link.")
        obj.DrivenProperty = ['None', 'Span', 'SpanStart', 'SpanEnd', 'Step']
        obj.DrivenProperty = 'Span'

        obj.addProperty("App::PropertyEnumeration", "OrientMode",
                        "Lattice Array", "Orientation of elements")
        obj.OrientMode = ['None', 'Along axis']
        obj.OrientMode = 'Along axis'

        self.assureGenerator(obj)
        obj.ValuesSource = "Generator"
        obj.GeneratorMode = "StepN"
        obj.EndInclusive = True
        obj.SpanStart = 0.0
        obj.SpanEnd = 12.0
        obj.Step = 3.0
        obj.Count = 5.0

        self.assureProperties(obj)

    def updateReadonlyness(self, obj):
        link = screen(obj.Link)
        obj.setEditorMode("Dir", 1 if (link and obj.DirIsDriven) else 0)
        obj.setEditorMode("Point", 1 if (link and obj.PointIsDriven) else 0)
        obj.setEditorMode("DirIsDriven", 0 if link else 1)
        obj.setEditorMode("PointIsDriven", 0 if link else 1)
        obj.setEditorMode("DrivenProperty", 0 if link else 1)

        self.generator.updateReadonlyness()

    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self, "generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(
            groupname="Lattice Array",
            groupname_gen="Lattice Series Generator",
            valuesdoc=
            "List of distances. Distance is measured from Point, along Dir, in millimeters.",
            valuestype="App::PropertyDistance")
        self.updateReadonlyness(obj)

    def assureProperties(self, selfobj):
        assureProperty(
            selfobj, "App::PropertyLinkSub", "SubLink",
            sublinkFromApart(screen(selfobj.Link), selfobj.LinkSubelement),
            "Lattice Array", "Mirror of Object+SubNames properties")

    def derivedExecute(self, obj):
        self.assureGenerator(obj)
        self.assureProperties(obj)
        self.updateReadonlyness(obj)

        # Apply links
        if screen(obj.Link):
            if lattice2BaseFeature.isObjectLattice(screen(obj.Link)):
                lattice2Executer.warning(
                    obj,
                    "For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape."
                )

            #resolve the link
            if len(obj.LinkSubelement) > 0:
                linkedShape = screen(obj.Link).Shape.getElement(
                    obj.LinkSubelement)
            else:
                linkedShape = screen(obj.Link).Shape

            #Type check
            if linkedShape.ShapeType != 'Edge':
                raise ValueError('Axis link must be an edge; it is ' +
                                 linkedShape.ShapeType + ' instead.')
            if type(linkedShape.Curve) is not Part.Line:
                raise ValueError('Axis link must be a line; it is ' +
                                 type(linkedShape.Curve) + ' instead.')

            #obtain
            start_point = linkedShape.valueAt(linkedShape.FirstParameter)
            end_point = linkedShape.valueAt(linkedShape.LastParameter)
            dir = end_point - start_point
            point = start_point if not obj.Reverse else end_point

            if obj.DirIsDriven:
                obj.Dir = dir
            if obj.PointIsDriven:
                obj.Point = point
            if obj.DrivenProperty != 'None':
                if obj.DrivenProperty == 'Span':
                    propname = "SpanEnd"
                    obj.SpanEnd = obj.SpanStart + App.Units.Quantity(
                        'mm') * dir.Length
                else:
                    propname = obj.DrivenProperty
                    setattr(obj, propname, dir.Length)
                if self.generator.isPropertyControlledByGenerator(propname):
                    lattice2Executer.warning(
                        obj, "Property " + propname +
                        " is driven by both generator and link. Generator has priority."
                    )

        # Generate series of values
        self.generator.execute()
        values = [float(strv) for strv in obj.Values]

        #Apply reversal
        if obj.Reverse:
            obj.Dir = obj.Dir * (-1.0)
            if not (obj.DirIsDriven and screen(obj.Link)):
                obj.Reverse = False

        # precompute orientation
        if obj.OrientMode == 'Along axis':
            ori = lattice2GeomUtils.makeOrientationFromLocalAxes(
                ZAx=obj.Dir).multiply(
                    lattice2GeomUtils.makeOrientationFromLocalAxes(
                        ZAx=App.Vector(1, 0, 0), XAx=App.Vector(0, 0, 1)))
        else:
            ori = App.Rotation()

        dir = obj.Dir
        dir.normalize()

        # Make the array
        output = []  # list of placements
        for v in values:
            output.append(App.Placement(obj.Point + obj.Dir * v, ori))

        return output

    def onChanged(self, selfobj,
                  prop):  #prop is a string - name of the property
        # synchronize SubLink and Object+SubNames properties
        syncSublinkApart(selfobj, prop, 'SubLink', 'Link', 'LinkSubelement')
        return lattice2BaseFeature.LatticeFeature.onChanged(
            self, selfobj, prop)
Exemplo n.º 10
0
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
Exemplo n.º 11
0
class PolarArray(lattice2BaseFeature.LatticeFeature):
    "The Lattice PolarArray object"
    def derivedInit(self,obj):
        self.Type = "LatticePolarArray"
                
        obj.addProperty("App::PropertyLength","Radius","Lattice Array","Radius of the array (set to zero for just rotation).")  
        obj.Radius = 3 
        
        obj.addProperty("App::PropertyVector","AxisDir","Lattice Array","Vector that defines axis direction")  
        obj.AxisDir = App.Vector(0,0,1)
        
        obj.addProperty("App::PropertyVector","AxisPoint","Lattice Array","Center of rotation")  
        
        obj.addProperty("App::PropertyLink","AxisLink","Lattice Array","Link to the axis (Edge1 is used for the axis).")  
        obj.addProperty("App::PropertyString","AxisLinkSubelement","Lattice Array","subelement to take from axis link shape")        
        
        obj.addProperty("App::PropertyBool","AxisDirIsDriven","Lattice Array","If True, AxisDir is not updated based on the link.")
        obj.addProperty("App::PropertyBool","AxisPointIsDriven","Lattice Array","If True, AxisPoint is not updated based on the link.")
        obj.AxisDirIsDriven = True
        obj.AxisPointIsDriven = True
        
        obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice Array","Orientation of elements")
        obj.OrientMode = ['None','Against axis']
        obj.OrientMode = 'Against axis'
        
        self.assureGenerator(obj)

        obj.ValuesSource = "Generator"
        obj.SpanStart = 0
        obj.SpanEnd = 360
        obj.EndInclusive = False
        obj.Count = 5

    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self,"generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(groupname= "Lattice Array", 
                                     groupname_gen= "Lattice Series Generator", 
                                     valuesdoc= "List of angles, in degrees.",
                                     valuestype= "App::PropertyFloat")
        self.updateReadonlyness(obj)
        
    def updateReadonlyness(self, obj):
        obj.setEditorMode("AxisDir", 1 if (obj.AxisLink and obj.AxisDirIsDriven) else 0)
        obj.setEditorMode("AxisPoint", 1 if (obj.AxisLink and obj.AxisPointIsDriven) else 0)
        obj.setEditorMode("AxisDirIsDriven", 0 if obj.AxisLink else 1)
        obj.setEditorMode("AxisPointIsDriven", 0 if obj.AxisLink else 1)
        self.generator.updateReadonlyness()
        

    def derivedExecute(self,obj):
        self.assureGenerator(obj)
        self.updateReadonlyness(obj)
        
        # Apply links
        if obj.AxisLink:
            if lattice2BaseFeature.isObjectLattice(obj.AxisLink):
                lattice2Executer.warning(obj,"For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape.")
                
            #resolve the link        
            if len(obj.AxisLinkSubelement) > 0:
                linkedShape = obj.AxisLink.Shape.getElement(obj.AxisLinkSubelement)
            else:
                linkedShape = obj.AxisLink.Shape

            #Type check
            if linkedShape.ShapeType != 'Edge':
                raise ValueError('Axis link must be an edge; it is '+linkedShape.ShapeType+' instead.')
            
            #prepare
            dir = App.Vector()
            point = App.Vector()
            if isinstance(linkedShape.Curve, Part.Line):
                dir = linkedShape.Curve.EndPoint - linkedShape.Curve.StartPoint
                point = linkedShape.Curve.StartPoint
            elif isinstance(linkedShape.Curve, Part.Circle):
                dir = linkedShape.Curve.Axis
                point = linkedShape.Curve.Center
            else:
                raise ValueError("Edge " + repr(linkedShape) + " can't be used to derive an axis. It must be either a line or a circle/arc.")
            
            #apply
            if obj.AxisDirIsDriven:
                obj.AxisDir = dir
            if obj.AxisPointIsDriven:
                obj.AxisPoint = point
        
        self.generator.execute()
        
        # cache properties into variables
        radius = float(obj.Radius)
        values = [float(strv) for strv in obj.Values]
        
        # compute initial vector. It is to be perpendicular to Axis
        rot_ini = lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= obj.AxisDir)
        overallPlacement = App.Placement(obj.AxisPoint, rot_ini)
        
        # Make the array
        output = [] # list of placements
        for ang in values:
            p = Part.Vertex()
            localrot = App.Rotation(App.Vector(0,0,1), ang)
            localtransl = localrot.multVec(App.Vector(radius,0,0))
            localplm = App.Placement(localtransl, localrot)
            resultplm = overallPlacement.multiply(localplm)
            if obj.OrientMode == 'None':
                resultplm.Rotation = App.Rotation()
            output.append(resultplm)

        return output
Exemplo n.º 12
0
class PolarArray(lattice2BaseFeature.LatticeFeature):
    "The Lattice PolarArray object"

    def derivedInit(self, obj):
        self.Type = "LatticePolarArray"

        obj.addProperty(
            "App::PropertyLength", "Radius", "Lattice Array",
            "Radius of the array (set to zero for just rotation).")
        obj.Radius = 3

        obj.addProperty("App::PropertyVector", "AxisDir", "Lattice Array",
                        "Vector that defines axis direction")
        obj.AxisDir = App.Vector(0, 0, 1)

        obj.addProperty("App::PropertyVector", "AxisPoint", "Lattice Array",
                        "Center of rotation")

        obj.addProperty("App::PropertyLink", "AxisLink", "Lattice Array",
                        "Link to the axis (Edge1 is used for the axis).")
        obj.addProperty("App::PropertyString", "AxisLinkSubelement",
                        "Lattice Array",
                        "subelement to take from axis link shape")

        obj.addProperty("App::PropertyBool", "AxisDirIsDriven",
                        "Lattice Array",
                        "If True, AxisDir is not updated based on the link.")
        obj.addProperty(
            "App::PropertyBool", "AxisPointIsDriven", "Lattice Array",
            "If True, AxisPoint is not updated based on the link.")
        obj.AxisDirIsDriven = True
        obj.AxisPointIsDriven = True

        obj.addProperty("App::PropertyEnumeration", "OrientMode",
                        "Lattice Array", "Orientation of elements")
        obj.OrientMode = ['None', 'Against axis']
        obj.OrientMode = 'Against axis'

        self.assureGenerator(obj)

        obj.ValuesSource = "Generator"
        obj.SpanStart = 0
        obj.SpanEnd = 360
        obj.EndInclusive = False
        obj.Count = 5

        self.assureProperties(obj)

    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self, "generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(groupname="Lattice Array",
                                     groupname_gen="Lattice Series Generator",
                                     valuesdoc="List of angles, in degrees.",
                                     valuestype="App::PropertyFloat")
        self.updateReadonlyness(obj)

    def updateReadonlyness(self, obj):
        axislink = screen(obj.AxisLink)
        obj.setEditorMode("AxisDir", 1 if
                          (axislink and obj.AxisDirIsDriven) else 0)
        obj.setEditorMode("AxisPoint", 1 if
                          (axislink and obj.AxisPointIsDriven) else 0)
        obj.setEditorMode("AxisDirIsDriven", 0 if axislink else 1)
        obj.setEditorMode("AxisPointIsDriven", 0 if axislink else 1)
        self.generator.updateReadonlyness()

    def assureProperties(self, selfobj):
        assureProperty(
            selfobj, "App::PropertyLinkSub", "AxisSubLink",
            sublinkFromApart(screen(selfobj.AxisLink),
                             selfobj.AxisLinkSubelement), "Lattice Array",
            "Mirror of Object+SubNames properties")

    def derivedExecute(self, obj):
        self.assureGenerator(obj)
        self.assureProperties(obj)
        self.updateReadonlyness(obj)

        # Apply links
        if screen(obj.AxisLink):
            if lattice2BaseFeature.isObjectLattice(screen(obj.AxisLink)):
                lattice2Executer.warning(
                    obj,
                    "For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape."
                )

            #resolve the link
            if len(obj.AxisLinkSubelement) > 0:
                linkedShape = screen(obj.AxisLink).Shape.getElement(
                    obj.AxisLinkSubelement)
            else:
                linkedShape = screen(obj.AxisLink).Shape

            #Type check
            if linkedShape.ShapeType != 'Edge':
                raise ValueError('Axis link must be an edge; it is ' +
                                 linkedShape.ShapeType + ' instead.')

            #prepare
            dir = App.Vector()
            point = App.Vector()
            if isinstance(linkedShape.Curve, Part.Line):
                start_point = linkedShape.valueAt(linkedShape.FirstParameter)
                end_point = linkedShape.valueAt(linkedShape.LastParameter)
                dir = end_point - start_point
                point = start_point
            elif isinstance(linkedShape.Curve, Part.Circle):
                dir = linkedShape.Curve.Axis
                point = linkedShape.Curve.Center
            else:
                raise ValueError(
                    "Edge " + repr(linkedShape) +
                    " can't be used to derive an axis. It must be either a line or a circle/arc."
                )

            #apply
            if obj.AxisDirIsDriven:
                obj.AxisDir = dir
            if obj.AxisPointIsDriven:
                obj.AxisPoint = point

        self.generator.execute()

        # cache properties into variables
        radius = float(obj.Radius)
        values = [float(strv) for strv in obj.Values]

        # compute initial vector. It is to be perpendicular to Axis
        rot_ini = lattice2GeomUtils.makeOrientationFromLocalAxes(
            ZAx=obj.AxisDir)
        overallPlacement = App.Placement(obj.AxisPoint, rot_ini)

        # Make the array
        output = []  # list of placements
        for ang in values:
            p = Part.Vertex()
            localrot = App.Rotation(App.Vector(0, 0, 1), ang)
            localtransl = localrot.multVec(App.Vector(radius, 0, 0))
            localplm = App.Placement(localtransl, localrot)
            resultplm = overallPlacement.multiply(localplm)
            if obj.OrientMode == 'None':
                resultplm.Rotation = App.Rotation()
            output.append(resultplm)

        return output

    def onChanged(self, selfobj,
                  prop):  #prop is a string - name of the property
        # synchronize SubLink and Object+SubNames properties
        syncSublinkApart(selfobj, prop, 'AxisSubLink', 'AxisLink',
                         'AxisLinkSubelement')
        return lattice2BaseFeature.LatticeFeature.onChanged(
            self, selfobj, prop)
Exemplo n.º 13
0
class LatticeParaSeries(lattice2BaseFeature.LatticeFeature):
    "The Lattice ParaSeries object"
    
    def derivedInit(self,obj):
        self.Type = "LatticeParaSeries"
                
        obj.addProperty("App::PropertyLink","Object","Lattice ParaSeries","Object to make series from. Can be any generic shape, as well as an array of placements.")
        
        obj.addProperty("App::PropertyEnumeration","ParameterType","Lattice ParaSeries","Data type of parameter to vary.")
        obj.ParameterType = ['float','int','string']
        
        obj.addProperty("App::PropertyString","ParameterRef","Lattice ParaSeries","Reference to the parameter to vary. Syntax: ObjectName.Property. Examples: 'Box.Height'; 'Sketch.Constaints.myLength'.")
                        
        obj.addProperty("App::PropertyEnumeration","Recomputing","Lattice ParaSeries","Sets recomputing policy.")
        obj.Recomputing = ["Disabled", "Recompute Once", "Enabled"]
        obj.Recomputing = "Disabled" # recomputing ParaSeries can be very long, so disable it by default
        
        self.assureGenerator(obj)
        
    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self,"generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(groupname= "Lattice ParaSeries", 
                                     groupname_gen= "Lattice ParaSeries Generator", 
                                     valuesdoc= "List of parameter values to compute object for.")
        self.generator.updateReadonlyness()

    def derivedExecute(self,selfobj):
        # values generator should be functional even if recomputing is disabled, so do it first
        self.assureGenerator(selfobj)
        self.generator.updateReadonlyness()
        self.generator.execute()
        
        if selfobj.Recomputing == "Disabled":
            raise ValueError(selfobj.Name+": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it.")
        try:            
            #test parameter references and read out their current values
            refstr = selfobj.ParameterRef #dict(selfobj.ExpressionEngine)["ParameterRef"]
            refstrs = refstr.replace(";","\t").split("\t")
            defvalues = []
            for refstr in refstrs:
                refstr = refstr.strip();
                val = None;
                try:
                    val = getParameter(selfobj.Document,refstr)
                except Exception as err:
                    App.Console.PrintError("{obj}: failed to read out parameter '{param}': {err}\n"
                                            .format(obj= selfobj.Name,
                                                    param= refstr,
                                                    err= err.message))
                defvalues.append(val)
            N_params = len(defvalues)
            if N_params == 0:
                raise ValueError(selfobj.Name+": ParameterRef is not set. It is required.")
            
            #parse values
            values = []
            for strrow in selfobj.Values:
                if len(strrow) == 0:
                    break;
                row = strrow.split(";")
                row = [(strv.strip() if len(strv.strip())>0 else None) for strv in row] # clean out spaces and replace empty strings with None
                if len(row) < N_params:
                    row += [None]*(N_params - len(row))
                values.append(row)
            
            # convert values to type, filling in defaults where values are missing
            for row in values:
                for icol in range(N_params):
                    strv = row[icol]
                    val = None
                    if strv is None:
                        val = defvalues[icol]
                    elif selfobj.ParameterType == 'float' or selfobj.ParameterType == 'int':
                        val = float(strv.replace(",","."))
                        if selfobj.ParameterType == 'int':
                            val = int(round(val))
                    elif selfobj.ParameterType == 'string':
                        val = strv.strip()
                    else:
                        raise ValueError(selfobj.Name + ": ParameterType option not implemented: "+selfobj.ParameterType)
                    row[icol] = val
            
            if len(values) == 0:
                scale = 1.0
                try:
                    if not selfobj.Object.Shape.isNull():
                        scale = selfobj.Object.Shape.BoundBox.DiagonalLength/math.sqrt(3)
                except Exception:
                    pass
                if scale < DistConfusion * 100:
                    scale = 1.0
                selfobj.Shape = markers.getNullShapeShape(scale)
                raise ValueError(selfobj.Name + ": list of values is empty.") 
            
            bGui = False #bool(App.GuiUp) #disabled temporarily, because it causes a crash if property edits are approved by hitting Enter
            if bGui:
                import PySide
                progress = PySide.QtGui.QProgressDialog(u"Recomputing "+selfobj.Label, u"Abort", 0, len(values)+1)
                progress.setModal(True)
                progress.show()
            
            doc1 = selfobj.Document
            doc2 = App.newDocument()
            object_in_doc2 = None # define the variable, to prevent del() in finally block from raising another error
            try:
                doc2.copyObject(selfobj.Object, True)
                
                #if there are nested paraseries in the dependencies, make sure to enable them
                for objd2 in doc2.Objects:
                    if hasattr(objd2,"Recomputing"):
                        try:
                            objd2.Recomputing = "Enabled"
                            objd2.purgeTouched()
                        except exception:
                            lattice2Executer.warning(selfobj,"Failed to enable recomputing of "+objd2.Name)
                
                object_in_doc2 = doc2.getObject(selfobj.Object.Name)
                if bGui:
                    progress.setValue(1)
                output_shapes = []
                for row in values:
                    for icol in range(len(row)):
                        setParameter(doc2, refstrs[icol].strip(), row[icol])
                    
                    #recompute
                    doc2.recompute()
                    
                    #get shape
                    shape = None
                    for obj in doc2.Objects:
                        if 'Invalid' in obj.State:
                            lattice2Executer.error(obj,"Recomputing shape for parameter value of "+repr(row)+" failed.")
                            
                            scale = 1.0
                            try:
                                if not selfobj.Object.Shape.isNull():
                                    scale = selfobj.Object.Shape.BoundBox.DiagonalLength/math.sqrt(3)
                            except Exception:
                                pass
                            if scale < DistConfusion * 100:
                                scale = 1.0
                            shape = markers.getNullShapeShape(scale)
                    if shape is None:
                        shape = object_in_doc2.Shape.copy()
                    output_shapes.append(shape)
                    
                    #update progress
                    if bGui:
                        progress.setValue(progress.value()+1)
                        if progress.wasCanceled():
                            raise lattice2Executer.CancelError()
                    
            finally:
                #delete all references, before destroying the document. Probably not required, but to be sure...
                del(object_in_doc2)
                doc2_name = doc2.Name
                del(doc2)
                App.closeDocument(doc2_name)
                if bGui:
                    progress.setValue(len(values)+1)

                
            selfobj.Shape = Part.makeCompound(output_shapes)

            output_is_lattice = lattice2BaseFeature.isObjectLattice(selfobj.Object)
            if 'Auto' in selfobj.isLattice:
                new_isLattice = 'Auto-On' if output_is_lattice else 'Auto-Off'
                if selfobj.isLattice != new_isLattice:#check, to not cause onChanged without necessity (onChange messes with colors, it's better to keep user color)
                    selfobj.isLattice = new_isLattice                    
        finally:
            if selfobj.Recomputing == "Recompute Once":
                selfobj.Recomputing = "Disabled"
        return "suppress" # "suppress" disables most convenience code of lattice2BaseFeature. We do it because we build a nested array, which are not yet supported by lattice WB.
Exemplo n.º 14
0
class LatticeParaSeries(lattice2BaseFeature.LatticeFeature):
    "The Lattice ParaSeries object"

    def derivedInit(self, obj):
        self.Type = "LatticeParaSeries"

        obj.addProperty(
            "App::PropertyLink", "Object", "Lattice ParaSeries",
            "Object to make series from. Can be any generic shape, as well as an array of placements."
        )

        obj.addProperty("App::PropertyEnumeration", "ParameterType",
                        "Lattice ParaSeries",
                        "Data type of parameter to vary.")
        obj.ParameterType = ['float', 'int', 'string']

        obj.addProperty(
            "App::PropertyString", "ParameterRef", "Lattice ParaSeries",
            "Reference to the parameter to vary. Syntax: ObjectName.Property. Examples: 'Box.Height'; 'Sketch.Constraints.myLength'."
        )

        obj.addProperty("App::PropertyEnumeration", "Recomputing",
                        "Lattice ParaSeries", "Sets recomputing policy.")
        obj.Recomputing = ["Disabled", "Recompute Once", "Enabled"]
        obj.Recomputing = "Disabled"  # recomputing ParaSeries can be very long, so disable it by default

        self.assureGenerator(obj)

    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self, "generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(
            groupname="Lattice ParaSeries",
            groupname_gen="Lattice ParaSeries Generator",
            valuesdoc="List of parameter values to compute object for.")
        self.generator.updateReadonlyness()

    def derivedExecute(self, selfobj):
        # values generator should be functional even if recomputing is disabled, so do it first
        self.assureGenerator(selfobj)
        self.generator.updateReadonlyness()
        self.generator.execute()

        if selfobj.Recomputing == "Disabled":
            raise ValueError(
                selfobj.Name +
                ": recomputing of this object is currently disabled. Modify 'Recomputing' property to enable it."
            )
        try:
            #test parameter references and read out their current values
            refstr = selfobj.ParameterRef  #dict(selfobj.ExpressionEngine)["ParameterRef"]
            refstrs = refstr.replace(";", "\t").split("\t")
            defvalues = []
            for refstr in refstrs:
                refstr = refstr.strip()
                val = None
                try:
                    val = getParameter(selfobj.Document, refstr)
                except Exception as err:
                    App.Console.PrintError(
                        "{obj}: failed to read out parameter '{param}': {err}\n"
                        .format(obj=selfobj.Name, param=refstr, err=str(err)))
                defvalues.append(val)
            N_params = len(defvalues)
            if N_params == 0:
                raise ValueError(selfobj.Name +
                                 ": ParameterRef is not set. It is required.")

            #parse values
            values = []
            for strrow in selfobj.Values:
                if len(strrow) == 0:
                    break
                row = strrow.split(";")
                row = [
                    (strv.strip() if len(strv.strip()) > 0 else None)
                    for strv in row
                ]  # clean out spaces and replace empty strings with None
                if len(row) < N_params:
                    row += [None] * (N_params - len(row))
                values.append(row)

            # convert values to type, filling in defaults where values are missing
            for row in values:
                for icol in range(N_params):
                    strv = row[icol]
                    val = None
                    if strv is None:
                        val = defvalues[icol]
                    elif selfobj.ParameterType == 'float' or selfobj.ParameterType == 'int':
                        val = float(strv.replace(",", "."))
                        if selfobj.ParameterType == 'int':
                            val = int(round(val))
                    elif selfobj.ParameterType == 'string':
                        val = strv.strip()
                    else:
                        raise ValueError(
                            selfobj.Name +
                            ": ParameterType option not implemented: " +
                            selfobj.ParameterType)
                    row[icol] = val

            if len(values) == 0:
                scale = 1.0
                try:
                    if not screen(selfobj.Object).Shape.isNull():
                        scale = screen(
                            selfobj.Object
                        ).Shape.BoundBox.DiagonalLength / math.sqrt(3)
                except Exception:
                    pass
                if scale < DistConfusion * 100:
                    scale = 1.0
                selfobj.Shape = markers.getNullShapeShape(scale)
                raise ValueError(selfobj.Name + ": list of values is empty.")

            bGui = False  #bool(App.GuiUp) #disabled temporarily, because it causes a crash if property edits are approved by hitting Enter
            if bGui:
                import PySide
                progress = PySide.QtGui.QProgressDialog(
                    u"Recomputing " + selfobj.Label, u"Abort", 0,
                    len(values) + 1)
                progress.setModal(True)
                progress.show()

            doc1 = selfobj.Document
            doc2 = App.newDocument(
            )  #create temporary doc to do the computations

            # assign doc's filename before copying objects, otherwise we get errors with xlinks
            try:
                doc2.FileName = doc1.FileName
            except Exception as err:
                pass  #in old FreeCADs, FileName property is read-only, we can safely ignore that

            object_in_doc2 = None  # define the variable, to prevent del() in finally block from raising another error
            try:
                doc2.copyObject(screen(selfobj.Object), True)

                #if there are nested paraseries in the dependencies, make sure to enable them
                for objd2 in doc2.Objects:
                    if hasattr(objd2, "Recomputing"):
                        try:
                            objd2.Recomputing = "Enabled"
                            objd2.purgeTouched()
                        except exception:
                            lattice2Executer.warning(
                                selfobj, "Failed to enable recomputing of " +
                                objd2.Name)

                object_in_doc2 = doc2.getObject(screen(selfobj.Object).Name)
                if bGui:
                    progress.setValue(1)
                output_shapes = []
                for row in values:
                    for icol in range(len(row)):
                        setParameter(doc2, refstrs[icol].strip(), row[icol])

                    #recompute
                    doc2.recompute()

                    #get shape
                    shape = None
                    for obj in doc2.Objects:
                        if 'Invalid' in obj.State:
                            lattice2Executer.error(
                                obj,
                                "Recomputing shape for parameter value of " +
                                repr(row) + " failed.")

                            scale = 1.0
                            try:
                                if not screen(selfobj.Object).Shape.isNull():
                                    scale = screen(
                                        selfobj.Object
                                    ).Shape.BoundBox.DiagonalLength / math.sqrt(
                                        3)
                            except Exception:
                                pass
                            if scale < DistConfusion * 100:
                                scale = 1.0
                            shape = markers.getNullShapeShape(scale)
                    if shape is None:
                        shape = object_in_doc2.Shape.copy()
                    output_shapes.append(shape)

                    #update progress
                    if bGui:
                        progress.setValue(progress.value() + 1)
                        if progress.wasCanceled():
                            raise lattice2Executer.CancelError()

            finally:
                #delete all references, before destroying the document. Probably not required, but to be sure...
                del (object_in_doc2)
                doc2_name = doc2.Name
                del (doc2)
                App.closeDocument(doc2_name)
                if bGui:
                    progress.setValue(len(values) + 1)

            selfobj.Shape = Part.makeCompound(output_shapes)

            output_is_lattice = lattice2BaseFeature.isObjectLattice(
                screen(selfobj.Object))
            if 'Auto' in selfobj.isLattice:
                new_isLattice = 'Auto-On' if output_is_lattice else 'Auto-Off'
                if selfobj.isLattice != new_isLattice:  #check, to not cause onChanged without necessity (onChange messes with colors, it's better to keep user color)
                    selfobj.isLattice = new_isLattice
        finally:
            if selfobj.Recomputing == "Recompute Once":
                selfobj.Recomputing = "Disabled"
        return "suppress"  # "suppress" disables most convenience code of lattice2BaseFeature. We do it because we build a nested array, which are not yet supported by lattice WB.
Exemplo n.º 15
0
class LinearArray(lattice2BaseFeature.LatticeFeature):
    "The Lattice LinearArray object"
    def derivedInit(self,obj):
        self.Type = "LatticeLinearArray"

        obj.addProperty("App::PropertyVector","Dir","Lattice Array","Vector that defines axis direction")  
        obj.Dir = App.Vector(1,0,0)
        
        obj.addProperty("App::PropertyVector","Point","Lattice Array","Position of base (the point through which the axis passes, and from which positions of elements are measured)")  
        
        obj.addProperty("App::PropertyLink","Link","Lattice Array","Link to the axis (Edge1 is used for the axis).")  
        obj.addProperty("App::PropertyString","LinkSubelement","Lattice Array","subelement to take from axis link shape")

        obj.addProperty("App::PropertyBool","Reverse","Lattice Array","Set to true to reverse direction")

        obj.addProperty("App::PropertyBool","DirIsDriven","Lattice Array","If True, Dir property is driven by link.")
        obj.DirIsDriven = True

        obj.addProperty("App::PropertyBool","PointIsDriven","Lattice Array","If True, AxisPoint is not updated based on the link.")
        obj.PointIsDriven = True

        obj.addProperty("App::PropertyEnumeration","DrivenProperty","Lattice Array","Select, which property is to be driven by length of axis link.")
        obj.DrivenProperty = ['None','Span','SpanStart','SpanEnd','Step']
        obj.DrivenProperty = 'Span'
                        
        obj.addProperty("App::PropertyEnumeration","OrientMode","Lattice Array","Orientation of elements")
        obj.OrientMode = ['None','Along axis']
        obj.OrientMode = 'Along axis'
        
        self.assureGenerator(obj)
        obj.ValuesSource = "Generator"
        obj.GeneratorMode = "StepN"
        obj.EndInclusive = True
        obj.SpanStart = 0.0
        obj.SpanEnd = 12.0
        obj.Step = 3.0
        obj.Count = 5.0

    def updateReadonlyness(self, obj):
        obj.setEditorMode("Dir", 1 if (obj.Link and obj.DirIsDriven) else 0)
        obj.setEditorMode("Point", 1 if (obj.Link and obj.PointIsDriven) else 0)
        obj.setEditorMode("DirIsDriven", 0 if obj.Link else 1)
        obj.setEditorMode("PointIsDriven", 0 if obj.Link else 1)
        obj.setEditorMode("DrivenProperty", 0 if obj.Link else 1)
        
        self.generator.updateReadonlyness()

    def assureGenerator(self, obj):
        '''Adds an instance of value series generator, if one doesn't exist yet.'''
        if hasattr(self,"generator"):
            return
        self.generator = ValueSeriesGenerator(obj)
        self.generator.addProperties(groupname= "Lattice Array", 
                                     groupname_gen= "Lattice Series Generator", 
                                     valuesdoc= "List of distances. Distance is measured from Point, along Dir, in millimeters.",
                                     valuestype= "App::PropertyDistance")
        self.updateReadonlyness(obj)
        
        

    def derivedExecute(self,obj):
        self.assureGenerator(obj)
        self.updateReadonlyness(obj)

        # Apply links
        if obj.Link:
            if lattice2BaseFeature.isObjectLattice(obj.Link):
                lattice2Executer.warning(obj,"For polar array, axis link is expected to be a regular shape. Lattice objct was supplied instead, it's going to be treated as a generic shape.")
            
            #resolve the link
            if len(obj.LinkSubelement) > 0:
                linkedShape = obj.Link.Shape.getElement(obj.LinkSubelement)
            else:
                linkedShape = obj.Link.Shape
            
            #Type check
            if linkedShape.ShapeType != 'Edge':
                raise ValueError('Axis link must be an edge; it is '+linkedShape.ShapeType+' instead.')
            if type(linkedShape.Curve) is not Part.Line:
                raise ValueError('Axis link must be a line; it is '+type(linkedShape.Curve)+' instead.')
            
            #obtain
            dir = linkedShape.Curve.EndPoint - linkedShape.Curve.StartPoint
            point = linkedShape.Curve.StartPoint if not obj.Reverse else linkedShape.Curve.EndPoint
            
            if obj.DirIsDriven:
                obj.Dir = dir
            if obj.PointIsDriven:
                obj.Point = point
            if obj.DrivenProperty != 'None':
                if obj.DrivenProperty == 'Span':
                    propname = "SpanEnd"
                    obj.SpanEnd = obj.SpanStart + App.Units.Quantity('mm')*dir.Length
                else:
                    propname = obj.DrivenProperty
                    setattr(obj, propname, dir.Length)
                if self.generator.isPropertyControlledByGenerator(propname):
                    lattice2Executer.warning(obj, "Property "+propname+" is driven by both generator and link. Generator has priority.")

        
        # Generate series of values
        self.generator.execute()
        values = [float(strv) for strv in obj.Values]
        
        #Apply reversal
        if obj.Reverse:
            obj.Dir = obj.Dir*(-1.0)
            if not(obj.DirIsDriven and obj.Link):
                obj.Reverse = False

        # precompute orientation
        if obj.OrientMode == 'Along axis':
            ori = lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= obj.Dir).multiply(
                    lattice2GeomUtils.makeOrientationFromLocalAxes(ZAx= App.Vector(1,0,0), XAx= App.Vector(0,0,1)) )
        else:
            ori = App.Rotation()
        
        dir = obj.Dir
        dir.normalize()
        
        # Make the array
        output = [] # list of placements
        for v in values:
            output.append( App.Placement(obj.Point + obj.Dir*v, ori) )
            
        return output