2025-12-01
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,12 @@
|
||||
schema_version = "1.0.0"
|
||||
id = "amzncharactertools"
|
||||
version = "0.6.3"
|
||||
name = "AMZNCharacterTools"
|
||||
tagline = "AMZNCharacterTools"
|
||||
maintainer = "Nathan Lindsay"
|
||||
type = "add-on"
|
||||
tags = ["3D View"]
|
||||
blender_version_min = "4.5.0"
|
||||
license = [
|
||||
"SPDX:GPL-2.0-or-later",
|
||||
]
|
||||
@@ -1,70 +1,15 @@
|
||||
[3.73.41]
|
||||
[3.76.15]
|
||||
|
||||
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
|
||||
Version: Now supports the Blender 5 API, while maintaining backward-compatibility with older Blender versions. Rig, Remap, Export and Picker modules updated
|
||||
Export: New "Reset Transforms" setting to reset controllers transforms between each actions when baking. Useful if some bones are not keyframes, avoiding persistent offsets shared across actions.
|
||||
Rig: Eyebrows: Support of arbitrary amount of eyebrow bones
|
||||
Rig: Eyebrows: Preserve shape when changing amount
|
||||
Rig: Tongue: Support arbitrary amount of tongue bones
|
||||
Remap: New "Bake Only Existing Keyframes" setting
|
||||
|
||||
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
|
||||
QuickRig Compatibility: "Auto IK Roll" not applied in limb options
|
||||
Rig: Lips corners mini not removed when disable the whole head
|
||||
Export: Kilt: Master controller was not exported anymore
|
||||
Unknown compatibility breakage: flush() function not recognized, likely due to a custom python environment
|
||||
@@ -15,15 +15,13 @@
|
||||
# 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
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Auto-Rig Pro",
|
||||
"author": "Artell",
|
||||
"version": (3, 73, 41),
|
||||
"version": (3, 76, 15),
|
||||
"blender": (4, 2, 0),
|
||||
"location": "3D View > Properties> Auto-Rig Pro",
|
||||
"description": "Automatic rig generation based on reference bones and various tools",
|
||||
@@ -33,7 +31,7 @@ bl_info = {
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
import bpy, shutil
|
||||
from bpy.app.handlers import persistent
|
||||
from .src import auto_rig_prefs
|
||||
from .src import rig_functions
|
||||
@@ -49,14 +47,22 @@ from .src import utils
|
||||
|
||||
|
||||
# gltf export specials
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
from .src.lib.animation import get_action_slot_idx
|
||||
|
||||
|
||||
class glTF2ExportUserExtension:
|
||||
|
||||
export_action_only = ''
|
||||
|
||||
def __init__(self):
|
||||
self.action = None
|
||||
self.base_action = None
|
||||
self.base_action_slot_idx = 0
|
||||
|
||||
|
||||
def gather_actions_hook(self, blender_object, params, export_settings):
|
||||
def gather_actions_hook(self, blender_object, params, export_settings):
|
||||
# This hook collects exportable baked actions
|
||||
|
||||
# Filter actions
|
||||
# Only filter ARP rigs
|
||||
if not 'arp_rig_name' in blender_object:
|
||||
@@ -65,6 +71,7 @@ class glTF2ExportUserExtension:
|
||||
# convert string list with fancy separators to list
|
||||
export_actions_names = []
|
||||
sep = '|%%|'
|
||||
print('export_action_only', self.export_action_only)
|
||||
if sep in self.export_action_only:
|
||||
for actname in self.export_action_only.split(sep):
|
||||
export_actions_names.append(actname)
|
||||
@@ -73,50 +80,103 @@ class glTF2ExportUserExtension:
|
||||
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 = []
|
||||
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
# With version 4.4.0 and higher, params is an object that contains all needed data
|
||||
for action_id in list(params.actions.keys()):
|
||||
for act_id, act in enumerate(params.actions[action_id][:]):
|
||||
if "arp_baked_action" in act.action.keys():
|
||||
if self.export_action_only == 'all_actions':
|
||||
pass # Do nothing, all actions are exported
|
||||
elif len(export_actions_names) == 0 and self.export_action_only == params.actions[action_id][act_id].action.name:# single action
|
||||
pass
|
||||
elif len(export_actions_names) and params.actions[action_id][act_id].action.name in export_actions_names:# multiple actions
|
||||
pass # Do nothing, this action is exported
|
||||
else:
|
||||
# We are going to remove this action from the list
|
||||
params.actions[action_id].remove(act)
|
||||
else:
|
||||
params.actions[action_id].remove(act)
|
||||
else:
|
||||
# With version previous to 4.4.0, params has 3 fields: blender_actions, blender_tracks, action_on_type
|
||||
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
|
||||
|
||||
for (k, v) in params.blender_tracks.items():
|
||||
print('k', k)
|
||||
print('v', v)
|
||||
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
|
||||
# This hook ensures that the original rig active action is conserved after exporting, since it is switched during the export
|
||||
|
||||
# Store active action of original rig before looping through actions
|
||||
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
|
||||
# save action
|
||||
self.base_action = original_rig.animation_data.action
|
||||
|
||||
# save slot
|
||||
if bpy.app.version >= (4,4,0):# backward-compatibility
|
||||
self.base_action_slot_idx = get_action_slot_idx(self.base_action, original_rig.animation_data.action_slot)
|
||||
|
||||
# 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
|
||||
# assign action
|
||||
original_rig.animation_data.action = self.base_action
|
||||
|
||||
# assign slot
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
original_rig.animation_data.action_slot = self.base_action.slots[self.base_action_slot_idx]
|
||||
|
||||
self.base_action = None
|
||||
self.base_action_slot_idx = 0
|
||||
|
||||
|
||||
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")
|
||||
def post_animation_switch_hook(self, *args, **kwargs):
|
||||
# This hook is necessary for shape keys export
|
||||
# (if shape keys are driven by bones, then contained in the action data)
|
||||
# When switching the exported rig action, make sure to also switch the action of the original rig
|
||||
|
||||
# the original action name and slot index are now stored in the 'arp_baked_action' property located on the baked action:
|
||||
# ["arp_baked_action"] = action.name+'|||'+str(slot_idx)
|
||||
# to be fetched with:
|
||||
# act_name, slot_idx = blender_action["arp_baked_action"].split('|||')
|
||||
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
blender_object, blender_action, slot, track_name, on_type, export_settings = args
|
||||
else:
|
||||
blender_object, blender_action, track_name, on_type, export_settings = args
|
||||
|
||||
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"]
|
||||
act_name, slot_idx = blender_action["arp_baked_action"].split('|||')
|
||||
|
||||
# assign action
|
||||
original_rig.animation_data.action = bpy.data.actions[act_name]
|
||||
|
||||
# assign slot
|
||||
if bpy.app.version >= (4, 4, 0):
|
||||
original_rig.animation_data.action_slot = \
|
||||
original_rig.animation_data.action.slots[int(slot_idx)]
|
||||
|
||||
|
||||
def menu_func_export(self, context):
|
||||
|
||||
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.
@@ -1,6 +1,6 @@
|
||||
schema_version = "1.0.0"
|
||||
id = "auto_rig_pro"
|
||||
version = "3.73.41"
|
||||
version = "3.76.15"
|
||||
name = "Auto-Rig Pro"
|
||||
tagline = "Automatic rig generation based on reference bones and various tools"
|
||||
maintainer = "Artell <elartblender@gmail.com>"
|
||||
|
||||
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,870 @@
|
||||
c_foot_ik.l%False%RELATIVE_CHAIN%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Ankle_L
|
||||
False
|
||||
True
|
||||
c_leg_pole.l
|
||||
c_toes_ik.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Toes_L
|
||||
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%
|
||||
Ankle_R
|
||||
False
|
||||
True
|
||||
c_leg_pole.r
|
||||
c_toes_ik.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Toes_R
|
||||
False
|
||||
False
|
||||
|
||||
c_shoulder.l%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Scapula_L
|
||||
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%
|
||||
Wrist_L
|
||||
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%
|
||||
Shoulder_L
|
||||
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%
|
||||
Elbow_L
|
||||
False
|
||||
False
|
||||
|
||||
c_shoulder.r%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Scapula_R
|
||||
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%
|
||||
Wrist_R
|
||||
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%
|
||||
Shoulder_R
|
||||
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%
|
||||
Elbow_R
|
||||
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_M
|
||||
True
|
||||
False
|
||||
|
||||
c_spine_01.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RootPart1_M
|
||||
False
|
||||
False
|
||||
|
||||
c_spine_02.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RootPart2_M
|
||||
False
|
||||
False
|
||||
|
||||
c_spine_03.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Spine1_M
|
||||
False
|
||||
False
|
||||
|
||||
c_spine_04.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Spine1Part1_M
|
||||
False
|
||||
False
|
||||
|
||||
c_spine_05.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Spine1Part2_M
|
||||
False
|
||||
False
|
||||
|
||||
c_spine_06.x%True%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Chest_M
|
||||
False
|
||||
False
|
||||
|
||||
c_subneck_1.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Neck_M
|
||||
False
|
||||
False
|
||||
|
||||
c_subneck_2.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
NeckPart1_M
|
||||
False
|
||||
False
|
||||
|
||||
c_neck.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
NeckPart2_M
|
||||
False
|
||||
False
|
||||
|
||||
c_head.x%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Head_M
|
||||
False
|
||||
False
|
||||
|
||||
c_thumb1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger1_L
|
||||
False
|
||||
False
|
||||
|
||||
c_thumb2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger2_L
|
||||
False
|
||||
False
|
||||
|
||||
c_thumb3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger3_L
|
||||
False
|
||||
False
|
||||
|
||||
c_index1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger1_L
|
||||
False
|
||||
False
|
||||
|
||||
c_index2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger2_L
|
||||
False
|
||||
False
|
||||
|
||||
c_index3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger3_L
|
||||
False
|
||||
False
|
||||
|
||||
c_middle1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger1_L
|
||||
False
|
||||
False
|
||||
|
||||
c_middle2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger2_L
|
||||
False
|
||||
False
|
||||
|
||||
c_middle3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger3_L
|
||||
False
|
||||
False
|
||||
|
||||
c_ring1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger1_L
|
||||
False
|
||||
False
|
||||
|
||||
c_ring2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger2_L
|
||||
False
|
||||
False
|
||||
|
||||
c_ring3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger3_L
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky1.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger1_L
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky2.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger2_L
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky3.l%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger3_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%
|
||||
ThumbFinger1_R
|
||||
False
|
||||
False
|
||||
|
||||
c_thumb2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger2_R
|
||||
False
|
||||
False
|
||||
|
||||
c_thumb3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger3_R
|
||||
False
|
||||
False
|
||||
|
||||
c_index1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger1_R
|
||||
False
|
||||
False
|
||||
|
||||
c_index2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger2_R
|
||||
False
|
||||
False
|
||||
|
||||
c_index3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger3_R
|
||||
False
|
||||
False
|
||||
|
||||
c_middle1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger1_R
|
||||
False
|
||||
False
|
||||
|
||||
c_middle2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger2_R
|
||||
False
|
||||
False
|
||||
|
||||
c_middle3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger3_R
|
||||
False
|
||||
False
|
||||
|
||||
c_ring1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger1_R
|
||||
False
|
||||
False
|
||||
|
||||
c_ring2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger2_R
|
||||
False
|
||||
False
|
||||
|
||||
c_ring3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger3_R
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky1.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger1_R
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky2.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger2_R
|
||||
False
|
||||
False
|
||||
|
||||
c_pinky3.r%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger3_R
|
||||
False
|
||||
False
|
||||
|
||||
MiddleFinger4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger4_L
|
||||
False
|
||||
True
|
||||
|
||||
Cup_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Cup_L
|
||||
False
|
||||
True
|
||||
|
||||
PinkyFinger4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger4_L
|
||||
False
|
||||
True
|
||||
|
||||
RingFinger4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger4_L
|
||||
False
|
||||
True
|
||||
|
||||
IndexFinger4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger4_L
|
||||
False
|
||||
True
|
||||
|
||||
ThumbFinger4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger4_L
|
||||
False
|
||||
True
|
||||
|
||||
MiddleFinger4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
MiddleFinger4_R
|
||||
False
|
||||
True
|
||||
|
||||
Cup_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Cup_R
|
||||
False
|
||||
True
|
||||
|
||||
PinkyFinger4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PinkyFinger4_R
|
||||
False
|
||||
True
|
||||
|
||||
RingFinger4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
RingFinger4_R
|
||||
False
|
||||
True
|
||||
|
||||
IndexFinger4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IndexFinger4_R
|
||||
False
|
||||
True
|
||||
|
||||
ThumbFinger4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ThumbFinger4_R
|
||||
False
|
||||
True
|
||||
|
||||
HipPart2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
HipPart2_L
|
||||
False
|
||||
True
|
||||
|
||||
HipPart2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
HipPart2_R
|
||||
False
|
||||
True
|
||||
|
||||
ShoulderPart2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ShoulderPart2_L
|
||||
False
|
||||
True
|
||||
|
||||
ShoulderPart2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
ShoulderPart2_R
|
||||
False
|
||||
True
|
||||
|
||||
FaceJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
FaceJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
EyeJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
IrisJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IrisJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
PupilJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PupilJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
EyeJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
IrisJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
IrisJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
PupilJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
PupilJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain0_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain0_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain1_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain1_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain2_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain3_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain3_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain4_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain5_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain5_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain6_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain6_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain7_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain7_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain8_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain8_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain9_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain9_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain10_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain10_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain11_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain11_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain12_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain12_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain13_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain13_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain14_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain14_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidBaseJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidBaseJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain1_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain1_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain2_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain3_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain3_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain4_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain4_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain5_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain5_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain6_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain6_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain7_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain7_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain8_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain8_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain9_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain9_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain10_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain10_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain11_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain11_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain12_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain12_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain13_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain13_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain0_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain0_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain1_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain1_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain2_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain3_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain3_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain4_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain5_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain5_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain6_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain6_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain7_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain7_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain8_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain8_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain9_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain9_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain10_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain10_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain11_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain11_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain12_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain12_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain13_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain13_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidMain14_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidMain14_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLidBaseJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLidBaseJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain1_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain1_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain2_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain3_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain3_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain4_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain4_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain5_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain5_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain6_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain6_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain7_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain7_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain8_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain8_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain9_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain9_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain10_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain10_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain11_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain11_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain12_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain12_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLidMain13_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLidMain13_L
|
||||
False
|
||||
True
|
||||
|
||||
EyeBrowInnerJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeBrowInnerJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
EyeBrowOuterJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeBrowOuterJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
EyeBrowCenterJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeBrowCenterJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
EyeBrowInnerJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeBrowInnerJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
EyeBrowOuterJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
EyeBrowOuterJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
LipJoints_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
LipJoints_M
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint0_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint0_M
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint1_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint1_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint1_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint1_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint2_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint2_L
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint3_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint3_R
|
||||
False
|
||||
True
|
||||
|
||||
upperLipJoint3_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperLipJoint3_L
|
||||
False
|
||||
True
|
||||
|
||||
LipJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
LipJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
LipJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
LipJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint0_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint0_M
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint1_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint1_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint1_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint1_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint2_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint2_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint2_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint2_L
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint3_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint3_R
|
||||
False
|
||||
True
|
||||
|
||||
lowerLipJoint3_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerLipJoint3_L
|
||||
False
|
||||
True
|
||||
|
||||
SmileBulgeJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
SmileBulgeJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
FrownBulgeJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
FrownBulgeJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
SmileBulgeJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
SmileBulgeJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
FrownBulgeJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
FrownBulgeJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
CheekRaiserJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
CheekRaiserJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
CheekRaiserJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
CheekRaiserJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
CheekJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
CheekJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
CheekJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
CheekJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
JawJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
JawJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
upperTeethJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
upperTeethJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
lowerTeethJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
lowerTeethJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
NoseJoint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
NoseJoint_M
|
||||
False
|
||||
True
|
||||
|
||||
NoseCornerJoint_R%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
NoseCornerJoint_R
|
||||
False
|
||||
True
|
||||
|
||||
NoseCornerJoint_L%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
NoseCornerJoint_L
|
||||
False
|
||||
True
|
||||
|
||||
Tongue0Joint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Tongue0Joint_M
|
||||
False
|
||||
True
|
||||
|
||||
Tongue1Joint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Tongue1Joint_M
|
||||
False
|
||||
True
|
||||
|
||||
Tongue2Joint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Tongue2Joint_M
|
||||
False
|
||||
True
|
||||
|
||||
Tongue3Joint_M%False%ABSOLUTE%0.0,0.0,0.0%0.0,0.0,0.0%1.0%False%False%Y%
|
||||
Tongue3Joint_M
|
||||
False
|
||||
True
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ thumb_ref_list = [j for i, j in thumb_ref_dict.items()]
|
||||
thumb_control_dict = {'base':'c_thumb1_base', '1':'c_thumb1', '2':'c_thumb2', '3':'c_thumb3'}
|
||||
thumb_control_list = [j for i, j in thumb_control_dict.items()]
|
||||
|
||||
thumb_intern_dict = {'base':'thumb1', 'bend_all':'thumb_bend_all', 'rot1':'c_thumb1_rot', 'rot2':'c_thumb2_rot', 'rot3':'c_thumb3_rot'}
|
||||
thumb_intern_dict = {'base':'thumb1', 'bend_all':'thumb_bend_all', 'rot1':'thumb1_rot', 'rot2':'thumb2_rot', 'rot3':'thumb3_rot'}
|
||||
thumb_intern_list = [j for i, j in thumb_intern_dict.items()]
|
||||
|
||||
# index
|
||||
@@ -87,12 +87,13 @@ arm_bones_dict = {
|
||||
'shoulder':{'control':'c_shoulder', 'deform':'shoulder', 'track_pole':'shoulder_track_pole', 'pole':'shoulder_pole'},
|
||||
'arm':{'base':'arm', 'twist':'arm_twist', 'twist_twk':'arm_twist_twk', 'control_twist':'c_arm_twist_offset', 'stretch':'arm_stretch', 'control_fk':'c_arm_fk', 'fk':'arm_fk', 'ik':'arm_ik', 'control_ik':'c_arm_ik', 'ik_scale_fix':'arm_ik_nostr_scale_fix', 'ik_nostr':'arm_ik_nostr', 'secondary_00':'c_shoulder_bend','secondary_01':'c_arm_bend'},
|
||||
'forearm':{'base':'forearm', 'control_fk':'c_forearm_fk', 'fk':'forearm_fk', 'ik':'forearm_ik', 'ik_nostr':'forearm_ik_nostr', 'stretch':'forearm_stretch', 'twist':'forearm_twist', 'secondary_00':'c_elbow_bend', 'secondary_01':'c_forearm_bend', 'secondary_02':'c_wrist_bend'},
|
||||
'hand':{'deform':'hand', 'control_fk':'c_hand_fk', 'control_ik':'c_hand_ik', 'control_ik_offset':'c_hand_ik_offset', 'fk_scale_fix':'c_hand_fk_scale_fix', 'rot_twist':'hand_rot_twist', 'secondary_00':'hand_bend'},
|
||||
'hand':{'deform':'hand', 'control_fk':'c_hand_fk', 'control_ik':'c_hand_ik', 'control_ik_offset':'c_hand_ik_offset', 'ik_pivot':'hand_ik_pivot', 'control_ik_pivot':'c_hand_ik_pivot', 'fk_scale_fix':'hand_fk_scale_fix', 'rot_twist':'hand_rot_twist', 'secondary_00':'hand_bend'},
|
||||
'prepole':'arm_fk_pre_pole',
|
||||
'fk_pole':'arm_fk_pole',
|
||||
'control_pin':'c_stretch_arm_pin',
|
||||
'control_stretch':'c_stretch_arm',
|
||||
'control_pole_ik': 'c_arms_pole'}
|
||||
'control_pole_ik': 'c_arms_pole',
|
||||
'pole_line': 'arm_pole_line'}
|
||||
|
||||
arm_bones = []
|
||||
for i, j in arm_bones_dict.items():
|
||||
@@ -184,10 +185,11 @@ leg_bones_dict = {
|
||||
'upthigh_helper': {'1': 'thigh_b_h', '2': 'thigh_b_loc'},
|
||||
'thigh': {'base':'thigh', 'fk':'thigh_fk', 'ik':'thigh_ik', 'ik_nostr':'thigh_ik_nostr', 'control_fk':'c_thigh_fk', 'control_ik':'c_thigh_ik', 'twist':'thigh_twist', 'stretch':'thigh_stretch', 'secondary_00':'c_thigh_bend_contact', 'secondary_01':'c_thigh_bend_01', 'secondary_02':'c_thigh_bend_02'},
|
||||
'calf':{'base':'leg', 'fk':'leg_fk', 'ik':'leg_ik', 'ik_nostr':'leg_ik_nostr', 'control_fk':'c_leg_fk', 'control_ik3':'c_leg_ik3', 'twist':'leg_twist', 'stretch':'leg_stretch', 'secondary_00':'c_knee_bend', 'secondary_01':'c_leg_bend_01', 'secondary_02':'c_leg_bend_02', 'secondary_03':'c_ankle_bend'},
|
||||
'foot':{'fk':'foot_fk', 'control_fk':'c_foot_fk', 'snap_fk':'foot_snap_fk', 'ik':'foot_ik', 'ik_target':'foot_ik_target', 'control_ik':'c_foot_ik', 'deform':'foot', 'pole':'foot_pole', 'fk_scale_fix':'c_foot_fk_scale_fix', 'shape_override_fk':'c_p_foot_fk', 'shape_override_ik':'c_p_foot_ik', 'bank_01':'c_foot_bank_01', 'bank_02':'c_foot_bank_02', 'foot_heel':'c_foot_heel', 'control_reverse':'c_foot_01', 'pole_01':'foot_01_pole', 'roll':'c_foot_roll', 'control_roll':'c_foot_roll_cursor', 'secondary_00':'foot_bend', 'control_ik_offset':'c_foot_ik_offset', 'foot_fans_helper':'foot_fans_h'},
|
||||
'toes':{'01': 'toes_01', '02': 'toes_02', '01_ik': 'toes_01_ik', 'control_fk': 'c_toes_fk', 'control_ik':'c_toes_ik', 'toes_track': 'c_toes_track', 'toes_end': 'c_toes_end', 'toes_end_01':'c_toes_end_01', 'control_pivot':'c_toes_pivot'},
|
||||
'foot':{'fk':'foot_fk', 'control_fk':'c_foot_fk', 'snap_fk':'foot_snap_fk', 'ik':'foot_ik', 'ik_target':'foot_ik_target', 'control_ik':'c_foot_ik', 'deform':'foot', 'pole':'foot_pole', 'fk_scale_fix':'foot_fk_scale_fix', 'shape_override_fk':'c_p_foot_fk', 'shape_override_ik':'c_p_foot_ik', 'bank_01':'foot_bank_01', 'bank_02':'foot_bank_02', 'foot_heel':'foot_heel', 'control_reverse':'c_foot_01', 'pole_01':'foot_01_pole', 'roll':'foot_roll', 'control_roll':'c_foot_roll_cursor', 'secondary_00':'foot_bend', 'ik_pivot':'foot_ik_pivot', 'control_ik_pivot':'c_foot_ik_pivot','control_ik_offset':'c_foot_ik_offset', 'foot_fans_helper':'foot_fans_h'},
|
||||
'toes':{'01': 'toes_01', '02': 'toes_02', '01_ik': 'toes_01_ik', 'control_fk': 'c_toes_fk', 'control_ik':'c_toes_ik', 'toes_track': 'toes_track', 'toes_end': 'toes_end', 'toes_end_01':'toes_end_01', 'control_pivot':'c_toes_pivot'},
|
||||
'prepole':'leg_fk_pre_pole',
|
||||
'control_pole_ik':'c_leg_pole',
|
||||
'pole_line': 'leg_pole_line',
|
||||
'fk_pole':'leg_fk_pole',
|
||||
'control_stretch':'c_stretch_leg',
|
||||
'control_pin':'c_stretch_leg_pin',
|
||||
@@ -240,7 +242,7 @@ leg_deform = [
|
||||
leg_bones_dict['upthigh2']]
|
||||
|
||||
leg_control = [
|
||||
leg_bones_dict['upthigh'], leg_bones_dict['thigh']['control_fk'], leg_bones_dict['thigh']['control_ik'], leg_bones_dict['calf']['control_fk'], leg_bones_dict['toes_pinky1'], leg_bones_dict['toes_pinky2'], leg_bones_dict['toes_pinky3'], leg_bones_dict['toes_ring1'], leg_bones_dict['toes_ring2'], leg_bones_dict['toes_ring3'], leg_bones_dict['toes_middle1'], leg_bones_dict['toes_middle2'], leg_bones_dict['toes_middle3'], leg_bones_dict['toes_index1'], leg_bones_dict['toes_index2'], leg_bones_dict['toes_index3'], leg_bones_dict['toes_thumb1'], leg_bones_dict['toes_thumb2'], leg_bones_dict['foot']['control_fk'], leg_bones_dict['toes']['control_fk'], leg_bones_dict['control_stretch'], leg_bones_dict['calf']['secondary_03'], leg_bones_dict['calf']['secondary_02'], leg_bones_dict['calf']['secondary_01'], leg_bones_dict['calf']['secondary_00'], leg_bones_dict['thigh']['secondary_02'], leg_bones_dict['thigh']['secondary_01'], leg_bones_dict['thigh']['secondary_00'], leg_bones_dict['control_pin'], leg_bones_dict['foot']['control_ik'], leg_bones_dict['foot']['control_ik_offset'], leg_bones_dict['toes']['control_ik'], leg_bones_dict['foot']['control_reverse'], leg_bones_dict['foot']['control_roll'], leg_bones_dict['control_pole_ik']]
|
||||
leg_bones_dict['upthigh'], leg_bones_dict['thigh']['control_fk'], leg_bones_dict['thigh']['control_ik'], leg_bones_dict['calf']['control_fk'], leg_bones_dict['toes_pinky1'], leg_bones_dict['toes_pinky2'], leg_bones_dict['toes_pinky3'], leg_bones_dict['toes_ring1'], leg_bones_dict['toes_ring2'], leg_bones_dict['toes_ring3'], leg_bones_dict['toes_middle1'], leg_bones_dict['toes_middle2'], leg_bones_dict['toes_middle3'], leg_bones_dict['toes_index1'], leg_bones_dict['toes_index2'], leg_bones_dict['toes_index3'], leg_bones_dict['toes_thumb1'], leg_bones_dict['toes_thumb2'], leg_bones_dict['foot']['control_fk'], leg_bones_dict['toes']['control_fk'], leg_bones_dict['control_stretch'], leg_bones_dict['calf']['secondary_03'], leg_bones_dict['calf']['secondary_02'], leg_bones_dict['calf']['secondary_01'], leg_bones_dict['calf']['secondary_00'], leg_bones_dict['thigh']['secondary_02'], leg_bones_dict['thigh']['secondary_01'], leg_bones_dict['thigh']['secondary_00'], leg_bones_dict['control_pin'], leg_bones_dict['foot']['control_ik'], leg_bones_dict['foot']['control_ik_offset'], leg_bones_dict['foot']['control_ik_pivot'],leg_bones_dict['toes']['control_ik'], leg_bones_dict['foot']['control_reverse'], leg_bones_dict['foot']['control_roll'], leg_bones_dict['control_pole_ik']]
|
||||
|
||||
|
||||
leg_props = {'soft_ik': 'leg_softik', 'auto_ik_roll': 'leg_auto_ik_roll'}
|
||||
@@ -275,7 +277,6 @@ def get_leg_toes_ikfk(leg_side, btype='ALL', no_side=False):
|
||||
return list
|
||||
|
||||
|
||||
|
||||
def get_leg_joint_fans(leg_side, btype='ALL', no_side=False):
|
||||
types = [btype]
|
||||
if btype == 'ALL':
|
||||
@@ -336,6 +337,7 @@ mouth_bones_ref_dict = {'lips_top_mid': 'lips_top_ref.x',
|
||||
'lips_roll_top': 'lips_roll_top_ref.x',
|
||||
'lips_roll_bot': 'lips_roll_bot_ref.x',
|
||||
'lips_offset': 'lips_offset_ref.x',
|
||||
'muzzle': 'muzzle_ref.x',
|
||||
'jaw':'jaw_ref.x'
|
||||
}
|
||||
|
||||
@@ -368,7 +370,8 @@ mouth_bones_dict = {
|
||||
'c_lips_smile_offset': {'name':'c_lips_smile_offset', 'deform':False, 'control':False},
|
||||
'c_lips_smile': {'name':'c_lips_smile', 'deform':True, 'control':True},
|
||||
'c_lips_corner_mini': {'name':'c_lips_corner_mini', 'deform':True, 'control':True},
|
||||
'c_lips_offset': {'name':'c_lips_offset.x', 'deform':False, 'control':True}
|
||||
'c_lips_offset': {'name':'c_lips_offset.x', 'deform':False, 'control':True},
|
||||
'c_muzzle': {'name':'c_muzzle.x', 'deform':False, 'control':True}
|
||||
}
|
||||
|
||||
|
||||
@@ -403,7 +406,7 @@ def get_variable_lips(head_side, btype='REFERENCE', no_side=False, levels=['top_
|
||||
lips_list = []
|
||||
|
||||
for subtype in types:
|
||||
for lip_id in range(1,32):
|
||||
for lip_id in range(1, 32):
|
||||
for _side in ['.l', '.r']:
|
||||
for lvl in levels:
|
||||
bname = ''
|
||||
@@ -533,7 +536,6 @@ for i in teeth_ref_base:
|
||||
teeth_ref.append(i+'.r')
|
||||
|
||||
# tongues
|
||||
|
||||
tongue_bones_ref_dict = {'tong_01':'tong_01_ref.x',
|
||||
'tong_02':'tong_02_ref.x',
|
||||
'tong_03':'tong_03_ref.x'
|
||||
@@ -550,6 +552,33 @@ tongue_bones_dict = {'c_tong_01': {'name':'c_tong_01.x', 'deform':False, 'contro
|
||||
tongue_ref = [j for i, j in tongue_bones_ref_dict.items()]
|
||||
tongue_bones = [tongue_bones_dict[i]['name'] for i in tongue_bones_dict]
|
||||
|
||||
|
||||
def get_tongues(side='.x', type='ALL', no_side=True, side_x=False):
|
||||
list = []
|
||||
ctrl = []
|
||||
ref = []
|
||||
deform = []
|
||||
_side = '' if no_side else side
|
||||
_sidex = '.x' if side_x else ''
|
||||
for _i in range(1, 33):
|
||||
stri = '%02d' % _i
|
||||
if bpy.context.active_object.data.bones.get('tong_'+stri+'_ref'+side):
|
||||
ctrl.append('c_tong_'+stri+_side+_sidex)
|
||||
ref.append('tong_'+stri+'_ref'+_side+_sidex)
|
||||
deform.append('tong_'+stri+_side+_sidex)
|
||||
|
||||
if type == 'ALL':
|
||||
list = ctrl + ref + deform
|
||||
elif type == 'CTRL':
|
||||
list = ctrl
|
||||
elif type == 'REF':
|
||||
list = ref
|
||||
elif type == 'DEF':
|
||||
list = deform
|
||||
elif type == 'NON_REF':
|
||||
list = ctrl + deform
|
||||
|
||||
return list
|
||||
|
||||
|
||||
# eyes
|
||||
@@ -662,26 +691,24 @@ eyebrow_bones_left = [i+'.l' for i in eyebrow_bones] + [i+'.l' for i in eyebrow_
|
||||
eyebrow_bones_right = [i+'.r' for i in eyebrow_bones] + [i+'.r' for i in eyebrow_ref]
|
||||
|
||||
|
||||
def get_eyebrows(type='ALL', include_full=True):
|
||||
def get_eyebrows(side='.l', type='ALL', include_full=True):
|
||||
list = []
|
||||
main_ctrl = []
|
||||
ref = []
|
||||
|
||||
main_ctrl = [eyebrow_bones_dict['eyebrow_01_end']['name'],
|
||||
eyebrow_bones_dict['eyebrow_01']['name'],
|
||||
eyebrow_bones_dict['eyebrow_02']['name'],
|
||||
eyebrow_bones_dict['eyebrow_03']['name']]
|
||||
|
||||
ref = [eyebrow_bones_ref_dict['eyebrow_01_end'],
|
||||
eyebrow_bones_ref_dict['eyebrow_01'],
|
||||
eyebrow_bones_ref_dict['eyebrow_02'],
|
||||
eyebrow_bones_ref_dict['eyebrow_03']]
|
||||
if side.endswith('.x'):
|
||||
side = side[:-2]+'.l'# same count for left and right brows for now
|
||||
|
||||
for _i in range(0, 32):
|
||||
stri = '%02d' % _i if _i > 0 else '01_end'
|
||||
if bpy.context.active_object.data.bones.get('eyebrow_'+stri+'_ref'+side):
|
||||
main_ctrl.append('c_eyebrow_'+stri)
|
||||
ref.append('eyebrow_'+stri+'_ref')
|
||||
|
||||
|
||||
master_ctrl = [eyebrow_bones_dict['eyebrow_full']['name']]
|
||||
|
||||
master_ctrl = [eyebrow_bones_dict['eyebrow_full']['name']]
|
||||
master_ref = [eyebrow_bones_ref_dict['eyebrow_full']]
|
||||
|
||||
offsets = []
|
||||
|
||||
|
||||
if type == 'ALL':
|
||||
list = main_ctrl + ref
|
||||
if include_full:
|
||||
@@ -885,9 +912,6 @@ def get_spline_ik(rig, side):
|
||||
return None
|
||||
|
||||
|
||||
# Tail
|
||||
tail_bones = ['c_tail_master']
|
||||
|
||||
|
||||
#SMART FACIAL MARKERS
|
||||
facial_markers = {'eyebrow_01_end.l': 15, 'eyebrow_01.l':16, 'eyebrow_02.l':17, 'eyebrow_03.l':18, 'eyebrow_01_end.r':40, 'eyebrow_01.r':41, 'eyebrow_02.r':42, 'eyebrow_03.r':43,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
import bpy, os, platform, sys, ast
|
||||
import bpy, os, platform, sys, ast, subprocess
|
||||
|
||||
def update_all_tab_names(self, context):
|
||||
try:
|
||||
@@ -79,11 +79,13 @@ class ARP_OT_save_prefs(bpy.types.Operator):
|
||||
'arp_tab_name':get_prefs().arp_tab_name,
|
||||
'arp_tools_tab_name':get_prefs().arp_tools_tab_name,
|
||||
'beginner_mode': get_prefs().beginner_mode,
|
||||
'xray_display': get_prefs().xray_display,
|
||||
'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,
|
||||
'ai_presets_path': get_prefs().ai_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,
|
||||
@@ -104,6 +106,70 @@ class ARP_OT_save_prefs(bpy.types.Operator):
|
||||
print(fp)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def get_downloads_folder():
|
||||
"""Get the default downloads folder path based on the operating system."""
|
||||
system = platform.system().lower()
|
||||
home = os.path.expanduser("~") # Gets the user's home directory
|
||||
downloads = os.path.join(home, "Downloads")
|
||||
|
||||
# Ensure the path exists (optional, remove if you just want the path)
|
||||
if not os.path.exists(downloads):
|
||||
os.makedirs(downloads, exist_ok=True)
|
||||
|
||||
return downloads
|
||||
|
||||
|
||||
def extract_zip(source_zip, destination_folder):
|
||||
import zipfile, stat
|
||||
|
||||
os.makedirs(destination_folder, exist_ok=True)
|
||||
|
||||
print("Unzip...")
|
||||
|
||||
# Use unzip process instead of zipfile for better fidelity on Linux and Mac
|
||||
if platform.system() in ["Darwin", "Linux"]:
|
||||
if platform.system() == "Darwin":
|
||||
result = subprocess.run(['xattr', source_zip], capture_output=True, text=True)
|
||||
if 'com.apple.quarantine' in result.stdout:
|
||||
subprocess.run(['xattr', '-d', 'com.apple.quarantine', source_zip], check=True)
|
||||
|
||||
result = subprocess.run(["unzip", "-o", source_zip, "-d", destination_folder], capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
raise Exception(f"unzip failed: {result.stderr}")
|
||||
print(f"unzip output: {result.stdout}")
|
||||
else:
|
||||
# Fallback to zipfile for other platforms
|
||||
with zipfile.ZipFile(source_zip, 'r') as zip_ref:
|
||||
zip_ref.extractall(destination_folder)
|
||||
|
||||
return True, f"Successfully extracted {source_zip} to {destination_folder}"
|
||||
|
||||
|
||||
class ARP_OT_install_ext(bpy.types.Operator):
|
||||
"""Install external dependencies"""
|
||||
|
||||
bl_idname = 'arp.install_ext'
|
||||
bl_label = 'Install Files'
|
||||
|
||||
filepath: bpy.props.StringProperty(subtype='FILE_PATH', default='name')
|
||||
filename_ext = '.zip'
|
||||
|
||||
def invoke(self, context, event):
|
||||
self.filepath = downloads_path = get_downloads_folder()
|
||||
context.window_manager.fileselect_add(self)
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def execute(self, context):
|
||||
if not self.filepath.endswith('.zip'):
|
||||
self.report({'ERROR'}, 'Select the zip file to install')
|
||||
return {'FINISHED'}
|
||||
print("Extract zip...")
|
||||
extract_zip(self.filepath, get_prefs().ai_presets_path)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ARP_MT_arp_addon_preferences(bpy.types.AddonPreferences):
|
||||
@@ -111,62 +177,123 @@ class ARP_MT_arp_addon_preferences(bpy.types.AddonPreferences):
|
||||
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)
|
||||
beginner_mode: bpy.props.BoolProperty(name='Beginner Mode', default=True, description='Show helper icons in the menus, to open contextual documentation links')
|
||||
xray_display: bpy.props.BoolProperty(name='XRay Display', default=False, description='Always show the rig in X-Ray display/ In Front\nApplied when Match to Rig')
|
||||
|
||||
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')
|
||||
ai_presets_path: bpy.props.StringProperty(name='AI', subtype='FILE_PATH', default=get_documents_path('AI'), description='Path to store AI executables')
|
||||
|
||||
parent_bound_objects: bpy.props.BoolProperty(default=True, description='Parent the objects to the armature when binding.\nTurning it off *is not recommended* as it can lead to issues, but it may be useful for some custom workflows')
|
||||
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')
|
||||
remove_existing_arm_mods: bpy.props.BoolProperty(default=True, name='Remove Armature Modifiers', description='Remove any existing armature modifiers on meshes, when binding.\nIt is generally recommended to enable it, in order to avoid older modifiers to interfere with new ones')
|
||||
remove_existing_vgroups: bpy.props.BoolProperty(default=True, name='Remove Existing Vertex Groups', description='Remove existing vertex groups when binding. It is generally recommended to enable it, in order to avoid older vertex groups to interfere with new ones')
|
||||
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')
|
||||
|
||||
arp_master_collec_name: bpy.props.StringProperty(default='', description='Group all Auto-Rig Pro armature collections under this collection. None if blank name\nApplied when Match to Rig')
|
||||
|
||||
|
||||
def draw(self, context):
|
||||
col = self.layout.column(align=True)
|
||||
layout = self.layout
|
||||
col = 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')
|
||||
def show_rig_ui(panel):
|
||||
if panel:
|
||||
col = panel.column(align=True)
|
||||
else:
|
||||
col = layout.column(align=True)
|
||||
col.label(text='Rig:')
|
||||
col.prop(self, 'arp_master_collec_name', text='Master ARP Collec')
|
||||
col.prop(self, 'xray_display', text='X-Ray Display')
|
||||
col.prop(self, 'parent_bound_objects', text='Parent Objects when Binding')
|
||||
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()
|
||||
|
||||
if bpy.app.version >= (4,1,0):
|
||||
header_rig, panel_rig = layout.panel("arp_pref_ui_rig", default_closed=False)
|
||||
header_rig.label(text="Rig")
|
||||
if panel_rig:# None if collapsed
|
||||
show_rig_ui(panel_rig)
|
||||
else:
|
||||
show_rig_ui(None)
|
||||
|
||||
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 show_interface_ui(panel):
|
||||
if panel:
|
||||
col = panel.column(align=True)
|
||||
else:
|
||||
col = layout.column(align=True)
|
||||
col.label(text='Interface:')
|
||||
row = col.row(align=True)
|
||||
row.prop(self, 'beginner_mode', text='Beginner Mode (help buttons)')
|
||||
col.separator()
|
||||
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()
|
||||
|
||||
if bpy.app.version >= (4,1,0):
|
||||
header_interface, panel_interface = layout.panel("arp_pref_ui_interface", default_closed=False)
|
||||
header_interface.label(text="Interface")
|
||||
if panel_interface:# None if collapsed
|
||||
show_interface_ui(panel_interface)
|
||||
else:
|
||||
show_interface_ui(None)
|
||||
|
||||
def show_paths_ui(panel):
|
||||
if panel:
|
||||
col = panel.column(align=True)
|
||||
else:
|
||||
col = layout.column(align=True)
|
||||
col.label(text='Paths:')
|
||||
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.prop(self, 'ai_presets_path')
|
||||
col.operator('arp.install_ext', text='Install AI files...')
|
||||
col.separator()
|
||||
|
||||
if bpy.app.version >= (4,1,0):
|
||||
header_paths, panel_paths = layout.panel("arp_pref_ui_paths", default_closed=False)
|
||||
header_paths.label(text="Paths")
|
||||
if panel_paths:# None if collapsed
|
||||
show_paths_ui(panel_paths)
|
||||
else:
|
||||
show_paths_ui(None)
|
||||
|
||||
def show_debug_ui(panel):
|
||||
if panel:
|
||||
col = panel.column()
|
||||
else:
|
||||
col = layout.column(align=True)
|
||||
col.label(text='Debug:')
|
||||
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')
|
||||
|
||||
if bpy.app.version >= (4,1,0):
|
||||
header_debug, panel_debug = layout.panel("arp_pref_ui_debug", default_closed=True)
|
||||
header_debug.label(text="Debug")
|
||||
if panel_debug:# None if collapsed
|
||||
show_debug_ui(panel_debug)
|
||||
else:
|
||||
show_debug_ui(None)
|
||||
|
||||
|
||||
def load_arp_prefs():
|
||||
@@ -214,6 +341,7 @@ def register():
|
||||
try:
|
||||
register_class(ARP_MT_arp_addon_preferences)
|
||||
register_class(ARP_OT_save_prefs)
|
||||
register_class(ARP_OT_install_ext)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -225,6 +353,7 @@ def register():
|
||||
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)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
{'verts': [(-0.09828728437423706, -0.09828728437423706, -0.09828728437423706), (-0.09828728437423706, -0.09828728437423706, 0.09828728437423706), (-0.09828728437423706, 0.09828728437423706, -0.09828728437423706), (-0.09828728437423706, 0.09828728437423706, 0.09828728437423706), (0.09828728437423706, -0.09828728437423706, -0.09828728437423706), (0.09828728437423706, -0.09828728437423706, 0.09828728437423706), (0.09828728437423706, 0.09828728437423706, -0.09828728437423706), (0.09828728437423706, 0.09828728437423706, 0.09828728437423706)], 'edges': [(2, 0), (0, 1), (1, 3), (3, 2), (6, 2), (3, 7), (7, 6), (4, 6), (7, 5), (5, 4), (0, 4), (5, 1)], 'faces': []}
|
||||
@@ -0,0 +1 @@
|
||||
{'verts': [(2.2649766151516815e-07, 0.15570558607578278, -7.078047481456906e-09), (-0.059585727751255035, 0.14385320246219635, -7.078047481456906e-09), (-0.11010024696588516, 0.11010047048330307, -7.0780492578137455e-09), (-0.14385297894477844, 0.05958595126867294, -7.0780492578137455e-09), (-0.15570536255836487, -6.806108121537591e-09, -7.078051922349005e-09), (-0.14385297894477844, -0.05958595871925354, -7.078052810527424e-09), (-0.11010024696588516, -0.11010047048330307, -7.078053698705844e-09), (-0.059585727751255035, -0.14385320246219635, -7.078055475062683e-09), (2.0298676872698707e-07, -0.15570558607578278, -7.078056363241103e-09), (0.059586141258478165, -0.14385321736335754, -7.078055475062683e-09), (0.11010067909955978, -0.11010051518678665, -7.078053698705844e-09), (0.14385342597961426, -0.059585969895124435, -7.078052810527424e-09), (0.15570582449436188, 1.856770626140758e-09, -7.078051922349005e-09), (0.14385342597961426, 0.05958597734570503, -7.0780492578137455e-09), (0.11010066419839859, 0.11010051518678665, -7.0780492578137455e-09), (0.05958610400557518, 0.14385323226451874, -7.078047481456906e-09), (2.2649766151516815e-07, -1.2993086784263141e-07, -0.15570560097694397), (-0.05958572402596474, -1.2993086784263141e-07, -0.14385321736335754), (-0.11010023206472397, -9.280776680498093e-08, -0.11010048538446426), (-0.14385296404361725, -5.1044271032196775e-08, -0.05958595499396324), (-0.15570534765720367, 6.084951285385701e-15, -2.7194335672220404e-10), (-0.14385296404361725, 5.1044271032196775e-08, 0.05958595499396324), (-0.11010023206472397, 9.280776680498093e-08, 0.11010045558214188), (-0.059585727751255035, 1.2993086784263141e-07, 0.14385318756103516), (2.0298676872698707e-07, 1.2993086784263141e-07, 0.15570557117462158), (0.059586141258478165, 1.2993086784263141e-07, 0.14385321736335754), (0.11010066419839859, 9.280776680498093e-08, 0.11010050028562546), (0.14385342597961426, 5.1044271032196775e-08, 0.05958596616983414), (0.1557057946920395, -1.6595322553699244e-15, -8.934822659512065e-09), (0.14385341107845306, -5.1044271032196775e-08, -0.05958598479628563), (0.110100656747818, -9.280776680498093e-08, -0.11010053008794785), (0.05958609655499458, -1.2993086784263141e-07, -0.14385324716567993), (2.0112997844989877e-07, 0.15570557117462158, -3.244572610583418e-08), (2.5433996597712394e-07, 0.14385318756103516, -0.05958598479628563), (3.007438635904691e-07, 0.11010044813156128, -0.11010050773620605), (3.471477612038143e-07, 0.05958591774106026, -0.14385321736335754), (3.5642852935779956e-07, -3.217376942643568e-08, -0.15570560097694397), (3.657092975117848e-07, -0.05958598107099533, -0.14385320246219635), (3.3786699304982903e-07, -0.11010048538446426, -0.11010046303272247), (3.007438635904691e-07, -0.14385320246219635, -0.059585943818092346), (2.518653445804375e-07, -0.15570557117462158, -5.22126430979597e-09), (1.9865532863150293e-07, -0.14385320246219635, 0.05958593636751175), (1.5225145943986718e-07, -0.11010048538446426, 0.11010045558214188), (1.0584753340481257e-07, -0.059585943818092346, 0.14385321736335754), (9.656679367253673e-08, 2.7224428933436684e-08, 0.15570557117462158), (8.728602551855147e-08, 0.059585995972156525, 0.14385317265987396), (1.1512832998050726e-07, 0.11010052263736725, 0.11010041832923889), (1.5225145943986718e-07, 0.14385323226451874, 0.059585850685834885)], 'edges': [(1, 0), (2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (9, 8), (10, 9), (11, 10), (12, 11), (13, 12), (14, 13), (15, 14), (0, 15), (17, 16), (18, 17), (19, 18), (20, 19), (21, 20), (22, 21), (23, 22), (24, 23), (25, 24), (26, 25), (27, 26), (28, 27), (29, 28), (30, 29), (31, 30), (16, 31), (33, 32), (34, 33), (35, 34), (36, 35), (37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45), (47, 46), (32, 47)], 'faces': []}
|
||||
@@ -0,0 +1 @@
|
||||
{'verts': [(2.2649766151516815e-07, 0.6557056903839111, 0.000808675482403487), (-0.059585727751255035, 0.6438533067703247, 0.000808675482403487), (-0.11010024696588516, 0.6101005673408508, 0.000808675482403487), (-0.14385297894477844, 0.5595860481262207, 0.000808675482403487), (-0.15570536255836487, 0.5000001192092896, 0.000808675482403487), (-0.14385297894477844, 0.440414160490036, 0.000808675482403487), (-0.11010024696588516, 0.3898996412754059, 0.000808675482403487), (-0.059585727751255035, 0.3561469316482544, 0.000808675482403487), (2.0298676872698707e-07, 0.34429454803466797, 0.000808675482403487), (0.059586141258478165, 0.356146901845932, 0.000808675482403487), (0.11010067909955978, 0.3898996114730835, 0.000808675482403487), (0.14385342597961426, 0.440414160490036, 0.000808675482403487), (0.15570582449436188, 0.5000001192092896, 0.000808675482403487), (0.14385342597961426, 0.5595861077308655, 0.000808675482403487), (0.11010066419839859, 0.6101006269454956, 0.000808675482403487), (0.05958610400557518, 0.6438533663749695, 0.000808675482403487), (2.2649766151516815e-07, 0.5, -0.15489688515663147), (-0.05958572402596474, 0.5, -0.14304450154304504), (-0.11010023206472397, 0.5, -0.10929182916879654), (-0.14385296404361725, 0.5000000596046448, -0.058777254074811935), (-0.15570534765720367, 0.5000001192092896, 0.000808675482403487), (-0.14385296404361725, 0.5000001788139343, 0.06039463356137276), (-0.11010023206472397, 0.5000002384185791, 0.1109091266989708), (-0.059585727751255035, 0.5000002384185791, 0.14466187357902527), (2.0298676872698707e-07, 0.5000002384185791, 0.1565142571926117), (0.059586141258478165, 0.5000002384185791, 0.14466190338134766), (0.11010066419839859, 0.5000002384185791, 0.11090918630361557), (0.14385342597961426, 0.5000001788139343, 0.06039463356137276), (0.1557057946920395, 0.5000001192092896, 0.000808675482403487), (0.14385341107845306, 0.5000000596046448, -0.05877731367945671), (0.110100656747818, 0.5, -0.10929182916879654), (0.05958609655499458, 0.5, -0.14304456114768982), (2.0112997844989877e-07, 0.6557056903839111, 0.0008086158777587116), (2.5433996597712394e-07, 0.6438533067703247, -0.05877731367945671), (3.007438635904691e-07, 0.6101005673408508, -0.10929182916879654), (3.471477612038143e-07, 0.5595860481262207, -0.14304450154304504), (3.5642852935779956e-07, 0.5000000596046448, -0.15489688515663147), (3.657092975117848e-07, 0.4404141306877136, -0.14304450154304504), (3.3786699304982903e-07, 0.3898996412754059, -0.10929176956415176), (3.007438635904691e-07, 0.3561469316482544, -0.058777254074811935), (2.518653445804375e-07, 0.34429454803466797, 0.000808675482403487), (1.9865532863150293e-07, 0.3561469316482544, 0.06039460375905037), (1.5225145943986718e-07, 0.3898996412754059, 0.1109091266989708), (1.0584753340481257e-07, 0.4404141902923584, 0.14466190338134766), (9.656679367253673e-08, 0.5000001192092896, 0.1565142571926117), (8.728602551855147e-08, 0.5595861077308655, 0.14466187357902527), (1.1512832998050726e-07, 0.6101006269454956, 0.11090909689664841), (1.5225145943986718e-07, 0.6438533663749695, 0.060394514352083206)], 'edges': [(1, 0), (2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (9, 8), (10, 9), (11, 10), (12, 11), (13, 12), (14, 13), (15, 14), (0, 15), (17, 16), (18, 17), (19, 18), (20, 19), (21, 20), (22, 21), (23, 22), (24, 23), (25, 24), (26, 25), (27, 26), (28, 27), (29, 28), (30, 29), (31, 30), (16, 31), (33, 32), (34, 33), (35, 34), (36, 35), (37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45), (47, 46), (32, 47)], 'faces': []}
|
||||
@@ -0,0 +1 @@
|
||||
{'verts': [(2.2649766151516815e-07, 1.1557058095932007, 0.000906810280866921), (-0.059585727751255035, 1.1438534259796143, 0.000906810280866921), (-0.11010024696588516, 1.1101007461547852, 0.000906810280866921), (-0.14385297894477844, 1.0595861673355103, 0.000906810280866921), (-0.15570536255836487, 1.000000238418579, 0.000906810280866921), (-0.14385297894477844, 0.940414309501648, 0.000906810280866921), (-0.11010024696588516, 0.889899730682373, 0.000906810280866921), (-0.059585727751255035, 0.856147050857544, 0.000906810280866921), (2.0298676872698707e-07, 0.8442946672439575, 0.000906810280866921), (0.059586141258478165, 0.856147050857544, 0.000906810280866921), (0.11010067909955978, 0.889899730682373, 0.000906810280866921), (0.14385342597961426, 0.940414309501648, 0.000906810280866921), (0.15570582449436188, 1.000000238418579, 0.000906810280866921), (0.14385342597961426, 1.0595862865447998, 0.000906810280866921), (0.11010066419839859, 1.1101007461547852, 0.000906810280866921), (0.05958610400557518, 1.1438534259796143, 0.000906810280866921), (2.2649766151516815e-07, 1.0000001192092896, -0.1547987461090088), (-0.05958572402596474, 1.0000001192092896, -0.14294636249542236), (-0.11010023206472397, 1.0000001192092896, -0.10919369757175446), (-0.14385296404361725, 1.000000238418579, -0.05867911875247955), (-0.15570534765720367, 1.000000238418579, 0.000906810280866921), (-0.14385296404361725, 1.000000238418579, 0.06049273908138275), (-0.11010023206472397, 1.0000003576278687, 0.11100725829601288), (-0.059585727751255035, 1.0000003576278687, 0.14476001262664795), (2.0298676872698707e-07, 1.0000003576278687, 0.15661239624023438), (0.059586141258478165, 1.0000003576278687, 0.14476001262664795), (0.11010066419839859, 1.0000003576278687, 0.11100731790065765), (0.14385342597961426, 1.000000238418579, 0.06049273908138275), (0.1557057946920395, 1.000000238418579, 0.000906810280866921), (0.14385341107845306, 1.000000238418579, -0.058679237961769104), (0.110100656747818, 1.0000001192092896, -0.10919369757175446), (0.05958609655499458, 1.0000001192092896, -0.14294636249542236), (2.0112997844989877e-07, 1.1557058095932007, 0.000906810280866921), (2.5433996597712394e-07, 1.1438534259796143, -0.058679237961769104), (3.007438635904691e-07, 1.1101007461547852, -0.10919369757175446), (3.471477612038143e-07, 1.0595861673355103, -0.14294636249542236), (3.5642852935779956e-07, 1.000000238418579, -0.1547987461090088), (3.657092975117848e-07, 0.9404142498970032, -0.14294636249542236), (3.3786699304982903e-07, 0.889899730682373, -0.10919369757175446), (3.007438635904691e-07, 0.856147050857544, -0.05867911875247955), (2.518653445804375e-07, 0.8442946672439575, 0.000906810280866921), (1.9865532863150293e-07, 0.856147050857544, 0.06049273908138275), (1.5225145943986718e-07, 0.889899730682373, 0.11100725829601288), (1.0584753340481257e-07, 0.940414309501648, 0.14476001262664795), (9.656679367253673e-08, 1.000000238418579, 0.15661239624023438), (8.728602551855147e-08, 1.0595862865447998, 0.14476001262664795), (1.1512832998050726e-07, 1.1101007461547852, 0.1110071986913681), (1.5225145943986718e-07, 1.1438534259796143, 0.0604926198720932)], 'edges': [(1, 0), (2, 1), (3, 2), (4, 3), (5, 4), (6, 5), (7, 6), (8, 7), (9, 8), (10, 9), (11, 10), (12, 11), (13, 12), (14, 13), (15, 14), (0, 15), (17, 16), (18, 17), (19, 18), (20, 19), (21, 20), (22, 21), (23, 22), (24, 23), (25, 24), (26, 25), (27, 26), (28, 27), (29, 28), (30, 29), (31, 30), (16, 31), (33, 32), (34, 33), (35, 34), (36, 35), (37, 36), (38, 37), (39, 38), (40, 39), (41, 40), (42, 41), (43, 42), (44, 43), (45, 44), (46, 45), (47, 46), (32, 47)], 'faces': []}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{'verts': [(-0.09828728437423706, 0.40223199129104614, -0.09828728437423706), (-0.09828728437423706, 0.40223199129104614, 0.09828728437423706), (-0.09828728437423706, 0.5988066792488098, -0.09828728437423706), (-0.09828728437423706, 0.5988066792488098, 0.09828728437423706), (0.09828728437423706, 0.40223199129104614, -0.09828728437423706), (0.09828728437423706, 0.40223199129104614, 0.09828728437423706), (0.09828728437423706, 0.5988066792488098, -0.09828728437423706), (0.09828728437423706, 0.5988066792488098, 0.09828728437423706)], 'edges': [(2, 0), (0, 1), (1, 3), (3, 2), (6, 2), (3, 7), (7, 6), (4, 6), (7, 5), (5, 4), (0, 4), (5, 1)], 'faces': []}
|
||||
@@ -0,0 +1 @@
|
||||
{'verts': [(-0.09828728437423706, 0.9076300859451294, -0.09828728437423706), (-0.09828728437423706, 0.9076300859451294, 0.09828728437423706), (-0.09828728437423706, 1.104204773902893, -0.09828728437423706), (-0.09828728437423706, 1.104204773902893, 0.09828728437423706), (0.09828728437423706, 0.9076300859451294, -0.09828728437423706), (0.09828728437423706, 0.9076300859451294, 0.09828728437423706), (0.09828728437423706, 1.104204773902893, -0.09828728437423706), (0.09828728437423706, 1.104204773902893, 0.09828728437423706), (0.00014829635620117188, 1.0, 0.0)], 'edges': [(2, 0), (0, 1), (1, 3), (3, 2), (6, 2), (3, 7), (7, 6), (4, 6), (7, 5), (5, 4), (0, 4), (5, 1)], 'faces': []}
|
||||
@@ -224,6 +224,7 @@ def fbx_template_def_light(scene, settings, override_defaults=None, nbr_users=0)
|
||||
b"CastLight": (True, "p_bool", False),
|
||||
b"Color": ((1.0, 1.0, 1.0), "p_color", True),
|
||||
b"Intensity": (100.0, "p_number", True), # Times 100 compared to Blender values...
|
||||
b"Exposure" : (0.0, "p_number", True ),
|
||||
b"DecayType": (2, "p_enum", False), # Quadratic.
|
||||
b"DecayStart": (30.0 * gscale, "p_double", False),
|
||||
b"CastShadows": (True, "p_bool", False),
|
||||
@@ -611,11 +612,12 @@ def fbx_data_light_elements(root, lamp, scene_data):
|
||||
light_key = scene_data.data_lights[lamp]
|
||||
do_light = True
|
||||
do_shadow = False
|
||||
# NOTE: this was removed from lamps, always write black.
|
||||
shadow_color = Vector((0.0, 0.0, 0.0))
|
||||
if lamp.type not in {'HEMI'}:
|
||||
do_light = True
|
||||
do_shadow = lamp.use_shadow
|
||||
shadow_color = lamp.shadow_color
|
||||
# `shadow_color = lamp.shadow_color`: now removed.
|
||||
|
||||
light = elem_data_single_int64(root, b"NodeAttribute", get_fbx_uuid_from_key(light_key))
|
||||
light.add_string(fbx_name_class(lamp.name.encode(), b"NodeAttribute"))
|
||||
@@ -623,12 +625,20 @@ def fbx_data_light_elements(root, lamp, scene_data):
|
||||
|
||||
elem_data_single_int32(light, b"GeometryVersion", FBX_GEOMETRY_VERSION) # Sic...
|
||||
|
||||
intensity = lamp.energy * 100.0 * pow(2.0, lamp.exposure)
|
||||
color = lamp.color.copy()
|
||||
if lamp.use_temperature:
|
||||
temperature_color = lamp.temperature_color
|
||||
color[0] *= temperature_color[0]
|
||||
color[1] *= temperature_color[1]
|
||||
color[2] *= temperature_color[2]
|
||||
|
||||
tmpl = elem_props_template_init(scene_data.templates, b"Light")
|
||||
props = elem_properties(light)
|
||||
elem_props_template_set(tmpl, props, "p_enum", b"LightType", FBX_LIGHT_TYPES[lamp.type])
|
||||
elem_props_template_set(tmpl, props, "p_bool", b"CastLight", do_light)
|
||||
elem_props_template_set(tmpl, props, "p_color", b"Color", lamp.color)
|
||||
elem_props_template_set(tmpl, props, "p_number", b"Intensity", lamp.energy * 100.0)
|
||||
elem_props_template_set(tmpl, props, "p_color", b"Color", color)
|
||||
elem_props_template_set(tmpl, props, "p_number", b"Intensity", intensity)
|
||||
elem_props_template_set(tmpl, props, "p_enum", b"DecayType", FBX_LIGHT_DECAY_TYPES['INVERSE_SQUARE'])
|
||||
elem_props_template_set(tmpl, props, "p_double", b"DecayStart", 25.0 * gscale) # 25 is old Blender default
|
||||
elem_props_template_set(tmpl, props, "p_bool", b"CastShadows", do_shadow)
|
||||
@@ -838,7 +848,16 @@ def fbx_data_mesh_shapes_elements(root, me_obj, me, scene_data, fbx_me_tmpl, fbx
|
||||
mv_shape_verts_idx = shape_verts_idx.data
|
||||
vg_idx = me_obj.bdata.vertex_groups[shape.vertex_group].index
|
||||
for sk_idx, v_idx in enumerate(mv_shape_verts_idx):
|
||||
curr_groups = []
|
||||
|
||||
for vg in vertices[v_idx].groups:
|
||||
# weird bug, some vertices were reported with corrupted vgroups data (two times the same vertex group!).
|
||||
# Ensure this does not happen
|
||||
vgname = me_obj.bdata.vertex_groups[vg.group].name
|
||||
if vgname in curr_groups:
|
||||
continue
|
||||
curr_groups.append(vgname)
|
||||
|
||||
if vg.group == vg_idx:
|
||||
mv_shape_verts_weights[sk_idx] = vg.weight
|
||||
break
|
||||
@@ -931,8 +950,9 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
fbx_data_element_custom_properties(props, me)
|
||||
|
||||
# Subdivision levels. Take them from the first found subsurf modifier from the
|
||||
# first object that has the mesh. Write crease information if the object has
|
||||
# and subsurf modifier.
|
||||
# first object that has the mesh. Always write crease information if present,
|
||||
# if the modifier explicitly uses creases ("use_creases" setting) and mesh lacks them,
|
||||
# still provide zeros (see TODO comment below)
|
||||
write_crease = False
|
||||
if scene_data.settings.use_subsurf:
|
||||
last_subsurf = None
|
||||
@@ -956,6 +976,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
elem_data_single_int32(geom, b"PropagateEdgeHardness", 0)
|
||||
|
||||
write_crease = last_subsurf.use_creases
|
||||
write_crease = (write_crease or me.edge_creases)
|
||||
|
||||
elem_data_single_int32(geom, b"GeometryVersion", FBX_GEOMETRY_VERSION)
|
||||
|
||||
@@ -1091,7 +1112,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
# And now, layers!
|
||||
|
||||
# Smoothing.
|
||||
if smooth_type in {'FACE', 'EDGE'}:
|
||||
if smooth_type in {'FACE', 'EDGE', 'SMOOTH_GROUP'}:
|
||||
ps_fbx_dtype = np.int32
|
||||
_map = b""
|
||||
if smooth_type == 'FACE':
|
||||
@@ -1106,6 +1127,10 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
# The mesh has no "sharp_face" attribute, so every face is smooth.
|
||||
t_ps = np.ones(len(me.polygons), dtype=ps_fbx_dtype)
|
||||
_map = b"ByPolygon"
|
||||
elif smooth_type == 'SMOOTH_GROUP':
|
||||
smoothing_groups = me.calc_smooth_groups(use_bitflags=True, use_boundary_vertices_for_bitflags=True)[0]
|
||||
t_ps = np.asarray(smoothing_groups, dtype=ps_fbx_dtype)
|
||||
_map = b"ByPolygon"
|
||||
else: # EDGE
|
||||
_map = b"ByEdge"
|
||||
if t_pvi_edge_indices.size:
|
||||
@@ -1231,8 +1256,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
# normal_source = me.polygon_normals
|
||||
# normal_mapping = b"ByPolygon"
|
||||
case 'CORNER' | 'FACE':
|
||||
# We have a mix of sharp/smooth edges/faces or custom split normals, so need to get normals from
|
||||
# corners.
|
||||
# We have a mix of sharp/smooth edges/faces or custom normals, so need to get normals from corners.
|
||||
normal_source = me.corner_normals
|
||||
normal_mapping = b"ByPolygonVertex"
|
||||
case _:
|
||||
@@ -1250,13 +1274,19 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
# FBX SDK documentation says that normals should use IndexToDirect.
|
||||
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect")
|
||||
|
||||
# Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_normal.
|
||||
# Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
|
||||
# helper function.
|
||||
t_normal, t_normal_idx = fast_first_axis_unique(t_normal.reshape(-1, 3), return_inverse=True)
|
||||
# Workaround for Unity FBX import issue where the normals are considered invalid if any normals are
|
||||
# deduplicated. See #123088.
|
||||
if normal_mapping == b"ByVertice":
|
||||
# Write every normal without any deduplication, so the indices array will be [0, 1, 2, ..., n].
|
||||
t_normal_idx = np.arange(len(t_normal.reshape(-1, 3)), dtype=normal_idx_fbx_dtype)
|
||||
else:
|
||||
# Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_normal.
|
||||
# Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
|
||||
# helper function.
|
||||
t_normal, t_normal_idx = fast_first_axis_unique(t_normal.reshape(-1, 3), return_inverse=True)
|
||||
|
||||
# Convert to the type for fbx
|
||||
t_normal_idx = astype_view_signedness(t_normal_idx, normal_idx_fbx_dtype)
|
||||
# Convert to the type for fbx
|
||||
t_normal_idx = astype_view_signedness(t_normal_idx, normal_idx_fbx_dtype)
|
||||
|
||||
elem_data_single_float64_array(lay_nor, b"Normals", t_normal)
|
||||
# Normal weights, no idea what it is.
|
||||
@@ -1288,13 +1318,15 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
num_loops = len(me.loops)
|
||||
t_ln = np.empty(num_loops * 3, dtype=normal_bl_dtype)
|
||||
# t_lnw = np.zeros(len(me.loops), dtype=np.float64)
|
||||
uv_names = [uvlayer.name for uvlayer in me.uv_layers]
|
||||
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
||||
if num_loops > 0:
|
||||
for name in uv_names:
|
||||
me.calc_tangents(uvmap=name)
|
||||
for idx, uvlayer in enumerate(me.uv_layers):
|
||||
name = uvlayer.name
|
||||
# WARNING: Since tangent layers are recomputed inside the loop, do not directly iterate over the
|
||||
# uvlayers. Instead, cache their keys (names), and use this cached data inside the loop to compute
|
||||
# the tangent layers.
|
||||
uvlayer_names = [uvl.name for uvl in me.uv_layers]
|
||||
for idx, name in enumerate(uvlayer_names):
|
||||
# Annoying, `me.calc_tangent` errors in case there is no geometry...
|
||||
if num_loops > 0:
|
||||
me.calc_tangents(uvmap=name)
|
||||
|
||||
# Loop bitangents (aka binormals).
|
||||
# NOTE: this is not supported by importer currently.
|
||||
me.loops.foreach_get("bitangent", t_ln)
|
||||
@@ -1556,14 +1588,14 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
|
||||
lay_tan = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_tan, b"Type", b"LayerElementTangent")
|
||||
elem_data_single_int32(lay_tan, b"TypedIndex", 0)
|
||||
if smooth_type in {'FACE', 'EDGE'}:
|
||||
if smooth_type in {'FACE', 'EDGE', 'SMOOTH_GROUP'}:
|
||||
lay_smooth = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_smooth, b"Type", b"LayerElementSmoothing")
|
||||
elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
|
||||
if write_crease:
|
||||
lay_smooth = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_smooth, b"Type", b"LayerElementEdgeCrease")
|
||||
elem_data_single_int32(lay_smooth, b"TypedIndex", 0)
|
||||
lay_crease = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_crease, b"Type", b"LayerElementEdgeCrease")
|
||||
elem_data_single_int32(lay_crease, b"TypedIndex", 0)
|
||||
if vcolnumber:
|
||||
lay_vcol = elem_empty(layer, b"LayerElement")
|
||||
elem_data_single_string(lay_vcol, b"Type", b"LayerElementColor")
|
||||
@@ -1808,7 +1840,7 @@ def fbx_data_video_elements(root, vid, scene_data):
|
||||
with open(filepath, 'br') as f:
|
||||
elem_data_single_bytes(fbx_vid, b"Content", f.read())
|
||||
except Exception as e:
|
||||
print("WARNING: embedding file {} failed ({})".format(filepath, e))
|
||||
print("WARNING: embedding file {:s} failed ({:s})".format(filepath, str(e)))
|
||||
elem_data_single_bytes(fbx_vid, b"Content", b"")
|
||||
msetts.embedded_set.add(filepath)
|
||||
# Looks like we'd rather not write any 'Content' element in this case (see T44442).
|
||||
@@ -1885,7 +1917,15 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
|
||||
valid_idxs = set(bo_vg_idx.values())
|
||||
vgroups = {vg.index: {} for vg in ob.vertex_groups}
|
||||
for idx, v in enumerate(me.vertices):
|
||||
curr_groups = []
|
||||
for vg in v.groups:
|
||||
# weird bug, some vertices were reported with corrupted vgroups data (two times the same vertex group!).
|
||||
# Ensure this does not happen
|
||||
vgname = ob.vertex_groups[vg.group].name
|
||||
if vgname in curr_groups:
|
||||
continue
|
||||
curr_groups.append(vgname)
|
||||
|
||||
if (w := vg.weight) and (vg_idx := vg.group) in valid_idxs:
|
||||
vgroups[vg_idx][idx] = w
|
||||
|
||||
@@ -1923,7 +1963,6 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
|
||||
|
||||
elif bpy.context.scene.arp_ge_force_rest_pose_export:
|
||||
# Auto-Rig Pro implant to export the bind pose when there are no deformers (skeleton only)
|
||||
# warning, UE doesn't import skeleton only. Unity does
|
||||
print("Skeleton only, export bind pose anyway...")
|
||||
mat_world_obj, mat_world_bones = fbx_data_bindpose_only(root, scene_data, armature=arm_obj, mat_world_armature=mat_world_arm, bones_list=bones)
|
||||
|
||||
@@ -2325,7 +2364,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
depsgraph = scene_data.depsgraph
|
||||
force_keying = scene_data.settings.bake_anim_use_all_bones
|
||||
force_sek = scene_data.settings.bake_anim_force_startend_keying
|
||||
force_sek_sk = scene_data.settings.bake_anim_force_startend_keying_sk
|
||||
force_sek_sk = scene_data.settings.bake_anim_force_startend_keying_sk# Auto Rig Pro Implant
|
||||
gscale = scene_data.settings.global_scale
|
||||
|
||||
if objects is not None:
|
||||
@@ -2375,7 +2414,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
is_animated = False
|
||||
act_sk = me.shape_keys.animation_data.action
|
||||
if act_sk:
|
||||
fc = act_sk.fcurves.find(f'key_blocks["{shape.name}"].value')
|
||||
fc = find_fcurve(act_sk, f'key_blocks["{shape.name}"].value')
|
||||
is_animated = fc != None
|
||||
|
||||
if (is_animated or is_driven) or force_sek_sk:# Auto-Rig Pro Implant
|
||||
@@ -2652,7 +2691,7 @@ def fbx_animations(scene_data):
|
||||
# All actions.
|
||||
if scene_data.settings.bake_anim_use_all_actions:
|
||||
def validate_actions(act, path_resolve):
|
||||
for fc in act.fcurves:
|
||||
for fc in get_action_fcurves(act):
|
||||
data_path = fc.data_path
|
||||
if fc.array_index:
|
||||
data_path = data_path + "[%d]" % fc.array_index
|
||||
@@ -2746,7 +2785,7 @@ def fbx_animations(scene_data):
|
||||
for pbo, mat in zip(ob.pose.bones, pbones_matrices):
|
||||
pbo.matrix_basis = mat.copy()
|
||||
ob.animation_data.action = org_act
|
||||
|
||||
|
||||
bpy.data.objects.remove(ob_copy)
|
||||
scene.frame_set(scene.frame_current, subframe=0.0)
|
||||
|
||||
@@ -2890,14 +2929,19 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
||||
tmp_me = bpy.data.meshes.new_from_object(
|
||||
ob_to_convert, preserve_all_data_layers=True, depsgraph=depsgraph)
|
||||
|
||||
# Usually the materials of the evaluated object will be the same, but modifiers, such as Geometry Nodes,
|
||||
# can change the materials.
|
||||
orig_mats = tuple(slot.material for slot in ob.material_slots)
|
||||
eval_mats = tuple(slot.material.original if slot.material else None
|
||||
for slot in ob_to_convert.material_slots)
|
||||
# Usually the materials of the evaluated Object converted to a Mesh will be the same as the original
|
||||
# Object, but modifiers, such as Geometry Nodes, can change the materials.
|
||||
orig_mats = [slot.material for slot in ob.material_slots]
|
||||
eval_mats = list(tmp_me.materials)
|
||||
if orig_mats != eval_mats:
|
||||
# Override the default behaviour of getting materials from ob_obj.bdata.material_slots.
|
||||
ob_obj.override_materials = eval_mats
|
||||
# An object-linked material slot replaces the material on the data at the slot's index. If applying
|
||||
# modifiers changes the materials on the data, the object-linked material slot will replace the new
|
||||
# material at the same index as before.
|
||||
for i, slot in zip(range(len(eval_mats)), ob.material_slots):
|
||||
if slot.link == 'OBJECT':
|
||||
eval_mats[i] = slot.material
|
||||
# Override the default behavior of getting materials from `ob_obj.bdata.material_slots`.
|
||||
ob_obj.override_materials = tuple(eval_mats)
|
||||
elif do_convert:
|
||||
tmp_me = bpy.data.meshes.new_from_object(ob, preserve_all_data_layers=True, depsgraph=depsgraph)
|
||||
elif do_copy:
|
||||
@@ -2926,15 +2970,6 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
||||
# store the object and corresponding tmp_me for renaming later
|
||||
# must be done out of this loop, otherwise tmp_me names are incorrect
|
||||
obj_tmp_meshes[ob] = tmp_me
|
||||
'''
|
||||
# since shape keys are removed after the mesh conversion with modifiers applied,
|
||||
# then copy them back from the original to the converted object if no changes in the mesh topology were found
|
||||
if ob_to_convert.data.shape_keys:
|
||||
if len(ob_to_convert.data.vertices) == len(tmp_me.vertices):
|
||||
temp_obj = bpy.data.objects.new("temp_obj_for_sk_transfer", tmp_me)# a temp object must be created to add shape keys
|
||||
copy_shape_keys(ob_to_convert, temp_obj)
|
||||
bpy.data.objects.remove(temp_obj)
|
||||
'''
|
||||
|
||||
# Change armatures back.
|
||||
for armature, pose_position in backup_pose_positions:
|
||||
@@ -3284,7 +3319,7 @@ def fbx_data_from_scene(scene, depsgraph, settings):
|
||||
idx = _objs_indices[ob_obj] = _objs_indices.get(ob_obj, -1) + 1
|
||||
# XXX If a mesh has multiple material slots with the same material, they are combined into one slot.
|
||||
# Even if duplicate materials were exported without combining them into one slot, keeping duplicate
|
||||
# materials separated does not appear to be common behaviour of external software when importing FBX.
|
||||
# materials separated does not appear to be common behavior of external software when importing FBX.
|
||||
mesh_material_indices.setdefault(me, {})[ma] = idx
|
||||
del _objs_indices
|
||||
|
||||
@@ -3717,7 +3752,6 @@ def save_single(operator, scene, depsgraph, filepath="",
|
||||
).to_4x4()
|
||||
bone_correction_matrix_inv = bone_correction_matrix.inverted()
|
||||
|
||||
|
||||
media_settings = FBXExportSettingsMedia(
|
||||
path_mode,
|
||||
os.path.dirname(bpy.data.filepath), # base_src
|
||||
@@ -3748,7 +3782,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
||||
import bpy_extras.io_utils
|
||||
|
||||
print('\nFBX export starting... %r' % filepath)
|
||||
start_time = time.process_time()
|
||||
start_time = time.time()
|
||||
|
||||
# Generate some data about exported scene...
|
||||
scene_data = fbx_data_from_scene(scene, depsgraph, settings)
|
||||
@@ -3791,7 +3825,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
||||
if not media_settings.embed_textures:
|
||||
bpy_extras.io_utils.path_reference_copy(media_settings.copy_set)
|
||||
|
||||
print('export finished in %.4f sec.' % (time.process_time() - start_time))
|
||||
print('export finished in %.4f sec.' % (time.time() - start_time))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
@@ -3836,11 +3870,13 @@ def defaults_unity3d():
|
||||
"batch_mode": 'OFF',
|
||||
}
|
||||
|
||||
|
||||
def arp_save(operator, context,
|
||||
filepath="",
|
||||
use_selection=False,
|
||||
use_visible=False,
|
||||
use_active_collection=False,
|
||||
collection="",
|
||||
batch_mode='OFF',
|
||||
use_batch_own_dir=False,
|
||||
**kwargs
|
||||
@@ -3861,13 +3897,23 @@ def arp_save(operator, context,
|
||||
|
||||
if batch_mode == 'OFF':
|
||||
kwargs_mod = kwargs.copy()
|
||||
|
||||
source_collection = None
|
||||
if use_active_collection:
|
||||
if use_selection:
|
||||
ctx_objects = tuple(obj
|
||||
for obj in context.view_layer.active_layer_collection.collection.all_objects
|
||||
if obj.select_get())
|
||||
source_collection = context.view_layer.active_layer_collection.collection
|
||||
elif collection:
|
||||
local_collection = bpy.data.collections.get((collection, None))
|
||||
if local_collection:
|
||||
source_collection = local_collection
|
||||
else:
|
||||
ctx_objects = context.view_layer.active_layer_collection.collection.all_objects
|
||||
operator.report({'ERROR'}, "Collection '%s' was not found" % collection)
|
||||
return {'CANCELLED'}
|
||||
|
||||
if source_collection:
|
||||
if use_selection:
|
||||
ctx_objects = tuple(obj for obj in source_collection.all_objects if obj.select_get())
|
||||
else:
|
||||
ctx_objects = source_collection.all_objects
|
||||
else:
|
||||
if use_selection:
|
||||
ctx_objects = context.selected_objects
|
||||
@@ -3876,9 +3922,12 @@ def arp_save(operator, context,
|
||||
if use_visible:
|
||||
ctx_objects = tuple(obj for obj in ctx_objects if obj.visible_get())
|
||||
|
||||
# Sort exported objects by their names.
|
||||
ctx_objects = sorted(ctx_objects, key=lambda ob: ob.name)
|
||||
|
||||
# Ensure no Objects are in Edit mode.
|
||||
# Copy to a tuple for safety, to avoid the risk of modifying ctx_objects while iterating.
|
||||
for obj in tuple(ctx_objects):
|
||||
for obj in ctx_objects:
|
||||
if not ensure_object_not_in_edit_mode(context, obj):
|
||||
operator.report({'ERROR'}, "%s could not be set out of Edit Mode, so cannot be exported" % obj.name)
|
||||
return {'CANCELLED'}
|
||||
|
||||
@@ -1944,5 +1944,5 @@ FBXImportSettings = namedtuple("FBXImportSettings", (
|
||||
"use_custom_props", "use_custom_props_enum_as_string",
|
||||
"nodal_material_wrap_map", "image_cache",
|
||||
"ignore_leaf_bones", "force_connect_children", "automatic_bone_orientation", "bone_correction_matrix",
|
||||
"use_prepost_rot", "colors_type",
|
||||
"use_prepost_rot", "colors_type", "mtl_name_collision_mode",
|
||||
))
|
||||
|
||||
@@ -247,6 +247,11 @@ class ARP_OT_export_fbx_wrap(bpy.types.Operator, ExportHelper):
|
||||
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)",
|
||||
|
||||
@@ -2320,6 +2320,7 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
depsgraph = scene_data.depsgraph
|
||||
force_keying = scene_data.settings.bake_anim_use_all_bones
|
||||
force_sek = scene_data.settings.bake_anim_force_startend_keying
|
||||
force_sek_sk = scene_data.settings.bake_anim_force_startend_keying_sk# Auto Rig Pro Implant
|
||||
gscale = scene_data.settings.global_scale
|
||||
|
||||
if objects is not None:
|
||||
@@ -2359,10 +2360,22 @@ def fbx_animations_do(scene_data, ref_id, f_start, f_end, start_zero, objects=No
|
||||
if not me.shape_keys.use_relative:
|
||||
continue
|
||||
for shape, (channel_key, geom_key, _shape_verts_co, _shape_verts_idx) in shapes.items():
|
||||
acnode = AnimationCurveNodeWrapper(channel_key, 'SHAPE_KEY', force_key, force_sek, (0.0,))
|
||||
# Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
|
||||
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
|
||||
animdata_shapes[channel_key] = (acnode, me, shape)
|
||||
# Auto-Rig Pro Implant
|
||||
# Only export animation for driven or keyframed shapes
|
||||
dr = me.shape_keys.animation_data.drivers.find(f'key_blocks["{shape.name}"].value')
|
||||
is_driven = dr != None
|
||||
|
||||
is_animated = False
|
||||
act_sk = me.shape_keys.animation_data.action
|
||||
if act_sk:
|
||||
fc = act_sk.fcurves.find(f'key_blocks["{shape.name}"].value')
|
||||
is_animated = fc != None
|
||||
|
||||
if (is_animated or is_driven) or force_sek_sk:# Auto-Rig Pro Implant
|
||||
acnode = AnimationCurveNodeWrapper(channel_key, 'SHAPE_KEY', force_key, force_sek, (0.0,))
|
||||
# Sooooo happy to have to twist again like a mad snake... Yes, we need to write those curves twice. :/
|
||||
acnode.add_group(me_key, shape.name, shape.name, (shape.name,))
|
||||
animdata_shapes[channel_key] = (acnode, me, shape)
|
||||
|
||||
animdata_cameras = {}
|
||||
for cam_obj, cam_key in scene_data.data_cameras.items():
|
||||
@@ -3523,6 +3536,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
||||
bake_anim_step=1.0,
|
||||
bake_anim_simplify_factor=1.0,
|
||||
bake_anim_force_startend_keying=True,
|
||||
bake_anim_force_startend_keying_sk=False,
|
||||
add_leaf_bones=False,
|
||||
primary_bone_axis='Y',
|
||||
secondary_bone_axis='X',
|
||||
@@ -3609,7 +3623,7 @@ def save_single(operator, scene, depsgraph, filepath="",
|
||||
bake_anim, bake_anim_use_all_bones, bake_anim_use_nla_strips, bake_anim_use_all_actions,
|
||||
bake_anim_step, bake_anim_simplify_factor, bake_anim_force_startend_keying,
|
||||
False, media_settings, use_custom_props, colors_type, prioritize_active_color,
|
||||
shape_keys_baked_data_dict, export_action_only#Auto-Rig Pro implant
|
||||
shape_keys_baked_data_dict, export_action_only, bake_anim_force_startend_keying_sk#Auto-Rig Pro implant
|
||||
)
|
||||
|
||||
import bpy_extras.io_utils
|
||||
|
||||
@@ -1565,7 +1565,7 @@ FBXExportSettings = namedtuple("FBXExportSettings", (
|
||||
"bake_anim", "bake_anim_use_all_bones", "bake_anim_use_nla_strips", "bake_anim_use_all_actions",
|
||||
"bake_anim_step", "bake_anim_simplify_factor", "bake_anim_force_startend_keying",
|
||||
"use_metadata", "media_settings", "use_custom_props", "colors_type", "prioritize_active_color",
|
||||
"shape_keys_baked_data_dict", "export_action_only"# Auto-Rig Pro implant
|
||||
"shape_keys_baked_data_dict", "export_action_only", "bake_anim_force_startend_keying_sk"# Auto-Rig Pro implant
|
||||
))
|
||||
|
||||
# Helper container gathering some data we need multiple times:
|
||||
|
||||
@@ -5,15 +5,61 @@ from .version import *
|
||||
from .sys_print import *
|
||||
|
||||
|
||||
def create_action(actionname):
|
||||
new_act = bpy.data.actions.new(actionname)
|
||||
if bpy.app.version >= (5,0,0):
|
||||
slot = new_act.slots.new('OBJECT', 'Slot 1')
|
||||
actlay = new_act.layers.new('Layer')
|
||||
actlay.strips.new(type='KEYFRAME')
|
||||
return new_act
|
||||
|
||||
|
||||
def assign_armature_action(armature, _action, _slot_idx=0):
|
||||
if armature.animation_data:
|
||||
armature.animation_data.action = _action
|
||||
if bpy.app.version >= (4,4,0) and _action != None:
|
||||
if len(_action.slots):# debug old files compatibility
|
||||
armature.animation_data.action_slot = _action.slots[_slot_idx]
|
||||
|
||||
|
||||
def get_action_slot_idx(act, act_slot):
|
||||
for sloti, slot in enumerate(act.slots):
|
||||
if slot == act_slot:
|
||||
return sloti
|
||||
|
||||
|
||||
def get_action_slot_name(act, act_slot_idx):
|
||||
for sloti, slot in enumerate(act.slots):
|
||||
if sloti == act_slot_idx:
|
||||
return slot.name_display
|
||||
|
||||
|
||||
def get_action_slot_frame_range(act, slot_idx):
|
||||
cb = act.layers[0].strips[0].channelbag(act.slots[slot_idx])
|
||||
min = float('inf')
|
||||
max = -min
|
||||
if cb:
|
||||
for fc in cb.fcurves:
|
||||
for kf in fc.keyframe_points:
|
||||
kf_x = kf.co[0]
|
||||
if kf_x < min:
|
||||
min = kf_x
|
||||
if kf_x > max:
|
||||
max = kf_x
|
||||
|
||||
return min, max
|
||||
|
||||
|
||||
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_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
|
||||
# Disable current action restore for now, buggy in some cases. To investigate later
|
||||
# on exit, the active action is set to None. Bring it back
|
||||
# active_obj.animation_data.action = active_action
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -102,7 +148,7 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
matrices_dict = {}
|
||||
|
||||
for pbone in armature.pose.bones:
|
||||
if only_selected and not pbone.bone.select:
|
||||
if only_selected and not is_pbone_selected(pbone):#pbone.bone.select:
|
||||
continue
|
||||
|
||||
def_matrix = None
|
||||
@@ -218,18 +264,20 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
_self.shape_keys_data[dict_entry] = sk.value
|
||||
|
||||
|
||||
print_progress_bar("Baking phase 1", f-frame_start, frame_end-frame_start)
|
||||
print_progress_bar("Baking", f-frame_start, frame_end-frame_start, start_percent=0, end_percent=90)
|
||||
f += sampling_rate
|
||||
f = round(f, 3)# round frame value because of decimals issues
|
||||
|
||||
print("")
|
||||
#print("")
|
||||
|
||||
# set new action
|
||||
action = None
|
||||
if new_action:
|
||||
action = bpy.data.actions.new(new_action_name)
|
||||
#action = bpy.data.actions.new(new_action_name)
|
||||
action = create_action(new_action_name)
|
||||
anim_data = armature.animation_data_create()
|
||||
anim_data.action = action
|
||||
#anim_data.action = action
|
||||
assign_armature_action(armature, action)
|
||||
else:
|
||||
action = armature.animation_data.action
|
||||
|
||||
@@ -241,17 +289,16 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
keyframes[fc_key].extend((frame, value))
|
||||
|
||||
|
||||
# set transforms and store keyframes
|
||||
|
||||
# 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)
|
||||
print_progress_bar("Baking", bone_count, total_bone_count, start_percent=90, end_percent=100)
|
||||
|
||||
if only_selected and not pbone.bone.select:
|
||||
if only_selected and not is_pbone_selected(pbone):#pbone.bone.select:
|
||||
continue
|
||||
|
||||
euler_prev = None
|
||||
@@ -260,10 +307,12 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
|
||||
for (f, matrix) in bones_data:
|
||||
# optional, only keyframe given frames
|
||||
if keyframes_dict:
|
||||
if keyframes_dict and len(keyframes_dict):
|
||||
if not pbone.name in keyframes_dict: continue
|
||||
|
||||
keyf_list = keyframes_dict[pbone.name]
|
||||
if not f in keyf_list:
|
||||
continue
|
||||
|
||||
if not f in keyf_list: continue
|
||||
|
||||
pbone.matrix_basis = matrix[pbone.name].copy()
|
||||
|
||||
@@ -307,13 +356,16 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
|
||||
for fc_key, key_values in keyframes.items():
|
||||
data_path, index = fc_key
|
||||
fcurve = action.fcurves.find(data_path=data_path, index=index)
|
||||
#fcurve = action.fcurves.find(data_path=data_path, index=index)
|
||||
fcurve = find_fcurve(action, data_path, fc_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)
|
||||
#fcurve = action.fcurves.new(data_path, index=index, action_group=pbone.name)
|
||||
fcurve = create_fcurve(action, data_path, fc_index=index, action_group=pbone.name)
|
||||
if fcurve == None:
|
||||
fcurve = action.fcurves.new(data_path, index=index, action_group=pbone.name)
|
||||
|
||||
#fcurve = action.fcurves.new(data_path, index=index, action_group=pbone.name)
|
||||
fcurve = create_fcurve(action, data_path, fc_index=index, action_group=pbone.name)
|
||||
|
||||
# set keyframes points
|
||||
num_keys = len(key_values) // 2
|
||||
fcurve.keyframe_points.add(num_keys)
|
||||
@@ -390,35 +442,39 @@ def bake_anim(frame_start=0, frame_end=10, only_selected=False, bake_bones=True,
|
||||
print("\n")
|
||||
|
||||
|
||||
def get_bone_keyframes_list(pb, act):
|
||||
def get_bone_keyframes_list(pb, act, all_rot_modes=False, bonename=''):
|
||||
# return a list containing all keyframes frames of the given pose bone
|
||||
key_list = []
|
||||
|
||||
if pb: bonename = pb.name
|
||||
|
||||
# loc
|
||||
for i in range(0,3):
|
||||
fc = act.fcurves.find('pose.bones["'+pb.name+'"].location', index=i)
|
||||
fc = get_action_fcurves(act, as_list=False).find('pose.bones["'+bonename+'"].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])
|
||||
|
||||
rot_modes = []
|
||||
if all_rot_modes:
|
||||
rot_modes = ['rotation_euler', 'rotation_quaternion']
|
||||
else:
|
||||
rot_path = 'rotation_quaternion' if pb.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||||
rot_modes.append(rot_path)
|
||||
|
||||
for rotmode in rot_modes:
|
||||
_range = 3 if rotmode == 'rotation_euler' else 4
|
||||
for i in range(0, _range):
|
||||
fc = get_action_fcurves(act, as_list=False).find('pose.bones["'+bonename+'"].'+rotmode, 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)
|
||||
fc = get_action_fcurves(act, as_list=False).find('pose.bones["'+bonename+'"].scale', index=i)
|
||||
if fc:
|
||||
for k in fc.keyframe_points:
|
||||
if not k.co[0] in key_list:
|
||||
|
||||
@@ -4,13 +4,12 @@ from .objects import *
|
||||
class ARP_BonesData:
|
||||
custom_bones_list = []
|
||||
softlink_bones = []
|
||||
const_interp_bones = []
|
||||
armature_name = ''
|
||||
#renamed_bones = {}
|
||||
|
||||
|
||||
def init_values(self):
|
||||
self.custom_bones_list = []
|
||||
self.softlink_bones = []
|
||||
#self.renamed_bones = {}
|
||||
self.softlink_bones = []
|
||||
self.const_interp_bones = []
|
||||
|
||||
def collect(self, arm_name):
|
||||
@@ -37,12 +36,8 @@ class ARP_BonesData:
|
||||
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']
|
||||
|
||||
add_stretch_bones(b)
|
||||
|
||||
if 'const_interp' in b.keys():
|
||||
if not b.name in self.const_interp_bones:
|
||||
self.const_interp_bones.append(b.name)
|
||||
@@ -72,12 +67,8 @@ class ARP_BonesData:
|
||||
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']
|
||||
|
||||
add_stretch_bones(b)
|
||||
|
||||
if 'const_interp' in b.keys():
|
||||
if not b.name in self.const_interp_bones:
|
||||
self.const_interp_bones.append(b.name)
|
||||
@@ -121,7 +112,7 @@ def retarget_bone_side(bone_name, target_side, dupli_only=False):#"head.x", "_du
|
||||
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
|
||||
if dupli_only:# we only want to set the dupli ID and preserve the left/right/center side suffix
|
||||
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'
|
||||
@@ -162,3 +153,17 @@ def duplicate(type=None):
|
||||
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')
|
||||
|
||||
|
||||
def get_bone_children(b):
|
||||
children_list = []
|
||||
children_list = get_bone_children_recur(b, list=children_list)
|
||||
return children_list
|
||||
|
||||
|
||||
def get_bone_children_recur(b, list=None):
|
||||
if b.children:
|
||||
for child in b.children:
|
||||
list.append(child)
|
||||
get_bone_children_recur(child, list=list)
|
||||
return list
|
||||
@@ -100,19 +100,19 @@ def create_edit_bone(bone_name, deform=False, tag=None):
|
||||
return _b
|
||||
|
||||
|
||||
def select_edit_bone(name, mode=1):
|
||||
o = bpy.context.active_object
|
||||
ebone = get_edit_bone(name)
|
||||
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
|
||||
_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
|
||||
_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
|
||||
_ebone.select_head = True
|
||||
_ebone.select_tail = True
|
||||
_ebone.select = True
|
||||
|
||||
|
||||
def delete_edit_bone(editbone):
|
||||
|
||||
@@ -10,7 +10,7 @@ def get_selected_pose_bones():
|
||||
|
||||
|
||||
def get_pose_bone(name):
|
||||
return bpy.context.active_object.pose.bones.get(name)
|
||||
return bpy.context.active_object.pose.bones.get(name)
|
||||
|
||||
|
||||
def get_custom_shape_scale_prop_name():
|
||||
@@ -34,27 +34,33 @@ def set_custom_shape_scale(pbone, scale):
|
||||
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)
|
||||
def scale_custom_shape(custom_shape, scale, origin='cog', copy=True):
|
||||
# scale the custom shape vertices and return the object
|
||||
|
||||
if copy:
|
||||
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 cs_base_scaled:# if the scaled copy already exists, skip
|
||||
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
|
||||
else:
|
||||
cs_base_scaled = custom_shape
|
||||
|
||||
# get centroid
|
||||
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))
|
||||
|
||||
|
||||
# scale verts
|
||||
for v in cs_base_scaled.data.vertices:
|
||||
scale_vec = cog - v.co
|
||||
v.co = v.co + (scale_vec * (1-scale))
|
||||
@@ -62,7 +68,9 @@ def scale_custom_shape(custom_shape, scale, origin='cog'):
|
||||
return cs_base_scaled
|
||||
|
||||
|
||||
def get_custom_shape_scale(pbone, uniform=True, as_list=False):
|
||||
def get_custom_shape_scale(pbone, uniform=True, as_list=False):
|
||||
# returns the custom shape scale setting values
|
||||
|
||||
if bpy.app.version >= (3,0,0):
|
||||
if uniform:
|
||||
# uniform scale
|
||||
@@ -79,21 +87,55 @@ def get_custom_shape_scale(pbone, uniform=True, as_list=False):
|
||||
# pre-Blender 3.0
|
||||
else:
|
||||
return pbone.custom_shape_scale
|
||||
|
||||
|
||||
def get_custom_shape_translation(pbone, as_list=False):
|
||||
if bpy.app.version >= (3,0,0):
|
||||
if as_list:
|
||||
return vector_to_list(pbone.custom_shape_translation)
|
||||
else:
|
||||
return pbone.custom_shape_translation
|
||||
else:
|
||||
return [0,0,0]
|
||||
|
||||
|
||||
def get_custom_shape_rotation(pbone, as_list=False):
|
||||
if bpy.app.version >= (3,0,0):
|
||||
if as_list:
|
||||
return vector_to_list(pbone.custom_shape_rotation_euler)
|
||||
else:
|
||||
return pbone.custom_shape_rotation_euler
|
||||
else:
|
||||
return [0,0,0]
|
||||
|
||||
|
||||
def set_bone_custom_shape(pbone, cs_name):
|
||||
cs = get_object(cs_name)
|
||||
# set the bone custom shape object
|
||||
# append first the object from the master file if not present in current file
|
||||
|
||||
_sel_rig = bpy.context.active_object
|
||||
cs = None
|
||||
|
||||
# is the custom shape already there?
|
||||
for _o in bpy.data.objects:
|
||||
if _o.name.split('.')[0] == cs_name:# support multiple rigs per file, duplicate shapes
|
||||
if _o.parent:
|
||||
if _o.parent.parent:
|
||||
if _o.parent.parent == _sel_rig.parent:# parented to the active rig's char_grp empty
|
||||
cs = _o
|
||||
break
|
||||
|
||||
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':
|
||||
if __o.name.startswith('cs_grp') and __o.type == 'EMPTY' and __o.parent == _sel_rig.parent:
|
||||
cs_grp = __o
|
||||
break
|
||||
if cs_grp:
|
||||
@@ -208,9 +250,12 @@ def get_bone_colors(bone_data, list=False):
|
||||
return bone_data.color.palette
|
||||
|
||||
|
||||
def set_bone_color(bone_data, bcolors):
|
||||
def set_bone_color(bone_data, bcolors, assign_only_if_empty=False):
|
||||
# Blender 4 and higher only
|
||||
|
||||
if assign_only_if_empty:# do not change color group if a group is already assigned
|
||||
if bone_data.color.palette != 'DEFAULT':
|
||||
return
|
||||
|
||||
if type(bcolors) == str:# set the color palette string
|
||||
bone_data.color.palette = bcolors
|
||||
else:# set the color lists
|
||||
|
||||
@@ -133,4 +133,50 @@ def search_layer_collection(layerColl, collName):
|
||||
for layer in layerColl.children:
|
||||
found = search_layer_collection(layer, collName)
|
||||
if found:
|
||||
return found
|
||||
return found
|
||||
|
||||
|
||||
def set_collection_viz(collection_name, show, set_render=False):
|
||||
# set collection visibility
|
||||
# returns true if a change was necessary
|
||||
|
||||
collection = bpy.data.collections.get(collection_name)
|
||||
collection_has_switched = False
|
||||
|
||||
if collection:
|
||||
if collection.hide_viewport == show:
|
||||
collection.hide_viewport = not show
|
||||
collection_has_switched = True
|
||||
|
||||
layer_col = search_layer_collection(bpy.context.view_layer.layer_collection, collection_name)
|
||||
if layer_col:
|
||||
if layer_col.hide_viewport == show:
|
||||
layer_col.hide_viewport = not show
|
||||
collection_has_switched = True
|
||||
|
||||
if set_render:
|
||||
if collection.hide_render == show:
|
||||
collection.hide_render = not show
|
||||
collection_has_switched = True
|
||||
|
||||
if collection_has_switched:
|
||||
return True
|
||||
|
||||
|
||||
def get_obj_collections(obj):
|
||||
# returns all collections that an object belongs to, including recursive parent collections
|
||||
collections = set()
|
||||
|
||||
def find_parent_collections(collection):
|
||||
for parent in bpy.data.collections:
|
||||
if collection.name in parent.children.keys():
|
||||
collections.add(parent)
|
||||
find_parent_collections(parent)
|
||||
|
||||
# Find all direct collections the object belongs to
|
||||
for collection in bpy.data.collections:
|
||||
if obj.name in collection.objects.keys():
|
||||
collections.add(collection)
|
||||
find_parent_collections(collection)
|
||||
|
||||
return collections
|
||||
@@ -4,6 +4,38 @@ from mathutils import *
|
||||
from math import *
|
||||
|
||||
|
||||
def restore_actions_cns_lib(dict):
|
||||
for pbname in dict:
|
||||
cns_dict = dict[pbname]
|
||||
pb = get_pose_bone(pbname)
|
||||
for cns_name in cns_dict:
|
||||
lib_action_name = cns_dict[cns_name]
|
||||
lib_action = None
|
||||
for _act in bpy.data.actions:
|
||||
if _act.name == lib_action_name and _act.library:
|
||||
lib_action = _act
|
||||
break
|
||||
|
||||
if lib_action:
|
||||
cns = pb.constraints.get(cns_name)
|
||||
cns.action = lib_action
|
||||
#print("Replace with lib action:", pbname, lib_action_name)
|
||||
|
||||
|
||||
def save_actions_cns_lib():
|
||||
actions_cns_lib_dict = {}
|
||||
for pb in bpy.context.active_object.pose.bones:
|
||||
cnss = {}
|
||||
for cns in pb.constraints:
|
||||
if cns.type == "ACTION":
|
||||
if cns.action and cns.action.library:
|
||||
cnss[cns.name] = cns.action.name
|
||||
actions_cns_lib_dict[pb.name] = cnss
|
||||
|
||||
return actions_cns_lib_dict
|
||||
|
||||
|
||||
|
||||
def enable_constraint(cns, value):
|
||||
if bpy.app.version >= (3,0,0):
|
||||
cns.enabled = value
|
||||
|
||||
@@ -3,6 +3,24 @@ from .objects import *
|
||||
from .bone_pose import *
|
||||
from .context import *
|
||||
|
||||
|
||||
def remove_constraint_drivers(pb_name, cns_name):
|
||||
_dp_cns = 'pose.bones["'+pb_name+'"].constraints["'+cns_name+'"].'
|
||||
_to_del = []
|
||||
|
||||
# collect constraint drivers
|
||||
for dra in bpy.context.active_object.animation_data.drivers:
|
||||
if dra.data_path.startswith(_dp_cns):
|
||||
_to_del.append((dra.data_path, dra.array_index))
|
||||
|
||||
# remove
|
||||
for drdp in _to_del:
|
||||
_dp, arridx = drdp[0], drdp[1]
|
||||
drb = bpy.context.active_object.animation_data.drivers.find(_dp, index=arridx)
|
||||
if drb:# maybe superfluous
|
||||
bpy.context.active_object.animation_data.drivers.remove(drb)
|
||||
print("Removed cns driver:", _dp, arridx)
|
||||
|
||||
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()
|
||||
|
||||
@@ -3,6 +3,119 @@ from math import *
|
||||
from mathutils import *
|
||||
import numpy as np
|
||||
|
||||
|
||||
def get_axis(matrix, axis):
|
||||
if axis == 0:
|
||||
return Vector((matrix[0][0], matrix[1][0], matrix[2][0])).normalized()
|
||||
elif axis == 1:
|
||||
return Vector((matrix[0][1], matrix[1][1], matrix[2][1])).normalized()
|
||||
elif axis == 2:
|
||||
return Vector((matrix[0][2], matrix[1][2], matrix[2][2])).normalized()
|
||||
|
||||
|
||||
def lerp(a, b, t):
|
||||
return a + t * (b - a)
|
||||
|
||||
|
||||
def matrix_blend(matrix1, matrix2, blend_factor):
|
||||
matrix1 = matrix1.to_4x4()
|
||||
matrix2 = matrix2.to_4x4()
|
||||
|
||||
blend_factor = max(0.0, min(1.0, blend_factor))
|
||||
result = Matrix()
|
||||
|
||||
for __i in range(4):
|
||||
for __j in range(4):
|
||||
result[__i][__j] = lerp(matrix1[__i][__j], matrix2[__i][__j], blend_factor)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def damped_track(matrix, target, fac=1.0):
|
||||
# Return the rotation matrix making Y axis looking at a given target point in space
|
||||
# minimize rotations on other axes
|
||||
|
||||
curr_pos = matrix.to_translation()
|
||||
current_y = Vector((matrix[0][1], matrix[1][1], matrix[2][1])).normalized()
|
||||
desired_y = Vector(target - curr_pos).normalized()
|
||||
|
||||
dot = current_y.dot(desired_y)
|
||||
|
||||
if dot > 0.999999:
|
||||
# Handle the case when vectors are very close or opposite
|
||||
result = Matrix(matrix)
|
||||
elif dot < -0.999999:
|
||||
# Pointing opposite direction, pick current x_axis
|
||||
up_axis = 0
|
||||
up = Vector((matrix[0][up_axis], matrix[1][up_axis], matrix[2][up_axis])).normalized()
|
||||
right = up.cross(desired_y).normalized()
|
||||
up = desired_y.cross(right).normalized()
|
||||
|
||||
rotation_matrix = Matrix((
|
||||
[up[0], desired_y[0], right[0], 0],
|
||||
[up[1], desired_y[1], right[1], 0],
|
||||
[up[2], desired_y[2], right[2], 0],
|
||||
[0, 0, 0, 1]
|
||||
))
|
||||
result = rotation_matrix
|
||||
else:
|
||||
# Normal case: calculate minimal rotation
|
||||
axis = current_y.cross(desired_y).normalized()
|
||||
angle = current_y.angle(desired_y)
|
||||
rotation = Matrix.Rotation(angle, 4, axis)
|
||||
|
||||
# Apply rotation to original matrix while preserving translation
|
||||
result = rotation @ matrix
|
||||
result.translation = curr_pos
|
||||
|
||||
if fac != 1.0:
|
||||
return matrix_blend(matrix.copy(), result, fac)
|
||||
else:
|
||||
return result
|
||||
|
||||
|
||||
def lookat_up(matrix, target, up_axis):
|
||||
# Return the rotation matrix making Y axis looking at a given target point in space
|
||||
# using a given up vector
|
||||
|
||||
eye = matrix.to_translation()
|
||||
forward = Vector(target-eye).normalized()
|
||||
|
||||
up = up_axis.normalized()
|
||||
right = up.cross(forward).normalized()
|
||||
up = forward.cross(right).normalized()
|
||||
|
||||
rotation_matrix = Matrix((
|
||||
[up[0], forward[0], right[0], 0],
|
||||
[up[1], forward[1], right[1], 0],
|
||||
[up[2], forward[2], right[2], 0],
|
||||
[0, 0, 0, 1]
|
||||
))
|
||||
|
||||
return rotation_matrix
|
||||
|
||||
|
||||
def lookat_up_camera(matrix, target, up_axis):
|
||||
# Return the rotation matrix making -Z axis looking at a given target point in space
|
||||
# using Y as up vector
|
||||
|
||||
eye = matrix.to_translation()
|
||||
forward = Vector(eye - target).normalized() # -Z direction, so reverse the vector
|
||||
|
||||
up = up_axis.normalized()
|
||||
right = up.cross(forward).normalized()
|
||||
up = forward.cross(right).normalized()
|
||||
|
||||
rotation_matrix = Matrix((
|
||||
[right[0], up[0], forward[0], 0], # -forward for -Z
|
||||
[right[1], up[1], forward[1], 0],
|
||||
[right[2], up[2], forward[2], 0],
|
||||
[0, 0, 0, 1]
|
||||
))
|
||||
|
||||
return rotation_matrix
|
||||
|
||||
|
||||
def compare_transform(transf1, transf2):
|
||||
for i, j in enumerate(transf1):
|
||||
if j == transf2[i]:
|
||||
@@ -11,31 +124,78 @@ def compare_transform(transf1, transf2):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def catmull_rom_spline(p0, p1, p2, p3, num_points=20):
|
||||
t = np.linspace(0, 1, num_points)
|
||||
t2 = t**2
|
||||
t3 = t**3
|
||||
|
||||
f0 = -0.5*t3 + t2 - 0.5*t
|
||||
f1 = 1.5*t3 - 2.5*t2 + 1
|
||||
f2 = -1.5*t3 + 2.0*t2 + 0.5*t
|
||||
f3 = 0.5*t3 - 0.5*t2
|
||||
|
||||
return np.outer(f0, p0) + np.outer(f1, p1) + np.outer(f2, p2) + np.outer(f3, p3)
|
||||
|
||||
|
||||
def generate_catrom_curve(points, num_points=20, closed=True):
|
||||
points = np.array(points)
|
||||
num_key_points = len(points)
|
||||
curve = []
|
||||
|
||||
for i in range(num_key_points):
|
||||
p0 = points[i - 1]
|
||||
if not closed and i == 0:
|
||||
p0 = points[i]
|
||||
p1 = points[i]
|
||||
p2 = points[(i + 1) % num_key_points]
|
||||
if not closed and i >= num_key_points-1:
|
||||
p2 = points[i]
|
||||
p3 = points[(i + 2) % num_key_points]
|
||||
if not closed and i >= num_key_points-2:
|
||||
p3 = points[i]
|
||||
segment = catmull_rom_spline(p0, p1, p2, p3, num_points)
|
||||
curve.append(segment)
|
||||
|
||||
_pts = np.vstack(curve).tolist()
|
||||
_pts.pop(len(_pts)-1)# remove double cyclic point
|
||||
vec_list = [Vector((i[0], i[1], i[2])) for i in _pts]
|
||||
|
||||
return vec_list
|
||||
|
||||
|
||||
def resample_curve(coords, length=1.0, amount=5, symmetrical=True, generate_normals=False):
|
||||
def resample_curve(coords, length=1.0, amount=5, symmetrical=True, generate_normals=False, offset_first=True, keep_tip=False):
|
||||
# resample a given set of points belonging to a curve
|
||||
# only works by reduction
|
||||
|
||||
resampled_coords = []
|
||||
dist_sum = 0.0
|
||||
dist = length/amount
|
||||
dist = length/(amount-1) if keep_tip else 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]:
|
||||
if coord == coords[0]:# skip first
|
||||
continue
|
||||
|
||||
p_prev = coords[i-1]
|
||||
p_prev = coords[i-1]# average second
|
||||
cur_dist = (coord-p_prev).magnitude
|
||||
dist_sum += cur_dist
|
||||
|
||||
if dist_sum >= dist/2:
|
||||
dist_sum = 0.0
|
||||
resampled_coords.append(coord.copy())
|
||||
resampled_coords.append(coord.copy())
|
||||
|
||||
else:
|
||||
p_prev = coords[i-1]
|
||||
if i == 0:
|
||||
if offset_first:
|
||||
p_prev = coords[i-1]
|
||||
else:
|
||||
resampled_coords.append(coord.copy())
|
||||
continue
|
||||
else:
|
||||
p_prev = coords[i-1]
|
||||
|
||||
cur_dist = (coord-p_prev).magnitude
|
||||
dist_sum += cur_dist
|
||||
|
||||
@@ -47,10 +207,8 @@ def resample_curve(coords, length=1.0, amount=5, symmetrical=True, generate_norm
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
# 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]
|
||||
@@ -139,29 +297,27 @@ 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
|
||||
# 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
|
||||
# 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)
|
||||
# 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
|
||||
# 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
|
||||
if i == len(u_new)-1:# the last one must be set manually
|
||||
x[i] += control_points[len(points)-1, 0]
|
||||
y[i] += control_points[len(points)-1, 1]
|
||||
z[i] += control_points[len(points)-1, 2]
|
||||
@@ -182,9 +338,26 @@ def generate_nurbs_curve(points, degree=3, num_points=100):
|
||||
|
||||
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
|
||||
vector_u = vector_u.normalized()
|
||||
vector_v = vector_v.normalized()
|
||||
|
||||
a = vector_u.angle(vector_v)
|
||||
cross = vector_u.cross(vector_v)
|
||||
|
||||
# parallel vectors case
|
||||
if cross.length < 1e-6:
|
||||
if vector_u.dot(vector_v) > 0:
|
||||
return 0.0
|
||||
else:
|
||||
arbitrary = vector_u.orthogonal()
|
||||
if arbitrary.dot(normal) < 0:
|
||||
return -a
|
||||
else:
|
||||
return a
|
||||
else:
|
||||
if cross.angle(normal) < 1:
|
||||
a = -a
|
||||
|
||||
return a
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import bpy, bmesh
|
||||
from .objects import *
|
||||
from .bone_data import *
|
||||
from .version import *
|
||||
|
||||
@@ -34,6 +33,19 @@ def overwrite_vgroup(obj, vgroup, new_vgname):
|
||||
vgroup.name = new_vgname
|
||||
|
||||
|
||||
def export_mesh_data(mesh):
|
||||
_verts = [(v.co[0], v.co[1], v.co[2]) for v in mesh.vertices]
|
||||
_edges = [(edge.vertices[0], edge.vertices[1]) for edge in mesh.edges]
|
||||
_faces = []
|
||||
for face in mesh.polygons:
|
||||
face_verts = []
|
||||
for v in face.vertices:
|
||||
face_verts.append(v)
|
||||
_faces.append(face_verts)
|
||||
|
||||
return _verts, _edges, _faces
|
||||
|
||||
|
||||
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)
|
||||
@@ -42,11 +54,18 @@ def create_mesh_data(mesh_name, verts, edges, faces):
|
||||
|
||||
|
||||
def create_object_mesh(obj_name, verts, edges, faces):
|
||||
# create an new object with given mesh data
|
||||
shape_mesh = create_mesh_data(obj_name, verts, edges, faces)
|
||||
# create object
|
||||
shape = bpy.data.objects.new(obj_name, shape_mesh)
|
||||
return shape
|
||||
|
||||
|
||||
|
||||
def create_object_mesh_empty(obj_name):
|
||||
_mesh = bpy.data.meshes.new(name=obj_name)
|
||||
_obj = bpy.data.objects.new(name=obj_name, object_data=_mesh)
|
||||
bpy.context.collection.objects.link(_obj)
|
||||
return _obj
|
||||
|
||||
|
||||
def transfer_shape_keys(source_obj, target_obj):
|
||||
if source_obj == None or target_obj == None:
|
||||
@@ -92,9 +111,10 @@ def transfer_shape_keys(source_obj, 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 = []
|
||||
|
||||
#print("Disable mods...")
|
||||
# disable all non-armature modifiers to solve issues when baking the mesh
|
||||
disabled_mod = {}
|
||||
if apply_mods == False:
|
||||
@@ -116,16 +136,17 @@ def transfer_shape_keys_deformed(source_obj, target_obj, apply_mods=False):
|
||||
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
|
||||
#print("Pin Basis...")
|
||||
source_obj.active_shape_key_index = basis_index
|
||||
source_obj.show_only_shape_key = True
|
||||
#print("Update Depsgraph...")
|
||||
bpy.context.evaluated_depsgraph_get().update()
|
||||
|
||||
# store the vert coords in basis shape keys
|
||||
#print("Store vert coords...")
|
||||
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)
|
||||
@@ -139,8 +160,10 @@ def transfer_shape_keys_deformed(source_obj, target_obj, apply_mods=False):
|
||||
|
||||
if 'mesh_baked' in locals():
|
||||
del mesh_baked
|
||||
|
||||
source_shape_keys = source_obj.data.shape_keys.key_blocks
|
||||
|
||||
# store the vert coords in basis shape keys
|
||||
#print("Iterate through shapes...")
|
||||
for sk_index, sk in enumerate(source_shape_keys):
|
||||
|
||||
if sk_index == basis_index:
|
||||
@@ -226,7 +249,7 @@ def transfer_shape_keys_deformed(source_obj, target_obj, apply_mods=False):
|
||||
|
||||
# restore disabled modifiers
|
||||
for objname in disabled_mod:
|
||||
ob = get_object(objname)
|
||||
ob = bpy.data.objects.get(objname)
|
||||
for modname in disabled_mod[objname]:
|
||||
ob.modifiers[modname].show_viewport = True
|
||||
|
||||
@@ -377,13 +400,13 @@ def transfer_weight_verts(object=None, dict=None, list=None, target_group_name=N
|
||||
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)
|
||||
|
||||
# operate on a copy, creating vgroups in the same loop corrupts pointers on Mac OS
|
||||
grps_dict = {object.vertex_groups[grp.group].name: grp.weight for grp in vert.groups if grp.group < len(object.vertex_groups)}# sic, there may be corrupted vgroup ID anyway
|
||||
|
||||
for grp_name in grps_dict:
|
||||
weight = grps_dict[grp_name]
|
||||
transfer_weight(object=object, vertice=vert, vertex_weight=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):
|
||||
@@ -419,6 +442,7 @@ 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
|
||||
@@ -436,7 +460,7 @@ def transfer_weight_prefix(object=None, vertice=None, vertex_weight=None, group_
|
||||
object.vertex_groups[tar_group_name].add([vertice.index], vertex_weight, 'ADD')
|
||||
|
||||
|
||||
def copy_vgroup(object=None, dict=None, use_side=False):
|
||||
def copy_vgroup(object=None, dict=None, use_side=False, copy_method='OPERATOR'):
|
||||
# dict = {'arm_stretch': ['c_arm_twist_offset'],...}
|
||||
vgroups_names_copy = [i.name for i in object.vertex_groups]
|
||||
|
||||
@@ -458,10 +482,26 @@ def copy_vgroup(object=None, dict=None, use_side=False):
|
||||
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
|
||||
if copy_method == 'OPERATOR':
|
||||
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
|
||||
|
||||
elif copy_method == 'RAW':
|
||||
copy_grp = object.vertex_groups.new(name=tar_grp_name+side)
|
||||
for vert in object.data.vertices:
|
||||
try:
|
||||
weight = vg.weight(vert.index)
|
||||
copy_grp.add([vert.index], weight, 'REPLACE')
|
||||
except:
|
||||
continue
|
||||
|
||||
|
||||
def copy_vertex_group(obj, source_vg_name, new_vg_name):
|
||||
source_vg = obj.vertex_groups.get(source_vg_name)
|
||||
|
||||
|
||||
|
||||
|
||||
def multiply_weight(object=None, vertice=None, vertex_weight=None, group_name=None, dict=None):
|
||||
@@ -495,4 +535,45 @@ def clamp_weights(object=None, vertice=None, vertex_weight=None, group_name=None
|
||||
target_weight = grp.weight
|
||||
|
||||
def_weight = min(vertex_weight, target_weight)
|
||||
object.vertex_groups[group_name].add([vertice.index], def_weight, 'REPLACE')
|
||||
object.vertex_groups[group_name].add([vertice.index], def_weight, 'REPLACE')
|
||||
|
||||
|
||||
def count_closed_mesh_elements(obj):
|
||||
# count the amount of islands/closed elements contained in the same mesh object
|
||||
if obj.type != 'MESH':
|
||||
print("Cannot count mesh elements, not a mesh:", obj.name)
|
||||
return 0
|
||||
|
||||
# Create a BMesh from the object's mesh data
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
|
||||
# Find connected components
|
||||
islands = []
|
||||
visited_faces = set()
|
||||
|
||||
for face in bm.faces:
|
||||
if face.index not in visited_faces:
|
||||
# Perform a breadth-first search (BFS) to find all connected faces
|
||||
island = set()
|
||||
stack = [face]
|
||||
|
||||
while stack:
|
||||
current_face = stack.pop()
|
||||
if current_face.index not in visited_faces:
|
||||
visited_faces.add(current_face.index)
|
||||
island.add(current_face.index)
|
||||
|
||||
# Add neighboring faces to the stack
|
||||
for edge in current_face.edges:
|
||||
for linked_face in edge.link_faces:
|
||||
if linked_face.index not in visited_faces:
|
||||
stack.append(linked_face)
|
||||
|
||||
islands.append(island)
|
||||
|
||||
# Free the BMesh
|
||||
bm.free()
|
||||
|
||||
# Count the islands
|
||||
return len(islands)
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import bpy, os
|
||||
from .bone_edit import *
|
||||
from .context import *
|
||||
from .armature import *
|
||||
from .collections import *
|
||||
import bpy, os, bmesh, sys, ast
|
||||
|
||||
|
||||
def create_empty(empty_name, empty_pos):
|
||||
empty = bpy.data.objects.new(empty_name, None)
|
||||
bpy.context.collection.objects.link(empty)
|
||||
empty.location = empty_pos
|
||||
|
||||
|
||||
def get_object_boundaries(obj):
|
||||
@@ -37,93 +44,140 @@ def get_object_boundaries(obj):
|
||||
return {'front':front, 'back':back, 'left':left, 'right':right, 'top':top, 'bottom':bottom}
|
||||
|
||||
|
||||
def append_from_arp(nodes=None, type=None):
|
||||
def append_from_arp(nodes=None, type=None, from_raw_file=False, new_name=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]
|
||||
if from_raw_file:
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# Add the objects in the scene
|
||||
for obj in data_to.objects:
|
||||
if obj:
|
||||
# link
|
||||
bpy.context.scene.collection.objects.link(obj)
|
||||
def _create_object_mesh(obj_name, verts, edges, faces):
|
||||
# create an new object with given mesh data
|
||||
shape_mesh = _create_mesh_data(obj_name, verts, edges, faces)
|
||||
shape = bpy.data.objects.new(obj_name, shape_mesh)
|
||||
return shape
|
||||
|
||||
for cs_name in nodes:
|
||||
filepath = addon_directory+'/src/cs/'+cs_name+'.py'
|
||||
file = open(filepath, 'r') if sys.version_info >= (3, 11) else open(filepath, 'rU')
|
||||
file_lines = file.readlines()
|
||||
dict = ast.literal_eval(file_lines[0])
|
||||
file.close()
|
||||
|
||||
new_cs_name = new_name if new_name != None else cs_name
|
||||
cs_obj = _create_object_mesh(new_cs_name, dict['verts'], dict['edges'], dict['faces'])
|
||||
|
||||
# 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
|
||||
break
|
||||
|
||||
# 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
|
||||
# parent to cs_grp, collec
|
||||
if cs_grp:
|
||||
cs_obj.parent = cs_grp
|
||||
|
||||
for collec in cs_grp.users_collection:
|
||||
collec.objects.link(cs_obj)
|
||||
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)
|
||||
bpy.context.scene.collection.objects.link(cs_obj)
|
||||
|
||||
else:
|
||||
filepath = addon_directory + "/armature_presets/" + "cs.blend"
|
||||
|
||||
# Clean the cs_ materials names (avoid .001, .002...)
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("cs_"):
|
||||
if mat.name[-3:].isdigit() and bpy.data.materials.get(mat.name[:-4]) == None:
|
||||
mat.name = mat.name[:-4]
|
||||
|
||||
if 'obj' in locals():
|
||||
del obj
|
||||
# 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:
|
||||
try:
|
||||
collec.objects.link(obj)
|
||||
assigned_collections.append(collec)
|
||||
except:# already in collec
|
||||
pass
|
||||
|
||||
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":
|
||||
filepath = addon_directory + "/armature_presets/" + "master.blend"
|
||||
|
||||
# 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]
|
||||
@@ -131,6 +185,8 @@ def append_from_arp(nodes=None, type=None):
|
||||
bpy.context.evaluated_depsgraph_get().update()
|
||||
|
||||
if type == "font":
|
||||
filepath = addon_directory + "/armature_presets/" + "master.blend"
|
||||
|
||||
# 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]
|
||||
@@ -186,10 +242,31 @@ def delete_object(obj):
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
|
||||
def set_active_object(object_name):
|
||||
def set_active_object(object_name, force_display=False):
|
||||
# set active/select object
|
||||
# return the collections that were hidden and are now displayed, if force_display=True
|
||||
bpy.context.view_layer.objects.active = bpy.data.objects[object_name]
|
||||
|
||||
enabled_collections = []
|
||||
object_unhidden = False
|
||||
if force_display:
|
||||
# ensure the object is visible, and collections too
|
||||
_obj_cols = get_obj_collections(bpy.data.objects.get(object_name))
|
||||
for _obj_col in _obj_cols:
|
||||
has_switched = set_collection_viz(_obj_col.name, True)
|
||||
if has_switched:
|
||||
enabled_collections.append(_obj_col.name)
|
||||
|
||||
# unhide object
|
||||
if is_object_hidden(bpy.data.objects[object_name]):
|
||||
unhide_object(bpy.data.objects[object_name])
|
||||
object_unhidden = True
|
||||
|
||||
bpy.data.objects[object_name].select_set(state=True)
|
||||
|
||||
|
||||
if force_display:
|
||||
return enabled_collections, object_unhidden
|
||||
|
||||
|
||||
def hide_object(obj_to_set):
|
||||
try:# object may not be in current view layer
|
||||
@@ -223,23 +300,38 @@ def unhide_object(obj_to_set):
|
||||
|
||||
|
||||
def duplicate_object(new_name="", method='operator', obj=None):
|
||||
if method == 'operator':
|
||||
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()
|
||||
_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')
|
||||
col.objects.link(_obj_dupli)
|
||||
_obj_dupli.data = _obj_dupli.data.copy()
|
||||
if new_name != "":
|
||||
_obj_dupli.name = new_name
|
||||
return _obj_dupli
|
||||
else:
|
||||
print('Cannot duplicate object, not found')
|
||||
|
||||
elif method == 'raw':
|
||||
if obj:
|
||||
new_mesh = bpy.data.meshes.new(new_name)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(obj.data)
|
||||
_obj_dupli = bpy.data.objects.new(new_name, new_mesh)
|
||||
bm.to_mesh(new_mesh)
|
||||
bm.free()
|
||||
for col in obj.users_collection:
|
||||
col.objects.link(_obj_dupli)
|
||||
new_mesh.update()
|
||||
return _obj_dupli
|
||||
|
||||
|
||||
def delete_children(passed_node, type):
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import sys
|
||||
|
||||
def print_progress_bar(job_title, progress, length):
|
||||
def print_progress_bar(job_title, progress, length, start_percent=0, end_percent=100):
|
||||
if length != 0:
|
||||
progress = int((progress * 100) / length)
|
||||
else:
|
||||
progress = 100
|
||||
# optional remap
|
||||
progress = start_percent + progress/(100 / (end_percent-start_percent))
|
||||
|
||||
sys.stdout.write("\r " + job_title + " %d%%" % progress)
|
||||
sys.stdout.flush()
|
||||
try:# unknown compatibility breakage with flush() in a rare case reported by a user
|
||||
sys.stdout.flush()
|
||||
except: pass
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import bpy
|
||||
from math import *
|
||||
from mathutils import *
|
||||
|
||||
|
||||
def propmatrix_to_matrix(arr):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
return Matrix([arr[0:4], arr[4:8], arr[8:12], arr[12:16]])
|
||||
else:
|
||||
return Matrix(arr)
|
||||
|
||||
|
||||
def vectorize3(list):
|
||||
return Vector((list[0], list[1], list[2]))
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import bpy
|
||||
import addon_utils
|
||||
import addon_utils
|
||||
from bpy_extras import anim_utils
|
||||
from .. import auto_rig_datas as ard
|
||||
from .collections import *
|
||||
from .armature import *
|
||||
@@ -18,6 +19,18 @@ class ARP_blender_version:
|
||||
blender_version = ARP_blender_version()
|
||||
|
||||
|
||||
def is_pbone_selected(pb):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
return pb.select
|
||||
else: return pb.bone.select
|
||||
|
||||
|
||||
def select_pb_db(pbone, state=True):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
pbone.select = state
|
||||
else: pbone.bone.select = state
|
||||
|
||||
|
||||
def is_proxy(obj):
|
||||
# proxy atttribute removed in Blender 3.3
|
||||
if 'proxy' in dir(obj):
|
||||
@@ -70,7 +83,7 @@ def convert_drivers_cs_to_xyz(armature):
|
||||
|
||||
# tag in prop
|
||||
armature.data["arp_updated_3.0"] = True
|
||||
print("Converted custom shape scale drivers to xyz")
|
||||
#print("Converted custom shape scale drivers to xyz")
|
||||
|
||||
|
||||
def convert_armature_layers_to_collection(armature):
|
||||
@@ -90,7 +103,7 @@ def convert_armature_layers_to_collection(armature):
|
||||
for col_name in col_names:#armature.data.collections_all:
|
||||
_col = get_armature_collections(armature).get(col_name)
|
||||
if _col == None:# debug...
|
||||
print(" Collec is None, error when removing collection! Exit")
|
||||
#print(" Collec is None, error when removing collection! Exit")
|
||||
continue
|
||||
|
||||
# remove deprecated bones color groups
|
||||
@@ -103,11 +116,13 @@ def convert_armature_layers_to_collection(armature):
|
||||
_col.name = 'color_'+_col.name
|
||||
|
||||
# rename bones collections
|
||||
print('Rename collections...')
|
||||
#print('Rename collections...')
|
||||
|
||||
get_armature_collections(armature).update()
|
||||
|
||||
for i, _col in enumerate(get_armature_collections(armature)):
|
||||
if _col == None:#Debug, for some reasons Mac throws a None collection when appending the Bird armature
|
||||
continue
|
||||
if _col.name.startswith('Layer '):
|
||||
lidx = int(_col.name.split(' ')[1])-1
|
||||
|
||||
@@ -142,7 +157,7 @@ def convert_armature_layers_to_collection(armature):
|
||||
for col_name in ard.layer_col_map:
|
||||
if col_name[0].isupper():# only main collections with capital letters
|
||||
if get_armature_collections(armature).get(col_name) == None:
|
||||
print('Create collection', col_name)
|
||||
#print('Create collection', col_name)
|
||||
armature.data.collections.new(col_name)
|
||||
|
||||
sort_armature_collections(armature)
|
||||
@@ -281,7 +296,7 @@ def invert_angle_with_blender_versions(angle=None, bone=False, axis=None):
|
||||
if invert:
|
||||
angle = -angle
|
||||
|
||||
return angle
|
||||
return angle
|
||||
|
||||
|
||||
def disable_bone_inherit_scale(editbone):
|
||||
@@ -298,6 +313,50 @@ def enable_bone_inherit_scale(editbone):
|
||||
editbone.use_inherit_scale = True
|
||||
|
||||
|
||||
def find_fcurve(act, dp, slot_idx=0, fc_index=0):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
cb = anim_utils.action_get_channelbag_for_slot(act, act.slots[slot_idx])
|
||||
if cb == None: return None
|
||||
return cb.fcurves.find(dp, index=fc_index)
|
||||
else:
|
||||
return act.fcurves.find(data_path=dp, index=fc_index)
|
||||
|
||||
|
||||
def create_fcurve(act, dp, slot_idx=0, fc_index=0, action_group=''):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
cb = anim_utils.action_ensure_channelbag_for_slot(act, act.slots[slot_idx])
|
||||
return cb.fcurves.new(dp, index=fc_index, group_name=action_group)
|
||||
else:
|
||||
return act.fcurves.new(dp, index=fc_index, action_group=action_group)
|
||||
|
||||
|
||||
def delete_fcurve(act, fc, slot_idx=0):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
cb = anim_utils.action_ensure_channelbag_for_slot(act, act.slots[slot_idx])
|
||||
cb.fcurves.remove(fc)
|
||||
else:
|
||||
act.fcurves.remove(fc)
|
||||
|
||||
|
||||
def get_action_fcurves(act, slot_idx=0, as_list=True):
|
||||
if bpy.app.version >= (4,4,0):
|
||||
if bpy.app.version >= (5,0,0):
|
||||
cb = anim_utils.action_ensure_channelbag_for_slot(act, act.slots[slot_idx])
|
||||
else:
|
||||
cb = act.layers[0].strips[0].channelbag(act.slots[slot_idx])
|
||||
|
||||
if cb:
|
||||
if as_list:
|
||||
return [_fc for _fc in cb.fcurves if _fc != None]# check for None curve, debug
|
||||
else:
|
||||
return cb.fcurves
|
||||
else:
|
||||
print("No fcurves found in this slot:", slot_idx)
|
||||
return []
|
||||
else:
|
||||
return act.fcurves
|
||||
|
||||
|
||||
def get_prefs():
|
||||
if bpy.app.version >= (4,2,0):
|
||||
return bpy.context.preferences.addons[__package__[:-8]].preferences
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
from mathutils import Matrix
|
||||
|
||||
|
||||
matrix_coords_l = {'c_pinky1_base.l': Matrix(((-0.1321406364440918, 0.543448269367218, 0.8289769887924194, 0.4933325946331024),
|
||||
(-0.9845015406608582, -0.16924019157886505, -0.04598342627286911, -0.10675017535686493),
|
||||
(0.11530676484107971, -0.8222053647041321, 0.5573893189430237, 1.0162558555603027),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky1.l': Matrix(((0.3489053249359131, 0.41421136260032654, 0.8406510353088379, 0.5218664407730103),
|
||||
(-0.9321953058242798, 0.06120216101408005, 0.3567441999912262, -0.11563616245985031),
|
||||
(0.0963180884718895, -0.9081208109855652, 0.40747979283332825, 0.9730858206748962),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky2.l': Matrix(((0.4049581289291382, 0.5478314161300659, 0.7320449352264404, 0.5371885895729065),
|
||||
(-0.894914984703064, 0.07334459573030472, 0.4401679039001465, -0.11337228119373322),
|
||||
(0.1874462366104126, -0.8333672881126404, 0.5199640989303589, 0.9394932985305786),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky3.l': Matrix(((0.4344693124294281, 0.6926425099372864, 0.5757458209991455, 0.5472332239151001),
|
||||
(-0.8724089860916138, 0.16470646858215332, 0.4601897597312927, -0.11202748119831085),
|
||||
(0.2239179015159607, -0.7022236585617065, 0.6758272647857666, 0.9242132306098938),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1_base.l': Matrix(((-0.24063082039356232, 0.573573112487793, 0.7830138802528381, 0.4974002540111542),
|
||||
(-0.9458432197570801, -0.3196682929992676, -0.05650714412331581, -0.11789528280496597),
|
||||
(0.21789370477199554, -0.7542055249214172, 0.619432270526886, 1.0223618745803833),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1.l': Matrix(((-0.05169704183936119, 0.4065243601799011, 0.9121761918067932, 0.5282756686210632),
|
||||
(-0.9341012239456177, -0.34276989102363586, 0.09982069581747055, -0.1351030468940735),
|
||||
(0.3532460331916809, -0.8469040393829346, 0.3974550664424896, 0.9817630052566528),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring2.l': Matrix(((0.07245957851409912, 0.6446757316589355, 0.7610143423080444, 0.5470014214515686),
|
||||
(-0.9164383411407471, -0.25805044174194336, 0.3058597445487976, -0.15089207887649536),
|
||||
(0.3935604691505432, -0.7195852994918823, 0.5721074342727661, 0.942751944065094),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring3.l': Matrix(((0.2263454794883728, 0.8068910241127014, 0.5456141233444214, 0.5622686743736267),
|
||||
(-0.8494088053703308, -0.11064670979976654, 0.5160052180290222, -0.15700331330299377),
|
||||
(0.47673046588897705, -0.5802450180053711, 0.6603360176086426, 0.9257106781005859),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1_base.l': Matrix(((-0.41135188937187195, 0.5918521881103516, 0.6931815147399902, 0.49848878383636475),
|
||||
(-0.8688518404960632, -0.4844593107700348, -0.10195799171924591, -0.12799011170864105),
|
||||
(0.2754744291305542, -0.6442126035690308, 0.7135152816772461, 1.028594970703125),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1.l': Matrix(((-0.29555466771125793, 0.41192948818206787, 0.8619521856307983, 0.5332725048065186),
|
||||
(-0.860168993473053, -0.5072991251945496, -0.05250336974859238, -0.15646222233772278),
|
||||
(0.4156401455402374, -0.756942093372345, 0.5042637586593628, 0.9907339215278625),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle2.l': Matrix(((-0.2989175617694855, 0.5045099854469299, 0.8100113868713379, 0.5524937510490417),
|
||||
(-0.819093644618988, -0.5711621046066284, 0.053475141525268555, -0.18013334274291992),
|
||||
(0.48962658643722534, -0.6474900841712952, 0.5839712619781494, 0.9554142355918884),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle3.l': Matrix(((0.03374910354614258, 0.6838750839233398, 0.728818416595459, 0.5666195154190063),
|
||||
(-0.7648073434829712, -0.45176589488983154, 0.4593227207660675, -0.19612523913383484),
|
||||
(0.6433745622634888, -0.5729071497917175, 0.5077859163284302, 0.9372851252555847),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1_base.l': Matrix(((-0.5358661413192749, 0.556251585483551, 0.6351626515388489, 0.4885375201702118),
|
||||
(-0.7715515494346619, -0.6281226873397827, -0.10084662586450577, -0.13851317763328552),
|
||||
(0.3428639769554138, -0.5441008806228638, 0.7657666206359863, 1.033432960510254),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1.l': Matrix(((-0.5446334481239319, 0.4174560010433197, 0.7273959517478943, 0.5232923626899719),
|
||||
(-0.6641221046447754, -0.7443320751190186, -0.07008165121078491, -0.17775851488113403),
|
||||
(0.5121680498123169, -0.5212484002113342, 0.6826301217079163, 0.9994372725486755),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index2.l': Matrix(((-0.250714510679245, 0.43092647194862366, 0.8668590188026428, 0.5410243272781372),
|
||||
(-0.7369382381439209, -0.665624737739563, 0.11775188148021698, -0.20937474071979523),
|
||||
(0.6277450919151306, -0.6092994809150696, 0.484448105096817, 0.9772966504096985),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index3.l': Matrix(((-0.42731034755706787, 0.5787864923477173, 0.6945587396621704, 0.5504752993583679),
|
||||
(-0.7405635714530945, -0.6647520661354065, 0.09833458065986633, -0.2239731252193451),
|
||||
(0.5186238884925842, -0.47234559059143066, 0.7126837968826294, 0.9639335870742798),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1_base.l': Matrix(((-0.742074191570282, 0.16673608124256134, -0.6492496132850647, 0.4722246825695038),
|
||||
(0.06269612163305283, -0.9470593333244324, -0.3148776590824127, -0.14717695116996765),
|
||||
(-0.6673793196678162, -0.2743678689002991, 0.6923345923423767, 1.0296953916549683),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1.l': Matrix(((-0.8646366000175476, -0.03816646337509155, -0.5009462237358093, 0.47222477197647095),
|
||||
(0.11270685493946075, -0.9864310026168823, -0.11937803030014038, -0.14717693626880646),
|
||||
(-0.48959267139434814, -0.15967853367328644, 0.857205867767334, 1.0296955108642578),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb2.l': Matrix(((-0.9756026864051819, -0.10725779831409454, -0.19156037271022797, 0.4707222878932953),
|
||||
(0.15552102029323578, -0.9534976482391357, -0.25817763805389404, -0.1860094517469406),
|
||||
(-0.1549607515335083, -0.2816705107688904, 0.9469156265258789, 1.0234094858169556),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb3.l': Matrix(((-0.8365055918693542, -0.21642035245895386, -0.5034093260765076, 0.46735304594039917),
|
||||
(0.17929044365882874, -0.9762313961982727, 0.12176759541034698, -0.21596266329288483),
|
||||
(-0.5177969336509705, 0.011602729558944702, 0.8554251194000244, 1.0145611763000488),
|
||||
(0.0, 0.0, 0.0, 1.0)))}
|
||||
|
||||
|
||||
matrix_coords_r = {'c_pinky1_base.r': Matrix(((-0.13214083015918732, -0.5434483885765076, -0.8289767503738403, -0.49333253502845764),
|
||||
(0.9845014810562134, -0.1692400574684143, -0.04598373547196388, -0.10675013810396194),
|
||||
(-0.11530639231204987, -0.8222052454948425, 0.557389497756958, 1.0162558555603027),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky1.r': Matrix(((0.3489048182964325, -0.4142115116119385, -0.8406510353088379, -0.5218664407730103),
|
||||
(0.9321953058242798, 0.061202313750982285, 0.356743723154068, -0.11563613265752792),
|
||||
(-0.09631766378879547, -0.9081205725669861, 0.40748000144958496, 0.9730857014656067),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky2.r': Matrix(((0.40495792031288147, -0.547831654548645, -0.7320449352264404, -0.537188708782196),
|
||||
(0.8949152231216431, 0.07334486395120621, 0.4401676654815674, -0.11337225884199142),
|
||||
(-0.18744593858718872, -0.833367109298706, 0.5199644565582275, 0.93949294090271),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky3.r': Matrix(((0.4344694912433624, -0.6926425695419312, -0.5757454633712769, -0.547233521938324),
|
||||
(0.8724088072776794, 0.1647067666053772, 0.4601900577545166, -0.11202739179134369),
|
||||
(-0.2239178717136383, -0.7022233605384827, 0.6758275032043457, 0.9242128133773804),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1_base.r': Matrix(((-0.24063074588775635, -0.5735732316970825, -0.7830137610435486, -0.4974001944065094),
|
||||
(0.9458429217338562, -0.3196679651737213, -0.056507356464862823, -0.11789528280496597),
|
||||
(-0.2178933024406433, -0.7542052865028381, 0.6194326281547546, 1.0223618745803833),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1.r': Matrix(((-0.051697127521038055, -0.40652453899383545, -0.9121761322021484, -0.5282756090164185),
|
||||
(0.9341010451316833, -0.34276944398880005, 0.09982036799192429, -0.13510306179523468),
|
||||
(-0.35324567556381226, -0.8469040989875793, 0.39745569229125977, 0.9817630052566528),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring2.r': Matrix(((0.07245972752571106, -0.6446757912635803, -0.7610142230987549, -0.5470013618469238),
|
||||
(0.9164387583732605, -0.2580502927303314, 0.3058595061302185, -0.15089204907417297),
|
||||
(-0.39356034994125366, -0.7195854187011719, 0.5721078515052795, 0.9427520632743835),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring3.r': Matrix(((0.2263452708721161, -0.8068908452987671, -0.5456140041351318, -0.5622686147689819),
|
||||
(0.8494092226028442, -0.11064684391021729, 0.516004741191864, -0.15700317919254303),
|
||||
(-0.47673019766807556, -0.5802450180053711, 0.6603364944458008, 0.9257107377052307),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1_base.r': Matrix(((-0.4113517999649048, -0.5918522477149963, -0.6931816339492798, -0.49848875403404236),
|
||||
(0.8688518404960632, -0.48445942997932434, -0.10195799916982651, -0.12799015641212463),
|
||||
(-0.27547430992126465, -0.6442127823829651, 0.7135154008865356, 1.028594970703125),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1.r': Matrix(((-0.2955547571182251, -0.41192927956581116, -0.8619523048400879, -0.5332725644111633),
|
||||
(0.860168993473053, -0.5072991251945496, -0.052503712475299835, -0.15646234154701233),
|
||||
(-0.41563984751701355, -0.7569423317909241, 0.5042638778686523, 0.9907339215278625),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle2.r': Matrix(((-0.298917680978775, -0.5045096278190613, -0.8100114464759827, -0.5524936318397522),
|
||||
(0.819093644618988, -0.5711621642112732, 0.053474873304367065, -0.18013350665569305),
|
||||
(-0.48962661623954773, -0.6474902033805847, 0.5839712023735046, 0.9554141163825989),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle3.r': Matrix(((0.03374888375401497, -0.6838746070861816, -0.7288186550140381, -0.566619336605072),
|
||||
(0.7648075819015503, -0.4517658054828644, 0.4593222737312317, -0.19612538814544678),
|
||||
(-0.6433743834495544, -0.5729072093963623, 0.507785975933075, 0.9372850656509399),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1_base.r': Matrix(((-0.5358662605285645, -0.5562516450881958, -0.6351625323295593, -0.4885374903678894),
|
||||
(0.7715515494346619, -0.6281226873397827, -0.10084672272205353, -0.13851313292980194),
|
||||
(-0.3428637981414795, -0.5441008806228638, 0.7657667398452759, 1.033432960510254),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1.r': Matrix(((-0.5446333289146423, -0.4174560308456421, -0.7273959517478943, -0.5232923030853271),
|
||||
(0.6641222238540649, -0.7443320751190186, -0.07008160650730133, -0.17775849997997284),
|
||||
(-0.5121681690216064, -0.5212485194206238, 0.6826300024986267, 0.9994373321533203),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index2.r': Matrix(((-0.25071465969085693, -0.43092650175094604, -0.8668591380119324, -0.5410242676734924),
|
||||
(0.7369382977485657, -0.6656247973442078, 0.1177515834569931, -0.20937487483024597),
|
||||
(-0.6277451515197754, -0.6092994213104248, 0.48444831371307373, 0.9772967100143433),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index3.r': Matrix(((-0.42731064558029175, -0.5787865519523621, -0.69455885887146, -0.5504752993583679),
|
||||
(0.7405636310577393, -0.6647522449493408, 0.09833402186632156, -0.22397328913211823),
|
||||
(-0.5186238288879395, -0.47234559059143066, 0.7126840949058533, 0.9639335870742798),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1_base.r': Matrix(((-0.7420739531517029, -0.16673603653907776, 0.6492499709129333, -0.47222471237182617),
|
||||
(-0.0626964196562767, -0.947059154510498, -0.31487780809402466, -0.14717699587345123),
|
||||
(0.6673798561096191, -0.2743680477142334, 0.6923341155052185, 1.0296955108642578),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1.r': Matrix(((-0.8646363615989685, 0.038166627287864685, 0.5009466409683228, -0.4722246825695038),
|
||||
(-0.11270713806152344, -0.9864309430122375, -0.11937820911407471, -0.14717702567577362),
|
||||
(0.4895932376384735, -0.15967872738838196, 0.8572055697441101, 1.0296955108642578),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb2.r': Matrix(((-0.9756026268005371, 0.10725804418325424, 0.19156040251255035, -0.4707222580909729),
|
||||
(-0.1555212289094925, -0.9534975290298462, -0.25817784667015076, -0.18600955605506897),
|
||||
(0.15496063232421875, -0.2816707491874695, 0.946915864944458, 1.0234094858169556),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb3.r': Matrix(((-0.8365054726600647, 0.21642057597637177, 0.5034092664718628, -0.4673529863357544),
|
||||
(-0.1792907863855362, -0.9762312769889832, 0.12176735699176788, -0.2159627228975296),
|
||||
(0.5177968740463257, 0.011602476239204407, 0.8554254174232483, 1.0145611763000488),
|
||||
(0.0, 0.0, 0.0, 1.0)))}
|
||||
@@ -0,0 +1,141 @@
|
||||
from mathutils import Matrix
|
||||
hand_mat_l = Matrix(((-0.35261183977127075, 0.516721785068512, 0.7801688313484192, 0.4798411726951599),
|
||||
(-0.8112916350364685, -0.5842888355255127, 0.020308198407292366, -0.11578542739152908),
|
||||
(0.4663377106189728, -0.6257834434509277, 0.6252393126487732, 1.0348492860794067),
|
||||
(0.0, 0.0, 0.0, 1.0)))
|
||||
|
||||
|
||||
hand_mat_r = Matrix(((-0.3526119887828827, -0.516721785068512, -0.7801688313484192, -0.47984111309051514),
|
||||
(0.8112916946411133, -0.5842887759208679, 0.020307956263422966, -0.11578543484210968),
|
||||
(-0.4663374722003937, -0.6257835030555725, 0.6252394914627075, 1.0348492860794067),
|
||||
(0.0, 0.0, 0.0, 1.0)))
|
||||
|
||||
|
||||
matrix_coords_l = {'c_pinky1_base.l': Matrix(((-0.3588298559188843, 0.424203485250473, 0.8314400315284729, 0.49337443709373474),
|
||||
(-0.8281897306442261, -0.5555429458618164, -0.07398698478937149, -0.10673553496599197),
|
||||
(0.43051522970199585, -0.7151384949684143, 0.5506664514541626, 1.0163047313690186),
|
||||
(0.0, 0.0, 0.0, 1.0))),
|
||||
'c_pinky1.l': Matrix(((-0.05721341073513031, -0.9644581079483032, 0.25796768069267273, 0.5156472325325012),
|
||||
(-0.8807923793792725, -0.0728902518749237, -0.4678591787815094, -0.1359044313430786),
|
||||
(0.47003400325775146, -0.25398388504981995, -0.8453166484832764, 0.9787562489509583),
|
||||
(0.0, 0.0, 0.0, 1.0))),
|
||||
'c_pinky2.l': Matrix(((0.03900858014822006, 0.06956508010625839, -0.9968141913414001, 0.47997069358825684),
|
||||
(-0.919760525226593, 0.39238572120666504, -0.008609596639871597, -0.13860078155994415),
|
||||
(0.3905368149280548, 0.9171664714813232, 0.07928968966007233, 0.9693610668182373),
|
||||
(0.0, 0.0, 0.0, 1.0))),
|
||||
'c_pinky3.l': Matrix(((-0.012231852859258652, 0.9586328268051147, 0.28438279032707214, 0.4812462031841278),
|
||||
(-0.9238848090171814, -0.11962065100669861, 0.363493949174881, -0.1314062774181366),
|
||||
(0.3824753165245056, -0.25829076766967773, 0.8871296048164368, 0.9861776828765869),
|
||||
(0.0, 0.0, 0.0, 1.0))),
|
||||
'c_ring1_base.l': Matrix(((-0.39991500973701477, 0.47596868872642517, 0.7832762598991394, 0.49744197726249695),
|
||||
(-0.8209208250045776, -0.5660759210586548, -0.07515108585357666, -0.1178804263472557),
|
||||
(0.4076242744922638, -0.6730616688728333, 0.6171146631240845, 1.0224111080169678),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1.l': Matrix(((-0.14413531124591827, -0.9160363078117371, 0.37430238723754883, 0.5230634212493896),
|
||||
(-0.8027083277702332, -0.11296883970499039, -0.5855748057365417, -0.1483522206544876),
|
||||
(0.5786923766136169, -0.38485753536224365, -0.7190269827842712, 0.9861801862716675),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring2.l': Matrix(((-0.10324987769126892, -0.19098883867263794, -0.9761467576026917, 0.48086801171302795),
|
||||
(-0.8736379742622375, 0.48656898736953735, -0.002792816609144211, -0.15355591475963593),
|
||||
(0.4754961431026459, 0.8525105118751526, -0.21709322929382324, 0.9684524536132812),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring3.l': Matrix(((-0.286427766084671, 0.9244988560676575, 0.2515174150466919, 0.47634512186050415),
|
||||
(-0.848092257976532, -0.3667835295200348, 0.38237467408180237, -0.1420329213142395),
|
||||
(0.44575732946395874, -0.10378727316856384, 0.8891169428825378, 0.9886415600776672),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1_base.l': Matrix(((-0.49470439553260803, 0.5256193280220032, 0.6920921802520752, 0.4985300898551941),
|
||||
(-0.7925525307655334, -0.5995886921882629, -0.11114682257175446, -0.1279752105474472),
|
||||
(0.35654982924461365, -0.6035042405128479, 0.7132004499435425, 1.0286451578140259),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1.l': Matrix(((-0.18281525373458862, -0.8775211572647095, 0.4433228373527527, 0.5294212102890015),
|
||||
(-0.8266451358795166, -0.10689479857683182, -0.5524771809577942, -0.16321365535259247),
|
||||
(0.5321995615959167, -0.46747225522994995, -0.7058566212654114, 0.9931765794754028),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle2.l': Matrix(((-0.09921594709157944, -0.1985192596912384, -0.9750621318817139, 0.48847508430480957),
|
||||
(-0.8331379890441895, 0.5523718595504761, -0.027686286717653275, -0.16820143163204193),
|
||||
(0.5440933704376221, 0.8096145987510681, -0.22019770741462708, 0.9713637232780457),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle3.l': Matrix(((-0.45292720198631287, 0.8585988879203796, 0.24013452231884003, 0.48291686177253723),
|
||||
(-0.7653985619544983, -0.5125921964645386, 0.38912028074264526, -0.15273569524288177),
|
||||
(0.45718955993652344, -0.007555872201919556, 0.8893374800682068, 0.9940320253372192),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1_base.l': Matrix(((-0.5358661413192749, 0.556251585483551, 0.6351626515388489, 0.4885786473751068),
|
||||
(-0.7715515494346619, -0.6281226873397827, -0.10084662586450577, -0.1384982317686081),
|
||||
(0.3428639769554138, -0.5441008806228638, 0.7657666206359863, 1.0334831476211548),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1.l': Matrix(((-0.3491906523704529, -0.8236109614372253, 0.4469125270843506, 0.5233335494995117),
|
||||
(-0.7358909249305725, -0.054232120513916016, -0.6749246716499329, -0.17774353921413422),
|
||||
(0.5801123976707458, -0.5645565986633301, -0.5871503949165344, 0.999487578868866),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index2.l': Matrix(((-0.29383838176727295, -0.25366896390914917, -0.9215805530548096, 0.4883497357368469),
|
||||
(-0.9045541882514954, 0.38540804386138916, 0.18232452869415283, -0.18004705011844635),
|
||||
(0.30893459916114807, 0.8871937394142151, -0.34270498156547546, 0.9755074977874756),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index3.l': Matrix(((-0.09548855572938919, 0.9815612435340881, 0.1655871868133545, 0.48278629779815674),
|
||||
(-0.9254596829414368, -0.1488049030303955, 0.3483980894088745, -0.17159435153007507),
|
||||
(0.36661431193351746, -0.11997638642787933, 0.9226047396659851, 0.9949652552604675),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1_base.l': Matrix(((-0.742074191570282, 0.16673608124256134, -0.6492496132850647, 0.47226616740226746),
|
||||
(0.06269612163305283, -0.9470593333244324, -0.3148776590824127, -0.1471620798110962),
|
||||
(-0.6673793196678162, -0.2743678689002991, 0.6923345923423767, 1.02974534034729),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1.l': Matrix(((-0.916347861289978, 0.030809633433818817, -0.3991962671279907, 0.47226616740226746),
|
||||
(0.18215209245681763, -0.8557999730110168, -0.4841769337654114, -0.1471620798110962),
|
||||
(-0.3565494418144226, -0.5163886547088623, 0.778598427772522, 1.02974534034729),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb2.l': Matrix(((-0.9960366487503052, 0.006717206910252571, -0.08869118243455887, 0.47347909212112427),
|
||||
(0.08558714389801025, -0.199024498462677, -0.9762499332427979, -0.1808519959449768),
|
||||
(-0.024209430441260338, -0.9799714088439941, 0.19766077399253845, 1.009416937828064),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb3.l': Matrix(((-0.8867418766021729, 0.45290955901145935, -0.0925314724445343, 0.4736900329589844),
|
||||
(0.4336596429347992, 0.7457115054130554, -0.5058196783065796, -0.18710407614707947),
|
||||
(-0.16008882224559784, -0.4886585474014282, -0.8576620221138, 0.9786321520805359),
|
||||
(0.0, 0.0, 0.0, 1.0)))}
|
||||
|
||||
|
||||
matrix_coords_r = {'c_pinky1_base.r': Matrix(((-0.3588300347328186, -0.4242035448551178, -0.831439733505249, -0.49333256483078003),
|
||||
(0.8281898498535156, -0.5555428266525269, -0.07398723065853119, -0.10675002634525299),
|
||||
(-0.4305148422718048, -0.7151386141777039, 0.5506665706634521, 1.0162558555603027),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky1.r': Matrix(((-0.05721384286880493, 0.9644578099250793, -0.25796759128570557, -0.5156054496765137),
|
||||
(0.8807926774024963, -0.07288983464241028, -0.4678589701652527, -0.13591890037059784),
|
||||
(-0.4700336754322052, -0.2539840042591095, -0.8453166484832764, 0.9787073731422424),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky2.r': Matrix(((0.03900822252035141, -0.06956491619348526, 0.9968144297599792, -0.47992897033691406),
|
||||
(0.9197608232498169, 0.3923851549625397, -0.008609358221292496, -0.1386151909828186),
|
||||
(-0.3905361294746399, 0.9171666502952576, 0.07928939163684845, 0.9693121910095215),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_pinky3.r': Matrix(((-0.012232892215251923, -0.9586328864097595, -0.28438252210617065, -0.48120447993278503),
|
||||
(0.92388516664505, -0.11962135881185532, 0.3634931743144989, -0.13142070174217224),
|
||||
(-0.3824746608734131, -0.25829029083251953, 0.8871298432350159, 0.9861287474632263),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1_base.r': Matrix(((-0.39991509914398193, -0.4759686291217804, -0.7832764387130737, -0.49740028381347656),
|
||||
(0.8209203481674194, -0.5660758018493652, -0.07515129446983337, -0.11789508163928986),
|
||||
(-0.4076239764690399, -0.673061728477478, 0.617114782333374, 1.0223617553710938),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring1.r': Matrix(((-0.1441354602575302, 0.9160371422767639, -0.37430238723754883, -0.5230216979980469),
|
||||
(0.802708089351654, -0.11296850442886353, -0.5855749249458313, -0.14836692810058594),
|
||||
(-0.5786920189857483, -0.38485804200172424, -0.7190274596214294, 0.9861308336257935),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring2.r': Matrix(((-0.10325005650520325, 0.19098877906799316, 0.976146936416626, -0.4808262884616852),
|
||||
(0.8736380934715271, 0.4865686595439911, -0.00279245525598526, -0.15357057750225067),
|
||||
(-0.4754956364631653, 0.8525108695030212, -0.2170931100845337, 0.968403160572052),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_ring3.r': Matrix(((-0.28642746806144714, -0.9244990348815918, -0.25151753425598145, -0.4763032793998718),
|
||||
(0.8480921983718872, -0.3667834401130676, 0.38237419724464417, -0.14204759895801544),
|
||||
(-0.445756733417511, -0.10378794372081757, 0.8891172409057617, 0.9885924458503723),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1_base.r': Matrix(((-0.49470436573028564, -0.5256195068359375, -0.6920923590660095, -0.4984886348247528),
|
||||
(0.792552649974823, -0.5995886921882629, -0.11114684492349625, -0.12799014151096344),
|
||||
(-0.3565496802330017, -0.6035044193267822, 0.7132005095481873, 1.0285948514938354),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle1.r': Matrix(((-0.18281537294387817, 0.8775213360786438, -0.4433228373527527, -0.5293797850608826),
|
||||
(0.8266454935073853, -0.10689447820186615, -0.5524771809577942, -0.16322852671146393),
|
||||
(-0.5321993827819824, -0.4674723744392395, -0.7058568000793457, 0.993126392364502),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle2.r': Matrix(((-0.09921589493751526, 0.19851899147033691, 0.9750621318817139, -0.4884335994720459),
|
||||
(0.8331380486488342, 0.5523715019226074, -0.027686096727848053, -0.16821634769439697),
|
||||
(-0.5440930128097534, 0.8096147179603577, -0.2201976180076599, 0.9713135957717896),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_middle3.r': Matrix(((-0.4529270827770233, -0.8585991263389587, -0.24013493955135345, -0.4828752875328064),
|
||||
(0.7653987407684326, -0.5125921964645386, 0.3891199827194214, -0.15275052189826965),
|
||||
(-0.45718950033187866, -0.007556170225143433, 0.8893375396728516, 0.9939819574356079),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1_base.r': Matrix(((-0.5358662605285645, -0.5562516450881958, -0.6351625323295593, -0.4885374903678894),
|
||||
(0.7715515494346619, -0.6281226873397827, -0.10084672272205353, -0.13851310312747955),
|
||||
(-0.3428637981414795, -0.5441008806228638, 0.7657667398452759, 1.033432960510254),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index1.r': Matrix(((-0.3491906523704529, 0.8236109018325806, -0.44691264629364014, -0.5232923030853271),
|
||||
(0.7358911037445068, -0.0542321503162384, -0.6749247312545776, -0.17775849997997284),
|
||||
(-0.5801124572753906, -0.564556360244751, -0.5871505737304688, 0.9994373321533203),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index2.r': Matrix(((-0.29383835196495056, 0.25366878509521484, 0.9215806722640991, -0.4883083999156952),
|
||||
(0.9045540690422058, 0.3854084312915802, 0.18232448399066925, -0.18006213009357452),
|
||||
(-0.3089349865913391, 0.8871937394142151, -0.342704713344574, 0.9754570126533508),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_index3.r': Matrix(((-0.09548826515674591, -0.9815613031387329, -0.1655874252319336, -0.48274490237236023),
|
||||
(0.9254595637321472, -0.14880481362342834, 0.3483985364437103, -0.17160934209823608),
|
||||
(-0.36661475896835327, -0.11997681856155396, 0.9226045608520508, 0.9949148893356323),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1_base.r': Matrix(((-0.7420739531517029, -0.16673603653907776, 0.6492499709129333, -0.47222471237182617),
|
||||
(-0.0626964196562767, -0.947059154510498, -0.31487780809402466, -0.14717699587345123),
|
||||
(0.6673798561096191, -0.2743680477142334, 0.6923341155052185, 1.0296955108642578),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb1.r': Matrix(((-0.9163479208946228, -0.03080972284078598, 0.3991967439651489, -0.4722246825695038),
|
||||
(-0.18215233087539673, -0.855799674987793, -0.4841769337654114, -0.14717702567577362),
|
||||
(0.35655027627944946, -0.5163888931274414, 0.7785980105400085, 1.0296955108642578),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb2.r': Matrix(((-0.9960367679595947, -0.0067172907292842865, 0.08869123458862305, -0.4734375476837158),
|
||||
(-0.08558713644742966, -0.19902411103248596, -0.9762501120567322, -0.18086694180965424),
|
||||
(0.024209478870034218, -0.9799717664718628, 0.19766059517860413, 1.0093668699264526),
|
||||
(0.0, 0.0, 0.0, 1.0))), 'c_thumb3.r': Matrix(((-0.8867420554161072, -0.45290952920913696, 0.09253138303756714, -0.4736485183238983),
|
||||
(-0.43365973234176636, 0.7457118630409241, -0.5058193206787109, -0.18711905181407928),
|
||||
(0.16008873283863068, -0.48865842819213867, -0.8576623797416687, 0.978581964969635),
|
||||
(0.0, 0.0, 0.0, 1.0)))}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
from mathutils import Vector, Euler, Matrix
|
||||
|
||||
coords={'pelvis': Vector((1.1648752433757181e-06, -0.03546452522277832, 9.705429077148438)), 'spine_01': Vector((2.2931205876375316e-06, -0.6347151398658752, 19.094528198242188)), 'spine_02': Vector((1.598883045517141e-06, 0.6980798244476318, 13.395744323730469)), 'spine_03': Vector((1.6606641111138742e-06, 1.0556304454803467, 13.992294311523438)), 'clavicle_l': Vector((14.954872131347656, 4.510254859924316, -2.2741546630859375)), 'upperarm_l': Vector((15.369140625, -0.2687036991119385, -1.2218170166015625)), 'lowerarm_l': Vector((20.141151428222656, -3.486912250518799, -1.384307861328125)), 'hand_l': Vector((10.576087951660156, -1.987648606300354, -0.7958221435546875)), 'index_01_l': Vector((4.158714294433594, -0.7536659240722656, -0.720947265625)), 'index_02_l': Vector((3.249267578125, -0.3236865997314453, -0.9248504638671875)), 'index_03_l': Vector((3.1864013671875, -0.4611630439758301, -1.073333740234375)), 'middle_01_l': Vector((4.579429626464844, -0.4014453887939453, -0.6331024169921875)), 'middle_02_l': Vector((3.5030288696289062, -0.391385555267334, -0.943206787109375)), 'middle_03_l': Vector((3.4647369384765625, -0.42128419876098633, -1.06402587890625)), 'pinky_01_l': Vector((3.498687744140625, 0.6987745761871338, -0.15118408203125)), 'pinky_02_l': Vector((2.8414535522460938, 0.5827560424804688, -0.70745849609375)), 'pinky_03_l': Vector((2.8664474487304688, 0.37882447242736816, -0.744293212890625)), 'ring_01_l': Vector((4.408515930175781, 0.2745676636695862, -0.3408203125)), 'ring_02_l': Vector((3.3953475952148438, 0.27488279342651367, -0.695098876953125)), 'ring_03_l': Vector((3.3896865844726562, 0.14387929439544678, -0.75921630859375)), 'thumb_01_l': Vector((2.0802688598632812, -2.6089048385620117, -1.9597320556640625)), 'thumb_02_l': Vector((2.58538818359375, -1.9712257385253906, -2.4354095458984375)), 'thumb_03_l': Vector((2.8783111572265625, -1.8077516555786133, -2.2245330810546875)), 'clavicle_r': Vector((-14.95479679107666, 4.510228633880615, -2.274139404296875)), 'upperarm_r': Vector((-15.369194030761719, -0.26871156692504883, -1.2218170166015625)), 'lowerarm_r': Vector((-20.141193389892578, -3.486935615539551, -1.384307861328125)), 'hand_r': Vector((-10.576080322265625, -1.987655758857727, -0.7958221435546875)), 'index_01_r': Vector((-4.158897399902344, -0.7537021636962891, -0.720977783203125)), 'index_02_r': Vector((-3.249267578125, -0.32368993759155273, -0.9248504638671875)), 'index_03_r': Vector((-3.1864089965820312, -0.4611630439758301, -1.0733184814453125)), 'middle_01_r': Vector((-4.579620361328125, -0.40146565437316895, -0.6331329345703125)), 'middle_02_r': Vector((-3.5030975341796875, -0.39139294624328613, -0.9432220458984375)), 'middle_03_r': Vector((-3.4648056030273438, -0.42128705978393555, -1.06402587890625)), 'pinky_01_r': Vector((-3.4987640380859375, 0.6987893581390381, -0.1511993408203125)), 'pinky_02_r': Vector((-2.84124755859375, 0.5827126502990723, -0.7073974609375)), 'pinky_03_r': Vector((-2.8662338256835938, 0.37879395484924316, -0.744232177734375)), 'ring_01_r': Vector((-4.408203125, 0.2745439410209656, -0.3408050537109375)), 'ring_02_r': Vector((-3.3953475952148438, 0.27488067746162415, -0.695098876953125)), 'ring_03_r': Vector((-3.3897171020507812, 0.14389744400978088, -0.7591552734375)), 'thumb_01_r': Vector((-2.0802078247070312, -2.608829975128174, -1.9596710205078125)), 'thumb_02_r': Vector((-2.58538818359375, -1.9712257385253906, -2.4354248046875)), 'thumb_03_r': Vector((-2.878326416015625, -1.8077640533447266, -2.2245025634765625)), 'neck_01': Vector((1.083126335288398e-06, -2.8042922019958496, 8.857421875)), 'head': Vector((1.1140700735268183e-06, 0.1489097774028778, 16.57891845703125)), 'thigh_l': Vector((3.5867919921875, -0.9371733665466309, -32.11991882324219)), 'calf_l': Vector((1.8110084533691406, 1.1467314958572388, -30.26090431213379)), 'foot_l': Vector((0.8927364349365234, -19.544645309448242, -1.1506776809692383)), 'thigh_r': Vector((-3.586806297302246, -0.9371740818023682, -32.120018005371094)), 'calf_r': Vector((-1.8110074996948242, 1.1467384099960327, -30.261043548583984)), 'foot_r': Vector((-0.8925895690917969, -19.54458999633789, -1.1506767272949219))}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "BlenderKit Online Asset Library",
|
||||
"author": "Vilem Duha, Petr Dlouhy, A. Gajdosik",
|
||||
"version": (3, 16, 1, 250612), # X.Y.Z.yymmdd
|
||||
"version": (3, 18, 0, 251121), # X.Y.Z.yymmdd
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D > Properties > BlenderKit",
|
||||
"description": "Boost your workflow with drag&drop assets from the community driven library.",
|
||||
@@ -28,7 +28,7 @@ bl_info = {
|
||||
"tracker_url": "https://github.com/BlenderKit/blenderkit/issues",
|
||||
"category": "3D View",
|
||||
}
|
||||
VERSION = (3, 16, 1, 250612)
|
||||
VERSION = (3, 18, 0, 251121)
|
||||
|
||||
import logging
|
||||
import random
|
||||
@@ -297,6 +297,11 @@ def asset_type_callback(self, context):
|
||||
6,
|
||||
),
|
||||
]
|
||||
|
||||
# Only add addon option for Blender 4.2+
|
||||
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
items.append(("ADDON", "Add-ons", "Find add-ons", "PLUGIN", 7))
|
||||
else:
|
||||
items = [
|
||||
("MODEL", "Model", "Upload a model", "OBJECT_DATAMODE", 0),
|
||||
@@ -314,6 +319,11 @@ def asset_type_callback(self, context):
|
||||
),
|
||||
]
|
||||
|
||||
# Only add addon option for Blender 4.2+
|
||||
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
items.append(("ADDON", "Add-on", "Upload an addon", "PLUGIN", 7))
|
||||
|
||||
return items
|
||||
|
||||
|
||||
@@ -380,6 +390,61 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
max=10,
|
||||
update=search.search_update_delayed,
|
||||
)
|
||||
search_order_by: EnumProperty(
|
||||
name="Order",
|
||||
description="Search result order",
|
||||
items=(
|
||||
(
|
||||
"default",
|
||||
"Default",
|
||||
"By default, the sorting algorithm changes dynamically based on search filters.",
|
||||
),
|
||||
("-created", "Newest", "Sort results from newest to oldest."),
|
||||
("created", "Oldest", "Sort results from oldest to newest."),
|
||||
(
|
||||
"-bookmarks",
|
||||
"▼ Bookmarks",
|
||||
"Sort results from most bookmarked to least.",
|
||||
),
|
||||
(
|
||||
"bookmarks",
|
||||
"▲ Bookmarks",
|
||||
"Sort results from least bookmarked to most.",
|
||||
),
|
||||
(
|
||||
"-score",
|
||||
"▼ Score",
|
||||
"Sort results from highest asset score to the lowest.",
|
||||
),
|
||||
(
|
||||
"score",
|
||||
"▲ Score",
|
||||
"Sort results from lowest asset score to the highest.",
|
||||
),
|
||||
(
|
||||
"-working_hours",
|
||||
"▼ Complexity",
|
||||
"Sort results from most complex to the least.",
|
||||
),
|
||||
(
|
||||
"working_hours",
|
||||
"▲ Complexity",
|
||||
"Sort results from least complex to the most.",
|
||||
),
|
||||
(
|
||||
"-quality",
|
||||
"▼ Quality",
|
||||
"Sort results from highest quality rating to the lowest.",
|
||||
),
|
||||
(
|
||||
"quality",
|
||||
"▲ Quality",
|
||||
"Sort results from lowest quality rating to the highest.",
|
||||
),
|
||||
),
|
||||
default="default",
|
||||
update=search.search_update,
|
||||
)
|
||||
search_license: EnumProperty(
|
||||
name="License",
|
||||
items=(
|
||||
@@ -408,7 +473,7 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
)
|
||||
search_blender_version_max: StringProperty(
|
||||
name="Maximum version (excluding, lower than)",
|
||||
default="4.99",
|
||||
default="5.99",
|
||||
description="Limit the assets by maximum version of Blender in which they were created, exluding the specified version and all newer versions from the search results. "
|
||||
+ "Only assets created in LOWER THAN (< max) maximum version will be shown. Use semantic versioning format: X.Y.Z.\n\n"
|
||||
+ "E.g.: exclude all Blender 4 assets by specifying 4, 4.0, or 4.0.0. Assets created in 3.6 and lower will be shown",
|
||||
@@ -1109,6 +1174,19 @@ class BlenderKitGeoToolSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
|
||||
pass
|
||||
|
||||
|
||||
class BlenderKitAddonSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
|
||||
search_installed: BoolProperty(
|
||||
name="Installed Only",
|
||||
description="Show only addons that are already installed in Blender",
|
||||
default=False,
|
||||
update=lambda self, context: (
|
||||
search.refresh_search()
|
||||
if context.window_manager.blenderkitUI.asset_type == "ADDON"
|
||||
else None
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class BlenderKitHDRUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
texture_resolution_max: IntProperty(
|
||||
name="Texture Resolution Max",
|
||||
@@ -1927,7 +2005,11 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
|
||||
api_key: StringProperty(
|
||||
name="BlenderKit API Key",
|
||||
description="Your blenderkit API Key. Get it from your page on the website",
|
||||
description=(
|
||||
"Your unique API key authenticates downloads and requests inside the add-on. "
|
||||
"No manual setup is required, the API Key is auto-filled at login and cleared at logout. "
|
||||
"However, you can also paste the key from your profile settings on the BlenderKit website."
|
||||
),
|
||||
default="",
|
||||
subtype="PASSWORD",
|
||||
update=utils.api_key_property_updated,
|
||||
@@ -1980,6 +2062,13 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
sidebar_panels: BoolProperty(
|
||||
name="Hide sidebar panels",
|
||||
description="Hide BlenderKit sidebar panels (search, upload, and selected model functionality). This prevents upload and it's also the only place for import settings. Reenable this to access these features.",
|
||||
default=False,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
header_menu_fold: BoolProperty(
|
||||
name="Header menu fold", default=False, update=ui_panels.update_header_menu_fold
|
||||
)
|
||||
@@ -2209,15 +2298,21 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
max_assetbar_rows: IntProperty(
|
||||
name="Max Assetbar Rows",
|
||||
description="max rows of assetbar in the 3D view",
|
||||
default=1,
|
||||
min=1,
|
||||
maximized_assetbar_rows: IntProperty(
|
||||
name="Maximized Assetbar Rows",
|
||||
description="Maximum rows of assetbar in the 3D view when expanded",
|
||||
default=4,
|
||||
min=2,
|
||||
max=20,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
assetbar_expanded: BoolProperty(
|
||||
name="Assetbar Expanded",
|
||||
description="Whether the assetbar is currently expanded to show maximum rows",
|
||||
default=False,
|
||||
)
|
||||
|
||||
thumb_size: IntProperty(
|
||||
name="Assetbar Thumbnail Size",
|
||||
default=96,
|
||||
@@ -2329,6 +2424,12 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
update=search.search_update,
|
||||
) # In future we can subsets like sexualized, pornography or violence subset. And allow users choose what is part of NSFW.
|
||||
|
||||
temp_enabled_addons: StringProperty(
|
||||
name="Temporarily Enabled Addons",
|
||||
description="JSON string of temporarily enabled addon package IDs that should be disabled on next session",
|
||||
default="[]",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if self.api_key.strip() == "":
|
||||
@@ -2362,9 +2463,10 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
gui_settings.label(text="GUI settings")
|
||||
gui_settings.prop(self, "show_on_start")
|
||||
gui_settings.prop(self, "thumb_size")
|
||||
gui_settings.prop(self, "max_assetbar_rows")
|
||||
gui_settings.prop(self, "maximized_assetbar_rows")
|
||||
gui_settings.prop(self, "search_field_width")
|
||||
gui_settings.prop(self, "search_in_header")
|
||||
gui_settings.prop(self, "sidebar_panels")
|
||||
gui_settings.prop(self, "show_VIEW3D_MT_blenderkit_model_properties")
|
||||
gui_settings.prop(self, "tips_on_start")
|
||||
gui_settings.prop(self, "announcements_on_start")
|
||||
@@ -2434,6 +2536,7 @@ classes = (
|
||||
BlenderKitBrushUploadProps,
|
||||
BlenderKitGeoToolSearchProps,
|
||||
BlenderKitNodeGroulUploadProps,
|
||||
BlenderKitAddonSearchProps,
|
||||
)
|
||||
|
||||
|
||||
@@ -2500,6 +2603,9 @@ def register():
|
||||
bpy.types.NodeTree.blenderkit = PointerProperty( # for uploads, not now...
|
||||
type=BlenderKitNodeGroulUploadProps
|
||||
)
|
||||
bpy.types.WindowManager.blenderkit_addon = PointerProperty(
|
||||
type=BlenderKitAddonSearchProps
|
||||
)
|
||||
if bpy.app.factory_startup is False:
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
global_vars.PREFS = utils.get_preferences_as_dict()
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
include:
|
||||
- "**/*.py"
|
||||
|
||||
exclude_dirs:
|
||||
- "tests"
|
||||
- ".venv"
|
||||
- "__pycache__"
|
||||
|
||||
skips:
|
||||
- "B404" # https://bandit.readthedocs.io/en/1.7.10/blacklists/blacklist_imports.html#b404-import-subprocess
|
||||
- "B603" # https://bandit.readthedocs.io/en/1.7.10/plugins/b603_subprocess_without_shell_equals_true.html
|
||||
- "B608" # https://bandit.readthedocs.io/en/1.7.10/plugins/b608_hardcoded_sql_expressions.html
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import bpy
|
||||
|
||||
@@ -55,11 +56,34 @@ def append_brush(file_name, brushname=None, link=False, fake_user=True):
|
||||
|
||||
|
||||
def append_nodegroup(
|
||||
file_name, nodegroupname=None, link=False, fake_user=True, node_x=0, node_y=0
|
||||
file_name,
|
||||
nodegroupname=None,
|
||||
link=False,
|
||||
fake_user=True,
|
||||
node_x=0,
|
||||
node_y=0,
|
||||
target_object=None,
|
||||
nodegroup_mode="",
|
||||
model_location=(0, 0, 0),
|
||||
model_rotation=(0, 0, 0),
|
||||
**kwargs,
|
||||
):
|
||||
"""Append selected node group. If nodegroupname is None, first node group is appended.
|
||||
If node group with the same name is already in the scene, it is not appended again.
|
||||
Try to look for a suitable node editor and insert the node group there, in the middle of the area.
|
||||
Try to look for a suitable node editor and insert the node group there, or create/use modifier based on mode.
|
||||
For geometry nodegroups, if no target object is provided, a target object will be created automatically.
|
||||
|
||||
Args:
|
||||
file_name: Path to the .blend file containing the nodegroup
|
||||
nodegroupname: Name of the nodegroup to append
|
||||
link: Whether to link or append
|
||||
fake_user: Whether to set fake user
|
||||
node_x: X position for node placement in editor
|
||||
node_y: Y position for node placement in editor
|
||||
target_object: Target object for modifier mode (name string). If None and nodegroup is geometry type, a target object will be created
|
||||
nodegroup_mode: How to add the nodegroup - "MODIFIER" for new modifier, "NODE" for node in editor, "" for default behavior
|
||||
model_location: Location for the target object (used when creating new target)
|
||||
model_rotation: Rotation for the target object (used when creating new target)
|
||||
|
||||
Returns:
|
||||
tuple: (nodegroup, added_to_editor) - The nodegroup and whether it was added to an editor
|
||||
@@ -76,6 +100,22 @@ def append_nodegroup(
|
||||
nodegroup = bpy.data.node_groups[nodegroupname]
|
||||
nodegroup.use_fake_user = fake_user
|
||||
|
||||
# Create target object automatically for geometry nodegroups when no target is provided
|
||||
auto_created_target: Optional[bpy.types.Object] = None
|
||||
if nodegroup.bl_rna.identifier == "GeometryNodeTree" and not target_object:
|
||||
# Create a default mesh cube
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
size=2, location=model_location, rotation=model_rotation
|
||||
)
|
||||
target_obj = bpy.context.active_object
|
||||
target_obj.name = "GeometryNodeTarget"
|
||||
target_object = target_obj.name
|
||||
auto_created_target = target_obj
|
||||
|
||||
# Make sure it's selected and active
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
target_obj.select_set(True)
|
||||
|
||||
# Mapping dict for node editor tree types to node group node types
|
||||
sdict = {
|
||||
"GeometryNodeTree": "GeometryNodeGroup",
|
||||
@@ -86,25 +126,119 @@ def append_nodegroup(
|
||||
# Get the nodegroup type
|
||||
nodegroup_type = nodegroup.bl_rna.identifier
|
||||
|
||||
# Find a suitable node editor
|
||||
# If no explicit mode is set, try to detect if we should add to an existing editor first
|
||||
# This allows drag-drop into existing node editors to work properly
|
||||
if not nodegroup_mode:
|
||||
# Find a suitable node editor
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type != "NODE_EDITOR":
|
||||
continue
|
||||
|
||||
if area.spaces.active.tree_type == nodegroup_type:
|
||||
nt = area.spaces.active.edit_tree
|
||||
if nt is None:
|
||||
continue
|
||||
|
||||
# Add node to this editor
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
|
||||
node_type = sdict.get(nodegroup_type)
|
||||
if node_type:
|
||||
node = nt.nodes.new(node_type)
|
||||
node.node_tree = nodegroup
|
||||
node.location = (node_x, node_y)
|
||||
node.select = True
|
||||
nt.nodes.active = node
|
||||
return (nodegroup, True)
|
||||
|
||||
# Handle modifier mode for geometry nodegroups
|
||||
if nodegroup_mode == "MODIFIER" and target_object:
|
||||
target_obj = bpy.data.objects.get(target_object)
|
||||
if target_obj and nodegroup.bl_rna.identifier == "GeometryNodeTree":
|
||||
# Create a new geometry nodes modifier with this nodegroup
|
||||
gn_mod = target_obj.modifiers.new(name=nodegroup.name, type="NODES")
|
||||
gn_mod.node_group = nodegroup
|
||||
|
||||
# Select the target object to make the change visible
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
if target_obj not in bpy.context.selected_objects:
|
||||
target_obj.select_set(True)
|
||||
|
||||
return (
|
||||
nodegroup,
|
||||
True,
|
||||
) # Return True as we "added" it successfully to the modifier
|
||||
|
||||
# Handle node mode for geometry nodegroups with target object
|
||||
# Create a modifier setup and then add the nodegroup as a node to the tree
|
||||
if (
|
||||
nodegroup_mode == "NODE"
|
||||
and target_object
|
||||
and nodegroup.bl_rna.identifier == "GeometryNodeTree"
|
||||
):
|
||||
target_obj = bpy.data.objects.get(target_object)
|
||||
if target_obj:
|
||||
# Select the target object to make it active
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
if target_obj not in bpy.context.selected_objects:
|
||||
target_obj.select_set(True)
|
||||
# look for the geometry nodes modifier
|
||||
gn_mod = None
|
||||
for mod in target_obj.modifiers:
|
||||
if mod.type == "NODES" and mod.node_group:
|
||||
gn_mod = mod
|
||||
break
|
||||
if not gn_mod:
|
||||
# create a new geometry nodes modifier
|
||||
gn_mod = target_obj.modifiers.new(name="GeometryNodes", type="NODES")
|
||||
if not gn_mod.node_group:
|
||||
# create a new node group
|
||||
bpy.ops.node.new_geometry_node_group_assign()
|
||||
|
||||
node_tree = gn_mod.node_group
|
||||
|
||||
if node_tree:
|
||||
# Add the nodegroup as a node to the tree
|
||||
group_node = node_tree.nodes.new("GeometryNodeGroup")
|
||||
group_node.node_tree = nodegroup
|
||||
group_node.location = (node_x, node_y)
|
||||
group_node.select = True
|
||||
node_tree.nodes.active = group_node
|
||||
|
||||
return (nodegroup, True)
|
||||
|
||||
# If not added yet through modes or if no mode specified, try to find any compatible editor
|
||||
added_to_editor = False
|
||||
|
||||
# First try: exact match for tree type
|
||||
# Try any compatible editor
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type != "NODE_EDITOR":
|
||||
continue
|
||||
|
||||
if area.spaces.active.tree_type == nodegroup_type:
|
||||
nt = area.spaces.active.edit_tree
|
||||
if nt is None:
|
||||
continue
|
||||
nt = area.spaces.active.edit_tree
|
||||
if nt is None:
|
||||
continue
|
||||
|
||||
# Check if this editor type is compatible
|
||||
if area.spaces.active.tree_type in sdict:
|
||||
# Add node to this editor
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
|
||||
node_type = sdict.get(nodegroup_type)
|
||||
node_type = sdict.get(area.spaces.active.tree_type)
|
||||
if node_type:
|
||||
# Check if nodegroup is compatible with this editor
|
||||
# For example, don't add shader nodegroups to geometry node editor
|
||||
if (
|
||||
nodegroup_type == "ShaderNodeTree"
|
||||
and area.spaces.active.tree_type != "ShaderNodeTree"
|
||||
) or (
|
||||
nodegroup_type == "GeometryNodeTree"
|
||||
and area.spaces.active.tree_type != "GeometryNodeTree"
|
||||
):
|
||||
continue
|
||||
|
||||
node = nt.nodes.new(node_type)
|
||||
node.node_tree = nodegroup
|
||||
node.location = (node_x, node_y)
|
||||
@@ -113,42 +247,20 @@ def append_nodegroup(
|
||||
added_to_editor = True
|
||||
break
|
||||
|
||||
# If not added yet, try any compatible editor
|
||||
if not added_to_editor:
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type != "NODE_EDITOR":
|
||||
continue
|
||||
|
||||
nt = area.spaces.active.edit_tree
|
||||
if nt is None:
|
||||
continue
|
||||
|
||||
# Check if this editor type is compatible
|
||||
if area.spaces.active.tree_type in sdict:
|
||||
# Add node to this editor
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
|
||||
node_type = sdict.get(area.spaces.active.tree_type)
|
||||
if node_type:
|
||||
# Check if nodegroup is compatible with this editor
|
||||
# For example, don't add shader nodegroups to geometry node editor
|
||||
if (
|
||||
nodegroup_type == "ShaderNodeTree"
|
||||
and area.spaces.active.tree_type != "ShaderNodeTree"
|
||||
) or (
|
||||
nodegroup_type == "GeometryNodeTree"
|
||||
and area.spaces.active.tree_type != "GeometryNodeTree"
|
||||
):
|
||||
continue
|
||||
|
||||
node = nt.nodes.new(node_type)
|
||||
node.node_tree = nodegroup
|
||||
node.location = (node_x, node_y)
|
||||
node.select = True
|
||||
nt.nodes.active = node
|
||||
added_to_editor = True
|
||||
break
|
||||
# Ensure automatically created targets receive the nodegroup as modifier
|
||||
if auto_created_target:
|
||||
gn_mod = None
|
||||
for mod in auto_created_target.modifiers:
|
||||
if mod.type == "NODES":
|
||||
gn_mod = mod
|
||||
break
|
||||
if not gn_mod:
|
||||
gn_mod = auto_created_target.modifiers.new(
|
||||
name=nodegroup.name, type="NODES"
|
||||
)
|
||||
gn_mod.node_group = nodegroup
|
||||
auto_created_target.select_set(True)
|
||||
bpy.context.view_layer.objects.active = auto_created_target
|
||||
|
||||
return nodegroup, added_to_editor
|
||||
|
||||
@@ -246,15 +358,18 @@ def hdr_swap(name, hdr):
|
||||
:return: None
|
||||
"""
|
||||
w = bpy.context.scene.world
|
||||
if w:
|
||||
if not w:
|
||||
new_hdr_world(name, hdr)
|
||||
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
w.use_nodes = True
|
||||
w.name = name
|
||||
nt = w.node_tree
|
||||
for n in nt.nodes:
|
||||
if "ShaderNodeTexEnvironment" == n.bl_rna.identifier:
|
||||
env_node = n
|
||||
env_node.image = hdr
|
||||
return
|
||||
w.name = name
|
||||
nt = w.node_tree
|
||||
for n in nt.nodes:
|
||||
if "ShaderNodeTexEnvironment" == n.bl_rna.identifier:
|
||||
env_node = n
|
||||
env_node.image = hdr
|
||||
return
|
||||
new_hdr_world(name, hdr)
|
||||
|
||||
|
||||
@@ -266,7 +381,8 @@ def new_hdr_world(name, hdr):
|
||||
:return: None
|
||||
"""
|
||||
w = bpy.data.worlds.new(name=name)
|
||||
w.use_nodes = True
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
w.use_nodes = True
|
||||
bpy.context.scene.world = w
|
||||
|
||||
nt = w.node_tree
|
||||
@@ -302,11 +418,11 @@ def load_HDR(file_name, name):
|
||||
|
||||
def link_collection(
|
||||
file_name,
|
||||
obnames=None,
|
||||
obnames: Optional[list] = None,
|
||||
location=(0, 0, 0),
|
||||
link=False,
|
||||
parent=None,
|
||||
collection="",
|
||||
link: bool = False,
|
||||
parent: Optional[str] = None,
|
||||
collection: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
"""link an instanced group - model type asset"""
|
||||
@@ -314,7 +430,7 @@ def link_collection(
|
||||
obnames = []
|
||||
sel = utils.selection_get()
|
||||
# Store the original active collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection # type: ignore[union-attr]
|
||||
|
||||
# Activate target collection if specified
|
||||
if collection:
|
||||
@@ -322,10 +438,10 @@ def link_collection(
|
||||
if target_collection:
|
||||
# Find and activate the layer collection
|
||||
layer_collection = find_layer_collection(
|
||||
bpy.context.view_layer.layer_collection, collection
|
||||
bpy.context.view_layer.layer_collection, collection # type: ignore[union-attr]
|
||||
)
|
||||
if layer_collection:
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection # type: ignore[union-attr]
|
||||
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
data_from,
|
||||
@@ -340,33 +456,33 @@ def link_collection(
|
||||
rotation = kwargs["rotation"]
|
||||
|
||||
bpy.ops.object.empty_add(type="PLAIN_AXES", location=location, rotation=rotation)
|
||||
main_object = bpy.context.view_layer.objects.active
|
||||
main_object.instance_type = "COLLECTION"
|
||||
main_object = bpy.context.view_layer.objects.active # type: ignore[union-attr]
|
||||
main_object.instance_type = "COLLECTION" # type: ignore[union-attr]
|
||||
|
||||
if parent is not None:
|
||||
main_object.parent = bpy.data.objects.get(parent)
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects.get(parent) # type: ignore[union-attr]
|
||||
|
||||
main_object.matrix_world.translation = location
|
||||
main_object.matrix_world.translation = location # type: ignore[union-attr]
|
||||
|
||||
for col in bpy.data.collections:
|
||||
if col.library is not None:
|
||||
fp = bpy.path.abspath(col.library.filepath)
|
||||
fp1 = bpy.path.abspath(file_name)
|
||||
if fp == fp1:
|
||||
main_object.instance_collection = col
|
||||
main_object.instance_collection = col # type: ignore[union-attr]
|
||||
break
|
||||
|
||||
# sometimes, the lib might already be without the actual link.
|
||||
if not main_object.instance_collection and kwargs["name"]:
|
||||
if not main_object.instance_collection and kwargs["name"]: # type: ignore[union-attr]
|
||||
col = bpy.data.collections.get(kwargs["name"])
|
||||
if col:
|
||||
main_object.instance_collection = col
|
||||
main_object.instance_collection = col # type: ignore[union-attr]
|
||||
|
||||
main_object.name = main_object.instance_collection.name
|
||||
main_object.name = main_object.instance_collection.name # type: ignore[union-attr]
|
||||
|
||||
# Restore original active collection
|
||||
if orig_active_collection:
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection # type: ignore[union-attr]
|
||||
|
||||
utils.selection_set(sel)
|
||||
return main_object, []
|
||||
@@ -449,7 +565,13 @@ def append_particle_system(
|
||||
|
||||
|
||||
def append_objects(
|
||||
file_name, obnames=None, location=(0, 0, 0), link=False, collection="", **kwargs
|
||||
file_name,
|
||||
obnames: Optional[list] = None,
|
||||
location=(0, 0, 0),
|
||||
link: bool = False,
|
||||
parent: Optional[str] = None,
|
||||
collection: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
"""Append object into scene individually. 2 approaches based in definition of name argument.
|
||||
TODO: really split this function into 2 functions: kwargs.get('name')==None and else.
|
||||
@@ -461,7 +583,7 @@ def append_objects(
|
||||
scene = bpy.context.scene
|
||||
sel = utils.selection_get()
|
||||
# Store the original active collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection # type: ignore[union-attr]
|
||||
|
||||
# Activate target collection if specified
|
||||
if collection:
|
||||
@@ -469,10 +591,10 @@ def append_objects(
|
||||
if target_collection:
|
||||
# Find and activate the layer collection
|
||||
layer_collection = find_layer_collection(
|
||||
bpy.context.view_layer.layer_collection, collection
|
||||
bpy.context.view_layer.layer_collection, collection # type: ignore[union-attr]
|
||||
)
|
||||
if layer_collection:
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection # type: ignore[union-attr]
|
||||
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
@@ -486,6 +608,9 @@ def append_objects(
|
||||
|
||||
path = file_name + "/Collection"
|
||||
collection_name = kwargs.get("name")
|
||||
if collection_name is None:
|
||||
bk_logger.warning("collection_name is None")
|
||||
collection_name = ""
|
||||
bpy.ops.wm.append(filename=collection_name, directory=path)
|
||||
|
||||
# fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
|
||||
@@ -496,13 +621,13 @@ def append_objects(
|
||||
appended_collection = None
|
||||
main_object = None
|
||||
# get first at least one parent for sure
|
||||
for ob in bpy.context.scene.objects:
|
||||
for ob in bpy.context.scene.objects: # type: ignore[union-attr]
|
||||
if ob.select_get():
|
||||
if not ob.parent:
|
||||
main_object = ob
|
||||
ob.location = location
|
||||
# do once again to ensure hidden objects are hidden
|
||||
for ob in bpy.context.scene.objects:
|
||||
for ob in bpy.context.scene.objects: # type: ignore[union-attr]
|
||||
if ob.select_get():
|
||||
return_obs.append(ob)
|
||||
# check for object that should be hidden
|
||||
@@ -521,14 +646,14 @@ def append_objects(
|
||||
if kwargs.get("rotation"):
|
||||
main_object.rotation_euler = kwargs["rotation"]
|
||||
|
||||
if kwargs.get("parent") is not None:
|
||||
main_object.parent = bpy.data.objects[kwargs["parent"]]
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects[parent]
|
||||
main_object.matrix_world.translation = location
|
||||
|
||||
# move objects that should be hidden to a sub collection
|
||||
if len(to_hidden_collection) > 0 and appended_collection is not None:
|
||||
hidden_collections = []
|
||||
scene_collection = bpy.context.scene.collection
|
||||
scene_collection = bpy.context.scene.collection # type: ignore[union-attr]
|
||||
for ob in to_hidden_collection:
|
||||
hide_collection = ob.users_collection[0]
|
||||
|
||||
@@ -577,7 +702,7 @@ def append_objects(
|
||||
|
||||
# Restore original active collection
|
||||
if orig_active_collection:
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection # type: ignore[union-attr]
|
||||
|
||||
utils.selection_set(sel)
|
||||
# let collection also store info that it was created by BlenderKit, for purging reasons
|
||||
@@ -618,7 +743,7 @@ def append_objects(
|
||||
for obj in data_to.objects:
|
||||
if obj is not None:
|
||||
# if obj.name not in scene.objects:
|
||||
scene.collection.objects.link(obj)
|
||||
scene.collection.objects.link(obj) # type: ignore[union-attr]
|
||||
if obj.parent is None:
|
||||
obj.location = location
|
||||
main_object = obj
|
||||
@@ -637,11 +762,11 @@ def append_objects(
|
||||
ob.hide_viewport = True
|
||||
|
||||
if kwargs.get("rotation") is not None:
|
||||
main_object.rotation_euler = kwargs["rotation"]
|
||||
main_object.rotation_euler = kwargs["rotation"] # type: ignore[union-attr]
|
||||
|
||||
if kwargs.get("parent") is not None:
|
||||
main_object.parent = bpy.data.objects[kwargs["parent"]]
|
||||
main_object.matrix_world.translation = location
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects[parent] # type: ignore[union-attr]
|
||||
main_object.matrix_world.translation = location # type: ignore[union-attr]
|
||||
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -82,7 +82,7 @@ def get_texture_ui(tpath, iname):
|
||||
|
||||
|
||||
def check_thumbnail(props, imgpath):
|
||||
# TODO implement check if the file exists, if size is corect etc. needs some care
|
||||
# TODO implement check if the file exists, if size is correct etc. needs some care
|
||||
if imgpath == "":
|
||||
props.has_thumbnail = False
|
||||
return None
|
||||
|
||||
@@ -104,7 +104,7 @@ if __name__ == "__main__":
|
||||
asset_data["files"][0]["file_name"] = file_name
|
||||
if not has_url:
|
||||
bg_blender.progress(
|
||||
"couldn't download asset for thumnbail re-rendering"
|
||||
"couldn't download asset for thumbnail re-rendering"
|
||||
)
|
||||
exit()
|
||||
# download first, or rather make sure if it's already downloaded
|
||||
|
||||
@@ -163,7 +163,7 @@ if __name__ == "__main__":
|
||||
asset_data["files"][0]["file_name"] = file_name
|
||||
if has_url is not True:
|
||||
bg_blender.progress(
|
||||
"couldn't download asset for thumnbail re-rendering"
|
||||
"couldn't download asset for thumbnail re-rendering"
|
||||
)
|
||||
bg_blender.progress("downloading asset")
|
||||
fpath = bg_utils.download_asset_file(
|
||||
|
||||
@@ -31,6 +31,8 @@ from bpy.props import BoolProperty
|
||||
|
||||
from . import client_lib, client_tasks, datas, global_vars, reports, tasks_queue, utils
|
||||
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
from . import override_extension_draw
|
||||
|
||||
CLIENT_ID = "IdFRwa3SGA8eMpzhRVFMg5Ts8sPK93xBjif93x0F"
|
||||
REFRESH_RESERVE = 60 * 60 * 24 * 3 # 3 days
|
||||
@@ -103,10 +105,9 @@ def clean_login_data():
|
||||
preferences.api_key_timeout = 0
|
||||
global_vars.BKIT_PROFILE = datas.MineProfile()
|
||||
# Cleanup also the api key in the extensions repository setting and clean the cache
|
||||
from . import override_extension_draw
|
||||
|
||||
override_extension_draw.ensure_repository(api_key="")
|
||||
override_extension_draw.clear_repo_cache()
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
override_extension_draw.ensure_repository(api_key="")
|
||||
override_extension_draw.clear_repo_cache()
|
||||
|
||||
|
||||
def logout() -> None:
|
||||
@@ -158,8 +159,6 @@ def write_tokens(auth_token, refresh_token, oauth_response):
|
||||
preferences.api_key = auth_token # triggers api_key update function
|
||||
# write token also to extensions repository setting and clear the cache
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
from . import override_extension_draw
|
||||
|
||||
override_extension_draw.ensure_repository(api_key=auth_token)
|
||||
override_extension_draw.clear_repo_cache()
|
||||
|
||||
|
||||
+144
-58
@@ -20,7 +20,7 @@
|
||||
bl_info = {
|
||||
"name": "BlenderKit Online Asset Library",
|
||||
"author": "Vilem Duha, Petr Dlouhy, A. Gajdosik",
|
||||
"version": (3, 15, 1, 250403), # X.Y.Z.yymmdd
|
||||
"version": (3, 17, 0, 251008), # X.Y.Z.yymmdd
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D > Properties > BlenderKit",
|
||||
"description": "Boost your workflow with drag&drop assets from the community driven library.",
|
||||
@@ -28,9 +28,10 @@ bl_info = {
|
||||
"tracker_url": "https://github.com/BlenderKit/blenderkit/issues",
|
||||
"category": "3D View",
|
||||
}
|
||||
VERSION = (3, 15, 1, 250403)
|
||||
VERSION = (3, 17, 0, 251008)
|
||||
|
||||
import logging
|
||||
import random
|
||||
import sys
|
||||
from importlib import reload
|
||||
from os import path
|
||||
@@ -279,52 +280,39 @@ def asset_type_callback(self, context):
|
||||
items for Enum property, depending on the down_up property - BlenderKit is either in search or in upload mode.
|
||||
"""
|
||||
pcoll = icons.icon_collections["main"]
|
||||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
if self.down_up == "SEARCH":
|
||||
items = [
|
||||
("MODEL", "Models", "Find models", "OBJECT_DATAMODE", 0),
|
||||
("MATERIAL", "Materials", "Find materials", "MATERIAL", 2),
|
||||
("SCENE", "Scenes", "Find scenes", "SCENE_DATA", 3),
|
||||
("HDR", "HDRs", "Find HDRs", "WORLD", 4),
|
||||
("BRUSH", "Brushes", "Find brushes", "BRUSH_DATA", 5),
|
||||
("NODEGROUP", "Node Groups", "Find tools", "NODETREE", 6),
|
||||
("MATERIAL", "Materials", "Find materials", "MATERIAL", 1),
|
||||
("SCENE", "Scenes", "Find scenes", "SCENE_DATA", 2),
|
||||
("HDR", "HDRs", "Find HDRs", "WORLD", 3),
|
||||
("BRUSH", "Brushes", "Find brushes", "BRUSH_DATA", 4),
|
||||
("NODEGROUP", "Node Groups", "Find tools", "NODETREE", 5),
|
||||
(
|
||||
"PRINTABLE",
|
||||
"Printables",
|
||||
"Find 3D printable models",
|
||||
pcoll["asset_type_printable"].icon_id,
|
||||
6,
|
||||
),
|
||||
]
|
||||
# Add printable under experimental features
|
||||
if preferences.experimental_features:
|
||||
# Insert printable after MODEL (at index 1)
|
||||
items.insert(
|
||||
1,
|
||||
(
|
||||
"PRINTABLE",
|
||||
"Printable",
|
||||
"Find 3D printable models",
|
||||
pcoll["asset_type_printable"].icon_id,
|
||||
1,
|
||||
),
|
||||
)
|
||||
else:
|
||||
items = [
|
||||
("MODEL", "Model", "Upload a model", "OBJECT_DATAMODE", 0),
|
||||
("MATERIAL", "Material", "Upload a material", "MATERIAL", 2),
|
||||
("SCENE", "Scene", "Upload a scene", "SCENE_DATA", 3),
|
||||
("HDR", "HDR", "Upload a HDR", "WORLD", 4),
|
||||
("BRUSH", "Brush", "Upload a brush", "BRUSH_DATA", 5),
|
||||
("NODEGROUP", "Node Groups", "Upload a tool", "NODETREE", 6),
|
||||
("MATERIAL", "Material", "Upload a material", "MATERIAL", 1),
|
||||
("SCENE", "Scene", "Upload a scene", "SCENE_DATA", 2),
|
||||
("HDR", "HDR", "Upload a HDR", "WORLD", 3),
|
||||
("BRUSH", "Brush", "Upload a brush", "BRUSH_DATA", 4),
|
||||
("NODEGROUP", "Node Groups", "Upload a tool", "NODETREE", 5),
|
||||
(
|
||||
"PRINTABLE",
|
||||
"Printable",
|
||||
"Upload a 3D printable model",
|
||||
pcoll["asset_type_printable"].icon_id,
|
||||
6,
|
||||
),
|
||||
]
|
||||
# Add printable under experimental features
|
||||
if preferences.experimental_features:
|
||||
# Insert printable after MODEL (at index 1)
|
||||
items.insert(
|
||||
1,
|
||||
(
|
||||
"PRINTABLE",
|
||||
"Printable",
|
||||
"Upload a 3D printable model",
|
||||
pcoll["asset_type_printable"].icon_id,
|
||||
1,
|
||||
),
|
||||
)
|
||||
|
||||
return items
|
||||
|
||||
@@ -392,6 +380,61 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
max=10,
|
||||
update=search.search_update_delayed,
|
||||
)
|
||||
search_order_by: EnumProperty(
|
||||
name="Order",
|
||||
description="Search result order",
|
||||
items=(
|
||||
(
|
||||
"default",
|
||||
"Default",
|
||||
"By default, the sorting algorithm changes dynamically based on search filters.",
|
||||
),
|
||||
("-created", "Newest", "Sort results from newest to oldest."),
|
||||
("created", "Oldest", "Sort results from oldest to newest."),
|
||||
(
|
||||
"-bookmarks",
|
||||
"▼ Bookmarks",
|
||||
"Sort results from most bookmarked to least.",
|
||||
),
|
||||
(
|
||||
"bookmarks",
|
||||
"▲ Bookmarks",
|
||||
"Sort results from least bookmarked to most.",
|
||||
),
|
||||
(
|
||||
"-score",
|
||||
"▼ Score",
|
||||
"Sort results from highest asset score to the lowest.",
|
||||
),
|
||||
(
|
||||
"score",
|
||||
"▲ Score",
|
||||
"Sort results from lowest asset score to the highest.",
|
||||
),
|
||||
(
|
||||
"-working_hours",
|
||||
"▼ Complexity",
|
||||
"Sort results from most complex to the least.",
|
||||
),
|
||||
(
|
||||
"working_hours",
|
||||
"▲ Complexity",
|
||||
"Sort results from least complex to the most.",
|
||||
),
|
||||
(
|
||||
"-quality",
|
||||
"▼ Quality",
|
||||
"Sort results from highest quality rating to the lowest.",
|
||||
),
|
||||
(
|
||||
"quality",
|
||||
"▲ Quality",
|
||||
"Sort results from lowest quality rating to the highest.",
|
||||
),
|
||||
),
|
||||
default="default",
|
||||
update=search.search_update,
|
||||
)
|
||||
search_license: EnumProperty(
|
||||
name="License",
|
||||
items=(
|
||||
@@ -420,7 +463,7 @@ class BlenderKitUIProps(PropertyGroup):
|
||||
)
|
||||
search_blender_version_max: StringProperty(
|
||||
name="Maximum version (excluding, lower than)",
|
||||
default="4.99",
|
||||
default="5.99",
|
||||
description="Limit the assets by maximum version of Blender in which they were created, exluding the specified version and all newer versions from the search results. "
|
||||
+ "Only assets created in LOWER THAN (< max) maximum version will be shown. Use semantic versioning format: X.Y.Z.\n\n"
|
||||
+ "E.g.: exclude all Blender 4 assets by specifying 4, 4.0, or 4.0.0. Assets created in 3.6 and lower will be shown",
|
||||
@@ -938,12 +981,6 @@ class BlenderKitMaterialSearchProps(PropertyGroup, BlenderKitCommonSearchProps):
|
||||
"aren't editable directly, but also don't increase your file size",
|
||||
default="APPEND",
|
||||
)
|
||||
automap: BoolProperty(
|
||||
name="Auto-Map",
|
||||
description="reset object texture space and also add automatically a cube mapped UV "
|
||||
"to the object. \n this allows most materials to apply instantly to any mesh",
|
||||
default=True,
|
||||
)
|
||||
|
||||
|
||||
class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
@@ -1031,7 +1068,7 @@ class BlenderKitMaterialUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
thumbnail_background_lightness: FloatProperty(
|
||||
name="Thumbnail Background Lightness",
|
||||
description="Set to make your material stand out with enough contrast",
|
||||
default=0.9,
|
||||
default=0.7,
|
||||
min=0.00001,
|
||||
max=1,
|
||||
)
|
||||
@@ -1271,15 +1308,23 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
thumbnail_background_lightness: FloatProperty(
|
||||
name="Thumbnail Background Lightness",
|
||||
description="Set to make your Model stand out",
|
||||
default=1.0,
|
||||
default=0.7,
|
||||
min=0.01,
|
||||
max=10,
|
||||
)
|
||||
|
||||
# for printable models
|
||||
thumbnail_material_color: FloatVectorProperty(
|
||||
name="Thumbnail Material Color",
|
||||
description="Color of the material for printable models",
|
||||
default=(random.random(), random.random(), random.random()),
|
||||
subtype="COLOR",
|
||||
)
|
||||
|
||||
thumbnail_angle: EnumProperty(
|
||||
name="Thumbnail Angle",
|
||||
items=autothumb.thumbnail_angles,
|
||||
default="DEFAULT",
|
||||
default="ANGLE_1",
|
||||
description="Thumbnailer angle",
|
||||
)
|
||||
|
||||
@@ -1455,6 +1500,19 @@ class BlenderKitModelUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
default=False,
|
||||
)
|
||||
|
||||
# Add this new property for printable assets
|
||||
photo_thumbnail: StringProperty(
|
||||
name="Photo Thumbnail",
|
||||
description="Photo of the 3D printed object (JPG or PNG, preferred size is 1024x1024 or higher)",
|
||||
subtype="FILE_PATH",
|
||||
default="",
|
||||
)
|
||||
photo_thumbnail_will_upload_on_website: BoolProperty(
|
||||
name="I will upload photo on website",
|
||||
description="True if the photo thumbnail will upload on the website\n please read upload tutorial for more information",
|
||||
default=False,
|
||||
)
|
||||
|
||||
|
||||
class BlenderKitSceneUploadProps(PropertyGroup, BlenderKitCommonUploadProps):
|
||||
style: EnumProperty(
|
||||
@@ -1924,7 +1982,11 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
|
||||
api_key: StringProperty(
|
||||
name="BlenderKit API Key",
|
||||
description="Your blenderkit API Key. Get it from your page on the website",
|
||||
description=(
|
||||
"Your unique API key authenticates downloads and requests inside the add-on. "
|
||||
"No manual setup is required, the API Key is auto-filled at login and cleared at logout. "
|
||||
"However, you can also paste the key from your profile settings on the BlenderKit website."
|
||||
),
|
||||
default="",
|
||||
subtype="PASSWORD",
|
||||
update=utils.api_key_property_updated,
|
||||
@@ -1977,6 +2039,13 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
sidebar_panels: BoolProperty(
|
||||
name="Hide sidebar panels",
|
||||
description="Hide BlenderKit sidebar panels (search, upload, and selected model functionality). This prevents upload and it's also the only place for import settings. Reenable this to access these features.",
|
||||
default=False,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
header_menu_fold: BoolProperty(
|
||||
name="Header menu fold", default=False, update=ui_panels.update_header_menu_fold
|
||||
)
|
||||
@@ -2056,6 +2125,16 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
default="2048",
|
||||
)
|
||||
|
||||
material_import_automap: BoolProperty(
|
||||
name="Auto-Map",
|
||||
description="Reset object texture space and also add automatically a cube mapped UV to the object.\n"
|
||||
"This allows most materials to apply instantly to any mesh",
|
||||
default=True,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
# NETWORKING
|
||||
|
||||
ip_version: EnumProperty(
|
||||
name="IP version",
|
||||
items=(
|
||||
@@ -2133,7 +2212,7 @@ class BlenderKitAddonPreferences(AddonPreferences):
|
||||
name="Custom proxy address",
|
||||
description="""Set custom HTTP proxy for HTTPS requests of add-on. This setting preceeds any system wide proxy settings. If left empty custom proxy will not be set.
|
||||
|
||||
If you use simple HTTP proxy, set in format http://ip:port, or http://username:password@ip:port if your HTTP proxy requires authentication. You have to specify the address with http:// prefix.
|
||||
If you use simple HTTP proxy, set in format http://ip:port, or http://username:password@ip:port if your HTTP proxy requires authentication (make sure to escape special characters like #$%:^&*() etc. in username and password). You have to specify the address with http:// prefix.
|
||||
|
||||
HTTPS proxies are not supported! We wait for support in Python 3.11 and in aiohttp module. You can specify the HTTPS proxy with https:// prefix for hacking around and development purposes, but functionality cannot be guaranteed.
|
||||
In this case you should also set path to your system CA bundle containing proxy's certificates in the field "Custom CA certificates path" below""",
|
||||
@@ -2196,15 +2275,21 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
max_assetbar_rows: IntProperty(
|
||||
name="Max Assetbar Rows",
|
||||
description="max rows of assetbar in the 3D view",
|
||||
default=1,
|
||||
min=1,
|
||||
maximized_assetbar_rows: IntProperty(
|
||||
name="Maximized Assetbar Rows",
|
||||
description="Maximum rows of assetbar in the 3D view when expanded",
|
||||
default=4,
|
||||
min=2,
|
||||
max=20,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
|
||||
assetbar_expanded: BoolProperty(
|
||||
name="Assetbar Expanded",
|
||||
description="Whether the assetbar is currently expanded to show maximum rows",
|
||||
default=False,
|
||||
)
|
||||
|
||||
thumb_size: IntProperty(
|
||||
name="Assetbar Thumbnail Size",
|
||||
default=96,
|
||||
@@ -2225,7 +2310,7 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
|
||||
experimental_features: BoolProperty(
|
||||
name="Enable experimental features",
|
||||
description="""Enable experimental features of BlenderKit: \n - 3D printable assets (search and upload models optimized for 3D printing)""",
|
||||
description="Enable experimental features of BlenderKit. Note: There are no experimental features in this version.",
|
||||
default=False,
|
||||
update=utils.save_prefs,
|
||||
)
|
||||
@@ -2349,9 +2434,10 @@ In this case you should also set path to your system CA bundle containing proxy'
|
||||
gui_settings.label(text="GUI settings")
|
||||
gui_settings.prop(self, "show_on_start")
|
||||
gui_settings.prop(self, "thumb_size")
|
||||
gui_settings.prop(self, "max_assetbar_rows")
|
||||
gui_settings.prop(self, "maximized_assetbar_rows")
|
||||
gui_settings.prop(self, "search_field_width")
|
||||
gui_settings.prop(self, "search_in_header")
|
||||
gui_settings.prop(self, "sidebar_panels")
|
||||
gui_settings.prop(self, "show_VIEW3D_MT_blenderkit_model_properties")
|
||||
gui_settings.prop(self, "tips_on_start")
|
||||
gui_settings.prop(self, "announcements_on_start")
|
||||
|
||||
+323
-69
@@ -19,15 +19,27 @@
|
||||
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import bpy
|
||||
|
||||
from . import utils
|
||||
from . import utils, reports
|
||||
|
||||
|
||||
bk_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def find_layer_collection(layer_collection, collection_name):
|
||||
"""Helper function to find a layer_collection by name"""
|
||||
if layer_collection.collection.name == collection_name:
|
||||
return layer_collection
|
||||
for child in layer_collection.children:
|
||||
result = find_layer_collection(child, collection_name)
|
||||
if result:
|
||||
return result
|
||||
return None
|
||||
|
||||
|
||||
def append_brush(file_name, brushname=None, link=False, fake_user=True):
|
||||
"""append a brush"""
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
@@ -43,10 +55,38 @@ def append_brush(file_name, brushname=None, link=False, fake_user=True):
|
||||
return brush
|
||||
|
||||
|
||||
def append_nodegroup(file_name, nodegroupname=None, link=False, fake_user=True):
|
||||
def append_nodegroup(
|
||||
file_name,
|
||||
nodegroupname=None,
|
||||
link=False,
|
||||
fake_user=True,
|
||||
node_x=0,
|
||||
node_y=0,
|
||||
target_object=None,
|
||||
nodegroup_mode="",
|
||||
model_location=(0, 0, 0),
|
||||
model_rotation=(0, 0, 0),
|
||||
**kwargs,
|
||||
):
|
||||
"""Append selected node group. If nodegroupname is None, first node group is appended.
|
||||
If node group with the same name is already in the scene, it is not appended again.
|
||||
Try to look for a suitable node editor and insert the node group there, in the middle of the area.
|
||||
Try to look for a suitable node editor and insert the node group there, or create/use modifier based on mode.
|
||||
For geometry nodegroups, if no target object is provided, a target object will be created automatically.
|
||||
|
||||
Args:
|
||||
file_name: Path to the .blend file containing the nodegroup
|
||||
nodegroupname: Name of the nodegroup to append
|
||||
link: Whether to link or append
|
||||
fake_user: Whether to set fake user
|
||||
node_x: X position for node placement in editor
|
||||
node_y: Y position for node placement in editor
|
||||
target_object: Target object for modifier mode (name string). If None and nodegroup is geometry type, a target object will be created
|
||||
nodegroup_mode: How to add the nodegroup - "MODIFIER" for new modifier, "NODE" for node in editor, "" for default behavior
|
||||
model_location: Location for the target object (used when creating new target)
|
||||
model_rotation: Rotation for the target object (used when creating new target)
|
||||
|
||||
Returns:
|
||||
tuple: (nodegroup, added_to_editor) - The nodegroup and whether it was added to an editor
|
||||
"""
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
data_from,
|
||||
@@ -59,35 +99,153 @@ def append_nodegroup(file_name, nodegroupname=None, link=False, fake_user=True):
|
||||
nodegroupname = g
|
||||
nodegroup = bpy.data.node_groups[nodegroupname]
|
||||
nodegroup.use_fake_user = fake_user
|
||||
# if there's an open node editor, let's find if it matches the type of the asset group and insert it
|
||||
# in middle of the area.
|
||||
# mapping dict for editor type to node group node types
|
||||
|
||||
# Create target object automatically for geometry nodegroups when no target is provided
|
||||
if nodegroup.bl_rna.identifier == "GeometryNodeTree" and not target_object:
|
||||
# Create a default mesh cube
|
||||
bpy.ops.mesh.primitive_cube_add(
|
||||
size=2, location=model_location, rotation=model_rotation
|
||||
)
|
||||
target_obj = bpy.context.active_object
|
||||
target_obj.name = "GeometryNodeTarget"
|
||||
target_object = target_obj.name
|
||||
|
||||
# Make sure it's selected and active
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
target_obj.select_set(True)
|
||||
|
||||
# Mapping dict for node editor tree types to node group node types
|
||||
sdict = {
|
||||
"GeometryNodeTree": "GeometryNodeGroup",
|
||||
"ShaderNodeTree": "ShaderNodeGroup",
|
||||
"CompositorNodeTree": "CompositorNodeGroup",
|
||||
}
|
||||
# Look for a suitable node editor and insert the node group there, in the middle of the area.
|
||||
|
||||
# Get the nodegroup type
|
||||
nodegroup_type = nodegroup.bl_rna.identifier
|
||||
|
||||
# If no explicit mode is set, try to detect if we should add to an existing editor first
|
||||
# This allows drag-drop into existing node editors to work properly
|
||||
if not nodegroup_mode:
|
||||
# Find a suitable node editor
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type != "NODE_EDITOR":
|
||||
continue
|
||||
|
||||
if area.spaces.active.tree_type == nodegroup_type:
|
||||
nt = area.spaces.active.edit_tree
|
||||
if nt is None:
|
||||
continue
|
||||
|
||||
# Add node to this editor
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
|
||||
node_type = sdict.get(nodegroup_type)
|
||||
if node_type:
|
||||
node = nt.nodes.new(node_type)
|
||||
node.node_tree = nodegroup
|
||||
node.location = (node_x, node_y)
|
||||
node.select = True
|
||||
nt.nodes.active = node
|
||||
return (nodegroup, True)
|
||||
|
||||
# Handle modifier mode for geometry nodegroups
|
||||
if nodegroup_mode == "MODIFIER" and target_object:
|
||||
target_obj = bpy.data.objects.get(target_object)
|
||||
if target_obj and nodegroup.bl_rna.identifier == "GeometryNodeTree":
|
||||
# Create a new geometry nodes modifier with this nodegroup
|
||||
gn_mod = target_obj.modifiers.new(name=nodegroup.name, type="NODES")
|
||||
gn_mod.node_group = nodegroup
|
||||
|
||||
# Select the target object to make the change visible
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
if target_obj not in bpy.context.selected_objects:
|
||||
target_obj.select_set(True)
|
||||
|
||||
return (
|
||||
nodegroup,
|
||||
True,
|
||||
) # Return True as we "added" it successfully to the modifier
|
||||
|
||||
# Handle node mode for geometry nodegroups with target object
|
||||
# Create a modifier setup and then add the nodegroup as a node to the tree
|
||||
if (
|
||||
nodegroup_mode == "NODE"
|
||||
and target_object
|
||||
and nodegroup.bl_rna.identifier == "GeometryNodeTree"
|
||||
):
|
||||
target_obj = bpy.data.objects.get(target_object)
|
||||
if target_obj:
|
||||
# Select the target object to make it active
|
||||
bpy.context.view_layer.objects.active = target_obj
|
||||
if target_obj not in bpy.context.selected_objects:
|
||||
target_obj.select_set(True)
|
||||
# look for the geometry nodes modifier
|
||||
gn_mod = None
|
||||
for mod in target_obj.modifiers:
|
||||
if mod.type == "NODES" and mod.node_group:
|
||||
gn_mod = mod
|
||||
break
|
||||
if not gn_mod:
|
||||
# create a new geometry nodes modifier
|
||||
gn_mod = target_obj.modifiers.new(name="GeometryNodes", type="NODES")
|
||||
if not gn_mod.node_group:
|
||||
# create a new node group
|
||||
bpy.ops.node.new_geometry_node_group_assign()
|
||||
|
||||
node_tree = gn_mod.node_group
|
||||
|
||||
if node_tree:
|
||||
# Add the nodegroup as a node to the tree
|
||||
group_node = node_tree.nodes.new("GeometryNodeGroup")
|
||||
group_node.node_tree = nodegroup
|
||||
group_node.location = (node_x, node_y)
|
||||
group_node.select = True
|
||||
node_tree.nodes.active = group_node
|
||||
|
||||
return (nodegroup, True)
|
||||
|
||||
# If not added yet through modes or if no mode specified, try to find any compatible editor
|
||||
added_to_editor = False
|
||||
|
||||
# Try any compatible editor
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type != "NODE_EDITOR":
|
||||
continue
|
||||
|
||||
if area.spaces.active.tree_type != nodegroup.bl_rna.identifier:
|
||||
continue
|
||||
|
||||
nt = area.spaces.active.edit_tree
|
||||
|
||||
if nt is None:
|
||||
continue
|
||||
|
||||
# deselect all nodes
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
node = nt.nodes.new(sdict[area.spaces.active.tree_type])
|
||||
node.node_tree = nodegroup
|
||||
area.spaces.active.node_tree = nodegroup
|
||||
break
|
||||
return nodegroup
|
||||
# Check if this editor type is compatible
|
||||
if area.spaces.active.tree_type in sdict:
|
||||
# Add node to this editor
|
||||
for n in nt.nodes:
|
||||
n.select = False
|
||||
|
||||
node_type = sdict.get(area.spaces.active.tree_type)
|
||||
if node_type:
|
||||
# Check if nodegroup is compatible with this editor
|
||||
# For example, don't add shader nodegroups to geometry node editor
|
||||
if (
|
||||
nodegroup_type == "ShaderNodeTree"
|
||||
and area.spaces.active.tree_type != "ShaderNodeTree"
|
||||
) or (
|
||||
nodegroup_type == "GeometryNodeTree"
|
||||
and area.spaces.active.tree_type != "GeometryNodeTree"
|
||||
):
|
||||
continue
|
||||
|
||||
node = nt.nodes.new(node_type)
|
||||
node.node_tree = nodegroup
|
||||
node.location = (node_x, node_y)
|
||||
node.select = True
|
||||
nt.nodes.active = node
|
||||
added_to_editor = True
|
||||
break
|
||||
|
||||
return nodegroup, added_to_editor
|
||||
|
||||
|
||||
def append_material(file_name, matname=None, link=False, fake_user=True):
|
||||
@@ -183,15 +341,18 @@ def hdr_swap(name, hdr):
|
||||
:return: None
|
||||
"""
|
||||
w = bpy.context.scene.world
|
||||
if w:
|
||||
if not w:
|
||||
new_hdr_world(name, hdr)
|
||||
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
w.use_nodes = True
|
||||
w.name = name
|
||||
nt = w.node_tree
|
||||
for n in nt.nodes:
|
||||
if "ShaderNodeTexEnvironment" == n.bl_rna.identifier:
|
||||
env_node = n
|
||||
env_node.image = hdr
|
||||
return
|
||||
w.name = name
|
||||
nt = w.node_tree
|
||||
for n in nt.nodes:
|
||||
if "ShaderNodeTexEnvironment" == n.bl_rna.identifier:
|
||||
env_node = n
|
||||
env_node.image = hdr
|
||||
return
|
||||
new_hdr_world(name, hdr)
|
||||
|
||||
|
||||
@@ -203,7 +364,8 @@ def new_hdr_world(name, hdr):
|
||||
:return: None
|
||||
"""
|
||||
w = bpy.data.worlds.new(name=name)
|
||||
w.use_nodes = True
|
||||
if bpy.app.version < (5, 0, 0):
|
||||
w.use_nodes = True
|
||||
bpy.context.scene.world = w
|
||||
|
||||
nt = w.node_tree
|
||||
@@ -238,16 +400,36 @@ def load_HDR(file_name, name):
|
||||
|
||||
|
||||
def link_collection(
|
||||
file_name, obnames=[], location=(0, 0, 0), link=False, parent=None, **kwargs
|
||||
file_name,
|
||||
obnames: Optional[list] = None,
|
||||
location=(0, 0, 0),
|
||||
link: bool = False,
|
||||
parent: Optional[str] = None,
|
||||
collection: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
"""link an instanced group - model type asset"""
|
||||
if obnames is None:
|
||||
obnames = []
|
||||
sel = utils.selection_get()
|
||||
# Store the original active collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection # type: ignore[union-attr]
|
||||
|
||||
# Activate target collection if specified
|
||||
if collection:
|
||||
target_collection = bpy.data.collections.get(collection)
|
||||
if target_collection:
|
||||
# Find and activate the layer collection
|
||||
layer_collection = find_layer_collection(
|
||||
bpy.context.view_layer.layer_collection, collection # type: ignore[union-attr]
|
||||
)
|
||||
if layer_collection:
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection # type: ignore[union-attr]
|
||||
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
data_from,
|
||||
data_to,
|
||||
):
|
||||
scols = []
|
||||
for col in data_from.collections:
|
||||
if col == kwargs["name"]:
|
||||
data_to.collections = [col]
|
||||
@@ -257,46 +439,44 @@ def link_collection(
|
||||
rotation = kwargs["rotation"]
|
||||
|
||||
bpy.ops.object.empty_add(type="PLAIN_AXES", location=location, rotation=rotation)
|
||||
main_object = bpy.context.view_layer.objects.active
|
||||
main_object.instance_type = "COLLECTION"
|
||||
main_object = bpy.context.view_layer.objects.active # type: ignore[union-attr]
|
||||
main_object.instance_type = "COLLECTION" # type: ignore[union-attr]
|
||||
|
||||
if parent is not None:
|
||||
main_object.parent = bpy.data.objects.get(parent)
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects.get(parent) # type: ignore[union-attr]
|
||||
|
||||
main_object.matrix_world.translation = location
|
||||
main_object.matrix_world.translation = location # type: ignore[union-attr]
|
||||
|
||||
for col in bpy.data.collections:
|
||||
if col.library is not None:
|
||||
fp = bpy.path.abspath(col.library.filepath)
|
||||
fp1 = bpy.path.abspath(file_name)
|
||||
if fp == fp1:
|
||||
main_object.instance_collection = col
|
||||
main_object.instance_collection = col # type: ignore[union-attr]
|
||||
break
|
||||
|
||||
# sometimes, the lib might already be without the actual link.
|
||||
if not main_object.instance_collection and kwargs["name"]:
|
||||
if not main_object.instance_collection and kwargs["name"]: # type: ignore[union-attr]
|
||||
col = bpy.data.collections.get(kwargs["name"])
|
||||
if col:
|
||||
main_object.instance_collection = col
|
||||
main_object.instance_collection = col # type: ignore[union-attr]
|
||||
|
||||
main_object.name = main_object.instance_collection.name
|
||||
main_object.name = main_object.instance_collection.name # type: ignore[union-attr]
|
||||
|
||||
# bpy.ops.wm.link(directory=file_name + "/Collection/", filename=kwargs['name'], link=link, instance_collections=True,
|
||||
# autoselect=True)
|
||||
# main_object = bpy.context.view_layer.objects.active
|
||||
# if kwargs.get('rotation') is not None:
|
||||
# main_object.rotation_euler = kwargs['rotation']
|
||||
# main_object.location = location
|
||||
# Restore original active collection
|
||||
if orig_active_collection:
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection # type: ignore[union-attr]
|
||||
|
||||
utils.selection_set(sel)
|
||||
return main_object, []
|
||||
|
||||
|
||||
def append_particle_system(
|
||||
file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs
|
||||
file_name, obnames=None, location=(0, 0, 0), link=False, **kwargs
|
||||
):
|
||||
"""link an instanced group - model type asset"""
|
||||
|
||||
if obnames is None:
|
||||
obnames = []
|
||||
pss = []
|
||||
with bpy.data.libraries.load(file_name, link=link, relative=True) as (
|
||||
data_from,
|
||||
@@ -367,18 +547,53 @@ def append_particle_system(
|
||||
return target_object, []
|
||||
|
||||
|
||||
def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwargs):
|
||||
def append_objects(
|
||||
file_name,
|
||||
obnames: Optional[list] = None,
|
||||
location=(0, 0, 0),
|
||||
link: bool = False,
|
||||
parent: Optional[str] = None,
|
||||
collection: str = "",
|
||||
**kwargs,
|
||||
):
|
||||
"""Append object into scene individually. 2 approaches based in definition of name argument.
|
||||
TODO: really split this function into 2 functions: kwargs.get('name')==None and else.
|
||||
"""
|
||||
if obnames is None:
|
||||
obnames = []
|
||||
# simplified version of append
|
||||
if kwargs.get("name"):
|
||||
scene = bpy.context.scene
|
||||
sel = utils.selection_get()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
# Store the original active collection
|
||||
orig_active_collection = bpy.context.view_layer.active_layer_collection # type: ignore[union-attr]
|
||||
|
||||
# Activate target collection if specified
|
||||
if collection:
|
||||
target_collection = bpy.data.collections.get(collection)
|
||||
if target_collection:
|
||||
# Find and activate the layer collection
|
||||
layer_collection = find_layer_collection(
|
||||
bpy.context.view_layer.layer_collection, collection # type: ignore[union-attr]
|
||||
)
|
||||
if layer_collection:
|
||||
bpy.context.view_layer.active_layer_collection = layer_collection # type: ignore[union-attr]
|
||||
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
except Exception as e:
|
||||
reports.add_report(
|
||||
f"append_objects.1: {str(e)}",
|
||||
3,
|
||||
type="ERROR",
|
||||
)
|
||||
raise e
|
||||
|
||||
path = file_name + "/Collection"
|
||||
collection_name = kwargs.get("name")
|
||||
if collection_name is None:
|
||||
bk_logger.warning("collection_name is None")
|
||||
collection_name = ""
|
||||
bpy.ops.wm.append(filename=collection_name, directory=path)
|
||||
|
||||
# fc = utils.get_fake_context(bpy.context, area_type='VIEW_3D')
|
||||
@@ -386,22 +601,22 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
|
||||
return_obs = []
|
||||
to_hidden_collection = []
|
||||
collection = None
|
||||
appended_collection = None
|
||||
main_object = None
|
||||
# get first at least one parent for sure
|
||||
for ob in bpy.context.scene.objects:
|
||||
for ob in bpy.context.scene.objects: # type: ignore[union-attr]
|
||||
if ob.select_get():
|
||||
if not ob.parent:
|
||||
main_object = ob
|
||||
ob.location = location
|
||||
# do once again to ensure hidden objects are hidden
|
||||
for ob in bpy.context.scene.objects:
|
||||
for ob in bpy.context.scene.objects: # type: ignore[union-attr]
|
||||
if ob.select_get():
|
||||
return_obs.append(ob)
|
||||
# check for object that should be hidden
|
||||
if ob.users_collection[0].name == collection_name:
|
||||
collection = ob.users_collection[0]
|
||||
collection["is_blenderkit_asset"] = True
|
||||
appended_collection = ob.users_collection[0]
|
||||
appended_collection["is_blenderkit_asset"] = True
|
||||
if not ob.parent:
|
||||
main_object = ob
|
||||
ob.location = location
|
||||
@@ -414,14 +629,14 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
if kwargs.get("rotation"):
|
||||
main_object.rotation_euler = kwargs["rotation"]
|
||||
|
||||
if kwargs.get("parent") is not None:
|
||||
main_object.parent = bpy.data.objects[kwargs["parent"]]
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects[parent]
|
||||
main_object.matrix_world.translation = location
|
||||
|
||||
# move objects that should be hidden to a sub collection
|
||||
if len(to_hidden_collection) > 0 and collection is not None:
|
||||
if len(to_hidden_collection) > 0 and appended_collection is not None:
|
||||
hidden_collections = []
|
||||
scene_collection = bpy.context.scene.collection
|
||||
scene_collection = bpy.context.scene.collection # type: ignore[union-attr]
|
||||
for ob in to_hidden_collection:
|
||||
hide_collection = ob.users_collection[0]
|
||||
|
||||
@@ -434,7 +649,11 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
h_col = bpy.data.collections.get(hidden_collection_name)
|
||||
if h_col is None:
|
||||
h_col = bpy.data.collections.new(name=hidden_collection_name)
|
||||
collection.children.link(h_col)
|
||||
# If target collection is specified, make the hidden collection a child of target collection
|
||||
if collection and bpy.data.collections.get(collection):
|
||||
bpy.data.collections.get(collection).children.link(h_col)
|
||||
else:
|
||||
appended_collection.children.link(h_col)
|
||||
utils.exclude_collection(hidden_collection_name)
|
||||
|
||||
ob.users_collection[0].objects.unlink(ob)
|
||||
@@ -443,12 +662,31 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
if hide_collection in hidden_collections:
|
||||
continue
|
||||
# All other collections are moved to be children of the model collection
|
||||
bk_logger.info(f"{hide_collection}, {collection}")
|
||||
utils.move_collection(hide_collection, collection)
|
||||
bk_logger.info(f"{hide_collection}, {appended_collection}")
|
||||
# If target collection is specified, move collections there instead
|
||||
if collection and bpy.data.collections.get(collection):
|
||||
utils.move_collection(
|
||||
hide_collection, bpy.data.collections.get(collection)
|
||||
)
|
||||
else:
|
||||
utils.move_collection(hide_collection, appended_collection)
|
||||
utils.exclude_collection(hide_collection.name)
|
||||
hidden_collections.append(hide_collection)
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
except Exception as e:
|
||||
reports.add_report(
|
||||
f"append_objects.2: {str(e)}",
|
||||
3,
|
||||
type="ERROR",
|
||||
)
|
||||
raise e
|
||||
|
||||
# Restore original active collection
|
||||
if orig_active_collection:
|
||||
bpy.context.view_layer.active_layer_collection = orig_active_collection # type: ignore[union-attr]
|
||||
|
||||
utils.selection_set(sel)
|
||||
# let collection also store info that it was created by BlenderKit, for purging reasons
|
||||
|
||||
@@ -471,7 +709,15 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
# link them to scene
|
||||
scene = bpy.context.scene
|
||||
sel = utils.selection_get()
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
except Exception as e:
|
||||
reports.add_report(
|
||||
f"append_objects.3: {str(e)}",
|
||||
3,
|
||||
type="ERROR",
|
||||
)
|
||||
raise e
|
||||
|
||||
return_obs = [] # this might not be needed, but better be sure to rewrite the list.
|
||||
main_object = None
|
||||
@@ -480,7 +726,7 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
for obj in data_to.objects:
|
||||
if obj is not None:
|
||||
# if obj.name not in scene.objects:
|
||||
scene.collection.objects.link(obj)
|
||||
scene.collection.objects.link(obj) # type: ignore[union-attr]
|
||||
if obj.parent is None:
|
||||
obj.location = location
|
||||
main_object = obj
|
||||
@@ -499,13 +745,21 @@ def append_objects(file_name, obnames=[], location=(0, 0, 0), link=False, **kwar
|
||||
ob.hide_viewport = True
|
||||
|
||||
if kwargs.get("rotation") is not None:
|
||||
main_object.rotation_euler = kwargs["rotation"]
|
||||
main_object.rotation_euler = kwargs["rotation"] # type: ignore[union-attr]
|
||||
|
||||
if kwargs.get("parent") is not None:
|
||||
main_object.parent = bpy.data.objects[kwargs["parent"]]
|
||||
main_object.matrix_world.translation = location
|
||||
if parent is not None and parent != "":
|
||||
main_object.parent = bpy.data.objects[parent] # type: ignore[union-attr]
|
||||
main_object.matrix_world.translation = location # type: ignore[union-attr]
|
||||
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
try:
|
||||
bpy.ops.object.select_all(action="DESELECT")
|
||||
except Exception as e:
|
||||
reports.add_report(
|
||||
f"append_objects.4: {str(e)}",
|
||||
3,
|
||||
type="ERROR",
|
||||
)
|
||||
raise e
|
||||
utils.selection_set(sel)
|
||||
|
||||
return main_object, return_obs
|
||||
|
||||
+202
-24
@@ -30,7 +30,6 @@ from . import (
|
||||
global_vars,
|
||||
paths,
|
||||
ratings_utils,
|
||||
reports,
|
||||
search,
|
||||
ui,
|
||||
ui_panels,
|
||||
@@ -76,6 +75,27 @@ def modal_inside(self, context, event):
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
# HANDLE PHOTO THUMBNAIL SWITCH
|
||||
if hasattr(self, "needs_tooltip_update") and self.needs_tooltip_update:
|
||||
self.needs_tooltip_update = False
|
||||
sr = search.get_search_results()
|
||||
if sr and self.active_index < len(sr):
|
||||
asset_data = sr[self.active_index]
|
||||
if asset_data["assetType"].lower() == "printable":
|
||||
if self.show_photo_thumbnail:
|
||||
photo_img = ui.get_full_photo_thumbnail(asset_data)
|
||||
if photo_img:
|
||||
self.tooltip_image.set_image(photo_img.filepath)
|
||||
self.tooltip_image.set_image_colorspace("")
|
||||
else:
|
||||
self.tooltip_image.set_image(
|
||||
paths.get_addon_thumbnail_path("thumbnail_notready.jpg")
|
||||
)
|
||||
else:
|
||||
set_thumb_check(
|
||||
self.tooltip_image, asset_data, thumb_type="thumbnail"
|
||||
)
|
||||
|
||||
if ui_props.turn_off:
|
||||
ui_props.turn_off = False
|
||||
self.finish()
|
||||
@@ -96,9 +116,15 @@ def modal_inside(self, context, event):
|
||||
if sr is not None:
|
||||
# this check runs more search, usefull especially for first search. Could be moved to a better place where the check
|
||||
# doesn't run that often.
|
||||
# Calculate current max rows based on expanded state
|
||||
if user_preferences.assetbar_expanded:
|
||||
current_max_rows = user_preferences.maximized_assetbar_rows
|
||||
else:
|
||||
current_max_rows = 1
|
||||
|
||||
if (
|
||||
len(sr) - ui_props.scroll_offset
|
||||
< (ui_props.wcount * user_preferences.max_assetbar_rows) + 15
|
||||
< (ui_props.wcount * current_max_rows) + 15
|
||||
):
|
||||
self.search_more()
|
||||
|
||||
@@ -427,6 +453,13 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
default="Runs search and displays the asset bar at the same time"
|
||||
)
|
||||
|
||||
show_photo_thumbnail: BoolProperty( # type: ignore[valid-type]
|
||||
name="Show Photo Thumbnail",
|
||||
description="Toggle between normal and photo thumbnail - use [ or ] to cycle through thumbnails. Currently used only for printables.",
|
||||
default=False,
|
||||
options={"SKIP_SAVE"},
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def description(cls, context, properties):
|
||||
return properties.tooltip
|
||||
@@ -446,9 +479,6 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.tooltip_scale = 1.0
|
||||
self.tooltip_height = self.tooltip_size
|
||||
self.tooltip_width = self.tooltip_size
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
if ui_props.asset_type == "HDR":
|
||||
self.tooltip_width = self.tooltip_size * 2
|
||||
# total_size = tooltip# + 2 * self.margin
|
||||
self.tooltip_panel = BL_UI_Drag_Panel(
|
||||
0, 0, self.tooltip_width, self.tooltip_height
|
||||
@@ -722,15 +752,19 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.max_hcount = math.floor(
|
||||
max(region.width, context.window.width) / self.button_size
|
||||
)
|
||||
self.max_wcount = user_preferences.max_assetbar_rows
|
||||
self.max_wcount = user_preferences.maximized_assetbar_rows
|
||||
|
||||
history_step = search.get_active_history_step()
|
||||
search_results = history_step.get("search_results")
|
||||
# we need to init all possible thumb previews in advance/
|
||||
# self.hcount = user_preferences.max_assetbar_rows
|
||||
# Calculate hcount based on expanded state
|
||||
if search_results is not None and self.wcount > 0:
|
||||
if user_preferences.assetbar_expanded:
|
||||
max_rows = user_preferences.maximized_assetbar_rows
|
||||
else:
|
||||
max_rows = 1
|
||||
self.hcount = min(
|
||||
user_preferences.max_assetbar_rows,
|
||||
max_rows,
|
||||
math.ceil(len(search_results) / self.wcount),
|
||||
)
|
||||
self.hcount = max(self.hcount, 1)
|
||||
@@ -768,6 +802,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.button_close.set_location(
|
||||
self.bar_width - self.other_button_size, -self.other_button_size
|
||||
)
|
||||
self.button_expand.set_location(
|
||||
self.bar_width - self.other_button_size, self.bar_height
|
||||
)
|
||||
# if hasattr(self, 'button_notifications'):
|
||||
# self.button_notifications.set_location(self.bar_width - self.other_button_size * 2, -self.other_button_size)
|
||||
self.button_scroll_up.set_location(self.bar_width, 0)
|
||||
@@ -1026,6 +1063,25 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
self.widgets_panel.append(self.button_close)
|
||||
|
||||
# Expand/collapse button (positioned at bottom of assetbar)
|
||||
self.button_expand = BL_UI_Button(
|
||||
self.bar_width - self.other_button_size,
|
||||
self.bar_height,
|
||||
self.other_button_size,
|
||||
self.other_button_size,
|
||||
)
|
||||
self.button_expand.bg_color = self.button_bg_color
|
||||
self.button_expand.hover_bg_color = self.button_hover_color
|
||||
self.button_expand.text = ""
|
||||
self.button_expand.text_size = self.other_button_size * 0.8
|
||||
self.button_expand.set_image_position((0, 0))
|
||||
self.button_expand.set_image_size(
|
||||
(self.other_button_size, self.other_button_size)
|
||||
)
|
||||
self.button_expand.set_mouse_down(self.toggle_expand)
|
||||
|
||||
self.widgets_panel.append(self.button_expand)
|
||||
|
||||
self.scroll_width = 30
|
||||
self.button_scroll_down = BL_UI_Button(
|
||||
-self.scroll_width, 0, self.scroll_width, self.bar_height
|
||||
@@ -1069,9 +1125,14 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
)
|
||||
self.history_back_button.bg_color = self.button_bg_color
|
||||
self.history_back_button.hover_bg_color = self.button_hover_color
|
||||
self.history_back_button.text = "◀"
|
||||
self.history_back_button.text_size = button_size * 0.5
|
||||
self.history_back_button.text_color = self.text_color
|
||||
self.history_back_button.text = ""
|
||||
icon_size = int(button_size * 0.6)
|
||||
margin_lr = int((button_size - icon_size) / 2)
|
||||
self.history_back_button.set_image(
|
||||
paths.get_addon_thumbnail_path("history_back.png")
|
||||
)
|
||||
self.history_back_button.set_image_size((icon_size, icon_size))
|
||||
self.history_back_button.set_image_position((margin_lr, margin_lr))
|
||||
|
||||
self.history_forward_button = BL_UI_Button(
|
||||
margin * 2 + button_size,
|
||||
@@ -1081,9 +1142,12 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
)
|
||||
self.history_forward_button.bg_color = self.button_bg_color
|
||||
self.history_forward_button.hover_bg_color = self.button_hover_color
|
||||
self.history_forward_button.text = "▶"
|
||||
self.history_forward_button.text_size = button_size * 0.5
|
||||
self.history_forward_button.text_color = self.text_color
|
||||
self.history_forward_button.text = ""
|
||||
self.history_forward_button.set_image(
|
||||
paths.get_addon_thumbnail_path("history_forward.png")
|
||||
)
|
||||
self.history_forward_button.set_image_size((icon_size, icon_size))
|
||||
self.history_forward_button.set_image_position((margin_lr, margin_lr))
|
||||
|
||||
# Tab buttons
|
||||
tabs = global_vars.TABS["tabs"]
|
||||
@@ -1211,6 +1275,9 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
# Update tab icons
|
||||
self.update_tab_icons()
|
||||
|
||||
# Update expand button icon
|
||||
self.update_expand_button_icon()
|
||||
|
||||
def update_tab_icons(self):
|
||||
"""Update tab icons based on the active history step's asset type"""
|
||||
tabs = global_vars.TABS["tabs"]
|
||||
@@ -1240,6 +1307,16 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
tab_button.set_image(icon_path)
|
||||
tab_button.set_image_colorspace("")
|
||||
|
||||
def update_expand_button_icon(self):
|
||||
"""Update expand button icon based on current expanded state."""
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
if user_preferences.assetbar_expanded:
|
||||
# Show up arrow when expanded (to collapse)
|
||||
self.button_expand.text = "▲"
|
||||
else:
|
||||
# Show down arrow when collapsed (to expand)
|
||||
self.button_expand.text = "▼"
|
||||
|
||||
def position_and_hide_buttons(self):
|
||||
# position and layout buttons
|
||||
sr = search.get_search_results()
|
||||
@@ -1320,6 +1397,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
self.tooltip_base_size_pixels = 512
|
||||
self.tooltip_scale = 1.0
|
||||
self.bottom_panel_fraction = 0.15
|
||||
self.needs_tooltip_update = False
|
||||
self.update_ui_size(bpy.context)
|
||||
|
||||
# todo move all this to update UI size
|
||||
@@ -1441,7 +1519,6 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
def update_tooltip_image(self, asset_id):
|
||||
"""Update tootlip image when it finishes downloading and the downloaded image matches the active one."""
|
||||
|
||||
search_results = search.get_search_results()
|
||||
if search_results is None:
|
||||
return
|
||||
@@ -1499,7 +1576,23 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
# self.tooltip = asset_data['tooltip']
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
ui_props.active_index = search_index # + self.scroll_offset
|
||||
set_thumb_check(self.tooltip_image, asset_data, thumb_type="thumbnail")
|
||||
|
||||
# Update tooltip size based on asset type
|
||||
if (
|
||||
asset_data["assetType"].lower() == "printable"
|
||||
and self.show_photo_thumbnail
|
||||
):
|
||||
photo_img = ui.get_full_photo_thumbnail(asset_data)
|
||||
if photo_img:
|
||||
self.tooltip_image.set_image(photo_img.filepath)
|
||||
self.tooltip_image.set_image_colorspace("")
|
||||
else:
|
||||
self.tooltip_image.set_image(
|
||||
paths.get_addon_thumbnail_path("thumbnail_notready.jpg")
|
||||
)
|
||||
else:
|
||||
set_thumb_check(self.tooltip_image, asset_data, thumb_type="thumbnail")
|
||||
|
||||
get_tooltip_data(asset_data)
|
||||
an = asset_data["displayName"]
|
||||
max_name_length = 30
|
||||
@@ -1615,7 +1708,7 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
|
||||
now = time.time()
|
||||
# avoid double click to download assets under panels, mainly category panel
|
||||
if now - ui_panels.last_time_dropdown_active < 0.5:
|
||||
if now - ui_panels.last_time_overlay_panel_active < 0.5:
|
||||
return
|
||||
# start drag drop
|
||||
bpy.ops.view3d.asset_drag_drop(
|
||||
@@ -1626,6 +1719,17 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
def cancel_press(self, widget):
|
||||
self.finish()
|
||||
|
||||
def toggle_expand(self, widget):
|
||||
"""Toggle the expanded state of the assetbar."""
|
||||
user_preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
user_preferences.assetbar_expanded = not user_preferences.assetbar_expanded
|
||||
|
||||
# Update the button icon
|
||||
self.update_expand_button_icon()
|
||||
|
||||
# Restart the asset bar to apply the new layout
|
||||
self.restart_asset_bar()
|
||||
|
||||
def asset_menu(self, widget):
|
||||
self.hide_tooltip()
|
||||
bpy.ops.wm.blenderkit_asset_popup("INVOKE_DEFAULT")
|
||||
@@ -1668,14 +1772,21 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
bookmark_button.set_image(img_fp)
|
||||
|
||||
def update_progress_bar(self, asset_button, asset_data):
|
||||
if asset_data["downloaded"] > 0:
|
||||
pb = asset_button.progress_bar
|
||||
w = int(self.button_size * asset_data["downloaded"] / 100.0)
|
||||
asset_button.progress_bar.width = w
|
||||
asset_button.progress_bar.update(pb.x_screen, pb.y_screen)
|
||||
asset_button.progress_bar.visible = True
|
||||
else:
|
||||
"""Update progress bar for an asset button."""
|
||||
pb = asset_button.progress_bar
|
||||
if pb is None:
|
||||
return
|
||||
|
||||
if asset_data["downloaded"] == 0:
|
||||
asset_button.progress_bar.visible = False
|
||||
return
|
||||
|
||||
w = int(self.button_size * asset_data["downloaded"] / 100.0)
|
||||
asset_button.progress_bar.width = w
|
||||
asset_button.progress_bar.update(pb.x_screen, pb.y_screen)
|
||||
asset_button.progress_bar.visible = True
|
||||
if bpy.context.region is not None:
|
||||
bpy.context.region.tag_redraw()
|
||||
|
||||
def update_validation_icon(self, asset_button, asset_data: dict):
|
||||
if utils.profile_is_validator():
|
||||
@@ -1853,6 +1964,30 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
search.search()
|
||||
|
||||
def handle_key_input(self, event):
|
||||
# Check if enough time has passed since last popup/text input activity
|
||||
# to prevent shortcuts from triggering while typing in text fields
|
||||
now = time.time()
|
||||
if now - ui_panels.last_time_overlay_panel_active < 0.5:
|
||||
return False
|
||||
|
||||
# Shortcut: Toggle between normal and photo thumbnail
|
||||
if event.type in {"ONE"}:
|
||||
if self.show_photo_thumbnail == True:
|
||||
self.show_photo_thumbnail = False
|
||||
self.needs_tooltip_update = True
|
||||
if event.type in {"TWO"}:
|
||||
if self.show_photo_thumbnail == False:
|
||||
self.show_photo_thumbnail = True
|
||||
self.needs_tooltip_update = True
|
||||
if (
|
||||
event.type in {"LEFT_BRACKET", "RIGHT_BRACKET"}
|
||||
and not event.shift
|
||||
and self.active_index > -1
|
||||
):
|
||||
self.show_photo_thumbnail = not self.show_photo_thumbnail
|
||||
self.needs_tooltip_update = True
|
||||
return True
|
||||
|
||||
# Shortcut: Search by author
|
||||
if event.type == "A":
|
||||
self.search_by_author(self.active_index)
|
||||
@@ -2166,6 +2301,49 @@ class BlenderKitAssetBarOperator(BL_UI_OT_draw_operator):
|
||||
)
|
||||
|
||||
|
||||
def handle_bkclientjs_get_asset(task: search.client_tasks.Task):
|
||||
"""Handle incoming bkclientjs/get_asset task after the user asked for download in online gallery. How it goes:
|
||||
1. set search in the history
|
||||
2. set the results in the history step
|
||||
3. open the asset bar
|
||||
We handle the task in asset_bar_op because we need access to the asset_bar_operator without circular import from search.
|
||||
"""
|
||||
bk_logger.info(f"handle_bkclientjs_get_asset: {task.result['asset_data']['name']}")
|
||||
|
||||
# Get asset data from task result
|
||||
asset_data = task.result.get("asset_data")
|
||||
if not asset_data:
|
||||
bk_logger.error("No asset data found in task")
|
||||
return
|
||||
|
||||
# Parse the asset data
|
||||
parsed_asset_data = search.parse_result(asset_data)
|
||||
if not parsed_asset_data:
|
||||
bk_logger.error("Failed to parse asset data")
|
||||
return
|
||||
|
||||
search.append_history_step(
|
||||
search_keywords=f"asset_base_id:{asset_data['assetBaseId']}",
|
||||
search_results=[parsed_asset_data],
|
||||
asset_type=asset_data.get("assetType", "").upper(),
|
||||
search_results_orig={"results": [asset_data], "count": 1},
|
||||
)
|
||||
|
||||
# If asset bar is not open, try to open it
|
||||
if asset_bar_operator is None:
|
||||
try:
|
||||
bpy.ops.view3d.run_assetbar_fix_context(keep_running=True, do_search=False) # type: ignore[attr-defined]
|
||||
except Exception as e:
|
||||
bk_logger.error(f"Failed to open asset bar: {e}")
|
||||
return
|
||||
|
||||
# Force redraw of the region if asset bar exists
|
||||
if asset_bar_operator and asset_bar_operator.area:
|
||||
search.load_preview(parsed_asset_data)
|
||||
asset_bar_operator.update_image(parsed_asset_data["assetBaseId"])
|
||||
asset_bar_operator.area.tag_redraw()
|
||||
|
||||
|
||||
BlenderKitAssetBarOperator.modal = asset_bar_modal # type: ignore[method-assign]
|
||||
BlenderKitAssetBarOperator.invoke = asset_bar_invoke # type: ignore[method-assign]
|
||||
|
||||
|
||||
+1181
-123
File diff suppressed because it is too large
Load Diff
+52
-26
@@ -20,12 +20,19 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import subprocess
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
from bpy.props import BoolProperty, EnumProperty, FloatProperty, IntProperty
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
FloatVectorProperty,
|
||||
)
|
||||
|
||||
from . import bg_blender, global_vars, paths, tasks_queue, utils, upload, search
|
||||
|
||||
@@ -41,7 +48,8 @@ thumbnail_resolutions = (
|
||||
)
|
||||
|
||||
thumbnail_angles = (
|
||||
("DEFAULT", "default", ""),
|
||||
("ANGLE_1", "Angle 1", "Lower hanging camera angle"),
|
||||
("ANGLE_2", "Angle 2", "Higher hanging camera angle"),
|
||||
("FRONT", "front", ""),
|
||||
("SIDE", "side", ""),
|
||||
("TOP", "top", ""),
|
||||
@@ -320,13 +328,17 @@ class GenerateThumbnailOperator(bpy.types.Operator):
|
||||
return bpy.context.view_layer.objects.active is not None
|
||||
|
||||
def draw(self, context):
|
||||
ob = bpy.context.active_object
|
||||
while ob.parent is not None:
|
||||
ob = ob.parent
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
asset_type = ui_props.asset_type
|
||||
|
||||
ob = utils.get_active_model()
|
||||
props = ob.blenderkit
|
||||
layout = self.layout
|
||||
layout.label(text="thumbnailer settings")
|
||||
layout.prop(props, "thumbnail_background_lightness")
|
||||
# for printable models
|
||||
if asset_type == "PRINTABLE":
|
||||
layout.prop(props, "thumbnail_material_color")
|
||||
layout.prop(props, "thumbnail_angle")
|
||||
layout.prop(props, "thumbnail_snap_to")
|
||||
layout.prop(props, "thumbnail_samples")
|
||||
@@ -382,20 +394,27 @@ class GenerateThumbnailOperator(bpy.types.Operator):
|
||||
obnames = []
|
||||
for ob in obs:
|
||||
obnames.append(ob.name)
|
||||
|
||||
# asset type can be model or printable
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
asset_type = ui_props.asset_type
|
||||
args_dict = {
|
||||
"type": "material",
|
||||
"type": asset_type,
|
||||
"asset_name": asset.name,
|
||||
"filepath": filepath,
|
||||
"thumbnail_path": thumb_path,
|
||||
"tempdir": tempdir,
|
||||
}
|
||||
thumbnail_args = {
|
||||
"type": "model",
|
||||
"type": asset_type,
|
||||
"models": str(obnames),
|
||||
"thumbnail_angle": bkit.thumbnail_angle,
|
||||
"thumbnail_snap_to": bkit.thumbnail_snap_to,
|
||||
"thumbnail_background_lightness": bkit.thumbnail_background_lightness,
|
||||
"thumbnail_material_color": (
|
||||
bkit.thumbnail_material_color[0],
|
||||
bkit.thumbnail_material_color[1],
|
||||
bkit.thumbnail_material_color[2],
|
||||
),
|
||||
"thumbnail_resolution": bkit.thumbnail_resolution,
|
||||
"thumbnail_samples": bkit.thumbnail_samples,
|
||||
"thumbnail_denoising": bkit.thumbnail_denoising,
|
||||
@@ -409,12 +428,6 @@ class GenerateThumbnailOperator(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
# if bpy.data.filepath == '':
|
||||
# ui_panels.ui_message(
|
||||
# title="Can't render thumbnail",
|
||||
# message="please save your file first")
|
||||
#
|
||||
# return {'FINISHED'}
|
||||
|
||||
return wm.invoke_props_dialog(self, width=400)
|
||||
|
||||
@@ -448,10 +461,17 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
max=10,
|
||||
)
|
||||
|
||||
thumbnail_material_color: FloatVectorProperty(
|
||||
name="Thumbnail Material Color",
|
||||
description="Color of the material for printable models",
|
||||
default=(random.random(), random.random(), random.random()),
|
||||
subtype="COLOR",
|
||||
)
|
||||
|
||||
thumbnail_angle: EnumProperty( # type: ignore[valid-type]
|
||||
name="Thumbnail Angle",
|
||||
items=thumbnail_angles,
|
||||
default="DEFAULT",
|
||||
default="ANGLE_1",
|
||||
description="thumbnailer angle",
|
||||
)
|
||||
|
||||
@@ -491,6 +511,9 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
layout.label(text="Server-side rendering may take several hours", icon="INFO")
|
||||
layout.label(text="thumbnailer settings")
|
||||
layout.prop(props, "thumbnail_background_lightness")
|
||||
# for printable models
|
||||
if self.asset_type == "PRINTABLE":
|
||||
layout.prop(props, "thumbnail_material_color")
|
||||
layout.prop(props, "thumbnail_angle")
|
||||
layout.prop(props, "thumbnail_snap_to")
|
||||
layout.prop(props, "thumbnail_samples")
|
||||
@@ -503,17 +526,12 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
if not self.asset_index > -1:
|
||||
return {"CANCELLED"}
|
||||
|
||||
# Get search results from history
|
||||
history_step = search.get_active_history_step()
|
||||
sr = history_step.get("search_results", [])
|
||||
asset_data = sr[self.asset_index]
|
||||
|
||||
preferences = bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
if not self.render_locally:
|
||||
# Use server-side thumbnail regeneration
|
||||
success = upload.mark_for_thumbnail(
|
||||
asset_id=asset_data["id"],
|
||||
asset_id=self.asset_data["id"],
|
||||
api_key=preferences.api_key,
|
||||
use_gpu=preferences.thumbnail_use_gpu,
|
||||
samples=self.thumbnail_samples,
|
||||
@@ -536,13 +554,16 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
# Local thumbnail generation (original functionality)
|
||||
tempdir = tempfile.mkdtemp()
|
||||
|
||||
an_slug = paths.slugify(asset_data["name"])
|
||||
an_slug = paths.slugify(self.asset_data["name"])
|
||||
thumb_path = os.path.join(tempdir, an_slug)
|
||||
|
||||
# asset type can be model or printable
|
||||
ui_props = bpy.context.window_manager.blenderkitUI
|
||||
self.asset_type = ui_props.asset_type
|
||||
args_dict = {
|
||||
"type": "material",
|
||||
"asset_name": asset_data["name"],
|
||||
"asset_data": asset_data,
|
||||
"type": self.asset_type,
|
||||
"asset_name": self.asset_data["name"],
|
||||
"asset_data": self.asset_data,
|
||||
# "filepath": filepath,
|
||||
"thumbnail_path": thumb_path,
|
||||
"tempdir": tempdir,
|
||||
@@ -550,7 +571,7 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
"upload_after_render": True,
|
||||
}
|
||||
thumbnail_args = {
|
||||
"type": "model",
|
||||
"type": self.asset_type,
|
||||
"thumbnail_angle": self.thumbnail_angle,
|
||||
"thumbnail_snap_to": self.thumbnail_snap_to,
|
||||
"thumbnail_background_lightness": self.thumbnail_background_lightness,
|
||||
@@ -565,6 +586,11 @@ class ReGenerateThumbnailOperator(bpy.types.Operator):
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
# Get search results from history
|
||||
history_step = search.get_active_history_step()
|
||||
sr = history_step.get("search_results", [])
|
||||
self.asset_data = sr[self.asset_index]
|
||||
|
||||
return wm.invoke_props_dialog(self, width=400)
|
||||
|
||||
|
||||
|
||||
+59
-5
@@ -21,6 +21,8 @@
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import random
|
||||
import colorsys
|
||||
import sys
|
||||
from traceback import print_exc
|
||||
|
||||
@@ -110,6 +112,25 @@ def patch_imports(addon_module_name: str):
|
||||
print(f"- Local repository {parts[1]} added")
|
||||
|
||||
|
||||
def replace_materials(obs, material_name):
|
||||
"""Replace all materials on objects with the specified material
|
||||
Args:
|
||||
obs: List of objects to process
|
||||
material_name: Name of the material to apply to all objects
|
||||
"""
|
||||
material = bpy.data.materials.get(material_name)
|
||||
if not material:
|
||||
bg_blender.progress(f"Material {material_name} not found")
|
||||
return
|
||||
|
||||
for ob in obs:
|
||||
if ob.type == "MESH":
|
||||
# Clear all material slots and add the specified material
|
||||
ob.data.materials.clear()
|
||||
ob.data.materials.append(material)
|
||||
return material
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# args order must match the order in blenderkit/autothumb.py:get_thumbnailer_args()!
|
||||
@@ -187,10 +208,11 @@ if __name__ == "__main__":
|
||||
bpy.context.preferences.addons["cycles"].preferences.refresh_devices()
|
||||
|
||||
fdict = {
|
||||
"DEFAULT": 1,
|
||||
"FRONT": 2,
|
||||
"SIDE": 3,
|
||||
"TOP": 4,
|
||||
"ANGLE_1": 1,
|
||||
"ANGLE_2": 2,
|
||||
"FRONT": 3,
|
||||
"SIDE": 4,
|
||||
"TOP": 5,
|
||||
}
|
||||
s = bpy.context.scene
|
||||
s.frame_set(fdict[data["thumbnail_angle"]])
|
||||
@@ -210,9 +232,41 @@ if __name__ == "__main__":
|
||||
collection.hide_select = False
|
||||
|
||||
main_object.rotation_euler = (0, 0, 0)
|
||||
|
||||
# Add material replacement for printable assets
|
||||
# works directly with the specific material that has a color node for input
|
||||
if data.get("type") == "PRINTABLE":
|
||||
material = replace_materials(allobs, "PrintableMaterial")
|
||||
# Find the BaseColor node in this material
|
||||
base_color_node = material.node_tree.nodes.get("BaseColor")
|
||||
if base_color_node:
|
||||
# randomize the color value, needs to be defined by random hue and saturation = 0.95, we need to convert it to RGB then
|
||||
# random_color = (random.random(), 0.95, 0.5)
|
||||
# # convert to RGB
|
||||
# random_color = colorsys.hsv_to_rgb(
|
||||
# random_color[0], random_color[1], random_color[2]
|
||||
# )
|
||||
random_color = data["thumbnail_material_color"]
|
||||
base_color_node.outputs[0].default_value = (
|
||||
random_color[0],
|
||||
random_color[1],
|
||||
random_color[2],
|
||||
1,
|
||||
)
|
||||
# now let's make background color complementary to the material color
|
||||
bpy.data.materials["bkit background"].node_tree.nodes[
|
||||
"BaseColor"
|
||||
].outputs["Color"].default_value = (
|
||||
1 - random_color[0],
|
||||
1 - random_color[1],
|
||||
1 - random_color[2],
|
||||
1,
|
||||
)
|
||||
|
||||
bpy.data.materials["bkit background"].node_tree.nodes["Value"].outputs[
|
||||
"Value"
|
||||
].default_value = data["thumbnail_background_lightness"]
|
||||
|
||||
s.cycles.samples = data["thumbnail_samples"]
|
||||
bpy.context.view_layer.cycles.use_denoising = data["thumbnail_denoising"]
|
||||
bpy.context.view_layer.update()
|
||||
@@ -237,11 +291,11 @@ if __name__ == "__main__":
|
||||
|
||||
bg_blender.progress("rendering thumbnail")
|
||||
render_thumbnails()
|
||||
|
||||
if not data.get("upload_after_render") or not data.get("asset_data"):
|
||||
bg_blender.progress(
|
||||
"background autothumbnailer finished successfully (no upload)"
|
||||
)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
bg_blender.progress("uploading thumbnail")
|
||||
|
||||
+7
-1
@@ -102,6 +102,11 @@ def clean_login_data():
|
||||
preferences.api_key = ""
|
||||
preferences.api_key_timeout = 0
|
||||
global_vars.BKIT_PROFILE = datas.MineProfile()
|
||||
# Cleanup also the api key in the extensions repository setting and clean the cache
|
||||
from . import override_extension_draw
|
||||
|
||||
override_extension_draw.ensure_repository(api_key="")
|
||||
override_extension_draw.clear_repo_cache()
|
||||
|
||||
|
||||
def logout() -> None:
|
||||
@@ -151,11 +156,12 @@ def write_tokens(auth_token, refresh_token, oauth_response):
|
||||
preferences.login_attempt = False
|
||||
preferences.api_key_refresh = refresh_token
|
||||
preferences.api_key = auth_token # triggers api_key update function
|
||||
# write token also to extensions repository setting
|
||||
# write token also to extensions repository setting and clear the cache
|
||||
if bpy.app.version >= (4, 2, 0):
|
||||
from . import override_extension_draw
|
||||
|
||||
override_extension_draw.ensure_repository(api_key=auth_token)
|
||||
override_extension_draw.clear_repo_cache()
|
||||
|
||||
#
|
||||
|
||||
|
||||
+6
-6
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"last_check": "2025-06-23 09:38:03.256719",
|
||||
"backup_date": "",
|
||||
"last_check": "2025-12-01 11:02:25.858363",
|
||||
"backup_date": "October-27-2025",
|
||||
"update_ready": true,
|
||||
"ignore": false,
|
||||
"just_restored": false,
|
||||
"just_updated": false,
|
||||
"version_text": {
|
||||
"link": "https://github.com/BlenderKit/BlenderKit/releases/download/v3.16.1.250612/blenderkit-v3.16.1.250612.zip",
|
||||
"link": "https://github.com/BlenderKit/BlenderKit/releases/download/v3.18.0.251121/blenderkit-v3.18.0.251121.zip",
|
||||
"version": [
|
||||
3,
|
||||
16,
|
||||
1,
|
||||
250612
|
||||
18,
|
||||
0,
|
||||
251121
|
||||
]
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -159,8 +159,11 @@ class BL_UI_Button(BL_UI_Widget):
|
||||
|
||||
def draw_text(self, area_height):
|
||||
font_id = 1
|
||||
|
||||
if bpy.app.version < (4, 0, 0):
|
||||
if bpy.app.version < (3, 1, 0):
|
||||
# Blender 3.0 requires size:int https://docs.blender.org/api/3.0/blf.html#blf.size
|
||||
# but assetBar's search tab text is float - needs conversion in here
|
||||
blf.size(font_id, int(self._text_size), 72)
|
||||
elif bpy.app.version < (4, 0, 0):
|
||||
blf.size(font_id, self._text_size, 72)
|
||||
else:
|
||||
blf.size(font_id, self._text_size)
|
||||
@@ -204,7 +207,9 @@ class BL_UI_Button(BL_UI_Widget):
|
||||
try:
|
||||
self.mouse_down_func(self)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
import traceback
|
||||
import bpy
|
||||
from bpy.types import Operator
|
||||
|
||||
@@ -113,4 +114,4 @@ def draw_callback_px_separated(self, op, context):
|
||||
for widget in self.widgets:
|
||||
widget.draw()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
traceback.print_exc()
|
||||
|
||||
+2
-2
@@ -35,9 +35,9 @@ class BL_UI_Widget:
|
||||
|
||||
@bg_color.setter
|
||||
def bg_color(self, value):
|
||||
if value != self._bg_color:
|
||||
bpy.context.region.tag_redraw()
|
||||
self._bg_color = value
|
||||
if bpy.context.region is not None:
|
||||
bpy.context.region.tag_redraw()
|
||||
|
||||
@property
|
||||
def visible(self):
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ schema_version = "1.0.0"
|
||||
|
||||
id = "blenderkit"
|
||||
type = "add-on"
|
||||
version = "3.15.1-250403" # X.Y.Z-YYYYMMDD, must have a dash instead of a dot
|
||||
version = "3.17.0-251008" # X.Y.Z-YYYYMMDD, must have a dash instead of a dot
|
||||
|
||||
name = "BlenderKit Online Asset Library"
|
||||
tagline = "Drag & drop of assets from the community driven library"
|
||||
|
||||
BIN
Binary file not shown.
+1
-1
@@ -134,7 +134,7 @@ def handle_categories_task(task: client_tasks.Task):
|
||||
) # TODO: do this in Client, just saving the file so next time it is updated even without internet
|
||||
return
|
||||
|
||||
bk_logger.warning(task.message)
|
||||
bk_logger.warning(f"Could not load categories: {task.message}")
|
||||
if not os.path.exists(categories_filepath):
|
||||
source_path = paths.get_addon_file(subpath="data" + os.sep + "categories.json")
|
||||
try:
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user