2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,70 @@
[3.73.41]
New/improved:
Compatibility: Blender 4.3: FBX export
Export: New "Actions Linker" menu, to link an action to another one. Useful if exporting RigA animation, that depends on RigB through constraints (such as two characters holding each other's hands).\nIf animations are interdependent, it is necessary to declare these links for correct animation export.
Export: New "One File per Actions List" setting, when enabling both "As Multiple Files" and "Actions Manager". This allows to export one file per Action List, including all actions in the list.
Export: Godot: New setting to comply with Godot Root Motion axes
Export Godot: Removed the "Root Motion" setting that bakes the c_traj animation to armature object, not relevant for Godot Root Motion.
Export: Actions Lists: Filtered actions in the actions selector, so that actions that are already part of the current list are removed from the search list
Export: GLTF: Exposed the "Start from frame 0" setting, in order to force the first frame of each action to be zero if enabled
Export: UE: Removed the extra orientation tweaks applied to the spine bones when exporting with Mannequin axes. It was offsetting the angles by a few degrees to improve the match in case of straight spine bones, but can be misleading. It is best to align the spine bones manually. It can still be enabled in the "Legacy" menu though.
Export: Actions Manager: New "Batch" feature to add multiple actions at once to a list, with filtering
Export: UI: Added the "Rename Bones from File" checkbox in the properties panel too. Was confusing to have it only in the menu located in file export browser
Export: Avoid operator when removing shape keys for faster performances
Export: FBX: New "Force Shape Keys Keying" setting. If disabled (default), non-driven or non-animated shape keys are now exported as static, without animation data
Rig: Spline IK: Changed "Parent External Bone to" setting, was limited to the tip bone before. Now applies to all bones of the chain, and to Deforming or Twist bones instead of controllers. Can still be parented to controllers if necessary, by directly parenting Spline reference bones to them.
Rig: Spline IK: New "Add Tail Bone" option. This adds an extra bone at the tip of the chain, that is not part of the main IK chain. Useful when needing to parent other limb to the tip, that must not be stretched or rotated with the main chain, such as rigging a spine for quadrupeds.
Rig: Spline IK: New "Preserve Shape" option, to preserve the current shape when changing the amount of the spline bones.
Rig: Spine: New "Preserve Shape" option, to preserve the current spine shape when changing the amount of bones
Rig: Leg: New Limb Option to skip the foot IK offset alignment when Match to Rig, allowing custom positioning
Rig: Spine: New "Parent Fallback" parameter in Limb Options, to set the default spine parent in case the root reference bone is not parented. By default parented to "c_traj". No parent if set to None.
Rig: Tail: New "Preserve Shape" option, to preserve the current shape when changing the amount of the tail bones
Rig: Spine, Tail, Spline IK: Removed "Update Transform" option, was confusing. Now, automatically update the bones transforms if the count was changed, using grid align or preserve shape if enabled.
Rig: New Bulge setting for arm Joint Fans, allowing to add extra bulge deformation when the fans are spreading/folding. The created constraint can be manually edited afterwards.
Rig: Apply Pose as Rest Pose: New "Apply Deformed Shape Keys" toggle, can be useful to speed up baking time in case deformations are not affecting shape keys
Rig: New Bulge setting for leg Joints Fans too
Rig: New Ankle Joints Fans
Rig: Leg: New "Foot Roll Break" option. If enabled, the foot will start rotating from the ball, then from the tip toes, when moving the foot roll cursor. Settings can be found in "Rig Main Properties".
Skin: Warning message in case of negative mesh scale when binding
Picker: The picker camera is now set automatically when clicking "Add Picker"
Picker: Support reverse spine bones
Rig Tools: New "Extract Root Motion" function that tracks and bakes the "c_traj" controller to the pelvis position, while maintaining it on the floor, and preserving the rig animation
Rig Tools: Extract Root Motion now supports Location Z with initial offset applied
Rig Tools: Improved Extract Root Motion performances, around 4x faster (before: 24 sec for 400 frames, after: 6 sec)
Rig Tools: Extract Root Motion: Added X and Y offsets
Remap: Added "Extract Root Motion" as an option when retargetting
Smart: Facial features are now togglable: Eyebrows, Eyes, Ears, Nose, Lips, Tongue, Teeth, Chin
Smart: Auto filter mesh objects when clicking "Get Selected Objects". This avoids throwing an error if non-mesh objects are selected.
Smart: UI: Moved the "Fingers" checkbox in the header of the Fingers tab to minimize redundancy
Fixed:
Rig: The horse rig was containing unwanted actions data leftovers
Rig: Removed the "body_mid" collections coming from old armature presets
Rig: Facial: Error when disabling the mouth bones
Rig: Bottom bones had accidentally their Deform property turned off
Rig: Apply Pose as Rest Pose crashing in case of numerous shape keys
Rig: The Tail limb could have incorrect parent. No uses the default parent_retarget function
Rig: The Dog preset had a "Connected" second spine bone, leading ot issue
Rig: Thigh Joint Fans incorrectly parented to 3 Bones Leg Type 2
Rig: Custom shapes of reversed spine controllers could be offset
Rig: Kilt: When changing the amount of bones, the Z axis could be incorrectly oriented. Now calculates the curve normal vectors to align Z axes on.
Rig: Changed the arm, leg Stretch-Length minimum value to 0.1
Rig: Fixed the new Ankle Joint Fans bulge, was not evaluating the foot angle correctly, new helper foot bone added
Rig: Apply Pose as Rest Pose: Arm and leg stretch controller was not supported
Rig: Error when setting up secondary bones color group in pre-Blender 4 versions
Rig: Disabled "Extrapolate" from the c_foot_heel bone and cleared out of range mapping to avoid accidental rotation flip
Skin: Error when restoring current vertices weights if a temp object was deleted (Apply Shape Keys)
Export: Skinned objects having same name as export skeleton were clashing. It now throws a warning before exporting.
Export: Add batch actions to Action Manager could generate an error if an action was removed from file
Export: GLTF: Disabled the "export_apply" argument from the actual export, was preventing shape keys to export. Modifiers are pre-applied by AutoRigPro functions anyway, so no changes on the user side.
Export: Error when exporting actions list containing removed actions
Export: The exported skeleton was incorrectly named with an "_arpbob" suffix when the rig export name was the same as the armature name
Export: Apply Shape Keys was failing if the basis shape key was not named "Basis". Now, always refer to the shape key at index 0 as Basis
Export: Humanoid: Invalid fingers export when exporting more than 2 arms
Export: Shape keys could not apply and export correctly under certain circumstances, was missing "from_mix" set to false
Export: GLTF: Apply Modifiers was failing
Export: Displace modifiers were not scaled when exporting with Units x100
Rig Tools: Extract Root Motion quat_prev error
Smart: The arm/leg reference bones roll was not calculated. Was expecting the Match to Rig function to do it, but now that "Auto IK Roll" can be disabled, the roll must be aligned
Remap: Added further checks to evaluate if the source and target armature are really Armature objects, to avoid user error
@@ -0,0 +1,6 @@
Auto-Rig Pro is released under the terms of multiple licenses:
The add-on source code components of Auto-Rig Pro are released under the terms of the GNU General Public License, version 3.
Assets files such as icon image components, and rig components in .blend format, are released under the terms of the Royalty Free license and CC0.
Refer to the LICENSE file in the subfolders for more details.
NOTE: in any case, a rig generated with Auto-Rig Pro by the end-user (such as in the process of rigging a character for example), is the sole property of the end-user who has full rights on it.
@@ -0,0 +1,161 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ***** END GPL LICENCE BLOCK *****
import os, shutil, bpy
bl_info = {
"name": "Auto-Rig Pro",
"author": "Artell",
"version": (3, 73, 41),
"blender": (4, 2, 0),
"location": "3D View > Properties> Auto-Rig Pro",
"description": "Automatic rig generation based on reference bones and various tools",
"tracker_url": "http://lucky3d.fr/auto-rig-pro/doc/bug_report.html",
"doc_url": "http://lucky3d.fr/auto-rig-pro/doc/",
"category": "Rigging",
}
import bpy
from bpy.app.handlers import persistent
from .src import auto_rig_prefs
from .src import rig_functions
from .src import auto_rig
from .src import auto_rig_smart
from .src import auto_rig_remap
from .src import auto_rig_ge
if bpy.app.version >= (4,1,0):
from .src.export_fbx import arp_fbx_init
else:
from .src.export_fbx_old import arp_fbx_init
from .src import utils
# gltf export specials
class glTF2ExportUserExtension:
export_action_only = ''
def __init__(self):
self.action = None
def gather_actions_hook(self, blender_object, params, export_settings):
# Filter actions
# Only filter ARP rigs
if not 'arp_rig_name' in blender_object:
return
# convert string list with fancy separators to list
export_actions_names = []
sep = '|%%|'
if sep in self.export_action_only:
for actname in self.export_action_only.split(sep):
export_actions_names.append(actname)
if len(export_actions_names):
print('Actions:', export_actions_names)
# collection actions
act_list = []
for act in params.blender_actions:
if len(act.keys()):
if "arp_baked_action" in act.keys():
if self.export_action_only == 'all_actions':# all
act_list.append(act)
elif self.export_action_only == act.name:# single action export
act_list.append(act)
elif len(export_actions_names):# actions list export
if act.name in export_actions_names:
act_list.append(act)
params.blender_actions = act_list
params.blender_tracks = {k:v for (k, v) in params.blender_tracks.items() if k in [act.name for act in params.blender_actions]}
params.action_on_type = {k:v for (k, v) in params.action_on_type.items() if k in [act.name for act in params.blender_actions]}
def animation_switch_loop_hook(self, blender_object, post, export_settings):
# Before looping on actions to export
# Store used action of original rig
if 'arp_rig_name' in blender_object and post is False:
original_rig = bpy.data.objects[blender_object['arp_rig_name']]
if original_rig.animation_data and original_rig.animation_data.action:
self.action = original_rig.animation_data.action
# Restore initial action of the original rig
# After looping on actions to export
if 'arp_rig_name' in blender_object and post is True:
original_rig = bpy.data.objects[blender_object['arp_rig_name']]
if original_rig.animation_data:
original_rig.animation_data.action = self.action
self.action = None
def post_animation_switch_hook(self, blender_object, blender_action, track_name, on_type, export_settings):
# When switching the exported rig, also switch the original rig (same action + "_%temp")
if 'arp_rig_name' in blender_object:
original_rig = bpy.data.objects[blender_object['arp_rig_name']]
if original_rig.animation_data:
original_rig.animation_data.action = bpy.data.actions[blender_action.name + "_%temp"]
def menu_func_export(self, context):
self.layout.operator(auto_rig_ge.ARP_OT_GE_export_fbx_panel.bl_idname, text="Auto-Rig Pro FBX (.fbx)")
if bpy.app.version >= (3, 4, 0):
self.layout.operator(auto_rig_ge.ARP_OT_GE_export_gltf_panel.bl_idname, text="Auto-Rig Pro GLTF (.glb/.gltf)")
def cleanse_modules():
import sys
all_modules = sys.modules
all_modules = dict(sorted(all_modules.items(),key= lambda x:x[0]))
for k in all_modules:
if k.startswith(__name__):
del sys.modules[k]
def register():
auto_rig_prefs.register()
auto_rig.register()
auto_rig_smart.register()
auto_rig_remap.register()
auto_rig_ge.register()
rig_functions.register()
arp_fbx_init.register()
bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
def unregister():
auto_rig_prefs.unregister()
auto_rig.unregister()
auto_rig_smart.unregister()
auto_rig_remap.unregister()
auto_rig_ge.unregister()
rig_functions.unregister()
arp_fbx_init.unregister()
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
cleanse_modules()
if __name__ == "__main__":
register()
@@ -0,0 +1,30 @@
Asset files in .blend format containing rig components in this folder are released under the CC0 License.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
moral rights retained by the original author(s) and/or performer(s);
publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
rights protecting the extraction, dissemination, use and reuse of data in a Work;
database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
4. Limitations and Disclaimers.
No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
@@ -0,0 +1,48 @@
schema_version = "1.0.0"
id = "auto_rig_pro"
version = "3.73.41"
name = "Auto-Rig Pro"
tagline = "Automatic rig generation based on reference bones and various tools"
maintainer = "Artell <elartblender@gmail.com>"
type = "add-on"
website = "https://www.lucky3d.fr/auto-rig-pro/doc/"
tags = ["Rigging", "Animation"]
blender_version_min = "2.8.0"
# No upper version breakage for now
# blender_version_max = "5.1.0"
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
"SPDX:GPL-2.0-or-later", "Royalty Free", "CC0"
]
copyright = [
"2016-2024 Lucas Veber",
]
# Optional: bundle 3rd party Python modules.
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
# "./wheels/hexdump-3.3-py3-none-any.whl",
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]
# # If using network, remember to also check `bpy.app.online_access`
# # https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
# #
[permissions]
network = "To look for updates."
files = "To save and write rig preset files on disk, export FBX, GLTF files"
clipboard = "Copy and paste bone transforms"
# Optional: build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
# [build]
# paths_exclude_pattern = [
# "__pycache__/",
# "/.git/",
# "/*.zip",
# ]
@@ -0,0 +1,3 @@
Image icon files in this folder are released under the terms of a Royalty Free license:
a one-time fee is paid when purchasing the addon, for the right to use the content without having to pay any additional royalties or fees for each use.
Files cannot be freely redistributed and sold.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
Asset files in .png format containing icons components in this folder are from Blender 2.79 icons set
released under the Creative Commons Attribution-ShareAlike 4.0 International License
You are free to:
Share — copy and redistribute the material in any medium or format
Adapt — remix, transform, and build upon the material
for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.
https://creativecommons.org/licenses/by-sa/4.0/
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
Assets files in this folder are released under the terms of a Royalty Free license:
a one-time fee is paid when purchasing the addon, for the right to use the content without having to pay any additional royalties or fees for each use.
Files cannot be freely redistributed and sold.
@@ -0,0 +1 @@
Assets files (presets files in .blend format and images) contained in Auto-Rig Pro are released under the terms of a Royalty Free license: a one-time fee is paid when purchasing the addon, for the right to use the content without having to pay any additional royalties or fees for each use. The asset files cannot be freely redistributed and sold.
@@ -0,0 +1,13 @@
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,505 @@
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_BoneRoot
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_FacialBone
False
False
c_head.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Head
False
False
c_root_master.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Hip
True
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_JawRoot
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_BigToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Breast
False
False
c_leg_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Calf
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_CalfTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_CalfTwist02
False
False
c_shoulder.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Clavicle
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ElbowShareBone
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Eye
False
False
c_foot_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Foot
False
True
c_leg_pole.l
c_forearm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Forearm
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ForearmTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ForearmTwist02
False
False
c_hand_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Hand
False
False
c_index1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Index1
False
False
c_index2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Index2
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Index3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_IndexToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_KneeShareBone
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Mid1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Mid2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Mid3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_MidToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Pinky1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Pinky2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Pinky3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_PinkyToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_RibsTwist
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Ring1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Ring2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Ring3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_RingToe1
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Thigh
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ThighTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ThighTwist02
False
False
c_thumb1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Thumb1
False
False
c_thumb2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Thumb2
False
False
c_thumb3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Thumb3
False
False
c_toes_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ToeBase
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_ToeBaseShareBone
False
False
c_arm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_Upperarm
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_UpperarmTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_L_UpperarmTwist02
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_NeckTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_NeckTwist02
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Pelvis
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_BigToe1
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Breast
False
False
c_leg_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Calf
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_CalfTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_CalfTwist02
False
False
c_shoulder.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Clavicle
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ElbowShareBone
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Eye
False
False
c_foot_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Foot
False
True
c_leg_pole.r
c_forearm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Forearm
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ForearmTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ForearmTwist02
False
False
c_hand_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Hand
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Index1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Index2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Index3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_IndexToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_KneeShareBone
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Mid1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Mid2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Mid3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_MidToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Pinky1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Pinky2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Pinky3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_PinkyToe1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_RibsTwist
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Ring1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Ring2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Ring3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_RingToe1
False
False
c_thigh_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Thigh
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ThighTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ThighTwist02
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Thumb1
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Thumb2
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Thumb3
False
False
c_toes_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ToeBase
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_ToeBaseShareBone
False
False
c_arm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_Upperarm
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_UpperarmTwist01
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_R_UpperarmTwist02
False
False
c_spine_01.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Spine01
False
False
c_spine_02.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Spine02
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Teeth01
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Teeth02
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Tongue01
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Tongue02
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Tongue03
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_UpperJaw
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
CC_Base_Waist
False
False
@@ -0,0 +1,505 @@
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
abdomenLower
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
abdomenUpper
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
chestLower
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
chestUpper
False
False
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
head
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
hip
True
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lBigToe
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lBigToe_2
False
False
c_index1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lCarpal1
False
False
c_middle1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lCarpal2
False
False
c_ring1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lCarpal3
False
False
c_pinky1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lCarpal4
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lCollar
False
False
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lFoot
False
True
c_leg_pole.l
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lForearmBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lForearmTwist
False
False
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lHand
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lHand_IK
False
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lIndex1
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lIndex2
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lIndex3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lMetatarsals
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lMid1
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lMid2
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lMid3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lPectoral
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lPinky1
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lPinky2
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lPinky3
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lRing1
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lRing2
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lRing3
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lShin
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lShin_IK
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lShin_P
False
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lShldrBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lShldrTwist
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe1
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe1_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe2_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe3_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe4
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lSmallToe4_2
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lThighBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lThighTwist
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lThumb1
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lThumb2
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lThumb3
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lToe
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
neckLower
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
neckUpper
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pelvis
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rBigToe
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rBigToe_2
False
False
c_index1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rCarpal1
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rCarpal2
False
False
c_ring1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rCarpal3
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rCarpal4
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rCollar
False
False
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rFoot
False
True
c_leg_pole.r
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rForearmBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rForearmTwist
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rHand
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rHand_IK
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rIndex1
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rIndex2
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rIndex3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rMetatarsals
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rMid1
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rMid2
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rMid3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rPectoral
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rPinky1
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rPinky2
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rPinky3
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rRing1
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rRing2
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rRing3
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rShin
False
False
%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rShin_IK
False
True
c_leg_pole.r
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rShin_P
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rShldrBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rShldrTwist
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe1
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe1_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe2_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe3
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe3_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe4
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rSmallToe4_2
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rThighBend
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rThighTwist
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rThumb1
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rThumb2
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rThumb3
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
rToe
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
root
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
upperFaceRig
False
False
@@ -0,0 +1,270 @@
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
head_JNT
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,-0.05000000074505806,0.0%1.0%False%False%
hips_JNT
True
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_arm_JNT
False
False
c_eye.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_eye_JNT
False
False
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_foot_JNT
False
True
c_leg_pole.l
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_forearm_JNT
False
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handIndex1_JNT
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handIndex2_JNT
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handIndex3_JNT
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handMiddle1_JNT
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handMiddle2_JNT
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handMiddle3_JNT
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handPinky1_JNT
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handPinky2_JNT
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handPinky3_JNT
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handRing1_JNT
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handRing2_JNT
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handRing3_JNT
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handThumb1_JNT
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handThumb2_JNT
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_handThumb3_JNT
False
False
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_hand_JNT
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_leg_JNT
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_shoulder_JNT
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_toebase_JNT
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
l_upleg_JNT
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
neck_JNT
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_arm_JNT
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_eye_JNT
False
False
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_foot_JNT
False
True
c_leg_pole.r
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_forearm_JNT
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handIndex1_JNT
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handIndex2_JNT
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handIndex3_JNT
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handMiddle1_JNT
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handMiddle2_JNT
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handMiddle3_JNT
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handPinky1_JNT
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handPinky2_JNT
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handPinky3_JNT
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handRing1_JNT
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handRing2_JNT
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handRing3_JNT
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handThumb1_JNT
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handThumb2_JNT
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_handThumb3_JNT
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_hand_JNT
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_leg_JNT
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_shoulder_JNT
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_toebase_JNT
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
r_upleg_JNT
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine1_JNT
False
False
c_spine_03.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine2_JNT
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine_JNT
False
False
@@ -0,0 +1,264 @@
c_root_master.x
heat_Hips
True
False
heat_Spine
False
False
c_spine_01.x
heat_Spine1
False
False
c_spine_02.x
heat_Spine2
False
False
c_neck.x
heat_Neck
False
False
c_head.x
heat_Head
False
False
None
heat_HeadTop_End
False
False
c_shoulder.l
heat_Shoulder_l
False
False
c_arm_fk.l
heat_UpperArm_l
False
False
c_forearm_fk.l
heat_LowerArm_l
False
False
c_hand_fk.l
heat_Hand_l
False
False
c_arms_pole.l
c_middle1.l
heat_Middle1_l
False
False
c_middle2.l
heat_Middle2_l
False
False
c_middle3.l
heat_Middle3_l
False
False
c_thumb1.l
heat_Thumb1_l
False
False
c_thumb2.l
heat_Thumb2_l
False
False
c_thumb3.l
heat_Thumb3_l
False
False
c_index1.l
heat_Index1_l
False
False
c_index2.l
heat_Index2_l
False
False
c_index3.l
heat_Index3_l
False
False
c_ring1.l
heat_Ring1_l
False
False
c_ring2.l
heat_Ring2_l
False
False
c_ring3.l
heat_Ring3_l
False
False
c_pinky1.l
heat_Pinky1_l
False
False
c_pinky2.l
heat_Pinky2_l
False
False
c_pinky3.l
heat_Pinky3_l
False
False
c_shoulder.r
heat_Shoulder_r
False
False
c_arm_fk.r
heat_UpperArm_r
False
False
c_forearm_fk.r
heat_LowerArm_r
False
False
c_hand_fk.r
heat_Hand_r
False
False
c_arms_pole.r
c_middle1.r
heat_Middle1_r
False
False
c_middle2.r
heat_Middle2_r
False
False
c_middle3.r
heat_Middle3_r
False
False
c_thumb1.r
heat_Thumb1_r
False
False
c_thumb2.r
heat_Thumb2_r
False
False
c_thumb3.r
heat_Thumb3_r
False
False
c_index1.r
heat_Index1_r
False
False
c_index2.r
heat_Index2_r
False
False
c_index3.r
heat_Index3_r
False
False
c_ring1.r
heat_Ring1_r
False
False
c_ring2.r
heat_Ring2_r
False
False
c_ring3.r
heat_Ring3_r
False
False
c_pinky1.r
heat_Pinky1_r
False
False
c_pinky2.r
heat_Pinky2_r
False
False
c_pinky3.r
heat_Pinky3_r
False
False
c_thigh_fk.r
heat_UpLeg_r
False
False
c_leg_fk.r
heat_Leg_r
False
False
c_foot_fk.r
heat_Foot_r
False
False
c_leg_pole.r
c_toes_fk.r
heat_ToeBase_r
False
False
c_thigh_fk.l
heat_UpLeg_l
False
False
c_leg_fk.l
heat_Leg_l
False
False
c_foot_fk.l
heat_Foot_l
False
False
c_leg_pole.l
c_toes_fk.l
heat_ToeBase_l
False
False
@@ -0,0 +1,270 @@
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Foot_l
False
True
c_leg_pole.l
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Foot_r
False
True
c_leg_pole.r
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Hand_l
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Hand_r
False
False
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Head
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_HeadTop_End
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Hips
True
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index1_l
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index1_r
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index2_l
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index2_r
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index3_l
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Index3_r
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Leg_l
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Leg_r
False
False
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_LowerArm_l
False
False
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_LowerArm_r
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle1_l
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle1_r
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle2_l
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle2_r
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle3_l
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Middle3_r
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Neck
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky1_l
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky1_r
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky2_l
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky2_r
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky3_l
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Pinky3_r
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring1_l
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring1_r
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring2_l
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring2_r
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring3_l
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Ring3_r
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Root
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Shoulder_l
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Shoulder_r
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Spine
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Spine1
False
False
c_spine_03.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Spine2
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb1_l
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb1_r
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb2_l
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb2_r
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb3_l
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_Thumb3_r
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_ToeBase_l
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_ToeBase_r
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_UpLeg_l
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_UpLeg_r
False
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_UpperArm_l
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
heat_UpperArm_r
False
False
@@ -0,0 +1,355 @@
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
breast_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
breast_R
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
calf_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
calf_R
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
calf_twist_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
calf_twist_R
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
clavicle_L
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
clavicle_R
False
False
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
foot_L
False
True
c_leg_pole.l
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
foot_R
False
True
c_leg_pole.r
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
hand_L
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
hand_R
False
False
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
head
False
False
c_index1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index00_L
False
False
c_index1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index00_R
False
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index01_L
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index01_R
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index02_L
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index02_R
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index03_L
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
index03_R
False
False
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lowerarm_L
False
False
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lowerarm_R
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lowerarm_twist_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
lowerarm_twist_R
False
False
c_middle1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle00_L
False
False
c_middle1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle00_R
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle01_L
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle01_R
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle02_L
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle02_R
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle03_L
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
middle03_R
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
neck
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pelvis
True
False
c_pinky1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky00_L
False
False
c_pinky1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky00_R
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky01_L
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky01_R
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky02_L
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky02_R
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky03_L
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
pinky03_R
False
False
c_ring1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring00_L
False
False
c_ring1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring00_R
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring01_L
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring01_R
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring02_L
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring02_R
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring03_L
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
ring03_R
False
False
c_root.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
root
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine01
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine02
False
False
c_spine_03.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
spine03
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thigh_L
False
False
c_thigh_b.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thigh_R
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thigh_twist_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thigh_twist_R
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb01_L
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb01_R
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb02_L
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb02_R
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb03_L
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
thumb03_R
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
toes_L
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
toes_R
False
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
upperarm_L
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
upperarm_R
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
upperarm_twist_L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
upperarm_twist_R
False
False
@@ -0,0 +1,260 @@
c_middle2.r
mixamorig:RightHandMiddle2
False
False
c_middle1.r
mixamorig:RightHandMiddle1
False
False
c_shoulder.r
mixamorig:RightShoulder
False
False
c_arm_fk.l
mixamorig:LeftArm
False
False
c_pinky1.l
mixamorig:LeftHandPinky1
False
False
c_thumb1.r
mixamorig:RightHandThumb1
False
False
c_index2.l
mixamorig:LeftHandIndex2
False
False
c_thumb3.r
mixamorig:RightHandThumb3
False
False
c_ring3.r
mixamorig:RightHandRing3
False
False
c_thumb3.l
mixamorig:LeftHandThumb3
False
False
c_thumb1.l
mixamorig:LeftHandThumb1
False
False
c_toes_ik.l
mixamorig:LeftToeBase
False
False
c_pinky3.l
mixamorig:LeftHandPinky3
False
False
c_pinky2.l
mixamorig:LeftHandPinky2
False
False
c_index3.r
mixamorig:RightHandIndex3
False
False
c_hand_fk.l
mixamorig:LeftHand
False
False
c_forearm_fk.r
mixamorig:RightForeArm
False
False
c_pinky1.r
mixamorig:RightHandPinky1
False
False
c_middle2.l
mixamorig:LeftHandMiddle2
False
False
c_head.x
mixamorig:Head
False
False
c_spine_01.x
mixamorig:Spine
False
False
c_thumb2.l
mixamorig:LeftHandThumb2
False
False
c_foot_ik.l
mixamorig:LeftFoot
False
True
c_leg_pole.l
c_spine_03.x
mixamorig:Spine2
False
False
c_middle3.r
mixamorig:RightHandMiddle3
False
False
c_thumb2.r
mixamorig:RightHandThumb2
False
False
c_pinky2.r
mixamorig:RightHandPinky2
False
False
c_toes_ik.r
mixamorig:RightToeBase
False
False
c_index3.l
mixamorig:LeftHandIndex3
False
False
c_leg_fk.l
mixamorig:LeftLeg
False
False
c_shoulder.l
mixamorig:LeftShoulder
False
False
c_ring2.r
mixamorig:RightHandRing2
False
False
c_arm_fk.r
mixamorig:RightArm
False
False
c_index1.l
mixamorig:LeftHandIndex1
False
False
c_ring1.r
mixamorig:RightHandRing1
False
False
c_hand_fk.r
mixamorig:RightHand
False
False
c_leg_fk.r
mixamorig:RightLeg
False
False
c_foot_ik.r
mixamorig:RightFoot
False
True
c_leg_pole.r
c_ring2.l
mixamorig:LeftHandRing2
False
False
c_root_master.x
mixamorig:Hips
True
False
c_middle1.l
mixamorig:LeftHandMiddle1
False
False
c_pinky3.r
mixamorig:RightHandPinky3
False
False
c_thigh_fk.l
mixamorig:LeftUpLeg
False
False
c_ring3.l
mixamorig:LeftHandRing3
False
False
c_neck.x
mixamorig:Neck
False
False
c_forearm_fk.l
mixamorig:LeftForeArm
False
False
c_index2.r
mixamorig:RightHandIndex2
False
False
c_ring1.l
mixamorig:LeftHandRing1
False
False
c_middle3.l
mixamorig:LeftHandMiddle3
False
False
c_spine_02.x
mixamorig:Spine1
False
False
c_thigh_fk.r
mixamorig:RightUpLeg
False
False
c_index1.r
mixamorig:RightHandIndex1
False
False
@@ -0,0 +1,275 @@
c_root_master.x
Hips
True
False
Spine
False
False
c_spine_01.x
Spine1
False
False
c_spine_02.x
Spine2
False
False
c_neck.x
Neck
False
False
c_head.x
Head
False
False
None
HeadTop_End
False
False
LeftEye
False
False
RightEye
False
False
c_shoulder.l
LeftShoulder
False
False
c_arm_fk.l
LeftArm
False
False
c_forearm_fk.l
LeftForeArm
False
False
c_hand_fk.l
LeftHand
False
False
c_arms_pole.l
c_middle1.l
LeftHandMiddle1
False
False
c_middle2.l
LeftHandMiddle2
False
False
c_middle3.l
LeftHandMiddle3
False
False
c_thumb1.l
LeftHandThumb1
False
False
c_thumb2.l
LeftHandThumb2
False
False
c_thumb3.l
LeftHandThumb3
False
False
c_index1.l
LeftHandIndex1
False
False
c_index2.l
LeftHandIndex2
False
False
c_index3.l
LeftHandIndex3
False
False
c_ring1.l
LeftHandRing1
False
False
c_ring2.l
LeftHandRing2
False
False
c_ring3.l
LeftHandRing3
False
False
c_pinky1.l
LeftHandPinky1
False
False
c_pinky2.l
LeftHandPinky2
False
False
c_pinky3.l
LeftHandPinky3
False
False
c_shoulder.r
RightShoulder
False
False
c_arm_fk.r
RightArm
False
False
c_forearm_fk.r
RightForeArm
False
False
c_hand_fk.r
RightHand
False
False
c_arms_pole.r
c_middle1.r
RightHandMiddle1
False
False
c_middle2.r
RightHandMiddle2
False
False
c_middle3.r
RightHandMiddle3
False
False
c_thumb1.r
RightHandThumb1
False
False
c_thumb2.r
RightHandThumb2
False
False
c_thumb3.r
RightHandThumb3
False
False
c_index1.r
RightHandIndex1
False
False
c_index2.r
RightHandIndex2
False
False
c_index3.r
RightHandIndex3
False
False
c_ring1.r
RightHandRing1
False
False
c_ring2.r
RightHandRing2
False
False
c_ring3.r
RightHandRing3
False
False
c_pinky1.r
RightHandPinky1
False
False
c_pinky2.r
RightHandPinky2
False
False
c_pinky3.r
RightHandPinky3
False
False
c_thigh_fk.r
RightUpLeg
False
False
c_leg_fk.r
RightLeg
False
False
c_foot_fk.r
RightFoot
False
False
c_leg_pole.r
c_toes_fk.r
RightToeBase
False
False
c_thigh_fk.l
LeftUpLeg
False
False
c_leg_fk.l
LeftLeg
False
False
c_foot_fk.l
LeftFoot
False
False
c_leg_pole.l
c_toes_fk.l
LeftToeBase
False
False
@@ -0,0 +1,275 @@
c_root_master.x
Hips
True
False
Spine
False
False
c_spine_01.x
Spine1
False
False
c_spine_02.x
Spine2
False
False
c_neck.x
Neck
False
False
c_head.x
Head
False
False
None
HeadTop_End
False
False
None
LeftEye
False
False
None
RightEye
False
False
c_shoulder.l
LeftShoulder
False
False
c_arm_fk.l
LeftArm
False
False
c_forearm_fk.l
LeftForeArm
False
False
c_hand_ik.l
LeftHand
False
True
c_arms_pole.l
c_middle1.l
LeftHandMiddle1
False
False
c_middle2.l
LeftHandMiddle2
False
False
c_middle3.l
LeftHandMiddle3
False
False
c_thumb1.l
LeftHandThumb1
False
False
c_thumb2.l
LeftHandThumb2
False
False
c_thumb3.l
LeftHandThumb3
False
False
c_index1.l
LeftHandIndex1
False
False
c_index2.l
LeftHandIndex2
False
False
c_index3.l
LeftHandIndex3
False
False
c_ring1.l
LeftHandRing1
False
False
c_ring2.l
LeftHandRing2
False
False
c_ring3.l
LeftHandRing3
False
False
c_pinky1.l
LeftHandPinky1
False
False
c_pinky2.l
LeftHandPinky2
False
False
c_pinky3.l
LeftHandPinky3
False
False
c_shoulder.r
RightShoulder
False
False
c_arm_fk.r
RightArm
False
False
c_forearm_fk.r
RightForeArm
False
False
c_hand_ik.r
RightHand
False
True
c_arms_pole.r
c_middle1.r
RightHandMiddle1
False
False
c_middle2.r
RightHandMiddle2
False
False
c_middle3.r
RightHandMiddle3
False
False
c_thumb1.r
RightHandThumb1
False
False
c_thumb2.r
RightHandThumb2
False
False
c_thumb3.r
RightHandThumb3
False
False
c_index1.r
RightHandIndex1
False
False
c_index2.r
RightHandIndex2
False
False
c_index3.r
RightHandIndex3
False
False
c_ring1.r
RightHandRing1
False
False
c_ring2.r
RightHandRing2
False
False
c_ring3.r
RightHandRing3
False
False
c_pinky1.r
RightHandPinky1
False
False
c_pinky2.r
RightHandPinky2
False
False
c_pinky3.r
RightHandPinky3
False
False
c_thigh_fk.r
RightUpLeg
False
False
c_leg_fk.r
RightLeg
False
False
c_foot_ik.r
RightFoot
False
True
c_leg_pole.r
c_toes_ik.r
RightToeBase
False
False
c_thigh_fk.l
LeftUpLeg
False
False
c_leg_fk.l
LeftLeg
False
False
c_foot_ik.l
LeftFoot
False
True
c_leg_pole.l
c_toes_ik.l
LeftToeBase
False
False
@@ -0,0 +1,135 @@
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
head
False
False
c_foot_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_foot
False
True
c_leg_pole.l
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_hand
False
False
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_low_arm
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_low_leg
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_shoulder
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_toes
False
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_up_arm
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
l_up_leg
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
neck_1
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
neck_2
False
False
c_foot_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_foot
False
True
c_leg_pole.r
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_hand
False
False
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_low_arm
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_low_leg
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_shoulder
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_toes
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_up_arm
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
r_up_leg
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
root
True
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_1
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_2
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_3
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_4
False
False
c_spine_03.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_5
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_6
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
torso_7
False
False
@@ -0,0 +1,315 @@
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Head
False
False
%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Head_end
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Hips
True
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftArm
False
False
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftFoot
False
True
c_leg_pole.l
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftForeArm
False
False
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHand
False
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandIndex1
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandIndex2
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandIndex3
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandMiddle1
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandMiddle2
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandMiddle3
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandPinky1
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandPinky2
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandPinky3
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandRing1
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandRing2
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandRing3
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandThumb1
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandThumb2
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftHandThumb3
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftLeg
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftShoulder
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftToeBase
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:LeftUpLeg
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Neck
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightArm
False
False
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightFoot
False
True
c_leg_pole.r
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightForeArm
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHand
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandIndex1
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandIndex2
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandIndex3
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandMiddle1
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandMiddle2
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandMiddle3
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandPinky1
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandPinky2
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandPinky3
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandRing1
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandRing2
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandRing3
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandThumb1
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandThumb2
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightHandThumb3
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightLeg
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightShoulder
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightToeBase
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:RightUpLeg
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Root
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Spine
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:Spine1
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:heel.02.L
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:heel.02.R
False
False
c_index1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.01.L
False
False
c_index1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.01.R
False
False
c_middle1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.02.L
False
False
c_middle1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.02.R
False
False
c_ring1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.03.L
False
False
c_ring1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.03.R
False
False
c_pinky1_base.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.04.L
False
False
c_pinky1_base.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
_1:palm.04.R
False
False
@@ -0,0 +1,295 @@
c_head.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Head
False
False
c_root_master.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Hips
True
False
c_arm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftArm
False
False
c_foot_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftFoot
False
True
c_leg_pole.l
c_forearm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftForeArm
False
False
c_hand_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHand
False
False
c_index1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandIndex1
False
False
c_index2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandIndex2
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandIndex3
False
False
c_middle1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandMiddle1
False
False
c_middle2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandMiddle2
False
False
c_middle3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandMiddle3
False
False
c_pinky1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandPinky1
False
False
c_pinky2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandPinky2
False
False
c_pinky3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandPinky3
False
False
c_ring1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandRing1
False
False
c_ring2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandRing2
False
False
c_ring3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandRing3
False
False
c_thumb1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandThumb1
False
False
c_thumb2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandThumb2
False
False
c_thumb3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftHandThumb3
False
False
c_index1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftInHandIndex
False
False
c_middle1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftInHandMiddle
False
False
c_pinky1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftInHandPinky
False
False
c_ring1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftInHandRing
False
False
c_leg_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftLeg
False
False
c_shoulder.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftShoulder
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
LeftUpLeg
False
False
c_neck.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Neck
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Neck1
False
False
c_arm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightArm
False
False
c_foot_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightFoot
False
True
c_leg_pole.r
c_forearm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightForeArm
False
False
c_hand_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHand
False
False
c_index1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandIndex1
False
False
c_index2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandIndex2
False
False
c_index3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandIndex3
False
False
c_middle1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandMiddle1
False
False
c_middle2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandMiddle2
False
False
c_middle3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandMiddle3
False
False
c_pinky1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandPinky1
False
False
c_pinky2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandPinky2
False
False
c_pinky3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandPinky3
False
False
c_ring1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandRing1
False
False
c_ring2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandRing2
False
False
c_ring3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandRing3
False
False
c_thumb1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandThumb1
False
False
c_thumb2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandThumb2
False
False
c_thumb3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightHandThumb3
False
False
c_index1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightInHandIndex
False
False
c_middle1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightInHandMiddle
False
False
c_pinky1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightInHandPinky
False
False
c_ring1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightInHandRing
False
False
c_leg_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightLeg
False
False
c_shoulder.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightShoulder
False
False
c_thigh_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
RightUpLeg
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Spine
False
False
c_spine_01.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Spine1
False
False
c_spine_02.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%
Spine2
False
False
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,325 @@
c_head.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Head
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
HeadEnd
False
False
c_root_master.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Hips
True
False
c_arm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftArm
False
False
c_foot_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFoot
False
True
c_forearm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftForeArm
False
False
c_hand_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHand
False
False
c_index1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandIndex1
False
False
c_index2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandIndex2
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandIndex3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandIndex4
False
False
c_middle1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandMiddle1
False
False
c_middle2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandMiddle2
False
False
c_middle3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandMiddle3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandMiddle4
False
False
c_pinky1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandPinky1
False
False
c_pinky2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandPinky2
False
False
c_pinky3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandPinky3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandPinky4
False
False
c_ring1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandRing1
False
False
c_ring2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandRing2
False
False
c_ring3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandRing3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandRing4
False
False
c_thumb1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandThumb1
False
False
c_thumb2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandThumb2
False
False
c_thumb3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandThumb3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHandThumb4
False
False
c_leg_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftLeg
False
False
c_shoulder.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftShoulder
False
False
c_toes_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftToeBase
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftToeBaseEnd
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftUpLeg
False
False
c_neck.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Neck
False
False
c_arm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightArm
False
False
c_foot_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFoot
False
True
c_forearm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightForeArm
False
False
c_hand_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHand
False
False
c_index1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandIndex1
False
False
c_index2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandIndex2
False
False
c_index3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandIndex3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandIndex4
False
False
c_middle1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandMiddle1
False
False
c_middle2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandMiddle2
False
False
c_middle3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandMiddle3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandMiddle4
False
False
c_pinky1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandPinky1
False
False
c_pinky2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandPinky2
False
False
c_pinky3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandPinky3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandPinky4
False
False
c_ring1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandRing1
False
False
c_ring2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandRing2
False
False
c_ring3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandRing3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandRing4
False
False
c_thumb1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandThumb1
False
False
c_thumb2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandThumb2
False
False
c_thumb3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandThumb3
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHandThumb4
False
False
c_leg_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightLeg
False
False
c_shoulder.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightShoulder
False
False
c_toes_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightToeBase
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightToeBaseEnd
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightUpLeg
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine
False
False
c_spine_01.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine1
False
False
c_spine_02.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine2
False
False
@@ -0,0 +1,305 @@
c_head.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Head
False
False
c_root_master.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Hips
True
False
c_arm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftArm
False
False
c_thumb3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger1Distal
False
False
c_thumb1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger1Metacarpal
False
False
c_thumb2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger1Proximal
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger2Distal
False
False
c_index2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger2Medial
False
False
c_index1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger2Metacarpal
False
False
c_index1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger2Proximal
False
False
c_middle3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger3Distal
False
False
c_middle2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger3Medial
False
False
c_middle1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger3Metacarpal
False
False
c_middle1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger3Proximal
False
False
c_ring3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger4Distal
False
False
c_ring2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger4Medial
False
False
c_ring1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger4Metacarpal
False
False
c_ring1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger4Proximal
False
False
c_pinky3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger5Distal
False
False
c_pinky2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger5Medial
False
False
c_pinky1_base.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger5Metacarpal
False
False
c_pinky1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFinger5Proximal
False
False
c_foot_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftFoot
False
True
c_leg_pole.l
c_forearm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftForeArm
False
False
c_hand_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftHand
False
False
c_leg_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftShin
False
False
c_shoulder.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftShoulder
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftThigh
False
False
c_toes_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
LeftToe
False
False
c_neck.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Neck
False
False
c_arm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightArm
False
False
c_thumb3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger1Distal
False
False
c_thumb1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger1Metacarpal
False
False
c_thumb2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger1Proximal
False
False
c_index3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger2Distal
False
False
c_index2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger2Medial
False
False
c_index1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger2Metacarpal
False
False
c_index1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger2Proximal
False
False
c_middle3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger3Distal
False
False
c_middle2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger3Medial
False
False
c_middle1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger3Metacarpal
False
False
c_middle1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger3Proximal
False
False
c_ring3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger4Distal
False
False
c_ring2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger4Medial
False
False
c_ring1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger4Metacarpal
False
False
c_ring1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger4Proximal
False
False
c_pinky3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger5Distal
False
False
c_pinky2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger5Medial
False
False
c_pinky1_base.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger5Metacarpal
False
False
c_pinky1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFinger5Proximal
False
False
c_foot_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightFoot
False
True
c_leg_pole.r
c_forearm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightForeArm
False
False
c_hand_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightHand
False
False
c_leg_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightShin
False
False
c_shoulder.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightShoulder
False
False
c_thigh_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightThigh
False
False
c_toes_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
RightToe
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine1
False
False
c_spine_01.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine2
False
False
%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine3
False
False
c_spine_02.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
Spine4
False
False
@@ -0,0 +1,275 @@
Hips
False
False
c_root_master.x
Spine
True
False
c_spine_01.x
Spine1
False
False
c_spine_02.x
Spine2
False
False
c_neck.x
Neck
False
False
c_head.x
Head
False
False
c_head.x
HeadTop_End
False
False
None
LeftEye
False
False
None
RightEye
False
False
c_shoulder.l
LeftShoulder
False
False
c_arm_fk.l
LeftArm
False
False
c_forearm_fk.l
LeftForeArm
False
False
c_hand_fk.l
LeftHand
False
False
c_arms_pole.l
c_middle1.l
LeftHandMiddle1
False
False
c_middle2.l
LeftHandMiddle2
False
False
c_middle3.l
LeftHandMiddle3
False
False
c_thumb1.l
LeftHandThumb1
False
False
c_thumb2.l
LeftHandThumb2
False
False
c_thumb3.l
LeftHandThumb3
False
False
c_index1.l
LeftHandIndex1
False
False
c_index2.l
LeftHandIndex2
False
False
c_index3.l
LeftHandIndex3
False
False
c_ring1.l
LeftHandRing1
False
False
c_ring2.l
LeftHandRing2
False
False
c_ring3.l
LeftHandRing3
False
False
c_pinky1.l
LeftHandPinky1
False
False
c_pinky2.l
LeftHandPinky2
False
False
c_pinky3.l
LeftHandPinky3
False
False
c_shoulder.r
RightShoulder
False
False
c_arm_fk.r
RightArm
False
False
c_forearm_fk.r
RightForeArm
False
False
c_hand_fk.r
RightHand
False
False
c_arms_pole.r
c_middle1.r
RightHandMiddle1
False
False
c_middle2.r
RightHandMiddle2
False
False
c_middle3.r
RightHandMiddle3
False
False
c_thumb1.r
RightHandThumb1
False
False
c_thumb2.r
RightHandThumb2
False
False
c_thumb3.r
RightHandThumb3
False
False
c_index1.r
RightHandIndex1
False
False
c_index2.r
RightHandIndex2
False
False
c_index3.r
RightHandIndex3
False
False
c_ring1.r
RightHandRing1
False
False
c_ring2.r
RightHandRing2
False
False
c_ring3.r
RightHandRing3
False
False
c_pinky1.r
RightHandPinky1
False
False
c_pinky2.r
RightHandPinky2
False
False
c_pinky3.r
RightHandPinky3
False
False
c_thigh_fk.r
RightUpLeg
False
False
c_leg_fk.r
RightLeg
False
False
c_foot_ik.r
RightFoot
False
True
c_leg_pole.r
c_toes_ik.r
RightToeBase
False
False
c_thigh_fk.l
LeftUpLeg
False
False
c_leg_fk.l
LeftLeg
False
False
c_foot_ik.l
LeftFoot
False
True
c_leg_pole.l
c_toes_ik.l
LeftToeBase
False
False
@@ -0,0 +1,335 @@
c_toes_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ball_l
False
False
c_toes_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ball_r
False
False
c_leg_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
calf_l
False
False
c_leg_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
calf_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
calf_twist_01_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
calf_twist_01_r
False
False
c_shoulder.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
clavicle_l
False
False
c_shoulder.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
clavicle_r
False
False
c_foot_ik.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
foot_l
False
True
c_leg_pole.l
c_foot_ik.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
foot_r
False
True
c_leg_pole.r
c_hand_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
hand_l
False
False
c_hand_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
hand_r
False
False
c_head.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
head
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_foot_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_foot_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_foot_root
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_hand_gun
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_hand_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_hand_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ik_hand_root
False
False
c_index1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_01_l
False
False
c_index1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_01_r
False
False
c_index2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_02_l
False
False
c_index2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_02_r
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_03_l
False
False
c_index3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
index_03_r
False
False
c_forearm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
lowerarm_l
False
False
c_forearm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
lowerarm_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
lowerarm_twist_01_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
lowerarm_twist_01_r
False
False
c_middle1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_01_l
False
False
c_middle1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_01_r
False
False
c_middle2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_02_l
False
False
c_middle2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_02_r
False
False
c_middle3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_03_l
False
False
c_middle3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
middle_03_r
False
False
c_neck.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
neck_01
False
False
c_root_master.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pelvis
True
False
c_pinky1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_01_l
False
False
c_pinky1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_01_r
False
False
c_pinky2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_02_l
False
False
c_pinky2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_02_r
False
False
c_pinky3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_03_l
False
False
c_pinky3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
pinky_03_r
False
False
c_ring1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_01_l
False
False
c_ring1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_01_r
False
False
c_ring2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_02_l
False
False
c_ring2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_02_r
False
False
c_ring3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_03_l
False
False
c_ring3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
ring_03_r
False
False
c_spine_01.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
spine_01
False
False
c_spine_02.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
spine_02
False
False
c_spine_03.x%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
spine_03
False
False
c_thigh_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thigh_l
False
False
c_thigh_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thigh_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thigh_twist_01_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thigh_twist_01_r
False
False
c_thumb1.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_01_l
False
False
c_thumb1.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_01_r
False
False
c_thumb2.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_02_l
False
False
c_thumb2.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_02_r
False
False
c_thumb3.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_03_l
False
False
c_thumb3.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
thumb_03_r
False
False
c_arm_fk.l%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
upperarm_l
False
False
c_arm_fk.r%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
upperarm_r
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
upperarm_twist_01_l
False
False
None%False%False%0.0,0.0,0.0%0.0,0.0,0.0%1.0
upperarm_twist_01_r
False
False
@@ -0,0 +1,265 @@
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Head
False
False
c_root_master.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Hips
True
False
c_arm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftArm
False
False
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftFoot
False
True
c_leg_pole.l
c_forearm_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftForeArm
False
False
c_hand_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHand
False
False
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandIndex1
False
False
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandIndex2
False
False
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandIndex3
False
False
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandMiddle1
False
False
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandMiddle2
False
False
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandMiddle3
False
False
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandPinky1
False
False
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandPinky2
False
False
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandPinky3
False
False
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandRing1
False
False
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandRing2
False
False
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandRing3
False
False
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandThumb1
False
False
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandThumb2
False
False
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftHandThumb3
False
False
c_leg_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftLeg
False
False
c_shoulder.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftShoulder
False
False
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftToeBase
False
False
c_thigh_fk.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
LeftUpLeg
False
False
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Neck
False
False
c_arm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightArm
False
False
c_foot_ik.r%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightFoot
False
True
c_leg_pole.r
c_forearm_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightForeArm
False
False
c_hand_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHand
False
False
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandIndex1
False
False
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandIndex2
False
False
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandIndex3
False
False
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandMiddle1
False
False
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandMiddle2
False
False
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandMiddle3
False
False
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandPinky1
False
False
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandPinky2
False
False
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandPinky3
False
False
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandRing1
False
False
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandRing2
False
False
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandRing3
False
False
c_thumb1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandThumb1
False
False
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandThumb2
False
False
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightHandThumb3
False
False
c_leg_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightLeg
False
False
c_shoulder.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightShoulder
False
False
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightToeBase
False
False
c_thigh_fk.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
RightUpLeg
False
False
c_spine_01.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Spine
False
False
None%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Spine1
False
False
c_spine_02.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Spine2
False
False
c_spine_03.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%
Spine3
False
False
@@ -0,0 +1,15 @@
The Auto-Rig Pro addon source code in this folder and subfolders is released under the GNU GPL license.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{'c_foot_bank_01': ('Transformation', (0.0, 0.0, 0.0, 0.0, -37.836708068847656, 37.836708068847656), 4.100761890411377), 'c_foot_bank_02': ('Transformation', (0.0, 0.0, 0.0, 0.0, -37.83671188354492, 37.83671188354492), 4.100762844085693), 'c_foot_heel': ('Transformation', (-75.67342376708984, 75.67342376708984, 0.0, 0.0, 0.0, 0.0), 4.100762844085693), 'c_toes_end': ('Transformation', (-50.0000114440918, 50.0000114440918, 0.0, 0.0, 0.0, 0.0), 2.68825364112854)}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,235 @@
import bpy, os, platform, sys, ast
def update_all_tab_names(self, context):
try:
from . import auto_rig
auto_rig.update_arp_tab()
except:
pass
try:
from . import auto_rig_ge
auto_rig_ge.update_arp_tab()
except:
pass
try:
from . import auto_rig_smart
auto_rig_smart.update_arp_tab()
except:
pass
try:
from . import auto_rig_remap
auto_rig_remap.update_arp_tab()
except:
pass
try:
from . import rig_functions
rig_functions.update_arp_tab()
except:
pass
def get_documents_path(other_folder):
home = os.path.expanduser("~") # get the user documents path
documents = os.path.join(home, "Documents")
p = os.path.join(documents, 'AutoRigPro')
p = os.path.join(p, other_folder)
return p
def get_prefs():
if bpy.app.version >= (4,2,0):
return bpy.context.preferences.addons[__package__[:-4]].preferences
else:
return bpy.context.preferences.addons[__package__.split('.')[0]].preferences
class ARP_OT_save_prefs(bpy.types.Operator):
"""Save addon preferences to file, to preserve them when installing a new version"""
bl_idname = 'arp.prefs_save'
bl_label = 'Save ARP prefs'
def execute(self, context):
scn = bpy.context.scene
fp = os.path.abspath(__file__)#get_prefs().prefs_presets_path
fp = os.path.dirname(fp)
fp = os.path.dirname(fp)
fp = os.path.dirname(fp)#Blender addons folder
#print(fp)
if not (fp.endswith("\\") or fp.endswith('/')):
fp += '/'
fp = fp+'autorigpro.prefs'
fp = os.path.abspath(fp)# automatically adds the driver letter if the path does not contain any
#print(fp)
if not os.path.exists(os.path.dirname(fp)):
try:
os.makedirs(os.path.dirname(fp))
except:
pass
file = open(fp, 'w', encoding='utf8', newline='\n')
prefs_settings = {
'arp_tab_name':get_prefs().arp_tab_name,
'arp_tools_tab_name':get_prefs().arp_tools_tab_name,
'beginner_mode': get_prefs().beginner_mode,
'custom_armatures_path': get_prefs().custom_armatures_path,
'custom_limb_path': get_prefs().custom_limb_path,
'rig_layers_path': get_prefs().rig_layers_path,
'remap_presets_path': get_prefs().remap_presets_path,
'ge_presets_path': get_prefs().ge_presets_path,
#'prefs_presets_path': get_prefs().prefs_presets_path,
'default_ikfk_arm':get_prefs().default_ikfk_arm,
'default_ikfk_leg': get_prefs().default_ikfk_leg,
'default_head_lock': get_prefs().default_head_lock,
'remove_existing_arm_mods': get_prefs().remove_existing_arm_mods,
'remove_existing_vgroups': get_prefs().remove_existing_vgroups,
'show_export_popup': get_prefs().show_export_popup,
'arp_debug_mode': scn.arp_debug_mode,
'arp_debug_bind': scn.arp_debug_bind,
'arp_experimental_mode': scn.arp_experimental_mode,
'arp_disable_smart_fx': scn.arp_disable_smart_fx,
}
file.write(str(prefs_settings))
file.close()
print("Auto-Rig Pro preferences saved successfully!")
print(fp)
return {'FINISHED'}
class ARP_MT_arp_addon_preferences(bpy.types.AddonPreferences):
bl_idname = __package__[:-4] if bpy.app.version >= (4,2,0) else __package__.split('.')[0]
arp_tab_name : bpy.props.StringProperty(name='Interface Tab', description='Name of the tab to display the interface in', default='ARP', update=update_all_tab_names)
arp_tools_tab_name : bpy.props.StringProperty(name='Tools Interface Tab', description='Name of the tab to display the tools (IK-FK snap...) interface in', default='Tool', update=update_all_tab_names)
beginner_mode: bpy.props.BoolProperty(name='Beginner Mode', default=True)
custom_armatures_path: bpy.props.StringProperty(name='Armatures', subtype='FILE_PATH', default=get_documents_path('Armatures Presets'), description='Path to store armature presets')
custom_limb_path: bpy.props.StringProperty(name='Limbs', subtype='FILE_PATH', default=get_documents_path('Custom Limbs'), description='Path to store custom limb presets')
rig_layers_path: bpy.props.StringProperty(name='Rig Layers', subtype='FILE_PATH', default=get_documents_path('Rig Layers'), description='Path to store rig layers presets')
remap_presets_path: bpy.props.StringProperty(name='Remap Presets', subtype='FILE_PATH', default=get_documents_path('Remap Presets'), description='Path to store remap presets')
ge_presets_path: bpy.props.StringProperty(name='Export Presets', subtype='FILE_PATH', default=get_documents_path('Game Engine Presets'), description='Path to store game engine export presets')
#prefs_presets_path: bpy.props.StringProperty(name='Preferences', subtype='FILE_PATH', default=get_documents_path('Preferences'), description='Path to store Auto-Rig Pro preferences')
default_ikfk_arm: bpy.props.EnumProperty(items=(('IK', 'IK', 'IK'), ('FK', 'FK', 'FK')), description='Default value for arms IK-FK switch', name='IK-FK Arms Default')
default_ikfk_leg: bpy.props.EnumProperty(items=(('IK', 'IK', 'IK'), ('FK', 'FK', 'FK')), description='Default value for legs IK-FK switch', name='IK-FK Legs Default')
default_head_lock: bpy.props.BoolProperty(default=True, name='Head Lock Default', description='Default value for the Head Lock switch')
remove_existing_arm_mods: bpy.props.BoolProperty(default=True, name='Remove Armature Modifiers', description='Remove existing armature modifiers when binding')
remove_existing_vgroups: bpy.props.BoolProperty(default=True, name='Remove Existing Vertex Groups', description='Remove existing vertex groups when binding')
rem_arm_mods_set: bpy.props.BoolProperty(default=False, description='Toggle to be executed the first time binding, to set default prefs')
rem_vgroups_set: bpy.props.BoolProperty(default=False, description='Toggle to be executed the first time binding, to set default prefs')
show_export_popup: bpy.props.BoolProperty(default=True, description='Show a popup notification on export completion')
def draw(self, context):
col = self.layout.column(align=True)
col.operator(ARP_OT_save_prefs.bl_idname, text='Save Preferences')
col.prop(self, 'beginner_mode', text='Beginner Mode (help buttons)')
col.separator()
col.label(text='Rig:')
col.prop(self, 'remove_existing_arm_mods', text='Remove Existing Armature Modifiers when Binding')
col.prop(self, 'remove_existing_vgroups', text='Remove Existing Vertex Groups when Binding')
col.separator()
col.prop(self, 'default_ikfk_arm', text='IK-FK Arms')
col.prop(self, 'default_ikfk_leg', text='IK-FK Legs')
col.prop(self, 'default_head_lock', text='Head Lock')
col.separator()
col.separator()
col.label(text='Interface:')
col.prop(self, 'arp_tab_name', text='Main ARP Tab')
col.prop(self, 'arp_tools_tab_name', text='Tools Tab')
col.prop(self, 'show_export_popup', text='Show Popup when Export Finished')
col.separator()
col.separator()
col.label(text='Paths:')
#col.prop(self, 'prefs_presets_path')
col.prop(self, 'custom_armatures_path')
col.prop(self, 'custom_limb_path')
col.prop(self, 'rig_layers_path')
col.prop(self, 'remap_presets_path')
col.prop(self, 'ge_presets_path')
col.separator()
col.separator()
col.label(text='Special-Debug:', icon='ERROR')
col.prop(context.scene, 'arp_disable_smart_fx')
col.prop(context.scene, 'arp_debug_mode')
col.prop(context.scene, 'arp_debug_bind')
col.prop(context.scene, 'arp_experimental_mode')
def load_arp_prefs():
fp = os.path.abspath(__file__)#get_prefs().prefs_presets_path
fp = os.path.dirname(fp)
fp = os.path.dirname(fp)
fp = os.path.dirname(fp)#Blender addons folder
#print(fp)
if not (fp.endswith("\\") or fp.endswith('/')):
fp += '/'
fp = fp+'autorigpro.prefs'
fp = os.path.abspath(fp)# automatically adds the driver letter if the path does not contain any
#print(fp)
if not os.path.exists(os.path.dirname(fp)):
print("Auto-Rig Pro preferences are not saved yet")
return
file = None
settings = None
try:
file = open(fp, 'r') if sys.version_info >= (3, 11) else open(fp, 'rU')
file_lines = file.readlines()
settings= str(file_lines[0])
except:
print("Cannot read ARP prefs")
return
settings_dict = ast.literal_eval(settings)
for setting in settings_dict:
setattr(get_prefs(), setting, settings_dict[setting])
#print('Loaded setting', setting)
file.close()
print('Auto-Rig Pro preferences loaded successfully!')
def register():
from bpy.utils import register_class
try:
register_class(ARP_MT_arp_addon_preferences)
register_class(ARP_OT_save_prefs)
except:
pass
# load exported prefs on addon startup
load_arp_prefs()
bpy.types.Scene.arp_debug_mode = bpy.props.BoolProperty(name='Debug Mode', default=False, description = 'Run the addon in debug mode for debugging purposes.\nWarning, can generate earthquakes and solar tempest. Do not enable for normal usage!', options={'HIDDEN'})
bpy.types.Scene.arp_debug_bind = bpy.props.BoolProperty(name='Debug Bind', default=False, description='Enable Debug mode for bind functions, for debugging purposes.\nWarning, will break tools and generate earthquakes!\nDo not enable for normal usage!', options={'HIDDEN'})
bpy.types.Scene.arp_experimental_mode = bpy.props.BoolProperty(name='Experimental Mode', default=False, description = 'Enable experimental, unstable tools. Warning, can lead to errors. Use it at your own risks.', options={'HIDDEN'})
bpy.types.Scene.arp_disable_smart_fx = bpy.props.BoolProperty(name='Disable Smart FX', default=False, description='Disable Smart markers FX for debug purposes, such as Mac systems not supporting some graphics. Safe to use.', options={'HIDDEN'})
def unregister():
from bpy.utils import unregister_class
unregister_class(ARP_MT_arp_addon_preferences)
del bpy.types.Scene.arp_debug_mode
del bpy.types.Scene.arp_debug_bind
del bpy.types.Scene.arp_experimental_mode
del bpy.types.Scene.arp_disable_smart_fx
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
#######################################################
## Reset functions for internal usage (Match to Rig)
## resets all controllers transforms (pose mode)
#######################################################
import bpy
def reset_all():
rig = bpy.context.active_object
def set_inverse_child(cns):
# direct inverse matrix method
if cns.subtarget != '':
if rig.data.bones.get(cns.subtarget):
cns.inverse_matrix = rig.pose.bones[cns.subtarget].matrix.inverted()
else:
print("Child Of constraint could not be reset, bone does not exist:", '"'+cns.subtarget+'" from', cns.name)
# Reset transforms------------------------------
bpy.ops.pose.select_all(action='SELECT')
bpy.ops.pose.loc_clear()
bpy.ops.pose.rot_clear()
# "scale clear" leads to resetting bbones_easeout/in value, we need to preserve them
bdict = {}
for b in rig.pose.bones:
bdict[b.name] = [b.bbone_easein, b.bbone_easeout]
bpy.ops.pose.scale_clear()
for bname in bdict:
pbone = rig.pose.bones[bname]
pbone.bbone_easein, pbone.bbone_easeout = bdict[bname]
for pbone in rig.pose.bones:
# Reset locked transforms
for i, rot in enumerate(pbone.rotation_euler):
if pbone.lock_rotation[i]:
pbone.rotation_euler[i] = 0.0
# Reset Properties
if len(pbone.keys()):
try:# Error in some rare cases > Error RuntimeError: IDPropertyGroup changed size during iteration
for key in pbone.keys():
if key == 'ik_fk_switch':
try:
pbone['ik_fk_switch'] = get_prop_setting(pbone, 'ik_fk_switch', 'default')
except:
if 'hand' in pbone.name:
pbone['ik_fk_switch'] = 1.0
else:
pbone['ik_fk_switch'] = 0.0
if key == 'stretch_length':
pbone[key] = 1.0
# don't set auto-stretch to 1 for now, it's not compatible with Fbx export
if key == 'leg_pin':
pbone[key] = 0.0
if key == 'elbow_min':
pbone[key] = 0.0
if key == 'bend_all':
pbone[key] = 0.0
except:
pass
reset_child_of_bones = {'c_leg_pole':'startswith', 'c_arms_pole':'startswith', 'hand':'in', 'foot':'in', 'head':'in', 'c_thumb':'startswith', 'c_index':'startswith', 'c_middle':'startswith', 'c_ring':'startswith', 'c_pinky':'startswith', 'c_eye_target':'startswith', 'c_toes_':'startswith'}
valid = False
if not 'cc' in pbone.keys():# do not set inverse for custom bones
for bname in reset_child_of_bones:
type = reset_child_of_bones[bname]
if type == 'startswith':
if pbone.name.startswith(bname):
valid = True
elif type == 'in':
if bname in pbone.name:
valid = True
if valid:
for cns in pbone.constraints:
if cns.type == 'CHILD_OF':
set_inverse_child(cns)
bpy.ops.pose.select_all(action='DESELECT')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,345 @@
if "bpy" in locals():
import importlib
if "export_fbx_bin" in locals():
importlib.reload(export_fbx_bin)
import bpy
import addon_utils, sys
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
EnumProperty,
)
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
orientation_helper,
path_reference_mode,
axis_conversion,
)
@orientation_helper(axis_forward='-Z', axis_up='Y')
class ARP_OT_export_fbx_wrap(bpy.types.Operator, ExportHelper):
"""Write a FBX file"""
bl_idname = "arp_export_scene.fbx"
bl_label = "Export ARP FBX"
bl_options = {'UNDO', 'PRESET'}
filename_ext = ".fbx"
filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
ui_tab: EnumProperty(
items=(('MAIN', "Main", "Main basic settings"),
('GEOMETRY', "Geometries", "Geometry-related settings"),
('ARMATURE', "Armatures", "Armature-related settings"),
('ANIMATION', "Animation", "Animation-related settings"),
),
name="ui_tab",
description="Export options categories",
)
use_selection: BoolProperty(
name="Selected Objects",
description="Export selected and visible objects only",
default=False,
)
use_active_collection: BoolProperty(
name="Active Collection",
description="Export only objects from the active collection (and its children)",
default=False,
)
global_scale: FloatProperty(
name="Scale",
description="Scale all data (Some importers do not support scaled armatures!)",
min=0.001, max=1000.0,
soft_min=0.01, soft_max=1000.0,
default=1.0,
)
apply_unit_scale: BoolProperty(
name="Apply Unit",
description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)",
default=True,
)
apply_scale_options: EnumProperty(
items=(('FBX_SCALE_NONE', "All Local",
"Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"),
('FBX_SCALE_UNITS', "FBX Units Scale",
"Apply custom scaling to each object transformation, and units scaling to FBX scale"),
('FBX_SCALE_CUSTOM', "FBX Custom Scale",
"Apply custom scaling to FBX scale, and units scaling to each object transformation"),
('FBX_SCALE_ALL', "FBX All",
"Apply custom scaling and units scaling to FBX scale"),
),
name="Apply Scalings",
description="How to apply custom and units scalings in generated FBX file "
"(Blender uses FBX scale to detect units on import, "
"but many other applications do not handle the same way)",
)
use_space_transform: BoolProperty(
name="Use Space Transform",
description="Apply global space transform to the object rotations. When disabled "
"only the axis space is written to the file and all object transforms are left as-is",
default=True,
)
bake_space_transform: BoolProperty(
name="!EXPERIMENTAL! Apply Transform",
description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
"target space is not aligned with Blender's space "
"(WARNING! experimental option, use at own risks, known broken with armatures/animations)",
default=False,
)
colors_type: EnumProperty(
name="Vertex Colors",
items=(('NONE', "None", "Do not import color attributes"),
('SRGB', "sRGB", "Expect file colors in sRGB color space"),
('LINEAR', "Linear", "Expect file colors in linear color space"),
),
description="Import vertex color attributes",
default='SRGB',
)
prioritize_active_color: BoolProperty(
name="Prioritize Active Color",
description="Make sure active color will be exported first. Could be important "
"since some other software can discard other color attributes besides the first one",
default=False,
)
object_types: EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
('LIGHT', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'},
)
use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply modifiers to mesh objects (except Armature ones) - "
"WARNING: prevents exporting shape keys",
default=True,
)
use_mesh_modifiers_render: BoolProperty(
name="Use Modifiers Render Setting",
description="Use render settings when applying modifiers to mesh objects",
default=True,
)
mesh_smooth_type: EnumProperty(
name="Smoothing",
items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"),
('FACE', "Face", "Write face smoothing"),
('EDGE', "Edge", "Write edge smoothing"),
),
description="Export smoothing information "
"(prefer 'Normals Only' option if your target importer understand split normals)",
default='OFF',
)
use_subsurf: BoolProperty(
name="Export Subdivision Surface",
description="Export the last Catmull-Rom subdivision modifier as FBX subdivision "
"(does not apply the modifier even if 'Apply Modifiers' is enabled)",
default=False,
)
use_mesh_edges: BoolProperty(
name="Loose Edges",
description="Export loose edges (as two-vertices polygons)",
default=False,
)
use_tspace: BoolProperty(
name="Tangent Space",
description="Add binormal and tangent vectors, together with normal they form the tangent space "
"(will only work correctly with tris/quads only meshes!)",
default=False,
)
use_triangles: BoolProperty(
name="Triangulate Faces",
description="Convert all faces to triangles",
default=False,
)
use_custom_props: BoolProperty(
name="Custom Properties",
description="Export custom properties",
default=False,
)
add_leaf_bones: BoolProperty(
name="Add Leaf Bones",
description="Append a final bone to the end of each chain to specify last bone length "
"(use this when you intend to edit the armature from exported data)",
default=True # False for commit!
)
primary_bone_axis: EnumProperty(
name="Primary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
('Z', "Z Axis", ""),
('-X', "-X Axis", ""),
('-Y', "-Y Axis", ""),
('-Z', "-Z Axis", ""),
),
default='Y',
)
secondary_bone_axis: EnumProperty(
name="Secondary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
('Z', "Z Axis", ""),
('-X', "-X Axis", ""),
('-Y', "-Y Axis", ""),
('-Z', "-Z Axis", ""),
),
default='X',
)
use_armature_deform_only: BoolProperty(
name="Only Deform Bones",
description="Only write deforming bones (and non-deforming ones when they have deforming children)",
default=False,
)
armature_nodetype: EnumProperty(
name="Armature FBXNode Type",
items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"),
('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."),
('LIMBNODE', "LimbNode", "'LimbNode' FBX node, a regular joint between two bones..."),
),
description="FBX type of node (object) used to represent Blender's armatures "
"(use Null one unless you experience issues with other app, other choices may no import back "
"perfectly in Blender...)",
default='NULL',
)
bake_anim: BoolProperty(
name="Baked Animation",
description="Export baked keyframe animation",
default=True,
)
bake_anim_use_all_bones: BoolProperty(
name="Key All Bones",
description="Force exporting at least one key of animation for all bones "
"(needed with some target applications, like UE4)",
default=True,
)
bake_anim_use_nla_strips: BoolProperty(
name="NLA Strips",
description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, "
"instead of global scene animation",
default=True,
)
bake_anim_use_all_actions: BoolProperty(
name="All Actions",
description="Export each action as a separated FBX's AnimStack, instead of global scene animation "
"(note that animated objects will get all actions compatible with them, "
"others will get no animation at all)",
default=True,
)
bake_anim_force_startend_keying: BoolProperty(
name="Force Start/End Keying",
description="Always add a keyframe at start and end of actions for animated channels",
default=True,
)
bake_anim_force_startend_keying_sk: BoolProperty(
name="Force Start/End Keying for Shape Keys",
description="Always add a keyframe at start and end of actions for animated channels",
default=False,
)
bake_anim_step: FloatProperty(
name="Sampling Rate",
description="How often to evaluate animated values (in frames)",
min=0.01, max=100.0,
soft_min=0.1, soft_max=10.0,
default=1.0,
)
bake_anim_simplify_factor: FloatProperty(
name="Simplify",
description="How much to simplify baked values (0.0 to disable, the higher the more simplified)",
min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance.
soft_min=0.0, soft_max=10.0,
default=1.0, # default: min slope: 0.005, max frame step: 10.
)
path_mode: path_reference_mode
embed_textures: BoolProperty(
name="Embed Textures",
description="Embed textures in FBX binary file (only for \"Copy\" path mode!)",
default=False,
)
batch_mode: EnumProperty(
name="Batch Mode",
items=(('OFF', "Off", "Active scene to file"),
('SCENE', "Scene", "Each scene as a file"),
('COLLECTION', "Collection",
"Each collection (data-block ones) as a file, does not include content of children collections"),
('SCENE_COLLECTION', "Scene Collections",
"Each collection (including master, non-data-block ones) of each scene as a file, "
"including content from children collections"),
('ACTIVE_SCENE_COLLECTION', "Active Scene Collections",
"Each collection (including master, non-data-block one) of the active scene as a file, "
"including content from children collections"),
),
)
use_batch_own_dir: BoolProperty(
name="Batch Own Dir",
description="Create a dir for each exported file",
default=True,
)
use_metadata: BoolProperty(
name="Use Metadata",
default=True,
options={'HIDDEN'},
)
#humanoid_actions: BoolProperty(name="Humanoid Actions Only", default=True)
shape_keys_baked_data: StringProperty(name="sk data", default="")
mesh_names_data: StringProperty(name="mesh names", default="")
export_action_only: StringProperty(name="", default="")
@property
def check_extension(self):
return self.batch_mode == 'OFF'
def execute(self, context):
from mathutils import Matrix
if not self.filepath:
raise Exception("filepath not set")
global_matrix = (axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4()
if self.use_space_transform else Matrix())
keywords = self.as_keywords(ignore=("check_existing",
"filter_glob",
"ui_tab",
))
keywords["global_matrix"] = global_matrix
from . import export_fbx_bin
return export_fbx_bin.arp_save(self, context, **keywords)
def register():
if bpy.app.version >= (4,1,0):
from bpy.utils import register_class
try:
register_class(ARP_OT_export_fbx_wrap)
except:
pass
def unregister():
if bpy.app.version >= (4,1,0):
from bpy.utils import unregister_class
try:
unregister_class(ARP_OT_export_fbx_wrap)
except:
pass
@@ -0,0 +1,62 @@
# SPDX-FileCopyrightText: 2006-2012 assimp team
# SPDX-FileCopyrightText: 2013 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
BOOL = b'B'[0]
CHAR = b'C'[0]
INT8 = b'Z'[0]
INT16 = b'Y'[0]
INT32 = b'I'[0]
INT64 = b'L'[0]
FLOAT32 = b'F'[0]
FLOAT64 = b'D'[0]
BYTES = b'R'[0]
STRING = b'S'[0]
INT32_ARRAY = b'i'[0]
INT64_ARRAY = b'l'[0]
FLOAT32_ARRAY = b'f'[0]
FLOAT64_ARRAY = b'd'[0]
BOOL_ARRAY = b'b'[0]
BYTE_ARRAY = b'c'[0]
# Some other misc defines
# Known combinations so far - supposed meaning: A = animatable, A+ = animated, U = UserProp
# VALID_NUMBER_FLAGS = {b'A', b'A+', b'AU', b'A+U'} # Not used...
# array types - actual length may vary (depending on underlying C implementation)!
import array
# For now, bytes and bool are assumed always 1byte.
ARRAY_BOOL = 'b'
ARRAY_BYTE = 'B'
ARRAY_INT32 = None
ARRAY_INT64 = None
for _t in 'ilq':
size = array.array(_t).itemsize
if size == 4:
ARRAY_INT32 = _t
elif size == 8:
ARRAY_INT64 = _t
if ARRAY_INT32 and ARRAY_INT64:
break
if not ARRAY_INT32:
raise Exception("Impossible to get a 4-bytes integer type for array!")
if not ARRAY_INT64:
raise Exception("Impossible to get an 8-bytes integer type for array!")
ARRAY_FLOAT32 = None
ARRAY_FLOAT64 = None
for _t in 'fd':
size = array.array(_t).itemsize
if size == 4:
ARRAY_FLOAT32 = _t
elif size == 8:
ARRAY_FLOAT64 = _t
if ARRAY_FLOAT32 and ARRAY_FLOAT64:
break
if not ARRAY_FLOAT32:
raise Exception("Impossible to get a 4-bytes float type for array!")
if not ARRAY_FLOAT64:
raise Exception("Impossible to get an 8-bytes float type for array!")
@@ -0,0 +1,434 @@
# SPDX-FileCopyrightText: 2013 Campbell Barton
#
# SPDX-License-Identifier: GPL-2.0-or-later
try:
from . import data_types
from .fbx_utils_threading import MultiThreadedTaskConsumer
except:
import data_types
from fbx_utils_threading import MultiThreadedTaskConsumer
from struct import pack
from contextlib import contextmanager
import array
import numpy as np
import zlib
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
_ELEM_META_FORMAT = ...
_ELEM_META_SIZE = ...
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
# fbx has very strict CRC rules, all based on file timestamp
# until we figure these out, write files at a fixed time. (workaround!)
# Assumes: CreationTime
_TIME_ID = b'1970-01-01 10:00:00:000'
_FILE_ID = b'\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1'
_FOOT_ID = b'\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e'
# Awful exceptions: those "classes" of elements seem to need block sentinel even when having no children and some props.
_ELEMS_ID_ALWAYS_BLOCK_SENTINEL = {b"AnimationStack", b"AnimationLayer"}
class FBXElem:
__slots__ = (
"id",
"props",
"props_type",
"elems",
"_props_length", # combine length of props
"_end_offset", # byte offset from the start of the file.
)
def __init__(self, id):
assert(len(id) < 256) # length must fit in a uint8
self.id = id
self.props = []
self.props_type = bytearray()
self.elems = []
self._end_offset = -1
self._props_length = -1
@classmethod
@contextmanager
def enable_multithreading_cm(cls):
"""Temporarily enable multithreaded array compression.
The context manager handles starting up and shutting down the threads.
Only exits once all the threads are done (either all tasks were completed or an error occurred and the threads
were stopped prematurely).
Writing to a file is temporarily disabled as a safeguard."""
# __enter__()
orig_func = cls._add_compressed_array_helper
orig_write = cls._write
def insert_compressed_array(props, insert_at, data, length):
# zlib.compress releases the GIL, so can be multithreaded.
data = zlib.compress(data, 1)
comp_len = len(data)
encoding = 1
data = pack('<3I', length, encoding, comp_len) + data
props[insert_at] = data
with MultiThreadedTaskConsumer.new_cpu_bound_cm(insert_compressed_array) as wrapped_func:
try:
def _add_compressed_array_helper_multi(self, data, length):
# Append a dummy value that will be replaced with the compressed array data later.
self.props.append(...)
# The index to insert the compressed array into.
insert_at = len(self.props) - 1
# Schedule the array to be compressed on a separate thread and then inserted into the hierarchy at
# `insert_at`.
wrapped_func(self.props, insert_at, data, length)
# As an extra safeguard, temporarily replace the `_write` function to raise an error if called.
def temp_write(*_args, **_kwargs):
raise RuntimeError("Writing is not allowed until multithreaded array compression has been disabled")
cls._add_compressed_array_helper = _add_compressed_array_helper_multi
cls._write = temp_write
# Return control back to the caller of __enter__().
yield
finally:
# __exit__()
# Restore the original functions.
cls._add_compressed_array_helper = orig_func
cls._write = orig_write
# Exiting the MultiThreadedTaskConsumer context manager will wait for all scheduled tasks to complete.
def add_bool(self, data):
assert(isinstance(data, bool))
data = pack('?', data)
self.props_type.append(data_types.BOOL)
self.props.append(data)
def add_char(self, data):
assert(isinstance(data, bytes))
assert(len(data) == 1)
data = pack('<c', data)
self.props_type.append(data_types.CHAR)
self.props.append(data)
def add_int8(self, data):
assert(isinstance(data, int))
data = pack('<b', data)
self.props_type.append(data_types.INT8)
self.props.append(data)
def add_int16(self, data):
assert(isinstance(data, int))
data = pack('<h', data)
self.props_type.append(data_types.INT16)
self.props.append(data)
def add_int32(self, data):
assert(isinstance(data, int))
data = pack('<i', data)
self.props_type.append(data_types.INT32)
self.props.append(data)
def add_int64(self, data):
assert(isinstance(data, int))
data = pack('<q', data)
self.props_type.append(data_types.INT64)
self.props.append(data)
def add_float32(self, data):
assert(isinstance(data, float))
data = pack('<f', data)
self.props_type.append(data_types.FLOAT32)
self.props.append(data)
def add_float64(self, data):
assert(isinstance(data, float))
data = pack('<d', data)
self.props_type.append(data_types.FLOAT64)
self.props.append(data)
def add_bytes(self, data):
assert(isinstance(data, bytes))
data = pack('<I', len(data)) + data
self.props_type.append(data_types.BYTES)
self.props.append(data)
def add_string(self, data):
assert(isinstance(data, bytes))
data = pack('<I', len(data)) + data
self.props_type.append(data_types.STRING)
self.props.append(data)
def add_string_unicode(self, data):
assert(isinstance(data, str))
data = data.encode('utf8')
data = pack('<I', len(data)) + data
self.props_type.append(data_types.STRING)
self.props.append(data)
def _add_compressed_array_helper(self, data, length):
"""Note: This function may be swapped out by enable_multithreading_cm with an equivalent that supports
multithreading."""
data = zlib.compress(data, 1)
comp_len = len(data)
encoding = 1
data = pack('<3I', length, encoding, comp_len) + data
self.props.append(data)
def _add_array_helper(self, data, prop_type, length):
self.props_type.append(prop_type)
# mimic behavior of fbxconverter (also common sense)
# we could make this configurable.
encoding = 0 if len(data) <= 128 else 1
if encoding == 0:
data = pack('<3I', length, encoding, len(data)) + data
self.props.append(data)
elif encoding == 1:
self._add_compressed_array_helper(data, length)
def _add_parray_helper(self, data, array_type, prop_type):
assert (isinstance(data, array.array))
assert (data.typecode == array_type)
length = len(data)
if _IS_BIG_ENDIAN:
data = data[:]
data.byteswap()
data = data.tobytes()
self._add_array_helper(data, prop_type, length)
def _add_ndarray_helper(self, data, dtype, prop_type):
assert (isinstance(data, np.ndarray))
assert (data.dtype == dtype)
length = data.size
if _IS_BIG_ENDIAN and data.dtype.isnative:
data = data.byteswap()
data = data.tobytes()
self._add_array_helper(data, prop_type, length)
def add_int32_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.int32, data_types.INT32_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_INT32, data)
self._add_parray_helper(data, data_types.ARRAY_INT32, data_types.INT32_ARRAY)
def add_int64_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.int64, data_types.INT64_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_INT64, data)
self._add_parray_helper(data, data_types.ARRAY_INT64, data_types.INT64_ARRAY)
def add_float32_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.float32, data_types.FLOAT32_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_FLOAT32, data)
self._add_parray_helper(data, data_types.ARRAY_FLOAT32, data_types.FLOAT32_ARRAY)
def add_float64_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.float64, data_types.FLOAT64_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_FLOAT64, data)
self._add_parray_helper(data, data_types.ARRAY_FLOAT64, data_types.FLOAT64_ARRAY)
def add_bool_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, bool, data_types.BOOL_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_BOOL, data)
self._add_parray_helper(data, data_types.ARRAY_BOOL, data_types.BOOL_ARRAY)
def add_byte_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.byte, data_types.BYTE_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_BYTE, data)
self._add_parray_helper(data, data_types.ARRAY_BYTE, data_types.BYTE_ARRAY)
# -------------------------
# internal helper functions
def _calc_offsets(self, offset, is_last):
"""
Call before writing, calculates fixed offsets.
"""
assert(self._end_offset == -1)
assert(self._props_length == -1)
offset += _ELEM_META_SIZE # 3 uints (or 3 ulonglongs for FBX 7500 and later)
offset += 1 + len(self.id) # len + idname
props_length = 0
for data in self.props:
# 1 byte for the prop type
props_length += 1 + len(data)
self._props_length = props_length
offset += props_length
offset = self._calc_offsets_children(offset, is_last)
self._end_offset = offset
return offset
def _calc_offsets_children(self, offset, is_last):
if self.elems:
elem_last = self.elems[-1]
for elem in self.elems:
offset = elem._calc_offsets(offset, (elem is elem_last))
offset += _BLOCK_SENTINEL_LENGTH
elif (not self.props and not is_last) or self.id in _ELEMS_ID_ALWAYS_BLOCK_SENTINEL:
offset += _BLOCK_SENTINEL_LENGTH
return offset
def _write(self, write, tell, is_last):
assert(self._end_offset != -1)
assert(self._props_length != -1)
write(pack(_ELEM_META_FORMAT, self._end_offset, len(self.props), self._props_length))
write(bytes((len(self.id),)))
write(self.id)
for i, data in enumerate(self.props):
write(bytes((self.props_type[i],)))
write(data)
self._write_children(write, tell, is_last)
if tell() != self._end_offset:
raise IOError("scope length not reached, "
"something is wrong (%d)" % (self._end_offset - tell()))
def _write_children(self, write, tell, is_last):
if self.elems:
elem_last = self.elems[-1]
for elem in self.elems:
assert(elem.id != b'')
elem._write(write, tell, (elem is elem_last))
write(_BLOCK_SENTINEL_DATA)
elif (not self.props and not is_last) or self.id in _ELEMS_ID_ALWAYS_BLOCK_SENTINEL:
write(_BLOCK_SENTINEL_DATA)
def _write_timedate_hack(elem_root):
# perform 2 changes
# - set the FileID
# - set the CreationTime
ok = 0
for elem in elem_root.elems:
if elem.id == b'FileId':
assert(elem.props_type[0] == b'R'[0])
assert(len(elem.props_type) == 1)
elem.props.clear()
elem.props_type.clear()
elem.add_bytes(_FILE_ID)
ok += 1
elif elem.id == b'CreationTime':
assert(elem.props_type[0] == b'S'[0])
assert(len(elem.props_type) == 1)
elem.props.clear()
elem.props_type.clear()
elem.add_string(_TIME_ID)
ok += 1
if ok == 2:
break
if ok != 2:
print("Missing fields!")
# FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
# * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
# * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
def init_version(fbx_version):
global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, _ELEM_META_FORMAT, _ELEM_META_SIZE
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
_ELEM_META_FORMAT = ...
_ELEM_META_SIZE = ...
if fbx_version < 7500:
_ELEM_META_FORMAT = '<3I'
_ELEM_META_SIZE = 12
else:
_ELEM_META_FORMAT = '<3Q'
_ELEM_META_SIZE = 24
_BLOCK_SENTINEL_LENGTH = _ELEM_META_SIZE + 1
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
def write(fn, elem_root, version):
assert(elem_root.id == b'')
with open(fn, 'wb') as f:
write = f.write
tell = f.tell
init_version(version)
write(_HEAD_MAGIC)
write(pack('<I', version))
# hack since we don't decode time.
# ideally we would _not_ modify this data.
_write_timedate_hack(elem_root)
elem_root._calc_offsets_children(tell(), False)
elem_root._write_children(write, tell, False)
write(_FOOT_ID)
write(b'\x00' * 4)
# padding for alignment (values between 1 & 16 observed)
# if already aligned to 16, add a full 16 bytes padding.
ofs = tell()
pad = ((ofs + 15) & ~15) - ofs
if pad == 0:
pad = 16
write(b'\0' * pad)
write(pack('<I', version))
# unknown magic (always the same)
write(b'\0' * 120)
write(b'\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
"""
Usage
=====
fbx2json [FILES]...
This script will write a JSON file for each FBX argument given.
Output
======
The JSON data is formatted into a list of nested lists of 4 items:
``[id, [data, ...], "data_types", [subtree, ...]]``
Where each list may be empty, and the items in
the subtree are formatted the same way.
data_types is a string, aligned with data that spesifies a type
for each property.
The types are as follows:
* 'Z': - INT8
* 'Y': - INT16
* 'C': - BOOL
* 'I': - INT32
* 'F': - FLOAT32
* 'D': - FLOAT64
* 'L': - INT64
* 'R': - BYTES
* 'S': - STRING
* 'f': - FLOAT32_ARRAY
* 'i': - INT32_ARRAY
* 'd': - FLOAT64_ARRAY
* 'l': - INT64_ARRAY
* 'b': - BOOL ARRAY
* 'c': - BYTE ARRAY
Note that key:value pairs aren't used since the id's are not
ensured to be unique.
"""
# ----------------------------------------------------------------------------
# FBX Binary Parser
from struct import unpack
import array
import zlib
# at the end of each nested block, there is a NUL record to indicate
# that the sub-scope exists (i.e. to distinguish between P: and P : {})
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
from collections import namedtuple
FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
del namedtuple
def read_uint(read):
return unpack(b'<I', read(4))[0]
def read_uint64(read):
return unpack(b'<Q', read(8))[0]
def read_ubyte(read):
return unpack(b'B', read(1))[0]
def read_string_ubyte(read):
size = read_ubyte(read)
data = read(size)
return data
def unpack_array(read, array_type, array_stride, array_byteswap):
length = read_uint(read)
encoding = read_uint(read)
comp_len = read_uint(read)
data = read(comp_len)
if encoding == 0:
pass
elif encoding == 1:
data = zlib.decompress(data)
assert(length * array_stride == len(data))
data_array = array.array(array_type, data)
if array_byteswap and _IS_BIG_ENDIAN:
data_array.byteswap()
return data_array
read_data_dict = {
b'Z'[0]: lambda read: unpack(b'<b', read(1))[0], # 8 bit int
b'Y'[0]: lambda read: unpack(b'<h', read(2))[0], # 16 bit int
b'C'[0]: lambda read: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
b'I'[0]: lambda read: unpack(b'<i', read(4))[0], # 32 bit int
b'F'[0]: lambda read: unpack(b'<f', read(4))[0], # 32 bit float
b'D'[0]: lambda read: unpack(b'<d', read(8))[0], # 64 bit float
b'L'[0]: lambda read: unpack(b'<q', read(8))[0], # 64 bit int
b'R'[0]: lambda read: read(read_uint(read)), # binary data
b'S'[0]: lambda read: read(read_uint(read)), # string data
b'f'[0]: lambda read: unpack_array(read, 'f', 4, False), # array (float)
b'i'[0]: lambda read: unpack_array(read, 'i', 4, True), # array (int)
b'd'[0]: lambda read: unpack_array(read, 'd', 8, False), # array (double)
b'l'[0]: lambda read: unpack_array(read, 'q', 8, True), # array (long)
b'b'[0]: lambda read: unpack_array(read, 'b', 1, False), # array (bool)
b'c'[0]: lambda read: unpack_array(read, 'B', 1, False), # array (ubyte)
}
# FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
# * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
# * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
def init_version(fbx_version):
global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, read_fbx_elem_uint
assert(_BLOCK_SENTINEL_LENGTH == ...)
assert(_BLOCK_SENTINEL_DATA == ...)
if fbx_version < 7500:
_BLOCK_SENTINEL_LENGTH = 13
read_fbx_elem_uint = read_uint
else:
_BLOCK_SENTINEL_LENGTH = 25
read_fbx_elem_uint = read_uint64
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
def read_elem(read, tell, use_namedtuple):
# [0] the offset at which this block ends
# [1] the number of properties in the scope
# [2] the length of the property list
end_offset = read_fbx_elem_uint(read)
if end_offset == 0:
return None
prop_count = read_fbx_elem_uint(read)
prop_length = read_fbx_elem_uint(read)
elem_id = read_string_ubyte(read) # elem name of the scope/key
elem_props_type = bytearray(prop_count) # elem property types
elem_props_data = [None] * prop_count # elem properties (if any)
elem_subtree = [] # elem children (if any)
for i in range(prop_count):
data_type = read(1)[0]
elem_props_data[i] = read_data_dict[data_type](read)
elem_props_type[i] = data_type
if tell() < end_offset:
while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
elem_subtree.append(read_elem(read, tell, use_namedtuple))
if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
raise IOError("failed to read nested block sentinel, "
"expected all bytes to be 0")
if tell() != end_offset:
raise IOError("scope length not reached, something is wrong")
args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
return FBXElem(*args) if use_namedtuple else args
def parse_version(fn):
"""
Return the FBX version,
if the file isn't a binary FBX return zero.
"""
with open(fn, 'rb') as f:
read = f.read
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
return 0
return read_uint(read)
def parse(fn, use_namedtuple=True):
root_elems = []
with open(fn, 'rb') as f:
read = f.read
tell = f.tell
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
raise IOError("Invalid header")
fbx_version = read_uint(read)
init_version(fbx_version)
while True:
elem = read_elem(read, tell, use_namedtuple)
if elem is None:
break
root_elems.append(elem)
args = (b'', [], bytearray(0), root_elems)
return FBXElem(*args) if use_namedtuple else args, fbx_version
# ----------------------------------------------------------------------------
# Inline Modules
# pyfbx.data_types
data_types = type(array)("data_types")
data_types.__dict__.update(
dict(
INT8 = b'Z'[0],
INT16 = b'Y'[0],
BOOL = b'C'[0],
INT32 = b'I'[0],
FLOAT32 = b'F'[0],
FLOAT64 = b'D'[0],
INT64 = b'L'[0],
BYTES = b'R'[0],
STRING = b'S'[0],
FLOAT32_ARRAY = b'f'[0],
INT32_ARRAY = b'i'[0],
FLOAT64_ARRAY = b'd'[0],
INT64_ARRAY = b'l'[0],
BOOL_ARRAY = b'b'[0],
BYTE_ARRAY = b'c'[0],
))
# pyfbx.parse_bin
parse_bin = type(array)("parse_bin")
parse_bin.__dict__.update(
dict(
parse = parse
))
# ----------------------------------------------------------------------------
# JSON Converter
# from pyfbx import parse_bin, data_types
import json
import array
def fbx2json_property_as_string(prop, prop_type):
if prop_type == data_types.STRING:
prop_str = prop.decode('utf-8')
prop_str = prop_str.replace('\x00\x01', '::')
return json.dumps(prop_str)
else:
prop_py_type = type(prop)
if prop_py_type == bytes:
return json.dumps(repr(prop)[2:-1])
elif prop_py_type == bool:
return json.dumps(prop)
elif prop_py_type == array.array:
return repr(list(prop))
return repr(prop)
def fbx2json_properties_as_string(fbx_elem):
return ", ".join(fbx2json_property_as_string(*prop_item)
for prop_item in zip(fbx_elem.props,
fbx_elem.props_type))
def fbx2json_recurse(fw, fbx_elem, ident, is_last):
fbx_elem_id = fbx_elem.id.decode('utf-8')
fw('%s["%s", ' % (ident, fbx_elem_id))
fw('[%s], ' % fbx2json_properties_as_string(fbx_elem))
fw('"%s", ' % (fbx_elem.props_type.decode('ascii')))
fw('[')
if fbx_elem.elems:
fw('\n')
ident_sub = ident + " "
for fbx_elem_sub in fbx_elem.elems:
fbx2json_recurse(fw, fbx_elem_sub, ident_sub,
fbx_elem_sub is fbx_elem.elems[-1])
fw(']')
fw(']%s' % ('' if is_last else ',\n'))
def fbx2json(fn):
import os
fn_json = "%s.json" % os.path.splitext(fn)[0]
print("Writing: %r " % fn_json, end="")
fbx_root_elem, fbx_version = parse(fn, use_namedtuple=True)
print("(Version %d) ..." % fbx_version)
with open(fn_json, 'w', encoding="ascii", errors='xmlcharrefreplace') as f:
fw = f.write
fw('[\n')
ident_sub = " "
for fbx_elem_sub in fbx_root_elem.elems:
fbx2json_recurse(f.write, fbx_elem_sub, ident_sub,
fbx_elem_sub is fbx_root_elem.elems[-1])
fw(']\n')
# ----------------------------------------------------------------------------
# Command Line
def main():
import sys
if "--help" in sys.argv:
print(__doc__)
return
for arg in sys.argv[1:]:
try:
fbx2json(arg)
except:
print("Failed to convert %r, error:" % arg)
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,194 @@
# SPDX-FileCopyrightText: 2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
from contextlib import contextmanager, nullcontext
import os
from queue import SimpleQueue
# Note: `bpy` cannot be imported here because this module is also used by the fbx2json.py and json2fbx.py scripts.
# For debugging/profiling purposes, can be modified at runtime to force single-threaded execution.
_MULTITHREADING_ENABLED = True
# The concurrent.futures module may not work or may not be available on WebAssembly platforms wasm32-emscripten and
# wasm32-wasi.
try:
from concurrent.futures import ThreadPoolExecutor
except ModuleNotFoundError:
_MULTITHREADING_ENABLED = False
ThreadPoolExecutor = None
else:
try:
# The module may be available, but not be fully functional. An error may be raised when attempting to start a
# new thread.
with ThreadPoolExecutor() as tpe:
# Attempt to start a thread by submitting a callable.
tpe.submit(lambda: None)
except Exception:
# Assume that multithreading is not supported and fall back to single-threaded execution.
_MULTITHREADING_ENABLED = False
def get_cpu_count():
"""Get the number of cpus assigned to the current process if that information is available on this system.
If not available, get the total number of cpus.
If the cpu count is indeterminable, it is assumed that there is only 1 cpu available."""
sched_getaffinity = getattr(os, "sched_getaffinity", None)
if sched_getaffinity is not None:
# Return the number of cpus assigned to the current process.
return len(sched_getaffinity(0))
count = os.cpu_count()
return count if count is not None else 1
class MultiThreadedTaskConsumer:
"""Helper class that encapsulates everything needed to run a function on separate threads, with a single-threaded
fallback if multithreading is not available.
Lower overhead than typical use of ThreadPoolExecutor because no Future objects are returned, which makes this class
more suitable to running many smaller tasks.
As with any threaded parallelization, because of Python's Global Interpreter Lock, only one thread can execute
Python code at a time, so threaded parallelization is only useful when the functions used release the GIL, such as
many IO related functions."""
# A special task value used to signal task consumer threads to shut down.
_SHUT_DOWN_THREADS = object()
__slots__ = ("_consumer_function", "_shared_task_queue", "_task_consumer_futures", "_executor",
"_max_consumer_threads", "_shutting_down", "_max_queue_per_consumer")
def __init__(self, consumer_function, max_consumer_threads, max_queue_per_consumer=5):
# It's recommended to use MultiThreadedTaskConsumer.new_cpu_bound_cm() instead of creating new instances
# directly.
# __init__ should only be called after checking _MULTITHREADING_ENABLED.
assert(_MULTITHREADING_ENABLED)
# The function that will be called on separate threads to consume tasks.
self._consumer_function = consumer_function
# All the threads share a single queue. This is a simplistic approach, but it is unlikely to be problematic
# unless the main thread is expected to wait a long time for the consumer threads to finish.
self._shared_task_queue = SimpleQueue()
# Reference to each thread is kept through the returned Future objects. This is used as part of determining when
# new threads should be started and is used to be able to receive and handle exceptions from the threads.
self._task_consumer_futures = []
# Create the executor.
self._executor = ThreadPoolExecutor(max_workers=max_consumer_threads)
# Technically the max workers of the executor is accessible through its `._max_workers`, but since it's private,
# meaning it could be changed without warning, we'll store the max workers/consumers ourselves.
self._max_consumer_threads = max_consumer_threads
# The maximum task queue size (before another consumer thread is started) increases by this amount with every
# additional consumer thread.
self._max_queue_per_consumer = max_queue_per_consumer
# When shutting down the threads, this is set to True as an extra safeguard to prevent new tasks being
# scheduled.
self._shutting_down = False
@classmethod
def new_cpu_bound_cm(cls, consumer_function, other_cpu_bound_threads_in_use=1, hard_max_threads=32):
"""Return a context manager that, when entered, returns a wrapper around `consumer_function` that schedules
`consumer_function` to be run on a separate thread.
If the system can't use multithreading, then the context manager's returned function will instead be the input
`consumer_function` argument, causing tasks to be run immediately on the calling thread.
When exiting the context manager, it waits for all scheduled tasks to complete and prevents the creation of new
tasks, similar to calling ThreadPoolExecutor.shutdown(). For these reasons, the wrapped function should only be
called from the thread that entered the context manager, otherwise there is no guarantee that all tasks will get
scheduled before the context manager exits.
Any task that fails with an exception will cause all task consumer threads to stop.
The maximum number of threads used matches the number of cpus available up to a maximum of `hard_max_threads`.
`hard_max_threads`'s default of 32 matches ThreadPoolExecutor's default behaviour.
The maximum number of threads used is decreased by `other_cpu_bound_threads_in_use`. Defaulting to `1`, assuming
that the calling thread will also be doing CPU-bound work.
Most IO-bound tasks can probably use a ThreadPoolExecutor directly instead because there will typically be fewer
tasks and, on average, each individual task will take longer.
If needed, `cls.new_cpu_bound_cm(consumer_function, -4)` could be suitable for lots of small IO-bound tasks,
because it ensures a minimum of 5 threads, like the default ThreadPoolExecutor."""
if _MULTITHREADING_ENABLED:
max_threads = get_cpu_count() - other_cpu_bound_threads_in_use
max_threads = min(max_threads, hard_max_threads)
if max_threads > 0:
return cls(consumer_function, max_threads)._wrap_executor_cm()
# Fall back to single-threaded.
return nullcontext(consumer_function)
def _task_consumer_callable(self):
"""Callable that is run by each task consumer thread.
Signals the other task consumer threads to stop when stopped intentionally or when an exception occurs."""
try:
while True:
# Blocks until it can get a task.
task_args = self._shared_task_queue.get()
if task_args is self._SHUT_DOWN_THREADS:
# This special value signals that it's time for all the threads to stop.
break
else:
# Call the task consumer function.
self._consumer_function(*task_args)
finally:
# Either the thread has been told to shut down because it received _SHUT_DOWN_THREADS or an exception has
# occurred.
# Add _SHUT_DOWN_THREADS to the queue so that the other consumer threads will also shut down.
self._shared_task_queue.put(self._SHUT_DOWN_THREADS)
def _schedule_task(self, *args):
"""Task consumer threads are only started as tasks are added.
To mitigate starting lots of threads if many tasks are scheduled in quick succession, new threads are only
started if the number of queued tasks grows too large.
This function is a slight misuse of ThreadPoolExecutor. Normally each task to be scheduled would be submitted
through ThreadPoolExecutor.submit, but doing so is noticeably slower for small tasks. We could start new Thread
instances manually without using ThreadPoolExecutor, but ThreadPoolExecutor gives us a higher level API for
waiting for threads to finish and handling exceptions without having to implement an API using Thread ourselves.
"""
if self._shutting_down:
# Shouldn't occur through normal usage.
raise RuntimeError("Cannot schedule new tasks after shutdown")
# Schedule the task by adding it to the task queue.
self._shared_task_queue.put(args)
# Check if more consumer threads need to be added to account for the rate at which tasks are being scheduled
# compared to the rate at which tasks are being consumed.
current_consumer_count = len(self._task_consumer_futures)
if current_consumer_count < self._max_consumer_threads:
# The max queue size increases as new threads are added, otherwise, by the time the next task is added, it's
# likely that the queue size will still be over the max, causing another new thread to be added immediately.
# Increasing the max queue size whenever a new thread is started gives some time for the new thread to start
# up and begin consuming tasks before it's determined that another thread is needed.
max_queue_size_for_current_consumers = self._max_queue_per_consumer * current_consumer_count
if self._shared_task_queue.qsize() > max_queue_size_for_current_consumers:
# Add a new consumer thread because the queue has grown too large.
self._task_consumer_futures.append(self._executor.submit(self._task_consumer_callable))
@contextmanager
def _wrap_executor_cm(self):
"""Wrap the executor's context manager to instead return self._schedule_task and such that the threads
automatically start shutting down before the executor itself starts shutting down."""
# .__enter__()
# Exiting the context manager of the executor will wait for all threads to finish and prevent new
# threads from being created, as if its shutdown() method had been called.
with self._executor:
try:
yield self._schedule_task
finally:
# .__exit__()
self._shutting_down = True
# Signal all consumer threads to finish up and shut down so that the executor can shut down.
# When this is run on the same thread that schedules new tasks, this guarantees that no more tasks will
# be scheduled after the consumer threads start to shut down.
self._shared_task_queue.put(self._SHUT_DOWN_THREADS)
# Because `self._executor` was entered with a context manager, it will wait for all the consumer threads
# to finish even if we propagate an exception from one of the threads here.
for future in self._task_consumer_futures:
# .exception() waits for the future to finish and returns its raised exception or None.
ex = future.exception()
if ex is not None:
# If one of the threads raised an exception, propagate it to the main thread.
# Only the first exception will be propagated if there were multiple.
raise ex
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2014 Blender Foundation
"""
Usage
=====
json2fbx [FILES]...
This script will write a binary FBX file for each JSON argument given.
Input
======
The JSON data is formatted into a list of nested lists of 4 items:
``[id, [data, ...], "data_types", [subtree, ...]]``
Where each list may be empty, and the items in
the subtree are formatted the same way.
data_types is a string, aligned with data that spesifies a type
for each property.
The types are as follows:
* 'Z': - INT8
* 'Y': - INT16
* 'C': - BOOL
* 'I': - INT32
* 'F': - FLOAT32
* 'D': - FLOAT64
* 'L': - INT64
* 'R': - BYTES
* 'S': - STRING
* 'f': - FLOAT32_ARRAY
* 'i': - INT32_ARRAY
* 'd': - FLOAT64_ARRAY
* 'l': - INT64_ARRAY
* 'b': - BOOL ARRAY
* 'c': - BYTE ARRAY
Note that key:value pairs aren't used since the id's are not
ensured to be unique.
"""
def elem_empty(elem, name):
import encode_bin
sub_elem = encode_bin.FBXElem(name)
if elem is not None:
elem.elems.append(sub_elem)
return sub_elem
def parse_json_rec(fbx_root, json_node):
name, data, data_types, children = json_node
ver = 0
assert(len(data_types) == len(data))
e = elem_empty(fbx_root, name.encode())
for d, dt in zip(data, data_types):
if dt == "C":
e.add_bool(d)
elif dt == "Z":
e.add_int8(d)
elif dt == "Y":
e.add_int16(d)
elif dt == "I":
e.add_int32(d)
elif dt == "L":
e.add_int64(d)
elif dt == "F":
e.add_float32(d)
elif dt == "D":
e.add_float64(d)
elif dt == "R":
d = eval('b"""' + d + '"""')
e.add_bytes(d)
elif dt == "S":
d = d.encode().replace(b"::", b"\x00\x01")
e.add_string(d)
elif dt == "i":
e.add_int32_array(d)
elif dt == "l":
e.add_int64_array(d)
elif dt == "f":
e.add_float32_array(d)
elif dt == "d":
e.add_float64_array(d)
elif dt == "b":
e.add_bool_array(d)
elif dt == "c":
e.add_byte_array(d)
if name == "FBXVersion":
assert(data_types == "I")
ver = int(data[0])
for child in children:
_ver = parse_json_rec(e, child)
if _ver:
ver = _ver
return ver
def parse_json(json_root):
root = elem_empty(None, b"")
ver = 0
for n in json_root:
_ver = parse_json_rec(root, n)
if _ver:
ver = _ver
return root, ver
def json2fbx(fn):
import os
import json
import encode_bin
fn_fbx = "%s.fbx" % os.path.splitext(fn)[0]
print("Writing: %r " % fn_fbx, end="")
json_root = []
with open(fn) as f_json:
json_root = json.load(f_json)
fbx_root, fbx_version = parse_json(json_root)
print("(Version %d) ..." % fbx_version)
encode_bin.write(fn_fbx, fbx_root, fbx_version)
# ----------------------------------------------------------------------------
# Command Line
def main():
import sys
if "--help" in sys.argv:
print(__doc__)
return
for arg in sys.argv[1:]:
try:
json2fbx(arg)
except:
print("Failed to convert %r, error:" % arg)
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
@@ -0,0 +1,194 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
__all__ = (
"parse",
"data_types",
"parse_version",
"FBXElem",
)
from struct import unpack
import array
import zlib
from . import data_types
# at the end of each nested block, there is a NUL record to indicate
# that the sub-scope exists (i.e. to distinguish between P: and P : {})
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
from collections import namedtuple
FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
del namedtuple
def read_uint(read):
return unpack(b'<I', read(4))[0]
def read_uint64(read):
return unpack(b'<Q', read(8))[0]
def read_ubyte(read):
return unpack(b'B', read(1))[0]
def read_string_ubyte(read):
size = read_ubyte(read)
data = read(size)
return data
def unpack_array(read, array_type, array_stride, array_byteswap):
length = read_uint(read)
encoding = read_uint(read)
comp_len = read_uint(read)
data = read(comp_len)
if encoding == 0:
pass
elif encoding == 1:
data = zlib.decompress(data)
assert(length * array_stride == len(data))
data_array = array.array(array_type, data)
if array_byteswap and _IS_BIG_ENDIAN:
data_array.byteswap()
return data_array
read_data_dict = {
b'Y'[0]: lambda read: unpack(b'<h', read(2))[0], # 16 bit int
b'C'[0]: lambda read: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
b'I'[0]: lambda read: unpack(b'<i', read(4))[0], # 32 bit int
b'F'[0]: lambda read: unpack(b'<f', read(4))[0], # 32 bit float
b'D'[0]: lambda read: unpack(b'<d', read(8))[0], # 64 bit float
b'L'[0]: lambda read: unpack(b'<q', read(8))[0], # 64 bit int
b'R'[0]: lambda read: read(read_uint(read)), # binary data
b'S'[0]: lambda read: read(read_uint(read)), # string data
b'f'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT32, 4, False), # array (float)
b'i'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT32, 4, True), # array (int)
b'd'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT64, 8, False), # array (double)
b'l'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT64, 8, True), # array (long)
b'b'[0]: lambda read: unpack_array(read, data_types.ARRAY_BOOL, 1, False), # array (bool)
b'c'[0]: lambda read: unpack_array(read, data_types.ARRAY_BYTE, 1, False), # array (ubyte)
}
# FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
# * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
# * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
def init_version(fbx_version):
global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, read_fbx_elem_uint
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
if fbx_version < 7500:
_BLOCK_SENTINEL_LENGTH = 13
read_fbx_elem_uint = read_uint
else:
_BLOCK_SENTINEL_LENGTH = 25
read_fbx_elem_uint = read_uint64
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
def read_elem(read, tell, use_namedtuple):
# [0] the offset at which this block ends
# [1] the number of properties in the scope
# [2] the length of the property list
end_offset = read_fbx_elem_uint(read)
if end_offset == 0:
return None
prop_count = read_fbx_elem_uint(read)
prop_length = read_fbx_elem_uint(read)
elem_id = read_string_ubyte(read) # elem name of the scope/key
elem_props_type = bytearray(prop_count) # elem property types
elem_props_data = [None] * prop_count # elem properties (if any)
elem_subtree = [] # elem children (if any)
for i in range(prop_count):
data_type = read(1)[0]
elem_props_data[i] = read_data_dict[data_type](read)
elem_props_type[i] = data_type
if tell() < end_offset:
while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
elem_subtree.append(read_elem(read, tell, use_namedtuple))
if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
raise IOError("failed to read nested block sentinel, "
"expected all bytes to be 0")
if tell() != end_offset:
raise IOError("scope length not reached, something is wrong")
args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
return FBXElem(*args) if use_namedtuple else args
def parse_version(fn):
"""
Return the FBX version,
if the file isn't a binary FBX return zero.
"""
with open(fn, 'rb') as f:
read = f.read
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
return 0
return read_uint(read)
def parse(fn, use_namedtuple=True):
root_elems = []
with open(fn, 'rb') as f:
read = f.read
tell = f.tell
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
raise IOError("Invalid header")
fbx_version = read_uint(read)
init_version(fbx_version)
while True:
elem = read_elem(read, tell, use_namedtuple)
if elem is None:
break
root_elems.append(elem)
args = (b'', [], bytearray(0), root_elems)
return FBXElem(*args) if use_namedtuple else args, fbx_version
@@ -0,0 +1,337 @@
if "bpy" in locals():
import importlib
if "export_fbx_bin" in locals():
importlib.reload(export_fbx_bin)
import bpy
import addon_utils, sys
from bpy.props import (
StringProperty,
BoolProperty,
FloatProperty,
EnumProperty,
)
from bpy_extras.io_utils import (
ImportHelper,
ExportHelper,
orientation_helper,
path_reference_mode,
axis_conversion,
)
@orientation_helper(axis_forward='-Z', axis_up='Y')
class ARP_OT_export_fbx_wrap(bpy.types.Operator, ExportHelper):
"""Write a FBX file"""
bl_idname = "arp_export_scene.fbx"
bl_label = "Export ARP FBX"
bl_options = {'UNDO', 'PRESET'}
filename_ext = ".fbx"
filter_glob: StringProperty(default="*.fbx", options={'HIDDEN'})
# List of operator properties, the attributes will be assigned
# to the class instance from the operator settings before calling.
ui_tab: EnumProperty(
items=(('MAIN', "Main", "Main basic settings"),
('GEOMETRY', "Geometries", "Geometry-related settings"),
('ARMATURE', "Armatures", "Armature-related settings"),
('ANIMATION', "Animation", "Animation-related settings"),
),
name="ui_tab",
description="Export options categories",
)
use_selection: BoolProperty(
name="Selected Objects",
description="Export selected and visible objects only",
default=False,
)
use_active_collection: BoolProperty(
name="Active Collection",
description="Export only objects from the active collection (and its children)",
default=False,
)
global_scale: FloatProperty(
name="Scale",
description="Scale all data (Some importers do not support scaled armatures!)",
min=0.001, max=1000.0,
soft_min=0.01, soft_max=1000.0,
default=1.0,
)
apply_unit_scale: BoolProperty(
name="Apply Unit",
description="Take into account current Blender units settings (if unset, raw Blender Units values are used as-is)",
default=True,
)
apply_scale_options: EnumProperty(
items=(('FBX_SCALE_NONE', "All Local",
"Apply custom scaling and units scaling to each object transformation, FBX scale remains at 1.0"),
('FBX_SCALE_UNITS', "FBX Units Scale",
"Apply custom scaling to each object transformation, and units scaling to FBX scale"),
('FBX_SCALE_CUSTOM', "FBX Custom Scale",
"Apply custom scaling to FBX scale, and units scaling to each object transformation"),
('FBX_SCALE_ALL', "FBX All",
"Apply custom scaling and units scaling to FBX scale"),
),
name="Apply Scalings",
description="How to apply custom and units scalings in generated FBX file "
"(Blender uses FBX scale to detect units on import, "
"but many other applications do not handle the same way)",
)
use_space_transform: BoolProperty(
name="Use Space Transform",
description="Apply global space transform to the object rotations. When disabled "
"only the axis space is written to the file and all object transforms are left as-is",
default=True,
)
bake_space_transform: BoolProperty(
name="!EXPERIMENTAL! Apply Transform",
description="Bake space transform into object data, avoids getting unwanted rotations to objects when "
"target space is not aligned with Blender's space "
"(WARNING! experimental option, use at own risks, known broken with armatures/animations)",
default=False,
)
colors_type: EnumProperty(
name="Vertex Colors",
items=(('NONE', "None", "Do not import color attributes"),
('SRGB', "sRGB", "Expect file colors in sRGB color space"),
('LINEAR', "Linear", "Expect file colors in linear color space"),
),
description="Import vertex color attributes",
default='SRGB',
)
prioritize_active_color: BoolProperty(
name="Prioritize Active Color",
description="Make sure active color will be exported first. Could be important "
"since some other software can discard other color attributes besides the first one",
default=False,
)
object_types: EnumProperty(
name="Object Types",
options={'ENUM_FLAG'},
items=(('EMPTY', "Empty", ""),
('CAMERA', "Camera", ""),
('LIGHT', "Lamp", ""),
('ARMATURE', "Armature", "WARNING: not supported in dupli/group instances"),
('MESH', "Mesh", ""),
('OTHER', "Other", "Other geometry types, like curve, metaball, etc. (converted to meshes)"),
),
description="Which kind of object to export",
default={'EMPTY', 'CAMERA', 'LIGHT', 'ARMATURE', 'MESH', 'OTHER'},
)
use_mesh_modifiers: BoolProperty(
name="Apply Modifiers",
description="Apply modifiers to mesh objects (except Armature ones) - "
"WARNING: prevents exporting shape keys",
default=True,
)
use_mesh_modifiers_render: BoolProperty(
name="Use Modifiers Render Setting",
description="Use render settings when applying modifiers to mesh objects",
default=True,
)
mesh_smooth_type: EnumProperty(
name="Smoothing",
items=(('OFF', "Normals Only", "Export only normals instead of writing edge or face smoothing data"),
('FACE', "Face", "Write face smoothing"),
('EDGE', "Edge", "Write edge smoothing"),
),
description="Export smoothing information "
"(prefer 'Normals Only' option if your target importer understand split normals)",
default='OFF',
)
use_subsurf: BoolProperty(
name="Export Subdivision Surface",
description="Export the last Catmull-Rom subdivision modifier as FBX subdivision "
"(does not apply the modifier even if 'Apply Modifiers' is enabled)",
default=False,
)
use_mesh_edges: BoolProperty(
name="Loose Edges",
description="Export loose edges (as two-vertices polygons)",
default=False,
)
use_tspace: BoolProperty(
name="Tangent Space",
description="Add binormal and tangent vectors, together with normal they form the tangent space "
"(will only work correctly with tris/quads only meshes!)",
default=False,
)
use_triangles: BoolProperty(
name="Triangulate Faces",
description="Convert all faces to triangles",
default=False,
)
use_custom_props: BoolProperty(
name="Custom Properties",
description="Export custom properties",
default=False,
)
add_leaf_bones: BoolProperty(
name="Add Leaf Bones",
description="Append a final bone to the end of each chain to specify last bone length "
"(use this when you intend to edit the armature from exported data)",
default=True # False for commit!
)
primary_bone_axis: EnumProperty(
name="Primary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
('Z', "Z Axis", ""),
('-X', "-X Axis", ""),
('-Y', "-Y Axis", ""),
('-Z', "-Z Axis", ""),
),
default='Y',
)
secondary_bone_axis: EnumProperty(
name="Secondary Bone Axis",
items=(('X', "X Axis", ""),
('Y', "Y Axis", ""),
('Z', "Z Axis", ""),
('-X', "-X Axis", ""),
('-Y', "-Y Axis", ""),
('-Z', "-Z Axis", ""),
),
default='X',
)
use_armature_deform_only: BoolProperty(
name="Only Deform Bones",
description="Only write deforming bones (and non-deforming ones when they have deforming children)",
default=False,
)
armature_nodetype: EnumProperty(
name="Armature FBXNode Type",
items=(('NULL', "Null", "'Null' FBX node, similar to Blender's Empty (default)"),
('ROOT', "Root", "'Root' FBX node, supposed to be the root of chains of bones..."),
('LIMBNODE', "LimbNode", "'LimbNode' FBX node, a regular joint between two bones..."),
),
description="FBX type of node (object) used to represent Blender's armatures "
"(use Null one unless you experience issues with other app, other choices may no import back "
"perfectly in Blender...)",
default='NULL',
)
bake_anim: BoolProperty(
name="Baked Animation",
description="Export baked keyframe animation",
default=True,
)
bake_anim_use_all_bones: BoolProperty(
name="Key All Bones",
description="Force exporting at least one key of animation for all bones "
"(needed with some target applications, like UE4)",
default=True,
)
bake_anim_use_nla_strips: BoolProperty(
name="NLA Strips",
description="Export each non-muted NLA strip as a separated FBX's AnimStack, if any, "
"instead of global scene animation",
default=True,
)
bake_anim_use_all_actions: BoolProperty(
name="All Actions",
description="Export each action as a separated FBX's AnimStack, instead of global scene animation "
"(note that animated objects will get all actions compatible with them, "
"others will get no animation at all)",
default=True,
)
bake_anim_force_startend_keying: BoolProperty(
name="Force Start/End Keying",
description="Always add a keyframe at start and end of actions for animated channels",
default=True,
)
bake_anim_step: FloatProperty(
name="Sampling Rate",
description="How often to evaluate animated values (in frames)",
min=0.01, max=100.0,
soft_min=0.1, soft_max=10.0,
default=1.0,
)
bake_anim_simplify_factor: FloatProperty(
name="Simplify",
description="How much to simplify baked values (0.0 to disable, the higher the more simplified)",
min=0.0, max=100.0, # No simplification to up to 10% of current magnitude tolerance.
soft_min=0.0, soft_max=10.0,
default=1.0, # default: min slope: 0.005, max frame step: 10.
)
path_mode: path_reference_mode
embed_textures: BoolProperty(
name="Embed Textures",
description="Embed textures in FBX binary file (only for \"Copy\" path mode!)",
default=False,
)
batch_mode: EnumProperty(
name="Batch Mode",
items=(('OFF', "Off", "Active scene to file"),
('SCENE', "Scene", "Each scene as a file"),
('COLLECTION', "Collection",
"Each collection (data-block ones) as a file, does not include content of children collections"),
('SCENE_COLLECTION', "Scene Collections",
"Each collection (including master, non-data-block ones) of each scene as a file, "
"including content from children collections"),
('ACTIVE_SCENE_COLLECTION', "Active Scene Collections",
"Each collection (including master, non-data-block one) of the active scene as a file, "
"including content from children collections"),
),
)
use_batch_own_dir: BoolProperty(
name="Batch Own Dir",
description="Create a dir for each exported file",
default=True,
)
use_metadata: BoolProperty(
name="Use Metadata",
default=True,
options={'HIDDEN'},
)
#humanoid_actions: BoolProperty(name="Humanoid Actions Only", default=True)
shape_keys_baked_data: StringProperty(name="sk data", default="")
mesh_names_data: StringProperty(name="mesh names", default="")
export_action_only: StringProperty(name="", default="")
@property
def check_extension(self):
return self.batch_mode == 'OFF'
def execute(self, context):
from mathutils import Matrix
if not self.filepath:
raise Exception("filepath not set")
global_matrix = (axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4()
if self.use_space_transform else Matrix())
keywords = self.as_keywords(ignore=("check_existing",
"filter_glob",
"ui_tab",
))
keywords["global_matrix"] = global_matrix
from . import export_fbx_bin
return export_fbx_bin.arp_save(self, context, **keywords)
def register():
if bpy.app.version < (4,1,0):
from bpy.utils import register_class
register_class(ARP_OT_export_fbx_wrap)
def unregister():
if bpy.app.version < (4,1,0):
from bpy.utils import unregister_class
unregister_class(ARP_OT_export_fbx_wrap)
@@ -0,0 +1,61 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
BOOL = b'C'[0]
INT8 = b'Z'[0]
INT16 = b'Y'[0]
INT32 = b'I'[0]
INT64 = b'L'[0]
FLOAT32 = b'F'[0]
FLOAT64 = b'D'[0]
BYTES = b'R'[0]
STRING = b'S'[0]
INT32_ARRAY = b'i'[0]
INT64_ARRAY = b'l'[0]
FLOAT32_ARRAY = b'f'[0]
FLOAT64_ARRAY = b'd'[0]
BOOL_ARRAY = b'b'[0]
BYTE_ARRAY = b'c'[0]
# Some other misc defines
# Known combinations so far - supposed meaning: A = animatable, A+ = animated, U = UserProp
# VALID_NUMBER_FLAGS = {b'A', b'A+', b'AU', b'A+U'} # Not used...
# array types - actual length may vary (depending on underlying C implementation)!
import array
# For now, bytes and bool are assumed always 1byte.
ARRAY_BOOL = 'b'
ARRAY_BYTE = 'B'
ARRAY_INT32 = None
ARRAY_INT64 = None
for _t in 'ilq':
size = array.array(_t).itemsize
if size == 4:
ARRAY_INT32 = _t
elif size == 8:
ARRAY_INT64 = _t
if ARRAY_INT32 and ARRAY_INT64:
break
if not ARRAY_INT32:
raise Exception("Impossible to get a 4-bytes integer type for array!")
if not ARRAY_INT64:
raise Exception("Impossible to get an 8-bytes integer type for array!")
ARRAY_FLOAT32 = None
ARRAY_FLOAT64 = None
for _t in 'fd':
size = array.array(_t).itemsize
if size == 4:
ARRAY_FLOAT32 = _t
elif size == 8:
ARRAY_FLOAT64 = _t
if ARRAY_FLOAT32 and ARRAY_FLOAT64:
break
if not ARRAY_FLOAT32:
raise Exception("Impossible to get a 4-bytes float type for array!")
if not ARRAY_FLOAT64:
raise Exception("Impossible to get an 8-bytes float type for array!")
@@ -0,0 +1,344 @@
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2013 Campbell Barton
try:
from . import data_types
except:
import data_types
from struct import pack
import array
import numpy as np
import zlib
_BLOCK_SENTINEL_LENGTH = 13
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
# fbx has very strict CRC rules, all based on file timestamp
# until we figure these out, write files at a fixed time. (workaround!)
# Assumes: CreationTime
_TIME_ID = b'1970-01-01 10:00:00:000'
_FILE_ID = b'\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1'
_FOOT_ID = b'\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e'
# Awful exceptions: those "classes" of elements seem to need block sentinel even when having no children and some props.
_ELEMS_ID_ALWAYS_BLOCK_SENTINEL = {b"AnimationStack", b"AnimationLayer"}
class FBXElem:
__slots__ = (
"id",
"props",
"props_type",
"elems",
"_props_length", # combine length of props
"_end_offset", # byte offset from the start of the file.
)
def __init__(self, id):
assert(len(id) < 256) # length must fit in a uint8
self.id = id
self.props = []
self.props_type = bytearray()
self.elems = []
self._end_offset = -1
self._props_length = -1
def add_bool(self, data):
assert(isinstance(data, bool))
data = pack('?', data)
self.props_type.append(data_types.BOOL)
self.props.append(data)
def add_int8(self, data):
assert(isinstance(data, int))
data = pack('<b', data)
self.props_type.append(data_types.INT8)
self.props.append(data)
def add_int16(self, data):
assert(isinstance(data, int))
data = pack('<h', data)
self.props_type.append(data_types.INT16)
self.props.append(data)
def add_int32(self, data):
assert(isinstance(data, int))
data = pack('<i', data)
self.props_type.append(data_types.INT32)
self.props.append(data)
def add_int64(self, data):
assert(isinstance(data, int))
data = pack('<q', data)
self.props_type.append(data_types.INT64)
self.props.append(data)
def add_float32(self, data):
assert(isinstance(data, float))
data = pack('<f', data)
self.props_type.append(data_types.FLOAT32)
self.props.append(data)
def add_float64(self, data):
assert(isinstance(data, float))
data = pack('<d', data)
self.props_type.append(data_types.FLOAT64)
self.props.append(data)
def add_bytes(self, data):
assert(isinstance(data, bytes))
data = pack('<I', len(data)) + data
self.props_type.append(data_types.BYTES)
self.props.append(data)
def add_string(self, data):
assert(isinstance(data, bytes))
data = pack('<I', len(data)) + data
self.props_type.append(data_types.STRING)
self.props.append(data)
def add_string_unicode(self, data):
assert(isinstance(data, str))
data = data.encode('utf8')
data = pack('<I', len(data)) + data
self.props_type.append(data_types.STRING)
self.props.append(data)
def _add_array_helper(self, data, prop_type, length):
# mimic behavior of fbxconverter (also common sense)
# we could make this configurable.
encoding = 0 if len(data) <= 128 else 1
if encoding == 0:
pass
elif encoding == 1:
data = zlib.compress(data, 1)
comp_len = len(data)
data = pack('<3I', length, encoding, comp_len) + data
self.props_type.append(prop_type)
self.props.append(data)
def _add_parray_helper(self, data, array_type, prop_type):
assert (isinstance(data, array.array))
assert (data.typecode == array_type)
length = len(data)
if _IS_BIG_ENDIAN:
data = data[:]
data.byteswap()
data = data.tobytes()
self._add_array_helper(data, prop_type, length)
def _add_ndarray_helper(self, data, dtype, prop_type):
assert (isinstance(data, np.ndarray))
assert (data.dtype == dtype)
length = data.size
if _IS_BIG_ENDIAN and data.dtype.isnative:
data = data.byteswap()
data = data.tobytes()
self._add_array_helper(data, prop_type, length)
def add_int32_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.int32, data_types.INT32_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_INT32, data)
self._add_parray_helper(data, data_types.ARRAY_INT32, data_types.INT32_ARRAY)
def add_int64_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.int64, data_types.INT64_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_INT64, data)
self._add_parray_helper(data, data_types.ARRAY_INT64, data_types.INT64_ARRAY)
def add_float32_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.float32, data_types.FLOAT32_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_FLOAT32, data)
self._add_parray_helper(data, data_types.ARRAY_FLOAT32, data_types.FLOAT32_ARRAY)
def add_float64_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.float64, data_types.FLOAT64_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_FLOAT64, data)
self._add_parray_helper(data, data_types.ARRAY_FLOAT64, data_types.FLOAT64_ARRAY)
def add_bool_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, bool, data_types.BOOL_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_BOOL, data)
self._add_parray_helper(data, data_types.ARRAY_BOOL, data_types.BOOL_ARRAY)
def add_byte_array(self, data):
if isinstance(data, np.ndarray):
self._add_ndarray_helper(data, np.byte, data_types.BYTE_ARRAY)
else:
if not isinstance(data, array.array):
data = array.array(data_types.ARRAY_BYTE, data)
self._add_parray_helper(data, data_types.ARRAY_BYTE, data_types.BYTE_ARRAY)
# -------------------------
# internal helper functions
def _calc_offsets(self, offset, is_last):
"""
Call before writing, calculates fixed offsets.
"""
assert(self._end_offset == -1)
assert(self._props_length == -1)
offset += 12 # 3 uints
offset += 1 + len(self.id) # len + idname
props_length = 0
for data in self.props:
# 1 byte for the prop type
props_length += 1 + len(data)
self._props_length = props_length
offset += props_length
offset = self._calc_offsets_children(offset, is_last)
self._end_offset = offset
return offset
def _calc_offsets_children(self, offset, is_last):
if self.elems:
elem_last = self.elems[-1]
for elem in self.elems:
offset = elem._calc_offsets(offset, (elem is elem_last))
offset += _BLOCK_SENTINEL_LENGTH
elif not self.props or self.id in _ELEMS_ID_ALWAYS_BLOCK_SENTINEL:
if not is_last:
offset += _BLOCK_SENTINEL_LENGTH
return offset
def _write(self, write, tell, is_last):
assert(self._end_offset != -1)
assert(self._props_length != -1)
write(pack('<3I', self._end_offset, len(self.props), self._props_length))
write(bytes((len(self.id),)))
write(self.id)
for i, data in enumerate(self.props):
write(bytes((self.props_type[i],)))
write(data)
self._write_children(write, tell, is_last)
if tell() != self._end_offset:
raise IOError("scope length not reached, "
"something is wrong (%d)" % (end_offset - tell()))
def _write_children(self, write, tell, is_last):
if self.elems:
elem_last = self.elems[-1]
for elem in self.elems:
assert(elem.id != b'')
elem._write(write, tell, (elem is elem_last))
write(_BLOCK_SENTINEL_DATA)
elif not self.props or self.id in _ELEMS_ID_ALWAYS_BLOCK_SENTINEL:
if not is_last:
write(_BLOCK_SENTINEL_DATA)
def _write_timedate_hack(elem_root):
# perform 2 changes
# - set the FileID
# - set the CreationTime
ok = 0
for elem in elem_root.elems:
if elem.id == b'FileId':
assert(elem.props_type[0] == b'R'[0])
assert(len(elem.props_type) == 1)
elem.props.clear()
elem.props_type.clear()
elem.add_bytes(_FILE_ID)
ok += 1
elif elem.id == b'CreationTime':
assert(elem.props_type[0] == b'S'[0])
assert(len(elem.props_type) == 1)
elem.props.clear()
elem.props_type.clear()
elem.add_string(_TIME_ID)
ok += 1
if ok == 2:
break
if ok != 2:
print("Missing fields!")
def write(fn, elem_root, version):
assert(elem_root.id == b'')
with open(fn, 'wb') as f:
write = f.write
tell = f.tell
write(_HEAD_MAGIC)
write(pack('<I', version))
# hack since we don't decode time.
# ideally we would _not_ modify this data.
_write_timedate_hack(elem_root)
elem_root._calc_offsets_children(tell(), False)
elem_root._write_children(write, tell, False)
write(_FOOT_ID)
write(b'\x00' * 4)
# padding for alignment (values between 1 & 16 observed)
# if already aligned to 16, add a full 16 bytes padding.
ofs = tell()
pad = ((ofs + 15) & ~15) - ofs
if pad == 0:
pad = 16
write(b'\0' * pad)
write(pack('<I', version))
# unknown magic (always the same)
write(b'\0' * 120)
write(b'\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b')
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
"""
Usage
=====
fbx2json [FILES]...
This script will write a JSON file for each FBX argument given.
Output
======
The JSON data is formatted into a list of nested lists of 4 items:
``[id, [data, ...], "data_types", [subtree, ...]]``
Where each list may be empty, and the items in
the subtree are formatted the same way.
data_types is a string, aligned with data that spesifies a type
for each property.
The types are as follows:
* 'Z': - INT8
* 'Y': - INT16
* 'C': - BOOL
* 'I': - INT32
* 'F': - FLOAT32
* 'D': - FLOAT64
* 'L': - INT64
* 'R': - BYTES
* 'S': - STRING
* 'f': - FLOAT32_ARRAY
* 'i': - INT32_ARRAY
* 'd': - FLOAT64_ARRAY
* 'l': - INT64_ARRAY
* 'b': - BOOL ARRAY
* 'c': - BYTE ARRAY
Note that key:value pairs aren't used since the id's are not
ensured to be unique.
"""
# ----------------------------------------------------------------------------
# FBX Binary Parser
from struct import unpack
import array
import zlib
# at the end of each nested block, there is a NUL record to indicate
# that the sub-scope exists (i.e. to distinguish between P: and P : {})
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
from collections import namedtuple
FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
del namedtuple
def read_uint(read):
return unpack(b'<I', read(4))[0]
def read_uint64(read):
return unpack(b'<Q', read(8))[0]
def read_ubyte(read):
return unpack(b'B', read(1))[0]
def read_string_ubyte(read):
size = read_ubyte(read)
data = read(size)
return data
def unpack_array(read, array_type, array_stride, array_byteswap):
length = read_uint(read)
encoding = read_uint(read)
comp_len = read_uint(read)
data = read(comp_len)
if encoding == 0:
pass
elif encoding == 1:
data = zlib.decompress(data)
assert(length * array_stride == len(data))
data_array = array.array(array_type, data)
if array_byteswap and _IS_BIG_ENDIAN:
data_array.byteswap()
return data_array
read_data_dict = {
b'Z'[0]: lambda read: unpack(b'<b', read(1))[0], # 8 bit int
b'Y'[0]: lambda read: unpack(b'<h', read(2))[0], # 16 bit int
b'C'[0]: lambda read: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
b'I'[0]: lambda read: unpack(b'<i', read(4))[0], # 32 bit int
b'F'[0]: lambda read: unpack(b'<f', read(4))[0], # 32 bit float
b'D'[0]: lambda read: unpack(b'<d', read(8))[0], # 64 bit float
b'L'[0]: lambda read: unpack(b'<q', read(8))[0], # 64 bit int
b'R'[0]: lambda read: read(read_uint(read)), # binary data
b'S'[0]: lambda read: read(read_uint(read)), # string data
b'f'[0]: lambda read: unpack_array(read, 'f', 4, False), # array (float)
b'i'[0]: lambda read: unpack_array(read, 'i', 4, True), # array (int)
b'd'[0]: lambda read: unpack_array(read, 'd', 8, False), # array (double)
b'l'[0]: lambda read: unpack_array(read, 'q', 8, True), # array (long)
b'b'[0]: lambda read: unpack_array(read, 'b', 1, False), # array (bool)
b'c'[0]: lambda read: unpack_array(read, 'B', 1, False), # array (ubyte)
}
# FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
# * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
# * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
def init_version(fbx_version):
global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, read_fbx_elem_uint
assert(_BLOCK_SENTINEL_LENGTH == ...)
assert(_BLOCK_SENTINEL_DATA == ...)
if fbx_version < 7500:
_BLOCK_SENTINEL_LENGTH = 13
read_fbx_elem_uint = read_uint
else:
_BLOCK_SENTINEL_LENGTH = 25
read_fbx_elem_uint = read_uint64
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
def read_elem(read, tell, use_namedtuple):
# [0] the offset at which this block ends
# [1] the number of properties in the scope
# [2] the length of the property list
end_offset = read_fbx_elem_uint(read)
if end_offset == 0:
return None
prop_count = read_fbx_elem_uint(read)
prop_length = read_fbx_elem_uint(read)
elem_id = read_string_ubyte(read) # elem name of the scope/key
elem_props_type = bytearray(prop_count) # elem property types
elem_props_data = [None] * prop_count # elem properties (if any)
elem_subtree = [] # elem children (if any)
for i in range(prop_count):
data_type = read(1)[0]
elem_props_data[i] = read_data_dict[data_type](read)
elem_props_type[i] = data_type
if tell() < end_offset:
while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
elem_subtree.append(read_elem(read, tell, use_namedtuple))
if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
raise IOError("failed to read nested block sentinel, "
"expected all bytes to be 0")
if tell() != end_offset:
raise IOError("scope length not reached, something is wrong")
args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
return FBXElem(*args) if use_namedtuple else args
def parse_version(fn):
"""
Return the FBX version,
if the file isn't a binary FBX return zero.
"""
with open(fn, 'rb') as f:
read = f.read
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
return 0
return read_uint(read)
def parse(fn, use_namedtuple=True):
root_elems = []
with open(fn, 'rb') as f:
read = f.read
tell = f.tell
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
raise IOError("Invalid header")
fbx_version = read_uint(read)
init_version(fbx_version)
while True:
elem = read_elem(read, tell, use_namedtuple)
if elem is None:
break
root_elems.append(elem)
args = (b'', [], bytearray(0), root_elems)
return FBXElem(*args) if use_namedtuple else args, fbx_version
# ----------------------------------------------------------------------------
# Inline Modules
# pyfbx.data_types
data_types = type(array)("data_types")
data_types.__dict__.update(
dict(
INT8 = b'Z'[0],
INT16 = b'Y'[0],
BOOL = b'C'[0],
INT32 = b'I'[0],
FLOAT32 = b'F'[0],
FLOAT64 = b'D'[0],
INT64 = b'L'[0],
BYTES = b'R'[0],
STRING = b'S'[0],
FLOAT32_ARRAY = b'f'[0],
INT32_ARRAY = b'i'[0],
FLOAT64_ARRAY = b'd'[0],
INT64_ARRAY = b'l'[0],
BOOL_ARRAY = b'b'[0],
BYTE_ARRAY = b'c'[0],
))
# pyfbx.parse_bin
parse_bin = type(array)("parse_bin")
parse_bin.__dict__.update(
dict(
parse = parse
))
# ----------------------------------------------------------------------------
# JSON Converter
# from pyfbx import parse_bin, data_types
import json
import array
def fbx2json_property_as_string(prop, prop_type):
if prop_type == data_types.STRING:
prop_str = prop.decode('utf-8')
prop_str = prop_str.replace('\x00\x01', '::')
return json.dumps(prop_str)
else:
prop_py_type = type(prop)
if prop_py_type == bytes:
return json.dumps(repr(prop)[2:-1])
elif prop_py_type == bool:
return json.dumps(prop)
elif prop_py_type == array.array:
return repr(list(prop))
return repr(prop)
def fbx2json_properties_as_string(fbx_elem):
return ", ".join(fbx2json_property_as_string(*prop_item)
for prop_item in zip(fbx_elem.props,
fbx_elem.props_type))
def fbx2json_recurse(fw, fbx_elem, ident, is_last):
fbx_elem_id = fbx_elem.id.decode('utf-8')
fw('%s["%s", ' % (ident, fbx_elem_id))
fw('[%s], ' % fbx2json_properties_as_string(fbx_elem))
fw('"%s", ' % (fbx_elem.props_type.decode('ascii')))
fw('[')
if fbx_elem.elems:
fw('\n')
ident_sub = ident + " "
for fbx_elem_sub in fbx_elem.elems:
fbx2json_recurse(fw, fbx_elem_sub, ident_sub,
fbx_elem_sub is fbx_elem.elems[-1])
fw(']')
fw(']%s' % ('' if is_last else ',\n'))
def fbx2json(fn):
import os
fn_json = "%s.json" % os.path.splitext(fn)[0]
print("Writing: %r " % fn_json, end="")
fbx_root_elem, fbx_version = parse(fn, use_namedtuple=True)
print("(Version %d) ..." % fbx_version)
with open(fn_json, 'w', encoding="ascii", errors='xmlcharrefreplace') as f:
fw = f.write
fw('[\n')
ident_sub = " "
for fbx_elem_sub in fbx_root_elem.elems:
fbx2json_recurse(f.write, fbx_elem_sub, ident_sub,
fbx_elem_sub is fbx_root_elem.elems[-1])
fw(']\n')
# ----------------------------------------------------------------------------
# Command Line
def main():
import sys
if "--help" in sys.argv:
print(__doc__)
return
for arg in sys.argv[1:]:
try:
fbx2json(arg)
except:
print("Failed to convert %r, error:" % arg)
import traceback
traceback.print_exc()
if bpy.app.version < (4,1,0):
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,161 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Script copyright (C) 2014 Blender Foundation
"""
Usage
=====
json2fbx [FILES]...
This script will write a binary FBX file for each JSON argument given.
Input
======
The JSON data is formatted into a list of nested lists of 4 items:
``[id, [data, ...], "data_types", [subtree, ...]]``
Where each list may be empty, and the items in
the subtree are formatted the same way.
data_types is a string, aligned with data that spesifies a type
for each property.
The types are as follows:
* 'Z': - INT8
* 'Y': - INT16
* 'C': - BOOL
* 'I': - INT32
* 'F': - FLOAT32
* 'D': - FLOAT64
* 'L': - INT64
* 'R': - BYTES
* 'S': - STRING
* 'f': - FLOAT32_ARRAY
* 'i': - INT32_ARRAY
* 'd': - FLOAT64_ARRAY
* 'l': - INT64_ARRAY
* 'b': - BOOL ARRAY
* 'c': - BYTE ARRAY
Note that key:value pairs aren't used since the id's are not
ensured to be unique.
"""
def elem_empty(elem, name):
import encode_bin
sub_elem = encode_bin.FBXElem(name)
if elem is not None:
elem.elems.append(sub_elem)
return sub_elem
def parse_json_rec(fbx_root, json_node):
name, data, data_types, children = json_node
ver = 0
assert(len(data_types) == len(data))
e = elem_empty(fbx_root, name.encode())
for d, dt in zip(data, data_types):
if dt == "C":
e.add_bool(d)
elif dt == "Z":
e.add_int8(d)
elif dt == "Y":
e.add_int16(d)
elif dt == "I":
e.add_int32(d)
elif dt == "L":
e.add_int64(d)
elif dt == "F":
e.add_float32(d)
elif dt == "D":
e.add_float64(d)
elif dt == "R":
d = eval('b"""' + d + '"""')
e.add_bytes(d)
elif dt == "S":
d = d.encode().replace(b"::", b"\x00\x01")
e.add_string(d)
elif dt == "i":
e.add_int32_array(d)
elif dt == "l":
e.add_int64_array(d)
elif dt == "f":
e.add_float32_array(d)
elif dt == "d":
e.add_float64_array(d)
elif dt == "b":
e.add_bool_array(d)
elif dt == "c":
e.add_byte_array(d)
if name == "FBXVersion":
assert(data_types == "I")
ver = int(data[0])
for child in children:
_ver = parse_json_rec(e, child)
if _ver:
ver = _ver
return ver
def parse_json(json_root):
root = elem_empty(None, b"")
ver = 0
for n in json_root:
_ver = parse_json_rec(root, n)
if _ver:
ver = _ver
return root, ver
def json2fbx(fn):
import os
import json
import encode_bin
fn_fbx = "%s.fbx" % os.path.splitext(fn)[0]
print("Writing: %r " % fn_fbx, end="")
json_root = []
with open(fn) as f_json:
json_root = json.load(f_json)
fbx_root, fbx_version = parse_json(json_root)
print("(Version %d) ..." % fbx_version)
encode_bin.write(fn_fbx, fbx_root, fbx_version)
# ----------------------------------------------------------------------------
# Command Line
def main():
import sys
if "--help" in sys.argv:
print(__doc__)
return
for arg in sys.argv[1:]:
try:
json2fbx(arg)
except:
print("Failed to convert %r, error:" % arg)
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
@@ -0,0 +1,194 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# Script copyright (C) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
__all__ = (
"parse",
"data_types",
"parse_version",
"FBXElem",
)
from struct import unpack
import array
import zlib
from . import data_types
# at the end of each nested block, there is a NUL record to indicate
# that the sub-scope exists (i.e. to distinguish between P: and P : {})
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
_HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
from collections import namedtuple
FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
del namedtuple
def read_uint(read):
return unpack(b'<I', read(4))[0]
def read_uint64(read):
return unpack(b'<Q', read(8))[0]
def read_ubyte(read):
return unpack(b'B', read(1))[0]
def read_string_ubyte(read):
size = read_ubyte(read)
data = read(size)
return data
def unpack_array(read, array_type, array_stride, array_byteswap):
length = read_uint(read)
encoding = read_uint(read)
comp_len = read_uint(read)
data = read(comp_len)
if encoding == 0:
pass
elif encoding == 1:
data = zlib.decompress(data)
assert(length * array_stride == len(data))
data_array = array.array(array_type, data)
if array_byteswap and _IS_BIG_ENDIAN:
data_array.byteswap()
return data_array
read_data_dict = {
b'Y'[0]: lambda read: unpack(b'<h', read(2))[0], # 16 bit int
b'C'[0]: lambda read: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
b'I'[0]: lambda read: unpack(b'<i', read(4))[0], # 32 bit int
b'F'[0]: lambda read: unpack(b'<f', read(4))[0], # 32 bit float
b'D'[0]: lambda read: unpack(b'<d', read(8))[0], # 64 bit float
b'L'[0]: lambda read: unpack(b'<q', read(8))[0], # 64 bit int
b'R'[0]: lambda read: read(read_uint(read)), # binary data
b'S'[0]: lambda read: read(read_uint(read)), # string data
b'f'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT32, 4, False), # array (float)
b'i'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT32, 4, True), # array (int)
b'd'[0]: lambda read: unpack_array(read, data_types.ARRAY_FLOAT64, 8, False), # array (double)
b'l'[0]: lambda read: unpack_array(read, data_types.ARRAY_INT64, 8, True), # array (long)
b'b'[0]: lambda read: unpack_array(read, data_types.ARRAY_BOOL, 1, False), # array (bool)
b'c'[0]: lambda read: unpack_array(read, data_types.ARRAY_BYTE, 1, False), # array (ubyte)
}
# FBX 7500 (aka FBX2016) introduces incompatible changes at binary level:
# * The NULL block marking end of nested stuff switches from 13 bytes long to 25 bytes long.
# * The FBX element metadata (end_offset, prop_count and prop_length) switch from uint32 to uint64.
def init_version(fbx_version):
global _BLOCK_SENTINEL_LENGTH, _BLOCK_SENTINEL_DATA, read_fbx_elem_uint
_BLOCK_SENTINEL_LENGTH = ...
_BLOCK_SENTINEL_DATA = ...
read_fbx_elem_uint = ...
if fbx_version < 7500:
_BLOCK_SENTINEL_LENGTH = 13
read_fbx_elem_uint = read_uint
else:
_BLOCK_SENTINEL_LENGTH = 25
read_fbx_elem_uint = read_uint64
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
def read_elem(read, tell, use_namedtuple):
# [0] the offset at which this block ends
# [1] the number of properties in the scope
# [2] the length of the property list
end_offset = read_fbx_elem_uint(read)
if end_offset == 0:
return None
prop_count = read_fbx_elem_uint(read)
prop_length = read_fbx_elem_uint(read)
elem_id = read_string_ubyte(read) # elem name of the scope/key
elem_props_type = bytearray(prop_count) # elem property types
elem_props_data = [None] * prop_count # elem properties (if any)
elem_subtree = [] # elem children (if any)
for i in range(prop_count):
data_type = read(1)[0]
elem_props_data[i] = read_data_dict[data_type](read)
elem_props_type[i] = data_type
if tell() < end_offset:
while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
elem_subtree.append(read_elem(read, tell, use_namedtuple))
if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
raise IOError("failed to read nested block sentinel, "
"expected all bytes to be 0")
if tell() != end_offset:
raise IOError("scope length not reached, something is wrong")
args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
return FBXElem(*args) if use_namedtuple else args
def parse_version(fn):
"""
Return the FBX version,
if the file isn't a binary FBX return zero.
"""
with open(fn, 'rb') as f:
read = f.read
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
return 0
return read_uint(read)
def parse(fn, use_namedtuple=True):
root_elems = []
with open(fn, 'rb') as f:
read = f.read
tell = f.tell
if read(len(_HEAD_MAGIC)) != _HEAD_MAGIC:
raise IOError("Invalid header")
fbx_version = read_uint(read)
init_version(fbx_version)
while True:
elem = read_elem(read, tell, use_namedtuple)
if elem is None:
break
root_elems.append(elem)
args = (b'', [], bytearray(0), root_elems)
return FBXElem(*args) if use_namedtuple else args, fbx_version
@@ -0,0 +1,453 @@
import bpy, sys, math
from .maths_geo import *
from .bone_pose import *
from .version import *
from .sys_print import *
def nla_exit_tweak():
active_obj = bpy.context.active_object
if active_obj.animation_data:
if active_obj.animation_data.use_tweak_mode:
print('NLA is in tweak mode, disable it')
active_action = active_obj.animation_data.action
active_obj.animation_data.use_tweak_mode = False
# on exit, the active action is set to None. Bring it back
active_obj.animation_data.action = active_action
return True
return False
def nla_restore_tweak(state):
active_obj = bpy.context.active_object
if state:
if active_obj.animation_data:
try:
print(' restore tweak mode')
if state:# the active action must be set to None if tweak mode
active_obj.animation_data.action = None
# set tweak state
active_obj.animation_data.use_tweak_mode = state
except:
pass
def nla_mute(object):
muted_tracks = []
if object == None:
return muted_tracks
if object.animation_data:
if object.animation_data.nla_tracks:
for track in object.animation_data.nla_tracks:
if track.mute == False:
track.mute = True
muted_tracks.append(track.name)
return muted_tracks
def nla_unmute(object, tracks_names):
if object == None:
return
if object.animation_data:
if object.animation_data.nla_tracks:
for track_name in tracks_names:
track = object.animation_data.nla_tracks.get(track_name)
track.mute = False
def clear_fcurve(fcurve):
found = True
while found:
try:
fcurve.keyframe_points.remove(fcurve.keyframe_points[0])
except:
found = False
def get_keyf_data(key):
# return keyframe point data
return [key.co[0], key.co[1], key.handle_left[0], key.handle_left[1], key.handle_right[0], key.handle_right[1],
key.handle_left_type, key.handle_right_type, key.easing]
def set_keyf_data(key, data):
# set keyframe point from data (list)
key.co[0] = data[0]
key.co[1] = data[1]
key.handle_left[0] = data[2]
key.handle_left[1] = data[3]
key.handle_right[0] = data[4]
key.handle_right[1] = data[5]
key.handle_left_type = data[6]
key.handle_right_type = data[7]
key.easing = data[8]
def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True, bake_object=False,
shape_keys=False, _self=None, action_export_name=None, new_action=True, new_action_name='Action',
interpolation_type='LINEAR', handle_type='DEFAULT',
keyframes_dict=None, sampling_rate=1.0,
support_constraints=False):
scn = bpy.context.scene
obj_data = []
bones_data = []
armature = bpy.data.objects.get(bpy.context.active_object.name)
def get_bones_matrix():
matrices_dict = {}
for pbone in armature.pose.bones:
if only_selected and not pbone.bone.select:
continue
def_matrix = None
constraint = None
bparent_name = ''
parent_type = ''
valid_constraint = True
if support_constraints:# counter transform the ChildOf/Armature constraints
if len(pbone.constraints):
for c in pbone.constraints:
if not c.mute and c.influence > 0.5:
if c.type == 'CHILD_OF':
if c.target:
#if bone
if c.target.type == 'ARMATURE':
bparent_name = c.subtarget
parent_type = "bone"
constraint = c
break
#if object
else:
bparent_name = c.target.name
parent_type = "object"
constraint = c
break
elif c.type == 'ARMATURE':
for tar in c.targets:
if tar.weight > 0.5:
bparent_name = tar.subtarget
parent_type = "bone"
constraint = c
break
if constraint:
if parent_type == 'bone':
if bparent_name == '':
valid_constraint = False
# apply constraint parent
if constraint and valid_constraint:
if parent_type == "bone":
bone_parent = get_pose_bone(bparent_name)
def_matrix = bone_parent.matrix_channel.inverted() @ pbone.matrix
if parent_type == "object":
rig = bpy.data.objects[bparent_name]
def_matrix = constraint.inverse_matrix.inverted() @ rig.matrix_world.inverted() @ def_matrix.matrix
# apply armature object matrix
def_matrix = armature.convert_space(pose_bone=pbone, matrix=def_matrix, from_space="POSE", to_space="LOCAL")
else:
def_matrix = armature.convert_space(pose_bone=pbone, matrix=pbone.matrix, from_space="POSE", to_space="LOCAL")
matrices_dict[pbone.name] = def_matrix
return matrices_dict
def get_obj_matrix():
parent = armature.parent
matrix = armature.matrix_world
if parent:
return parent.matrix_world.inverted_safe() @ matrix
else:
return matrix.copy()
# make list of meshes with valid shape keys
sk_objects = []
if shape_keys and _self and action_export_name:# bake shape keys value for animation export
for ob_name in _self.char_objects:
ob = bpy.data.objects.get(ob_name+"_arpexp")
if ob.type != "MESH":
continue
if ob.data.shape_keys == None:
continue
if len(ob.data.shape_keys.key_blocks) <= 1:
continue
sk_objects.append(ob)
# store matrices
current_frame = scn.frame_current
f = float(int(frame_start))
while f <= int(frame_end):
f = round(f, 3)# round frame value because of decimals issues
scn.frame_set(math.floor(f), subframe=f-math.floor(f))
bpy.context.view_layer.update()
if bake_bones:
bones_data.append((f, get_bones_matrix()))
if bake_object:
obj_data.append((f, get_obj_matrix()))
# shape keys data (for animation export only)
#print('f', f)
for ob in sk_objects:
for i, sk in enumerate(ob.data.shape_keys.key_blocks):
if (sk.name == "Basis" or sk.name == "00_Basis") and i == 0:
continue
frame_in_action = float(f-int(frame_start))
frame_in_action = round(frame_in_action, 3)# round frame value because of decimals issues
if scn.arp_retro_ge_mesh == False:
obj_base = bpy.data.objects.get(ob.name.replace('_arpexp', ''))
obj_data_name = obj_base.data.name
else:
obj_data_name = ob.data.name
#print('Bake shape key', obj_data_name, sk.name, sk.value)
dict_entry = action_export_name+'|'+'BMesh#'+obj_data_name+'|Shape|BShape Key#'+sk.name+'|'+str(frame_in_action)
_self.shape_keys_data[dict_entry] = sk.value
print_progress_bar("Baking phase 1", f-frame_start, frame_end-frame_start)
f += sampling_rate
f = round(f, 3)# round frame value because of decimals issues
print("")
# set new action
action = None
if new_action:
action = bpy.data.actions.new(new_action_name)
anim_data = armature.animation_data_create()
anim_data.action = action
else:
action = armature.animation_data.action
def store_keyframe(bone_name, prop_type, fc_array_index, frame, value):
fc_data_path = 'pose.bones["' + bone_name + '"].' + prop_type
fc_key = (fc_data_path, fc_array_index)
if not keyframes.get(fc_key):
keyframes[fc_key] = []
keyframes[fc_key].extend((frame, value))
# set transforms and store keyframes
if bake_bones:
bone_count = 0
total_bone_count = len(armature.pose.bones)
for pbone in armature.pose.bones:
bone_count += 1
print_progress_bar("Baking phase 2", bone_count, total_bone_count)
if only_selected and not pbone.bone.select:
continue
euler_prev = None
quat_prev = None
keyframes = {}
for (f, matrix) in bones_data:
# optional, only keyframe given frames
if keyframes_dict:
keyf_list = keyframes_dict[pbone.name]
if not f in keyf_list:
continue
pbone.matrix_basis = matrix[pbone.name].copy()
for arr_idx, value in enumerate(pbone.location):
store_keyframe(pbone.name, "location", arr_idx, f, value)
rotation_mode = pbone.rotation_mode
if rotation_mode == 'QUATERNION':
if quat_prev is not None:
quat = pbone.rotation_quaternion.copy()
if bpy.app.version >= (2,82,0):# previous versions don't know this function
quat.make_compatible(quat_prev)
pbone.rotation_quaternion = quat
quat_prev = quat
del quat
else:
quat_prev = pbone.rotation_quaternion.copy()
for arr_idx, value in enumerate(pbone.rotation_quaternion):
store_keyframe(pbone.name, "rotation_quaternion", arr_idx, f, value)
elif rotation_mode == 'AXIS_ANGLE':
for arr_idx, value in enumerate(pbone.rotation_axis_angle):
store_keyframe(pbone.name, "rotation_axis_angle", arr_idx, f, value)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev)
pbone.rotation_euler = euler
del euler
euler_prev = pbone.rotation_euler.copy()
for arr_idx, value in enumerate(pbone.rotation_euler):
store_keyframe(pbone.name, "rotation_euler", arr_idx, f, value)
for arr_idx, value in enumerate(pbone.scale):
store_keyframe(pbone.name, "scale", arr_idx, f, value)
# Add keyframes
for fc_key, key_values in keyframes.items():
data_path, index = fc_key
fcurve = action.fcurves.find(data_path=data_path, index=index)
if new_action == False and fcurve:# for now always remove existing keyframes if overwriting current action, must be driven by constraints only
action.fcurves.remove(fcurve)
fcurve = action.fcurves.new(data_path, index=index, action_group=pbone.name)
if fcurve == None:
fcurve = action.fcurves.new(data_path, index=index, action_group=pbone.name)
# set keyframes points
num_keys = len(key_values) // 2
fcurve.keyframe_points.add(num_keys)
fcurve.keyframe_points.foreach_set('co', key_values)
# set interpolation type
key_interp = interpolation_type
if 'const_interp' in pbone.bone.keys():
if pbone.bone['const_interp'] == True:
key_interp = 'CONSTANT'
if bpy.app.version >= (2,90,0):# internal error when doing so with Blender 2.83, only for Blender 2.90 and higher
interp_value = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[key_interp].value
fcurve.keyframe_points.foreach_set('interpolation', (interp_value,) * num_keys)
# set handle type
if handle_type != 'DEFAULT':
handle_enum_value = bpy.types.Keyframe.bl_rna.properties['handle_left_type'].enum_items[handle_type].value
fcurve.keyframe_points.foreach_set('handle_left_type', (handle_enum_value,) * num_keys)
fcurve.keyframe_points.foreach_set('handle_right_type', (handle_enum_value,) * num_keys)
else:
for kf in fcurve.keyframe_points:
# set interpolation type (pre Blender 2.90 versions)
kf.interpolation = key_interp
# set handle type (pre Blender 2.90 versions)
if handle_type != 'DEFAULT':
kf.handle_type_right = handle_type
kf.handle_type_left = handle_type
fcurve.update()
if bake_object:
euler_prev = None
quat_prev = None
for (f, matrix) in obj_data:
name = "Action Bake"
armature.matrix_basis = matrix
armature.keyframe_insert("location", index=-1, frame=f, group=name)
rotation_mode = armature.rotation_mode
if rotation_mode == 'QUATERNION':
if quat_prev is not None:
quat = armature.rotation_quaternion.copy()
if bpy.app.version >= (2,82,0):# previous versions don't know this function
quat.make_compatible(quat_prev)
armature.rotation_quaternion = quat
quat_prev = quat
del quat
else:
quat_prev = armature.rotation_quaternion.copy()
armature.keyframe_insert("rotation_quaternion", index=-1, frame=f, group=name)
elif rotation_mode == 'AXIS_ANGLE':
armature.keyframe_insert("rotation_axis_angle", index=-1, frame=f, group=name)
else: # euler, XYZ, ZXY etc
if euler_prev is not None:
euler = armature.rotation_euler.copy()
euler.make_compatible(euler_prev)
armature.rotation_euler = euler
euler_prev = euler
del euler
else:
euler_prev = armature.rotation_euler.copy()
armature.keyframe_insert("rotation_euler", index=-1, frame=f, group=name)
armature.keyframe_insert("scale", index=-1, frame=f, group=name)
# restore current frame
scn.frame_set(current_frame)
print("\n")
def get_bone_keyframes_list(pb, act):
# return a list containing all keyframes frames of the given pose bone
key_list = []
# loc
for i in range(0,3):
fc = act.fcurves.find('pose.bones["'+pb.name+'"].location', index=i)
if fc:
for k in fc.keyframe_points:
if not k.co[0] in key_list:
key_list.append(k.co[0])
# rot
_range = 3
rot_path = 'rotation_euler'
if pb.rotation_mode == 'QUATERNION':
_range = 4
rot_path = 'rotation_quaternion'
for i in range(0,_range):# rot
fc = act.fcurves.find('pose.bones["'+pb.name+'"].'+rot_path, index=i)
if fc:
for k in fc.keyframe_points:
if not k.co[0] in key_list:
key_list.append(k.co[0])
# scale
for i in range(0,3):
fc = act.fcurves.find('pose.bones["'+pb.name+'"].scale', index=i)
if fc:
for k in fc.keyframe_points:
if not k.co[0] in key_list:
key_list.append(k.co[0])
return key_list
def copy_shapekeys_tracks(obj1, obj2):
# copy the NLA shape keys tracks from one object to another
if obj1.data.shape_keys == None:
return
if obj1.data.shape_keys.animation_data == None:
return
for anim_track in obj1.data.shape_keys.animation_data.nla_tracks:
# copy sk tracks
if obj2.data.shape_keys.animation_data == None:
obj2.data.shape_keys.animation_data_create()
track2 = obj2.data.shape_keys.animation_data.nla_tracks.get(anim_track.name)
if track2 == None:
track2 = obj2.data.shape_keys.animation_data.nla_tracks.new()
track2.name = anim_track.name
for strip in anim_track.strips:
strip2 = track2.strips.get(strip.name)
if strip2 == None:
strip2 = track2.strips.new(strip.name, int(strip.frame_start), strip.action)
for setting in ['action_frame_end', 'action_frame_start', 'blend_in', 'blend_out', 'blend_type', 'extrapolation', 'frame_end', 'frame_start', 'mute', 'repeat']:
setattr(strip2, setting, getattr(strip, setting))
@@ -0,0 +1,211 @@
import bpy
from .. import auto_rig_datas as ard
from .version_arm_collec import *
def remove_bone_from_layer(bone, layer_type):
if bpy.app.version >= (4,0,0):
arma = bpy.context.active_object
col = get_armature_collections(arma).get(layer_type)
if col:
if bpy.context.mode == 'EDIT_ARMATURE':
col.unassign(bone)
else:
col.unassign(arma.data.bones[bone.name])
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:
layer_idx = ard.layer_col_map[layer_type]
bone.layers[layer_idx] = False
def set_bone_layer(bone, layer_type, show_new_layer=False, multi=False):
if bpy.app.version >= (4,0,0):
arma = bpy.context.active_object
col = get_armature_collections(arma).get(layer_type)
if col == None:# create the collection if necessary
col = arma.data.collections.new(layer_type)
col.is_visible = show_new_layer
col['arp_collec'] = True# custom tag
if bpy.context.mode == 'EDIT_ARMATURE':
col.assign(bone)
else:
col.assign(arma.data.bones[bone.name])
if multi:
return
for col in get_armature_collections(arma):
if col.name != layer_type:
if bpy.context.mode == 'EDIT_ARMATURE':
col.unassign(bone)
else:
col.unassign(arma.data.bones[bone.name])
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:# standard layer/collec conversion
layer_idx = ard.layer_col_map[layer_type]
bone.layers[layer_idx] = True
if multi:
return
for i, lay in enumerate(bone.layers):
if i != layer_idx:
bone.layers[i] = False
def is_arp_collec(col):
if col.name in ard.layer_col_map or col.name in ard.layer_col_map_special or col.name.startswith('color_body.') or col.name.startswith('mch_'):
return True
def is_bone_in_layer(bone_name, layer_type):
if bpy.app.version >= (4,0,0):
if bpy.context.mode == 'EDIT_ARMATURE':# # in Edit mode, access edit bones only. Prone to error otherwise (bone data not up to date)
in_collection = [ebone.name for ebone in bpy.context.active_object.data.edit_bones if layer_type in ebone.collections]
return bone_name in in_collection
else:
return layer_type in bpy.context.active_object.data.bones.get(bone_name).collections
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:# standard ARP layer-collec conversion
layer_idx = ard.layer_col_map[layer_type]
if bpy.context.mode == 'EDIT_ARMATURE':# in Edit mode, access edit bones only. Prone to error otherwise (bone data not up to date)
return bpy.context.active_object.data.edit_bones.get(bone_name).layers[layer_idx]
else:
return bpy.context.active_object.data.bones.get(bone_name).layers[layer_idx]
def is_layer_enabled(layer_type):
if bpy.app.version >= (4,0,0):
if layer_type == 'mch_disabled':# only there for backward-compatibility, this collection is no more used
col = get_armature_collections(bpy.context.active_object.data).get(layer_type)
if col == None:
bpy.context.active_object.data.collections.new(mch_disabled)
col = get_armature_collections(bpy.context.active_object).get(layer_type)
if col:
return col.is_visible
else:# old layer system
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:
layer_idx = ard.layer_col_map[layer_type]
return bpy.context.active_object.data.layers[layer_idx]
def hide_layer(layer_type):
if bpy.app.version >= (4,0,0):
col = get_armature_collections(bpy.context.active_object).get(layer_type)
col.is_visible = False
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:
layer_idx = ard.layer_col_map[layer_type]
bpy.context.active_object.data.layers[layer_idx] = False
def enable_layer_exclusive(layer_type, use_solo=False):
if bpy.app.version >= (4,1,0) and use_solo:
for col in get_armature_collections(bpy.context.active_object):
col.is_solo = col.name == layer_type
if col.name == layer_type:
col.is_visible = True
return
if bpy.app.version >= (4,0,0):
for col in get_armature_collections(bpy.context.active_object):
# ensure to disable pinned collections (Blender 4.1+)
if bpy.app.version >= (4,1,0):
col.is_solo = False
if col.name == layer_type:
col.is_visible = True
else:
col.is_visible = False
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:
layer_idx = ard.layer_col_map[layer_type]
bpy.context.active_object.data.layers[layer_idx] = True
for i in range(0, 32):
if i != layer_idx:
bpy.context.active_object.data.layers[i] = False
def enable_layer(layer_type):
if bpy.app.version >= (4,0,0):
col = get_armature_collections(bpy.context.active_object).get(layer_type)
col.is_visible = True
else:
if layer_type in ard.layer_col_map_special:# layer idx special cases
layer_idx = ard.layer_col_map_special[layer_type]
else:
layer_idx = ard.layer_col_map[layer_type]
bpy.context.active_object.data.layers[layer_idx] = True
def restore_armature_layers(layers_select):
if bpy.app.version >= (4,0,0):
for col_name in layers_select:
col = get_armature_collections(bpy.context.active_object).get(col_name)
if col:# may have been renamed or deleted
if bpy.app.version >= (4,1,0):
viz, solo = layers_select[col_name]
col.is_visible = viz
col.is_solo = solo
else:
col.is_visible = layers_select[col_name]
for col in get_armature_collections(bpy.context.active_object):#disable newly created layers
if not col.name in layers_select:
col.is_visible = False
else:
# must enabling at least one
bpy.context.active_object.data.layers[layers_select[0]] = True
# restore the armature layers visibility
for i in range(0, 32):
bpy.context.active_object.data.layers[i] = layers_select[i]
def enable_all_armature_layers():
# enable all layers/collections
# and return the list of each layer visibility
if bpy.app.version >= (4,0,0):
layers_select = {}
for col in get_armature_collections(bpy.context.active_object):
layers_select[col.name] = col.is_visible
if bpy.app.version >= (4,1,0):
layers_select[col.name] = col.is_visible, col.is_solo
col.is_solo = False
col.is_visible = True
return layers_select
else:
layers_select = []
_layers = bpy.context.active_object.data.layers
for i in range(0, 32):
layers_select.append(_layers[i])
for i in range(0, 32):
bpy.context.active_object.data.layers[i] = True
return layers_select
@@ -0,0 +1,164 @@
import bpy
from .objects import *
class ARP_BonesData:
custom_bones_list = []
softlink_bones = []
armature_name = ''
#renamed_bones = {}
def init_values(self):
self.custom_bones_list = []
self.softlink_bones = []
#self.renamed_bones = {}
self.const_interp_bones = []
def collect(self, arm_name):
self.armature_name = arm_name
arm = get_object(self.armature_name)
self.init_values()
def add_stretch_bones(b):
# the main stretch arm/leg bones must be added as well in case
# of Humanoid export and Secondary Controllers set to Twist
for f in ["arm", "forearm", "thigh", "leg"]:
s = get_bone_side(b.name)
if b.name == "c_"+f+"_stretch"+s:
self.softlink_bones.append(f+"_stretch"+s)
# collect props in Edit/Object mode
for b in arm.data.bones:
found_bone = False
if len(b.keys()):
if "custom_bone" in b.keys() or "cc" in b.keys():
found_bone = True
if "softlink" in b.keys():
if not b.name in self.softlink_bones:
self.softlink_bones.append(b.name)
add_stretch_bones(b)
#if 'rename' in b.keys():
# if not b.name in self.renamed_bones:
# self.renamed_bones[b.name] = b['rename']
if 'const_interp' in b.keys():
if not b.name in self.const_interp_bones:
self.const_interp_bones.append(b.name)
if b.name.startswith("cc_"):
found_bone = True
if found_bone and not b.name in self.custom_bones_list:
self.custom_bones_list.append(b.name)
if "b" in locals():
del b
# also collect props in Pose Mode
set_active_object(self.armature_name)
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
set_active_object(self.armature_name)
bpy.ops.object.mode_set(mode='POSE')
for b in arm.pose.bones:
if len(b.keys()):
if "custom_bone" in b.keys() or "cc" in b.keys():
if not b.name in self.custom_bones_list:
self.custom_bones_list.append(b.name)
if "softlink" in b.keys():
if not b.name in self.softlink_bones:
self.softlink_bones.append(b.name)
add_stretch_bones(b)
#if 'rename' in b.keys():
# if not b.name in self.renamed_bones:
# self.renamed_bones[b.name] = b['rename']
if 'const_interp' in b.keys():
if not b.name in self.const_interp_bones:
self.const_interp_bones.append(b.name)
arp_bones_data = ARP_BonesData()
def is_custom_bone(bone_name):
return bone_name in arp_bones_data.custom_bones_list
def exclude_custom_bone(bone_name):
arp_bones_data.custom_bones_list.remove(bone_name)
def is_softlink_bone(bone_name):
return bone_name in arp_bones_data.softlink_bones
def is_const_interp_bone(bone_name):
return bone_name in arp_bones_data.const_interp_bones
def get_renamed_bone(bone_name):
if bone_name in arp_bones_data.renamed_bones:
return arp_bones_data.renamed_bones[bone_name]
return ''
def get_bone_base_name(bone_name):
base_name = bone_name[:-2]# head.x > head
if "_dupli_" in bone_name:
base_name = bone_name[:-12]
return base_name
def retarget_bone_side(bone_name, target_side, dupli_only=False):#"head.x", "_dupli_001.x"
current_side = get_bone_side(bone_name)#'.x'
base_name = get_bone_base_name(bone_name)#'head'
new_name = ""
if dupli_only:# we only want to set the dupli target side and preserve the left/right/center end letters
current_side_letters = bone_name[-2:]#.l
dupli_side = target_side[:-2]#'_dupli_001' or ''
new_name = base_name+dupli_side+current_side_letters #'eyelid'+'_dupli_001'+'.l'
else:
new_name = base_name+target_side#'head'+'_dupli_001.x'
#if bone_name != new_name:
# print("retarget bone side", bone_name, new_name)
return new_name
def get_bone_side(bone_name):
side = ""
if not "_dupli_" in bone_name:
side = bone_name[-2:]
else:
side = bone_name[-12:]
return side
def get_opposite_side(side):
if side.endswith('.l'):
return side[:-2] + '.r'
elif side.endswith('.r'):
return side[:-2] + '.l'
else:
return ''
def get_data_bone(bonename):
return bpy.context.active_object.data.bones.get(bonename)
def duplicate(type=None):
# runs the operator to duplicate the selected objects/bones
if type == "EDIT_BONE":
bpy.ops.armature.duplicate_move(ARMATURE_OT_duplicate={}, TRANSFORM_OT_translate={"value": (0.0, 0.0, 0.0), "constraint_axis": (False, False, False),"orient_type": 'LOCAL', "mirror": False, "use_proportional_edit": False, "snap": False, "remove_on_cancel": False, "release_confirm": False})
elif type == "OBJECT":
bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
@@ -0,0 +1,144 @@
import bpy
from math import *
from mathutils import *
from .maths_geo import *
from .collections import *
from .. import auto_rig_datas as ard
def init_bone_coordinates(edit_bone):
edit_bone.head = [0,0,0]
edit_bone.tail = [0,0,0.1]
def is_deforming(bone):
if get_edit_bone(bone):
return get_edit_bone(bone).use_deform
def get_selected_edit_bones():
return bpy.context.selected_editable_bones
def get_edit_bone(name):
return bpy.context.object.data.edit_bones.get(name)
def move_bone_to_bone(bone1, bone2):
# move editbone bone1 to bone2 based on the head location
vec_delta = bone2.head - bone1.head
roll = bone1.roll
bone1.head += vec_delta
bone1.tail += vec_delta
bone1.roll = roll
def move_bone(bone, value, axis):
get_edit_bone(bone).head[axis] += value / bpy.context.scene.unit_settings.scale_length
get_edit_bone(bone).tail[axis] += value / bpy.context.scene.unit_settings.scale_length
def copy_bone_rotation(bone1, bone2):
# copy editbone bone1 rotation to bone2
bone1_vec = bone1.tail-bone1.head
bone2_length = (bone2.tail-bone2.head).magnitude
bone2.tail = bone2.head + (bone1_vec.normalized() * bone2_length)
bone2.roll = bone1.roll
def copy_bone_transforms(bone1, bone2):
# copy editbone bone1 transforms to bone 2
if bone1 == None or bone2 == None:
return
bone2.head = bone1.head.copy()
bone2.tail = bone1.tail.copy()
bone2.roll = bone1.roll
def copy_bone_transforms_mirror(bone1, bone2):
bone01 = get_edit_bone(bone1 + ".l")
bone02 = get_edit_bone(bone2 + ".l")
bone02.head = bone01.head
bone02.tail = bone01.tail
bone02.roll = bone01.roll
bone01 = get_edit_bone(bone1 + ".r")
bone02 = get_edit_bone(bone2 + ".r")
bone02.head = bone01.head
bone02.tail = bone01.tail
bone02.roll = bone01.roll
def rotate_edit_bone(edit_bone, angle_radian, axis):
old_head = edit_bone.head.copy()
# rotate
R = Matrix.Rotation(angle_radian, 4, axis.normalized())
edit_bone.transform(R, roll=True)
# back to initial head pos
offset_vec = -(edit_bone.head - old_head)
new_x_axis = edit_bone.x_axis.copy()
edit_bone.head += offset_vec
edit_bone.tail += offset_vec
# preserve roll
align_bone_x_axis(edit_bone, new_x_axis)
def create_edit_bone(bone_name, deform=False, tag=None):
_b = bpy.context.active_object.data.edit_bones.get(bone_name)
if _b == None:
_b = bpy.context.active_object.data.edit_bones.new(bone_name)
_b.use_deform = deform
_b.head = Vector((0.0,0.0,0.0))
_b.tail = Vector((0.0,0.0,0.1))
#init_bone_coordinates(_b)
if tag:# optional tag as custom prop
_b[tag] = 1
return _b
def select_edit_bone(name, mode=1):
o = bpy.context.active_object
ebone = get_edit_bone(name)
if mode == 1:
o.data.bones.active = o.pose.bones[name].bone
elif mode == 2:
o.data.edit_bones.active = o.data.edit_bones[name]
o.data.edit_bones.active.select = True
ebone.select_head = True
ebone.select_tail = True
ebone.select = True
def delete_edit_bone(editbone):
bpy.context.active_object.data.edit_bones.remove(editbone)
def mirror_bones_transforms(ebones_list):
roll_copy = {}
for ebone in ebones_list:
roll_copy[ebone.name] = ebone.roll
# mirror head-tails
for ebone in ebones_list:
ebone.head[0] *= -1
# use_connect handling
found_connected_child = False
if len(ebone.children):
for ch in ebone.children:
if ch.use_connect:
found_connected_child = True
break
if not found_connected_child:
ebone.tail[0] *= -1
# mirror roll
for ebone in ebones_list:
ebone.roll = -roll_copy[ebone.name]
@@ -0,0 +1,228 @@
import bpy
from .objects import *
from .version import blender_version
from .types_convert import *
from .armature import *
def get_selected_pose_bones():
return bpy.context.selected_pose_bones
def get_pose_bone(name):
return bpy.context.active_object.pose.bones.get(name)
def get_custom_shape_scale_prop_name():
if bpy.app.version >= (3,0,0):
return 'custom_shape_scale_xyz'
else:
return 'custom_shape_scale'
def set_custom_shape_scale(pbone, scale):
if bpy.app.version >= (3,0,0):
# uniform scale
if type(scale) == int or type(scale) == float:
for i in range(0,3):
pbone.custom_shape_scale_xyz[i] = scale
# array scale
else:
pbone.custom_shape_scale_xyz = scale
# pre-Blender 3.0
else:
pbone.custom_shape_scale = scale
def scale_custom_shape(custom_shape, scale, origin='cog'):
cs_base_name = custom_shape.name
cs_scaled_name = cs_base_name+'_scaled_'+str(scale)
cs_base_scaled = get_object(cs_scaled_name)
if cs_base_scaled:
return cs_base_scaled
# make
cs_base_scaled = duplicate_object(new_name=cs_scaled_name, method='data', obj=custom_shape)
cs_base_scaled.data.name = cs_scaled_name
cog = Vector((0.0,0.0,0.0))
if origin == 'cog':
for v in cs_base_scaled.data.vertices:
cog += v.co
cog = cog/len(cs_base_scaled.data.vertices)
elif origin == 'zero':
cog = Vector((0.0,0.0,0.0))
for v in cs_base_scaled.data.vertices:
scale_vec = cog - v.co
v.co = v.co + (scale_vec * (1-scale))
return cs_base_scaled
def get_custom_shape_scale(pbone, uniform=True, as_list=False):
if bpy.app.version >= (3,0,0):
if uniform:
# uniform scale
val = 0
for i in range(0,3):
val += pbone.custom_shape_scale_xyz[i]
return val/3
# array scale
else:
if as_list:
return vector_to_list(pbone.custom_shape_scale_xyz)
else:
return pbone.custom_shape_scale_xyz
# pre-Blender 3.0
else:
return pbone.custom_shape_scale
def set_bone_custom_shape(pbone, cs_name):
cs = get_object(cs_name)
if cs == None:
# load custom shape
append_from_arp(nodes=[cs_name], type='object')
cs = get_object(cs_name)
elif len(cs.users_collection) == 0:
# custom shape is found, but not in any collection. Fix it
cs_grp = None
for __o in bpy.context.scene.objects:
if __o.name.startswith('cs_grp') and __o.type == 'EMPTY':
cs_grp = __o
break
if cs_grp:
for col in cs_grp.users_collection:
col.objects.link(cs)
# assign custom shape
pbone.custom_shape = cs
def set_bone_custom_shape_rot(pbone, rot_angle, axis):
if bpy.app.version >= (3,0,0):
axis_int = 0
if axis == 'Y':
axis_int = 1
elif axis == 'Z':
axis_int = 2
pbone.custom_shape_rotation_euler[axis_int] = rot_angle
else:# no custom shape rot setting in older versions
return
def set_bone_color_group(obj, bone_data, grp_name, custom_color=None, custom_highlight=None, assign_only_if_empty=False, body_side=None):
grp_color = (0.5,0.5,0.5)# default color
color_collec = None
if grp_name:
if grp_name == 'body_mid':
grp_color = bpy.context.scene.color_set_middle
color_collec = 'color_body.x'
elif grp_name == 'body_left':
grp_color = bpy.context.scene.color_set_left
color_collec = 'color_body.l'
elif grp_name == 'body_right':
grp_color = bpy.context.scene.color_set_right
color_collec = 'color_body.r'
elif grp_name == 'yellow':
grp_color = (1.0, 1.0, 0.0)
elif grp_name == 'red':
grp_color = (1.0, 0.0, 0.0)
elif grp_name == 'kilt':
grp_color = (0.2, 1.0, 0.3)
if body_side:
if body_side.endswith('.l'):
grp_color = bpy.context.scene.color_set_left
grp_name = 'body.l'
color_collec = 'color_body.l'
elif body_side.endswith('.r'):
grp_color = bpy.context.scene.color_set_right
grp_name = 'body.r'
color_collec = 'color_body.r'
elif body_side.endswith('.x'):
grp_color = bpy.context.scene.color_set_middle
grp_name = 'body.x'
color_collec = 'color_body.x'
if custom_color:
grp_color = custom_color
if custom_highlight == None:
custom_highlight = [0.2, 0.4]
if bpy.app.version >= (4,0,0):
if color_collec:
set_bone_layer(bone_data, color_collec, multi=True)
if assign_only_if_empty:# do not change color group if a group is already assigned
if bone_data.color.palette != 'DEFAULT':
return
bone_data.color.palette = 'CUSTOM'
# set normal color
bone_data.color.custom.normal = grp_color
# set select, active colors
for col_idx in range(0,3):
bone_data.color.custom.select[col_idx] = grp_color[col_idx] + custom_highlight[0]
bone_data.color.custom.active[col_idx] = grp_color[col_idx] + custom_highlight[1]
else:
bone_pose = get_pose_bone(bone_data.name)
if assign_only_if_empty:# do not change color group if a group is already assigned
if bone_pose.bone_group != None:
return
grp = obj.pose.bone_groups.get(grp_name)
if grp == None:
grp = obj.pose.bone_groups.new(name=grp_name)
grp.color_set = 'CUSTOM'
# set normal color
grp.colors.normal = grp_color
# set select color/active color
for col_idx in range(0,3):
grp.colors.select[col_idx] = grp_color[col_idx] + custom_highlight[0]
grp.colors.active[col_idx] = grp_color[col_idx] + custom_highlight[1]
bone_pose.bone_group = grp
def get_bone_colors(bone_data, list=False):
if bone_data.color.palette == 'CUSTOM':
if list == False:
return bone_data.color.custom.normal, bone_data.color.custom.select, bone_data.color.custom.active
else:
return [i for i in bone_data.color.custom.normal], [i for i in bone_data.color.custom.select], [i for i in bone_data.color.custom.active]
else:
return bone_data.color.palette
def set_bone_color(bone_data, bcolors):
# Blender 4 and higher only
if type(bcolors) == str:# set the color palette string
bone_data.color.palette = bcolors
else:# set the color lists
col_normal, col_select, col_active = bcolors
bone_data.color.palette = 'CUSTOM'
bone_data.color.custom.normal = col_normal
bone_data.color.custom.select = col_select
bone_data.color.custom.active = col_active
def reset_pbone_transforms(pbone):
pbone.location = [0,0,0]
pbone.rotation_euler = [0,0,0]
pbone.rotation_quaternion = [1,0,0,0]
pbone.scale = [1,1,1]
@@ -0,0 +1,136 @@
import bpy
from .version_arm_collec import *
def get_arm_col_idx(armature, name):
for i, coll in enumerate(get_armature_collections(armature)):
if coll.name == name:
return i
def sort_armature_collections(armature, only_collection=None, custom_collection=None, to_index=None):
order = {'Main':0, 'Secondary':1, 'Deform':2, 'Reference':3}
# sort a specific custom collection with custom index
if custom_collection and to_index != None:
col = get_armature_collections(armature).get(custom_collection)
cur_idx = get_arm_col_idx(armature, custom_collection)
armature.data.collections.move(cur_idx, to_index)
return
# sort collections as defined in the "order" dict
for col_name in order:
if only_collection:
if only_collection != col_name:
continue
col = get_armature_collections(armature).get(col_name)
cur_idx = get_arm_col_idx(armature, col_name)
to_idx = order[col_name]
armature.data.collections.move(cur_idx, to_idx)
def get_parent_collections(target):
# return the list of all parent collections to the specified target collection
# with a recursive function. A sub-function is used, string based, to ease the process
def get_parent_collections_string(target_name):
parent_collections = ""
found = None
for collec in bpy.data.collections:
for child in collec.children:
if child.name == target_name:
#print("found", collec.name)
parent_collections += collec.name + ","
parent_collections += get_parent_collections_string(collec.name)
return parent_collections
string_result = get_parent_collections_string(target.name)
to_list = [bpy.data.collections[i] for i in string_result[:-1].split(",") if i != ""]
return to_list
meshes_data = []
for child in children:
# store the mesh data for removal afterward
if child.data:
if not child.data.name in meshes_data:
meshes_data.append(child.data.name)
bpy.data.objects.remove(child, do_unlink=True, do_id_user=True, do_ui_user=True)
for data_name in meshes_data:
current_mesh = bpy.data.meshes.get(data_name)
if current_mesh:
bpy.data.meshes.remove(current_mesh, do_unlink=True, do_id_user=True, do_ui_user=True)
bpy.data.objects.remove(passed_node, do_unlink = True)
def get_all_collections_viewlayer():
def mt_traverse_tree(t):
yield t
for child in t.children:
yield from mt_traverse_tree(child)
colls = []
coll = bpy.context.view_layer.layer_collection
for c in mt_traverse_tree(coll):
colls.append(c)
return colls
def get_rig_collection(rig):
if rig == None:
return None
for col in rig.users_collection:
#if col.name.endswith('_rig'):
return col
return None
def get_master_collection(rig_col):
if rig_col == None:
return None
for col in bpy.data.collections:
if len(col.children):
for child_col in col.children:
if child_col == rig_col:
return col
return None
def get_cs_collection(col_master):
if col_master:
for child_col in col_master.children:
if len(child_col.objects):
for o in child_col.objects:
if o.name.startswith('cs_grp'):
return child_col
# the collection haven't been found, the collection hierarchy isn't correct
# look for any collection called cs_grp
for collec in bpy.data.collections:
if collec.name.startswith("cs_grp"):
return collec
return None
def search_layer_collection(layerColl, collName):
# Recursivly transverse layer_collection for a particular name
found = None
if (layerColl.name == collName):
return layerColl
for layer in layerColl.children:
found = search_layer_collection(layer, collName)
if found:
return found
@@ -0,0 +1,101 @@
import bpy
from .bone_pose import *
from mathutils import *
from math import *
def enable_constraint(cns, value):
if bpy.app.version >= (3,0,0):
cns.enabled = value
else:
cns.mute = not value
def set_constraint_inverse_matrix(cns):
# set the inverse matrix of Child Of constraint
subtarget_pbone = get_pose_bone(cns.subtarget)
if subtarget_pbone:
cns.inverse_matrix = subtarget_pbone.bone.matrix_local.to_4x4().inverted()
def add_copy_transf(p_bone, tar=None, subtar='', h_t=0.0, no_scale=False):
if tar == None:
tar = bpy.context.active_object
if no_scale:
cns1 = p_bone.constraints.new("COPY_LOCATION")
cns1.name = "Copy Location"
cns1.target = tar
cns1.subtarget = subtar
cns1.head_tail = h_t
cns2 = p_bone.constraints.new("COPY_ROTATION")
cns2.name = "Copy Rotation"
cns2.target = tar
cns2.subtarget = subtar
return cns1, cns2
else:
cns1 = p_bone.constraints.new("COPY_TRANSFORMS")
cns1.name = "Copy Transforms"
cns1.target = tar
cns1.subtarget = subtar
cns1.head_tail=h_t
return cns1, None
def get_constraint_index(pb, cns):
for i, c in enumerate(pb.constraints):
if c == cns:
return i
def move_constraint(pbone, cns, dir, repeat):
# must be in pose mode
armature = bpy.context.active_object
# the bone layer must be visible
enabled_layers = []
if bpy.app.version >= (4,0,0):
for collec in armature.data.collections:
if is_bone_in_layer(pbone.name, collec.name):
if collec.is_visible == False:
collec.is_visible = True
enabled_layers.append(collec.name)
else:
for i, lay in enumerate(pbone.bone.layers):
if lay and armature.data.layers[i] == False:
armature.data.layers[i] = True
enabled_layers.append(i)
# move
if bpy.app.version >= (2, 81, 16):
cns_idx = get_constraint_index(pbone, cns)
#print('cns_idx', cns_idx)
#print('repeat', repeat)
to_idx = cns_idx+repeat if dir == 'DOWN' else cns_idx-repeat
if to_idx > len(pbone.constraints)-1:
to_idx = len(pbone.constraints)-1
if to_idx < 0:
to_idx = 0
pbone.constraints.move(cns_idx, to_idx)
else:# backward-compatibility
bpy.context.active_object.data.bones.active = pbone.bone
my_context = bpy.context.copy()
my_context["constraint"] = cns
for i in range(0, repeat):
if dir == 'UP':
bpy.ops.constraint.move_up(my_context, constraint=cns.name, owner='BONE')
elif dir == 'DOWN':
bpy.ops.constraint.move_down(my_context, constraint=cns.name, owner='BONE')
# restore layers
for idx in enabled_layers:
if bpy.app.version >= (4,0,0):
armature.data.collections.get(idx).is_visible = False
else:
armature.data.layers[idx] = False
@@ -0,0 +1,34 @@
import bpy
def get_current_mode():
return bpy.context.mode
def restore_current_mode(current_mode):
if current_mode == 'EDIT_ARMATURE':
current_mode = 'EDIT'
if current_mode == "EDIT_MESH":
current_mode = "EDIT"
bpy.ops.object.mode_set(mode=current_mode)
def simplify_scene(self):
self.simplify_value = bpy.context.scene.render.use_simplify
self.simplify_subd = bpy.context.scene.render.simplify_subdivision
bpy.context.scene.render.use_simplify = True
bpy.context.scene.render.simplify_subdivision = 0
def restore_simplify(self):
bpy.context.scene.render.use_simplify = self.simplify_value
bpy.context.scene.render.simplify_subdivision = self.simplify_subd
def disable_autokeyf():
cur_state = bpy.context.scene.tool_settings.use_keyframe_insert_auto
bpy.context.scene.tool_settings.use_keyframe_insert_auto = False
return cur_state
def restore_autokeyf(cur_state):
bpy.context.scene.tool_settings.use_keyframe_insert_auto = cur_state
@@ -0,0 +1,196 @@
import bpy
from .objects import *
from .bone_pose import *
from .context import *
def add_driver_to_prop(obj, dr_dp, tar_dp, array_idx=-1, exp="var", multi_var=False):
if obj.animation_data == None:
obj.animation_data_create()
drivers_list = obj.animation_data.drivers
dr = drivers_list.find(dr_dp, index=array_idx)
if dr == None:
dr = obj.driver_add(dr_dp, array_idx)
if multi_var == False:
var = dr.driver.variables.get('var')
if var == None:
var = dr.driver.variables.new()
var.name = 'var'
var.type = 'SINGLE_PROP'
var.targets[0].id = obj
var.targets[0].data_path = tar_dp
else:# create multiple variables, tar_dp is a dict in that case
for var_name in tar_dp:
var = dr.driver.variables.get(var_name)
if var == None:
var = dr.driver.variables.new()
var.name = var_name
var.type = 'SINGLE_PROP'
var.targets[0].id = obj
var.targets[0].data_path = tar_dp[var_name]
dr.driver.expression = exp
def get_pbone_name_from_data_path(dp):
# return the pbone name from the driver data path
if not '"' in dp:
return None
return dp.split('"')[1]
def replace_driver_target_object(dr, current_obj_name, new_obj_name):
# replace the given driver target object as set in the variables, with a new one
for var in dr.driver.variables:
for tar in var.targets:
if tar.id == get_object(current_obj_name):
tar.id = get_object(new_obj_name)
def copy_driver_variables(variables, source_driver, suffix):
for v1 in variables:
# create a variable
clone_var = source_driver.driver.variables.new()
clone_var.name = v1.name
clone_var.type = v1.type
# copy variable path
try:
clone_var.targets[0].data_path = v1.targets[0].data_path
# increment bone data path name
if '.r"]' in v1.targets[0].data_path:
new_d_path = v1.targets[0].data_path
new_d_path = new_d_path.replace('.r"]', suffix + '"]')
if '.l"]' in v1.targets[0].data_path:
new_d_path = v1.targets[0].data_path
new_d_path = new_d_path.replace('.l"]', suffix + '"]')
clone_var.targets[0].data_path = new_d_path
except:
print("no data_path for: " + v1.name)
try:
clone_var.targets[0].bone_target = v1.targets[0].bone_target
if ".r" in v1.targets[0].bone_target:
clone_var.targets[0].bone_target = v1.targets[0].bone_target.replace(".r", suffix)
if ".l" in v1.targets[0].bone_target:
clone_var.targets[0].bone_target = v1.targets[0].bone_target.replace(".l", suffix)
except:
print("no bone_target for: " + v1.name)
try:
clone_var.targets[0].transform_type = v1.targets[0].transform_type
except:
print("no transform_type for: " + v1.name)
try:
clone_var.targets[0].transform_space = v1.targets[0].transform_space
except:
print("no transform_space for: " + v1.name)
try:
clone_var.targets[0].id_type = v1.targets[0].id_type
except:
print("no id_type for: " + v1.name)
try:
clone_var.targets[0].id = v1.targets[0].id
except:
print("no id for: " + v1.name)
def remove_duplicated_drivers():
arm = bpy.context.active_object
to_delete = []
for i, dr in enumerate(arm.animation_data.drivers):
found = False
# find duplicates only if the current one is not already found
for d in to_delete:
if d[0] == dr.data_path and d[1] == dr.array_index:
found = True
break
if not found:
dp = dr.data_path
array_idx = dr.array_index
for j, dr1 in enumerate(arm.animation_data.drivers):
if i != j:
if dp == dr1.data_path and array_idx == dr1.array_index:
to_delete.append([dp, array_idx])
print("Found", len(to_delete), "duplicated drivers, delete them...")
for dri in to_delete:
try:
arm.driver_remove(dri[0], dri[1])
except:
arm.driver_remove(dri[0], -1)
def remove_invalid_drivers():
obj = bpy.context.active_object
if obj.animation_data == None:
return
current_mode = bpy.context.mode
bpy.ops.object.mode_set(mode='POSE')
invalid_drivers_total = 0
def is_driver_valid(dr, bone_name):
if not dr.is_valid:
return False
if not obj.data.bones.get(bone_name):
return False
if "constraints" in dr.data_path:
cns_name = dr.data_path.split('"')[3]
target_bone = get_pose_bone(bone_name)
found_cns = False
if len(target_bone.constraints) > 0:
for cns in target_bone.constraints:
if cns.name == cns_name:
found_cns = True
if "cns" in locals():
del cns
if not found_cns:
return False
return True
for dr in obj.animation_data.drivers:
if dr.data_path.startswith('pose.bones'):
b = dr.data_path.split('"')[1]
if not is_driver_valid(dr, b):
# the driver is invalid
# assign a dummy but valid data path since we can't remove drivers
# with invalid data path
# print("Invalid driver found:", dr.data_path)
invalid_drivers_total += 1
dr.array_index = 0
dr.data_path = 'delta_scale'
if 'dr' in locals():
del dr
#print("Found", invalid_drivers_total, "invalid drivers")
count = 0
for dr in obj.animation_data.drivers:
if dr.data_path == "delta_scale":
obj.animation_data.drivers.remove(dr)
count += 1
#print(count, "invalid drivers deleted")
# restore saved mode
restore_current_mode(current_mode)
@@ -0,0 +1,21 @@
import bpy
from .version import *
def is_action_baked(action):
# check if the action is a baked one, for either humanoid or universal skeleton
scn = bpy.context.scene
if scn.arp_export_rig_type == 'HUMANOID' or scn.arp_export_rig_type == 'UNIVERSAL':
if scn.arp_bake_anim and check_id_root(action):
if len(action.keys()):
if "arp_baked_action" in action.keys():
return True
return False
def is_action_exportable(action):
# check if the action is marked as exportable
if len(action.keys()):
if 'arp_export' in action.keys():
return action['arp_export']
return True
@@ -0,0 +1,424 @@
import bpy
from math import *
from mathutils import *
import numpy as np
def compare_transform(transf1, transf2):
for i, j in enumerate(transf1):
if j == transf2[i]:
continue
else:
return False
return True
def resample_curve(coords, length=1.0, amount=5, symmetrical=True, generate_normals=False):
# resample a given set of points belonging to a curve
# only works by reduction
resampled_coords = []
dist_sum = 0.0
dist = length/amount
for i, coord in enumerate(coords):
# special case, since we need symmetrical positioning,
# the first coord must be positioned half-distance
if len(resampled_coords) == 0 and symmetrical:
if coord == coords[0]:
continue
p_prev = coords[i-1]
cur_dist = (coord-p_prev).magnitude
dist_sum += cur_dist
if dist_sum >= dist/2:
dist_sum = 0.0
resampled_coords.append(coord.copy())
else:
p_prev = coords[i-1]
cur_dist = (coord-p_prev).magnitude
dist_sum += cur_dist
if dist_sum < dist:
continue
else:
dist_sum = 0.0
resampled_coords.append(coord.copy())
# In case of precision error, the last coord did not fit in.
# Make sure to include it
#print('resampled_coords', len(resampled_coords), 'amount', amount)
if len(resampled_coords) == amount - 1:
#print('Curve resampling error, add last coord as tip coord')
tip_coord = coords[len(coords)-2]
resampled_coords.append(tip_coord)
# Normals generation
# only works for closed curves for now
if generate_normals:
normals = []
# get curve center to correct invalid normals
curve_center = Vector((0,0,0))
for loc in resampled_coords:
curve_center += loc
curve_center = curve_center / len(resampled_coords)
# evaluate normals from neighbours points
for i in range(0, len(resampled_coords)):
p_curr = resampled_coords[i]
if i == 0:#start
p_prev = resampled_coords[len(resampled_coords)-1]
p_next = resampled_coords[i+1]
elif i == len(resampled_coords)-1:#tip
p_prev = resampled_coords[i-1]
p_next = resampled_coords[0]
else:
p_prev = resampled_coords[i-1]
p_next = resampled_coords[i+1]
p1 = p_curr + (p_prev-p_curr).normalized()
p2 = p_curr + (p_next-p_curr).normalized()
mid = (p1 + p2) * 0.5
norm = p_curr - mid
# invalid normal due to straight points
# apply artificial offset from curve center
if norm.magnitude <= 0.00001:
offset = (p_prev-p_curr).magnitude
norm = (p_curr + (p_curr - curve_center).normalized() * offset) - mid
# check if inverted normal by evaluating point_on_normal-curve_center distance
p_norm = p_curr + (norm)
if (curve_center-p_norm).magnitude < (curve_center-p_curr).magnitude:
norm *= -1
normals.append(norm.normalized())
return resampled_coords, normals
else:
return resampled_coords
def get_curve_length(coords):
length = 0.0
p_last = None
for coord in coords:
if p_last == None:
p_last = coord.copy()
else:
length += (coord-p_last).magnitude
p_last = coord.copy()
print("Nurbs length:", length)
return length
def nurbs_basis(i, degree, u, knots):
if degree == 0:
return 1.0 if knots[i] <= u < knots[i + 1] else 0.0
if knots[i + degree] == knots[i]:
left = 0.0
else:
left = (u - knots[i]) / (knots[i + degree] - knots[i]) * nurbs_basis(i, degree - 1, u, knots)
if knots[i + degree + 1] == knots[i + 1]:
right = 0.0
else:
right = (knots[i + degree + 1] - u) / (knots[i + degree + 1] - knots[i + 1]) * nurbs_basis(i + 1, degree - 1, u, knots)
return left + right
def generate_nurbs_curve(points, degree=3, num_points=100):
if len(points) < degree + 1:
raise ValueError("Number of points should be at least degree + 1.")
# Convert control points to numpy array
control_points = np.array(points)
# Calculate the number of knots needed for a closed curve
num_knots = len(control_points) + degree + 1
# Create a list of equally spaced parameter values for the control points
parameter_values = np.linspace(0, 1, len(control_points))
# Compute the knot vector (closed curve)
knots = np.zeros(num_knots)
knots[degree:-degree] = np.linspace(0, 1, num_knots - 2*degree)
knots[-degree:] = 1
# Evaluate the NURBS curve at 'num_points' points
u_new = np.linspace(0, 1, num_points)
x = np.zeros(num_points)
y = np.zeros(num_points)
z = np.zeros(num_points)
for i in range(len(u_new)):
if i == len(u_new)-1:# the last one must be set manually, sigh
x[i] += control_points[len(points)-1, 0]
y[i] += control_points[len(points)-1, 1]
z[i] += control_points[len(points)-1, 2]
break
for j in range(len(control_points)):
basis = nurbs_basis(j, degree, u_new[i], knots)
x[i] += control_points[j, 0] * basis
y[i] += control_points[j, 1] * basis
z[i] += control_points[j, 2] * basis
coords = []
for _x, _y, _z in zip(x, y, z):
coord = Vector((_x, _y, _z))
coords.append(coord)
return coords#x, y, z
def signed_angle(vector_u, vector_v, normal):
normal = normal.normalized()
a = vector_u.angle(vector_v)
if vector_u.cross(vector_v).angle(normal) < 1:
a = -a
return a
def mat3_to_vec_roll(mat, ret_vec=False):
vec = mat.col[1]
vecmat = vec_roll_to_mat3(mat.col[1], 0)
vecmatinv = vecmat.inverted()
rollmat = vecmatinv @ mat
roll = atan2(rollmat[0][2], rollmat[2][2])
if ret_vec:
return vec, roll
else:
return roll
def vec_roll_to_mat3(vec, roll):
epsi = 1e-10
target = Vector((0, 0.1, 0))
nor = vec.normalized()
axis = target.cross(nor)
if axis.dot(axis) > epsi:
axis.normalize()
theta = target.angle(nor)
bMatrix = Matrix.Rotation(theta, 3, axis)
else:
updown = 1 if target.dot(nor) > 0 else -1
bMatrix = Matrix.Scale(updown, 3)
bMatrix[2][2] = 1.0
rMatrix = Matrix.Rotation(roll, 3, nor)
mat = rMatrix @ bMatrix
return mat
def align_bone_x_axis(edit_bone, new_x_axis):
new_x_axis = new_x_axis.cross(edit_bone.y_axis)
new_x_axis.normalize()
dot = max(-1.0, min(1.0, edit_bone.z_axis.dot(new_x_axis)))
angle = acos(dot)
edit_bone.roll += angle
dot1 = edit_bone.z_axis.dot(new_x_axis)
edit_bone.roll -= angle * 2.0
dot2 = edit_bone.z_axis.dot(new_x_axis)
if dot1 > dot2:
edit_bone.roll += angle * 2.0
def align_bone_z_axis(edit_bone, new_z_axis):
new_z_axis = -(new_z_axis.cross(edit_bone.y_axis))
new_z_axis.normalize()
dot = max(-1.0, min(1.0, edit_bone.x_axis.dot(new_z_axis)))
angle = acos(dot)
edit_bone.roll += angle
dot1 = edit_bone.x_axis.dot(new_z_axis)
edit_bone.roll -= angle * 2.0
dot2 = edit_bone.x_axis.dot(new_z_axis)
if dot1 > dot2:
edit_bone.roll += angle * 2.0
def project_point_onto_plane(q, p, n):
# q = point
# p = point belonging to the plane
# n = plane normal
n = n.normalized()
return q - ((q - p).dot(n)) * n
def project_vec_onto_plane(x, n):
# x: Vector
# n: plane normal vector
d = x.dot(n) / n.magnitude
p = [d * n.normalized()[i] for i in range(len(n))]
return Vector([x[i] - p[i] for i in range(len(x))])
def get_pole_angle(base_bone, ik_bone, pole_location):
pole_normal = (ik_bone.tail - base_bone.head).cross(pole_location - base_bone.head)
projected_pole_axis = pole_normal.cross(base_bone.tail - base_bone.head)
return signed_angle(base_bone.x_axis, projected_pole_axis, base_bone.tail - base_bone.head)
def smooth_interpolate(value, linear=0.0, repeat=1):
# value: float belonging to [0, 1]
# return the smooth interpolated value using cosinus function
base_value = value
value_smooth = value
for i in range(0, repeat):
value_smooth = (cos((value_smooth*pi + pi )) + 1) /2
result = (value_smooth*(1-linear)) + (base_value*linear)
if linear >= 0.0:
return result
else:#when linear is negative, smooth even more: repeat 6 times and blend between the base smoothed value and extra one
smooth_x6 = smooth_interpolate(base_value, linear=0.0, repeat=4)
fac = abs(linear)
return (result*(1-fac)) + (smooth_x6*fac)
def round_interpolate(value, linear=0.0, repeat=1):
# value: float belonging to [0, 1]
# return the smooth-rounded interpolated value using cosinus function
value = abs(value)
base_value = value
result = None
for i in range(0, repeat):
smooth_value1 = (cos((value/2*pi + pi)) + 1)
smooth_value2 = (cos((smooth_value1/2*pi + pi)) + 1)
value = (smooth_value1+smooth_value2)*0.5
result = (value*(1-linear)) + (base_value*linear)
if linear >= 0.0:
return result
else:#when linear is negative, smooth even more: repeat 6 times and blend between the base smoothed value and extra one
smooth_x6 = round_interpolate(base_value, linear=0.0, repeat=4)
fac = abs(linear)
return (result*(1-fac)) + (smooth_x6*fac)
def get_point_projection_onto_line_factor(a, b, p):
# return the factor of the projected point 'p' onto the line 'a,b'
# if below a, factor[0] < 0
# if above b, factor[1] < 0
return ((p - a).dot(b - a), (p - b).dot(b - a))
def project_point_onto_line(a, b, p):
# project the point p onto the line a,b
ap = p - a
ab = b - a
result_pos = a + ap.dot(ab) / ab.dot(ab) * ab
return result_pos
def project_vector_onto_vector(a, b):
abdot = (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2])
blensq = (b[0] ** 2) + (b[1] ** 2) + (b[2] ** 2)
temp = abdot / blensq
c = Vector((b[0] * temp, b[1] * temp, b[2] * temp))
return c
def cross(a, b):
c = Vector((a[1]*b[2] - a[2]*b[1], a[2]*b[0] - a[0]*b[2], a[0]*b[1] - a[1]*b[0]))
return c
def get_line_plane_intersection(planeNormal, planePoint, rayDirection, rayPoint, epsilon=1e-6):
ndotu = planeNormal.dot(rayDirection)
if abs(ndotu) < epsilon:
raise RuntimeError("no intersection or line is within plane")
w = rayPoint - planePoint
si = -planeNormal.dot(w) / ndotu
Psi = w + si @ rayDirection + planePoint
return Psi
def translate_object(obj, dist, dir):
# move an object for a given distance "dist" (float)
# along a given direction "dir" (vector 3)
obj_rot_euler = obj.rotation_euler.copy()
obj_rot_quat = obj.rotation_quaternion.copy()
obj_scale = obj.scale.copy()
loc, rot, scale = obj.matrix_world.decompose()
tar_loc = loc + (dir*dist)
tar_mat = Matrix.Translation(tar_loc).to_4x4()
obj.matrix_world = tar_mat
# restore rot and scale
obj.rotation_euler = obj_rot_euler
obj.rotation_quaternion = obj_rot_quat
obj.scale = obj_scale
def rotate_object(obj, angle, axis, origin):
# rotate an object around a given axis "axis" (vector 3)
# for the angle value "angle" (radians)
# around the origin point "origin" (vector 3)
rot_mat = Matrix.Rotation(angle, 4, axis.normalized())
loc, rot, scale = obj.matrix_world.decompose()
loc = loc - origin
obj_mat = Matrix.Translation(loc) @ rot.to_matrix().to_4x4()
obj_mat_rotated = rot_mat @ obj_mat
loc, rot, scale = obj_mat_rotated.decompose()
loc = loc + origin
obj.location = loc.copy()
obj.rotation_euler = rot.to_euler()
# fix numerical imprecisions
for i in range(0,3):
rot = obj.rotation_euler[i]
obj.rotation_euler[i] = round(rot, 4)
def rotate_point(point_loc, angle, axis, origin):
# rotate the point_loc (vector 3) around the "axis" (vector 3)
# for the angle value (radians)
# around the origin (vector 3)
rot_mat = Matrix.Rotation(angle, 4, axis.normalized())
loc = point_loc.copy()
loc = loc - origin
point_mat = Matrix.Translation(loc).to_4x4()
point_mat_rotated = rot_mat @ point_mat
loc, rot, scale = point_mat_rotated.decompose()
loc = loc + origin
return loc
def matrix_loc_rot(mat_full):
# returns a loc + rot matrix from a global transformation matrix (loc, rot, scale)
mat_loc = Matrix.Translation(mat_full.to_translation())
mat_rot = matrix_rot(mat_full)
return mat_loc @ mat_rot
def matrix_rot(mat_full):
# return a rotation matrix only from a global transformation matrix (loc, rot, scale)
return mat_full.to_quaternion().to_matrix().to_4x4()
def compare_mat(mat1, mat2, prec):
for i in range(0,4):
for j in range(0,4):
if round(mat1[i][j], prec) != round(mat2[i][j], prec):
return False
return True
@@ -0,0 +1,498 @@
import bpy, bmesh
from .objects import *
from .bone_data import *
from .version import *
def find_armature(mesh_obj):
# the built-in function object.find_armature() is flawed,
# returns None if multiple armature modifiers, and one of them is None.
# Use this as a working alternative
for mod in mesh_obj.modifiers:
if mod.type == 'ARMATURE':
if mod.object:
return mod.object
def get_skinned_objects(armature):
deformed_objects = []
for ob in bpy.data.objects:
if ob.type != 'MESH':
continue
if find_armature(ob) == None:
continue
if find_armature(ob).name == armature.name:
deformed_objects.append(ob.name)
return deformed_objects
def overwrite_vgroup(obj, vgroup, new_vgname):
new_vgrp = obj.vertex_groups.get(new_vgname)
if new_vgrp:
obj.vertex_groups.remove(new_vgrp)
vgroup.name = new_vgname
def create_mesh_data(mesh_name, verts, edges, faces):
# create an new mesh data given verts, edges and faces data
new_mesh = bpy.data.meshes.new(name=mesh_name)
new_mesh.from_pydata(verts, edges, faces)
return new_mesh
def create_object_mesh(obj_name, verts, edges, faces):
shape_mesh = create_mesh_data(obj_name, verts, edges, faces)
# create object
shape = bpy.data.objects.new(obj_name, shape_mesh)
return shape
def transfer_shape_keys(source_obj, target_obj):
if source_obj == None or target_obj == None:
return
if source_obj.data.shape_keys == None:
return
source_shape_keys = source_obj.data.shape_keys.key_blocks
for sk_index, sk in enumerate(source_shape_keys):
if sk_index == 0:# basis
continue
source_obj.active_shape_key_index = sk_index
bpy.ops.object.shape_key_transfer()
target_sk = target_obj.data.shape_keys.key_blocks.get(sk.name)
target_sk.value = sk.value
target_sk.slider_min, target_sk.slider_max = sk.slider_min, sk.slider_max
source_obj.show_only_shape_key = False
target_obj.show_only_shape_key = False
# copy drivers
anim_data = source_obj.data.shape_keys.animation_data
if anim_data and anim_data.drivers:
if target_obj.data.shape_keys:# If None, shape keys couldn't transfer previously, invalid modifier
obj_anim_data = target_obj.data.shape_keys.animation_data_create()
for fcurve in anim_data.drivers:
new_fc = obj_anim_data.drivers.from_existing(src_driver=fcurve)
new_fc.driver.is_valid = True
for dvar in new_fc.driver.variables:
for dtar in dvar.targets:
if dtar.id == source_obj:
dtar.id = target_obj
def transfer_shape_keys_deformed(source_obj, target_obj, apply_mods=False):
if source_obj == None or target_obj == None:
return
failed_sk = []
# disable all non-armature modifiers to solve issues when baking the mesh
disabled_mod = {}
if apply_mods == False:
for obj in [source_obj, target_obj]:
for mod in obj.modifiers:
if mod.type != "ARMATURE" and mod.show_viewport:
mod.show_viewport = False
if not obj.name in disabled_mod:
disabled_mod[obj.name] = {}
disabled_mod[obj.name][mod.name] = mod.name
if apply_mods:
# enable viewport modifier level if render is enabled
# necessary to bake mesh properly
for mod in source_obj.modifiers:
if mod.show_render:
mod.show_viewport = True
if source_obj.data.shape_keys == None:
return
source_shape_keys = source_obj.data.shape_keys.key_blocks
basis_index = 0
# pin the Basis key
source_obj.active_shape_key_index = basis_index
source_obj.show_only_shape_key = True
bpy.context.evaluated_depsgraph_get().update()
# store the vert coords in basis shape keys
mesh_baked = bmesh.new()
if bpy.app.version < (2,93,0):
mesh_baked.from_object(source_obj, bpy.context.evaluated_depsgraph_get(), deform=True, face_normals=False)
elif bpy.app.version >= (2,93,0) and bpy.app.version < (3,0,2):
mesh_baked.from_object(source_obj, bpy.context.evaluated_depsgraph_get(), face_normals=False)
elif bpy.app.version >= (3,0,2):
mesh_baked.from_object(source_obj, bpy.context.evaluated_depsgraph_get())#, cage=False, face_normals=False, vertex_normals=False)
mesh_baked.verts.ensure_lookup_table()
base_verts_coords = [i.co.copy() for i in mesh_baked.verts]
if 'mesh_baked' in locals():
del mesh_baked
# store the vert coords in basis shape keys
for sk_index, sk in enumerate(source_shape_keys):
if sk_index == basis_index:
continue
source_obj.active_shape_key_index = sk_index
bpy.context.evaluated_depsgraph_get().update()
# get the verts moved in shape key
mesh_baked1 = bmesh.new()
if bpy.app.version < (2,93,0):
mesh_baked1.from_object(source_obj, bpy.context.evaluated_depsgraph_get(), deform=True, face_normals=False)
elif bpy.app.version >= (2,93,0) and bpy.app.version < (3,0,2):
mesh_baked1.from_object(source_obj, bpy.context.evaluated_depsgraph_get(), face_normals=False)
elif bpy.app.version >= (3,0,2):
mesh_baked1.from_object(source_obj, bpy.context.evaluated_depsgraph_get())#, cage=False, face_normals=False, vertex_normals=False)
mesh_baked1.verts.ensure_lookup_table()
if len(mesh_baked1.verts) == len(base_verts_coords):
deformed_verts_coords = [i.co.copy() for i in mesh_baked1.verts]
deformed_verts_index_list = []
for vi, v in enumerate(mesh_baked1.verts):
if v.co != base_verts_coords[vi]:
deformed_verts_index_list.append(vi)
# transfer the shape key
#bpy.ops.object.shape_key_transfer()
create_basis = False
if target_obj.data.shape_keys == None:
create_basis = True
elif len(target_obj.data.shape_keys.key_blocks) == 0:
create_basis = True
if create_basis:# add basis
target_obj.shape_key_add(name='Basis')
target_sk = target_obj.shape_key_add(name=sk.name, from_mix=False)
target_sk.value = sk.value
target_sk.slider_min, target_sk.slider_max = sk.slider_min, sk.slider_max
#print('target_sk vert count:', len(target_sk.data))
#print('mesh_baked1 vert count:', len(mesh_baked1.verts))
# correct the deformed vert coordinates
for deformed_vert_index in deformed_verts_index_list:
#print("set vertex", deformed_vert_index, "from", target_sk.data[deformed_vert_index].co, "TO", mesh_baked1.verts[deformed_vert_index].co)
if deformed_vert_index < len(target_sk.data):
target_sk.data[deformed_vert_index].co = mesh_baked1.verts[deformed_vert_index].co
else:
if not sk.name in failed_sk:
failed_sk.append(sk.name)
print('Cannot transfer shape key, different amount of vertices. ShapeKey:', sk.name, 'Object:', source_obj.name, '> Aborting')
else:# looks like a modifier is adding or removing verts from one shape key to another... not supported! (e.g Bevel, angle based, Decimate...)
if not sk.name in failed_sk:
failed_sk.append(sk.name)
print('Cannot transfer shape key, different amount of vertices. ShapeKey:', sk.name, 'Object:', source_obj.name, '> Aborting')
if 'mesh_baked1' in locals():
del mesh_baked1
source_obj.show_only_shape_key = False
target_obj.show_only_shape_key = False
# copy drivers
anim_data = source_obj.data.shape_keys.animation_data
if anim_data and anim_data.drivers:
if target_obj.data.shape_keys:# If None, shape keys couldn't transfer previously, invalid modifier
obj_anim_data = target_obj.data.shape_keys.animation_data_create()
for fcurve in anim_data.drivers:
new_fc = obj_anim_data.drivers.from_existing(src_driver=fcurve)
new_fc.driver.is_valid = True
for dvar in new_fc.driver.variables:
for dtar in dvar.targets:
if dtar.id == source_obj:
dtar.id = target_obj
# restore disabled modifiers
for objname in disabled_mod:
ob = get_object(objname)
for modname in disabled_mod[objname]:
ob.modifiers[modname].show_viewport = True
return failed_sk
# Transfer weights with operators. Slower or faster than per vertex depending on the context
def transfer_weight_mod_operator(object=None, src_grp_name=None, tar_grp_name=None, replace=False):
mix_mod = object.modifiers.new(type='VERTEX_WEIGHT_MIX', name='ARP_VWM')
mix_mod.vertex_group_a = tar_grp_name
mix_mod.vertex_group_b = src_grp_name
mix_mod.mix_set = 'ALL'
if replace:
mix_mod.mix_mode = 'SET'
else:
mix_mod.mix_mode = 'ADD'
# set in first position
if bpy.app.version < (3,5,0):# backward-compatibility
i_test = 0# safety check, some modifiers can't be moved, limit maximum trials
while object.modifiers[0] != mix_mod and i_test < 50:
i_test += 1
bpy.ops.object.modifier_move_up(modifier=mix_mod.name)
else:
object.modifiers.move(len(object.modifiers)-1, 0)
bpy.ops.object.modifier_apply(modifier=mix_mod.name)
def transfer_weight_mod(object=None, dict=None, list=None, replace=False, tar_grp_name=""):
#vgroups_copy = [i for i in object.vertex_groups]
vgroups_names_copy = [i.name for i in object.vertex_groups]
for vgroup_name in vgroups_names_copy:
v_group = object.vertex_groups.get(vgroup_name)
grp_name_base = get_bone_base_name(v_group.name)
side = get_bone_side(v_group.name)
if dict:
if grp_name_base in dict:
for tar_grp_base_name in dict[grp_name_base]:
if tar_grp_base_name.endswith('.x'):
side = side[:-2]
tar_grp_name = tar_grp_base_name+side
if object.vertex_groups.get(tar_grp_name) == None:
object.vertex_groups.new(name=tar_grp_name)
transfer_weight_mod_operator(object=object, src_grp_name=v_group.name, tar_grp_name=tar_grp_name, replace=replace)
if list:
if grp_name_base in list:
if object.vertex_groups.get(tar_grp_name) == None:
object.vertex_groups.new(name=tar_grp_name)
transfer_weight_mod_operator(object=object, src_grp_name=v_group.name, tar_grp_name=tar_grp_name, replace=replace)
def transfer_weight_prefix_mod(object=None, prefix=None, tar_grp_base_name=None):
vgroups_names_copy = [i.name for i in object.vertex_groups]# iterating over a copy fix bug: UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 1: invalid start byte
for vgr_n in vgroups_names_copy:
vgr = object.vertex_groups.get(vgr_n)
if vgr.index == -1:
continue
if vgr.name.startswith(prefix):
side = vgr.name[-2:]
tar_grp_name = tar_grp_base_name+side
if object.vertex_groups.get(tar_grp_name):#if exists
transfer_weight_mod_operator(object=object, src_grp_name=vgr.name, tar_grp_name=tar_grp_name)
def clamp_weight_mod(object=None, dict=None, list=None):
for vg in object.vertex_groups:
grp_name_base = get_bone_base_name(vg.name)
side = get_bone_side(vg.name)
if dict:
if grp_name_base in dict:
tar_grp_base_name = dict[grp_name_base]
if tar_grp_base_name.endswith('.x'):
side = side[:-2]
tar_grp_name = tar_grp_base_name+side
tar_grp = object.vertex_groups.get(tar_grp_name)
if tar_grp:
clamp_weight_mod_operator(object=object, src_grp_name=vg.name, tar_grp_name=tar_grp_name)
def clamp_weight_mod_operator(object=None, src_grp_name=None, tar_grp_name=None):
mix_mod = object.modifiers.new(type='VERTEX_WEIGHT_MIX', name='ARP_VWM')
mix_mod.vertex_group_a = tar_grp_name
mix_mod.vertex_group_b = src_grp_name
mix_mod.mix_set = 'A'
mix_mod.mix_mode = 'SET'
# set in first position
i_test = 0# safety check, some modifiers can't be moved
while object.modifiers[0] != mix_mod and i_test < 50:
i_test += 1
bpy.ops.object.modifier_move_up(modifier="ARP_VWM")
# apply
bpy.ops.object.modifier_apply(modifier='ARP_VWM')
def multiply_weight_mod_operator(object=None, tar_grp_name='', fac=0.5):
mix_mod = object.modifiers.new(type='VERTEX_WEIGHT_MIX', name='ARP_VWM')
mix_mod.vertex_group_a = tar_grp_name
mix_mod.mix_set = 'ALL'
mix_mod.mix_mode = 'SET'
mix_mod.mask_constant = 1 - fac
# set in first position
i_test = 0# safety check, some modifiers can't be moved
while object.modifiers[0] != mix_mod and i_test < 50:
i_test += 1
bpy.ops.object.modifier_move_up(modifier="ARP_VWM")
# apply
bpy.ops.object.modifier_apply(modifier='ARP_VWM')
def multiply_weight_mod(object=None, dict=None):
for _vg in object.vertex_groups:
grp_name_base = get_bone_base_name(_vg.name)
side = get_bone_side(_vg.name)
if grp_name_base in dict:
fac = dict[grp_name_base]
multiply_weight_mod_operator(object=object, tar_grp_name=_vg.name, fac=fac)
# Transfer weights functions per vertex. Slower or faster than operator depending on the context
def transfer_weight_verts(object=None, dict=None, list=None, target_group_name=None, use_side=False):
for vert in object.data.vertices:
if len(vert.groups) == 0:
continue
for grp in vert.groups:
try:
grp_name = object.vertex_groups[grp.group].name
except:
continue
transfer_weight(object=object, vertice=vert, vertex_weight=grp.weight, group_name=grp_name, dict=dict, list=list, target_group_name=target_group_name, use_side=use_side)
def transfer_weight(object=None, vertice=None, vertex_weight=None, group_name=None, dict=None, list=None, target_group_name=None, use_side=False):
grp_name_base = get_bone_base_name(group_name) if not use_side else group_name
side = get_bone_side(group_name) if not use_side else ''
# Dict mode
if dict:
if grp_name_base in dict:
for target_grp in dict[grp_name_base]:
if target_grp.endswith('.x') and not use_side:
side = side[:-2]
target_group_name = target_grp+side
target_group = object.vertex_groups.get(target_group_name)
if target_group == None:
target_group = object.vertex_groups.new(name=target_group_name)
target_group.add([vertice.index], vertex_weight, 'ADD')
# List mode
if list:
if grp_name_base in list:
target_group = object.vertex_groups.get(target_group_name)
if target_group == None:
target_group = object.vertex_groups.new(name=target_group_name)
target_group.add([vertice.index], vertex_weight, 'ADD')
def transfer_weight_prefix_verts(object=None, prefix='', tar_grp_base_name=''):
for vert in object.data.vertices:
if len(vert.groups) == 0:
continue
for grp in vert.groups:
try:
grp_name = object.vertex_groups[grp.group].name
except:
continue
transfer_weight_prefix(object=object, vertice=vert, vertex_weight=grp.weight, group_name=grp_name, prefix=prefix, target_group=tar_grp_base_name)
def transfer_weight_prefix(object=None, vertice=None, vertex_weight=None, group_name=None, prefix='', target_group=''):
if group_name.startswith(prefix):
side = group_name[-2:]
tar_group_name = target_group+side
if object.vertex_groups.get(tar_group_name):# if exists
object.vertex_groups[tar_group_name].add([vertice.index], vertex_weight, 'ADD')
def copy_vgroup(object=None, dict=None, use_side=False):
# dict = {'arm_stretch': ['c_arm_twist_offset'],...}
vgroups_names_copy = [i.name for i in object.vertex_groups]
for vg_name in vgroups_names_copy:
vg = object.vertex_groups.get(vg_name)
#print(vg.index)
#print('vg.name', vg.name)
grp_name_base = get_bone_base_name(vg.name) if not use_side else vg.name
side = get_bone_side(vg.name) if not use_side else ''
if grp_name_base in dict:
tar_grp_names = dict[grp_name_base]
for tar_grp_name in tar_grp_names:
tar_grp = object.vertex_groups.get(tar_grp_name+side)
if tar_grp == None:
continue
# remove current tar group
object.vertex_groups.remove(tar_grp)
# copy source group
object.vertex_groups.active_index = vg.index
bpy.ops.object.vertex_group_copy()
copy_idx = object.vertex_groups.active_index
copy_grp = object.vertex_groups[copy_idx]
copy_grp.name = tar_grp_name+side
def multiply_weight(object=None, vertice=None, vertex_weight=None, group_name=None, dict=None):
grp_name_base = get_bone_base_name(group_name)
side = get_bone_side(group_name)
if grp_name_base in dict:
if object.vertex_groups.get(group_name) != None:#if exists
object.vertex_groups[group_name].add([vertice.index], vertex_weight * dict[grp_name_base], 'REPLACE')
def clamp_weights(object=None, vertice=None, vertex_weight=None, group_name=None, dict=None):
grp_name_base = group_name[:-2]
side = group_name[-2:]
if "_dupli_" in group_name:
grp_name_base = group_name[:-12]
side = "_" + group_name[-11:]
if grp_name_base in dict:
if dict[grp_name_base][-2:] == '.x':
side = ''
_target_group = dict[grp_name_base]+side
target_weight = 0.0
if object.vertex_groups.get(_target_group) != None:
for grp in vertice.groups:
if object.vertex_groups[grp.group].name == _target_group:
target_weight = grp.weight
def_weight = min(vertex_weight, target_weight)
object.vertex_groups[group_name].add([vertice.index], def_weight, 'REPLACE')
@@ -0,0 +1,13 @@
import bpy
from .version import *
def apply_modifier(mod_name):
try:# crash if modifier is viewport disabled
if bpy.app.version >= (2,90,0):
bpy.ops.object.modifier_apply(modifier=mod_name)
else:
bpy.ops.object.modifier_apply(apply_as="DATA", modifier=mod_name)
except:
print('Modifier could not be applied: '+mod_name)
pass
@@ -0,0 +1,17 @@
def trim_dupli_name(name):
# trim last digits of duplicated names e.g. myobj.002
split_names = name.split('.')
if len(split_names) > 1:
last_digits = split_names[len(split_names)-1]
for i in last_digits:# make sure they're integer, otherwise it shouldn't be a duplicated name
try:
int(i)
#print('int i', int(i))
except:
return name
len_to_trim = len(last_digits) + 1
return name[:-len_to_trim]
else:
return name
@@ -0,0 +1,359 @@
import bpy, os
from .bone_edit import *
from .context import *
from .armature import *
def get_object_boundaries(obj):
bound_box_world = []
for coord in obj.bound_box:
vec = Vector((coord[0], coord[1], coord[2]))
global_coord = obj.matrix_world @ vec
bound_box_world.append(global_coord)
front = 1000000000
back = -front
left = -front
right = front
top = -front
bottom = front
# get bound box front/back/left/right bounds
for coord in bound_box_world:
if coord[1] < front:
front = coord[1]
if coord[1] > back:
back = coord[1]
if coord[0] > left:
left = coord[0]
if coord[0] < right:
right = coord[0]
if coord[2] > top:
top = coord[2]
if coord[2] < bottom:
bottom = coord[2]
return {'front':front, 'back':back, 'left':left, 'right':right, 'top':top, 'bottom':bottom}
def append_from_arp(nodes=None, type=None):
context = bpy.context
scene = context.scene
addon_directory = os.path.dirname(os.path.abspath(__file__))
addon_directory = os.path.dirname(addon_directory)
addon_directory = os.path.dirname(addon_directory)
filepath = addon_directory + "/armature_presets/" + "master.blend"
if type == "object":
# Clean the cs_ materials names (avoid .001, .002...)
for mat in bpy.data.materials:
if mat.name[:3] == "cs_":
if mat.name[-3:].isdigit() and bpy.data.materials.get(mat.name[:-4]) == None:
mat.name = mat.name[:-4]
# make a list of current custom shapes objects in the scene for removal later
cs_objects = [obj.name for obj in bpy.data.objects if obj.name.startswith('cs_')]
# Load the objects data in the file
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
data_to.objects = [name for name in data_from.objects if name in nodes]
# get cs_grp
cs_grp = None
for _ob in bpy.context.scene.objects:
if _ob.name.startswith('cs_grp') and _ob.type == 'EMPTY':
cs_grp = _ob
print('FOUND cs_grp', cs_grp.name)
break
# Add the objects in the scene
for obj in data_to.objects:
if obj:
# link
bpy.context.scene.collection.objects.link(obj)
# apply existing scene material if exists
if len(obj.material_slots):
mat_name = obj.material_slots[0].name
found_mat = None
for mat in bpy.data.materials:
if mat.name == mat_name[:-4]: # substract .001, .002...
found_mat = mat.name
break
# assign existing material if already in file and delete the imported one
if found_mat:
obj.material_slots[0].material = bpy.data.materials[found_mat]
bpy.data.materials.remove(bpy.data.materials[mat_name], do_unlink=True)
# If we append a custom shape
if obj.name.startswith('cs_') or 'c_sphere' in obj.name:
if cs_grp:
# parent the custom shape
obj.parent = cs_grp
# assign to new collection
assigned_collections = []
for collec in cs_grp.users_collection:
collec.objects.link(obj)
assigned_collections.append(collec)
if len(assigned_collections):
# remove previous collections
for i in obj.users_collection:
if not i in assigned_collections:
i.objects.unlink(obj)
# and the scene collection
try:
bpy.context.scene.collection.objects.unlink(obj)
except:
pass
# If we append other objects,
# find added/useless custom shapes and delete them
else:
for obj in bpy.data.objects:
if obj.name.startswith('cs_'):
if not obj.name in cs_objects:
bpy.data.objects.remove(obj, do_unlink=True)
if 'obj' in locals():
del obj
if type == "text":
# Load the objects data in the file
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
data_to.texts = [name for name in data_from.texts if name in nodes]
print("Loading text file:", data_to.texts)
bpy.context.evaluated_depsgraph_get().update()
if type == "font":
# Load the data in the file
with bpy.data.libraries.load(filepath, link=False) as (data_from, data_to):
data_to.fonts = [name for name in data_from.fonts if name in nodes]
print("Loading font file:", data_to.fonts)
bpy.context.evaluated_depsgraph_get().update()
def get_object(name, view_layer_change=False):
ob = bpy.data.objects.get(name)
if ob:
if view_layer_change:
found = False
for v_o in bpy.context.view_layer.objects:
if v_o == ob:
found = True
if not found:# object not in view layer, add to the base collection
bpy.context.collection.objects.link(ob)
return ob
def is_obj_in_current_view_layer(obj):
for v_o in bpy.context.view_layer.objects:
if v_o == obj:
return True
return False
def get_object_id(arp_id):
for _ob in bpy.data.objects:
if len(_ob.keys()):
if 'arp_id' in _ob.keys():
if _ob['arp_id'] == arp_id:
return _ob
return None
def is_object_id(_ob, arp_id, suffix_only=False):
object_has_id = False
if len(_ob.keys()):
if 'arp_id' in _ob.keys():
if suffix_only:
if _ob['arp_id'].endswith(arp_id):
object_has_id = True
else:
if _ob['arp_id'] == arp_id:
object_has_id = True
return object_has_id
def delete_object(obj):
bpy.data.objects.remove(obj, do_unlink=True)
def set_active_object(object_name):
bpy.context.view_layer.objects.active = bpy.data.objects[object_name]
bpy.data.objects[object_name].select_set(state=True)
def hide_object(obj_to_set):
try:# object may not be in current view layer
obj_to_set.hide_set(True)
obj_to_set.hide_viewport = True
except:
pass
def hide_object_visual(obj_to_set):
obj_to_set.hide_set(True)
def is_object_hidden(obj_to_get):
try:
if obj_to_get.hide_get() == False and obj_to_get.hide_viewport == False:
return False
else:
return True
except:# the object must be in another view layer, it can't be accessed
return True
def unhide_object(obj_to_set):
# we can only operate on the object if it's in the active view layer...
try:
obj_to_set.hide_set(False)
obj_to_set.hide_viewport = False
except:
print("Could not reveal object:", obj_to_set.name)
def duplicate_object(new_name="", method='operator', obj=None):
if method == 'operator':
try:
bpy.ops.object.duplicate(linked=False, mode='TRANSLATION')
except:
bpy.ops.object.duplicate('TRANSLATION', False)
if new_name != "":
bpy.context.active_object.name = new_name
elif method == 'data':
if obj:
obj_dupli = obj.copy()
for col in obj.users_collection:
col.objects.link(obj_dupli)
obj_dupli.data = obj_dupli.data.copy()
obj_dupli.name = new_name
return obj_dupli
else:
print('Cannot duplicate object, not found')
def delete_children(passed_node, type):
if passed_node:
if type == "OBJECT":
parent_obj = passed_node
children = []
for obj in bpy.data.objects:
if obj.parent:
if obj.parent == parent_obj:
children.append(obj)
for _obj in children:
for obj_1 in bpy.data.objects:
if obj_1.parent:
if obj_1.parent == _obj:
children.append(obj_1)
meshes_data = []
for child in children:
# store the mesh data for removal afterward
try:
if child.data:
if not child.data.name in meshes_data:
meshes_data.append(child.data.name)
except:
continue
bpy.data.objects.remove(child, do_unlink=True, do_id_user=True, do_ui_user=True)
for data_name in meshes_data:
current_mesh = bpy.data.meshes.get(data_name)
if current_mesh:
bpy.data.meshes.remove(current_mesh, do_unlink=True, do_id_user=True, do_ui_user=True)
bpy.data.objects.remove(passed_node, do_unlink=True)
elif type == "EDIT_BONE":
current_mode = bpy.context.mode
bpy.ops.object.mode_set(mode='EDIT')
if bpy.context.active_object.data.edit_bones.get(passed_node.name):
# Save displayed layers
_layers = enable_all_armature_layers()
bpy.ops.armature.select_all(action='DESELECT')
bpy.context.evaluated_depsgraph_get().update()
bpy.context.active_object.data.edit_bones.active = get_edit_bone(passed_node.name)
bpy.ops.armature.select_similar(type='CHILDREN')
bpy.ops.armature.delete()
restore_armature_layers(_layers)
# restore saved mode
restore_current_mode(current_mode)
def parent_objects(_obj_list, target, mesh_only=True):
for obj in _obj_list:
if mesh_only:
if obj.type != "MESH":
continue
#print("parenting", obj.name)
obj_mat = obj.matrix_world.copy()
obj.parent = target
obj.matrix_world = obj_mat
def select_children(obname, ob_type=None):
_ob = bpy.data.objects.get(obname)
for child in _ob.children:
if ob_type:
if ob_type != child.type:
continue
set_active_object(child.name)
bpy.ops.object.mode_set(mode='OBJECT')
if len(child.children):
select_children(child.name)
def get_children(obname, ob_type=None):
children_list = []
children_list = get_children_recursive(obname, obtype=ob_type, list=children_list)
return children_list
def get_children_recursive(obname, obtype=None, list=None):
_ob = get_object(obname)
if _ob.children:
for child in _ob.children:
if obtype:
if obtype != child.type:
continue
list.append(child.name)
get_children_recursive(child.name, obtype=obtype, list=list)
return list
def has_delta_transforms(obj):
for i in obj.delta_location:
if i != 0.0:
return True
for i in obj.delta_rotation_euler:
if i != 0.0:
return True
for j, i in enumerate(obj.delta_rotation_quaternion):
if (i != 0.0 and j != 0) or (i != 1.0 and j == 0):
return True
for i in obj.delta_scale:
if i != 1.0:
return True
return False
@@ -0,0 +1,67 @@
import bpy
from .version import blender_version
def get_prop_setting(node, prop_name, setting):
if bpy.app.version >= (3,0,0):
return node.id_properties_ui(prop_name).as_dict()[setting]
else:
return node['_RNA_UI'][prop_name][setting]
def set_prop_setting(node, prop_name, setting, value):
if bpy.app.version >= (3,0,0):
ui_data = node.id_properties_ui(prop_name)
if setting == 'default':
ui_data.update(default=value)
elif setting == 'min':
ui_data.update(min=value)
elif setting == 'max':
ui_data.update(max=value)
elif setting == 'soft_min':
ui_data.update(soft_min=value)
elif setting == 'soft_max':
ui_data.update(soft_max=value)
elif setting == 'description':
ui_data.update(description=value)
else:
if not "_RNA_UI" in node.keys():
node["_RNA_UI"] = {}
node['_RNA_UI'][prop_name][setting] = value
def create_custom_prop(node=None, prop_name="", prop_val=1.0, prop_min=0.0, prop_max=1.0, prop_description="", soft_min=None, soft_max=None, default=None):
if soft_min == None:
soft_min = prop_min
if soft_max == None:
soft_max = prop_max
if bpy.app.version < (3,0,0):
if not "_RNA_UI" in node.keys():
node["_RNA_UI"] = {}
node[prop_name] = prop_val
if default == None:
default = prop_val
if bpy.app.version < (3,0,0):
node["_RNA_UI"][prop_name] = {'use_soft_limits':True, 'min': prop_min, 'max': prop_max, 'description': prop_description, 'soft_min':soft_min, 'soft_max':soft_max, 'default':default}
else:
if type(prop_val) != str and type(prop_val) != bool:#string props have no min, max, soft min, soft max
set_prop_setting(node, prop_name, 'min', prop_min)
set_prop_setting(node, prop_name, 'max', prop_max)
set_prop_setting(node, prop_name, 'soft_min', soft_min)
set_prop_setting(node, prop_name, 'soft_max', soft_max)
set_prop_setting(node, prop_name, 'description', prop_description)
set_prop_setting(node, prop_name, 'default', default)
# set as overridable
node.property_overridable_library_set('["'+prop_name+'"]', True)
def is_rna_prop(prop_name):
if prop_name in ['bl_rna', 'rna_type'] or prop_name.startswith('__'):
return True
return False
@@ -0,0 +1,9 @@
import sys
def print_progress_bar(job_title, progress, length):
if length != 0:
progress = int((progress * 100) / length)
else:
progress = 100
sys.stdout.write("\r " + job_title + " %d%%" % progress)
sys.stdout.flush()

Some files were not shown because too many files have changed in this diff Show More