svg2stl/svg2stl.py
2021-12-22 18:53:10 +01:00

153 lines
4.4 KiB
Python

import argparse
import pathlib
from xml.dom import minidom
import gmsh
import numpy as np
from svg.path import parse_path
from svg.path import Close, CubicBezier, Line, Move
def parse_svg_into_steps(path: str) -> list:
path_str = minidom.parse(path).getElementsByTagName("path")[0].getAttribute("d")
return parse_path(path_str)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Convert an SVG into an STL.")
parser.add_argument("svg_path", type=str, help="path towards an SVG file")
parser.add_argument("--thickness", default=1, type=float)
parser.add_argument("--show", dest="show", action="store_true", default=False)
args = parser.parse_args()
steps = parse_svg_into_steps(args.svg_path)
# Interpolate points
shapes = []
shape = []
for step in steps:
if isinstance(step, Line):
shape.append([step.start.real, step.start.imag])
elif isinstance(step, CubicBezier):
x0 = step.start.real
y0 = step.start.imag
x1 = step.control1.real
y1 = step.control1.imag
x2 = step.control2.real
y2 = step.control2.imag
x3 = step.end.real
y3 = step.end.imag
t = np.linspace(0, 1, 5, endpoint=False)
x = (
x0 * (1 - t) ** 3
+ x1 * (1 - t) ** 2 * 3 * t
+ x2 * (1 - t) * 3 * t ** 2
+ x3 * t ** 3
)
y = (
y0 * (1 - t) ** 3
+ y1 * (1 - t) ** 2 * 3 * t
+ y2 * (1 - t) * 3 * t ** 2
+ y3 * t ** 3
)
shape.extend(list(zip(x, y)))
elif isinstance(step, Close):
shapes.append(shape)
shape = []
x_min, y_min = np.vstack(shapes).min(axis=0)
x_max, y_max = np.vstack(shapes).max(axis=0)
x_pad = 0.1 * (x_max - x_min)
y_pad = 0.1 * (y_max - y_min)
corners = [
[x_min - x_pad, y_min - y_pad],
[x_min - x_pad, y_max + y_pad],
[x_max + x_pad, y_max + y_pad],
[x_max + x_pad, y_min - y_pad],
]
shapes.append(corners)
# Build walls
gmsh.initialize()
gmsh.model.add("test")
z_floor = 0
z_ceiling = args.thickness
factory = gmsh.model.geo
floor_lines = []
ceiling_lines = []
wall_lines = []
for shape in shapes:
floor_lines.append([])
floor_points = [factory.addPoint(*shape[0], z_floor)]
for vertex in shape[1:]:
floor_points.append(factory.addPoint(*vertex, z_floor))
floor_lines[-1].append(factory.addLine(floor_points[-2], floor_points[-1]))
floor_lines[-1].append(factory.addLine(floor_points[-1], floor_points[0]))
ceiling_lines.append([])
ceiling_points = [factory.addPoint(*shape[0], z_ceiling)]
for vertex in shape[1:]:
ceiling_points.append(factory.addPoint(*vertex, z_ceiling))
ceiling_lines[-1].append(
factory.addLine(ceiling_points[-2], ceiling_points[-1])
)
ceiling_lines[-1].append(factory.addLine(ceiling_points[-1], ceiling_points[0]))
wall_lines.append([])
for floor_point, ceiling_point in zip(floor_points, ceiling_points):
wall_line = factory.addLine(floor_point, ceiling_point)
wall_lines[-1].append(wall_line)
for i in range(1, len(floor_lines[-1])):
wall = factory.addCurveLoop(
[
floor_lines[-1][i - 1],
wall_lines[-1][i],
-ceiling_lines[-1][i - 1],
-wall_lines[-1][i - 1],
]
)
factory.addPlaneSurface([wall])
wall = factory.addCurveLoop(
[
floor_lines[-1][-1],
wall_lines[-1][0],
-ceiling_lines[-1][-1],
-wall_lines[-1][-1],
]
)
factory.addPlaneSurface([wall])
floor = []
for lines in floor_lines:
hole = factory.addCurveLoop(lines)
floor.append(hole)
factory.addPlaneSurface(floor)
ceiling = []
for lines in ceiling_lines:
hole = factory.addCurveLoop(lines)
ceiling.append(hole)
factory.addPlaneSurface(ceiling)
gmsh.model.geo.synchronize()
gmsh.model.mesh.generate()
gmsh.write(str(pathlib.Path(args.svg_path).with_suffix(".stl")))
if args.show:
gmsh.fltk.run()