2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,23 @@
'''
Copyright (C) 2023 CG Cookie
http://cgcookie.com
hello@cgcookie.com
Created by Jonathan Denning, Jonathan Williamson, and Patrick Moore
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
__all__ = ['cookiecutter', 'test']
@@ -0,0 +1,144 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import sys
import copy
import math
import time
import bpy
from bpy.types import Operator
from ..common.blender import perform_redraw_all
from ..common.debug import debugger, tprint
from ..common.profiler import profiler
from .cookiecutter_actions import CookieCutter_Actions
from .cookiecutter_blender import CookieCutter_Blender
from .cookiecutter_debug import CookieCutter_Debug
from .cookiecutter_exceptions import CookieCutter_Exceptions
from .cookiecutter_fsm import CookieCutter_FSM
from .cookiecutter_modal import CookieCutter_Modal
from .cookiecutter_ui import CookieCutter_UI
is_broken = False
class CookieCutter(
Operator,
CookieCutter_UI,
CookieCutter_Actions,
CookieCutter_FSM,
CookieCutter_Blender,
CookieCutter_Exceptions,
CookieCutter_Debug,
CookieCutter_Modal,
):
'''
CookieCutter is used to create advanced operators very quickly!
To use:
- specify CookieCutter as a subclass
- provide appropriate values for Blender class attributes: bl_idname, bl_label, etc.
- provide appropriate dictionary that maps user action labels to keyboard and mouse actions
- override the start function
- register finite state machine state callbacks with the FSM.on_state(state) function decorator
- state can be any string that is a state in your FSM
- Must provide at least a 'main' state
- return values of each on_state decorated function tell FSM which state to switch into
- None, '', or no return: stay in same state
- register drawing callbacks with the CookieCutter.Draw(mode) function decorator
- mode: 'pre3d', 'post3d', 'post2d'
'''
# registry = []
# def __init_subclass__(cls, *args, **kwargs):
# super().__init_subclass__(*args, **kwargs)
# if not hasattr(cls, '_cookiecutter_index'):
# # add cls to registry (might get updated later) and add FSM,Draw
# cls._rfwidget_index = len(CookieCutter.registry)
# CookieCutter.registry.append(cls)
# cls.fsm = FSM()
# cls.drawcallbacks = DrawCallbacks()
# else:
# # update registry, but do not add new FSM
# CookieCutter.registry[cls._cookiecutter_index] = cls
############################################################################
# override the following values and functions
bl_idname = "view3d.cookiecutter_unnamed"
bl_label = "CookieCutter Unnamed"
is_running = False
@classmethod
def can_start(cls, context): return True
def prestart(self): pass
def is_ready_to_start(self): return True
def start(self): pass
def update(self): pass
def end_commit(self): pass
def end_cancel(self): pass
def end(self): pass
def should_pass_through(self, context, event): return False
############################################################################
@staticmethod
def cc_break():
global is_broken
is_broken = True
@classmethod
def poll(cls, context):
global is_broken
if is_broken: return False
with cls.try_exception('call can_start()'):
return cls.can_start(context)
print('BREAKING COOKIECUTTER')
print(f'{cls.bl_idname}')
cls.cc_break()
return False
def invoke(self, context, event):
CookieCutter.is_running = True
self._cc_stage = 'prestart'
self.context = context
self.event = event
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
def stop_running(self):
CookieCutter.is_running = False
def done(self, *, cancel=False, emergency_bail=False):
if emergency_bail:
self._done = 'bail'
else:
self._done = 'commit' if not cancel else 'cancel'
@@ -0,0 +1,43 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import sys
import copy
import math
import time
import bpy
from ..common.useractions import ActionHandler
class CookieCutter_Actions:
def _cc_actions_init(self):
self._cc_actions = ActionHandler(self.context)
self._timer = self._cc_actions.start_timer(10)
def _cc_actions_update(self):
self._cc_actions.update(self.context, self.event, fn_debug=self.debug_print_actions)
def _cc_actions_end(self):
self._timer.done()
self._cc_actions.done()
@@ -0,0 +1,58 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import math
from inspect import ismethod, isfunction, signature
from contextlib import contextmanager
import bpy
from ..common.blender import region_label_to_data, create_simple_context, StoreRestore, BlenderSettings
from ..common.decorators import blender_version_wrapper, ignore_exceptions
from ..common.functools import find_fns, self_wrapper
from ..common.debug import debugger
from ..common.blender_cursors import Cursors
from ..common.utils import iter_head
class CookieCutter_Blender(BlenderSettings):
def _cc_blenderui_init(self):
self.storerestore_init()
for _,fn in find_fns(self, '_blender_change_callback'):
self.register_blender_change_callback(self_wrapper(self, fn))
self._storerestore.store_all()
@staticmethod
def blender_change_callback(fn):
fn._blender_change_callback = True
return fn
def register_blender_change_callback(self, fn):
self._storerestore.register_storage_change_callback(fn)
def blender_change_init(self, storage):
self._storerestore.init_storage(storage)
def _cc_blenderui_end(self, ignore=None):
self._storerestore.restore_all(ignore=ignore)
self.header_text_restore()
self.statusbar_text_restore()
self.cursor_restore()
@@ -0,0 +1,45 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
from ..common.blender import get_text_block
class CookieCutter_Debug:
cc_debug_print_to = 'CookieCutter_Debug'
cc_debug_all_enabled = False
cc_debug_actions_enabled = False
def debug_print(self, label, *args, override_enabled=False, **kwargs):
if not override_enabled and not self.cc_debug_all_enabled: return
text_block = get_text_block(self.cc_debug_print_to)
assert text_block
text_block.cursor_set(0x7fffffff) # move cursor to last line
text_block.write(f'{label}: {", ".join(args)}\n')
for k,v in kwargs.items():
text_block.write(f' {k} = {v}\n')
text_block.write('\n')
def debug_print_actions(self, *args, **kwargs):
self.debug_print(
'Action',
*args,
override_enabled=self.cc_debug_actions_enabled,
**kwargs
)
@@ -0,0 +1,72 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import contextlib
from ..common.fsm import FSM
from ..common.debug import debugger, ExceptionHandler
from ..common.functools import find_fns
class CookieCutter_Exceptions:
@staticmethod
def _handle_exception(e, action, fatal=False):
print(f'CookieCutter_Exceptions: handling caught exception')
print(f' action: {action}')
debugger.print_exception()
if fatal: assert False
if hasattr(CookieCutter_Exceptions, '_instance'):
CookieCutter_Exceptions._instance._callback_exception_callbacks(e)
@staticmethod
@contextlib.contextmanager
def try_exception(action, *, fatal=False, fn_succeed=None, fn_exception=None, fn_finally=None):
try:
yield
if fn_succeed: fn_succeed()
except Exception as e:
CookieCutter_Exceptions._handle_exception(e, action, fatal=fatal)
if fn_exception: fn_exception(e)
finally:
if fn_finally: fn_finally()
@staticmethod
def _exception_callback_wrapper(fn):
fn._cc_exception_callback = True
return fn
Exception_Callback = _exception_callback_wrapper
@ExceptionHandler.on_exception
def _callback_exception_callbacks(self, e):
print(f'CookieCutter_Exceptions._callback_exception_callbacks: {e}')
# debugger.dcallstack(0)
for fn_name in self._exception_callbacks:
try:
fn = getattr(self, fn_name)
fn(e)
except Exception as e2:
print(f'CookieCutter caught exception while calling back exception callbacks: {fn.__name__}')
debugger.print_exception()
def _cc_exception_init(self):
self._exception_callbacks = [fn.__name__ for (_,fn) in find_fns(self, '_cc_exception_callback')]
self._exceptionhandler = ExceptionHandler(self)
#self._exceptionhandler.add_callback(self._callback_exception_callbacks, universal=True)
CookieCutter_Exceptions._instance = self
def _cc_exception_done(self):
del self._exceptionhandler
del CookieCutter_Exceptions._instance
self._exceptionhandler = None
ExceptionHandler.clear_universal_callbacks()
@@ -0,0 +1,55 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import bpy
from ..common.debug import debugger
from ..common.fsm import FSM
from ..common.timerhandler import TimerHandler
class CookieCutter_FSM:
def _cc_fsm_init(self):
self.fsm = FSM(self, start='main')
self.fsm.add_exception_callback(lambda e: self._handle_exception(e, 'handle exception caught by FSM'))
# def callback(e): self._handle_exception(e, 'handle exception caught by FSM')
# self.fsm.add_exception_callback(callback)
def _cc_fsm_update(self):
self.fsm.update()
def _cc_fsm_force_event(self):
# add some NOP event to event queue to force modal operator to be called again right away
# # warp cursor to same spot
# # DOES NOT WORK: (event.mouse_x, event.mouse_y) might be incorrect!!!
# self.context.window.cursor_warp(self.event.mouse_x, self.event.mouse_y)
# # simulate an event
# # DOES NOT WORK: only works with `--enable-event-simulate`, but then Blender cannot accept any input!!!
# self.context.window.event_simulate(type='NONE', value='NOTHING')
# # register a short-lived timer (only returns `None`)
# # DOES NOT WORK: these timers do NOT cause modal operator to be called for some reason :(
# bpy.app.timers.register(lambda:None, first_interval=0.01)
# create a short-lived WindowManager timer
# self.actions might not yet be created!
if not hasattr(self, '_cc_force_event_handler'):
self._cc_force_event_handler = TimerHandler(120, context=self.context, enabled=False)
self._cc_force_event_handler.start()
def _cc_fsm_stop_force_event(self):
if hasattr(self, '_cc_force_event_handler'):
self._cc_force_event_handler.stop()
@@ -0,0 +1,166 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import sys
import copy
import math
import time
import random
import bpy
from bpy.types import Operator
from ..common.blender import perform_redraw_all, get_view3d_area
from ..common.debug import debugger, tprint
from ..common.profiler import profiler
class CookieCutter_Modal:
def modal(self, context, event):
self.context = context
self.event = event
if self._cc_stage == 'quit': return {'FINISHED'}
# if we're not yet in the main loop, create a NOP event so that we can
# work our way through the initialization stuff as quickly as possible!
if self._cc_stage != 'main loop': self._cc_fsm_force_event()
else: self._cc_fsm_stop_force_event()
# get the method corresponding to the current stage
fn_modal = {
'prestart': self.modal_prestart,
'start when ready': self.modal_start_when_ready,
'init CC internals': self.modal_init_cc_internals,
'init CC start': self.modal_init_cc_start,
'init CC UI': self.modal_init_cc_ui,
'main loop': self.modal_mainloop,
}.get(self._cc_stage, None)
assert fn_modal, f"Unhandled CC stage: '{self._cc_stage}'"
ret = fn_modal()
# if ret == {'PASS_THROUGH'}:
# print(f'passing through {random.random()}')
return ret
def modal_prestart(self):
with self.try_exception('prestarting'):
self.prestart()
self._cc_stage = 'start when ready'
return {'RUNNING_MODAL'}
self.cc_break()
return {'CANCELLED'}
def modal_start_when_ready(self):
with self.try_exception('waiting for start readiness'):
if self.is_ready_to_start():
self._cc_stage = 'init CC internals'
return {'RUNNING_MODAL'}
self.cc_break()
return {'CANCELLED'}
def modal_init_cc_internals(self):
with self.try_exception('initialize internals (Exception Callbacks, FSM, UI, Actions)'):
self._nav = False
self._nav_time = 0
self._done = False
self._start_time = time.time()
self._tmp_time = self._start_time
self._cc_exception_init()
self._cc_fsm_init()
self._cc_ui_init()
self._cc_actions_init()
self._cc_stage = 'init CC start'
return {'RUNNING_MODAL'}
self.cc_break()
return {'CANCELLED'}
def modal_init_cc_start(self):
with self.try_exception('initialize start'):
self.start()
self._cc_stage = 'init CC UI'
return {'RUNNING_MODAL'}
self.cc_break()
return {'CANCELLED'}
def modal_init_cc_ui(self):
with self.try_exception('initialize ui'):
self._cc_ui_start()
self._cc_stage = 'main loop'
return {'RUNNING_MODAL'}
self.cc_break()
return {'CANCELLED'}
def modal_mainloop(self):
self.drawcallbacks.reset_pre()
if time.time() - self._tmp_time >= 1:
self._tmp_time = time.time()
# print('--- %d ---' % int(self._tmp_time - self._start_time))
profiler.printfile()
if self._done:
self.modal_maindone()
self._cc_ui_end()
self._cc_actions_end()
self._cc_exception_done()
return {'FINISHED'} if self._done=='finish' else {'CANCELLED'}
ret = None
if self._nav:
self._nav = False
self._nav_time = time.time()
self._cc_actions_update()
if self._cc_ui_update():
# UI handled the action
ret = {'RUNNING_MODAL'}
elif self._cc_actions.using('blender window action'):
# allow window actions to pass through to Blender
ret = {'PASS_THROUGH'}
elif self._cc_actions.is_navigating or (self._cc_actions.timer and self._nav):
self._nav = True
return {'PASS_THROUGH'}
with self.try_exception('call update'):
self.update()
if self.should_pass_through(self.context, self.event):
ret = {'PASS_THROUGH'}
if not ret:
self._cc_fsm_update()
ret = {'RUNNING_MODAL'}
perform_redraw_all(only_area=get_view3d_area(self.context))
return ret
def modal_maindone(self):
if self._done == 'bail':
return
try:
fn_end = self.end_commit if self._done == 'commit' else self.end_cancel
fn_end()
self.end()
self.stop_running()
except Exception as e:
self._handle_exception(e, 'call end() with %s' % self._done)
@@ -0,0 +1,197 @@
'''
Copyright (C) 2023 CG Cookie
https://github.com/CGCookie/retopoflow
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import math
import random
import bpy
from bpy.types import SpaceView3D
from mathutils import Matrix
from ..common import gpustate
from ..common.globals import Globals
from ..common.gpustate import ScissorStack
from ..common.blender import bversion, tag_redraw_all, get_view3d_area, get_view3d_region, get_view3d_space
from ..common.decorators import blender_version_wrapper
from ..common.debug import debugger, tprint
from ..common.drawing import Drawing, DrawCallbacks
from ..common.ui_core_images import preload_image
from ..common.ui_document import UI_Document
if not bpy.app.background:
import gpu
from gpu_extras.batch import batch_for_shader
# https://docs.blender.org/api/blender2.8/gpu.html#triangle-with-custom-shader
cover_vshader = '''
in vec2 position;
void main() {
gl_Position = vec4(position, 0.0f, 1.0f);
}
'''
cover_fshader = '''
uniform float darken;
out vec4 outColor;
void main() {
// float r = length(gl_FragCoord.xy - vec2(0.5, 0.5));
if(mod(floor(gl_FragCoord.x+gl_FragCoord.y), 2.0) == 0) {
outColor = vec4(0.0,0.0,0.0,1.0);
} else {
outColor = vec4(0.0f, 0.0f, 0.0f, darken);
}
}
'''
Drawing.glCheckError(f'Pre-compile check: cover shader')
shader, _ = gpustate.gpu_shader(f'blender ui cover', cover_vshader, cover_fshader)
Drawing.glCheckError(f'Post-compile check: cover shader')
# create batch to draw large triangle that covers entire clip space (-1,-1)--(+1,+1)
batch_full = batch_for_shader(shader, 'TRIS', {"position": [(-100, -100), (300, -100), (-100, 300)]})
class CookieCutter_UI:
'''
Assumes that direct subclass will have singleton instance (shared CookieCutter among all instances of that subclass and any subclasses)
'''
def _cc_ui_init(self):
# preload images
preload_image(
'checkmark.png', 'close.png', 'collapse_close.png', 'collapse_open.png', 'radio.png'
)
self.document = Globals.ui_document # UI_Document(self.context)
self.document.init(self.context)
self.document.add_exception_callback(lambda e: self._handle_exception(e, 'handle exception caught by UI'))
self.drawing = Globals.drawing
area = get_view3d_area()
space = get_view3d_space()
region = get_view3d_region()
self.drawing.set_region(area, space, region, space.region_3d, bpy.context.window)
self.drawcallbacks = DrawCallbacks(self)
self._cc_blenderui_init()
self._ignore_ui_events = False
self._hover_ui = False
tag_redraw_all('CC ui_init', only_tag=False)
@property
def ignore_ui_events(self):
return self._ignore_ui_events
@ignore_ui_events.setter
def ignore_ui_events(self, v):
self._ignore_ui_events = bool(v)
def _cc_ui_start(self):
def preview():
try: self.drawcallbacks.pre3d()
except Exception as e:
self._handle_exception(e, 'draw pre3d')
ScissorStack.end(force=True)
def postview():
# print('***** postview')
try: self.drawcallbacks.post3d()
except Exception as e:
self._handle_exception(e, 'draw post3d')
ScissorStack.end(force=True)
def postpixel():
# print('***** postpixel')
gpustate.blend('ALPHA')
try: self.drawcallbacks.post2d()
except Exception as e:
self._handle_exception(e, 'draw post2d')
ScissorStack.end(force=True)
try: self.document.draw(self.context)
except Exception as e:
self._handle_exception(e, 'draw window UI')
ScissorStack.end(force=True)
self._done = True # consider this a fatal failure
space = bpy.types.SpaceView3D
self._handle_preview = space.draw_handler_add(preview, tuple(), 'WINDOW', 'PRE_VIEW')
self._handle_postview = space.draw_handler_add(postview, tuple(), 'WINDOW', 'POST_VIEW')
self._handle_postpixel = space.draw_handler_add(postpixel, tuple(), 'WINDOW', 'POST_PIXEL')
tag_redraw_all('CC ui_start', only_tag=False)
def _cc_ui_update(self):
self.drawing.update_dpi()
if self.ignore_ui_events: return False
ret = self.document.update(self.context, self.event)
self._hover_ui = ret and 'hover' in ret
return self._hover_ui
def _cc_ui_end(self):
self._cc_blenderui_end()
space = bpy.types.SpaceView3D
space.draw_handler_remove(self._handle_preview, 'WINDOW')
space.draw_handler_remove(self._handle_postview, 'WINDOW')
space.draw_handler_remove(self._handle_postpixel, 'WINDOW')
self.region_restore()
self.context.workspace.status_text_set(None)
tag_redraw_all('CC ui_end', only_tag=False)
#########################################
# Region Darkening
def _cc_region_draw_cover(self, a):
gpustate.blend('ALPHA')
gpustate.depth_test('NONE')
shader.bind()
shader.uniform_float("darken", 0.50)
batch_full.draw(shader)
gpu.shader.unbind()
def region_darken(self):
if hasattr(self, '_region_darkened'): return # already darkened!
self._region_darkened = True
self._postpixel_callbacks = []
# darken all spaces
spaces = [(getattr(bpy.types, n), n) for n in dir(bpy.types) if n.startswith('Space')]
spaces = [(s,n) for (s,n) in spaces if hasattr(s, 'draw_handler_add')]
# https://docs.blender.org/api/blender2.8/bpy.types.Region.html#bpy.types.Region.type
# ['WINDOW', 'HEADER', 'CHANNELS', 'TEMPORARY', 'UI', 'TOOLS', 'TOOL_PROPS', 'PREVIEW', 'NAVIGATION_BAR', 'EXECUTE']
# NOTE: b280 has no TOOL_PROPS region for SpaceView3D!
# handling SpaceView3D differently!
general_areas = ['WINDOW', 'HEADER', 'CHANNELS', 'TEMPORARY', 'UI', 'TOOLS', 'TOOL_PROPS', 'PREVIEW', 'HUD', 'NAVIGATION_BAR', 'EXECUTE', 'FOOTER', 'TOOL_HEADER'] #['WINDOW', 'HEADER', 'UI', 'TOOLS', 'NAVIGATION_BAR']
SpaceView3D_areas = ['TOOLS', 'UI', 'HEADER', 'TOOL_PROPS']
for (s,n) in spaces:
areas = SpaceView3D_areas if n == 'SpaceView3D' else general_areas
for a in areas:
try:
cb = s.draw_handler_add(self._cc_region_draw_cover, (a,), a, 'POST_PIXEL')
self._postpixel_callbacks += [(s, a, cb)]
except:
pass
tag_redraw_all('CC region_darken', only_tag=False)
def region_restore(self):
# remove callback handlers
if hasattr(self, '_postpixel_callbacks'):
for (s,a,cb) in self._postpixel_callbacks: s.draw_handler_remove(cb, a)
del self._postpixel_callbacks
if hasattr(self, '_region_darkened'):
del self._region_darkened
tag_redraw_all('CC region_restore', only_tag=False)