diff --git a/src/Mod/Arch/ArchAxis.py b/src/Mod/Arch/ArchAxis.py index 5bd132941..26de5866c 100644 --- a/src/Mod/Arch/ArchAxis.py +++ b/src/Mod/Arch/ArchAxis.py @@ -21,7 +21,7 @@ #* * #*************************************************************************** -import FreeCAD,FreeCADGui,Draft,math,DraftVecUtils +import FreeCAD,FreeCADGui,Draft,math,DraftVecUtils,ArchCommands from FreeCAD import Vector from PyQt4 import QtCore, QtGui from pivy import coin diff --git a/src/Mod/Draft/DraftTools.py b/src/Mod/Draft/DraftTools.py index 9f3b6fde0..f8eeca25e 100644 --- a/src/Mod/Draft/DraftTools.py +++ b/src/Mod/Draft/DraftTools.py @@ -333,10 +333,15 @@ class SelectPlane(DraftTool): sel = FreeCADGui.Selection.getSelectionEx() if len(sel) == 1: sel = sel[0] - if sel.HasSubObjects: + self.ui = FreeCADGui.draftToolBar + if Draft.getType(sel.Object) == "Axis": + plane.alignToEdges(sel.Object.Shape.Edges) + self.display(plane.axis) + self.finish() + return + elif sel.HasSubObjects: if len(sel.SubElementNames) == 1: if "Face" in sel.SubElementNames[0]: - self.ui = FreeCADGui.draftToolBar plane.alignToFace(sel.SubObjects[0], self.offset) self.display(plane.axis) self.finish() diff --git a/src/Mod/Draft/WorkingPlane.py b/src/Mod/Draft/WorkingPlane.py index 6c1d6066b..ddac62c5a 100644 --- a/src/Mod/Draft/WorkingPlane.py +++ b/src/Mod/Draft/WorkingPlane.py @@ -34,267 +34,284 @@ This module provides a class called plane to assist in selecting and maintaining ''' class plane: - '''A WorkPlane object''' + '''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 __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 __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 - ''' + 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: + ''' + A picture will help explain the computation: p //| / / | - / / | - / / | - / / | - -------------------- plane -----c-----x-----a-------- + / / | + / / | + / / | + -------------------- 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, + 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)) + ''' + 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 = DraftVecUtils.neg(direction) - 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 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 = DraftVecUtils.neg(direction) + 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 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 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 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 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 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 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 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 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 reset(self): - self.doc = None - self.weak = True + 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 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 reset(self): + self.doc = None + self.weak = True - 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 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 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 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 save(self): - "stores the current plane state" - self.stored = [self.u,self.v,self.axis,self.position,self.weak] + 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 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 save(self): + "stores the current plane state" + self.stored = [self.u,self.v,self.axis,self.position,self.weak] - 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 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 getGlobalCoords(self,point): - "returns the global coordinates of the given point, taken relatively to this working plane" - vx = DraftVecUtils.scale(self.u,point.x) - vy = DraftVecUtils.scale(self.v,point.y) - vz = DraftVecUtils.scale(self.axis,point.z) - pt = (vx.add(vy)).add(vz) - return pt.add(self.position) + 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 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 getGlobalCoords(self,point): + "returns the global coordinates of the given point, taken relatively to this working plane" + vx = DraftVecUtils.scale(self.u,point.x) + vy = DraftVecUtils.scale(self.v,point.y) + vz = DraftVecUtils.scale(self.axis,point.z) + pt = (vx.add(vy)).add(vz) + return pt.add(self.position) - def getGlobalRot(self,point): - "Same as getGlobalCoords, but discards the WP position" - vx = DraftVecUtils.scale(self.u,point.x) - vy = DraftVecUtils.scale(self.v,point.y) - vz = DraftVecUtils.scale(self.axis,point.z) - pt = (vx.add(vy)).add(vz) - return pt + 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 = DraftVecUtils.scale(self.u,point.x) + vy = DraftVecUtils.scale(self.v,point.y) + vz = DraftVecUtils.scale(self.axis,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(DraftVecUtils.neg(self.u)) - by = point.getAngle(DraftVecUtils.neg(self.v)) - bz = point.getAngle(DraftVecUtils.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 + 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(DraftVecUtils.neg(self.u)) + by = point.getAngle(DraftVecUtils.neg(self.v)) + bz = point.getAngle(DraftVecUtils.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 def getPlacementFromPoints(points): - "returns a placement from a list of 3 or 4 vectors" - pl = plane() - try: - pl.position = points[0] - pl.u = (points[1].sub(points[0]).normalize()) - pl.v = (points[2].sub(points[0]).normalize()) - if len(points) == 4: - pl.axis = (points[3].sub(points[0]).normalize()) - else: - pl.axis = ((pl.u).cross(pl.v)).normalize() - except: - pass - p = pl.getPlacement() - del pl - return p + "returns a placement from a list of 3 or 4 vectors" + pl = plane() + try: + pl.position = points[0] + pl.u = (points[1].sub(points[0]).normalize()) + pl.v = (points[2].sub(points[0]).normalize()) + if len(points) == 4: + pl.axis = (points[3].sub(points[0]).normalize()) + else: + pl.axis = ((pl.u).cross(pl.v)).normalize() + except: + pass + p = pl.getPlacement() + del pl + return p