A retopotools
This commit is contained in:
		
							parent
							
								
									2797e6146c
								
							
						
					
					
						commit
						16e1fedf82
					
				
					 5 changed files with 360 additions and 0 deletions
				
			
		
							
								
								
									
										1
									
								
								retopo_tools/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								retopo_tools/.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					*.zip
 | 
				
			||||||
							
								
								
									
										8
									
								
								retopo_tools/Makefile
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								retopo_tools/Makefile
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					all: retopo_tools_addon.zip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					%.zip: *.py
 | 
				
			||||||
 | 
						zip -r $@ $^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: clean
 | 
				
			||||||
 | 
					clean:
 | 
				
			||||||
 | 
						${RM} *.zip
 | 
				
			||||||
							
								
								
									
										81
									
								
								retopo_tools/__init__.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								retopo_tools/__init__.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,81 @@
 | 
				
			||||||
 | 
					# 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
 | 
				
			||||||
 | 
					# MERCHANTIBILITY 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 bpy.types import Context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bl_info = {
 | 
				
			||||||
 | 
					    "name" : "RetopoTools",
 | 
				
			||||||
 | 
					    "author" : "Name",
 | 
				
			||||||
 | 
					    "description" : "Set up a standard retopology workflow for a selected object, if 2 objects are selected, then the non-active object is used as the target of mirror and shrinkwrap",
 | 
				
			||||||
 | 
					    "blender" : (2, 80, 0),
 | 
				
			||||||
 | 
					    "version" : (0, 0, 1),
 | 
				
			||||||
 | 
					    "location" : "",
 | 
				
			||||||
 | 
					    "warning" : "",
 | 
				
			||||||
 | 
					    "category" : "Generic"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from .tool import RetopoProjectModeOperator, RetopoVertexModeOperator
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def RetopoProjectModeOperator_menu_func(self, context: Context):
 | 
				
			||||||
 | 
					    return self.layout.operator(RetopoProjectModeOperator.bl_idname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def RetopoVertexModeOperator_menu_func(self, context: Context):
 | 
				
			||||||
 | 
					    return self.layout.operator(RetopoVertexModeOperator.bl_idname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from . import auto_load
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					auto_load.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					addon_keymaps = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def register_keymaps():
 | 
				
			||||||
 | 
					    addon_kc = bpy.context.window_manager.keyconfigs.addon
 | 
				
			||||||
 | 
					    if addon_kc is None:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    for mode in ("Window",):
 | 
				
			||||||
 | 
					        km = addon_kc.keymaps.new(name=mode)
 | 
				
			||||||
 | 
					        kmis = tuple(
 | 
				
			||||||
 | 
					            km.keymap_items.new(
 | 
				
			||||||
 | 
					                Operator.bl_idname,
 | 
				
			||||||
 | 
					                type="NONE",
 | 
				
			||||||
 | 
					                value="ANY",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            for Operator in (RetopoProjectModeOperator, RetopoVertexModeOperator)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        addon_keymaps.append((km, kmis))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unregister_keymaps():
 | 
				
			||||||
 | 
					    # Don't know if this is really neccessary but this is what they do in the docs tutorial...
 | 
				
			||||||
 | 
					    for km, kmis in addon_keymaps:
 | 
				
			||||||
 | 
					        for kmi in kmis:
 | 
				
			||||||
 | 
					            km.keymap_items.remove(kmi)
 | 
				
			||||||
 | 
					        bpy.context.window_manager.keyconfigs.addon.keymaps.remove(km)
 | 
				
			||||||
 | 
					    addon_keymaps.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def register():
 | 
				
			||||||
 | 
					    auto_load.register()
 | 
				
			||||||
 | 
					    bpy.types.VIEW3D_MT_object.append(RetopoProjectModeOperator_menu_func)
 | 
				
			||||||
 | 
					    bpy.types.VIEW3D_MT_object.append(RetopoVertexModeOperator_menu_func)
 | 
				
			||||||
 | 
					    register_keymaps()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unregister():
 | 
				
			||||||
 | 
					    auto_load.unregister()
 | 
				
			||||||
 | 
					    bpy.types.VIEW3D_MT_object.remove(RetopoProjectModeOperator_menu_func)
 | 
				
			||||||
 | 
					    bpy.types.VIEW3D_MT_object.remove(RetopoVertexModeOperator_menu_func)
 | 
				
			||||||
 | 
					    unregister_keymaps()
 | 
				
			||||||
							
								
								
									
										162
									
								
								retopo_tools/auto_load.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								retopo_tools/auto_load.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,162 @@
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					File is autogenerated by the vscode blender extension.
 | 
				
			||||||
 | 
					Handles the tedium of registering blender classes by searching for them and automagically registering them
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					import bpy
 | 
				
			||||||
 | 
					import sys
 | 
				
			||||||
 | 
					import typing
 | 
				
			||||||
 | 
					import inspect
 | 
				
			||||||
 | 
					import pkgutil
 | 
				
			||||||
 | 
					import importlib
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					__all__ = (
 | 
				
			||||||
 | 
					    "init",
 | 
				
			||||||
 | 
					    "register",
 | 
				
			||||||
 | 
					    "unregister",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					blender_version = bpy.app.version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					modules = None
 | 
				
			||||||
 | 
					ordered_classes = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def init():
 | 
				
			||||||
 | 
					    global modules
 | 
				
			||||||
 | 
					    global ordered_classes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modules = get_all_submodules(Path(__file__).parent)
 | 
				
			||||||
 | 
					    ordered_classes = get_ordered_classes_to_register(modules)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def register():
 | 
				
			||||||
 | 
					    for cls in ordered_classes:
 | 
				
			||||||
 | 
					        bpy.utils.register_class(cls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for module in modules:
 | 
				
			||||||
 | 
					        if module.__name__ == __name__:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if hasattr(module, "register"):
 | 
				
			||||||
 | 
					            module.register()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def unregister():
 | 
				
			||||||
 | 
					    for cls in reversed(ordered_classes):
 | 
				
			||||||
 | 
					        bpy.utils.unregister_class(cls)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for module in modules:
 | 
				
			||||||
 | 
					        if module.__name__ == __name__:
 | 
				
			||||||
 | 
					            continue
 | 
				
			||||||
 | 
					        if hasattr(module, "unregister"):
 | 
				
			||||||
 | 
					            module.unregister()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Import modules
 | 
				
			||||||
 | 
					#################################################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_all_submodules(directory):
 | 
				
			||||||
 | 
					    return list(iter_submodules(directory, directory.name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_submodules(path, package_name):
 | 
				
			||||||
 | 
					    for name in sorted(iter_submodule_names(path)):
 | 
				
			||||||
 | 
					        yield importlib.import_module("." + name, package_name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_submodule_names(path, root=""):
 | 
				
			||||||
 | 
					    for _, module_name, is_package in pkgutil.iter_modules([str(path)]):
 | 
				
			||||||
 | 
					        if is_package:
 | 
				
			||||||
 | 
					            sub_path = path / module_name
 | 
				
			||||||
 | 
					            sub_root = root + module_name + "."
 | 
				
			||||||
 | 
					            yield from iter_submodule_names(sub_path, sub_root)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            yield root + module_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Find classes to register
 | 
				
			||||||
 | 
					#################################################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_ordered_classes_to_register(modules):
 | 
				
			||||||
 | 
					    return toposort(get_register_deps_dict(modules))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_register_deps_dict(modules):
 | 
				
			||||||
 | 
					    my_classes = set(iter_my_classes(modules))
 | 
				
			||||||
 | 
					    my_classes_by_idname = {cls.bl_idname : cls for cls in my_classes if hasattr(cls, "bl_idname")}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deps_dict = {}
 | 
				
			||||||
 | 
					    for cls in my_classes:
 | 
				
			||||||
 | 
					        deps_dict[cls] = set(iter_my_register_deps(cls, my_classes, my_classes_by_idname))
 | 
				
			||||||
 | 
					    return deps_dict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_my_register_deps(cls, my_classes, my_classes_by_idname):
 | 
				
			||||||
 | 
					    yield from iter_my_deps_from_annotations(cls, my_classes)
 | 
				
			||||||
 | 
					    yield from iter_my_deps_from_parent_id(cls, my_classes_by_idname)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_my_deps_from_annotations(cls, my_classes):
 | 
				
			||||||
 | 
					    for value in typing.get_type_hints(cls, {}, {}).values():
 | 
				
			||||||
 | 
					        dependency = get_dependency_from_annotation(value)
 | 
				
			||||||
 | 
					        if dependency is not None:
 | 
				
			||||||
 | 
					            if dependency in my_classes:
 | 
				
			||||||
 | 
					                yield dependency
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_dependency_from_annotation(value):
 | 
				
			||||||
 | 
					    if blender_version >= (2, 93):
 | 
				
			||||||
 | 
					        if isinstance(value, bpy.props._PropertyDeferred):
 | 
				
			||||||
 | 
					            return value.keywords.get("type")
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        if isinstance(value, tuple) and len(value) == 2:
 | 
				
			||||||
 | 
					            if value[0] in (bpy.props.PointerProperty, bpy.props.CollectionProperty):
 | 
				
			||||||
 | 
					                return value[1]["type"]
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_my_deps_from_parent_id(cls, my_classes_by_idname):
 | 
				
			||||||
 | 
					    if bpy.types.Panel in cls.__bases__:
 | 
				
			||||||
 | 
					        parent_idname = getattr(cls, "bl_parent_id", None)
 | 
				
			||||||
 | 
					        if parent_idname is not None:
 | 
				
			||||||
 | 
					            parent_cls = my_classes_by_idname.get(parent_idname)
 | 
				
			||||||
 | 
					            if parent_cls is not None:
 | 
				
			||||||
 | 
					                yield parent_cls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_my_classes(modules):
 | 
				
			||||||
 | 
					    base_types = get_register_base_types()
 | 
				
			||||||
 | 
					    for cls in get_classes_in_modules(modules):
 | 
				
			||||||
 | 
					        if any(base in base_types for base in cls.__bases__):
 | 
				
			||||||
 | 
					            if not getattr(cls, "is_registered", False):
 | 
				
			||||||
 | 
					                yield cls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_classes_in_modules(modules):
 | 
				
			||||||
 | 
					    classes = set()
 | 
				
			||||||
 | 
					    for module in modules:
 | 
				
			||||||
 | 
					        for cls in iter_classes_in_module(module):
 | 
				
			||||||
 | 
					            classes.add(cls)
 | 
				
			||||||
 | 
					    return classes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def iter_classes_in_module(module):
 | 
				
			||||||
 | 
					    for value in module.__dict__.values():
 | 
				
			||||||
 | 
					        if inspect.isclass(value):
 | 
				
			||||||
 | 
					            yield value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_register_base_types():
 | 
				
			||||||
 | 
					    return set(getattr(bpy.types, name) for name in [
 | 
				
			||||||
 | 
					        "Panel", "Operator", "PropertyGroup",
 | 
				
			||||||
 | 
					        "AddonPreferences", "Header", "Menu",
 | 
				
			||||||
 | 
					        "Node", "NodeSocket", "NodeTree",
 | 
				
			||||||
 | 
					        "UIList", "RenderEngine",
 | 
				
			||||||
 | 
					        "Gizmo", "GizmoGroup",
 | 
				
			||||||
 | 
					    ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Find order to register to solve dependencies
 | 
				
			||||||
 | 
					#################################################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def toposort(deps_dict):
 | 
				
			||||||
 | 
					    sorted_list = []
 | 
				
			||||||
 | 
					    sorted_values = set()
 | 
				
			||||||
 | 
					    while len(deps_dict) > 0:
 | 
				
			||||||
 | 
					        unsorted = []
 | 
				
			||||||
 | 
					        for value, deps in deps_dict.items():
 | 
				
			||||||
 | 
					            if len(deps) == 0:
 | 
				
			||||||
 | 
					                sorted_list.append(value)
 | 
				
			||||||
 | 
					                sorted_values.add(value)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                unsorted.append(value)
 | 
				
			||||||
 | 
					        deps_dict = {value : deps_dict[value] - sorted_values for value in unsorted}
 | 
				
			||||||
 | 
					    return sorted_list
 | 
				
			||||||
							
								
								
									
										108
									
								
								retopo_tools/tool.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								retopo_tools/tool.py
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,108 @@
 | 
				
			||||||
 | 
					import bpy
 | 
				
			||||||
 | 
					from bpy.types import Context, Object, Mesh, Scene
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_mirror_modifier(obj: Object, target: Object = None):
 | 
				
			||||||
 | 
					    modifier = next((modifier for modifier in obj.modifiers if modifier.type == "MIRROR"), None)
 | 
				
			||||||
 | 
					    if modifier is None:
 | 
				
			||||||
 | 
					        modifier = obj.modifiers.new("Mirror", "MIRROR")
 | 
				
			||||||
 | 
					    modifier.use_clip = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if target is not None and modifier.mirror_object is None:
 | 
				
			||||||
 | 
					        modifier.mirror_object = target
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def assert_shrinkwrap_modifier(obj: Object, target: Object = None):
 | 
				
			||||||
 | 
					    modifier = next((modifier for modifier in obj.modifiers if modifier.type == "SHRINKWRAP"), None)
 | 
				
			||||||
 | 
					    if modifier is None:
 | 
				
			||||||
 | 
					        modifier = obj.modifiers.new("Shrinkwrap", "SHRINKWRAP")
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    modifier.show_viewport = False
 | 
				
			||||||
 | 
					    modifier.wrap_method = "PROJECT"
 | 
				
			||||||
 | 
					    modifier.wrap_mode = "ABOVE_SURFACE"
 | 
				
			||||||
 | 
					    modifier.project_limit = 0.0
 | 
				
			||||||
 | 
					    modifier.use_negative_direction = True
 | 
				
			||||||
 | 
					    modifier.use_positive_direction = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    vgroup = next((group for group in obj.vertex_groups if "no_shrinkwrap" in group.name), None)
 | 
				
			||||||
 | 
					    if vgroup is None:
 | 
				
			||||||
 | 
					        vgroup = obj.vertex_groups.new(name="no_shrinkwrap")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    modifier.vertex_group = vgroup.name
 | 
				
			||||||
 | 
					    modifier.invert_vertex_group = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if target is not None and modifier.target is None:
 | 
				
			||||||
 | 
					        modifier.target = target
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					def assert_retopo_modifiers(ctx: Context, target: Object = None):
 | 
				
			||||||
 | 
					    active = ctx.active_object
 | 
				
			||||||
 | 
					    assert_mirror_modifier(active, target)
 | 
				
			||||||
 | 
					    assert_shrinkwrap_modifier(active, target)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_project_snap_settings(scene: Scene):
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap = True
 | 
				
			||||||
 | 
					    scene.tool_settings.snap_elements = {"FACE"}
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_self = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_edit = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_nonedit = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_selectable = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_align_rotation = False
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_backface_culling = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_project = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_translate = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_rotate = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_scale = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def setup_vertex_snap_settings(scene: Scene):
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap = True
 | 
				
			||||||
 | 
					    scene.tool_settings.snap_elements = {"VERTEX"}
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_self = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_edit = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_nonedit = False
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_selectable = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_align_rotation = False
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_backface_culling = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_translate = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_rotate = True
 | 
				
			||||||
 | 
					    scene.tool_settings.use_snap_scale = True
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_context_target(ctx: Context):
 | 
				
			||||||
 | 
					    if len(ctx.selected_objects) != 2:
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    return next(obj for obj in ctx.selected_objects if obj is not ctx.active_object)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RetopoProjectModeOperator(bpy.types.Operator):
 | 
				
			||||||
 | 
					    bl_idname = "object.retopoprojectmode"
 | 
				
			||||||
 | 
					    bl_label = "RetopoProjectMode"
 | 
				
			||||||
 | 
					    bl_options = {"REGISTER", "UNDO"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @classmethod
 | 
				
			||||||
 | 
					    def poll(Cls, context: Context):
 | 
				
			||||||
 | 
					        return context.active_object is not None and type(context.active_object.data) is Mesh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self, context: Context):
 | 
				
			||||||
 | 
					        bpy.ops.object.mode_set(mode="EDIT")
 | 
				
			||||||
 | 
					        target = get_context_target(context)
 | 
				
			||||||
 | 
					        assert_retopo_modifiers(context, target)
 | 
				
			||||||
 | 
					        setup_project_snap_settings(context.scene)
 | 
				
			||||||
 | 
					        self.report({"INFO"}, "Project Mode")
 | 
				
			||||||
 | 
					        return {'FINISHED'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RetopoVertexModeOperator(bpy.types.Operator):
 | 
				
			||||||
 | 
					    bl_idname = "object.retopovertexmode"
 | 
				
			||||||
 | 
					    bl_label = "RetopoVertexMode"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def execute(self, context):
 | 
				
			||||||
 | 
					        bpy.ops.object.mode_set(mode="EDIT")
 | 
				
			||||||
 | 
					        target = get_context_target(context)
 | 
				
			||||||
 | 
					        assert_retopo_modifiers(context, target)
 | 
				
			||||||
 | 
					        setup_vertex_snap_settings(context.scene)
 | 
				
			||||||
 | 
					        self.report({"INFO"}, "Vertex Mode")
 | 
				
			||||||
 | 
					        return {'FINISHED'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue