]> git.tdb.fi Git - libs/gl.git/blob - blender/io_mspgl/export_scene.py
Further refactoring of instance handling in the Blender exporter
[libs/gl.git] / blender / io_mspgl / export_scene.py
1 import math
2 import os
3 import itertools
4 import mathutils
5
6 class SceneExporter:
7         def export_to_file(self, ctx, out_fn, *, selected_only=False, visible_only=True, collection=True, skip_existing=True):
8                 from .scene import create_scene_from_current
9                 task = ctx.task("Preparing scene", 0.1)
10                 scene = create_scene_from_current(task, selected_only=selected_only, visible_only=visible_only)
11
12                 resources = {}
13                 task = ctx.task("Exporting resources", 0.9)
14                 self.export_scene_resources(task, scene, resources)
15                 task = ctx.task(scene, 1.0)
16                 scene_res = self.export_scene(scene, resources)
17
18                 path, base = os.path.split(out_fn)
19                 base, ext = os.path.splitext(base)
20
21                 task = ctx.task("Writing files", 1.0)
22                 if collection:
23                         existing = None
24                         if skip_existing:
25                                 existing = lambda r: not os.path.exists(os.path.join(path, r.name))
26                         scene_res.write_collection(out_fn, filter=existing)
27                 else:
28                         scene_res.write_to_file(out_fn)
29                         for r in scene_res.collect_references():
30                                 r.write_to_file(os.path.join(path, r.name))
31
32         def export_scene_resources(self, ctx, scene, resources):
33                 from .export import DataExporter
34                 data_exporter = DataExporter()
35
36                 data_exporter.export_resources(ctx, [p.object for p in scene.prototypes], resources)
37
38         def export_scene(self, scene, resources):
39                 from .datafile import Resource, Statement, Token
40                 scene_res = Resource(scene.name+".scene", "scene")
41
42                 if scene.background_set or (scene.instances and scene.blended_instances):
43                         scene_res.statements.append(Statement("type", Token("ordered")))
44                         if scene.background_set:
45                                 scene_res.statements.append(scene_res.create_reference_statement("scene", resources[scene.background_set.name+".scene"]))
46
47                         if scene.instances:
48                                 st = Statement("scene")
49                                 st.sub.append(Statement("type", Token("simple")))
50                                 self.add_instances(scene_res, st.sub, scene.instances, resources)
51                                 scene_res.statements.append(st)
52
53                         if scene.blended_instances:
54                                 st = Statement("scene")
55                                 st.sub.append(Statement("type", Token("zsorted")))
56                                 self.add_instances(scene_res, st.sub, scene.blended_instances, resources)
57                                 scene_res.statements.append(st)
58                 else:
59                         scene_type = "zsorted" if scene.blended_instances else "simple"
60                         scene_res.statements.append(Statement("type", Token(scene_type)))
61
62                         self.add_instances(scene_res, scene_res.statements, scene.instances, resources)
63                         self.add_instances(scene_res, scene_res.statements, scene.blended_instances, resources)
64
65                 return scene_res
66
67         def add_instances(self, scene_res, statements, instances, resources):
68                 from .datafile import Statement
69
70                 for i in instances:
71                         obj_res = resources[i.prototype.name+".object"]
72                         st = scene_res.create_reference_statement("object", obj_res)
73                         if i.name:
74                                 st.append(i.name)
75
76                         st.sub.append(self.create_transform_statement(i))
77                         statements.append(st)
78
79         def create_transform_statement(self, instance):
80                 from .datafile import Statement
81
82                 st = Statement("transform")
83
84                 loc = instance.matrix_world.to_translation()
85                 st.sub.append(Statement("position", *tuple(loc)))
86
87                 quat = instance.matrix_world.to_quaternion()
88                 if instance.rotation_mode in ('XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX'):
89                         angles = [a*180/math.pi for a in quat.to_euler()]
90                         st.sub.append(Statement("euler", *angles));
91                 else:
92                         st.sub.append(Statement("rotation", quat.angle*180/math.pi, *tuple(quat.axis)))
93
94                 scale = instance.matrix_world.to_scale()
95                 st.sub.append(Statement("scale", *tuple(scale)))
96
97                 return st
98
99         def export_sequence_resources(self, scene, resources):
100                 from .datafile import Resource, Statement, Token
101
102                 lights = []
103                 s = scene
104                 while s:
105                         lights += s.lights
106                         s = s.background_set
107
108                 from .util import make_unique
109                 lights = make_unique(lights)
110
111                 from .export_light import LightExporter
112                 light_exporter = LightExporter()
113                 for l in lights:
114                         light_name = l.name+".light"
115                         if light_name not in resources:
116                                 resources[light_name] = light_exporter.export_light(l)
117
118                 lighting_name = scene.name+".lightn"
119                 if lighting_name not in resources:
120                         lighting_res = Resource(lighting_name, "lighting")
121                         lighting_res.statements.append(Statement("ambient", *tuple(scene.ambient_light)))
122                         for l in lights:
123                                 lighting_res.statements.append(lighting_res.create_reference_statement("light", resources[l.name+".light"]))
124
125                         resources[lighting_name] = lighting_res
126
127         def export_sequence(self, scene, resources):
128                 from .datafile import Resource, Statement, Token
129                 seq_res = Resource(scene.name+".seq", "sequence")
130
131                 if scene.use_hdr:
132                         seq_res.statements.append(Statement("hdr", True))
133
134                 self.add_clear(seq_res.statements, (0.0, 0.0, 0.0, 0.0), 1.0)
135
136                 scene_res = resources[scene.name+".scene"]
137                 seq_res.statements.append(seq_res.create_reference_statement("renderable", "content", scene_res))
138
139                 lighting_res = resources[scene.name+".lightn"]
140
141                 any_opaque = False
142                 any_blended = False
143                 use_ibl = False
144                 use_shadow = False
145                 shadowed_lights = []
146                 shadow_casters = []
147                 s = scene
148                 while s:
149                         if s.instances:
150                                 any_opaque = True
151                         if s.blended_instances:
152                                 any_blended = True
153                         if s.use_ibl:
154                                 use_ibl = True
155                         if s.use_shadow:
156                                 use_shadow = True
157                         shadowed_lights += [l.data for l in s.lights if l.data.use_shadow]
158                         for i in itertools.chain(s.instances, s.blended_instances):
159                                 o = i.prototype.object
160                                 if o.material_slots and o.material_slots[0].material and o.material_slots[0].material.shadow_method!='NONE':
161                                         shadow_casters.append(i)
162                         s = s.background_set
163
164                 shadowed_lights.sort(key=lambda l:l.shadow_map_size, reverse=True)
165
166                 main_tags = []
167                 if any_opaque:
168                         main_tags.append("")
169                 if any_blended:
170                         main_tags.append("blended")
171
172                 content = "content"
173                 if use_ibl and scene.use_sky:
174                         self.add_auxiliary_sequence(seq_res, "environment", "sky", ((0.0, 0.0, 0.0, 0.0), 1.0), main_tags, lighting_res)
175
176                         st = Statement("effect", "environment")
177                         st.sub.append(Statement("type", Token("environment_map")))
178                         st.sub.append(Statement("size", 32))
179                         st.sub.append(Statement("roughness_levels", 2))
180                         st.sub.append(Statement("fixed_position", 0.0, 0.0, 0.0))
181                         st.sub.append(Statement("content", content))
182                         st.sub.append(Statement("environment", "environment_sequence"))
183
184                         seq_res.statements.append(st)
185                         content = "environment"
186
187                 if scene.use_sky:
188                         st = Statement("effect", "sky")
189                         st.sub.append(Statement("type", Token("sky")))
190                         st.sub.append(seq_res.create_reference_statement("sun", resources[scene.sun_light.name+".light"]))
191                         st.sub.append(Statement("content", content))
192
193                         seq_res.statements.append(st)
194                         content = "sky"
195
196                 if use_shadow:
197                         self.add_auxiliary_sequence(seq_res, "shadow", "content", (None, 1.0), ["shadow"], None)
198                         self.add_auxiliary_sequence(seq_res, "thsm", "content", (None, 1.0), ["shadow_thsm"], None)
199
200                         st = Statement("effect", "shadow_map")
201                         st.sub.append(Statement("type", Token("shadow_map")))
202                         st.sub.append(Statement("enable_for_method", "blended"))
203                         st.sub.append(Statement("size", *self.compute_shadowmap_size(shadowed_lights)))
204                         target, radius = self.compute_bounding_sphere(shadow_casters)
205                         st.sub.append(Statement("target", *target))
206                         st.sub.append(Statement("radius", radius))
207                         st.sub.append(Statement("content", content))
208                         st.sub.append(seq_res.create_reference_statement("lighting", lighting_res))
209                         for l in shadowed_lights:
210                                 ss = seq_res.create_reference_statement("light", resources[l.name+".light"])
211                                 ss.sub.append(Statement("size", int(l.shadow_map_size)))
212                                 shadow_caster = "thsm_sequence" if l.type=='POINT' else "shadow_sequence"
213                                 ss.sub.append(Statement("shadow_caster", shadow_caster))
214                                 st.sub.append(ss)
215
216                         seq_res.statements.append(st)
217                         content = "shadow_map"
218
219                 self.add_content_steps(seq_res, content, lighting_res, main_tags)
220
221                 if scene.use_ao:
222                         ss = Statement("postprocessor")
223                         ss.sub.append(Statement("type", Token("ambient_occlusion")))
224                         ss.sub.append(Statement("occlusion_radius", scene.ao_distance))
225                         ss.sub.append(Statement("samples", scene.ao_samples))
226                         seq_res.statements.append(ss)
227
228                 if scene.use_hdr:
229                         ss = Statement("postprocessor")
230                         ss.sub.append(Statement("type", Token("bloom")))
231                         seq_res.statements.append(ss)
232
233                         ss = Statement("postprocessor")
234                         ss.sub.append(Statement("type", Token("colorcurve")))
235                         ss.sub.append(Statement("exposure_adjust", scene.exposure))
236                         ss.sub.append(Statement("srgb"))
237                         seq_res.statements.append(ss)
238                 else:
239                         # Add a colorcurve with linear response to convert into sRGB color space
240                         ss = Statement("postprocessor")
241                         ss.sub.append(Statement("type", Token("colorcurve")))
242                         ss.sub.append(Statement("brightness_response", 1.0))
243                         ss.sub.append(Statement("srgb"))
244                         seq_res.statements.append(ss)
245
246                 return seq_res
247
248         def add_clear(self, statements, color, depth):
249                 from .datafile import Statement
250
251                 st = Statement("clear")
252                 if color is not None:
253                         st.sub.append(Statement("color", *color))
254                 if depth is not None:
255                         st.sub.append(Statement("depth", depth))
256                 statements.append(st)
257
258         def add_content_steps(self, seq_res, renderable, lighting, tags):
259                 from .datafile import Statement, Token
260
261                 for t in tags:
262                         st = Statement("step", t, renderable)
263                         st.sub.append(Statement("depth_test", Token("LEQUAL")))
264                         if lighting:
265                                 st.sub.append(seq_res.create_reference_statement("lighting", lighting))
266                         seq_res.statements.append(st)
267
268         def add_auxiliary_sequence(self, seq_res, aux_name, content, clear_values, step_tags, lighting):
269                 seq_name = os.path.splitext(seq_res.name)[0]
270
271                 from .datafile import Resource, Statement
272                 aux_seq_res = Resource("{}_{}.seq".format(seq_name, aux_name), "sequence")
273                 self.add_clear(aux_seq_res.statements, *clear_values)
274                 aux_seq_res.statements.append(Statement("renderable", "content"))
275                 self.add_content_steps(aux_seq_res, "content", lighting, step_tags)
276
277                 st = seq_res.create_reference_statement("sequence", aux_name+"_sequence", aux_seq_res)
278                 st.sub.append(Statement("renderable", "content", content))
279                 seq_res.statements.append(st)
280
281         def compute_shadowmap_size(self, lights):
282                 total_area = 0
283                 for l in lights:
284                         s = int(l.shadow_map_size)
285                         total_area += s*s
286
287                 size = 1
288                 while size*size<total_area:
289                         size *= 2
290                 if size*size>total_area*2:
291                         return (size, size//2)
292                 else:
293                         return (size, size)
294
295         def compute_bounding_sphere(self, instances):
296                 points = []
297                 for i in instances:
298                         points += [i.matrix_world@mathutils.Vector(c) for c in i.prototype.object.bound_box]
299
300                 from .util import compute_bounding_sphere
301                 return compute_bounding_sphere(points)