Upstream Snapshot bb87928bce Import upstream snapshot 80e710700aac9573a2230f74f7ce9e094833a0bc
Upstream: https://github.com/Rhoban/onshape-to-robot
Upstream-Commit: 80e710700aac9573a2230f74f7ce9e094833a0bc
Upstream-Branch: master
2026-08-28 15:42:31 +08:00

193 lines
7.1 KiB
Python

import subprocess
import re
import json
import os
from .message import bright, info, error
from .processor import Processor
from .config import Config
from .robot import Robot
from .geometry import Box, Cylinder, Sphere, Shape
import numpy as np
class ProcessorScad(Processor):
"""
Scad processor. This processor will parse OpenSCAD files to create pure shapes when available.
The code is a naive parser of the intermediate CSG file produced by OpenSCAD, gathering Box, Sphere and Cylinders.
"""
is_safe: bool = False
def __init__(self, config: Config):
super().__init__(config)
# OpenSCAD pure shapes
self.use_scads: bool = config.get("use_scads", False)
self.pure_shape_dilatation: float = config.get("pure_shape_dilatation", 0.0)
if self.use_scads:
self.check_openscad()
def check_openscad(self):
if self.use_scads:
print(bright("* Checking OpenSCAD presence..."))
try:
subprocess.run(["openscad", "-v"])
except FileNotFoundError:
print(bright("Can't run openscad -v, disabling OpenSCAD support"))
print(info("TIP: consider installing openscad:"))
print(info("Linux:"))
print(info("sudo add-apt-repository ppa:openscad/releases"))
print(info("sudo apt-get update"))
print(info("sudo apt-get install openscad"))
print(info("Windows:"))
print(info("go to: https://openscad.org/downloads.html "))
self.use_scads = False
def process(self, robot: Robot):
if self.use_scads:
getcwd = os.getcwd()
os.chdir(self.config.output_directory)
print(info("+ Parsing OpenSCAD files..."))
for link in robot.links:
for part in link.parts:
converted_meshes = []
for mesh in part.meshes:
if mesh.collision:
scad_file = mesh.filename.replace(".stl", ".scad")
if os.path.exists(scad_file):
part.shapes += self.parse_scad(scad_file, mesh.color)
converted_meshes.append(mesh)
for converted_mesh in converted_meshes:
converted_mesh.collision = False
part.prune_unused_geometry()
os.chdir(getcwd)
def multmatrix_parse(self, parameters: str):
matrix = np.array(json.loads(parameters), dtype=float)
matrix[0, 3] /= 1000.0
matrix[1, 3] /= 1000.0
matrix[2, 3] /= 1000.0
return matrix
def cube_parse(self, parameters: str):
results = re.findall(r"^size = (.+), center = (.+)$", parameters)
if len(results) != 1:
raise Exception(f"! Can't parse CSG cube parameters: {parameters}")
extra = np.array([self.pure_shape_dilatation] * 3)
return (
extra + np.array(json.loads(results[0][0]), dtype=float) / 1000.0
), results[0][1] == "true"
def cylinder_parse(self, parameters: str):
results = re.findall(
r"h = (.+), r1 = (.+), r2 = (.+), center = (.+)", parameters
)
if len(results) != 1:
raise Exception(f"! Can't parse CSG cylinder parameters: {parameters}")
result = results[0]
extra = np.array([self.pure_shape_dilatation / 2, self.pure_shape_dilatation])
return (extra + np.array([result[0], result[1]], dtype=float) / 1000.0), result[
3
] == "true"
def sphere_parse(self, parameters: str):
results = re.findall(r"r = (.+)$", parameters)
if len(results) != 1:
raise Exception(f"! Can't parse CSG sphere parameters: {parameters}")
return self.pure_shape_dilatation + float(results[0]) / 1000.0
def extract_node_parameters(self, line: str):
line = line.strip()
parts = line.split("(", 1)
node = parts[0]
parameters = parts[1]
if parameters[-1] == ";":
parameters = parameters[:-2]
if parameters[-1] == "{":
parameters = parameters[:-3]
return node, parameters
def translation(self, x: float, y: float, z: float):
m = np.eye(4)
m[:3, 3] = [x, y, z]
return m
def parse_csg(self, csg_data: str, color):
shapes: list[Shape] = []
lines = csg_data.split("\n")
matrices = []
for line in lines:
line = line.strip()
if line != "":
if line[-1] == "{":
node, parameters = self.extract_node_parameters(line)
if node == "multmatrix":
matrix = self.multmatrix_parse(parameters)
else:
matrix = np.eye(4)
matrices.append(matrix)
elif line[-1] == "}":
matrices.pop()
else:
node, parameters = self.extract_node_parameters(line)
transform = np.eye(4)
for matrix in matrices:
transform = transform @ matrix
if node == "cube":
size, center = self.cube_parse(parameters)
if not center:
transform = transform @ self.translation(
size[0] / 2.0, size[1] / 2.0, size[2] / 2.0
)
shapes.append(
Box(transform, size, color, visual=False, collision=True)
)
if node == "cylinder":
size, center = self.cylinder_parse(parameters)
if not center:
transform = transform @ self.translation(
0, 0, size[0] / 2.0
)
shapes.append(
Cylinder(
transform,
size[0],
size[1],
color,
visual=False,
collision=True,
)
)
if node == "sphere":
shapes.append(
Sphere(
transform,
self.sphere_parse(parameters),
color,
visual=False,
collision=True,
)
)
return shapes
def parse_scad(self, scad_file: str, color: np.ndarray):
tmp_data = os.getcwd() + "/_tmp_data.csg"
os.system("openscad " + scad_file + " -o " + tmp_data)
with open(tmp_data, "r", encoding="utf-8") as stream:
data = stream.read()
os.system("rm " + tmp_data)
return self.parse_csg(data, color)