155 lines
4.5 KiB
Python
155 lines
4.5 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 = []
|
|
|
|
shapes = shapes[1:]
|
|
|
|
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()
|