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 # ########################################
24 # blendernetwork.org/dalai-felinto
25 # www.dalaifelinto.com
28 # Rio de Janeiro, September 2015
31 # Rio de Janeiro, July 2016
32 # ########################################
36 from bpy
.app
.handlers
import persistent
38 from bpy
.types
import (
43 from bpy
.props
import (
49 "author": "Dalai Felinto",
51 "blender": (2, 80, 0),
52 "location": "Render Panel",
54 "warning": "Needs Updating",
55 "doc_url": "https://github.com/dfelinto/render_cube_map",
60 # ############################################################
62 # ############################################################
64 def do_run(cube_map
, use_force
):
65 if not (cube_map
.use_cube_map
or use_force
):
68 if cube_map
.is_enabled
and not use_force
:
74 # ############################################################
76 # ############################################################
79 def __init__(self
, scene
):
80 self
._use
_nodes
= scene
.use_nodes
81 self
._use
_compositing
= scene
.render
.use_compositing
86 self
._scene
.render
.use_compositing
= True
88 if not self
._use
_nodes
:
89 scene
.use_nodes
= True
95 def _storeNodes(self
):
97 store the existent nodes and if they are muted
99 nodes
= self
._scene
.node_tree
.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
111 def cleanupScene(self
):
113 remove all the new nodes, and unmute original ones
116 scene
.use_nodes
= self
._use
_nodes
117 scene
.render
.use_compositing
= self
._use
_compositing
122 def _cleanNodes(self
):
124 remove all the nodes created temporarily
126 nodes
= self
._scene
.node_tree
.nodes
128 keys
= self
._nodes
_mute
.keys()
131 if hash(node
) not in keys
:
137 def _unMuteNodes(self
):
139 unmute all the existent nodes
141 nodes
= self
._scene
.node_tree
.nodes
143 node
.mute
= self
._nodes
_mute
[hash(node
)]
147 def __init__(self
, name
, euler_rotation
):
149 self
._collection
= None
151 self
._scene
_camera
= None
154 self
._euler
_rotation
= euler_rotation
156 def setScene(self
, scene
):
157 scene
.name
= self
._name
160 scene
.cube_map
.use_cube_map
= False
161 scene
.render
.use_compositing
= False
165 def _setFilepath(self
):
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
182 # TODO if there were nodetrees, duplicate them here
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()
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
)
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
226 cube_map
= scene
.cube_map
228 if not do_run(cube_map
, use_force
):
232 hashes
= [hash(scene
) for scene
in bpy
.data
.scenes
]
237 Euler((half_pi
, 0.0, 0.0)),
238 cube_map
.use_view_north
,
242 Euler((half_pi
, 0.0, pi
)),
243 cube_map
.use_view_south
,
247 Euler((half_pi
, 0.0, half_pi
)),
248 cube_map
.use_view_west
,
252 Euler((half_pi
, 0.0, -half_pi
)),
253 cube_map
.use_view_east
,
257 Euler((pi
, 0.0, 0.0)),
258 cube_map
.use_view_zenith
,
262 Euler((0.0, 0.0, 0.0)),
263 cube_map
.use_view_nadir
,
268 View(name
, euler
) for (name
, euler
, use
) in views_raw
269 if use
or not cube_map
.is_advanced
]
272 # create a scene per view
273 # XXX : line below crashes Blender 2.80
274 bpy
.ops
.scene
.new(type='LINK_COPY')
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
))
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
294 node_output
= node_tree
.nodes
.new('CompositorNodeOutputFile')
295 node_output
.inputs
.clear()
298 node
= node_tree
.nodes
.new('CompositorNodeRLayers')
299 view
.setNode(node
, node_tree
.links
, node_output
)
302 bpy
.cube_map_node_tree_data
= node_tree_data
303 bpy
.cube_map_views
= views
306 # ############################################################
308 # ############################################################
311 def cube_map_render_pre(scene
, use_force
=False):
313 if not do_run(scene
.cube_map
, use_force
):
316 from math
import radians
318 camera
= scene
.camera
319 data
= camera
.data
.copy()
321 data
.lens_unit
= 'FOV'
322 data
.angle
= radians(90)
325 mat
= camera
.matrix_world
327 loc
= mat
.to_translation()
331 views
= bpy
.cube_map_views
334 view
.setCamera(data
, loc
, zed
)
338 def cube_map_render_post(scene
, use_force
=False):
340 if not do_run(scene
.cube_map
, use_force
):
343 views
= bpy
.cube_map_views
349 # ############################################################
351 # ############################################################
354 def cube_map_render_cancel(scene
):
355 cube_map_cleanup(scene
)
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
):
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)
383 scene
for scene
in bpy
.data
.scenes
if
384 scene
.cube_map
.is_temporary
]
387 bpy
.app
.handlers
.scene_update_post
.remove(cube_map_post_update_cleanup
)
390 scenes_temp
[0].user_clear()
392 bpy
.data
.scenes
.remove(scenes_temp
[0], do_unlink
=False)
394 bpy
.data
.scenes
.remove(scenes_temp
[0])
397 # ############################################################
399 # ############################################################
401 class CubeMapSetup(Operator
):
403 bl_idname
= "render.cube_map_setup"
404 bl_label
= "Cube Map Render Setup"
407 action
: bpy
.props
.EnumProperty(
409 items
=(("SETUP", "Setup", "Created linked scenes and setup cube map"),
410 ("RESET", "Reset", "Delete added scenes"),
413 options
={'SKIP_SAVE'},
417 def poll(cls
, context
):
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':
446 if cube_map
.is_temporary
:
449 "Cannot reset cube map from one of "
450 "the created scenes")
457 self
.report({'ERROR'}, "Cube Map render is not setup")
462 self
.report({'ERROR'}, "Cube Map render is already setup")
465 self
.setup(context
.window
, scene
)
469 # ############################################################
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"
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
):
489 col
= layout
.column()
491 scene
= context
.scene
492 cube_map
= scene
.cube_map
494 if not cube_map
.is_enabled
:
496 "render.cube_map_setup",
497 text
="Scene Setup").action
= 'SETUP'
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
:
508 box
.active
= cube_map
.use_cube_map
and cube_map
.is_advanced
510 row
.prop(cube_map
, "use_view_north")
511 row
.prop(cube_map
, "use_view_west")
512 row
.prop(cube_map
, "use_view_zenith")
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 # ############################################################
522 # ############################################################
524 class CubeMapInfo(bpy
.types
.PropertyGroup
):
525 use_cube_map
: BoolProperty(
530 is_temporary
: BoolProperty(
535 is_enabled
: BoolProperty(
541 is_advanced
: BoolProperty(
544 description
="Decide which views to render",
547 use_view_north
: BoolProperty(
552 use_view_south
: BoolProperty(
557 use_view_west
: BoolProperty(
562 use_view_east
: BoolProperty(
567 use_view_zenith
: BoolProperty(
572 use_view_nadir
: BoolProperty(
578 # ############################################################
580 # ############################################################
583 bpy
.utils
.register_class(CubeMapInfo
)
584 bpy
.utils
.register_class(CubeMapSetup
)
585 bpy
.types
.Scene
.cube_map
= bpy
.props
.PointerProperty(
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
)
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__':