6c3b78075b
maybe put in maya config? idk what funiman's preference is
1530 lines
48 KiB
Python
1530 lines
48 KiB
Python
|
|
from bpy.types import Panel, Menu, Operator, PropertyGroup
|
|
from bpy.props import *
|
|
from bpy.utils import register_classes_factory
|
|
from bpy_extras import anim_utils
|
|
import bpy
|
|
import os
|
|
import bmesh
|
|
from mathutils import Vector
|
|
import shutil
|
|
from pathlib import Path
|
|
import random
|
|
import addon_utils
|
|
import json
|
|
import numpy as np
|
|
|
|
|
|
#
|
|
# FUNCTIONS
|
|
#
|
|
|
|
|
|
def create_collider_object(
|
|
objects, ob_name, collide_coll, combine_ob_ng, convex_hull=True
|
|
):
|
|
mesh_dat = bpy.data.meshes.new(ob_name)
|
|
|
|
mesh_dat.from_pydata([], [], [])
|
|
mesh_dat.update()
|
|
|
|
collider_object = bpy.data.objects.new(ob_name, mesh_dat)
|
|
collide_coll.objects.link(collider_object)
|
|
|
|
collider_object.lock_location = [True, True, True]
|
|
collider_object.lock_scale = [True, True, True]
|
|
collider_object.lock_rotation = [True, True, True]
|
|
collider_object.hide_render = True
|
|
# collider_object.hide_viewport = True
|
|
|
|
mod = collider_object.modifiers.new("GP_Nodes Combine Objects", "NODES")
|
|
mod.node_group = combine_ob_ng
|
|
num = 0
|
|
for ob in objects:
|
|
set_geo_nodes_input(mod, "Object " + str((num % 20) + 1), ob)
|
|
num += 1
|
|
|
|
if (num % 20) == 0:
|
|
mod = collider_object.modifiers.new("GP_Nodes Combine Objects", "NODES")
|
|
mod.node_group = combine_ob_ng
|
|
|
|
set_geo_nodes_input(mod, "Convex Hull", convex_hull)
|
|
|
|
return collider_object
|
|
|
|
|
|
def create_sb_simple_mesh(name, b_len):
|
|
verts = [[0.0, 0.0, 0.0], [0.0, b_len, 0.0]]
|
|
edges = [[0, 1]]
|
|
|
|
mesh_dat = bpy.data.meshes.new(name)
|
|
|
|
mesh_dat.from_pydata(verts, edges, [])
|
|
|
|
mesh_dat.update()
|
|
|
|
ob = bpy.data.objects.new(name, mesh_dat)
|
|
|
|
vg = ob.vertex_groups.new(name="Head")
|
|
vg.add([0], 1.0, "REPLACE")
|
|
vg = ob.vertex_groups.new(name="Tail")
|
|
vg.add([1], 1.0, "REPLACE")
|
|
|
|
return ob
|
|
|
|
|
|
def create_jg_simple_mesh(name, b_len):
|
|
verts = [[0.0, 0.0, 0.0], [0.0, b_len, 0.0]]
|
|
edges = [[0, 1]]
|
|
|
|
mesh_dat = bpy.data.meshes.new(name)
|
|
|
|
mesh_dat.from_pydata(verts, edges, [])
|
|
|
|
mesh_dat.update()
|
|
|
|
ob = bpy.data.objects.new(name, mesh_dat)
|
|
|
|
vg = ob.vertex_groups.new(name="Head")
|
|
vg.add([0], 1.0, "REPLACE")
|
|
|
|
vg = ob.vertex_groups.new(name="Goal")
|
|
vg.add([0, 1], 1.0, "REPLACE")
|
|
|
|
return ob
|
|
|
|
|
|
def create_sb_coll_mesh(name, thickness, b_len, segments):
|
|
verts = []
|
|
faces = [[0, 1, 2, 3]]
|
|
|
|
for i in range(segments):
|
|
len_size = b_len * (i / (segments - 1))
|
|
|
|
seg_ind = i * 4
|
|
|
|
for ii in range(4):
|
|
x_val = -thickness if ii in [1, 2] else thickness
|
|
z_val = -thickness if ii in [2, 3] else thickness
|
|
verts.append([x_val, len_size, z_val])
|
|
|
|
if i < segments - 1:
|
|
if ii == 3:
|
|
faces.append(
|
|
[
|
|
seg_ind,
|
|
seg_ind + ii,
|
|
seg_ind + 4 + ii,
|
|
seg_ind + 4,
|
|
]
|
|
)
|
|
else:
|
|
faces.append(
|
|
[
|
|
seg_ind + 1 + ii,
|
|
seg_ind + ii,
|
|
seg_ind + 4 + ii,
|
|
seg_ind + 5 + ii,
|
|
]
|
|
)
|
|
|
|
faces.append([len(verts) - 1, len(verts) - 2, len(verts) - 3, len(verts) - 4])
|
|
|
|
mesh_dat = bpy.data.meshes.new(name)
|
|
|
|
mesh_dat.from_pydata(verts, [], faces)
|
|
|
|
mesh_dat.update()
|
|
|
|
ob = bpy.data.objects.new(name, mesh_dat)
|
|
|
|
vg = ob.vertex_groups.new(name="Head")
|
|
vg.add(faces[0], 1.0, "REPLACE")
|
|
vg = ob.vertex_groups.new(name="Tail")
|
|
vg.add(faces[-1], 1.0, "REPLACE")
|
|
|
|
return ob
|
|
|
|
|
|
def get_bone_chains(bones):
|
|
|
|
# Cache selected bones children checking for 1 level of constraint distance
|
|
valid_bones, child_bones = [], []
|
|
for pb in bones:
|
|
if pb.parent is None:
|
|
continue
|
|
|
|
children = []
|
|
for cpb in pb.children:
|
|
child_sel = get_bone_sel(cpb)
|
|
|
|
if child_sel and cpb in bones:
|
|
children.append(cpb)
|
|
|
|
valid_bones.append(pb)
|
|
child_bones.append(children)
|
|
|
|
dep_bones = child_bones.copy()
|
|
|
|
# Find all bones constrained to our valid bones
|
|
for ob in bpy.context.selected_objects:
|
|
if ob.type == "ARMATURE":
|
|
for pb in ob.pose.bones:
|
|
|
|
for con in pb.constraints:
|
|
|
|
if con.type in ["COPY_TRANSFORMS", "COPY_ROTATION", "COPY_LOCATION"]:
|
|
if con.target is None or con.target.type != "ARMATURE" or con.subtarget == "":
|
|
continue
|
|
|
|
if con.subtarget not in con.target.pose.bones:
|
|
continue
|
|
|
|
tar = con.target.pose.bones[con.subtarget]
|
|
if tar in valid_bones:
|
|
for cpb in pb.children:
|
|
child_sel = get_bone_sel(cpb)
|
|
|
|
if child_sel:
|
|
if len(child_bones[valid_bones.index(tar)]) == 0:
|
|
dep_bones[valid_bones.index(tar)].append(cpb)
|
|
|
|
# Get a list of all bones that are dependencies
|
|
chain_roots, is_dep = [], []
|
|
for d in dep_bones:
|
|
if len(d) == 1:
|
|
is_dep += d
|
|
else:
|
|
for dpb in d:
|
|
if dpb not in chain_roots:
|
|
chain_roots.append(dpb)
|
|
|
|
# Search through valid bones
|
|
for p, pb in enumerate(valid_bones):
|
|
if pb not in is_dep and pb not in chain_roots:
|
|
chain_roots.append(pb)
|
|
|
|
chain_parts = []
|
|
for pb in valid_bones:
|
|
if pb not in chain_roots:
|
|
chain_parts.append(pb)
|
|
|
|
chains, used_bones = [], []
|
|
for pb in chain_roots:
|
|
if pb.id_data.name + "_" + pb.name in used_bones:
|
|
continue
|
|
|
|
chain = [[pb.id_data, pb.name]]
|
|
used_bones.append(pb.id_data.name + "_" + pb.name)
|
|
|
|
for i in range(len(chain_parts)):
|
|
if pb in valid_bones:
|
|
deps = dep_bones[valid_bones.index(pb)]
|
|
|
|
if len(deps) == 1:
|
|
if deps[0] in chain_roots:
|
|
break
|
|
pb = deps[0]
|
|
|
|
if pb.id_data.name + "_" + pb.name in used_bones:
|
|
break
|
|
|
|
chain.append([pb.id_data, pb.name])
|
|
used_bones.append(pb.id_data.name + "_" + pb.name)
|
|
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
chains.append(chain[::-1])
|
|
|
|
return chains
|
|
|
|
|
|
def get_bone_chains_from_ids(bones, bone_ids):
|
|
|
|
# Cache selected bones children checking for 1 level of constraint distance
|
|
valid_bones, child_bones = [], []
|
|
for p, pb in enumerate(bones):
|
|
if pb.parent is None:
|
|
continue
|
|
|
|
children = []
|
|
for cpb in pb.children:
|
|
|
|
if cpb not in bones:
|
|
continue
|
|
|
|
if bone_ids[bones.index(cpb)] != bone_ids[p]:
|
|
continue
|
|
|
|
children.append(cpb)
|
|
|
|
valid_bones.append(pb)
|
|
child_bones.append(children)
|
|
|
|
dep_bones = child_bones.copy()
|
|
|
|
# Find all bones constrained to our valid bones
|
|
for ob in bpy.context.selected_objects:
|
|
if ob.type == "ARMATURE":
|
|
for pb in ob.pose.bones:
|
|
|
|
for con in pb.constraints:
|
|
|
|
if con.type in ["COPY_TRANSFORMS", "COPY_ROTATION", "COPY_LOCATION"]:
|
|
if con.target is None or con.target.type != "ARMATURE" or con.subtarget == "":
|
|
continue
|
|
|
|
if con.subtarget not in con.target.pose.bones:
|
|
continue
|
|
|
|
tar = con.target.pose.bones[con.subtarget]
|
|
if tar in valid_bones:
|
|
for cpb in pb.children:
|
|
|
|
if cpb not in bones:
|
|
continue
|
|
|
|
if bone_ids[bones.index(cpb)] != bone_ids[bones.index(pb)]:
|
|
continue
|
|
|
|
if len(child_bones[valid_bones.index(tar)]) == 0:
|
|
dep_bones[valid_bones.index(tar)].append(cpb)
|
|
|
|
# Get a list of all bones that are dependencies
|
|
chain_roots, is_dep = [], []
|
|
for d in dep_bones:
|
|
if len(d) == 1:
|
|
is_dep += d
|
|
else:
|
|
for dpb in d:
|
|
if dpb not in chain_roots:
|
|
chain_roots.append(dpb)
|
|
|
|
# Search through valid bones
|
|
for p, pb in enumerate(valid_bones):
|
|
if pb not in is_dep and pb not in chain_roots:
|
|
chain_roots.append(pb)
|
|
|
|
chain_parts = []
|
|
for pb in valid_bones:
|
|
if pb not in chain_roots:
|
|
chain_parts.append(pb)
|
|
|
|
chains, used_bones = [], []
|
|
for pb in chain_roots:
|
|
if pb.id_data.name + "_" + pb.name in used_bones:
|
|
continue
|
|
|
|
chain = [[pb.id_data, pb.name]]
|
|
used_bones.append(pb.id_data.name + "_" + pb.name)
|
|
|
|
for i in range(len(chain_parts)):
|
|
deps = dep_bones[valid_bones.index(pb)]
|
|
|
|
if len(deps) == 1:
|
|
if deps[0] in chain_roots:
|
|
break
|
|
pb = deps[0]
|
|
|
|
if pb.id_data.name + "_" + pb.name in used_bones:
|
|
break
|
|
|
|
chain.append([pb.id_data, pb.name])
|
|
used_bones.append(pb.id_data.name + "_" + pb.name)
|
|
|
|
else:
|
|
break
|
|
chains.append(chain[::-1])
|
|
|
|
return chains
|
|
|
|
|
|
def clear_sim_chains(bone_chains):
|
|
|
|
for chain in bone_chains:
|
|
for b, bo_dat in enumerate(chain):
|
|
rig = bo_dat[0]
|
|
pb = rig.pose.bones[bo_dat[1]]
|
|
|
|
track_cons = pb.constraints.get("GP_Track")
|
|
if track_cons:
|
|
pb.constraints.remove(track_cons)
|
|
|
|
follow_cons = pb.constraints.get("GP_Follow")
|
|
if follow_cons:
|
|
pb.constraints.remove(follow_cons)
|
|
|
|
ob = bpy.data.objects.get(pb.gp_sim_object)
|
|
if ob:
|
|
try:
|
|
mod = ob.modifiers.get("GP_Nodes Sim")
|
|
if mod:
|
|
root_ob = get_geo_nodes_input(mod, "Root Ref Object")
|
|
if root_ob is not None:
|
|
bpy.data.objects.remove(root_ob)
|
|
|
|
except:
|
|
pass
|
|
bpy.data.objects.remove(ob)
|
|
|
|
pb.gp_has_sb_physics = False
|
|
pb.gp_has_cl_physics = False
|
|
pb.gp_has_gn_physics = False
|
|
pb.gp_has_jg_physics = False
|
|
pb.gp_end_of_chain = False
|
|
pb.gp_chain_id = ""
|
|
pb.gp_sim_object = ""
|
|
|
|
return
|
|
|
|
|
|
def get_partial_selected_bone_chains(bones, geo_nodes_only=False, only_active=False, include_all=False):
|
|
chains, skip_bones = [], []
|
|
for pb in bones:
|
|
if pb.name in skip_bones:
|
|
continue
|
|
|
|
if only_active and bpy.context.active_pose_bone.gp_chain_id != pb.gp_chain_id:
|
|
continue
|
|
|
|
track_cons = pb.constraints.get("GP_Track")
|
|
follow_cons = pb.constraints.get("GP_Follow")
|
|
if follow_cons and pb.gp_has_jg_physics:
|
|
chain = [[pb.id_data, pb.name]]
|
|
|
|
skip_bones += [c[1] for c in chain]
|
|
chains.append(chain)
|
|
|
|
elif track_cons and (geo_nodes_only == False or pb.gp_has_gn_physics):
|
|
chain = [[pb.id_data, pb.name]]
|
|
|
|
for par in pb.parent_recursive:
|
|
if par.constraints.get("GP_Track"):
|
|
chain.insert(0, [par.id_data, par.name])
|
|
|
|
if par.gp_end_of_chain:
|
|
break
|
|
|
|
else:
|
|
break
|
|
searching = True
|
|
search_pb = pb
|
|
while searching:
|
|
search_len = len(chain)
|
|
for child in search_pb.children:
|
|
if child.constraints.get("GP_Track"):
|
|
if child.gp_end_of_chain == False:
|
|
chain.append([child.id_data, child.name])
|
|
search_pb = child
|
|
break
|
|
|
|
else:
|
|
break
|
|
|
|
searching = len(chain) != search_len
|
|
|
|
skip_bones += [c[1] for c in chain]
|
|
chains.append(chain)
|
|
|
|
else:
|
|
if include_all:
|
|
chains.append([[pb.id_data, pb.name]])
|
|
|
|
return chains
|
|
|
|
|
|
def create_geonodes_chains(chains, coll, collide_colls, divisions):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
link_fp = get_nodes_fp()
|
|
|
|
# Link in geo nodes node groups
|
|
simulation_ng = bpy.data.node_groups.get("GooPhysics_SimulationProcess")
|
|
if simulation_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_SimulationProcess"]
|
|
]
|
|
|
|
simulation_ng = data_to.node_groups[0]
|
|
|
|
coll_ob_ng = bpy.data.node_groups.get("GooPhysics_CollisionCreate")
|
|
if coll_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_CollisionCreate"]
|
|
]
|
|
|
|
coll_ob_ng = data_to.node_groups[0]
|
|
|
|
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]
|
|
|
|
#
|
|
|
|
chain_obs = []
|
|
for c, chain in enumerate(chains):
|
|
next_num = c
|
|
for ob in coll.objects:
|
|
try:
|
|
cur_num = int(
|
|
ob.name.replace("GP_GeoNodes_Chain_Root_Ref_", "")
|
|
.replace("GP_GeoNodes_Chain_", "")
|
|
.split("_")[0]
|
|
)
|
|
|
|
if next_num < cur_num:
|
|
next_num = cur_num
|
|
except:
|
|
pass
|
|
|
|
num = "%02d" % (next_num + 1)
|
|
verts, edges = [], []
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
bone.gp_has_gn_physics = True
|
|
bone.gp_chain_id = num
|
|
|
|
head_co = bone.head
|
|
tail_co = bone.tail
|
|
|
|
vec = tail_co - head_co
|
|
|
|
if cc == 0:
|
|
verts.append(head_co)
|
|
|
|
for i in range(divisions):
|
|
fac = (i + 1) / (divisions + 1)
|
|
verts.append(head_co + vec * fac)
|
|
edges.append([len(verts) - 2, len(verts) - 1])
|
|
|
|
verts.append(tail_co)
|
|
edges.append([len(verts) - 2, len(verts) - 1])
|
|
|
|
bone.gp_end_of_chain = cc == 0
|
|
|
|
#
|
|
|
|
rig = chain[-1][0]
|
|
bone = rig.pose.bones[chain[-1][1]]
|
|
|
|
chain_name = "GP_GeoNodes_Chain_" + num
|
|
mesh_dat = bpy.data.meshes.new(chain_name)
|
|
|
|
mesh_dat.from_pydata(verts, edges, [])
|
|
mesh_dat.update()
|
|
|
|
chain_ob = bpy.data.objects.new(chain_name, mesh_dat)
|
|
coll.objects.link(chain_ob)
|
|
|
|
chain_root_ref = bpy.data.objects.new(
|
|
chain_name.replace("_Chain_", "_Chain_Root_Ref_"), None
|
|
)
|
|
coll.objects.link(chain_root_ref)
|
|
chain_root_ref.empty_display_size = 0.025
|
|
chain_root_ref.empty_display_type = "CUBE"
|
|
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
c_ob = chain_ob.gp_bone_coll.coll.add()
|
|
c_ob.rig_object = rig.name
|
|
c_ob.rig_bone = c_dat[1]
|
|
|
|
chain_ob.lock_location = [True, True, True]
|
|
chain_ob.lock_scale = [True, True, True]
|
|
chain_ob.lock_rotation = [True, True, True]
|
|
|
|
chain_ob.matrix_world = rig.matrix_world
|
|
chain_ob.parent = rig
|
|
|
|
hook = chain_ob.modifiers.new("GP_Root Hook", "HOOK")
|
|
|
|
mod = chain_ob.modifiers.new("GP_Nodes Sim", "NODES")
|
|
mod.show_viewport = prefs.gp_physics_active
|
|
mod.node_group = simulation_ng
|
|
set_geo_nodes_input(mod, "Root Ref Object", chain_root_ref)
|
|
set_geo_nodes_input(mod, "Seed", random.randint(-5000, 5000))
|
|
|
|
if bone.gp_chain_limit_collision and prefs.keep_existing_settings:
|
|
set_geo_nodes_input(mod, "Collision Collection", collide_colls[4])
|
|
else:
|
|
set_geo_nodes_input(mod, "Collision Collection", collide_colls[0])
|
|
|
|
set_geo_nodes_input(mod, "Use Collision", True)
|
|
|
|
#
|
|
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
if cc == 0:
|
|
set_geo_nodes_input(mod, "Velocity Scaler", bone.gp_chain_velocity)
|
|
set_geo_nodes_input(mod, "Velocity Dampening", bone.gp_chain_dampening)
|
|
set_geo_nodes_input(mod, "Gravity Strength", bone.gp_chain_gravity)
|
|
set_geo_nodes_input(mod, "Root Pin Size", bone.gp_chain_root_falloff)
|
|
set_geo_nodes_input(mod, "Goal Strength", bone.gp_chain_stiffness)
|
|
set_geo_nodes_input(mod, "Goal End Strength", bone.gp_chain_stiff_end_fac)
|
|
set_geo_nodes_input(mod, "Goal Velocity Factor", bone.gp_chain_stiff_vel_fac)
|
|
set_geo_nodes_input(mod, "Goal Velocity Min", bone.gp_chain_stiff_vel_min)
|
|
set_geo_nodes_input(mod, "Goal Velocity Max", bone.gp_chain_stiff_vel_max)
|
|
set_geo_nodes_input(mod, "Wind Strength", bone.gp_chain_wind_strength)
|
|
set_geo_nodes_input(mod, "Wind Noise Strength", bone.gp_chain_wind_noise_strength)
|
|
set_geo_nodes_input(mod, "Wind Noise Scale", bone.gp_chain_wind_noise_scale)
|
|
set_geo_nodes_input(mod, "Collision Distance", bone.gp_chain_collision_dist)
|
|
set_geo_nodes_input(mod, "Collision Friction", bone.gp_chain_collision_friction)
|
|
|
|
|
|
# chain_root_ref.parent = rig
|
|
# chain_root_ref.parent_type = "BONE"
|
|
# chain_root_ref.parent_bone = bone.parent.name
|
|
|
|
# chain_root_ref.matrix_world = rig.matrix_world @ bone.parent.matrix
|
|
|
|
# chain_root_ref.location = chain_root_ref.matrix_world.inverted() @ (
|
|
# rig.matrix_world @ bone.head
|
|
# )
|
|
# chain_root_ref.location[1] -= bone.parent.length
|
|
# chain_root_ref.scale = [1.0, 1.0, 1.0]
|
|
# chain_root_ref.rotation_euler = [0.0, 0.0, 0.0]
|
|
|
|
chain_root_ref.lock_location = [True, True, True]
|
|
chain_root_ref.lock_rotation = [True, True, True]
|
|
chain_root_ref.lock_scale = [True, True, True]
|
|
|
|
mat = rig.matrix_world @ bone.matrix
|
|
|
|
chain_root_ref.matrix_world = mat
|
|
|
|
con = chain_root_ref.constraints.new("CHILD_OF")
|
|
con.target = rig
|
|
con.subtarget = bone.parent.name
|
|
|
|
# con = chain_root_ref.constraints.new("COPY_TRANSFORMS")
|
|
# con.target = rig
|
|
# con.subtarget = bone.name
|
|
|
|
vg = chain_ob.vertex_groups.new(name="Root")
|
|
vg.add([0], 1.0, "REPLACE")
|
|
|
|
vg_name = "Bone_" + str(cc + 1)
|
|
vg = chain_ob.vertex_groups.new(name=vg_name)
|
|
vg.add([cc + divisions * (cc + 1) + 1], 1.0, "REPLACE")
|
|
|
|
bone.gp_sim_object = chain_ob.name
|
|
|
|
con = bone.constraints.new("DAMPED_TRACK")
|
|
con.name = "GP_Track"
|
|
con.target = chain_ob
|
|
con.subtarget = vg_name
|
|
# con.influence = 1.0
|
|
con.influence = bone.gp_sim_influence
|
|
con.enabled = prefs.gp_physics_active
|
|
|
|
vg = chain_ob.vertex_groups.new(name="All")
|
|
vg.add([i for i in range(len(chain_ob.data.vertices))], 1.0, "REPLACE")
|
|
|
|
hook.matrix_inverse = mat.inverted()
|
|
hook.object = chain_root_ref
|
|
hook.vertex_group = "All"
|
|
chain_obs.append(chain_ob)
|
|
|
|
return chain_obs
|
|
|
|
|
|
def create_soft_chains(chains, coll, collide_colls):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
chain_sets = []
|
|
for c, chain in enumerate(chains):
|
|
next_num = c
|
|
for ob in coll.objects:
|
|
try:
|
|
cur_num = int(
|
|
ob.name.replace("GP_SoftBody_Chain_Root_Ref_", "")
|
|
.replace("GP_SoftBody_Chain_", "")
|
|
.split("_")[0]
|
|
)
|
|
|
|
if next_num < cur_num:
|
|
next_num = cur_num
|
|
except:
|
|
pass
|
|
|
|
num = "%02d" % (next_num + 1)
|
|
|
|
chain_obs = []
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
b_ind = "%02d" % (cc + 1)
|
|
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
bone.gp_has_sb_physics = True
|
|
bone.gp_chain_id = num
|
|
|
|
chain_name = "GP_SoftBody_Chain_" + num + "_" + b_ind
|
|
|
|
chain_ob = create_sb_simple_mesh(
|
|
chain_name,
|
|
bone.length,
|
|
)
|
|
|
|
coll.objects.link(chain_ob)
|
|
|
|
bone.gp_end_of_chain = cc == 0
|
|
bone.gp_sim_object = chain_ob.name
|
|
|
|
c_ob = chain_ob.gp_bone_coll.coll.add()
|
|
c_ob.rig_object = c_dat[0].name
|
|
c_ob.rig_bone = c_dat[1]
|
|
|
|
chain_obs.append(chain_ob)
|
|
|
|
con = bone.constraints.new("DAMPED_TRACK")
|
|
con.name = "GP_Track"
|
|
con.target = chain_ob
|
|
con.subtarget = "Tail"
|
|
con.influence = bone.gp_sim_influence
|
|
con.enabled = prefs.gp_physics_active
|
|
|
|
chain_ob.parent = rig
|
|
chain_ob.parent_type = "BONE"
|
|
chain_ob.parent_bone = bone.parent.name
|
|
|
|
chain_ob.matrix_world = rig.matrix_world @ bone.matrix
|
|
|
|
chain_ob.lock_location = [True, True, True]
|
|
chain_ob.lock_scale = [True, True, True]
|
|
chain_ob.lock_rotation = [True, True, True]
|
|
|
|
mod = chain_ob.modifiers.new("GP_SoftBody Sim", "SOFT_BODY")
|
|
mod.show_viewport = prefs.gp_physics_active
|
|
|
|
#
|
|
|
|
if bpy.context.scene.use_preview_range:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_preview_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_preview_end
|
|
else:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_end
|
|
|
|
mod.settings.use_edge_collision = True
|
|
mod.settings.bend = 10
|
|
mod.settings.pull = 1.0
|
|
mod.settings.push = 1.0
|
|
|
|
mod.settings.step_min = 100
|
|
mod.settings.choke = 0
|
|
mod.settings.fuzzy = 0
|
|
|
|
mod.settings.spring_length = 100
|
|
|
|
mod.settings.vertex_group_goal = "Head"
|
|
mod.settings.goal_spring = bone.gp_sim_stiffness
|
|
mod.settings.goal_friction = bone.gp_sim_damping
|
|
mod.settings.goal_default = 1.0
|
|
mod.settings.goal_min = bone.gp_sim_strength
|
|
mod.settings.goal_max = 1.0
|
|
mod.settings.effector_weights.gravity = bone.gp_sim_gravity
|
|
|
|
mod.settings.speed = bone.gp_sim_speed
|
|
|
|
mod.settings.friction = bone.gp_sim_friction
|
|
mod.settings.mass = bone.gp_sim_mass
|
|
|
|
if bone.gp_chain_limit_collision and prefs.keep_existing_settings:
|
|
mod.settings.collision_collection = collide_colls[2]
|
|
else:
|
|
mod.settings.collision_collection = None
|
|
|
|
#
|
|
|
|
chain_sets.append(chain_obs)
|
|
|
|
return chain_sets
|
|
|
|
|
|
def create_spring_chains(chains, coll, collide_colls):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
chain_sets = []
|
|
for c, chain in enumerate(chains):
|
|
next_num = c
|
|
for ob in coll.objects:
|
|
try:
|
|
cur_num = int(
|
|
ob.name.replace("GP_Jiggle_Root_Ref_", "")
|
|
.replace("GP_Jiggle_", "")
|
|
.split("_")[0]
|
|
)
|
|
|
|
if next_num < cur_num:
|
|
next_num = cur_num
|
|
except:
|
|
pass
|
|
|
|
num = "%02d" % (next_num + 1)
|
|
|
|
chain_obs = []
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
b_ind = "%02d" % (cc + 1)
|
|
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
bone.gp_has_jg_physics = True
|
|
bone.gp_chain_id = num
|
|
|
|
chain_name = "GP_Jiggle_" + num + "_" + b_ind
|
|
|
|
chain_ob = create_jg_simple_mesh(
|
|
chain_name,
|
|
bone.length,
|
|
)
|
|
|
|
coll.objects.link(chain_ob)
|
|
|
|
bone.gp_end_of_chain = cc == 0
|
|
bone.gp_sim_object = chain_ob.name
|
|
|
|
c_ob = chain_ob.gp_bone_coll.coll.add()
|
|
c_ob.rig_object = c_dat[0].name
|
|
c_ob.rig_bone = c_dat[1]
|
|
|
|
chain_obs.append(chain_ob)
|
|
|
|
con = bone.constraints.new("COPY_LOCATION")
|
|
con.name = "GP_Follow"
|
|
con.target = chain_ob
|
|
con.subtarget = "Head"
|
|
con.influence = bone.gp_sim_influence
|
|
con.enabled = prefs.gp_physics_active
|
|
|
|
chain_ob.parent = rig
|
|
chain_ob.parent_type = "BONE"
|
|
chain_ob.parent_bone = bone.parent.name
|
|
|
|
chain_ob.matrix_world = rig.matrix_world @ bone.matrix
|
|
|
|
chain_ob.lock_location = [True, True, True]
|
|
chain_ob.lock_scale = [True, True, True]
|
|
chain_ob.lock_rotation = [True, True, True]
|
|
|
|
mod = chain_ob.modifiers.new("GP_Jiggle Sim", "SOFT_BODY")
|
|
mod.show_viewport = prefs.gp_physics_active
|
|
|
|
#
|
|
|
|
if bpy.context.scene.use_preview_range:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_preview_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_preview_end
|
|
else:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_end
|
|
|
|
mod.settings.use_edge_collision = True
|
|
mod.settings.bend = 10
|
|
mod.settings.pull = 1.0
|
|
mod.settings.push = 1.0
|
|
|
|
mod.settings.step_min = 100
|
|
mod.settings.choke = 0
|
|
mod.settings.fuzzy = 0
|
|
|
|
mod.settings.spring_length = 100
|
|
|
|
mod.settings.vertex_group_goal = "Goal"
|
|
mod.settings.goal_spring = bone.gp_sim_stiffness
|
|
mod.settings.goal_friction = bone.gp_sim_damping
|
|
mod.settings.goal_default = 1.0
|
|
mod.settings.goal_min = 0.99
|
|
mod.settings.goal_max = 0.99
|
|
mod.settings.effector_weights.gravity = bone.gp_sim_gravity
|
|
|
|
mod.settings.speed = bone.gp_sim_speed
|
|
|
|
mod.settings.friction = bone.gp_sim_friction
|
|
mod.settings.mass = bone.gp_sim_mass
|
|
|
|
if bone.gp_chain_limit_collision and prefs.keep_existing_settings:
|
|
mod.settings.collision_collection = collide_colls[1]
|
|
else:
|
|
mod.settings.collision_collection = None
|
|
|
|
#
|
|
|
|
chain_sets.append(chain_obs)
|
|
|
|
return chain_sets
|
|
|
|
|
|
def create_cloth_chains(chains, coll, collide_colls):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
thick = .001
|
|
|
|
chain_obs = []
|
|
for c, chain in enumerate(chains):
|
|
next_num = c
|
|
for ob in coll.objects:
|
|
try:
|
|
cur_num = int(
|
|
ob.name.replace("GP_Cloth_Chain_Root_Ref_", "")
|
|
.replace("GP_Cloth_Chain_", "")
|
|
.split("_")[0]
|
|
)
|
|
|
|
if next_num < cur_num:
|
|
next_num = cur_num
|
|
except:
|
|
pass
|
|
|
|
num = "%02d" % (next_num + 1)
|
|
verts, faces = [], []
|
|
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
bone.gp_has_cl_physics = True
|
|
bone.gp_chain_id = num
|
|
|
|
mat = rig.matrix_world @ bone.matrix
|
|
|
|
if cc == 0:
|
|
verts.append(mat @ Vector((thick, 0.0, thick)))
|
|
verts.append(mat @ Vector((-thick, 0.0, thick)))
|
|
verts.append(mat @ Vector((-thick, 0.0, -thick)))
|
|
verts.append(mat @ Vector((thick, 0.0, -thick)))
|
|
faces.append([0, 1, 2, 3])
|
|
else:
|
|
verts[-4] = (verts[-4] + mat @ Vector((thick, 0.0, thick))) * 0.5
|
|
verts[-3] = (verts[-3] + mat @ Vector((-thick, 0.0, thick))) * 0.5
|
|
verts[-2] = (verts[-2] + mat @ Vector((-thick, 0.0, -thick))) * 0.5
|
|
verts[-1] = (verts[-1] + mat @ Vector((thick, 0.0, -thick))) * 0.5
|
|
|
|
ind = len(verts) - 4
|
|
|
|
verts.append(mat @ Vector((thick, bone.length, thick)))
|
|
verts.append(mat @ Vector((-thick, bone.length, thick)))
|
|
verts.append(mat @ Vector((-thick, bone.length, -thick)))
|
|
verts.append(mat @ Vector((thick, bone.length, -thick)))
|
|
faces.append([ind, ind + 4, ind + 5, ind + 1])
|
|
faces.append([ind + 1, ind + 5, ind + 6, ind + 2])
|
|
faces.append([ind + 2, ind + 6, ind + 7, ind + 3])
|
|
faces.append([ind + 3, ind + 7, ind + 4, ind])
|
|
|
|
if cc == len(chain) - 1:
|
|
ind = len(verts) - 4
|
|
faces.append([ind, ind + 1, ind + 2, ind + 3])
|
|
|
|
bone.gp_end_of_chain = cc == 0
|
|
|
|
#
|
|
|
|
rig = chain[-1][0]
|
|
bone = rig.pose.bones[chain[-1][1]]
|
|
|
|
chain_name = "GP_Cloth_Chain_" + num
|
|
mesh_dat = bpy.data.meshes.new(chain_name)
|
|
|
|
mesh_dat.from_pydata(verts, [], faces)
|
|
mesh_dat.update()
|
|
|
|
chain_ob = bpy.data.objects.new(chain_name, mesh_dat)
|
|
coll.objects.link(chain_ob)
|
|
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
c_ob = chain_ob.gp_bone_coll.coll.add()
|
|
c_ob.rig_object = rig.name
|
|
c_ob.rig_bone = c_dat[1]
|
|
|
|
chain_ob.lock_location = [True, True, True]
|
|
chain_ob.lock_scale = [True, True, True]
|
|
chain_ob.lock_rotation = [True, True, True]
|
|
|
|
mod = chain_ob.modifiers.new("GP_Cloth Sim", "CLOTH")
|
|
mod.show_viewport = prefs.gp_physics_active
|
|
|
|
#
|
|
|
|
if bpy.context.scene.use_preview_range:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_preview_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_preview_end
|
|
else:
|
|
mod.point_cache.frame_start = bpy.context.scene.frame_start
|
|
mod.point_cache.frame_end = bpy.context.scene.frame_end
|
|
|
|
mod.settings.quality = 12
|
|
mod.settings.mass = bone.gp_chain_mass
|
|
|
|
mod.settings.bending_model = "LINEAR"
|
|
|
|
mod.settings.use_pressure = True
|
|
mod.settings.uniform_pressure_force = 1.0
|
|
mod.settings.pressure_factor = 0.25
|
|
|
|
mod.settings.air_damping = bone.gp_chain_air_dampening
|
|
|
|
mod.settings.tension_stiffness = 15.0
|
|
mod.settings.shear_stiffness = 5.0
|
|
mod.settings.bending_stiffness = 1.0
|
|
|
|
mod.settings.tension_damping = 5.0
|
|
mod.settings.shear_damping = 5.0
|
|
mod.settings.bending_damping = bone.gp_chain_bend_damping
|
|
mod.settings.effector_weights.gravity = bone.gp_chain_gravity
|
|
|
|
mod.settings.time_scale = bone.gp_chain_speed
|
|
|
|
mod.collision_settings.use_collision = True
|
|
mod.collision_settings.distance_min = bone.gp_chain_collision_dist
|
|
|
|
if bone.gp_chain_limit_collision and prefs.keep_existing_settings:
|
|
mod.collision_settings.collection = collide_colls[3]
|
|
else:
|
|
mod.collision_settings.collection = None
|
|
|
|
pg_name = "Pin"
|
|
pin_group = chain_ob.vertex_groups.new(name=pg_name)
|
|
mod.settings.vertex_group_mass = pg_name
|
|
|
|
#
|
|
|
|
end_cnt = round(len(chain) * 0.25)
|
|
if end_cnt == 0:
|
|
end_cnt += 1
|
|
|
|
fac = 1.0 / end_cnt
|
|
for cc, c_dat in enumerate(chain[::-1]):
|
|
rig = c_dat[0]
|
|
bone = rig.pose.bones[c_dat[1]]
|
|
|
|
if cc == 0:
|
|
mat = chain_ob.matrix_world.copy()
|
|
|
|
chain_ob.parent = rig
|
|
chain_ob.parent_type = "BONE"
|
|
chain_ob.parent_bone = bone.parent.name
|
|
|
|
mat = bone.parent.matrix.copy()
|
|
mat[0][3] = mat.translation[0] + bone.parent.y_axis[0] * bone.parent.length
|
|
mat[1][3] = mat.translation[1] + bone.parent.y_axis[1] * bone.parent.length
|
|
mat[2][3] = mat.translation[2] + bone.parent.y_axis[2] * bone.parent.length
|
|
|
|
mat = rig.matrix_world @ mat
|
|
|
|
chain_ob.data.transform(mat.inverted())
|
|
|
|
# chain_ob.matrix_world = mat
|
|
# chain_ob.matrix_parent_inverse = chain_ob.matrix_basis
|
|
|
|
chain_ob.location = [0.0, 0.0, 0.0]
|
|
chain_ob.scale = [1.0, 1.0, 1.0]
|
|
chain_ob.rotation_euler = [0.0, 0.0, 0.0]
|
|
|
|
vg = chain_ob.vertex_groups.new(name="Root")
|
|
vg.add([0, 1, 2, 3], 1.0, "REPLACE")
|
|
|
|
pin_group.add([0, 1, 2, 3], 1.0, "REPLACE")
|
|
|
|
vg_name = "Bone_" + str(cc + 1)
|
|
vg = chain_ob.vertex_groups.new(name=vg_name)
|
|
vg.add(
|
|
[(cc + 1) * 4, (cc + 1) * 4 + 1, (cc + 1) * 4 + 2, (cc + 1) * 4 + 3],
|
|
1.0,
|
|
"REPLACE",
|
|
)
|
|
|
|
pin_group.add(
|
|
[cc * 4, cc * 4 + 1, cc * 4 + 2, cc * 4 + 3],
|
|
1.0 - (cc * fac),
|
|
"REPLACE",
|
|
)
|
|
|
|
bone.gp_sim_object = chain_ob.name
|
|
|
|
con = bone.constraints.new("DAMPED_TRACK")
|
|
con.name = "GP_Track"
|
|
con.target = chain_ob
|
|
con.subtarget = vg_name
|
|
con.influence = bone.gp_sim_influence
|
|
con.enabled = prefs.gp_physics_active
|
|
|
|
chain_obs.append(chain_ob)
|
|
|
|
return chain_obs
|
|
|
|
|
|
def get_preset_data(preset_override=None):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
preset_fp, user_preset_fp = None, None
|
|
bone_has_settings = False
|
|
|
|
pb = bpy.context.active_pose_bone
|
|
presets_enum = None
|
|
if pb.gp_has_cl_physics:
|
|
preset_fp = Path(os.path.dirname(__file__)) / "presets" / "cloth_presets.json"
|
|
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "cloth_presets.json"
|
|
presets_enum = prefs.cl_presets
|
|
bone_has_settings = "gp_chain_air_dampening" in pb
|
|
|
|
elif pb.gp_has_sb_physics:
|
|
preset_fp = Path(os.path.dirname(__file__)) / "presets" / "soft_body_presets.json"
|
|
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "soft_body_presets.json"
|
|
presets_enum = prefs.sb_presets
|
|
bone_has_settings = "gp_sim_strength" in pb
|
|
|
|
elif pb.gp_has_jg_physics:
|
|
preset_fp = Path(os.path.dirname(__file__)) / "presets" / "jiggle_spring_presets.json"
|
|
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "jiggle_spring_presets.json"
|
|
presets_enum = prefs.jg_presets
|
|
bone_has_settings = "gp_sim_friction" in pb
|
|
|
|
elif pb.gp_has_gn_physics:
|
|
preset_fp = Path(os.path.dirname(__file__)) / "presets" / "geo_nodes_presets.json"
|
|
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "geo_nodes_presets.json"
|
|
presets_enum = prefs.gn_presets
|
|
bone_has_settings = "gp_chain_root_falloff" in pb
|
|
|
|
if preset_override is not None:
|
|
presets_enum = preset_override
|
|
|
|
user_presets_data = {}
|
|
if user_preset_fp is not None and os.path.exists(user_preset_fp):
|
|
user_presets_data = json.load(open(str(user_preset_fp)))
|
|
|
|
base_presets_data = {}
|
|
if preset_fp is not None and os.path.exists(preset_fp):
|
|
base_presets_data = json.load(open(str(preset_fp)))
|
|
|
|
presets_data = base_presets_data | user_presets_data
|
|
|
|
if presets_enum is not None and presets_enum.replace(" ", "").upper() in presets_data.keys():
|
|
p_data = presets_data[presets_enum.replace(" ", "").upper()]["Settings"]
|
|
|
|
if prefs.keep_existing_settings and bone_has_settings:
|
|
for k in p_data.keys():
|
|
if k in pb:
|
|
p_data[k] = pb[k]
|
|
|
|
return p_data
|
|
|
|
return None
|
|
|
|
|
|
def get_bone_map_preset_data():
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
user_preset_fp = Path(os.path.dirname(__file__)) / "user_created_presets" / "bone_map_presets.json"
|
|
presets_enum = prefs.bm_presets
|
|
|
|
user_presets_data = {}
|
|
if user_preset_fp is not None and os.path.exists(user_preset_fp):
|
|
user_presets_data = json.load(open(str(user_preset_fp)))
|
|
|
|
if presets_enum.replace(" ", "").upper() in user_presets_data.keys():
|
|
p_data = user_presets_data[presets_enum.replace(" ", "").upper()]["Chain Settings"]
|
|
return p_data
|
|
|
|
return None
|
|
|
|
|
|
def apply_preset(preset_data, bone):
|
|
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
if prefs.apply_to_all_chains:
|
|
sim_chains = get_partial_selected_bone_chains(bpy.context.selected_pose_bones)
|
|
else:
|
|
sim_chains = get_partial_selected_bone_chains(bpy.context.selected_pose_bones_from_active_object, only_active=True)
|
|
|
|
for s in preset_data.keys():
|
|
|
|
if prefs.keep_existing_settings and s in bone:
|
|
continue
|
|
|
|
if isinstance(preset_data[s], list) == False:
|
|
|
|
if hasattr(bone, s):
|
|
setattr(bone, s, preset_data[s])
|
|
|
|
else:
|
|
|
|
vals = np.array(preset_data[s], dtype=np.float32)
|
|
|
|
source_t_vals = np.arange(vals.shape[0], dtype=np.float32) / (vals.shape[0] - 1)
|
|
for chain in sim_chains:
|
|
|
|
t_vals = np.arange(len(chain), dtype=np.float32) / (len(chain) - 1)
|
|
|
|
tar_inds = np.searchsorted(source_t_vals, t_vals)
|
|
|
|
p_inds = tar_inds - 1
|
|
tar_inds[0] = 1
|
|
p_inds[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
|
|
|
|
lerp_vals[0] = vals[0]
|
|
lerp_vals[-1] = vals[-1]
|
|
|
|
for b, b_dat in enumerate(chain):
|
|
bo = b_dat[0].pose.bones[b_dat[1]]
|
|
|
|
if hasattr(bo, s):
|
|
bo.gp_update = False
|
|
setattr(bo, s, lerp_vals[b])
|
|
bo.gp_update = True
|
|
|
|
return
|
|
|
|
|
|
def apply_bone_map_preset(preset_data, coll, collide_colls):
|
|
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
rig = bpy.context.active_object
|
|
|
|
for pb in rig.pose.bones:
|
|
pb.gp_chain_id = ""
|
|
|
|
missing_bones, existing_bones, existing_ids, existing_types = [], [], [], []
|
|
for b_dat in preset_data:
|
|
pb = rig.pose.bones.get(b_dat["Bone Name"])
|
|
|
|
if pb is None:
|
|
missing_bones.append(b_dat["Bone Name"])
|
|
|
|
else:
|
|
existing_bones.append(pb)
|
|
existing_ids.append(b_dat["Chain ID"])
|
|
existing_types.append(b_dat["Chain Type"])
|
|
|
|
sim_chains = get_bone_chains_from_ids(existing_bones, existing_ids)
|
|
|
|
spring_chains, cloth_chains, soft_chains, geo_chains = [], [], [], []
|
|
|
|
for c, chain in enumerate(sim_chains):
|
|
pb = chain[0][0].pose.bones[chain[0][1]]
|
|
|
|
chain_type = existing_types[existing_bones.index(pb)]
|
|
|
|
if chain_type == "GEO_NODES":
|
|
geo_chains.append(chain)
|
|
|
|
elif chain_type == "SOFT_BODY":
|
|
soft_chains.append(chain)
|
|
|
|
elif chain_type == "CLOTH":
|
|
cloth_chains.append(chain)
|
|
|
|
elif chain_type == "JIGGLE":
|
|
spring_chains.append(chain)
|
|
|
|
if len(spring_chains) > 0:
|
|
sim_spring_obs = create_spring_chains(spring_chains, coll, collide_colls)
|
|
|
|
if len(cloth_chains) > 0:
|
|
sim_cloth_obs = create_cloth_chains(cloth_chains, coll, collide_colls)
|
|
|
|
if len(soft_chains) > 0:
|
|
sim_soft_obs = create_soft_chains(soft_chains, coll, collide_colls)
|
|
|
|
if len(geo_chains) > 0:
|
|
sim_soft_obs = create_geonodes_chains(
|
|
geo_chains, coll, collide_colls, prefs.gp_divisions
|
|
)
|
|
|
|
return missing_bones
|
|
|
|
|
|
def get_falloff(value):
|
|
return 1 - (value * value)
|
|
|
|
|
|
def get_inputs_container(node_group):
|
|
if bpy.app.version[0] >= 4:
|
|
input_container = node_group.interface.items_tree
|
|
|
|
else:
|
|
input_container = node_group.inputs
|
|
|
|
return input_container
|
|
|
|
|
|
def is_addon_enabled(addon_name, version=None):
|
|
modules = addon_utils.modules()
|
|
for module in modules:
|
|
module_name = module.__name__
|
|
|
|
if module_name == addon_name:
|
|
is_enabled = addon_utils.check(module_name)[1]
|
|
if is_enabled and version is not None:
|
|
return module.bl_info["version"] >= version
|
|
else:
|
|
return is_enabled
|
|
|
|
return False
|
|
|
|
|
|
def is_addon_installed(addon_name):
|
|
for module in addon_utils.modules():
|
|
if addon_name == module.__name__:
|
|
return True
|
|
return False
|
|
|
|
|
|
def create_collider_coll(name, par_coll):
|
|
|
|
c_coll = bpy.data.collections.get(name)
|
|
if c_coll is None or c_coll.library is not None or c_coll.override_library is not None:
|
|
c_coll = bpy.data.collections.new(name)
|
|
c_coll.hide_render = True
|
|
|
|
if c_coll.name not in par_coll.children:
|
|
par_coll.children.link(c_coll)
|
|
|
|
return c_coll
|
|
|
|
|
|
def ensure_collections():
|
|
|
|
coll = bpy.data.collections.get("Goo Physics")
|
|
if coll is None or coll.library is not None:
|
|
coll = bpy.data.collections.new("Goo Physics")
|
|
coll.hide_render = True
|
|
|
|
if "Shot" in bpy.context.scene.collection.children:
|
|
bpy.context.scene.collection.children["Shot"].children.link(coll)
|
|
else:
|
|
bpy.context.scene.collection.children.link(coll)
|
|
|
|
collide_coll = create_collider_coll("Goo Physics All Colliders", coll)
|
|
sb_collide_coll = create_collider_coll("Goo Physics Soft Body Colliders", collide_coll)
|
|
jg_collide_coll = create_collider_coll("Goo Physics Jiggle Colliders", collide_coll)
|
|
cl_collide_coll = create_collider_coll("Goo Physics Cloth Colliders", collide_coll)
|
|
gn_collide_coll = create_collider_coll("Goo Physics Geo Nodes Colliders", collide_coll)
|
|
|
|
controller_coll = create_collider_coll("Goo Physics Controllers", coll)
|
|
bake_coll = create_collider_coll("Goo Physics Bakes", coll)
|
|
|
|
return coll, [collide_coll, sb_collide_coll, jg_collide_coll, cl_collide_coll, gn_collide_coll], controller_coll, bake_coll
|
|
|
|
|
|
def get_nodes_fp():
|
|
|
|
if bpy.app.version[0] < 4:
|
|
link_fp = Path(os.path.dirname(__file__)) / "gp_sim_nodes_tree_3_6.blend"
|
|
else:
|
|
link_fp = Path(os.path.dirname(__file__)) / "gp_sim_nodes_tree.blend"
|
|
|
|
return link_fp
|
|
|
|
|
|
def clean_action_of_props(action):
|
|
|
|
if bpy.app.version[0] >= 5:
|
|
fcs = []
|
|
for slot in action.slots:
|
|
fcs.append(anim_utils.action_get_channelbag_for_slot(action, slot))
|
|
else:
|
|
fcs = [action]
|
|
|
|
props = ["gp_end_of_chain",
|
|
"gp_update",
|
|
"gp_sim_",
|
|
"gp_chain_",
|
|
"gp_has_"]
|
|
|
|
for slot in fcs:
|
|
rem_curves = []
|
|
for fc in slot.fcurves:
|
|
if (any([p in fc.data_path for p in props])):
|
|
rem_curves.append(fc)
|
|
|
|
for fc in rem_curves:
|
|
slot.fcurves.remove(fc)
|
|
|
|
return
|
|
|
|
|
|
def set_geo_nodes_input(modifier, input_name, setting):
|
|
|
|
inputs = get_inputs_container(modifier.node_group)
|
|
if bpy.app.version[0] < 5 or (bpy.app.version[0] == 5 and bpy.app.version[1] < 2):
|
|
modifier[inputs[input_name].identifier] = setting
|
|
else:
|
|
prop_attr = getattr(modifier.properties.inputs, inputs[input_name].identifier)
|
|
prop_attr.value = setting
|
|
|
|
return
|
|
|
|
|
|
def get_geo_nodes_input(modifier, input_name):
|
|
|
|
inputs = get_inputs_container(modifier.node_group)
|
|
if bpy.app.version[0] < 5 or (bpy.app.version[0] == 5 and bpy.app.version[1] < 2):
|
|
input_val = modifier[inputs[input_name].identifier]
|
|
else:
|
|
input_val = getattr(modifier.properties.inputs, inputs[input_name].identifier).value
|
|
|
|
return input_val
|
|
|
|
|
|
def get_geo_nodes_input_str(modifier, input_name):
|
|
|
|
inputs = get_inputs_container(modifier.node_group)
|
|
if bpy.app.version[0] < 5 or (bpy.app.version[0] == 5 and bpy.app.version[1] < 2):
|
|
input_container = modifier
|
|
input_str = '["' + inputs[input_name].identifier + '"]'
|
|
else:
|
|
input_str = "value"
|
|
input_container = getattr(modifier.properties.inputs, inputs[input_name].identifier)
|
|
|
|
return input_container, input_str
|
|
|
|
|
|
def get_bone_sel(bone):
|
|
|
|
if bpy.app.version[0] >= 5:
|
|
return bone.select
|
|
else:
|
|
return bone.bone.select
|
|
|
|
|
|
def set_bone_sel(bone, status):
|
|
|
|
if bpy.app.version[0] >= 5:
|
|
bone.select = status
|
|
else:
|
|
bone.bone.select = status
|
|
return
|
|
|
|
|
|
def smart_bake_pose(obj, use_existing=False, frame_start=1, frame_end=250, **kwargs):
|
|
act = getattr(obj.animation_data, "action", None)
|
|
if act is None:
|
|
return
|
|
|
|
keyed_frames = set()
|
|
|
|
action = get_action_slots(act)
|
|
|
|
for curve in action[0].fcurves:
|
|
frame_nums = [0.0 for i in range(len(curve.keyframe_points) * 2)]
|
|
curve.keyframe_points.foreach_get("co", frame_nums)
|
|
for fnum in frame_nums[::2]:
|
|
keyed_frames.add(int(fnum))
|
|
|
|
frames = sorted(filter(lambda x: frame_start <= x <= frame_end, keyed_frames))
|
|
|
|
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=kwargs["only_selected"],
|
|
do_pose=kwargs["do_pose"],
|
|
do_object=kwargs["do_object"],
|
|
do_visual_keying=kwargs["do_visual_keying"],
|
|
do_constraint_clear=kwargs["do_constraint_clear"],
|
|
do_parents_clear=kwargs["do_parents_clear"],
|
|
do_clean=kwargs["do_clean"],
|
|
do_location=True,
|
|
do_rotation=True,
|
|
do_scale=True,
|
|
do_bbone=False,
|
|
do_custom_props=False,
|
|
)
|
|
|
|
anim_utils.bake_action(
|
|
obj,
|
|
action=act if use_existing else None,
|
|
frames=frames,
|
|
bake_options=bake_opts,
|
|
)
|
|
else:
|
|
anim_utils.bake_action(
|
|
obj, action=act if use_existing else None, frames=frames, **kwargs
|
|
)
|
|
|
|
|
|
#
|
|
# HANDLERS
|
|
#
|
|
|
|
|
|
def timeline_sync_handler_frame_post(scene):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
prefs.active_timeline_sync_frames += "," + str(scene.frame_current)
|
|
|
|
frame_start = scene.frame_start
|
|
if scene.use_preview_range:
|
|
frame_start = scene.frame_preview_start
|
|
|
|
if scene.frame_current == frame_start:
|
|
prefs.timeline_sync_frames = prefs.active_timeline_sync_frames
|
|
prefs.active_timeline_sync_frames = str(frame_start)
|
|
|
|
if prefs.precaching:
|
|
bpy.ops.screen.animation_cancel()
|
|
prefs.precaching = False
|
|
|
|
return
|
|
|
|
|
|
def timeline_sync_handler_playback_pre(scene):
|
|
prefs = bpy.context.preferences.addons["goo_physics"].preferences
|
|
|
|
frame_start = scene.frame_start
|
|
if scene.use_preview_range:
|
|
frame_start = scene.frame_preview_start
|
|
|
|
prefs.active_timeline_sync_frames = str(frame_start)
|
|
|
|
if scene.frame_current != frame_start:
|
|
prefs.active_timeline_sync_frames += "," + str(scene.frame_current)
|
|
|
|
bpy.app.handlers.frame_change_post.append(timeline_sync_handler_frame_post)
|
|
|
|
return
|
|
|
|
|
|
def timeline_sync_handler_playback_post(scene):
|
|
|
|
bpy.app.handlers.frame_change_post[:] = [h for h in bpy.app.handlers.frame_change_post if h.__name__ != "timeline_sync_handler_frame_post"]
|
|
|
|
return
|