Ejemplo n.º 1
0
def add_or_overwrite_bone_constraint_follow_path(armature_object_name,
                                                 bone_name,
                                                 constraint_name,
                                                 target_curve_name,
                                                 use_curve_follow=False,  # default blender value
                                                 use_fixed_location=False,  # default blender value
                                                 forward_axis='FORWARD_Y'
                                                 ):

    logger.info('add_bone_constraint_follow_path: ...')

    set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]
    current_bone = armature_object.pose.bones[bone_name]

    # Note: The name / identifier of a constraint of a specific object is UNIQUE
    if constraint_name in current_bone.constraints:
        logger.warning('Follow Path constraint exists already')
        # Make sure the constraint has the correct type
        assert current_bone.constraints[constraint_name].type == ConstraintTypes.FOLLOW_PATH
        current_constraint = current_bone.constraints[constraint_name]
    else:
        current_constraint = current_bone.constraints.new(ConstraintTypes.FOLLOW_PATH)
    current_constraint.name = constraint_name
    current_constraint.target = bpy.data.objects[target_curve_name]
    current_constraint.use_curve_follow = use_curve_follow
    current_constraint.use_fixed_location = use_fixed_location
    current_constraint.forward_axis = forward_axis

    bpy.ops.object.mode_set(mode='OBJECT')
    logger.info('add_bone_constraint_follow_path: Done')
Ejemplo n.º 2
0
def add_bone_constraint_IK(armature_object_name,
                           bone_name,
                           constraint_name,
                           target_object_name,
                           chain_count=None,
                           subtarget_name=None):

    """
    :param armature_object_name:
    :param bone_name:
    :param constraint_name:
    :param target_object_name:
    :param chain_count:
    :param subtarget_name:
    :return:
    """

    # Bone constraints. Armature must be in pose mode.
    set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]

    current_bone = armature_object.pose.bones[bone_name]

    current_constraint = current_bone.constraints.new(ConstraintTypes.IK)
    current_constraint.name = constraint_name
    if chain_count is not None:
        current_constraint.chain_count = chain_count
    current_constraint.target = bpy.data.objects[target_object_name]
    if subtarget_name is not None:
        current_constraint.subtarget = subtarget_name

    bpy.ops.object.mode_set(mode='OBJECT')
Ejemplo n.º 3
0
def set_bone_head_tail(armature_object_name, bone_name, head_location=None, tail_location=None):

    """ A CHILD is attached with its HEAD to the TAIL of the PARENT """

    logger.info('set_bone_head_tail: ...')

    # ===================
    # If head_location and tail_location is set to the same value, THE BONE DISAPPEARS
    # ===================
    assert not head_location == tail_location

    set_mode(
        active_object_name=armature_object_name,
        mode='EDIT',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]

    if head_location is not None:
        logger.debug('head_location: ' + str(head_location))
        armature_object.data.edit_bones[bone_name].head = Vector(head_location)

    if tail_location is not None:
        logger.debug('tail_location: ' + str(tail_location))
        armature_object.data.edit_bones[bone_name].tail = Vector(tail_location)

    bpy.ops.object.mode_set(mode='OBJECT')
    logger.info('set_bone_head_tail: Done')
Ejemplo n.º 4
0
def add_bone_to_armature(armature_object_name,
                         bone_name,
                         bone_head_pos,
                         bone_tail_pos,
                         world_coordinates=False):  # set to true, to use world coordinates

    """

    :param armature_object_name:
    :param bone_name:
    :param bone_head_pos: in local or world coordinates (point which is attached to parent)
    :param bone_tail_pos: in local or world coordinates
    :param world_coordinates
    :return:
    """

    logger.info('add_bone_to_armature: ...')

    # https://docs.blender.org/api/blender_python_api_2_75_0/info_gotcha.html#editbones-posebones-bone-bones

    set_mode(active_object_name=armature_object_name, mode='EDIT', configure_scene_for_basic_ops=False)

    # if parent_bone is not None:
    #     armature_object.data.bones[parent_bone.name].select = True

    armature_object = bpy.data.objects[armature_object_name]

    # Create single bone
    bone = armature_object.data.edit_bones.new(bone_name)

    # https://docs.blender.org/api/blender_python_api_current/bpy.types.Object.html#bpy.types.Object.matrix_world
    if world_coordinates:
        world_to_object_matrix = armature_object.matrix_world.inverted()
        bone_head_pos = (world_to_object_matrix * bone_head_pos.to_4d()).to_3d()
        bone_tail_pos = (world_to_object_matrix * bone_tail_pos.to_4d()).to_3d()

    bone.head = bone_head_pos
    bone.tail = bone_tail_pos

    # logger.info('parent_bone')
    # logger.info(parent_bone)

    # if parent_bone is not None:
    #     bone.parent = parent_bone
    #     # Connect this bone with its parent (or not)
    #     # (i.e. moving parent/child moves also child/parent)
    #     bone.use_connect = use_connect

    # https://docs.blender.org/api/blender_python_api_2_75_0/info_gotcha.html#armature-mode-switching
    #   While writing scripts that deal with armatures you may find you have to switch between modes,
    #   when doing so take care when switching out of editmode not to keep references to the edit-bones or
    #   their head/tail vectors. Further access to these will crash blender so its important the script clearly
    #   separates sections of the code which operate in different modes.

    bpy.ops.object.mode_set(mode='OBJECT')

    logger.info('add_bone_to_armature: Done')
Ejemplo n.º 5
0
def mute_object_constraint(object_name, constraint_name, mute):
    previous_mode = set_mode(active_object_name=object_name,
                             mode='OBJECT',
                             configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[object_name]
    armature_object.constraints[constraint_name].mute = mute
    bpy.ops.object.mode_set(mode=previous_mode)
Ejemplo n.º 6
0
def show_viewport_object_modifier(object_name, modifier_name, show_viewport):
    previous_mode = set_mode(active_object_name=object_name,
                             mode='OBJECT',
                             configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[object_name]
    armature_object.modifiers[modifier_name].show_viewport = show_viewport
    bpy.ops.object.mode_set(mode=previous_mode)


# ==============================================================================================================
#                                               Deprecated
# ==============================================================================================================

# DEPRECATED
# def scale_object(object_name, scaling_parameter, scale_in_edit_mode=False):
#     bpy.ops.object.select_all(action='DESELECT')
#     bpy.context.scene.objects.active = bpy.context.scene.objects[object_name]
#     bpy.context.scene.objects[object_name].select = True
#
#     if scale_in_edit_mode:
#         # change to edit mode, so the resizing is directly applied to geometry
#         bpy.ops.object.mode_set(mode='EDIT')
#
#     bpy.ops.transform.resize(value=(scaling_parameter, scaling_parameter, scaling_parameter))
#
#     bpy.ops.object.mode_set(mode='OBJECT')
#     bpy.context.scene.objects[object_name].select = False
#     bpy.ops.object.select_all(action='DESELECT')
Ejemplo n.º 7
0
def set_object_parent_bone(child_object_name,
                           armature_object_name,
                           bone_name,
                           keep_transform=False):

    logger.info('set_object_parent_bone: ...')
    # https://docs.blender.org/api/blender_python_api_current/bpy.ops.object.html#bpy.ops.object.parent_set

    logger.info('child_object_name:' + str(child_object_name))
    logger.info('armature_object_name: ' + str(armature_object_name))

    #  Idea taken from:
    # http://nullege.com/codes/show/src%40b%40l%40blender_mmd_tools-HEAD%40mmd_tools%40utils.py/32/bpy.ops.object.parent_set/python

    set_mode(armature_object_name,
             mode='POSE',
             configure_scene_for_basic_ops=True)

    parent_object = bpy.data.objects[armature_object_name]
    bpy.data.objects[child_object_name].select = True

    # make the object active in pose mode
    parent_armature_data = parent_object.data
    parent_bone = parent_armature_data.bones[bone_name]
    parent_armature_data.bones.active = parent_bone
    parent_bone.select = False

    logger.info('parent_armature_data.name: ' + str(parent_armature_data))
    logger.debug('bpy.context.scene.objects.active: (before ops) ' +
                 str(bpy.context.scene.objects.active))
    logger.debug('Selected objects: (before ops) ' +
                 str([obj for obj in bpy.data.objects if obj.select]))
    logger.debug('parent_armature_data.bones.active: (before ops) ' +
                 str(parent_armature_data.bones.active))
    #logger.debug('Selected bones: (before ops) ' + str([bone for bone in parent_armature_data.bones if bone.select]))

    check_ops_prerequisites(active_object_name=armature_object_name,
                            selected_object_names=[child_object_name])

    bpy.ops.object.parent_set(type='BONE',
                              xmirror=False,
                              keep_transform=keep_transform)

    bpy.ops.object.mode_set(mode='OBJECT')

    logger.info('set_object_parent_bone: Done')
Ejemplo n.º 8
0
def mute_bone_constraint(armature_object_name, bone_name, constraint_name, mute):
    previous_mode = set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]
    current_bone = armature_object.pose.bones[bone_name]
    current_bone.constraints[constraint_name].mute = mute
    bpy.ops.object.mode_set(mode=previous_mode)
Ejemplo n.º 9
0
def snap_single_bone_to_cursor(armature_object_name, bone_name):
    """
    This emulates blender's GUI snapping functionality.
    The translation vector is determined using the bones head
    location and the cursor position.
    """
    logger.info('snap_single_bone_to_cursor: ...')
    logger.info('armature_object_name: ' + armature_object_name)
    logger.info('bone_name: ' + bone_name)

    previous_mode = set_mode(
        active_object_name=armature_object_name,
        mode='OBJECT',
        configure_scene_for_basic_ops=False)

    armature_object = bpy.data.objects[armature_object_name]

    # NOTE:
    # IN armature_object.data.bones (NOT armature_object.data.edit_bones) there are 2 TYPES of coordinates
    # armature_object.data.bones[bone_name].head
    # head: Location of head end of the bone relative to its parent (the parent can be a BONE or the ARMATURE)
    # armature_object.data.bones[bone_name].head_local        # => Armature coordinates
    # head_local: Location of head end of the bone relative to armature (SAME as under Head in the GUI)

    # armature_object.data.bones[bone_name].tail and armature_object.data.bones[bone_name].tail_local same as for head

    # NOTE:
    # For the highest bone in the hierarchy .head and .head_local are equal (and .tail and .tail_local)
    # The GUI values are the same as in head_local and tail_local

    world_to_object_matrix = armature_object.matrix_world.inverted()
    # Cursor location is in WORLD COORDINATES
    cursor_location_in_armature_coordinates = \
        (world_to_object_matrix * bpy.context.scene.cursor_location.to_4d()).to_3d()

    shift_vec_armature_coordinates = \
        cursor_location_in_armature_coordinates - \
        armature_object.data.bones[bone_name].head_local

    # logger.debug('armature_object.data.bones[bone_name].head: ' +
    #   str(armature_object.data.bones[bone_name].head))
    # logger.debug('armature_object.data.bones[bone_name].head_local: ' +
    #   str(armature_object.data.bones[bone_name].head_local))

    new_head_pos = armature_object.data.bones[bone_name].head_local + shift_vec_armature_coordinates
    new_tail_pos = armature_object.data.bones[bone_name].tail_local + shift_vec_armature_coordinates

    set_bone_head_tail(
        armature_object_name,
        bone_name,
        head_location=new_head_pos,
        tail_location=new_tail_pos)

    bpy.ops.object.mode_set(mode=previous_mode)

    logger.info('snap_single_bone_to_cursor: Done')
Ejemplo n.º 10
0
def set_pose_position(armature_object_name, pose_position):

    previous_mode = set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]
    armature_object.data.pose_position = pose_position

    bpy.ops.object.mode_set(mode=previous_mode)
Ejemplo n.º 11
0
def add_bone_constraint_transformation(armature_object_name,
                                       bone_name,
                                       constraint_name,
                                       target_object_name,
                                       target_bone_name=None,
                                       extrapolate=True):

    set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]
    current_bone = armature_object.pose.bones[bone_name]
    current_constraint = current_bone.constraints.new(ConstraintTypes.TRANSFORM)
    current_constraint.name = constraint_name
    current_constraint.target = bpy.data.objects[target_object_name]

    if target_bone_name is not None:
        current_constraint.subtarget = target_bone_name

    current_constraint.use_motion_extrapolate = extrapolate

    # Source
    current_constraint.map_from = 'LOCATION'
    current_constraint.from_min_y = -1
    current_constraint.from_max_y = 1

    current_constraint.map_to_x_from = 'Y'
    current_constraint.map_to_y_from = 'Y'
    current_constraint.map_to_z_from = 'Y'

    # Destination
    current_constraint.map_to = 'ROTATION'
    current_constraint.to_min_x_rot = -360  # degrees
    current_constraint.to_max_x_rot = 360  # degrees

    current_constraint.target_space = 'LOCAL'
    current_constraint.owner_space = 'LOCAL'

    bpy.ops.object.mode_set(mode='OBJECT')
Ejemplo n.º 12
0
def set_bone_parent(armature_object_name,
                    child_bone_name,
                    parent_bone_name,
                    connected=False,
                    inherit_rotation=True,
                    inherit_scale=True):

    logger.info('set_bone_parent: ...')

    set_mode(active_object_name=armature_object_name, mode='EDIT', configure_scene_for_basic_ops=False)
    # bpy.ops.object.mode_set(mode='EDIT')    # TODO use set_mode

    armature_object = bpy.data.objects[armature_object_name]
    armature_object.data.edit_bones[child_bone_name].parent = armature_object.data.edit_bones[parent_bone_name]
    armature_object.data.edit_bones[child_bone_name].use_connect = connected
    armature_object.data.edit_bones[child_bone_name].use_inherit_rotation = inherit_rotation
    armature_object.data.edit_bones[child_bone_name].use_inherit_scale = inherit_scale

    # armature_object.data.edit_bones[child_bone_name].parent = armature_object.data.edit_bones[parent_bone_name]
    bpy.ops.object.mode_set(mode='OBJECT')

    logger.info('set_bone_parent: Done')
Ejemplo n.º 13
0
def add_bone_constraint_locked_track(armature_object_name,
                                     bone_name,
                                     constraint_name,
                                     target_object_name,
                                     target_bone_name=None):

    # Note: "track to" is outdated and should be replaced with "damped track" or "locked track"

    # Bone constraints. Armature must be in pose mode.
    set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]
    current_bone = armature_object.pose.bones[bone_name]
    current_constraint = current_bone.constraints.new(ConstraintTypes.LOCKED_TRACK)
    current_constraint.name = constraint_name
    current_constraint.target = bpy.data.objects[target_object_name]

    if target_bone_name is not None:
        current_constraint.subtarget = target_bone_name

    bpy.ops.object.mode_set(mode='OBJECT')
Ejemplo n.º 14
0
def clear_and_set_inverse_object_constraint_child_of(object_name,
                                                     constraint_name):

    previous_mode = set_mode(active_object_name=object_name,
                             mode='OBJECT',
                             configure_scene_for_basic_ops=False)

    current_object = bpy.data.objects[object_name]

    override_context = bpy.context.copy()
    override_context['constraint'] = current_object.constraints[
        constraint_name]

    bpy.ops.constraint.childof_clear_inverse(override_context,
                                             constraint=constraint_name,
                                             owner='OBJECT')
    bpy.ops.constraint.childof_set_inverse(override_context,
                                           constraint=constraint_name,
                                           owner='OBJECT')

    bpy.ops.object.mode_set(mode=previous_mode)
Ejemplo n.º 15
0
def clear_and_set_inverse_bone_constraint_child_of(armature_object_name,
                                                   bone_name,
                                                   constraint_name,
                                                   toggle_pose_position=False):

    previous_mode = set_mode(
        active_object_name=armature_object_name,
        mode='POSE',
        configure_scene_for_basic_ops=False)
    armature_object = bpy.data.objects[armature_object_name]

    if toggle_pose_position:
        old_pose_position = armature_object.data.pose_position
        armature_object.data.pose_position = 'POSE'

    # pose position must be in 'POSE', otherwise the resetting of the inverse has no effect
    assert armature_object.data.pose_position == 'POSE'

    current_bone = armature_object.pose.bones[bone_name]

    # active bone
    override_context = bpy.context.copy()
    override_context['constraint'] = current_bone.constraints[constraint_name]
    armature_object.data.bones.active = armature_object.data.bones[bone_name]

    bpy.ops.constraint.childof_clear_inverse(
        override_context,
        constraint=constraint_name,
        owner='BONE')
    bpy.ops.constraint.childof_set_inverse(
        override_context,
        constraint=constraint_name,
        owner='BONE')

    if toggle_pose_position:
        armature_object.data.pose_position = old_pose_position

    bpy.ops.object.mode_set(mode=previous_mode)
def apply_rig_to_model(car_rig_prefix,
                       car_body_group_name,
                       car_wheels_group_name,
                       tire_fl_name,
                       tire_fr_name,
                       tire_bl_name,
                       tire_br_name):
    logger.info('bpy.context.scene.active_layer:' + str(bpy.context.scene.active_layer))
    rig_layer_id = bpy.context.scene.active_layer

    # ======== IMPORTANT TO AVOID POLL ERRORS ========
    configure_scene_for_basic_ops()
    # ======== ======== ======== ======== ========

    logger.info('Adjust Rig to Car Model')

    car_rig_name = 'CarRig'
    car_rig_ground_detect_name = 'CarRigGroundDetect'
    car_rig_ik_name = 'CarRigIK'
    car_rig_suspension_sim_name = 'CarRigSuspensionSim'

    # CarRig Armature with Bones (parent/child/sibling) structure:
    #   MASTER
    #       car.roll
    #           axle.fl
    #               steer.fl
    #                   wheel.fl
    #           axle.fr
    #           axle.bl
    #           axle.br

    set_cursor_to_objects(object_names=[tire_fl_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['steer.fl', 'wheel.fl', 'hub.fl'])

    set_cursor_to_objects(object_names=[tire_fr_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['steer.fr', 'wheel.fr', 'hub.fr'])

    set_cursor_to_objects(object_names=[tire_bl_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['rotation.bl', 'wheel.bl', 'hub.bl'])

    set_cursor_to_objects(object_names=[tire_br_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['rotation.br', 'wheel.br', 'hub.br'])

    # for vert in bpy.data.objects['CarRigGroundDetect'].data.vertices:
    #     #logger.info(str(vert.co.x) + ' ' + str(vert.co.y) + ' ' + str(vert.co.z))
    #     logger.info(vert.co)

    # Pos x = Left side, Neg X = Right side
    # Neg y = Front side, Pos y = Back side

    ground_detect_obj_loc = bpy.data.objects[car_rig_ground_detect_name].location

    # Right Side, Back
    br_vert = bpy.data.objects[car_rig_ground_detect_name].data.vertices[0]
    br_vert.co.xy = bpy.data.objects[tire_br_name].location.xy - ground_detect_obj_loc.xy
    # Left Side, Front
    fl_vert = bpy.data.objects[car_rig_ground_detect_name].data.vertices[1]
    fl_vert.co.xy = bpy.data.objects[tire_fl_name].location.xy - ground_detect_obj_loc.xy
    # Right Side, Front
    fr_vert = bpy.data.objects[car_rig_ground_detect_name].data.vertices[2]
    fr_vert.co.xy = bpy.data.objects[tire_fr_name].location.xy - ground_detect_obj_loc.xy
    # Left Side, Back
    bl_vert = bpy.data.objects[car_rig_ground_detect_name].data.vertices[3]
    bl_vert.co.xy = bpy.data.objects[tire_bl_name].location.xy - ground_detect_obj_loc.xy

    # Configure the front and back axles
    set_cursor_to_objects(object_names=[tire_bl_name, tire_br_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['rear.rotation', 'axle.bl', 'axle.br'])

    set_cursor_to_objects(object_names=[tire_fl_name, tire_fr_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['front.rotation', 'steer.pointer', 'axle.fl', 'axle.fr'])

    set_cursor_to_objects(object_names=[tire_fl_name, tire_fr_name, tire_bl_name, tire_br_name])
    snap_bones_to_cursor(
        armature_object_name=car_rig_name,
        bone_names_list=['MASTER', 'car.roll'])

    # CarRig Armature with Bones (parent/child/sibling) structure:
    #   MainControl
    #   IK
    #       DriftControl
    #       IKTarget

    # # Configure the RigIK
    set_cursor_to_objects(object_names=[tire_bl_name, tire_br_name])
    bpy.context.scene.cursor_location.z = 0
    snap_bones_to_cursor(
        armature_object_name=car_rig_ik_name,
        bone_names_list=['IKTarget'])

    # Configure the CarRIgSuspensionSim
    # The last vertex (index 8) in the CarRIgSuspensionSim mesh is the median of the mesh
    set_cursor_to_objects(object_names=[tire_fl_name, tire_fr_name, tire_bl_name, tire_br_name])
    y_diff = bpy.data.objects[car_rig_suspension_sim_name].data.vertices[
                 8].co.y - bpy.context.scene.cursor_location.y
    for vert in bpy.data.objects[car_rig_suspension_sim_name].data.vertices:
        vert.co.y -= y_diff

    move_group_to_layer(group_name=car_body_group_name, layer_id=rig_layer_id)
    car_body_obj_name = bpy.data.groups[car_body_group_name].objects[0].name
    if not car_body_obj_name.endswith('Body'):
        logger.vinfo('car_body_group_name', car_body_group_name)
        logger.vinfo('car_body_obj_name', car_body_obj_name)
        assert False  # car_body_obj_name is incorrect (does not end with Body)

    for ob in bpy.data.groups[car_body_group_name].objects:
        set_object_parent_bone(
            child_object_name=ob.name,
            armature_object_name=car_rig_name,
            bone_name='car.roll')

    # Hide objects
    # bpy.data.objects['CarRigGround'].hide = True
    # bpy.data.objects[car_rig_ik_name].hide = True
    # bpy.data.objects[car_rig_suspension_sim_name].hide = True


    move_group_to_layer(
        group_name=car_wheels_group_name,
        layer_id=rig_layer_id)
    set_object_parent_bone(
        child_object_name=tire_fl_name,
        armature_object_name=car_rig_name,
        bone_name='wheel.fl')
    set_object_parent_bone(
        child_object_name=tire_fr_name,
        armature_object_name=car_rig_name,
        bone_name='wheel.fr')
    set_object_parent_bone(
        child_object_name=tire_bl_name,
        armature_object_name=car_rig_name,
        bone_name='wheel.bl')
    set_object_parent_bone(
        child_object_name=tire_br_name,
        armature_object_name=car_rig_name,
        bone_name='wheel.br')

    # Reset child_of constraints of bones
    # IMPORTANT: THERE WILL BE NO EFFECT WITHOUT POSE POSITION == 'POSE'
    set_pose_position(armature_object_name=car_rig_name, pose_position='POSE')

    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='hub.fl',
        constraint_name='Child Of')
    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='hub.fr',
        constraint_name='Child Of')
    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='hub.bl',
        constraint_name='Child Of')
    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='hub.br',
        constraint_name='Child Of')

    # IMPORTANT: DO NOT SET THE POSE POSITION BACK TO 'REST'

    # Reset child_of constraints of suspension sim
    bpy.data.objects[car_rig_suspension_sim_name].constraints['Child Of'].mute = False
    clear_and_set_inverse_object_constraint_child_of(
        object_name=car_rig_suspension_sim_name,
        constraint_name='Child Of')

    # Reset child_of constraints of MASTER bone
    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='MASTER',
        constraint_name='Child Of')

    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_name,
        bone_name='MASTER',
        constraint_name='Child Of.001')

    clear_and_set_inverse_bone_constraint_child_of(
        armature_object_name=car_rig_ik_name,
        bone_name='IK',
        constraint_name='Child Of')

    mute_bone_constraint(
        armature_object_name=car_rig_ik_name,
        bone_name='MainControl',
        constraint_name='Follow Path',
        mute=False)

    show_viewport_object_modifier(
        object_name=car_rig_ground_detect_name,
        modifier_name='Shrinkwrap',
        show_viewport=True)

    # Adjust the rig names
    bpy.data.objects[car_rig_name].name = car_rig_prefix + car_rig_name
    bpy.data.objects[car_rig_ik_name].name = car_rig_prefix + car_rig_ik_name
    bpy.data.objects[car_rig_suspension_sim_name].name = car_rig_prefix + car_rig_suspension_sim_name
    bpy.data.objects[car_rig_ground_detect_name].name = car_rig_prefix + car_rig_ground_detect_name

    # Adjust scale of car body
    for child in bpy.data.groups[car_body_group_name].objects:
        set_mode(active_object_name=child.name,
                 mode='OBJECT',
                 configure_scene_for_basic_ops=False)
        bpy.ops.object.transform_apply(scale=True)

        obj = bpy.data.objects[child.name]

    # THIS IS IMPORTANT (Otherwise the body and the wheels are not loaded while importing the rig)
    car_rig_group = bpy.data.groups.get(car_rig_name)
    for child in bpy.data.groups[car_body_group_name].objects:
        obj = bpy.data.objects[child.name]
        car_rig_group.objects.link(obj)

    for child in bpy.data.groups[car_wheels_group_name].objects:
        obj = bpy.data.objects[child.name]
        car_rig_group.objects.link(obj)
def prepare_model_for_rigging(tire_fl_name, tire_fr_name, tire_bl_name, tire_br_name, check_axle=True):
    """
    This method automates the following steps

    Add an empty between the front wheels
      Select both front wheels (shift select)
      Shift + s / Cursor to selected
      Shift + A / Empty

    Parent everything to the empty
      Select everything, select the empty, ctrl + p / Object

    Clear the location of the empty
      Select the empty, alt + g

    Move the empty, so the wheels touch the x-y plane

    Clear parents
      Select everything, alt + p / clear and keep transformation

    Delete the empty

    Apply rotation and scale
      select everything, ctlr + a / Rotation & Scale


    """

    # for obj in bpy.data.objects:
    #     logger.vinfo('obj.name', obj.name)

    check_wheel_origins(tire_fl_name, tire_fr_name, tire_bl_name, tire_br_name)

    recenter_objects_if_necessary(tire_fl_name, tire_fr_name, tire_bl_name, tire_br_name)

    front_axle_center = compute_xy_axle_center(
        tire_fl_name, tire_fr_name, check_axle=check_axle)
    front_axle_center_xy0 = front_axle_center.xyz
    front_axle_center_xy0.z = 0

    back_axle_center = compute_xy_axle_center(tire_bl_name, tire_br_name, check_axle=check_axle)
    back_axle_center_xy0 = back_axle_center.xyz
    back_axle_center_xy0.z = 0

    car_dir_vec = front_axle_center_xy0 - back_axle_center_xy0

    # The car will look along the negative y axis (in blenders front view, the front side of the car is visible)
    future_alignment_axis = Vector((0, -1, 0))

    angle_between_rad = -car_dir_vec.angle(future_alignment_axis)
    angle_between_degrees = degrees(angle_between_rad)
    logger.vinfo('front_axle_center_xy0', front_axle_center_xy0)
    logger.vinfo('future_alignment_axis', future_alignment_axis)
    logger.vinfo('angle_between_degrees', angle_between_degrees)

    rot_mat = Matrix.Rotation(angle_between_rad, 4, 'Z')

    # rotate all objects
    for obj in bpy.data.objects:
        obj.matrix_world = rot_mat * obj.matrix_world

    rotated_front_axle_center_xy = rot_mat * front_axle_center_xy0

    # translate all objects
    # assert rotated_front_axle_center_xy.x == 0 or rotated_front_axle_center_xy.y == 0
    some_tire = bpy.data.objects[tire_fl_name]
    tire_height = some_tire.dimensions[2] / some_tire.scale[2]
    trans_vec = Vector((rotated_front_axle_center_xy.x,
                        rotated_front_axle_center_xy.y,
                        front_axle_center.z - tire_height / 2.0))

    logger.vinfo('front_axle_center.z', front_axle_center.z)
    logger.vinfo('tire_height/ 2.0', tire_height/ 2.0)
    logger.vinfo('trans_vec', trans_vec)

    for obj in bpy.data.objects:
        obj.location -= trans_vec

    # Apply rotation and scale
    for obj in bpy.data.objects:
        set_mode(active_object_name=obj.name,
                 mode='OBJECT',
                 configure_scene_for_basic_ops=False)
        bpy.ops.object.transform_apply(rotation=True, scale=True)