Download src/Mod/Draft/draftfunctions/svg.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 42.5 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftfunctions/svg.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/Draft/draftfunctions/svg.py
-
curl -L -o svg.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/Draft/draftfunctions/svg.py
42.5 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # -*- coding: utf8 -*- | |
| # *************************************************************************** | |
| # * Copyright (c) 2009 Yorik van Havre <yorik@uncreated.net> * | |
| # * Copyright (c) 2018 George Shuklin (amarao) * | |
| # * Copyright (c) 2020 Eliud Cabrera Castillo <e.cabrera-castillo@tum.de> * | |
| # * * | |
| # * 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.""" | |
| ## @package svg | |
| # \ingroup draftfunctions | |
| # \brief Provides functions to return the SVG representation of shapes. | |
| import math | |
| import lazy_loader.lazy_loader as lz | |
| import FreeCAD as App | |
| import DraftVecUtils | |
| import WorkingPlane | |
| from draftfunctions import svgtext | |
| from draftfunctions.svgshapes import get_proj, get_circle, get_path | |
| from draftobjects import layer | |
| from draftutils import params | |
| from draftutils import utils | |
| from draftutils.messages import _wrn, _err | |
| # Delay import of module until first use because it is heavy | |
| Part = lz.LazyLoader("Part", globals(), "Part") | |
| DraftGeomUtils = lz.LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils") | |
| ## \addtogroup draftfunctions | |
| # @{ | |
| def get_line_style(line_style, scale): | |
| """Return a linestyle scaled by a factor.""" | |
| style = None | |
| if line_style == "Dashed": | |
| style = params.get_param("svgDashedLine") | |
| elif line_style == "Dashdot": | |
| style = params.get_param("svgDashdotLine") | |
| elif line_style == "Dotted": | |
| style = params.get_param("svgDottedLine") | |
| 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 Exception: | |
| # TODO: trap only specific exception; what is the problem? | |
| # Bad string specification? | |
| 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_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_arrow(obj, arrowtype, point, arrowsize, color, linewidth, angle=0): | |
| """Get the SVG representation from an arrow.""" | |
| svg = "" | |
| vobj = _get_view_object(obj) | |
| if not App.GuiUp or vobj is None: | |
| return svg | |
| _cx_cy_r = 'cx="{}" cy="{}" r="{}"'.format(point.x, point.y, arrowsize) | |
| _rotate = "rotate({},{},{})".format(math.degrees(angle), point.x, point.y) | |
| _transl = "translate({},{})".format(point.x, point.y) | |
| _scale = "scale({size},{size})".format(size=arrowsize) | |
| _style = 'style="stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:square"' | |
| if arrowtype == "Circle": | |
| svg += "<circle " | |
| svg += _cx_cy_r + " " | |
| svg += 'fill="{}" stroke="{}" '.format("none", color) | |
| svg += 'style="stroke-width:{};'.format(linewidth) | |
| svg += 'stroke-miterlimit:4;stroke-dasharray:none;stroke-linecap:square" ' | |
| svg += 'freecad:skip="1"' | |
| svg += "/>\n" | |
| elif arrowtype == "Dot": | |
| svg += "<circle " | |
| svg += _cx_cy_r + " " | |
| svg += 'fill="{}" stroke="{}" '.format(color, "none") | |
| svg += _style + " " | |
| svg += 'freecad:skip="1"' | |
| svg += "/>\n" | |
| elif arrowtype == "Arrow": | |
| svg += "<path " | |
| svg += 'transform="' | |
| svg += _rotate + " " | |
| svg += _transl + " " | |
| svg += _scale + '" ' | |
| svg += 'freecad:skip="1" ' | |
| svg += 'fill="{}" stroke="{}" '.format(color, "none") | |
| svg += _style + " " | |
| svg += 'd="M 0 0 L 4 1 L 4 -1 Z"' | |
| svg += "/>\n" | |
| elif arrowtype == "Tick": | |
| svg += "<path " | |
| svg += 'transform="' | |
| svg += _rotate + " " | |
| svg += _transl + " " | |
| svg += _scale + '" ' | |
| svg += 'freecad:skip="1" ' | |
| svg += 'fill="{}" stroke="{}" '.format(color, "none") | |
| svg += _style + " " | |
| svg += 'd="M -1 -2 L 0 2 L 1 2 L 0 -2 Z"' | |
| svg += "/>\n" | |
| elif arrowtype == "Tick-2": | |
| svg += "<line " | |
| svg += 'transform="' | |
| svg += "rotate({},{},{}) ".format(math.degrees(angle) + 45, point.x, point.y) | |
| svg += _transl + '" ' | |
| svg += 'freecad:skip="1" ' | |
| svg += 'fill="{}" stroke="{}" '.format("none", color) | |
| svg += 'style="stroke-dasharray:none;stroke-linecap:square;' | |
| svg += 'stroke-width:{}" '.format(linewidth) | |
| svg += 'x1="-{}" y1="0" '.format(2 * arrowsize) | |
| svg += 'x2="{}" y2="0"'.format(2 * arrowsize) | |
| svg += "/>\n" | |
| elif arrowtype == "None": | |
| svg += "" | |
| else: | |
| _wrn("getSVG: arrow type not implemented") | |
| return svg | |
| def getArrow(obj, arrowtype, point, arrowsize, color, linewidth, angle=0): | |
| """Get the SVG representation from an arrow. DEPRECATED.""" | |
| utils.use_instead("get_arrow") | |
| return get_arrow(obj, arrowtype, point, arrowsize, color, linewidth, angle) | |
| def get_overshoot(point, shootsize, color, linewidth, angle=0): | |
| """Get the SVG representation of a dimension line overshoot.""" | |
| svg = "<line " | |
| svg += 'transform="' | |
| svg += "rotate({},{},{}) ".format(math.degrees(angle), point.x, point.y) | |
| svg += 'translate({},{})" '.format(point.x, point.y) | |
| svg += 'freecad:skip="1" ' | |
| svg += 'fill="{}" stroke="{}" '.format("none", color) | |
| svg += 'style="stroke-dasharray:none;stroke-linecap:square;' | |
| svg += 'stroke-width:{}" '.format(linewidth) | |
| svg += 'x1="0" y1="0" ' | |
| svg += 'x2="{}" y2="0"'.format(-1 * shootsize) | |
| svg += "/>\n" | |
| return svg | |
| def getOvershoot(point, shootsize, color, linewidth, angle=0): | |
| """Get the SVG representation of a dimension line overshoot. DEPRECATED.""" | |
| utils.use_instead("get_overshoot") | |
| return get_overshoot(point, shootsize, color, linewidth, angle) | |
| def format_point(coords, action="L"): | |
| """Return a string with a formatted point.""" | |
| return "{action}{x},{y}".format(x=coords.x, y=coords.y, action=action) | |
| def _svg_shape(svg, obj, plane, fillstyle, pathdata, stroke, linewidth, lstyle): | |
| """Return the SVG representation of a Part.Shape.""" | |
| if "#" in fillstyle: | |
| fill = fillstyle | |
| elif fillstyle == "shape color": | |
| fill = "#888888" | |
| elif fillstyle in ("none", None): | |
| fill = "none" | |
| else: | |
| fill = "url(#" + fillstyle + ")" | |
| svg += get_path( | |
| obj, | |
| plane, | |
| fill, | |
| pathdata, | |
| stroke, | |
| linewidth, | |
| lstyle, | |
| fill_opacity=None, | |
| edges=obj.Edges, | |
| pathname="", | |
| ) | |
| return svg | |
| def _svg_dimension( | |
| obj, plane, scale, linewidth, fontsize, stroke, tstroke, pointratio, techdraw, rotation | |
| ): | |
| """Return the SVG representation of a linear dimension.""" | |
| if not App.GuiUp: | |
| _wrn("'{}': SVG can only be generated " "in GUI mode".format(obj.Label)) | |
| return "" | |
| vobj = _get_view_object(obj) | |
| if not hasattr(vobj, "Proxy") or not vobj.Proxy: | |
| _err("'{}': doesn't have Proxy, " "SVG cannot be generated".format(obj.Label)) | |
| return "" | |
| prx = vobj.Proxy | |
| if not hasattr(prx, "p1"): | |
| _err("'{}': doesn't have points, " "SVG cannot be generated".format(obj.Label)) | |
| return "" | |
| ts = len(prx.string) * vobj.FontSize.Value / 4.0 | |
| rm = (prx.p3 - prx.p2).Length / 2.0 - ts | |
| _diff32 = prx.p3 - prx.p2 | |
| _diff23 = prx.p2 - prx.p3 | |
| _v32 = DraftVecUtils.scaleTo(_diff32, rm) | |
| _v23 = DraftVecUtils.scaleTo(_diff23, rm) | |
| p2a = get_proj(prx.p2 + _v32, plane) | |
| p2b = get_proj(prx.p3 + _v23, 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 = App.Rotation(r[0], r[1], r[2], r[3]) | |
| rv = _rv.multVec(App.Vector(1, 0, 0)) | |
| angle = -DraftVecUtils.angle(get_proj(rv, plane)) | |
| # angle = -DraftVecUtils.angle(p3.sub(p2)) | |
| svg = "" | |
| nolines = False | |
| if hasattr(vobj, "ShowLine"): | |
| if not vobj.ShowLine: | |
| nolines = True | |
| # drawing lines | |
| if not nolines: | |
| svg += "<path " | |
| if vobj.DisplayMode == "World": | |
| 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 | |
| if rotation != 0: | |
| # print("dim: tangle:", tangle, | |
| # " rot: ", rotation, | |
| # " text: ", prx.string) | |
| if abs(tangle + math.radians(rotation)) < 0.0001: | |
| tangle += math.pi | |
| _v = App.Vector(0, 2.0 / scale, 0) | |
| _rot = DraftVecUtils.rotate(_v, tangle) | |
| tbase = tbase + _rot | |
| 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 + App.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;stroke-linecap:square" ' | |
| 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(vobj, "DimOvershoot") and vobj.DimOvershoot.Value: | |
| shootsize = vobj.DimOvershoot.Value / pointratio | |
| svg += get_overshoot(p2, shootsize, stroke, linewidth, angle) | |
| svg += get_overshoot(p3, shootsize, stroke, linewidth, angle + math.pi) | |
| if hasattr(vobj, "ExtOvershoot") and vobj.ExtOvershoot.Value: | |
| shootsize = vobj.ExtOvershoot.Value / pointratio | |
| shootangle = -DraftVecUtils.angle(p1 - p2) | |
| svg += get_overshoot(p2, shootsize, stroke, linewidth, shootangle) | |
| svg += get_overshoot(p3, shootsize, stroke, linewidth, shootangle) | |
| # drawing arrows | |
| if ( | |
| hasattr(vobj, "ArrowTypeStart") | |
| and hasattr(vobj, "ArrowTypeEnd") | |
| and hasattr(vobj, "ArrowSizeStart") | |
| and hasattr(vobj, "ArrowSizeEnd") | |
| ): | |
| if getattr(vobj, "FlipArrows", False): | |
| angle = angle + math.pi | |
| if getattr(vobj, "FlipText", False): | |
| angle = angle + math.pi | |
| svg += get_arrow( | |
| obj, | |
| vobj.ArrowTypeStart, | |
| p2, | |
| vobj.ArrowSizeStart.Value / pointratio, | |
| stroke, | |
| linewidth, | |
| angle, | |
| ) | |
| svg += get_arrow( | |
| obj, | |
| vobj.ArrowTypeEnd, | |
| p3, | |
| vobj.ArrowSizeEnd.Value / pointratio, | |
| stroke, | |
| linewidth, | |
| angle + math.pi, | |
| ) | |
| # drawing text | |
| svg += svgtext.get_text( | |
| plane, techdraw, tstroke, fontsize, vobj.FontName, tangle, tbase, prx.string | |
| ) | |
| return svg | |
| def get_svg( | |
| 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.PlaneBase` | |
| 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 ( | |
| obj.isDerivedFrom("App::DocumentObjectGroup") | |
| or utils.get_type(obj) in ["Layer", "BuildingPart", "IfcGroup"] | |
| or obj.isDerivedFrom("App::LinkGroup") | |
| or ( | |
| obj.isDerivedFrom("App::Link") | |
| and obj.LinkedObject.isDerivedFrom("App::DocumentObjectGroup") | |
| ) | |
| ): | |
| hidden_doc = None | |
| if obj.isDerivedFrom("App::LinkGroup") or ( | |
| obj.isDerivedFrom("App::Link") | |
| and obj.LinkedObject.isDerivedFrom("App::DocumentObjectGroup") | |
| ): | |
| if obj.Placement.isIdentity(): | |
| if obj.isDerivedFrom("App::LinkGroup"): | |
| group = obj.ElementList | |
| else: | |
| group = obj.Group | |
| else: | |
| # Hidden doc hack: | |
| hidden_doc = App.newDocument(name="hidden", hidden=True, temp=True) | |
| new = hidden_doc.copyObject(obj, True) | |
| pla = new.Placement | |
| new.Placement = App.Placement() | |
| if new.isDerivedFrom("App::LinkGroup"): | |
| group = new.ElementList | |
| else: | |
| group = new.Group | |
| for child in group: | |
| child.Placement = pla * child.Placement | |
| else: | |
| group = obj.Group | |
| svg = "" | |
| for child in group: | |
| svg += get_svg( | |
| child, | |
| scale, | |
| linewidth, | |
| fontsize, | |
| fillstyle, | |
| direction, | |
| linestyle, | |
| color, | |
| linespacing, | |
| techdraw, | |
| rotation, | |
| fillspaces, | |
| override, | |
| ) | |
| if hidden_doc is not None: | |
| try: | |
| App.closeDocument(hidden_doc.Name) | |
| except: | |
| pass | |
| return svg | |
| # Handle Links to texts and dimensions. These Links do not have a Shape. | |
| if obj.isDerivedFrom("App::Link") and obj.LinkedObject and not hasattr(obj, "Shape"): | |
| # Hidden doc hack: | |
| hidden_doc = App.newDocument(name="hidden", hidden=True, temp=True) | |
| new = hidden_doc.copyObject(obj.LinkedObject, True) | |
| if utils.get_type(new) in ("Dimension", "LinearDimension", "AngularDimension"): | |
| new.Proxy.transform(new, obj.Placement) | |
| elif utils.get_type(new) == "Text" or obj.LinkTransform: | |
| new.Placement = obj.Placement * new.Placement | |
| else: | |
| new.Placement = obj.Placement | |
| svg = get_svg( | |
| new, | |
| scale, | |
| linewidth, | |
| fontsize, | |
| fillstyle, | |
| direction, | |
| linestyle, | |
| color, | |
| linespacing, | |
| techdraw, | |
| rotation, | |
| fillspaces, | |
| override, | |
| ) | |
| try: | |
| App.closeDocument(hidden_doc.Name) | |
| except: | |
| pass | |
| return svg | |
| vobj = _get_view_object(obj) | |
| pathdata = [] | |
| svg = "" | |
| linewidth = float(linewidth) / scale | |
| if not override: | |
| if vobj is not None and hasattr(vobj, "LineWidth"): | |
| if hasattr(vobj.LineWidth, "Value"): | |
| lw = vobj.LineWidth.Value | |
| else: | |
| lw = vobj.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, App.Vector): | |
| if direction != App.Vector(0, 0, 0): | |
| plane = WorkingPlane.PlaneBase() | |
| plane.align_to_point_and_axis_svg( | |
| App.Vector(0, 0, 0), direction.negative().negative(), 0 | |
| ) | |
| else: | |
| raise ValueError("'direction' cannot be: Vector(0, 0, 0)") | |
| elif isinstance(direction, WorkingPlane.PlaneBase): | |
| plane = direction | |
| stroke = "#000000" | |
| tstroke = stroke | |
| if color and override: | |
| if "#" in color: | |
| stroke = color | |
| else: | |
| stroke = utils.get_rgb(color) | |
| tstroke = stroke | |
| elif App.GuiUp: | |
| # find print color | |
| pc = get_print_color(obj) | |
| if pc: | |
| stroke = utils.get_rgb(pc) | |
| # get line color | |
| elif vobj is not None: | |
| if hasattr(vobj, "LineColor"): | |
| stroke = utils.get_rgb(vobj.LineColor) | |
| elif hasattr(vobj, "TextColor"): | |
| stroke = utils.get_rgb(vobj.TextColor) | |
| if hasattr(vobj, "TextColor"): | |
| tstroke = utils.get_rgb(vobj.TextColor) | |
| lstyle = "none" | |
| if override: | |
| lstyle = get_line_style(linestyle, scale) | |
| else: | |
| if vobj is not None and hasattr(vobj, "DrawStyle"): | |
| lstyle = get_line_style(vobj.DrawStyle, scale) | |
| if not obj: | |
| pass | |
| elif isinstance(obj, Part.Shape): | |
| svg = _svg_shape(svg, obj, plane, fillstyle, pathdata, stroke, linewidth, lstyle) | |
| elif utils.get_type(obj) in ["Dimension", "LinearDimension"] or ( | |
| utils.get_type(obj) == "IfcAnnotation" and obj.ObjectType == "DIMENSION" | |
| ): | |
| svg = _svg_dimension( | |
| obj, plane, scale, linewidth, fontsize, stroke, tstroke, pointratio, techdraw, rotation | |
| ) | |
| elif utils.get_type(obj) == "AngularDimension": | |
| if not App.GuiUp: | |
| _wrn("Export of dimensions to SVG is only available in GUI mode") | |
| if App.GuiUp: | |
| if vobj.Proxy: | |
| if hasattr(vobj.Proxy, "circle"): | |
| prx = vobj.Proxy | |
| # drawing arc | |
| fill = "none" | |
| if vobj.DisplayMode == "World": | |
| 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(vobj, "ArrowTypeStart") | |
| and hasattr(vobj, "ArrowTypeEnd") | |
| and hasattr(vobj, "ArrowSizeStart") | |
| and hasattr(vobj, "ArrowSizeEnd") | |
| ): | |
| p2 = get_proj(prx.p2, plane) | |
| p3 = get_proj(prx.p3, plane) | |
| arrowsizestart = vobj.ArrowSizeStart.Value / pointratio | |
| halfstartarrowlength = 2 * arrowsizestart | |
| startarrowangle = 2 * math.asin( | |
| halfstartarrowlength / prx.circle.Curve.Radius | |
| ) | |
| arrowsizeend = vobj.ArrowSizeEnd.Value / pointratio | |
| halfendarrowlength = 2 * arrowsizeend | |
| endarrowangle = 2 * math.asin(halfendarrowlength / prx.circle.Curve.Radius) | |
| if hasattr(vobj, "FlipArrows") and vobj.FlipArrows: | |
| startarrowangle = -startarrowangle | |
| endarrowangle = -endarrowangle | |
| _v1a = prx.circle.valueAt(prx.circle.FirstParameter + startarrowangle) | |
| _v1b = prx.circle.valueAt(prx.circle.FirstParameter) | |
| _v2a = prx.circle.valueAt(prx.circle.LastParameter - endarrowangle) | |
| _v2b = prx.circle.valueAt(prx.circle.LastParameter) | |
| u1 = get_proj(_v1a - _v1b, plane) | |
| u2 = get_proj(_v2a - _v2b, plane) | |
| angle1 = -DraftVecUtils.angle(u1) | |
| angle2 = -DraftVecUtils.angle(u2) | |
| svg += get_arrow( | |
| obj, vobj.ArrowTypeStart, p2, arrowsizestart, stroke, linewidth, angle1 | |
| ) | |
| svg += get_arrow( | |
| obj, vobj.ArrowTypeEnd, p3, arrowsizeend, stroke, linewidth, angle2 | |
| ) | |
| # drawing text | |
| if vobj.DisplayMode == "World": | |
| _diff = prx.circle.LastParameter - prx.circle.FirstParameter | |
| t = prx.circle.tangentAt(prx.circle.FirstParameter + _diff / 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 | |
| _diff = prx.circle.LastParameter - prx.circle.FirstParameter | |
| _va = prx.circle.valueAt(prx.circle.FirstParameter + _diff / 2.0) | |
| tbase = get_proj(_va, plane) | |
| _v = App.Vector(0, 2.0 / scale, 0) | |
| tbase = tbase + DraftVecUtils.rotate(_v, tangle) | |
| # print(tbase) | |
| else: | |
| tangle = 0 | |
| tbase = get_proj(prx.tbase, plane) | |
| svg += svgtext.get_text( | |
| plane, techdraw, tstroke, fontsize, vobj.FontName, tangle, tbase, prx.string | |
| ) | |
| elif utils.get_type(obj) == "Label": | |
| if not App.GuiUp: | |
| _wrn("Export of texts to SVG is only available in GUI mode") | |
| if App.GuiUp: | |
| if getattr(vobj, "Line", True): | |
| # Some Labels may have no Line property | |
| # 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 = "<path " | |
| svg_path += 'fill="none" ' | |
| svg_path += 'stroke="{}" '.format(stroke) | |
| svg_path += 'stroke-width="{}" '.format(linewidth) | |
| svg_path += 'stroke-linecap="square" ' | |
| svg_path += 'd="{}"'.format(path_dir_str) | |
| svg_path += "/>" | |
| svg += svg_path | |
| # Draw arrow. | |
| # We are different here from 3D view | |
| # if Line is set to 'off', no arrow is drawn | |
| if hasattr(vobj, "ArrowTypeStart") and ( | |
| hasattr(vobj, "ArrowSizeStart") and len(obj.Points) > 1 | |
| ): | |
| last_segment = App.Vector(obj.Points[-1] - obj.Points[-2]) | |
| _v = get_proj(last_segment, plane) | |
| angle = -DraftVecUtils.angle(_v) + math.pi | |
| svg += get_arrow( | |
| obj, | |
| vobj.ArrowTypeStart, | |
| proj_points[-1], | |
| vobj.ArrowSizeStart.Value / pointratio, | |
| stroke, | |
| linewidth, | |
| angle, | |
| ) | |
| fontname = vobj.FontName | |
| position = get_proj(obj.Placement.Base, plane) | |
| rotation = obj.Placement.Rotation | |
| justification = vobj.Justification | |
| text = obj.Text | |
| svg += svgtext.get_text( | |
| plane, | |
| techdraw, | |
| tstroke, | |
| fontsize, | |
| fontname, | |
| rotation, | |
| position, | |
| text, | |
| linespacing, | |
| justification, | |
| ) | |
| elif utils.get_type(obj) in ["Annotation", "DraftText", "Text"] or ( | |
| utils.get_type(obj) == "IfcAnnotation" and obj.ObjectType == "TEXT" | |
| ): | |
| # returns an svg representation of a document annotation | |
| if not App.GuiUp: | |
| _wrn("Export of texts to SVG is only available in GUI mode") | |
| if App.GuiUp: | |
| n = vobj.FontName | |
| if utils.get_type(obj) == "Annotation": | |
| p = get_proj(obj.Position, plane) | |
| r = vobj.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 = vobj.Justification | |
| svg += svgtext.get_text(plane, techdraw, tstroke, fontsize, n, r, p, t, linespacing, j) | |
| elif utils.get_type(obj) == "Axis": | |
| # returns the SVG representation of an Arch Axis system | |
| if not App.GuiUp: | |
| _wrn("Export of axes to SVG is only available in GUI mode") | |
| if App.GuiUp: | |
| fn = vobj.FontName | |
| fill = "none" | |
| rad = vobj.BubbleSize.Value / 2 | |
| n = 0 | |
| for e in obj.Shape.Edges: | |
| svg += get_path( | |
| obj, | |
| plane, | |
| fill, | |
| pathdata, | |
| stroke, | |
| linewidth, | |
| lstyle, | |
| fill_opacity=None, | |
| edges=[e], | |
| ) | |
| for t in vobj.Proxy.getTextData(): | |
| pos = t[1].add(App.Vector(0, -fontsize / 2, 0)) | |
| svg += svgtext.get_text( | |
| plane, techdraw, tstroke, fontsize, fn, 0.0, pos, t[0], 1.0, "center" | |
| ) | |
| for b in vobj.Proxy.getShapeData(): | |
| if hasattr(b, "Curve") and isinstance(b.Curve, Part.Circle): | |
| svg += get_circle(plane, fill, stroke, linewidth, "none", b) | |
| else: | |
| sfill = stroke | |
| svg += get_path( | |
| obj, | |
| plane, | |
| sfill, | |
| pathdata, | |
| stroke, | |
| linewidth, | |
| "none", | |
| fill_opacity=None, | |
| edges=b.Edges, | |
| ) | |
| 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 += get_circle(plane, fill, stroke, linewidth, lstyle, 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 not App.GuiUp: | |
| _wrn("Export of spaces to SVG is only available in GUI mode") | |
| if App.GuiUp: | |
| if fillspaces and hasattr(obj, "Proxy"): | |
| if not hasattr(obj.Proxy, "face"): | |
| obj.Proxy.getArea(obj, notouch=True) | |
| if hasattr(obj.Proxy, "face"): | |
| # setting fill | |
| if App.GuiUp and hasattr(vobj, "ShapeColor"): | |
| fill = utils.get_rgb(vobj.ShapeColor, testbw=False) | |
| fill_opacity = 1 - vobj.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(vobj.TextColor) | |
| n = vobj.FontName | |
| a = 0 | |
| if rotation != 0: | |
| a = math.radians(rotation) | |
| t1 = vobj.Proxy.text1.string.getValues() | |
| t2 = vobj.Proxy.text2.string.getValues() | |
| scale = vobj.FirstLine.Value / vobj.FontSize.Value | |
| f1 = fontsize * scale | |
| if round(plane.axis.getAngle(App.Vector(0, 0, 1)), 2) not in [0, 3.14]: | |
| # if not in XY view, place the label at center | |
| p2 = obj.Shape.CenterOfMass | |
| else: | |
| _v = vobj.Proxy.coords.translation.getValue().getValue() | |
| p2 = obj.Placement.multVec(App.Vector(_v)) | |
| _h = vobj.Proxy.header.translation.getValue().getValue() | |
| lspc = App.Vector(_h) | |
| p1 = p2 + lspc | |
| j = vobj.TextAlign | |
| t3 = svgtext.get_text( | |
| plane, techdraw, c, f1, n, a, get_proj(p1, plane), t1, linespacing, j, flip=True | |
| ) | |
| svg += t3 | |
| if t2: | |
| ofs = App.Vector(0, -lspc.Length, 0) | |
| if a: | |
| Z = App.Vector(0, 0, 1) | |
| ofs = App.Rotation(Z, -rotation).multVec(ofs) | |
| t4 = svgtext.get_text( | |
| plane, | |
| techdraw, | |
| c, | |
| fontsize, | |
| n, | |
| a, | |
| get_proj(p1, plane).add(ofs), | |
| t2, | |
| linespacing, | |
| j, | |
| flip=True, | |
| ) | |
| svg += t4 | |
| elif hasattr(obj, "Shape"): | |
| # In the past we tested for a Part Feature | |
| # elif obj.isDerivedFrom('Part::Feature'): | |
| # | |
| # however, this doesn't work for App::Links; instead we | |
| # test for a 'Shape'. All Part::Features should have a Shape, | |
| # and App::Links can have one as well. | |
| if obj.Shape.isNull(): | |
| return "" | |
| fill_opacity = 1 | |
| # setting fill | |
| if obj.Shape.Faces: | |
| if App.GuiUp: | |
| try: | |
| m = vobj.DisplayMode | |
| except AttributeError: | |
| m = None | |
| if m != "Wireframe": | |
| if (fillstyle == "shape color") and hasattr(vobj, "ShapeColor"): | |
| fill = utils.get_rgb(vobj.ShapeColor, testbw=False) | |
| fill_opacity = 1 - vobj.Transparency / 100.0 | |
| elif fillstyle in ("none", None): | |
| fill = "none" | |
| 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): | |
| fill = "none" # Required if obj has a face. Edges processed here have no face. | |
| def get_edge_descriptor(edge): | |
| return ( | |
| str(edge.Curve), | |
| str(edge.Vertexes[0].Point), | |
| str(edge.Vertexes[-1].Point), | |
| ) | |
| wiredEdgesSet = set([get_edge_descriptor(e) for e in wiredEdges]) | |
| for i, e in enumerate(obj.Shape.Edges): | |
| if get_edge_descriptor(e) not in wiredEdgesSet: | |
| 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 = get_circle(plane, fill, stroke, linewidth, lstyle, obj.Shape.Edges[0]) | |
| else: | |
| svg = get_path( | |
| obj, | |
| plane, | |
| fill, | |
| pathdata, | |
| stroke, | |
| linewidth, | |
| lstyle, | |
| fill_opacity=fill_opacity, | |
| edges=obj.Shape.Edges, | |
| ) | |
| if ( | |
| App.GuiUp | |
| and hasattr(vobj, "ArrowTypeStart") | |
| and hasattr(vobj, "ArrowTypeEnd") | |
| and hasattr(vobj, "ArrowSizeStart") | |
| and hasattr(vobj, "ArrowSizeEnd") | |
| and len(obj.Shape.Vertexes) > 1 | |
| ): | |
| # Draft_Wire | |
| p1 = get_proj(obj.Shape.Vertexes[0].Point, plane) | |
| p2 = get_proj(obj.Shape.Vertexes[1].Point, plane) | |
| svg += get_arrow( | |
| obj, | |
| vobj.ArrowTypeStart, | |
| p1, | |
| vobj.ArrowSizeStart.Value / pointratio, | |
| stroke, | |
| linewidth, | |
| -DraftVecUtils.angle(p2 - p1), | |
| ) | |
| p1 = get_proj(obj.Shape.Vertexes[-1].Point, plane) | |
| p2 = get_proj(obj.Shape.Vertexes[-2].Point, plane) | |
| svg += get_arrow( | |
| obj, | |
| vobj.ArrowTypeEnd, | |
| p1, | |
| vobj.ArrowSizeEnd.Value / pointratio, | |
| stroke, | |
| linewidth, | |
| -DraftVecUtils.angle(p2 - p1), | |
| ) | |
| # techdraw expects bottom-to-top coordinates | |
| if techdraw: | |
| svg = '<g transform ="scale(1,-1)">\n ' + svg + "</g>\n" | |
| return svg | |
| def _get_view_object(obj): | |
| if ( | |
| obj.isDerivedFrom("App::Link") | |
| and hasattr(obj, "ViewObject") | |
| and hasattr(obj.ViewObject, "OverrideMaterial") | |
| and not obj.ViewObject.OverrideMaterial | |
| and hasattr(obj.LinkedObject, "ViewObject") | |
| ): | |
| return obj.LinkedObject.ViewObject | |
| if hasattr(obj, "ViewObject"): | |
| return obj.ViewObject | |
| return None | |
| def get_print_color(obj): | |
| """Return the print color of the parent layer, if available.""" | |
| # Layers are not in the Inlist of obj because a layer's Group is App::PropertyLinkListHidden: | |
| lyr = layer.get_layer(obj) | |
| if lyr is None: | |
| return None | |
| if lyr.ViewObject.UsePrintColor: | |
| return lyr.ViewObject.LinePrintColor | |
| return None | |
| 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 SVG string of the object. DEPRECATED. Use 'get_svg'.""" | |
| utils.use_instead("get_svg") | |
| return get_svg( | |
| obj, | |
| scale=scale, | |
| linewidth=linewidth, | |
| fontsize=fontsize, | |
| fillstyle=fillstyle, | |
| direction=direction, | |
| linestyle=linestyle, | |
| color=color, | |
| linespacing=linespacing, | |
| techdraw=techdraw, | |
| rotation=rotation, | |
| fillspaces=fillSpaces, | |
| override=override, | |
| ) | |
| ## @} | |