-
Notifications
You must be signed in to change notification settings - Fork 17
Armatures and skinned objects #436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a lot for me to wrap my head around. I think my major concern here is that I'm not sure that I like how the temporary bone empties are being handled. I worked pretty hard to try to establish a paradigm where temporary objects are yield
ed to the exporter for lifetime management, and I'd like to see that continued.
You may want to submit the bugfixes in a separate PR so we can get them merged quickly.
import functools | ||
import itertools | ||
import math | ||
import mathutils | ||
from typing import * | ||
import weakref | ||
import re |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be between mathutils
and typing
.
|
||
class AnimationConverter: | ||
def __init__(self, exporter): | ||
self._exporter = weakref.ref(exporter) | ||
self._bl_fps = bpy.context.scene.render.fps | ||
self._bone_data_path_regex = re.compile('^pose\\.bones\\["(.*)"]\\.(.*)$') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self._bone_data_path_regex = re.compile('^pose\\.bones\\["(.*)"]\\.(.*)$') | |
self._bone_data_path_regex = re.compile("^pose\\.bones\\["(.*)"]\\.(.*)$") |
toggle.track(anim, "bake", False) | ||
toggle.track(anim_group, "enabled", True) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do these changes need to be tracked and reset after the entire export process, or could we get away with only tracking them in this function?
handle_temporary(toggle) | ||
handle_temporary(exit_stack) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I like this idea, to be honest.
|
||
# Copy animation data. | ||
anim_data = bone.animation_data_create() | ||
action = bpy.data.actions.new("{}_action".format(bone.name)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
action = bpy.data.actions.new("{}_action".format(bone.name)) | |
action = bpy.data.actions.new(f"{bone.name}_action") |
geospan.format |= plGeometrySpan.kSkin3Weights | plGeometrySpan.kSkinIndices | ||
elif max_deform_bones == 2: | ||
geospan.format |= plGeometrySpan.kSkin2Weights | plGeometrySpan.kSkinIndices | ||
else: # max_bones_per_vert == 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's do this explicitly and error if there's an unexpected value.
# No skin indices required... BUT! We have assigned some weight to the null bone on top of the only bone, so we need to fix that. | ||
for vtx in data.vertices: | ||
weight = vtx.weights[0] | ||
weight = 1 - weight |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weight = 1 - weight | |
weight = 1.0 - weight |
@@ -522,6 +559,11 @@ def _(temporary, parent): | |||
return temporary | |||
|
|||
def do_pre_export(bo): | |||
if bo.type == "ARMATURE": |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels a little out of place, but I haven't wrapped my head around what's going on well enough to say what should change.
@@ -522,6 +559,11 @@ def _(temporary, parent): | |||
return temporary | |||
|
|||
def do_pre_export(bo): | |||
if bo.type == "ARMATURE": | |||
so = self.mgr.find_create_object(plSceneObject, bl=bo) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize we can't do everything in one go, but I am thinking toward the future where we can (hopefully) export avatar animations from Korman, and IIRC avatar animation PRPs don't have a scene object in them at all.
so = self.mgr.find_create_object(plSceneObject, bl=bo) | ||
self._export_actor(so, bo) | ||
# Bake all armature bones to empties - this will make it easier to export animations and such. | ||
self.armature.convert_armature_to_empties(bo, lambda obj: handle_temporary(obj, bo)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is where we need to be generator aware, instead of passing down this lambda.
Thanks for the extensive feedback ! Yeah, the priority was to make the PR before the start of the contest, I expected a lot would require improvements. I'll clean this all up once the contest is out of the way... |
Alright, this is a big one. I probably won't have a lot of time to implement suggestions until the RAD is over, so feel free to take your time reviewing. I also didn't check how optimized the code is (especially regarding Python generators), suggestions are welcome.
The first thing that needs to be mentioned: I decided to let Korman generate a LOT of temporary Blender objects during export. This may feel messy, but I've rewritten the code several times and AFAICT is still the best solution. Temporary objects should all get cleaned up nicely whatever happens.
Second, there are still two small problems, that I'll get around to later:
Armatures
SceneObjects
.SceneObjects
. Bone hierarchy is respected.SceneObjects
are: the rest position of the bone, and the actual deform bone. For instance, the following bone hierarchy:SceneObjects
per bone avoids a whole slew of problems when exporting animations. Otherwise we'd be running in the same problem as an object having amatrix_parent_inverse
.Animations
plMsgForwarder
.plATCAnim
per bone, per animation. Ideally we would group all bones in a singleplATCAnim
, but in my tests this doesn't work - seems it's only available to special objects like avatars.Rigged/deformed meshes
CoordinateInterface
, ever. Since people usually parent the rigged mesh to the armature itself, Korman will simply ignore that relationship on export.DrawableSpans
work, this would require duplicating the bones for each object, so screw that.Other
Backwards compatibility: completely new feature so no problem expected unless I messed up. It does fix a bug with
TemporaryCollectionItem
though.And here is a test file so you can see it in action.