1 # ***** BEGIN GPL LICENSE BLOCK *****
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # Contributor(s): Campbell Barton
19 # ***** END GPL LICENSE BLOCK *****
26 def build_property_typemap(skip_classes):
30 for attr in dir(bpy.types):
31 cls = getattr(bpy.types, attr)
32 if issubclass(cls, skip_classes):
35 properties = cls.bl_rna.properties.keys()
36 properties.remove("rna_type")
37 property_typemap[attr] = properties
39 return property_typemap
46 def rna2xml(fw=print_ln,
48 root_rna=None, # must be set
51 skip_classes=(bpy.types.Operator,
59 from xml.sax.saxutils import quoteattr
60 property_typemap = build_property_typemap(skip_classes)
62 def number_to_str(val, val_type):
65 elif val_type == float:
67 elif val_type == bool:
68 return "TRUE" if val else "FALSE"
70 raise NotImplemented("this type is not a number %s" % val_type)
72 def rna2xml_node(ident, value, parent):
73 ident_next = ident + ident_val
75 # divide into attrs and nodes.
80 value_type = type(value)
82 if issubclass(value_type, skip_classes):
85 # XXX, fixme, pointcache has eternal nested pointer to its self.
89 value_type_name = value_type.__name__
90 for prop in property_typemap[value_type_name]:
92 subvalue = getattr(value, prop)
93 subvalue_type = type(subvalue)
95 if subvalue_type in (int, bool, float):
96 node_attrs.append("%s=\"%s\"" % (prop, number_to_str(subvalue, subvalue_type)))
97 elif subvalue_type is str:
98 node_attrs.append("%s=%s" % (prop, quoteattr(subvalue)))
99 elif subvalue_type == set:
100 node_attrs.append("%s=%s" % (prop, quoteattr("{" + ",".join(list(subvalue)) + "}")))
101 elif subvalue is None:
102 node_attrs.append("%s=\"NONE\"" % prop)
103 elif issubclass(subvalue_type, bpy.types.ID):
104 # special case, ID's are always referenced.
105 node_attrs.append("%s=%s" % (prop, quoteattr(subvalue_type.__name__ + "::" + subvalue.name)))
108 subvalue_ls = list(subvalue)
112 if subvalue_ls is None:
113 nodes_items.append((prop, subvalue, subvalue_type))
115 # check if the list contains native types
116 subvalue_rna = value.path_resolve(prop, False)
117 if type(subvalue_rna).__name__ == "bpy_prop_array":
118 # check if this is a 0-1 color (rgb, rgba)
119 # in that case write as a hexidecimal
120 prop_rna = value.bl_rna.properties[prop]
121 if (prop_rna.subtype == 'COLOR_GAMMA' and
122 prop_rna.hard_min == 0.0 and
123 prop_rna.hard_max == 1.0 and
124 prop_rna.array_length in {3, 4}):
127 array_value = "#" + "".join(("%.2x" % int(v * 255) for v in subvalue_rna))
131 def str_recursive(s):
132 subsubvalue_type = type(s)
133 if subsubvalue_type in (int, float, bool):
134 return number_to_str(s, subsubvalue_type)
136 return " ".join([str_recursive(si) for si in s])
138 array_value = " ".join(str_recursive(v) for v in subvalue_rna)
140 node_attrs.append("%s=\"%s\"" % (prop, array_value))
142 nodes_lists.append((prop, subvalue_ls, subvalue_type))
144 # declare + attributes
146 tmp_str = "<%s " % value_type_name
147 tmp_ident = "\n" + ident + (" " * len(tmp_str))
149 fw("%s%s%s>\n" % (ident, tmp_str, tmp_ident.join(node_attrs)))
154 fw("%s<%s %s>\n" % (ident, value_type_name, " ".join(node_attrs)))
157 for prop, subvalue, subvalue_type in nodes_items:
158 fw("%s<%s>\n" % (ident_next, prop)) # XXX, this is awkward, how best to solve?
159 rna2xml_node(ident_next + ident_val, subvalue, value)
160 fw("%s</%s>\n" % (ident_next, prop)) # XXX, need to check on this.
163 for prop, subvalue, subvalue_type in nodes_lists:
164 fw("%s<%s>\n" % (ident_next, prop))
165 for subvalue_item in subvalue:
166 if subvalue_item is not None:
167 rna2xml_node(ident_next + ident_val, subvalue_item, value)
168 fw("%s</%s>\n" % (ident_next, prop))
170 fw("%s</%s>\n" % (ident, value_type_name))
172 # -------------------------------------------------------------------------
173 # needs re-workign to be generic
176 fw("<%s>\n" % root_node)
180 for attr in dir(root_rna):
183 if attr.startswith("_"):
185 elif attr in root_rna_skip:
188 value = getattr(root_rna, attr)
195 fw("%s<%s>\n" % (ident_val, attr))
197 rna2xml_node(ident_val + ident_val, blend_id, None)
198 fw("%s</%s>\n" % (ident_val, attr))
200 elif method == 'ATTR':
201 rna2xml_node("", root_rna, None)
204 fw("</%s>\n" % root_node)
207 def xml2rna(root_xml,
208 root_rna=None, # must be set
211 def rna2xml_node(xml_node, value):
212 # print("evaluating:", xml_node.nodeName)
214 # ---------------------------------------------------------------------
217 for attr in xml_node.attributes.keys():
219 subvalue = getattr(value, attr, Ellipsis)
221 if subvalue is Ellipsis:
222 print("%s.%s not found" % (type(value).__name__, attr))
224 value_xml = xml_node.attributes[attr].value
226 subvalue_type = type(subvalue)
228 if subvalue_type == float:
229 value_xml_coerce = float(value_xml)
231 elif subvalue_type == int:
232 value_xml_coerce = int(value_xml)
234 elif subvalue_type == bool:
235 value_xml_coerce = {'TRUE': True, 'FALSE': False}[value_xml]
237 elif subvalue_type == str:
238 value_xml_coerce = value_xml
240 elif hasattr(subvalue, "__len__"):
241 if value_xml.startswith("#"):
242 # read hexidecimal value as float array
243 value_xml_split = value_xml[1:]
244 value_xml_coerce = [int(value_xml_split[i:i + 2], 16) / 255 for i in range(0, len(value_xml_split), 2)]
247 value_xml_split = value_xml.split()
249 value_xml_coerce = [int(v) for v in value_xml_split]
251 value_xml_coerce = [float(v) for v in value_xml_split]
255 # print(" %s.%s (%s) --- %s" % (type(value).__name__, attr, tp_name, subvalue_type))
256 setattr(value, attr, value_xml_coerce)
258 # ---------------------------------------------------------------------
260 for child_xml in xml_node.childNodes:
261 if child_xml.nodeType == child_xml.ELEMENT_NODE:
263 # print(child_xml.nodeName)
264 subvalue = getattr(value, child_xml.nodeName, None)
265 if subvalue is not None:
268 for child_xml_real in child_xml.childNodes:
269 if child_xml_real.nodeType == child_xml_real.ELEMENT_NODE:
270 elems.append(child_xml_real)
273 if hasattr(subvalue, "__len__"):
275 if len(elems) != len(subvalue):
276 print("Size Mismatch! collection:", child_xml.nodeName)
278 for i in range(len(elems)):
279 child_xml_real = elems[i]
280 subsubvalue = subvalue[i]
282 if child_xml_real is None or subsubvalue is None:
283 print("None found %s - %d collection:", (child_xml.nodeName, i))
285 rna2xml_node(child_xml_real, subsubvalue)
291 # sub node named by its type
292 child_xml_real, = elems
294 # print(child_xml_real, subvalue)
295 rna2xml_node(child_xml_real, subvalue)
300 rna2xml_node(root_xml, root_rna)
304 # -----------------------------------------------------------------------------
305 # Utility function used by presets.
306 # The idea is you can run a preset like a script with a few args.
308 # This roughly matches the operator 'bpy.ops.script.python_file_run'
310 def _get_context_val(context, path):
311 path_full = "context." + path
313 value = eval(path_full)
316 traceback.print_exc()
317 print("Error: %r could not be found" % path_full)
323 def xml_file_run(context, filepath, rna_map):
325 import xml.dom.minidom
327 xml_nodes = xml.dom.minidom.parse(filepath)
328 bpy_xml = xml_nodes.getElementsByTagName("bpy")[0]
330 for rna_path, xml_tag in rna_map:
334 xml_node = bpy_xml.getElementsByTagName(xml_tag)[0]
336 value = _get_context_val(context, rna_path)
338 if value is not Ellipsis and value is not None:
339 print(" loading XML: %r" % rna_path)
340 xml2rna(xml_node, root_rna=value)
343 def xml_file_write(context, filepath, rna_map):
345 file = open(filepath, 'w', encoding='utf-8')
350 for rna_path, xml_tag in rna_map:
351 # xml_tag is ignored, we get this from the rna
352 value = _get_context_val(context, rna_path)
353 rna2xml(fw, root_rna=value, method='ATTR')