# -*- coding: utf8 -*- # *************************************************************************** # * Copyright (c) 2009 Yorik van Havre * # * Copyright (c) 2018 George Shuklin (amarao) * # * * # * This program is free software; you can redistribute it and/or modify * # * it under the terms of the GNU Lesser General Public License (LGPL) * # * as published by the Free Software Foundation; either version 2 of * # * the License, or (at your option) any later version. * # * for detail see the LICENCE text file. * # * * # * This program is distributed in the hope that it will be useful, * # * but WITHOUT ANY WARRANTY; without even the implied warranty of * # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * # * GNU Library General Public License for more details. * # * * # * You should have received a copy of the GNU Library General Public * # * License along with this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * # *************************************************************************** """Provides functions to return the SVG representation of various shapes.""" ## @defgroup getSVG getSVG # \ingroup DRAFT # \brief Provides functions to return the SVG representation of shapes. import math import six import lazy_loader.lazy_loader as lz import FreeCAD import DraftVecUtils import WorkingPlane import draftutils.utils as utils from FreeCAD import Vector from draftutils.messages import _msg, _wrn # Delay import of module until first use because it is heavy Part = lz.LazyLoader("Part", globals(), "Part") DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") Drawing = lz.LazyLoader("Drawing", globals(), "Drawing") param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") ## \addtogroup getSVG # @{ def get_line_style(line_style, scale): """Return a linestyle scaled by a factor.""" style = None if line_style == "Dashed": style = param.GetString("svgDashedLine", "0.09,0.05") elif line_style == "Dashdot": style = param.GetString("svgDashdotLine", "0.09,0.05,0.02,0.05") elif line_style == "Dotted": style = param.GetString("svgDottedLine", "0.02,0.02") elif line_style: if "," in line_style: style = line_style if style: style = style.split(",") try: # scale dashes style = ",".join([str(float(d)/scale) for d in style]) # print("lstyle ", style) except: return "none" else: return style return "none" def getLineStyle(linestyle, scale): """Return a Line style. DEPRECATED. Use get_line_style.""" utils.use_instead("get_line_style") return get_line_style(linestyle, scale) def get_proj(vec, plane=None): """Get a projection of the vector in the plane's u and v directions. Parameters ---------- vec: Base::Vector3 An arbitrary vector that will be projected on the U and V directions. plane: WorkingPlane.Plane An object of type `WorkingPlane`. """ if not plane: return vec nx = DraftVecUtils.project(vec, plane.u) lx = nx.Length if abs(nx.getAngle(plane.u)) > 0.1: lx = -lx ny = DraftVecUtils.project(vec, plane.v) ly = ny.Length if abs(ny.getAngle(plane.v)) > 0.1: ly = -ly # if techdraw: buggy - we now simply do it at the end # ly = -ly return Vector(lx, ly, 0) def getProj(vec, plane=None): """Get a projection of a vector. DEPRECATED.""" utils.use_instead("get_proj") return get_proj(vec, plane) def get_discretized(edge, plane): """Get a discretized edge on a plane.""" pieces = param.GetFloat("svgDiscretization", 10.0) if pieces == 0: pieces = 10 d = int(edge.Length/pieces) if d == 0: d = 1 edata = "" for i in range(d + 1): _length = edge.LastParameter - edge.FirstParameter _point = edge.FirstParameter + float(i)/d * _length _vec = edge.valueAt(_point) v = get_proj(_vec, plane) if not edata: edata += 'M ' + str(v.x) + ' ' + str(v.y) + ' ' else: edata += 'L ' + str(v.x) + ' ' + str(v.y) + ' ' return edata def getDiscretized(edge, plane): """Get a discretized edge on a plane. DEPRECATED.""" utils.use_instead("get_discretized") return get_discretized(edge, plane) def get_pattern(pat): """Get an SVG pattern.""" patterns = utils.svg_patterns() if pat in patterns: return patterns[pat][0] return '' def getPattern(pat): """Get an SVG pattern. DEPRECATED.""" utils.use_instead("get_pattern") return get_pattern(pat) def get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[], wires=[], pathname=None): """Get the SVG representation from an object's edges or wires.""" svg = " 1e-6: vs.reverse() if edgeindex == 0: v = get_proj(vs[0].Point, plane) edata += 'M {} {} '.format(v.x, 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: 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 ax = c.Axis if round(ax.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 snip = Drawing.projectToSVG(e, drawing_plane_normal) if snip: try: _a = snip.split('path d="')[1] _a = _a.split('"')[0] _a = _a.split("A")[1] A = "A " + _a except: _wrn("Circle or ellipse: " "error spliting the projection snip") else: edata += A done = True if not done: if len(e.Vertexes) == 1 and iscircle: # Complete circle not only arc svg = getCircle(e) return svg elif len(e.Vertexes) == 1 and isellipse: # Complete ellipse not only arc # svg = getEllipse(e) # return svg # Difference in angles _diff = (c.LastParameter - c.FirstParameter)/2.0 endpoints = [get_proj(c.value(_diff), plane), get_proj(vs[-1].Point, plane)] else: endpoints = [get_proj(vs[-1].Point, plane)] # Arc with more than one vertex if iscircle: rx = ry = c.Radius rot = 0 else: # ellipse rx = c.MajorRadius ry = c.MinorRadius rot = math.degrees(c.AngleXU * c.Axis * Vector(0, 0, 1)) if rot > 90: rot -= 180 if rot < -90: rot += 180 # Be careful with the sweep flag _diff = e.ParameterRange[1] - e.ParameterRange[0] _diff = _diff / math.pi flag_large_arc = (_diff % 2) > 1 # flag_sweep = (c.Axis * drawing_plane_normal >= 0) \ # == (e.LastParameter > e.FirstParameter) # == (e.Orientation == "Forward") # Another method: check the direction of the angle # between tangents _diff = e.LastParameter - e.FirstParameter t1 = e.tangentAt(e.FirstParameter) t2 = e.tangentAt(e.FirstParameter + _diff/10) flag_sweep = DraftVecUtils.angle(t1, t2, drawing_plane_normal) < 0 for v in endpoints: edata += ('A {} {} {} ' '{} {} ' '{} {} '.format(rx, ry, rot, int(flag_large_arc), int(flag_sweep), v.x, v.y)) else: edata += get_discretized(e, plane) elif DraftGeomUtils.geomType(e) == "Line": v = get_proj(vs[-1].Point, plane) edata += 'L {} {} '.format(v.x, v.y) else: # If it's not a circle nor ellipse nor straight line # convert the curve to BSpline 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: _wrn("Debug: unable to approximate bspline from edge") if bspline.Degree <= 3 and not bspline.isRational(): for bezierseg in bspline.toBezier(): if bezierseg.Degree > 3: # should not happen _wrn("Bezier segment of degree > 3") 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 = get_proj(pole, plane) edata += '{} {} '.format(v.x, v.y) else: _msg("Debug: one edge (hash {}) " "has been discretized " "with parameter 0.1".format(e.hashCode())) for linepoint in bspline.discretize(0.1)[1:]: v = get_proj(linepoint, plane) edata += 'L {} {} '.format(v.x, 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="{}" '.format(stroke) svg += 'stroke-width="{} px" '.format(linewidth) svg += 'style="' svg += 'stroke-width:{};'.format(linewidth) svg += 'stroke-miterlimit:4;' svg += 'stroke-dasharray:{};'.format(lstyle) svg += 'fill:{};'.format(fill) # fill_opacity must be a number, but if it's `None` it is omitted if fill_opacity is not None: svg += 'fill-opacity:{};'.format(fill_opacity) svg += 'fill-rule: evenodd"' svg += '/>\n' return svg def getPath(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity, edges=[], wires=[], pathname=None): """Get the SVG representation from a path. DEPRECATED.""" utils.use_instead("get_path") return get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity, edges=edges, wires=wires, pathname=pathname) 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): """Return a string containing an SVG representation of the object. Paramaeters ----------- scale: float, optional It defaults to 1. It allows scaling line widths down, so they are resolution-independent. linewidth: float, optional It defaults to 0.35. fontsize: float, optional It defaults to 12, which is interpreted as `pt` unit (points). It is used if the given object contains any text. fillstyle: str, optional It defaults to 'shape color'. direction: Base::Vector3, optional It defaults to `None`. It is an arbitrary projection vector or a `WorkingPlane.Plane` instance. linestyle: optional It defaults to `None`. color: optional It defaults to `None`. linespacing: float, optional It defaults to `None`. techdraw: bool, optional It defaults to `False`. If it is `True`, it sets some options for generating SVG strings for displaying inside TechDraw. rotation: float, optional It defaults to 0. fillSpaces: bool, optional It defaults to `False`. override: bool, optional It defaults to `True`. """ # If this is a group, recursively call this function to gather # all the SVG strings from the contents of the group if hasattr(obj, "isDerivedFrom"): if (obj.isDerivedFrom("App::DocumentObjectGroup") or utils.get_type(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") and 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) # The number of times the dots are smaller than the arrow size pointratio = 0.75 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 = utils.get_rgb(color) elif FreeCAD.GuiUp: if hasattr(obj, "ViewObject"): if hasattr(obj.ViewObject, "LineColor"): stroke = utils.get_rgb(obj.ViewObject.LineColor) elif hasattr(obj.ViewObject, "TextColor"): stroke = utils.get_rgb(obj.ViewObject.TextColor) lstyle = "none" if override: lstyle = get_line_style(linestyle, scale) else: if hasattr(obj, "ViewObject") and hasattr(obj.ViewObject, "DrawStyle"): lstyle = get_line_style(obj.ViewObject.DrawStyle, scale) def getCircle(edge): cen = get_proj(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 = '\n' else: print("getSVG: arrow type not implemented") return svg def getOvershoot(point,shootsize,color,linewidth,angle=0): svg = '\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 += '\n' else: svg = '",">") except: svg += text[0].decode("utf8").replace("&","&").replace("<","<").replace(">",">") else: for i in range(len(text)): if i == 0: svg += '' else: svg += '' try: svg += text[i].replace("&","&").replace("<","<").replace(">",">") except: svg += text[i].decode("utf8").replace("&","&").replace("<","<").replace(">",">") svg += '\n' svg += '\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 += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=obj.Edges, pathname="") elif utils.get_type(obj) in ["Dimension", "LinearDimension"]: if FreeCAD.GuiUp: 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 = get_proj(prx.p2.add(DraftVecUtils.scaleTo(prx.p3.sub(prx.p2),rm)), plane) p2b = get_proj(prx.p3.add(DraftVecUtils.scaleTo(prx.p2.sub(prx.p3),rm)), plane) p1 = get_proj(prx.p1, plane) p2 = get_proj(prx.p2, plane) p3 = get_proj(prx.p3, plane) p4 = get_proj(prx.p4, plane) tbase = get_proj(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(get_proj(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 += ' 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 utils.get_type(obj) == "AngularDimension": if FreeCAD.GuiUp: 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 += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[prx.circle]) else: if hasattr(prx,"circle1"): svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[prx.circle1]) svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[prx.circle2]) else: svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[prx.circle]) # drawing arrows if hasattr(obj.ViewObject,"ArrowType"): p2 = get_proj(prx.p2, plane) p3 = get_proj(prx.p3, plane) arrowsize = obj.ViewObject.ArrowSize.Value/pointratio arrowlength = 4*obj.ViewObject.ArrowSize.Value u1 = get_proj((prx.circle.valueAt(prx.circle.FirstParameter+arrowlength)).sub(prx.circle.valueAt(prx.circle.FirstParameter)), plane) u2 = get_proj((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 = get_proj(t, plane) tangle = DraftVecUtils.angle(t) if (tangle <= -math.pi/2) or (tangle > math.pi/2): tangle = tangle + math.pi tbase = get_proj(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 = get_proj(prx.tbase, plane) svg += getText(stroke,fontsize,obj.ViewObject.FontName,tangle,tbase,prx.string) elif utils.get_type(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: get_proj(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 = ''.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(get_proj(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 FreeCAD.GuiUp: if not obj.ViewObject: print("export of texts to SVG is only available in GUI mode") else: fontname = obj.ViewObject.TextFont position = get_proj(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 utils.get_type(obj) in ["Annotation", "DraftText", "Text"]: # returns an svg representation of a document annotation if FreeCAD.GuiUp: if not obj.ViewObject: print("export of texts to SVG is only available in GUI mode") else: n = obj.ViewObject.FontName if utils.get_type(obj) == "Annotation": p = get_proj(obj.Position, plane) r = obj.ViewObject.Rotation.getValueAs("rad") t = obj.LabelText else: # DraftText (old) or Text (new, 0.19) p = get_proj(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 utils.get_type(obj) == "Axis": # returns the SVG representation of an Arch Axis system if FreeCAD.GuiUp: 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 += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=[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 += '\n' svg += '\n' n += 1 lstyle = lorig elif utils.get_type(obj) == "Pipe": fill = stroke if obj.Base and obj.Diameter: svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, edges=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 utils.get_type(obj) == "Rebar": fill = "none" basewire = obj.Base.Shape.Wires[0].copy() # Not applying rounding because the results are not correct # if hasattr(obj, "Rounding") and obj.Rounding: # basewire = DraftGeomUtils.filletWire( # basewire, obj.Rounding * obj.Diameter.Value # ) wires = [] for placement in obj.PlacementList: wire = basewire.copy() wire.Placement = placement.multiply(basewire.Placement) wires.append(wire) svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=None, wires=wires) elif utils.get_type(obj) == "PipeConnector": pass elif utils.get_type(obj) == "Space": fill_opacity = 1 # returns an SVG fragment for the text of a space if FreeCAD.GuiUp: 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 FreeCAD.GuiUp: fill = utils.get_rgb(obj.ViewObject.ShapeColor, testbw=False) fill_opacity = 1 - (obj.ViewObject.Transparency / 100.0) else: fill = "#888888" svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=fill_opacity, wires=[obj.Proxy.face.OuterWire]) c = utils.get_rgb(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,get_proj(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,get_proj(p1, plane).add(ofs),t2,linespacing,j,flip=True) svg += t4 elif obj.isDerivedFrom('Part::Feature'): if obj.Shape.isNull(): return '' fill_opacity = 1 # setting fill if obj.Shape.Faces: if FreeCAD.GuiUp: try: m = obj.ViewObject.DisplayMode except AttributeError: m = None if (m != "Wireframe"): if fillstyle == "shape color": fill = utils.get_rgb(obj.ViewObject.ShapeColor, testbw=False) fill_opacity = 1 - (obj.ViewObject.Transparency / 100.0) else: fill = 'url(#'+fillstyle+')' svg += get_pattern(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 += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=fill_opacity, wires=f.Wires, pathname='%s_f%04d' % (obj.Name, i)) wiredEdges.extend(f.Edges) else: for i,w in enumerate(obj.Shape.Wires): svg += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=fill_opacity, edges=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 += get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=fill_opacity, edges=[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 = get_path(obj, plane, fill, pathdata, stroke, linewidth, lstyle, fill_opacity=fill_opacity, edges=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 = get_proj(obj.Shape.Vertexes[-1].Point, plane) p2 = get_proj(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 = '\n '+svg+'\n' return svg ## @}