UI: Move Extensions repositories popover to header
[blender-addons-contrib.git] / render_cube_map.py
blob363bc02d40ca2d69d5e7427dae470bd60886fcc9
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 # ======================= END GPL LICENSE BLOCK ========================
19 # ########################################
20 # Render Cube Map
22 # Dalai Felinto
23 # --
24 # blendernetwork.org/dalai-felinto
25 # www.dalaifelinto.com
27 # Original code:
28 # Rio de Janeiro, September 2015
30 # Latest update:
31 # Rio de Janeiro, July 2016
32 # ########################################
34 import bpy
36 from bpy.app.handlers import persistent
38 from bpy.types import (
39 Operator,
40 Panel,
43 from bpy.props import (
44 BoolProperty,
47 bl_info = {
48 "name": "Cube Map",
49 "author": "Dalai Felinto",
50 "version": (1, 0),
51 "blender": (2, 80, 0),
52 "location": "Render Panel",
53 "description": "",
54 "warning": "Needs Updating",
55 "doc_url": "https://github.com/dfelinto/render_cube_map",
56 "tracker_url": "",
57 "category": "Render"}
60 # ############################################################
61 # Global Check
62 # ############################################################
64 def do_run(cube_map, use_force):
65 if not (cube_map.use_cube_map or use_force):
66 return False
68 if cube_map.is_enabled and not use_force:
69 return False
71 return True
74 # ############################################################
75 # Callbacks
76 # ############################################################
78 class NodeTree:
79 def __init__(self, scene):
80 self._use_nodes = scene.use_nodes
81 self._use_compositing = scene.render.use_compositing
83 self._nodes_mute = {}
84 self._scene = scene
86 self._scene.render.use_compositing = True
88 if not self._use_nodes:
89 scene.use_nodes = True
90 self._muteNodes()
91 else:
92 self._storeNodes()
93 self._muteNodes()
95 def _storeNodes(self):
96 """
97 store the existent nodes and if they are muted
98 """
99 nodes = self._scene.node_tree.nodes
100 for node in nodes:
101 self._nodes_mute[hash(node)] = node.mute
103 def _muteNodes(self):
105 mute all the existent nodes
107 nodes = self._scene.node_tree.nodes
108 for node in nodes:
109 node.mute = True
111 def cleanupScene(self):
113 remove all the new nodes, and unmute original ones
115 scene = self._scene
116 scene.use_nodes = self._use_nodes
117 scene.render.use_compositing = self._use_compositing
119 self._cleanNodes()
120 self._unMuteNodes()
122 def _cleanNodes(self):
124 remove all the nodes created temporarily
126 nodes = self._scene.node_tree.nodes
127 to_del = []
128 keys = self._nodes_mute.keys()
130 for node in nodes:
131 if hash(node) not in keys:
132 to_del.append(node)
134 for node in to_del:
135 nodes.remove(node)
137 def _unMuteNodes(self):
139 unmute all the existent nodes
141 nodes = self._scene.node_tree.nodes
142 for node in nodes:
143 node.mute = self._nodes_mute[hash(node)]
146 class View:
147 def __init__(self, name, euler_rotation):
148 self._name = name
149 self._collection = None
150 self._scene = None
151 self._scene_camera = None
152 self._node = None
153 self._camera = None
154 self._euler_rotation = euler_rotation
156 def setScene(self, scene):
157 scene.name = self._name
158 self._scene = scene
160 scene.cube_map.use_cube_map = False
161 scene.render.use_compositing = False
163 self._setFilepath()
165 def _setFilepath(self):
166 import os
168 filepath = self._scene.render.filepath
170 dirname = os.path.dirname(filepath)
171 basename = os.path.basename(filepath)
173 path = os.path.join(dirname, "{0}{1}".format(self._name, basename))
174 self._scene.render.filepath = path
176 def setNode(self, node, links, node_output):
177 node.name = self._name
178 node.label = self._name
179 node.scene = self._scene
180 self._node = node
182 # TODO if there were nodetrees, duplicate them here
184 # connect to output
185 _input = node_output.layer_slots.new(self._name)
186 links.new(node.outputs[0], _input)
188 def setCamera(self, data, loc, zed):
189 self._scene_camera = self._scene.camera
191 self._camera = bpy.data.objects.new(self._name, data)
192 self._collection.objects.link(self._camera)
194 rotation = self._euler_rotation.copy()
195 rotation.z += zed
197 self._camera.rotation_euler = rotation
198 self._camera.location = loc
200 # change scene camera
201 self._scene.camera = self._camera
203 def resetCamera(self):
204 self._collection.objects.unlink(self._camera)
205 bpy.data.objects.remove(self._camera)
206 self._camera = None
208 @property
209 def scene(self):
210 return self._scene
212 @property
213 def name(self):
214 return self._name
217 @persistent
218 def cube_map_render_init(scene, use_force=False):
220 setup the cube map settings for all the render frames
222 from mathutils import Euler
223 from math import pi
224 half_pi = pi * 0.5
226 cube_map = scene.cube_map
228 if not do_run(cube_map, use_force):
229 return
231 main_scene = scene
232 hashes = [hash(scene) for scene in bpy.data.scenes]
234 views_raw = (
236 'NORTH_',
237 Euler((half_pi, 0.0, 0.0)),
238 cube_map.use_view_north,
241 'SOUTH_',
242 Euler((half_pi, 0.0, pi)),
243 cube_map.use_view_south,
246 'WEST_',
247 Euler((half_pi, 0.0, half_pi)),
248 cube_map.use_view_west,
251 'EAST_',
252 Euler((half_pi, 0.0, -half_pi)),
253 cube_map.use_view_east,
256 'ZENITH_',
257 Euler((pi, 0.0, 0.0)),
258 cube_map.use_view_zenith,
261 'NADIR_',
262 Euler((0.0, 0.0, 0.0)),
263 cube_map.use_view_nadir,
267 views = [
268 View(name, euler) for (name, euler, use) in views_raw
269 if use or not cube_map.is_advanced]
271 for view in views:
272 # create a scene per view
273 # XXX : line below crashes Blender 2.80
274 bpy.ops.scene.new(type='LINK_COPY')
275 scene = [
276 scene for scene in bpy.data.scenes if
277 hash(scene) not in hashes][0]
279 # mark the scene to remove it afterwards
280 scene.cube_map.is_temporary = True
282 hashes.append(hash(scene))
283 view.setScene(scene)
284 # have Dalai to look at this?
285 view._collection = bpy.context.collection # XXX TODO better fix
287 # create a scene from scratch
288 node_tree_data = NodeTree(main_scene)
290 # created the necessary nodetrees there
291 node_tree = main_scene.node_tree
293 # output node
294 node_output = node_tree.nodes.new('CompositorNodeOutputFile')
295 node_output.inputs.clear()
297 for view in views:
298 node = node_tree.nodes.new('CompositorNodeRLayers')
299 view.setNode(node, node_tree.links, node_output)
301 # globals
302 bpy.cube_map_node_tree_data = node_tree_data
303 bpy.cube_map_views = views
306 # ############################################################
307 # Cameras Setup
308 # ############################################################
310 @persistent
311 def cube_map_render_pre(scene, use_force=False):
313 if not do_run(scene.cube_map, use_force):
314 return
316 from math import radians
318 camera = scene.camera
319 data = camera.data.copy()
321 data.lens_unit = 'FOV'
322 data.angle = radians(90)
323 data.type = 'PERSP'
325 mat = camera.matrix_world
327 loc = mat.to_translation()
328 rot = mat.to_euler()
329 zed = rot.z
331 views = bpy.cube_map_views
333 for view in views:
334 view.setCamera(data, loc, zed)
337 @persistent
338 def cube_map_render_post(scene, use_force=False):
340 if not do_run(scene.cube_map, use_force):
341 return
343 views = bpy.cube_map_views
345 for view in views:
346 view.resetCamera()
349 # ############################################################
350 # Clean-Up
351 # ############################################################
353 @persistent
354 def cube_map_render_cancel(scene):
355 cube_map_cleanup(scene)
358 @persistent
359 def cube_map_render_complete(scene):
360 cube_map_cleanup(scene)
363 def cube_map_cleanup(scene, use_force=False):
365 remove all the temporary data created for the cube map
368 if not do_run(scene.cube_map, use_force):
369 return
371 bpy.cube_map_node_tree_data.cleanupScene()
372 del bpy.cube_map_node_tree_data
373 del bpy.cube_map_views
375 bpy.app.handlers.scene_update_post.append(cube_map_post_update_cleanup)
378 def cube_map_post_update_cleanup(scene):
380 delay removal of scenes (otherwise we get a crash)
382 scenes_temp = [
383 scene for scene in bpy.data.scenes if
384 scene.cube_map.is_temporary]
386 if not scenes_temp:
387 bpy.app.handlers.scene_update_post.remove(cube_map_post_update_cleanup)
389 else:
390 scenes_temp[0].user_clear()
391 try:
392 bpy.data.scenes.remove(scenes_temp[0], do_unlink=False)
393 except TypeError:
394 bpy.data.scenes.remove(scenes_temp[0])
397 # ############################################################
398 # Setup Operator
399 # ############################################################
401 class CubeMapSetup(Operator):
402 """"""
403 bl_idname = "render.cube_map_setup"
404 bl_label = "Cube Map Render Setup"
405 bl_description = ""
407 action: bpy.props.EnumProperty(
408 description="",
409 items=(("SETUP", "Setup", "Created linked scenes and setup cube map"),
410 ("RESET", "Reset", "Delete added scenes"),
412 default="SETUP",
413 options={'SKIP_SAVE'},
416 @classmethod
417 def poll(cls, context):
418 return True
420 def setup(self, window, scene):
421 cube_map = scene.cube_map
422 cube_map.is_enabled = True
424 cube_map_render_init(scene, use_force=True)
425 cube_map_render_pre(scene, use_force=True)
427 # set initial scene back as the main scene
428 window.screen.scene = scene
430 def reset(self, scene):
431 cube_map = scene.cube_map
432 cube_map.is_enabled = False
434 cube_map_render_post(scene, use_force=True)
435 cube_map_cleanup(scene, use_force=True)
437 def invoke(self, context, event):
438 scene = context.scene
439 cube_map = scene.cube_map
441 is_enabled = cube_map.is_enabled
443 if self.action == 'RESET':
445 if is_enabled:
446 if cube_map.is_temporary:
447 self.report(
448 {'ERROR'},
449 "Cannot reset cube map from one of "
450 "the created scenes")
452 return {'CANCELLED'}
453 else:
454 self.reset(scene)
455 return {'FINISHED'}
456 else:
457 self.report({'ERROR'}, "Cube Map render is not setup")
458 return {'CANCELLED'}
460 else: # SETUP
461 if is_enabled:
462 self.report({'ERROR'}, "Cube Map render is already setup")
463 return {'CANCELLED'}
464 else:
465 self.setup(context.window, scene)
466 return {'FINISHED'}
469 # ############################################################
470 # User Interface
471 # ############################################################
473 class RENDER_PT_cube_map(Panel):
474 bl_space_type = 'PROPERTIES'
475 bl_region_type = 'WINDOW'
476 bl_context = "render"
477 bl_label = "Cube Map"
479 @classmethod
480 def poll(cls, context):
481 scene = context.scene
482 return scene and (scene.render.engine != 'BLENDER_GAME')
484 def draw_header(self, context):
485 self.layout.prop(context.scene.cube_map, "use_cube_map", text="")
487 def draw(self, context):
488 layout = self.layout
489 col = layout.column()
491 scene = context.scene
492 cube_map = scene.cube_map
494 if not cube_map.is_enabled:
495 col.operator(
496 "render.cube_map_setup",
497 text="Scene Setup").action = 'SETUP'
498 else:
499 col.operator(
500 "render.cube_map_setup",
501 text="Scene Reset", icon="X").action = 'RESET'
503 col = layout.column()
504 col.active = cube_map.use_cube_map
505 col.prop(cube_map, "is_advanced")
506 if cube_map.is_advanced:
507 box = col.box()
508 box.active = cube_map.use_cube_map and cube_map.is_advanced
509 row = box.row()
510 row.prop(cube_map, "use_view_north")
511 row.prop(cube_map, "use_view_west")
512 row.prop(cube_map, "use_view_zenith")
514 row = box.row()
515 row.prop(cube_map, "use_view_south")
516 row.prop(cube_map, "use_view_east")
517 row.prop(cube_map, "use_view_nadir")
520 # ############################################################
521 # Scene Properties
522 # ############################################################
524 class CubeMapInfo(bpy.types.PropertyGroup):
525 use_cube_map: BoolProperty(
526 name="Cube Map",
527 default=False,
530 is_temporary: BoolProperty(
531 name="Temporary",
532 default=False,
535 is_enabled: BoolProperty(
536 name="Enabled",
537 default=False,
540 # per view settings
541 is_advanced: BoolProperty(
542 name="Advanced",
543 default=False,
544 description="Decide which views to render",
547 use_view_north: BoolProperty(
548 name="North",
549 default=True,
552 use_view_south: BoolProperty(
553 name="South",
554 default=True,
557 use_view_west: BoolProperty(
558 name="West",
559 default=True,
562 use_view_east: BoolProperty(
563 name="East",
564 default=True,
567 use_view_zenith: BoolProperty(
568 name="Zenith",
569 default=True,
572 use_view_nadir: BoolProperty(
573 name="Nadir",
574 default=True,
578 # ############################################################
579 # Un/Registration
580 # ############################################################
582 def register():
583 bpy.utils.register_class(CubeMapInfo)
584 bpy.utils.register_class(CubeMapSetup)
585 bpy.types.Scene.cube_map = bpy.props.PointerProperty(
586 name="cube_map",
587 type=CubeMapInfo,
588 options={'HIDDEN'},
591 bpy.utils.register_class(RENDER_PT_cube_map)
593 bpy.app.handlers.render_init.append(cube_map_render_init)
594 bpy.app.handlers.render_pre.append(cube_map_render_pre)
595 bpy.app.handlers.render_post.append(cube_map_render_post)
596 bpy.app.handlers.render_cancel.append(cube_map_render_cancel)
597 bpy.app.handlers.render_complete.append(cube_map_render_complete)
600 def unregister():
601 bpy.utils.unregister_class(CubeMapInfo)
602 bpy.utils.unregister_class(CubeMapSetup)
603 bpy.utils.unregister_class(RENDER_PT_cube_map)
605 bpy.app.handlers.render_init.remove(cube_map_render_init)
606 bpy.app.handlers.render_pre.remove(cube_map_render_pre)
607 bpy.app.handlers.render_post.remove(cube_map_render_post)
608 bpy.app.handlers.render_cancel.remove(cube_map_render_cancel)
609 bpy.app.handlers.render_complete.remove(cube_map_render_complete)
611 del bpy.types.Scene.cube_map
614 if __name__ == '__main__':
615 register()