From 176ff708526a566ff233d0ec2f4ae2e72c984afe Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Wed, 14 Jun 2023 21:29:37 -0400 Subject: [PATCH 1/7] Added fitView option to SVG export --- cadquery/occ_impl/exporters/svg.py | 10 +++++++++- doc/importexport.rst | 1 + tests/test_exporters.py | 1 + 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index d2b9c421c..5bf0acacb 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -180,6 +180,7 @@ def getSVG(shape, opts=None): hiddenColor = tuple(d["hiddenColor"]) showHidden = bool(d["showHidden"]) focus = float(d["focus"]) if d.get("focus") else None + fitView = bool(d["fitView"]) if d.get("fitView") else None hlr = HLRBRep_Algo() hlr.Add(shape.wrapped) @@ -235,8 +236,15 @@ def getSVG(shape, opts=None): # get bounding box -- these are all in 2D space bb = Compound.makeCompound(hidden + visible).BoundingBox() + # Determine whether the user wants to fit the drawing to the bounding box + bb_scale = 0.75 + if fitView: + bb_scale = 1.0 + width = bb.xlen + height = bb.ylen + # width pixels for x, height pixels for y - unitScale = min(width / bb.xlen * 0.75, height / bb.ylen * 0.75) + unitScale = min(width / bb.xlen * bb_scale, height / bb.ylen * bb_scale) # compute amount to translate-- move the top left into view (xTranslate, yTranslate) = ( diff --git a/doc/importexport.rst b/doc/importexport.rst index 3bbb9b479..a6f22de2a 100644 --- a/doc/importexport.rst +++ b/doc/importexport.rst @@ -216,6 +216,7 @@ options are as follows. * *hiddenColor* - Color of the line that hidden edges are drawn with. * *showHidden* - Whether or not to show hidden lines. * *focus* - If specified, creates a perspective SVG with the projector at the distance specified. +* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options should be set to 0 or the object will fall outside the viewport. The ``width`` and ``height`` options that were specified will be overridden, which can cause unexpected final image sizes. The options are passed to the exporter in a dictionary, and can be left out to force the SVG to be created with default options. Below are examples with and without options set. diff --git a/tests/test_exporters.py b/tests/test_exporters.py index 1be1fc3d1..c297a207e 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -562,6 +562,7 @@ def testSVGOptions(self): "hiddenColor": (0, 0, 255), "showHidden": True, "focus": 4, + "fitView": True, }, ) From 65de218e3c44c0e3f20ebf8a49c6a770caf29de8 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Wed, 14 Jun 2023 22:23:13 -0400 Subject: [PATCH 2/7] Trying to be smarter about using the desired image size --- cadquery/occ_impl/exporters/svg.py | 8 ++++++-- doc/importexport.rst | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index 5bf0acacb..acc320fb9 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -240,8 +240,12 @@ def getSVG(shape, opts=None): bb_scale = 0.75 if fitView: bb_scale = 1.0 - width = bb.xlen - height = bb.ylen + + # Figure out which dimension to base the adjusted image size on + if width / bb.xlen < height / bb.ylen: + height = width * (bb.ylen / bb.xlen) + else: + width = height * (bb.xlen / bb.ylen) # width pixels for x, height pixels for y unitScale = min(width / bb.xlen * bb_scale, height / bb.ylen * bb_scale) diff --git a/doc/importexport.rst b/doc/importexport.rst index a6f22de2a..e92728d24 100644 --- a/doc/importexport.rst +++ b/doc/importexport.rst @@ -216,7 +216,7 @@ options are as follows. * *hiddenColor* - Color of the line that hidden edges are drawn with. * *showHidden* - Whether or not to show hidden lines. * *focus* - If specified, creates a perspective SVG with the projector at the distance specified. -* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options should be set to 0 or the object will fall outside the viewport. The ``width`` and ``height`` options that were specified will be overridden, which can cause unexpected final image sizes. +* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options should be set to 0 or the object will fall outside the viewport. The ``width`` or ``height`` options that were specified will be overridden, but the largest dimension should be preserved. The options are passed to the exporter in a dictionary, and can be left out to force the SVG to be created with default options. Below are examples with and without options set. From b2e858a2d7c04bd6063227e69f52e6a4d7e458b8 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Wed, 14 Jun 2023 22:58:08 -0400 Subject: [PATCH 3/7] Add compensation for top and left margins when fitView is used --- cadquery/occ_impl/exporters/svg.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index acc320fb9..ad11815da 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -247,6 +247,12 @@ def getSVG(shape, opts=None): else: width = height * (bb.xlen / bb.ylen) + image_width = width + (marginLeft * 2.0) + image_height = height + (marginTop * 2.0) + else: + image_width = width + image_height = height + # width pixels for x, height pixels for y unitScale = min(width / bb.xlen * bb_scale, height / bb.ylen * bb_scale) @@ -290,8 +296,8 @@ def getSVG(shape, opts=None): "visibleContent": visibleContent, "xTranslate": str(xTranslate), "yTranslate": str(yTranslate), - "width": str(width), - "height": str(height), + "width": str(image_width), + "height": str(image_height), "textboxY": str(height - 30), "uom": str(uom), "axesIndicator": axesIndicator, From cea1a8aa07ec0ca2c8c5ce65638a2334ffc450a7 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Wed, 14 Jun 2023 23:01:30 -0400 Subject: [PATCH 4/7] Updated documentation for SVG fitView interaction with other options --- doc/importexport.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/importexport.rst b/doc/importexport.rst index e92728d24..257bad64d 100644 --- a/doc/importexport.rst +++ b/doc/importexport.rst @@ -216,7 +216,7 @@ options are as follows. * *hiddenColor* - Color of the line that hidden edges are drawn with. * *showHidden* - Whether or not to show hidden lines. * *focus* - If specified, creates a perspective SVG with the projector at the distance specified. -* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options should be set to 0 or the object will fall outside the viewport. The ``width`` or ``height`` options that were specified will be overridden, but the largest dimension should be preserved. +* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options will be respected and will add to the total image size so that the margin can be duplicated at the bottom right. The ``width`` or ``height`` options that were specified will be overridden, but the largest dimension should be preserved except when margins are used. The options are passed to the exporter in a dictionary, and can be left out to force the SVG to be created with default options. Below are examples with and without options set. From 08852eb4c9b519eed4a188da4610c4e9f9a316cb Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Fri, 16 Jun 2023 21:39:50 -0400 Subject: [PATCH 5/7] Trying to get the test coverage up --- tests/test_exporters.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_exporters.py b/tests/test_exporters.py index c297a207e..db2bb4f82 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -552,6 +552,25 @@ def testSVGOptions(self): "out.svg", opt={ "width": 100, + "height": 50, + "marginLeft": 10, + "marginTop": 10, + "showAxes": False, + "projectionDir": (0, 0, 1), + "strokeWidth": 0.25, + "strokeColor": (255, 0, 0), + "hiddenColor": (0, 0, 255), + "showHidden": True, + "focus": 4, + "fitView": True, + }, + ) + + exporters.export( + self._box(), + "out.svg", + opt={ + "width": 50, "height": 100, "marginLeft": 10, "marginTop": 10, From 1638fadca89458e4dc3f1e0c1e93dde75fa91038 Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Mon, 3 Jul 2023 14:49:40 -0400 Subject: [PATCH 6/7] Changes suggested by lorenz --- cadquery/occ_impl/exporters/svg.py | 36 +++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index ad11815da..05a549213 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -133,8 +133,8 @@ def getSVG(shape, opts=None): :type Shape: Vertex, Edge, Wire, Face, Shell, Solid, or Compound. :param opts: An options dictionary that influences the SVG that is output. :type opts: Dictionary, keys are as follows: - width: Document width of the resulting image. - height: Document height of the resulting image. + width: Width of the resulting image (-1 to fit based on height). + height: Height of the resulting image (-1 to fit based on width). marginLeft: Inset margin from the left side of the document. marginTop: Inset margin from the top side of the document. projectionDir: Direction the camera will view the shape from. @@ -180,7 +180,6 @@ def getSVG(shape, opts=None): hiddenColor = tuple(d["hiddenColor"]) showHidden = bool(d["showHidden"]) focus = float(d["focus"]) if d.get("focus") else None - fitView = bool(d["fitView"]) if d.get("fitView") else None hlr = HLRBRep_Algo() hlr.Add(shape.wrapped) @@ -237,24 +236,21 @@ def getSVG(shape, opts=None): bb = Compound.makeCompound(hidden + visible).BoundingBox() # Determine whether the user wants to fit the drawing to the bounding box - bb_scale = 0.75 - if fitView: - bb_scale = 1.0 - - # Figure out which dimension to base the adjusted image size on - if width / bb.xlen < height / bb.ylen: - height = width * (bb.ylen / bb.xlen) + if width <= 0 or height <= 0: + # Fit image to specified width (or height) + if width <= 0: + width = (height - (2.0 * marginTop)) * ( + bb.xlen / bb.ylen + ) + 2.0 * marginLeft else: - width = height * (bb.xlen / bb.ylen) + height = (width - 2.0 * marginLeft) * (bb.ylen / bb.xlen) + 2.0 * marginTop - image_width = width + (marginLeft * 2.0) - image_height = height + (marginTop * 2.0) + # width pixels for x, height pixels for y + unitScale = (width - 2.0 * marginLeft) / bb.xlen else: - image_width = width - image_height = height - - # width pixels for x, height pixels for y - unitScale = min(width / bb.xlen * bb_scale, height / bb.ylen * bb_scale) + bb_scale = 0.75 + # width pixels for x, height pixels for y + unitScale = min(width / bb.xlen * bb_scale, height / bb.ylen * bb_scale) # compute amount to translate-- move the top left into view (xTranslate, yTranslate) = ( @@ -296,8 +292,8 @@ def getSVG(shape, opts=None): "visibleContent": visibleContent, "xTranslate": str(xTranslate), "yTranslate": str(yTranslate), - "width": str(image_width), - "height": str(image_height), + "width": str(width), + "height": str(height), "textboxY": str(height - 30), "uom": str(uom), "axesIndicator": axesIndicator, From 2a8b41367af2e2173cb8ccdb922a3900637aa44d Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Mon, 3 Jul 2023 16:45:05 -0400 Subject: [PATCH 7/7] Reworked changes to use None instead of -1 to denote autofit --- cadquery/occ_impl/exporters/svg.py | 17 +++++++++++------ doc/importexport.rst | 5 ++--- tests/test_exporters.py | 6 ++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/cadquery/occ_impl/exporters/svg.py b/cadquery/occ_impl/exporters/svg.py index 05a549213..b41dee7b0 100644 --- a/cadquery/occ_impl/exporters/svg.py +++ b/cadquery/occ_impl/exporters/svg.py @@ -133,8 +133,8 @@ def getSVG(shape, opts=None): :type Shape: Vertex, Edge, Wire, Face, Shell, Solid, or Compound. :param opts: An options dictionary that influences the SVG that is output. :type opts: Dictionary, keys are as follows: - width: Width of the resulting image (-1 to fit based on height). - height: Height of the resulting image (-1 to fit based on width). + width: Width of the resulting image (None to fit based on height). + height: Height of the resulting image (None to fit based on width). marginLeft: Inset margin from the left side of the document. marginTop: Inset margin from the top side of the document. projectionDir: Direction the camera will view the shape from. @@ -169,8 +169,13 @@ def getSVG(shape, opts=None): # need to guess the scale and the coordinate center uom = guessUnitOfMeasure(shape) - width = float(d["width"]) - height = float(d["height"]) + # Handle the case where the height or width are None + width = d["width"] + if width != None: + width = float(d["width"]) + height = d["height"] + if d["height"] != None: + height = float(d["height"]) marginLeft = float(d["marginLeft"]) marginTop = float(d["marginTop"]) projectionDir = tuple(d["projectionDir"]) @@ -236,9 +241,9 @@ def getSVG(shape, opts=None): bb = Compound.makeCompound(hidden + visible).BoundingBox() # Determine whether the user wants to fit the drawing to the bounding box - if width <= 0 or height <= 0: + if width == None or height == None: # Fit image to specified width (or height) - if width <= 0: + if width == None: width = (height - (2.0 * marginTop)) * ( bb.xlen / bb.ylen ) + 2.0 * marginLeft diff --git a/doc/importexport.rst b/doc/importexport.rst index 257bad64d..ebcc37368 100644 --- a/doc/importexport.rst +++ b/doc/importexport.rst @@ -205,8 +205,8 @@ Exporting SVG The SVG exporter has several options which can be useful for achieving the desired final output. Those options are as follows. -* *width* - Document width of the resulting image. -* *height* - Document height of the resulting image. +* *width* - Width of the resulting image (None to fit based on height). +* *height* - Height of the resulting image (None to fit based on width). * *marginLeft* - Inset margin from the left side of the document. * *marginTop* - Inset margin from the top side of the document. * *projectionDir* - Direction the camera will view the shape from. @@ -216,7 +216,6 @@ options are as follows. * *hiddenColor* - Color of the line that hidden edges are drawn with. * *showHidden* - Whether or not to show hidden lines. * *focus* - If specified, creates a perspective SVG with the projector at the distance specified. -* *fitView* - If specified, will attempt to fit the height and width of the image to the contents. The ``marginLeft`` and ``marginTop`` options will be respected and will add to the total image size so that the margin can be duplicated at the bottom right. The ``width`` or ``height`` options that were specified will be overridden, but the largest dimension should be preserved except when margins are used. The options are passed to the exporter in a dictionary, and can be left out to force the SVG to be created with default options. Below are examples with and without options set. diff --git a/tests/test_exporters.py b/tests/test_exporters.py index db2bb4f82..f0f103438 100644 --- a/tests/test_exporters.py +++ b/tests/test_exporters.py @@ -552,7 +552,7 @@ def testSVGOptions(self): "out.svg", opt={ "width": 100, - "height": 50, + "height": None, "marginLeft": 10, "marginTop": 10, "showAxes": False, @@ -562,7 +562,6 @@ def testSVGOptions(self): "hiddenColor": (0, 0, 255), "showHidden": True, "focus": 4, - "fitView": True, }, ) @@ -570,7 +569,7 @@ def testSVGOptions(self): self._box(), "out.svg", opt={ - "width": 50, + "width": None, "height": 100, "marginLeft": 10, "marginTop": 10, @@ -581,7 +580,6 @@ def testSVGOptions(self): "hiddenColor": (0, 0, 255), "showHidden": True, "focus": 4, - "fitView": True, }, )