Exemplo n.º 1
0
    def makeStraightStairsWithLanding(self,obj,edge):
        
        "builds a straight staircase with a landing in the middle"

        if obj.NumberOfSteps < 3:
            return
        import Part,DraftGeomUtils
        v = DraftGeomUtils.vec(edge)
        reslength = edge.Length - obj.Width.Value
        vLength = DraftVecUtils.scaleTo(v,float(reslength)/(obj.NumberOfSteps-2))
        vLength = Vector(vLength.x,vLength.y,0)
        vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
        p1 = edge.Vertexes[0].Point
        if round(v.z,Draft.precision()) != 0:
            h = v.z
        else:
            h = obj.Height.Value
        hstep = h/obj.NumberOfSteps
        landing = obj.NumberOfSteps/2
        p2 = p1.add(DraftVecUtils.scale(vLength,landing-1).add(Vector(0,0,landing*hstep)))
        p3 = p2.add(DraftVecUtils.scaleTo(vLength,obj.Width.Value))
        p4 = p3.add(DraftVecUtils.scale(vLength,obj.NumberOfSteps-(landing+1)).add(Vector(0,0,(obj.NumberOfSteps-landing)*hstep)))
        self.makeStraightStairs(obj,Part.Line(p1,p2).toShape(),landing)
        self.makeStraightLanding(obj,Part.Line(p2,p3).toShape())
        self.makeStraightStairs(obj,Part.Line(p3,p4).toShape(),obj.NumberOfSteps-landing)
Exemplo n.º 2
0
 def getDeviation(self):
     "returns the deviation angle between the u axis and the horizontal plane"
     proj = Vector(self.u.x,self.u.y,0)
     if self.u.getAngle(proj) == 0:
         return 0
     else:
         norm = proj.cross(self.u)
         return DraftVecUtils.angle(self.u,proj,norm)
Exemplo n.º 3
0
def FSMesh(obj, recompute=False):
	""" Get free surface mesh in matrix mode.
	@param obj Created Part::FeaturePython object.
	@param recompute True if mesh must be recomputed, False otherwise.
	@return Faces matrix
	"""
	nx = obj.FS_Nx
	ny = obj.FS_Ny
	if not recompute:
		faces = []
		for i in range(0,nx):
			faces.append([])
			for j in range(0,ny):
				faces[i].append(FreeSurfaceFace(obj.FS_Position[j + i*ny],
												obj.FS_Normal[j + i*ny],
												obj.FS_Area[j + i*ny]))
		return faces
	# Transform positions into a mesh
	pos = []
	for i in range(0,nx):
		pos.append([])
		for j in range(0,ny):
			pos[i].append(obj.FS_Position[j + i*ny])
	# Recompute normals and dimensions
	normal = []
	l	  = []
	b	  = []
	for i in range(0,nx):
		normal.append([])
		l.append([])
		b.append([])
		for j in range(0,ny):
			i0 = i-1
			i1 = i+1
			fi = 1.0
			j0 = j-1
			j1 = j+1
			fj = 1.0
			if i == 0:
				i0 = i
				i1 = i+1
				fi = 2.0
			if i == nx-1:
				i0 = i-1
				i1 = i
				fi = 2.0
			if j == 0:
				j0 = j
				j1 = j+1
				fj = 2.0
			if j == ny-1:
				j0 = j-1
				j1 = j
				fj = 2.0
			l[i].append(fi*(obj.FS_Position[j + i1*ny].x - obj.FS_Position[j + i0*ny].x))
			b[i].append(fj*(obj.FS_Position[j1 + i*ny].y - obj.FS_Position[j0 + i*ny].y))
			xvec = Vector(obj.FS_Position[j + i1*ny].x - obj.FS_Position[j + i0*ny].x,
						  obj.FS_Position[j + i1*ny].y - obj.FS_Position[j + i0*ny].y,
						  obj.FS_Position[j + i1*ny].z - obj.FS_Position[j + i0*ny].z)
			yvec = Vector(obj.FS_Position[j1 + i*ny].x - obj.FS_Position[j0 + i*ny].x,
						  obj.FS_Position[j1 + i*ny].y - obj.FS_Position[j0 + i*ny].y,
						  obj.FS_Position[j1 + i*ny].z - obj.FS_Position[j0 + i*ny].z)
			n = Vector(xvec.cross(yvec))	# Z positive
			normal[i].append(n.normalize())
	# Create faces
	faces = []
	for i in range(0,nx):
		faces.append([])
		for j in range(0,ny):
			faces[i].append(FreeSurfaceFace(pos[i][j], normal[i][j], l[i][j], b[i][j]))
	# Reconstruct mesh data
	for i in range(0,nx):
		for j in range(0,ny):
			obj.FS_Position[j + i*ny] = faces[i][j].pos
			obj.FS_Normal[j + i*ny]   = faces[i][j].normal
			obj.FS_Area[j + i*ny]	 = faces[i][j].area
	return faces
Exemplo n.º 4
0
    def makeStraightStairs(self,obj,edge,numberofsteps=None):

        "builds a simple, straight staircase from a straight edge"

        # general data
        import Part,DraftGeomUtils
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        v = DraftGeomUtils.vec(edge)
        vLength = DraftVecUtils.scaleTo(v,float(edge.Length)/(numberofsteps-1))
        vLength = Vector(vLength.x,vLength.y,0)
        if round(v.z,Draft.precision()) != 0:
            h = v.z
        else:
            h = obj.Height.Value
        vHeight = Vector(0,0,float(h)/numberofsteps)
        vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
        vBase = edge.Vertexes[0].Point
        vNose = DraftVecUtils.scaleTo(vLength,-abs(obj.Nosing.Value))
        a = math.atan(vHeight.Length/vLength.Length)
        #print "stair data:",vLength.Length,":",vHeight.Length

        # steps
        for i in range(numberofsteps-1):
            p1 = vBase.add((Vector(vLength).multiply(i)).add(Vector(vHeight).multiply(i+1)))
            p1 = self.align(p1,obj.Align,vWidth)
            p1 = p1.add(vNose).add(Vector(0,0,-abs(obj.TreadThickness.Value)))
            p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
            p3 = p2.add(vWidth)
            p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
            step = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
            if obj.TreadThickness.Value:
                step = step.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
                self.steps.append(step)
            else:
                self.pseudosteps.append(step)

        # structure
        lProfile = []
        struct = None
        if obj.Structure == "Massive":
            if obj.StructureThickness.Value:
                for i in range(numberofsteps-1):
                    if not lProfile:
                        lProfile.append(vBase)
                    last = lProfile[-1]
                    if len(lProfile) == 1:
                        last = last.add(Vector(0,0,-abs(obj.TreadThickness.Value)))
                    lProfile.append(last.add(vHeight))
                    lProfile.append(lProfile[-1].add(vLength))
                resHeight1 = obj.StructureThickness.Value/math.cos(a)
                lProfile.append(lProfile[-1].add(Vector(0,0,-resHeight1)))
                resHeight2 = ((numberofsteps-1)*vHeight.Length)-(resHeight1+obj.TreadThickness.Value)
                resLength = (vLength.Length/vHeight.Length)*resHeight2
                h = DraftVecUtils.scaleTo(vLength,-resLength)
                lProfile.append(lProfile[-1].add(Vector(h.x,h.y,-resHeight2)))
                lProfile.append(vBase)
                #print lProfile
                pol = Part.makePolygon(lProfile)
                struct = Part.Face(pol)
                evec = vWidth
                if obj.StructureOffset.Value:
                    mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                    struct.translate(mvec)
                    evec = DraftVecUtils.scaleTo(evec,evec.Length-(2*mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer","Two stringers"]:
            if obj.StringerWidth.Value and obj.StructureThickness.Value:
                hyp = math.sqrt(vHeight.Length**2 + vLength.Length**2)
                l1 = Vector(vLength).multiply(numberofsteps-1)
                h1 = Vector(vHeight).multiply(numberofsteps-1).add(Vector(0,0,-abs(obj.TreadThickness.Value)))
                p1 = vBase.add(l1).add(h1)
                p1 = self.align(p1,obj.Align,vWidth)
                lProfile.append(p1)
                h2 = (obj.StructureThickness.Value/vLength.Length)*hyp
                lProfile.append(lProfile[-1].add(Vector(0,0,-abs(h2))))
                h3 = lProfile[-1].z-vBase.z
                l3 = (h3/vHeight.Length)*vLength.Length
                v3 = DraftVecUtils.scaleTo(vLength,-l3)
                lProfile.append(lProfile[-1].add(Vector(0,0,-abs(h3))).add(v3))
                l4 = (obj.StructureThickness.Value/vHeight.Length)*hyp
                v4 = DraftVecUtils.scaleTo(vLength,-l4)
                lProfile.append(lProfile[-1].add(v4))
                lProfile.append(lProfile[0])
                #print lProfile
                pol = Part.makePolygon(lProfile)
                pol = Part.Face(pol)
                evec = DraftVecUtils.scaleTo(vWidth,obj.StringerWidth.Value)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                    else:
                        mvec = DraftVecUtils.scaleTo(vWidth,(vWidth.Length/2)-obj.StringerWidth.Value/2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1,s2])
        if struct:
            self.structures.append(struct)
Exemplo n.º 5
0
    def makeStraightLanding(self,obj,edge,numberofsteps=None):

        "builds a landing from a straight edge"

        # general data
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        import Part,DraftGeomUtils
        v = DraftGeomUtils.vec(edge)
        vLength = Vector(v.x,v.y,0)
        vWidth = vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0,0,1)),obj.Width.Value)
        vBase = edge.Vertexes[0].Point
        vNose = DraftVecUtils.scaleTo(vLength,-abs(obj.Nosing.Value))
        h = obj.Height.Value
        l = obj.Length.Value
        if obj.Base:
            if obj.Base.isDerivedFrom("Part::Feature"):
                l = obj.Base.Shape.Length
                if obj.Base.Shape.BoundBox.ZLength:
                    h = obj.Base.Shape.BoundBox.ZLength
        fLength = float(l-obj.Width.Value)/(numberofsteps-2)
        fHeight = float(h)/numberofsteps
        a = math.atan(fHeight/fLength)
        print "landing data:",fLength,":",fHeight

        # step
        p1 = self.align(vBase,obj.Align,vWidth)
        p1 = p1.add(vNose).add(Vector(0,0,-abs(obj.TreadThickness.Value)))
        p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
        p3 = p2.add(vWidth)
        p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
        step = Part.Face(Part.makePolygon([p1,p2,p3,p4,p1]))
        if obj.TreadThickness.Value:
            step = step.extrude(Vector(0,0,abs(obj.TreadThickness.Value)))
            self.steps.append(step)
        else:
            self.pseudosteps.append(step)

        # structure
        lProfile = []
        struct = None
        p7 = None
        p1 = p1.add(DraftVecUtils.neg(vNose))
        p2 = p1.add(Vector(0,0,-fHeight)).add(Vector(0,0,-obj.StructureThickness.Value/math.cos(a)))
        resheight = p1.sub(p2).Length - obj.StructureThickness.Value
        reslength = resheight / math.tan(a)
        p3 = p2.add(DraftVecUtils.scaleTo(vLength,reslength)).add(Vector(0,0,resheight))
        p6 = p1.add(vLength)
        if obj.TreadThickness.Value:
            p7 = p6.add(Vector(0,0,obj.TreadThickness.Value))

        reslength = fLength + (obj.StructureThickness.Value/math.sin(a)-(fHeight-obj.TreadThickness.Value)/math.tan(a))
        if p7:
            p5 = p7.add(DraftVecUtils.scaleTo(vLength,reslength))
        else:
            p5 = p6.add(DraftVecUtils.scaleTo(vLength,reslength))
        resheight = obj.StructureThickness.Value + obj.TreadThickness.Value
        reslength = resheight/math.tan(a)
        p4 = p5.add(DraftVecUtils.scaleTo(vLength,-reslength)).add(Vector(0,0,-resheight))
        if obj.Structure == "Massive":
            if obj.StructureThickness.Value:
                if p7:
                    struct = Part.Face(Part.makePolygon([p1,p2,p3,p4,p5,p7,p6,p1]))
                else:
                    struct = Part.Face(Part.makePolygon([p1,p2,p3,p4,p5,p6,p1]))
                evec = vWidth
                if obj.StructureOffset.Value:
                    mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                    struct.translate(mvec)
                    evec = DraftVecUtils.scaleTo(evec,evec.Length-(2*mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer","Two stringers"]:
            if obj.StringerWidth.Value and obj.StructureThickness.Value:
                p1b = p1.add(Vector(0,0,-fHeight))
                reslength = fHeight/math.tan(a)
                p1c = p1.add(DraftVecUtils.scaleTo(vLength,reslength))
                p5b = None
                p5c = None
                if obj.TreadThickness.Value:
                    reslength = obj.StructureThickness.Value/math.sin(a)
                    p5b = p5.add(DraftVecUtils.scaleTo(vLength,-reslength))
                    reslength = obj.TreadThickness.Value/math.tan(a)
                    p5c = p5b.add(DraftVecUtils.scaleTo(vLength,-reslength)).add(Vector(0,0,-obj.TreadThickness.Value))
                    pol = Part.Face(Part.makePolygon([p1c,p1b,p2,p3,p4,p5,p5b,p5c,p1c]))
                else:
                    pol = Part.Face(Part.makePolygon([p1c,p1b,p2,p3,p4,p5,p1c]))
                evec = DraftVecUtils.scaleTo(vWidth,obj.StringerWidth.Value)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                    else:
                        mvec = DraftVecUtils.scaleTo(vWidth,(vWidth.Length/2)-obj.StringerWidth.Value/2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,obj.StructureOffset.Value)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1,s2])
        if struct:
            self.structures.append(struct)
class plane:
    '''A WorkPlane object'''

    def __init__(self):
        # keep track of active document.  Reset view when doc changes.
        self.doc = None
        # self.weak is true if the plane has been defined by self.setup or has been reset
        self.weak = True
        # u, v axes and position define plane, perpendicular axis is handy, though redundant.
        self.u = Vector(1,0,0)
        self.v = Vector(0,1,0)
        self.axis = Vector(0,0,1)
        self.position = Vector(0,0,0)
        # a placeholder for a stored state
        self.stored = None

    def __repr__(self):
        return "Workplane x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))

    def offsetToPoint(self, p, direction=None):
        '''
        Return the signed distance from p to the plane, such
        that p + offsetToPoint(p)*direction lies on the plane.
        direction defaults to -plane.axis
        '''

        '''
        A picture will help explain the computation:

                                                            p
                                                          //|
                                                        / / |
                                              /  /  |
                            /   /   |
                          /    /    |
        -------------------- plane -----c-----x-----a--------

                Here p is the specified point,
                     c is a point (in this case plane.position) on the plane
                     x is the intercept on the plane from p in the specified direction, and
                     a is the perpendicular intercept on the plane (i.e. along plane.axis)

            Using vertival bars to denote the length operator,
                     |ap| = |cp| * cos(apc) = |xp| * cos(apx)
                so
                     |xp| = |cp| * cos(apc) / cos(apx)
                          = (cp . axis) / (direction . axis)
        '''
        if direction == None: direction = self.axis
        return direction.dot(self.position.sub(p))

    def projectPoint(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal'''
        if not direction:
            direction = self.axis
        lp = self.getLocalCoords(p)
        gp = self.getGlobalCoords(Vector(lp.x,lp.y,0))
        a = direction.getAngle(gp.sub(p))
        if a > math.pi/2:
            direction = direction.negative()
            a = math.pi - a
        ld = self.getLocalRot(direction)
        gd = self.getGlobalRot(Vector(ld.x,ld.y,0))
        hyp = abs(math.tan(a) * lp.z)
        return gp.add(DraftVecUtils.scaleTo(gd,hyp))
                
    def projectPointOld(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal. Obsolete'''
        if not direction:
            direction = self.axis
        t = Vector(direction)
        #t.normalize()
        a = round(t.getAngle(self.axis),DraftVecUtils.precision())
        pp = round((math.pi)/2,DraftVecUtils.precision())
        if a == pp:
            return p
        t.multiply(self.offsetToPoint(p, direction))
        return p.add(t)

    def alignToPointAndAxis(self, point, axis, offset, upvec=None):
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis;
        self.axis.normalize()
        if (DraftVecUtils.equals(axis, Vector(1,0,0))):
            self.u = Vector(0,1,0)
            self.v = Vector(0,0,1)
        elif (DraftVecUtils.equals(axis, Vector(-1,0,0))):
            self.u = Vector(0,-1,0)
            self.v = Vector(0,0,1)
        elif upvec:
            self.v = upvec
            self.v.normalize()
            self.u = self.v.cross(self.axis)
        else:
            self.v = axis.cross(Vector(1,0,0))
            self.v.normalize()
            self.u = DraftVecUtils.rotate(self.v, -math.pi/2, self.axis)
        offsetVector = Vector(axis); offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # FreeCAD.Console.PrintMessage("Current workplane: x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))+"\n")

    def alignToCurve(self, shape, offset):
        if shape.ShapeType == 'Edge':
            #??? TODO: process curve here.  look at shape.edges[0].Curve
            return False
        elif shape.ShapeType == 'Wire':
            #??? TODO: determine if edges define a plane
            return False
        else:
            return False

    def alignToEdges(self,edges):
        # use a list of edges to find a plane position
        if len(edges) > 2:
            return False
        # for axes systems, we suppose the 2 first edges are parallel
        # ??? TODO: exclude other cases first
        v1 = edges[0].Vertexes[-1].Point.sub(edges[0].Vertexes[0].Point)
        v2 = edges[1].Vertexes[0].Point.sub(edges[0].Vertexes[0].Point)
        v3 = v1.cross(v2)
        v1.normalize()
        v2.normalize()
        v3.normalize()
        #print v1,v2,v3
        self.u = v1
        self.v = v2
        self.axis = v3

    def alignToFace(self, shape, offset=0):
        # Set face to the unique selected face, if found
        if shape.ShapeType == 'Face':
            #we should really use face.tangentAt to get u and v here, and implement alignToUVPoint
            self.alignToPointAndAxis(shape.Faces[0].CenterOfMass, shape.Faces[0].normalAt(0,0), offset)
            return True
        else:
            return False

    def alignToSelection(self, offset):
        '''If selection uniquely defines a plane, align working plane to it.  Return success (bool)'''
        sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
        if len(sex) == 0:
            return False
        elif len(sex) == 1:
            if not sex[0].Object.isDerivedFrom("Part::Shape"):
                return False
            return self.alignToCurve(sex[0].Object.Shape, offset) \
                or self.alignToFace(sex[0].Object.Shape, offset) \
                or (len(sex[0].SubObjects) == 1 and self.alignToFace(sex[0].SubObjects[0], offset))
        else:
            # len(sex) > 2, look for point and line, three points, etc.
            return False

    def setup(self, direction=None, point=None, upvec=None):
        '''If working plane is undefined, define it!'''
        if self.weak:
            if direction and point:
                self.alignToPointAndAxis(point, direction, 0, upvec)
            else:
                try:
                    from pivy import coin
                    rot = FreeCADGui.ActiveDocument.ActiveView.getCameraNode().getField("orientation").getValue()
                    upvec = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue())
                    vdir = FreeCADGui.ActiveDocument.ActiveView.getViewDirection()
                    self.alignToPointAndAxis(Vector(0,0,0), vdir.negative(), 0, upvec)
                except:
                    print "Draft: Unable to align the working plane to the current view"
            self.weak = True

    def reset(self):
        self.doc = None
        self.weak = True

    def getRotation(self):
        "returns a placement describing the working plane orientation ONLY"
        m = DraftVecUtils.getPlaneRotation(self.u,self.v,self.axis)
        return FreeCAD.Placement(m)

    def getPlacement(self,rotated=False):
        "returns the placement of the working plane"
        if rotated:
            m = FreeCAD.Matrix(
                self.u.x,self.axis.x,-self.v.x,self.position.x,
                self.u.y,self.axis.y,-self.v.y,self.position.y,
                self.u.z,self.axis.z,-self.v.z,self.position.z,
                0.0,0.0,0.0,1.0)
        else:
            m = FreeCAD.Matrix(
                self.u.x,self.v.x,self.axis.x,self.position.x,
                self.u.y,self.v.y,self.axis.y,self.position.y,
                self.u.z,self.v.z,self.axis.z,self.position.z,
                0.0,0.0,0.0,1.0)
        return FreeCAD.Placement(m)

    def setFromPlacement(self,pl):
        "sets the working plane from a placement (rotaton ONLY)"
        rot = FreeCAD.Placement(pl).Rotation
        self.u = rot.multVec(FreeCAD.Vector(1,0,0))
        self.v = rot.multVec(FreeCAD.Vector(0,1,0))
        self.axis = rot.multVec(FreeCAD.Vector(0,0,1))

    def save(self):
        "stores the current plane state"
        self.stored = [self.u,self.v,self.axis,self.position,self.weak]

    def restore(self):
        "restores a previously saved plane state, if exists"
        if self.stored:
            self.u = self.stored[0]
            self.v = self.stored[1]
            self.axis = self.stored[2]
            self.position = self.stored[3]
            self.weak = self.stored[4]
            self.stored = None

    def getLocalCoords(self,point):
        "returns the coordinates of a given point on the working plane"
        pt = point.sub(self.position)
        xv = DraftVecUtils.project(pt,self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
                x = -x
        yv = DraftVecUtils.project(pt,self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
                y = -y
        zv = DraftVecUtils.project(pt,self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
                z = -z
        return Vector(x,y,z)

    def getGlobalCoords(self,point):
        "returns the global coordinates of the given point, taken relatively to this working plane"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt.add(self.position)

    def getLocalRot(self,point):
        "Same as getLocalCoords, but discards the WP position"
        xv = DraftVecUtils.project(point,self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
                x = -x
        yv = DraftVecUtils.project(point,self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
                y = -y
        zv = DraftVecUtils.project(point,self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
                z = -z
        return Vector(x,y,z)

    def getGlobalRot(self,point):
        "Same as getGlobalCoords, but discards the WP position"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt
        
    def getClosestAxis(self,point):
        "returns which of the workingplane axes is closest from the given vector"
        ax = point.getAngle(self.u)
        ay = point.getAngle(self.v)
        az = point.getAngle(self.axis)
        bx = point.getAngle(self.u.negative())
        by = point.getAngle(self.v.negative())
        bz = point.getAngle(self.axis.negative())
        b = min(ax,ay,az,bx,by,bz)
        if b in [ax,bx]:
            return "x"
        elif b in [ay,by]:
            return "y"
        elif b in [az,bz]:
            return "z"
        else:
            return None
            
    def isGlobal(self):
        "returns True if the plane axes are equal to the global axes"
        if self.u != Vector(1,0,0):
            return False
        if self.v != Vector(0,1,0):
            return False
        if self.axis != Vector(0,0,1):
            return False
        return True
Exemplo n.º 7
0
class plane:
    '''A WorkPlane object'''

    def __init__(self,u=Vector(1,0,0),v=Vector(0,1,0),w=Vector(0,0,1),pos=Vector(0,0,0)):
        # keep track of active document.  Reset view when doc changes.
        self.doc = None
        # self.weak is true if the plane has been defined by self.setup or has been reset
        self.weak = True
        # u, v axes and position define plane, perpendicular axis is handy, though redundant.
        self.u = u
        self.v = v
        self.axis = w
        self.position = pos
        # a placeholder for a stored state
        self.stored = None

    def __repr__(self):
        return "Workplane x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))

    def copy(self):
        return plane(u=self.u,v=self.v,w=self.axis,pos=self.position)

    def offsetToPoint(self, p, direction=None):
        '''
        Return the signed distance from p to the plane, such
        that p + offsetToPoint(p)*direction lies on the plane.
        direction defaults to -plane.axis
        '''

        '''
        A picture will help explain the computation:

                                                            p
                                                          //|
                                                        / / |
                                              /  /  |
                            /   /   |
                          /    /    |
        -------------------- plane -----c-----x-----a--------

                Here p is the specified point,
                     c is a point (in this case plane.position) on the plane
                     x is the intercept on the plane from p in the specified direction, and
                     a is the perpendicular intercept on the plane (i.e. along plane.axis)

            Using vertival bars to denote the length operator,
                     |ap| = |cp| * cos(apc) = |xp| * cos(apx)
                so
                     |xp| = |cp| * cos(apc) / cos(apx)
                          = (cp . axis) / (direction . axis)
        '''
        if direction == None: direction = self.axis
        return direction.dot(self.position.sub(p))

    def projectPoint(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal'''
        if not direction:
            direction = self.axis
        lp = self.getLocalCoords(p)
        gp = self.getGlobalCoords(Vector(lp.x,lp.y,0))
        a = direction.getAngle(gp.sub(p))
        if a > math.pi/2:
            direction = direction.negative()
            a = math.pi - a
        ld = self.getLocalRot(direction)
        gd = self.getGlobalRot(Vector(ld.x,ld.y,0))
        hyp = abs(math.tan(a) * lp.z)
        return gp.add(DraftVecUtils.scaleTo(gd,hyp))
                
    def projectPointOld(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal. Obsolete'''
        if not direction:
            direction = self.axis
        t = Vector(direction)
        #t.normalize()
        a = round(t.getAngle(self.axis),DraftVecUtils.precision())
        pp = round((math.pi)/2,DraftVecUtils.precision())
        if a == pp:
            return p
        t.multiply(self.offsetToPoint(p, direction))
        return p.add(t)

    def alignToPointAndAxis(self, point, axis, offset=0, upvec=None):
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis;
        self.axis.normalize()
        if axis.getAngle(Vector(1,0,0)) < 0.00001:
            self.axis = Vector(1,0,0)
            self.u = Vector(0,1,0)
            self.v = Vector(0,0,1)
        elif axis.getAngle(Vector(-1,0,0)) < 0.00001:
            self.axis = Vector(-1,0,0)
            self.u = Vector(0,-1,0)
            self.v = Vector(0,0,1)
        elif upvec:
            self.v = upvec
            self.v.normalize()
            self.u = self.v.cross(self.axis)
        else:
            self.v = axis.cross(Vector(1,0,0))
            self.v.normalize()
            self.u = DraftVecUtils.rotate(self.v, -math.pi/2, self.axis)
        offsetVector = Vector(axis); offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # FreeCAD.Console.PrintMessage("Current workplane: x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))+"\n")

    def alignToPointAndAxis_SVG(self, point, axis, offset):
        # based on cases table
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis;
        self.axis.normalize()
        ref_vec = Vector(0.0, 1.0, 0.0)

        if ((abs(axis.x) > abs(axis.y)) and (abs(axis.y) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "Case new"
        
        elif ((abs(axis.y) > abs(axis.z)) and (abs(axis.z) >= abs(axis.x))):
            ref_vec = Vector(1.0, 0.0, 0.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "Y>Z, View Y"

        elif ((abs(axis.y) >= abs(axis.x)) and (abs(axis.x) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "ehem. XY, Case XY"

        elif ((abs(axis.x) > abs(axis.z)) and (abs(axis.z) >= abs(axis.y))):
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "X>Z, View X"

        elif ((abs(axis.z) >= abs(axis.y)) and (abs(axis.y) > abs(axis.x))):
            ref_vec = Vector(1.0, 0., 0.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "Y>X, Case YZ"

        else:
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi/2, self.axis)
            #projcase = "else"

        #spat_vec = self.u.cross(self.v)
        #spat_res = spat_vec.dot(axis)
        #FreeCAD.Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n")
        
        offsetVector = Vector(axis); offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # FreeCAD.Console.PrintMessage("Current workplane: x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))+"\n")


    def alignToCurve(self, shape, offset):
        if shape.ShapeType == 'Edge':
            #??? TODO: process curve here.  look at shape.edges[0].Curve
            return False
        elif shape.ShapeType == 'Wire':
            #??? TODO: determine if edges define a plane
            return False
        else:
            return False

    def alignToEdges(self,edges):
        # use a list of edges to find a plane position
        if len(edges) > 2:
            return False
        # for axes systems, we suppose the 2 first edges are parallel
        # ??? TODO: exclude other cases first
        v1 = edges[0].Vertexes[-1].Point.sub(edges[0].Vertexes[0].Point)
        v2 = edges[1].Vertexes[0].Point.sub(edges[0].Vertexes[0].Point)
        v3 = v1.cross(v2)
        v1.normalize()
        v2.normalize()
        v3.normalize()
        #print v1,v2,v3
        self.u = v1
        self.v = v2
        self.axis = v3

    def alignToFace(self, shape, offset=0):
        # Set face to the unique selected face, if found
        if shape.ShapeType == 'Face':
            self.alignToPointAndAxis(shape.Faces[0].CenterOfMass, shape.Faces[0].normalAt(0,0), offset)
            import DraftGeomUtils
            q = DraftGeomUtils.getQuad(shape)
            if q:
                self.u = q[1]
                self.v = q[2]
                if not DraftVecUtils.equals(self.u.cross(self.v),self.axis):
                    self.u = q[2]
                    self.v = q[1]
                if DraftVecUtils.equals(self.u,Vector(0,0,1)):
                    # the X axis is vertical: rotate 90 degrees
                    self.u,self.v = self.v.negative(),self.u
                elif DraftVecUtils.equals(self.u,Vector(0,0,-1)):
                    self.u,self.v = self.v,self.u.negative()
                    
            self.weak = False
            return True
        else:
            return False

    def alignTo3Points(self,p1,p2,p3,offset=0):
        import Part
        w = Part.makePolygon([p1,p2,p3,p1])
        f = Part.Face(w)
        return self.alignToFace(f,offset)

    def alignToSelection(self, offset):
        '''If selection uniquely defines a plane, align working plane to it.  Return success (bool)'''
        import FreeCADGui
        sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
        if len(sex) == 0:
            return False
        elif len(sex) == 1:
            if not sex[0].Object.isDerivedFrom("Part::Shape"):
                return False
            return self.alignToCurve(sex[0].Object.Shape, offset) \
                or self.alignToFace(sex[0].Object.Shape, offset) \
                or (len(sex[0].SubObjects) == 1 and self.alignToFace(sex[0].SubObjects[0], offset))
        else:
            # len(sex) > 2, look for point and line, three points, etc.
            return False

    def setup(self, direction=None, point=None, upvec=None):
        '''If working plane is undefined, define it!'''
        if self.weak:
            if direction and point:
                self.alignToPointAndAxis(point, direction, 0, upvec)
            else:
                try:
                    import FreeCADGui
                    from pivy import coin
                    rot = FreeCADGui.ActiveDocument.ActiveView.getCameraNode().getField("orientation").getValue()
                    upvec = Vector(rot.multVec(coin.SbVec3f(0,1,0)).getValue())
                    vdir = FreeCADGui.ActiveDocument.ActiveView.getViewDirection()
                    self.alignToPointAndAxis(Vector(0,0,0), vdir.negative(), 0, upvec)
                except:
                    pass
            self.weak = True

    def reset(self):
        self.doc = None
        self.weak = True

    def getRotation(self):
        "returns a placement describing the working plane orientation ONLY"
        m = DraftVecUtils.getPlaneRotation(self.u,self.v,self.axis)
        p = FreeCAD.Placement(m)
        # Arch active container
        if FreeCAD.GuiUp:
            import FreeCADGui
            if FreeCADGui.ActiveDocument.ActiveView:
                a = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch")
                if a:
                    p = a.Placement.inverse().multiply(p)
        return p

    def getPlacement(self,rotated=False):
        "returns the placement of the working plane"
        if rotated:
            m = FreeCAD.Matrix(
                self.u.x,self.axis.x,-self.v.x,self.position.x,
                self.u.y,self.axis.y,-self.v.y,self.position.y,
                self.u.z,self.axis.z,-self.v.z,self.position.z,
                0.0,0.0,0.0,1.0)
        else:
            m = FreeCAD.Matrix(
                self.u.x,self.v.x,self.axis.x,self.position.x,
                self.u.y,self.v.y,self.axis.y,self.position.y,
                self.u.z,self.v.z,self.axis.z,self.position.z,
                0.0,0.0,0.0,1.0)
        p = FreeCAD.Placement(m)
        # Arch active container if based on App Part
        #if FreeCAD.GuiUp:
        #    import FreeCADGui
        #    a = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch")
        #    if a:
        #        p = a.Placement.inverse().multiply(p)
        return p

    def getNormal(self):
        n = self.axis
        # Arch active container if based on App Part
        #if FreeCAD.GuiUp:
        #    import FreeCADGui
        #    a = FreeCADGui.ActiveDocument.ActiveView.getActiveObject("Arch")
        #    if a:
        #        n = a.Placement.inverse().Rotation.multVec(n)
        return n

    def setFromPlacement(self,pl,rebase=False):
        "sets the working plane from a placement (rotaton ONLY, unless rebase=True)"
        rot = FreeCAD.Placement(pl).Rotation
        self.u = rot.multVec(FreeCAD.Vector(1,0,0))
        self.v = rot.multVec(FreeCAD.Vector(0,1,0))
        self.axis = rot.multVec(FreeCAD.Vector(0,0,1))
        if rebase:
            self.position = pl.Base
        
    def inverse(self):
        "inverts the direction of the working plane"
        self.u = self.u.negative()
        self.axis = self.axis.negative()

    def save(self):
        "stores the current plane state"
        self.stored = [self.u,self.v,self.axis,self.position,self.weak]

    def restore(self):
        "restores a previously saved plane state, if exists"
        if self.stored:
            self.u = self.stored[0]
            self.v = self.stored[1]
            self.axis = self.stored[2]
            self.position = self.stored[3]
            self.weak = self.stored[4]
            self.stored = None

    def getLocalCoords(self,point):
        "returns the coordinates of a given point on the working plane"
        pt = point.sub(self.position)
        xv = DraftVecUtils.project(pt,self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
                x = -x
        yv = DraftVecUtils.project(pt,self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
                y = -y
        zv = DraftVecUtils.project(pt,self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
                z = -z
        return Vector(x,y,z)

    def getGlobalCoords(self,point):
        "returns the global coordinates of the given point, taken relatively to this working plane"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt.add(self.position)

    def getLocalRot(self,point):
        "Same as getLocalCoords, but discards the WP position"
        xv = DraftVecUtils.project(point,self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
                x = -x
        yv = DraftVecUtils.project(point,self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
                y = -y
        zv = DraftVecUtils.project(point,self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
                z = -z
        return Vector(x,y,z)

    def getGlobalRot(self,point):
        "Same as getGlobalCoords, but discards the WP position"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt
        
    def getClosestAxis(self,point):
        "returns which of the workingplane axes is closest from the given vector"
        ax = point.getAngle(self.u)
        ay = point.getAngle(self.v)
        az = point.getAngle(self.axis)
        bx = point.getAngle(self.u.negative())
        by = point.getAngle(self.v.negative())
        bz = point.getAngle(self.axis.negative())
        b = min(ax,ay,az,bx,by,bz)
        if b in [ax,bx]:
            return "x"
        elif b in [ay,by]:
            return "y"
        elif b in [az,bz]:
            return "z"
        else:
            return None
            
    def isGlobal(self):
        "returns True if the plane axes are equal to the global axes"
        if self.u != Vector(1,0,0):
            return False
        if self.v != Vector(0,1,0):
            return False
        if self.axis != Vector(0,0,1):
            return False
        return True
        
    def isOrtho(self):
        "returns True if the plane axes are following the global axes"
        if round(self.u.getAngle(Vector(0,1,0)),6) in [0,-1.570796,1.570796,-3.141593,3.141593,-4.712389,4.712389,6.283185]:
            if round(self.v.getAngle(Vector(0,1,0)),6) in [0,-1.570796,1.570796,-3.141593,3.141593,-4.712389,4.712389,6.283185]:
                if round(self.axis.getAngle(Vector(0,1,0)),6) in [0,-1.570796,1.570796,-3.141593,3.141593,-4.712389,4.712389,6.283185]:
                    return True
        return False
        
    def getDeviation(self):
        "returns the deviation angle between the u axis and the horizontal plane"
        proj = Vector(self.u.x,self.u.y,0)
        if self.u.getAngle(proj) == 0:
            return 0
        else:
            norm = proj.cross(self.u)
            return DraftVecUtils.angle(self.u,proj,norm)
Exemplo n.º 8
0
class plane:
    '''A WorkPlane object'''
    def __init__(self,
                 u=Vector(1, 0, 0),
                 v=Vector(0, 1, 0),
                 w=Vector(0, 0, 1),
                 pos=Vector(0, 0, 0)):
        # keep track of active document.  Reset view when doc changes.
        self.doc = None
        # self.weak is true if the plane has been defined by self.setup or has been reset
        self.weak = True
        # u, v axes and position define plane, perpendicular axis is handy, though redundant.
        self.u = u
        self.v = v
        self.axis = w
        self.position = pos
        # a placeholder for a stored state
        self.stored = None

    def __repr__(self):
        return "Workplane x=" + str(DraftVecUtils.rounded(
            self.u)) + " y=" + str(DraftVecUtils.rounded(
                self.v)) + " z=" + str(DraftVecUtils.rounded(self.axis))

    def offsetToPoint(self, p, direction=None):
        '''
        Return the signed distance from p to the plane, such
        that p + offsetToPoint(p)*direction lies on the plane.
        direction defaults to -plane.axis
        '''
        '''
        A picture will help explain the computation:

                                                            p
                                                          //|
                                                        / / |
                                              /  /  |
                            /   /   |
                          /    /    |
        -------------------- plane -----c-----x-----a--------

                Here p is the specified point,
                     c is a point (in this case plane.position) on the plane
                     x is the intercept on the plane from p in the specified direction, and
                     a is the perpendicular intercept on the plane (i.e. along plane.axis)

            Using vertival bars to denote the length operator,
                     |ap| = |cp| * cos(apc) = |xp| * cos(apx)
                so
                     |xp| = |cp| * cos(apc) / cos(apx)
                          = (cp . axis) / (direction . axis)
        '''
        if direction == None: direction = self.axis
        return direction.dot(self.position.sub(p))

    def projectPoint(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal'''
        if not direction:
            direction = self.axis
        lp = self.getLocalCoords(p)
        gp = self.getGlobalCoords(Vector(lp.x, lp.y, 0))
        a = direction.getAngle(gp.sub(p))
        if a > math.pi / 2:
            direction = direction.negative()
            a = math.pi - a
        ld = self.getLocalRot(direction)
        gd = self.getGlobalRot(Vector(ld.x, ld.y, 0))
        hyp = abs(math.tan(a) * lp.z)
        return gp.add(DraftVecUtils.scaleTo(gd, hyp))

    def projectPointOld(self, p, direction=None):
        '''project point onto plane, default direction is orthogonal. Obsolete'''
        if not direction:
            direction = self.axis
        t = Vector(direction)
        #t.normalize()
        a = round(t.getAngle(self.axis), DraftVecUtils.precision())
        pp = round((math.pi) / 2, DraftVecUtils.precision())
        if a == pp:
            return p
        t.multiply(self.offsetToPoint(p, direction))
        return p.add(t)

    def alignToPointAndAxis(self, point, axis, offset=0, upvec=None):
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis
        self.axis.normalize()
        if axis.getAngle(Vector(1, 0, 0)) < 0.00001:
            self.axis = Vector(1, 0, 0)
            self.u = Vector(0, 1, 0)
            self.v = Vector(0, 0, 1)
        elif axis.getAngle(Vector(-1, 0, 0)) < 0.00001:
            self.axis = Vector(-1, 0, 0)
            self.u = Vector(0, -1, 0)
            self.v = Vector(0, 0, 1)
        elif upvec:
            self.v = upvec
            self.v.normalize()
            self.u = self.v.cross(self.axis)
        else:
            self.v = axis.cross(Vector(1, 0, 0))
            self.v.normalize()
            self.u = DraftVecUtils.rotate(self.v, -math.pi / 2, self.axis)
        offsetVector = Vector(axis)
        offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # FreeCAD.Console.PrintMessage("Current workplane: x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))+"\n")

    def alignToPointAndAxis_SVG(self, point, axis, offset):
        # based on cases table
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis
        self.axis.normalize()
        ref_vec = Vector(0.0, 1.0, 0.0)

        if ((abs(axis.x) > abs(axis.y)) and (abs(axis.y) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "Case new"

        elif ((abs(axis.y) > abs(axis.z)) and (abs(axis.z) >= abs(axis.x))):
            ref_vec = Vector(1.0, 0.0, 0.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "Y>Z, View Y"

        elif ((abs(axis.y) >= abs(axis.x)) and (abs(axis.x) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "ehem. XY, Case XY"

        elif ((abs(axis.x) > abs(axis.z)) and (abs(axis.z) >= abs(axis.y))):
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "X>Z, View X"

        elif ((abs(axis.z) >= abs(axis.y)) and (abs(axis.y) > abs(axis.x))):
            ref_vec = Vector(1.0, 0., 0.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "Y>X, Case YZ"

        else:
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            #projcase = "else"

        #spat_vec = self.u.cross(self.v)
        #spat_res = spat_vec.dot(axis)
        #FreeCAD.Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n")

        offsetVector = Vector(axis)
        offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # FreeCAD.Console.PrintMessage("Current workplane: x="+str(DraftVecUtils.rounded(self.u))+" y="+str(DraftVecUtils.rounded(self.v))+" z="+str(DraftVecUtils.rounded(self.axis))+"\n")

    def alignToCurve(self, shape, offset):
        if shape.ShapeType == 'Edge':
            #??? TODO: process curve here.  look at shape.edges[0].Curve
            return False
        elif shape.ShapeType == 'Wire':
            #??? TODO: determine if edges define a plane
            return False
        else:
            return False

    def alignToEdges(self, edges):
        # use a list of edges to find a plane position
        if len(edges) > 2:
            return False
        # for axes systems, we suppose the 2 first edges are parallel
        # ??? TODO: exclude other cases first
        v1 = edges[0].Vertexes[-1].Point.sub(edges[0].Vertexes[0].Point)
        v2 = edges[1].Vertexes[0].Point.sub(edges[0].Vertexes[0].Point)
        v3 = v1.cross(v2)
        v1.normalize()
        v2.normalize()
        v3.normalize()
        #print v1,v2,v3
        self.u = v1
        self.v = v2
        self.axis = v3

    def alignToFace(self, shape, offset=0):
        # Set face to the unique selected face, if found
        if shape.ShapeType == 'Face':
            self.alignToPointAndAxis(shape.Faces[0].CenterOfMass,
                                     shape.Faces[0].normalAt(0, 0), offset)
            import DraftGeomUtils
            q = DraftGeomUtils.getQuad(shape)
            if q:
                self.u = q[1]
                self.v = q[2]
                if not DraftVecUtils.equals(self.u.cross(self.v), self.axis):
                    self.u = q[2]
                    self.v = q[1]
                if DraftVecUtils.equals(self.u, Vector(0, 0, 1)):
                    # the X axis is vertical: rotate 90 degrees
                    self.u, self.v = self.v.negative(), self.u
                elif DraftVecUtils.equals(self.u, Vector(0, 0, -1)):
                    self.u, self.v = self.v, self.u.negative()

            self.weak = False
            return True
        else:
            return False

    def alignToSelection(self, offset):
        '''If selection uniquely defines a plane, align working plane to it.  Return success (bool)'''
        import FreeCADGui
        sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
        if len(sex) == 0:
            return False
        elif len(sex) == 1:
            if not sex[0].Object.isDerivedFrom("Part::Shape"):
                return False
            return self.alignToCurve(sex[0].Object.Shape, offset) \
                or self.alignToFace(sex[0].Object.Shape, offset) \
                or (len(sex[0].SubObjects) == 1 and self.alignToFace(sex[0].SubObjects[0], offset))
        else:
            # len(sex) > 2, look for point and line, three points, etc.
            return False

    def setup(self, direction=None, point=None, upvec=None):
        '''If working plane is undefined, define it!'''
        if self.weak:
            if direction and point:
                self.alignToPointAndAxis(point, direction, 0, upvec)
            else:
                try:
                    import FreeCADGui
                    from pivy import coin
                    rot = FreeCADGui.ActiveDocument.ActiveView.getCameraNode(
                    ).getField("orientation").getValue()
                    upvec = Vector(
                        rot.multVec(coin.SbVec3f(0, 1, 0)).getValue())
                    vdir = FreeCADGui.ActiveDocument.ActiveView.getViewDirection(
                    )
                    self.alignToPointAndAxis(Vector(0, 0, 0), vdir.negative(),
                                             0, upvec)
                except:
                    pass
            self.weak = True

    def reset(self):
        self.doc = None
        self.weak = True

    def getRotation(self):
        "returns a placement describing the working plane orientation ONLY"
        m = DraftVecUtils.getPlaneRotation(self.u, self.v, self.axis)
        return FreeCAD.Placement(m)

    def getPlacement(self, rotated=False):
        "returns the placement of the working plane"
        if rotated:
            m = FreeCAD.Matrix(self.u.x, self.axis.x, -self.v.x,
                               self.position.x, self.u.y, self.axis.y,
                               -self.v.y, self.position.y, self.u.z,
                               self.axis.z, -self.v.z, self.position.z, 0.0,
                               0.0, 0.0, 1.0)
        else:
            m = FreeCAD.Matrix(self.u.x, self.v.x, self.axis.x,
                               self.position.x, self.u.y, self.v.y,
                               self.axis.y, self.position.y, self.u.z,
                               self.v.z, self.axis.z, self.position.z, 0.0,
                               0.0, 0.0, 1.0)
        return FreeCAD.Placement(m)

    def setFromPlacement(self, pl):
        "sets the working plane from a placement (rotaton ONLY)"
        rot = FreeCAD.Placement(pl).Rotation
        self.u = rot.multVec(FreeCAD.Vector(1, 0, 0))
        self.v = rot.multVec(FreeCAD.Vector(0, 1, 0))
        self.axis = rot.multVec(FreeCAD.Vector(0, 0, 1))

    def inverse(self):
        "inverts the direction of the working plane"
        self.u = self.u.negative()
        self.axis = self.axis.negative()

    def save(self):
        "stores the current plane state"
        self.stored = [self.u, self.v, self.axis, self.position, self.weak]

    def restore(self):
        "restores a previously saved plane state, if exists"
        if self.stored:
            self.u = self.stored[0]
            self.v = self.stored[1]
            self.axis = self.stored[2]
            self.position = self.stored[3]
            self.weak = self.stored[4]
            self.stored = None

    def getLocalCoords(self, point):
        "returns the coordinates of a given point on the working plane"
        pt = point.sub(self.position)
        xv = DraftVecUtils.project(pt, self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
            x = -x
        yv = DraftVecUtils.project(pt, self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
            y = -y
        zv = DraftVecUtils.project(pt, self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
            z = -z
        return Vector(x, y, z)

    def getGlobalCoords(self, point):
        "returns the global coordinates of the given point, taken relatively to this working plane"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt.add(self.position)

    def getLocalRot(self, point):
        "Same as getLocalCoords, but discards the WP position"
        xv = DraftVecUtils.project(point, self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
            x = -x
        yv = DraftVecUtils.project(point, self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
            y = -y
        zv = DraftVecUtils.project(point, self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
            z = -z
        return Vector(x, y, z)

    def getGlobalRot(self, point):
        "Same as getGlobalCoords, but discards the WP position"
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt

    def getClosestAxis(self, point):
        "returns which of the workingplane axes is closest from the given vector"
        ax = point.getAngle(self.u)
        ay = point.getAngle(self.v)
        az = point.getAngle(self.axis)
        bx = point.getAngle(self.u.negative())
        by = point.getAngle(self.v.negative())
        bz = point.getAngle(self.axis.negative())
        b = min(ax, ay, az, bx, by, bz)
        if b in [ax, bx]:
            return "x"
        elif b in [ay, by]:
            return "y"
        elif b in [az, bz]:
            return "z"
        else:
            return None

    def isGlobal(self):
        "returns True if the plane axes are equal to the global axes"
        if self.u != Vector(1, 0, 0):
            return False
        if self.v != Vector(0, 1, 0):
            return False
        if self.axis != Vector(0, 0, 1):
            return False
        return True

    def isOrtho(self):
        "returns True if the plane axes are following the global axes"
        if round(self.u.getAngle(Vector(0, 1, 0)), 6) in [
                0, -1.570796, 1.570796, -3.141593, 3.141593, -4.712389,
                4.712389, 6.283185
        ]:
            if round(self.v.getAngle(Vector(0, 1, 0)), 6) in [
                    0, -1.570796, 1.570796, -3.141593, 3.141593, -4.712389,
                    4.712389, 6.283185
            ]:
                if round(self.axis.getAngle(Vector(0, 1, 0)), 6) in [
                        0, -1.570796, 1.570796, -3.141593, 3.141593, -4.712389,
                        4.712389, 6.283185
                ]:
                    return True
        return False

    def getDeviation(self):
        "returns the deviation angle between the u axis and the horizontal plane"
        proj = Vector(self.u.x, self.u.y, 0)
        if self.u.getAngle(proj) == 0:
            return 0
        else:
            norm = proj.cross(self.u)
            return DraftVecUtils.angle(self.u, proj, norm)
Exemplo n.º 9
0
    def make_curves(self):
        item = ""
        edges=[]
        s=Gui.Selection.getSelectionEx()
        for i in s:
            for e in i.SubElementNames:
                edges.append(getattr(i.Object.Shape,e))


        sorted_edges = []
        sorted_edges = sortEdges(edges)
        #item += '#another test after sorted_edges \n'
        def isSameVertex(V1, V2):#borrowed from yorik's fcgeo.py- thanks yorik!
            ''' Test if vertexes have same coordinates with precision 10E(-precision)'''
            if round(V1.X-V2.X,1)==0 and round(V1.Y-V2.Y,1)==0 and round(V1.Z-V2.Z,1)==0 :
                return True
            else :
                return False

        start=sorted_edges[0]
        end=sorted_edges[-1]
        startingZ = start.Vertexes[0].Z
        #set starting depth to same Z as starting curve element
        self.form.lineEditStartDepth.setText(str(start.Vertexes[0].Z))
        item += "curve = area.Curve()\n"
        if isSameVertex(start.Vertexes[0],end.Vertexes[1]) :
            item += '#closed path\n'
            path = 'closedpath'
        else:
            item += '#open path\n'
            path = 'openpath'

        if path ==  'openpath' :
            item += "curve.append(area.Point(" + str(start.Vertexes[0].X) + "," + str(start.Vertexes[0].Y)+ "))\n"

        for s in sorted_edges:
            #edges.append(s)
            if (isinstance(s.Curve,Part.Circle)):
                mp = findMidpoint(s)
                ce = s.Curve.Center
                tang1 = s.Curve.tangent(s.ParameterRange[0]) ; tang2 = s.Curve.tangent(s.ParameterRange[1])
                cross1 = Vector.cross(Base.Vector(tang1[0][0],tang1[0][1],tang1[0][2]),Base.Vector(tang2[0][0],tang2[0][1],tang2[0][2]))
                if cross1[2] > 0:
                    direct = '1 ' #we seem to be working in a rh system in FreeCAD 
                else:
                    direct = '-1 ' 
                item += "curve.append(area.Vertex("+str(direct)+ ", area.Point( "+ str(s.Vertexes[-1].Point[0])+", "+str(s.Vertexes[-1].Point[1])+ "), area.Point("+str(s.Curve.Center [0])+ ", " + str(s.Curve.Center[1])+ ")))\n"

            elif (isinstance(s.Curve,Part.Line)):
                item += "curve.append(area.Point( "+str(s.Vertexes[-1].Point[0])+", " +str(s.Vertexes[-1].Point[1])+ "))\n"
            else:
                pass

        #export curve elements to heekscnc
        #to reverse the curve:

        #item += "curve.append(area.Point(" + str(end.Vertexes[0].X) + "," + str(end.Vertexes[0].Y) + "))"
        if path ==  'closedpath':
            item += "curve.append(area.Point(" + str(start.Vertexes[1].X) + "," + str(start.Vertexes[1].Y)+ "))\n"
            item += "curve.Reverse()\n"
        self.form.textEditCurve.append(item)
Exemplo n.º 10
0
    def makeStraightLanding(self, obj, edge, numberofsteps=None):

        "builds a landing from a straight edge"

        # general data
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        import Part, DraftGeomUtils
        v = DraftGeomUtils.vec(edge)
        vLength = Vector(v.x, v.y, 0)
        vWidth = vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0, 0, 1)),
                                                obj.Width.Value)
        vBase = edge.Vertexes[0].Point
        vNose = DraftVecUtils.scaleTo(vLength, -abs(obj.Nosing.Value))
        h = obj.Height.Value
        l = obj.Length.Value
        if obj.Base:
            if obj.Base.isDerivedFrom("Part::Feature"):
                l = obj.Base.Shape.Length
                if obj.Base.Shape.BoundBox.ZLength:
                    h = obj.Base.Shape.BoundBox.ZLength
        fLength = float(l - obj.Width.Value) / (numberofsteps - 2)
        fHeight = float(h) / numberofsteps
        a = math.atan(fHeight / fLength)
        print("landing data:", fLength, ":", fHeight)

        # step
        p1 = self.align(vBase, obj.Align, vWidth)
        p1 = p1.add(vNose).add(Vector(0, 0, -abs(obj.TreadThickness.Value)))
        p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
        p3 = p2.add(vWidth)
        p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
        step = Part.Face(Part.makePolygon([p1, p2, p3, p4, p1]))
        if obj.TreadThickness.Value:
            step = step.extrude(Vector(0, 0, abs(obj.TreadThickness.Value)))
            self.steps.append(step)
        else:
            self.pseudosteps.append(step)

        # structure
        lProfile = []
        struct = None
        p7 = None
        p1 = p1.add(DraftVecUtils.neg(vNose))
        p2 = p1.add(Vector(0, 0, -fHeight)).add(
            Vector(0, 0, -obj.StructureThickness.Value / math.cos(a)))
        resheight = p1.sub(p2).Length - obj.StructureThickness.Value
        reslength = resheight / math.tan(a)
        p3 = p2.add(DraftVecUtils.scaleTo(vLength, reslength)).add(
            Vector(0, 0, resheight))
        p6 = p1.add(vLength)
        if obj.TreadThickness.Value:
            p7 = p6.add(Vector(0, 0, obj.TreadThickness.Value))

        reslength = fLength + (
            obj.StructureThickness.Value / math.sin(a) -
            (fHeight - obj.TreadThickness.Value) / math.tan(a))
        if p7:
            p5 = p7.add(DraftVecUtils.scaleTo(vLength, reslength))
        else:
            p5 = p6.add(DraftVecUtils.scaleTo(vLength, reslength))
        resheight = obj.StructureThickness.Value + obj.TreadThickness.Value
        reslength = resheight / math.tan(a)
        p4 = p5.add(DraftVecUtils.scaleTo(vLength, -reslength)).add(
            Vector(0, 0, -resheight))
        if obj.Structure == "Massive":
            if obj.StructureThickness.Value:
                if p7:
                    struct = Part.Face(
                        Part.makePolygon([p1, p2, p3, p4, p5, p7, p6, p1]))
                else:
                    struct = Part.Face(
                        Part.makePolygon([p1, p2, p3, p4, p5, p6, p1]))
                evec = vWidth
                if obj.StructureOffset.Value:
                    mvec = DraftVecUtils.scaleTo(vWidth,
                                                 obj.StructureOffset.Value)
                    struct.translate(mvec)
                    evec = DraftVecUtils.scaleTo(
                        evec, evec.Length - (2 * mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer", "Two stringers"]:
            if obj.StringerWidth.Value and obj.StructureThickness.Value:
                p1b = p1.add(Vector(0, 0, -fHeight))
                reslength = fHeight / math.tan(a)
                p1c = p1.add(DraftVecUtils.scaleTo(vLength, reslength))
                p5b = None
                p5c = None
                if obj.TreadThickness.Value:
                    reslength = obj.StructureThickness.Value / math.sin(a)
                    p5b = p5.add(DraftVecUtils.scaleTo(vLength, -reslength))
                    reslength = obj.TreadThickness.Value / math.tan(a)
                    p5c = p5b.add(DraftVecUtils.scaleTo(
                        vLength, -reslength)).add(
                            Vector(0, 0, -obj.TreadThickness.Value))
                    pol = Part.Face(
                        Part.makePolygon(
                            [p1c, p1b, p2, p3, p4, p5, p5b, p5c, p1c]))
                else:
                    pol = Part.Face(
                        Part.makePolygon([p1c, p1b, p2, p3, p4, p5, p1c]))
                evec = DraftVecUtils.scaleTo(vWidth, obj.StringerWidth.Value)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                    else:
                        mvec = DraftVecUtils.scaleTo(
                            vWidth,
                            (vWidth.Length / 2) - obj.StringerWidth.Value / 2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1, s2])
        if struct:
            self.structures.append(struct)
Exemplo n.º 11
0
    def makeStraightStairs(self, obj, edge, numberofsteps=None):

        "builds a simple, straight staircase from a straight edge"

        # Upgrade obj if it is from an older version of FreeCAD
        if not (hasattr(obj, "StringerOverlap")):
            obj.addProperty(
                "App::PropertyLength", "StringerOverlap", "Structure",
                QT_TRANSLATE_NOOP(
                    "App::Property",
                    "The overlap of the stringers above the bottom of the treads"
                ))

        # general data
        import Part, DraftGeomUtils
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        v = DraftGeomUtils.vec(edge)
        vLength = DraftVecUtils.scaleTo(
            v,
            float(edge.Length) / (numberofsteps - 1))
        vLength = Vector(vLength.x, vLength.y, 0)
        if round(v.z, Draft.precision()) != 0:
            h = v.z
        else:
            h = obj.Height.Value
        vHeight = Vector(0, 0, float(h) / numberofsteps)
        vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0, 0, 1)),
                                       obj.Width.Value)
        vBase = edge.Vertexes[0].Point
        vNose = DraftVecUtils.scaleTo(vLength, -abs(obj.Nosing.Value))
        a = math.atan(vHeight.Length / vLength.Length)
        #print("stair data:",vLength.Length,":",vHeight.Length)

        # steps
        for i in range(numberofsteps - 1):
            p1 = vBase.add((Vector(vLength).multiply(i)).add(
                Vector(vHeight).multiply(i + 1)))
            p1 = self.align(p1, obj.Align, vWidth)
            p1 = p1.add(vNose).add(Vector(0, 0,
                                          -abs(obj.TreadThickness.Value)))
            p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
            p3 = p2.add(vWidth)
            p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
            step = Part.Face(Part.makePolygon([p1, p2, p3, p4, p1]))
            if obj.TreadThickness.Value:
                step = step.extrude(Vector(0, 0,
                                           abs(obj.TreadThickness.Value)))
                self.steps.append(step)
            else:
                self.pseudosteps.append(step)

        # structure
        lProfile = []
        struct = None
        if obj.Structure == "Massive":
            if obj.StructureThickness.Value:
                for i in range(numberofsteps - 1):
                    if not lProfile:
                        lProfile.append(vBase)
                    last = lProfile[-1]
                    if len(lProfile) == 1:
                        last = last.add(
                            Vector(0, 0, -abs(obj.TreadThickness.Value)))
                    lProfile.append(last.add(vHeight))
                    lProfile.append(lProfile[-1].add(vLength))
                resHeight1 = obj.StructureThickness.Value / math.cos(a)
                lProfile.append(lProfile[-1].add(Vector(0, 0, -resHeight1)))
                resHeight2 = ((numberofsteps - 1) * vHeight.Length) - (
                    resHeight1 + obj.TreadThickness.Value)
                resLength = (vLength.Length / vHeight.Length) * resHeight2
                h = DraftVecUtils.scaleTo(vLength, -resLength)
                lProfile.append(lProfile[-1].add(Vector(h.x, h.y,
                                                        -resHeight2)))
                lProfile.append(vBase)
                #print(lProfile)
                pol = Part.makePolygon(lProfile)
                struct = Part.Face(pol)
                evec = vWidth
                if obj.StructureOffset.Value:
                    mvec = DraftVecUtils.scaleTo(vWidth,
                                                 obj.StructureOffset.Value)
                    struct.translate(mvec)
                    evec = DraftVecUtils.scaleTo(
                        evec, evec.Length - (2 * mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer", "Two stringers"]:
            if obj.StringerWidth.Value and obj.StructureThickness.Value:
                hyp = math.sqrt(vHeight.Length**2 + vLength.Length**2)
                l1 = Vector(vLength).multiply(numberofsteps - 1)
                h1 = Vector(vHeight).multiply(numberofsteps - 1).add(
                    Vector(
                        0, 0, -abs(obj.TreadThickness.Value) +
                        obj.StringerOverlap.Value))
                p1 = vBase.add(l1).add(h1)
                p1 = self.align(p1, obj.Align, vWidth)
                if obj.StringerOverlap.Value <= float(h) / numberofsteps:
                    lProfile.append(p1)
                else:
                    p1b = vBase.add(l1).add(Vector(0, 0, float(h)))
                    p1a = p1b.add(
                        Vector(vLength).multiply(
                            (p1b.z - p1.z) / vHeight.Length))
                    lProfile.append(p1a)
                    lProfile.append(p1b)
                h2 = (obj.StructureThickness.Value / vLength.Length) * hyp
                lProfile.append(p1.add(Vector(0, 0, -abs(h2))))
                h3 = lProfile[-1].z - vBase.z
                l3 = (h3 / vHeight.Length) * vLength.Length
                v3 = DraftVecUtils.scaleTo(vLength, -l3)
                lProfile.append(lProfile[-1].add(Vector(0, 0,
                                                        -abs(h3))).add(v3))
                l4 = (obj.StructureThickness.Value / vHeight.Length) * hyp
                v4 = DraftVecUtils.scaleTo(vLength, -l4)
                lProfile.append(lProfile[-1].add(v4))
                lProfile.append(lProfile[0])
                #print(lProfile)
                pol = Part.makePolygon(lProfile)
                pol = Part.Face(pol)
                evec = DraftVecUtils.scaleTo(vWidth, obj.StringerWidth.Value)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                    else:
                        mvec = DraftVecUtils.scaleTo(
                            vWidth,
                            (vWidth.Length / 2) - obj.StringerWidth.Value / 2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1, s2])
        if struct:
            self.structures.append(struct)
Exemplo n.º 12
0
class plane:
	'''A WorkPlane object'''

	def __init__(self):
		# keep track of active document.  Reset view when doc changes.
		self.doc = None
		# self.weak is true if the plane has been defined by self.setup or has been reset
		self.weak = True
		# u, v axes and position define plane, perpendicular axis is handy, though redundant.
		self.u = Vector(1,0,0)
		self.v = Vector(0,1,0)
		self.axis = Vector(0,0,1)
		self.position = Vector(0,0,0)
                # a placeholder for a stored state
                self.stored = None

        def __repr__(self):
                return "Workplane x="+str(fcvec.rounded(self.u))+" y="+str(fcvec.rounded(self.v))+" z="+str(fcvec.rounded(self.axis))

	def offsetToPoint(self, p, direction=None):
		'''
		Return the signed distance from p to the plane, such
		that p + offsetToPoint(p)*direction lies on the plane.
		direction defaults to -plane.axis
		'''

		'''
		A picture will help explain the computation:

                                                            p
                                                          //|
                                                        / / |
                              			      /  /  |
						    /  	/   |
						  /    /    |
		-------------------- plane -----c-----x-----a--------

                Here p is the specified point,
                     c is a point (in this case plane.position) on the plane
                     x is the intercept on the plane from p in the specified direction, and
                     a is the perpendicular intercept on the plane (i.e. along plane.axis)

	        Using vertival bars to denote the length operator,
                     |ap| = |cp| * cos(apc) = |xp| * cos(apx)
                so
                     |xp| = |cp| * cos(apc) / cos(apx)
                          = (cp . axis) / (direction . axis)
		'''
		if direction == None: direction = self.axis
		return direction.dot(self.position.sub(p))

	def projectPoint(self, p, direction=None):
		'''project point onto plane, default direction is orthogonal'''
		if not direction: direction = self.axis
		t = Vector(direction)
		t.multiply(self.offsetToPoint(p, direction))
		return p.add(t)

	def alignToPointAndAxis(self, point, axis, offset, upvec=None):
		self.doc = FreeCAD.ActiveDocument
		self.axis = axis;
		self.axis.normalize()
		if (fcvec.equals(axis, Vector(1,0,0))):
			self.u = Vector(0,1,0)
			self.v = Vector(0,0,1)
                elif (fcvec.equals(axis, Vector(-1,0,0))):
                        self.u = Vector(0,-1,0)
                        self.v = Vector(0,0,1)
                elif upvec:
                        self.v = upvec
                        self.v.normalize()
                        self.u = self.v.cross(self.axis)
		else:
			self.v = axis.cross(Vector(1,0,0))
			self.v.normalize()
			self.u = fcvec.rotate(self.v, -math.pi/2, self.axis)
		offsetVector = Vector(axis); offsetVector.multiply(offset)
		self.position = point.add(offsetVector)
		self.weak = False
		# FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
		# FreeCAD.Console.PrintMessage("Current workplane: x="+str(fcvec.rounded(self.u))+" y="+str(fcvec.rounded(self.v))+" z="+str(fcvec.rounded(self.axis))+"\n")

	def alignToCurve(self, shape, offset):
		if shape.ShapeType == 'Edge':
			#??? TODO: process curve here.  look at shape.edges[0].Curve
			return False
		elif shape.ShapeType == 'Wire':
			#??? TODO: determine if edges define a plane
			return False
		else:
			return False

	def alignToFace(self, shape, offset=0):
		# Set face to the unique selected face, if found
		if shape.ShapeType == 'Face':
			#we should really use face.tangentAt to get u and v here, and implement alignToUVPoint
			self.alignToPointAndAxis(shape.Faces[0].CenterOfMass, shape.Faces[0].normalAt(0,0), offset)
			return True
		else:
			return False

	def alignToSelection(self, offset):
		'''If selection uniquely defines a plane, align working plane to it.  Return success (bool)'''
		sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
		if len(sex) == 0:
			return False
		elif len(sex) == 1:
                        if not sex[0].Object.isDerivedFrom("Part::Shape"):
                                return False
			return self.alignToCurve(sex[0].Object.Shape, offset) \
				or self.alignToFace(sex[0].Object.Shape, offset) \
				or (len(sex[0].SubObjects) == 1 and self.alignToFace(sex[0].SubObjects[0], offset))
		else:
			# len(sex) > 2, look for point and line, three points, etc.
			return False

	def setup(self, direction, point, upvec=None):
		'''If working plane is undefined, define it!'''
		if self.weak:
			self.alignToPointAndAxis(point, direction, 0, upvec)
			self.weak = True

	def reset(self):
		self.doc = None
                self.weak = True

        def getRotation(self):
                "returns a placement describing the working plane orientation ONLY"
                m = fcvec.getPlaneRotation(self.u,self.v,self.axis)
                return FreeCAD.Placement(m)

        def getPlacement(self):
                "returns the placement of the working plane"
                m = FreeCAD.Matrix(
                        self.u.x,self.v.x,self.axis.x,self.position.x,
                        self.u.y,self.v.y,self.axis.y,self.position.y,
                        self.u.z,self.v.z,self.axis.z,self.position.z,
                        0.0,0.0,0.0,1.0)
                return FreeCAD.Placement(m)

        def setFromPlacement(self,pl):
                "sets the working plane from a placement (rotaton ONLY)"
                rot = FreeCAD.Placement(pl).Rotation
                self.u = rot.multVec(FreeCAD.Vector(1,0,0))
                self.v = rot.multVec(FreeCAD.Vector(0,1,0))
                self.axis = rot.multVec(FreeCAD.Vector(0,0,1))

        def save(self):
                "stores the current plane state"
                self.stored = [self.u,self.v,self.axis,self.position,self.weak]

        def restore(self):
                "restores a previously saved plane state, if exists"
                if self.stored:
                        self.u = self.stored[0]
                        self.v = self.stored[1]
                        self.axis = self.stored[2]
                        self.position = self.stored[3]
                        self.weak = self.stored[4]
                        self.stored = None

        def getLocalCoords(self,point):
                "returns the coordinates of a given point on the working plane"
                xv = fcvec.project(point,self.u)
                x = xv.Length
                if xv.getAngle(self.u) > 1:
                        x = -x
                yv = fcvec.project(point,self.v)
                y = yv.Length
                if yv.getAngle(self.v) > 1:
                        y = -y
                zv = fcvec.project(point,self.axis)
                z = zv.Length
                if zv.getAngle(self.axis) > 1:
                        z = -z
                return Vector(x,y,z)

        def getGlobalCoords(self,point):
                "returns the global coordinates of the given point, taken relatively to this working plane"
                vx = fcvec.scale(self.u,point.x)
                vy = fcvec.scale(self.v,point.y)
                vz = fcvec.scale(self.axis,point.z)
                return (vx.add(vy)).add(vz)

        def getClosestAxis(self,point):
                "returns which of the workingplane axes is closest from the given vector"
                ax = point.getAngle(self.u)
                ay = point.getAngle(self.v)
                az = point.getAngle(self.axis)
                bx = point.getAngle(fcvec.neg(self.u))
                by = point.getAngle(fcvec.neg(self.v))
                bz = point.getAngle(fcvec.neg(self.axis))
                b = min(ax,ay,az,bx,by,bz)
                if b in [ax,bx]:
                        return "x"
                elif b in [ay,by]:
                        return "y"
                elif b in [az,bz]:
                        return "z"
                else:
                        return None
Exemplo n.º 13
0
    def makeStraightStairs(self, obj, edge, numberofsteps=None):

        "builds a simple, straight staircase from a straight edge"

        # general data
        import Part, DraftGeomUtils
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        v = DraftGeomUtils.vec(edge)
        vLength = DraftVecUtils.scaleTo(
            v,
            float(edge.Length) / (numberofsteps - 1))
        vLength = Vector(vLength.x, vLength.y, 0)
        if round(v.z, Draft.precision()) != 0:
            h = v.z
        else:
            h = obj.Height
        vHeight = Vector(0, 0, float(h) / numberofsteps)
        vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0, 0, 1)),
                                       obj.Width)
        vBase = edge.Vertexes[0].Point
        vNose = DraftVecUtils.scaleTo(vLength, -abs(obj.Nosing))
        a = math.atan(vHeight.Length / vLength.Length)
        print "stair data:", vLength.Length, ":", vHeight.Length

        # steps
        for i in range(numberofsteps - 1):
            p1 = vBase.add((Vector(vLength).multiply(i)).add(
                Vector(vHeight).multiply(i + 1)))
            p1 = self.align(p1, obj.Align, vWidth)
            p1 = p1.add(vNose).add(Vector(0, 0, -abs(obj.TreadThickness)))
            p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
            p3 = p2.add(vWidth)
            p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)
            step = Part.Face(Part.makePolygon([p1, p2, p3, p4, p1]))
            if obj.TreadThickness:
                step = step.extrude(Vector(0, 0, abs(obj.TreadThickness)))
            self.steps.append(step)

        # structure
        lProfile = []
        struct = None
        if obj.Structure == "Massive":
            if obj.StructureThickness:
                for i in range(numberofsteps - 1):
                    if not lProfile:
                        lProfile.append(vBase)
                    last = lProfile[-1]
                    if len(lProfile) == 1:
                        last = last.add(Vector(0, 0, -abs(obj.TreadThickness)))
                    lProfile.append(last.add(vHeight))
                    lProfile.append(lProfile[-1].add(vLength))
                resHeight1 = obj.StructureThickness / math.cos(a)
                lProfile.append(lProfile[-1].add(Vector(0, 0, -resHeight1)))
                resHeight2 = ((numberofsteps - 1) * vHeight.Length) - (
                    resHeight1 + obj.TreadThickness)
                resLength = (vLength.Length / vHeight.Length) * resHeight2
                h = DraftVecUtils.scaleTo(vLength, -resLength)
                lProfile.append(lProfile[-1].add(Vector(h.x, h.y,
                                                        -resHeight2)))
                lProfile.append(vBase)
                #print lProfile
                pol = Part.makePolygon(lProfile)
                struct = Part.Face(pol)
                evec = vWidth
                if obj.StructureOffset:
                    mvec = DraftVecUtils.scaleTo(vWidth, obj.StructureOffset)
                    struct.translate(mvec)
                    evec = DraftVecUtils.scaleTo(
                        evec, evec.Length - (2 * mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer", "Two stringers"]:
            if obj.StringerWidth and obj.StructureThickness:
                hyp = math.sqrt(vHeight.Length**2 + vLength.Length**2)
                l1 = Vector(vLength).multiply(numberofsteps - 1)
                h1 = Vector(vHeight).multiply(numberofsteps - 1).add(
                    Vector(0, 0, -abs(obj.TreadThickness)))
                p1 = vBase.add(l1).add(h1)
                p1 = self.align(p1, obj.Align, vWidth)
                lProfile.append(p1)
                h2 = (obj.StructureThickness / vLength.Length) * hyp
                lProfile.append(lProfile[-1].add(Vector(0, 0, -abs(h2))))
                h3 = lProfile[-1].z - vBase.z
                l3 = (h3 / vHeight.Length) * vLength.Length
                v3 = DraftVecUtils.scaleTo(vLength, -l3)
                lProfile.append(lProfile[-1].add(Vector(0, 0,
                                                        -abs(h3))).add(v3))
                l4 = (obj.StructureThickness / vHeight.Length) * hyp
                v4 = DraftVecUtils.scaleTo(vLength, -l4)
                lProfile.append(lProfile[-1].add(v4))
                lProfile.append(lProfile[0])
                #print lProfile
                pol = Part.makePolygon(lProfile)
                pol = Part.Face(pol)
                evec = DraftVecUtils.scaleTo(vWidth, obj.StringerWidth)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset)
                    else:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     (vWidth.Length / 2) -
                                                     obj.StringerWidth / 2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1, s2])
        if struct:
            self.structures.append(struct)
Exemplo n.º 14
0
class Plane:
    """A WorkPlane object.

    Parameters
    ----------
    u: Base::Vector3, optional
        An axis (vector) that helps define the working plane.
        It defaults to `(1, 0, 0)`, or the +X axis.

    v: Base::Vector3, optional
        An axis (vector) that helps define the working plane.
        It defaults to `(0, 1, 0)`, or the +Y axis.

    w: Base::Vector3, optional
        An axis that is supposed to be perpendicular to `u` and `v`;
        it is redundant.
        It defaults to `(0, 0, 1)`, or the +Z axis.

    pos: Base::Vector3, optional
        A point through which the plane goes through.
        It defaults to the origin `(0, 0, 0)`.

    Attributes
    ----------
    doc: App::Document
        The active document. Reset view when `doc` changes.

    weak: bool
        It is `True` if the plane has been defined by `setup()`
        or has been reset. A weak plane can be changed
        (it is the "auto" mode), while a strong plane will keep
        its position until weakened (it is "locked")

    u: Base::Vector3
        An axis (vector) that helps define the working plane.

    v: Base::Vector3
        An axis (vector) that helps define the working plane.

    axis: Base::Vector3
        A vector that is supposed to be perpendicular to `u` and `v`;
        it is helpful although redundant.

    position: Base::Vector3
        A point, which the plane goes through,
        that helps define the working plane.

    stored: bool
        A placeholder for a stored state.
    """
    def __init__(self,
                 u=Vector(1, 0, 0),
                 v=Vector(0, 1, 0),
                 w=Vector(0, 0, 1),
                 pos=Vector(0, 0, 0)):

        # keep track of active document.  Reset view when doc changes.
        self.doc = None
        self.weak = True
        self.u = u
        self.v = v
        self.axis = w
        self.position = pos
        # a placeholder for a stored state
        self.stored = None

    def __repr__(self):
        """Show the string representation of the object."""
        text = "Workplane"
        text += " x=" + str(DraftVecUtils.rounded(self.u))
        text += " y=" + str(DraftVecUtils.rounded(self.v))
        text += " z=" + str(DraftVecUtils.rounded(self.axis))
        return text

    def copy(self):
        """Return a new plane that is a copy of the present object."""
        return plane(u=self.u, v=self.v, w=self.axis, pos=self.position)

    def offsetToPoint(self, p, direction=None):
        """Return the signed distance from a point to the plane.

        Parameters
        ----------
        p : Base::Vector3
            The external point to consider.

        direction : Base::Vector3, optional
            The unit vector that indicates the direction of the distance.

            It defaults to `None`, which then uses the `plane.axis` (normal)
            value, meaning that the measured distance is perpendicular
            to the plane.

        Returns
        -------
        float
            The distance from the point to the plane.

        Notes
        -----
        The signed distance `d`, from `p` to the plane, is such that
        ::
            x = p + d*direction,

        where `x` is a point that lies on the plane.

        The `direction` is a unit vector that specifies the direction
        in which the distance is measured.
        It defaults to `plane.axis`,
        meaning that it is the perpendicular distance.

        A picture will help explain the computation
        ::
                                            p
                                          //|
                                        / / |
                                    d /  /  | axis
                                    /   /   |
                                  /    /    |
            -------- plane -----x-----c-----a--------

        The points are as follows

         * `p` is an arbitrary point outside the plane.
         * `c` is a known point on the plane,
           for example, `plane.position`.
         * `x` is the intercept on the plane from `p` in
           the desired `direction`.
         * `a` is the perpendicular intercept on the plane,
           i.e. along `plane.axis`.

        The distance is calculated through the dot product
        of the vector `pc` (going from point `p` to point `c`,
        both of which are known) with the unit vector `direction`
        (which is provided or defaults to `plane.axis`).
        ::
            d = pc . direction
            d = (c - p) . direction

        **Warning:** this implementation doesn't calculate the entire
        distance `|xp|`, only the distance `|pc|` projected onto `|xp|`.

        Trigonometric relationships
        ---------------------------
        In 2D the distances can be calculated by trigonometric relationships
        ::
            |ap| = |cp| cos(apc) = |xp| cos(apx)

        Then the desired distance is `d = |xp|`
        ::
            |xp| = |cp| cos(apc) / cos(apx)

        The cosines can be obtained from the definition of the dot product
        ::
            A . B = |A||B| cos(angleAB)

        If one vector is a unit vector
        ::
            A . uB = |A| cos(angleAB)
            cp . axis = |cp| cos(apc)

        and if both vectors are unit vectors
        ::
            uA . uB = cos(angleAB).
            direction . axis = cos(apx)

        Then
        ::
            d = (cp . axis) / (direction . axis)

        **Note:** for 2D these trigonometric operations
        produce the full `|xp|` distance.
        """
        if direction is None:
            direction = self.axis
        return direction.dot(self.position.sub(p))

    def projectPoint(self, p, direction=None):
        """Project a point onto the plane, by default orthogonally.

        Parameters
        ----------
        p : Base::Vector3
            The point to project.
        direction : Base::Vector3, optional
            The unit vector that indicates the direction of projection.

            It defaults to `None`, which then uses the `plane.axis` (normal)
            value, meaning that the point is projected perpendicularly
            to the plane.

        Returns
        -------
        Base::Vector3
            The projected vector, scaled to the appropriate distance.
        """
        if not direction:
            direction = self.axis
        lp = self.getLocalCoords(p)
        gp = self.getGlobalCoords(Vector(lp.x, lp.y, 0))
        a = direction.getAngle(gp.sub(p))
        if a > math.pi / 2:
            direction = direction.negative()
            a = math.pi - a
        ld = self.getLocalRot(direction)
        gd = self.getGlobalRot(Vector(ld.x, ld.y, 0))
        hyp = abs(math.tan(a) * lp.z)
        return gp.add(DraftVecUtils.scaleTo(gd, hyp))

    def projectPointOld(self, p, direction=None):
        """Project a point onto the plane. OBSOLETE.

        Parameters
        ----------
        p : Base::Vector3
            The point to project.
        direction : Base::Vector3, optional
            The unit vector that indicates the direction of projection.

            It defaults to `None`, which then uses the `plane.axis` (normal)
            value, meaning that the point is projected perpendicularly
            to the plane.

        Returns
        -------
        Base::Vector3
            The projected point,
            or the original point if the angle between the `direction`
            and the `plane.axis` is 90 degrees.
        """
        if not direction:
            direction = self.axis
        t = Vector(direction)
        # t.normalize()
        a = round(t.getAngle(self.axis), DraftVecUtils.precision())
        pp = round((math.pi) / 2, DraftVecUtils.precision())
        if a == pp:
            return p
        t.multiply(self.offsetToPoint(p, direction))
        return p.add(t)

    def alignToPointAndAxis(self, point, axis, offset=0, upvec=None):
        """Align the working plane to a point and an axis (vector).

        Set `v` as the cross product of `axis` with `(1, 0, 0)` or `+X`,
        and `u` as `v` rotated -90 degrees around the `axis`.
        Also set `weak` to `False`.

        Parameters
        ----------
        point : Base::Vector3
            The new `position` of the plane, adjusted by
            the `offset`.
        axis : Base::Vector3
            A vector whose unit vector will be used as the new `axis`
            of the plane.
            If it is very close to the `X` or `-X` axes,
            it will use this axis exactly, and will adjust `u` and `v`
            to `+Y` and `+Z`, or `-Y` and `+Z`, respectively.
        offset : float, optional
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.
        upvec : Base::Vector3, optional
            Defaults to `None`.
            If it exists, its unit vector will be used as `v`,
            and will set `u` as the cross product of `v` with `axis`.
        """
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis
        self.axis.normalize()
        if axis.getAngle(Vector(1, 0, 0)) < 0.00001:
            self.axis = Vector(1, 0, 0)
            self.u = Vector(0, 1, 0)
            self.v = Vector(0, 0, 1)
        elif axis.getAngle(Vector(-1, 0, 0)) < 0.00001:
            self.axis = Vector(-1, 0, 0)
            self.u = Vector(0, -1, 0)
            self.v = Vector(0, 0, 1)
        elif upvec:
            self.v = upvec
            self.v.normalize()
            self.u = self.v.cross(self.axis)
        else:
            self.v = axis.cross(Vector(1, 0, 0))
            self.v.normalize()
            self.u = DraftVecUtils.rotate(self.v, -math.pi / 2, self.axis)
        offsetVector = Vector(axis)
        offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # Console.PrintMessage(self.__repr__() + "\n")

    def alignToPointAndAxis_SVG(self, point, axis, offset=0):
        """Align the working plane to a point and an axis (vector).

        It aligns `u` and `v` based on the magnitude of the components
        of `axis`.
        Also set `weak` to `False`.

        Parameters
        ----------
        point : Base::Vector3
            The new `position` of the plane, adjusted by
            the `offset`.
        axis : Base::Vector3
            A vector whose unit vector will be used as the new `axis`
            of the plane.
            The magnitudes of the `x`, `y`, `z` components of the axis
            determine the orientation of `u` and `v` of the plane.
        offset : float, optional
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.

        Cases
        -----
        The `u` and `v` are always calculated the same

            * `u` is the cross product of the positive or negative of `axis`
              with a `reference vector`.
              ::
                  u = [+1|-1] axis.cross(ref_vec)
            * `v` is `u` rotated 90 degrees around `axis`.

        Whether the `axis` is positive or negative, and which reference
        vector is used, depends on the absolute values of the `x`, `y`, `z`
        components of the `axis` unit vector.

         #. If `x > y`, and `y > z`
             The reference vector is +Z
             ::
                 u = -1 axis.cross(+Z)
         #. If `y > z`, and `z >= x`
             The reference vector is +X.
             ::
                 u = -1 axis.cross(+X)
         #. If `y >= x`, and `x > z`
             The reference vector is +Z.
             ::
                 u = +1 axis.cross(+Z)
         #. If `x > z`, and `z >= y`
             The reference vector is +Y.
             ::
                 u = +1 axis.cross(+Y)
         #. If `z >= y`, and `y > x`
             The reference vector is +X.
             ::
                 u = +1 axis.cross(+X)
         #. otherwise
             The reference vector is +Y.
             ::
                 u = -1 axis.cross(+Y)
        """
        self.doc = FreeCAD.ActiveDocument
        self.axis = axis
        self.axis.normalize()
        ref_vec = Vector(0.0, 1.0, 0.0)

        if ((abs(axis.x) > abs(axis.y)) and (abs(axis.y) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "Case new"

        elif ((abs(axis.y) > abs(axis.z)) and (abs(axis.z) >= abs(axis.x))):
            ref_vec = Vector(1.0, 0.0, 0.0)
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "Y>Z, View Y"

        elif ((abs(axis.y) >= abs(axis.x)) and (abs(axis.x) > abs(axis.z))):
            ref_vec = Vector(0.0, 0., 1.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "ehem. XY, Case XY"

        elif ((abs(axis.x) > abs(axis.z)) and (abs(axis.z) >= abs(axis.y))):
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "X>Z, View X"

        elif ((abs(axis.z) >= abs(axis.y)) and (abs(axis.y) > abs(axis.x))):
            ref_vec = Vector(1.0, 0., 0.0)
            self.u = axis.cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "Y>X, Case YZ"

        else:
            self.u = axis.negative().cross(ref_vec)
            self.u.normalize()
            self.v = DraftVecUtils.rotate(self.u, math.pi / 2, self.axis)
            # projcase = "else"

        # spat_vec = self.u.cross(self.v)
        # spat_res = spat_vec.dot(axis)
        # Console.PrintMessage(projcase + " spat Prod = " + str(spat_res) + "\n")

        offsetVector = Vector(axis)
        offsetVector.multiply(offset)
        self.position = point.add(offsetVector)
        self.weak = False
        # Console.PrintMessage("(position = " + str(self.position) + ")\n")
        # Console.PrintMessage(self.__repr__() + "\n")

    def alignToCurve(self, shape, offset=0):
        """Align plane to curve. NOT YET IMPLEMENTED.

        Parameters
        ----------
        shape : Part.Shape
            A curve that will serve to align the plane.
            It can be an `'Edge'` or `'Wire'`.
        offset : float
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.

        Returns
        -------
        False
            Returns `False` if the shape is null.
            Currently it always returns `False`.
        """
        if shape.isNull():
            return False
        elif shape.ShapeType == 'Edge':
            # ??? TODO: process curve here.  look at shape.edges[0].Curve
            return False
        elif shape.ShapeType == 'Wire':
            # ??? TODO: determine if edges define a plane
            return False
        else:
            return False

    def alignToEdges(self, edges):
        """Align plane to two edges.

        Uses the two points of the first edge to define the direction
        of the unit vector `u`, the other two points of the other edge
        to define the other unit vector `v`, and then the cross product
        of `u` with `v` to define the `axis`.

        Parameters
        ----------
        edges : list
            A list of two edges.

        Returns
        -------
        False
            Return `False` if `edges` is a list of more than 2 elements.
        """
        # use a list of edges to find a plane position
        if len(edges) > 2:
            return False
        # for axes systems, we suppose the 2 first edges are parallel
        # ??? TODO: exclude other cases first
        v1 = edges[0].Vertexes[-1].Point.sub(edges[0].Vertexes[0].Point)
        v2 = edges[1].Vertexes[0].Point.sub(edges[0].Vertexes[0].Point)
        v3 = v1.cross(v2)
        v1.normalize()
        v2.normalize()
        v3.normalize()
        # print(v1,v2,v3)
        self.u = v1
        self.v = v2
        self.axis = v3

    def alignToFace(self, shape, offset=0):
        """Align the plane to a face.

        It uses the center of mass of the face as `position`,
        and its normal in the center of the face as `axis`,
        then calls `alignToPointAndAxis(position, axis, offset)`.

        If the face is a quadrilateral, then it adjusts the position
        of the plane according to its reported X direction and Y direction.

        Also set `weak` to `False`.

        Parameter
        --------
        shape : Part.Face
            A shape of type `'Face'`.

        offset : float
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.

        Returns
        -------
        bool
            `True` if the operation was successful, and `False` if the shape
            is not a `'Face'`.

        See Also
        --------
        alignToPointAndAxis, DraftGeomUtils.getQuad
        """
        # Set face to the unique selected face, if found
        if shape.ShapeType == 'Face':
            self.alignToPointAndAxis(shape.Faces[0].CenterOfMass,
                                     shape.Faces[0].normalAt(0, 0), offset)
            import DraftGeomUtils
            q = DraftGeomUtils.getQuad(shape)
            if q:
                self.u = q[1]
                self.v = q[2]
                if not DraftVecUtils.equals(self.u.cross(self.v), self.axis):
                    self.u = q[2]
                    self.v = q[1]
                if DraftVecUtils.equals(self.u, Vector(0, 0, 1)):
                    # the X axis is vertical: rotate 90 degrees
                    self.u, self.v = self.v.negative(), self.u
                elif DraftVecUtils.equals(self.u, Vector(0, 0, -1)):
                    self.u, self.v = self.v, self.u.negative()

            self.weak = False
            return True
        else:
            return False

    def alignTo3Points(self, p1, p2, p3, offset=0):
        """Align the plane to three points.

        It makes a closed quadrilateral face with the three points,
        and then calls `alignToFace(shape, offset)`.

        Parameter
        ---------
        p1 : Base::Vector3
            The first point.
        p2 : Base::Vector3
            The second point.
        p3 : Base::Vector3
            The third point.

        offset : float
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.

        Returns
        -------
        bool
            `True` if the operation was successful, and `False` otherwise.
        """
        import Part
        w = Part.makePolygon([p1, p2, p3, p1])
        f = Part.Face(w)
        return self.alignToFace(f, offset)

    def alignToSelection(self, offset=0):
        """Align the plane to a selection if it defines a plane.

        If the selection uniquely defines a plane it will be used.
        Currently it only works with one object selected, a `'Face'`.
        It extracts the shape of the object or subobject
        and then calls `alignToFace(shape, offset)`.

        This method only works when `FreeCAD.GuiUp` is `True`,
        that is, when the graphical interface is loaded.

        Parameter
        ---------
        offset : float
            Defaults to zero. A value which will be used to offset
            the plane in the direction of its `axis`.

        Returns
        -------
        bool
            `True` if the operation was successful, and `False` otherwise.
            It returns `False` if the selection has no elements,
            or if it has more than one element,
            or if the object is not derived from `'Part::Feature'`
            or if the object doesn't have a `Shape`.

        To do
        -----
        The method returns `False` if the selection list has more than
        one element.
        The method should search the list for objects like faces, points,
        edges, wires, etc., and call the appropriate aligning submethod.

        The method could work for curves (`'Edge'`  or `'Wire'`) but
        `alignToCurve()` isn't fully implemented.

        When the interface is not loaded it should fail and print
        a message, `FreeCAD.Console.PrintError()`.

        See Also
        --------
        alignToFace, alignToCurve
        """
        import FreeCADGui
        sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
        if len(sex) == 0:
            return False
        elif len(sex) == 1:
            if (not sex[0].Object.isDerivedFrom("Part::Feature")
                    or not sex[0].Object.Shape):
                return False
            return (self.alignToFace(sex[0].Object.Shape, offset)
                    or (len(sex[0].SubObjects) == 1
                        and self.alignToFace(sex[0].SubObjects[0], offset))
                    or self.alignToCurve(sex[0].Object.Shape, offset))
        else:
            # len(sex) > 2, look for point and line, three points, etc.
            return False

    def setup(self, direction=None, point=None, upvec=None, force=False):
        """Set up the working plane if it exists but is undefined.

        If `direction` and `point` are present,
        it calls `alignToPointAndAxis(point, direction, 0, upvec)`.

        Otherwise, it gets the camera orientation to define
        a working plane that is perpendicular to the current view,
        centered at the origin, and with `v` pointing up on the screen.

        This method only works when the `weak` attribute is `True`.
        This method also sets `weak` to `True`.

        This method only works when `FreeCAD.GuiUp` is `True`,
        that is, when the graphical interface is loaded.
        Otherwise it fails silently.

        Parameters
        ----------
        direction : Base::Vector3, optional
            It defaults to `None`. It is the new `axis` of the plane.
        point : Base::Vector3, optional
            It defaults to `None`. It is the new `position` of the plane.
        upvec : Base::Vector3, optional
            It defaults to `None`. It is the new `v` orientation of the plane.
        force : Bool
            If True, it sets the plane even if the plane is not in weak mode

        To do
        -----
        When the interface is not loaded it should fail and print
        a message, `FreeCAD.Console.PrintError()`.
        """
        if self.weak or force:
            if direction and point:
                self.alignToPointAndAxis(point, direction, 0, upvec)
            elif FreeCAD.GuiUp:
                try:
                    import FreeCADGui
                    from pivy import coin
                    view = FreeCADGui.ActiveDocument.ActiveView
                    camera = view.getCameraNode()
                    rot = camera.getField("orientation").getValue()
                    coin_up = coin.SbVec3f(0, 1, 0)
                    upvec = Vector(rot.multVec(coin_up).getValue())
                    vdir = view.getViewDirection()
                    # The angle is between 0 and 180 degrees.
                    angle = vdir.getAngle(self.axis)
                    if (angle > 0.001) and (angle < 3.14159):
                        # don't change the plane if it is already
                        # perpendicular to the current view
                        self.alignToPointAndAxis(Vector(0, 0, 0),
                                                 vdir.negative(), 0, upvec)
                except Exception:
                    pass
            if force:
                self.weak = False
            else:
                self.weak = True

    def reset(self):
        """Reset the plane.

        Set the `doc` attribute to `None`, and `weak` to `True`.
        """
        self.doc = None
        self.weak = True

    def setTop(self):
        """sets the WP to top position and updates the GUI"""
        self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0),
                                 FreeCAD.Vector(0, 0, 1), 0.0)
        if FreeCAD.GuiUp:
            import FreeCADGui
            from draftutils.translate import translate
            if hasattr(FreeCADGui, "Snapper"):
                FreeCADGui.Snapper.setGrid()
            if hasattr(FreeCADGui, "draftToolBar"):
                FreeCADGui.draftToolBar.wplabel.setText(
                    translate("draft", "Top"))

    def setFront(self):
        """sets the WP to front position and updates the GUI"""
        self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0),
                                 FreeCAD.Vector(0, 1, 0), 0.0)
        if FreeCAD.GuiUp:
            import FreeCADGui
            from draftutils.translate import translate
            if hasattr(FreeCADGui, "Snapper"):
                FreeCADGui.Snapper.setGrid()
            if hasattr(FreeCADGui, "draftToolBar"):
                FreeCADGui.draftToolBar.wplabel.setText(
                    translate("draft", "Front"))

    def setSide(self):
        """sets the WP to top position and updates the GUI"""
        self.alignToPointAndAxis(FreeCAD.Vector(0.0, 0.0, 0.0),
                                 FreeCAD.Vector(-1, 0, 0), 0.0)
        if FreeCAD.GuiUp:
            import FreeCADGui
            from draftutils.translate import translate
            if hasattr(FreeCADGui, "Snapper"):
                FreeCADGui.Snapper.setGrid()
            if hasattr(FreeCADGui, "draftToolBar"):
                FreeCADGui.draftToolBar.wplabel.setText(
                    translate("draft", "Side"))

    def getRotation(self):
        """Return a placement describing the plane orientation only.

        If `FreeCAD.GuiUp` is `True`, that is, if the graphical interface
        is loaded, it will test if the active object is an `Arch` container
        and will calculate the placement accordingly.

        Returns
        -------
        Base::Placement
            A placement, comprised of a `Base` (`Base::Vector3`),
            and a `Rotation` (`Base::Rotation`).
        """
        m = DraftVecUtils.getPlaneRotation(self.u, self.v, self.axis)
        p = FreeCAD.Placement(m)
        # Arch active container
        if FreeCAD.GuiUp:
            import FreeCADGui
            view = FreeCADGui.ActiveDocument.ActiveView
            if view:
                a = view.getActiveObject("Arch")
                if a:
                    p = a.Placement.inverse().multiply(p)
        return p

    def getPlacement(self, rotated=False):
        """Return the placement of the plane.

        Parameters
        ----------
        rotated : bool, optional
            It defaults to `False`. If it is `True`, it switches `axis`
            with `-v` to produce a rotated placement.

        Returns
        -------
        Base::Placement
            A placement, comprised of a `Base` (`Base::Vector3`),
            and a `Rotation` (`Base::Rotation`).
        """
        if rotated:
            m = FreeCAD.Matrix(self.u.x, self.axis.x, -self.v.x,
                               self.position.x, self.u.y, self.axis.y,
                               -self.v.y, self.position.y, self.u.z,
                               self.axis.z, -self.v.z, self.position.z, 0.0,
                               0.0, 0.0, 1.0)
        else:
            m = FreeCAD.Matrix(self.u.x, self.v.x, self.axis.x,
                               self.position.x, self.u.y, self.v.y,
                               self.axis.y, self.position.y, self.u.z,
                               self.v.z, self.axis.z, self.position.z, 0.0,
                               0.0, 0.0, 1.0)
        p = FreeCAD.Placement(m)
        # Arch active container if based on App Part
        # if FreeCAD.GuiUp:
        #    import FreeCADGui
        #    view = FreeCADGui.ActiveDocument.ActiveView
        #    a = view.getActiveObject("Arch")
        #    if a:
        #        p = a.Placement.inverse().multiply(p)
        return p

    def getNormal(self):
        """Return the normal vector of the plane (axis).

        Returns
        -------
        Base::Vector3
            The `axis` attribute of the plane.
        """
        n = self.axis
        # Arch active container if based on App Part
        # if FreeCAD.GuiUp:
        #    import FreeCADGui
        #    view = FreeCADGui.ActiveDocument.ActiveView
        #    a = view.getActiveObject("Arch")
        #    if a:
        #        n = a.Placement.inverse().Rotation.multVec(n)
        return n

    def setFromPlacement(self, pl, rebase=False):
        """Set the plane from a placement.

        It normally uses only the rotation, unless `rebase` is `True`.

        Parameters
        ----------
        pl : Base::Placement or Base::Matrix4D
            A placement, comprised of a `Base` (`Base::Vector3`),
            and a `Rotation` (`Base::Rotation`),
            or a `Base::Matrix4D` that defines a placement.
        rebase : bool, optional
            It defaults to `False`.
            If `True`, it will use `pl.Base` as the new `position`
            of the plane. Otherwise it will only consider `pl.Rotation`.

        To do
        -----
        If `pl` is a `Base::Matrix4D`, it shouldn't try to use `pl.Base`
        because a matrix has no `Base`.
        """
        rot = FreeCAD.Placement(pl).Rotation
        self.u = rot.multVec(FreeCAD.Vector(1, 0, 0))
        self.v = rot.multVec(FreeCAD.Vector(0, 1, 0))
        self.axis = rot.multVec(FreeCAD.Vector(0, 0, 1))
        if rebase:
            self.position = pl.Base

    def inverse(self):
        """Invert the direction of the plane.

        It inverts the `u` and `axis` vectors.
        """
        self.u = self.u.negative()
        self.axis = self.axis.negative()

    def save(self):
        """Store the plane attributes.

        Store `u`, `v`, `axis`, `position` and `weak`
        in a list in `stored`.
        """
        self.stored = [self.u, self.v, self.axis, self.position, self.weak]

    def restore(self):
        """Restore the plane attributes that were saved.

        Restores the attributes `u`, `v`, `axis`, `position` and `weak`
        from `stored`, and set `stored` to `None`.
        """
        if self.stored:
            self.u = self.stored[0]
            self.v = self.stored[1]
            self.axis = self.stored[2]
            self.position = self.stored[3]
            self.weak = self.stored[4]
            self.stored = None

    def getLocalCoords(self, point):
        """Return the coordinates of the given point, from the plane.

        If the `point` was constructed using the plane as origin,
        return the relative coordinates from the `point` to the plane.

        A vector is calculated from the plane's `position`
        to the external `point`, and this vector is projected onto
        each of the `u`, `v` and `axis` of the plane to determine
        the local, relative vector.

        Parameters
        ----------
        point : Base::Vector3
            The point external to the plane.

        Returns
        -------
        Base::Vector3
            The relative coordinates of the point from the plane.

        See Also
        --------
        getGlobalCoords, getLocalRot, getGlobalRot

        Notes
        -----
        The following graphic explains the coordinates.
        ::
                                  g GlobalCoords (1, 11)
                                  |
                                  |
                                  |
                              (n) p point (1, 6)
                                  | LocalCoords (1, 1)
                                  |
            ----plane--------c-------- position (0, 5)

        In the graphic

            * `p` is an arbitrary point, external to the plane
            * `c` is the plane's `position`
            * `g` is the global coordinates of `p` when added to the plane
            * `n` is the relative coordinates of `p` when referred to the plane

        To do
        -----
        Maybe a better name would be getRelativeCoords?
        """
        pt = point.sub(self.position)
        xv = DraftVecUtils.project(pt, self.u)
        x = xv.Length
        # If the angle between the projection xv and u
        # is larger than 1 radian (57.29 degrees), use the negative
        # of the magnitude. Why exactly 1 radian?
        if xv.getAngle(self.u) > 1:
            x = -x
        yv = DraftVecUtils.project(pt, self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
            y = -y
        zv = DraftVecUtils.project(pt, self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
            z = -z
        return Vector(x, y, z)

    def getGlobalCoords(self, point):
        """Return the coordinates of the given point, added to the plane.

        If the `point` was constructed using the plane as origin,
        return the absolute coordinates from the `point`
        to the global origin.

        The `u`, `v`, and `axis` vectors scale the components of `point`,
        and the result is added to the planes `position`.

        Parameters
        ----------
        point : Base::Vector3
            The external point.

        Returns
        -------
        Base::Vector3
            The coordinates of the point from the absolute origin.

        See Also
        --------
        getLocalCoords, getLocalRot, getGlobalRot

        Notes
        -----
        The following graphic explains the coordinates.
        ::
                                  g GlobalCoords (1, 11)
                                  |
                                  |
                                  |
                              (n) p point (1, 6)
                                  | LocalCoords (1, 1)
                                  |
            ----plane--------c-------- position (0, 5)

        In the graphic

            * `p` is an arbitrary point, external to the plane
            * `c` is the plane's `position`
            * `g` is the global coordinates of `p` when added to the plane
            * `n` is the relative coordinates of `p` when referred to the plane

        """
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt.add(self.position)

    def getLocalRot(self, point):
        """Like getLocalCoords, but doesn't use the plane's position.

        If the `point` was constructed using the plane as origin,
        return the relative coordinates from the `point` to the plane.
        However, in this case, the plane is assumed to have its `position`
        at the global origin, therefore, the returned coordinates
        will only consider the orientation of the plane.

        The external `point` is a vector, which is projected onto
        each of the `u`, `v` and `axis` of the plane to determine
        the local, relative vector.

        Parameters
        ----------
        point : Base::Vector3
            The point external to the plane.

        Returns
        -------
        Base::Vector3
            The relative coordinates of the point from the plane,
            if the plane had its `position` at the global origin.

        See Also
        --------
        getLocalCoords, getGlobalCoords, getGlobalRot
        """
        xv = DraftVecUtils.project(point, self.u)
        x = xv.Length
        if xv.getAngle(self.u) > 1:
            x = -x
        yv = DraftVecUtils.project(point, self.v)
        y = yv.Length
        if yv.getAngle(self.v) > 1:
            y = -y
        zv = DraftVecUtils.project(point, self.axis)
        z = zv.Length
        if zv.getAngle(self.axis) > 1:
            z = -z
        return Vector(x, y, z)

    def getGlobalRot(self, point):
        """Like getGlobalCoords, but doesn't use the plane's position.

        If the `point` was constructed using the plane as origin,
        return the absolute coordinates from the `point`
        to the global origin.
        However, in this case, the plane is assumed to have its `position`
        at the global origin, therefore, the returned coordinates
        will only consider the orientation of the plane.

        The `u`, `v`, and `axis` vectors scale the components of `point`.

        Parameters
        ----------
        point : Base::Vector3
            The external point.

        Returns
        -------
        Base::Vector3
            The coordinates of the point from the absolute origin.

        See Also
        --------
        getGlobalCoords, getLocalCoords, getLocalRot
        """
        vx = Vector(self.u).multiply(point.x)
        vy = Vector(self.v).multiply(point.y)
        vz = Vector(self.axis).multiply(point.z)
        pt = (vx.add(vy)).add(vz)
        return pt

    def getClosestAxis(self, point):
        """Return the closest axis of the plane to the given point (vector).

        It tests the angle that the `point` vector makes with the unit vectors
        `u`, `v`, and `axis`, as well their negatives.
        The smallest angle indicates the closest axis.

        Parameters
        ----------
        point : Base::Vector3
            The external point to test.

        Returns
        -------
        str
            * It is `'x'` if the closest axis is `u` or `-u`.
            * It is `'y'` if the closest axis is `v` or `-v`.
            * It is `'z'` if the closest axis is `axis` or `-axis`.
        """
        ax = point.getAngle(self.u)
        ay = point.getAngle(self.v)
        az = point.getAngle(self.axis)
        bx = point.getAngle(self.u.negative())
        by = point.getAngle(self.v.negative())
        bz = point.getAngle(self.axis.negative())
        b = min(ax, ay, az, bx, by, bz)
        if b in [ax, bx]:
            return "x"
        elif b in [ay, by]:
            return "y"
        elif b in [az, bz]:
            return "z"
        else:
            return None

    def isGlobal(self):
        """Return True if the plane axes are equal to the global axes.

        Return `False` if any of `u`, `v`, or `axis` does not correspond
        to `+X`, `+Y`, or `+Z`, respectively.
        """
        if self.u != Vector(1, 0, 0):
            return False
        if self.v != Vector(0, 1, 0):
            return False
        if self.axis != Vector(0, 0, 1):
            return False
        return True

    def isOrtho(self):
        """Return True if the plane axes are orthogonal with the global axes.

        Orthogonal means that the angle between `u` and the global axis `+Y`
        is a multiple of 90 degrees, meaning 0, -90, 90, -180, 180,
        -270, 270, or 360 degrees.
        And similarly for `v` and `axis`.
        All three axes should be orthogonal to the `+Y` axis.

        Due to rounding errors, the angle difference is rounded
        to 6 decimal digits to do the test.

        Returns
        -------
        bool
            Returns `True` if all three `u`, `v`, and `axis`
            are orthogonal with the global axis `+Y`.
            Otherwise it returns `False`.
        """
        ortho = [
            0, -1.570796, 1.570796, -3.141593, 3.141593, -4.712389, 4.712389,
            6.283185
        ]
        # Shouldn't the angle difference be calculated with
        # the other global axes `+X` and `+Z` as well?
        if round(self.u.getAngle(Vector(0, 1, 0)), 6) in ortho:
            if round(self.v.getAngle(Vector(0, 1, 0)), 6) in ortho:
                if round(self.axis.getAngle(Vector(0, 1, 0)), 6) in ortho:
                    return True
        return False

    def getDeviation(self):
        """Return the angle between the u axis and the horizontal plane.

        It defines a projection of `u` on the horizontal plane
        (without a Z component), and then measures the angle between
        this projection and `u`.

        It also considers the cross product of the projection
        and `u` to determine the sign of the angle.

        Returns
        -------
        float
            Angle between the `u` vector, and a projected vector
            on the global horizontal plane.

        See Also
        --------
        DraftVecUtils.angle
        """
        proj = Vector(self.u.x, self.u.y, 0)
        if self.u.getAngle(proj) == 0:
            return 0
        else:
            norm = proj.cross(self.u)
            return DraftVecUtils.angle(self.u, proj, norm)
Exemplo n.º 15
0
    def makeStraightStairsWithLanding(self, obj, edge):

        "builds a straight staircase with a landing in the middle"

        if obj.NumberOfSteps < 3:
            return
        import Part, DraftGeomUtils
        v = DraftGeomUtils.vec(edge)
        if obj.LandingDepth:
            reslength = edge.Length - obj.LandingDepth.Value
        else:
            reslength = edge.Length - obj.Width.Value
        vLength = DraftVecUtils.scaleTo(
            v,
            float(reslength) / (obj.NumberOfSteps - 2))
        vLength = Vector(vLength.x, vLength.y, 0)
        vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0, 0, 1)),
                                       obj.Width.Value)
        p1 = edge.Vertexes[0].Point
        if round(v.z, Draft.precision()) != 0:
            h = v.z
        else:
            h = obj.Height.Value
        hstep = h / obj.NumberOfSteps
        landing = int(obj.NumberOfSteps / 2)

        if obj.LastSegment:
            print("obj.LastSegment is: ")
            print(obj.LastSegment.Name)
            lastSegmentAbsTop = obj.LastSegment.AbsTop
            print("lastSegmentAbsTop is: ")
            print(lastSegmentAbsTop)
            p1 = Vector(
                p1.x, p1.y,
                lastSegmentAbsTop.z)  # use Last Segment top's z-coordinate
            print(p1)
        obj.AbsTop = p1.add(Vector(0, 0, h))

        p2 = p1.add(
            DraftVecUtils.scale(vLength,
                                landing - 1).add(Vector(0, 0,
                                                        landing * hstep)))
        if obj.LandingDepth:
            p3 = p2.add(DraftVecUtils.scaleTo(vLength, obj.LandingDepth.Value))
        else:
            p3 = p2.add(DraftVecUtils.scaleTo(vLength, obj.Width.Value))
        if obj.Flight == "HalfTurnLeft":
            p3r = p2
            p4r = p2.add(
                DraftVecUtils.scale(
                    -vLength, obj.NumberOfSteps - (landing + 1)).add(
                        Vector(0, 0, (obj.NumberOfSteps - landing) * hstep)))
        else:
            p4 = p3.add(
                DraftVecUtils.scale(
                    vLength, obj.NumberOfSteps - (landing + 1)).add(
                        Vector(0, 0, (obj.NumberOfSteps - landing) * hstep)))
        self.makeStraightStairs(obj,
                                Part.LineSegment(p1, p2).toShape(), landing)

        self.makeStraightLanding(obj,
                                 Part.LineSegment(p2, p3).toShape(), None,
                                 True)

        if obj.Flight == "HalfTurnLeft":
            self.makeStraightStairs(obj,
                                    Part.LineSegment(p3r, p4r).toShape(),
                                    obj.NumberOfSteps - landing)
        else:
            self.makeStraightStairs(obj,
                                    Part.LineSegment(p3, p4).toShape(),
                                    obj.NumberOfSteps - landing)
Exemplo n.º 16
0
    def makeStraightLanding(self,
                            obj,
                            edge,
                            numberofsteps=None,
                            callByMakeStraightStairsWithLanding=False):
        "builds a landing from a straight edge"

        # general data
        if not numberofsteps:
            numberofsteps = obj.NumberOfSteps
        import Part, DraftGeomUtils
        v = DraftGeomUtils.vec(edge)
        vLength = Vector(v.x, v.y, 0)
        vWidth = vWidth = DraftVecUtils.scaleTo(vLength.cross(Vector(0, 0, 1)),
                                                obj.Width.Value)
        vBase = edge.Vertexes[0].Point

        # if not call by makeStraightStairsWithLanding() - not 're-base' in function there, then 're-base' here
        if not callByMakeStraightStairsWithLanding:
            vBase = self.vbaseFollowLastSement(obj, vBase)
            obj.AbsTop = vBase

        vNose = DraftVecUtils.scaleTo(vLength, -abs(obj.Nosing.Value))
        h = obj.Height.Value
        l = obj.Length.Value
        if obj.Base:
            if obj.Base.isDerivedFrom("Part::Feature"):
                l = obj.Base.Shape.Length
                if obj.Base.Shape.BoundBox.ZLength:
                    h = obj.Base.Shape.BoundBox.ZLength

        if obj.LandingDepth:
            fLength = float(l - obj.LandingDepth.Value) / (numberofsteps - 2)
        else:
            fLength = float(l - obj.Width.Value) / (numberofsteps - 2)

        fHeight = float(h) / numberofsteps
        a = math.atan(fHeight / fLength)
        print("landing data:", fLength, ":", fHeight)

        # step
        p1 = self.align(vBase, obj.Align, vWidth)
        p1o = p1.add(Vector(0, 0, -abs(obj.TreadThickness.Value)))

        p1 = p1.add(vNose).add(Vector(0, 0, -abs(obj.TreadThickness.Value)))
        p2 = p1.add(DraftVecUtils.neg(vNose)).add(vLength)
        p3 = p2.add(vWidth)
        p4 = p3.add(DraftVecUtils.neg(vLength)).add(vNose)

        p4o = p3.add(DraftVecUtils.neg(vLength))
        if not callByMakeStraightStairsWithLanding:
            p2o = p2
            p3o = p3

        if obj.Flight == "HalfTurnLeft":
            p1 = p1.add(-vWidth)
            p2 = p2.add(-vWidth)

        step = Part.Face(Part.makePolygon([p1, p2, p3, p4, p1]))
        if obj.TreadThickness.Value:
            step = step.extrude(Vector(0, 0, abs(obj.TreadThickness.Value)))
            self.steps.append(step)
        else:
            self.pseudosteps.append(step)

        # structure
        lProfile = []
        struct = None
        p7 = None
        p1 = p1.add(DraftVecUtils.neg(vNose))
        p2 = p1.add(Vector(0, 0, -fHeight)).add(
            Vector(0, 0, -obj.StructureThickness.Value / math.cos(a)))
        resheight = p1.sub(p2).Length - obj.StructureThickness.Value
        reslength = resheight / math.tan(a)
        p3 = p2.add(DraftVecUtils.scaleTo(vLength, reslength)).add(
            Vector(0, 0, resheight))
        p6 = p1.add(vLength)
        if obj.TreadThickness.Value:
            if obj.Flight == "Straight":
                p7 = p6.add(Vector(0, 0, obj.TreadThickness.Value))
        reslength = fLength + (
            obj.StructureThickness.Value / math.sin(a) -
            (fHeight - obj.TreadThickness.Value) / math.tan(a))
        if p7:
            p5 = p7.add(DraftVecUtils.scaleTo(vLength, reslength))
        else:
            if obj.Flight == "Straight":
                p5 = p6.add(DraftVecUtils.scaleTo(vLength, reslength))
            else:
                p5 = None
        resheight = obj.StructureThickness.Value + obj.TreadThickness.Value
        reslength = resheight / math.tan(a)
        if obj.Flight == "Straight":
            p4 = p5.add(DraftVecUtils.scaleTo(vLength, -reslength)).add(
                Vector(0, 0, -resheight))
        else:
            p4 = p6.add(Vector(0, 0, -obj.StructureThickness.Value))
        if obj.Structure == "Massive":
            if obj.StructureThickness.Value:
                if p7:
                    struct = Part.Face(
                        Part.makePolygon([p1, p2, p3, p4, p5, p7, p6, p1]))
                elif p5:
                    struct = Part.Face(
                        Part.makePolygon([p1, p2, p3, p4, p5, p6, p1]))
                else:
                    struct = Part.Face(
                        Part.makePolygon([p1, p2, p3, p4, p6, p1]))
                evec = vWidth
                mvec = FreeCAD.Vector(0.0, 0)
                if obj.StructureOffset.Value:
                    mvec = DraftVecUtils.scaleTo(vWidth,
                                                 obj.StructureOffset.Value)
                    struct.translate(mvec)
                if obj.Flight == "HalfTurnLeft":
                    evec = DraftVecUtils.scaleTo(
                        evec, 2 * evec.Length - 2 * mvec.Length)
                else:
                    evec = DraftVecUtils.scaleTo(
                        evec, evec.Length - (2 * mvec.Length))
                struct = struct.extrude(evec)
        elif obj.Structure in ["One stringer", "Two stringers"]:
            if obj.StringerWidth.Value and obj.StructureThickness.Value:
                p1b = p1.add(Vector(0, 0, -fHeight))
                reslength = fHeight / math.tan(a)
                p1c = p1.add(DraftVecUtils.scaleTo(vLength, reslength))
                p5b = None
                p5c = None
                if obj.TreadThickness.Value:
                    reslength = obj.StructureThickness.Value / math.sin(a)
                    p5b = p5.add(DraftVecUtils.scaleTo(vLength, -reslength))
                    reslength = obj.TreadThickness.Value / math.tan(a)
                    p5c = p5b.add(DraftVecUtils.scaleTo(
                        vLength, -reslength)).add(
                            Vector(0, 0, -obj.TreadThickness.Value))
                    pol = Part.Face(
                        Part.makePolygon(
                            [p1c, p1b, p2, p3, p4, p5, p5b, p5c, p1c]))
                else:
                    pol = Part.Face(
                        Part.makePolygon([p1c, p1b, p2, p3, p4, p5, p1c]))
                evec = DraftVecUtils.scaleTo(vWidth, obj.StringerWidth.Value)
                if obj.Structure == "One stringer":
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                    else:
                        mvec = DraftVecUtils.scaleTo(
                            vWidth,
                            (vWidth.Length / 2) - obj.StringerWidth.Value / 2)
                    pol.translate(mvec)
                    struct = pol.extrude(evec)
                elif obj.Structure == "Two stringers":
                    pol2 = pol.copy()
                    if obj.StructureOffset.Value:
                        mvec = DraftVecUtils.scaleTo(vWidth,
                                                     obj.StructureOffset.Value)
                        pol.translate(mvec)
                        mvec = vWidth.add(mvec.negative())
                        pol2.translate(mvec)
                    else:
                        pol2.translate(vWidth)
                    s1 = pol.extrude(evec)
                    s2 = pol2.extrude(evec.negative())
                    struct = Part.makeCompound([s1, s2])

        # Overwriting result of above functions if case fit - should better avoid running the above in first place (better rewrite later)
        if not callByMakeStraightStairsWithLanding:
            if obj.StructureThickness.Value:
                struct = None
                landingFace = Part.Face(
                    Part.makePolygon([p1o, p2o, p3o, p4o, p1o]))
                struct = landingFace.extrude(
                    Vector(0, 0, -abs(obj.StructureThickness.Value)))

        if struct:
            self.structures.append(struct)