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.
 
 
 
 
 

254 lines
9.2 KiB

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()