cadspace / CADFusion /src /rendering_utils /utils /obj_reconverter.py
kshdes37's picture
Upload 50 files
91daf98 verified
import numpy as np
from collections import OrderedDict
from .util import create_point, create_unit_vec, get_transform, create_sketch_plane
# OCC
from OCC.Core.BRepCheck import BRepCheck_Analyzer
from OCC.Core.GC import GC_MakeArcOfCircle
from OCC.Core.BRepBuilderAPI import (
BRepBuilderAPI_MakeFace,
BRepBuilderAPI_MakeWire,
BRepBuilderAPI_MakeEdge,
)
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Fuse, BRepAlgoAPI_Cut, BRepAlgoAPI_Common
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakePrism
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
from OCC.Core.BRepGProp import brepgprop_VolumeProperties, brepgprop_SurfaceProperties
from OCC.Core.GProp import GProp_GProps
from OCC.Core.ShapeFix import ShapeFix_Face, ShapeFix_Wire
from OCC.Core.gp import gp_Vec, gp_Ax2, gp_Dir, gp_Circ
from OCC.Extend.DataExchange import write_stl_file
class OBJReconverter:
"""OBJ Data Reconverter"""
def __init__(self):
self.vertex_dict = OrderedDict()
self.PRECISION = 1e-5
self.eps = 1e-7
self.x_axis = gp_Dir(1.0, 0.0, 0.0)
def convert_curve(self, curve):
"""
convert to json dict format
"""
json_curve = {}
if curve.type == "circle":
json_curve["type"] = "Circle3D"
json_curve["center_point"] = {
"x": curve.center[0],
"y": curve.center[1],
"z": 0,
}
json_curve["radius"] = curve.radius
if curve.type == "line":
json_curve["type"] = "Line3D"
json_curve["start_point"] = {
"x": curve.start[0],
"y": curve.start[1],
"z": 0,
}
json_curve["end_point"] = {"x": curve.end[0], "y": curve.end[1], "z": 0}
if curve.type == "arc":
json_curve["type"] = "Arc3D"
json_curve["start_point"] = {
"x": curve.start[0],
"y": curve.start[1],
"z": 0,
}
json_curve["end_point"] = {"x": curve.end[0], "y": curve.end[1], "z": 0}
json_curve["mid_point"] = {"x": curve.mid[0], "y": curve.mid[1], "z": 0}
json_curve["center_point"] = {
"x": curve.center[0],
"y": curve.center[1],
"z": 0,
}
json_curve["is_outer"] = curve.is_outer
return json_curve
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 parse_obj(self, faces, meta_info):
"""
reconstruct brep from obj file
"""
# At least one needs to match
for face in faces:
for loop in face:
if len(loop) > 1:
for idx, curve in enumerate(loop[:-1]):
next_curve = np.vstack([loop[idx + 1].start, loop[idx + 1].end])
diff1 = np.sum(np.abs(curve.start - next_curve), 1)
diff2 = np.sum(np.abs(curve.end - next_curve), 1)
if min(diff2) == 0 or min(diff1) == 0:
continue # edge connected
assert (
min(diff1) < 1e-3 or min(diff2) < 1e-3
) # difference should be small
if min(diff1) > min(diff2):
min_idx = np.argmin(diff2)
if min_idx == 0:
loop[idx + 1].start_idx = curve.end_idx
loop[idx + 1].start = curve.end
else:
loop[idx + 1].end_idx = curve.end_idx
loop[idx + 1].end = curve.end
else:
min_idx = np.argmin(diff1)
if min_idx == 0:
loop[idx + 1].start_idx = curve.start_idx
loop[idx + 1].start = curve.start
else:
loop[idx + 1].end_idx = curve.start_idx
loop[idx + 1].end = curve.start
# Solve start / end connection
shared_idx = list(
set([loop[-2].start_idx, loop[-2].end_idx]).intersection(
set([loop[-1].start_idx, loop[-1].end_idx])
)
)
assert len(shared_idx) >= 1
if len(shared_idx) == 2:
assert len(loop) == 2 # do nothing
else:
if shared_idx[0] == loop[-1].start_idx:
do_start = False
else:
do_start = True
start_curve = np.vstack([loop[0].start, loop[0].end])
if do_start:
diff = np.sum(np.abs(loop[-1].start - start_curve), 1)
else:
diff = np.sum(np.abs(loop[-1].end - start_curve), 1)
assert min(diff) < 1e-3
min_idx = np.argmin(diff)
if min_idx == 0:
if do_start:
loop[-1].start_idx = loop[0].start_idx
loop[-1].start = loop[0].start
else:
loop[-1].end_idx = loop[0].start_idx
loop[-1].end = loop[0].start
else:
if do_start:
loop[-1].start_idx = loop[0].end_idx
loop[-1].start = loop[0].end
else:
loop[-1].end_idx = loop[0].end_idx
loop[-1].end = loop[0].end
# Parse groups to json loop/curve profile
extrusion = {}
extrusion["profiles"] = []
for face in faces:
profile = {}
profile["loops"] = []
for loop in face:
pl = {}
pl["profile_curves"] = []
for curve in loop:
# convert to json format
pl["profile_curves"].append(self.convert_curve(curve))
profile["loops"].append(pl)
extrusion["profiles"].append(profile)
# Parse transform
sketch = {}
transform = {}
transform["origin"] = {
"x": meta_info["t_orig"][0],
"y": meta_info["t_orig"][1],
"z": meta_info["t_orig"][2],
}
transform["x_axis"] = {
"x": meta_info["t_x"][0],
"y": meta_info["t_x"][1],
"z": meta_info["t_x"][2],
}
transform["y_axis"] = {
"x": meta_info["t_y"][0],
"y": meta_info["t_y"][1],
"z": meta_info["t_y"][2],
}
transform["z_axis"] = {
"x": meta_info["t_z"][0],
"y": meta_info["t_z"][1],
"z": meta_info["t_z"][2],
}
sketch["transform"] = transform
# Parse extrude
extrude_params = {}
extrude_params["extrude_type"] = meta_info["set_op"]
extrude_params["extrude_values"] = meta_info["extrude_value"]
# Create sketch
all_faces = []
curve_strings = ""
curve_count = 0
for profile in extrusion["profiles"]:
ref_face, face, curve_string, c_count = self.parse_sketch(sketch, profile)
curve_strings += curve_string
curve_count += c_count
all_faces.append(face)
# Merge all faces in the same plane
plane_face = all_faces[0]
for face in all_faces[1:]:
plane_face = self.my_op(plane_face, face, "fuse")
solid = self.extrude_face(ref_face, plane_face, extrude_params)
return solid, curve_strings, curve_count
def my_op(self, big, small, op_name):
if op_name == "cut":
op = BRepAlgoAPI_Cut(big, small)
elif op_name == "fuse":
op = BRepAlgoAPI_Fuse(big, small)
elif op_name == "common":
op = BRepAlgoAPI_Common(big, small)
op.SetFuzzyValue(self.PRECISION)
op.Build()
return op.Shape()
def build_body(self, face, normal, value):
extrusion_vec = gp_Vec(normal).Multiplied(value)
make_prism = BRepPrimAPI_MakePrism(face, extrusion_vec)
make_prism.Build()
prism = make_prism.Prism()
return prism.Shape()
def extrudeBasedOnType(self, face, normal, distance):
# Extrude based on the two bound values
if not (distance[0] < distance[1]):
raise Exception("incorrect distance")
large_value = distance[1]
small_value = distance[0]
if large_value == 0:
return self.build_body(face, -normal, -small_value)
elif small_value == 0:
return self.build_body(face, normal, large_value)
elif np.sign(large_value) == np.sign(small_value):
if large_value < 0:
body1 = self.build_body(face, -normal, -small_value)
body2 = self.build_body(face, -normal, -large_value)
return self.my_op(body1, body2, "cut")
else:
assert large_value > 0
body1 = self.build_body(face, normal, small_value)
body2 = self.build_body(face, normal, large_value)
return self.my_op(body2, body1, "cut")
else:
assert np.sign(large_value) != np.sign(small_value)
body1 = self.build_body(face, normal, large_value)
body2 = self.build_body(face, -normal, -small_value)
return self.my_op(body1, body2, "fuse")
def extrude_face(self, ref_face, face, extrude_params):
distance = extrude_params["extrude_values"]
surf = BRepAdaptor_Surface(ref_face).Plane()
normal = surf.Axis().Direction()
extruded_shape = self.extrudeBasedOnType(face, normal, distance)
return extruded_shape
def parse_sketch(self, sketch, profile):
"""
Sketch in one closed loop (one out, multiple ins)
"""
# Transformation from local to global xyz coord
transform = get_transform(sketch["transform"])
# Create face region (automatically infer from all wires)
outer_facelist = []
inner_facelist = []
curve_count = 0
outer_string = []
inner_string = []
plane = create_sketch_plane(sketch["transform"])
for idx, pl in enumerate(profile["loops"]):
# Create loop
loop, curve_string, num_curve = self.parse_loop(
pl["profile_curves"], transform
)
# Create face
face_builder = BRepBuilderAPI_MakeFace(plane, loop)
if not face_builder.IsDone():
raise Exception("face builder not done")
face = face_builder.Face()
# Fix face
fixer = ShapeFix_Face(face)
fixer.SetPrecision(self.PRECISION)
fixer.FixOrientation()
analyzer = BRepCheck_Analyzer(fixer.Face())
if not analyzer.IsValid():
raise Exception("face check failed")
curve_count += num_curve
if pl["profile_curves"][0]["is_outer"]:
outer_facelist.append(fixer.Face())
outer_string.append(curve_string)
else:
inner_facelist.append(fixer.Face())
inner_string.append(curve_string)
# Create final closed loop face
assert len(outer_facelist) > 0
final_face = outer_facelist[0]
for face in outer_facelist[1:]:
final_face = self.my_op(final_face, face, "fuse")
for face in inner_facelist:
final_face = self.my_op(final_face, face, "cut")
# Append inner outer information to string
assert len(outer_string) == 1
out_str = ""
in_str = ""
for c_str in outer_string:
out_str += "out\n" + c_str + "\n"
for c_str in inner_string:
in_str += "in\n" + c_str + "\n"
final_str = "face\n" + out_str + in_str
return outer_facelist[0], final_face, final_str, curve_count
def parse_loop(self, profile_loop, transform):
"""Create face in one closed loop"""
topo_wire = BRepBuilderAPI_MakeWire()
curve_strings = ""
curve_count = 0
# Loop through all the curves in one loop
for profile_curve in profile_loop:
curve_edge, curve_string = self.parse_curve(profile_curve, transform)
topo_wire.Add(curve_edge)
if not topo_wire.IsDone():
raise Exception("wire builder not done")
curve_string += "\n"
curve_count += 1
curve_strings += curve_string
fixer = ShapeFix_Wire()
fixer.Load(topo_wire.Wire())
fixer.SetPrecision(self.PRECISION)
fixer.FixClosed()
fixer.Perform()
return fixer.Wire(), curve_strings, curve_count
def parse_curve(self, curve, transform):
if curve["type"] == "Line3D":
return self.create_line(curve, transform)
elif curve["type"] == "Circle3D":
return self.create_circle(curve, transform)
elif curve["type"] == "Arc3D":
return self.create_arc(curve, transform)
else:
raise Exception("unknown curve type")
def create_line(self, line, transform):
start = create_point(line["start_point"], transform)
end = create_point(line["end_point"], transform)
if start.Distance(end) == 0:
raise Exception("start/end point same location")
topo_edge = BRepBuilderAPI_MakeEdge(start, end)
# Save pre-transform
star_idx = self.save_vertex(
line["start_point"]["x"] + 0.0, line["start_point"]["y"] + 0.0, "p"
)
end_idx = self.save_vertex(
line["end_point"]["x"] + 0.0, line["end_point"]["y"] + 0.0, "p"
)
curve_string = f"l {star_idx} {end_idx}"
return topo_edge.Edge(), curve_string
def create_arc(self, arc, transform):
start = create_point(arc["start_point"], transform)
mid = create_point(arc["mid_point"], transform)
end = create_point(arc["end_point"], transform)
arc_occ = GC_MakeArcOfCircle(start, mid, end).Value()
topo_edge = BRepBuilderAPI_MakeEdge(arc_occ)
# Save pre-transform
start_idx = self.save_vertex(
arc["start_point"]["x"] + 0.0, arc["start_point"]["y"] + 0.0, "p"
)
end_idx = self.save_vertex(
arc["end_point"]["x"] + 0.0, arc["end_point"]["y"] + 0.0, "p"
)
center_idx = self.save_vertex(
arc["center_point"]["x"] + 0.0, arc["center_point"]["y"] + 0.0, "p"
)
mid_idx = self.save_vertex(
arc["mid_point"]["x"] + 0.0, arc["mid_point"]["y"] + 0.0, "p"
)
curve_string = f"a {start_idx} {mid_idx} {center_idx} {end_idx}"
return topo_edge.Edge(), curve_string
def create_circle(self, circle, transform):
center = create_point(circle["center_point"], transform)
radius = circle["radius"]
normal = create_unit_vec({"x": 0.0, "y": 0.0, "z": 1.0}, transform)
ref_vector3d = self.x_axis.Transformed(transform)
axis = gp_Ax2(center, normal, ref_vector3d)
gp_circle = gp_Circ(axis, abs(float(radius)))
topo_edge = BRepBuilderAPI_MakeEdge(gp_circle)
center_idx = self.save_vertex(
circle["center_point"]["x"] + 0.0, circle["center_point"]["y"] + 0.0, "p"
)
radius_idx = self.save_vertex(abs(float(radius)) + 0.0, 0, "r")
curve_string = f"c {center_idx} {radius_idx}"
return topo_edge.Edge(), curve_string
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