TextAlignment is the vertical alignment, while Justification is the
horizontal alignment. here, get_text was passed the vertical, while it
expected the horizontal. This caused the alignment of a Label to be
wrong in the resulting SVG (e.g. in a TechDraw draft view).
This seems to have been broken since SVG support for Labels was first
introduced in commit 3391a5ea4b (Initial work, only text (no lines)).
933 lines
37 KiB
Python
933 lines
37 KiB
Python
# -*- 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
|
|
import draftutils.utils as utils
|
|
import draftfunctions.svgtext as svgtext
|
|
|
|
from draftfunctions.svgshapes import get_proj, get_circle, get_path
|
|
from draftutils.utils import param
|
|
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 = 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 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 = ""
|
|
|
|
if not App.GuiUp or not obj.ViewObject:
|
|
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 obj.ViewObject.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 obj.ViewObject.ArrowType == "Dot":
|
|
svg += '<circle '
|
|
svg += _cx_cy_r + ' '
|
|
svg += 'fill="{}" stroke="{}" '.format(color, "none")
|
|
svg += _style + ' '
|
|
svg += 'freecad:skip="1"'
|
|
svg += '/>\n'
|
|
elif obj.ViewObject.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 obj.ViewObject.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 obj.ViewObject.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'
|
|
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, 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 ""
|
|
|
|
if not hasattr(obj.ViewObject, "Proxy") or not obj.ViewObject.Proxy:
|
|
_err("'{}': doesn't have Proxy, "
|
|
"SVG cannot be generated".format(obj.Label))
|
|
return ""
|
|
|
|
vobj = obj.ViewObject
|
|
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 == "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
|
|
|
|
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, "ArrowType"):
|
|
arrowsize = vobj.ArrowSize.Value/pointratio
|
|
if hasattr(vobj, "FlipArrows"):
|
|
if vobj.FlipArrows:
|
|
angle = angle + math.pi
|
|
|
|
svg += get_arrow(obj,
|
|
vobj.ArrowType,
|
|
p2, arrowsize, stroke, linewidth,
|
|
angle)
|
|
svg += get_arrow(obj,
|
|
vobj.ArrowType,
|
|
p3, arrowsize, stroke, linewidth,
|
|
angle + math.pi)
|
|
|
|
# drawing text
|
|
svg += svgtext.get_text(plane, techdraw,
|
|
stroke, 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.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) in ["Layer","BuildingPart"]):
|
|
svg = ""
|
|
for child in obj.Group:
|
|
svg += get_svg(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, App.Vector):
|
|
if direction != App.Vector(0, 0, 0):
|
|
plane = WorkingPlane.plane()
|
|
plane.alignToPointAndAxis_SVG(App.Vector(0, 0, 0),
|
|
direction.negative().negative(),
|
|
0)
|
|
else:
|
|
raise ValueError("'direction' cannot be: Vector(0, 0, 0)")
|
|
elif isinstance(direction, WorkingPlane.plane):
|
|
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 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)
|
|
if hasattr(obj.ViewObject, "TextColor"):
|
|
tstroke = 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)
|
|
|
|
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"]:
|
|
svg = _svg_dimension(obj, plane, scale, linewidth, fontsize,
|
|
stroke, 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 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
|
|
|
|
_v1a = prx.circle.valueAt(prx.circle.FirstParameter
|
|
+ arrowlength)
|
|
_v1b = prx.circle.valueAt(prx.circle.FirstParameter)
|
|
|
|
_v2a = prx.circle.valueAt(prx.circle.LastParameter
|
|
- arrowlength)
|
|
_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)
|
|
|
|
if hasattr(obj.ViewObject, "FlipArrows"):
|
|
if obj.ViewObject.FlipArrows:
|
|
angle1 = angle1 + math.pi
|
|
angle2 = angle2 + math.pi
|
|
|
|
svg += get_arrow(obj,
|
|
obj.ViewObject.ArrowType,
|
|
p2, arrowsize, stroke, linewidth,
|
|
angle1)
|
|
svg += get_arrow(obj,
|
|
obj.ViewObject.ArrowType,
|
|
p3, arrowsize, stroke, linewidth,
|
|
angle2)
|
|
|
|
# drawing text
|
|
if obj.ViewObject.DisplayMode == "2D":
|
|
_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,
|
|
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
|
|
# 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(obj.ViewObject, "ArrowType") and len(obj.Points) >= 2:
|
|
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,
|
|
obj.ViewObject.ArrowType,
|
|
proj_points[-1],
|
|
obj.ViewObject.ArrowSize.Value/pointratio,
|
|
stroke, linewidth, angle)
|
|
|
|
if not App.GuiUp:
|
|
_wrn("Export of texts to SVG is only available in GUI mode")
|
|
|
|
# print text
|
|
if App.GuiUp:
|
|
fontname = obj.ViewObject.TextFont
|
|
position = get_proj(obj.Placement.Base, plane)
|
|
rotation = obj.Placement.Rotation
|
|
justification = obj.ViewObject.Justification
|
|
text = obj.Text
|
|
svg += svgtext.get_text(plane, techdraw,
|
|
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 not App.GuiUp:
|
|
_wrn("Export of texts to SVG is only available in GUI mode")
|
|
|
|
if App.GuiUp:
|
|
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 += 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:
|
|
vobj = obj.ViewObject
|
|
fn = obj.ViewObject.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 obj.ViewObject.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 obj.ViewObject.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:
|
|
vobj = obj.ViewObject
|
|
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 = obj.ViewObject.DisplayMode
|
|
except AttributeError:
|
|
m = None
|
|
|
|
vobj = obj.ViewObject
|
|
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):
|
|
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 = 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(obj.ViewObject, "EndArrow")
|
|
and obj.ViewObject.EndArrow
|
|
and hasattr(obj.ViewObject, "ArrowType")
|
|
and len(obj.Shape.Vertexes) > 1):
|
|
p1 = get_proj(obj.Shape.Vertexes[-1].Point, plane)
|
|
p2 = get_proj(obj.Shape.Vertexes[-2].Point, plane)
|
|
angle = -DraftVecUtils.angle(p2 - p1)
|
|
|
|
arrowsize = obj.ViewObject.ArrowSize.Value/pointratio
|
|
svg += get_arrow(obj,
|
|
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 get_print_color(obj):
|
|
"""returns the print color of the parent layer, if available"""
|
|
for parent in obj.InListRecursive:
|
|
if (hasattr(parent,"ViewObject")
|
|
and hasattr(parent.ViewObject,"UsePrintColor")
|
|
and parent.ViewObject.UsePrintColor):
|
|
if hasattr(parent.ViewObject,"LinePrintColor"):
|
|
return parent.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)
|
|
|
|
## @}
|