Upstream: https://github.com/Rhoban/onshape-to-robot Upstream-Commit: 80e710700aac9573a2230f74f7ce9e094833a0bc Upstream-Branch: master
193 lines
7.1 KiB
Python
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)
|