<div dir="ltr"><div dir="ltr"><div>Apparently, Bone type has both head and head_local fields whereas EditBone and PoseBone types have a head field, which I use.  This may be more useful than figuring out matrix_local.</div><div><br></div><div>There's also a tail_local with Bone, and all the bones have tail as well.</div><div><br></div><div>Here's some sample code for how to create several joints in Blender,and export them to X3D.  I'm not seeing HAnim.</div></div><div class="gmail_quote gmail_quote_container"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><blockquote type="cite">import bpy<br>from mathutils import Vector<br><br>def create_objects_for_joints():<br>    """Create basic objects to demonstrate joints"""<br>    # Clear existing objects<br>    bpy.ops.object.select_all(action='SELECT')<br>    bpy.ops.object.delete()<br>    <br>    # Create a cube as parent<br>    bpy.ops.mesh.primitive_cube_add(location=(0, 0, 0))<br>    parent = bpy.context.active_object<br>    <a href="http://parent.name">parent.name</a> = 'Parent'<br>    <br>    # Create a cylinder as child<br>    bpy.ops.mesh.primitive_cylinder_add(location=(3, 0, 0))<br>    child = bpy.context.active_object<br>    <a href="http://child.name">child.name</a> = 'Child'<br>    <br>    return parent, child<br><br>def create_fixed_joint(parent, child):<br>    """Create a fixed joint (Parent constraint)"""<br>    constraint = child.constraints.new('CHILD_OF')<br>    constraint.target = parent<br>    <a href="http://constraint.name">constraint.name</a> = "Fixed Joint"<br><br>def create_hinge_joint(parent, child, axis='X'):<br>    """Create a hinge joint (Rotation constraint)"""<br>    constraint = child.constraints.new('LIMIT_ROTATION')<br>    <a href="http://constraint.name">constraint.name</a> = f"Hinge Joint ({axis})"<br>    <br>    # Lock all rotations except the specified axis<br>    setattr(constraint, 'use_limit_x', axis != 'X')<br>    setattr(constraint, 'use_limit_y', axis != 'Y')<br>    setattr(constraint, 'use_limit_z', axis != 'Z')<br>    <br>    # Set rotation limits for the free axis<br>    if axis == 'X':<br>        constraint.min_x = -1.5708  # -90 degrees<br>        constraint.max_x = 1.5708   # 90 degrees<br>    elif axis == 'Y':<br>        constraint.min_y = -1.5708<br>        constraint.max_y = 1.5708<br>    elif axis == 'Z':<br>        constraint.min_z = -1.5708<br>        constraint.max_z = 1.5708<br><br>def create_ball_joint(parent, child):<br>    """Create a ball joint (Track To constraint)"""<br>    constraint = child.constraints.new('TRACK_TO')<br>    constraint.target = parent<br>    <a href="http://constraint.name">constraint.name</a> = "Ball Joint"<br>    constraint.track_axis = 'TRACK_Z'<br>    constraint.up_axis = 'UP_Y'<br><br>def create_prismatic_joint(parent, child, axis='X'):<br>    """Create a prismatic joint (Limit Location constraint)"""<br>    constraint = child.constraints.new('LIMIT_LOCATION')<br>    <a href="http://constraint.name">constraint.name</a> = f"Prismatic Joint ({axis})"<br>    <br>    # Lock all translations except the specified axis<br>    setattr(constraint, 'use_min_x', axis != 'X')<br>    setattr(constraint, 'use_max_x', axis != 'X')<br>    setattr(constraint, 'use_min_y', axis != 'Y')<br>    setattr(constraint, 'use_max_y', axis != 'Y')<br>    setattr(constraint, 'use_min_z', axis != 'Z')<br>    setattr(constraint, 'use_max_z', axis != 'Z')<br>    <br>    # Set translation limits for the free axis<br>    if axis == 'X':<br>        constraint.min_x = -5<br>        constraint.max_x = 5<br>    elif axis == 'Y':<br>        constraint.min_y = -5<br>        constraint.max_y = 5<br>    elif axis == 'Z':<br>        constraint.min_z = -5<br>        constraint.max_z = 5<br><br>def create_distance_joint(parent, child, distance=2.0):<br>    """Create a distance joint (Limit Distance constraint)"""<br>    constraint = child.constraints.new('LIMIT_DISTANCE')<br>    constraint.target = parent<br>    <a href="http://constraint.name">constraint.name</a> = "Distance Joint"<br>    constraint.distance = distance<br>    constraint.limit_mode = 'LIMITDIST_ONSURFACE'<br><br>def create_spring_joint(parent, child):<br>    """Create a spring joint (using Hooks)"""<br>    # Add Hook modifier to child<br>    hook_mod = child.modifiers.new(name="Spring Joint", type='HOOK')<br>    hook_mod.object = parent<br>    hook_mod.strength = 0.5  # Adjust spring stiffness<br><br>def main():<br>    # Create example objects<br>    parent, child = create_objects_for_joints()<br>    <br>    # Example: Create a hinge joint<br>    create_hinge_joint(parent, child, 'Z')<br>    <br>    # Note: You would typically only use one joint type at a time<br>    # Uncomment the joint type you want to test:<br>    <br>    # create_fixed_joint(parent, child)<br>    # create_ball_joint(parent, child)<br>    # create_prismatic_joint(parent, child, 'X')<br>    # create_distance_joint(parent, child, 2.0)<br>    # create_spring_joint(parent, child)<br><br>if __name__ == "__main__":<br>    main()</blockquote></div></blockquote><div><br></div><div>=====================================================================================================================</div><div>Export code</div><div><br></div>import bpy<br>import os<br>import xml.etree.ElementTree as ET<br>import mathutils<br><br>def setup_x3d_scene():<br>    """Create a basic X3D scene structure"""<br>    x3d = ET.Element('X3D')<br>    x3d.set('version', '3.3')<br>    x3d.set('profile', 'Immersive')<br>    <br>    scene = ET.SubElement(x3d, 'Scene')<br>    return x3d, scene<br><br>def matrix_to_transform_string(matrix):<br>    """Convert Blender matrix to X3D transform string"""<br>    # X3D uses row-major order<br>    flat_matrix = [str(f) for f in sum((tuple(row) for row in matrix), ())]<br>    return ' '.join(flat_matrix)<br><br>def export_object_geometry(obj):<br>    """Export object mesh data to X3D format"""<br>    if obj.type != 'MESH':<br>        return None<br>    <br>    shape = ET.Element('Shape')<br>    <br>    # Export material if present<br>    if obj.active_material:<br>        appearance = ET.SubElement(shape, 'Appearance')<br>        material = ET.SubElement(appearance, 'Material')<br>        material.set('diffuseColor', '0.8 0.8 0.8')  # Default gray color<br>    <br>    # Export geometry<br>    if obj.data.name.startswith('Cube'):<br>        box = ET.SubElement(shape, 'Box')<br>        box.set('size', '2 2 2')  # Default cube size<br>    elif obj.data.name.startswith('Cylinder'):<br>        cylinder = ET.SubElement(shape, 'Cylinder')<br>        cylinder.set('radius', '1')<br>        cylinder.set('height', '2')<br>    <br>    return shape<br><br>def export_joint(parent, child, constraint, scene):<br>    """Export joint data to X3D format"""<br>    transform_group = ET.SubElement(scene, 'Transform')<br>    transform_group.set('translation', f"{parent.location.x} {parent.location.y} {parent.location.z}")<br>    <br>    # Export parent object<br>    parent_shape = export_object_geometry(parent)<br>    if parent_shape is not None:<br>        transform_group.append(parent_shape)<br>    <br>    # Handle different joint types<br>    if constraint.type == 'LIMIT_ROTATION':  # Hinge joint<br>        joint = ET.SubElement(transform_group, 'HingeJoint')<br>        joint.set('axisOfRotation', '0 0 1')  # Z-axis by default<br>        <br>        joint_body = ET.SubElement(joint, 'RigidBody')<br>        child_transform = ET.SubElement(joint_body, 'Transform')<br>        child_transform.set('translation', f"{child.location.x - parent.location.x} "<br>                                         f"{child.location.y - parent.location.y} "<br>                                         f"{child.location.z - parent.location.z}")<br>        <br>        child_shape = export_object_geometry(child)<br>        if child_shape is not None:<br>            child_transform.append(child_shape)<br>    <br>    elif constraint.type == 'TRACK_TO':  # Ball joint<br>        joint = ET.SubElement(transform_group, 'BallJoint')<br>        joint.set('anchorPoint', f"{parent.location.x} {parent.location.y} {parent.location.z}")<br>        <br>        joint_body = ET.SubElement(joint, 'RigidBody')<br>        child_transform = ET.SubElement(joint_body, 'Transform')<br>        child_transform.set('translation', f"{child.location.x - parent.location.x} "<br>                                         f"{child.location.y - parent.location.y} "<br>                                         f"{child.location.z - parent.location.z}")<br>        <br>        child_shape = export_object_geometry(child)<br>        if child_shape is not None:<br>            child_transform.append(child_shape)<br>    <br>    elif constraint.type == 'LIMIT_LOCATION':  # Prismatic joint<br>        joint = ET.SubElement(transform_group, 'SliderJoint')<br>        joint.set('axis', '1 0 0')  # X-axis by default<br>        <br>        joint_body = ET.SubElement(joint, 'RigidBody')<br>        child_transform = ET.SubElement(joint_body, 'Transform')<br>        child_transform.set('translation', f"{child.location.x - parent.location.x} "<br>                                         f"{child.location.y - parent.location.y} "<br>                                         f"{child.location.z - parent.location.z}")<br>        <br>        child_shape = export_object_geometry(child)<br>        if child_shape is not None:<br>            child_transform.append(child_shape)<br><br>def export_scene_to_x3d(filepath):<br>    """Export entire scene with joints to X3D file"""<br>    x3d, scene = setup_x3d_scene()<br>    <br>    # Export each object with constraints (joints)<br>    for obj in bpy.data.objects:<br>        if obj.constraints:<br>            for constraint in obj.constraints:<br>                if constraint.target:  # If constraint has a target (parent)<br>                    export_joint(constraint.target, obj, constraint, scene)<br>    <br>    # Write X3D file<br>    tree = ET.ElementTree(x3d)<br>    tree.write(filepath, encoding='utf-8', xml_declaration=True, pretty_print=True)<br><br>def main():<br>    # Example usage<br>    output_path = "C:/temp/blender_joints.x3d"  # Adjust path as needed<br>    <br>    # First create some joints using the previous script...<br>    parent, child = create_objects_for_joints()  # From previous example<br>    create_hinge_joint(parent, child, 'Z')<br>    <br>    # Then export to X3D<br>    export_scene_to_x3d(output_path)<br>    print(f"Exported X3D file to: {output_path}")<br><br>if __name__ == "__main__":<br><div>    main() </div><div>=========================================================================================</div><div>HAnim Export code:</div><div><br></div><div>import bpy<br>import xml.etree.ElementTree as ET<br>import mathutils<br>from math import degrees<br><br>def create_hanim_skeleton():<br>    """Create a basic humanoid skeleton structure"""<br>    armature = bpy.data.armatures.new('HumanoidSkeleton')<br>    armature_obj = bpy.data.objects.new('Humanoid', armature)<br>    bpy.context.scene.collection.objects.link(armature_obj)<br>    <br>    # Enter edit mode to add bones<br>    bpy.context.view_layer.objects.active = armature_obj<br>    bpy.ops.object.mode_set(mode='EDIT')<br>    <br>    # Dictionary to store edit_bones for parenting<br>    bones = {}<br>    <br>    # Create spine and head<br>    bones['HumanoidRoot'] = armature.edit_bones.new('HumanoidRoot')<br>    bones['HumanoidRoot'].head = (0, 0, 0)<br>    bones['HumanoidRoot'].tail = (0, 0, 0.1)<br>    <br>    bones['vl5'] = armature.edit_bones.new('vl5')<br>    bones['vl5'].head = (0, 0, 0.1)<br>    bones['vl5'].tail = (0, 0, 0.3)<br>    bones['vl5'].parent = bones['HumanoidRoot']<br>    <br>    bones['Sacrum'] = armature.edit_bones.new('Sacrum')<br>    bones['Sacrum'].head = (0, 0, 0.3)<br>    bones['Sacrum'].tail = (0, 0, 0.5)<br>    bones['Sacrum'].parent = bones['vl5']<br>    <br>    # Create legs<br>    for side in ['l', 'r']:<br>        bones[f'{side}_hip'] = armature.edit_bones.new(f'{side}_hip')<br>        bones[f'{side}_hip'].head = (0.1 if side == 'r' else -0.1, 0, 0.3)<br>        bones[f'{side}_hip'].tail = (0.1 if side == 'r' else -0.1, 0, 0.2)<br>        bones[f'{side}_hip'].parent = bones['Sacrum']<br>        <br>        bones[f'{side}_knee'] = armature.edit_bones.new(f'{side}_knee')<br>        bones[f'{side}_knee'].head = bones[f'{side}_hip'].tail<br>        bones[f'{side}_knee'].tail = (0.1 if side == 'r' else -0.1, 0, 0.1)<br>        bones[f'{side}_knee'].parent = bones[f'{side}_hip']<br>        <br>        bones[f'{side}_ankle'] = armature.edit_bones.new(f'{side}_ankle')<br>        bones[f'{side}_ankle'].head = bones[f'{side}_knee'].tail<br>        bones[f'{side}_ankle'].tail = (0.1 if side == 'r' else -0.1, 0.1, 0)<br>        bones[f'{side}_ankle'].parent = bones[f'{side}_knee']<br>    <br>    bpy.ops.object.mode_set(mode='OBJECT')<br>    return armature_obj<br><br>def matrix_to_transform_string(matrix):<br>    """Convert Blender matrix to X3D transform string"""<br>    flat_matrix = [str(f) for f in sum((tuple(row) for row in matrix), ())]<br>    return ' '.join(flat_matrix)<br><br>def create_hanim_joint_element(bone, parent_element):<br>    """Create X3D HAnimJoint element for a bone"""<br>    joint = ET.SubElement(parent_element, 'HAnimJoint')<br>    joint.set('name', <a href="http://bone.name">bone.name</a>)<br>    <br>    # Calculate joint center (head position)<br>    joint_center = bone.head_local<br>    joint.set('center', f"{joint_center.x} {joint_center.y} {joint_center.z}")<br>    <br>    # Calculate rotation and translation from bone matrix<br>    if bone.parent:<br>        local_matrix = bone.parent.matrix_local.inverted() @ bone.matrix_local<br>        pos, rot, scale = local_matrix.decompose()<br>        <br>        # Convert rotation to axis-angle<br>        axis_angle = rot.to_axis_angle()<br>        if axis_angle[1] != 0:<br>            joint.set('rotation', f"{axis_angle[0].x} {axis_angle[0].y} {axis_angle[0].z} {axis_angle[1]}")<br>        <br>        joint.set('translation', f"{pos.x} {pos.y} {pos.z}")<br>    <br>    return joint<br><br>def export_hanim_to_x3d(armature_obj, filepath):<br>    """Export humanoid animation skeleton to X3D"""<br>    # Create X3D document<br>    x3d = ET.Element('X3D')<br>    x3d.set('version', '3.3')<br>    x3d.set('profile', 'Immersive')<br>    <br>    scene = ET.SubElement(x3d, 'Scene')<br>    <br>    # Create HAnimHumanoid<br>    humanoid = ET.SubElement(scene, 'HAnimHumanoid')<br>    humanoid.set('name', 'Humanoid')<br>    humanoid.set('version', '2.0')<br>    <br>    # Create skeleton<br>    skeleton = ET.SubElement(humanoid, 'HAnimJoint')<br>    skeleton.set('name', 'HumanoidRoot')<br>    <br>    # Export joints hierarchy<br>    def export_bone_hierarchy(bone, parent_element):<br>        if bone.parent is None or bone.parent.parent is None:<br>            joint_element = create_hanim_joint_element(bone, parent_element)<br>        else:<br>            joint_element = create_hanim_joint_element(bone, parent_element)<br>        <br>        # Export child bones<br>        for child in bone.children:<br>            export_bone_hierarchy(child, joint_element)<br>    <br>    # Start export from root bone<br>    root_bone = armature_obj.data.bones[0]<br>    export_bone_hierarchy(root_bone, skeleton)<br>    <br>    # Add animation if present<br>    if armature_obj.animation_data and armature_obj.animation_data.action:<br>        action = armature_obj.animation_data.action<br>        <br>        # Create TimeSensor<br>        time_sensor = ET.SubElement(scene, 'TimeSensor')<br>        time_sensor.set('DEF', 'AnimationTimer')<br>        time_sensor.set('cycleInterval', str(action.frame_range[1] / 24.0))  # Assuming 24 fps<br>        time_sensor.set('loop', 'true')<br>        <br>        # Create animation for each bone<br>        for fcurve in action.fcurves:<br>            if fcurve.data_path.startswith('pose.bones'):<br>                bone_name = fcurve.data_path.split('"')[1]<br>                <br>                # Create OrientationInterpolator for rotation<br>                if fcurve.array_index < 3:  # rotation channels<br>                    interpolator = ET.SubElement(scene, 'OrientationInterpolator')<br>                    interpolator.set('DEF', f'{bone_name}_rot')<br>                    <br>                    # Export keyframes<br>                    key_times = []<br>                    key_values = []<br>                    for keyframe in fcurve.keyframe_points:<br>                        time = keyframe.co.x / 24.0  # Convert frames to seconds<br>                        value = keyframe.co.y<br>                        key_times.append(str(time))<br>                        key_values.append(str(value))<br>                    <br>                    interpolator.set('key', ' '.join(key_times))<br>                    interpolator.set('keyValue', ' '.join(key_values))<br>    <br>    # Write X3D file<br>    tree = ET.ElementTree(x3d)<br>    tree.write(filepath, encoding='utf-8', xml_declaration=True)<br><br>def main():<br>    # Create a humanoid skeleton<br>    armature_obj = create_hanim_skeleton()<br>    <br>    # Export to X3D<br>    export_path = "C:/temp/humanoid.x3d"  # Adjust path as needed<br>    export_hanim_to_x3d(armature_obj, export_path)<br>    print(f"Exported H-Anim X3D file to: {export_path}")<br><br>if __name__ == "__main__":<br>    main()</div></div></div>