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