def constrain(self,point,basepoint=None,axis=None): '''constrain(point,basepoint=None,axis=None: Returns a constrained point. Axis can be "x","y" or "z" or a custom vector. If None, the closest working plane axis will be picked. Basepoint is the base point used to figure out from where the point must be constrained. If no basepoint is given, the current point is used as basepoint.''' # without the Draft module fully loaded, no axes system!" if not hasattr(FreeCAD,"DraftWorkingPlane"): return point point = Vector(point) # setup trackers if needed if not self.constrainLine: self.constrainLine = DraftTrackers.lineTracker(dotted=True) # setting basepoint if not basepoint: if not self.basepoint: self.basepoint = point else: self.basepoint = basepoint delta = point.sub(self.basepoint) # setting constraint axis if not self.affinity: self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) if isinstance(axis,FreeCAD.Vector): self.constraintAxis = axis elif axis == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u elif axis == "y": self.constraintAxis = FreeCAD.DraftWorkingPlane.v elif axis == "z": self.constraintAxis = FreeCAD.DraftWorkingPlane.axis else: if self.affinity == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u elif self.affinity == "y": self.constraintAxis = FreeCAD.DraftWorkingPlane.v else: self.constraintAxis = FreeCAD.DraftWorkingPlane.axis # calculating constrained point cdelta = fcvec.project(delta,self.constraintAxis) npoint = self.basepoint.add(cdelta) # setting constrain line if self.constrainLine: if point != npoint: self.constrainLine.p1(point) self.constrainLine.p2(npoint) self.constrainLine.on() else: self.constrainLine.off() return npoint
def setString(self,text=None): "sets the dim string to the given value or auto value" self.dimnode.param1.setValue(.5) p1 = Vector(self.dimnode.pnts.getValues()[0].getValue()) p2 = Vector(self.dimnode.pnts.getValues()[-1].getValue()) m = self.dimnode.datumtype.getValue() if m == 2: self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(1,0,0))).Length elif m == 3: self.Distance = (DraftVecUtils.project(p2.sub(p1),Vector(0,1,0))).Length else: self.Distance = (p2.sub(p1)).Length if not text: text = Draft.getParam("dimPrecision",2) text = "%."+str(text)+"f" text = (text % self.Distance) self.dimnode.string.setValue(text)
def equals(p1, p2): '''returns True if vertexes have same coordinates within precision amount of digits ''' precision = 12 p = precision u = Vector(p1.X, p1.Y, p1.Z) v = Vector(p2.X, p2.Y, p2.Z) vector = (u.sub(v)) isNull = (round(vector.x, p) == 0 and round(vector.y, p) == 0 and round(vector.z, p) == 0) return isNull
def equals(p1, p2): '''returns True if vertexes have same coordinates within precision amount of digits ''' precision = 12 #hardcoded p = precision u = Vector(p1.X, p1.Y, p1.Z) v = Vector(p2.X, p2.Y, p2.Z) vector = (u.sub(v)) isNull = (round(vector.x, p) == 0 and round(vector.y, p) == 0 and round(vector.z, p) == 0) return isNull
def setString(self, text=None): "sets the dim string to the given value or auto value" self.dimnode.param1.setValue(.5) p1 = Vector(self.dimnode.pnts.getValues()[0].getValue()) p2 = Vector(self.dimnode.pnts.getValues()[-1].getValue()) m = self.dimnode.datumtype.getValue() if m == 2: self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(1, 0, 0))).Length elif m == 3: self.Distance = (DraftVecUtils.project(p2.sub(p1), Vector(0, 1, 0))).Length else: self.Distance = (p2.sub(p1)).Length if not text: text = Draft.getParam("dimPrecision", 2) text = "%." + str(text) + "f" text = (text % self.Distance) self.dimnode.string.setValue(text)
def scaleGhost(self,x,y,z,rel): delta = Vector(x,y,z) if rel: delta = FreeCAD.DraftWorkingPlane.getGlobalCoords(delta) self.ghost.scale(delta) # calculate a correction factor depending on the scaling center corr = Vector(self.node[0].x,self.node[0].y,self.node[0].z) corr.scale(delta.x,delta.y,delta.z) corr = (corr.sub(self.node[0])).negative() self.ghost.move(corr) self.ghost.on()
def update( self, inclination=0.0, anchor_idx=0, base_snap_vertex=None, final_snap_vertex=None, ): '''Update the tracker.''' import DraftGeomUtils if base_snap_vertex == None: base_snap_vertex = Vector(0.0, 0.0, 0.0) if final_snap_vertex == None: final_snap_vertex = Vector(1.0, 0.0, 0.0) bp = base_snap_vertex lvec = final_snap_vertex.sub(bp) self.cube.width.setValue(lvec.Length) inclination = inclination * -1 p = App.Placement() # objet Placement p.Base = bp # base est coordonnée bp if lvec.x == 0 and lvec.y == 0: # orientation verticale up = Vector(0, -1, 0) yaxis = up.cross(lvec) xaxis = lvec.cross(yaxis) if round(lvec.Length, 3) > 0: p.Rotation = App.Rotation(lvec, yaxis, xaxis, "ZXY") p.Rotation = App.Rotation( p.Rotation.multVec(Vector(1, 0, 0)), inclination ).multiply(p.Rotation) else: up = Vector(0, 0, 1) # vector up = Z yaxis = up.cross(lvec) # yaxis = produit vectoriel entre Z et lvec xaxis = lvec.cross(yaxis) # xaxis = produit vectoriel entre lvec et yaxis if round(lvec.Length, 3) > 0: #p.Rotation = App.Rotation(lvec, xaxis, yaxis, "ZXY") p.Rotation = App.Rotation(lvec, yaxis, xaxis, "ZXY") p.Rotation = App.Rotation( p.Rotation.multVec(Vector(1, 0, 0)), inclination ).multiply(p.Rotation) self.setRotation(p.Rotation) delta = self.getDelta(anchor_idx) delta = p.Rotation.multVec(delta) delta = delta.add(lvec.multiply(0.5)) bp = bp.add(delta) self.pos(bp)
def scale(shape, delta=Vector(1, 1, 1), center=Vector(0, 0, 0), copy=True): if copy: sh = shape.copy() else: sh = shape if delta == Vector(1, 1, 1): return sh #if len(obj.Shape.Solids) > 0: # sh.Placement.Base = Vector(0,0,0) # sh.Placement.Rotation.Angle = -obj.Placement.Rotation.Angle # delta = sh.Placement.Rotation.multVec(delta) # sh.Placement.Rotation.Angle = 0 m = FreeCAD.Matrix() m.scale(delta) sh = sh.transformGeometry(m) corr = Vector(center.x, center.y, center.z) corr.scale(delta.x, delta.y, delta.z) corr = (corr.sub(center)).negative() sh.translate(corr) sh.Placement = shape.Placement return sh
def getLength(self): "returns the length of the line" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length
def getSize(self): "returns (length,width) of the rectangle" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[2].getValue()) diag = p2.sub(p1) return ((DraftVecUtils.project(diag,self.u)).Length,(DraftVecUtils.project(diag,self.v)).Length)
def getLength(self): "returns the length of the line" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[-1].getValue()) return (p2.sub(p1)).Length
def getSize(self): "returns (length,width) of the rectangle" p1 = Vector(self.coords.point.getValues()[0].getValue()) p2 = Vector(self.coords.point.getValues()[2].getValue()) diag = p2.sub(p1) return ((DraftVecUtils.project(diag,self.u)).Length,(DraftVecUtils.project(diag,self.v)).Length)
class bimMove(Move): "The bimMove command definition" def __init__(self, sel_dict): super().__init__() self.sel_dict = sel_dict def Activated(self): from bimEdit import hideAttribute self.name = translate("draft","bimMove", utf8_decode=True) Modifier.Activated(self,self.name) self.ghost = {} for typ in self.sel_dict: if typ not in self.ghost: self.ghost.update({typ:[]}) for o in self.sel_dict[typ]: self.ghost[typ].append(o.ghost[typ]) ## Proceeding if self.call: self.view.removeEventCallback("SoEvent",self.call) self.ui.pointUi(self.name) self.ui.modUi() if self.copymode: self.ui.isCopy.setChecked(True) self.ui.xValue.setFocus() self.ui.xValue.selectAll() self.call = self.view.addEventCallback("SoEvent",self.action) msg(translate("draft", "Pick start point:")+"\n") def finish(self,closed=False,cont=False): if self.ghost: for typ in self.ghost: for g in [i for i in self.ghost[typ]]: if g.switch: g.off() g.finalize() if cont and self.ui: if self.ui.continueMode: todo.delayAfter(self.Activated,[]) Modifier.finish(self) def move(self,delta,copy=False): "moving the real shape's bases" FreeCADGui.addModule("Draft") sel_to_edit = [o for typ in self.sel_dict \ for o in self.sel_dict[typ] if typ == 'toEdit'] if copy: obj_to_edit = replica(sel_to_edit) else: obj_to_edit = [s.obj for s in sel_to_edit] sel = '[' for o in obj_to_edit: if len(sel) > 1: sel += ',' sel += 'FreeCAD.ActiveDocument.' + o.Name sel += ']' self.commit(translate("draft","Move"), ['Draft.move('+sel+','+DraftVecUtils.toString(delta)+ \ ',copy=False)', 'FreeCAD.ActiveDocument.recompute()']) def action(self,arg): "scene event handler" if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": #mouse movement detection #if self.ghost: # self.ghost.off() self.point,ctrlPoint,info = getPoint(self,arg) if (len(self.node) > 0): last = self.node[len(self.node)-1] delta = self.point.sub(last) if self.ghost: for typ in self.ghost: if typ == 'toEdit' or typ == 'dirDeps': for g in [i for i in self.ghost[typ]]: g.move(delta) g.on() if self.extendedCopy: if not hasMod(arg,MODALT): self.finish() redraw3DView() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): if self.point: self.ui.redraw() if (self.node == []): self.node.append(self.point) self.ui.isRelative.show() if self.ghost: for typ in self.ghost: if typ == 'toEdit' or typ == 'dirDeps': for g in [i for i in self.ghost[typ]]: g.on() msg(translate("draft", "Pick end point:")+"\n") if self.planetrack: self.planetrack.set(self.point) else: last = self.node[0] if self.ui.isCopy.isChecked() or hasMod(arg,MODALT): self.move(self.point.sub(last),True) else: self.move(self.point.sub(last)) if hasMod(arg,MODALT): self.extendedCopy = True else: self.finish(cont=True) def numericInput(self,numx,numy,numz): "this function gets called by the toolbar when valid x, y, and z \ have been entered there" self.point = Vector(numx,numy,numz) if not self.node: self.node.append(self.point) self.ui.isRelative.show() self.ui.isCopy.show() for typ in self.ghost: if typ == 'toEdit' or typ == 'dirDeps': for g in [i for i in self.ghost[typ]]: g.on() msg(translate("draft", "Pick end point:")+"\n") else: last = self.node[-1] if self.ui.isCopy.isChecked(): self.move(self.point.sub(last),True) else: self.move(self.point.sub(last)) self.finish()
def scale(objectslist,delta=Vector(1,1,1),center=Vector(0,0,0),copy=False,legacy=False): '''scale(objects,vector,[center,copy,legacy]): Scales the objects contained in objects (that can be a list of objects or an object) of the given scale factors defined by the given vector (in X, Y and Z directions) around given center. If legacy is True, direct (old) mode is used, otherwise a parametric copy is made. If copy is True, the actual objects are not moved, but copies are created instead. The objects (or their copies) are returned.''' if not isinstance(objectslist,list): objectslist = [objectslist] if legacy: newobjlist = [] for obj in objectslist: if copy: newobj = makeCopy(obj) else: newobj = obj if obj.isDerivedFrom("Part::Feature"): sh = obj.Shape.copy() m = FreeCAD.Matrix() m.scale(delta) sh = sh.transformGeometry(m) corr = Vector(center.x,center.y,center.z) corr.scale(delta.x,delta.y,delta.z) corr = (corr.sub(center)).negative() sh.translate(corr) if getType(obj) == "Rectangle": p = [] for v in sh.Vertexes: p.append(v.Point) pl = obj.Placement.copy() pl.Base = p[0] diag = p[2].sub(p[0]) bb = p[1].sub(p[0]) bh = p[3].sub(p[0]) nb = DraftVecUtils.project(diag,bb) nh = DraftVecUtils.project(diag,bh) if obj.Length < 0: l = -nb.Length else: l = nb.Length if obj.Height < 0: h = -nh.Length else: h = nh.Length newobj.Length = l newobj.Height = h tr = p[0].sub(obj.Shape.Vertexes[0].Point) newobj.Placement = pl elif getType(obj) == "Wire": p = [] for v in sh.Vertexes: p.append(v.Point) #print(p) newobj.Points = p elif getType(obj) == "BSpline": p = [] for p1 in obj.Points: p2 = p1.sub(center) p2.scale(delta.x,delta.y,delta.z) p.append(p2) newobj.Points = p elif (obj.isDerivedFrom("Part::Feature")): newobj.Shape = sh elif (obj.TypeId == "App::Annotation"): factor = delta.y * obj.ViewObject.FontSize newobj.ViewObject.FontSize = factor d = obj.Position.sub(center) newobj.Position = center.add(Vector(d.x*delta.x,d.y*delta.y,d.z*delta.z)) if copy: formatObject(newobj,obj) newobjlist.append(newobj) if copy and getParam("selectBaseObjects",False): select(objectslist) else: select(newobjlist) if len(newobjlist) == 1: return newobjlist[0] return newobjlist else: obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Scale") _Clone(obj) obj.Objects = objectslist obj.Scale = delta corr = Vector(center.x,center.y,center.z) corr.scale(delta.x,delta.y,delta.z) corr = (corr.sub(center)).negative() p = obj.Placement p.move(corr) obj.Placement = p if not copy: for o in objectslist: o.ViewObject.hide() if gui: _ViewProviderClone(obj.ViewObject) formatObject(obj,objectslist[-1]) select(obj) return obj
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 = 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 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 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 = DraftVecUtils.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" 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 = 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 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
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
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
def constrain(self, point, basepoint=None, axis=None): '''constrain(point,basepoint=None,axis=None: Returns a constrained point. Axis can be "x","y" or "z" or a custom vector. If None, the closest working plane axis will be picked. Basepoint is the base point used to figure out from where the point must be constrained. If no basepoint is given, the current point is used as basepoint.''' # without the Draft module fully loaded, no axes system!" if not hasattr(FreeCAD, "DraftWorkingPlane"): return point point = Vector(point) # setup trackers if needed if not self.constrainLine: self.constrainLine = DraftTrackers.lineTracker(dotted=True) # setting basepoint if not basepoint: if not self.basepoint: self.basepoint = point else: self.basepoint = basepoint delta = point.sub(self.basepoint) # setting constraint axis if self.mask: self.affinity = self.mask if not self.affinity: self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta) if isinstance(axis, FreeCAD.Vector): self.constraintAxis = axis elif axis == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u elif axis == "y": self.constraintAxis = FreeCAD.DraftWorkingPlane.v elif axis == "z": self.constraintAxis = FreeCAD.DraftWorkingPlane.axis else: if self.affinity == "x": self.constraintAxis = FreeCAD.DraftWorkingPlane.u elif self.affinity == "y": self.constraintAxis = FreeCAD.DraftWorkingPlane.v else: self.constraintAxis = FreeCAD.DraftWorkingPlane.axis # calculating constrained point cdelta = DraftVecUtils.project(delta, self.constraintAxis) npoint = self.basepoint.add(cdelta) # setting constrain line if self.constrainLine: if point != npoint: self.constrainLine.p1(point) self.constrainLine.p2(npoint) self.constrainLine.on() else: self.constrainLine.off() return npoint
def validate_datum(self): """ Ensure the datum is valid, assuming 0+00 / (0,0,0) for station and coordinate where none is suplpied and it cannot be inferred fromt the starting geometry """ _datum = self.data.get('meta') _geo = self.data.get('geometry')[0] if not _geo or not _datum: print('Unable to validate alignment datum') return _datum_truth = [not _datum.get('StartStation') is None, not _datum.get('Start') is None] _geo_truth = [not _geo.get('StartStation') is None, not _geo.get('Start') is None] #---------------------------- #CASE 0 #---------------------------- #both defined? nothing to do if all(_datum_truth): return #---------------------------- #Parameter Initialization #---------------------------- _geo_station = 0 _geo_start = Vector() if _geo_truth[0]: _geo_station = _geo.get('StartStation') if _geo_truth[1]: _geo_start = _geo.get('Start') #--------------------- #CASE 1 #--------------------- #no datum defined? use initial geometry or zero defaults if not any(_datum_truth): _datum['StartStation'] = _geo_station _datum['Start'] = _geo_start return #-------------------- #CASE 2 #-------------------- #station defined? #if the geometry has a station and coordinate, #project the start coordinate if _datum_truth[0]: _datum['Start'] = _geo_start #assume geometry start if no geometry station if not _geo_truth[0]: return #scale the distance to the system units delta = _geo_station - _datum['StartStation'] #cutoff if error is below tolerance if not support.within_tolerance(delta): delta *= units.scale_factor() else: delta = 0.0 #assume geometry start if station delta is zero if delta: #calculate the start based on station delta _datum['Start'] = _datum.get('Start').sub( support.vector_from_angle( _geo.get('BearingIn')).multiply(delta) ) return #--------------------- #CASE 3 #--------------------- #datum start coordinate is defined #if the geometry has station and coordinate, #project the start station _datum['StartStation'] = _geo_station #assume geometry station if no geometry start if _geo_truth[1]: #scale the length to the document units delta = \ _geo_start.sub(_datum.get('Start')).Length/units.scale_factor() _datum['StartStation'] -= delta