def getbase(wire): "returns a full shape from a base wire" dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) dvec.normalize() if obj.Align == "Left": dvec = dvec.multiply(width) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Right": dvec = dvec.multiply(width) dvec = DraftVecUtils.neg(dvec) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Center": dvec = dvec.multiply(width/2) w1 = DraftGeomUtils.offsetWire(wire,dvec) dvec = DraftVecUtils.neg(dvec) w2 = DraftGeomUtils.offsetWire(wire,dvec) sh = DraftGeomUtils.bind(w1,w2) # fixing self-intersections sh.fix(0.1,0,1) if height and (not flat): norm = Vector(normal).multiply(height) sh = sh.extrude(norm) return sh
def backHigher(self, i): import DraftGeomUtils profilCurrent = self.findProfil(i) profilBack1 = self.findProfil(i - 1) dec = profilCurrent["height"] / math.tan(math.radians(profilBack1["angle"])) edgeRidgeOnPane = DraftGeomUtils.offset( profilBack1["edge"], self.getPerpendicular(profilBack1["vec"], profilBack1["rot"], dec) ) ptInterRidges = DraftGeomUtils.findIntersection( edgeRidgeOnPane, profilCurrent["ridge"], infinite1=True, infinite2=True ) self.ptsPaneProject.append(FreeCAD.Vector(ptInterRidges[0])) edgeHip = DraftGeomUtils.edg(FreeCAD.Vector(ptInterRidges[0]), profilCurrent["edge"].Vertexes[0].Point) ptInterHipEave = DraftGeomUtils.findIntersection( edgeHip, profilCurrent["eave"], infinite1=True, infinite2=False ) if ptInterHipEave: self.ptsPaneProject.append(FreeCAD.Vector(ptInterHipEave[0])) else: ptInterHipEave = DraftGeomUtils.findIntersection( edgeHip, profilBack1["eaveD"], infinite1=True, infinite2=True ) self.ptsPaneProject.append(FreeCAD.Vector(ptInterHipEave[0])) self.ptsPaneProject.append(FreeCAD.Vector(profilCurrent["eave"].Vertexes[0].Point[0]))
def update(self, line=None, normal=None): import WorkingPlane, DraftGeomUtils if not normal: normal = FreeCAD.DraftWorkingPlane.axis if line: if isinstance(line, list): bp = line[0] lvec = line[1].sub(line[0]) else: lvec = DraftGeomUtils.vec(line.Shape.Edges[0]) bp = line.Shape.Edges[0].Vertexes[0].Point elif self.baseline: lvec = DraftGeomUtils.vec(self.baseline.Shape.Edges[0]) bp = self.baseline.Shape.Edges[0].Vertexes[0].Point else: return right = lvec.cross(normal) self.cube.width.setValue(lvec.Length) p = WorkingPlane.getPlacementFromPoints([bp, bp.add(lvec), bp.add(right)]) if p: self.trans.rotation.setValue(p.Rotation.Q) bp = bp.add(lvec.multiply(0.5)) bp = bp.add(DraftVecUtils.scaleTo(normal, self.cube.depth.getValue() / 2)) self.pos(bp)
def getIndices(shape,offset): "returns a list with 2 lists: vertices and face indexes, offsetted with the given amount" vlist = [] elist = [] flist = [] for v in shape.Vertexes: vlist.append(" "+str(round(v.X,p))+" "+str(round(v.Y,p))+" "+str(round(v.Z,p))) if not shape.Faces: for e in shape.Edges: if DraftGeomUtils.geomType(e) == "Line": ei = " " + str(findVert(e.Vertexes[0],shape.Vertexes) + offset) ei += " " + str(findVert(e.Vertexes[-1],shape.Vertexes) + offset) elist.append(ei) for f in shape.Faces: if len(f.Wires) > 1: # if we have holes, we triangulate tris = f.tessellate(1) for fdata in tris[1]: fi = "" for vi in fdata: vdata = Part.Vertex(tris[0][vi]) fi += " " + str(findVert(vdata,shape.Vertexes) + offset) flist.append(fi) else: fi = "" # OCC vertices are unsorted. We need to sort in the right order... edges = DraftGeomUtils.sortEdges(f.Wire.Edges) #print edges for e in edges: #print e.Vertexes[0].Point,e.Vertexes[1].Point v = e.Vertexes[0] fi += " " + str(findVert(v,shape.Vertexes) + offset) flist.append(fi) return vlist,elist,flist
def findPivotIntersection(self, pivot, pivotEdge, edge, refPt, d, color): debugPrint( "Intersection (%.2f, %.2f)^%.2f - [(%.2f, %.2f), (%.2f, %.2f)]" % ( pivotEdge.Curve.Center.x, pivotEdge.Curve.Center.y, pivotEdge.Curve.Radius, edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y, ) ) ppt = None pptDistance = 0 for pt in DraftGeomUtils.findIntersection(edge, pivotEdge, dts=False): # debugMarker(pt, "pti.%d-%s.in" % (self.boneId, d), color, 0.2) distance = (pt - refPt).Length debugPrint(" --> (%.2f, %.2f): %.2f" % (pt.x, pt.y, distance)) if not ppt or pptDistance < distance: ppt = pt pptDistance = distance if not ppt: tangent = DraftGeomUtils.findDistance(pivot, edge) if tangent: debugPrint("Taking tangent as intersect %s" % tangent) ppt = pivot + tangent else: debugPrint("Taking chord start as intersect %s" % inChordStart) ppt = inChord.Start # debugMarker(ppt, "ptt.%d-%s.in" % (self.boneId, d), color, 0.2) debugPrint(" --> (%.2f, %.2f)" % (ppt.x, ppt.y)) return ppt
def getBase(self,obj,wire,normal,width,height): "returns a full shape from a base wire" import DraftGeomUtils,Part flat = False if hasattr(obj.ViewObject,"DisplayMode"): flat = (obj.ViewObject.DisplayMode == "Flat 2D") dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) if not DraftVecUtils.isNull(dvec): dvec.normalize() if obj.Align == "Left": dvec = dvec.multiply(width) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Right": dvec = dvec.multiply(width) dvec = DraftVecUtils.neg(dvec) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Center": dvec = dvec.multiply(width/2) w1 = DraftGeomUtils.offsetWire(wire,dvec) dvec = DraftVecUtils.neg(dvec) w2 = DraftGeomUtils.offsetWire(wire,dvec) sh = DraftGeomUtils.bind(w1,w2) # fixing self-intersections sh.fix(0.1,0,1) if height and (not flat): norm = Vector(normal).multiply(height) sh = sh.extrude(norm) return sh
def calcEave(self, i): import DraftGeomUtils pt0Eave1 = DraftGeomUtils.findIntersection(self.findProfil(i-1)["eaveD"],self.findProfil(i)["eaveD"],infinite1=True,infinite2=True,) pt1Eave1 = DraftGeomUtils.findIntersection(self.findProfil(i)["eaveD"],self.findProfil(i+1)["eaveD"],infinite1=True,infinite2=True,) eave = DraftGeomUtils.edg(FreeCAD.Vector(pt0Eave1[0]),FreeCAD.Vector(pt1Eave1[0])) self.profilsDico[i]["eave"] = eave
def removeShape(objs, mark=True): """takes an arch object (wall or structure) built on a cubic shape, and removes the inner shape, keeping its length, width and height as parameters.""" import DraftGeomUtils if not isinstance(objs, list): objs = [objs] for obj in objs: if DraftGeomUtils.isCubic(obj.Shape): dims = DraftGeomUtils.getCubicDimensions(obj.Shape) if dims: name = obj.Name tp = Draft.getType(obj) print tp if tp == "Structure": FreeCAD.ActiveDocument.removeObject(name) import ArchStructure str = ArchStructure.makeStructure(length=dims[1], width=dims[2], height=dims[3], name=name) str.Placement = dims[0] elif tp == "Wall": FreeCAD.ActiveDocument.removeObject(name) import ArchWall length = dims[1] width = dims[2] v1 = Vector(length / 2, 0, 0) v2 = v1.negative() v1 = dims[0].multVec(v1) v2 = dims[0].multVec(v2) line = Draft.makeLine(v1, v2) wal = ArchWall.makeWall(line, width=width, height=dims[3], name=name) else: if mark: obj.ViewObject.ShapeColor = (1.0, 0.0, 0.0, 1.0)
def connectNodes(self,other=None): if not other: self.observer = StructSelectionObserver(self.connectNodes) FreeCADGui.Selection.addObserver(self.observer) FreeCAD.Console.PrintMessage(translate("Arch","Pick another Structure object: ")) else: FreeCADGui.Selection.removeObserver(self.observer) self.observer = None if Draft.getType(other) != "Structure": FreeCAD.Console.PrintError(translate("Arch","The picked object is not a Structure\n")) else: if not other.Nodes: FreeCAD.Console.PrintError(translate("Arch","The picked object has no structural nodes\n")) else: if (len(self.Object.Nodes) != 2) or (len(other.Nodes) != 2): FreeCAD.Console.PrintError(translate("Arch","One of these objects has more than 2 nodes\n")) else: import DraftGeomUtils nodes1 = [self.Object.Placement.multVec(v) for v in self.Object.Nodes] nodes2 = [other.Placement.multVec(v) for v in other.Nodes] intersect = DraftGeomUtils.findIntersection(nodes1[0],nodes1[1],nodes2[0],nodes2[1],True,True) if not intersect: FreeCAD.Console.PrintError(translate("Arch","Unable to find a suitable intersection point\n")) else: intersect = intersect[0] FreeCAD.Console.PrintMessage(translate("Arch","Intersection found.\n")) if DraftGeomUtils.findClosest(intersect,nodes1) == 0: self.Object.Nodes = [self.Object.Placement.inverse().multVec(intersect),self.Object.Nodes[1]] else: self.Object.Nodes = [self.Object.Nodes[0],self.Object.Placement.inverse().multVec(intersect)] if DraftGeomUtils.findClosest(intersect,nodes2) == 0: other.Nodes = [other.Placement.inverse().multVec(intersect),other.Nodes[1]] else: other.Nodes = [other.Nodes[0],other.Placement.inverse().multVec(intersect)]
def backPignon(self, i): import DraftGeomUtils profilCurrent = self.findProfil(i) profilBack1 = self.findProfil(i - 1) profilBack2 = self.findProfil(i - 2) pt1 = DraftGeomUtils.findIntersection( profilCurrent["edge"], profilBack1["eave"], infinite1=True, infinite2=True ) pt2 = DraftGeomUtils.findIntersection(profilBack2["edge"], profilBack1["eave"], infinite1=True, infinite2=True) eaveWithoutOverhang = DraftGeomUtils.edg(pt1[0], pt2[0]) if profilCurrent["run"] + profilBack2["run"] != eaveWithoutOverhang.Length: points = [FreeCAD.Vector(0.0, 0.0, 0.0)] points.append(FreeCAD.Vector(profilCurrent["run"], profilCurrent["height"], 0.0)) rampantCurrent = DraftGeomUtils.edg(points[0], points[1]) points = [FreeCAD.Vector(eaveWithoutOverhang.Length, 0.0, 0.0)] points.append(FreeCAD.Vector(eaveWithoutOverhang.Length - profilBack2["run"], profilBack2["height"], 0.0)) rampantBack2 = DraftGeomUtils.edg(points[0], points[1]) point = DraftGeomUtils.findIntersection(rampantCurrent, rampantBack2, infinite1=True, infinite2=True) ridgeCurrent = DraftGeomUtils.offset( profilCurrent["edge"], self.getPerpendicular(profilCurrent["vec"], profilCurrent["rot"], point[0].x) ) point = DraftGeomUtils.findIntersection(ridgeCurrent, profilBack1["eave"], infinite1=True, infinite2=True) else: point = DraftGeomUtils.findIntersection( profilCurrent["ridge"], profilBack1["eave"], infinite1=True, infinite2=True ) self.ptsPaneProject.append(FreeCAD.Vector(point[0])) point = DraftGeomUtils.findIntersection( profilCurrent["eave"], profilBack1["eave"], infinite1=True, infinite2=True ) self.ptsPaneProject.append(FreeCAD.Vector(point[0]))
def nextPignon(self, i): print("Next : pignon") profilCurrent = self.findProfil(i) profilNext1 = self.findProfil(i+1) profilNext2 = self.findProfil(i+2) point = DraftGeomUtils.findIntersection(profilCurrent["eave"],profilNext1["eave"],infinite1=True,infinite2=True,) print "a ",FreeCAD.Vector(point[0]) self.ptsPaneProject.append(FreeCAD.Vector(point[0])) pt1 = DraftGeomUtils.findIntersection(profilCurrent["edge"],profilNext1["eave"],infinite1=True,infinite2=True,) pt2 = DraftGeomUtils.findIntersection(profilNext2["edge"],profilNext1["eave"],infinite1=True,infinite2=True,) eaveWithoutOverhang = DraftGeomUtils.edg(pt1[0],pt2[0]) if profilCurrent["run"]+profilNext2["run"] != eaveWithoutOverhang.Length : points = [FreeCAD.Vector(0.0,0.0,0.0),] points.append(FreeCAD.Vector(profilCurrent["run"],profilCurrent["height"],0.0)) rampantCurrent = DraftGeomUtils.edg(points[0],points[1]) points = [FreeCAD.Vector(eaveWithoutOverhang.Length,0.0,0.0),] points.append(FreeCAD.Vector(eaveWithoutOverhang.Length-profilNext2["run"],profilNext2["height"],0.0)) rampantNext2 = DraftGeomUtils.edg(points[0],points[1]) point = DraftGeomUtils.findIntersection(rampantCurrent,rampantNext2,infinite1=True,infinite2=True,) ridgeCurrent = DraftGeomUtils.offset(profilCurrent["edge"],self.getPerpendicular(profilCurrent["vec"],profilCurrent["rot"],point[0].x)) point = DraftGeomUtils.findIntersection(ridgeCurrent,profilNext1["eave"],infinite1=True,infinite2=True,) else: point = DraftGeomUtils.findIntersection(profilCurrent["ridge"],profilNext1["eaveD"],infinite1=True,infinite2=True,) print "b ",FreeCAD.Vector(point[0]) self.ptsPaneProject.append(FreeCAD.Vector(point[0]))
def SortPath(wire, Side, radius, clockwise, firstedge=None, SegLen=0.5): """SortPath(wire,Side,radius,clockwise,firstedge=None,SegLen =0.5) Sorts the wire and reverses it, if needed. Splits arcs over 180 degrees in two. Returns the reordered offset of the wire. """ if firstedge: edgelist = wire.Edges[:] if wire.isClosed(): elindex = None n = 0 for e in edgelist: if isSameEdge(e, firstedge): # FreeCAD.Console.PrintMessage('found first edge\n') elindex = n n = n + 1 l1 = edgelist[:elindex] l2 = edgelist[elindex:] newedgelist = l2 + l1 if clockwise: newedgelist.reverse() last = newedgelist.pop(-1) newedgelist.insert(0, last) preoffset = [] for e in newedgelist: if clockwise: r = reverseEdge(e) preoffset.append(r) else: preoffset.append(e) sortedpreoff = DraftGeomUtils.sortEdgesOld(preoffset) wire = Part.Wire(sortedpreoff) else: sortedpreoff = DraftGeomUtils.sortEdgesOld(edgelist) wire = Part.Wire(sortedpreoff) edgelist = [] for e in wire.Edges: if geomType(e) == "Circle": arclist = filterArcs(e) for a in arclist: edgelist.append(a) elif geomType(e) == "Line": edgelist.append(e) elif geomType(e) == "BSplineCurve" or geomType(e) == "BezierCurve" or geomType(e) == "Ellipse": edgelist.append(Part.Wire(curvetowire(e, (SegLen)))) newwire = Part.Wire(edgelist) if Side == "Left": # we use the OCC offset feature offset = newwire.makeOffset(radius) # tool is outside line elif Side == "Right": offset = newwire.makeOffset(-radius) # tool is inside line else: if wire.isClosed(): offset = newwire.makeOffset(0.0) else: offset = newwire return offset
def export(exportList,filename): "called when freecad exports a file" faces = [] edges = [] # getting faces and edges for ob in exportList: if ob.Shape.Faces: for f in ob.Shape.Faces: faces.append(f) else: for e in ob.Shape.Edges: edges.append(e) if not (edges or faces): print("oca: found no data to export") return # writing file oca = pythonopen(filename,'wb') oca.write("#oca file generated from FreeCAD\r\n") oca.write("# edges\r\n") count = 1 for e in edges: if DraftGeomUtils.geomType(e) == "Line": oca.write("L"+str(count)+"=") oca.write(writepoint(e.Vertexes[0].Point)) oca.write(" ") oca.write(writepoint(e.Vertexes[-1].Point)) oca.write("\r\n") elif DraftGeomUtils.geomType(e) == "Circle": if (len(e.Vertexes) > 1): oca.write("C"+str(count)+"=ARC ") oca.write(writepoint(e.Vertexes[0].Point)) oca.write(" ") oca.write(writepoint(DraftGeomUtils.findMidpoint(e))) oca.write(" ") oca.write(writepoint(e.Vertexes[-1].Point)) else: oca.write("C"+str(count)+"= ") oca.write(writepoint(e.Curve.Center)) oca.write(" ") oca.write(str(e.Curve.Radius)) oca.write("\r\n") count += 1 oca.write("# faces\r\n") for f in faces: oca.write("A"+str(count)+"=S(POL") for v in f.Vertexes: oca.write(" ") oca.write(writepoint(v.Point)) oca.write(" ") oca.write(writepoint(f.Vertexes[0].Point)) oca.write(")\r\n") count += 1 # closing oca.close() FreeCAD.Console.PrintMessage("successfully exported "+filename)
def getIndices(shape,offset): "returns a list with 2 lists: vertices and face indexes, offsetted with the given amount" vlist = [] elist = [] flist = [] curves = None for e in shape.Edges: try: if not isinstance(e.Curve,Part.Line): if not curves: curves = shape.tessellate(1) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating\n")) break except: # unimplemented curve type curves = shape.tessellate(1) FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating\n")) break if curves: for v in curves[0]: vlist.append(" "+str(round(v.x,p))+" "+str(round(v.y,p))+" "+str(round(v.z,p))) for f in curves[1]: fi = "" for vi in f: fi += " " + str(vi + offset) flist.append(fi) else: for v in shape.Vertexes: vlist.append(" "+str(round(v.X,p))+" "+str(round(v.Y,p))+" "+str(round(v.Z,p))) if not shape.Faces: for e in shape.Edges: if DraftGeomUtils.geomType(e) == "Line": ei = " " + str(findVert(e.Vertexes[0],shape.Vertexes) + offset) ei += " " + str(findVert(e.Vertexes[-1],shape.Vertexes) + offset) elist.append(ei) for f in shape.Faces: if len(f.Wires) > 1: # if we have holes, we triangulate tris = f.tessellate(1) for fdata in tris[1]: fi = "" for vi in fdata: vdata = Part.Vertex(tris[0][vi]) fi += " " + str(findVert(vdata,shape.Vertexes) + offset) flist.append(fi) else: fi = "" # OCC vertices are unsorted. We need to sort in the right order... edges = DraftGeomUtils.sortEdges(f.OuterWire.Edges) #print edges for e in edges: #print e.Vertexes[0].Point,e.Vertexes[1].Point v = e.Vertexes[0] fi += " " + str(findVert(v,shape.Vertexes) + offset) flist.append(fi) return vlist,elist,flist
def calcDraftEdges(self, i): edge = self.profilsDico[i]["edge"] vec = self.profilsDico[i]["vec"] rot = self.profilsDico[i]["rot"] overhang = self.profilsDico[i]["overhang"] run = self.profilsDico[i]["run"] perpendicular = self.getPerpendicular(vec,rot,self.profilsDico[i]["overhang"]).negative() eave = DraftGeomUtils.offset(edge,perpendicular) self.profilsDico[i]["eaveD"] = eave perpendicular = self.getPerpendicular(vec,rot,self.profilsDico[i]["run"]) ridge = DraftGeomUtils.offset(edge,perpendicular) self.profilsDico[i]["ridge"] = ridge
def backSameHeight(self, i): import DraftGeomUtils profilCurrent = self.findProfil(i) profilBack1 = self.findProfil(i-1) ptInterRidges = DraftGeomUtils.findIntersection(profilCurrent["ridge"],profilBack1["ridge"],infinite1=True,infinite2=True,) self.ptsPaneProject.append(FreeCAD.Vector(ptInterRidges[0])) edgeHip = DraftGeomUtils.edg(FreeCAD.Vector(ptInterRidges[0]),profilCurrent["edge"].Vertexes[0].Point) ptInterHipEave = DraftGeomUtils.findIntersection(edgeHip,profilCurrent["eave"],infinite1=True,infinite2=False,) if ptInterHipEave: self.ptsPaneProject.append(FreeCAD.Vector(ptInterHipEave[0])) else: ptInterHipEave = DraftGeomUtils.findIntersection(edgeHip,profilBack1["eaveD"],infinite1=True,infinite2=True,) self.ptsPaneProject.append(FreeCAD.Vector(ptInterHipEave[0])) self.ptsPaneProject.append(FreeCAD.Vector(profilCurrent["eave"].Vertexes[0].Point))
def getBaseAndAxis(self,wire): "returns a base point and orientation axis from the base wire" import DraftGeomUtils if wire: e = wire.Edges[0] v = DraftGeomUtils.vec(e).normalize() return e.Vertexes[0].Point,v if obj.Base: if obj.Base.Shape: if obj.Base.Shape.Wires: e = obj.Base.Shape.Wires[0].Edges[0] v = DraftGeomUtils.vec(e).normalize() return e.Vertexes[0].Point,v return None,None
def intersectionCLines(thing1=None, thing2=None): ''' intersectionCLines(thing1=None, thing2=None) Returns the intersection (vector) of the center lines of thing1 and thing2. Things can be any combination of intersecting beams, pipes or edges. If less than 2 arguments are given, thing1 and thing2 are the first 2 beams or pipes found in the selection set. ''' if not (thing1 and thing2): try: thing1,thing2=beams()[:2] except: FreeCAD.Console.PrintError('Insufficient arguments for intersectionCLines\n') return None edges=[] for thing in [thing1,thing2]: if beams([thing]): edges.append(vec2edge(thing.Placement.Base,beamAx(thing))) elif hasattr(thing,'ShapeType') and thing.ShapeType=='Edge': edges.append(thing) intersections=dgu.findIntersection(*edges, infinite1=True, infinite2=True) if len(intersections): return rounded(intersections[0]) else: FreeCAD.Console.PrintError('No intersection found\n') return None
def findHoles(self, obj, shape): import DraftGeomUtils as dgu PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] tooldiameter = obj.ToolController.Proxy.getTool(obj.ToolController).Diameter PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter)) if dgu.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode())) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): PathLog.debug('face candidate: {} is drillable '.format(f)) x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True}) PathLog.debug("holes found: {}".format(holelist)) return holelist
def rebase(self,shape): import DraftGeomUtils,math if not isinstance(shape,list): shape = [shape] if hasattr(shape[0],"CenterOfMass"): v = shape[0].CenterOfMass else: v = shape[0].BoundBox.Center n = DraftGeomUtils.getNormal(shape[0]) r = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),n) if round(r.Angle,8) == round(math.pi,8): r = FreeCAD.Rotation() shapes = [] for s in shape: s = s.copy() s.translate(v.negative()) s.rotate(FreeCAD.Vector(0,0,0),r.inverted().Axis,math.degrees(r.inverted().Angle)) shapes.append(s) p = FreeCAD.Placement() p.Base = v p.Rotation = r if len(shapes) == 1: return (shapes[0],p) else: return(shapes,p)
def calc(self): import Part if (self.p1 != None) and (self.p2 != None): points = [ DraftVecUtils.tup(self.p1, True), DraftVecUtils.tup(self.p2, True), DraftVecUtils.tup(self.p1, True), DraftVecUtils.tup(self.p2, True), ] if self.p3 != None: p1 = self.p1 p4 = self.p2 if DraftVecUtils.equals(p1, p4): proj = None else: base = Part.Line(p1, p4).toShape() proj = DraftGeomUtils.findDistance(self.p3, base) if not proj: p2 = p1 p3 = p4 else: p2 = p1.add(proj.negative()) p3 = p4.add(proj.negative()) points = [DraftVecUtils.tup(p1), DraftVecUtils.tup(p2), DraftVecUtils.tup(p3), DraftVecUtils.tup(p4)] self.coords.point.setValues(0, 4, points)
def rebase(self,shape): """returns a shape that is a copy of the original shape but centered on the (0,0) origin, and a placement that is needed to reposition that shape to its original location/orientation""" import DraftGeomUtils,math if not isinstance(shape,list): shape = [shape] if hasattr(shape[0],"CenterOfMass"): v = shape[0].CenterOfMass else: v = shape[0].BoundBox.Center n = DraftGeomUtils.getNormal(shape[0]) r = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),n) if round(abs(r.Angle),8) == round(math.pi,8): r = FreeCAD.Rotation() shapes = [] for s in shape: s = s.copy() s.translate(v.negative()) s.rotate(FreeCAD.Vector(0,0,0),r.Axis,math.degrees(-r.Angle)) shapes.append(s) p = FreeCAD.Placement() p.Base = v p.Rotation = r if len(shapes) == 1: return (shapes[0],p) else: return(shapes,p)
def getTuples(data, scale=1, placement=None): """getTuples(data,[scale,placement]): returns a tuple or a list of tuples from a vector or from the vertices of a shape. Scale can indicate a scale factor""" import Part if isinstance(data, FreeCAD.Vector): if placement: data = placement.multVec(data) return (data.x * scale, data.y * scale, data.z * scale) elif isinstance(data, Part.Shape): t = [] if len(data.Wires) == 1: import Part, DraftGeomUtils data = Part.Wire(DraftGeomUtils.sortEdges(data.Wires[0].Edges)) verts = data.Vertexes # verts.reverse() for v in verts: pt = v.Point if placement: pt = placement.multVec(pt) t.append((pt.x * scale, pt.y * scale, pt.z * scale)) t.append(t[0]) # for IFC verts lists must be closed else: print "Arch.getTuples(): Wrong profile data" return t
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)
def projectFace(self,face): "projects a single face on the WP" #print "VRM: projectFace start: ",len(face[0].Vertexes)," verts, ",len(face[0].Edges)," edges" wires = [] if not face[0].Wires: if DEBUG: print "Error: Unable to project face on the WP" return None norm = face[0].normalAt(0,0) for w in face[0].Wires: verts = [] edges = DraftGeomUtils.sortEdges(w.Edges) #print len(edges)," edges after sorting" for e in edges: v = e.Vertexes[0].Point #print v v = self.wp.getLocalCoords(v) verts.append(v) verts.append(verts[0]) if len(verts) > 2: #print "new wire with ",len(verts) wires.append(Part.makePolygon(verts)) try: sh = ArchCommands.makeFace(wires) except: if DEBUG: print "Error: Unable to project face on the WP" return None else: # restoring flipped normals vnorm = self.wp.getLocalCoords(norm) if vnorm.getAngle(sh.normalAt(0,0)) > 1: sh.reverse() #print "VRM: projectFace end: ",len(sh.Vertexes)," verts" return [sh]+face[1:]
def getAxisPoints(self,obj): "returns the gridpoints of linked axes" import DraftGeomUtils pts = [] if len(obj.Axes) == 1: if hasattr(obj,"Align"): if obj.Align == True : p0 = obj.Axes[0].Shape.Edges[0].Vertexes[1].Point for e in obj.Axes[0].Shape.Edges: p = e.Vertexes[1].Point p = p.sub(p0) pts.append(p) else: for e in obj.Axes[0].Shape.Edges: pts.append(e.Vertexes[0].Point) else: for e in obj.Axes[0].Shape.Edges: pts.append(e.Vertexes[0].Point) elif len(obj.Axes) >= 2: set1 = obj.Axes[0].Shape.Edges set2 = obj.Axes[1].Shape.Edges for e1 in set1: for e2 in set2: pts.extend(DraftGeomUtils.findIntersection(e1,e2)) return pts
def mergeShapes(w1,w2): "returns a Shape built on two walls that share same properties and have a coincident endpoint" if not areSameWallTypes([w1,w2]): return None if (not hasattr(w1.Base,"Shape")) or (not hasattr(w2.Base,"Shape")): return None if w1.Base.Shape.Faces or w2.Base.Shape.Faces: return None # TODO fix this return None eds = w1.Base.Shape.Edges + w2.Base.Shape.Edges import DraftGeomUtils w = DraftGeomUtils.findWires(eds) if len(w) == 1: #print("found common wire") normal,length,width,height = w1.Proxy.getDefaultValues(w1) print(w[0].Edges) sh = w1.Proxy.getBase(w1,w[0],normal,width,height) print(sh) return sh return None
def projectFace(self,face): "projects a single face on the WP" wires = [] norm = face[0].normalAt(0,0) for w in face[0].Wires: verts = [] edges = DraftGeomUtils.sortEdges(w.Edges) for e in edges: v = e.Vertexes[0].Point v = self.wp.getLocalCoords(v) verts.append(v) verts.append(verts[0]) if len(verts) > 2: wires.append(Part.makePolygon(verts)) try: sh = ArchCommands.makeFace(wires) except: if DEBUG: print "Error: Unable to project face on the WP" return None else: # restoring flipped normals vnorm = self.wp.getLocalCoords(norm) if vnorm.getAngle(sh.normalAt(0,0)) > 1: sh.reverse() return [sh]+face[1:]
def closeHole(shape): """closeHole(shape): closes a hole in an open shape""" import DraftGeomUtils, Part # creating an edges lookup table lut = {} for face in shape.Faces: for edge in face.Edges: hc = edge.hashCode() if lut.has_key(hc): lut[hc] = lut[hc] + 1 else: lut[hc] = 1 # filter out the edges shared by more than one face bound = [] for e in shape.Edges: if lut[e.hashCode()] == 1: bound.append(e) bound = DraftGeomUtils.sortEdges(bound) try: nface = Part.Face(Part.Wire(bound)) shell = Part.makeShell(shape.Faces + [nface]) solid = Part.Solid(shell) except: raise else: return solid
def execute(self,obj): import Part, math, DraftGeomUtils pl = obj.Placement self.baseface = None base = None if obj.Base and obj.Angle: w = None if obj.Base.isDerivedFrom("Part::Feature"): if (obj.Base.Shape.Faces and obj.Face): w = obj.Base.Shape.Faces[obj.Face-1].Wires[0] elif obj.Base.Shape.Wires: w = obj.Base.Shape.Wires[0] if w: if w.isClosed(): f = Part.Face(w) self.baseface = f.copy() norm = f.normalAt(0,0) c = round(math.tan(math.radians(obj.Angle)),Draft.precision()) d = f.BoundBox.DiagonalLength edges = DraftGeomUtils.sortEdges(f.Edges) l = len(edges) edges.append(edges[0]) shps = [] for i in range(l): v = DraftGeomUtils.vec(DraftGeomUtils.angleBisection(edges[i],edges[i+1])) v.normalize() bis = v.getAngle(DraftGeomUtils.vec(edges[i])) delta = 1/math.cos(bis) v.multiply(delta) n = (FreeCAD.Vector(norm)).multiply(c) dv = v.add(n) dv.normalize() dv.scale(d,d,d) shps.append(f.extrude(dv)) base = shps.pop() for s in shps: base = base.common(s) base = base.removeSplitter() if not base.isNull(): if not DraftGeomUtils.isNull(pl): base.Placement = pl base = self.processSubShapes(obj,base) if base: if not base.isNull(): obj.Shape = base
def execute(self, obj): if self.clone(obj): return if not obj.Base: return if not obj.Base.Shape: return if not obj.Base.Shape.Wires: return pl = obj.Placement if obj.Base.Shape.Solids: obj.Shape = obj.Base.Shape.copy() if not pl.isNull(): obj.Placement = obj.Shape.Placement.multiply(pl) else: if not obj.Profile: return if not obj.Profile.isDerivedFrom("Part::Part2DObject"): return if not obj.Profile.Shape: return if not obj.Profile.Shape.Wires: return if not obj.Profile.Shape.Faces: for w in obj.Profile.Shape.Wires: if not w.isClosed(): return import DraftGeomUtils, Part, math baseprofile = obj.Profile.Shape.copy() if hasattr(obj, "ProfilePlacement"): if not obj.ProfilePlacement.isNull(): baseprofile.Placement = obj.ProfilePlacement.multiply( baseprofile.Placement) if not baseprofile.Faces: f = [] for w in baseprofile.Wires: f.append(Part.Face(w)) if len(f) == 1: baseprofile = f[0] else: baseprofile = Part.makeCompound(f) shapes = [] normal = DraftGeomUtils.getNormal(obj.Base.Shape) #for wire in obj.Base.Shape.Wires: edges = obj.Base.Shape.Edges if hasattr(obj, "Edges"): if obj.Edges == "Vertical edges": rv = obj.Base.Placement.Rotation.multVec( FreeCAD.Vector(0, 1, 0)) edges = [ e for e in edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416] ] elif obj.Edges == "Horizontal edges": rv = obj.Base.Placement.Rotation.multVec( FreeCAD.Vector(1, 0, 0)) edges = [ e for e in edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416] ] elif obj.Edges == "Top Horizontal edges": rv = obj.Base.Placement.Rotation.multVec( FreeCAD.Vector(1, 0, 0)) edges = [ e for e in edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416] ] edges = sorted(edges, key=lambda x: x.CenterOfMass.z, reverse=True) z = edges[0].CenterOfMass.z edges = [ e for e in edges if abs(e.CenterOfMass.z - z) < 0.00001 ] elif obj.Edges == "Bottom Horizontal edges": rv = obj.Base.Placement.Rotation.multVec( FreeCAD.Vector(1, 0, 0)) edges = [ e for e in edges if round(rv.getAngle(e.tangentAt(e.FirstParameter)), 4) in [0, 3.1416] ] edges = sorted(edges, key=lambda x: x.CenterOfMass.z) z = edges[0].CenterOfMass.z edges = [ e for e in edges if abs(e.CenterOfMass.z - z) < 0.00001 ] for e in edges: #e = wire.Edges[0] bvec = DraftGeomUtils.vec(e) bpoint = e.Vertexes[0].Point profile = baseprofile.copy() #basepoint = profile.Placement.Base if hasattr(obj, "BasePoint"): edges = Part.__sortEdges__(profile.Edges) basepointliste = [profile.CenterOfMass] for edge in edges: basepointliste.append( DraftGeomUtils.findMidpoint(edge)) basepointliste.append(edge.Vertexes[-1].Point) try: basepoint = basepointliste[obj.BasePoint] except IndexError: FreeCAD.Console.PrintMessage( translate( "Arch", "Crossing point not found in profile.\n")) basepoint = basepointliste[0] else: basepoint = profile.CenterOfMass profile.translate(bpoint.sub(basepoint)) if obj.Align: axis = profile.Placement.Rotation.multVec( FreeCAD.Vector(0, 0, 1)) angle = bvec.getAngle(axis) if round(angle, Draft.precision()) != 0: if round(angle, Draft.precision()) != round( math.pi, Draft.precision()): rotaxis = axis.cross(bvec) profile.rotate(DraftVecUtils.tup(bpoint), DraftVecUtils.tup(rotaxis), math.degrees(angle)) if obj.Rotation: profile.rotate( DraftVecUtils.tup(bpoint), DraftVecUtils.tup(FreeCAD.Vector(bvec).normalize()), obj.Rotation) #profile = wire.makePipeShell([profile],True,False,2) TODO buggy profile = profile.extrude(bvec) if obj.Offset: if not DraftVecUtils.isNull(obj.Offset): profile.translate(obj.Offset) shapes.append(profile) if shapes: if hasattr(obj, "Fuse"): if obj.Fuse: if len(shapes) > 1: s = shapes[0].multiFuse(shapes[1:]) s = s.removeSplitter() obj.Shape = s obj.Placement = pl return obj.Shape = Part.makeCompound(shapes) obj.Placement = pl
def execute(self, obj): if self.clone(obj): return import Part, math, DraftGeomUtils pl = obj.Placement #self.baseface = None base = None w = None if obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape.Solids: base = obj.Base.Shape #pl = obj.Base.Placement else: if (obj.Base.Shape.Faces and obj.Face): w = obj.Base.Shape.Faces[obj.Face - 1].Wires[0] elif obj.Base.Shape.Wires: w = obj.Base.Shape.Wires[0] if w: if w.isClosed(): self.profilsDico = [] self.shps = [] self.subVolshps = [] heights = [] edges = Part.__sortEdges__(w.Edges) l = len(edges) for i in range(l): self.makeRoofProfilsDic(i, obj.Angles[i], obj.Runs[i], obj.IdRel[i], obj.Overhang[i], obj.Thickness[i]) for i in range(l): self.calcMissingData(i) for i in range(l): self.calcEdgeGeometry(edges, i) for i in range(l): self.calcDraftEdges(i) for i in range(l): self.calcEave(i) for p in self.profilsDico: heights.append(p["height"]) obj.Heights = heights for i in range(l): self.getRoofPaneProject(i) profilCurrent = self.findProfil(i) midpoint = DraftGeomUtils.findMidpoint( profilCurrent["edge"]) ptsPaneProject = profilCurrent["points"] lp = len(ptsPaneProject) if lp != 0: ptsPaneProject.append(ptsPaneProject[0]) edgesWire = [] for p in range(lp): edge = Part.makeLine(ptsPaneProject[p], ptsPaneProject[p + 1]) edgesWire.append(edge) wire = Part.Wire(edgesWire) d = wire.BoundBox.DiagonalLength thicknessV = profilCurrent["thickness"] / (math.cos( math.radians(profilCurrent["angle"]))) overhangV = profilCurrent["overhang"] * math.tan( math.radians(profilCurrent["angle"])) if wire.isClosed(): f = Part.Face(wire) f = f.extrude( FreeCAD.Vector( 0, 0, profilCurrent["height"] + 1000000.0)) f.translate( FreeCAD.Vector(0.0, 0.0, -2 * overhangV)) ptsPaneProfil = [ FreeCAD.Vector(-profilCurrent["overhang"], -overhangV, 0.0), FreeCAD.Vector(profilCurrent["run"], profilCurrent["height"], 0.0), FreeCAD.Vector( profilCurrent["run"], profilCurrent["height"] + thicknessV, 0.0), FreeCAD.Vector(-profilCurrent["overhang"], -overhangV + thicknessV, 0.0) ] self.shps.append( self.createProfilShape(ptsPaneProfil, midpoint, profilCurrent["rot"], profilCurrent["vec"], profilCurrent["run"], d, f)) ## subVolume shape ptsSubVolumeProfil = [ FreeCAD.Vector(-profilCurrent["overhang"], -overhangV, 0.0), FreeCAD.Vector(profilCurrent["run"], profilCurrent["height"], 0.0), FreeCAD.Vector(profilCurrent["run"], profilCurrent["height"] + 900000.0, 0.0), FreeCAD.Vector(-profilCurrent["overhang"], profilCurrent["height"] + 900000.0, 0.0) ] self.subVolshps.append( self.createProfilShape(ptsSubVolumeProfil, midpoint, profilCurrent["rot"], profilCurrent["vec"], profilCurrent["run"], d, f)) ## SubVolume self.sub = self.subVolshps.pop() for s in self.subVolshps: self.sub = self.sub.fuse(s) self.sub = self.sub.removeSplitter() if not self.sub.isNull(): if not DraftGeomUtils.isNull(pl): self.sub.Placement = pl ## BaseVolume base = self.shps.pop() for s in self.shps: base = base.fuse(s) base = self.processSubShapes(obj, base) self.applyShape(obj, base, pl, allownosolid=True) elif base: base = self.processSubShapes(obj, base) self.applyShape(obj, base, pl, allownosolid=True) else: FreeCAD.Console.PrintMessage( translate("Arch", "Unable to create a roof"))
def buildpathocc(self, obj, shape): """Build pocket Path using Native OCC algorithm.""" import Part import DraftGeomUtils from PathScripts.PathUtils import fmt, helicalPlunge, rampPlunge, depth_params FreeCAD.Console.PrintMessage(translate("PathPocket", "Generating toolpath with OCC native offsets.\n")) extraoffset = obj.MaterialAllowance.Value # Build up the offset loops output = "" if obj.Comment != "": output += '(' + str(obj.Comment)+')\n' output += 'G0 Z' + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" offsets = [] nextradius = self.radius + extraoffset result = DraftGeomUtils.pocket2d(shape, nextradius) while result: offsets.extend(result) nextradius += (self.radius * 2) * (float(obj.StepOver)/100) result = DraftGeomUtils.pocket2d(shape, nextradius) # revert the list so we start with the outer wires if obj.StartAt != 'Edge': offsets.reverse() plungePos = None rampEdge = None if obj.UseEntry: # Try to find an entry location toold = self.radius*2 helixBounds = DraftGeomUtils.pocket2d(shape, self.radius * (1 + obj.HelixSize)) if helixBounds: rampD = obj.RampSize if obj.StartAt == 'Edge': plungePos = helixBounds[0].Edges[0].Vertexes[0].Point else: plungePos = offsets[0].Edges[0].Vertexes[0].Point # If it turns out this is invalid for some reason, nuke plungePos [perp, idx] = DraftGeomUtils.findPerpendicular(plungePos, shape.Edges) if not perp or perp.Length < self.radius * (1 + obj.HelixSize): plungePos = None FreeCAD.Console.PrintError(translate("PathPocket", "Helical Entry location not found.\n")) # FIXME: Really need to do a point-in-polygon operation to make sure this is within helixBounds # Or some math to prove that it has to be (doubt that's true) # Maybe reverse helixBounds and pick off that? if plungePos is None: # If we didn't find a place to helix, how about a ramp? FreeCAD.Console.PrintMessage(translate("PathPocket", "Attempting ramp entry.\n")) if (offsets[0].Edges[0].Length >= toold * rampD) and not (isinstance(offsets[0].Edges[0].Curve, Part.Circle)): rampEdge = offsets[0].Edges[0] # The last edge also connects with the starting location- try that elif (offsets[0].Edges[-1].Length >= toold * rampD) and not (isinstance(offsets[0].Edges[-1].Curve, Part.Circle)): rampEdge = offsets[0].Edges[-1] else: FreeCAD.Console.PrintError(translate("PathPocket", "Ramp Entry location not found.\n")) # print "Neither edge works: " + str(offsets[0].Edges[0]) + ", " + str(offsets[0].Edges[-1]) # FIXME: There's got to be a smarter way to find a place to ramp # For helix-ing/ramping, know where we were last time # FIXME: Can probably get this from the "machine"? lastZ = obj.ClearanceHeight.Value startPoint = None depthparams = depth_params( obj.ClearanceHeight.Value, obj.SafeHeight.Value, obj.StartDepth.Value, obj.StepDown, obj.FinishDepth.Value, obj.FinalDepth.Value) for vpos in depthparams.get_depths(): first = True # loop over successive wires for currentWire in offsets: last = None for edge in currentWire.Edges: if not last: # we set the base GO to our fast move to our starting pos if first: # If we can helix, do so if plungePos: output += helicalPlunge(plungePos, obj.RampAngle, vpos, lastZ, self.radius*2, obj.HelixSize, self.horizFeed) lastZ = vpos # Otherwise, see if we can ramp # FIXME: This could be a LOT smarter (eg, searching for a longer leg of the edge to ramp along) elif rampEdge: output += rampPlunge(rampEdge, obj.RampAngle, vpos, lastZ) lastZ = vpos # Otherwise, straight plunge... Don't want to, but sometimes you might not have a choice. # FIXME: At least not with the lazy ramp programming above... else: print "WARNING: Straight-plunging... probably not good, but we didn't find a place to helix or ramp" startPoint = edge.Vertexes[0].Point output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" output += "G0 X" + fmt(startPoint.x) + " Y" + fmt(startPoint.y) +\ " Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.horizRapid) + "\n" first = False # then move slow down to our starting point for our profile last = edge.Vertexes[0].Point output += "G1 X" + fmt(last.x) + " Y" + fmt(last.y) + " Z" + fmt(vpos) + " F" + fmt(self.vertFeed) + "\n" if DraftGeomUtils.geomType(edge) == "Circle": point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point center = edge.Curve.Center relcenter = center.sub(last) v1 = last.sub(center) v2 = point.sub(center) if v1.cross(v2).z < 0: output += "G2" else: output += "G3" output += " X" + fmt(point.x) + " Y" + fmt(point.y) + " Z" + fmt(vpos) output += " I" + fmt(relcenter.x) + " J" + fmt(relcenter.y) + " K" + fmt(relcenter.z) + " F" + fmt(self.horizFeed) output += "\n" last = point else: point = edge.Vertexes[-1].Point if point == last: # edges can come flipped point = edge.Vertexes[0].Point output += "G1 X" + fmt(point.x) + " Y" + fmt(point.y) + " Z" + fmt(vpos) + " F" + fmt(self.horizFeed) + "\n" last = point # move back up output += "G0 Z" + fmt(obj.ClearanceHeight.Value) + "F " + PathUtils.fmt(self.vertRapid) + "\n" return output
def getRebarData(self, obj): if not obj.Host: return if Draft.getType(obj.Host) != "Structure": return if not obj.Host.Shape: return if not obj.Base: return if not obj.Base.Shape: return if not obj.Base.Shape.Wires: return if not obj.Diameter.Value: return if not obj.Amount: return father = obj.Host wire = obj.Base.Shape.Wires[0] if Draft.getType( obj.Base) == "Wire": # Draft Wires can have "wrong" placement import DraftGeomUtils axis = DraftGeomUtils.getNormal(obj.Base.Shape) else: axis = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector( 0, 0, -1)) size = (ArchCommands.projectToVector(father.Shape.copy(), axis)).Length if hasattr(obj, "Direction"): if not DraftVecUtils.isNull(obj.Direction): axis = FreeCAD.Vector(obj.Direction) axis.normalize() if hasattr(obj, "Distance"): if obj.Distance.Value: size = obj.Distance.Value if hasattr(obj, "Rounding"): if obj.Rounding: radius = obj.Rounding * obj.Diameter.Value import DraftGeomUtils wire = DraftGeomUtils.filletWire(wire, radius) wires = [] if obj.Amount == 1: offset = DraftVecUtils.scaleTo(axis, size / 2) wire.translate(offset) wires.append(wire) else: if obj.OffsetStart.Value: baseoffset = DraftVecUtils.scaleTo(axis, obj.OffsetStart.Value) else: baseoffset = None if obj.ViewObject.RebarShape == "Stirrup": interval = size - (obj.OffsetStart.Value + obj.OffsetEnd.Value + obj.Diameter.Value) else: interval = size - (obj.OffsetStart.Value + obj.OffsetEnd.Value) interval = interval / (obj.Amount - 1) vinterval = DraftVecUtils.scaleTo(axis, interval) for i in range(obj.Amount): if i == 0: if baseoffset: wire.translate(baseoffset) wires.append(wire) else: wire = wire.copy() wire.translate(vinterval) wires.append(wire) return [wires, obj.Diameter.Value / 2]
def buildpathocc(self, obj, wires, zValues): '''buildpathocc(obj, wires, zValues) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) for wire in wires: offset = wire # reorder the wire offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) last = None for z in zValues: if last: self.commandlist.append( Path.Command('G1', { 'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed })) for edge in offset.Edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point if len(offset.Edges) > 1: e2 = offset.Edges[1] if not PathGeom.pointsCoincide( edge.Vertexes[-1].Point, e2.Vertexes[0]. Point) and not PathGeom.pointsCoincide( edge.Vertexes[-1].Point, e2.Vertexes[-1].Point): PathLog.debug("flip first edge") last = edge.Vertexes[-1].Point else: PathLog.debug("original first edge") else: PathLog.debug("not enough edges to flip") self.commandlist.append( Path.Command( 'G0', { 'X': last.x, 'Y': last.y, 'Z': obj.ClearanceHeight.Value, 'F': self.horizRapid })) self.commandlist.append( Path.Command( 'G0', { 'X': last.x, 'Y': last.y, 'Z': obj.SafeHeight.Value, 'F': self.vertRapid })) self.commandlist.append( Path.Command( 'G0', { 'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed })) if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): for cmd in PathGeom.cmdsForEdge(edge): params = cmd.Parameters params.update({'Z': z, 'F': self.horizFeed}) self.commandlist.append( Path.Command(cmd.Name, params)) last = edge.Vertexes[-1].Point else: for cmd in PathGeom.cmdsForEdge(edge, True): params = cmd.Parameters params.update({'Z': z, 'F': self.horizFeed}) self.commandlist.append( Path.Command(cmd.Name, params)) last = edge.Vertexes[0].Point self.commandlist.append( Path.Command('G0', { 'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid })) if self.commandlist: self.commandlist.pop()
def execute(self,obj): if not obj.Base: return if not obj.Base.Shape: return if not obj.Base.Shape.Wires: return pl = obj.Placement if obj.Base.Shape.Solids: obj.Shape = obj.Base.Shape.copy() if not pl.isNull(): obj.Placement = obj.Shape.Placement.multiply(pl) else: if not obj.Profile: return if not obj.Profile.isDerivedFrom("Part::Part2DObject"): return if not obj.Profile.Shape: return if not obj.Profile.Shape.Wires: return if not obj.Profile.Shape.Faces: for w in obj.Profile.Shape.Wires: if not w.isClosed(): return import DraftGeomUtils, Part, math baseprofile = obj.Profile.Shape.copy() if not baseprofile.Faces: f = [] for w in baseprofile.Wires: f.append(Part.Face(w)) if len(f) == 1: baseprofile = f[0] else: baseprofile = Part.makeCompound(f) shapes = [] normal = DraftGeomUtils.getNormal(obj.Base.Shape) #for wire in obj.Base.Shape.Wires: for e in obj.Base.Shape.Edges: #e = wire.Edges[0] bvec = DraftGeomUtils.vec(e) bpoint = e.Vertexes[0].Point profile = baseprofile.copy() #basepoint = profile.Placement.Base basepoint = profile.CenterOfMass profile.translate(bpoint.sub(basepoint)) if obj.Align: axis = profile.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1)) angle = bvec.getAngle(axis) if round(angle,Draft.precision()) != 0: if round(angle,Draft.precision()) != round(math.pi,Draft.precision()): rotaxis = axis.cross(bvec) profile.rotate(DraftVecUtils.tup(bpoint), DraftVecUtils.tup(rotaxis), math.degrees(angle)) if obj.Rotation: profile.rotate(DraftVecUtils.tup(bpoint), DraftVecUtils.tup(FreeCAD.Vector(bvec).normalize()), obj.Rotation) #profile = wire.makePipeShell([profile],True,False,2) TODO buggy profile = profile.extrude(bvec) if obj.Offset: if not DraftVecUtils.isNull(obj.Offset): profile.translate(obj.Offset) shapes.append(profile) if shapes: obj.Shape = Part.makeCompound(shapes) obj.Placement = pl
def getSVG(obj, scale=1, linewidth=0.35, fontsize=12, fillstyle="shape color", direction=None, linestyle=None, color=None, linespacing=None, techdraw=False, rotation=0, fillSpaces=False, override=True): '''getSVG(object,[scale], [linewidth],[fontsize],[fillstyle],[direction],[linestyle],[color],[linespacing]): returns a string containing a SVG representation of the given object, with the given linewidth and fontsize (used if the given object contains any text). You can also supply an arbitrary projection vector. the scale parameter allows to scale linewidths down, so they are resolution-independant.''' import Part, DraftGeomUtils # if this is a group, gather all the svg views of its children if hasattr(obj, "isDerivedFrom"): if obj.isDerivedFrom("App::DocumentObjectGroup") or getType( obj) == "Layer": svg = "" for child in obj.Group: svg += getSVG(child, scale, linewidth, fontsize, fillstyle, direction, linestyle, color, linespacing, techdraw, rotation, fillSpaces, override) return svg pathdata = [] svg = "" linewidth = float(linewidth) / scale if not override: if hasattr(obj, "ViewObject"): if hasattr(obj.ViewObject, "LineWidth"): if hasattr(obj.ViewObject.LineWidth, "Value"): lw = obj.ViewObject.LineWidth.Value else: lw = obj.ViewObject.LineWidth linewidth = lw * linewidth fontsize = (float(fontsize) / scale) / 2 if linespacing: linespacing = float(linespacing) / scale else: linespacing = 0.5 #print obj.Label," line spacing ",linespacing,"scale ",scale pointratio = .75 # the number of times the dots are smaller than the arrow size plane = None if direction: if isinstance(direction, FreeCAD.Vector): if direction != Vector(0, 0, 0): plane = WorkingPlane.plane() plane.alignToPointAndAxis_SVG(Vector(0, 0, 0), direction.negative().negative(), 0) elif isinstance(direction, WorkingPlane.plane): plane = direction stroke = "#000000" if color and override: if "#" in color: stroke = color else: stroke = getrgb(color) elif gui: if hasattr(obj, "ViewObject"): if hasattr(obj.ViewObject, "LineColor"): stroke = getrgb(obj.ViewObject.LineColor) elif hasattr(obj.ViewObject, "TextColor"): stroke = getrgb(obj.ViewObject.TextColor) lstyle = "none" if override: lstyle = getLineStyle(linestyle, scale) else: if hasattr(obj, "ViewObject"): if hasattr(obj.ViewObject, "DrawStyle"): lstyle = getLineStyle(obj.ViewObject.DrawStyle, scale) def getPath(edges=[], wires=[], pathname=None): svg = "<path " if pathname is None: svg += 'id="%s" ' % obj.Name elif pathname != "": svg += 'id="%s" ' % pathname svg += ' d="' if not wires: egroups = Part.sortEdges(edges) else: egroups = [] first = True for w in wires: w1 = w.copy() if first: first = False else: # invert further wires to create holes w1 = DraftGeomUtils.invert(w1) w1.fixWire() egroups.append(Part.__sortEdges__(w1.Edges)) for egroupindex, edges in enumerate(egroups): edata = "" vs = () #skipped for the first edge for edgeindex, e in enumerate(edges): previousvs = vs # vertexes of an edge (reversed if needed) vs = e.Vertexes if previousvs: if (vs[0].Point - previousvs[-1].Point).Length > 1e-6: vs.reverse() if edgeindex == 0: v = getProj(vs[0].Point, plane) edata += 'M ' + str(v.x) + ' ' + str(v.y) + ' ' else: if (vs[0].Point - previousvs[-1].Point).Length > 1e-6: raise ValueError('edges not ordered') iscircle = DraftGeomUtils.geomType(e) == "Circle" isellipse = DraftGeomUtils.geomType(e) == "Ellipse" if iscircle or isellipse: import math if hasattr(FreeCAD, "DraftWorkingPlane"): drawing_plane_normal = FreeCAD.DraftWorkingPlane.axis else: drawing_plane_normal = FreeCAD.Vector(0, 0, 1) if plane: drawing_plane_normal = plane.axis c = e.Curve if round(c.Axis.getAngle(drawing_plane_normal), 2) in [0, 3.14]: occversion = Part.OCC_VERSION.split(".") done = False if (int(occversion[0]) >= 7) and (int(occversion[1]) >= 1): # if using occ >= 7.1, use HLR algorithm import Drawing snip = Drawing.projectToSVG( e, drawing_plane_normal) if snip: try: a = "A " + snip.split("path d=\"")[ 1].split("\"")[0].split("A")[1] except: pass else: edata += a done = True if not done: if len(e.Vertexes ) == 1 and iscircle: #complete curve svg = getCircle(e) return svg elif len(e.Vertexes) == 1 and isellipse: #svg = getEllipse(e) #return svg endpoints = [ getProj( c.value((c.LastParameter - c.FirstParameter) / 2.0), plane), getProj(vs[-1].Point, plane) ] else: endpoints = [getProj(vs[-1].Point, plane)] # arc if iscircle: rx = ry = c.Radius rot = 0 else: #ellipse rx = c.MajorRadius ry = c.MinorRadius rot = math.degrees(c.AngleXU * (c.Axis * \ FreeCAD.Vector(0,0,1))) if rot > 90: rot -= 180 if rot < -90: rot += 180 #be careful with the sweep flag flag_large_arc = (((e.ParameterRange[1] - \ e.ParameterRange[0]) / math.pi) % 2) > 1 #flag_sweep = (c.Axis * drawing_plane_normal >= 0) \ # == (e.LastParameter > e.FirstParameter) # == (e.Orientation == "Forward") # other method: check the direction of the angle between tangents t1 = e.tangentAt(e.FirstParameter) t2 = e.tangentAt( e.FirstParameter + (e.LastParameter - e.FirstParameter) / 10) flag_sweep = (DraftVecUtils.angle( t1, t2, drawing_plane_normal) < 0) for v in endpoints: edata += 'A %s %s %s %s %s %s %s ' % \ (str(rx),str(ry),str(rot),\ str(int(flag_large_arc)),\ str(int(flag_sweep)),str(v.x),str(v.y)) else: edata += getDiscretized(e, plane) elif DraftGeomUtils.geomType(e) == "Line": v = getProj(vs[-1].Point, plane) edata += 'L ' + str(v.x) + ' ' + str(v.y) + ' ' else: bspline = e.Curve.toBSpline(e.FirstParameter, e.LastParameter) if bspline.Degree > 3 or bspline.isRational(): try: bspline = bspline.approximateBSpline( 0.05, 50, 3, 'C0') except RuntimeError: print("Debug: unable to approximate bspline") if bspline.Degree <= 3 and not bspline.isRational(): for bezierseg in bspline.toBezier(): if bezierseg.Degree > 3: #should not happen raise AssertionError elif bezierseg.Degree == 1: edata += 'L ' elif bezierseg.Degree == 2: edata += 'Q ' elif bezierseg.Degree == 3: edata += 'C ' for pole in bezierseg.getPoles()[1:]: v = getProj(pole, plane) edata += str(v.x) + ' ' + str(v.y) + ' ' else: print("Debug: one edge (hash ",e.hashCode(),\ ") has been discretized with parameter 0.1") for linepoint in bspline.discretize(0.1)[1:]: v = getProj(linepoint, plane) edata += 'L ' + str(v.x) + ' ' + str(v.y) + ' ' if fill != 'none': edata += 'Z ' if edata in pathdata: # do not draw a path on another identical path return "" else: svg += edata pathdata.append(edata) svg += '" ' svg += 'stroke="' + stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:' + str(linewidth) svg += ';stroke-miterlimit:4' svg += ';stroke-dasharray:' + lstyle svg += ';fill:' + fill try: svg += ';fill-opacity:' + str(fill_opacity) except NameError: pass svg += ';fill-rule: evenodd "' svg += '/>\n' return svg def getCircle(edge): cen = getProj(edge.Curve.Center, plane) rad = edge.Curve.Radius if hasattr(FreeCAD, "DraftWorkingPlane"): drawing_plane_normal = FreeCAD.DraftWorkingPlane.axis else: drawing_plane_normal = FreeCAD.Vector(0, 0, 1) if plane: drawing_plane_normal = plane.axis if round(edge.Curve.Axis.getAngle(drawing_plane_normal), 2) in [0, 3.14]: # perpendicular projection: circle svg = '<circle cx="' + str(cen.x) svg += '" cy="' + str(cen.y) svg += '" r="' + str(rad) + '" ' else: # any other projection: ellipse svg = '<path d="' svg += getDiscretized(edge, plane) svg += '" ' svg += 'stroke="' + stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:' + str(linewidth) svg += ';stroke-miterlimit:4' svg += ';stroke-dasharray:' + lstyle svg += ';fill:' + fill + '"' svg += '/>\n' return svg def getEllipse(edge): cen = getProj(edge.Curve.Center, plane) mir = edge.Curve.MinorRadius mar = edge.Curve.MajorRadius svg = '<ellipse cx="' + str(cen.x) svg += '" cy="' + str(cen.y) svg += '" rx="' + str(mar) svg += '" ry="' + str(mir) + '" ' svg += 'stroke="' + stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:' + str(linewidth) svg += ';stroke-miterlimit:4' svg += ';stroke-dasharray:' + lstyle svg += ';fill:' + fill + '"' svg += '/>\n' return svg def getArrow(arrowtype, point, arrowsize, color, linewidth, angle=0): svg = "" if gui: if not obj.ViewObject: return svg if obj.ViewObject.ArrowType == "Circle": svg += '<circle cx="' + str(point.x) + '" cy="' + str(point.y) svg += '" r="' + str(arrowsize) + '" ' svg += 'fill="none" stroke="' + color + '" ' svg += 'style="stroke-width:' + str( linewidth) + ';stroke-miterlimit:4;stroke-dasharray:none" ' svg += 'freecad:skip="1"' svg += '/>\n' elif obj.ViewObject.ArrowType == "Dot": svg += '<circle cx="' + str(point.x) + '" cy="' + str(point.y) svg += '" r="' + str(arrowsize) + '" ' svg += 'fill="' + color + '" stroke="none" ' svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" ' svg += 'freecad:skip="1"' svg += '/>\n' elif obj.ViewObject.ArrowType == "Arrow": svg += '<path transform="rotate(' + str(math.degrees(angle)) svg += ',' + str(point.x) + ',' + str(point.y) + ') ' svg += 'translate(' + str(point.x) + ',' + str(point.y) + ') ' svg += 'scale(' + str(arrowsize) + ',' + str( arrowsize) + ')" freecad:skip="1" ' svg += 'fill="' + color + '" stroke="none" ' svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" ' svg += 'd="M 0 0 L 4 1 L 4 -1 Z"/>\n' elif obj.ViewObject.ArrowType == "Tick": svg += '<path transform="rotate(' + str(math.degrees(angle)) svg += ',' + str(point.x) + ',' + str(point.y) + ') ' svg += 'translate(' + str(point.x) + ',' + str(point.y) + ') ' svg += 'scale(' + str(arrowsize) + ',' + str( arrowsize) + ')" freecad:skip="1" ' svg += 'fill="' + color + '" stroke="none" ' svg += 'style="stroke-miterlimit:4;stroke-dasharray:none" ' svg += 'd="M -1 -2 L 0 2 L 1 2 L 0 -2 Z"/>\n' elif obj.ViewObject.ArrowType == "Tick-2": svg += '<line transform="rotate(' + str( math.degrees(angle) + 45) svg += ',' + str(point.x) + ',' + str(point.y) + ') ' svg += 'translate(' + str(point.x) + ',' + str(point.y) + ') ' svg += '" freecad:skip="1" ' svg += 'fill="none" stroke="' + color + '" ' svg += 'style="stroke-dasharray:none;stroke-linecap:square;' svg += 'stroke-width:' + str(linewidth) + '" ' svg += 'x1="-' + str(arrowsize * 2) + '" y1="0" ' svg += 'x2="' + str(arrowsize * 2) + '" y2="0" />\n' else: print("getSVG: arrow type not implemented") return svg def getOvershoot(point, shootsize, color, linewidth, angle=0): svg = '<line transform="rotate(' + str(math.degrees(angle)) svg += ',' + str(point.x) + ',' + str(point.y) + ') ' svg += 'translate(' + str(point.x) + ',' + str(point.y) + ') ' svg += '" freecad:skip="1" ' svg += 'fill="none" stroke="' + color + '" ' svg += 'style="stroke-dasharray:none;stroke-linecap:square;' svg += 'stroke-width:' + str(linewidth) + '" ' svg += 'x1="0" y1="0" ' svg += 'x2="' + str(shootsize * -1) + '" y2="0" />\n' return svg def getText(tcolor, fontsize, fontname, angle, base, text, linespacing=0.5, align="center", flip=True): if isinstance(angle, FreeCAD.Rotation): if not plane: angle = angle.Angle else: if plane.axis.getAngle(angle.Axis) < 0.001: angle = angle.Angle elif abs(plane.axis.getAngle(angle.Axis) - math.pi) < 0.001: if abs(angle.Angle) > 0.1: angle = -angle.Angle else: angle = angle.Angle elif abs(plane.axis.getAngle(angle.Axis) - math.pi / 2) < 0.001: return "" # text is perpendicular to view, so it shouldn't appear else: angle = 0 #TODO maybe there is something better to do here? if not isinstance(text, list): text = text.split("\n") if align.lower() == "center": anchor = "middle" elif align.lower() == "left": anchor = "start" else: anchor = "end" if techdraw: svg = "" for i in range(len(text)): t = text[i].replace("&", "&").replace("<", "<").replace( ">", ">") if six.PY2 and not isinstance(t, six.text_type): t = t.decode("utf8") # possible workaround if UTF8 is unsupported # import unicodedata # t = u"".join([c for c in unicodedata.normalize("NFKD",t) if not unicodedata.combining(c)]).encode("utf8") svg += '<text stroke-width="0" stroke="' + tcolor + '" fill="' + tcolor + '" font-size="' + str( fontsize) + '" ' svg += 'style="text-anchor:' + anchor + ';text-align:' + align.lower( ) + ';' svg += 'font-family:' + fontname + '" ' svg += 'transform="rotate(' + str(math.degrees(angle)) svg += ',' + str( base.x) + ',' + str(base.y - linespacing * i) + ') ' svg += 'translate(' + str( base.x) + ',' + str(base.y - linespacing * i) + ') ' svg += 'scale(1,-1)" ' #svg += '" freecad:skip="1"' svg += '>\n' + t + '</text>\n' else: svg = '<text stroke-width="0" stroke="' + tcolor + '" fill="' svg += tcolor + '" font-size="' svg += str(fontsize) + '" ' svg += 'style="text-anchor:' + anchor + ';text-align:' + align.lower( ) + ';' svg += 'font-family:' + fontname + '" ' svg += 'transform="rotate(' + str(math.degrees(angle)) svg += ',' + str(base.x) + ',' + str(base.y) + ') ' if flip: svg += 'translate(' + str(base.x) + ',' + str(base.y) + ')' else: svg += 'translate(' + str(base.x) + ',' + str(-base.y) + ')' #svg += 'scale('+str(tmod/2000)+',-'+str(tmod/2000)+') ' if flip: svg += ' scale(1,-1) ' else: svg += ' scale(1,1) ' svg += '" freecad:skip="1"' svg += '>\n' if len(text) == 1: try: svg += text[0].replace("&", "&").replace( "<", "<").replace(">", ">") except: svg += text[0].decode("utf8").replace( "&", "&").replace("<", "<").replace(">", ">") else: for i in range(len(text)): if i == 0: svg += '<tspan>' else: svg += '<tspan x="0" dy="' + str(linespacing) + '">' try: svg += text[i].replace("&", "&").replace( "<", "<").replace(">", ">") except: svg += text[i].decode("utf8").replace( "&", "&").replace("<", "<").replace(">", ">") svg += '</tspan>\n' svg += '</text>\n' return svg if not obj: pass elif isinstance(obj, Part.Shape): if "#" in fillstyle: fill = fillstyle elif fillstyle == "shape color": fill = "#888888" else: fill = 'url(#' + fillstyle + ')' svg += getPath(obj.Edges, pathname="") elif getType(obj) in ["Dimension", "LinearDimension"]: if gui: if not obj.ViewObject: print( "export of dimensions to SVG is only available in GUI mode" ) elif obj.ViewObject.Proxy: if hasattr(obj.ViewObject.Proxy, "p1"): prx = obj.ViewObject.Proxy ts = (len(prx.string) * obj.ViewObject.FontSize.Value) / 4.0 rm = ((prx.p3.sub(prx.p2)).Length / 2.0) - ts p2a = getProj( prx.p2.add( DraftVecUtils.scaleTo(prx.p3.sub(prx.p2), rm)), plane) p2b = getProj( prx.p3.add( DraftVecUtils.scaleTo(prx.p2.sub(prx.p3), rm)), plane) p1 = getProj(prx.p1, plane) p2 = getProj(prx.p2, plane) p3 = getProj(prx.p3, plane) p4 = getProj(prx.p4, plane) tbase = getProj(prx.tbase, plane) r = prx.textpos.rotation.getValue().getValue() rv = FreeCAD.Rotation(r[0], r[1], r[2], r[3]).multVec( FreeCAD.Vector(1, 0, 0)) angle = -DraftVecUtils.angle(getProj(rv, plane)) #angle = -DraftVecUtils.angle(p3.sub(p2)) svg = '' nolines = False if hasattr(obj.ViewObject, "ShowLine"): if not obj.ViewObject.ShowLine: nolines = True # drawing lines if not nolines: svg += '<path ' if obj.ViewObject.DisplayMode == "2D": tangle = angle if tangle > math.pi / 2: tangle = tangle - math.pi #elif (tangle <= -math.pi/2) or (tangle > math.pi/2): # tangle = tangle+math.pi #tbase = tbase.add(DraftVecUtils.rotate(Vector(0,2/scale,0),tangle)) if rotation != 0: #print "dim: tangle:",tangle," rot: ",rotation," text: ",prx.string if abs(tangle + math.radians(rotation)) < 0.0001: tangle += math.pi tbase = tbase.add( DraftVecUtils.rotate( Vector(0, 2 / scale, 0), tangle)) if not nolines: svg += 'd="M ' + str(p1.x) + ' ' + str(p1.y) + ' ' svg += 'L ' + str(p2.x) + ' ' + str(p2.y) + ' ' svg += 'L ' + str(p3.x) + ' ' + str(p3.y) + ' ' svg += 'L ' + str(p4.x) + ' ' + str(p4.y) + '" ' else: tangle = 0 if rotation != 0: tangle = -math.radians(rotation) tbase = tbase.add(Vector(0, -2.0 / scale, 0)) if not nolines: svg += 'd="M ' + str(p1.x) + ' ' + str(p1.y) + ' ' svg += 'L ' + str(p2.x) + ' ' + str(p2.y) + ' ' svg += 'L ' + str(p2a.x) + ' ' + str(p2a.y) + ' ' svg += 'M ' + str(p2b.x) + ' ' + str(p2b.y) + ' ' svg += 'L ' + str(p3.x) + ' ' + str(p3.y) + ' ' svg += 'L ' + str(p4.x) + ' ' + str(p4.y) + '" ' if not nolines: svg += 'fill="none" stroke="' svg += stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:' + str(linewidth) svg += ';stroke-miterlimit:4;stroke-dasharray:none" ' svg += 'freecad:basepoint1="' + str(p1.x) + ' ' + str( p1.y) + '" ' svg += 'freecad:basepoint2="' + str(p4.x) + ' ' + str( p4.y) + '" ' svg += 'freecad:dimpoint="' + str(p2.x) + ' ' + str( p2.y) + '"' svg += '/>\n' # drawing dimension and extension lines overshoots if hasattr(obj.ViewObject, "DimOvershoot" ) and obj.ViewObject.DimOvershoot.Value: shootsize = obj.ViewObject.DimOvershoot.Value / pointratio svg += getOvershoot(p2, shootsize, stroke, linewidth, angle) svg += getOvershoot(p3, shootsize, stroke, linewidth, angle + math.pi) if hasattr(obj.ViewObject, "ExtOvershoot" ) and obj.ViewObject.ExtOvershoot.Value: shootsize = obj.ViewObject.ExtOvershoot.Value / pointratio shootangle = -DraftVecUtils.angle(p1.sub(p2)) svg += getOvershoot(p2, shootsize, stroke, linewidth, shootangle) svg += getOvershoot(p3, shootsize, stroke, linewidth, shootangle) # drawing arrows if hasattr(obj.ViewObject, "ArrowType"): arrowsize = obj.ViewObject.ArrowSize.Value / pointratio if hasattr(obj.ViewObject, "FlipArrows"): if obj.ViewObject.FlipArrows: angle = angle + math.pi svg += getArrow(obj.ViewObject.ArrowType, p2, arrowsize, stroke, linewidth, angle) svg += getArrow(obj.ViewObject.ArrowType, p3, arrowsize, stroke, linewidth, angle + math.pi) # drawing text svg += getText(stroke, fontsize, obj.ViewObject.FontName, tangle, tbase, prx.string) elif getType(obj) == "AngularDimension": if gui: if not obj.ViewObject: print( "export of dimensions to SVG is only available in GUI mode" ) elif obj.ViewObject.Proxy: if hasattr(obj.ViewObject.Proxy, "circle"): prx = obj.ViewObject.Proxy # drawing arc fill = "none" if obj.ViewObject.DisplayMode == "2D": svg += getPath([prx.circle]) else: if hasattr(prx, "circle1"): svg += getPath([prx.circle1]) svg += getPath([prx.circle2]) else: svg += getPath([prx.circle]) # drawing arrows if hasattr(obj.ViewObject, "ArrowType"): p2 = getProj(prx.p2, plane) p3 = getProj(prx.p3, plane) arrowsize = obj.ViewObject.ArrowSize.Value / pointratio arrowlength = 4 * obj.ViewObject.ArrowSize.Value u1 = getProj( (prx.circle.valueAt(prx.circle.FirstParameter + arrowlength) ).sub( prx.circle.valueAt( prx.circle.FirstParameter)), plane) u2 = getProj( (prx.circle.valueAt(prx.circle.LastParameter - arrowlength) ).sub(prx.circle.valueAt( prx.circle.LastParameter)), plane) angle1 = -DraftVecUtils.angle(u1) angle2 = -DraftVecUtils.angle(u2) if hasattr(obj.ViewObject, "FlipArrows"): if obj.ViewObject.FlipArrows: angle1 = angle1 + math.pi angle2 = angle2 + math.pi svg += getArrow(obj.ViewObject.ArrowType, p2, arrowsize, stroke, linewidth, angle1) svg += getArrow(obj.ViewObject.ArrowType, p3, arrowsize, stroke, linewidth, angle2) # drawing text if obj.ViewObject.DisplayMode == "2D": t = prx.circle.tangentAt(prx.circle.FirstParameter + (prx.circle.LastParameter - prx.circle.FirstParameter) / 2.0) t = getProj(t, plane) tangle = DraftVecUtils.angle(t) if (tangle <= -math.pi / 2) or (tangle > math.pi / 2): tangle = tangle + math.pi tbase = getProj( prx.circle.valueAt(prx.circle.FirstParameter + (prx.circle.LastParameter - prx.circle.FirstParameter) / 2.0), plane) tbase = tbase.add( DraftVecUtils.rotate(Vector(0, 2.0 / scale, 0), tangle)) #print(tbase) else: tangle = 0 tbase = getProj(prx.tbase, plane) svg += getText(stroke, fontsize, obj.ViewObject.FontName, tangle, tbase, prx.string) elif getType(obj) == "Label": if getattr(obj.ViewObject, "Line", True): # some Labels may have no Line property def format_point(coords, action='L'): return "{action}{x},{y}".format(x=coords.x, y=coords.y, action=action) # Draw multisegment line proj_points = list(map(lambda x: getProj(x, plane), obj.Points)) path_dir_list = [format_point(proj_points[0], action='M')] path_dir_list += map(format_point, proj_points[1:]) path_dir_str = " ".join(path_dir_list) svg_path = '<path fill="none" stroke="{stroke}" stroke-width="{linewidth}" d="{directions}"/>'.format( stroke=stroke, linewidth=linewidth, directions=path_dir_str) svg += svg_path # Draw arrow. # We are different here from 3D view # if Line is set to 'off', no arrow is drawn if hasattr(obj.ViewObject, "ArrowType") and len(obj.Points) >= 2: last_segment = FreeCAD.Vector(obj.Points[-1] - obj.Points[-2]) angle = -DraftVecUtils.angle(getProj(last_segment, plane)) + math.pi svg += getArrow(arrowtype=obj.ViewObject.ArrowType, point=proj_points[-1], arrowsize=obj.ViewObject.ArrowSize.Value / pointratio, color=stroke, linewidth=linewidth, angle=angle) # print text if gui: if not obj.ViewObject: print("export of texts to SVG is only available in GUI mode") else: fontname = obj.ViewObject.TextFont position = getProj(obj.Placement.Base, plane) rotation = obj.Placement.Rotation justification = obj.ViewObject.TextAlignment text = obj.Text svg += getText(stroke, fontsize, fontname, rotation, position, text, linespacing, justification) elif getType(obj) in ["Annotation", "DraftText"]: "returns an svg representation of a document annotation" if gui: if not obj.ViewObject: print("export of texts to SVG is only available in GUI mode") else: n = obj.ViewObject.FontName if getType(obj) == "Annotation": p = getProj(obj.Position, plane) r = obj.ViewObject.Rotation.getValueAs("rad") t = obj.LabelText else: # DraftText p = getProj(obj.Placement.Base, plane) r = obj.Placement.Rotation t = obj.Text j = obj.ViewObject.Justification svg += getText(stroke, fontsize, n, r, p, t, linespacing, j) elif getType(obj) == "Axis": "returns the SVG representation of an Arch Axis system" if gui: if not obj.ViewObject: print("export of axes to SVG is only available in GUI mode") else: vobj = obj.ViewObject lorig = lstyle fill = 'none' rad = vobj.BubbleSize.Value / 2 n = 0 for e in obj.Shape.Edges: lstyle = lorig svg += getPath([e]) lstyle = "none" pos = ["Start"] if hasattr(vobj, "BubblePosition"): if vobj.BubblePosition == "Both": pos = ["Start", "End"] else: pos = [vobj.BubblePosition] for p in pos: if p == "Start": p1 = e.Vertexes[0].Point p2 = e.Vertexes[1].Point else: p1 = e.Vertexes[1].Point p2 = e.Vertexes[0].Point dv = p2.sub(p1) dv.normalize() center = p2.add(dv.scale(rad, rad, rad)) svg += getCircle(Part.makeCircle(rad, center)) if hasattr(vobj.Proxy, "bubbletexts"): if len(vobj.Proxy.bubbletexts) >= n: svg += '<text fill="' + stroke + '" ' svg += 'font-size="' + str(rad) + '" ' svg += 'style="text-anchor:middle;' svg += 'text-align:center;' svg += 'font-family: sans;" ' svg += 'transform="translate(' + str( center.x + rad / 4.0) + ',' + str(center.y - rad / 3.0) + ') ' svg += 'scale(1,-1)"> ' svg += '<tspan>' + obj.ViewObject.Proxy.bubbletexts[ n].string.getValues()[0] + '</tspan>\n' svg += '</text>\n' n += 1 lstyle = lorig elif getType(obj) == "Pipe": fill = stroke if obj.Base and obj.Diameter: svg += getPath(obj.Base.Shape.Edges) for f in obj.Shape.Faces: if len(f.Edges) == 1: if isinstance(f.Edges[0].Curve, Part.Circle): svg += getCircle(f.Edges[0]) elif getType(obj) == "Rebar": fill = "none" if obj.Proxy: if not hasattr(obj.Proxy, "wires"): obj.Proxy.execute(obj) if hasattr(obj.Proxy, "wires"): svg += getPath(wires=obj.Proxy.wires) elif getType(obj) == "PipeConnector": pass elif getType(obj) == "Space": "returns an SVG fragment for the text of a space" if gui: if not obj.ViewObject: print("export of spaces to SVG is only available in GUI mode") else: if fillSpaces: if hasattr(obj, "Proxy"): if not hasattr(obj.Proxy, "face"): obj.Proxy.getArea(obj, notouch=True) if hasattr(obj.Proxy, "face"): # setting fill if gui: fill = getrgb(obj.ViewObject.ShapeColor, testbw=False) fill_opacity = 1 - ( obj.ViewObject.Transparency / 100.0) else: fill = "#888888" svg += getPath(wires=[obj.Proxy.face.OuterWire]) c = getrgb(obj.ViewObject.TextColor) n = obj.ViewObject.FontName a = 0 if rotation != 0: a = math.radians(rotation) t1 = obj.ViewObject.Proxy.text1.string.getValues() t2 = obj.ViewObject.Proxy.text2.string.getValues() scale = obj.ViewObject.FirstLine.Value / obj.ViewObject.FontSize.Value f1 = fontsize * scale p2 = obj.Placement.multVec( FreeCAD.Vector(obj.ViewObject.Proxy.coords.translation. getValue().getValue())) lspc = FreeCAD.Vector(obj.ViewObject.Proxy.header.translation. getValue().getValue()) p1 = p2.add(lspc) j = obj.ViewObject.TextAlign t3 = getText(c, f1, n, a, getProj(p1, plane), t1, linespacing, j, flip=True) svg += t3 if t2: ofs = FreeCAD.Vector(0, -lspc.Length, 0) if a: ofs = FreeCAD.Rotation(FreeCAD.Vector(0, 0, 1), -rotation).multVec(ofs) t4 = getText(c, fontsize, n, a, getProj(p1, plane).add(ofs), t2, linespacing, j, flip=True) svg += t4 elif obj.isDerivedFrom('Part::Feature'): if obj.Shape.isNull(): return '' # setting fill if obj.Shape.Faces: if gui: try: m = obj.ViewObject.DisplayMode except AttributeError: m = None if (m != "Wireframe"): if fillstyle == "shape color": fill = getrgb(obj.ViewObject.ShapeColor, testbw=False) fill_opacity = 1 - (obj.ViewObject.Transparency / 100.0) else: fill = 'url(#' + fillstyle + ')' svg += getPattern(fillstyle) else: fill = "none" else: fill = "#888888" else: fill = 'none' if len(obj.Shape.Vertexes) > 1: wiredEdges = [] if obj.Shape.Faces: for i, f in enumerate(obj.Shape.Faces): # place outer wire first wires = [f.OuterWire] wires.extend([ w for w in f.Wires if w.hashCode() != f.OuterWire.hashCode() ]) svg += getPath(wires=f.Wires,pathname='%s_f%04d' % \ (obj.Name,i)) wiredEdges.extend(f.Edges) else: for i, w in enumerate(obj.Shape.Wires): svg += getPath(w.Edges,pathname='%s_w%04d' % \ (obj.Name,i)) wiredEdges.extend(w.Edges) if len(wiredEdges) != len(obj.Shape.Edges): for i, e in enumerate(obj.Shape.Edges): if (DraftGeomUtils.findEdge(e, wiredEdges) is None): svg += getPath([e],pathname='%s_nwe%04d' % \ (obj.Name,i)) else: # closed circle or spline if obj.Shape.Edges: if isinstance(obj.Shape.Edges[0].Curve, Part.Circle): svg = getCircle(obj.Shape.Edges[0]) else: svg = getPath(obj.Shape.Edges) if FreeCAD.GuiUp: if hasattr(obj.ViewObject, "EndArrow") and hasattr( obj.ViewObject, "ArrowType") and (len(obj.Shape.Vertexes) > 1): if obj.ViewObject.EndArrow: p1 = getProj(obj.Shape.Vertexes[-1].Point, plane) p2 = getProj(obj.Shape.Vertexes[-2].Point, plane) angle = -DraftVecUtils.angle(p2.sub(p1)) arrowsize = obj.ViewObject.ArrowSize.Value / pointratio svg += getArrow(obj.ViewObject.ArrowType, p1, arrowsize, stroke, linewidth, angle) # techdraw expects bottom-to-top coordinates if techdraw: svg = '<g transform ="scale(1,-1)">\n ' + svg + '</g>\n' return svg
def findHoles(self, obj, baseobject): '''findHoles(obj, baseobject) ... inspect baseobject and identify all features that resemble a straight cricular hole.''' shape = baseobject.Shape PathLog.track('obj: {} shape: {}'.format(obj, shape)) holelist = [] features = [] # tooldiameter = float(obj.ToolController.Proxy.getTool(obj.ToolController).Diameter) tooldiameter = None PathLog.debug('search for holes larger than tooldiameter: {}: '.format( tooldiameter)) if DraftGeomUtils.isPlanar(shape): PathLog.debug("shape is planar") for i in range(len(shape.Edges)): candidateEdgeName = "Edge" + str(i + 1) e = shape.getElement(candidateEdgeName) if PathUtils.isDrillable(shape, e, tooldiameter): PathLog.debug( 'edge candidate: {} (hash {})is drillable '.format( e, e.hashCode())) x = e.Curve.Center.x y = e.Curve.Center.y diameter = e.BoundBox.XLength holelist.append({ 'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True }) features.append((baseobject, candidateEdgeName)) PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateEdgeName)) else: PathLog.debug("shape is not planar") for i in range(len(shape.Faces)): candidateFaceName = "Face" + str(i + 1) f = shape.getElement(candidateFaceName) if PathUtils.isDrillable(shape, f, tooldiameter): PathLog.debug('face candidate: {} is drillable '.format(f)) if hasattr(f.Surface, 'Center'): x = f.Surface.Center.x y = f.Surface.Center.y diameter = f.BoundBox.XLength else: center = f.Edges[0].Curve.Center x = center.x y = center.y diameter = f.Edges[0].Curve.Radius * 2 holelist.append({ 'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True }) features.append((baseobject, candidateFaceName)) PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateFaceName)) PathLog.debug("holes found: {}".format(holelist)) return features
def smoothChordCommands(self, bone, inChord, outChord, edge, wire, corner, smooth, color = None): if smooth == 0: PathLog.info(" No smoothing requested") return [ bone.lastCommand, outChord.g1Command() ] d = 'in' refPoint = inChord.Start if smooth == Smooth.Out: d = 'out' refPoint = outChord.End if DraftGeomUtils.areColinear(inChord.asEdge(), outChord.asEdge()): PathLog.info(" straight edge %s" % d) return [ outChord.g1Command() ] pivot = None pivotDistance = 0 PathLog.info("smooth: (%.2f, %.2f)-(%.2f, %.2f)" % (edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y)) for e in wire.Edges: self.dbg.append(e) if type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line: PathLog.debug(" (%.2f, %.2f)-(%.2f, %.2f)" % (e.Vertexes[0].Point.x, e.Vertexes[0].Point.y, e.Vertexes[1].Point.x, e.Vertexes[1].Point.y)) else: PathLog.debug(" (%.2f, %.2f)^%.2f" % (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius)) for pt in DraftGeomUtils.findIntersection(edge, e, True, findAll=True): if not PathGeom.pointsCoincide(pt, corner) and self.pointIsOnEdge(pt, e): #debugMarker(pt, "candidate-%d-%s" % (self.boneId, d), color, 0.05) PathLog.debug(" -> candidate") distance = (pt - refPoint).Length if not pivot or pivotDistance > distance: pivot = pt pivotDistance = distance else: PathLog.debug(" -> corner intersect") if pivot: #debugCircle(pivot, self.toolRadius, "pivot.%d-%s" % (self.boneId, d), color) pivotEdge = Part.Edge(Part.Circle(pivot, FreeCAD.Vector(0,0,1), self.toolRadius)) t1 = self.findPivotIntersection(pivot, pivotEdge, inChord.asEdge(), inChord.End, d, color) t2 = self.findPivotIntersection(pivot, pivotEdge, outChord.asEdge(), inChord.End, d, color) commands = [] if not PathGeom.pointsCoincide(t1, inChord.Start): PathLog.debug(" add lead in") commands.append(Chord(inChord.Start, t1).g1Command()) if bone.obj.Side == Side.Left: PathLog.debug(" add g3 command") commands.append(Chord(t1, t2).g3Command(pivot)) else: PathLog.debug(" add g2 command center=(%.2f, %.2f) -> from (%2f, %.2f) to (%.2f, %.2f" % (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y)) commands.append(Chord(t1, t2).g2Command(pivot)) if not PathGeom.pointsCoincide(t2, outChord.End): PathLog.debug(" add lead out") commands.append(Chord(t2, outChord.End).g1Command()) #debugMarker(pivot, "pivot.%d-%s" % (self.boneId, d), color, 0.2) #debugMarker(t1, "pivot.%d-%s.in" % (self.boneId, d), color, 0.1) #debugMarker(t2, "pivot.%d-%s.out" % (self.boneId, d), color, 0.1) return commands PathLog.info(" no pivot found - straight command") return [ inChord.g1Command(), outChord.g1Command() ]
def action(self, arg): """Handle the 3D scene events. This is installed as an EventCallback in the Inventor view. Parameters ---------- arg: dict Dictionary with strings that indicates the type of event received from the 3D view. """ if arg["Type"] == "SoKeyboardEvent": if arg["Key"] == "ESCAPE": self.finish() elif arg["Type"] == "SoLocation2Event": # mouse movement detection import DraftGeomUtils shift = gui_tool_utils.hasMod(arg, gui_tool_utils.MODCONSTRAIN) if self.arcmode or self.point2: gui_tool_utils.setMod(arg, gui_tool_utils.MODCONSTRAIN, False) (self.point, ctrlPoint, self.info) = gui_tool_utils.getPoint( self, arg, noTracker=(len(self.node) > 0)) if (gui_tool_utils.hasMod(arg, gui_tool_utils.MODALT) or self.selectmode) and (len(self.node) < 3): self.dimtrack.off() if not self.altdown: self.altdown = True self.ui.switchUi(True) if hasattr(Gui, "Snapper"): Gui.Snapper.setSelectMode(True) snapped = self.view.getObjectInfo( (arg["Position"][0], arg["Position"][1])) if snapped: ob = self.doc.getObject(snapped['Object']) if "Edge" in snapped['Component']: num = int(snapped['Component'].lstrip('Edge')) - 1 ed = ob.Shape.Edges[num] v1 = ed.Vertexes[0].Point v2 = ed.Vertexes[-1].Point self.dimtrack.update([v1, v2, self.cont]) else: if self.node and (len(self.edges) < 2): self.dimtrack.on() if len(self.edges) == 2: # angular dimension self.dimtrack.off() r = self.point.sub(self.center) self.arctrack.setRadius(r.Length) a = self.arctrack.getAngle(self.point) pair = DraftGeomUtils.getBoundaryAngles(a, self.pts) if not (pair[0] < a < pair[1]): self.angledata = [ 4 * math.pi - pair[0], 2 * math.pi - pair[1] ] else: self.angledata = [ 2 * math.pi - pair[0], 2 * math.pi - pair[1] ] self.arctrack.setStartAngle(self.angledata[0]) self.arctrack.setEndAngle(self.angledata[1]) if self.altdown: self.altdown = False self.ui.switchUi(False) if hasattr(Gui, "Snapper"): Gui.Snapper.setSelectMode(False) if self.dir: _p = DraftVecUtils.project(self.point.sub(self.node[0]), self.dir) self.point = self.node[0].add(_p) if len(self.node) == 2: if self.arcmode and self.edges: cen = self.edges[0].Curve.Center rad = self.edges[0].Curve.Radius baseray = self.point.sub(cen) v2 = DraftVecUtils.scaleTo(baseray, rad) v1 = v2.negative() if shift: self.node = [cen, cen.add(v2)] self.arcmode = "radius" else: self.node = [cen.add(v1), cen.add(v2)] self.arcmode = "diameter" self.dimtrack.update(self.node) # Draw constraint tracker line. if shift and (not self.arcmode): if len(self.node) == 2: if not self.point2: self.point2 = self.node[1] else: self.node[1] = self.point2 if not self.force: _p = self.point.sub(self.node[0]) a = abs(_p.getAngle(App.DraftWorkingPlane.u)) if (a > math.pi / 4) and (a <= 0.75 * math.pi): self.force = 1 else: self.force = 2 if self.force == 1: self.node[1] = App.Vector(self.node[0].x, self.node[1].y, self.node[0].z) elif self.force == 2: self.node[1] = App.Vector(self.node[1].x, self.node[0].y, self.node[0].z) else: self.force = None if self.point2 and (len(self.node) > 1): self.node[1] = self.point2 self.point2 = None # update the dimline if self.node and not self.arcmode: self.dimtrack.update(self.node + [self.point] + [self.cont]) gui_tool_utils.redraw3DView() elif arg["Type"] == "SoMouseButtonEvent": if (arg["State"] == "DOWN") and (arg["Button"] == "BUTTON1"): import DraftGeomUtils if self.point: self.ui.redraw() if (not self.node) and (not self.support): gui_tool_utils.getSupport(arg) if (gui_tool_utils.hasMod(arg, gui_tool_utils.MODALT) or self.selectmode) and (len(self.node) < 3): # print("snapped: ",self.info) if self.info: ob = self.doc.getObject(self.info['Object']) if 'Edge' in self.info['Component']: num = int( self.info['Component'].lstrip('Edge')) - 1 ed = ob.Shape.Edges[num] v1 = ed.Vertexes[0].Point v2 = ed.Vertexes[-1].Point i1 = i2 = None for i in range(len(ob.Shape.Vertexes)): if v1 == ob.Shape.Vertexes[i].Point: i1 = i if v2 == ob.Shape.Vertexes[i].Point: i2 = i if (i1 is not None) and (i2 is not None): self.indices.append(num) if not self.edges: # nothing snapped yet, we treat it # as a normal edge-snapped dimension self.node = [v1, v2] self.link = [ob, i1, i2] self.edges.append(ed) if DraftGeomUtils.geomType( ed) == "Circle": # snapped edge is an arc self.arcmode = "diameter" self.link = [ob, num] else: # there is already a snapped edge, # so we start angular dimension self.edges.append(ed) # self.node now has the 4 endpoints self.node.extend([v1, v2]) c = DraftGeomUtils.findIntersection( self.node[0], self.node[1], self.node[2], self.node[3], True, True) if c: # print("centers:",c) self.center = c[0] self.arctrack.setCenter( self.center) self.arctrack.on() for e in self.edges: for v in e.Vertexes: self.pts.append( self.arctrack.getAngle( v.Point)) self.link = [self.link[0], ob] else: _msg( translate( "draft", "Edges don't intersect!")) self.finish() return self.dimtrack.on() else: self.node.append(self.point) self.selectmode = False # print("node", self.node) self.dimtrack.update(self.node) if len(self.node) == 2: self.point2 = self.node[1] if len(self.node) == 1: self.dimtrack.on() if self.planetrack: self.planetrack.set(self.node[0]) elif len(self.node) == 2 and self.cont: self.node.append(self.cont) self.createObject() if not self.cont: self.finish() elif len(self.node) == 3: # for unlinked arc mode: # if self.arcmode: # v = self.node[1].sub(self.node[0]) # v.multiply(0.5) # cen = self.node[0].add(v) # self.node = [self.node[0], self.node[1], cen] self.createObject() if not self.cont: self.finish() elif self.angledata: self.node.append(self.point) self.createObject() self.finish()
def getExtrusionData(self,obj): """returns (shape,extrusion vector,placement) or None""" import Part,DraftGeomUtils data = ArchComponent.Component.getExtrusionData(self,obj) if data: if not isinstance(data[0],list): # multifuses not considered here return data length = obj.Length.Value width = obj.Width.Value height = obj.Height.Value if not height: for p in obj.InList: if Draft.getType(p) == "Floor": if p.Height.Value: height = p.Height.Value if obj.Normal == Vector(0,0,0): normal = Vector(0,0,1) else: normal = Vector(obj.Normal) base = None placement = None basewires = None if obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape: if obj.Base.Shape.Solids: return None elif obj.Face > 0: if len(obj.Base.Shape.Faces) >= obj.Face: face = obj.Base.Shape.Faces[obj.Face-1] # this wall is based on a specific face of its base object normal = face.normalAt(0,0) if normal.getAngle(Vector(0,0,1)) > math.pi/4: normal.multiply(width) base = face.extrude(normal) if obj.Align == "Center": base.translate(normal.negative().multiply(0.5)) elif obj.Align == "Right": base.translate(normal.negative()) else: normal.multiply(height) base = face.extrude(normal) base,placement = self.rebase(base) return (base,normal,placement) elif obj.Base.Shape.Faces: if not DraftGeomUtils.isCoplanar(obj.Base.Shape.Faces): return None else: base,placement = self.rebase(obj.Base.Shape) elif obj.Base.Shape.Wires: basewires = obj.Base.Shape.Wires elif len(obj.Base.Shape.Edges) == 1: basewires = [Part.Wire(obj.Base.Shape.Edges)] if basewires and width: baseface = None for wire in basewires: e = wire.Edges[0] if isinstance(e.Curve,Part.Circle): dvec = e.Vertexes[0].Point.sub(e.Curve.Center) else: dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) if not DraftVecUtils.isNull(dvec): dvec.normalize() sh = None if obj.Align == "Left": dvec.multiply(width) if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) wire = DraftGeomUtils.offsetWire(wire,dvec2) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Right": dvec.multiply(width) dvec = dvec.negative() if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) wire = DraftGeomUtils.offsetWire(wire,dvec2) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(Part.__sortEdges__(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Center": dvec.multiply(width/2) w1 = DraftGeomUtils.offsetWire(wire,dvec) dvec = dvec.negative() w2 = DraftGeomUtils.offsetWire(wire,dvec) sh = DraftGeomUtils.bind(w1,w2) if sh: sh.fix(0.1,0,1) # fixes self-intersecting wires f = Part.Face(sh) if baseface: baseface = baseface.fuse(f) else: baseface = f if baseface: base,placement = self.rebase(baseface) else: l2 = length/2 or 0.5 w2 = width/2 or 0.5 v1 = Vector(-l2,-w2,0) v2 = Vector(l2,-w2,0) v3 = Vector(l2,w2,0) v4 = Vector(-l2,w2,0) base = Part.Face(Part.makePolygon([v1,v2,v3,v4,v1])) placement = FreeCAD.Placement() if base and placement: extrusion = normal.multiply(height) return (base,extrusion,placement) return None
def createObject(self): """Create the actual object in the current document.""" import DraftGeomUtils Gui.addModule("Draft") if self.angledata: normal = "None" if len(self.edges) == 2: v1 = DraftGeomUtils.vec(self.edges[0]) v2 = DraftGeomUtils.vec(self.edges[1]) normal = DraftVecUtils.toString((v1.cross(v2)).normalize()) _cmd = 'Draft.makeAngularDimension(' _cmd += 'center=' + DraftVecUtils.toString(self.center) + ', ' _cmd += 'angles=' _cmd += '[' _cmd += str(self.angledata[0]) + ', ' _cmd += str(self.angledata[1]) _cmd += '], ' _cmd += 'p3=' + DraftVecUtils.toString(self.node[-1]) + ', ' _cmd += 'normal=' + normal _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) elif self.link and not self.arcmode: ops = [] # Linear dimension, linked if self.force == 1: _cmd = 'Draft.makeDimension' _cmd += '(' _cmd += 'FreeCAD.ActiveDocument.' + self.link[0].Name + ', ' _cmd += str(self.link[1]) + ', ' _cmd += str(self.link[2]) + ', ' _cmd += DraftVecUtils.toString(self.node[2]) _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'dim.Direction = FreeCAD.Vector(0, 1, 0)', 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) elif self.force == 2: _cmd = 'Draft.makeDimension' _cmd += '(' _cmd += 'FreeCAD.ActiveDocument.' + self.link[0].Name + ', ' _cmd += str(self.link[1]) + ', ' _cmd += str(self.link[2]) + ', ' _cmd += DraftVecUtils.toString(self.node[2]) _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'dim.Direction = FreeCAD.Vector(1, 0, 0)', 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) else: _cmd = 'Draft.makeDimension' _cmd += '(' _cmd += 'FreeCAD.ActiveDocument.' + self.link[0].Name + ', ' _cmd += str(self.link[1]) + ', ' _cmd += str(self.link[2]) + ', ' _cmd += DraftVecUtils.toString(self.node[2]) _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) elif self.arcmode: # Radius or dimeter dimension, linked _cmd = 'Draft.makeDimension' _cmd += '(' _cmd += 'FreeCAD.ActiveDocument.' + self.link[0].Name + ', ' _cmd += str(self.link[1]) + ', ' _cmd += '"' + str(self.arcmode) + '", ' _cmd += DraftVecUtils.toString(self.node[2]) _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) else: # Linear dimension, non-linked _cmd = 'Draft.makeDimension' _cmd += '(' _cmd += DraftVecUtils.toString(self.node[0]) + ', ' _cmd += DraftVecUtils.toString(self.node[1]) + ', ' _cmd += DraftVecUtils.toString(self.node[2]) _cmd += ')' _cmd_list = [ 'dim = ' + _cmd, 'Draft.autogroup(dim)', 'FreeCAD.ActiveDocument.recompute()' ] self.commit(translate("draft", "Create Dimension"), _cmd_list) if self.ui.continueMode: self.cont = self.node[2] if not self.dir: if self.link: v1 = self.link[0].Shape.Vertexes[self.link[1]].Point v2 = self.link[0].Shape.Vertexes[self.link[2]].Point self.dir = v2.sub(v1) else: self.dir = self.node[1].sub(self.node[0]) self.node = [self.node[1]] self.link = None
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)
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)
def getProfiles(self, obj, noplacement=False): "Returns the base profile(s) of this component, if applicable" wires = [] n, l, w, h = self.getDefaultValues(obj) if obj.Base: if obj.Base.isDerivedFrom("Part::Extrusion"): if obj.Base.Base: base = obj.Base.Base.Shape.copy() if noplacement: base.Placement = FreeCAD.Placement() return [base] elif obj.Base.isDerivedFrom("Part::Feature"): if obj.Base.Shape: base = obj.Base.Shape.copy() if noplacement: base.Placement = FreeCAD.Placement() if not base.Solids: if base.Faces: import DraftGeomUtils if not DraftGeomUtils.isCoplanar(base.Faces): return [] return [base] basewires = [] if not base.Wires: if len(base.Edges) == 1: import Part basewires = [Part.Wire(base.Edges)] else: basewires = base.Wires if basewires: import DraftGeomUtils, DraftVecUtils, Part for wire in basewires: e = wire.Edges[0] if isinstance(e.Curve, Part.Circle): dvec = e.Vertexes[0].Point.sub( e.Curve.Center) else: dvec = DraftGeomUtils.vec( wire.Edges[0]).cross(n) if not DraftVecUtils.isNull(dvec): dvec.normalize() sh = None if hasattr(obj, "Align"): if obj.Align == "Left": dvec.multiply(w) if hasattr(obj, "Offset"): if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo( dvec, obj.Offset.Value) wire = DraftGeomUtils.offsetWire( wire, dvec2) w2 = DraftGeomUtils.offsetWire( wire, dvec) w1 = Part.Wire( Part.__sortEdges__(wire.Edges)) sh = DraftGeomUtils.bind(w1, w2) elif obj.Align == "Right": dvec.multiply(w) dvec = dvec.negative() if hasattr(obj, "Offset"): if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo( dvec, obj.Offset.Value) wire = DraftGeomUtils.offsetWire( wire, dvec2) w2 = DraftGeomUtils.offsetWire( wire, dvec) w1 = Part.Wire( Part.__sortEdges__(wire.Edges)) sh = DraftGeomUtils.bind(w1, w2) elif obj.Align == "Center": dvec.multiply(w / 2) w1 = DraftGeomUtils.offsetWire( wire, dvec) dvec = dvec.negative() w2 = DraftGeomUtils.offsetWire( wire, dvec) sh = DraftGeomUtils.bind(w1, w2) if sh: wires.append(sh) else: wires.append(wire) elif Draft.getType(obj) in ["Wall", "Structure"]: if (Draft.getType(obj) == "Structure") and (l > h): if noplacement: h2 = h / 2 or 0.5 w2 = w / 2 or 0.5 v1 = Vector(-h2, -w2, 0) v2 = Vector(h2, -w2, 0) v3 = Vector(h2, w2, 0) v4 = Vector(-h2, w2, 0) else: h2 = h / 2 or 0.5 w2 = w / 2 or 0.5 v1 = Vector(0, -w2, -h2) v2 = Vector(0, -w2, h2) v3 = Vector(0, w2, h2) v4 = Vector(0, w2, -h2) else: l2 = l / 2 or 0.5 w2 = w / 2 or 0.5 v1 = Vector(-l2, -w2, 0) v2 = Vector(l2, -w2, 0) v3 = Vector(l2, w2, 0) v4 = Vector(-l2, w2, 0) import Part base = Part.makePolygon([v1, v2, v3, v4, v1]) return [base] return wires
def make_circle(radius, placement=None, face=None, startangle=None, endangle=None, support=None): """make_circle(radius, [placement, face, startangle, endangle]) or make_circle(edge,[face]): Creates a circle object with given parameters. Parameters ---------- radius : the radius of the circle. placement : If placement is given, it is used. face : Bool If face is False, the circle is shown as a wireframe, otherwise as a face. startangle : start angle of the arc (in degrees) endangle : end angle of the arc (in degrees) if startangle and endangle are equal, a circle is created, if they are different an arc is created edge : edge.Curve must be a 'Part.Circle' the circle is created from the given edge support : TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return if placement: utils.type_check([(placement, App.Placement)], "make_circle") if startangle != endangle: _name = "Arc" else: _name = "Circle" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", _name) Circle(obj) if face != None: obj.MakeFace = face if isinstance(radius, Part.Edge): edge = radius if DraftGeomUtils.geomType(edge) == "Circle": obj.Radius = edge.Curve.Radius placement = App.Placement(edge.Placement) delta = edge.Curve.Center.sub(placement.Base) placement.move(delta) # Rotation of the edge rotOk = App.Rotation(edge.Curve.XAxis, edge.Curve.YAxis, edge.Curve.Axis, "ZXY") placement.Rotation = rotOk if len(edge.Vertexes) > 1: v0 = edge.Curve.XAxis v1 = (edge.Vertexes[0].Point).sub(edge.Curve.Center) v2 = (edge.Vertexes[-1].Point).sub(edge.Curve.Center) # Angle between edge.Curve.XAxis and the vector from center to start of arc a0 = math.degrees(App.Vector.getAngle(v0, v1)) # Angle between edge.Curve.XAxis and the vector from center to end of arc a1 = math.degrees(App.Vector.getAngle(v0, v2)) obj.FirstAngle = a0 obj.LastAngle = a1 else: obj.Radius = radius if (startangle != None) and (endangle != None): if startangle == -0: startangle = 0 obj.FirstAngle = startangle obj.LastAngle = endangle obj.Support = support if placement: obj.Placement = placement if App.GuiUp: ViewProviderDraft(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
def execute(self, obj): if self.clone(obj): return if not obj.Base: return if not obj.Base.Shape: return if not obj.Base.Shape.Wires: return if not obj.Diameter.Value: return if not obj.Amount: return father = obj.Host fathershape = None if not father: # support for old-style rebars if obj.InList: if hasattr(obj.InList[0], "Armatures"): if obj in obj.InList[0].Armatures: father = obj.InList[0] if father: if father.isDerivedFrom("Part::Feature"): fathershape = father.Shape wire = obj.Base.Shape.Wires[0] if hasattr(obj, "Rounding"): #print(obj.Rounding) if obj.Rounding: radius = obj.Rounding * obj.Diameter.Value import DraftGeomUtils wire = DraftGeomUtils.filletWire(wire, radius) bpoint, bvec = self.getBaseAndAxis(wire) if not bpoint: return axis = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, -1)) if fathershape: size = (ArchCommands.projectToVector(fathershape.copy(), axis)).Length else: size = 1 if hasattr(obj, "Direction"): if not DraftVecUtils.isNull(obj.Direction): axis = FreeCAD.Vector(obj.Direction) axis.normalize() if fathershape: size = (ArchCommands.projectToVector( fathershape.copy(), axis)).Length else: size = 1 if hasattr(obj, "Distance"): if obj.Distance.Value: size = obj.Distance.Value #print(axis) #print(size) spacinglist = None if hasattr(obj, "CustomSpacing"): if obj.CustomSpacing: spacinglist = strprocessOfCustomSpacing(obj.CustomSpacing) influenceArea = sum( spacinglist) - spacinglist[0] / 2 - spacinglist[-1] / 2 if (obj.OffsetStart.Value + obj.OffsetEnd.Value) > size: return # all tests ok! if hasattr(obj, "Length"): length = getLengthOfRebar(obj) if length: obj.Length = length pl = obj.Placement import Part circle = Part.makeCircle(obj.Diameter.Value / 2, bpoint, bvec) circle = Part.Wire(circle) try: bar = wire.makePipeShell([circle], True, False, 2) basewire = wire.copy() except Part.OCCError: print("Arch: error sweeping rebar profile along the base sketch") return # building final shape shapes = [] placementlist = [] self.wires = [] if father: rot = father.Placement.Rotation else: rot = FreeCAD.Rotation() if obj.Amount == 1: barplacement = CalculatePlacement(obj.Amount, 1, size, axis, rot, obj.OffsetStart.Value, obj.OffsetEnd.Value) placementlist.append(barplacement) if hasattr(obj, "Spacing"): obj.Spacing = 0 else: if obj.OffsetStart.Value: baseoffset = DraftVecUtils.scaleTo(axis, obj.OffsetStart.Value) else: baseoffset = None interval = size - (obj.OffsetStart.Value + obj.OffsetEnd.Value) interval = interval / (obj.Amount - 1) for i in range(obj.Amount): barplacement = CalculatePlacement(obj.Amount, i + 1, size, axis, rot, obj.OffsetStart.Value, obj.OffsetEnd.Value) placementlist.append(barplacement) if hasattr(obj, "Spacing"): obj.Spacing = interval # Calculate placement of bars from custom spacing. if spacinglist: placementlist[:] = [] reqInfluenceArea = size - (obj.OffsetStart.Value + obj.OffsetEnd.Value) # Avoid unnecessary checks to pass like. For eg.: when we have values # like influenceArea is 100.00001 and reqInflueneArea is 100 if round(influenceArea) > round(reqInfluenceArea): return FreeCAD.Console.PrintError( "Influence area of rebars is greater than " + str(reqInfluenceArea) + ".\n") elif round(influenceArea) < round(reqInfluenceArea): FreeCAD.Console.PrintWarning( "Last span is greater that end offset.\n") for i in range(len(spacinglist)): if i == 0: barplacement = CustomSpacingPlacement( spacinglist, 1, axis, father.Placement.Rotation, obj.OffsetStart.Value, obj.OffsetEnd.Value) placementlist.append(barplacement) else: barplacement = CustomSpacingPlacement( spacinglist, i + 1, axis, father.Placement.Rotation, obj.OffsetStart.Value, obj.OffsetEnd.Value) placementlist.append(barplacement) obj.Amount = len(spacinglist) obj.Spacing = 0 obj.PlacementList = placementlist for i in range(len(obj.PlacementList)): if i == 0: bar.Placement = obj.PlacementList[i] shapes.append(bar) basewire.Placement = obj.PlacementList[i] self.wires.append(basewire) else: bar = bar.copy() bar.Placement = obj.PlacementList[i] shapes.append(bar) w = basewire.copy() w.Placement = obj.PlacementList[i] self.wires.append(w) if shapes: obj.Shape = Part.makeCompound(shapes) obj.Placement = pl obj.TotalLength = obj.Length * len(obj.PlacementList)
def execute(self, obj): if self.clone(obj): return pl = obj.Placement #self.baseface = None self.flip = False if hasattr(obj, "Flip"): if obj.Flip: self.flip = True base = None baseWire = None if obj.Base: if hasattr(obj.Base, "Shape"): if obj.Base.Shape.Solids: base = obj.Base.Shape #pl = obj.Base.Placement else: if (obj.Base.Shape.Faces and obj.Face): baseWire = obj.Base.Shape.Faces[obj.Face - 1].Wires[0] elif obj.Base.Shape.Wires: baseWire = obj.Base.Shape.Wires[0] if baseWire: if baseWire.isClosed(): self.profilsDico = [] self.shps = [] self.subVolShps = [] heights = [] edges = Part.__sortEdges__(baseWire.Edges) if self.flip: edges = self.flipEdges(edges) ln = len(edges) obj.Angles = adjust_list_len(obj.Angles, ln, obj.Angles[0]) obj.Runs = adjust_list_len(obj.Runs, ln, obj.Runs[0]) obj.IdRel = adjust_list_len(obj.IdRel, ln, obj.IdRel[0]) obj.Thickness = adjust_list_len(obj.Thickness, ln, obj.Thickness[0]) obj.Overhang = adjust_list_len(obj.Overhang, ln, obj.Overhang[0]) for i in range(ln): self.makeRoofProfilsDic(i, obj.Angles[i], obj.Runs[i], obj.IdRel[i], obj.Overhang[i], obj.Thickness[i]) for i in range(ln): self.calcEdgeGeometry(i, edges[i]) for i in range(ln): self.calcApex( i, ln) # after calcEdgeGeometry as it uses vec data for i in range(ln): self.calcMissingData( i, ln ) # after calcApex so it can use recalculated heights for i in range(ln): self.calcDraftEdges(i) for i in range(ln): self.calcEave(i) for profil in self.profilsDico: heights.append(profil["height"]) obj.Heights = heights for i in range(ln): self.getRoofPaneProject(i) profilCurr = self.profilsDico[i] ptsPaneProject = profilCurr["points"] if len(ptsPaneProject) == 0: continue face = face_from_points(ptsPaneProject) if face: diag = face.BoundBox.DiagonalLength midpoint = DraftGeomUtils.findMidpoint( profilCurr["edge"]) thicknessV = profilCurr["thickness"] / (math.cos( math.radians(profilCurr["angle"]))) overhangV = profilCurr["overhang"] * math.tan( math.radians(profilCurr["angle"])) sol = face.extrude( Vector(0.0, 0.0, profilCurr["height"] + 1000000.0)) sol.translate(Vector(0.0, 0.0, -2.0 * overhangV)) ## baseVolume shape ptsPaneProfil = [ Vector(-profilCurr["overhang"], -overhangV, 0.0), Vector(profilCurr["run"], profilCurr["height"], 0.0), Vector(profilCurr["run"], profilCurr["height"] + thicknessV, 0.0), Vector(-profilCurr["overhang"], -overhangV + thicknessV, 0.0) ] self.shps.append( self.createProfilShape(ptsPaneProfil, midpoint, profilCurr["rot"], profilCurr["vec"], profilCurr["run"], diag, sol)) ## subVolume shape ptsSubVolProfil = [ Vector(-profilCurr["overhang"], -overhangV, 0.0), Vector(profilCurr["run"], profilCurr["height"], 0.0), Vector(profilCurr["run"], profilCurr["height"] + 900000.0, 0.0), Vector(-profilCurr["overhang"], profilCurr["height"] + 900000.0, 0.0) ] self.subVolShps.append( self.createProfilShape(ptsSubVolProfil, midpoint, profilCurr["rot"], profilCurr["vec"], profilCurr["run"], diag, sol)) if len( self.shps ) == 0: # occurs if all segments have angle=90 or run=0. # create a flat roof using the eavePtLst outline: ptsPaneProject = [] for i in range(ln): ptsPaneProject.append( self.profilsDico[i]["eavePtLst"][0]) face = face_from_points(ptsPaneProject) if face: thk = max( 1.0, self.profilsDico[0]["thickness"] ) # FreeCAD will crash when extruding with a null vector here self.shps = [face.extrude(Vector(0.0, 0.0, thk))] self.subVolShps = [ face.extrude(Vector(0.0, 0.0, 1000000.0)) ] ## baseVolume base = self.shps.pop() for s in self.shps: base = base.fuse(s) base = self.processSubShapes(obj, base, pl) self.applyShape(obj, base, pl, allownosolid=True) ## subVolume self.sub = self.subVolShps.pop() for s in self.subVolShps: self.sub = self.sub.fuse(s) self.sub = self.sub.removeSplitter() if not self.sub.isNull(): if not DraftGeomUtils.isNull(pl): self.sub.Placement = pl elif base: base = self.processSubShapes(obj, base, pl) self.applyShape(obj, base, pl, allownosolid=True) else: FreeCAD.Console.PrintMessage( translate("Arch", "Unable to create a roof"))
def getPath(edges=[], wires=[], pathname=None): svg = "<path " if pathname is None: svg += 'id="%s" ' % obj.Name elif pathname != "": svg += 'id="%s" ' % pathname svg += ' d="' if not wires: egroups = Part.sortEdges(edges) else: egroups = [] first = True for w in wires: w1 = w.copy() if first: first = False else: # invert further wires to create holes w1 = DraftGeomUtils.invert(w1) w1.fixWire() egroups.append(Part.__sortEdges__(w1.Edges)) for egroupindex, edges in enumerate(egroups): edata = "" vs = () #skipped for the first edge for edgeindex, e in enumerate(edges): previousvs = vs # vertexes of an edge (reversed if needed) vs = e.Vertexes if previousvs: if (vs[0].Point - previousvs[-1].Point).Length > 1e-6: vs.reverse() if edgeindex == 0: v = getProj(vs[0].Point, plane) edata += 'M ' + str(v.x) + ' ' + str(v.y) + ' ' else: if (vs[0].Point - previousvs[-1].Point).Length > 1e-6: raise ValueError('edges not ordered') iscircle = DraftGeomUtils.geomType(e) == "Circle" isellipse = DraftGeomUtils.geomType(e) == "Ellipse" if iscircle or isellipse: import math if hasattr(FreeCAD, "DraftWorkingPlane"): drawing_plane_normal = FreeCAD.DraftWorkingPlane.axis else: drawing_plane_normal = FreeCAD.Vector(0, 0, 1) if plane: drawing_plane_normal = plane.axis c = e.Curve if round(c.Axis.getAngle(drawing_plane_normal), 2) in [0, 3.14]: occversion = Part.OCC_VERSION.split(".") done = False if (int(occversion[0]) >= 7) and (int(occversion[1]) >= 1): # if using occ >= 7.1, use HLR algorithm import Drawing snip = Drawing.projectToSVG( e, drawing_plane_normal) if snip: try: a = "A " + snip.split("path d=\"")[ 1].split("\"")[0].split("A")[1] except: pass else: edata += a done = True if not done: if len(e.Vertexes ) == 1 and iscircle: #complete curve svg = getCircle(e) return svg elif len(e.Vertexes) == 1 and isellipse: #svg = getEllipse(e) #return svg endpoints = [ getProj( c.value((c.LastParameter - c.FirstParameter) / 2.0), plane), getProj(vs[-1].Point, plane) ] else: endpoints = [getProj(vs[-1].Point, plane)] # arc if iscircle: rx = ry = c.Radius rot = 0 else: #ellipse rx = c.MajorRadius ry = c.MinorRadius rot = math.degrees(c.AngleXU * (c.Axis * \ FreeCAD.Vector(0,0,1))) if rot > 90: rot -= 180 if rot < -90: rot += 180 #be careful with the sweep flag flag_large_arc = (((e.ParameterRange[1] - \ e.ParameterRange[0]) / math.pi) % 2) > 1 #flag_sweep = (c.Axis * drawing_plane_normal >= 0) \ # == (e.LastParameter > e.FirstParameter) # == (e.Orientation == "Forward") # other method: check the direction of the angle between tangents t1 = e.tangentAt(e.FirstParameter) t2 = e.tangentAt( e.FirstParameter + (e.LastParameter - e.FirstParameter) / 10) flag_sweep = (DraftVecUtils.angle( t1, t2, drawing_plane_normal) < 0) for v in endpoints: edata += 'A %s %s %s %s %s %s %s ' % \ (str(rx),str(ry),str(rot),\ str(int(flag_large_arc)),\ str(int(flag_sweep)),str(v.x),str(v.y)) else: edata += getDiscretized(e, plane) elif DraftGeomUtils.geomType(e) == "Line": v = getProj(vs[-1].Point, plane) edata += 'L ' + str(v.x) + ' ' + str(v.y) + ' ' else: bspline = e.Curve.toBSpline(e.FirstParameter, e.LastParameter) if bspline.Degree > 3 or bspline.isRational(): try: bspline = bspline.approximateBSpline( 0.05, 50, 3, 'C0') except RuntimeError: print("Debug: unable to approximate bspline") if bspline.Degree <= 3 and not bspline.isRational(): for bezierseg in bspline.toBezier(): if bezierseg.Degree > 3: #should not happen raise AssertionError elif bezierseg.Degree == 1: edata += 'L ' elif bezierseg.Degree == 2: edata += 'Q ' elif bezierseg.Degree == 3: edata += 'C ' for pole in bezierseg.getPoles()[1:]: v = getProj(pole, plane) edata += str(v.x) + ' ' + str(v.y) + ' ' else: print("Debug: one edge (hash ",e.hashCode(),\ ") has been discretized with parameter 0.1") for linepoint in bspline.discretize(0.1)[1:]: v = getProj(linepoint, plane) edata += 'L ' + str(v.x) + ' ' + str(v.y) + ' ' if fill != 'none': edata += 'Z ' if edata in pathdata: # do not draw a path on another identical path return "" else: svg += edata pathdata.append(edata) svg += '" ' svg += 'stroke="' + stroke + '" ' svg += 'stroke-width="' + str(linewidth) + ' px" ' svg += 'style="stroke-width:' + str(linewidth) svg += ';stroke-miterlimit:4' svg += ';stroke-dasharray:' + lstyle svg += ';fill:' + fill try: svg += ';fill-opacity:' + str(fill_opacity) except NameError: pass svg += ';fill-rule: evenodd "' svg += '/>\n' return svg
def execute(self, obj): if len(obj.InList) != 1: return if Draft.getType(obj.InList[0]) != "Structure": return if not obj.InList[0].Shape: return if not obj.Base: return if not obj.Base.Shape: return if not obj.Base.Shape.Wires: return if not obj.Diameter.Value: return if not obj.Amount: return father = obj.InList[0] wire = obj.Base.Shape.Wires[0] if hasattr(obj, "Rounding"): #print obj.Rounding if obj.Rounding: radius = obj.Rounding * obj.Diameter.Value import DraftGeomUtils wire = DraftGeomUtils.filletWire(wire, radius) bpoint, bvec = self.getBaseAndAxis(obj) if not bpoint: return axis = obj.Base.Placement.Rotation.multVec(FreeCAD.Vector(0, 0, -1)) size = (ArchCommands.projectToVector(father.Shape.copy(), axis)).Length if hasattr(obj, "Direction"): if not DraftVecUtils.isNull(obj.Direction): axis = FreeCAD.Vector(obj.Direction) #.normalize() # don't normalize so the vector can also be used to determine the distance size = axis.Length #print axis #print size if (obj.OffsetStart.Value + obj.OffsetEnd.Value) > size: return # all tests ok! pl = obj.Placement import Part circle = Part.makeCircle(obj.Diameter.Value / 2, bpoint, bvec) circle = Part.Wire(circle) try: bar = wire.makePipeShell([circle], True, False, 2) except: print "Arch: error sweeping rebar profile along the base sketch" return # building final shape shapes = [] if obj.Amount == 1: offset = DraftVecUtils.scaleTo(axis, size / 2) bar.translate(offset) shapes.append(bar) if hasattr(obj, "Spacing"): obj.Spacing = 0 else: if obj.OffsetStart.Value: baseoffset = DraftVecUtils.scaleTo(axis, obj.OffsetStart.Value) else: baseoffset = None interval = size - (obj.OffsetStart.Value + obj.OffsetEnd.Value) interval = interval / (obj.Amount - 1) vinterval = DraftVecUtils.scaleTo(axis, interval) for i in range(obj.Amount): if i == 0: if baseoffset: bar.translate(baseoffset) shapes.append(bar) else: bar = bar.copy() bar.translate(vinterval) shapes.append(bar) if hasattr(obj, "Spacing"): obj.Spacing = interval if shapes: obj.Shape = Part.makeCompound(shapes) obj.Placement = pl
def backPignon(self, i): import DraftGeomUtils profilCurrent = self.findProfil(i) profilBack1 = self.findProfil(i - 1) profilBack2 = self.findProfil(i - 2) pt1 = DraftGeomUtils.findIntersection( profilCurrent["edge"], profilBack1["eave"], infinite1=True, infinite2=True, ) pt2 = DraftGeomUtils.findIntersection( profilBack2["edge"], profilBack1["eave"], infinite1=True, infinite2=True, ) eaveWithoutOverhang = DraftGeomUtils.edg(pt1[0], pt2[0]) if profilCurrent["run"] + profilBack2[ "run"] != eaveWithoutOverhang.Length: points = [ FreeCAD.Vector(0.0, 0.0, 0.0), ] points.append( FreeCAD.Vector(profilCurrent["run"], profilCurrent["height"], 0.0)) rampantCurrent = DraftGeomUtils.edg(points[0], points[1]) points = [ FreeCAD.Vector(eaveWithoutOverhang.Length, 0.0, 0.0), ] points.append( FreeCAD.Vector(eaveWithoutOverhang.Length - profilBack2["run"], profilBack2["height"], 0.0)) rampantBack2 = DraftGeomUtils.edg(points[0], points[1]) point = DraftGeomUtils.findIntersection( rampantCurrent, rampantBack2, infinite1=True, infinite2=True, ) ridgeCurrent = DraftGeomUtils.offset( profilCurrent["edge"], self.getPerpendicular(profilCurrent["vec"], profilCurrent["rot"], point[0].x)) point = DraftGeomUtils.findIntersection( ridgeCurrent, profilBack1["eave"], infinite1=True, infinite2=True, ) else: point = DraftGeomUtils.findIntersection( profilCurrent["ridge"], profilBack1["eave"], infinite1=True, infinite2=True, ) self.ptsPaneProject.append(FreeCAD.Vector(point[0])) point = DraftGeomUtils.findIntersection( profilCurrent["eave"], profilBack1["eave"], infinite1=True, infinite2=True, ) self.ptsPaneProject.append(FreeCAD.Vector(point[0]))
def onChanged(self, obj, prop): if prop in ["Source", "RenderingMode", "ShowCut"]: import Part, DraftGeomUtils if hasattr(obj, "Source"): if obj.Source: if obj.Source.Objects: objs = Draft.getGroupContents(obj.Source.Objects, walls=True, addgroups=True) objs = Draft.removeHidden(objs) # separate spaces self.spaces = [] os = [] for o in objs: if Draft.getType(o) == "Space": self.spaces.append(o) else: os.append(o) objs = os self.svg = '' fillpattern = '<pattern id="sectionfill" patternUnits="userSpaceOnUse" patternTransform="matrix(5,0,0,5,0,0)"' fillpattern += ' x="0" y="0" width="10" height="10">' fillpattern += '<g>' fillpattern += '<rect width="10" height="10" style="stroke:none; fill:#ffffff" /><path style="stroke:#000000; stroke-width:1" d="M0,0 l10,10" /></g></pattern>' # generating SVG if obj.RenderingMode == "Solid": # render using the Arch Vector Renderer import ArchVRM, WorkingPlane wp = WorkingPlane.plane() wp.setFromPlacement(obj.Source.Placement) #wp.inverse() render = ArchVRM.Renderer() render.setWorkingPlane(wp) render.addObjects(objs) if hasattr(obj, "ShowCut"): render.cut(obj.Source.Shape, obj.ShowCut) else: render.cut(obj.Source.Shape) self.svg += '<g transform="scale(1,-1)">\n' self.svg += render.getViewSVG( linewidth="LWPlaceholder") self.svg += fillpattern self.svg += render.getSectionSVG( linewidth="SWPlaceholder", fillpattern="sectionfill") if hasattr(obj, "ShowCut"): if obj.ShowCut: self.svg += render.getHiddenSVG( linewidth="LWPlaceholder") self.svg += '</g>\n' # print render.info() else: # render using the Drawing module import Drawing, Part shapes = [] hshapes = [] sshapes = [] p = FreeCAD.Placement(obj.Source.Placement) self.direction = p.Rotation.multVec( FreeCAD.Vector(0, 0, 1)) for o in objs: if o.isDerivedFrom("Part::Feature"): if o.Shape.isNull(): pass #FreeCAD.Console.PrintWarning(translate("Arch","Skipping empty object: ")+o.Name) elif o.Shape.isValid(): if hasattr(obj.Source, "OnlySolids"): if obj.Source.OnlySolids: shapes.extend(o.Shape.Solids) else: shapes.append(o.Shape) else: shapes.extend(o.Shape.Solids) else: FreeCAD.Console.PrintWarning( translate( "Arch", "Skipping invalid object: ") + o.Name) cutface, cutvolume, invcutvolume = ArchCommands.getCutVolume( obj.Source.Shape.copy(), shapes) if cutvolume: nsh = [] for sh in shapes: for sol in sh.Solids: if sol.Volume < 0: sol.reverse() c = sol.cut(cutvolume) s = sol.section(cutface) try: wires = DraftGeomUtils.findWires( s.Edges) for w in wires: f = Part.Face(w) sshapes.append(f) #s = Part.Wire(s.Edges) #s = Part.Face(s) except Part.OCCError: #print "ArchDrawingView: unable to get a face" sshapes.append(s) nsh.extend(c.Solids) #sshapes.append(s) if hasattr(obj, "ShowCut"): if obj.ShowCut: c = sol.cut(invcutvolume) hshapes.append(c) shapes = nsh if shapes: self.shapes = shapes self.baseshape = Part.makeCompound(shapes) svgf = Drawing.projectToSVG( self.baseshape, self.direction) if svgf: svgf = svgf.replace( 'stroke-width="0.35"', 'stroke-width="LWPlaceholder"') svgf = svgf.replace( 'stroke-width="1"', 'stroke-width="LWPlaceholder"') svgf = svgf.replace( 'stroke-width:0.01', 'stroke-width:LWPlaceholder') self.svg += svgf if hshapes: hshapes = Part.makeCompound(hshapes) self.hiddenshape = hshapes svgh = Drawing.projectToSVG( hshapes, self.direction) if svgh: svgh = svgh.replace( 'stroke-width="0.35"', 'stroke-width="LWPlaceholder"') svgh = svgh.replace( 'stroke-width="1"', 'stroke-width="LWPlaceholder"') svgh = svgh.replace( 'stroke-width:0.01', 'stroke-width:LWPlaceholder') svgh = svgh.replace( 'fill="none"', 'fill="none"\nstroke-dasharray="DAPlaceholder"' ) self.svg += svgh if sshapes: svgs = "" if hasattr(obj, "ShowFill"): if obj.ShowFill: svgs += fillpattern svgs += '<g transform="rotate(180)">\n' for s in sshapes: if s.Edges: f = Draft.getSVG( s, direction=self.direction. negative(), linewidth=0, fillstyle="sectionfill", color=(0, 0, 0)) svgs += f svgs += "</g>\n" sshapes = Part.makeCompound(sshapes) self.sectionshape = sshapes svgs += Drawing.projectToSVG( sshapes, self.direction) if svgs: svgs = svgs.replace( 'stroke-width="0.35"', 'stroke-width="SWPlaceholder"') svgs = svgs.replace( 'stroke-width="1"', 'stroke-width="SWPlaceholder"') svgs = svgs.replace( 'stroke-width:0.01', 'stroke-width:SWPlaceholder') svgs = svgs.replace( 'stroke-width="0.35 px"', 'stroke-width="SWPlaceholder"') svgs = svgs.replace( 'stroke-width:0.35', 'stroke-width:SWPlaceholder') self.svg += svgs
def makeStraightLanding(self,obj,edge,numberofsteps=None, callByMakeStraightStairsWithLanding=False): # what is use of numberofsteps ? "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 = 0 if obj.RiserHeightEnforce != 0: h = obj.RiserHeightEnforce * numberofsteps elif obj.Base: # TODO - should this happen? - though in original code if obj.Base.isDerivedFrom("Part::Feature"): #l = obj.Base.Shape.Length #if obj.Base.Shape.BoundBox.ZLength: if round(obj.Base.Shape.BoundBox.ZLength,Draft.precision()) != 0: # ? - need precision h = obj.Base.Shape.BoundBox.ZLength #.Value? else: print ("obj.Base has 0 z-value") print (h) if h==0 and obj.Height.Value != 0: h = obj.Height.Value else: print (h) if obj.TreadDepthEnforce != 0: l = obj.TreadDepthEnforce.Value * (numberofsteps-2) if obj.LandingDepth: l += obj.LandingDepth.Value else: l += obj.Width.Value elif obj.Base: if obj.Base.isDerivedFrom("Part::Feature"): l = obj.Base.Shape.Length #.Value? elif obj.Length.Value != 0: l = obj.Length.Value 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 callByMakeStraightStairsWithLanding: if obj.Flight == "HalfTurnLeft": p1 = p1.add(-vWidth) p2 = p2.add(-vWidth) elif obj.Flight == "HalfTurnRight": p3 = p3.add(vWidth) p4 = p4.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 in ["HalfTurnLeft","HalfTurnRight"]: 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)
def run(self): """run(): Runs a nesting operation. Returns a list of lists of shapes, each primary list being one filled container, or None if the operation failed.""" # reset abort mechanism and variables self.running = True self.progress = 0 starttime = datetime.now() # general conformity tests print("Executing conformity tests ... ", end="") if not self.container: print("Empty container. Aborting") return if not self.shapes: print("Empty shapes. Aborting") return if not isinstance(self.container, Part.Face): print("Container is not a face. Aborting") return normal = self.container.normalAt(0, 0) for s in self.shapes: if not self.update(): return if len(s.Faces) != 1: print( "One of the shapes does not contain exactly one face. Aborting" ) return # check if all faces correctly oriented (same normal) if s.Faces[0].normalAt(0, 0).getAngle(normal) > TOLERANCE: # let pass faces with inverted normal if s.Faces[0].normalAt( 0, 0).getAngle(normal) - math.pi > TOLERANCE: print( "One of the face doesn't have the same orientation as the container. Aborting" ) return # TODO # allow to use a non-rectangular container # manage margins/paddings # allow to prevent or force specific rotations for a piece # LONG-TERM TODO # add genetic algo to swap pieces, and check if the result is better # track progresses step = 100.0 / (len(self.shapes) * len(ROTATIONS)) # store hashCode together with the face so we can change the order # and still identify the original face, so we can calculate a transform afterwards self.indexedfaces = [[shape.hashCode(), shape] for shape in self.shapes] # build a clean copy so we don't touch the original faces = list(self.indexedfaces) # replace shapes by their face faces = [[f[0], f[1].Faces[0]] for f in faces] # order by area faces = sorted(faces, key=lambda face: face[1].Area) # discretize non-linear edges and remove holes nfaces = [] for face in faces: if not self.update(): return nedges = [] allLines = True for edge in face[1].OuterWire.OrderedEdges: if isinstance(edge.Curve, (Part.LineSegment, Part.Line)): nedges.append(edge) else: allLines = False last = edge.Vertexes[0].Point for i in range(DISCRETIZE): s = float(i + 1) / DISCRETIZE par = edge.FirstParameter + (edge.LastParameter - edge.FirstParameter) * s new = edge.valueAt(par) nedges.append(Part.LineSegment(last, new).toShape()) last = new f = Part.Face(Part.Wire(nedges)) if not f.isValid(): if allLines: print("Invalid face found in set. Aborting") else: print("Face distretizing failed. Aborting") return nfaces.append([face[0], f]) faces = nfaces # container for sheets with a first, empty sheet sheets = [[]] print("Everything OK (", datetime.now() - starttime, ")") # main loop facenumber = 1 facesnumber = len(faces) #print("Vertices per face:",[len(face[1].Vertexes) for face in faces]) while faces: print("Placing piece", facenumber, "/", facesnumber, "Area:", FreeCAD.Units.Quantity( faces[-1][1].Area, FreeCAD.Units.Area).getUserPreferred()[0], ": ", end="") face = faces.pop() boc = self.container.BoundBox # this stores the available solutions for each rotation of a piece # contains [sheetnumber,face,xlength] lists, # face being [hascode,transformed face] and xlength # the X size of all boundboxes of placed pieces available = [] # this stores the possible positions on a blank # sheet, in case we need to create a new one initials = [] # this checks if the piece don't fit in the container unfit = True for rotation in ROTATIONS: if not self.update(): return self.progress += step print(rotation, ", ", end="") hashcode = face[0] rotface = face[1].copy() if rotation: rotface.rotate(rotface.CenterOfMass, normal, rotation) bof = rotface.BoundBox rotverts = self.order(rotface) #for i,v in enumerate(rotverts): # Draft.makeText([str(i)],point=v) basepoint = rotverts[0] # leftmost point of the rotated face basecorner = boc.getPoint( 0) # lower left corner of the container # See if the piece fits in the container dimensions if (bof.XLength < boc.XLength) and (bof.YLength < boc.YLength): unfit = False # Get the fit polygon of the container # that is, the polygon inside which basepoint can # circulate, and the face still be fully inside the container v1 = basecorner.add(basepoint.sub(bof.getPoint(0))) v2 = v1.add(FreeCAD.Vector(0, boc.YLength - bof.YLength, 0)) v3 = v2.add(FreeCAD.Vector(boc.XLength - bof.XLength, 0, 0)) v4 = v3.add(FreeCAD.Vector(0, -(boc.YLength - bof.YLength), 0)) binpol = Part.Face(Part.makePolygon([v1, v2, v3, v4, v1])) initials.append([binpol, [hashcode, rotface], basepoint]) # check for available space on each existing sheet for sheetnumber, sheet in enumerate(sheets): # Get the no-fit polygon for each already placed face in # current sheet. That is, a polygon in which basepoint # cannot be, if we want our face to not overlap with the # placed face. # To do this, we "circulate" the face around the placed face if not self.update(): return nofitpol = [] for placed in sheet: pts = [] pi = 0 for placedvert in self.order(placed[1], right=True): fpts = [] for i, rotvert in enumerate(rotverts): if not self.update(): return facecopy = rotface.copy() facecopy.translate(placedvert.sub(rotvert)) # test if all the points of the face are outside the # placed face (except the base point, which is coincident) outside = True faceverts = self.order(facecopy) for vert in faceverts: if (vert.sub(placedvert) ).Length > TOLERANCE: if placed[1].isInside( vert, TOLERANCE, True): outside = False break # also need to test for edge intersection, because even # if all vertices are outside, the pieces could still # overlap if outside: for e1 in facecopy.OuterWire.Edges: for e2 in placed[1].OuterWire.Edges: if not self.update(): return if True: # Draft code (SLOW) p = DraftGeomUtils.findIntersection( e1, e2) if p: p = p[0] p1 = e1.Vertexes[0].Point p2 = e1.Vertexes[1].Point p3 = e2.Vertexes[0].Point p4 = e2.Vertexes[1].Point if (p.sub(p1).Length > TOLERANCE) and (p.sub(p2).Length > TOLERANCE) \ and (p.sub(p3).Length > TOLERANCE) and (p.sub(p4).Length > TOLERANCE): outside = False break else: # alt code: using distToShape (EVEN SLOWER!) p = e1.distToShape(e2) if p: if p[0] < TOLERANCE: # allow vertex-to-vertex intersection if (p[2][0][0] != "Vertex" ) or (p[2][0][3] != "Vertex"): outside = False break if outside: fpts.append([faceverts[0], i]) #Draft.makeText([str(i)],point=faceverts[0]) # reorder available solutions around a same point if needed # ensure they are in the correct order idxs = [p[1] for p in fpts] if (0 in idxs) and (len(faceverts) - 1 in idxs): slicepoint = len(fpts) last = len(faceverts) for p in reversed(fpts): if p[1] == last - 1: slicepoint -= 1 last -= 1 else: break fpts = fpts[slicepoint:] + fpts[:slicepoint] #print(fpts) pts.extend(fpts) # create the polygon if len(pts) < 3: print( "Error calculating a no-fit polygon. Aborting") return pts = [p[0] for p in pts] pol = Part.Face(Part.makePolygon(pts + [pts[0]])) if not pol.isValid(): # fix overlapping edges overlap = True while overlap: overlap = False for i in range(len(pol.OuterWire.Edges) - 1): if not self.update(): return v1 = DraftGeomUtils.vec( pol.OuterWire.OrderedEdges[i]) v2 = DraftGeomUtils.vec( pol.OuterWire.OrderedEdges[i + 1]) if abs(v1.getAngle(v2) - math.pi) <= TOLERANCE: overlap = True ne = Part.LineSegment( pol.OuterWire.OrderedEdges[i]. Vertexes[0].Point, pol.OuterWire.OrderedEdges[i + 1]. Vertexes[-1].Point).toShape() pol = Part.Face( Part.Wire(pol.OuterWire. OrderedEdges[:i] + [ne] + pol.OuterWire. OrderedEdges[i + 2:])) break if not pol.isValid(): # trying basic OCC fix pol.fix(0, 0, 0) if pol.isValid(): if pol.ShapeType == "Face": pol = Part.Face( pol.OuterWire ) # discard possible inner holes elif pol.Faces: # several faces after the fix, keep the biggest one a = 0 ff = None for f in pol.Faces: if f.Area > a: a = f.Area ff = f if ff: pol = ff else: print( "Unable to fix invalid no-fit polygon. Aborting" ) Part.show(pol) return if not pol.isValid(): # none of the fixes worked. Epic fail. print("Invalid no-fit polygon. Aborting") Part.show(pol.OuterWire) for p in sheet: Part.show(p[1]) Part.show(facecopy) #for i,p in enumerate(faceverts): # Draft.makeText([str(i)],point=p) return if pol.isValid(): nofitpol.append(pol) #Part.show(pol) # Union all the no-fit pols into one if len(nofitpol) == 1: nofitpol = nofitpol[0] elif len(nofitpol) > 1: b = nofitpol.pop() for n in nofitpol: if not self.update(): return b = b.fuse(n) nofitpol = b # remove internal edges (discard edges shared by 2 faces) lut = {} for f in fitpol.Faces: for e in f.Edges: h = e.hashCode() if h in lut: lut[h].append(e) else: lut[h] = [e] edges = [e[0] for e in lut.values() if len(e) == 1] try: pol = Part.Face(Part.Wire(edges)) except: # above method can fail sometimes. Try a slower method w = DraftGeomUtils.findWires(edges) if len(w) == 1: if w[0].isClosed(): try: pol = Part.Face(w[0]) except: print( "Error merging polygons. Aborting") try: Part.show(Part.Wire(edges)) except: for e in edges: Part.show(e) return # subtract the no-fit polygon from the container's fit polygon # we then have the zone where the face can be placed if nofitpol: fitpol = binpol.cut(nofitpol) else: fitpol = binpol.copy() # check that we have some space on this sheet if (fitpol.Area > 0) and fitpol.Vertexes: # order the fitpol vertexes by smallest X # and try to place the piece, making sure it doesn't # intersect with already placed pieces fitverts = sorted([v.Point for v in fitpol.Vertexes], key=lambda v: v.x) for p in fitverts: if not self.update(): return trface = rotface.copy() trface.translate(p.sub(basepoint)) ok = True for placed in sheet: if ok: for vert in trface.Vertexes: if placed[1].isInside( vert.Point, TOLERANCE, False): ok = False break if ok: for e1 in trface.OuterWire.Edges: for e2 in placed[1].OuterWire.Edges: p = DraftGeomUtils.findIntersection( e1, e2) if p: p = p[0] p1 = e1.Vertexes[0].Point p2 = e1.Vertexes[1].Point p3 = e2.Vertexes[0].Point p4 = e2.Vertexes[1].Point if (p.sub(p1).Length > TOLERANCE) and (p.sub(p2).Length > TOLERANCE) \ and (p.sub(p3).Length > TOLERANCE) and (p.sub(p4).Length > TOLERANCE): ok = False break if not ok: break if ok: rotface = trface break else: print( "Couldn't determine location on sheet. Aborting" ) return # check the X space occupied by this solution bb = rotface.BoundBox for placed in sheet: bb.add(placed[1].BoundBox) available.append([ sheetnumber, [hashcode, rotface], bb.XMax, fitpol ]) if unfit: print("One face doesn't fit in the container. Aborting") return if available: # order by smallest X size and take the first one available = sorted(available, key=lambda sol: sol[2]) print("Adding piece to sheet", available[0][0] + 1) sheets[available[0][0]].append(available[0][1]) #Part.show(available[0][3]) else: # adding to the leftmost vertex of the binpol sheet = [] print("Creating new sheet, adding piece to sheet", len(sheets)) # order initial positions by smallest X size initials = sorted(initials, key=lambda sol: sol[1][1].BoundBox.XLength) hashcode = initials[0][1][0] face = initials[0][1][1] # order binpol vertexes by X coord verts = sorted([v.Point for v in initials[0][0].Vertexes], key=lambda v: v.x) face.translate(verts[0].sub(initials[0][2])) sheet.append([hashcode, face]) sheets.append(sheet) facenumber += 1 print("Run time:", datetime.now() - starttime) self.results.append(sheets) return sheets
def make_wire(pointslist, closed=False, placement=None, face=None, support=None, bs2wire=False): """makeWire(pointslist,[closed],[placement]) Creates a Wire object from the given list of vectors. If face is true (and wire is closed), the wire will appear filled. Instead of a pointslist, you can also pass a Part Wire. Parameters ---------- pointslist : [Base.Vector] List of points to create the polyline closed : bool If closed is True or first and last points are identical, the created polyline will be closed. placement : Base.Placement If a placement is given, it is used. face : Bool If face is False, the rectangle is shown as a wireframe, otherwise as a face. support : TODO: Describe bs2wire : bool TODO: Describe """ if not App.ActiveDocument: App.Console.PrintError("No active document. Aborting\n") return None import Part if isinstance(pointslist, (list,tuple)): for pnt in pointslist: if not isinstance(pnt, App.Vector): App.Console.PrintError( "Items must be Base.Vector objects, not {}\n".format( type(pnt))) return None elif isinstance(pointslist, Part.Wire): for edge in pointslist.Edges: if not DraftGeomUtils.is_straight_line(edge): App.Console.PrintError("All edges must be straight lines\n") return None closed = pointslist.isClosed() pointslist = [v.Point for v in pointslist.OrderedVertexes] else: App.Console.PrintError("Can't make Draft Wire from {}\n".format( type(pointslist))) return None if len(pointslist) == 0: App.Console.PrintWarning("Draft Wire created with empty point list\n") if placement: utils.type_check([(placement, App.Placement)], "make_wire") ipl = placement.inverse() if not bs2wire: pointslist = [ipl.multVec(p) for p in pointslist] if len(pointslist) == 2: fname = "Line" else: fname = "Wire" obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname) Wire(obj) obj.Points = pointslist obj.Closed = closed obj.Support = support if face != None: obj.MakeFace = face if placement: obj.Placement = placement if App.GuiUp: ViewProviderWire(obj.ViewObject) gui_utils.format_object(obj) gui_utils.select(obj) return obj
def returnOutlines(self, obj, edges, align="left", offsetH=0, offsetV=0): # better omit 'obj' latter - 'currently' only for vbaseFollowLastSement()? import DraftGeomUtils v, vLength, vWidth, vBase = [], [], [], [] p1o, p2o, p1, p2, p3, p4 = [], [], [], [], [], [] outline, outlineP1P2, outlineP3P4 = [], [], [] enum_edges = enumerate(edges) for i, edge in enum_edges: v.append(DraftGeomUtils.vec(edge)) vLength.append(Vector(v[i].x,v[i].y,0)) # TODO obj.Width[i].Value for different 'edges' / 'sections' of the landing netWidth = obj.Width.Value - 2*offsetH vWidth.append(DraftVecUtils.scaleTo(vLength[i].cross(Vector(0,0,1)),netWidth)) vBase.append(edges[i].Vertexes[0].Point) vBase[i] = self.vbaseFollowLastSement(obj, vBase[i]) if offsetV != 0: # redundant? vBase[i] = vBase[i].add(Vector(0,0,offsetV)) if offsetH != 0: # redundant? vOffsetH = DraftVecUtils.scaleTo(vLength[i].cross(Vector(0,0,1)),offsetH) vBase[i] = self.align(vBase[i], "Right", -vOffsetH) # step + structure # assume all left-align first # no nosing p1o.append(vBase[i].add(Vector(0,0,-abs(obj.TreadThickness.Value)))) p2o.append(p1o[i].add(vLength[i])) p1.append(self.align(vBase[i],obj.Align,vWidth[i]).add(Vector(0,0,-abs(obj.TreadThickness.Value)))) p2.append(p1[i].add(vLength[i])) p3.append(p2[i].add(vWidth[i])) p4.append(p3[i].add(DraftVecUtils.neg(vLength[i]))) #if obj.Align == 'Left': if False: outlineP1P2.append(p1[i]) outlineP1P2.append(p2[i]) # can better skip 1 'supposedly' overlapping point every pair? if i > 0: print ("Debug - intersection calculation") print (p3[i-1]) print (p4[i-1]) print (p3[i]) print (p4[i]) intersection = DraftGeomUtils.findIntersection(p3[i-1],p4[i-1],p3[i],p4[i],True,True) print (intersection) outlineP3P4.insert(0, intersection[0]) else: outlineP3P4.insert(0, p4[i]) #elif obj.Align == 'Right': if False: if i > 0: intersection = DraftGeomUtils.findIntersection(p1[i-1],p2[i-1],p1[i],p2[i],True,True) outlineP1P2.append(intersection[0]) else: outlineP1P2.append(p1[i]) outlineP3P4.insert(0, p4[i]) outlineP3P4.insert(0, p3[i]) #elif obj.Align == 'Center': if True: if i > 0: intersection = DraftGeomUtils.findIntersection(p1[i-1],p2[i-1],p1[i],p2[i],True,True) outlineP1P2.append(intersection[0]) intersection = DraftGeomUtils.findIntersection(p3[i-1],p4[i-1],p3[i],p4[i],True,True) outlineP3P4.insert(0, intersection[0]) else: outlineP1P2.append(p1[i]) outlineP3P4.insert(0, p4[i]) else: outlineP1P2.append(p1[i]) outlineP1P2.append(p2[i]) outlineP3P4.insert(0, p4[i]) outlineP3P4.insert(0, p3[i]) # add back last/first 'missing' point(s) outlineP3P4.insert(0, p3[i]) outlineP1P2.append(p2[i]) outline = outlineP1P2 + outlineP3P4 outline.append(p1[0]) print (outlineP1P2) print (outlineP3P4) print (outline) return outline, outlineP1P2, outlineP3P4, vBase
def getBase(self,obj,wire,normal,width,height): "returns a full shape from a base wire" import DraftGeomUtils,Part flat = False if hasattr(obj.ViewObject,"DisplayMode"): flat = (obj.ViewObject.DisplayMode == "Flat 2D") dvec = DraftGeomUtils.vec(wire.Edges[0]).cross(normal) if not DraftVecUtils.isNull(dvec): dvec.normalize() if obj.Align == "Left": dvec.multiply(width) if hasattr(obj,"Offset"): if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) wire = DraftGeomUtils.offsetWire(wire,dvec2) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Right": dvec.multiply(width) dvec = dvec.negative() if hasattr(obj,"Offset"): if obj.Offset.Value: dvec2 = DraftVecUtils.scaleTo(dvec,obj.Offset.Value) wire = DraftGeomUtils.offsetWire(wire,dvec2) w2 = DraftGeomUtils.offsetWire(wire,dvec) w1 = Part.Wire(DraftGeomUtils.sortEdges(wire.Edges)) sh = DraftGeomUtils.bind(w1,w2) elif obj.Align == "Center": dvec.multiply(width/2) w1 = DraftGeomUtils.offsetWire(wire,dvec) dvec = dvec.negative() w2 = DraftGeomUtils.offsetWire(wire,dvec) sh = DraftGeomUtils.bind(w1,w2) # fixing self-intersections sh.fix(0.1,0,1) self.BaseProfile = sh if height and (not flat): self.ExtrusionVector = Vector(normal).multiply(height) sh = sh.extrude(self.ExtrusionVector) return sh
def execute(self, obj): """This method is run when the object is created or recomputed.""" import Part if (obj.Length.Value == 0) or (obj.Height.Value == 0): obj.positionBySupport() return plm = obj.Placement shape = None if hasattr(obj, "Rows") and hasattr(obj, "Columns"): # TODO: verify if this is needed: if obj.Rows > 1: rows = obj.Rows else: rows = 1 if obj.Columns > 1: columns = obj.Columns else: columns = 1 # TODO: till here if (rows > 1) or (columns > 1): shapes = [] l = obj.Length.Value / columns h = obj.Height.Value / rows for i in range(columns): for j in range(rows): p1 = App.Vector(i * l, j * h, 0) p2 = App.Vector(p1.x + l, p1.y, p1.z) p3 = App.Vector(p1.x + l, p1.y + h, p1.z) p4 = App.Vector(p1.x, p1.y + h, p1.z) p = Part.makePolygon([p1, p2, p3, p4, p1]) if "ChamferSize" in obj.PropertiesList: if obj.ChamferSize.Value != 0: w = DraftGeomUtils.filletWire( p, obj.ChamferSize.Value, chamfer=True) if w: p = w if "FilletRadius" in obj.PropertiesList: if obj.FilletRadius.Value != 0: w = DraftGeomUtils.filletWire( p, obj.FilletRadius.Value) if w: p = w if hasattr(obj, "MakeFace"): if obj.MakeFace: p = Part.Face(p) shapes.append(p) if shapes: shape = Part.makeCompound(shapes) if not shape: p1 = App.Vector(0, 0, 0) p2 = App.Vector(p1.x + obj.Length.Value, p1.y, p1.z) p3 = App.Vector(p1.x + obj.Length.Value, p1.y + obj.Height.Value, p1.z) p4 = App.Vector(p1.x, p1.y + obj.Height.Value, p1.z) shape = Part.makePolygon([p1, p2, p3, p4, p1]) if "ChamferSize" in obj.PropertiesList: if obj.ChamferSize.Value != 0: w = DraftGeomUtils.filletWire(shape, obj.ChamferSize.Value, chamfer=True) if w: shape = w if "FilletRadius" in obj.PropertiesList: if obj.FilletRadius.Value != 0: w = DraftGeomUtils.filletWire(shape, obj.FilletRadius.Value) if w: shape = w if hasattr(obj, "MakeFace"): if obj.MakeFace: shape = Part.Face(shape) else: shape = Part.Face(shape) obj.Shape = shape if hasattr(obj, "Area") and hasattr(shape, "Area"): obj.Area = shape.Area obj.Placement = plm obj.positionBySupport()
def getIndices(shape, offset): "returns a list with 2 lists: vertices and face indexes, offsetted with the given amount" vlist = [] elist = [] flist = [] curves = None for e in shape.Edges: try: if not isinstance(e.Curve, Part.Line): if not curves: curves = shape.tessellate(1) FreeCAD.Console.PrintWarning( translate( "Arch", "Found a shape containing curves, triangulating\n") ) break except: # unimplemented curve type curves = shape.tessellate(1) FreeCAD.Console.PrintWarning( translate("Arch", "Found a shape containing curves, triangulating\n")) break if curves: for v in curves[0]: vlist.append(" " + str(round(v.x, p)) + " " + str(round(v.y, p)) + " " + str(round(v.z, p))) for f in curves[1]: fi = "" for vi in f: fi += " " + str(vi + offset) flist.append(fi) else: for v in shape.Vertexes: vlist.append(" " + str(round(v.X, p)) + " " + str(round(v.Y, p)) + " " + str(round(v.Z, p))) if not shape.Faces: for e in shape.Edges: if DraftGeomUtils.geomType(e) == "Line": ei = " " + str( findVert(e.Vertexes[0], shape.Vertexes) + offset) ei += " " + str( findVert(e.Vertexes[-1], shape.Vertexes) + offset) elist.append(ei) for f in shape.Faces: if len(f.Wires) > 1: # if we have holes, we triangulate tris = f.tessellate(1) for fdata in tris[1]: fi = "" for vi in fdata: vdata = Part.Vertex(tris[0][vi]) fi += " " + str( findVert(vdata, shape.Vertexes) + offset) flist.append(fi) else: fi = "" # OCC vertices are unsorted. We need to sort in the right order... edges = Part.__sortEdges__(f.OuterWire.Edges) #print edges for e in edges: #print e.Vertexes[0].Point,e.Vertexes[1].Point v = e.Vertexes[0] ind = findVert(v, shape.Vertexes) if ind == None: return None, None, None fi += " " + str(ind + offset) flist.append(fi) return vlist, elist, flist
def makeStraightStairsWithLanding(self,obj,edge): "builds a straight staircase with/without a landing in the middle" if obj.NumberOfSteps < 3: return import Part,DraftGeomUtils v = DraftGeomUtils.vec(edge) landing = 0 if obj.TreadDepthEnforce == 0: if obj.Landings == "At center": 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)) else: reslength = edge.Length vLength = DraftVecUtils.scaleTo(v,float(reslength)/(obj.NumberOfSteps-1)) else: if obj.Landings == "At center": reslength = obj.TreadDepthEnforce * (obj.NumberOfSteps-2) # TODO any use ? else: reslength = obj.TreadDepthEnforce * (obj.NumberOfSteps-1) # TODO any use ? vLength = DraftVecUtils.scaleTo(v,float(obj.TreadDepthEnforce)) 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 obj.RiserHeightEnforce == 0: if round(v.z,Draft.precision()) != 0: h = v.z else: h = obj.Height.Value hstep = h/obj.NumberOfSteps else: h = obj.RiserHeightEnforce.Value * (obj.NumberOfSteps) hstep = obj.RiserHeightEnforce.Value if obj.Landings == "At center": landing = int(obj.NumberOfSteps/2) else: landing = obj.NumberOfSteps if obj.LastSegment: lastSegmentAbsTop = obj.LastSegment.AbsTop p1 = Vector(p1.x, p1.y,lastSegmentAbsTop.z) # use Last Segment top's z-coordinate obj.AbsTop = p1.add(Vector(0,0,h)) p2 = p1.add(DraftVecUtils.scale(vLength,landing-1).add(Vector(0,0,landing*hstep))) if obj.Landings == "At center": 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 in ["HalfTurnLeft HalfTurnLeft", "HalfTurnRight"]: if (obj.Align == "Left" and obj.Flight == "HalfTurnLeft") or (obj.Align == "Right" and obj.Flight == "HalfTurnRight"): p3r = p2 elif (obj.Align == "Left" and obj.Flight == "HalfTurnRight"): p3r = self.align(p2,"Right",-2*vWidth) # -ve / opposite direction of "Right" - no "Left" in _Stairs.Align() elif (obj.Align == "Right" and obj.Flight == "HalfTurnLeft"): p3r = self.align(p2,"Right",2*vWidth) elif (obj.Align == "Center" and obj.Flight == "HalfTurnLeft"): p3r = self.align(p2,"Right",vWidth) elif (obj.Align == "Center" and obj.Flight == "HalfTurnRight"): p3r = self.align(p2,"Right",-vWidth) # -ve / opposite direction of "Right" - no "Left" in _Stairs.Align() else: print("Should have a bug here, if see this") p4r = p3r.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.makeStraightLanding(obj,Part.LineSegment(p2,p3).toShape(), None, True) if obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]: self.makeStraightStairs(obj,Part.LineSegment(p3r,p4r).toShape(),obj.NumberOfSteps-landing) else: self.makeStraightStairs(obj,Part.LineSegment(p3,p4).toShape(),obj.NumberOfSteps-landing) self.makeStraightStairs(obj,Part.LineSegment(p1,p2).toShape(),landing) print (p1, p2) if obj.Landings == "At center" and obj.Flight not in ["HalfTurnLeft", "HalfTurnRight"]: print (p3, p4) elif obj.Landings == "At center" and obj.Flight in ["HalfTurnLeft", "HalfTurnRight"]: print (p3r, p4r)