You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 lines
9.2 KiB

2 years ago
import sys
import os
import pathlib
import json
import textwrap
import argparse
import numpy
from scipy.spatial.transform import Rotation
import pymesh
sys.path.append(str(pathlib.Path(__file__).resolve().parents[2] / "tools" / "fixtures")) # noqa
import fixture_utils
def convert_to_ipc_msh(input_path, output_path, remesh=False):
input_mesh = pymesh.load_mesh(str(input_path))
if input_mesh.num_voxels == 0: # tetrahedralize the mesh
tetgen = pymesh.tetgen()
tetgen.points = input_mesh.vertices
tetgen.triangles = input_mesh.faces
tetgen.split_boundary = remesh
tetgen.max_radius_edge_ratio = 2.0
tetgen.min_dihedral_angle = 0.0
tetgen.merge_coplanar = remesh
tetgen.run()
output_mesh = tetgen.mesh
# Reorient faces
faces = output_mesh.faces.copy()
faces[:, [1, 2]] = faces[:, [2, 1]]
output_mesh = pymesh.form_mesh(
output_mesh.vertices, faces, output_mesh.voxels)
else: # mesh is already a tet mesh, just convert to IPC compatible
output_mesh = input_mesh
if remesh:
pymesh.save_mesh(
str(output_path.with_suffix(".obj")), output_mesh, ascii=True)
with open(output_path, mode='w') as f:
f.write("$MeshFormat\n4 0 8\n$EndMeshFormat\n")
f.write("$Entities\n0 0 0 1\n")
f.write("0 {:g} {:g} {:g} {:g} {:g} {:g} 0 0\n".format(
*output_mesh.nodes.min(axis=0), *output_mesh.nodes.max(axis=0)))
f.write("$EndEntities\n")
f.write("$Nodes\n")
f.write("1 {0:d}\n0 3 0 {0:d}\n".format(output_mesh.num_nodes))
for i, node in enumerate(output_mesh.nodes):
f.write("{:d} {:g} {:g} {:g}\n".format(i + 1, *node))
f.write("$EndNodes\n")
f.write("$Elements\n")
f.write("1 {0:d}\n0 3 4 {0:d}\n".format(output_mesh.num_elements))
for i, element in enumerate(output_mesh.elements):
f.write("{:d} {:d} {:d} {:d} {:d}\n".format(i + 1, *(element + 1)))
f.write("$EndElements\n")
f.write("$Surface\n")
f.write("{:d}\n".format(output_mesh.num_faces))
for face in output_mesh.faces:
f.write("{:d} {:d} {:d}\n".format(*(face + 1)))
f.write("$EndSurface\n")
def convert_to_obj(input_path, output_path):
pymesh.save_mesh(str(output_path), pymesh.load_mesh(str(input_path)))
def fixture_to_ipc_script(fixture, output_path, remesh=False):
timestep = fixture.get("timestep", 0.01)
if "max_time" in fixture:
max_time = fixture["max_time"]
elif "max_iterations" in fixture:
max_time = fixture["max_iterations"] * timestep
else:
max_time = 10
dhat = fixture.get("distance_barrier_constraint", {}).get(
"initial_barrier_activation_distance", 1e-3)
bodies = fixture["rigid_body_problem"]["rigid_bodies"]
is_remeshed = {}
shapes = []
disabled_shapes = []
for body in bodies:
if "type" in body:
is_static = body["type"] == "static"
elif "is_dof_fixed" in body:
is_static = (body["is_dof_fixed"] if isinstance(
body["is_dof_fixed"], bool) else all(body["is_dof_fixed"]))
else:
is_static = False
is_kinematic = body.get("type", "dynamic") == "kinematic"
surface_mesh_path = pathlib.Path(body["mesh"])
if not surface_mesh_path.exists():
surface_mesh_path = (
fixture_utils.get_meshes_dir_path() / surface_mesh_path)
if not is_static:
try:
msh_path = surface_mesh_path.with_suffix('.msh').resolve()
msh_path = (
pathlib.Path(__file__).resolve().parent / "meshes" /
msh_path.relative_to(fixture_utils.get_meshes_dir_path()))
msh_path.parent.mkdir(parents=True, exist_ok=True)
except:
msh_path = surface_mesh_path.with_suffix('.msh')
if not msh_path.exists() or is_remeshed.get(surface_mesh_path, remesh):
convert_to_ipc_msh(surface_mesh_path, msh_path, remesh)
is_remeshed[surface_mesh_path] = remesh
mesh_path = msh_path
elif surface_mesh_path.suffix != ".obj":
try:
obj_path = surface_mesh_path.with_suffix('.obj').resolve()
obj_path = (
pathlib.Path(__file__).resolve().parent / "meshes" /
obj_path.relative_to(fixture_utils.get_meshes_dir_path()))
obj_path.parent.mkdir(parents=True, exist_ok=True)
except:
obj_path = surface_mesh_path.with_suffix('.obj')
if not obj_path.exists():
convert_to_obj(surface_mesh_path, obj_path)
mesh_path = obj_path
else:
mesh_path = surface_mesh_path
mesh_path = os.path.relpath(
mesh_path.resolve(), output_path.parent.resolve())
if "dimensions" in body:
vertices = pymesh.load_mesh(str(surface_mesh_path)).vertices
initial_dimensions = abs(
vertices.max(axis=0) - vertices.min(axis=0))
initial_dimensions[initial_dimensions == 0] = 1
scale = numpy.array(body["dimensions"], dtype=float)
scale /= initial_dimensions
else:
scale = body.get("scale", [1, 1, 1])
if isinstance(scale, int) or isinstance(scale, float):
scale = [scale] * 3
if "rotation" in body:
rotation = Rotation.from_euler(
"xyz", body["rotation"], degrees=True).as_euler("zyx", degrees=True)[::-1]
else:
rotation = [0, 0, 0]
linear_velocity = body.get("linear_velocity", [0, 0, 0])
angular_velocity = body.get("angular_velocity", [0, 0, 0])
force = body.get("force", [0, 0, 0])
if "force" in body:
nbc = (" NBC -1e300 -1e300 -1e300 1e300 1e300 1e300 "
" {:g} {:g} {:g}".format(*body["force"]))
else:
nbc = ""
if "torque" in body:
print("External torque is not supported in IPC! Dropping it!")
if is_static:
velocity_str = "linearVelocity 0 0 0"
elif is_kinematic:
velocity_str = (
"linearVelocity {:g} {:g} {:g} angularVelocity {:g} {:g} {:g}".format(
*linear_velocity, *angular_velocity))
else:
velocity_str = (
"initVel {:g} {:g} {:g} {:g} {:g} {:g}".format(
*linear_velocity, *angular_velocity))
body_line = "{} {:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g} {:g} material {:g} 2e11 0.3 {}{}".format(
mesh_path, *body.get("position", [0, 0, 0]),
*rotation, *scale, body.get("density", 1000), velocity_str, nbc)
is_enabled = (body.get("enabled", True) or body.get(
"ipc_comparison_enabled", False))
if is_enabled:
shapes.append(body_line)
else:
disabled_shapes.append(f"# {body_line}")
epsv = fixture.get("friction_constraints", {}).get(
"static_friction_speed_bound", 1e-3)
friction_iterations = fixture.get(
"friction_constraints", {}).get("iterations", 1)
gravity = ""
if sum(fixture["rigid_body_problem"].get("gravity", [0, 0, 0])) == 0:
gravity = "turnOffGravity"
# velocity_conv_tol = fixture.get(
# "newton_solver", {}).get("velocity_conv_tol", 1e-2)
velocity_conv_tol = fixture.get(
"ipc_solver", {}).get("velocity_conv_tol", 1e-2)
return textwrap.dedent(f"""\
energy NH
warmStart 0
time {max_time} {timestep}
{gravity}
shapes input {len(shapes)}
{{}}
{{}}
selfCollisionOn
selfFric {fixture["rigid_body_problem"].get("coefficient_friction", 0)}
constraintSolver interiorPoint
dHat {dhat}
epsv {epsv}
useAbsParameters
fricIterAmt {friction_iterations}
tol 1
{velocity_conv_tol}
# useAbsTol
""").format("\n".join(shapes), "\n".join(disabled_shapes))
def main():
parser = argparse.ArgumentParser(
description="Convert a rigid fixture to a IPC script")
parser.add_argument(
"-i", "--input", metavar="path/to/input", type=pathlib.Path,
dest="input", default=None, help="path to input json(s)", nargs="+")
parser.add_argument(
"--remesh", action="store_true", default=False, help="remesh meshes")
args, _ = parser.parse_known_args()
for input in args.input:
print(f"Converting {input}")
try:
output = input.with_suffix(".txt").resolve()
output = (
pathlib.Path(__file__).resolve().parent / "scripts" /
output.relative_to(fixture_utils.get_fixture_dir_path()))
output.parent.mkdir(parents=True, exist_ok=True)
except:
output = input.with_suffix(".txt")
with open(input) as input_file:
fixture = json.load(input_file)
ipc_script = fixture_to_ipc_script(fixture, output, args.remesh)
with open(output, 'w') as ipc_script_file:
ipc_script_file.write(ipc_script)
if __name__ == "__main__":
main()