WIP: X3D HAnim with Blender bones (no animation yet present) #23
@ -18,6 +18,7 @@ material_cache = {}
|
||||
conversion_scale = 1.0
|
||||
|
||||
EPSILON = 0.0000001 # Very crude.
|
||||
TIME_MULTIPLIER = 250
|
||||
|
||||
|
||||
def imageConvertCompat(path):
|
||||
@ -359,6 +360,9 @@ class vrmlNode(object):
|
||||
'node_type',
|
||||
'parent',
|
||||
'children',
|
||||
'skeleton', # TODO no joints, segments or sites yet.
|
||||
'skinCoord', # this is intentionally NOT skin_coord, but I don't know if HAnim should be here at all
|
||||
'skin',
|
||||
'parent',
|
||||
'array_data',
|
||||
'reference',
|
||||
@ -1594,8 +1598,11 @@ def translateTexTransform(node, ancestry):
|
||||
|
||||
def getFinalMatrix(node, mtx, ancestry, global_matrix):
|
||||
|
||||
transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
|
||||
if node.getSpec() == 'Transform':
|
||||
transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() in ('Transform', 'HAnimHumanoid', 'HAnimJoint', 'HAnimSite', 'HAnimDisplacer')]
|
||||
if node.getSpec() in ('Transform', 'HAnimHumanoid', 'HAnimJoint', 'HAnimSite', 'HAnimDisplacer'):
|
||||
# This comment is here so I can quick replace the above in testing
|
||||
#transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
|
||||
#if node.getSpec() == 'Transform':
|
||||
transform_nodes.append(node)
|
||||
transform_nodes.reverse()
|
||||
|
||||
@ -1639,6 +1646,16 @@ def set_new_float_color_attribute(bpymesh, color_data, name: str = "ColorPerCorn
|
||||
bpymesh.color_attributes.new(name, 'FLOAT_COLOR', 'CORNER')
|
||||
bpymesh.color_attributes[name].data.foreach_set("color", color_data)
|
||||
|
||||
# TODO not tested
|
||||
def set_new_float_color_attribute_curve(bpycurve, color_data, name: str = "ColorPerCorner", convert_to_linear: bool = True):
|
||||
if (convert_to_linear):
|
||||
# convert color spaces to account for api changes from legacy to newer api
|
||||
color_data = [srgb_to_linear(col_val) for col_val in color_data]
|
||||
mat = bpy.data.materials.new(name="ColorMaterial")
|
||||
mat.color = (1, 0, 0, 1)
|
||||
curve_obj.data.materials.append(mat)
|
||||
|
||||
|
||||
# Assumes that the mesh has polygons.
|
||||
def importMesh_ApplyColors(bpymesh, geom, ancestry):
|
||||
colors = geom.getChildBySpec(['ColorRGBA', 'Color'])
|
||||
@ -1907,6 +1924,80 @@ def importMesh_TriangleFanSet(geom, ancestry):
|
||||
bpymesh.polygons.foreach_set("vertices", [x for x in triangles()])
|
||||
return importMesh_FinalizeTriangleMesh(bpymesh, geom, ancestry)
|
||||
|
||||
# TODO, not for this release
|
||||
def processColors_IndexedLineSet(geom, ancestry, bpycurve, lines):
|
||||
colors = geom.getChildBySpec(['ColorRGBA', 'Color'])
|
||||
index = geom.getFieldAsArray('coordIndex', 0, ancestry)
|
||||
if colors:
|
||||
cco = []
|
||||
if colors.getSpec() == 'ColorRGBA':
|
||||
rgb = colors.getFieldAsArray('color', 4, ancestry)
|
||||
else:
|
||||
# Array of arrays; no need to flatten
|
||||
rgb = [c + [1.0] for c in colors.getFieldAsArray('color', 3, ancestry)]
|
||||
|
||||
color_per_vertex = geom.getFieldAsBool('colorPerVertex', True, ancestry)
|
||||
color_index = geom.getFieldAsArray('colorIndex', 0, ancestry)
|
||||
has_color_index = len(color_index) != 0
|
||||
has_valid_color_index = index.count(-1) == color_index.count(-1)
|
||||
|
||||
# rebuild a corrupted colorIndex field (assuming the end of face markers -1 are missing)
|
||||
if has_color_index and not has_valid_color_index:
|
||||
# remove all -1 beforehand to ensure clean working copy
|
||||
color_index = [x for x in color_index if x != -1]
|
||||
# copy all -1 from coordIndex to colorIndex
|
||||
for i, v in enumerate(index):
|
||||
if v == -1:
|
||||
color_index.insert(i, -1)
|
||||
|
||||
if color_per_vertex and has_color_index: # Color per vertex with index
|
||||
cco = [cco for f in processPerVertexIndex(color_index)
|
||||
for v in f
|
||||
for cco in rgb[v]]
|
||||
elif color_per_vertex: # Color per vertex without index
|
||||
# use vertex value by default, however if lengths mismatch use the positional value to access rgb value
|
||||
# ain't ideal by far, but should most likely work
|
||||
try:
|
||||
cco = [cco for f in lines
|
||||
for v in f
|
||||
for cco in rgb[v]]
|
||||
except IndexError:
|
||||
print("reattempting reading color_per_vertex without index by using positional value because vertex value failed")
|
||||
cco = [cco for f in lines
|
||||
for (i, v) in enumerate(f)
|
||||
for cco in rgb[i]]
|
||||
elif color_index: # Color per face with index
|
||||
cco = [cco for (i, f) in enumerate(lines)
|
||||
for j in f
|
||||
for cco in rgb[color_index[i]]]
|
||||
elif len(lines) > len(rgb): # Static color per face without index, when all lines have the same color.
|
||||
# Exported from SOLIDWORKS, see: `blender/blender-addons#105398`.
|
||||
cco = [cco for (i, f) in enumerate(lines)
|
||||
for j in f
|
||||
for cco in rgb[0]]
|
||||
else: # Color per face without index
|
||||
cco = [cco for (i, f) in enumerate(lines)
|
||||
for j in f
|
||||
for cco in rgb[i]]
|
||||
|
||||
for i, spline in enumerate(bpycurve.splines):
|
||||
# Example: Color based on spline index
|
||||
# TODO do RGBA
|
||||
if cco is not None:
|
||||
color = cco[i * 3:(i+1) * 3]
|
||||
color.append(1) # TODO includ transporency
|
||||
else:
|
||||
color = (1, 0, 0, 1)
|
||||
print(f"Color = {color}")
|
||||
# Apply color to the spline directly
|
||||
spline.material_index = i # Assign a material index (optional)
|
||||
|
||||
# Create a material for the spline
|
||||
if len(bpy.data.materials) <= i:
|
||||
mat = bpy.data.materials.new(name="ColorMaterial_" + str(i))
|
||||
mat.diffuse_color = color
|
||||
bpycurve.materials.append(mat)
|
||||
|
||||
|
||||
def importMesh_IndexedFaceSet(geom, ancestry):
|
||||
# Saw the following structure in X3Ds: the first mesh has a huge set
|
||||
@ -2503,6 +2594,8 @@ def importMesh_IndexedLineSet(geom, ancestry):
|
||||
nu.points.add(len(line) - 1) # the new nu has 1 point to begin with
|
||||
for il, pt in zip(line, nu.points):
|
||||
pt.co[0:3] = points[il]
|
||||
# TODO
|
||||
# processColors_IndexedLineSet(geom, ancestry, bpycurve, lines)
|
||||
|
||||
return bpycurve
|
||||
|
||||
@ -2850,6 +2943,8 @@ def appearance_LoadImageTextureFile(ima_urls, node):
|
||||
bpyima = None
|
||||
for f in ima_urls:
|
||||
dirname = os.path.dirname(node.getFilename())
|
||||
if f.startswith('"'):
|
||||
f = f[1:-1] # strip quotes (I want to strip both quotes, front and tail. I am not sure if this works)
|
||||
bpyima = image_utils.load_image(f, dirname,
|
||||
place_holder=False,
|
||||
recursive=False,
|
||||
@ -3163,7 +3258,7 @@ def importShape_ProcessObject(
|
||||
# Can transform data or object, better the object so we can instance
|
||||
# the data
|
||||
# bpymesh.transform(getFinalMatrix(node))
|
||||
bpyob = node.blendObject = bpy.data.objects.new(vrmlname, bpydata)
|
||||
bpyob = node.blendData = node.blendObject = bpy.data.objects.new(vrmlname, bpydata)
|
||||
bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
|
||||
bpycollection.objects.link(bpyob)
|
||||
bpyob.select_set(True)
|
||||
@ -3171,6 +3266,8 @@ def importShape_ProcessObject(
|
||||
if DEBUG:
|
||||
bpyob["source_line_no"] = geom.lineno
|
||||
|
||||
return bpyob
|
||||
|
||||
|
||||
def importText(geom, ancestry):
|
||||
fmt = geom.getChildBySpec('FontStyle')
|
||||
@ -3253,6 +3350,7 @@ def importShape(bpycollection, node, ancestry, global_matrix):
|
||||
|
||||
bpydata = None
|
||||
geom_spec = geom.getSpec()
|
||||
coord = geom.getChildBySpec('Coordinate')
|
||||
|
||||
# ccw is handled by every geometry importer separately; some
|
||||
# geometries are easier to flip than others
|
||||
@ -3266,14 +3364,127 @@ def importShape(bpycollection, node, ancestry, global_matrix):
|
||||
|
||||
# There are no geometry importers that can legally return
|
||||
# no object. It's either a bpy object, or an exception
|
||||
importShape_ProcessObject(
|
||||
bpypo = importShape_ProcessObject(
|
||||
bpycollection, vrmlname, bpydata, geom, geom_spec,
|
||||
node, bpymat, tex_has_alpha, texmtx,
|
||||
ancestry, global_matrix)
|
||||
|
||||
if bpypo is None:
|
||||
print('ImportX3D warning: importShape_ProcessObject did not return a shape to return for HAnim "%s"' % vrmlname)
|
||||
else:
|
||||
print('\tImportX3D warning: unsupported type "%s"' % geom_spec)
|
||||
bpypo = None
|
||||
return [ geom, bpypo, coord ]
|
||||
|
||||
|
||||
def importHAnimHumanoid(bpycollection, node, ancestry, global_matrix, joints, segments, jointSkin):
|
||||
vrmlname = node.getDefName()
|
||||
# print(vrmlname)
|
||||
prefix = ''
|
||||
if vrmlname:
|
||||
first_underscore = vrmlname.find('_')
|
||||
if first_underscore > 0:
|
||||
prefix = vrmlname[:first_underscore+1]
|
||||
else:
|
||||
vrmlname = 'HAnimHumanoid'
|
||||
|
||||
Bujus_Krachus marked this conversation as resolved
|
||||
# Create armature and object
|
||||
armature_data = bpy.data.armatures.new(prefix+"humanoid_root")
|
||||
skeleton = bpy.data.objects.new(vrmlname, armature_data)
|
||||
skeleton.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
|
||||
|
||||
# Link object to collection and make it active
|
||||
bpycollection.objects.link(skeleton)
|
||||
bpy.context.view_layer.objects.active = skeleton
|
||||
skeleton.select_set(True)
|
||||
|
||||
# Enter edit mode
|
||||
Bujus_Krachus marked this conversation as resolved
Cedric Steiert
commented
like above could get simplified using like above could get simplified using `or`
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
# Store reference to the object on the node
|
||||
bpyob = node.blendData = node.blendObject = skeleton
|
||||
|
||||
|
||||
# Process children joints, including USE, if present
|
||||
child = node.getChildBySpec('HAnimJoint') # 'HAnimJoint'
|
||||
if child:
|
||||
Bujus_Krachus marked this conversation as resolved
Cedric Steiert
commented
better move the nl after the validation section better move the nl after the validation section
|
||||
first_joint_name = child.getDefName() or child.getFieldAsString('name', None, ancestry)
|
||||
joint_center = child.getFieldAsFloatTuple('center', (0.0, 0.0, 0.0), ancestry)
|
||||
if DEBUG:
|
||||
print(f"Joint {first_joint_name} {joint_center}")
|
||||
importHAnimJoint(joints, segments, child, ancestry, first_joint_name, parent_center=joint_center[:])
|
||||
|
||||
# Create bones for each joint
|
||||
for joint_name, joint_start, joint_end, skinCoordWeight, skinCoordIndex in joints:
|
||||
if not joint_name:
|
||||
joint_name = vrmlname
|
||||
new_segment = armature_data.edit_bones.new(joint_name)
|
||||
child.blendData = child.blendObject = new_segment
|
||||
matrix_world_inv = skeleton.matrix_world.inverted()
|
||||
new_segment.head = joint_end
|
||||
new_segment.tail = joint_start
|
||||
# if joint_name != vrmlname:
|
||||
jointSkin[joint_name] = {
|
||||
'skinCoordWeight' : skinCoordWeight,
|
||||
'skinCoordIndex' : skinCoordIndex
|
||||
}
|
||||
for segment in segments:
|
||||
parent_joint, child_joint = segment
|
||||
if parent_joint in skeleton.data.edit_bones:
|
||||
parent = skeleton.data.edit_bones[parent_joint] # some things don't have a parent
|
||||
else:
|
||||
parent = None
|
||||
|
||||
if child_joint in skeleton.data.edit_bones:
|
||||
child = skeleton.data.edit_bones[child_joint]
|
||||
else:
|
||||
child = armature_data.edit_bones.new(child_joint)
|
||||
child.parent = parent
|
||||
else:
|
||||
print("Couldn't find child HAnimJoint")
|
||||
|
||||
|
||||
return skeleton
|
||||
|
||||
|
||||
def importHAnimJoints(joints, segments, children, ancestry, parent_bone_name, parent_center=[0, 0, 0]):
|
||||
for child in children:
|
||||
child_bone_name = child.getDefName() or child.getFieldAsString('name', None, ancestry) or parent_bone_name
|
||||
segments.append((parent_bone_name, child_bone_name))
|
||||
importHAnimJoint(joints, segments, child, ancestry, parent_bone_name, parent_center)
|
||||
|
||||
def importHAnimJoint(joints, segments, child, ancestry, parent_bone_name=None, parent_center=[0, 0, 0]):
|
||||
if child:
|
||||
child_bone_name = child.getDefName()
|
||||
if not child_bone_name:
|
||||
child_bone_name = child.getFieldAsString('name', None, ancestry)
|
||||
if not child_bone_name:
|
||||
child_bone_name = 'Armature'
|
||||
child_center = child.getFieldAsFloatTuple('center', None, ancestry)
|
||||
skinCoordWeight = child.getFieldAsArray('skinCoordWeight', 0, ancestry)
|
||||
skinCoordIndex = child.getFieldAsArray('skinCoordIndex', 0, ancestry)
|
||||
# I don't understand reviewer's comment:
|
||||
# "better move the nl after the validation section"
|
||||
if skinCoordWeight is None:
|
||||
skinCoordWeight = ()
|
||||
if skinCoordIndex is None:
|
||||
skinCoordIndex = ()
|
||||
|
||||
if not child_center:
|
||||
child_center = [0, 0, 0]
|
||||
joints.append((child_bone_name, tuple(child_center), tuple(parent_center), skinCoordWeight, skinCoordIndex))
|
||||
|
||||
# print(f"Joint IHAJ {joints[-1]}")
|
||||
children = child.getChildrenBySpec('HAnimJoint')
|
||||
if children:
|
||||
importHAnimJoints(joints, segments, children, ancestry, child_bone_name, child_center)
|
||||
else:
|
||||
childname = child.getFieldAsString('name', '', ancestry)
|
||||
if DEBUG:
|
||||
print(f"Didn't find children, {children} for {childname}")
|
||||
else:
|
||||
print(f"Didn't find child, {child}")
|
||||
|
||||
# -----------------------------------------------------------------------------------
|
||||
# Lighting
|
||||
|
||||
@ -3416,10 +3627,24 @@ def importTransform(bpycollection, node, ancestry, global_matrix):
|
||||
|
||||
bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
|
||||
|
||||
# so they are not too annoying
|
||||
# so the EMPTY is not too annoying
|
||||
bpyob.empty_display_type = 'PLAIN_AXES'
|
||||
bpyob.empty_display_size = 0.2
|
||||
|
||||
def importHAnimSegment(bpycollection, node, ancestry, global_matrix):
|
||||
name = node.getDefName() or node.getFieldAsString('name', None, ancestry) or 'HAnimSegment'
|
||||
|
||||
bpyob = node.blendData = node.blendObject = bpy.data.objects.new(name, None)
|
||||
bpycollection.objects.link(bpyob)
|
||||
bpyob.select_set(True)
|
||||
|
||||
bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
|
||||
|
||||
bpyob.empty_display_type = 'PLAIN_AXES'
|
||||
# so the EMPTY is not too annoying
|
||||
bpyob.empty_display_size = 0.2
|
||||
|
||||
return bpyob
|
||||
|
||||
#def importTimeSensor(node):
|
||||
def action_fcurve_ensure(action, data_path, array_index):
|
||||
@ -3429,7 +3654,6 @@ def action_fcurve_ensure(action, data_path, array_index):
|
||||
|
||||
return action.fcurves.new(data_path=data_path, index=array_index)
|
||||
|
||||
|
||||
def translatePositionInterpolator(node, action, ancestry):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 3, ancestry)
|
||||
@ -3437,26 +3661,62 @@ def translatePositionInterpolator(node, action, ancestry):
|
||||
loc_x = action_fcurve_ensure(action, "location", 0)
|
||||
loc_y = action_fcurve_ensure(action, "location", 1)
|
||||
loc_z = action_fcurve_ensure(action, "location", 2)
|
||||
if DEBUG:
|
||||
print (f"key {key} keyValue {keyValue} {action}")
|
||||
|
||||
for i, time in enumerate(key):
|
||||
try:
|
||||
x, y, z = keyValue[i]
|
||||
except:
|
||||
if DEBUG:
|
||||
print (f"i {i} x {x} y {y} z {z}")
|
||||
except: # There's 4 exception possible here, so just wildcard
|
||||
continue
|
||||
|
||||
loc_x.keyframe_points.insert(time, x)
|
||||
loc_y.keyframe_points.insert(time, y)
|
||||
loc_z.keyframe_points.insert(time, z)
|
||||
loc_x.keyframe_points.insert(time*TIME_MULTIPLIER, x)
|
||||
loc_y.keyframe_points.insert(time*TIME_MULTIPLIER, y)
|
||||
loc_z.keyframe_points.insert(time*TIME_MULTIPLIER, z)
|
||||
|
||||
for fcu in (loc_x, loc_y, loc_z):
|
||||
for kf in fcu.keyframe_points:
|
||||
kf.interpolation = 'BEZIER'
|
||||
|
||||
def translateCoordinateInterpolator(node, action, ancestry):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 0, ancestry)
|
||||
offset = int(len(keyValue) / len(key) / 3) # values divide by times divided by axes
|
||||
if DEBUG:
|
||||
print(f"ci {offset} = {len(keyValue)} / {len(key)}")
|
||||
loc_x = action_fcurve_ensure(action, "location", 0)
|
||||
loc_y = action_fcurve_ensure(action, "location", 1)
|
||||
loc_z = action_fcurve_ensure(action, "location", 2)
|
||||
|
||||
curoff = 0
|
||||
for i, time in enumerate(key): # loop through time
|
||||
# 0 1 2
|
||||
for off in range(offset): # for each data point
|
||||
# 0 1 2 up to offset
|
||||
# curoff = i*offset+off
|
||||
#print(f" coordinate index {off} num coordinates {offset} time index {i} time {time} current offset {curoff}")
|
||||
# then a vec3f
|
||||
x = keyValue[curoff+0]
|
||||
y = keyValue[curoff+1]
|
||||
z = keyValue[curoff+2]
|
||||
loc_x.keyframe_points.insert(time*TIME_MULTIPLIER, x)
|
||||
loc_y.keyframe_points.insert(time*TIME_MULTIPLIER, y)
|
||||
loc_z.keyframe_points.insert(time*TIME_MULTIPLIER, z)
|
||||
|
||||
curoff += 3
|
||||
|
||||
for fcu in (loc_x, loc_y, loc_z):
|
||||
for kf in fcu.keyframe_points:
|
||||
kf.interpolation = 'LINEAR'
|
||||
|
||||
|
||||
def translateOrientationInterpolator(node, action, ancestry):
|
||||
def translateOrientationInterpolator(node, action, ancestry, to_node):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
|
||||
|
||||
node.rotation_mode = 'XYZ'
|
||||
|
||||
rot_x = action_fcurve_ensure(action, "rotation_euler", 0)
|
||||
rot_y = action_fcurve_ensure(action, "rotation_euler", 1)
|
||||
rot_z = action_fcurve_ensure(action, "rotation_euler", 2)
|
||||
@ -3464,22 +3724,35 @@ def translateOrientationInterpolator(node, action, ancestry):
|
||||
for i, time in enumerate(key):
|
||||
try:
|
||||
x, y, z, w = keyValue[i]
|
||||
except:
|
||||
except: # There's 4 exception possible here, so just wildcard
|
||||
continue
|
||||
|
||||
mtx = translateRotation((x, y, z, w))
|
||||
eul = mtx.to_euler()
|
||||
rot_x.keyframe_points.insert(time, eul.x)
|
||||
rot_y.keyframe_points.insert(time, eul.y)
|
||||
rot_z.keyframe_points.insert(time, eul.z)
|
||||
rot_x.keyframe_points.insert(time*TIME_MULTIPLIER, eul.x)
|
||||
rot_y.keyframe_points.insert(time*TIME_MULTIPLIER, eul.y)
|
||||
rot_z.keyframe_points.insert(time*TIME_MULTIPLIER, eul.z)
|
||||
|
||||
for fcu in (rot_x, rot_y, rot_z):
|
||||
for kf in fcu.keyframe_points:
|
||||
kf.interpolation = 'LINEAR'
|
||||
kf.interpolation = 'BEZIER'
|
||||
|
||||
def translateBoneOrientationInterpolator(node, action, ancestry, to_id=None, skeleton=None):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
|
||||
|
||||
pose_bone = None
|
||||
if skeleton:
|
||||
pose_bone = skeleton.pose.bones.get(to_id)
|
||||
if pose_bone:
|
||||
pose_bone.rotation_mode = 'AXIS_ANGLE'
|
||||
for time, (x, y, z, w) in zip(key, keyValue):
|
||||
pose_bone.rotation_axis_angle = (w, x, y, z)
|
||||
pose_bone.keyframe_insert(data_path="rotation_axis_angle", frame=time * TIME_MULTIPLIER)
|
||||
|
||||
|
||||
# Untested!
|
||||
def translateScalarInterpolator(node, action, ancestry):
|
||||
def translateScaleInterpolator(node, action, ancestry):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 4, ancestry)
|
||||
|
||||
@ -3490,13 +3763,26 @@ def translateScalarInterpolator(node, action, ancestry):
|
||||
for i, time in enumerate(key):
|
||||
try:
|
||||
x, y, z = keyValue[i]
|
||||
except:
|
||||
except: # There's 4 exception possible here, so just wildcard
|
||||
continue
|
||||
|
||||
sca_x.keyframe_points.new(time, x)
|
||||
sca_y.keyframe_points.new(time, y)
|
||||
sca_z.keyframe_points.new(time, z)
|
||||
sca_x.keyframe_points.insert(time*TIME_MULTIPLIER, x)
|
||||
sca_y.keyframe_points.insert(time*TIME_MULTIPLIER, y)
|
||||
sca_z.keyframe_points.insert(time*TIME_MULTIPLIER, z)
|
||||
|
||||
def translateScalarInterpolator(node, action, ancestry, to_node, data_path):
|
||||
key = node.getFieldAsArray('key', 0, ancestry)
|
||||
keyValue = node.getFieldAsArray('keyValue', 0, ancestry)
|
||||
|
||||
scalar = action_fcurve_ensure(action, data_path, 0)
|
||||
|
||||
for i, time in enumerate(key):
|
||||
try:
|
||||
s = keyValue[i]
|
||||
except: # There's 4 exception possible here, so just wildcard
|
||||
continue
|
||||
|
||||
scalar.keyframe_points.insert(time*TIME_MULTIPLIER, s)
|
||||
|
||||
def translateTimeSensor(node, action, ancestry):
|
||||
"""
|
||||
@ -3527,14 +3813,7 @@ def translateTimeSensor(node, action, ancestry):
|
||||
if loop:
|
||||
time_cu.extend = Blender.IpoCurve.ExtendTypes.CYCLIC # or - EXTRAP, CYCLIC_EXTRAP, CONST,
|
||||
|
||||
|
||||
def importRoute(node, ancestry):
|
||||
"""
|
||||
Animation route only at the moment
|
||||
"""
|
||||
|
||||
if not hasattr(node, 'fields'):
|
||||
return
|
||||
def importRouteFromTo(node, from_id, from_type, to_id, to_type, ancestry, skeleton, hasMesh):
|
||||
|
||||
routeIpoDict = node.getRouteIpoDict()
|
||||
|
||||
@ -3542,11 +3821,63 @@ def importRoute(node, ancestry):
|
||||
try:
|
||||
action = routeIpoDict[act_id]
|
||||
except:
|
||||
action = routeIpoDict[act_id] = bpy.data.actions.new('web3d_ipo')
|
||||
action = routeIpoDict[act_id] = bpy.data.actions.new(act_id)
|
||||
#print(f"return action {act_id} {action}")
|
||||
return action
|
||||
|
||||
# for getting definitions
|
||||
defDict = node.getDefDict()
|
||||
|
||||
if from_type == 'value_changed':
|
||||
if to_type in ('set_translation', 'set_position'): # set translation may need some matrix multiplication
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
if DEBUG:
|
||||
print(f"Trying to create a position interpolator for something from {from_id} to {to_id} (may need something special?)")
|
||||
translatePositionInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
if to_type in {'rotation', "set_rotation"} and defDict[to_id].getSpec() == 'TextureTransform':
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
to_node = defDict[to_id]
|
||||
Bujus_Krachus marked this conversation as resolved
Cedric Steiert
commented
any specific error to be expected? any specific error to be expected?
|
||||
translateScalarInterpolator(set_data_from_node, action, ancestry, to_node, "rotation")
|
||||
elif to_type in {'set_orientation', 'rotation', "set_rotation"}:
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
to_node = defDict[to_id]
|
||||
if skeleton and skeleton.pose.bones.get(to_id):
|
||||
# print(f"Creating animation for joint {to_id}")
|
||||
translateBoneOrientationInterpolator(set_data_from_node, action, ancestry, to_id, skeleton)
|
||||
if not hasMesh:
|
||||
# print(f"Creating orientation animation for {to_id}")
|
||||
translateOrientationInterpolator(set_data_from_node, action, ancestry, to_node)
|
||||
|
||||
if to_type == 'set_scale':
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
translateScaleInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
if to_type == 'set_point':
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
translateCoordinateInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
elif from_type == 'bindTime':
|
||||
action = getIpo(from_id)
|
||||
time_node = defDict[to_id]
|
||||
translateTimeSensor(time_node, action, ancestry)
|
||||
|
||||
def importRoute(node, ancestry, skeleton=None, hasMesh=None):
|
||||
"""
|
||||
Animation route only at the moment
|
||||
"""
|
||||
|
||||
if node.getFieldAsString("fromNode", None, ancestry) and node.getFieldAsString("toNode", None, ancestry) and node.getFieldAsString("fromField", None, ancestry) and node.getFieldAsString("toField", None, ancestry):
|
||||
pass
|
||||
elif not hasattr(node, 'fields'):
|
||||
# print(f"return not hasattr fields")
|
||||
return
|
||||
|
||||
"""
|
||||
Handles routing nodes to each other
|
||||
|
||||
@ -3557,50 +3888,36 @@ ROUTE vpTs.fraction_changed TO vpOI.set_fraction
|
||||
ROUTE champFly001.bindTime TO vpTs.set_startTime
|
||||
"""
|
||||
|
||||
#from_id, from_type = node.id[1].split('.')
|
||||
#to_id, to_type = node.id[3].split('.')
|
||||
|
||||
#value_changed
|
||||
set_position_node = None
|
||||
set_orientation_node = None
|
||||
time_node = None
|
||||
|
||||
if len(node.fields) <= 0:
|
||||
from_id = node.getFieldAsString("fromNode", None, ancestry)
|
||||
from_type = node.getFieldAsString("fromField", None, ancestry)
|
||||
to_id = node.getFieldAsString("toNode", None, ancestry)
|
||||
to_type = node.getFieldAsString("toField", None, ancestry)
|
||||
if from_id and from_type and to_id and to_type:
|
||||
# print(f"ROUTE from {from_id}.{from_type} to {to_id}.{to_type}")
|
||||
importRouteFromTo(node, from_id, from_type, to_id, to_type, ancestry, skeleton, hasMesh)
|
||||
else:
|
||||
for field in node.fields:
|
||||
# print(f"return field {field}")
|
||||
if field and field[0] == 'ROUTE':
|
||||
try:
|
||||
from_id, from_type = field[1].split('.')
|
||||
to_id, to_type = field[3].split('.')
|
||||
except:
|
||||
print("Warning, invalid ROUTE", field)
|
||||
continue
|
||||
|
||||
if from_type == 'value_changed':
|
||||
if to_type == 'set_position':
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
translatePositionInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
if to_type in {'set_orientation', 'rotation'}:
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
translateOrientationInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
if to_type == 'set_scale':
|
||||
action = getIpo(to_id)
|
||||
set_data_from_node = defDict[from_id]
|
||||
translateScalarInterpolator(set_data_from_node, action, ancestry)
|
||||
|
||||
elif from_type == 'bindTime':
|
||||
action = getIpo(from_id)
|
||||
time_node = defDict[to_id]
|
||||
translateTimeSensor(time_node, action, ancestry)
|
||||
# print(f"ROUTE from {from_id}.{from_type} to {to_id}.{to_type}")
|
||||
importRouteFromTo(node, from_id, from_type, to_id, to_type, ancestry, skeleton, hasMesh)
|
||||
|
||||
def importSkinWeights(obj, joint, jointCoord, end):
|
||||
group = obj.vertex_groups.get(joint) or obj.vertex_groups.new(name=joint)
|
||||
# print(f"Created group {joint}")
|
||||
# print(f"Index {end} joint {joint}")
|
||||
for weight_index in range(len(jointCoord['skinCoordIndex'])):
|
||||
# print(f"Index {end} joint {joint} {jointCoord['skinCoordIndex'][weight_index]} weight {jointCoord['skinCoordWeight'][weight_index]}")
|
||||
group.add([jointCoord['skinCoordIndex'][weight_index]], jointCoord['skinCoordWeight'][weight_index], 'REPLACE')
|
||||
|
||||
def load_web3d(
|
||||
bpycontext,
|
||||
filepath,
|
||||
*,
|
||||
PREF_FLAT=False,
|
||||
PREF_FLAT=False, # So Tranforms will be imported
|
||||
PREF_CIRCLE_DIV=16,
|
||||
file_unit='M',
|
||||
global_scale=1.0,
|
||||
@ -3637,6 +3954,33 @@ def load_web3d(
|
||||
# fill with tuples - (node, [parents-parent, parent])
|
||||
all_nodes = root_node.getSerialized([], [])
|
||||
|
||||
all_shapes = []
|
||||
skeleton = None
|
||||
meshobj = None
|
||||
shape = None
|
||||
site = None
|
||||
displacers = {}
|
||||
skinCoord = None
|
||||
hAnimJoint = None
|
||||
hAnimSegment = None
|
||||
hAnimSite = None
|
||||
group = None
|
||||
|
||||
# collect shapes for sites
|
||||
for node, ancestry in all_nodes:
|
||||
Bujus_Krachus marked this conversation as resolved
Cedric Steiert
commented
dead code dead code
|
||||
spec = node.getSpec()
|
||||
|
||||
if spec.endswith('Shape'):
|
||||
shape = importShape(bpycollection, node, ancestry, global_matrix)
|
||||
if shape:
|
||||
if shape[1]:
|
||||
bpy.context.view_layer.objects.active = shape[1]
|
||||
shape.append(site)
|
||||
site = None
|
||||
all_shapes.append(shape)
|
||||
Bujus_Krachus marked this conversation as resolved
Cedric Steiert
commented
dead code & print needed above? dead code & print needed above?
|
||||
elif spec.endswith('HAnimSite'):
|
||||
site = node
|
||||
|
||||
for node, ancestry in all_nodes:
|
||||
#if 'castle.wrl' not in node.getFilename():
|
||||
# continue
|
||||
@ -3652,12 +3996,82 @@ def load_web3d(
|
||||
# Note, include this function so the VRML/X3D importer can be extended
|
||||
# by an external script. - gets first pick
|
||||
pass
|
||||
if spec == 'Shape':
|
||||
importShape(bpycollection, node, ancestry, global_matrix)
|
||||
elif spec in {'PointLight', 'DirectionalLight', 'SpotLight'}:
|
||||
if spec in {'PointLight', 'DirectionalLight', 'SpotLight'}:
|
||||
importLamp(bpycollection, node, spec, ancestry, global_matrix)
|
||||
elif spec == 'Viewpoint':
|
||||
importViewpoint(bpycollection, node, ancestry, global_matrix)
|
||||
elif spec == 'HAnimHumanoid':
|
||||
joints = []
|
||||
segments = []
|
||||
jointSkin = {}
|
||||
skeleton = importHAnimHumanoid(bpycollection, node, ancestry, global_matrix, joints, segments, jointSkin)
|
||||
skinCoord = node.getChildBySpec('Coordinate')
|
||||
if skinCoord:
|
||||
#if skinCoord.getFieldAsString("containerField", None, ancestry) == "skinCoord":
|
||||
if DEBUG:
|
||||
print(f"Skin coord is {skinCoord}")
|
||||
for shape in all_shapes:
|
||||
if shape:
|
||||
if DEBUG:
|
||||
print(f"Skin mesh is found")
|
||||
if shape[0] and shape[1] and shape[2] and skinCoord.getRealNode().getDefName() == shape[2].getRealNode().getDefName():
|
||||
if DEBUG:
|
||||
print("Got mesh obj")
|
||||
meshobj = shape[1]
|
||||
meshobj.modifiers.new(name='ArmatureToMesh', type='ARMATURE')
|
||||
meshobj.modifiers['ArmatureToMesh'].object = skeleton
|
||||
else:
|
||||
print(f"DEFs match? missing shape[:]? skinCoord.getRealNode().getDefName() == shape[2].getRealNode().getDefName()")
|
||||
else:
|
||||
print(f"no shape {shape} ? all shapes is {all_shapes}")
|
||||
|
||||
else:
|
||||
print("No skinCoord, no skin weights, no skin animation")
|
||||
|
||||
|
||||
if DEBUG:
|
||||
print(f"mesh is {meshobj}")
|
||||
#bpy.ops.object.mode_set(mode="EDIT")
|
||||
bpy.ops.object.mode_set(mode="OBJECT")
|
||||
imported = []
|
||||
if DEBUG:
|
||||
print(f"Number of segments {len(segments)}")
|
||||
for segment in segments:
|
||||
parent_joint, child_joint = segment
|
||||
# print(f"Segment {parent_joint} {child_joint} loading weights")
|
||||
if meshobj:
|
||||
if child_joint not in imported:
|
||||
importSkinWeights(meshobj, child_joint, jointSkin[child_joint], "child")
|
||||
imported.append(child_joint)
|
||||
if parent_joint not in imported:
|
||||
importSkinWeights(meshobj, parent_joint, jointSkin[parent_joint], "parent")
|
||||
imported.append(parent_joint)
|
||||
|
||||
elif spec in ('HAnimSegment'):
|
||||
child_segment_name = node.getDefName()
|
||||
hAnimSegment = importHAnimSegment(bpycollection, node, ancestry, global_matrix)
|
||||
attachMesh(all_shapes, child_segment_name, hAnimSegment) # mesh is in all_shapes
|
||||
hAnimSegment.parent = skeleton
|
||||
hAnimSegment.parent_bone = parent_joint_name
|
||||
hAnimSegment.parent_type = 'BONE'
|
||||
elif spec in ('HAnimDisplacer'):
|
||||
# TODO Intended to be implemented
|
||||
#if meshobj:
|
||||
#importHAnimDisplacer(node, ancestry, meshobj, displacers)
|
||||
pass
|
||||
elif spec in ('HAnimHumanoid'):
|
||||
humanoid_name = node.getDefName()
|
||||
hAnimHumanoid = importTransform(bpycollection, node, ancestry, global_matrix)
|
||||
elif spec in ('HAnimJoint'):
|
||||
parent_joint_name = node.getDefName()
|
||||
hAnimJoint = importTransform(bpycollection, node, ancestry, global_matrix)
|
||||
elif spec in ('Group'):
|
||||
group_name = node.getDefName()
|
||||
group = importTransform(bpycollection, node, ancestry, global_matrix)
|
||||
elif spec in ('HAnimSite'):
|
||||
site_name = node.getDefName()
|
||||
hAnimSite = importTransform(bpycollection, node, ancestry, global_matrix)
|
||||
attachMesh(all_shapes, site_name, hAnimSite) # mesh is in all_shapes
|
||||
elif spec == 'Transform':
|
||||
# Only use transform nodes when we are not importing a flat object hierarchy
|
||||
if PREF_FLAT == False:
|
||||
@ -3666,13 +4080,20 @@ def load_web3d(
|
||||
# These are delt with later within importRoute
|
||||
elif spec=='PositionInterpolator':
|
||||
action = bpy.data.ipos.new('web3d_ipo', 'Object')
|
||||
translatePositionInterpolator(node, action)
|
||||
translatePositionInterpolator(node, action, ancestry)
|
||||
'''
|
||||
|
||||
# After we import all nodes, route events - anim paths
|
||||
if skeleton:
|
||||
bpy.ops.object.mode_set(mode='POSE')
|
||||
for node, ancestry in all_nodes:
|
||||
importRoute(node, ancestry)
|
||||
importRoute(node, ancestry, skeleton, meshobj)
|
||||
bpy.context.scene.frame_set(0)
|
||||
|
||||
if not skeleton and shape is not None and shape[1]:
|
||||
bpy.context.view_layer.objects.active = shape[1]
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
for node, ancestry in all_nodes:
|
||||
if node.isRoot():
|
||||
# we know that all nodes referenced from will be in
|
||||
@ -3684,16 +4105,42 @@ def load_web3d(
|
||||
|
||||
# Assign anim curves
|
||||
node = defDict[key]
|
||||
# print(f"key {key} action {action} node {node}")
|
||||
bone = None
|
||||
if skeleton:
|
||||
if key in skeleton.pose.bones:
|
||||
bone = skeleton.pose.bones[key]
|
||||
else:
|
||||
print(f"There's no pose bone associated with key {key}, probably using a regular interpolator")
|
||||
else:
|
||||
print(f"There's no skeleton")
|
||||
if node.blendData is None: # Add an object if we need one for animation
|
||||
bpyob = node.blendData = node.blendObject = bpy.data.objects.new('AnimOb', None) # , name)
|
||||
bpycollection.objects.link(bpyob)
|
||||
bpyob.select_set(True)
|
||||
# print(f"Adding some blendData to the node for {key}. Did you forget to add it?")
|
||||
node.blendData = node.blendObject = bpy.data.objects.new(key, None)
|
||||
bpycollection.objects.link(node.blendObject)
|
||||
node.blendObject.select_set(True)
|
||||
|
||||
if node.blendData.animation_data is None:
|
||||
if hasattr(node.blendData, "animation_data"):
|
||||
if not node.blendData.animation_data:
|
||||
#print(f"Adding animation data for {node.blendData.name} 2 ")
|
||||
node.blendData.animation_data_create()
|
||||
|
||||
else:
|
||||
#print(f"Node {node.blendData.name} has animation_data")
|
||||
pass
|
||||
if not node.blendData.animation_data.action:
|
||||
#print(f"Setting an action {node.blendData.name}")
|
||||
node.blendData.animation_data.action = action
|
||||
|
||||
else:
|
||||
# print(f"Node {node.blendData.name} has actionnode. {node.blendData.animation_data.action}")
|
||||
pass
|
||||
# to disable NLA, comment out these 3 lines
|
||||
# print(f"Adding an nla_track to {node.blendData.name} {key}")
|
||||
track = node.blendData.animation_data.nla_tracks.new()
|
||||
track.name = "NLATRACK "+key
|
||||
node.blendData.animation_data.nla_tracks[track.name].strips.new(name=key, start=0, action=bpy.data.actions[key])
|
||||
else:
|
||||
# print(f"Node {node.blendData.name} has blendData, but no animation_data")
|
||||
pass
|
||||
# Add in hierarchy
|
||||
if PREF_FLAT is False:
|
||||
child_dict = {}
|
||||
@ -3721,13 +4168,36 @@ def load_web3d(
|
||||
|
||||
# Parent
|
||||
for parent, children in child_dict.items():
|
||||
if parent and children:
|
||||
for c in children:
|
||||
if c:
|
||||
if type(c) == type(parent):
|
||||
c.parent = parent
|
||||
else:
|
||||
if isinstance(c, bpy.types.EditBone):
|
||||
if DEBUG:
|
||||
print(f"Child is EditBone")
|
||||
if isinstance(parent, bpy.types.EditBone):
|
||||
if DEBUG:
|
||||
print(f"Parent is EditBone")
|
||||
c.parent = skeleton # Armature object
|
||||
print(f"Can't handle parent-child relationship, child {c} type {type(c)}, parent {parent} type {type(parent)}")
|
||||
else:
|
||||
print("Not a child")
|
||||
else:
|
||||
print("Children or parent may be None")
|
||||
|
||||
# update deps
|
||||
bpycontext.view_layer.update()
|
||||
del child_dict
|
||||
|
||||
def attachMesh(all_shapes, parent_name, parent_obj):
|
||||
# print(f"Found parent {parent_name}")
|
||||
for shape in all_shapes:
|
||||
if shape and shape[0] and shape[1] and shape[2] and shape[3]:
|
||||
if shape[3].getDefName() and parent_name == shape[3].getDefName(): # shape[3] is shape's parent node
|
||||
meshobj = shape[1]
|
||||
meshobj.parent = parent_obj
|
||||
|
||||
def load_with_profiler(
|
||||
context,
|
||||
@ -3756,7 +4226,7 @@ def load(context,
|
||||
|
||||
# loadWithProfiler(operator, context, filepath, global_matrix)
|
||||
load_web3d(context, filepath,
|
||||
PREF_FLAT=True,
|
||||
PREF_FLAT=False, # So Tranforms will be imported
|
||||
PREF_CIRCLE_DIV=16,
|
||||
global_scale=global_scale,
|
||||
global_matrix=global_matrix,
|
||||
|
Loading…
Reference in New Issue
Block a user
like above could get simplified using
or