Files
blender-portable-repo/scripts/addons/goo_physics/operators.py
T
Nathan 6c3b78075b work: restore shift+spacebar for media play/pause
maybe put in maya config? idk what funiman's preference is
2026-05-29 14:58:59 -06:00

1753 lines
61 KiB
Python

from .functions import *
#
# OPERATORS
#
class GP_OT_refresh_collision_collection(Operator):
bl_idname = "goophys.refresh_collision_collection"
bl_label = "Refresh Collision Objects"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Moves all collision enabled objects into the collider collections"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
add_obs = []
for ob in context.view_layer.objects:
if ob.type in ["MESH", "CURVES"]:
for mod in ob.modifiers:
if mod.type == "COLLISION":
add_obs.append(ob)
for i in range(len(collide_colls[0].objects)):
collide_colls[0].objects.unlink(collide_colls[0].objects[0])
for ob in add_obs:
collide_colls[0].objects.link(ob)
return {"FINISHED"}
class GP_OT_limit_to_type(Operator):
bl_idname = "goophys.limit_to_type"
bl_label = "Limit Collision to Physics Type"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Sets the active bones/chains to only collide with objects in the matching type collection"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
pb = context.active_pose_bone
if pb.gp_has_cl_physics:
pb.gp_chain_limit_collision = not pb.gp_chain_limit_collision
elif pb.gp_has_gn_physics:
pb.gp_chain_limit_collision = not pb.gp_chain_limit_collision
elif pb.gp_has_sb_physics:
pb.gp_sim_limit_collision = not pb.gp_sim_limit_collision
elif pb.gp_has_jg_physics:
pb.gp_sim_limit_collision = not pb.gp_sim_limit_collision
return {"FINISHED"}
class GP_OT_add_bone_map_preset(Operator):
bl_idname = "goophys.add_bone_map_preset"
bl_label = "Add Bone Map Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds a new Bone Map Preset from all physics bones settings"
name: bpy.props.StringProperty(
name="Preset Name",
description="Name of the preset",
default="",
)
description: bpy.props.StringProperty(
name="Preset Description",
description="Description of the preset",
default="",
)
def execute(self, context):
if self.name == "":
self.report(
{"ERROR"},
"You need to add a preset name to save the preset",
)
return {"CANCELLED"}
if context.active_object.type != "ARMATURE":
self.report(
{"ERROR"},
"Active object is not an armature",
)
return {"CANCELLED"}
p_dat = {}
p_dat["Name"] = self.name
p_dat["Description"] = self.description
p_dat["Chain Settings"] = []
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets"
if os.path.exists(user_preset_fp) == False:
os.makedirs(bpy.path.abspath(str(user_preset_fp)))
for pb in context.active_object.pose.bones:
b_dat = {}
s_dat = {}
if pb.gp_has_cl_physics:
phys_type = "CLOTH"
s_dat["gp_chain_speed"] = pb.gp_chain_speed
s_dat["gp_chain_mass"] = pb.gp_chain_mass
s_dat["gp_chain_air_dampening"] = pb.gp_chain_air_dampening
s_dat["gp_chain_bend_damping"] = pb.gp_chain_bend_damping
s_dat["gp_chain_collision_dist"] = pb.gp_chain_collision_dist
s_dat["gp_chain_gravity"] = pb.gp_chain_gravity
s_dat["gp_sim_influence"] = pb.gp_sim_influence
elif pb.gp_has_sb_physics:
phys_type = "SOFT_BODY"
s_dat["gp_sim_speed"] = pb.gp_sim_speed
s_dat["gp_sim_friction"] = pb.gp_sim_friction
s_dat["gp_sim_mass"] = pb.gp_sim_mass
s_dat["gp_sim_gravity"] = pb.gp_sim_gravity
s_dat["gp_sim_stiffness"] = pb.gp_sim_stiffness
s_dat["gp_sim_damping"] = pb.gp_sim_damping
s_dat["gp_sim_strength"] = pb.gp_sim_strength
s_dat["gp_sim_influence"] = pb.gp_sim_influence
elif pb.gp_has_jg_physics:
phys_type = "JIGGLE"
s_dat["gp_sim_speed"] = pb.gp_sim_speed
s_dat["gp_sim_friction"] = pb.gp_sim_friction
s_dat["gp_sim_mass"] = pb.gp_sim_mass
s_dat["gp_sim_gravity"] = pb.gp_sim_gravity
s_dat["gp_sim_stiffness"] = pb.gp_sim_stiffness
s_dat["gp_sim_damping"] = pb.gp_sim_damping
s_dat["gp_sim_influence"] = pb.gp_sim_influence
elif pb.gp_has_gn_physics:
phys_type = "GEO_NODES"
s_dat["gp_chain_velocity"] = pb.gp_chain_velocity
s_dat["gp_chain_dampening"] = pb.gp_chain_dampening
s_dat["gp_chain_gravity"] = pb.gp_chain_gravity
s_dat["gp_chain_root_falloff"] = pb.gp_chain_root_falloff
s_dat["gp_chain_stiffness"] = pb.gp_chain_stiffness
s_dat["gp_chain_stiff_end_fac"] = pb.gp_chain_stiff_end_fac
s_dat["gp_chain_stiff_vel_fac"] = pb.gp_chain_stiff_vel_fac
s_dat["gp_chain_stiff_vel_min"] = pb.gp_chain_stiff_vel_min
s_dat["gp_chain_stiff_vel_max"] = pb.gp_chain_stiff_vel_max
s_dat["gp_chain_wind_strength"] = pb.gp_chain_wind_strength
s_dat["gp_chain_wind_noise_strength"] = pb.gp_chain_wind_noise_strength
s_dat["gp_chain_wind_noise_scale"] = pb.gp_chain_wind_noise_scale
s_dat["gp_chain_collision_dist"] = pb.gp_chain_collision_dist
s_dat["gp_chain_collision_friction"] = pb.gp_chain_collision_friction
s_dat["gp_sim_influence"] = pb.gp_sim_influence
else:
continue
b_dat["Bone Name"] = pb.name
b_dat["Settings"] = s_dat
b_dat["Chain ID"] = pb.gp_chain_id
b_dat["Chain Type"] = phys_type
p_dat["Chain Settings"].append(b_dat)
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "bone_map_presets.json"
presets_data = {}
if os.path.exists(preset_fp):
presets_data = json.load(open(str(preset_fp)))
presets_data[self.name.replace(" ", "").upper()] = p_dat
preset_file = open(preset_fp, "w")
json.dump(presets_data, preset_file, indent=1, ensure_ascii=True)
preset_file.close()
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=800)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "name")
row = layout.row()
row.prop(self, "description")
class GP_OT_delete_bone_map_preset(Operator):
bl_idname = "goophys.delete_bone_map_preset"
bl_label = "Delete Bone Map Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Deletes the active preset"
def execute(self, context):
prefs = context.preferences.addons["goo_physics"].preferences
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "bone_map_presets.json"
presets_enum = prefs.bm_presets
presets_data = {}
if os.path.exists(preset_fp):
presets_data = json.load(open(str(preset_fp)))
if presets_enum.replace(" ", "").upper() in presets_data.keys():
del presets_data[presets_enum.replace(" ", "").upper()]
preset_file = open(preset_fp, "w")
json.dump(presets_data, preset_file, indent=1, ensure_ascii=True)
preset_file.close()
presets_enum = 0
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400, confirm_text="Delete Preset? Cannot be recovered")
class GP_OT_apply_bone_map_preset(Operator):
bl_idname = "goophys.apply_bone_map_preset"
bl_label = "Apply Bone Map Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Applys the active preset to the select bones/bone chains"
def execute(self, context):
if context.active_object.type != "ARMATURE":
self.report(
{"ERROR"},
"Active object is not an armature",
)
return {"CANCELLED"}
coll, collide_colls, control_coll, bake_coll = ensure_collections()
preset_data = get_bone_map_preset_data()
used_bones = []
for b_dat in preset_data:
used_bones.append(b_dat["Bone Name"])
phys_bones = []
for pb in context.active_object.pose.bones:
if pb.name not in used_bones:
continue
if pb.gp_has_cl_physics:
phys_bones.append(pb)
elif pb.gp_has_sb_physics:
phys_bones.append(pb)
elif pb.gp_has_jg_physics:
phys_bones.append(pb)
elif pb.gp_has_gn_physics:
phys_bones.append(pb)
sim_chains = get_partial_selected_bone_chains(phys_bones, include_all=True)
if len(sim_chains) > 0:
clear_sim_chains(sim_chains)
fail_bones = apply_bone_map_preset(preset_data, coll, collide_colls)
if len(fail_bones) > 0:
self.report(
{"ERROR"},
"These bones were missing from the active armature when applying the Bone Map Preset - " + str(fail_bones),
)
return {"FINISHED"}
def draw(self, context):
layout = self.layout
layout.label(text="WARNING:")
layout.label(text="Applying a Bone Map Preset will remove all goo physics chains and objects used by the preset")
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=600, confirm_text="Apply Bone Map Preset?")
class GP_OT_add_preset(Operator):
bl_idname = "goophys.add_preset"
bl_label = "Add Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds a new preset from the active bones settings"
name: bpy.props.StringProperty(
name="Preset Name",
description="Name of the preset",
default="",
)
description: bpy.props.StringProperty(
name="Preset Description",
description="Description of the preset",
default="",
)
chain_preset: bpy.props.BoolProperty(
name="Chain Preset",
description="This preset will save the value profile of the who chain for the bone settings",
default=True,
)
def execute(self, context):
if self.name == "":
self.report(
{"ERROR"},
"You need to add a preset name to save the preset",
)
return {"CANCELLED"}
resample_amount = 50
smooth_iters = 5
pb = context.active_pose_bone
p_dat = {}
if self.chain_preset and pb.gp_has_jg_physics == False:
p_dat["Name"] = self.name.replace("_CHAIN", "") + "_CHAIN"
else:
p_dat["Name"] = self.name
p_dat["Description"] = self.description
s_dat = {}
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets"
if os.path.exists(user_preset_fp) == False:
os.makedirs(bpy.path.abspath(str(user_preset_fp)))
sim_chains = get_partial_selected_bone_chains(context.selected_pose_bones_from_active_object, only_active=True)
for chain in sim_chains:
if pb.gp_has_cl_physics:
s_dat["gp_chain_speed"] = pb.gp_chain_speed
s_dat["gp_chain_mass"] = pb.gp_chain_mass
s_dat["gp_sim_gravity"] = pb.gp_chain_gravity
s_dat["gp_chain_air_dampening"] = pb.gp_chain_air_dampening
s_dat["gp_chain_bend_damping"] = pb.gp_chain_bend_damping
s_dat["gp_chain_collision_dist"] = pb.gp_chain_collision_dist
if self.chain_preset:
s_dat["gp_sim_influence"] = []
for b_dat in chain:
bo = b_dat[0].pose.bones[b_dat[1]]
s_dat["gp_sim_influence"].append(bo.gp_sim_influence)
else:
s_dat["gp_sim_influence"] = pb.gp_sim_influence
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "cloth_presets.json"
elif pb.gp_has_sb_physics:
if self.chain_preset:
s_dat["gp_sim_speed"] = []
s_dat["gp_sim_friction"] = []
s_dat["gp_sim_mass"] = []
s_dat["gp_sim_gravity"] = []
s_dat["gp_sim_stiffness"] = []
s_dat["gp_sim_damping"] = []
s_dat["gp_sim_strength"] = []
s_dat["gp_sim_influence"] = []
for b_dat in chain:
bo = b_dat[0].pose.bones[b_dat[1]]
s_dat["gp_sim_speed"].append(bo.gp_sim_speed)
s_dat["gp_sim_friction"].append(bo.gp_sim_friction)
s_dat["gp_sim_mass"].append(bo.gp_sim_mass)
s_dat["gp_sim_gravity"].append(bo.gp_sim_gravity)
s_dat["gp_sim_stiffness"].append(bo.gp_sim_stiffness)
s_dat["gp_sim_damping"].append(bo.gp_sim_damping)
s_dat["gp_sim_strength"].append(bo.gp_sim_strength)
s_dat["gp_sim_influence"].append(bo.gp_sim_influence)
else:
s_dat["gp_sim_speed"] = pb.gp_sim_speed
s_dat["gp_sim_friction"] = pb.gp_sim_friction
s_dat["gp_sim_mass"] = pb.gp_sim_mass
s_dat["gp_sim_gravity"] = pb.gp_sim_gravity
s_dat["gp_sim_stiffness"] = pb.gp_sim_stiffness
s_dat["gp_sim_damping"] = pb.gp_sim_damping
s_dat["gp_sim_strength"] = pb.gp_sim_strength
s_dat["gp_sim_influence"] = pb.gp_sim_influence
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "soft_body_presets.json"
elif pb.gp_has_jg_physics:
s_dat["gp_sim_speed"] = pb.gp_sim_speed
s_dat["gp_sim_friction"] = pb.gp_sim_frictio
s_dat["gp_sim_mass"] = pb.gp_sim_mass
s_dat["gp_sim_gravity"] = pb.gp_sim_gravity
s_dat["gp_sim_stiffness"] = pb.gp_sim_stiffness
s_dat["gp_sim_damping"] = pb.gp_sim_damping
s_dat["gp_sim_influence"] = pb.gp_sim_influence
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "jiggle_spring_presets.json"
elif pb.gp_has_gn_physics:
s_dat["gp_chain_velocity"] = pb.gp_chain_velocity
s_dat["gp_chain_dampening"] = pb.gp_chain_dampening
s_dat["gp_chain_gravity"] = pb.gp_chain_gravity
s_dat["gp_chain_root_falloff"] = pb.gp_chain_root_falloff
s_dat["gp_chain_stiffness"] = pb.gp_chain_stiffness
s_dat["gp_chain_stiff_end_fac"] = pb.gp_chain_stiff_end_fac
s_dat["gp_chain_stiff_vel_fac"] = pb.gp_chain_stiff_vel_fac
s_dat["gp_chain_stiff_vel_min"] = pb.gp_chain_stiff_vel_min
s_dat["gp_chain_stiff_vel_max"] = pb.gp_chain_stiff_vel_max
s_dat["gp_chain_wind_strength"] = pb.gp_chain_wind_strength
s_dat["gp_chain_wind_noise_strength"] = pb.gp_chain_wind_noise_strength
s_dat["gp_chain_wind_noise_scale"] = pb.gp_chain_wind_noise_scale
s_dat["gp_chain_collision_dist"] = pb.gp_chain_collision_dist
s_dat["gp_chain_collision_friction"] = pb.gp_chain_collision_friction
if self.chain_preset:
s_dat["gp_sim_influence"] = []
for b_dat in chain:
bo = b_dat[0].pose.bones[b_dat[1]]
s_dat["gp_sim_influence"].append(bo.gp_sim_influence)
else:
s_dat["gp_sim_influence"] = pb.gp_sim_influence
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "geo_nodes_presets.json"
# Resample the values profile and smooth the inbetween values
for k in s_dat.keys():
if isinstance(s_dat[k], list) == False:
continue
vals = np.array(s_dat[k], dtype=np.float32)
if vals.shape[0] == 1:
s_dat[k] = [float(vals[0]) for i in range(resample_amount)]
else:
source_t_vals = np.arange(vals.shape[0], dtype=np.float32) / max([1, (vals.shape[0] - 1)])
t_vals = np.arange(resample_amount, dtype=np.float32) / (resample_amount - 1)
tar_inds = np.searchsorted(source_t_vals, t_vals)
p_inds = tar_inds - 1
tar_inds[0] = 1
p_inds[0] = 0
tar_inds[tar_inds < 0] = 0
tar_inds[tar_inds > source_t_vals.shape[0]] = 0
p_inds[p_inds < 0] = 0
p_inds[p_inds > source_t_vals.shape[0]] = 0
l_range = source_t_vals[tar_inds] - source_t_vals[p_inds]
l_fac = (t_vals - source_t_vals[p_inds]) / l_range
lerp_vals = vals[p_inds] + (vals[tar_inds] - vals[p_inds]) * l_fac
inds = np.arange(lerp_vals.shape[0]-2, dtype=np.int32) + 1
next_inds = inds + 1
prev_inds = inds - 1
for i in range(smooth_iters):
neighbors = (lerp_vals[next_inds] + lerp_vals[prev_inds]) * 0.5
lerp_vals[inds] = lerp_vals[inds] * 0.5 + neighbors * 0.5
s_dat[k] = lerp_vals.tolist()
p_dat["Settings"] = s_dat
presets_data = {}
if os.path.exists(preset_fp):
presets_data = json.load(open(str(preset_fp)))
presets_data[self.name.replace(" ", "").upper()] = p_dat
preset_file = open(preset_fp, "w")
json.dump(presets_data, preset_file, indent=1, ensure_ascii=True)
preset_file.close()
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=800)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "name")
row = layout.row()
row.prop(self, "description")
if context.active_pose_bone.gp_has_jg_physics == False:
row = layout.row()
row.prop(self, "chain_preset")
class GP_OT_delete_preset(Operator):
bl_idname = "goophys.delete_preset"
bl_label = "Delete Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Deletes the active preset"
def execute(self, context):
prefs = context.preferences.addons["goo_physics"].preferences
pb = context.active_pose_bone
if pb.gp_has_cl_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "cloth_presets.json"
presets_enum = prefs.cl_presets
elif pb.gp_has_sb_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "soft_body_presets.json"
presets_enum = prefs.sb_presets
elif pb.gp_has_jg_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "jiggle_spring_presets.json"
presets_enum = prefs.jg_presets
elif pb.gp_has_gn_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "geo_nodes_presets.json"
presets_enum = prefs.gn_presets
presets_data = {}
if os.path.exists(preset_fp):
presets_data = json.load(open(str(preset_fp)))
if presets_enum.replace(" ", "").upper() in presets_data.keys():
del presets_data[presets_enum.replace(" ", "").upper()]
preset_file = open(preset_fp, "w")
json.dump(presets_data, preset_file, indent=1, ensure_ascii=True)
preset_file.close()
presets_enum = 0
return {"FINISHED"}
def invoke(self, context, event):
prefs = context.preferences.addons["goo_physics"].preferences
pb = context.active_pose_bone
if pb.gp_has_cl_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "cloth_presets.json"
presets_enum = prefs.cl_presets
elif pb.gp_has_sb_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "soft_body_presets.json"
presets_enum = prefs.sb_presets
elif pb.gp_has_jg_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "jiggle_spring_presets.json"
presets_enum = prefs.jg_presets
elif pb.gp_has_gn_physics:
preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "geo_nodes_presets.json"
presets_enum = prefs.gn_presets
if presets_enum in ["DEFAULTSOFTBODY",
"DEFAULTGEONODES",
"DEFAULTJIGGLE",
"DEFAULTCLOTH",
"FLOPPYCLOTH",
"HAIRFRINGE",
"HAIRSIDE",
"HAIRPONYTAIL",
"JIGGLELOOSE",
"JIGGLESTIFF",
"SLOWMO",
"GOOFLAPPY",
"GOOSTIFF",
"METAL/JEWELRY",
"GOOSIM",
"STIFFCLOTH",
"FLOPPYCLOTH",]:
self.report(
{"ERROR"},
"Cannot delete the base presets",
)
return {"CANCELLED"}
return context.window_manager.invoke_props_dialog(self, width=400, confirm_text="Delete Preset? Cannot be recovered")
class GP_OT_apply_preset(Operator):
bl_idname = "goophys.apply_preset"
bl_label = "Apply Preset"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Applys the active preset to the select bones/bone chains"
def execute(self, context):
pb = context.active_pose_bone
preset_data = get_preset_data()
if preset_data is None:
self.report(
{"ERROR"},
"Active Pose Bone has no physics",
)
return {"CANCELLED"}
apply_preset(preset_data, pb)
return {"FINISHED"}
class GP_OT_select_dynamic_bones(Operator):
bl_idname = "goophys.select_dynamic_bones"
bl_label = "Select Physics Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Selects all bones with physics currently on them"
def execute(self, context):
for pb in context.visible_pose_bones:
if pb.gp_has_sb_physics or pb.gp_has_cl_physics or pb.gp_has_gn_physics or pb.gp_has_jg_physics:
set_bone_sel(pb, True)
return {"FINISHED"}
class GP_OT_select_last_dynamic_bone(Operator):
bl_idname = "goophys.select_last_dynamic_bones"
bl_label = "Select Last In Chain"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Selects last bone in dynamic chain"
def execute(self, context):
for pb in context.visible_pose_bones:
if pb.gp_has_sb_physics or pb.gp_has_cl_physics or pb.gp_has_gn_physics or pb.gp_has_jg_physics:
not_last = False
for child in pb.children_recursive:
if child.gp_has_sb_physics or child.gp_has_cl_physics or child.gp_has_gn_physics or pb.gp_has_jg_physics:
not_last = True
set_bone_sel(pb, not not_last)
return {"FINISHED"}
class GP_OT_add_geonodes_physics_to_selected(Operator):
bl_idname = "goophys.add_geonodes_physics_to_selected"
bl_label = "Add Physics to Selected Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds Geo Nodes dynamic meshes to selected bone chains"
def execute(self, context):
prefs = context.preferences.addons["goo_physics"].preferences
coll, collide_colls, control_coll, bake_coll = ensure_collections()
sim_chains = get_bone_chains(context.selected_pose_bones)
if len(sim_chains) == 0:
self.report(
{"ERROR"},
"None of the bones you selected can be simmed. The bones need a parent to work",
)
# remove existing physics from selected bones
clear_sim_chains(sim_chains)
# cur_frame = context.scene.frame_current
context.scene.frame_set(context.scene.frame_start)
sim_geo_obs = create_geonodes_chains(
sim_chains, coll, collide_colls, prefs.gp_divisions
)
preset_data = get_preset_data()
if preset_data is not None:
apply_preset(preset_data, context.active_pose_bone)
return {"FINISHED"}
class GP_OT_add_soft_physics_to_selected(Operator):
bl_idname = "goophys.add_soft_physics_to_selected"
bl_label = "Add Physics to Selected Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds Soft Body dynamic meshes to selected bone chains"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
sim_chains = get_bone_chains(context.selected_pose_bones)
if len(sim_chains) == 0:
self.report(
{"ERROR"},
"None of the bones you selected can be simmed. The bones need a parent to work",
)
# remove existing physics from selected bones
clear_sim_chains(sim_chains)
sim_soft_obs = create_soft_chains(sim_chains, coll, collide_colls)
# cur_frame = context.scene.frame_current
context.scene.frame_set(context.scene.frame_start)
preset_data = get_preset_data()
if preset_data is not None:
apply_preset(preset_data, context.active_pose_bone)
return {"FINISHED"}
class GP_OT_add_cloth_physics_to_selected(Operator):
bl_idname = "goophys.add_cloth_physics_to_selected"
bl_label = "Add Physics to Selected Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds Soft Body dynamic meshes to selected bone chains"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
sim_chains = get_bone_chains(context.selected_pose_bones)
if len(sim_chains) == 0:
self.report(
{"ERROR"},
"None of the bones you selected can be simmed. The bones need a parent to work",
)
# remove existing physics from selected bones
clear_sim_chains(sim_chains)
sim_cloth_obs = create_cloth_chains(sim_chains, coll, collide_colls)
# cur_frame = context.scene.frame_current
context.scene.frame_set(context.scene.frame_start)
preset_data = get_preset_data()
if preset_data is not None:
apply_preset(preset_data, context.active_pose_bone)
return {"FINISHED"}
class GP_OT_add_jiggle_physics_to_selected(Operator):
bl_idname = "goophys.add_jiggle_physics_to_selected"
bl_label = "Add Physics to Selected Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds Soft Body dynamic meshes to selected bone chains"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
sim_chains = []
for bo in context.selected_pose_bones:
if bo.parent is not None and bo.bone.use_connect == False:
sim_chains.append([[bo.id_data, bo.name]])
if len(sim_chains) == 0:
self.report(
{"ERROR"},
"None of the bones you selected can be simmed. The bones need a Disconnected Parent (Keep Offset) to work",
)
# remove existing physics from selected bones
clear_sim_chains(sim_chains)
sim_spring_obs = create_spring_chains(sim_chains, coll, collide_colls)
# cur_frame = context.scene.frame_current
context.scene.frame_set(context.scene.frame_start)
preset_data = get_preset_data()
if preset_data is not None:
apply_preset(preset_data, context.active_pose_bone)
return {"FINISHED"}
class GP_OT_remove_physics_from_selected(Operator):
bl_idname = "goophys.remove_physics_from_selected"
bl_label = "Remove Physics from Selected Bones"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Removes Soft Body dynamic meshes assigned to the selected bones"
def execute(self, context):
sim_chains = get_partial_selected_bone_chains(context.selected_pose_bones, include_all=True)
if len(sim_chains) == 0:
return {"FINISHED"}
clear_sim_chains(sim_chains)
return {"FINISHED"}
class GP_OT_sync_frame_range(Operator):
bl_idname = "goophys.sync_frame_range"
bl_label = "Sync Frame Range"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Sync all soft body sim objects to the current frame range"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
if coll:
for ob in coll.objects:
mod = ob.modifiers.get("GP_SoftBody Sim")
if mod:
if context.scene.use_preview_range:
mod.point_cache.frame_start = context.scene.frame_preview_start
mod.point_cache.frame_end = context.scene.frame_preview_end
else:
mod.point_cache.frame_start = context.scene.frame_start
mod.point_cache.frame_end = context.scene.frame_end
mod = ob.modifiers.get("GP_Jiggle Sim")
if mod:
if context.scene.use_preview_range:
mod.point_cache.frame_start = context.scene.frame_preview_start
mod.point_cache.frame_end = context.scene.frame_preview_end
else:
mod.point_cache.frame_start = context.scene.frame_start
mod.point_cache.frame_end = context.scene.frame_end
mod = ob.modifiers.get("GP_Cloth Sim")
if mod:
if context.scene.use_preview_range:
mod.point_cache.frame_start = (
context.scene.frame_preview_start
)
mod.point_cache.frame_end = context.scene.frame_preview_end
else:
mod.point_cache.frame_start = context.scene.frame_start
mod.point_cache.frame_end = context.scene.frame_end
return {"FINISHED"}
class GP_OT_refresh_physics_status(Operator):
bl_idname = "goophys.refresh_physics_status"
bl_label = "Refresh Physics Status"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Sync all soft body sim objects to the current frame range"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
if coll:
skips = []
for ob in coll.objects:
if len(ob.gp_bone_coll.coll) == 0:
continue
mod = ob.modifiers.get("GP_SoftBody Sim")
has_sb = mod is not None
if has_sb:
mod.show_viewport = True
ob.location = ob.location
mod = ob.modifiers.get("GP_Jiggle Sim")
has_jp = mod is not None
if has_jp:
mod.show_viewport = True
ob.location = ob.location
mod = ob.modifiers.get("GP_Cloth Sim")
has_cl = mod is not None
if has_cl:
mod.show_viewport = True
ob.location = ob.location
mod = ob.modifiers.get("GP_Nodes Sim")
has_gn = mod is not None
if has_gn:
mod.show_viewport = True
ob.location = ob.location
for c_ob in ob.gp_bone_coll.coll:
if c_ob.rig_object not in bpy.data.objects:
continue
rig = bpy.data.objects[c_ob.rig_object]
if rig not in skips:
skips.append(rig)
if (
rig.animation_data is not None
and rig.animation_data.action is not None
):
clean_action_of_props(rig.animation_data.action)
if c_ob.rig_bone not in rig.pose.bones:
continue
bone = rig.pose.bones[c_ob.rig_bone]
bone.gp_has_sb_physics = has_sb
bone.gp_has_jg_physics = has_jp
bone.gp_has_cl_physics = has_cl
bone.gp_has_gn_physics = has_gn
return {"FINISHED"}
class GP_OT_apply_falloff_to_selected(Operator):
bl_idname = "goophys.apply_falloff_to_selected"
bl_label = "Apply Falloff to Selected"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Applies a linear or quadratic falloff to the sim influence of the selected dynamic chains. The tips of the chains will follow the simulation more and the roots will follow the animation more"
type: bpy.props.IntProperty()
def execute(self, context):
sim_chains = get_partial_selected_bone_chains(context.selected_pose_bones)
for chain in sim_chains:
for p, p_dat in enumerate(chain):
p_dat[0].pose.bones[p_dat[1]].gp_update = False
for chain in sim_chains:
tot_len = len(chain)
for p, p_dat in enumerate(chain):
val = (p + 1) / tot_len
if self.type == 0:
inf = val
elif self.type == 1:
inf = 1 - get_falloff(val)
pb = p_dat[0].pose.bones[p_dat[1]]
pb.gp_sim_influence = inf
track_cons = pb.constraints.get("GP_Track")
if track_cons:
track_cons.influence = inf
for chain in sim_chains:
for p, p_dat in enumerate(chain):
p_dat[0].pose.bones[p_dat[1]].gp_update = True
return {"FINISHED"}
class GP_OT_add_collider(Operator):
bl_idname = "goophys.add_collider"
bl_label = "Add Collider from Selected"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Creates a new collider object and adds to the list"
convex_hull: bpy.props.BoolProperty(name="Convex Hull Colliders", default=False, description="Converts each collider object to a convex hull mesh for simpler collision meshes")
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
link_fp = get_nodes_fp()
comb_ob_ng = bpy.data.node_groups.get("GooPhysics_CombineObjects")
if comb_ob_ng is None:
with bpy.data.libraries.load(
str(link_fp), relative=True
) as (data_from, data_to):
data_to.node_groups = [
nt
for nt in data_from.node_groups
if nt in ["GooPhysics_CombineObjects"]
]
comb_ob_ng = data_to.node_groups[0]
collider_obs = []
for ob in context.selected_objects:
if ob.type == "MESH":
collider_obs.append(ob)
sel_obs_name = "GP_Selected_Collision Objects"
collider_object = create_collider_object(
collider_obs, sel_obs_name, collide_colls[0], comb_ob_ng, convex_hull=self.convex_hull
)
collider_object.display_type = "WIRE"
collider_object.data.update()
collider_object.modifiers.new("Collision", "COLLISION")
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=400)
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, "convex_hull")
class GP_OT_add_wind_object(Operator):
bl_idname = "goophys.add_wind_objects"
bl_label = "Add Wind Controller to Chains"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds wind object to selected chains"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
wind_emp = bpy.data.objects.new("GP_Wind_Controller_(Scale_for_Power)", None)
wind_emp.empty_display_size = 1.25
wind_emp.empty_display_type = "SINGLE_ARROW"
control_coll.objects.link(wind_emp)
wind_loc = (0,0,5)
if context.active_pose_bone is not None:
wind_loc = context.active_pose_bone.id_data.matrix_world @ context.active_pose_bone.head
wind_loc[2] += 0.5
wind_emp.location = wind_loc
wind_emp.rotation_euler = (-1.5708,0,0)
wind_emp.select_set(True)
for pb in context.selected_pose_bones:
if pb.gp_has_gn_physics:
ob = bpy.data.objects.get(pb.gp_sim_object)
if ob:
mod = ob.modifiers.get("GP_Nodes Sim")
if mod:
set_geo_nodes_input(mod, "Wind Object", wind_emp)
ob.location = ob.location
return {"FINISHED"}
class GP_OT_remove_wind_object(Operator):
bl_idname = "goophys.remove_wind_objects"
bl_label = "Remove Wind Controller from Chains"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Removes the set wind object from selected chains"
def execute(self, context):
coll = bpy.data.collections.get("Goo Physics Controllers")
if coll is not None:
for pb in context.selected_pose_bones:
if pb.gp_has_gn_physics:
ob = bpy.data.objects.get(pb.gp_sim_object)
if ob:
mod = ob.modifiers.get("GP_Nodes Sim")
if mod:
set_geo_nodes_input(mod, "Wind Object", None)
ob.location = ob.location
return {"FINISHED"}
class GP_OT_copy_active_wind_controller(Operator):
bl_idname = "goophys.copy_active_wind_controller"
bl_label = "Copy Wind Controller to Selected Chains"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Adds the active chains wind object to the selected chains"
def execute(self, context):
coll = bpy.data.collections.get("Goo Physics Controllers")
if coll is not None:
wind_emp = None
if context.active_pose_bone.gp_has_gn_physics:
ob = bpy.data.objects.get(context.active_pose_bone.gp_sim_object)
if ob:
mod = ob.modifiers.get("GP_Nodes Sim")
if mod:
wind_emp = get_geo_nodes_input(mod, "Wind Object")
ob.location = ob.location
if wind_emp is not None:
for pb in context.selected_pose_bones:
if pb.gp_has_gn_physics:
ob = bpy.data.objects.get(pb.gp_sim_object)
if ob:
mod = ob.modifiers.get("GP_Nodes Sim")
if mod:
set_geo_nodes_input(mod, "Wind Object", wind_emp)
ob.location = ob.location
return {"FINISHED"}
class GP_OT_bake_physics(Operator):
bl_idname = "goophys.bake_physics"
bl_label = "Bake Bone Physics"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = (
"Bake the bone physics to keyframes on the current armature action"
)
use_magic: bpy.props.BoolProperty(name="Use Magic Empties", default=False, description="Bake the physics of the bones using Magic Empties settings")
sep_action: bpy.props.BoolProperty(name="Bake to New Action", default=False, description="Bake the physics to keyframes in a new action instead of overwriting the existing action")
def modal(self, context, event):
prefs = context.preferences.addons[__package__.split(".")[0]].preferences
context.area.tag_redraw()
frame_start = context.scene.frame_start
frame_end = context.scene.frame_end
if context.scene.use_preview_range:
frame_start = context.scene.frame_preview_start
frame_end = context.scene.frame_preview_end
if context.scene.frame_current == frame_start and self.is_going:
self.bake_physics(context)
return {"FINISHED"}
elif context.scene.frame_current > frame_start:
self.is_going = True
return {"RUNNING_MODAL"}
def execute(self, context):
self.is_going = False
if context.scene.sync_mode != "NONE":
prefs = context.preferences.addons["goo_physics"].preferences
frames = prefs.timeline_sync_frames.split(",")
if len(frames) < 5:
context.scene.frame_set(context.scene.frame_start)
context.window_manager.modal_handler_add(self)
prefs.precaching = True
bpy.ops.screen.animation_play()
return {"RUNNING_MODAL"}
self.bake_physics(context)
return {"FINISHED"}
def bake_physics(self, context):
prefs = context.preferences.addons["goo_physics"].preferences
coll, collide_colls, control_coll, bake_coll = ensure_collections()
coll.hide_viewport = False
for c in collide_colls:
c.hide_viewport = False
control_coll.hide_viewport = False
chain_ids, rigs = [], []
for bo in context.selected_pose_bones:
if (bo.gp_has_sb_physics or bo.gp_has_cl_physics or bo.gp_has_gn_physics) and bo.gp_chain_id not in chain_ids:
if [bo.id_data, bo.gp_chain_id] not in chain_ids:
chain_ids.append([bo.id_data, bo.gp_chain_id])
if bo.id_data not in rigs:
rigs.append(bo.id_data)
sim_chains = get_partial_selected_bone_chains(context.selected_pose_bones)
if len(sim_chains) == 0:
self.report(
{"ERROR"},
"Could not find any bone chains to bake. Try using Refresh Physics Status",
)
frame_start = context.scene.frame_start
frame_end = context.scene.frame_end
if context.scene.use_preview_range:
frame_start = context.scene.frame_preview_start
frame_end = context.scene.frame_preview_end
skip_rigs = []
for c, chain in enumerate(sim_chains):
for b, bo_dat in enumerate(chain):
rig = bo_dat[0]
bo = rig.pose.bones[bo_dat[1]]
if b == 0 and rig not in skip_rigs:
# Unselect bones of rigs so they dont all bake only physics bones bake
skip_rigs.append(rig)
chain_vis = []
rig.hide_viewport = False
rig.hide_select = False
for pb in rig.pose.bones:
set_bone_sel(pb, False)
rig.select_set(True)
context.view_layer.objects.active = rig
# Store bone layer/collection visibilities to restore after bake
coll_vis = []
if bpy.app.version[0] >= 4:
for bo_coll in rig.data.collections:
coll_vis.append(bo_coll.is_visible)
bo_coll.is_visible = True
else:
for l, layer in enumerate(rig.data.layers):
coll_vis.append(rig.data.layers[l])
rig.data.layers[l] = True
chain_vis.append([rig, coll_vis.copy()])
# Set physics object to right frame range for softbody and cloth
phys_ob = bpy.data.objects.get(bo.gp_sim_object)
if phys_ob:
mod = phys_ob.modifiers.get("GP_SoftBody Sim")
if mod:
mod.point_cache.frame_start = frame_start
mod.point_cache.frame_end = frame_end
mod = phys_ob.modifiers.get("GP_Jiggle Sim")
if mod:
mod.point_cache.frame_start = frame_start
mod.point_cache.frame_end = frame_end
mod = phys_ob.modifiers.get("GP_Cloth Sim")
if mod:
mod.point_cache.frame_start = frame_start
mod.point_cache.frame_end = frame_end
phys_ob.location = phys_ob.location
set_bone_sel(bo, True)
#
# BAKING
#
frames = None
if context.scene.sync_mode != "NONE":
fs = sorted([int(i) for i in prefs.timeline_sync_frames.split(",") if i.isdigit()])
frames = []
for f in fs:
if f not in frames:
frames.append(f)
if frame_start not in frames:
frames.insert(0, frame_start)
if frame_end not in frames:
frames.append(frame_end)
for f in frames:
context.scene.timeline_markers.new("GOO_BAKE_MARKER_" + str(f), frame=f)
need_reg_bake = True
if (is_addon_enabled("magic_empties", (1, 0, 0))) and self.use_magic:
try:
bpy.ops.magicemps.bake_to_empties(
use_armature=False,
uses_button=False,
use_marked_frames=context.scene.sync_mode != "NONE",
marker_filter="GOO_BAKE_MARKER_",
)
need_reg_bake = False
except:
need_reg_bake = True
if need_reg_bake:
bpy.ops.object.mode_set(mode="POSE")
b_types = {"POSE"}
for ob in context.selected_objects:
if ob.type != "ARMATURE":
ob.select_set(False)
try:
bpy.ops.nla.bake(
frame_start=frame_start,
frame_end=frame_end,
step=1,
clear_parents=False,
clear_constraints=True,
use_current_action=not self.sep_action,
only_selected=True,
visual_keying=True,
clean_curves=True,
bake_types=b_types,
use_marked_frames=context.scene.sync_mode != "NONE",
marker_filter="GOO_BAKE_MARKER_",
)
except:
bpy.ops.nla.bake(
frame_start=frame_start,
frame_end=frame_end,
step=1,
clear_parents=False,
clear_constraints=True,
use_current_action=not self.sep_action,
only_selected=True,
visual_keying=True,
bake_types=b_types,
use_marked_frames=context.scene.sync_mode != "NONE",
marker_filter="GOO_BAKE_MARKER_",
)
#
#
#
bpy.ops.object.mode_set(mode="POSE")
clear_sim_chains(sim_chains)
for c, c_dat in enumerate(chain_vis):
rig = c_dat[0]
if bpy.app.version[0] >= 4:
for b, bo_coll in enumerate(rig.data.collections):
bo_coll.is_visible = c_dat[1][b]
else:
for l, layer in enumerate(rig.data.layers):
rig.data.layers[l] = c_dat[1][l]
for act in bpy.data.actions:
clean_action_of_props(act)
for marker in [m for m in context.scene.timeline_markers if "GOO_BAKE_MARKER_" in m.name]:
context.scene.timeline_markers.remove(marker)
return {"FINISHED"}
def invoke(self, context, event):
prefs = context.preferences.addons[__package__.split(".")[0]].preferences
prefs.precaching = False
return context.window_manager.invoke_props_dialog(self, width=800)
def draw(self, context):
layout = self.layout
if is_addon_enabled("magic_empties", (1, 0, 0)):
row = layout.row()
row.prop(self, "use_magic")
row = layout.row()
row.prop(self, "sep_action")
class GP_OT_colliders_from_mesh(Operator):
bl_idname = "goophys.colliders_from_mesh"
bl_label = "Add Collider from Selected"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
bl_description = "Creates new collider objects from the deform vertex groups of the selected mesh objects"
def execute(self, context):
coll, collide_colls, control_coll, bake_coll = ensure_collections()
link_fp = get_nodes_fp()
vg_create_ng = bpy.data.node_groups.get("GooPhysics_ColliderVgroupCreate")
if vg_create_ng is None:
with bpy.data.libraries.load(
str(link_fp), relative=True
) as (data_from, data_to):
data_to.node_groups = [
nt
for nt in data_from.node_groups
if nt in ["GooPhysics_ColliderVgroupCreate"]
]
vg_create_ng = data_to.node_groups[0]
colliders = []
for ob in context.selected_objects:
if ob.type != "MESH":
continue
arm_obs = []
for mod in ob.modifiers:
if mod.type == "ARMATURE":
if mod.object is not None:
arm_obs.append(mod.object)
for vg in ob.vertex_groups:
is_def = False
for arm_ob in arm_obs:
pb = arm_ob.pose.bones.get(vg.name)
if pb is None:
continue
is_def = pb.bone.use_deform
if is_def == False:
continue
collide_dat = bpy.data.meshes.new(pb.name + "_Collider")
collide_ob = bpy.data.objects.new(pb.name + "_Collider", collide_dat)
colliders.append(collide_ob)
mod = collide_ob.modifiers.new("Create", "NODES")
mod.node_group = vg_create_ng
set_geo_nodes_input(mod, "Target", ob)
set_geo_nodes_input(mod, "Vgroup", vg.name)
collide_ob.parent = pb.id_data
collide_ob.parent_type = "BONE"
collide_ob.parent_bone = pb.name
collide_ob.matrix_parent_inverse[1][3] = pb.bone.length * -1
collide_ob.location = [0.0, 0.0, 0.0]
collide_ob.scale = [1.0, 1.0, 1.0]
collide_ob.rotation_euler = [0.0, 0.0, 0.0]
collide_colls[0].objects.link(collide_ob)
context.scene.frame_set(context.scene.frame_current)
del_obs = []
for ob in colliders:
bm = bmesh.new()
depsg = context.evaluated_depsgraph_get()
ob_eval = ob.evaluated_get(depsg)
mesh_eval = bpy.data.meshes.new_from_object(ob_eval)
bm = bmesh.new()
bm.from_mesh(mesh_eval)
ob.data.clear_geometry()
bm.to_mesh(ob.data)
bm.free()
bpy.data.meshes.remove(mesh_eval)
ob.modifiers.clear()
if len(ob.data.vertices) < 8:
del_obs.append(ob)
ob.modifiers.new("Collision", "COLLISION")
ob.hide_render = True
for ob in del_obs:
bpy.data.meshes.remove(ob.data)
return {"FINISHED"}
class NLA_OT_smart_bake(Operator):
"""Bake all selected objects loc/scale/rotation animation to an action"""
bl_idname = "nla.bake"
bl_label = "Bake Action"
bl_options = {"REGISTER", "UNDO"}
frame_start: IntProperty(
name="Start Frame",
description="Start frame for baking",
min=0,
max=300000,
default=1,
)
frame_end: IntProperty(
name="End Frame",
description="End frame for baking",
min=1,
max=300000,
default=250,
)
step: IntProperty(
name="Frame Step",
description="Frame Step",
min=1,
max=120,
default=1,
)
only_selected: BoolProperty(
name="Only Selected Bones",
description="Only key selected bones (Pose baking only)",
default=True,
)
visual_keying: BoolProperty(
name="Visual Keying",
description="Keyframe from the final transformations (with constraints applied)",
default=False,
)
clear_constraints: BoolProperty(
name="Clear Constraints",
description="Remove all constraints from keyed object/bones, and do 'visual' keying",
default=False,
)
clear_parents: BoolProperty(
name="Clear Parents",
description="Bake animation onto the object then clear parents (objects only)",
default=False,
)
use_current_action: BoolProperty(
name="Overwrite Current Action",
description="Bake animation into current action, instead of creating a new one "
"(useful for baking only part of bones in an armature)",
default=False,
)
clean_curves: BoolProperty(
name="Clean Curves",
description="After baking curves, remove redundant keys",
default=False,
)
bake_types: EnumProperty(
name="Bake Data",
description="Which data's transformations to bake",
options={"ENUM_FLAG"},
items=(
("POSE", "Pose", "Bake bones transformations"),
("OBJECT", "Object", "Bake object transformations"),
),
default={"POSE"},
)
do_smart_bake: BoolProperty(
name="Use Smart Bake",
description="Bake pose animation only on existing keyframes",
default=False,
)
use_marked_frames: BoolProperty(
name="Use Frames With Markers",
description="Only bake frames with markers",
default=False,
)
marker_filter: StringProperty(
name="Marker Filter",
description="Only use markers with the set filter in the name",
default="",
)
def execute(self, context):
from bpy_extras import anim_utils
do_pose = "POSE" in self.bake_types
do_object = "OBJECT" in self.bake_types
objects = context.selected_editable_objects
# If we have Smart Bake enabled, remove all pose objects and smart bake them separately
if self.do_smart_bake and "POSE" in self.bake_types:
non_pose_obs = []
for obj in objects:
if obj.type == "ARMATURE":
smart_bake_pose(
obj,
use_existing=self.use_current_action,
frame_start=self.frame_start,
frame_end=self.frame_end,
only_selected=self.only_selected,
do_visual_keying=self.visual_keying,
do_constraint_clear=self.clear_constraints,
do_parents_clear=self.clear_parents,
do_clean=True,
do_pose=True,
do_object=False,
)
else:
non_pose_obs.append(obj)
objects = non_pose_obs
# If we have Smart Bake enabled, remove all object objects and smart bake them separately
if self.do_smart_bake and "OBJECT" in self.bake_types:
for obj in objects:
smart_bake_pose(
obj,
use_existing=self.use_current_action,
frame_start=self.frame_start,
frame_end=self.frame_end,
only_selected=self.only_selected,
do_visual_keying=self.visual_keying,
do_constraint_clear=self.clear_constraints,
do_parents_clear=self.clear_parents,
do_clean=True,
do_pose=False,
do_object=True,
)
return {"FINISHED"}
if not len(objects):
return {"FINISHED"}
object_action_pairs = (
[(obj, getattr(obj.animation_data, "action", None)) for obj in objects]
if self.use_current_action
else [(obj, None) for obj in objects]
)
bake_frames = range(self.frame_start, self.frame_end + 1, self.step)
if self.use_marked_frames:
bake_frames = []
for m in context.scene.timeline_markers:
if self.marker_filter in m.name:
bake_frames.append(m.frame)
if bpy.app.version[0] >= 5 or (bpy.app.version[0] == 4 and bpy.app.version[1] >= 1):
bake_opts = anim_utils.BakeOptions(
only_selected=self.only_selected,
do_pose=do_pose,
do_object=do_object,
do_visual_keying=self.visual_keying,
do_constraint_clear=self.clear_constraints,
do_parents_clear=self.clear_parents,
do_clean=self.clean_curves,
do_location=True,
do_rotation=True,
do_scale=True,
do_bbone=False,
do_custom_props=False,
)
actions = anim_utils.bake_action_objects(
object_action_pairs,
frames=bake_frames,
bake_options=bake_opts,
)
else:
actions = anim_utils.bake_action_objects(
object_action_pairs,
frames=bake_frames,
only_selected=self.only_selected,
do_pose=do_pose,
do_object=do_object,
do_visual_keying=self.visual_keying,
do_constraint_clear=self.clear_constraints,
do_parents_clear=self.clear_parents,
do_clean=self.clean_curves,
)
if not any(actions):
self.report({"INFO"}, "Nothing to bake")
return {"CANCELLED"}
return {"FINISHED"}
def invoke(self, context, _event):
scene = context.scene
self.frame_start = scene.frame_start
self.frame_end = scene.frame_end
self.bake_types = {"POSE"} if context.mode == "POSE" else {"OBJECT"}
wm = context.window_manager
return wm.invoke_props_dialog(self)
#
#
#
_classes = [
GP_OT_refresh_collision_collection,
GP_OT_limit_to_type,
GP_OT_add_preset,
GP_OT_delete_preset,
GP_OT_apply_preset,
GP_OT_select_dynamic_bones,
GP_OT_select_last_dynamic_bone,
GP_OT_add_geonodes_physics_to_selected,
GP_OT_add_soft_physics_to_selected,
GP_OT_add_cloth_physics_to_selected,
GP_OT_add_jiggle_physics_to_selected,
GP_OT_sync_frame_range,
GP_OT_refresh_physics_status,
GP_OT_remove_physics_from_selected,
GP_OT_apply_falloff_to_selected,
GP_OT_add_collider,
GP_OT_add_wind_object,
GP_OT_remove_wind_object,
GP_OT_copy_active_wind_controller,
GP_OT_bake_physics,
GP_OT_colliders_from_mesh,
GP_OT_add_bone_map_preset,
GP_OT_delete_bone_map_preset,
GP_OT_apply_bone_map_preset,
]
_register, _unregister = register_classes_factory(_classes)
def register():
if is_addon_enabled("magic_empties", (1, 0, 0)) == False:
bpy.utils.register_class(NLA_OT_smart_bake)
_register()
def unregister():
_unregister()