-
Notifications
You must be signed in to change notification settings - Fork 335
exportSVG produces svgs with incorrect size. #1317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,7 +3,6 @@ | |||||
from ..shapes import Shape, Compound, TOLERANCE | ||||||
from ..geom import BoundBox | ||||||
|
||||||
|
||||||
from OCP.gp import gp_Ax2, gp_Pnt, gp_Dir | ||||||
from OCP.BRepLib import BRepLib | ||||||
from OCP.HLRBRep import HLRBRep_Algo, HLRBRep_HLRToShape | ||||||
|
@@ -12,26 +11,22 @@ | |||||
|
||||||
DISCRETIZATION_TOLERANCE = 1e-3 | ||||||
|
||||||
SVG_TEMPLATE = """<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||||
<svg | ||||||
xmlns:svg="http://www.w3.org/2000/svg" | ||||||
xmlns="http://www.w3.org/2000/svg" | ||||||
width="%(width)s" | ||||||
height="%(height)s" | ||||||
|
||||||
> | ||||||
<g transform="scale(%(unitScale)s, -%(unitScale)s) translate(%(xTranslate)s,%(yTranslate)s)" stroke-width="%(strokeWidth)s" fill="none"> | ||||||
<!-- hidden lines --> | ||||||
<g stroke="rgb(%(hiddenColor)s)" fill="none" stroke-dasharray="%(strokeWidth)s,%(strokeWidth)s" > | ||||||
SVG_TEMPLATE = """<?xml version='1.0' encoding='UTF-8' standalone='no'?> | ||||||
<svg baseProfile="tiny" version="1.2" width="%(width)s" height="%(height)s" xmlns:cc="http://creativecommons.org/ns#" xmlns="http://www.w3.org/2000/svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:svg="http://www.w3.org/2000/svg"> | ||||||
<title>CadQuery SVG Export</title> | ||||||
<desc>Page exported from CadQuery document</desc> | ||||||
<defs/> | ||||||
<g id="DrawingContent" stroke-linejoin="bevel" stroke-linecap="square" stroke="black" fill="none" fill-rule="evenodd" stroke-width="%(strokeWidth)s"> | ||||||
<!-- hidden lines --> | ||||||
<g stroke="rgb(%(hiddenColor)s)" fill="none" stroke-dasharray="%(strokeWidth)s,%(strokeWidth)s" > | ||||||
%(hiddenContent)s | ||||||
</g> | ||||||
|
||||||
<!-- solid lines --> | ||||||
<g stroke="rgb(%(strokeColor)s)" fill="none"> | ||||||
</g> | ||||||
<!-- solid lines --> | ||||||
<g stroke="rgb(%(strokeColor)s)" fill="none"> | ||||||
%(visibleContent)s | ||||||
</g> | ||||||
</g> | ||||||
%(axesIndicator)s | ||||||
</g> | ||||||
</g> | ||||||
%(axesIndicator)s | ||||||
</svg> | ||||||
""" | ||||||
|
||||||
|
@@ -51,36 +46,14 @@ | |||||
--> | ||||||
</g>""" | ||||||
|
||||||
PATHTEMPLATE = '\t\t\t<path d="%s" />\n' | ||||||
PATHTEMPLATE = ' <g stroke-opacity="1" stroke-linejoin="bevel" font-style="normal" stroke-linecap="square" font-family="MS Shell Dlg 2" stroke="rgb(%(strokeColor)s)" fill="none" font-weight="400" transform="scale(%(unitScale)s, %(unitScale)s) translate(%(xTranslate)s,%(yTranslate)s)" stroke-width="%(strokeWidth)s" font-size="55.5625">\n <path vector-effect="none" d="%(path)s" fill-rule="evenodd"/>\n </g>\n' | ||||||
|
||||||
|
||||||
class UNITS: | ||||||
MM = "mm" | ||||||
IN = "in" | ||||||
|
||||||
|
||||||
def guessUnitOfMeasure(shape): | ||||||
""" | ||||||
Guess the unit of measure of a shape. | ||||||
""" | ||||||
bb = BoundBox._fromTopoDS(shape.wrapped) | ||||||
|
||||||
dimList = [bb.xlen, bb.ylen, bb.zlen] | ||||||
# no real part would likely be bigger than 10 inches on any side | ||||||
if max(dimList) > 10: | ||||||
return UNITS.MM | ||||||
|
||||||
# no real part would likely be smaller than 0.1 mm on all dimensions | ||||||
if min(dimList) < 0.1: | ||||||
return UNITS.IN | ||||||
|
||||||
# no real part would have the sum of its dimensions less than about 5mm | ||||||
if sum(dimList) < 10: | ||||||
return UNITS.IN | ||||||
|
||||||
return UNITS.MM | ||||||
|
||||||
|
||||||
def makeSVGedge(e): | ||||||
""" | ||||||
Creates an SVG edge from a OCCT edge. | ||||||
|
@@ -125,7 +98,7 @@ def getPaths(visibleShapes, hiddenShapes): | |||||
return (hiddenPaths, visiblePaths) | ||||||
|
||||||
|
||||||
def getSVG(shape, opts=None): | ||||||
def getSVG(shapes, opts=None): | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since accidentally having the wrong unit could lead to unnecessary end user headache, I would consider moving unitOfMeasure all the way as a separate kwarg to make it more obvious to the user.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. shape -> shapes is a breaking change (and in fact breaks 18 tests). Backwards compatibility could be maintained by allowing both single and iterable inputs |
||||||
""" | ||||||
Export a shape to SVG text. | ||||||
|
||||||
|
@@ -150,9 +123,10 @@ def getSVG(shape, opts=None): | |||||
|
||||||
# Available options and their defaults | ||||||
d = { | ||||||
"unitOfMeasure": UNITS.MM, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Also, document in the docstring If the suggestion is not implemented, unitOfMeasure needs to be still added to the opts documentation. |
||||||
"width": 800, | ||||||
"height": 240, | ||||||
"marginLeft": 200, | ||||||
"height": 500, | ||||||
"marginLeft": 20, | ||||||
"marginTop": 20, | ||||||
"projectionDir": (-1.75, 1.1, 5), | ||||||
"showAxes": True, | ||||||
|
@@ -166,9 +140,7 @@ def getSVG(shape, opts=None): | |||||
if opts: | ||||||
d.update(opts) | ||||||
|
||||||
# need to guess the scale and the coordinate center | ||||||
uom = guessUnitOfMeasure(shape) | ||||||
|
||||||
uom = d["unitOfMeasure"] | ||||||
width = float(d["width"]) | ||||||
height = float(d["height"]) | ||||||
marginLeft = float(d["marginLeft"]) | ||||||
|
@@ -182,7 +154,9 @@ def getSVG(shape, opts=None): | |||||
focus = float(d["focus"]) if d.get("focus") else None | ||||||
|
||||||
hlr = HLRBRep_Algo() | ||||||
hlr.Add(shape.wrapped) | ||||||
|
||||||
for shape in shapes: | ||||||
hlr.Add(shape.wrapped) | ||||||
|
||||||
coordinate_system = gp_Ax2(gp_Pnt(), gp_Dir(*projectionDir)) | ||||||
|
||||||
|
@@ -236,12 +210,15 @@ def getSVG(shape, opts=None): | |||||
bb = Compound.makeCompound(hidden + visible).BoundingBox() | ||||||
|
||||||
# width pixels for x, height pixels for y | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. comment not relevant anymore |
||||||
unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75) | ||||||
if uom == UNITS.MM: | ||||||
unitScale = 3.7795 | ||||||
else: | ||||||
unitScale = 91 | ||||||
|
||||||
# compute amount to translate-- move the top left into view | ||||||
(xTranslate, yTranslate) = ( | ||||||
(0 - bb.xmin) + marginLeft / unitScale, | ||||||
(0 - bb.ymax) - marginTop / unitScale, | ||||||
(0 + bb.ymax) + marginTop / unitScale, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't be necessary if the Y axis is flipped. (ln 49) |
||||||
) | ||||||
|
||||||
# If the user did not specify a stroke width, calculate it based on the unit scale | ||||||
|
@@ -254,11 +231,29 @@ def getSVG(shape, opts=None): | |||||
# Prevent hidden paths from being added if the user disabled them | ||||||
if showHidden: | ||||||
for p in hiddenPaths: | ||||||
hiddenContent += PATHTEMPLATE % p | ||||||
hiddenContent += PATHTEMPLATE % ( | ||||||
{ | ||||||
"unitScale": str(unitScale), | ||||||
"xTranslate": str(xTranslate), | ||||||
"yTranslate": str(yTranslate), | ||||||
"strokeWidth": str(strokeWidth), | ||||||
"strokeColor": ",".join([str(x) for x in strokeColor]), | ||||||
"path": str(p), | ||||||
} | ||||||
) | ||||||
|
||||||
visibleContent = "" | ||||||
for p in visiblePaths: | ||||||
visibleContent += PATHTEMPLATE % p | ||||||
visibleContent += PATHTEMPLATE % ( | ||||||
{ | ||||||
"unitScale": str(unitScale), | ||||||
"xTranslate": str(xTranslate), | ||||||
"yTranslate": str(yTranslate), | ||||||
"strokeWidth": str(strokeWidth), | ||||||
"strokeColor": ",".join([str(x) for x in strokeColor]), | ||||||
"path": str(p), | ||||||
} | ||||||
) | ||||||
|
||||||
# If the caller wants the axes indicator and is using the default direction, add in the indicator | ||||||
if showAxes and projectionDir == (-1.75, 1.1, 5): | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Y axis should (must?) be flipped here (like in the old implementation). CQ/OpenCascade has Y axis moving upwards whereas SVG has Y axis moving downwards. Without flipping it the resulting SVG will be a horizontal mirror of the actual projection.