mujoco_worldgen/util/obj_util.py (163 lines of code) (raw):
import numpy as np
from collections import OrderedDict
# TODO: Write more comments.
from mujoco_worldgen.util.types import accepts, returns, maybe
def get_camera_xyaxes(camera_pos, target_pos):
'''
Calculate the "xyaxis" frame orientation for a camera,
given the camera position and target position.
Returns a 6-vector of the x axis and y axis of the camera frame.
See http://www.mujoco.org/book/modeling.html#COrientation "xyaxes"
'''
camera_pos = np.array(camera_pos)
target_pos = np.array(target_pos)
assert camera_pos.shape == (
3,), "Bad camera position {}".format(camera_pos)
assert target_pos.shape == (
3,), "Bad target position {}".format(target_pos)
vector = target_pos - camera_pos
cross = np.cross(vector, np.array([0, 0, 1]))
cross2 = np.cross(cross, vector)
return np.concatenate((cross, cross2))
@accepts(OrderedDict, str, maybe(np.ndarray))
def add_annotation_bound(xml_dict, annotation_name, bound):
'''
Add an annotation bounding box to and XML dictionary.
Annotation name will be "annotation:" + annotation_name
Bound is given as a 2 x 3 np.ndarray, and represents:
[[min_x, min_y, min_z], [max_x, max_y, max_z]]
'''
if bound is None:
return xml_dict # Nothing to do here
assert bound.shape == (2, 3), "Bound must be 2 x 3 (see docstring)."
assert 'worldbody' in xml_dict, "XML must have worldbody"
worldbody = xml_dict['worldbody']
assert 'body' in worldbody, "XML worldbody must have bodies"
name = 'annotation:' + annotation_name
# Remove old annotations with the same name before inserting new annotation
bodies = [body for body in worldbody['body'] if body.get('@name') != name]
rgba = np.random.uniform(size=4)
rgba[3] = 0.1 # annotation is almost transparent.
geom = OrderedDict([('@conaffinity', 0),
('@contype', 0),
('@mass', 0.0),
('@pos', np.zeros(3)),
('@rgba', rgba),
('@size', (bound[1] - bound[0]) / 2), # halfsize
('@type', 'box')])
annotation = OrderedDict([('@name', name),
('@pos', bound.mean(axis=0)), # center pos
('geom', [geom])])
bodies.append(annotation)
worldbody['body'] = bodies
print('adding annotation bound (size)', name, bound[1] - bound[0])
@accepts(OrderedDict)
@returns(OrderedDict)
def get_xml_meshes(xml_dict):
''' Get dictionary of all the mesh names -> filenames in a parsed XML. '''
meshes = OrderedDict()
for mesh in xml_dict.get('asset', {}).get('mesh', []):
assert '@name' in mesh, "Mesh missing name: {}".format(mesh)
assert '@file' in mesh, "Mesh missing file: {}".format(mesh)
scale = np.ones(3)
if "@scale" in mesh:
scale = mesh["@scale"]
meshes[mesh['@name']] = (mesh['@file'], scale)
return meshes
@accepts(OrderedDict, str)
def recursive_rename(xml_dict, prefix):
attrs = ["@name", "@joint", "@jointinparent", "@class", "@source",
"@target", "@childclass", "@body1", "@body2", "@mesh",
"@joint1", "@joint2", "@geom", "@geom1", "@geom2", "@site",
"@material", "@texture", "@tendon", "@sidesite", "@actuator"]
names = ["geom", "joint", "jointinparent", "body", "motor", "freejoint", "general",
"position", "default", "weld", "exclude", "mesh",
"site", "pair", "jointpos", "touch", "texture", "material",
"fixed", "spatial", "motor", "actuatorfrc"]
if not isinstance(xml_dict, OrderedDict):
return
for key in list(xml_dict.keys()):
value_dict = xml_dict[key]
if isinstance(value_dict, OrderedDict):
value_dict = [value_dict]
if key in names:
assert isinstance(
value_dict, list), "Invalid type for value {}".format(value_dict)
for value in value_dict:
for attr in list(value.keys()):
if attr in attrs:
if not value[attr].startswith(prefix + ':'):
value[attr] = prefix + ':' + value[attr]
if isinstance(value_dict, list):
for value in value_dict:
recursive_rename(value, prefix)
def establish_size(min_size, max_size):
if isinstance(min_size, (float, int)):
min_size = np.ones(3) * float(min_size)
if isinstance(max_size, (float, int)):
max_size = np.ones(3) * float(max_size)
if max_size is None and min_size is not None:
max_size = min_size
if max_size is None and min_size is None:
min_size = np.ones(3) * 0.1
max_size = np.ones(3) * 0.1
if isinstance(min_size, (list, tuple)):
min_size = np.array(min_size, dtype=np.float64)
if isinstance(max_size, (list, tuple)):
max_size = np.array(max_size, dtype=np.float64)
assert(isinstance(min_size[0], float))
assert(isinstance(max_size[0], float))
for i in range(3):
assert(max_size[i] >= min_size[i])
return min_size, max_size
def get_name_index(name_indexes, name):
'''
Update the name index and return new name
name - name to look up index for, e.g. "geom"
name_indexes - dictionary to keep track of names, e.g.
{'geom': 4} means there are 4 geom objects, and the next
geom object should be called "geom4" and the dictionary updated
to be {'geom': 5}
Returns name with index attached, e.g. "geom4"
'''
if name not in name_indexes:
name_indexes[name] = 0
result = '%s%d' % (name, name_indexes[name])
name_indexes[name] += 1
return result
def get_body_xml_node(name, use_joints=False):
'''
Build a body XML dict for use in object models.
name - name for the body (should be unique in the model, e.g. "geom4")
joints - if True, add 6 degrees of freedom joints (slide, hinge)
Returns named XML body node.
'''
body = OrderedDict()
body['@name'] = name
body['@pos'] = np.zeros(3)
if use_joints:
joints = []
for axis_type in ('slide', 'hinge'):
for i, axis in enumerate(np.eye(3)):
joint = OrderedDict()
joint['@name'] = "%s:%s%d" % (name, axis_type, i)
joint['@axis'] = axis
joint['@type'] = axis_type
joint['@damping'] = 0.01
joint['@pos'] = np.zeros(3)
joints.append(joint)
body['joint'] = joints
return body
@accepts(np.ndarray)
@returns(int)
def get_axis_index(axis):
'''
Returns axis index from a string:
# return 0 for axis = 1 0 0
# return 1 for axis = 0 1 0
# return 2 for axis = 0 0 1
'''
assert axis.shape[0] == 3
for i in range(3):
if axis[i] != 0:
return i
assert False, "axis should be of a form (1 0 0), or (0 1 0), or (0 0 1)." \
"Current axis = %s, it's not. Failing." % str(axis)