WIP: X3D HAnim with Blender bones (no animation yet present) #23
No reviewers
Labels
No Label
bug
confirmed
duplicate
enhancement
help wanted
High
known issue
Normal
not a task
question
task
v2.4
wontfix
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: extensions/io_scene_x3d#23
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "yottzumm/io_scene_x3d:main"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I'm hoping to share this to let others test loading HAnim into Blender (no animation yet). There may be animation with VRML, just to be wary. The difference between the two models is JinLOA4.x3d has the skin with +Z up, and the skeleton with +Y up. JoeKick.x3d has both skin and skeleton with +Y up
Here's what I used to import:
bpy.ops.import_scene.x3d(filepath="JoeKick.x3d", axis_forward='Y', axis_up='Z')
bpy.ops.import_scene.x3d(filepath="JinLOA4.x3d", axis_forward='Z', axis_up='Y')
When I alter JinLOA4.x3 to match JoeKick.x3d, then the skin/skeleton match up, I'm thinking there's a bug in my code.
This is the "correct" code, which put them with head towards positive Y:
importJin.py:bpy.ops.import_scene.x3d(filepath="JinLOA4.x3d", axis_forward='Y', axis_up='Z')
importJoe.py:bpy.ops.import_scene.x3d(filepath="JoeKick.x3d", axis_forward='Y', axis_up='Z')
WIP: X3D HAnim with Blender bones (no animation yet present)to X3D HAnim with Blender bones (no animation yet present)Animation of Joints and Skins has reached a state where it can be somewhat merged. I am concerned about skin deformation compared to
npx sunrize `pwd`/JoeKick.x3d
I am attaching better models to test animation with.Here's gramps UV stuff.
here are some test scripts.
Thanks for the PR, will test and review once i find sufficent time, probably next week; currently a bit (actually quite a lot) behind with work and studies due to vacation last week...
Could you until then either remove the commented out code or add a explaining comment on why it's commented/when it should get used? The commented print statements can be left in, as they are useful for understanding & debugging; however the currently still active ones should get revisited and cleaned up where possible (currently the console get's spammed which a) hurts performance and b) creates unnecessary noise to the actual important user facing prints; if those are needed for debugging wrap them into e.g. if debug).
Just as a good mesaure also explain in the PR message what the PR does, what it doesn't and what still needs to be done in that area of code, include explanation of current state and new state (after PR got applied). Sentences like "I'm hoping to share this to let others test loading HAnim into Blender (no animation yet). There may be animation with VRML" are a bit confusing to read as there is no real statement behind it?
If the HAnim is still heavily WIP we could introduce a dev branch for it, to keep PRs simple and not break the releases. Othewise either fix below listed issues or include a toggle in the importer "HAnim: Experimental - Import HAnim anitmation" to easily switch before/after.
From a quick test with attached JoeKick.x3d file with blender 4.2.1 i think this PR should still be marked WIP, as i don't think it should look like this? Did i maybe miss a step? I simply selected the JoeKick file and imported into blender 4.2.1.
Before (without PR > no animation, but also no weird transformation atrifacts):
After (with PR > slow animations, but with weird transformation artifacts):
Even with your suggested change of forward axis (why is this even happening) the animation looks more than wonky (i don't know i a) the displayed distortion should happen and b) the position flip happening at frame 63 is correct):
Also the JinLOA file looks a bit different (mainly in scale from the first looks, is this expected?):
Before (big, no animations):
After (small, no animations):
I dunno about gramps, i think granny's soup was a bit to salty:
Btw never seen such a robotic dog sceleton for human rigs:
Is there any reliable other free 3d software i could test such files against? Probably sunrize as suggested by your posted code fragment; any other as well?
Btw i just encountered a new error introduced by this PR when loading a animated sample file from xite (https://create3000.github.io/x_ite/), i'm sure other animated files are affected as well:
Please retest the thrown exception
I think you're onto something with the robotic dog thing. I will look into it. I was getting something like the humanoid_root joint wouldn't be create if the bone was zero length.
here's Joe with no ROUTEs. Everything's good until ROUTEs are added, which work with Interpolators. Since, AFAIK, there's been no animation in the io_scene_x3d, a lot of careful thought needs to be put into how animation is going to affect the model when it's loaded. Perhaps the animation should start a small fraction into the timeline?
About the groups file (the exception which now is resolved), i now notice a slight import difference in amount of objects imported.
Before:
cubes get imported as objects with position
After:
cubes get imported and get partially parented to USE_Cubes transform empties, however half of them don't and transform empties 001-036 have no child objects
Tbh i have no idea which one is more correct at the current state, the after is probably more plausable however quite messy (the empty empties and offsets) and unusable inside blender
About the other stuff:
JoeSkinTexcoordDisplacerKickUpdate2NoROUTE.x3d
has no animations due to no routes, right?Yeah i believe only object/mesh import/export. Animations and interactive stuff is tricky, as blender doesn't really support the x3d/vrml features natively. I believe that's why the OCs (mainly Campbell Barton) only did mesh IO.
speaking at least for the joe model, some of the skin stretching issues arise due to wrong weight painting.
E.g. at the right leg you can see, that the position of the bones don't match with the affection area of the mesh:
I don't see the use of it.
Hi John,
i've added some code style rev comments where i think it's necessary. Can't speak for the technical topics though, as it would take me too much time right now to do a deep dive into X3D animation and HAanim combined with blenders py api.
Most of the comments are about dead code (commented out fragments as those ceate noise and get outdated easily), print statements and minor code simplifications.
Additionally it would be great to see some detailed doc strings at the beginning of the new/modified functions and some more regular code comments, especially in the denser code parts.
I think the current issues and limitations of the PR at current state are known, mainly stuff concering
Those issues could get tackled directly or the feature marked as experimental (requiring a toggle checkbox for HAnim in the import operators window) and merged directly in order to get it into the next release (planned around mid november for blender 4.3 release). Though a bit more stable/reliable would be great.
Btw nice to see someone adding animation capabilities to the extension ;)
@ -18,6 +18,7 @@ material_cache = {}
conversion_scale = 1.0
EPSILON = 0.0000001 # Very crude.
PREF_TIME_MULT = 250
Ideally be a tad more concise, maybe something along the lines like
TIME_MULTIPLIER
@ -359,6 +360,9 @@ class vrmlNode(object):
'node_type',
'parent',
'children',
'skeleton', # no joints, segments or sites. TODO
Usually todos follow this pattern:
TODO: i want to be awesome!
@ -360,2 +361,4 @@
'parent',
'children',
'skeleton', # no joints, segments or sites. TODO
'skinCoord',
snake_case:
skin_coord
@ -1598,1 +1601,3 @@
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'):
#transform_nodes = [node_tx for node_tx in ancestry if node_tx.getSpec() == 'Transform']
dead code, either remove the commented out code pieces or explain why it's currently not used
@ -2851,2 +2857,4 @@
for f in ima_urls:
dirname = os.path.dirname(node.getFilename())
if f.startswith('"'):
f = f[1:-1] # strip quotes
simplify to
f = f.lstrip('"')
, the extra operation of checking first is not needed nor recommended@ -3277,0 +3295,4 @@
# print(vrmlname)
prefix = ''
if vrmlname:
fus = vrmlname.find('_')
what does fus stand for?
@ -3277,0 +3321,4 @@
# Process children joints, including USE, if present
child = node.getChildBySpec('HAnimJoint') # 'HAnimJoint'
if child:
# first_joint_name = child.getFieldAsString('name', None, ancestry)
dead code
@ -3277,0 +3323,4 @@
if child:
# first_joint_name = child.getFieldAsString('name', None, ancestry)
first_joint_name = child.getDefName()
if not first_joint_name:
i assume this triggers if
first_joint_name
isNone
, then simplify tofirst_joint_name = child.getDefName() or child.getFieldAsString('name', None, ancestry)
@ -3277,0 +3326,4 @@
if not first_joint_name:
first_joint_name = child.getFieldAsString('name', None, ancestry)
joint_center = child.getFieldAsFloatTuple('center', None, ancestry)
#if joint_center is None:
dead code
@ -3277,0 +3328,4 @@
joint_center = child.getFieldAsFloatTuple('center', None, ancestry)
#if joint_center is None:
joint_center = (0, 0, 0) # may delete joint at 0 0 0
print(f"Joint {first_joint_name} {joint_center}")
print needed?
@ -3277,0 +3333,4 @@
# Create bones for each joint
for joint_name, joint_start, joint_end, skinCoordWeight, skinCoordIndex in joints:
print(f"Creating {joint_name} {joint_start} {joint_end}")
print needed?
@ -3277,0 +3337,4 @@
# print(f"Joint {joint_name} {joint_start} {joint_end}")
if not joint_name:
joint_name = vrmlname
# bpy.ops.armature.bone_primitive_add(name=joint_name)
dead code
@ -3277,0 +3339,4 @@
joint_name = vrmlname
# bpy.ops.armature.bone_primitive_add(name=joint_name)
new_segment = armature_data.edit_bones.new(joint_name)
# new_segment = skeleton.data.edit_bones[joint_name]
dead code
@ -3277,0 +3346,4 @@
new_segment.head = joint_end
new_segment.tail = joint_start
# bpy.context.view_layer.objects.active = skeleton
dead code
@ -3277,0 +3355,4 @@
# pose_bone.location = mathutils.Vector(new_segment.head)
#else:
# print(f"There's no pose bone associated with {joint_name}")
if joint_name != vrmlname:
this check could be saved if the code gets moved into the else of line 3338, where the check
if not joint_name:
happens@ -3277,0 +3377,4 @@
print("Couldn't find child HAnimJoint")
# getFinalMatrix(node, None, ancestry, global_matrix)
dead code
@ -3277,0 +3387,4 @@
child_bone_name = child.getDefName()
if not child_bone_name:
child_bone_name = child.getFieldAsString('name', None, ancestry)
if not child_bone_name:
like above could get simplified using
or
@ -3277,0 +3398,4 @@
child_bone_name = child.getDefName()
if not child_bone_name:
child_bone_name = child.getFieldAsString('name', None, ancestry)
if not child_bone_name:
like above could get simplified using
or
@ -3277,0 +3407,4 @@
skinCoordWeight = ()
if skinCoordIndex is None:
skinCoordIndex = ()
better move the nl after the validation section
@ -3277,0 +3410,4 @@
if not child_center:
child_center = [0, 0, 0]
joints.append((child_bone_name, (child_center[0], child_center[1], child_center[2]), (parent_center[0], parent_center[1], parent_center[2]), skinCoordWeight, skinCoordIndex))
is unpacking needed or just the conversion to tuple?
joints.append((child_bone_name, tuple(child_center), tuple(parent_center), skinCoordWeight, skinCoordIndex))
@ -3422,1 +3569,4 @@
def importHAnimSegment(bpycollection, node, ancestry, global_matrix):
name = node.getDefName()
if not name:
like above with
or
@ -3423,0 +3578,4 @@
bpyob.matrix_world = getFinalMatrix(node, None, ancestry, global_matrix)
# so they are not too annoying
yeah, humans can be pretty annoying ^^
@ -3437,26 +3599,59 @@ 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)
print (f"key {key} keyValue {keyValue} {action}")
print needed?
@ -3441,3 +3604,4 @@
for i, time in enumerate(key):
try:
x, y, z = keyValue[i]
print (f"i {i} x {x} y {y} z {z}")
print needed?
@ -3450,0 +3620,4 @@
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
print(f"ci {offset} = {len(keyValue)} / {len(key)}")
print needed?
@ -3450,0 +3640,4 @@
loc_y.keyframe_points.insert(time*PREF_TIME_MULT, y)
loc_z.keyframe_points.insert(time*PREF_TIME_MULT, z)
curoff = curoff + 3
curoff += 3
@ -3479,0 +3679,4 @@
pose_bone = None
if skeleton:
pose_bone = skeleton.pose.bones.get(to_id)
if pose_bone:
could get indented further, as
pose_bone
is None if ``skeleton` is None@ -3500,0 +3714,4 @@
for i, time in enumerate(key):
try:
s = keyValue[i]
except:
which errors are expected? Currently it's a wildcard, thus ideally provide the exception type
@ -3550,0 +3767,4 @@
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]
print(f"Trying to create a position interpolator for something from {from_id} to {to_id} (may need something special?)")
print needed?
@ -3597,0 +3839,4 @@
to_id, to_type = field[3].split('.')
# 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)
except:
any specific error to be expected?
@ -3597,1 +3845,4 @@
def importSkinWeights(obj, joint, jointCoord, end):
group = obj.vertex_groups.get(joint)
if group is None:
could be simplified using
or
@ -3598,0 +3848,4 @@
if group is None:
group = obj.vertex_groups.new(name=joint)
# print(f"Created group {joint}")
print(f"Index {end} joint {joint}")
print needed?
@ -3598,0 +3849,4 @@
group = obj.vertex_groups.new(name=joint)
# print(f"Created group {joint}")
print(f"Index {end} joint {joint}")
for wi in range(len(jointCoord['skinCoordIndex'])):
wi?
@ -3657,1 +3939,3 @@
elif spec in {'PointLight', 'DirectionalLight', 'SpotLight'}:
#if spec == 'Shape':
# importShape(bpycollection, node, ancestry, global_matrix)
#el
dead code
@ -3661,0 +3948,4 @@
segments = []
jointSkin = {}
skeleton = importHAnimHumanoid(bpycollection, node, ancestry, global_matrix, joints, segments, jointSkin)
# skinCoord = node.getChildByName('skinCoord') # 'Coordinate'
dead code
@ -3661,0 +3952,4 @@
skinCoord = node.getChildBySpec('Coordinate')
if skinCoord:
#if skinCoord.getFieldAsString("containerField", None, ancestry) == "skinCoord":
print(f"Skin coord is {skinCoord}")
dead code & print needed?
@ -3661,0 +3955,4 @@
print(f"Skin coord is {skinCoord}")
for shape in all_shapes:
if shape:
print(f"Skin mesh is found")
print needed?
@ -3661,0 +3967,4 @@
print(f"no shape {shape} ? all shapes is {all_shapes}")
#else:
# print(f"Couldn't get containerField {skinCoord}")
dead code
@ -3661,0 +3977,4 @@
bpy.ops.object.mode_set(mode="OBJECT")
#bmesh.from_edit_mesh(meshobj.data)
imported = []
print(f"Number of segments {len(segments)}")
dead code & print needed above?
@ -3661,0 +3981,4 @@
for segment in segments:
parent_joint, child_joint = segment
# print(f"Segment {parent_joint} {child_joint} loading weights")
if meshobj and child_joint not in imported:
more readable and a extra unnecessary check gets saved:
@ -3661,0 +3996,4 @@
hAnimSegment.parent_bone = parent_joint_name
hAnimSegment.parent_type = 'BONE'
elif spec in ('HAnimDisplacer'):
#if meshobj:
dead code
@ -3687,0 +4048,4 @@
# print(f"key {key} action {action} node {node}")
bone = None
if skeleton:
if skeleton and key in skeleton.pose.bones:
second check of skeleton is not needed, as it's above already
@ -3696,0 +4060,4 @@
bpycollection.objects.link(node.blendObject)
node.blendObject.select_set(True)
# print(f"Adding some animation data for {key}?")
# node.blendData.animation_data_create()
dead code
@ -3757,3 +4172,3 @@
# loadWithProfiler(operator, context, filepath, global_matrix)
load_web3d(context, filepath,
PREF_FLAT=True,
PREF_FLAT=False,
what's the effect, why is this change needed?
I have pushed changes per review. I still need to check gramps and test other files. I don't know how to cherry-pick (I failed miserably) so I've included a couple of changes for adding colors to LineSet (maybe only IndexedLineSets). It's in my other branch, and I accidentally made the changes for the pull request in my other branch. Duh!. Feel free to exclude, but they should be commented out currently.
John
Also, about JinLOA4.x3d's scaling. Check out the scale attribute on HAnimHumanoid. I think that's what's going on.
Alright, thanks. Simply let me know when the PR is ready to merge from your side. Until then i'll leave you to it.
Side note: Using the WIP-Prefix automatically let's me know if the PR is still being worked on or finished ;)
X3D HAnim with Blender bones (no animation yet present)to WIP: X3D HAnim with Blender bones (no animation yet present)Checkout
From your project repository, check out a new branch and test the changes.