Skip to content

Commit

Permalink
add align parameter to import_svg
Browse files Browse the repository at this point in the history
  • Loading branch information
snoyer committed Feb 21, 2025
1 parent 40cf143 commit 8e4aa33
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 6 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ dependencies = [
"ezdxf >= 1.1.0, < 2",
"ipython >= 8.0.0, < 9",
"py-lib3mf >= 2.3.1",
"ocpsvg >= 0.4, < 0.5",
"ocpsvg >= 0.5, < 0.6",
"trianglesolver",
]

Expand Down
19 changes: 16 additions & 3 deletions src/build123d/importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@
from ocpsvg import ColorAndLabel, import_svg_document
from svgpathtools import svg2paths

from build123d.geometry import Color, Location
from build123d.build_enums import Align
from build123d.geometry import Color, Location, Vector, to_align_offset
from build123d.topology import (
Compound,
Edge,
Expand Down Expand Up @@ -337,6 +338,7 @@ def import_svg(
svg_file: str | Path | TextIO,
*,
flip_y: bool = True,
align: Align | tuple[Align, Align] | None = Align.MIN,
ignore_visibility: bool = False,
label_by: Literal["id", "class", "inkscape:label"] | str = "id",
is_inkscape_label: bool | None = None, # TODO remove for `1.0` release
Expand All @@ -346,6 +348,8 @@ def import_svg(
Args:
svg_file (Union[str, Path, TextIO]): svg file
flip_y (bool, optional): flip objects to compensate for svg orientation. Defaults to True.
align (Align | tuple[Align, Align] | None, optional): alignment of the SVG's viewbox,
if None, the viewbox's origin will be at `(0,0,0)`. Defaults to Align.MIN.
ignore_visibility (bool, optional): Defaults to False.
label_by (str, optional): XML attribute to use for imported shapes' `label` property.
Defaults to "id".
Expand All @@ -368,19 +372,28 @@ def import_svg(
label_by = re.sub(
r"^inkscape:(.+)", r"{http://www.inkscape.org/namespaces/inkscape}\1", label_by
)
for face_or_wire, color_and_label in import_svg_document(
imported = import_svg_document(
svg_file,
flip_y=flip_y,
ignore_visibility=ignore_visibility,
metadata=ColorAndLabel.Label_by(label_by),
):
)

doc_xy = Vector(imported.viewbox.x, imported.viewbox.y)
doc_wh = Vector(imported.viewbox.width, imported.viewbox.height)
offset = to_align_offset(doc_xy, doc_xy + doc_wh, align)

for face_or_wire, color_and_label in imported:
if isinstance(face_or_wire, TopoDS_Wire):
shape = Wire(face_or_wire)
elif isinstance(face_or_wire, TopoDS_Face):
shape = Face(face_or_wire)
else: # should not happen
raise ValueError(f"unexpected shape type: {type(face_or_wire).__name__}")

if offset.X != 0 or offset.Y != 0: # avoid copying if we don't need to
shape = shape.translate(offset)

if shape.wrapped:
shape.color = Color(*color_and_label.color_for(shape.wrapped))
shape.label = color_and_label.label
Expand Down
36 changes: 34 additions & 2 deletions tests/test_importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
import_step,
import_stl,
)
from build123d.geometry import Pos
from build123d.geometry import Pos, Vector
from build123d.exporters import ExportSVG
from build123d.exporters3d import export_brep, export_step
from build123d.build_enums import GeomType
from build123d.build_enums import Align, GeomType


class ImportSVG(unittest.TestCase):
Expand Down Expand Up @@ -116,6 +116,38 @@ def test_import_svg_colors(self):
self.assertEqual(str(svg[1].color), str(Color(1, 0, 0, 1)))
self.assertEqual(str(svg[2].color), str(Color(0, 0, 0, 1)))

def test_import_svg_origin(self):
svg_src = (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 1 6 8" width="6" height="8">'
'<circle r="1" cx="2" cy="3"/>'
"</svg>"
)

svg = import_svg(StringIO(svg_src), align=None, flip_y=False)
self.assertAlmostEqual(svg[0].bounding_box().center(), Vector(2.0, +3.0))

svg = import_svg(StringIO(svg_src), align=None, flip_y=True)
self.assertAlmostEqual(svg[0].bounding_box().center(), Vector(2.0, -3.0))

def test_import_svg_align(self):
svg_src = (
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="1 1 6 8" width="6" height="8">'
'<rect x="1" y="1" width="6" height="8"/>'
"</svg>"
)

svg = import_svg(StringIO(svg_src), align=Align.MIN, flip_y=False)
self.assertAlmostEqual(svg[0].bounding_box().min, Vector(0.0, 0.0))

svg = import_svg(StringIO(svg_src), align=Align.MIN, flip_y=True)
self.assertAlmostEqual(svg[0].bounding_box().min, Vector(0, 0))

svg = import_svg(StringIO(svg_src), align=Align.MAX, flip_y=False)
self.assertAlmostEqual(svg[0].bounding_box().max, Vector(0.0, 0.0))

svg = import_svg(StringIO(svg_src), align=Align.MAX, flip_y=True)
self.assertAlmostEqual(svg[0].bounding_box().max, Vector(0, 0))


class ImportBREP(unittest.TestCase):
def test_bad_filename(self):
Expand Down

0 comments on commit 8e4aa33

Please sign in to comment.