Files
blender-portable-repo/scripts/addons/RetopoFlow/retopoflow/retopoflow.py
T
2026-03-17 14:30:01 -06:00

270 lines
10 KiB
Python

'''
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/>.
'''
import os
import bpy
import glob
import time
import atexit
from .rf.rf_blender_objects import RetopoFlow_Blender_Objects
from .rf.rf_blender_save import RetopoFlow_Blender_Save
from .rf.rf_drawing import RetopoFlow_Drawing
from .rf.rf_fsm import RetopoFlow_FSM
from .rf.rf_grease import RetopoFlow_Grease
from .rf.rf_helpsystem import RetopoFlow_HelpSystem
from .rf.rf_instrument import RetopoFlow_Instrumentation
from .rf.rf_normalize import RetopoFlow_Normalize
from .rf.rf_piemenu import RetopoFlow_PieMenu
from .rf.rf_sources import RetopoFlow_Sources
from .rf.rf_spaces import RetopoFlow_Spaces
from .rf.rf_target import RetopoFlow_Target
from .rf.rf_tools import RetopoFlow_Tools
from .rf.rf_ui import RetopoFlow_UI
from .rf.rf_ui_alert import RetopoFlow_UI_Alert
from .rf.rf_undo import RetopoFlow_Undo
from .rf.rf_updatersystem import RetopoFlow_UpdaterSystem
from ..addon_common.common.blender import (
tag_redraw_all,
get_path_from_addon_root,
workspace_duplicate,
show_error_message, BlenderPopupOperator, BlenderIcon,
scene_duplicate,
)
from ..addon_common.common import ui_core
from ..addon_common.common.decorators import add_cache
from ..addon_common.common.debug import debugger
from ..addon_common.common.drawing import Drawing, DrawCallbacks
from ..addon_common.common.fontmanager import FontManager
from ..addon_common.common.fsm import FSM
from ..addon_common.common.globals import Globals
from ..addon_common.common.image_preloader import ImagePreloader
from ..addon_common.common.profiler import profiler
from ..addon_common.common.utils import delay_exec, abspath, StopWatch
from ..addon_common.common.ui_styling import load_defaultstylings
from ..addon_common.common.ui_core import UI_Element
from ..addon_common.common.useractions import ActionHandler
from ..addon_common.cookiecutter.cookiecutter import CookieCutter
from ..config.keymaps import get_keymaps
from ..config.options import options, sessionoptions
class RetopoFlow(
RetopoFlow_Blender_Objects,
RetopoFlow_Blender_Save,
RetopoFlow_Drawing,
RetopoFlow_FSM,
RetopoFlow_Grease,
RetopoFlow_HelpSystem,
RetopoFlow_Instrumentation,
RetopoFlow_Normalize,
RetopoFlow_PieMenu,
RetopoFlow_Sources,
RetopoFlow_Spaces,
RetopoFlow_Target,
RetopoFlow_Tools,
RetopoFlow_UI,
RetopoFlow_UI_Alert,
RetopoFlow_Undo,
RetopoFlow_UpdaterSystem,
):
instance = None
@classmethod
def can_start(cls, context):
# check that the context is correct
if not context.region or context.region.type != 'WINDOW': return False
if not context.space_data or context.space_data.type != 'VIEW_3D': return False
# check we are in mesh editmode
if context.mode != 'EDIT_MESH': return False
# make sure we are editing a mesh object
ob = context.active_object
if not ob or ob.type != 'MESH': return False
if not ob.visible_get(): return False
# make sure we have source meshes
if not cls.get_sources(): return False
# all seems good!
return True
# def prestart(self):
# # duplicate workspace and scene so we can alter (most) settings without need to store/restore
# self.workspace = workspace_duplicate(name='RetopoFlow')
# self.scene = scene_duplicate(name='RetopoFlow')
def start(self):
sw = StopWatch()
RetopoFlow.instance = self
keymaps = get_keymaps(force_reload=True)
self.actions = ActionHandler(self.context, keymaps)
self.scene = self.context.scene
self.view_layer = self.context.view_layer
print(f'RetopoFlow: Starting... keymaps and actions {sw.elapsed():0.2f}')
# start loading
self.statusbar_text_set('RetopoFlow is loading...')
# DO THESE BEFORE SWITCHING TO OBJECT MODE BELOW AND BEFORE SETTING UP SOURCES AND TARGET!
# we need to store which objects are sources and which is target
self.mark_sources_target()
print(f'RetopoFlow: Starting... statusbar and marking {sw.elapsed():0.2f}')
ui_core.ASYNC_IMAGE_LOADING = options['async image loading']
self.loading_done = False
self.init_undo() # hack to work around issue #949
print(f'RetopoFlow: Starting... undo {sw.elapsed():0.2f}')
# self.store_window_state(self.actions.r3d, self.actions.space)
bpy.ops.object.mode_set(mode='OBJECT')
self.init_normalize() # get scaling factor to fit all sources into unit box
print(f'RetopoFlow: Starting... normalizing {sw.elapsed():0.2f}')
self.setup_ui_blender()
print(f'RetopoFlow: Starting... Blender UI {sw.elapsed():0.2f}')
self.reload_stylings()
print(f'RetopoFlow: Starting... stylings {sw.elapsed():0.2f}')
# the rest of setup is handled in `loading` state
self.fsm.force_set_state('loading')
print(f'RetopoFlow: Starting Total: {sw.total_elapsed():0.2f}')
def end(self):
self.normal_check.stop()
options.clear_callbacks()
self.end_normalize(self.context)
self.blender_ui_reset()
self.undo_clear()
self.done_target()
self.done_sources()
FontManager.unload_fontids()
# one more toggle, because done_target() might push to target mesh
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.mode_set(mode='EDIT')
self.unmark_sources_target() # DO THIS AS ONE OF LAST
sessionoptions.clear()
RetopoFlow.instance = None
@FSM.on_state('loading', 'enter')
def setup_next_stage_enter(self):
win = UI_Element.fromHTMLFile(abspath('html/loading_dialog.html'))[0]
self.document.body.append_child(win)
self._setup_data = {
'timer': self.actions.start_timer(120),
'ui_window': win,
'ui_div': win.getElementById('loadingdiv'),
'broken': False, # indicates if a setup stage broke
'ui_drawn': False, # used to know when UI has been drawn
'i_stage': -1, # which stage are we currently on?
'stage_data': None, # which stage is to be run
'stages': [
('Pausing help image preloading', ImagePreloader.pause),
('Setting up target mesh', self.setup_target),
('Setting up source mesh(es)', self.setup_sources),
('Setting up symmetry data structures', self.setup_sources_symmetry), # must be called after self.setup_target()!!
('Setting up rotation target', self.setup_rotate_about_active),
('Setting up RetopoFlow states', self.setup_states),
('Setting up RetopoFlow tools', self.setup_rftools),
('Setting up grease marks', self.setup_grease),
('Setting up visualizations', self.setup_drawing),
('Setting up user interface', self.setup_ui), # must be called after self.setup_target() and self.setup_rftools()!!
('Setting up undo system', self.undo_clear), # must be called after self.setup_ui()!!
('Checking auto save / save', self.check_auto_save_warnings),
('Checking target symmetry', self.check_target_symmetry),
('Loading welcome message', self.show_welcome_message),
('Resuming help image preloading', ImagePreloader.resume),
],
}
@DrawCallbacks.on_draw('post2d')
@FSM.onlyinstate('loading')
def setup_next_stage_drawn(self):
self._setup_data['ui_drawn'] = True
tag_redraw_all('allow startup dialog to render')
@FSM.on_state('loading')
def setup_next_stage(self):
tag_redraw_all('allow startup dialog to render')
d = self._setup_data
if d['broken']:
self.done(cancel=True, emergency_bail=True)
show_error_message(
[
'An unexpected error occurred while trying to start RetopoFlow.',
'Please consider reporting this issue so that we can fix it.',
BlenderPopupOperator('cgcookie.retopoflow_web_blendermarket', icon='URL'),
BlenderPopupOperator('cgcookie.retopoflow_web_github_newissue', icon='URL'),
],
title='RetopoFlow Error',
)
return
if d['stage_data']:
if not d['ui_drawn']:
# UI has not been updated yet
return
stage_name, stage_fn = d['stage_data']
d['stage_data'] = None
start_time = time.time()
try:
print(f'RetopoFlow: {stage_name}')
stage_fn()
print(f' elapsed: {(time.time() - start_time):0.2f} secs')
except Exception as e:
print(f'RetopoFlow Exception: {e}')
debugger.print_exception()
d['broken'] = True
return
d['i_stage'] += 1
if d['i_stage'] == len(d['stages']):
# done with all stages!
print('RetopoFlow: done with start')
self.loading_done = True
self.fsm.force_set_state('main')
self.document.body.delete_child(d['ui_window'])
d['timer'].done()
return
# start next stage
stage_name, stage_fn = d['stages'][d['i_stage']]
d['ui_drawn'] = False
d['stage_data'] = (stage_name, stage_fn)
d['ui_div'].set_markdown(mdown=stage_name)
RetopoFlow.cc_debug_print_to = 'RetopoFlow_Debug'