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 getSVG(obj,scale=1,linewidth=0.35,fontsize=12,fillstyle="shape color",direction=None,linestyle=None,color=None,linespacing=None,techdraw=False,rotation=0): '''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.''' # if this is a group, gather all the svg views of its children if hasattr(obj,"isDerivedFrom"): if obj.isDerivedFrom("App::DocumentObjectGroup"): svg = "" for child in obj.Group: svg += getSVG(child,scale,linewidth,fontsize,fillstyle,direction,linestyle,color,linespacing,techdraw) return svg pathdata = [] svg = "" linewidth = float(linewidth)/scale 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: 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) def getPath(edges=[],wires=[],pathname=None): import Part,DraftGeomUtils 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 = [] for w in wires: w1=w.copy() 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) == 0: # 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(color,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: 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] if sys.version_info.major < 3 and (not isinstance(t,unicode)): 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 fill="' + color +'" 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 fill="' svg += color +'" 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] except: svg += text[0].decode("utf8") else: for i in range(len(text)): if i == 0: svg += '<tspan>' else: svg += '<tspan x="0" dy="'+str(linespacing)+'">' try: svg += text[i] except: svg += text[i].decode("utf8") 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+')' lstyle = getLineStyle(linestyle, scale) svg += getPath(obj.Edges,pathname="") elif getType(obj) == "Dimension": 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)) # drawing lines 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)) 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)) 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)+'" ' 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" lstyle = getLineStyle(linestyle, scale) 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 = getLineStyle(linestyle, scale) 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 elif getType(obj) == "Pipe": fill = stroke lstyle = getLineStyle(linestyle, scale) 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" lstyle = getLineStyle(linestyle, scale) 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: 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 = 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 svg += getText(c,f1,n,a,getProj(p1, plane),t1,linespacing,j,flip=True) if t2: ofs = FreeCAD.Vector(0,lspc.Length,0) if a: ofs = FreeCAD.Rotation(FreeCAD.Vector(0,0,1),-rotation).multVec(ofs) svg += getText(c,fontsize,n,a,getProj(p1, plane).add(ofs),t2,linespacing,j,flip=True) 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' lstyle = getLineStyle(linestyle, scale) if len(obj.Shape.Vertexes) > 1: wiredEdges = [] if obj.Shape.Faces: for i,f in enumerate(obj.Shape.Faces): 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) == 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[-2].Point, plane) p2 = getProj(obj.Shape.Vertexes[-1].Point, plane) angle = -DraftVecUtils.angle(p2.sub(p1)) arrowsize = obj.ViewObject.ArrowSize.Value/pointratio svg += getArrow(obj.ViewObject.ArrowType,p2,arrowsize,stroke,linewidth,angle) # techdraw expects bottom-to-top coordinates if techdraw: svg = '<g transform ="scale(1,-1)">'+svg+'</g>' return svg