1734 lines
67 KiB
Python
1734 lines
67 KiB
Python
# Copyright (C) 2021 Victor Soupday
|
|
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
|
|
#
|
|
# CC/iC Blender Tools is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# CC/iC Blender Tools is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
import math
|
|
import os
|
|
import mathutils
|
|
|
|
import bpy
|
|
|
|
from . import geom, bones, imageutils, meshutils, materials, modifiers, utils, jsonutils, vars
|
|
|
|
COLLISION_THICKESS = 0.001
|
|
HAIR_THICKNESS = 0.001
|
|
CLOTH_THICKNESS = 0.004
|
|
|
|
|
|
def apply_cloth_settings(obj, cloth_type, self_collision = False):
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
mod = modifiers.get_cloth_physics_mod(obj)
|
|
if mod is None:
|
|
return
|
|
obj_cache = props.get_object_cache(obj)
|
|
obj_cache.cloth_settings = cloth_type
|
|
|
|
utils.log_info("Setting " + obj.name + " cloth settings to: " + cloth_type)
|
|
mod.settings.vertex_group_mass = prefs.physics_group + "_Pin"
|
|
mod.settings.time_scale = 1
|
|
mod.collision_settings.use_self_collision = self_collision
|
|
|
|
cloth_area = geom.get_area(obj)
|
|
air_dampening_mod = cloth_area / 2.0
|
|
utils.log_info(f"Using cloth area: {cloth_area} sqm, air dampening mod: {air_dampening_mod}")
|
|
BASE_GSM = 1.0 / 2666
|
|
|
|
if cloth_type == "HAIR":
|
|
mod.settings.quality = 6
|
|
mod.settings.pin_stiffness = 0.025
|
|
# physical properties
|
|
mod.settings.mass = 0.05
|
|
mod.settings.air_damping = 1.0
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 5.0
|
|
mod.settings.compression_stiffness = 5.0
|
|
mod.settings.shear_stiffness = 5.0
|
|
mod.settings.bending_stiffness = 5.0
|
|
# dampening
|
|
mod.settings.tension_damping = 0.0
|
|
mod.settings.compression_damping = 0.0
|
|
mod.settings.shear_damping = 0.0
|
|
mod.settings.bending_damping = 0.0
|
|
# collision
|
|
mod.collision_settings.distance_min = HAIR_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0005 # 0.5mm
|
|
mod.collision_settings.self_friction = 1.0
|
|
|
|
elif cloth_type == "DENIM":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.5
|
|
# physical properties
|
|
mod.settings.mass = 400 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 0.35 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 40.0
|
|
mod.settings.compression_stiffness = 40.0
|
|
mod.settings.shear_stiffness = 40.0
|
|
mod.settings.bending_stiffness = 60.0
|
|
# dampening
|
|
mod.settings.tension_damping = 25.0
|
|
mod.settings.compression_damping = 25.0
|
|
mod.settings.shear_damping = 25.0
|
|
mod.settings.bending_damping = 10.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.005 # 5mm
|
|
mod.collision_settings.self_friction = 10.0
|
|
|
|
elif cloth_type == "LEATHER":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.5
|
|
# physical properties
|
|
mod.settings.mass = 800 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 0.25 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 80.0
|
|
mod.settings.compression_stiffness = 80.0
|
|
mod.settings.shear_stiffness = 80.0
|
|
mod.settings.bending_stiffness = 80.0
|
|
# dampening
|
|
mod.settings.tension_damping = 25.0
|
|
mod.settings.compression_damping = 25.0
|
|
mod.settings.shear_damping = 25.0
|
|
mod.settings.bending_damping = 10.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0025 # 5mm
|
|
mod.collision_settings.self_friction = 15.0
|
|
|
|
elif cloth_type == "RUBBER":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.25
|
|
# physical properties
|
|
mod.settings.mass = 650 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 0.25 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 15.0
|
|
mod.settings.compression_stiffness = 15.0
|
|
mod.settings.shear_stiffness = 15.0
|
|
mod.settings.bending_stiffness = 40.0
|
|
# dampening
|
|
mod.settings.tension_damping = 25.0
|
|
mod.settings.compression_damping = 25.0
|
|
mod.settings.shear_damping = 25.0
|
|
mod.settings.bending_damping = 0.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0025 # 2.5mm
|
|
mod.collision_settings.self_friction = 20.0
|
|
|
|
elif cloth_type == "LINEN":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.1
|
|
# physical properties
|
|
mod.settings.mass = 160 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 0.5 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 5.0
|
|
mod.settings.compression_stiffness = 5.0
|
|
mod.settings.shear_stiffness = 5.0
|
|
mod.settings.bending_stiffness = 20.0
|
|
# dampening
|
|
mod.settings.tension_damping = 5.0
|
|
mod.settings.compression_damping = 5.0
|
|
mod.settings.shear_damping = 5.0
|
|
mod.settings.bending_damping = 0.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0025 # 2.5mm
|
|
mod.collision_settings.self_friction = 5.0
|
|
|
|
elif cloth_type == "COTTON":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.075
|
|
# physical properties
|
|
mod.settings.mass = 140 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 0.75 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 2.0
|
|
mod.settings.compression_stiffness = 2.0
|
|
mod.settings.shear_stiffness = 2.0
|
|
mod.settings.bending_stiffness = 10.0
|
|
# dampening
|
|
mod.settings.tension_damping = 2.0
|
|
mod.settings.compression_damping = 2.0
|
|
mod.settings.shear_damping = 2.0
|
|
mod.settings.bending_damping = 0.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0025 # 2.5mm
|
|
mod.collision_settings.self_friction = 5.0
|
|
|
|
elif cloth_type == "SILK":
|
|
mod.settings.quality = 8
|
|
mod.settings.pin_stiffness = 0.05
|
|
# physical properties
|
|
mod.settings.mass = 120 * BASE_GSM
|
|
mod.settings.air_damping = 1.0 + 1.0 * air_dampening_mod
|
|
mod.settings.bending_model = 'ANGULAR'
|
|
# stiffness
|
|
mod.settings.tension_stiffness = 0.5
|
|
mod.settings.compression_stiffness = 0.5
|
|
mod.settings.shear_stiffness = 0.5
|
|
mod.settings.bending_stiffness = 1.0
|
|
# dampening
|
|
mod.settings.tension_damping = 0.0
|
|
mod.settings.compression_damping = 0.0
|
|
mod.settings.shear_damping = 0.0
|
|
mod.settings.bending_damping = 0.0
|
|
# collision
|
|
mod.collision_settings.distance_min = CLOTH_THICKNESS
|
|
mod.collision_settings.collision_quality = 4
|
|
mod.collision_settings.self_distance_min = 0.0025 # 2.5mm
|
|
mod.collision_settings.self_friction = 1.0
|
|
|
|
|
|
def uses_collision_physics(chr_cache, obj):
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
collision_setting = "OFF"
|
|
if obj == obj_cache.get_object():
|
|
collision_setting = obj_cache.collision_physics
|
|
if "rl_collision_physics" in obj:
|
|
collision_setting = obj["rl_collision_physics"]
|
|
if collision_setting == "ON" or collision_setting == "PROXY":
|
|
return True
|
|
# Body objects should use collision physics by default
|
|
if collision_setting == "DEFAULT" and \
|
|
(obj_cache.object_type == "BODY" or obj_cache.object_type == "OCCLUSION"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def apply_collision_physics(chr_cache, obj, obj_cache):
|
|
"""Adds a Collision modifier to the object, depending on the object cache settings.
|
|
Does not overwrite or re-create any existing Collision modifier.
|
|
"""
|
|
|
|
if not (chr_cache and obj and obj_cache):
|
|
return
|
|
|
|
# physics seem to apply better if done in rest pose
|
|
arm = chr_cache.get_armature()
|
|
if arm:
|
|
pose_position = arm.data.pose_position
|
|
arm.data.pose_position = "REST"
|
|
|
|
has_cloth = modifiers.get_cloth_physics_mod(obj) is not None
|
|
|
|
if uses_collision_physics(chr_cache, obj):
|
|
if obj_cache.use_collision_proxy and not has_cloth:
|
|
proxy = chr_cache.get_collision_proxy(obj)
|
|
if not proxy:
|
|
proxy = create_collision_proxy(chr_cache, obj_cache, obj)
|
|
if proxy:
|
|
obj = proxy
|
|
else:
|
|
remove_collision_proxy(chr_cache, obj)
|
|
collision_mod = modifiers.get_collision_physics_mod(obj)
|
|
if not collision_mod:
|
|
collision_mod = obj.modifiers.new(utils.unique_name("Collision"), type="COLLISION")
|
|
collision_mod.settings.thickness_outer = COLLISION_THICKESS
|
|
utils.log_info("Collision Modifier: " + collision_mod.name + " applied to " + obj.name)
|
|
|
|
elif obj_cache.collision_physics == "OFF":
|
|
remove_collision_physics(chr_cache, obj)
|
|
utils.log_info("Collision Physics disabled for: " + obj.name)
|
|
|
|
if arm:
|
|
arm.data.pose_position = pose_position
|
|
|
|
|
|
def remove_collision_physics(chr_cache, obj):
|
|
"""Removes the Collision modifier from the object.
|
|
"""
|
|
|
|
if chr_cache and obj:
|
|
|
|
proxy = chr_cache.get_collision_proxy(obj)
|
|
if proxy:
|
|
utils.delete_mesh_object(proxy)
|
|
|
|
for mod in obj.modifiers:
|
|
if mod.type == "COLLISION":
|
|
utils.log_info("Removing Collision modifer: " + mod.name + " from: " + obj.name)
|
|
obj.modifiers.remove(mod)
|
|
|
|
|
|
def add_cloth_physics(chr_cache, obj, add_weight_maps = False):
|
|
"""Adds a Cloth modifier to the object depending on the object cache settings.
|
|
|
|
Does not overwrite or re-create any existing Cloth modifier.
|
|
Sets the cache bake range to the same as any action on the character's armature.
|
|
Applies cloth settings based on the object cache settings.
|
|
Repopulates the existing weight maps, depending on their cache settings.
|
|
"""
|
|
|
|
prefs = vars.prefs()
|
|
props = vars.props()
|
|
|
|
if not (chr_cache and obj):
|
|
return
|
|
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
|
|
if (obj_cache and
|
|
obj_cache.cloth_physics == "ON"):
|
|
|
|
utils.object_mode_to(obj)
|
|
|
|
# Add weight maps
|
|
if add_weight_maps:
|
|
for mat in obj.data.materials:
|
|
if mat:
|
|
add_material_weight_map(chr_cache, obj, mat, create = False)
|
|
if modifiers.has_cloth_weight_map_mods(obj):
|
|
attach_cloth_weight_map_remap(obj, prefs.physics_weightmap_curve)
|
|
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
|
|
if not cloth_mod:
|
|
# Create the Cloth modifier
|
|
cloth_mod : bpy.types.ClothModifier
|
|
cloth_mod_id = utils.unique_name("Cloth")
|
|
cloth_mod = obj.modifiers.new(cloth_mod_id, type="CLOTH")
|
|
utils.log_info("Cloth Modifier: " + cloth_mod.name + " applied to " + obj.name)
|
|
|
|
# Set cache bake frame range
|
|
frame_start, frame_end = utils.get_scene_frame_range()
|
|
utils.log_info(f"Setting {obj.name} bake cache frame range to [{str(frame_start)} - {str(frame_end)}]")
|
|
cloth_mod.point_cache.frame_start = frame_start
|
|
cloth_mod.point_cache.frame_end = frame_end
|
|
if not cloth_mod.point_cache.name:
|
|
random_id = utils.generate_random_id(10)
|
|
cache_id = f"{obj.name}_{random_id}"
|
|
cloth_mod.point_cache.name = cache_id
|
|
|
|
# Apply cloth settings
|
|
if obj_cache.cloth_settings != "DEFAULT":
|
|
apply_cloth_settings(obj,
|
|
obj_cache.cloth_settings,
|
|
self_collision = obj_cache.cloth_self_collision)
|
|
elif obj_cache.object_type == "HAIR":
|
|
apply_cloth_settings(obj, "HAIR")
|
|
else:
|
|
apply_cloth_settings(obj, "COTTON")
|
|
|
|
# fix mod order
|
|
arrange_physics_modifiers(obj)
|
|
|
|
elif obj_cache.cloth_physics == "OFF":
|
|
utils.log_info("Cloth Physics disabled for: " + obj.name)
|
|
|
|
|
|
def remove_cloth_physics(obj):
|
|
"""Removes the Cloth modifier from the object.
|
|
|
|
Also removes any active weight maps and also removes the weight map vertex group.
|
|
"""
|
|
|
|
if not obj:
|
|
return
|
|
|
|
prefs = vars.prefs()
|
|
|
|
utils.object_mode_to(obj)
|
|
|
|
# Remove the Cloth modifier
|
|
for mod in obj.modifiers:
|
|
if mod.type == "CLOTH":
|
|
utils.log_info("Removing Cloth modifer: " + mod.name + " from: " + obj.name)
|
|
obj.modifiers.remove(mod)
|
|
|
|
# Remove any weight maps
|
|
for mat in obj.data.materials:
|
|
if mat:
|
|
remove_material_weight_maps(obj, mat)
|
|
weight_group = prefs.physics_group + "_" + utils.strip_name(mat.name)
|
|
if weight_group in obj.vertex_groups:
|
|
obj.vertex_groups.remove(obj.vertex_groups[weight_group])
|
|
remove_cloth_weight_map_remap(obj)
|
|
|
|
# If there are no weight maps left on the object, remove the vertex group
|
|
mods = 0
|
|
for mod in obj.modifiers:
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and vars.NODE_PREFIX in mod.name:
|
|
mods += 1
|
|
|
|
pin_group = prefs.physics_group + "_Pin"
|
|
if mods == 0 and pin_group in obj.vertex_groups:
|
|
utils.log_info("Removing vertex group: " + pin_group + " from: " + obj.name)
|
|
obj.vertex_groups.remove(obj.vertex_groups[pin_group])
|
|
|
|
|
|
def remove_all_physics_mods(obj):
|
|
"""Removes all physics modifiers from the object.
|
|
|
|
Used when (re)building the character materials.
|
|
"""
|
|
|
|
utils.log_info("Removing all related physics modifiers from: " + obj.name)
|
|
for mod in obj.modifiers:
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and vars.NODE_PREFIX in mod.name:
|
|
obj.modifiers.remove(mod)
|
|
elif mod.type == "VERTEX_WEIGHT_MIX" and vars.NODE_PREFIX in mod.name:
|
|
obj.modifiers.remove(mod)
|
|
elif mod.type == "CLOTH":
|
|
obj.modifiers.remove(mod)
|
|
elif mod.type == "COLLISION":
|
|
obj.modifiers.remove(mod)
|
|
|
|
|
|
def enable_collision_physics(chr_cache, obj):
|
|
props = vars.props()
|
|
if chr_cache and utils.object_exists_is_mesh(obj):
|
|
obj, proxy, is_proxy = chr_cache.get_related_physics_objects(obj)
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache:
|
|
has_cloth = modifiers.get_cloth_physics_mod(obj) is not None
|
|
collision_mode = "PROXY" if (obj_cache.use_collision_proxy and not has_cloth) else "ON"
|
|
if obj == obj_cache.get_object():
|
|
obj_cache.collision_physics = collision_mode
|
|
obj["rl_collision_physics"] = collision_mode
|
|
utils.log_info("Enabling Collision physics for: " + obj.name)
|
|
apply_collision_physics(chr_cache, obj, obj_cache)
|
|
|
|
|
|
def disable_collision_physics(chr_cache, obj):
|
|
if chr_cache and utils.object_exists_is_mesh(obj):
|
|
obj, proxy, is_proxy = chr_cache.get_related_physics_objects(obj)
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache:
|
|
if obj == obj_cache.get_object():
|
|
obj_cache.collision_physics = "OFF"
|
|
obj["rl_collision_physics"] = "OFF"
|
|
utils.log_info("Disabling Collision physics for: " + obj.name)
|
|
remove_collision_physics(chr_cache, obj)
|
|
|
|
|
|
def show_hide_collision_proxies(context, chr_cache, show, select=False, use_local=False):
|
|
if chr_cache:
|
|
objects = []
|
|
proxies = []
|
|
# get all character objects with proxies
|
|
for obj in chr_cache.get_cache_objects():
|
|
obj, proxy, is_proxy = chr_cache.get_related_physics_objects(obj)
|
|
if obj and proxy:
|
|
objects.append(obj)
|
|
proxies.append(proxy)
|
|
# show / hide all proxy objects
|
|
if show:
|
|
if proxies:
|
|
for proxy in proxies:
|
|
utils.unhide(proxy)
|
|
utils.object_mode()
|
|
if select or use_local:
|
|
utils.try_select_objects(proxies, clear_selection=True)
|
|
if use_local and not utils.is_local_view(context):
|
|
bpy.ops.view3d.localview()
|
|
return True
|
|
else:
|
|
if proxies:
|
|
for proxy in proxies:
|
|
utils.hide(proxy)
|
|
utils.object_mode()
|
|
if select:
|
|
utils.try_select_objects(objects, clear_selection=True)
|
|
if use_local and utils.is_local_view(context):
|
|
bpy.ops.view3d.localview()
|
|
return False
|
|
|
|
|
|
def enable_cloth_physics(chr_cache, obj, add_weight_maps = True):
|
|
props = vars.props()
|
|
if chr_cache and obj:
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache:
|
|
obj_cache.cloth_physics = "ON"
|
|
utils.log_info("Enabling Cloth physics for: " + obj.name)
|
|
add_cloth_physics(chr_cache, obj, add_weight_maps)
|
|
|
|
|
|
def disable_cloth_physics(chr_cache, obj):
|
|
props = vars.props()
|
|
if chr_cache and obj:
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache:
|
|
obj_cache.cloth_physics = "OFF"
|
|
utils.log_info("Removing cloth physics for: " + obj.name)
|
|
remove_cloth_physics(obj)
|
|
|
|
|
|
def remove_collision_proxy(chr_cache, obj):
|
|
proxy = chr_cache.get_collision_proxy(obj)
|
|
if proxy:
|
|
utils.log_info(f"Removing collision proxy: {proxy.name}")
|
|
utils.delete_mesh_object(proxy)
|
|
|
|
|
|
def create_collision_proxy(chr_cache, obj_cache, obj):
|
|
utils.log_info(f"Creating collision proxy mesh from: {obj.name}")
|
|
# remove old proxy
|
|
remove_collision_proxy(chr_cache, obj)
|
|
# clone obj to proxy
|
|
collision_proxy = utils.duplicate_object(obj)
|
|
collision_proxy.name = obj.name + ".Collision_Proxy"
|
|
collision_proxy["rl_collision_proxy"] = obj.name
|
|
if utils.object_mode_to(collision_proxy) and utils.set_only_active_object(collision_proxy):
|
|
# remove shape keys
|
|
collision_proxy.shape_key_clear()
|
|
# remove eye-lashes
|
|
eye_lash_mat = materials.get_material_by_type(chr_cache, collision_proxy, "EYELASH")
|
|
if eye_lash_mat:
|
|
meshutils.remove_material_verts(collision_proxy, eye_lash_mat)
|
|
# remove materials
|
|
collision_proxy.data.materials.clear()
|
|
# delete loose
|
|
utils.edit_mode_to(collision_proxy)
|
|
bpy.ops.mesh.select_all(action="SELECT")
|
|
bpy.ops.mesh.delete_loose(use_verts=True, use_edges=True, use_faces=True)
|
|
utils.object_mode_to(collision_proxy)
|
|
if obj_cache.collision_proxy_decimate < 1.0:
|
|
# add decimate modifier
|
|
mod = modifiers.add_decimate_modifier(collision_proxy,
|
|
obj_cache.collision_proxy_decimate,
|
|
"Decimate_Collision_Body")
|
|
modifiers.move_mod_first(collision_proxy, mod)
|
|
# apply decimate modifier
|
|
bpy.ops.object.modifier_apply(modifier=mod.name)
|
|
|
|
utils.log_info(f"Storing collision mesh: {collision_proxy.name}")
|
|
obj_cache.use_collision_proxy = True
|
|
obj_cache.collision_physics = "PROXY"
|
|
utils.hide(collision_proxy)
|
|
collision_proxy.hide_render = True
|
|
utils.set_only_active_object(obj)
|
|
return collision_proxy
|
|
|
|
|
|
def get_weight_map_from_modifiers(obj, mat):
|
|
mat_name = "_" + utils.strip_name(mat.name) + "_"
|
|
if obj.type == "MESH":
|
|
for mod in obj.modifiers:
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and vars.NODE_PREFIX in mod.name and mat_name in mod.name:
|
|
if mod.mask_texture is not None and mod.mask_texture.image is not None:
|
|
image = mod.mask_texture.image
|
|
return image
|
|
return None
|
|
|
|
|
|
def get_weight_map_image(chr_cache, obj, mat, create = False):
|
|
"""Returns the weight map image for the material.
|
|
|
|
Fetches the Image for the given materials weight map, if it exists.
|
|
If not, the image can be created and packed into the blend file and stored
|
|
in the material cache as a temporary weight map image.
|
|
"""
|
|
|
|
props = vars.props()
|
|
mat_cache = props.get_material_cache(mat)
|
|
weight_map = imageutils.find_material_image(mat, "WEIGHTMAP")
|
|
|
|
if mat_cache:
|
|
|
|
if weight_map:
|
|
if weight_map.size[0] == 0 or weight_map.size[1] == 0:
|
|
weight_map = None
|
|
else:
|
|
mat_cache.temp_weight_map = weight_map
|
|
|
|
if weight_map is None and create:
|
|
name = utils.strip_name(mat.name) + "_WeightMap"
|
|
tex_size = int(props.physics_tex_size)
|
|
weight_map = bpy.data.images.new(name, tex_size, tex_size, is_data=False)
|
|
# make the image 'dirty' so it converts to a file based image which can be saved:
|
|
weight_map.pixels[0] = 0.0
|
|
weight_map.file_format = "PNG"
|
|
weight_map.filepath_raw = os.path.join(mat_cache.get_tex_dir(chr_cache), name + ".png")
|
|
weight_map.save()
|
|
# keep track of which weight maps we created:
|
|
mat_cache.temp_weight_map = weight_map
|
|
utils.log_info("Weight-map image: " + weight_map.name + " created and saved.")
|
|
|
|
return weight_map
|
|
|
|
|
|
def add_material_weight_map(chr_cache, obj, mat, create = False):
|
|
"""Adds a weight map 'Vertex Weight Edit' modifier for the object's material.
|
|
|
|
Gets or creates (if instructed) the material's weight map then creates
|
|
or re-creates the modifier to generate the physics 'Pin' vertex group.
|
|
"""
|
|
|
|
if chr_cache and obj and mat:
|
|
|
|
if cloth_physics_available(chr_cache, obj, mat):
|
|
if create:
|
|
weight_map = get_weight_map_image(chr_cache, obj, mat, create)
|
|
else:
|
|
weight_map = imageutils.find_material_image(mat, "WEIGHTMAP")
|
|
|
|
remove_material_weight_maps(obj, mat)
|
|
if weight_map is not None:
|
|
attach_material_weight_map(obj, mat, weight_map)
|
|
else:
|
|
utils.log_info("Cloth Physics has been disabled for: " + obj.name)
|
|
|
|
|
|
def remove_material_weight_maps(obj, mat):
|
|
"""Removes the weight map 'Vertex Weight Edit' modifier for the object's material.
|
|
|
|
This does not remove or delete the weight map image or temporary packed image,
|
|
or the texture based on the weight map image, just the modifier.
|
|
"""
|
|
|
|
if obj and mat:
|
|
|
|
edit_mod, mix_mod = modifiers.get_material_weight_map_mods(obj, mat)
|
|
if edit_mod is not None:
|
|
utils.log_info("Removing weight map vertex edit modifer: " + edit_mod.name)
|
|
obj.modifiers.remove(edit_mod)
|
|
if mix_mod is not None:
|
|
utils.log_info("Removing weight map vertex mix modifer: " + mix_mod.name)
|
|
obj.modifiers.remove(mix_mod)
|
|
|
|
|
|
def enable_material_weight_map(chr_cache, obj, mat):
|
|
"""Enables the weight map for the object's material and (re)creates the Vertex Weight Edit modifier.
|
|
"""
|
|
|
|
prefs = vars.prefs()
|
|
props = vars.props()
|
|
|
|
if chr_cache and obj and mat:
|
|
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
if mat_cache:
|
|
if mat_cache.cloth_physics == "OFF":
|
|
mat_cache.cloth_physics = "ON"
|
|
add_material_weight_map(chr_cache, obj, mat, True)
|
|
if modifiers.has_cloth_weight_map_mods(obj):
|
|
attach_cloth_weight_map_remap(obj, prefs.physics_weightmap_curve)
|
|
# fix mod order
|
|
arrange_physics_modifiers(obj)
|
|
|
|
|
|
def disable_material_weight_map(chr_cache, obj, mat):
|
|
"""Disables the weight map for the object's material and removes the Vertex Weight Edit modifier.
|
|
"""
|
|
|
|
if chr_cache and obj and mat:
|
|
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
mat_cache.cloth_physics = "OFF"
|
|
remove_material_weight_maps(obj, mat)
|
|
|
|
|
|
def collision_physics_available(chr_cache, obj):
|
|
"""Is cloth collisions physics allowed on the character object?"""
|
|
|
|
if chr_cache and obj:
|
|
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
collision_mod = modifiers.get_collision_physics_mod(obj)
|
|
if collision_mod is None:
|
|
if obj_cache.collision_physics == "OFF":
|
|
return False
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def cloth_physics_available(chr_cache, obj, mat):
|
|
"""Is cloth physics allowed on this character object and material?"""
|
|
|
|
if chr_cache and obj and mat:
|
|
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
mat_cache = chr_cache.get_material_cache(mat)
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
if cloth_mod is None:
|
|
if obj_cache.cloth_physics == "OFF":
|
|
return False
|
|
if mat_cache is not None and mat_cache.cloth_physics == "OFF":
|
|
return False
|
|
else:
|
|
# if cloth physics was disabled by the add-on,
|
|
# but re-enabled in the physics panel,
|
|
# correct the object cache setting:
|
|
if obj_cache.cloth_physics == "OFF":
|
|
obj_cache.cloth_physics == "ON"
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def is_cloth_physics_enabled(mat_cache, mat, obj):
|
|
"""Is cloth physics enabled on this character object and material?"""
|
|
|
|
if mat_cache and obj and mat:
|
|
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
edit_mods, mix_mods = modifiers.get_material_weight_map_mods(obj, mat)
|
|
if cloth_mod and edit_mods and mix_mods:
|
|
if mat_cache and mat_cache.cloth_physics != "OFF":
|
|
return True
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
def attach_cloth_weight_map_remap(obj, replace = True, curve_power = 5.0):
|
|
"""Attach the final remap vertex weight edit to convert the physx weight
|
|
map values to something more blender physics friendly."""
|
|
|
|
prefs = vars.prefs()
|
|
remap_mod : bpy.types.VertexWeightEditModifier
|
|
|
|
if replace:
|
|
modifiers.remove_object_modifiers(obj, "VERTEX_WEIGHT_EDIT", "_WeightEditRemap")
|
|
else:
|
|
remap_mod = modifiers.get_object_modifier(obj, "VERTEX_WEIGHT_EDIT", "_WeightEditRemap")
|
|
if remap_mod:
|
|
return
|
|
|
|
pin_group = prefs.physics_group + "_Pin"
|
|
obj_name = utils.safe_export_name(obj.name)
|
|
|
|
remap_mod = obj.modifiers.new(utils.unique_name(obj_name + "_WeightEditRemap"), "VERTEX_WEIGHT_EDIT")
|
|
remap_mod.use_add = False
|
|
remap_mod.use_remove = False
|
|
remap_mod.vertex_group = pin_group
|
|
remap_mod.default_weight = 0.0
|
|
remap_mod.falloff_type = 'CURVE'
|
|
remap_mod.invert_falloff = False
|
|
#remap_mod.map_curve.curves[0].points.new(0.25, pow(0.25, curve_power))
|
|
#remap_mod.map_curve.curves[0].points.new(0.5, pow(0.5, curve_power))
|
|
remap_mod.map_curve.curves[0].points.new(0.75, pow(0.75, curve_power))
|
|
remap_mod.map_curve.update()
|
|
|
|
|
|
def remove_cloth_weight_map_remap(obj):
|
|
modifiers.remove_object_modifiers(obj, "VERTEX_WEIGHT_EDIT", "_WeightEditRemap")
|
|
|
|
|
|
def attach_material_weight_map(obj, mat, weight_map):
|
|
"""Attaches a weight map to the object's material via a 'Vertex Weight Edit' modifier.
|
|
|
|
This will attach the supplied weight map or will try to find an existing weight map,
|
|
but will not create a new weight map if it doesn't already exist.
|
|
"""
|
|
|
|
prefs = vars.prefs()
|
|
|
|
if obj and mat and weight_map:
|
|
|
|
# Make a texture based on the weight map image
|
|
# As we are matching names to find existing textures,
|
|
# get a name that keeps the duplication suffix
|
|
mat_name = utils.safe_export_name(mat.name)
|
|
tex_name = mat_name + "_Weight"
|
|
tex = None
|
|
for t in bpy.data.textures:
|
|
if t.name.startswith(vars.NODE_PREFIX + tex_name):
|
|
tex = t
|
|
if tex is None:
|
|
tex = bpy.data.textures.new(utils.unique_name(tex_name), "IMAGE")
|
|
utils.log_info("Texture: " + tex.name + " created for weight map transfer")
|
|
else:
|
|
utils.log_info("Texture: " + tex.name + " already exists for weight map transfer")
|
|
tex.image = weight_map
|
|
|
|
# Create the physics pin vertex group and the material weightmap group if they don't exist:
|
|
pin_group = prefs.physics_group + "_Pin"
|
|
material_group = prefs.physics_group + "_" + mat_name
|
|
if pin_group not in obj.vertex_groups:
|
|
pin_vertex_group = obj.vertex_groups.new(name = pin_group)
|
|
else:
|
|
pin_vertex_group = obj.vertex_groups[pin_group]
|
|
if material_group not in obj.vertex_groups:
|
|
weight_vertex_group = obj.vertex_groups.new(name = material_group)
|
|
else:
|
|
weight_vertex_group = obj.vertex_groups[material_group]
|
|
|
|
# The material weight map group should contain only those vertices affected by the material, default weight to 1.0
|
|
meshutils.clear_vertex_group(obj, weight_vertex_group)
|
|
mat_vert_indices = meshutils.get_material_vertex_indices(obj, mat)
|
|
weight_vertex_group.add(mat_vert_indices, 1.0, 'ADD')
|
|
|
|
# The pin group should contain all vertices in the mesh default weighted to 1.0
|
|
meshutils.set_vertex_group(obj, pin_vertex_group, 1.0)
|
|
|
|
# set the pin group in the cloth physics modifier
|
|
mod_cloth = modifiers.get_cloth_physics_mod(obj)
|
|
if mod_cloth is not None:
|
|
mod_cloth.settings.vertex_group_mass = pin_group
|
|
|
|
# re-create create the Vertex Weight Edit modifier and the Vertex Weight Mix modifer
|
|
remove_material_weight_maps(obj, mat)
|
|
edit_mod : bpy.types.VertexWeightEditModifier
|
|
edit_mod = obj.modifiers.new(utils.unique_name(mat_name + "_WeightEdit"), "VERTEX_WEIGHT_EDIT")
|
|
mix_mod = obj.modifiers.new(utils.unique_name(mat_name + "_WeightMix"), "VERTEX_WEIGHT_MIX")
|
|
# Use the texture as the modifiers vertex weight source
|
|
edit_mod.mask_texture = tex
|
|
# Setup the modifier to generate the inverse of the weight map in the vertex group
|
|
edit_mod.use_add = False
|
|
edit_mod.use_remove = False
|
|
edit_mod.add_threshold = 0.01
|
|
edit_mod.remove_threshold = 0.01
|
|
edit_mod.vertex_group = material_group
|
|
edit_mod.default_weight = 1
|
|
edit_mod.falloff_type = 'LINEAR'
|
|
edit_mod.invert_falloff = True
|
|
edit_mod.mask_constant = 1
|
|
edit_mod.mask_tex_mapping = 'UV'
|
|
edit_mod.mask_tex_use_channel = 'INT'
|
|
try:
|
|
edit_mod.normalize = False
|
|
except:
|
|
pass
|
|
# The Vertex Weight Mix modifier takes the material weight map group and mixes it into the pin weight group:
|
|
# (this allows multiple weight maps from different materials and UV layouts to combine in the same mesh)
|
|
mix_mod.vertex_group_a = pin_group
|
|
mix_mod.vertex_group_b = material_group
|
|
mix_mod.invert_mask_vertex_group = True
|
|
mix_mod.default_weight_a = 1
|
|
mix_mod.default_weight_b = 1
|
|
mix_mod.mix_set = 'B' #'ALL'
|
|
mix_mod.mix_mode = 'SET'
|
|
mix_mod.invert_mask_vertex_group = False
|
|
utils.log_info("Weight map: " + weight_map.name + " applied to: " + obj.name + "/" + mat.name)
|
|
|
|
|
|
def get_physx_weight_range(obj):
|
|
"""Get the range (min, max) of weights for physics pin group"""
|
|
|
|
props = vars.props()
|
|
prefs = vars.prefs()
|
|
|
|
weight_min = 1.0
|
|
weight_max = 0.0
|
|
|
|
if obj:
|
|
|
|
vertex_group_name = prefs.physics_group + "_Pin"
|
|
|
|
if obj.type == "MESH" and vertex_group_name in obj.vertex_groups:
|
|
|
|
if utils.set_active_object(obj):
|
|
|
|
# normalize pin vertex group range
|
|
pin_vg = obj.vertex_groups[vertex_group_name]
|
|
pin_vg_index = pin_vg.index
|
|
|
|
# determine range
|
|
for vertex in obj.data.vertices:
|
|
for vg in vertex.groups:
|
|
if vg.group == pin_vg_index:
|
|
w = vg.weight
|
|
weight_min = min(w, weight_min)
|
|
weight_max = max(w, weight_max)
|
|
|
|
return weight_min, weight_max
|
|
|
|
|
|
def count_weightmaps(objects):
|
|
num_maps = 0
|
|
num_dirty = 0
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
for mod in obj.modifiers:
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and vars.NODE_PREFIX in mod.name:
|
|
if mod.mask_texture is not None and mod.mask_texture.image is not None:
|
|
num_maps += 1
|
|
image = mod.mask_texture.image
|
|
if image.is_dirty:
|
|
num_dirty += 1
|
|
return num_maps, num_dirty
|
|
|
|
|
|
def get_dirty_weightmaps(objects):
|
|
maps = []
|
|
for obj in objects:
|
|
if obj.type == "MESH":
|
|
for mod in obj.modifiers:
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and vars.NODE_PREFIX in mod.name:
|
|
if mod.mask_texture is not None and mod.mask_texture.image is not None:
|
|
image = mod.mask_texture.image
|
|
abs_image_path = bpy.path.abspath(image.filepath)
|
|
if image.filepath != "" and (image.is_dirty or not os.path.exists(abs_image_path)):
|
|
maps.append(image)
|
|
return maps
|
|
|
|
|
|
def physics_paint_strength_update(self, context):
|
|
props = vars.props()
|
|
|
|
if context.mode == "PAINT_TEXTURE":
|
|
if not utils.B500():
|
|
ups = context.tool_settings.unified_paint_settings
|
|
prop_owner = ups if ups.use_unified_color else context.tool_settings.image_paint.brush
|
|
else:
|
|
prop_owner = context.tool_settings.image_paint.brush
|
|
s = props.physics_paint_strength
|
|
prop_owner.color = (s, s, s)
|
|
|
|
|
|
def weight_strength_update(self, context):
|
|
props = vars.props()
|
|
|
|
strength = props.weight_map_strength
|
|
influence = 1 - math.pow(1 - strength, 3)
|
|
edit_mod, mix_mod = modifiers.get_material_weight_map_mods(context.object, utils.get_context_material(context))
|
|
mix_mod.mask_constant = influence
|
|
|
|
|
|
def browse_weight_map(chr_cache, context):
|
|
obj = context.object
|
|
mat = utils.get_context_material(context)
|
|
if obj and mat:
|
|
weight_map = get_weight_map_image(chr_cache, obj, mat)
|
|
if weight_map:
|
|
path = bpy.path.abspath(weight_map.filepath)
|
|
utils.show_system_file_browser(path)
|
|
|
|
|
|
def begin_paint_weight_map(context, chr_cache):
|
|
obj = context.object
|
|
mat = utils.get_context_material(context)
|
|
props = vars.props()
|
|
shading = utils.get_view_3d_shading(context)
|
|
if obj is not None and mat is not None:
|
|
if shading:
|
|
props.paint_store_render = shading.type
|
|
else:
|
|
props.paint_store_render = "MATERIAL"
|
|
|
|
if context.mode != "PAINT_TEXTURE":
|
|
bpy.ops.object.mode_set(mode="TEXTURE_PAINT")
|
|
|
|
if context.mode == "PAINT_TEXTURE":
|
|
physics_paint_strength_update(None, context)
|
|
weight_map = get_weight_map_image(chr_cache, obj, mat)
|
|
weight_map.update()
|
|
props.paint_object = obj
|
|
props.paint_material = mat
|
|
props.paint_image = weight_map
|
|
shading = utils.get_view_3d_shading(context)
|
|
if weight_map is not None:
|
|
bpy.context.scene.tool_settings.image_paint.mode = 'IMAGE'
|
|
bpy.context.scene.tool_settings.image_paint.canvas = weight_map
|
|
if shading:
|
|
shading.type = 'SOLID'
|
|
|
|
|
|
def resize_weight_map(chr_cache, context, op):
|
|
props = vars.props()
|
|
|
|
if context.mode == "PAINT_TEXTURE":
|
|
return
|
|
|
|
obj = context.object
|
|
mat = utils.get_context_material(context)
|
|
props = vars.props()
|
|
if obj is not None and mat is not None:
|
|
weight_map : bpy.types.Image = get_weight_map_image(chr_cache, obj, mat)
|
|
size = int(props.physics_tex_size)
|
|
if weight_map and (weight_map.size[0] != size or weight_map.size[1] != size):
|
|
weight_map.scale(size, size)
|
|
# force Blender to update the image by changing a pixel value
|
|
# otherwise it doesn't recognise the size change.
|
|
value = weight_map.pixels[0]
|
|
weight_map.pixels[0] = 0.0
|
|
weight_map.pixels[0] = value
|
|
weight_map.update()
|
|
op.report({'INFO'}, f"Weightmap Resized to: {size} x {size}")
|
|
|
|
|
|
|
|
def end_paint_weight_map(op, context, chr_cache):
|
|
try:
|
|
props = vars.props()
|
|
shading = utils.get_view_3d_shading(context)
|
|
if context.mode != "OBJECT":
|
|
bpy.ops.object.mode_set(mode="OBJECT")
|
|
if shading:
|
|
shading.type = props.paint_store_render
|
|
#props.paint_image.save()
|
|
op.report({'INFO'}, f"Weightmap painting done, Save the weightmap to preserve changes.")
|
|
except Exception as e:
|
|
utils.log_error("Something went wrong restoring object mode from paint mode!", e)
|
|
|
|
|
|
def save_dirty_weight_maps(chr_cache, objects):
|
|
"""Saves all altered active weight map images to their respective material folders.
|
|
|
|
Also saves any missing weight maps.
|
|
"""
|
|
|
|
maps = get_dirty_weightmaps(objects)
|
|
|
|
for weight_map in maps:
|
|
if weight_map.is_dirty:
|
|
utils.log_info("Dirty weight map: " + weight_map.name + " : " + weight_map.filepath)
|
|
weight_map.save()
|
|
utils.log_info("Weight Map: " + weight_map.name + " saved to: " + weight_map.filepath)
|
|
if not os.path.exists(weight_map.filepath):
|
|
utils.log_info("Missing weight map: " + weight_map.name + " : " + weight_map.filepath)
|
|
weight_map.save()
|
|
utils.log_info("Weight Map: " + weight_map.name + " saved to: " + weight_map.filepath)
|
|
|
|
|
|
def delete_selected_weight_map(chr_cache, obj, mat):
|
|
if obj is not None and obj.type == "MESH" and mat is not None:
|
|
edit_mod, mix_mod = modifiers.get_material_weight_map_mods(obj, mat)
|
|
if edit_mod is not None and edit_mod.mask_texture is not None and edit_mod.mask_texture.image is not None:
|
|
image = edit_mod.mask_texture.image
|
|
abs_image_path = bpy.path.abspath(image.filepath)
|
|
try:
|
|
if image.filepath != "" and os.path.exists(abs_image_path):
|
|
utils.log_info("Removing weight map file: " + abs_image_path)
|
|
os.remove(abs_image_path)
|
|
except Exception as e:
|
|
utils.log_error("Removing weight map file: " + abs_image_path, e)
|
|
if edit_mod is not None:
|
|
utils.log_info("Removing 'Vertex Weight Edit' modifer")
|
|
obj.modifiers.remove(edit_mod)
|
|
if mix_mod is not None:
|
|
utils.log_info("Removing 'Vertex Weight Mix' modifer")
|
|
obj.modifiers.remove(mix_mod)
|
|
|
|
|
|
def get_context_physics_objects(context, from_selected=False):
|
|
props = vars.props()
|
|
chr_cache = props.get_context_character_cache(context)
|
|
physics_objects = []
|
|
if chr_cache:
|
|
if from_selected:
|
|
objects = context.selected_objects.copy()
|
|
else:
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
for obj in objects:
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
coll_mod = modifiers.get_collision_physics_mod(obj)
|
|
if cloth_mod or coll_mod:
|
|
physics_objects.append(obj)
|
|
|
|
return physics_objects
|
|
|
|
|
|
def get_scene_physics_state():
|
|
has_cloth = False
|
|
has_collision = False
|
|
has_rigidbody = False
|
|
all_baked = True
|
|
all_baking = True
|
|
any_baked = False
|
|
any_baking = False
|
|
|
|
for scene in bpy.data.scenes:
|
|
for object in scene.objects:
|
|
for modifier in object.modifiers:
|
|
if modifier.type == 'CLOTH':
|
|
has_cloth = True
|
|
if modifier.point_cache.is_baked:
|
|
any_baked = True
|
|
else:
|
|
all_baked = False
|
|
if modifier.point_cache.is_baking:
|
|
any_baking = True
|
|
else:
|
|
all_baking = False
|
|
elif modifier.type == 'COLLISION':
|
|
has_collision = True
|
|
|
|
all_baked = has_cloth and all_baked
|
|
all_baking = has_cloth and all_baking
|
|
|
|
if bpy.context.scene.rigidbody_world:
|
|
rbw = bpy.context.scene.rigidbody_world
|
|
has_rigidbody = True
|
|
if rbw.point_cache.is_baked:
|
|
any_baked = True
|
|
else:
|
|
all_baked = False
|
|
if rbw.point_cache.is_baking:
|
|
any_baking = True
|
|
else:
|
|
all_baking = False
|
|
|
|
return has_cloth, has_collision, has_rigidbody, all_baked, any_baked, all_baking, any_baking
|
|
|
|
|
|
def reset_physics_cache(obj, start, end):
|
|
cloth_mod : bpy.types.ClothModifier
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
if cloth_mod is not None:
|
|
# free the baked cache
|
|
if cloth_mod.point_cache.is_baked:
|
|
free_cache(obj)
|
|
# invalidate the cache
|
|
utils.log_info("Invalidating point cache...")
|
|
qs = cloth_mod.settings.quality
|
|
cloth_mod.point_cache.frame_start = 1
|
|
cloth_mod.point_cache.frame_end = 1
|
|
cloth_mod.settings.quality = 1
|
|
# reset the cache
|
|
utils.log_info("Setting " + obj.name + " bake cache frame range to [" + str(start) + " - " + str(end) + "]")
|
|
cloth_mod.point_cache.frame_start = start
|
|
cloth_mod.point_cache.frame_end = end
|
|
cloth_mod.settings.quality = qs
|
|
# use disk cache if the blend file is saved
|
|
if bpy.data.filepath:
|
|
cloth_mod.point_cache.use_disk_cache = True
|
|
return True
|
|
return False
|
|
|
|
|
|
def reset_physics(context: bpy.types.Context, all_objects=False):
|
|
# stop any playing animation
|
|
if context.screen.is_animation_playing:
|
|
bpy.ops.screen.animation_cancel(restore_frame=False)
|
|
# jump to end
|
|
bpy.ops.screen.frame_jump(end=True)
|
|
# reset the physics
|
|
reset_cache(context, all_objects=all_objects)
|
|
# reset the animation
|
|
bpy.ops.screen.frame_jump(end=False)
|
|
# set to no frame skip
|
|
context.scene.sync_mode = "NONE"
|
|
|
|
|
|
def reset_cache(context, all_objects=False):
|
|
if bpy.context.scene.use_preview_range:
|
|
start = bpy.context.scene.frame_preview_start
|
|
end = bpy.context.scene.frame_preview_end
|
|
else:
|
|
start = bpy.context.scene.frame_start
|
|
end = bpy.context.scene.frame_end
|
|
|
|
if all_objects:
|
|
objects = get_context_physics_objects(context)
|
|
else:
|
|
objects = [ context.object ]
|
|
|
|
for obj in objects:
|
|
if utils.object_exists_is_mesh(obj):
|
|
reset_physics_cache(obj, start, end)
|
|
|
|
|
|
def free_cache(obj):
|
|
cloth_mod = modifiers.get_cloth_physics_mod(obj)
|
|
if cloth_mod is not None:
|
|
# free the baked cache
|
|
if cloth_mod.point_cache.is_baked:
|
|
utils.log_info("Freeing point cache...")
|
|
utils.safe_free_bake(cloth_mod.point_cache)
|
|
|
|
|
|
def separate_physics_materials(chr_cache, obj):
|
|
|
|
if utils.object_exists_is_mesh(obj):
|
|
|
|
utils.object_mode_to(obj)
|
|
|
|
# remember which materials have active weight maps
|
|
temp = []
|
|
for mat in obj.data.materials:
|
|
if mat:
|
|
edit_mod, mix_mod = modifiers.get_material_weight_map_mods(obj, mat)
|
|
if edit_mod is not None:
|
|
temp.append(mat)
|
|
|
|
# remove cloth physics from the object
|
|
disable_cloth_physics(chr_cache, obj)
|
|
|
|
# split the mesh by materials
|
|
bpy.ops.mesh.separate(type='MATERIAL')
|
|
split_objects = [ o for o in bpy.context.selected_objects if o != obj ]
|
|
|
|
# re-apply cloth physics to the materials which had weight maps
|
|
for split in split_objects:
|
|
for mat in split.data.materials:
|
|
if mat in temp:
|
|
enable_cloth_physics(chr_cache, split, True)
|
|
break
|
|
temp = None
|
|
|
|
|
|
def disable_physics(chr_cache, physics_objects = None):
|
|
changed_objects = []
|
|
if not physics_objects:
|
|
physics_objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
for obj in physics_objects:
|
|
for mod in obj.modifiers:
|
|
if mod.type == "CLOTH":
|
|
if mod.show_render or mod.show_viewport:
|
|
mod.show_viewport = False
|
|
mod.show_render = False
|
|
changed_objects.append(obj)
|
|
elif mod.type == "COLLISION":
|
|
if obj.collision and obj.collision.use:
|
|
obj.collision.use = False
|
|
changed_objects.append(obj)
|
|
chr_cache.physics_disabled = True
|
|
return changed_objects
|
|
|
|
|
|
def enable_physics(chr_cache, physics_objects = None):
|
|
prefs = vars.prefs()
|
|
|
|
if not physics_objects:
|
|
physics_objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
for obj in physics_objects:
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
cloth_allowed = True
|
|
if obj_cache:
|
|
if ((obj_cache.is_hair() and not prefs.physics_cloth_hair) or
|
|
(not obj_cache.is_hair() and not prefs.physics_cloth_clothing)):
|
|
cloth_allowed = False
|
|
for mod in obj.modifiers:
|
|
if mod.type == "CLOTH" and cloth_allowed:
|
|
mod.show_viewport = True
|
|
mod.show_render = True
|
|
elif mod.type == "COLLISION":
|
|
if obj.collision:
|
|
obj.collision.use = True
|
|
chr_cache.physics_disabled = False
|
|
|
|
|
|
def cloth_physics_state(obj):
|
|
has_cloth = False
|
|
is_baked = False
|
|
is_baking = False
|
|
point_cache = None
|
|
mod : bpy.types.ClothModifier
|
|
if obj:
|
|
for mod in obj.modifiers:
|
|
if mod.type == "CLOTH":
|
|
point_cache = mod.point_cache
|
|
has_cloth = True
|
|
if point_cache.is_baked:
|
|
is_baked = True
|
|
if point_cache.is_baking:
|
|
is_baking = True
|
|
return has_cloth, is_baked, is_baking, point_cache
|
|
|
|
|
|
def detect_physics(chr_cache, obj, obj_cache, mat, mat_cache, chr_json):
|
|
"""Detect the physics material presets."""
|
|
|
|
if not (obj and mat and obj_cache and mat_cache and chr_json):
|
|
return
|
|
|
|
physics_json = jsonutils.get_physics_json(chr_json)
|
|
soft_physics_json = jsonutils.get_soft_physics_json(physics_json, obj, mat)
|
|
if soft_physics_json:
|
|
active = soft_physics_json["Activate Physics"]
|
|
mass = soft_physics_json["Mass"]
|
|
friction = soft_physics_json["Friction"]
|
|
damping = soft_physics_json["Damping"]
|
|
drag = soft_physics_json["Drag"]
|
|
elasticity = soft_physics_json["Elasticity"]
|
|
stretch = soft_physics_json["Stretch"]
|
|
bending = soft_physics_json["Bending"]
|
|
self_collision = soft_physics_json["Self Collision"]
|
|
|
|
cmp = mathutils.Vector((elasticity, bending))
|
|
presets = {
|
|
"DEFAULT": mathutils.Vector((10, 30)),
|
|
"SILK": mathutils.Vector((50, 80)),
|
|
"COTTON": mathutils.Vector((30, 40)),
|
|
"LINEN": mathutils.Vector((10, 0)),
|
|
"DENIM": mathutils.Vector((1, 0)),
|
|
"LEATHER": mathutils.Vector((10, 10)),
|
|
"RUBBER": mathutils.Vector((20, 20)),
|
|
}
|
|
|
|
best_dif = 1000
|
|
best_preset = "DEFAULT"
|
|
for preset in presets:
|
|
test = (cmp - presets[preset]).length
|
|
if test < best_dif:
|
|
best_preset = preset
|
|
best_dif = test
|
|
|
|
if obj_cache.object_type == "HAIR":
|
|
best_preset = "HAIR"
|
|
|
|
utils.log_info(f"Cloth Physics settings detected as: {best_preset}")
|
|
obj_cache.cloth_settings = best_preset
|
|
obj_cache.cloth_self_collision = self_collision
|
|
|
|
if active:
|
|
obj_cache.cloth_physics = "ON"
|
|
mat_cache.cloth_physics = "ON"
|
|
utils.log_info(f"Activating cloth physics on {obj.name} / {mat.name}")
|
|
else:
|
|
if obj_cache.cloth_physics == "DEFAULT":
|
|
obj_cache.cloth_physics = "OFF"
|
|
utils.log_info(f"Deactivating cloth physics on object {obj.name}")
|
|
if mat_cache.cloth_physics == "DEFAULT":
|
|
mat_cache.cloth_physics = "OFF"
|
|
utils.log_info(f"Deactivating cloth physics on material {mat.name}")
|
|
|
|
|
|
def apply_all_physics(chr_cache):
|
|
prefs = vars.prefs()
|
|
props = vars.props()
|
|
|
|
if chr_cache:
|
|
utils.log_info(f"Adding all Physics modifiers to: {chr_cache.character_name}")
|
|
utils.log_indent()
|
|
arm = chr_cache.get_armature()
|
|
objects = chr_cache.get_all_objects(include_armature=False,
|
|
include_children=True,
|
|
of_type="MESH")
|
|
objects_processed = []
|
|
accessory_colldiers = get_accessory_colliders(arm, objects, True)
|
|
|
|
for obj in chr_cache.get_cache_objects():
|
|
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
|
|
if obj_cache and obj_cache.is_mesh() and obj not in objects_processed and not obj_cache.disabled:
|
|
|
|
cloth_allowed = True
|
|
if ((obj_cache.is_hair() and not prefs.physics_cloth_hair) or
|
|
(not obj_cache.is_hair() and not prefs.physics_cloth_clothing)):
|
|
cloth_allowed = False
|
|
|
|
utils.log_info(f"Object: {obj.name}:")
|
|
utils.log_indent()
|
|
|
|
remove_all_physics_mods(obj)
|
|
|
|
if cloth_allowed:
|
|
|
|
for mat in obj.data.materials:
|
|
if mat and mat not in objects_processed:
|
|
add_material_weight_map(chr_cache, obj, mat, create = False)
|
|
objects_processed.append(mat)
|
|
|
|
objects_processed.append(obj)
|
|
|
|
apply_collision_physics(chr_cache, obj, obj_cache)
|
|
if obj in accessory_colldiers:
|
|
apply_collision_physics(chr_cache, obj, obj_cache)
|
|
|
|
if cloth_allowed:
|
|
if modifiers.has_cloth_weight_map_mods(obj):
|
|
attach_cloth_weight_map_remap(obj, prefs.physics_weightmap_curve)
|
|
enable_cloth_physics(chr_cache, obj, False)
|
|
|
|
utils.log_recess()
|
|
|
|
chr_cache.physics_applied = True
|
|
utils.log_recess()
|
|
|
|
|
|
def arrange_physics_modifiers(obj):
|
|
cloth_mod = None
|
|
remap_mod = None
|
|
subd_mods = []
|
|
before_cloth = True
|
|
for mod in obj.modifiers:
|
|
if mod.type == "CLOTH":
|
|
cloth_mod = mod
|
|
before_cloth = False
|
|
if mod.type == "SUBSURF":
|
|
subd_mods.append([mod, before_cloth])
|
|
if mod.type == "VERTEX_WEIGHT_EDIT" and "WeightEditRemap" in mod.name:
|
|
remap_map = mod
|
|
|
|
# order is:
|
|
# weight map edit/mix mods
|
|
# ...
|
|
# weight map edit remap mod
|
|
# subd mods before cloth)
|
|
# cloth mod
|
|
# subd mods after cloth
|
|
|
|
if remap_mod:
|
|
modifiers.move_mod_last(obj, remap_mod)
|
|
for mod, before_cloth in subd_mods:
|
|
if before_cloth:
|
|
modifiers.move_mod_last(obj, mod)
|
|
modifiers.move_mod_last(obj, cloth_mod)
|
|
for mod, before_cloth in subd_mods:
|
|
if not before_cloth:
|
|
modifiers.move_mod_last(obj, mod)
|
|
|
|
|
|
def remove_all_physics(chr_cache):
|
|
if chr_cache:
|
|
utils.log_info(f"Removing all Physics modifiers from: {chr_cache.character_name}")
|
|
utils.log_indent()
|
|
objects_processed = []
|
|
for obj in chr_cache.get_cache_objects():
|
|
if utils.object_exists(obj):
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache and obj_cache.is_mesh() and obj not in objects_processed and not obj_cache.disabled:
|
|
remove_all_physics_mods(obj)
|
|
remove_collision_proxy(chr_cache, obj)
|
|
chr_cache.physics_applied = False
|
|
utils.log_recess()
|
|
|
|
|
|
def get_accessory_colliders(arm, objects, hide = False):
|
|
|
|
# find all collider bone names
|
|
collider_bone_names = []
|
|
bone : bpy.types.Bone
|
|
pivot_bone : bpy.types.Bone
|
|
for bone in arm.data.bones:
|
|
if bone.name.startswith("CollisionShape"):
|
|
for child_bone in bone.children:
|
|
if child_bone.name.startswith(vars.ACCESORY_PIVOT_NAME):
|
|
pivot_bone = child_bone
|
|
for collider_bone in pivot_bone.children:
|
|
if collider_bone.name not in collider_bone_names:
|
|
collider_bone_names.append(collider_bone.name)
|
|
else:
|
|
collider_bone = child_bone
|
|
if collider_bone.name not in collider_bone_names:
|
|
collider_bone_names.append(collider_bone.name)
|
|
|
|
# use those names to find all the collider objects
|
|
collider_objects = []
|
|
for collider_bone_name in collider_bone_names:
|
|
source_name = utils.strip_name(collider_bone_name)
|
|
obj : bpy.types.Object
|
|
for obj in objects:
|
|
# this might be the right collider
|
|
if obj.name.startswith(source_name):
|
|
# check vertex group name to be sure
|
|
if obj.vertex_groups and len(obj.vertex_groups) > 0:
|
|
for vg in obj.vertex_groups:
|
|
if vg.name == collider_bone_name:
|
|
if obj not in collider_objects:
|
|
collider_objects.append(obj)
|
|
if hide:
|
|
utils.hide(obj)
|
|
break
|
|
|
|
return collider_objects
|
|
|
|
|
|
def delete_accessory_colliders(arm, objects):
|
|
colliders = get_accessory_colliders(arm, objects)
|
|
for collider in colliders:
|
|
utils.delete_mesh_object(collider)
|
|
objects.remove(collider)
|
|
|
|
|
|
def get_self_collision(chr_cache, obj):
|
|
if chr_cache and obj:
|
|
obj_cache = chr_cache.get_object_cache(obj)
|
|
if obj_cache:
|
|
return obj_cache.cloth_self_collision
|
|
return False
|
|
|
|
|
|
def restore_collision_proxy_view(context, chr_cache):
|
|
"""Return from local view. Hide the collision proxies.
|
|
Any collisions proxies selected or active, select their source mesh objects instead."""
|
|
active = utils.get_active_object()
|
|
selection = context.selected_objects.copy()
|
|
new_selection = []
|
|
if active:
|
|
active = chr_cache.get_related_physics_objects(active)[0]
|
|
if selection:
|
|
for obj in selection:
|
|
obj = chr_cache.get_related_physics_objects(obj)[0]
|
|
new_selection.append(obj)
|
|
|
|
utils.fix_local_view(context)
|
|
show_hide_collision_proxies(context, chr_cache, False)
|
|
|
|
if new_selection:
|
|
utils.try_select_objects(new_selection)
|
|
if active:
|
|
utils.set_active_object(active)
|
|
|
|
|
|
def set_physics_settings(op, context, param):
|
|
props = vars.props()
|
|
chr_cache = props.get_context_character_cache(context)
|
|
obj = None
|
|
if context.object and context.object.type == "MESH":
|
|
obj = context.object
|
|
|
|
if param == "PHYSICS_ADD_CLOTH":
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
for obj in context.selected_objects:
|
|
enable_cloth_physics(chr_cache, obj, True)
|
|
reset_physics(context)
|
|
|
|
elif param == "PHYSICS_REMOVE_CLOTH":
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
for obj in context.selected_objects:
|
|
disable_cloth_physics(chr_cache, obj)
|
|
|
|
elif param == "PHYSICS_ADD_COLLISION":
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
objects = context.selected_objects
|
|
for obj in objects:
|
|
enable_collision_physics(chr_cache, obj)
|
|
show_hide_collision_proxies(context, chr_cache, False)
|
|
reset_physics(context, all_objects=True)
|
|
|
|
elif param == "PHYSICS_REMOVE_COLLISION":
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
objects = context.selected_objects
|
|
for obj in objects:
|
|
disable_collision_physics(chr_cache, obj)
|
|
show_hide_collision_proxies(context, chr_cache, False)
|
|
reset_physics(context, all_objects=True)
|
|
|
|
elif param == "PHYSICS_ADD_WEIGHTMAP":
|
|
if obj:
|
|
enable_material_weight_map(chr_cache, obj, utils.get_context_material(context))
|
|
|
|
elif param == "PHYSICS_REMOVE_WEIGHTMAP":
|
|
if obj:
|
|
disable_material_weight_map(chr_cache, obj, utils.get_context_material(context))
|
|
|
|
elif param == "PHYSICS_RESIZE_WEIGHTMAP":
|
|
if obj:
|
|
resize_weight_map(chr_cache, context, op)
|
|
|
|
elif param == "PHYSICS_HAIR":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "HAIR", False)
|
|
|
|
elif param == "PHYSICS_DENIM":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "DENIM", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_LEATHER":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "LEATHER", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_RUBBER":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "RUBBER", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_SILK":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "SILK", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_COTTON":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "COTTON", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_LINEN":
|
|
for obj in bpy.context.selected_objects:
|
|
if obj.type == "MESH":
|
|
apply_cloth_settings(obj, "LINEN", get_self_collision(chr_cache, obj))
|
|
|
|
elif param == "PHYSICS_PAINT":
|
|
if obj:
|
|
begin_paint_weight_map(context, chr_cache)
|
|
|
|
elif param == "PHYSICS_DONE_PAINTING":
|
|
end_paint_weight_map(op, context, chr_cache)
|
|
|
|
elif param == "PHYSICS_SAVE":
|
|
save_dirty_weight_maps(chr_cache, bpy.context.selected_objects)
|
|
|
|
elif param == "BROWSE_WEIGHTMAP":
|
|
browse_weight_map(chr_cache, context)
|
|
|
|
elif param == "PHYSICS_DELETE":
|
|
if obj:
|
|
delete_selected_weight_map(chr_cache, obj, utils.get_context_material(context))
|
|
|
|
elif param == "PHYSICS_SEPARATE":
|
|
separate_physics_materials(chr_cache, obj)
|
|
|
|
elif param == "PHYSICS_FIX_DEGENERATE":
|
|
if obj:
|
|
if context.object.mode != "EDIT" and context.object.mode != "OBJECT":
|
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
|
if context.object.mode != "EDIT":
|
|
bpy.ops.object.mode_set(mode = 'EDIT')
|
|
if context.object.mode == "EDIT":
|
|
bpy.ops.mesh.select_all(action = 'SELECT')
|
|
bpy.ops.mesh.dissolve_degenerate()
|
|
bpy.ops.object.mode_set(mode = 'OBJECT')
|
|
op.report({'INFO'}, f"Degenerate elements removed for {obj.name}")
|
|
|
|
elif param == "DISABLE_PHYSICS":
|
|
if chr_cache:
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
disable_physics(chr_cache)
|
|
op.report({'INFO'}, f"Physics disabled for {chr_cache.character_name}")
|
|
|
|
elif param == "ENABLE_PHYSICS":
|
|
if chr_cache:
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
enable_physics(chr_cache)
|
|
reset_physics(context)
|
|
op.report({'INFO'}, f"Physics enabled for {chr_cache.character_name}")
|
|
|
|
elif param == "REMOVE_PHYSICS":
|
|
if chr_cache:
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
remove_all_physics(chr_cache)
|
|
op.report({'INFO'}, f"Physics removed for {chr_cache.character_name}")
|
|
|
|
elif param == "APPLY_PHYSICS":
|
|
if chr_cache:
|
|
restore_collision_proxy_view(context, chr_cache)
|
|
apply_all_physics(chr_cache)
|
|
reset_physics(context)
|
|
op.report({'INFO'}, f"Physics applied to {chr_cache.character_name}")
|
|
|
|
elif param == "PHYSICS_INC_STRENGTH":
|
|
strength = float(round(props.physics_paint_strength * 10)) / 10.0
|
|
props.physics_paint_strength = min(1.0, max(0.0, strength + 0.1))
|
|
|
|
elif param == "PHYSICS_DEC_STRENGTH":
|
|
strength = float(round(props.physics_paint_strength * 10)) / 10.0
|
|
props.physics_paint_strength = min(1.0, max(0.0, strength - 0.1))
|
|
|
|
elif param == "TOGGLE_SHOW_PROXY":
|
|
if chr_cache and obj:
|
|
context_object, context_proxy, context_is_proxy = chr_cache.get_related_physics_objects(obj)
|
|
if context_object and context_proxy:
|
|
proxy_visible = context_proxy.visible_get()
|
|
if show_hide_collision_proxies(context, chr_cache,
|
|
not proxy_visible,
|
|
select=False,
|
|
use_local=True):
|
|
utils.set_active_object(context_proxy)
|
|
else:
|
|
utils.set_active_object(context_object)
|
|
|
|
|
|
class CC3OperatorPhysics(bpy.types.Operator):
|
|
"""Physics Settings Functions"""
|
|
bl_idname = "cc3.setphysics"
|
|
bl_label = "Physics Settings Functions"
|
|
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
|
|
|
|
param: bpy.props.StringProperty(
|
|
name = "param",
|
|
default = ""
|
|
)
|
|
|
|
def execute(self, context):
|
|
|
|
set_physics_settings(self, context, self.param)
|
|
|
|
return {"FINISHED"}
|
|
|
|
@classmethod
|
|
def description(cls, context, properties):
|
|
|
|
if properties.param == "PHYSICS_ADD_CLOTH":
|
|
return "Add Cloth physics to the selected objects."
|
|
elif properties.param == "PHYSICS_REMOVE_CLOTH":
|
|
return "Remove Cloth physics from the selected objects and remove all weight map modifiers and physics vertex groups"
|
|
elif properties.param == "PHYSICS_ADD_COLLISION":
|
|
return "Add Collision physics to the selected objects"
|
|
elif properties.param == "PHYSICS_REMOVE_COLLISION":
|
|
return "Remove Collision physics from the selected objects"
|
|
elif properties.param == "PHYSICS_ADD_WEIGHTMAP":
|
|
return "Add a physics weight map to the material on the current object. " \
|
|
"If there is no existing weight map, a new blank weight map will be created. " \
|
|
"Modifiers to generate the physics vertex groups will be added to the object"
|
|
elif properties.param == "PHYSICS_REMOVE_WEIGHTMAP":
|
|
return "Removes the physics weight map, modifiers and physics vertex groups for this material from the object"
|
|
elif properties.param == "PHYSICS_RESIZE_WEIGHTMAP":
|
|
return "Resizes the physics weightmap to the current size"
|
|
elif properties.param == "PHYSICS_HAIR":
|
|
return "Sets the cloth physics settings for this object to simulate Hair.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_COTTON":
|
|
return "Sets the cloth physics settings for this object to simulate Cotton.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_DENIM":
|
|
return "Sets the cloth physics settings for this object to simulate Denim.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_LEATHER":
|
|
return "Sets the cloth physics settings for this object to simulate Leather.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_RUBBER":
|
|
return "Sets the cloth physics settings for this object to simulate Rubber.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_SILK":
|
|
return "Sets the cloth physics settings for this object to simulate Silk.\n" \
|
|
"Note: These settings are pure guess work and largely untested"
|
|
elif properties.param == "PHYSICS_PAINT":
|
|
return "Switches to texture paint mode and begins painting the current materials PhysX weight map"
|
|
elif properties.param == "PHYSICS_DONE_PAINTING":
|
|
return "Ends painting and returns to Object mode"
|
|
elif properties.param == "PHYSICS_SAVE":
|
|
return "Saves all changes to the weight maps to the source texture files\n" \
|
|
"**Warning: This will overwrite the existing weightmap files if you have altered them!**"
|
|
elif properties.param == "PHYSICS_DELETE":
|
|
return "Removes the weight map, modifiers and physics vertex groups from the objects, " \
|
|
"and then deletes the weight map texture file.\n" \
|
|
"**Warning: This will delete any existing weightmap file for this object and material!**"
|
|
elif properties.param == "PHYSICS_SEPARATE":
|
|
return "Separates the object by material and applies physics to the separated objects that have weight maps.\n" \
|
|
"Note: Some objects with many vertices and materials but only a small amount is cloth simulated " \
|
|
"may see performance benefits from being separated."
|
|
elif properties.param == "PHYSICS_FIX_DEGENERATE":
|
|
return "Removes degenerate mesh elements from the object.\n" \
|
|
"Note: Meshes with degenerate elements, loose vertices, orphaned edges, zero length edges etc...\n" \
|
|
"might not simulate properly. If the mesh misbehaves badly under simulation, try this."
|
|
elif properties.param == "DISABLE_PHYSICS":
|
|
return "Temporarily disable all physics modifiers for the characater."
|
|
elif properties.param == "ENABLE_PHYSICS":
|
|
return "Re-enable all physics modifiers for the characater."
|
|
elif properties.param == "REMOVE_PHYSICS":
|
|
return "Remove all physics modifiers for the characater."
|
|
elif properties.param == "APPLY_PHYSICS":
|
|
return "Add all possible physics modifiers for the characater."
|
|
elif properties.param == "PHYSICS_INC_STRENGTH":
|
|
return "Increase weight paint strength by 10%"
|
|
elif properties.param == "PHYSICS_DEC_STRENGTH":
|
|
return "Decrease weight paint strength by 10%"
|
|
|
|
return ""
|