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