Patch #21011: Tweaks to Sky/Atmosphere presets
[blender.git] / release / scripts / modules / graphviz_export.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8 compliant>
20
21 import bpy
22
23 header = '''
24 digraph ancestors           {
25 graph [fontsize=30 labelloc="t" label="" splines=false overlap=true, rankdir=BT];
26 ratio = "auto" ;
27 '''
28
29 footer = '''
30 }
31 '''
32
33
34 def compat_str(text, line_length=0):
35
36     if line_length:
37         text_ls = []
38         while len(text) > line_length:
39             text_ls.append(text[:line_length])
40             text = text[line_length:]
41
42         if text:
43             text_ls.append(text)
44         text = '\n  '.join(text_ls)
45
46
47     #text = text.replace('.', '.\n')
48     #text = text.replace(']', ']\n')
49     text = text.replace("\n", "\\n")
50     text = text.replace('"', '\\"')
51     return text
52
53
54 def graph_armature(obj, path, FAKE_PARENT=True, CONSTRAINTS=True, DRIVERS=True, XTRA_INFO=True):
55     CONSTRAINTS = DRIVERS = True
56
57     fileobject = open(path, "w")
58     fw = fileobject.write
59     fw(header)
60     fw('label = "%s::%s" ;' % (bpy.data.filename.split("/")[-1].split("\\")[-1], obj.name))
61
62     arm = obj.data
63
64     bones = [bone.name for bone in arm.bones]
65     bones.sort()
66     print("")
67     for bone in bones:
68         b = arm.bones[bone]
69         print(">>", bone, ["*>", "->"][b.connected], getattr(getattr(b, "parent", ""), "name", ""))
70         label = [bone]
71         bone = arm.bones[bone]
72
73         for key, value in obj.pose.bones[bone.name].items():
74             if key.startswith("_"):
75                 continue
76
77             if type(value) == float:
78                 value = "%.3f" % value
79             elif type(value) == str:
80                 value = compat_str(value)
81
82             label.append("%s = %s" % (key, value))
83
84         opts = ["shape=box", "regular=1", "style=filled", "fixedsize=false", 'label="%s"' % compat_str('\n'.join(label))]
85
86         if bone.name.startswith('ORG'):
87             opts.append("fillcolor=yellow")
88         else:
89             opts.append("fillcolor=white")
90
91
92         fw('"%s" [%s];\n' % (bone.name, ','.join(opts)))
93
94     fw('\n\n# Hierarchy:\n')
95
96     # Root node.
97     if FAKE_PARENT:
98         fw('"Object::%s" [];\n' % obj.name)
99
100     for bone in bones:
101         bone = arm.bones[bone]
102
103         parent = bone.parent
104         if parent:
105             parent_name = parent.name
106             connected = bone.connected
107         elif FAKE_PARENT:
108             parent_name = 'Object::%s' % obj.name
109             connected = False
110         else:
111             continue
112
113         opts = ["dir=forward", "weight=2", "arrowhead=normal"]
114         if not connected:
115             opts.append("style=dotted")
116
117         fw('"%s" -> "%s" [%s] ;\n' % (bone.name, parent_name, ','.join(opts)))
118     del bone
119
120     # constraints
121     if CONSTRAINTS:
122         fw('\n\n# Constraints:\n')
123         for bone in bones:
124             pbone = obj.pose.bones[bone]
125             # must be ordered
126             for constraint in pbone.constraints:
127                 subtarget = getattr(constraint, "subtarget", "")
128                 if subtarget:
129                     # TODO, not internal links
130                     opts = ['dir=forward', "weight=1", "arrowhead=normal", "arrowtail=none", "constraint=false", 'color="red"', 'labelfontsize=4']
131                     if XTRA_INFO:
132                         label = "%s\n%s" % (constraint.type, constraint.name)
133                         opts.append('label="%s"' % compat_str(label))
134                     fw('"%s" -> "%s" [%s] ;\n' % (pbone.name, subtarget, ','.join(opts)))
135
136     # Drivers
137     if DRIVERS:
138         fw('\n\n# Drivers:\n')
139
140         def rna_path_as_pbone(rna_path):
141             if not rna_path.startswith("pose.bones["):
142                 return None
143
144             #rna_path_bone = rna_path[:rna_path.index("]") + 1]
145             #return obj.path_resolve(rna_path_bone)
146             bone_name = rna_path.split("[")[1].split("]")[0]
147             return obj.pose.bones[bone_name[1:-1]]
148
149         animation_data = obj.animation_data
150         if animation_data:
151
152             fcurve_drivers = [fcurve_driver for fcurve_driver in animation_data.drivers]
153             fcurve_drivers.sort(key=lambda fcurve_driver: fcurve_driver.data_path)
154
155             for fcurve_driver in fcurve_drivers:
156                 rna_path = fcurve_driver.data_path
157                 pbone = rna_path_as_pbone(rna_path)
158
159                 if pbone:
160                     for target in fcurve_driver.driver.targets:
161                         pbone_target = rna_path_as_pbone(target.data_path)
162                         rna_path_target = target.data_path
163                         if pbone_target:
164                             opts = ['dir=forward', "weight=1", "arrowhead=normal", "arrowtail=none", "constraint=false", 'color="blue"', "labelfontsize=4"] # ,
165                             display_source = rna_path.replace("pose.bones", "")
166                             display_target = rna_path_target.replace("pose.bones", "")
167                             if XTRA_INFO:
168                                 label = "%s\\n%s" % (display_source, display_target)
169                                 opts.append('label="%s"' % compat_str(label))
170                             fw('"%s" -> "%s" [%s] ;\n' % (pbone_target.name, pbone.name, ','.join(opts)))
171
172     fw(footer)
173     fileobject.close()
174
175     '''
176     print(".", end='')
177     import sys
178     sys.stdout.flush()
179     '''
180     print("\nSaved:", path)
181     return True
182
183 if __name__ == "__main__":
184     import os
185     tmppath = "/tmp/test.dot"
186     graph_armature(bpy.context.object, tmppath, CONSTRAINTS=True, DRIVERS=True)
187     os.system("dot -Tpng %s > %s; eog %s &" % (tmppath, tmppath + '.png', tmppath + '.png'))