2023-08-15 14:20:26 +00:00
|
|
|
# SPDX-FileCopyrightText: 2009-2023 Blender Authors
|
2023-06-15 03:09:04 +00:00
|
|
|
#
|
2022-02-10 22:07:11 +00:00
|
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
2009-12-01 12:02:23 +00:00
|
|
|
|
2009-12-02 10:32:39 +00:00
|
|
|
import bpy
|
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
header = '''
|
|
|
|
digraph ancestors {
|
|
|
|
graph [fontsize=30 labelloc="t" label="" splines=false overlap=true, rankdir=BT];
|
|
|
|
ratio = "auto" ;
|
|
|
|
'''
|
|
|
|
|
|
|
|
footer = '''
|
|
|
|
}
|
|
|
|
'''
|
|
|
|
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-02 02:56:22 +00:00
|
|
|
def compat_str(text, line_length=0):
|
2009-12-01 22:45:56 +00:00
|
|
|
|
|
|
|
if line_length:
|
|
|
|
text_ls = []
|
|
|
|
while len(text) > line_length:
|
|
|
|
text_ls.append(text[:line_length])
|
|
|
|
text = text[line_length:]
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
if text:
|
|
|
|
text_ls.append(text)
|
|
|
|
text = '\n '.join(text_ls)
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2023-03-01 11:12:18 +00:00
|
|
|
# text = text.replace('.', '.\n')
|
|
|
|
# text = text.replace(']', ']\n')
|
2009-12-01 12:02:23 +00:00
|
|
|
text = text.replace("\n", "\\n")
|
|
|
|
text = text.replace('"', '\\"')
|
2009-12-10 11:56:31 +00:00
|
|
|
return text
|
2009-12-01 12:02:23 +00:00
|
|
|
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2010-06-14 03:52:10 +00:00
|
|
|
def graph_armature(obj, filepath, FAKE_PARENT=True, CONSTRAINTS=True, DRIVERS=True, XTRA_INFO=True):
|
2009-12-05 20:45:51 +00:00
|
|
|
CONSTRAINTS = DRIVERS = True
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2010-06-14 03:52:10 +00:00
|
|
|
fileobject = open(filepath, "w")
|
2009-12-05 22:03:07 +00:00
|
|
|
fw = fileobject.write
|
2009-12-01 12:02:23 +00:00
|
|
|
fw(header)
|
2024-04-27 06:06:51 +00:00
|
|
|
fw('label = "{:s}::{:s}" ;'.format(bpy.data.filepath.split("/")[-1].split("\\")[-1], obj.name))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
arm = obj.data
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
bones = [bone.name for bone in arm.bones]
|
|
|
|
bones.sort()
|
|
|
|
print("")
|
|
|
|
for bone in bones:
|
|
|
|
b = arm.bones[bone]
|
2010-08-18 07:45:32 +00:00
|
|
|
print(">>", bone, ["*>", "->"][b.use_connect], getattr(getattr(b, "parent", ""), "name", ""))
|
2009-12-01 22:45:56 +00:00
|
|
|
label = [bone]
|
|
|
|
bone = arm.bones[bone]
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
for key, value in obj.pose.bones[bone.name].items():
|
|
|
|
if key.startswith("_"):
|
|
|
|
continue
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
if type(value) == float:
|
2024-04-27 06:06:51 +00:00
|
|
|
value = "{:.3f}".format(value)
|
2009-12-01 12:02:23 +00:00
|
|
|
elif type(value) == str:
|
|
|
|
value = compat_str(value)
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2024-04-27 06:06:51 +00:00
|
|
|
label.append("{:s} = {:s}".format(key, value))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2020-10-02 00:15:51 +00:00
|
|
|
opts = [
|
|
|
|
"shape=box",
|
|
|
|
"regular=1",
|
|
|
|
"style=filled",
|
|
|
|
"fixedsize=false",
|
2024-04-27 06:06:51 +00:00
|
|
|
'label="{:s}"'.format(compat_str('\n'.join(label))),
|
2020-10-02 00:15:51 +00:00
|
|
|
]
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
if bone.name.startswith('ORG'):
|
|
|
|
opts.append("fillcolor=yellow")
|
|
|
|
else:
|
|
|
|
opts.append("fillcolor=white")
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2024-04-27 06:06:51 +00:00
|
|
|
fw('"{:s}" [{:s}];\n'.format(bone.name, ','.join(opts)))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-02 10:32:39 +00:00
|
|
|
fw('\n\n# Hierarchy:\n')
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:25:59 +00:00
|
|
|
# Root node.
|
|
|
|
if FAKE_PARENT:
|
2024-04-27 06:06:51 +00:00
|
|
|
fw('"Object::{:s}" [];\n'.format(obj.name))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-02 02:56:22 +00:00
|
|
|
for bone in bones:
|
|
|
|
bone = arm.bones[bone]
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
parent = bone.parent
|
|
|
|
if parent:
|
2009-12-01 12:25:59 +00:00
|
|
|
parent_name = parent.name
|
2010-08-18 07:45:32 +00:00
|
|
|
connected = bone.use_connect
|
2009-12-01 12:25:59 +00:00
|
|
|
elif FAKE_PARENT:
|
2024-04-27 06:06:51 +00:00
|
|
|
parent_name = "Object::{:s}".format(obj.name)
|
2009-12-01 12:25:59 +00:00
|
|
|
connected = False
|
|
|
|
else:
|
|
|
|
continue
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:25:59 +00:00
|
|
|
opts = ["dir=forward", "weight=2", "arrowhead=normal"]
|
|
|
|
if not connected:
|
|
|
|
opts.append("style=dotted")
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2024-04-27 06:06:51 +00:00
|
|
|
fw('"{:s}" -> "{:s}" [{:s}] ;\n'.format(bone.name, parent_name, ','.join(opts)))
|
2009-12-05 22:03:07 +00:00
|
|
|
del bone
|
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
# constraints
|
2009-12-01 22:45:56 +00:00
|
|
|
if CONSTRAINTS:
|
2009-12-02 10:32:39 +00:00
|
|
|
fw('\n\n# Constraints:\n')
|
|
|
|
for bone in bones:
|
|
|
|
pbone = obj.pose.bones[bone]
|
|
|
|
# must be ordered
|
2009-12-01 22:45:56 +00:00
|
|
|
for constraint in pbone.constraints:
|
2009-12-03 14:20:35 +00:00
|
|
|
subtarget = getattr(constraint, "subtarget", "")
|
2009-12-01 22:45:56 +00:00
|
|
|
if subtarget:
|
|
|
|
# TODO, not internal links
|
2020-10-02 00:15:51 +00:00
|
|
|
opts = [
|
|
|
|
'dir=forward',
|
|
|
|
"weight=1",
|
|
|
|
"arrowhead=normal",
|
|
|
|
"arrowtail=none",
|
|
|
|
"constraint=false",
|
|
|
|
'color="red"',
|
|
|
|
'labelfontsize=4',
|
|
|
|
]
|
2009-12-05 20:45:51 +00:00
|
|
|
if XTRA_INFO:
|
2024-04-27 06:06:51 +00:00
|
|
|
label = "{:s}\n{:s}".format(constraint.type, constraint.name)
|
|
|
|
opts.append('label="{:s}"'.format(compat_str(label)))
|
|
|
|
fw('"{:s}" -> "{:s}" [{:s}] ;\n'.format(pbone.name, subtarget, ','.join(opts)))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
# Drivers
|
2009-12-01 22:45:56 +00:00
|
|
|
if DRIVERS:
|
2009-12-02 10:32:39 +00:00
|
|
|
fw('\n\n# Drivers:\n')
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
def rna_path_as_pbone(rna_path):
|
|
|
|
if not rna_path.startswith("pose.bones["):
|
|
|
|
return None
|
2009-12-01 12:02:23 +00:00
|
|
|
|
2023-03-01 11:12:18 +00:00
|
|
|
# rna_path_bone = rna_path[:rna_path.index("]") + 1]
|
2018-07-03 04:27:53 +00:00
|
|
|
# return obj.path_resolve(rna_path_bone)
|
2009-12-01 22:45:56 +00:00
|
|
|
bone_name = rna_path.split("[")[1].split("]")[0]
|
|
|
|
return obj.pose.bones[bone_name[1:-1]]
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
animation_data = obj.animation_data
|
|
|
|
if animation_data:
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-02 02:56:22 +00:00
|
|
|
fcurve_drivers = [fcurve_driver for fcurve_driver in animation_data.drivers]
|
2009-12-10 22:23:09 +00:00
|
|
|
fcurve_drivers.sort(key=lambda fcurve_driver: fcurve_driver.data_path)
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-02 02:56:22 +00:00
|
|
|
for fcurve_driver in fcurve_drivers:
|
2009-12-10 22:23:09 +00:00
|
|
|
rna_path = fcurve_driver.data_path
|
2009-12-01 22:45:56 +00:00
|
|
|
pbone = rna_path_as_pbone(rna_path)
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 22:45:56 +00:00
|
|
|
if pbone:
|
2010-04-30 05:45:02 +00:00
|
|
|
for var in fcurve_driver.driver.variables:
|
|
|
|
for target in var.targets:
|
|
|
|
pbone_target = rna_path_as_pbone(target.data_path)
|
|
|
|
rna_path_target = target.data_path
|
|
|
|
if pbone_target:
|
2020-10-02 00:15:51 +00:00
|
|
|
opts = [
|
|
|
|
'dir=forward',
|
|
|
|
"weight=1",
|
|
|
|
"arrowhead=normal",
|
|
|
|
"arrowtail=none",
|
|
|
|
"constraint=false",
|
|
|
|
'color="blue"',
|
|
|
|
"labelfontsize=4",
|
|
|
|
]
|
2010-04-30 05:45:02 +00:00
|
|
|
display_source = rna_path.replace("pose.bones", "")
|
|
|
|
display_target = rna_path_target.replace("pose.bones", "")
|
|
|
|
if XTRA_INFO:
|
2024-04-27 06:06:51 +00:00
|
|
|
label = "{:s}\\n{:s}".format(display_source, display_target)
|
|
|
|
opts.append('label="{:s}"'.format(compat_str(label)))
|
|
|
|
fw('"{:s}" -> "{:s}" [{:s}] ;\n'.format(pbone_target.name, pbone.name, ','.join(opts)))
|
2009-12-05 22:03:07 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
fw(footer)
|
2009-12-05 22:03:07 +00:00
|
|
|
fileobject.close()
|
|
|
|
|
2009-12-02 10:32:39 +00:00
|
|
|
'''
|
2012-06-19 22:17:19 +00:00
|
|
|
print(".", end="")
|
2009-12-01 12:02:23 +00:00
|
|
|
import sys
|
|
|
|
sys.stdout.flush()
|
2009-12-02 10:32:39 +00:00
|
|
|
'''
|
2010-06-14 03:52:10 +00:00
|
|
|
print("\nSaved:", filepath)
|
2009-12-02 10:32:39 +00:00
|
|
|
return True
|
2009-12-01 12:02:23 +00:00
|
|
|
|
2018-07-03 04:27:53 +00:00
|
|
|
|
2009-12-01 12:02:23 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
import os
|
2009-12-05 22:03:07 +00:00
|
|
|
tmppath = "/tmp/test.dot"
|
|
|
|
graph_armature(bpy.context.object, tmppath, CONSTRAINTS=True, DRIVERS=True)
|
2024-04-27 06:06:51 +00:00
|
|
|
os.system("dot -Tpng {:s} > {:s}; eog {:s} &".format(tmppath, tmppath + '.png', tmppath + '.png'))
|