Asset Pipeline v2 #145
@ -1,6 +1,14 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
from . import transfer_functions
|
from .transfer_functions import (
|
||||||
|
attributes,
|
||||||
|
constraints,
|
||||||
|
modifers,
|
||||||
|
parent,
|
||||||
|
shape_keys,
|
||||||
|
vertex_groups,
|
||||||
|
materials,
|
||||||
|
)
|
||||||
|
|
||||||
from ... import constants
|
from ... import constants
|
||||||
|
|
||||||
@ -36,13 +44,12 @@ def copy_transfer_data_ownership(
|
|||||||
|
|
||||||
|
|
||||||
def transfer_data_clean(obj):
|
def transfer_data_clean(obj):
|
||||||
transfer_functions.vertex_groups_clean(obj)
|
vertex_groups.vertex_groups_clean(obj)
|
||||||
transfer_functions.modifiers_clean(obj)
|
modifers.modifiers_clean(obj)
|
||||||
transfer_functions.constraints_clean(obj)
|
constraints.constraints_clean(obj)
|
||||||
# transfer_functions.material_slots_clean(obj)
|
shape_keys.shape_keys_clean(obj)
|
||||||
transfer_functions.shape_keys_clean(obj)
|
attributes.attribute_clean(obj)
|
||||||
transfer_functions.attribute_clean(obj)
|
parent.parent_clean(obj)
|
||||||
transfer_functions.parent_clean(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def transfer_data_is_missing(transfer_data_item) -> bool:
|
def transfer_data_is_missing(transfer_data_item) -> bool:
|
||||||
@ -55,13 +62,12 @@ def transfer_data_is_missing(transfer_data_item) -> bool:
|
|||||||
bool: bool if item is missing
|
bool: bool if item is missing
|
||||||
"""
|
"""
|
||||||
return bool(
|
return bool(
|
||||||
transfer_functions.vertex_group_is_missing(transfer_data_item)
|
vertex_groups.vertex_group_is_missing(transfer_data_item)
|
||||||
or transfer_functions.modifier_is_missing(transfer_data_item)
|
or modifers.modifier_is_missing(transfer_data_item)
|
||||||
# or transfer_functions.material_slots_is_missing(transfer_data_item)
|
or constraints.constraint_is_missing(transfer_data_item)
|
||||||
or transfer_functions.constraint_is_missing(transfer_data_item)
|
or shape_keys.shape_key_is_missing(transfer_data_item)
|
||||||
or transfer_functions.shape_key_is_missing(transfer_data_item)
|
or attributes.attribute_is_missing(transfer_data_item)
|
||||||
or transfer_functions.attribute_is_missing(transfer_data_item)
|
or parent.parent_is_missing(transfer_data_item)
|
||||||
or transfer_functions.parent_is_missing(transfer_data_item)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -76,13 +82,13 @@ def init_transfer_data(
|
|||||||
task_layer_name (str): Name of task layer
|
task_layer_name (str): Name of task layer
|
||||||
temp_transfer_data: Item of class ASSET_TRANSFER_DATA_TEMP
|
temp_transfer_data: Item of class ASSET_TRANSFER_DATA_TEMP
|
||||||
"""
|
"""
|
||||||
transfer_functions.init_vertex_groups(scene, obj)
|
vertex_groups.init_vertex_groups(scene, obj)
|
||||||
transfer_functions.init_material_slots(scene, obj)
|
materials.init_materials(scene, obj)
|
||||||
transfer_functions.init_modifiers(scene, obj)
|
modifers.init_modifiers(scene, obj)
|
||||||
transfer_functions.init_constraints(scene, obj)
|
constraints.init_constraints(scene, obj)
|
||||||
transfer_functions.init_shape_keys(scene, obj)
|
shape_keys.init_shape_keys(scene, obj)
|
||||||
transfer_functions.init_attributes(scene, obj)
|
attributes.init_attributes(scene, obj)
|
||||||
transfer_functions.init_parent(scene, obj)
|
parent.init_parent(scene, obj)
|
||||||
|
|
||||||
|
|
||||||
def apply_transfer_data(context: bpy.types.Context, transfer_data_map) -> None:
|
def apply_transfer_data(context: bpy.types.Context, transfer_data_map) -> None:
|
||||||
@ -113,7 +119,7 @@ def apply_transfer_data(context: bpy.types.Context, transfer_data_map) -> None:
|
|||||||
if source_obj != target_obj:
|
if source_obj != target_obj:
|
||||||
if transfer_data_item.type == constants.VERTEX_GROUP_KEY:
|
if transfer_data_item.type == constants.VERTEX_GROUP_KEY:
|
||||||
print(f"Transfering Data {constants.VERTEX_GROUP_KEY}: {name}")
|
print(f"Transfering Data {constants.VERTEX_GROUP_KEY}: {name}")
|
||||||
transfer_functions.transfer_vertex_group(
|
vertex_groups.transfer_vertex_group(
|
||||||
context=context,
|
context=context,
|
||||||
vertex_group_name=transfer_data_item.name,
|
vertex_group_name=transfer_data_item.name,
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
@ -121,38 +127,38 @@ def apply_transfer_data(context: bpy.types.Context, transfer_data_map) -> None:
|
|||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.MODIFIER_KEY:
|
if transfer_data_item.type == constants.MODIFIER_KEY:
|
||||||
print(f"Transfering Data {constants.MODIFIER_KEY}: {name}")
|
print(f"Transfering Data {constants.MODIFIER_KEY}: {name}")
|
||||||
transfer_functions.transfer_modifier(
|
modifers.transfer_modifier(
|
||||||
modifier_name=transfer_data_item.name,
|
modifier_name=transfer_data_item.name,
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.CONSTRAINT_KEY:
|
if transfer_data_item.type == constants.CONSTRAINT_KEY:
|
||||||
transfer_functions.transfer_constraint(
|
constraints.transfer_constraint(
|
||||||
constraint_name=transfer_data_item.name,
|
constraint_name=transfer_data_item.name,
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.MATERIAL_SLOT_KEY:
|
if transfer_data_item.type == constants.MATERIAL_SLOT_KEY:
|
||||||
print(f"Transfering Data {constants.MATERIAL_SLOT_KEY}: {name}")
|
print(f"Transfering Data {constants.MATERIAL_SLOT_KEY}: {name}")
|
||||||
transfer_functions.transfer_material_slots(
|
materials.transfer_materials(
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.SHAPE_KEY_KEY:
|
if transfer_data_item.type == constants.SHAPE_KEY_KEY:
|
||||||
transfer_functions.transfer_shape_key(
|
shape_keys.transfer_shape_key(
|
||||||
context=context,
|
context=context,
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
shape_key_name=transfer_data_item.name,
|
shape_key_name=transfer_data_item.name,
|
||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.ATTRIBUTE_KEY:
|
if transfer_data_item.type == constants.ATTRIBUTE_KEY:
|
||||||
transfer_functions.transfer_attribute(
|
attributes.transfer_attribute(
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
attribute_name=transfer_data_item.name,
|
attribute_name=transfer_data_item.name,
|
||||||
)
|
)
|
||||||
if transfer_data_item.type == constants.PARENT_KEY:
|
if transfer_data_item.type == constants.PARENT_KEY:
|
||||||
transfer_functions.transfer_parent(
|
parent.transfer_parent(
|
||||||
target_obj=target_obj,
|
target_obj=target_obj,
|
||||||
source_obj=source_obj,
|
source_obj=source_obj,
|
||||||
)
|
)
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,247 @@
|
|||||||
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
import bmesh
|
||||||
|
import numpy as np
|
||||||
|
from .proximity_core import (
|
||||||
|
tris_per_face,
|
||||||
|
closest_face_to_point,
|
||||||
|
closest_tri_on_face,
|
||||||
|
is_obdata_identical,
|
||||||
|
transfer_corner_data,
|
||||||
|
)
|
||||||
|
from ..transfer_util import check_transfer_data_entry
|
||||||
|
from ...naming import get_basename
|
||||||
|
from ...task_layer import get_transfer_data_owner
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def attributes_get_editable(attributes):
|
||||||
|
return [
|
||||||
|
attribute
|
||||||
|
for attribute in attributes
|
||||||
|
if not (
|
||||||
|
attribute.is_internal
|
||||||
|
or attribute.is_required
|
||||||
|
# Material Index is part of material transfer and should be skipped
|
||||||
|
or attribute.name == 'material_index'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def attribute_clean(obj):
|
||||||
|
if obj.type != "MESH":
|
||||||
|
return
|
||||||
|
attributes = attributes_get_editable(obj.data.attributes)
|
||||||
|
attributes_to_remove = []
|
||||||
|
for attribute in attributes:
|
||||||
|
matches = check_transfer_data_entry(
|
||||||
|
obj.transfer_data_ownership,
|
||||||
|
get_basename(attribute.name),
|
||||||
|
constants.ATTRIBUTE_KEY,
|
||||||
|
)
|
||||||
|
if len(matches) == 0:
|
||||||
|
attributes_to_remove.append(attribute.name)
|
||||||
|
|
||||||
|
for attribute_name_to_remove in reversed(attributes_to_remove):
|
||||||
|
attribute_to_remove = obj.data.attributes.get(attribute_name_to_remove)
|
||||||
|
print(f"Cleaning attribute {attribute.name}")
|
||||||
|
obj.data.attributes.remove(attribute_to_remove)
|
||||||
|
|
||||||
|
|
||||||
|
def attribute_is_missing(transfer_data_item):
|
||||||
|
obj = transfer_data_item.id_data
|
||||||
|
if obj.type != "MESH":
|
||||||
|
return
|
||||||
|
attributes = attributes_get_editable(obj.data.attributes)
|
||||||
|
attribute_names = [attribute.name for attribute in attributes]
|
||||||
|
if (
|
||||||
|
transfer_data_item.type == constants.ATTRIBUTE_KEY
|
||||||
|
and not transfer_data_item["name"] in attribute_names
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def init_attributes(scene, obj):
|
||||||
|
asset_pipe = scene.asset_pipeline
|
||||||
|
if obj.type != "MESH":
|
||||||
|
return
|
||||||
|
transfer_data = obj.transfer_data_ownership
|
||||||
|
td_type_key = constants.ATTRIBUTE_KEY
|
||||||
|
for atttribute in attributes_get_editable(obj.data.attributes):
|
||||||
|
# Only add new ownership transfer_data_item if vertex group doesn't have an owner
|
||||||
|
matches = check_transfer_data_entry(transfer_data, atttribute.name, td_type_key)
|
||||||
|
if len(matches) == 0:
|
||||||
|
task_layer_owner, auto_surrender = get_transfer_data_owner(
|
||||||
|
asset_pipe, td_type_key, atttribute.name
|
||||||
|
)
|
||||||
|
asset_pipe.add_temp_transfer_data(
|
||||||
|
name=atttribute.name,
|
||||||
|
owner=task_layer_owner,
|
||||||
|
type=td_type_key,
|
||||||
|
obj=obj,
|
||||||
|
surrender=auto_surrender,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_attribute(
|
||||||
|
attribute_name: str,
|
||||||
|
target_obj: bpy.types.Object,
|
||||||
|
source_obj: bpy.types.Object,
|
||||||
|
):
|
||||||
|
source_attributes = source_obj.data.attributes
|
||||||
|
target_attributes = target_obj.data.attributes
|
||||||
|
source_attribute = source_attributes.get(attribute_name)
|
||||||
|
target_attribute = target_attributes.get(attribute_name)
|
||||||
|
|
||||||
|
if target_attribute:
|
||||||
|
target_attributes.remove(target_attribute)
|
||||||
|
|
||||||
|
target_attribute = target_attributes.new(
|
||||||
|
name=attribute_name,
|
||||||
|
type=source_attribute.data_type,
|
||||||
|
domain=source_attribute.domain,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not is_obdata_identical(source_obj, target_obj):
|
||||||
|
proximity_transfer_single_attribute(
|
||||||
|
source_obj, target_obj, source_attribute, target_attribute
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
for source_data_item in source_attribute.data.items():
|
||||||
|
index = source_data_item[0]
|
||||||
|
source_data = source_data_item[1]
|
||||||
|
keys = set(source_data.bl_rna.properties.keys()) - set(
|
||||||
|
bpy.types.Attribute.bl_rna.properties.keys()
|
||||||
|
)
|
||||||
|
for key in list(keys):
|
||||||
|
target_data = target_attribute.data[index]
|
||||||
|
setattr(target_data, key, getattr(source_data, key))
|
||||||
|
|
||||||
|
|
||||||
|
def proximity_transfer_single_attribute(
|
||||||
|
source_obj: bpy.types.Object,
|
||||||
|
target_obj: bpy.types.Object,
|
||||||
|
source_attribute: bpy.types.Attribute,
|
||||||
|
target_attribute: bpy.types.Attribute,
|
||||||
|
):
|
||||||
|
# src_dat = source_obj.data
|
||||||
|
# tgt_dat = target_obj.data
|
||||||
|
# if type(src_dat) is not type(tgt_dat) or not (src_dat or tgt_dat):
|
||||||
|
# return False
|
||||||
|
# if type(tgt_dat) is not bpy.types.Mesh: # TODO: support more types
|
||||||
|
# return False
|
||||||
|
|
||||||
|
# If target attribute already exists, remove it.
|
||||||
|
# tgt_attr = tgt_dat.attributes.get(source_attribute.name)
|
||||||
|
# if tgt_attr is not None:
|
||||||
|
# try:
|
||||||
|
# tgt_dat.attributes.remove(tgt_attr)
|
||||||
|
# except RuntimeError:
|
||||||
|
# # Built-ins like "position" cannot be removed, and should be skipped.
|
||||||
|
# return
|
||||||
|
|
||||||
|
# Create target attribute.
|
||||||
|
# target_attribute = tgt_dat.attributes.new(
|
||||||
|
# source_attribute.name, source_attribute.data_type, source_attribute.domain
|
||||||
|
# )
|
||||||
|
|
||||||
|
data_sfx = {
|
||||||
|
'INT8': 'value',
|
||||||
|
'INT': 'value',
|
||||||
|
'FLOAT': 'value',
|
||||||
|
'FLOAT2': 'vector',
|
||||||
|
'BOOLEAN': 'value',
|
||||||
|
'STRING': 'value',
|
||||||
|
'BYTE_COLOR': 'color',
|
||||||
|
'FLOAT_COLOR': 'color',
|
||||||
|
'FLOAT_VECTOR': 'vector',
|
||||||
|
}
|
||||||
|
|
||||||
|
data_sfx = data_sfx[source_attribute.data_type]
|
||||||
|
|
||||||
|
# if topo_match:
|
||||||
|
# # TODO: optimize using foreach_get/set rather than loop
|
||||||
|
# for i in range(len(source_attribute.data)):
|
||||||
|
# setattr(tgt_attr.data[i], data_sfx, getattr(source_attribute.data[i], data_sfx))
|
||||||
|
# return
|
||||||
|
|
||||||
|
# proximity fallback
|
||||||
|
if source_attribute.data_type == 'STRING':
|
||||||
|
# TODO: add NEAREST transfer fallback for attributes without interpolation
|
||||||
|
print(
|
||||||
|
f'Proximity based transfer for generic attributes of type STRING not supported yet. Skipping attribute {source_attribute.name} on {target_obj}.'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
domain = source_attribute.domain
|
||||||
|
if (
|
||||||
|
domain == 'POINT'
|
||||||
|
): # TODO: deduplicate interpolated point domain proximity transfer
|
||||||
|
bm_source = bmesh.new()
|
||||||
|
bm_source.from_mesh(source_obj.data)
|
||||||
|
bm_source.faces.ensure_lookup_table()
|
||||||
|
|
||||||
|
bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(bm_source)
|
||||||
|
|
||||||
|
tris_dict = tris_per_face(bm_source)
|
||||||
|
|
||||||
|
for i, vert in enumerate(target_obj.data.vertices):
|
||||||
|
p = vert.co
|
||||||
|
face = closest_face_to_point(bm_source, p, bvh_tree)
|
||||||
|
|
||||||
|
(tri, point) = closest_tri_on_face(tris_dict, face, p)
|
||||||
|
if not tri:
|
||||||
|
continue
|
||||||
|
weights = mathutils.interpolate.poly_3d_calc(
|
||||||
|
[tri[i].vert.co for i in range(3)], point
|
||||||
|
)
|
||||||
|
|
||||||
|
if data_sfx in ['color']:
|
||||||
|
vals_weighted = [
|
||||||
|
weights[i]
|
||||||
|
* (
|
||||||
|
np.array(
|
||||||
|
getattr(source_attribute.data[tri[i].vert.index], data_sfx)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for i in range(3)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
vals_weighted = [
|
||||||
|
weights[i]
|
||||||
|
* (getattr(source_attribute.data[tri[i].vert.index], data_sfx))
|
||||||
|
for i in range(3)
|
||||||
|
]
|
||||||
|
setattr(target_attribute.data[i], data_sfx, sum(np.array(vals_weighted)))
|
||||||
|
return
|
||||||
|
elif domain == 'EDGE':
|
||||||
|
# TODO support proximity fallback for generic edge attributes
|
||||||
|
print(
|
||||||
|
f'Proximity based transfer of generic edge attributes not supported yet. Skipping attribute {source_attribute.name} on {target_obj}.'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif domain == 'FACE':
|
||||||
|
bm_source = bmesh.new()
|
||||||
|
bm_source.from_mesh(source_obj.data)
|
||||||
|
bm_source.faces.ensure_lookup_table()
|
||||||
|
|
||||||
|
bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(bm_source)
|
||||||
|
for i, face in enumerate(target_obj.data.polygons):
|
||||||
|
p_target = face.center
|
||||||
|
closest_face = closest_face_to_point(bm_source, p_target, bvh_tree)
|
||||||
|
setattr(
|
||||||
|
target_attribute.data[i],
|
||||||
|
data_sfx,
|
||||||
|
getattr(source_attribute.data[closest_face.index], data_sfx),
|
||||||
|
)
|
||||||
|
return
|
||||||
|
elif domain == 'CORNER':
|
||||||
|
transfer_corner_data(
|
||||||
|
source_obj,
|
||||||
|
target_obj,
|
||||||
|
source_attribute.data,
|
||||||
|
target_attribute.data,
|
||||||
|
data_suffix=data_sfx,
|
||||||
|
)
|
||||||
|
return
|
@ -0,0 +1,96 @@
|
|||||||
|
import bpy
|
||||||
|
from ..transfer_util import (
|
||||||
|
transfer_data_clean,
|
||||||
|
transfer_data_item_is_missing,
|
||||||
|
check_transfer_data_entry,
|
||||||
|
)
|
||||||
|
from ...naming import task_layer_prefix_name_get
|
||||||
|
from ...drivers import find_drivers, copy_driver
|
||||||
|
from ...visibility import override_obj_visability
|
||||||
|
from ...task_layer import get_transfer_data_owner
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def constraints_clean(obj):
|
||||||
|
transfer_data_clean(
|
||||||
|
obj=obj, data_list=obj.constraints, td_type_key=constants.CONSTRAINT_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def constraint_is_missing(transfer_data_item):
|
||||||
|
return transfer_data_item_is_missing(
|
||||||
|
transfer_data_item=transfer_data_item,
|
||||||
|
td_type_key=constants.CONSTRAINT_KEY,
|
||||||
|
data_list=transfer_data_item.id_data.constraints,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init_constraints(scene, obj):
|
||||||
|
td_type_key = constants.CONSTRAINT_KEY
|
||||||
|
transfer_data = obj.transfer_data_ownership
|
||||||
|
asset_pipe = scene.asset_pipeline
|
||||||
|
task_layer_owner, auto_surrender = get_transfer_data_owner(
|
||||||
|
asset_pipe,
|
||||||
|
td_type_key,
|
||||||
|
)
|
||||||
|
for const in obj.constraints:
|
||||||
|
const.name = task_layer_prefix_name_get(const.name, task_layer_owner)
|
||||||
|
# Only add new ownership transfer_data_item if vertex group doesn't have an owner
|
||||||
|
matches = check_transfer_data_entry(transfer_data, const.name, td_type_key)
|
||||||
|
if len(matches) == 0:
|
||||||
|
asset_pipe.add_temp_transfer_data(
|
||||||
|
name=const.name,
|
||||||
|
owner=task_layer_owner,
|
||||||
|
type=td_type_key,
|
||||||
|
obj=obj,
|
||||||
|
surrender=auto_surrender,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_constraint(constraint_name, target_obj, source_obj):
|
||||||
|
context = bpy.context
|
||||||
|
# remove old and sync existing modifiers
|
||||||
|
old_mod = target_obj.constraints.get(constraint_name)
|
||||||
|
if old_mod:
|
||||||
|
target_obj.constraints.remove(old_mod)
|
||||||
|
|
||||||
|
# transfer new modifiers
|
||||||
|
for i, constraint in enumerate(source_obj.constraints):
|
||||||
|
if constraint.name == constraint_name:
|
||||||
|
constraint_new = target_obj.constraints.new(constraint.type)
|
||||||
|
constraint_new.name = constraint.name
|
||||||
|
# sort new modifier at correct index (default to beginning of the stack)
|
||||||
|
idx = 0
|
||||||
|
if i > 0:
|
||||||
|
name_prev = source_obj.constraints[i - 1].name
|
||||||
|
for target_mod_i, target_constraint in enumerate(
|
||||||
|
target_obj.constraints
|
||||||
|
):
|
||||||
|
if target_constraint.name == name_prev:
|
||||||
|
idx = target_mod_i + 1
|
||||||
|
|
||||||
|
if idx != i:
|
||||||
|
with override_obj_visability(obj=target_obj, scene=context.scene):
|
||||||
|
with context.temp_override(object=target_obj):
|
||||||
|
bpy.ops.constraint.move_to_index(
|
||||||
|
constraint=constraint_new.name, index=idx
|
||||||
|
)
|
||||||
|
constraint_target = target_obj.constraints.get(constraint.name)
|
||||||
|
props = [
|
||||||
|
p.identifier for p in constraint.bl_rna.properties if not p.is_readonly
|
||||||
|
]
|
||||||
|
for prop in props:
|
||||||
|
value = getattr(constraint, prop)
|
||||||
|
setattr(constraint_target, prop, value)
|
||||||
|
|
||||||
|
# HACK to cover edge case of armature constraints
|
||||||
|
if constraint.type == "ARMATURE":
|
||||||
|
for target_item in constraint.targets:
|
||||||
|
new_target = constraint_new.targets.new()
|
||||||
|
new_target.target = target_item.target
|
||||||
|
new_target.subtarget = target_item.subtarget
|
||||||
|
|
||||||
|
fcurves = find_drivers(source_obj, 'constraints', constraint_name)
|
||||||
|
|
||||||
|
for fcurve in fcurves:
|
||||||
|
copy_driver(from_fcurve=fcurve, target=target_obj)
|
@ -0,0 +1,86 @@
|
|||||||
|
import bpy
|
||||||
|
from .attributes import transfer_attribute
|
||||||
|
from ..transfer_util import check_transfer_data_entry
|
||||||
|
from ...task_layer import get_transfer_data_owner
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def materials_clean(obj):
|
||||||
|
# Material slots cannot use generic transfer_data_clean() function
|
||||||
|
|
||||||
|
matches = check_transfer_data_entry(
|
||||||
|
obj.transfer_data_ownership,
|
||||||
|
constants.MATERIAL_TRANSFER_DATA_ITEM_NAME,
|
||||||
|
constants.MATERIAL_SLOT_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear Materials if No Transfer Data is Found
|
||||||
|
if len(matches) != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
if obj.data and hasattr(obj.data, 'materials'):
|
||||||
|
obj.data.materials.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def materials_is_missing(transfer_data_item):
|
||||||
|
if (
|
||||||
|
transfer_data_item.type == constants.MATERIAL_SLOT_KEY
|
||||||
|
and len(transfer_data_item.id_data.material_slots) == 0
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def init_materials(scene, obj):
|
||||||
|
asset_pipe = scene.asset_pipeline
|
||||||
|
td_type_key = constants.MATERIAL_SLOT_KEY
|
||||||
|
name = constants.MATERIAL_TRANSFER_DATA_ITEM_NAME
|
||||||
|
transfer_data = obj.transfer_data_ownership
|
||||||
|
|
||||||
|
material_objects = [
|
||||||
|
'CURVE',
|
||||||
|
'GPENCIL',
|
||||||
|
'META',
|
||||||
|
'MESH',
|
||||||
|
'SURFACE',
|
||||||
|
'FONT',
|
||||||
|
'VOLUME',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Only Execute if Material Slots exist on object
|
||||||
|
if obj.type not in material_objects:
|
||||||
|
return
|
||||||
|
matches = check_transfer_data_entry(transfer_data, name, td_type_key)
|
||||||
|
# Only add new ownership transfer_data_item if vertex group doesn't have an owner
|
||||||
|
if len(matches) == 0:
|
||||||
|
task_layer_owner, auto_surrender = get_transfer_data_owner(
|
||||||
|
asset_pipe,
|
||||||
|
td_type_key,
|
||||||
|
)
|
||||||
|
asset_pipe.add_temp_transfer_data(
|
||||||
|
name=name,
|
||||||
|
owner=task_layer_owner,
|
||||||
|
type=td_type_key,
|
||||||
|
obj=obj,
|
||||||
|
surrender=auto_surrender,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_materials(target_obj: bpy.types.Object, source_obj):
|
||||||
|
# Delete all material slots of target object.
|
||||||
|
target_obj.data.materials.clear()
|
||||||
|
|
||||||
|
# Transfer material slots
|
||||||
|
for idx in range(len(source_obj.material_slots)):
|
||||||
|
target_obj.data.materials.append(source_obj.material_slots[idx].material)
|
||||||
|
target_obj.material_slots[idx].link = source_obj.material_slots[idx].link
|
||||||
|
|
||||||
|
# Transfer active material slot index
|
||||||
|
target_obj.active_material_index = source_obj.active_material_index
|
||||||
|
|
||||||
|
# Transfer material slot assignments for curve
|
||||||
|
if target_obj.type == "CURVE":
|
||||||
|
for spl_to, spl_from in zip(target_obj.data.splines, source_obj.data.splines):
|
||||||
|
spl_to.material_index = spl_from.material_index
|
||||||
|
|
||||||
|
if source_obj.data.attributes.get(constants.MATERIAL_ATTRIBUTE_NAME):
|
||||||
|
transfer_attribute(constants.MATERIAL_ATTRIBUTE_NAME, target_obj, source_obj)
|
@ -0,0 +1,112 @@
|
|||||||
|
import bpy
|
||||||
|
from ..transfer_util import (
|
||||||
|
transfer_data_clean,
|
||||||
|
transfer_data_item_is_missing,
|
||||||
|
check_transfer_data_entry,
|
||||||
|
)
|
||||||
|
from ...naming import task_layer_prefix_name_get
|
||||||
|
from ...drivers import find_drivers, copy_driver
|
||||||
|
from ...visibility import override_obj_visability
|
||||||
|
from ...task_layer import get_transfer_data_owner
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def modifiers_clean(obj):
|
||||||
|
transfer_data_clean(
|
||||||
|
obj=obj, data_list=obj.modifiers, td_type_key=constants.MODIFIER_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def modifier_is_missing(transfer_data_item):
|
||||||
|
return transfer_data_item_is_missing(
|
||||||
|
transfer_data_item=transfer_data_item,
|
||||||
|
td_type_key=constants.MODIFIER_KEY,
|
||||||
|
data_list=transfer_data_item.id_data.modifiers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init_modifiers(scene, obj):
|
||||||
|
asset_pipe = scene.asset_pipeline
|
||||||
|
td_type_key = constants.MODIFIER_KEY
|
||||||
|
transfer_data = obj.transfer_data_ownership
|
||||||
|
task_layer_owner, auto_surrender = get_transfer_data_owner(
|
||||||
|
asset_pipe,
|
||||||
|
td_type_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
for mod in obj.modifiers:
|
||||||
|
mod.name = task_layer_prefix_name_get(mod.name, task_layer_owner)
|
||||||
|
# Only add new ownership transfer_data_item if vertex group doesn't have an owner
|
||||||
|
matches = check_transfer_data_entry(transfer_data, mod.name, td_type_key)
|
||||||
|
if len(matches) == 0:
|
||||||
|
asset_pipe.add_temp_transfer_data(
|
||||||
|
name=mod.name,
|
||||||
|
owner=task_layer_owner,
|
||||||
|
type=td_type_key,
|
||||||
|
obj=obj,
|
||||||
|
surrender=auto_surrender,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_modifier(modifier_name, target_obj, source_obj):
|
||||||
|
# remove old and sync existing modifiers
|
||||||
|
context = bpy.context
|
||||||
|
scene = context.scene
|
||||||
|
old_mod = target_obj.modifiers.get(modifier_name)
|
||||||
|
if old_mod:
|
||||||
|
target_obj.modifiers.remove(old_mod)
|
||||||
|
|
||||||
|
# transfer new modifiers
|
||||||
|
for i, mod in enumerate(source_obj.modifiers):
|
||||||
|
if mod.name == modifier_name:
|
||||||
|
mod_new = target_obj.modifiers.new(mod.name, mod.type)
|
||||||
|
# sort new modifier at correct index (default to beginning of the stack)
|
||||||
|
idx = 0
|
||||||
|
if i > 0:
|
||||||
|
name_prev = source_obj.modifiers[i - 1].name
|
||||||
|
for target_mod_i, target_mod in enumerate(target_obj.modifiers):
|
||||||
|
if target_mod.name == name_prev:
|
||||||
|
idx = target_mod_i + 1
|
||||||
|
with override_obj_visability(obj=target_obj, scene=scene):
|
||||||
|
with context.temp_override(object=target_obj):
|
||||||
|
bpy.ops.object.modifier_move_to_index(
|
||||||
|
modifier=mod_new.name, index=idx
|
||||||
|
)
|
||||||
|
mod_target = target_obj.modifiers.get(mod.name)
|
||||||
|
props = [p.identifier for p in mod.bl_rna.properties if not p.is_readonly]
|
||||||
|
for prop in props:
|
||||||
|
value = getattr(mod, prop)
|
||||||
|
setattr(mod_target, prop, value)
|
||||||
|
|
||||||
|
# rebind modifiers (corr. smooth, surf. deform, mesh deform)
|
||||||
|
for mod in target_obj.modifiers:
|
||||||
|
if mod.type == 'SURFACE_DEFORM':
|
||||||
|
if not mod.is_bound:
|
||||||
|
continue
|
||||||
|
for i in range(2):
|
||||||
|
with override_obj_visability(obj=target_obj, scene=scene):
|
||||||
|
with context.temp_override(
|
||||||
|
object=target_obj, active_object=target_obj
|
||||||
|
):
|
||||||
|
bpy.ops.object.surfacedeform_bind(modifier=mod.name)
|
||||||
|
elif mod.type == 'MESH_DEFORM':
|
||||||
|
if not mod.is_bound:
|
||||||
|
continue
|
||||||
|
for i in range(2):
|
||||||
|
with override_obj_visability(obj=target_obj, scene=scene):
|
||||||
|
with context.temp_override(
|
||||||
|
object=target_obj, active_object=target_obj
|
||||||
|
):
|
||||||
|
bpy.ops.object.meshdeform_bind(modifier=mod.name)
|
||||||
|
elif mod.type == 'CORRECTIVE_SMOOTH':
|
||||||
|
if not mod.is_bind:
|
||||||
|
continue
|
||||||
|
for i in range(2):
|
||||||
|
with override_obj_visability(obj=target_obj, scene=scene):
|
||||||
|
with context.temp_override(
|
||||||
|
object=target_obj, active_object=target_obj
|
||||||
|
):
|
||||||
|
bpy.ops.object.correctivesmooth_bind(modifier=mod.name)
|
||||||
|
fcurves = find_drivers(source_obj, 'modifiers', modifier_name)
|
||||||
|
for fcurve in fcurves:
|
||||||
|
copy_driver(from_fcurve=fcurve, target=target_obj)
|
@ -0,0 +1,59 @@
|
|||||||
|
import bpy
|
||||||
|
from ..transfer_util import check_transfer_data_entry
|
||||||
|
from ...task_layer import get_transfer_data_owner
|
||||||
|
from ...naming import get_basename
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def parent_clean(obj):
|
||||||
|
matches = check_transfer_data_entry(
|
||||||
|
obj.transfer_data_ownership,
|
||||||
|
get_basename(constants.PARENT_TRANSFER_DATA_ITEM_NAME),
|
||||||
|
constants.PARENT_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(matches) != 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
obj.parent = None
|
||||||
|
print("Cleaning Parent Relationship")
|
||||||
|
|
||||||
|
|
||||||
|
def parent_is_missing(transfer_data_item):
|
||||||
|
if (
|
||||||
|
transfer_data_item.type == constants.PARENT_KEY
|
||||||
|
and transfer_data_item.id_data.parent == None
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def init_parent(scene, obj):
|
||||||
|
asset_pipe = scene.asset_pipeline
|
||||||
|
td_type_key = constants.PARENT_KEY
|
||||||
|
name = constants.PARENT_TRANSFER_DATA_ITEM_NAME
|
||||||
|
transfer_data = obj.transfer_data_ownership
|
||||||
|
|
||||||
|
# Only Execute if Material Slots exist on object
|
||||||
|
if obj.parent == None:
|
||||||
|
return
|
||||||
|
if obj.parent not in list(asset_pipe.asset_collection.all_objects):
|
||||||
|
raise Exception("Object parent cannot be outside of asset collection")
|
||||||
|
matches = check_transfer_data_entry(transfer_data, name, td_type_key)
|
||||||
|
# Only add new ownership transfer_data_item if vertex group doesn't have an owner
|
||||||
|
if len(matches) == 0:
|
||||||
|
task_layer_owner, auto_surrender = get_transfer_data_owner(
|
||||||
|
asset_pipe,
|
||||||
|
td_type_key,
|
||||||
|
)
|
||||||
|
asset_pipe.add_temp_transfer_data(
|
||||||
|
name=name,
|
||||||
|
owner=task_layer_owner,
|
||||||
|
type=td_type_key,
|
||||||
|
obj=obj,
|
||||||
|
surrender=auto_surrender,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_parent(target_obj, source_obj):
|
||||||
|
target_obj.parent = source_obj.parent
|
||||||
|
target_obj.matrix_parent_inverse = source_obj.parent.matrix_world.inverted()
|
@ -0,0 +1,231 @@
|
|||||||
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
import bmesh
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def closest_face_to_point(bm_source, p_target, bvh_tree=None):
|
||||||
|
if not bvh_tree:
|
||||||
|
bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(bm_source)
|
||||||
|
(loc, norm, index, distance) = bvh_tree.find_nearest(p_target)
|
||||||
|
return bm_source.faces[index]
|
||||||
|
|
||||||
|
|
||||||
|
def tris_per_face(bm_source):
|
||||||
|
tris_source = bm_source.calc_loop_triangles()
|
||||||
|
tris_dict = dict()
|
||||||
|
for face in bm_source.faces:
|
||||||
|
tris_face = []
|
||||||
|
for i in range(len(tris_source))[::-1]:
|
||||||
|
if tris_source[i][0] in face.loops:
|
||||||
|
tris_face.append(tris_source.pop(i))
|
||||||
|
tris_dict[face] = tris_face
|
||||||
|
return tris_dict
|
||||||
|
|
||||||
|
|
||||||
|
def closest_tri_on_face(tris_dict, face, p):
|
||||||
|
points = []
|
||||||
|
dist = []
|
||||||
|
tris = []
|
||||||
|
for tri in tris_dict[face]:
|
||||||
|
point = mathutils.geometry.closest_point_on_tri(
|
||||||
|
p, *[tri[i].vert.co for i in range(3)]
|
||||||
|
)
|
||||||
|
tris.append(tri)
|
||||||
|
points.append(point)
|
||||||
|
dist.append((point - p).length)
|
||||||
|
min_idx = np.argmin(np.array(dist))
|
||||||
|
point = points[min_idx]
|
||||||
|
tri = tris[min_idx]
|
||||||
|
return (tri, point)
|
||||||
|
|
||||||
|
|
||||||
|
def closest_edge_on_face_to_line(face, p1, p2, skip_edges=None):
|
||||||
|
"""Returns edge of a face which is closest to line."""
|
||||||
|
for edge in face.edges:
|
||||||
|
if skip_edges:
|
||||||
|
if edge in skip_edges:
|
||||||
|
continue
|
||||||
|
res = mathutils.geometry.intersect_line_line(
|
||||||
|
p1, p2, *[edge.verts[i].co for i in range(2)]
|
||||||
|
)
|
||||||
|
if not res:
|
||||||
|
continue
|
||||||
|
(p_traversal, p_edge) = res
|
||||||
|
frac_1 = (edge.verts[1].co - edge.verts[0].co).dot(
|
||||||
|
p_edge - edge.verts[0].co
|
||||||
|
) / (edge.verts[1].co - edge.verts[0].co).length ** 2.0
|
||||||
|
frac_2 = (p2 - p1).dot(p_traversal - p1) / (p2 - p1).length ** 2.0
|
||||||
|
if (frac_1 >= 0 and frac_1 <= 1) and (frac_2 >= 0 and frac_2 <= 1):
|
||||||
|
return edge
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def edge_data_split(edge, data_layer, data_suffix: str):
|
||||||
|
for vert in edge.verts:
|
||||||
|
vals = []
|
||||||
|
for loop in vert.link_loops:
|
||||||
|
loops_edge_vert = set([loop for f in edge.link_faces for loop in f.loops])
|
||||||
|
if loop not in loops_edge_vert:
|
||||||
|
continue
|
||||||
|
dat = data_layer[loop.index]
|
||||||
|
element = list(getattr(dat, data_suffix))
|
||||||
|
if not vals:
|
||||||
|
vals.append(element)
|
||||||
|
elif not vals[0] == element:
|
||||||
|
vals.append(element)
|
||||||
|
if len(vals) > 1:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def interpolate_data_from_face(
|
||||||
|
bm_source, tris_dict, face, p, data_layer_source, data_suffix=''
|
||||||
|
):
|
||||||
|
"""Returns interpolated value of a data layer within a face closest to a point."""
|
||||||
|
|
||||||
|
(tri, point) = closest_tri_on_face(tris_dict, face, p)
|
||||||
|
if not tri:
|
||||||
|
return None
|
||||||
|
weights = mathutils.interpolate.poly_3d_calc(
|
||||||
|
[tri[i].vert.co for i in range(3)], point
|
||||||
|
)
|
||||||
|
|
||||||
|
if not data_suffix:
|
||||||
|
cols_weighted = [
|
||||||
|
weights[i] * np.array(data_layer_source[tri[i].index]) for i in range(3)
|
||||||
|
]
|
||||||
|
col = sum(np.array(cols_weighted))
|
||||||
|
else:
|
||||||
|
cols_weighted = [
|
||||||
|
weights[i] * np.array(getattr(data_layer_source[tri[i].index], data_suffix))
|
||||||
|
for i in range(3)
|
||||||
|
]
|
||||||
|
col = sum(np.array(cols_weighted))
|
||||||
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_corner_data(
|
||||||
|
obj_source, obj_target, data_layer_source, data_layer_target, data_suffix=''
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Transfers interpolated face corner data from data layer of a source object to data layer of a
|
||||||
|
target object, while approximately preserving data seams (e.g. necessary for UV Maps).
|
||||||
|
The transfer is face interpolated per target corner within the source face that is closest
|
||||||
|
to the target corner point and does not have any data seams on the way back to the
|
||||||
|
source face that is closest to the target face's center.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bm_source = bmesh.new()
|
||||||
|
bm_source.from_mesh(obj_source.data)
|
||||||
|
bm_source.faces.ensure_lookup_table()
|
||||||
|
bm_target = bmesh.new()
|
||||||
|
bm_target.from_mesh(obj_target.data)
|
||||||
|
bm_target.faces.ensure_lookup_table()
|
||||||
|
|
||||||
|
bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(bm_source)
|
||||||
|
|
||||||
|
tris_dict = tris_per_face(bm_source)
|
||||||
|
|
||||||
|
for face_target in bm_target.faces:
|
||||||
|
face_target_center = face_target.calc_center_median()
|
||||||
|
|
||||||
|
face_source = closest_face_to_point(bm_source, face_target_center, bvh_tree)
|
||||||
|
|
||||||
|
for corner_target in face_target.loops:
|
||||||
|
# find nearest face on target compared to face that loop belongs to
|
||||||
|
p = corner_target.vert.co
|
||||||
|
|
||||||
|
face_source_closest = closest_face_to_point(bm_source, p, bvh_tree)
|
||||||
|
enclosed = face_source_closest is face_source
|
||||||
|
face_source_int = face_source
|
||||||
|
if not enclosed:
|
||||||
|
# traverse faces between point and face center
|
||||||
|
traversed_faces = set()
|
||||||
|
traversed_edges = set()
|
||||||
|
while face_source_int is not face_source_closest:
|
||||||
|
traversed_faces.add(face_source_int)
|
||||||
|
edge = closest_edge_on_face_to_line(
|
||||||
|
face_source_int,
|
||||||
|
face_target_center,
|
||||||
|
p,
|
||||||
|
skip_edges=traversed_edges,
|
||||||
|
)
|
||||||
|
if edge == None:
|
||||||
|
break
|
||||||
|
if len(edge.link_faces) != 2:
|
||||||
|
break
|
||||||
|
traversed_edges.add(edge)
|
||||||
|
|
||||||
|
split = edge_data_split(edge, data_layer_source, data_suffix)
|
||||||
|
if split:
|
||||||
|
break
|
||||||
|
|
||||||
|
# set new source face to other face belonging to edge
|
||||||
|
face_source_int = (
|
||||||
|
edge.link_faces[1]
|
||||||
|
if edge.link_faces[1] is not face_source_int
|
||||||
|
else edge.link_faces[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
# avoid looping behaviour
|
||||||
|
if face_source_int in traversed_faces:
|
||||||
|
face_source_int = face_source
|
||||||
|
break
|
||||||
|
|
||||||
|
# interpolate data from selected face
|
||||||
|
col = interpolate_data_from_face(
|
||||||
|
bm_source, tris_dict, face_source_int, p, data_layer_source, data_suffix
|
||||||
|
)
|
||||||
|
if col is None:
|
||||||
|
continue
|
||||||
|
if not data_suffix:
|
||||||
|
data_layer_target.data[corner_target.index] = col
|
||||||
|
else:
|
||||||
|
setattr(data_layer_target[corner_target.index], data_suffix, list(col))
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def is_mesh_identical(mesh_a, mesh_b) -> bool:
|
||||||
|
if len(mesh_a.vertices) != len(mesh_b.vertices):
|
||||||
|
return False
|
||||||
|
if len(mesh_a.edges) != len(mesh_b.edges):
|
||||||
|
return False
|
||||||
|
if len(mesh_a.polygons) != len(mesh_b.polygons):
|
||||||
|
return False
|
||||||
|
for e1, e2 in zip(mesh_a.edges, mesh_b.edges):
|
||||||
|
for v1, v2 in zip(e1.vertices, e2.vertices):
|
||||||
|
if v1 != v2:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_curve_identical(curve_a: bpy.types.Curve, curve_b: bpy.types.Curve) -> bool:
|
||||||
|
if len(curve_a.splines) != len(curve_b.splines):
|
||||||
|
return False
|
||||||
|
for spline1, spline2 in zip(curve_a.splines, curve_b.splines):
|
||||||
|
if len(spline1.points) != len(spline2.points):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def is_obdata_identical(
|
||||||
|
a: bpy.types.Object or bpy.types.Mesh, b: bpy.types.Object or bpy.types.Mesh
|
||||||
|
) -> bool:
|
||||||
|
"""Checks if two objects have matching topology (efficiency over exactness)"""
|
||||||
|
if type(a) == bpy.types.Object:
|
||||||
|
a = a.data
|
||||||
|
if type(b) == bpy.types.Object:
|
||||||
|
b = b.data
|
||||||
|
|
||||||
|
if type(a) != type(b):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if type(a) == bpy.types.Mesh:
|
||||||
|
return is_mesh_identical(a, b)
|
||||||
|
elif type(a) == bpy.types.Curve:
|
||||||
|
return is_curve_identical(a, b)
|
||||||
|
else:
|
||||||
|
# TODO: Support geometry types other than mesh or curve.
|
||||||
|
return
|
@ -0,0 +1,152 @@
|
|||||||
|
import bpy
|
||||||
|
import mathutils
|
||||||
|
import bmesh
|
||||||
|
import numpy as np
|
||||||
|
from .proximity_core import tris_per_face, closest_face_to_point, closest_tri_on_face
|
||||||
|
from ..transfer_util import (
|
||||||
|
transfer_data_item_is_missing,
|
||||||
|
transfer_data_item_init,
|
||||||
|
check_transfer_data_entry,
|
||||||
|
)
|
||||||
|
from ...naming import get_basename
|
||||||
|
from ...drivers import find_drivers, copy_driver
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def shape_key_set_active(obj, shape_key_name):
|
||||||
|
for index, shape_key in enumerate(obj.data.shape_keys.key_blocks):
|
||||||
|
if shape_key.name == shape_key_name:
|
||||||
|
obj.active_shape_key_index = index
|
||||||
|
|
||||||
|
|
||||||
|
def shape_keys_clean(obj):
|
||||||
|
if obj.type != "MESH" or obj.data.shape_keys is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
for shape_key in obj.data.shape_keys.key_blocks:
|
||||||
|
matches = check_transfer_data_entry(
|
||||||
|
obj.transfer_data_ownership,
|
||||||
|
get_basename(shape_key.name),
|
||||||
|
constants.SHAPE_KEY_KEY,
|
||||||
|
)
|
||||||
|
if len(matches) == 0:
|
||||||
|
obj.shape_key_remove(shape_key)
|
||||||
|
|
||||||
|
|
||||||
|
def shape_key_is_missing(transfer_data_item):
|
||||||
|
if not transfer_data_item.type == constants.SHAPE_KEY_KEY:
|
||||||
|
return
|
||||||
|
obj = transfer_data_item.id_data
|
||||||
|
if obj.type != 'MESH':
|
||||||
|
return
|
||||||
|
if not obj.data.shape_keys:
|
||||||
|
return True
|
||||||
|
return transfer_data_item_is_missing(
|
||||||
|
transfer_data_item=transfer_data_item,
|
||||||
|
td_type_key=constants.SHAPE_KEY_KEY,
|
||||||
|
data_list=obj.data.shape_keys.key_blocks,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init_shape_keys(scene, obj):
|
||||||
|
if obj.type != "MESH" or obj.data.shape_keys is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check that the order is legal.
|
||||||
|
# Key Blocks must be ordered after the key they are Relative To.
|
||||||
|
for i, kb in enumerate(obj.data.shape_keys.key_blocks):
|
||||||
|
if kb.relative_key:
|
||||||
|
base_shape_idx = obj.data.shape_keys.key_blocks.find(kb.relative_key.name)
|
||||||
|
if base_shape_idx > i:
|
||||||
|
raise Exception(
|
||||||
|
f'Shape Key "{kb.name}" must be ordered after its base shape "{kb.relative_key.name}" on object "{obj.name}".'
|
||||||
|
)
|
||||||
|
|
||||||
|
transfer_data_item_init(
|
||||||
|
scene=scene,
|
||||||
|
obj=obj,
|
||||||
|
data_list=obj.data.shape_keys.key_blocks,
|
||||||
|
td_type_key=constants.SHAPE_KEY_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_shape_key(
|
||||||
|
context: bpy.types.Context,
|
||||||
|
shape_key_name: str,
|
||||||
|
target_obj: bpy.types.Object,
|
||||||
|
source_obj: bpy.types.Object,
|
||||||
|
):
|
||||||
|
if not source_obj.data.shape_keys:
|
||||||
|
return
|
||||||
|
sk_source = source_obj.data.shape_keys.key_blocks.get(shape_key_name)
|
||||||
|
assert sk_source
|
||||||
|
|
||||||
|
sk_target = None
|
||||||
|
if not target_obj.data.shape_keys:
|
||||||
|
sk_target = target_obj.shape_key_add()
|
||||||
|
if not sk_target:
|
||||||
|
sk_target = target_obj.data.shape_keys.key_blocks.get(shape_key_name)
|
||||||
|
if not sk_target:
|
||||||
|
sk_target = target_obj.shape_key_add()
|
||||||
|
|
||||||
|
sk_target.name = sk_source.name
|
||||||
|
sk_target.vertex_group = sk_source.vertex_group
|
||||||
|
if sk_source.relative_key != sk_source:
|
||||||
|
relative_key = None
|
||||||
|
if target_obj.data.shape_keys:
|
||||||
|
relative_key = target_obj.data.shape_keys.key_blocks.get(
|
||||||
|
sk_source.relative_key.name
|
||||||
|
)
|
||||||
|
if relative_key:
|
||||||
|
sk_target.relative_key = relative_key
|
||||||
|
else:
|
||||||
|
# If the base shape of one of our shapes was removed by another task layer,
|
||||||
|
# the result will probably be pretty bad, but it's not a catastrophic failure.
|
||||||
|
# Proceed with a warning.
|
||||||
|
print(
|
||||||
|
f'Warning: Base shape "{sk_source.relative_key.name}" of Key "{sk_source.name}" was removed from "{target_obj.name}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
sk_target.slider_min = sk_source.slider_min
|
||||||
|
sk_target.slider_max = sk_source.slider_max
|
||||||
|
sk_target.value = sk_source.value
|
||||||
|
sk_target.mute = sk_source.mute
|
||||||
|
|
||||||
|
bm_source = bmesh.new()
|
||||||
|
bm_source.from_mesh(source_obj.data)
|
||||||
|
bm_source.faces.ensure_lookup_table()
|
||||||
|
|
||||||
|
bvh_tree = mathutils.bvhtree.BVHTree.FromBMesh(bm_source)
|
||||||
|
tris_dict = tris_per_face(bm_source)
|
||||||
|
for i, vert in enumerate(target_obj.data.vertices):
|
||||||
|
p = vert.co
|
||||||
|
face = closest_face_to_point(bm_source, p, bvh_tree)
|
||||||
|
|
||||||
|
(tri, point) = closest_tri_on_face(tris_dict, face, p)
|
||||||
|
if not tri:
|
||||||
|
continue
|
||||||
|
weights = mathutils.interpolate.poly_3d_calc(
|
||||||
|
[tri[i].vert.co for i in range(3)], point
|
||||||
|
)
|
||||||
|
|
||||||
|
vals_weighted = [
|
||||||
|
weights[i]
|
||||||
|
* (
|
||||||
|
sk_source.data[tri[i].vert.index].co
|
||||||
|
- source_obj.data.vertices[tri[i].vert.index].co
|
||||||
|
)
|
||||||
|
for i in range(3)
|
||||||
|
]
|
||||||
|
val = mathutils.Vector(sum(np.array(vals_weighted)))
|
||||||
|
sk_target.data[i].co = vert.co + val
|
||||||
|
|
||||||
|
if source_obj.data.shape_keys is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
fcurves = find_drivers(
|
||||||
|
source_obj.data.shape_keys,
|
||||||
|
'key_blocks',
|
||||||
|
shape_key_name,
|
||||||
|
)
|
||||||
|
for fcurve in fcurves:
|
||||||
|
copy_driver(from_fcurve=fcurve, target=target_obj.data.shape_keys)
|
@ -0,0 +1,193 @@
|
|||||||
|
import bpy
|
||||||
|
from mathutils import Vector, kdtree
|
||||||
|
from typing import Dict, Tuple, List
|
||||||
|
from ..transfer_util import (
|
||||||
|
transfer_data_clean,
|
||||||
|
transfer_data_item_is_missing,
|
||||||
|
transfer_data_item_init,
|
||||||
|
)
|
||||||
|
from .... import constants
|
||||||
|
|
||||||
|
|
||||||
|
def vertex_groups_clean(obj):
|
||||||
|
transfer_data_clean(
|
||||||
|
obj=obj, data_list=obj.vertex_groups, td_type_key=constants.VERTEX_GROUP_KEY
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def vertex_group_is_missing(transfer_data_item):
|
||||||
|
return transfer_data_item_is_missing(
|
||||||
|
transfer_data_item=transfer_data_item,
|
||||||
|
td_type_key=constants.VERTEX_GROUP_KEY,
|
||||||
|
data_list=transfer_data_item.id_data.vertex_groups,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def init_vertex_groups(scene, obj):
|
||||||
|
transfer_data_item_init(
|
||||||
|
scene=scene,
|
||||||
|
obj=obj,
|
||||||
|
data_list=obj.vertex_groups,
|
||||||
|
td_type_key=constants.VERTEX_GROUP_KEY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_vertex_group(
|
||||||
|
context,
|
||||||
|
vertex_group_name: str,
|
||||||
|
target_obj: bpy.types.Object,
|
||||||
|
source_obj: bpy.types.Object,
|
||||||
|
):
|
||||||
|
if target_obj == source_obj:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not source_obj.vertex_groups.get(vertex_group_name):
|
||||||
|
print(f"ERROR Vertex Group {vertex_group_name} not found in {source_obj.name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
precalc_and_transfer_single_group(
|
||||||
|
source_obj, target_obj, vertex_group_name, expand=2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def precalc_and_transfer_single_group(source_obj, target_obj, vgroup_name, expand=2):
|
||||||
|
"""Convenience function to transfer a single group. For transferring multiple groups,
|
||||||
|
this is very inefficient and shouldn't be used.
|
||||||
|
|
||||||
|
Instead, you should:
|
||||||
|
- build_kd_tree ONCE per source mesh.
|
||||||
|
- build_vert_influence_map and transfer_vertex_groups ONCE per object pair.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Remove group from the target obj if it already exists.
|
||||||
|
tgt_vg = target_obj.vertex_groups.get(vgroup_name)
|
||||||
|
if tgt_vg:
|
||||||
|
target_obj.vertex_groups.remove(tgt_vg)
|
||||||
|
|
||||||
|
kd_tree = build_kdtree(source_obj.data)
|
||||||
|
vert_influence_map = build_vert_influence_map(
|
||||||
|
source_obj, target_obj, kd_tree, expand
|
||||||
|
)
|
||||||
|
transfer_vertex_groups(
|
||||||
|
source_obj,
|
||||||
|
target_obj,
|
||||||
|
vert_influence_map,
|
||||||
|
[source_obj.vertex_groups[vgroup_name]],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_kdtree(mesh):
|
||||||
|
kd = kdtree.KDTree(len(mesh.vertices))
|
||||||
|
for i, v in enumerate(mesh.vertices):
|
||||||
|
kd.insert(v.co, i)
|
||||||
|
kd.balance()
|
||||||
|
return kd
|
||||||
|
|
||||||
|
|
||||||
|
def build_vert_influence_map(obj_from, obj_to, kd_tree, expand=2):
|
||||||
|
verts_of_edge = {
|
||||||
|
i: (e.vertices[0], e.vertices[1]) for i, e in enumerate(obj_from.data.edges)
|
||||||
|
}
|
||||||
|
|
||||||
|
edges_of_vert: Dict[int, List[int]] = {}
|
||||||
|
for edge_idx, edge in enumerate(obj_from.data.edges):
|
||||||
|
for vert_idx in edge.vertices:
|
||||||
|
if vert_idx not in edges_of_vert:
|
||||||
|
edges_of_vert[vert_idx] = []
|
||||||
|
edges_of_vert[vert_idx].append(edge_idx)
|
||||||
|
|
||||||
|
# A mapping from target vertex index to a list of source vertex indicies and
|
||||||
|
# their influence.
|
||||||
|
# This can be pre-calculated once per object pair, to minimize re-calculations
|
||||||
|
# of subsequent transferring of individual vertex groups.
|
||||||
|
vert_influence_map: List[int, List[Tuple[int, float]]] = {}
|
||||||
|
for i, dest_vert in enumerate(obj_to.data.vertices):
|
||||||
|
vert_influence_map[i] = get_source_vert_influences(
|
||||||
|
dest_vert, obj_from, kd_tree, expand, edges_of_vert, verts_of_edge
|
||||||
|
)
|
||||||
|
|
||||||
|
return vert_influence_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_source_vert_influences(
|
||||||
|
target_vert, obj_from, kd_tree, expand=2, edges_of_vert={}, verts_of_edge={}
|
||||||
|
) -> List[Tuple[int, float]]:
|
||||||
|
_coord, idx, dist = get_nearest_vert(target_vert.co, kd_tree)
|
||||||
|
source_vert_indices = [idx]
|
||||||
|
|
||||||
|
if dist == 0:
|
||||||
|
# If the vertex position is a perfect match, just use that one vertex with max influence.
|
||||||
|
return [(idx, 1)]
|
||||||
|
|
||||||
|
for i in range(0, expand):
|
||||||
|
new_indices = []
|
||||||
|
for vert_idx in source_vert_indices:
|
||||||
|
for edge in edges_of_vert[vert_idx]:
|
||||||
|
vert_other = other_vert_of_edge(edge, vert_idx, verts_of_edge)
|
||||||
|
if vert_other not in source_vert_indices:
|
||||||
|
new_indices.append(vert_other)
|
||||||
|
source_vert_indices.extend(new_indices)
|
||||||
|
|
||||||
|
distances: List[Tuple[int, float]] = []
|
||||||
|
distance_total = 0
|
||||||
|
for src_vert_idx in source_vert_indices:
|
||||||
|
distance = (target_vert.co - obj_from.data.vertices[src_vert_idx].co).length
|
||||||
|
distance_total += distance
|
||||||
|
distances.append((src_vert_idx, distance))
|
||||||
|
|
||||||
|
# Calculate influences such that the total of all influences adds up to 1.0,
|
||||||
|
# and the influence is inversely correlated with the distance.
|
||||||
|
parts = [1 / (dist / distance_total) for idx, dist in distances]
|
||||||
|
parts_sum = sum(parts)
|
||||||
|
|
||||||
|
influences = [
|
||||||
|
(idx, 1 if dist == 0 else part / parts_sum)
|
||||||
|
for part, dist in zip(parts, distances)
|
||||||
|
]
|
||||||
|
|
||||||
|
return influences
|
||||||
|
|
||||||
|
|
||||||
|
def get_nearest_vert(
|
||||||
|
coords: Vector, kd_tree: kdtree.KDTree
|
||||||
|
) -> Tuple[Vector, int, float]:
|
||||||
|
"""Return coordinate, index, and distance of nearest vert to coords in kd_tree."""
|
||||||
|
return kd_tree.find(coords)
|
||||||
|
|
||||||
|
|
||||||
|
def other_vert_of_edge(
|
||||||
|
edge: int, vert: int, verts_of_edge: Dict[int, Tuple[int, int]]
|
||||||
|
) -> int:
|
||||||
|
verts = verts_of_edge[edge]
|
||||||
|
assert vert in verts, f"Vert {vert} not part of edge {edge}."
|
||||||
|
return verts[0] if vert == verts[1] else verts[1]
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_vertex_groups(obj_from, obj_to, vert_influence_map, src_vgroups):
|
||||||
|
"""Transfer src_vgroups in obj_from to obj_to using a pre-calculated vert_influence_map."""
|
||||||
|
|
||||||
|
for src_vg in src_vgroups:
|
||||||
|
target_vg = obj_to.vertex_groups.get(src_vg.name)
|
||||||
|
if target_vg == None:
|
||||||
|
target_vg = obj_to.vertex_groups.new(name=src_vg.name)
|
||||||
|
|
||||||
|
for i, dest_vert in enumerate(obj_to.data.vertices):
|
||||||
|
source_verts = vert_influence_map[i]
|
||||||
|
|
||||||
|
# Vertex Group Name : Weight
|
||||||
|
vgroup_weights = {}
|
||||||
|
|
||||||
|
for src_vert_idx, influence in source_verts:
|
||||||
|
for group in obj_from.data.vertices[src_vert_idx].groups:
|
||||||
|
group_idx = group.group
|
||||||
|
vg = obj_from.vertex_groups[group_idx]
|
||||||
|
if vg not in src_vgroups:
|
||||||
|
continue
|
||||||
|
if vg.name not in vgroup_weights:
|
||||||
|
vgroup_weights[vg.name] = 0
|
||||||
|
vgroup_weights[vg.name] += vg.weight(src_vert_idx) * influence
|
||||||
|
|
||||||
|
# Assign final weights of this vertex in the vertex groups.
|
||||||
|
for vg_name in vgroup_weights.keys():
|
||||||
|
target_vg = obj_to.vertex_groups.get(vg_name)
|
||||||
|
target_vg.add([dest_vert.index], vgroup_weights[vg_name], 'REPLACE')
|
Loading…
Reference in New Issue
Block a user