2026-02-16

This commit is contained in:
2026-03-17 15:25:32 -06:00
parent d5dd373de0
commit 60100fbab2
560 changed files with 33397 additions and 20776 deletions
Binary file not shown.
@@ -1,17 +1,17 @@
{
"last_check": "2025-12-30 14:52:43.839129",
"backup_date": "December-15-2025",
"last_check": "2026-01-09 16:23:22.382598",
"backup_date": "December-30-2025",
"update_ready": true,
"ignore": false,
"just_restored": false,
"just_updated": false,
"version_text": {
"link": "https://api.github.com/repos/soupday/cc_blender_tools/zipball/refs/tags/2_3_4_p0",
"link": "https://api.github.com/repos/soupday/cc_blender_tools/zipball/refs/tags/2_3_4_p1",
"version": [
2,
3,
4,
0
1
]
}
}
@@ -1,6 +1,6 @@
{
"last_check": "2025-12-30 14:52:43.839129",
"backup_date": "December-30-2025",
"last_check": "2026-01-09 16:23:22.382598",
"backup_date": "January-9-2026",
"update_ready": false,
"ignore": false,
"just_restored": false,
@@ -1720,12 +1720,15 @@ class CC3Import(bpy.types.Operator):
elif self.param == "BUILD" or self.param == "BUILD_REBUILD":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
bm = props.build_mode
props.build_mode = "IMPORTED"
mode_selection = utils.store_mode_selection_state()
utils.object_mode()
self.build_materials(context)
self.build_drivers(context)
self.do_import_report(context, stage = 1)
utils.restore_mode_selection_state(mode_selection)
props.build_mode = bm
elif self.param == "BUILD_DRIVERS":
chr_cache = props.get_context_character_cache(context)
@@ -1764,32 +1767,41 @@ class CC3Import(bpy.types.Operator):
if chr_cache:
utils.object_mode()
if chr_cache.get_render_target() != "EEVEE":
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "PARALLAX"
utils.log_info("Character is currently build for Cycles Rendering.")
utils.log_info("Rebuilding Character for Eevee Rendering...")
self.build_materials(context, render_target="EEVEE")
self.build_drivers(context)
props.build_mode = bm
elif self.param == "REBUILD_BAKE":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
utils.object_mode()
props.wrinkle_mode = False
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "PARALLAX"
utils.log_info("Rebuilding Character for Eevee Bake...")
self.build_materials(context, render_target="EEVEE")
self.build_drivers(context)
props.build_mode = bm
elif self.param == "REBUILD_CYCLES":
chr_cache = props.get_context_character_cache(context)
if chr_cache:
utils.object_mode()
prefs.refractive_eyes = "SSR"
if chr_cache.get_render_target() != "CYCLES":
bm = props.build_mode
props.build_mode = "IMPORTED"
prefs.refractive_eyes = "SSR"
utils.log_info("Character is currently build for Eevee Rendering.")
utils.log_info("Rebuilding Character for Cycles Rendering...")
self.build_materials(context, render_target="CYCLES")
self.build_drivers(context)
props.build_mode = bm
return {"FINISHED"}
@@ -3869,7 +3869,9 @@ class LinkService():
# generate new action set data
set_id, set_generation = rigutils.generate_motion_set(actor_rig, motion_id, LINK_DATA.motion_prefix)
remove_actions = []
action_pairs = []
if actor_rig:
if actor.get_type() == "PROP":
# if it's a prop retarget the animation (or copy the rest pose):
# props have no bind pose so the rest pose is the first frame of
@@ -3888,6 +3890,7 @@ class LinkService():
rigutils.copy_rest_pose(motion_rig, actor_rig)
utils.safe_set_action(actor_rig, motion_rig_action)
rigutils.update_prop_rig(actor_rig)
else: # Avatar
if chr_cache.rigified:
update_link_status(f"Retargeting Motion...")
@@ -3897,11 +3900,14 @@ class LinkService():
rigutils.set_armature_action_name(armature_action, actor_rig_id, motion_id, LINK_DATA.motion_prefix)
remove_actions.append(motion_rig_action)
else:
actor_rig_action = utils.safe_get_action(actor_rig)
rigutils.add_motion_set_data(motion_rig_action, set_id, set_generation, rl_arm_id=rl_arm_id)
rigutils.set_armature_action_name(motion_rig_action, actor_rig_id, motion_id, LINK_DATA.motion_prefix)
motion_rig_action.use_fake_user = LINK_DATA.use_fake_user
utils.safe_set_action(actor_rig, motion_rig_action)
action_pairs.append((actor_rig_action, motion_rig_action))
rigutils.update_avatar_rig(actor_rig)
# assign motion object shape key actions:
key_actions = rigutils.apply_source_key_actions(actor_rig,
source_actions, copy=True,
@@ -3909,11 +3915,12 @@ class LinkService():
motion_prefix=LINK_DATA.motion_prefix,
all_matching=True,
set_id=set_id, set_generation=set_generation)
for action in key_actions.values():
actions = [ p[0] for p in key_actions.values() ]
for action in actions:
action.use_fake_user = LINK_DATA.use_fake_user
# remove unused motion key actions
for obj_action in source_actions["keys"].values():
if obj_action not in key_actions.values():
if obj_action not in actions:
remove_actions.append(obj_action)
# delete imported motion rig and objects
for obj in motion_objects:
@@ -21,7 +21,7 @@ import bpy
from . import imageutils, jsonutils, nodeutils, utils, params, vars
def detect_skin_material(mat):
def detect_skin_material_name(mat):
name = mat.name.lower()
if "std_skin_" in name or "ga_skin_" in name:
return True
@@ -67,7 +67,7 @@ def detect_key_words(hints, text):
return "False"
def detect_scalp_material(mat):
def detect_scalp_material_name(mat):
prefs = vars.prefs()
material_name = mat.name.lower()
hints = prefs.hair_scalp_hint.split(",")
@@ -79,14 +79,14 @@ def detect_scalp_material(mat):
return detect
def detect_eyelash_material(mat):
def detect_eyelash_material_name(mat):
name = mat.name.lower()
if "std_eyelash" in name or "ga_eyelash" in name:
return True
return False
def detect_teeth_material(mat):
def detect_teeth_material_name(mat):
name = mat.name.lower()
if "std_upper_teeth" in name:
return True
@@ -95,21 +95,21 @@ def detect_teeth_material(mat):
return False
def detect_tongue_material(mat):
def detect_tongue_material_name(mat):
name = mat.name.lower()
if "std_tongue" in name or "ga_tongue" in name:
return True
return False
def detect_nails_material(mat):
def detect_nails_material_name(mat):
name = mat.name.lower()
if "std_nails" in name or "ga_nails" in name:
return True
return False
def detect_body_object(chr_cache, obj):
def detect_body_object_name(obj):
name = obj.name.lower()
if "base_body" in name or "game_body" in name:
return True
@@ -282,16 +282,16 @@ def detect_materials_by_name(chr_cache, obj, mat):
if detect_hair_object(obj, tex_dirs, chr_cache.get_import_dir()) == "True":
object_type = "HAIR"
if detect_scalp_material(mat) == "True":
if detect_scalp_material_name(mat) == "True":
material_type = "SCALP"
elif detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir()) == "Deny":
material_type = "DEFAULT"
else:
material_type = "HAIR"
elif detect_body_object(chr_cache, obj):
elif detect_body_object_name(obj):
object_type = "BODY"
if detect_skin_material(mat):
if detect_skin_material_name(mat):
if "head" in mat_name:
material_type = "SKIN_HEAD"
elif "body" in mat_name:
@@ -300,9 +300,9 @@ def detect_materials_by_name(chr_cache, obj, mat):
material_type = "SKIN_ARM"
elif "leg" in mat_name:
material_type = "SKIN_LEG"
elif detect_nails_material(mat):
elif detect_nails_material_name(mat):
material_type = "NAILS"
elif detect_eyelash_material(mat):
elif detect_eyelash_material_name(mat):
material_type = "EYELASH"
elif detect_cornea_material(mat):
@@ -333,14 +333,14 @@ def detect_materials_by_name(chr_cache, obj, mat):
else:
material_type = "TEARLINE_RIGHT"
elif detect_teeth_material(mat):
elif detect_teeth_material_name(mat):
object_type = "TEETH"
if detect_material_side(mat, "UPPER"):
material_type = "TEETH_UPPER"
else:
material_type = "TEETH_LOWER"
elif detect_tongue_material(mat):
elif detect_tongue_material_name(mat):
object_type = "TONGUE"
material_type = "TONGUE"
@@ -368,12 +368,12 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
object_type = "HAIR"
if detect_hair_material(obj, mat, tex_dirs, chr_cache.get_import_dir(), mat_json) == "True":
material_type = "HAIR"
elif detect_scalp_material(mat) == "True":
elif detect_scalp_material_name(mat) == "True":
material_type = "SCALP"
else:
material_type = "DEFAULT"
elif detect_eyelash_material(mat):
elif detect_eyelash_material_name(mat):
object_type = "BODY"
material_type = "EYELASH"
@@ -402,7 +402,7 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
material_type = "SKIN_ARM"
elif "leg" in mat_name:
material_type = "SKIN_LEG"
elif detect_nails_material(mat):
elif detect_nails_material_name(mat):
material_type = "NAILS"
elif shader == "RLHead":
@@ -459,7 +459,12 @@ def detect_materials_from_json(chr_cache, obj, mat, obj_json, mat_json):
object_type = "DEFAULT"
material_type = "DEFAULT"
utils.log_info(f"Material: {mat_name} detected from Json data as: {material_type}")
# final check for body
if object_type == "DEFAULT" and material_type == "DEFAULT":
if detect_body_object_name(obj) and detect_skin_material_name(mat):
object_type = "BODY"
utils.log_info(f"Material: {obj.name}/{mat_name} detected from Json data as: {object_type}/{material_type}")
return object_type, material_type
@@ -1364,9 +1364,9 @@ SHADER_MATRIX = [
# modifier properties:
# [prop_name, material_type, modifier_type, modifier_id, expression]
"modifiers": [
[ "eye_iris_depth", "EYE_RIGHT", "DISPLACE", "Eye_Displace_R", "mod.strength = 1.5 * parameters.eye_iris_depth"],
[ "eye_iris_depth", "EYE_RIGHT", "DISPLACE", "Eye_Displace_R", "mod.strength = 1.0 * parameters.eye_iris_depth"],
[ "eye_pupil_scale", "EYE_RIGHT", "UV_WARP", "Eye_UV_Warp_R", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],
[ "eye_iris_depth", "EYE_LEFT", "DISPLACE", "Eye_Displace_L", "mod.strength = 1.5 * parameters.eye_iris_depth"],
[ "eye_iris_depth", "EYE_LEFT", "DISPLACE", "Eye_Displace_L", "mod.strength = 1.0 * parameters.eye_iris_depth"],
[ "eye_pupil_scale", "EYE_LEFT", "UV_WARP", "Eye_UV_Warp_L", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],
],
# material setting properties:
@@ -1391,7 +1391,7 @@ SHADER_MATRIX = [
"mapping": [
["DIFFUSE", "Sclera Scale", "", "eye_sclera_scale"],
["DIFFUSE", "Iris Radius", "", "eye_iris_radius"],
["DIFFUSE", "Pupil Scale", "", "eye_pupil_scale"],
["DIFFUSE", "Pupil Scale", "func_set_parallax_pupil_scale", "eye_pupil_scale"],
["DIFFUSE", "Depth", "func_set_parallax_iris_depth", "eye_iris_depth"],
["DIFFUSE", "IOR", "", "eye_ior"],
["SCLERA", "Invert", "func_eye_invert", "eye_is_left_eye"],
@@ -552,7 +552,7 @@ class CC3ToolsAddonPreferences(bpy.types.AddonPreferences):
("LOCAL","Local Machine","Connect to a DataLink server running on the local machine"),
("REMOTE","Remote Host","Connect to a DataLink server running on a remote machine"),
], default="LOCAL", name = "DataLink Target")
datalink_auto_lighting: bpy.props.BoolProperty(default=True,
datalink_auto_lighting: bpy.props.BoolProperty(default=False,
description="Use automatic lighting from CC/iC Go-B")
@@ -2778,7 +2778,7 @@ def full_retarget_source_rig_action(op, chr_cache, src_rig=None, src_action=None
rigutils.add_motion_set_data(armature_action, set_id, set_generation, rl_arm_id=rl_arm_id)
armature_action.use_fake_user = props.rigify_retarget_use_fake_user if use_ui_options else True
utils.log_info(f"Renaming armature action to: {armature_action.name}")
for obj_id, key_action in key_actions.items():
for obj_id, (key_action, old_key_action) in key_actions.items():
rigutils.set_key_action_name(key_action, rig_id, motion_id, obj_id, motion_prefix)
rigutils.add_motion_set_data(key_action, set_id, set_generation, obj_id=obj_id)
utils.log_info(f"Renaming key action ({obj_id}) to: {key_action.name}")
@@ -428,6 +428,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
if obj.type == "MESH":
if utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
old_action = utils.safe_get_action(obj.data.shape_keys)
if (obj_id in source_actions["keys"] and
obj_has_action_shape_keys(obj, source_actions["keys"][obj_id])):
action = source_actions["keys"][obj_id]
@@ -440,7 +441,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
key_actions[obj_id] = (action, old_action)
else:
utils.safe_set_action(obj.data.shape_keys, None)
@@ -452,6 +453,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
if filter and obj not in filter: continue
if obj not in obj_used and utils.object_has_shape_keys(obj):
obj_id = get_action_obj_id(obj)
old_action = utils.safe_get_action(obj.data.shape_keys)
if body_action:
if obj_has_action_shape_keys(obj, body_action):
action = body_action
@@ -464,7 +466,7 @@ def apply_source_key_actions(dst_rig, source_actions, all_matching=False, copy=F
utils.log_info(f" - Applying action: {action.name} to {obj_id}")
utils.safe_set_action(obj.data.shape_keys, action)
obj_used.append(obj)
key_actions[obj_id] = action
key_actions[obj_id] = (action, old_action)
return key_actions
+58 -13
View File
@@ -210,13 +210,13 @@ def import_rlx_light(data: BinaryData, data_folder):
cutoff_distance_cache = frame_cache(num_frames)
spot_blend_cache = frame_cache(num_frames)
spot_size_cache = frame_cache(num_frames)
visible_cache = frame_cache(num_frames)
render_cache = frame_cache(num_frames)
frame = 0
start = None
while not frames.eof():
frame += 1
time = frames.time()
frame = frames.int()
frame = frames.int() + 1
if start is None:
start = frame
active = frames.bool()
@@ -230,8 +230,6 @@ def import_rlx_light(data: BinaryData, data_folder):
falloff = frames.float() / 100
attenuation = frames.float() / 100
darkness = frames.float()
if not active:
multiplier = 0.0
cutoff_distance = range
store_frame(light, loc_cache, frame, start, loc)
store_frame(light, rot_cache, frame, start, rot)
@@ -254,6 +252,9 @@ def import_rlx_light(data: BinaryData, data_folder):
elif light_type == "POINT":
energy = ENERGY_SCALE * multiplier
store_frame(light, energy_cache, frame, start, energy)
store_frame(light, visible_cache, frame, start, 0.0 if active else 1.0)
store_frame(light, render_cache, frame, start, 0.0 if active else 1.0)
ob_action, light_action, ob_slot, light_slot = prep_rlx_actions(light, name, "Export",
reuse_existing=False,
@@ -261,6 +262,8 @@ def import_rlx_light(data: BinaryData, data_folder):
add_cache_fcurves(ob_action, light.path_from_id("location"), loc_cache, num_frames, "Location", slot=ob_slot)
add_cache_rotation_fcurves(light, ob_action, rot_cache, num_frames, slot=ob_slot)
add_cache_fcurves(ob_action, light.path_from_id("scale"), sca_cache, num_frames, "Scale", slot=ob_slot)
add_cache_fcurves(light_action, light.path_from_id("hide_viewport"), visible_cache, num_frames, "Hide Viewport", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.path_from_id("hide_render"), render_cache, num_frames, "Hide Render", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.data.path_from_id("color"), color_cache, num_frames, "Color", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("energy"), energy_cache, num_frames, "Energy", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("cutoff_distance"), cutoff_distance_cache, num_frames, "Cutoff Distance", slot=light_slot)
@@ -298,12 +301,10 @@ def import_rlx_camera(data: BinaryData, data_folder):
f_stop_cache = frame_cache(num_frames)
active_cache = []
frame = 0
start = None
while not frames.eof():
frame += 1
time = frames.time()
frame = frames.int()
frame = frames.int() + 1
if start is None:
start = frame
loc = frames.vector() / 100
@@ -427,7 +428,7 @@ def add_cache_rotation_fcurves(obj, action: bpy.types.Action, cache, num_frames,
add_cache_fcurves(action, data_path, cache, num_frames, group_name=group_name, slot=slot)
def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, group_name=None, slot=None):
def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, group_name=None, slot=None, interpolation="LINEAR"):
channels = utils.get_action_channels(action, slot)
num_curves = len(cache)
if channels:
@@ -436,9 +437,51 @@ def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, gr
channels.groups.new(group_name)
for i in range(0, num_curves):
fcurve = channels.fcurves.new(data_path, index=i)
fcurve.auto_smoothing = "NONE"
fcurve.group = channels.groups[group_name]
fcurve.keyframe_points.add(num_frames)
fcurve.keyframe_points.foreach_set('co', cache[i])
reduced = reduce_cache(cache[i], interpolation)
num_reduced = int(len(reduced) / 2)
fcurve.keyframe_points.add(num_reduced)
fcurve.keyframe_points.foreach_set('co', reduced)
if interpolation != "BEZIER":
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = interpolation
else:
L = len(fcurve.keyframe_points)
for i, keyframe in enumerate(fcurve.keyframe_points):
prev = fcurve.keyframe_points[i-1] if i > 0 else keyframe
next = fcurve.keyframe_points[i+1] if i < L-1 else keyframe
keyframe.handle_left_type = "AUTO"
keyframe.handle_left[0] = keyframe.co.x - 0.5
keyframe.handle_left[1] = (keyframe.co.y + prev.co.y) * 0.5
keyframe.handle_right_type = "AUTO"
keyframe.handle_right[0] = keyframe.co.x + 0.5
keyframe.handle_right[1] = (keyframe.co.y + next.co.y) * 0.5
def reduce_cache(cache, interpolation):
if not cache or len(cache) <= 4:
return cache
reduced = []
L = len(cache)
Lm2 = L - 2
# add first frame
reduced.append(cache[0])
reduced.append(cache[1])
use_next = interpolation != "CONSTANT"
for i in range(2, L, 2):
frame = cache[i]
value = cache[i+1]
value_last = cache[i-1]
value_next = cache[i+3] if i < Lm2 else value
last_changed = abs(value - value_last) > 0.0001
next_changed = abs(value - value_next) > 0.0001
if last_changed or (use_next and next_changed):
reduced.append(frame)
reduced.append(value)
# add last frame
#reduced.append(cache[-2])
#reduced.append(cache[-1])
return reduced
def add_camera_markers(camera, cache, num_frames, start):
@@ -567,8 +610,10 @@ def decode_rlx_light(light_data, light: bpy.types.Object=None, container=None):
light.data.contact_shadow_distance = 0.1
light.data.contact_shadow_bias = 0.03
light.data.contact_shadow_thickness = 0.001
if not active:
utils.hide(light)
light.hide_viewport = not active
light.hide_render = not active
return light
@@ -2641,7 +2641,7 @@ def cycles_setup(context):
pass
try:
context.scene.cycles.preview_denoiser = 'OPTIX'
context.scene.cycles.preview_denoiser = 'OPENIMAGEDENOISE'
context.scene.cycles.denoiser = 'OPTIX'
except:
pass
@@ -67,7 +67,12 @@ def apply_multi_res_shape(body):
mod = modifiers.get_object_modifier(body, modifiers.MOD_MULTIRES, modifiers.MOD_MULTIRES_NAME)
if mod and utils.set_only_active_object(body):
utils.log_info("Applying base shape")
bpy.ops.object.multires_base_apply(modifier=mod.name)
if utils.B500():
# apply_heuristic=True applies base assuming it will be used subdivided
# apply_heuristic=False applies base to level 0
bpy.ops.object.multires_base_apply(modifier=mod.name, apply_heuristic=False)
else:
bpy.ops.object.multires_base_apply(modifier=mod.name)
def displacement_map_func(value):
@@ -209,20 +214,22 @@ def do_multires_bake(context, chr_cache, multires_mesh, layer_target, apply_shap
utils.log_recess()
utils.log_info("Baking complete!")
utils.delete_mesh_object(norm_body)
if layer_target == LAYER_TARGET_SCULPT and apply_shape and source_body:
utils.log_info("Transfering sculpt base shape to source body...")
utils.unhide(multires_mesh)
utils.unhide(source_body)
copy_base_shape(multires_mesh, source_body, layer_target, True)
if norm_body and source_body:
copy_base_shape(norm_body, source_body, layer_target, True)
# if there is a detail sculpt body, update that with the new base shape too
detail_body = chr_cache.get_detail_body(context_object=source_body)
if detail_body:
copy_base_shape(multires_mesh, detail_body, layer_target, True)
# if there is a detail sculpt body, update that with the new base shape too
detail_body = chr_cache.get_detail_body(context_object=source_body)
if detail_body:
# the base shape has only been applied to the norm_body so far ...
copy_base_shape(norm_body, detail_body, layer_target, True)
utils.delete_mesh_object(norm_body)
# restore render engine
bake.post_bake(context, bake_state)
@@ -607,10 +607,13 @@ def func_export_eye_depth(cc, depth):
return (depth) * 3.0
def func_set_eye_depth(cc, depth):
return depth * 1.5
return depth * 1.0
def func_set_parallax_iris_depth(cc, depth):
return depth * 1.5 + 0.1
return depth * 1.0
def func_set_parallax_pupil_scale(cc, scale):
return scale * 0.6666
def func_index_f0(cc, v: list):
return v[0]
@@ -1499,6 +1499,10 @@ def hide_tree(obj, hide=True, render=False):
except: ...
def show(obj: bpy.types.Object, show=True, render=False):
hide(obj, hide=not show, render=render)
def hide(obj: bpy.types.Object, hide=True, render=False):
try:
obj.hide_set(hide)