kshdes37's picture
Upload 50 files
91daf98 verified
Raw History Blame Contribute Delete
16.1 kB
import numpy as np
from collections import OrderedDict
import re
from pathlib import Path
import argparse
import os
import json
import math
# hyperparameters from SkexGen project
SKETCH_R = 1
RADIUS_R = 1
EXTRUDE_R = 1.0
SCALE_R = 1.4
OFFSET_R = 0.9
PIX_PAD = 4
CMD_PAD = 3
COORD_PAD = 4
EXT_PAD = 1
EXTRA_PAD = 1
R_PAD = 2
class CADparser:
"""Parse CAD sequence to CAD object."""
def __init__(self, bit):
self.vertex_dict = OrderedDict()
self.bit = bit
def perform(self, cad_seq):
# divide into sketch and extrude
sketches, extrudes = self.get_SE(cad_seq)
if sketches is None or extrudes is None:
return None
# sequentially parse each pair of SE into obj
se_datas = []
for sketch, extrude in zip(sketches, extrudes):
extrude_param, scale, offset = self.parse_extrude(extrude)
if extrude_param is None or scale is None or offset is None:
return None
vertex_str, se_str = self.parse_sketch(sketch, scale, offset)
if vertex_str is None or se_str is None:
return None
se_datas.append(
{"vertex": vertex_str, "curve": se_str, "extrude": extrude_param}
)
self.vertex_dict.clear()
return se_datas
def parse_sketch(self, sketch, scale, offset):
faces = self.get_faces(sketch)
if len(faces) == 0:
return None, None
se_str = ""
for face_idx, face in enumerate(faces): # each face
face_str = "face\n"
loops = self.get_loops(face)
if len(loops) == 0:
return None, None
for loop_idx, loop in enumerate(loops): # each loop
curves = self.get_curves(loop)
if len(curves) == 0:
return None, None
next_curves = curves[1:]
next_curves += curves[:1]
cur_str = []
for curve, next_curve in zip(curves, next_curves): # each curve
if not self.obj_curve(curve, next_curve, cur_str, scale, offset):
return None, None
loop_str = ""
for c in cur_str:
loop_str += f"{c}\n"
if loop_idx == 0:
face_str += f"out\n{loop_str}\n"
else:
face_str += f"in\n{loop_str}\n"
se_str += face_str
vertex_str = self.convert_vertices()
return vertex_str, se_str
def parse_extrude(self, extrude):
ext = extrude.split(",")
if len(ext) != 18:
return None, None, None
# operation str to int
ext_op = {"add": 1, "cut": 2, "intersect": 3}.get(ext[0], None)
if ext_op is None:
return None, None, None
# dequantize ext_v, ext_T, scale and offset
ext_v, ext_T, scale, offset = self.dequantize_extrude_params(ext)
# get ext_R
ext_R = np.array(ext[6:15], dtype=int)
extrude_param = {"value": ext_v, "T": ext_T, "R": ext_R, "op": ext_op}
return extrude_param, scale, offset
def obj_curve(self, curve, next_curve, cur_str, scale, offset):
cur = curve.split(",")
next_cur = next_curve.split(",")
if cur[0] == "circle":
if len(cur) != 9:
return False
p1, p2, p3, p4 = self.dequantize_circle_points(
cur, next_cur, scale, offset)
center = np.asarray([0.5 * (p1[0] + p2[0]), 0.5 * (p3[1] + p4[1])])
radius = (np.linalg.norm(p1 - p2) + np.linalg.norm(p3 - p4)) / 4.0
center = center * scale + offset
radius = radius * scale
center_idx = self.save_vertex(center[0], center[1], "p")
radius_idx = self.save_vertex(radius, 0.0, "r")
cur_str.append(f"c {center_idx} {radius_idx}")
elif cur[0] == "arc":
if len(cur) != 5:
return False
if (
cur[1:3] == cur[3:5]
or cur[1:3] == next_cur[1:3]
or cur[3:5] == next_cur[3:5]
): # invalid arc
return False
start_v, mid_v, end_v = self.dequantize_arc_points(
cur, next_cur, scale, offset
)
try:
center, _, _, _ = find_arc_geometry(start_v, mid_v, end_v)
except Exception:
return False
start_v = start_v * scale + offset
mid_v = mid_v * scale + offset
end_v = end_v * scale + offset
center = center * scale + offset
center_idx = self.save_vertex(center[0], center[1], "p")
start_idx = self.save_vertex(start_v[0], start_v[1], "p")
mid_idx = self.save_vertex(mid_v[0], mid_v[1], "p")
end_idx = self.save_vertex(end_v[0], end_v[1], "p")
cur_str.append(f"a {start_idx} {mid_idx} {center_idx} {end_idx}")
elif cur[0] == "line":
if len(cur) != 3:
return False
if cur[1:3] == next_cur[1:3]:
return False
start_v, end_v = self.dequantize_line_points(
cur, next_cur, scale, offset)
start_v = start_v * scale + offset
end_v = end_v * scale + offset
start_idx = self.save_vertex(start_v[0], start_v[1], "p")
end_idx = self.save_vertex(end_v[0], end_v[1], "p")
cur_str.append(f"l {start_idx} {end_idx}")
else:
return False
return True
def get_SE(self, cad_seq):
# sketches: 1) between sequence start and sketch_end,
sketches_from_start = re.findall(r"^(.+?)(?=<sketch_end>)", cad_seq)
# sketches: 2) between extrude_end and sketch_end
sketches_after_extrude = re.findall(
r"(?<=<extrude_end>)(.+?)(?=<sketch_end>)", cad_seq
)
sketches = [x.strip() for x in sketches_from_start] + [
x.strip() for x in sketches_after_extrude
]
# extrudes: between sketch_end and extrude_end
extrudes = [
x.strip() for x in re.findall(r"<sketch_end>(.+?)<extrude_end>", cad_seq)
]
if len(sketches) != len(extrudes):
return None, None
return sketches, extrudes
def get_faces(self, sketch):
faces = sketch.split("<face_end>")
return [x.strip() for x in faces if x.strip() != ""]
def get_loops(self, face):
loops = face.split("<loop_end>")
return [x.strip() for x in loops if x.strip() != ""]
def get_curves(self, loop):
curves = loop.split("<curve_end>")
return [x.strip() for x in curves if x.strip() != ""]
def dequantize_circle_points(self, curve, next_curve, scale, offset):
p1 = dequantize_verts(
np.array(curve[1:3], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
p2 = dequantize_verts(
np.array(curve[3:5], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
p3 = dequantize_verts(
np.array(curve[5:7], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
p4 = dequantize_verts(
np.array(curve[7:9], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
return p1, p2, p3, p4
def dequantize_arc_points(self, curve, next_curve, scale, offset):
start_v = dequantize_verts(
np.array(curve[1:3], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
mid_v = dequantize_verts(
np.array(curve[3:5], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
end_v = dequantize_verts(
np.array(next_curve[1:3], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
return start_v, mid_v, end_v
def dequantize_line_points(self, curve, next_curve, scale, offset):
start_v = dequantize_verts(
np.array(curve[1:3], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
end_v = dequantize_verts(
np.array(next_curve[1:3], dtype=int),
n_bits=self.bit,
min_range=-SKETCH_R,
max_range=SKETCH_R,
add_noise=False,
)
return start_v, end_v
def dequantize_extrude_params(self, extrude):
ext_v = dequantize_verts(
np.array(extrude[1:3], dtype=int),
n_bits=self.bit,
min_range=-EXTRUDE_R,
max_range=EXTRUDE_R,
add_noise=False,
)
ext_T = dequantize_verts(
np.array(extrude[3:6], dtype=int),
n_bits=self.bit,
min_range=-EXTRUDE_R,
max_range=EXTRUDE_R,
add_noise=False,
)
scale = dequantize_verts(
np.array(extrude[15], dtype=int),
n_bits=self.bit,
min_range=0.0,
max_range=SCALE_R,
add_noise=False,
)
offset = dequantize_verts(
np.array(extrude[16:18], dtype=int),
n_bits=self.bit,
min_range=-OFFSET_R,
max_range=OFFSET_R,
add_noise=False,
)
return ext_v, ext_T, scale, offset
def save_vertex(self, h_x, h_y, text):
unique_key = f"{text}:x{h_x}y{h_y}"
index = 0
for key in self.vertex_dict.keys():
# Vertex location already exist in dict
if unique_key == key:
return index
index += 1
# Vertex location does not exist in dict
self.vertex_dict[unique_key] = [h_x, h_y]
return index
def convert_vertices(self):
"""Convert all the vertices to .obj format"""
vertex_strings = ""
for pt in self.vertex_dict.values():
# e.g. v 0.123 0.234 0.345 1.0
vertex_string = f"v {pt[0]} {pt[1]}\n"
vertex_strings += vertex_string
return vertex_strings
def find_arc_geometry(a, b, c):
A = b[0] - a[0]
B = b[1] - a[1]
C = c[0] - a[0]
D = c[1] - a[1]
E = A*(a[0] + b[0]) + B*(a[1] + b[1])
F = C*(a[0] + c[0]) + D*(a[1] + c[1])
G = 2.0*(A*(c[1] - b[1])-B*(c[0] - b[0]))
if G == 0:
raise Exception("zero G")
p_0 = (D*E - B*F) / G
p_1 = (A*F - C*E) / G
center = np.array([p_0, p_1])
radius = np.linalg.norm(center - a)
angles = []
for xx in [a, b, c]:
angle = angle_from_vector_to_x(xx - center)
angles.append(angle)
ab = b-a
ac = c-a
cp = np.cross(ab, ac)
if cp >= 0:
start_angle_rads = angles[0]
end_angle_rads = angles[2]
else:
start_angle_rads = angles[2]
end_angle_rads = angles[0]
return center, radius, start_angle_rads, end_angle_rads
def angle_from_vector_to_x(vec):
assert vec.size == 2
# We need to find a unit vector
angle = 0.0
l = np.linalg.norm(vec)
uvec = vec/l
# 2 | 1
# -------
# 3 | 4
if uvec[0] >= 0:
if uvec[1] >= 0:
# Qadrant 1
angle = math.asin(uvec[1])
else:
# Qadrant 4
angle = 2.0*math.pi - math.asin(-uvec[1])
else:
if vec[1] >= 0:
# Qadrant 2
angle = math.pi - math.asin(uvec[1])
else:
# Qadrant 3
angle = math.pi + math.asin(-uvec[1])
return angle
def dequantize_verts(verts, n_bits=8, min_range=-0.5, max_range=0.5, add_noise=False):
"""Convert quantized vertices to floats."""
range_quantize = 2**n_bits - 1
verts = verts.astype("float32")
verts = verts * (max_range - min_range) / range_quantize + min_range
return verts
def write_obj_sample(save_folder, data):
for idx, write_data in enumerate(data):
obj_name = Path(save_folder).stem + "_" + \
str(idx).zfill(3) + "_param.obj"
obj_file = Path(save_folder) / obj_name
extrude_param = write_data["extrude"]
vertex_strings = write_data["vertex"]
curve_strings = write_data["curve"]
"""Write an .obj file with the curves and verts"""
if extrude_param["op"] == 1: # 'add'
set_op = "NewBodyFeatureOperation"
elif extrude_param["op"] == 2: # 'cut'
set_op = "CutFeatureOperation"
elif extrude_param["op"] == 3: # 'cut'
set_op = "IntersectFeatureOperation"
with open(obj_file, "w") as fh:
# Write Meta info
fh.write("# WaveFront *.obj file\n")
fh.write("# ExtrudeOperation: " + set_op + "\n")
fh.write("\n")
# Write vertex and curve
fh.write(vertex_strings)
fh.write("\n")
fh.write(curve_strings)
fh.write("\n")
# Write extrude value
extrude_string = "Extrude "
for value in extrude_param["value"]:
extrude_string += str(value) + " "
fh.write(extrude_string)
fh.write("\n")
# Write refe plane value
p_orig = parse3d_sample(extrude_param["T"])
x_axis = parse3d_sample(extrude_param["R"][0:3])
y_axis = parse3d_sample(extrude_param["R"][3:6])
z_axis = parse3d_sample(extrude_param["R"][6:9])
fh.write("T_origin " + p_orig)
fh.write("\n")
fh.write("T_xaxis " + x_axis)
fh.write("\n")
fh.write("T_yaxis " + y_axis)
fh.write("\n")
fh.write("T_zaxis " + z_axis)
def parse3d_sample(point3d):
x = point3d[0]
y = point3d[1]
z = point3d[2]
return str(x) + " " + str(y) + " " + str(z)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--in-path", type=str, required=True)
parser.add_argument("--out-path", type=str, required=True)
args = parser.parse_args()
# with open(args.in_path, "r") as f:
# data = f.readlines()
with open(args.in_path, 'r') as file:
data = file.read()
data = json.loads(data)
num_valid_str = 0
for idx, item in enumerate(data):
try:
cad_parser = CADparser(bit=6)
# print(idx)
if type(item) == str:
parsed_data = cad_parser.perform(item)
elif type(item) == dict:
parsed_data = cad_parser.perform(item['output'])
else:
raise ValueError("Invalid data type")
out_path = os.path.join(args.out_path, str(idx).zfill(6))
os.makedirs(out_path, exist_ok=True)
if parsed_data is not None:
num_valid_str += 1
write_obj_sample(out_path, parsed_data)
except Exception as e:
print(e)
pass
print(f"Number of valid CAD strings: {num_valid_str}/{len(data)}")