├── .gitignore ├── LICENSE ├── README.md ├── __init__.py ├── images └── logo.jpg ├── op ├── mesh_modes.py ├── misc.py ├── pivot.py ├── quick_align.py ├── quick_lattice.py ├── quick_pipe.py ├── radial_symmetry.py ├── rebase_cylinder.py ├── selection.py ├── smart_delete.py ├── smart_extrude.py ├── smart_modify.py ├── smart_transform.py ├── super_smart_create.py └── uv_functions.py ├── ui ├── menus.py ├── pannels.py └── pies.py └── utils ├── debug.py ├── dictionaries.py ├── itools.py ├── load.py ├── mesh.py └── user_prefs.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | .vscode 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | # mypy 105 | .mypy_cache/ 106 | 107 | #Vs Code Settings 108 | .vscode/ 109 | itools_workspace.code-workspace -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Logo](./images/logo.jpg) 3 | 4 | ### What is Interactive Tools? 5 | Interactive Tools is a collection of tools for Blender that aims to provide intuitive, context sensitive tools. 6 | 7 | Due to its design these tools work better when used with hotkeys. 8 | 9 | ### Documentation: 10 | You can find the documentation for the tools and how to install them [here](https://maxivz.github.io/interactivetoolsblenderdocs.github.io/). 11 | 12 | ### License 13 | Interactive Tools uses the GPL 3.0 license 14 | 15 | ### Reporting a bug 16 | When it comes to reporting bugs, please submit them on Github [here](https://github.com/maxivz/interactivetoolsblender/issues). 17 | 18 | Please include a description of the problem and if possible the circustances in which it happends, as more detailed reports makes it easier to fix bugs. 19 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from . ui.menus import load_menus_itools, unload_menus_itools, VIEW3D_MT_object_mode_itools, VIEW3D_MT_edit_mesh_itools, VIEW3D_MT_edit_lattice_itools, VIEW3D_MT_edit_uvs_itools 3 | from . ui.pies import VIEW3D_MT_PIE_SSC_Duplicate,VIEW3D_MT_PIE_SM_uv ,VIEW3D_MT_PIE_SM_looptools, VIEW3D_MT_PIE_SM_lattice, VIEW3D_MT_PIE_SSC_New_Obj,VIEW3D_MT_PIE_TransformOptions, VIEW3D_MT_PIE_SM_object, VIEW3D_MT_PIE_SM_mesh, VIEW3D_MT_PIE_SM_curve 4 | from . ui.pannels import VIEW3D_PT_Itools 5 | #from . utils.debug import MaxivzToolsDebug_PT_Panel, DebugOp 6 | from . op.super_smart_create import SuperSmartCreate 7 | from . op.radial_symmetry import QuickRadialSymmetry 8 | from . op.quick_align import QuickAlign 9 | from . op.pivot import QuickPivot, QuickEditPivot 10 | from . op.smart_extrude import SmartExtrude, SmartExtrudeModal 11 | from . op.mesh_modes import SelectionModeCycle, QuickSelectionVert, QuickSelectionEdge, QuickSelectionFace 12 | from . op.misc import TransformModeCycle, CSBevel, QuickFlattenAxis, ContextSensitiveSlide, TargetWeldToggle, QuickModifierToggle, QuickWireToggle, WireShadedToggle, FlexiBezierToolsCreate, TransformOrientationCycle, TransformOrientationOp, QuickHpLpNamer, TransformOptionsPie, QuickVisualGeoToMesh, SnapPresetsOp, PropEditOp, ChildrenVisibility, TransformPivotPointOp 13 | from . op.smart_delete import SmartDelete 14 | from . op.smart_modify import SmartModify 15 | from . op.selection import SmartSelectLoop, SmartSelectRing 16 | from . op.smart_transform import SmartTranslate, CSMove, CSRotate, CSScale 17 | from . op.quick_lattice import QuickLattice, LatticeResolution2x2x2, LatticeResolution3x3x3, LatticeResolution4x4x4 18 | from . op.quick_pipe import QuickPipe 19 | from . op.rebase_cylinder import RebaseCylinder 20 | from . op.uv_functions import QuickRotateUv90Pos, QuickRotateUv90Neg, SeamsFromSharps, UvsFromSharps 21 | from . utils.user_prefs import AddonPreferences, OBJECT_OT_addon_prefs_example, MenuPlaceholder, unregister_keymaps, get_enable_legacy_tools 22 | 23 | bl_info = { 24 | "name": "Interactive Tools", 25 | "author": "Maxi Vazquez, Ajfurey", 26 | "description": "Collection of context sensitive tools", 27 | "blender": (4, 2, 0), 28 | "location": "View3D", 29 | "version": (1, 4, 1), 30 | "tracker_url": "https://github.com/maxivz/interactivetoolsblender/issues", 31 | "wiki_url": "https://maxivz.github.io/interactivetoolsblenderdocs.github.io/", 32 | "warning": "", 33 | "category": "Generic" 34 | } 35 | 36 | 37 | classes = (VIEW3D_PT_Itools, VIEW3D_MT_PIE_SSC_Duplicate, VIEW3D_MT_PIE_SSC_New_Obj, RebaseCylinder, 38 | VIEW3D_MT_object_mode_itools, VIEW3D_MT_edit_mesh_itools, VIEW3D_MT_edit_lattice_itools, 39 | VIEW3D_MT_PIE_SM_object, VIEW3D_MT_PIE_SM_mesh, TransformOptionsPie, 40 | VIEW3D_MT_edit_uvs_itools, VIEW3D_MT_PIE_TransformOptions, SuperSmartCreate, TransformModeCycle, QuickAlign, 41 | QuickRadialSymmetry,QuickPivot, QuickEditPivot, SelectionModeCycle, 42 | QuickSelectionEdge, QuickSelectionVert, QuickSelectionFace, VIEW3D_MT_PIE_SM_lattice, 43 | FlexiBezierToolsCreate, ContextSensitiveSlide, TargetWeldToggle, QuickModifierToggle, 44 | QuickWireToggle, WireShadedToggle, CSBevel, SmartDelete, TransformOrientationCycle, 45 | AddonPreferences, OBJECT_OT_addon_prefs_example, TransformOrientationOp, QuickFlattenAxis, 46 | SmartSelectLoop, SmartSelectRing, CSMove, CSRotate, CSScale, 47 | VIEW3D_MT_PIE_SM_looptools, VIEW3D_MT_PIE_SM_uv, VIEW3D_MT_PIE_SM_curve, 48 | QuickLattice,SmartExtrude , SeamsFromSharps, QuickVisualGeoToMesh, 49 | QuickRotateUv90Pos, QuickRotateUv90Neg, UvsFromSharps,QuickPipe, 50 | MenuPlaceholder, SmartModify, LatticeResolution2x2x2, 51 | SnapPresetsOp, PropEditOp, TransformPivotPointOp, 52 | LatticeResolution3x3x3, LatticeResolution4x4x4, QuickHpLpNamer, ChildrenVisibility) 53 | 54 | legacy_classes = (SmartExtrudeModal, SmartTranslate) 55 | 56 | classes += legacy_classes 57 | 58 | def register(): 59 | from bpy.utils import register_class 60 | for cls in classes: 61 | register_class(cls) 62 | 63 | # Load Custom Menus 64 | load_menus_itools() 65 | 66 | # Keymapping 67 | 68 | # register_keymaps() 69 | 70 | 71 | def unregister(): 72 | from bpy.utils import unregister_class 73 | # Keymap removal 74 | unregister_keymaps() 75 | 76 | # Unload Custom Menus 77 | unload_menus_itools() 78 | 79 | for cls in reversed(classes): 80 | unregister_class(cls) 81 | 82 | 83 | if __name__ == "__main__": 84 | register() 85 | -------------------------------------------------------------------------------- /images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxivz/interactivetoolsblender/519416be42342c1f184d2e4b20d849fa111fb6ce/images/logo.jpg -------------------------------------------------------------------------------- /op/mesh_modes.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .. utils import itools as itools 3 | from .. utils import dictionaries as dic 4 | from ..utils.user_prefs import get_enable_sticky_selection 5 | 6 | 7 | def store_sel_data(mode): 8 | if mode == 'VERT': 9 | dic.write("selected_verts", itools.get_selected(mode, item=False)) 10 | if mode == 'EDGE': 11 | dic.write("selected_edges", itools.get_selected(mode, item=False)) 12 | if mode == 'FACE': 13 | dic.write("selected_faces", itools.get_selected(mode, item=False)) 14 | 15 | 16 | def quick_selection(target_mode, safe_mode=False): 17 | current_mode = itools.get_mode() 18 | current_object = bpy.context.object 19 | 20 | if current_object != None: 21 | 22 | #Convert types from Mesh to Gpencil space 23 | if current_object.type == 'GPENCIL': 24 | if target_mode == 'VERT': 25 | target_mode = 'POINT' 26 | elif target_mode == 'EDGE': 27 | target_mode = 'STROKE' 28 | elif target_mode == 'FACE': 29 | target_mode = 'SEGMENT' 30 | 31 | other_modes = itools.list_difference(['VERT', 'EDGE', 'FACE', 'POINT', 'STROKE', 'SEGMENT', 'OBJECT'], [target_mode]) 32 | sticky = get_enable_sticky_selection() 33 | 34 | if current_mode in other_modes and current_object.type == 'MESH': 35 | if current_mode != 'OBJECT' and sticky: 36 | itools.update_indexes() 37 | store_sel_data(current_mode) 38 | 39 | itools.set_mode(target_mode) 40 | 41 | if sticky: 42 | if target_mode == 'VERT': 43 | stored_selection = dic.read("selected_verts") 44 | 45 | elif target_mode == 'EDGE': 46 | stored_selection = dic.read("selected_edges") 47 | 48 | elif target_mode == 'FACE': 49 | stored_selection = dic.read("selected_faces") 50 | 51 | if len(stored_selection) > 0 and "itools" in bpy.context.object: 52 | itools.update_indexes() 53 | indexes = [index for index in stored_selection] 54 | if safe_mode: 55 | itools.select(indexes, item=False, replace=True, safe_mode=safe_mode) 56 | else: 57 | itools.select(indexes, item=False, replace=True) 58 | 59 | elif current_mode == target_mode and current_object.type == 'MESH': 60 | if sticky: 61 | itools.update_indexes() 62 | store_sel_data(current_mode) 63 | itools.set_mode('OBJECT') 64 | 65 | if current_object.type == 'GPENCIL': 66 | bpy.ops.object.mode_set(mode="EDIT_GPENCIL") 67 | 68 | if current_mode in other_modes: 69 | if target_mode == 'POINT': 70 | bpy.context.scene.tool_settings.gpencil_selectmode_edit = 'POINT' 71 | elif target_mode == 'STROKE': 72 | bpy.context.scene.tool_settings.gpencil_selectmode_edit = 'STROKE' 73 | elif target_mode == 'SEGMENT': 74 | bpy.context.scene.tool_settings.gpencil_selectmode_edit = 'SEGMENT' 75 | 76 | elif current_mode == target_mode: 77 | bpy.ops.object.mode_set(mode="OBJECT") 78 | 79 | 80 | 81 | class SelectionModeCycle(bpy.types.Operator): 82 | bl_idname = "mesh.selection_mode_cycle" 83 | bl_label = "Selection Mode Cycle" 84 | bl_description = "Cycles trough selection modes" 85 | bl_options = {'REGISTER', 'UNDO'} 86 | 87 | def execute(self, context): 88 | mode = itools.get_mode() 89 | print(mode) 90 | if mode == 'OBJECT': 91 | bpy.ops.object.editmode_toggle() 92 | 93 | elif mode == 'VERT': 94 | quick_selection('EDGE', safe_mode=True) 95 | 96 | elif mode == 'EDGE': 97 | quick_selection('FACE', safe_mode=True) 98 | 99 | elif mode == 'FACE': 100 | quick_selection('VERT', safe_mode=True) 101 | 102 | elif mode in ['EDIT_CURVE', 'EDIT_LATTICE']: 103 | bpy.ops.object.editmode_toggle() 104 | 105 | return {'FINISHED'} 106 | 107 | 108 | class QuickSelectionVert(bpy.types.Operator): 109 | bl_idname = "mesh.quick_selection_vert" 110 | bl_label = "Quick Selection Vert" 111 | bl_description = "Set selection modes quickly" 112 | bl_options = {'REGISTER', 'UNDO'} 113 | 114 | def execute(self, context): 115 | quick_selection('VERT', safe_mode=True) 116 | return {'FINISHED'} 117 | 118 | 119 | class QuickSelectionEdge(bpy.types.Operator): 120 | bl_idname = "mesh.quick_selection_edge" 121 | bl_label = "Quick Selection Edge" 122 | bl_description = "Set selection modes quickly" 123 | bl_options = {'REGISTER', 'UNDO'} 124 | 125 | def execute(self, context): 126 | quick_selection('EDGE', safe_mode=True) 127 | return {'FINISHED'} 128 | 129 | 130 | class QuickSelectionFace(bpy.types.Operator): 131 | bl_idname = "mesh.quick_selection_face" 132 | bl_label = "Quick Selection Face" 133 | bl_description = "Set selection modes quickly" 134 | bl_options = {'REGISTER', 'UNDO'} 135 | 136 | def execute(self, context): 137 | quick_selection('FACE', safe_mode=True) 138 | return {'FINISHED'} 139 | -------------------------------------------------------------------------------- /op/misc.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | from .. utils import dictionaries as dic 4 | from .. utils.user_prefs import get_quickhplp_hp_suffix, get_quickhplp_lp_suffix, get_enable_wireshaded_cs, get_transform_mode_cycle_cyclic 5 | 6 | 7 | class TransformModeCycle(bpy.types.Operator): 8 | bl_idname = "mesh.transform_mode_cycle" 9 | bl_label = "Transform Mode Cycle" 10 | bl_description = "Cycle between Move/Rotate/Scale modes" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def execute(self, context): 14 | areas = bpy.context.workspace.screens[0].areas 15 | 16 | for area in areas: 17 | for space in area.spaces: 18 | if space.type == 'VIEW_3D': 19 | # Make active tool is set to select 20 | context_override = bpy.context.copy() 21 | context_override["space_data"] = area.spaces[0] 22 | context_override["area"] = area 23 | 24 | with context.temp_override(**context_override): 25 | bpy.ops.wm.tool_set_by_id( name="builtin.select_box") 26 | 27 | if space.show_gizmo_object_translate: 28 | space.show_gizmo_object_translate = False 29 | space.show_gizmo_object_rotate = True 30 | 31 | elif space.show_gizmo_object_rotate: 32 | space.show_gizmo_object_rotate = False 33 | space.show_gizmo_object_scale = True 34 | 35 | elif space.show_gizmo_object_scale: 36 | space.show_gizmo_object_scale = False 37 | 38 | if get_transform_mode_cycle_cyclic(): 39 | space.show_gizmo_object_translate = True 40 | 41 | else: 42 | space.show_gizmo_object_translate = True 43 | 44 | return{'FINISHED'} 45 | 46 | 47 | class TransformOrientationCycle(bpy.types.Operator): 48 | bl_idname = "mesh.transform_orientation_cycle" 49 | bl_label = "Transform Orientation Cycle" 50 | bl_description = "Cycles trough transform orientation modes" 51 | bl_options = {'REGISTER', 'UNDO'} 52 | 53 | def execute(self, context): 54 | space = bpy.context.scene.transform_orientation_slots[0].type 55 | 56 | if space == 'GLOBAL': 57 | new_space = 'LOCAL' 58 | 59 | elif space == 'LOCAL': 60 | new_space = 'NORMAL' 61 | 62 | elif space == 'NORMAL': 63 | new_space = 'GIMBAL' 64 | 65 | elif space == 'GIMBAL': 66 | new_space = 'VIEW' 67 | 68 | elif space == 'VIEW': 69 | new_space = 'CURSOR' 70 | 71 | elif space == 'CURSOR': 72 | new_space = 'GLOBAL' 73 | 74 | bpy.context.scene.transform_orientation_slots[0].type = new_space 75 | 76 | return {'FINISHED'} 77 | 78 | 79 | class CSBevel(bpy.types.Operator): 80 | bl_idname = "mesh.context_sensitive_bevel" 81 | bl_label = "Context Sensistive Bevel" 82 | bl_description = "Context Sensitive Bevels and Inset" 83 | bl_options = {'REGISTER', 'UNDO'} 84 | 85 | def cs_bevel(self): 86 | 87 | mode = itools.get_mode() 88 | 89 | version = bpy.app.version_string[:4] 90 | 91 | try: 92 | version = float(version) 93 | except ValueError: 94 | version = float(version[:-1]) 95 | 96 | if mode == 'VERT': 97 | if version >= 2.90: 98 | bpy.ops.mesh.bevel('INVOKE_DEFAULT', affect='VERTICES') 99 | else: 100 | bpy.ops.mesh.bevel('INVOKE_DEFAULT', vertex_only=True) 101 | 102 | if mode == 'EDGE': 103 | if version >= 2.90: 104 | bpy.ops.mesh.bevel('INVOKE_DEFAULT', affect='EDGES') 105 | else: 106 | bpy.ops.mesh.bevel('INVOKE_DEFAULT', vertex_only=False) 107 | 108 | if mode == 'FACE': 109 | bpy.ops.mesh.inset('INVOKE_DEFAULT') 110 | 111 | def execute(self, context): 112 | self.cs_bevel() 113 | return{'FINISHED'} 114 | 115 | 116 | class ChildrenVisibility(bpy.types.Operator): 117 | bl_idname = "object.children_visibility" 118 | bl_label = "Children Visibility" 119 | bl_description = "Hide or show children for the selected object" 120 | bl_options = {'REGISTER', 'UNDO'} 121 | 122 | hide: bpy.props.BoolProperty(default=True) 123 | 124 | def change_children_visibility(self): 125 | children = itools.get_children(itools.get_selected(item=False)[0]) 126 | for obj in children: 127 | bpy.data.objects[obj.name].hide_viewport = self.hide 128 | 129 | def execute(self, context): 130 | self.change_children_visibility() 131 | return{'FINISHED'} 132 | 133 | 134 | class ContextSensitiveSlide(bpy.types.Operator): 135 | bl_idname = "mesh.context_sensitive_slide" 136 | bl_label = "Context Sensitive Slide" 137 | bl_description = "Slide vert or edge based on selection" 138 | bl_options = {'REGISTER', 'UNDO'} 139 | 140 | def execute(self, context): 141 | bm = itools.get_bmesh() 142 | mode = itools.get_mode() 143 | 144 | if mode == 'VERT': 145 | bpy.ops.transform.vert_slide('INVOKE_DEFAULT') 146 | 147 | elif mode == 'EDGE': 148 | bpy.ops.transform.edge_slide('INVOKE_DEFAULT') 149 | 150 | return{'FINISHED'} 151 | 152 | 153 | class TargetWeldToggle(bpy.types.Operator): 154 | bl_idname = "mesh.target_weld_toggle" 155 | bl_label = "Target Weld On / Off" 156 | bl_description = "Toggles snap to vertex and automerge editing on and off" 157 | bl_options = {'REGISTER', 'UNDO'} 158 | 159 | def toggle_target_weld(self, context): 160 | if context.scene.tool_settings.use_mesh_automerge and bpy.context.scene.tool_settings.use_snap: 161 | context.scene.tool_settings.use_mesh_automerge = False 162 | bpy.context.scene.tool_settings.use_snap = False 163 | else: 164 | context.scene.tool_settings.snap_elements |= {'VERTEX'} 165 | context.scene.tool_settings.use_mesh_automerge = True 166 | bpy.context.scene.tool_settings.use_snap = True 167 | 168 | def execute(self, context): 169 | self.toggle_target_weld(context) 170 | return{'FINISHED'} 171 | 172 | 173 | class QuickModifierToggle(bpy.types.Operator): 174 | bl_idname = "mesh.modifier_toggle" 175 | bl_label = "Modifiers On / Off" 176 | bl_description = "Toggles the modifiers on and off for selected objects" 177 | bl_options = {'REGISTER', 'UNDO'} 178 | 179 | def modifier_toggle(self, context): 180 | mode = itools.get_mode() 181 | 182 | if mode in ['VERT', 'EDGE', 'FACE']: 183 | itools.set_mode('OBJECT') 184 | 185 | selected = itools.get_selected() 186 | 187 | for obj in selected: 188 | if all(modifier.show_in_editmode and modifier.show_viewport for modifier in obj.modifiers): 189 | for modifier in obj.modifiers: 190 | modifier.show_in_editmode = False 191 | modifier.show_viewport = False 192 | 193 | else: 194 | for modifier in obj.modifiers: 195 | modifier.show_in_editmode = True 196 | modifier.show_viewport = True 197 | 198 | if mode in ['VERT', 'EDGE', 'FACE']: 199 | itools.set_mode(mode) 200 | 201 | def execute(self, context): 202 | self.modifier_toggle(context) 203 | return {'FINISHED'} 204 | 205 | 206 | class QuickWireToggle(bpy.types.Operator): 207 | bl_idname = "mesh.wire_toggle" 208 | bl_label = "Wireframe On / Off" 209 | bl_description = "Toggles wire mode on and off on all objects" 210 | bl_options = {'REGISTER', 'UNDO'} 211 | 212 | def wire_toggle(self, context): 213 | if context.space_data.overlay.show_wireframes: 214 | context.space_data.overlay.show_wireframes = False 215 | else: 216 | context.space_data.overlay.show_wireframes = True 217 | 218 | def execute(self, context): 219 | self.wire_toggle(context) 220 | return{'FINISHED'} 221 | 222 | 223 | class TransformOrientationOp(bpy.types.Operator): 224 | bl_idname = "mesh.transform_orientation_op" 225 | bl_label = "Transform Orientation Operator" 226 | bl_description = "Sets up a transform orientation from selected" 227 | bl_options = {'REGISTER', 'UNDO'} 228 | 229 | # Mode 1 - Set Orientation 1 230 | # Mode 2 - Set Orientation 2 231 | # Mode 3 - Set Orientation 3 232 | # Mode 4 - Use Orientation 1 233 | # Mode 5 - Use Orientation 2 234 | # Mode 6 - Use Orientation 3 235 | # Mode 7 - Reset Orientation 236 | 237 | mode: bpy.props.IntProperty(default=0) 238 | target_space = 'NONE' 239 | 240 | def set_target_space(self, context): 241 | if self.mode in [1, 4]: 242 | self.target_space = 'Custom 1' 243 | 244 | elif self.mode in [2, 5]: 245 | self.target_space = 'Custom 2' 246 | 247 | elif self.mode in [3, 6]: 248 | self.target_space = 'Custom 3' 249 | 250 | def make_orientation(self, context): 251 | space = bpy.context.scene.transform_orientation_slots[0].type 252 | selection = itools.get_selected() 253 | 254 | if space in ['GLOBAL', 'LOCAL', 'NORMAL', 'GIMBAL', 'VIEW', 'CURSOR']: 255 | dic.write("stored_transform_orientation", space) 256 | 257 | bpy.ops.transform.create_orientation( 258 | name=self.target_space, use=True, overwrite=True) 259 | 260 | def set_orientation(self, context): 261 | if self.mode in [4, 5, 6]: 262 | try: 263 | bpy.context.scene.transform_orientation_slots[0].type = self.target_space 264 | 265 | except: 266 | self.make_orientation(context) 267 | 268 | else: 269 | if self.mode == 8: 270 | bpy.context.scene.transform_orientation_slots[0].type = 'GLOBAL' 271 | elif self.mode == 9: 272 | bpy.context.scene.transform_orientation_slots[0].type = 'LOCAL' 273 | elif self.mode == 10: 274 | bpy.context.scene.transform_orientation_slots[0].type = 'NORMAL' 275 | elif self.mode == 11: 276 | bpy.context.scene.transform_orientation_slots[0].type = 'GIMBAL' 277 | elif self.mode == 12: 278 | bpy.context.scene.transform_orientation_slots[0].type = 'VIEW' 279 | elif self.mode == 13: 280 | bpy.context.scene.transform_orientation_slots[0].type = 'CURSOR' 281 | 282 | def reset_orientation(self, context): 283 | space = bpy.context.scene.transform_orientation_slots[0].type 284 | new_space = dic.read("stored_transform_orientation") 285 | 286 | if new_space != '': 287 | bpy.context.scene.transform_orientation_slots[0].type = new_space 288 | else: 289 | bpy.context.scene.transform_orientation_slots[0].type = 'GLOBAL' 290 | 291 | def execute(self, context): 292 | self.set_target_space(context) 293 | 294 | if self.mode in [1, 2, 3]: 295 | self.make_orientation(context) 296 | 297 | elif self.mode in [4, 5, 6, 8, 9, 10, 11, 12, 13]: 298 | self.set_orientation(context) 299 | 300 | elif self.mode == 7: 301 | self.reset_orientation(context) 302 | 303 | return{'FINISHED'} 304 | 305 | 306 | class TransformPivotPointOp(bpy.types.Operator): 307 | bl_idname = "mesh.transform_pivot_point_op" 308 | bl_label = "Transform Pivot Point Operator" 309 | bl_description = "Sets up transform Pivot Point" 310 | bl_options = {'REGISTER', 'UNDO'} 311 | 312 | mode: bpy.props.IntProperty(default=0) 313 | 314 | def set_transform_pivot_point(self, context): 315 | if self.mode == 1: 316 | bpy.context.scene.tool_settings.transform_pivot_point = 'MEDIAN_POINT' 317 | elif self.mode == 2: 318 | bpy.context.scene.tool_settings.transform_pivot_point = 'ACTIVE_ELEMENT' 319 | elif self.mode == 3: 320 | bpy.context.scene.tool_settings.transform_pivot_point = 'INDIVIDUAL_ORIGINS' 321 | elif self.mode == 4: 322 | bpy.context.scene.tool_settings.transform_pivot_point = 'CURSOR' 323 | elif self.mode == 5: 324 | bpy.context.scene.tool_settings.transform_pivot_point = 'BOUNDING_BOX_CENTER' 325 | 326 | def execute(self, context): 327 | self.set_transform_pivot_point(context) 328 | 329 | return{'FINISHED'} 330 | 331 | 332 | class TransformOptionsPie(bpy.types.Operator): 333 | bl_idname = "mesh.transform_options_pie" 334 | bl_label = "Transform Orientation Pie" 335 | bl_description = "Sets up a transform orientation from selected" 336 | bl_options = {'REGISTER', 'UNDO'} 337 | 338 | def execute(self, context): 339 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_TransformOptions") 340 | return{'FINISHED'} 341 | 342 | 343 | class SnapPresetsOp(bpy.types.Operator): 344 | bl_idname = "mesh.snap_presets_op" 345 | bl_label = "Quick Snap Presets" 346 | bl_description = "Sets up snapping settings based on presets" 347 | bl_options = {'REGISTER', 'UNDO'} 348 | 349 | # Mode 1 - Grid Absolute 350 | # Mode 2 - Vert Center 351 | # Mode 3 - Vert Closest 352 | # Mode 4 - Face Normal 353 | 354 | mode: bpy.props.IntProperty(default=0) 355 | 356 | def set_preset(self, context): 357 | bpy.context.scene.tool_settings.use_snap_translate = True 358 | bpy.context.scene.tool_settings.use_snap_rotate = True 359 | bpy.context.scene.tool_settings.use_snap_scale = True 360 | 361 | if self.mode == 1: 362 | bpy.context.scene.tool_settings.snap_elements = {'INCREMENT'} 363 | bpy.context.scene.tool_settings.snap_target = 'CLOSEST' 364 | bpy.context.scene.tool_settings.use_snap_grid_absolute = True 365 | bpy.context.scene.tool_settings.use_snap_align_rotation = False 366 | 367 | elif self.mode == 2: 368 | bpy.context.scene.tool_settings.snap_elements = {'VERTEX'} 369 | bpy.context.scene.tool_settings.snap_target = 'CENTER' 370 | bpy.context.scene.tool_settings.use_snap_align_rotation = False 371 | 372 | elif self.mode == 3: 373 | bpy.context.scene.tool_settings.snap_elements = {'VERTEX'} 374 | bpy.context.scene.tool_settings.snap_target = 'CLOSEST' 375 | bpy.context.scene.tool_settings.use_snap_align_rotation = False 376 | 377 | elif self.mode == 4: 378 | bpy.context.scene.tool_settings.snap_elements = {'FACE'} 379 | bpy.context.scene.tool_settings.snap_target = 'CENTER' 380 | bpy.context.scene.tool_settings.use_snap_align_rotation = True 381 | bpy.context.scene.tool_settings.use_snap_project = True 382 | 383 | elif self.mode == 5: 384 | bpy.context.scene.tool_settings.snap_elements = {'EDGE_MIDPOINT'} 385 | bpy.context.scene.tool_settings.snap_target = 'MEDIAN' 386 | bpy.context.scene.tool_settings.use_snap_align_rotation = False 387 | bpy.context.scene.tool_settings.use_snap_project = False 388 | 389 | def execute(self, context): 390 | self.set_preset(context) 391 | return{'FINISHED'} 392 | 393 | 394 | class PropEditOp(bpy.types.Operator): 395 | bl_idname = "mesh.prop_edit_op" 396 | bl_label = "Proportional Editing Op" 397 | bl_description = "Sets up Proportional Editing Falloffs" 398 | bl_options = {'REGISTER', 'UNDO'} 399 | 400 | mode: bpy.props.IntProperty(default=0) 401 | 402 | def set_preset(self, context): 403 | 404 | if self.mode == 1: 405 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'SMOOTH' 406 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 407 | 408 | elif self.mode == 2: 409 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'SPHERE' 410 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 411 | 412 | elif self.mode == 3: 413 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'ROOT' 414 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 415 | 416 | elif self.mode == 4: 417 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'INVERSE_SQUARE' 418 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 419 | 420 | elif self.mode == 5: 421 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'SHARP' 422 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 423 | 424 | elif self.mode == 6: 425 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'LINEAR' 426 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 427 | 428 | elif self.mode == 7: 429 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'CONSTANT' 430 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 431 | 432 | elif self.mode == 8: 433 | bpy.context.scene.tool_settings.proportional_edit_falloff = 'RANDOM' 434 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 435 | 436 | elif self.mode == 9: 437 | if bpy.context.scene.tool_settings.use_proportional_edit_objects: 438 | bpy.context.scene.tool_settings.use_proportional_edit_objects = False 439 | else: 440 | bpy.context.scene.tool_settings.use_proportional_edit_objects = True 441 | 442 | elif self.mode == 10: 443 | if bpy.context.scene.tool_settings.use_proportional_connected: 444 | bpy.context.scene.tool_settings.use_proportional_connected = False 445 | else: 446 | bpy.context.scene.tool_settings.use_proportional_connected = True 447 | 448 | elif self.mode == 11: 449 | if bpy.context.scene.tool_settings.use_proportional_projected: 450 | bpy.context.scene.tool_settings.use_proportional_projected = False 451 | else: 452 | bpy.context.scene.tool_settings.use_proportional_projected = True 453 | 454 | def execute(self, context): 455 | self.set_preset(context) 456 | return{'FINISHED'} 457 | 458 | 459 | class ObjectPropertiesPie(bpy.types.Operator): 460 | bl_idname = "mesh.obj_properties_pie" 461 | bl_label = "Object Properties Pie" 462 | bl_description = "Sets up Object Properties" 463 | bl_options = {'REGISTER', 'UNDO'} 464 | 465 | def execute(self, context): 466 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_ObjectProperties") 467 | return{'FINISHED'} 468 | 469 | 470 | class WireShadedToggle(bpy.types.Operator): 471 | bl_idname = "mesh.wire_shaded_toggle" 472 | bl_label = "Wireframe / Shaded Toggle" 473 | bl_description = "Toggles between wireframe and shaded mode" 474 | bl_options = {'REGISTER', 'UNDO'} 475 | 476 | def wire_shaded_toggle(self, context): 477 | selection = itools.get_selected('OBJECT') 478 | 479 | if len(selection) > 0 and get_enable_wireshaded_cs(): 480 | if all(obj.display_type == 'WIRE' for obj in selection): 481 | for obj in selection: 482 | obj.display_type = 'TEXTURED' 483 | 484 | else: 485 | for obj in selection: 486 | obj.display_type = 'WIRE' 487 | 488 | else: 489 | areas = context.workspace.screens[0].areas 490 | for area in areas: 491 | for space in area.spaces: 492 | if space.type == 'VIEW_3D': 493 | if space.shading.type == 'WIREFRAME': 494 | stored_mode = dic.read("shading_mode") 495 | 496 | if len(stored_mode) < 1: 497 | stored_mode = 'SOLID' 498 | 499 | space.shading.type = stored_mode 500 | else: 501 | dic.write("shading_mode", space.shading.type) 502 | space.shading.type = 'WIREFRAME' 503 | 504 | def execute(self, context): 505 | self.wire_shaded_toggle(context) 506 | return{'FINISHED'} 507 | 508 | 509 | class FlexiBezierToolsCreate(bpy.types.Operator): 510 | bl_idname = "curve.flexitool_create" 511 | bl_label = "Flexi Bezier Tools Create" 512 | bl_description = "Executes Flexi Bezier Tools Create" 513 | bl_options = {'REGISTER', 'UNDO'} 514 | 515 | def execute(self, context): 516 | for area in bpy.context.screen.areas: 517 | if area.type == "VIEW_3D": 518 | bpy.ops.wm.tool_set_by_id(name='flexi_bezier.draw_tool') 519 | return{'FINISHED'} 520 | 521 | 522 | class QuickHpLpNamer(bpy.types.Operator): 523 | bl_idname = "mesh.quick_hplp_namer" 524 | bl_label = "Quick HP Lp Namer" 525 | bl_description = "Helps with naming the hp and lp for name matching baking" 526 | bl_options = {'REGISTER', 'UNDO'} 527 | 528 | def execute(self, context): 529 | selection = [] 530 | selection = itools.get_selected() 531 | lp_suffix = get_quickhplp_lp_suffix() 532 | hp_suffix = get_quickhplp_hp_suffix() 533 | 534 | lp = [obj for obj in selection if obj.name.endswith(lp_suffix)] 535 | if len(lp) != 1: 536 | polycount_list = [len(obj.data.polygons) for obj in selection] 537 | lowest_index = polycount_list.index(min(polycount_list)) 538 | lp = selection[lowest_index] 539 | lp.name = lp.name + lp_suffix 540 | 541 | elif lp[0].name.endswith(lp_suffix): 542 | lp = lp[0] 543 | 544 | for obj in selection: 545 | if obj != lp: 546 | obj.name = lp.name[:-len(lp_suffix)] + hp_suffix 547 | 548 | return{'FINISHED'} 549 | 550 | 551 | class QuickVisualGeoToMesh(bpy.types.Operator): 552 | bl_idname = "mesh.quick_visual_geo_to_mesh" 553 | bl_label = "Quick Visual Geo To Mesh" 554 | bl_description = "Visual Geo To Mesh that also workds from edit mode" 555 | bl_options = {'REGISTER', 'UNDO'} 556 | 557 | def execute(self, context): 558 | mode = itools.get_mode() 559 | if mode in ['VERT', 'EDGE', 'FACE']: 560 | itools.set_mode('OBJECT') 561 | bpy.ops.object.convert(target='MESH') 562 | itools.set_mode(mode) 563 | 564 | else: 565 | bpy.ops.object.convert(target='MESH') 566 | 567 | return{'FINISHED'} 568 | 569 | 570 | class QuickFlattenAxis(bpy.types.Operator): 571 | # Add support for local aligns 572 | 573 | bl_idname = "mesh.quick_flatten" 574 | bl_label = "Quick Flatten" 575 | bl_description = "Quick Flatten with axis options" 576 | bl_options = {'REGISTER', 'UNDO'} 577 | 578 | # Mode 1 - Global 579 | # Mode 2 - Flatten X 580 | # Mode 3 - Flatten Y 581 | # Mode 4 - Flatten Z 582 | 583 | mode: bpy.props.IntProperty(default=0) 584 | 585 | def execute(self, context): 586 | if self.mode == 1: 587 | bpy.ops.mesh.looptools_flatten() 588 | 589 | else: 590 | if self.mode == 2: 591 | axis_transform = (0, 1, 1) 592 | elif self.mode == 3: 593 | axis_transform = (1, 0, 1) 594 | elif self.mode == 4: 595 | axis_transform = (1, 1, 0) 596 | 597 | bpy.ops.transform.resize(value=axis_transform, orient_type='GLOBAL', 598 | mirror=True, use_proportional_edit=False, release_confirm=True) 599 | 600 | return{'FINISHED'} 601 | -------------------------------------------------------------------------------- /op/pivot.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | from ..utils.user_prefs import get_enable_legacy_origin 4 | # Needs optimization pass, possibly merge both into one. Make them proper operators 5 | 6 | 7 | class QuickPivot(bpy.types.Operator): 8 | bl_idname = "mesh.quick_pivot" 9 | bl_label = "Quick Origin" 10 | bl_description = "Quick Pivot Setup based on selection" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def quick_pivot(self, context): 14 | if context.mode == 'OBJECT': 15 | bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS') 16 | elif context.mode == 'EDIT_MESH': 17 | cl = context.scene.cursor.location 18 | pos2 = (cl[0], cl[1], cl[2]) 19 | bpy.ops.view3d.snap_cursor_to_selected() 20 | bpy.ops.object.editmode_toggle() 21 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR') 22 | bpy.ops.object.editmode_toggle() 23 | context.scene.cursor.location = (pos2[0], pos2[1], pos2[2]) 24 | 25 | def execute(self, context): 26 | self.quick_pivot(context) 27 | return{'FINISHED'} 28 | 29 | 30 | class QuickEditPivot(bpy.types.Operator): 31 | bl_idname = "mesh.simple_edit_pivot" 32 | bl_label = "Edit Origin" 33 | bl_description = "Edit pivot position and scale" 34 | bl_options = {'REGISTER', 'UNDO'} 35 | 36 | def create_pivot(self, context, obj): 37 | bpy.ops.object.empty_add(type='ARROWS', location=obj.location) 38 | pivot = bpy.context.active_object 39 | pivot.name = obj.name + ".PivotHelper" 40 | pivot.location = obj.location 41 | print("Pivot") 42 | 43 | def get_pivot(self, context, obj): 44 | pivot = obj.name + ".PivotHelper" 45 | if bpy.data.objects.get(pivot) is None: 46 | return False 47 | else: 48 | bpy.data.objects[obj.name].select_set(False) 49 | bpy.data.objects[pivot].select_set(True) 50 | context.view_layer.objects.active = bpy.data.objects[pivot] 51 | return True 52 | 53 | def apply_pivot(self, context, pivot): 54 | obj = bpy.data.objects[pivot.name[:-12]] 55 | piv_loc = pivot.location 56 | # I need to create piv as it seem like the pivot location is passed by reference? Still no idea why this happens 57 | cl = context.scene.cursor.location 58 | piv = (cl[0], cl[1], cl[2]) 59 | context.scene.cursor.location = piv_loc 60 | bpy.context.view_layer.objects.active = obj 61 | bpy.data.objects[obj.name].select_set(True) 62 | bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN') 63 | context.scene.cursor.location = (piv[0], piv[1], piv[2]) 64 | 65 | # Select pivot, delete it and select obj again 66 | bpy.data.objects[obj.name].select_set(False) 67 | bpy.data.objects[pivot.name].select_set(True) 68 | bpy.ops.object.delete() 69 | bpy.data.objects[obj.name].select_set(True) 70 | context.view_layer.objects.active = obj 71 | 72 | """ 73 | @classmethod 74 | def poll(cls, context): 75 | mode = itools.get_mode() 76 | return mode not in ['VERT', 'EDGE', 'FACE'] 77 | """ 78 | 79 | def execute(self, context): 80 | version = bpy.app.version_string[:4] 81 | 82 | try: 83 | version = float(version) 84 | except ValueError: 85 | version = float(version[:-1]) 86 | 87 | if version >= 2.82 and not get_enable_legacy_origin(): 88 | mode = itools.get_mode() 89 | if mode in ['VERT', 'EDGE', 'FACE']: 90 | itools.set_mode('OBJECT') 91 | 92 | if bpy.context.scene.tool_settings.use_transform_data_origin: 93 | bpy.context.scene.tool_settings.use_transform_data_origin = False 94 | else: 95 | bpy.context.scene.tool_settings.use_transform_data_origin = True 96 | 97 | else: 98 | obj = bpy.context.active_object 99 | if obj.name.endswith(".PivotHelper"): 100 | self.apply_pivot(context, obj) 101 | elif self.get_pivot(context, obj): 102 | piv = bpy.context.active_object 103 | else: 104 | mode = itools.get_mode() 105 | if mode in ['VERT', 'EDGE', 'FACE']: 106 | itools.set_mode('OBJECT') 107 | 108 | self.create_pivot(context, obj) 109 | return{'FINISHED'} 110 | -------------------------------------------------------------------------------- /op/quick_align.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d 3 | from bpy.props import BoolProperty, EnumProperty 4 | from mathutils import Vector 5 | from .. utils import itools as itools 6 | 7 | 8 | class QuickAlign(bpy.types.Operator): 9 | bl_idname = "mesh.quick_align" 10 | bl_label = "Quick Align" 11 | bl_description = "Quickly Align Objects" 12 | bl_options = {'REGISTER', 'UNDO'} 13 | 14 | bb_quality: BoolProperty( 15 | name="High Quality", 16 | description=( 17 | "Enables high quality calculation of the " 18 | "bounding box for perfect results on complex " 19 | "shape meshes with rotation/scale (Slow)" 20 | ), 21 | default=True, 22 | ) 23 | align_mode: EnumProperty( 24 | name="Align Mode:", 25 | description="Side of object to use for alignment", 26 | items=( 27 | ('OPT_1', "Negative Sides", ""), 28 | ('OPT_2', "Centers", ""), 29 | ('OPT_3', "Positive Sides", ""), 30 | ), 31 | default='OPT_2', 32 | ) 33 | relative_to: EnumProperty( 34 | name="Relative To:", 35 | description="Reference location to align to", 36 | items=( 37 | ('OPT_1', "Scene Origin", "Use the Scene Origin as the position for the selected objects to align to"), 38 | ('OPT_2', "3D Cursor", "Use the 3D cursor as the position for the selected objects to align to"), 39 | ('OPT_3', "Selection", "Use the selected objects as the position for the selected objects to align to"), 40 | ('OPT_4', "Active", "Use the active object as the position for the selected objects to align to"), 41 | ), 42 | default='OPT_4', 43 | ) 44 | align_axis: EnumProperty( 45 | name="Align", 46 | description="Align on axis", 47 | items=( 48 | ('X', "X", ""), 49 | ('Y', "Y", ""), 50 | ('Z', "Z", ""), 51 | ), 52 | options={'ENUM_FLAG'}, 53 | default= {'X', 'Y', 'Z'}, 54 | ) 55 | rotation_axis: EnumProperty( 56 | name="Rotation", 57 | description="Align rotation on axis", 58 | items=( 59 | ('X', "X", ""), 60 | ('Y', "Y", ""), 61 | ('Z', "Z", ""), 62 | ), 63 | options={'ENUM_FLAG'}, 64 | ) 65 | scale_axis: EnumProperty( 66 | name="Scale", 67 | description="Align scale on axis", 68 | items=( 69 | ('X', "X", ""), 70 | ('Y', "Y", ""), 71 | ('Z', "Z", ""), 72 | ), 73 | options={'ENUM_FLAG'}, 74 | ) 75 | 76 | raycast = False 77 | target = "" 78 | 79 | 80 | # Get name of the object under the mouse, if nothing is under it will return 'World' 81 | def mouse_raycast(self, context, event): 82 | region = context.region 83 | rv3d = context.region_data 84 | coord = event.mouse_region_x, event.mouse_region_y 85 | 86 | if not all((region, coord, rv3d)): 87 | return 'World' 88 | 89 | view_vector = region_2d_to_vector_3d(region, rv3d, coord) 90 | ray_origin = region_2d_to_origin_3d(region, rv3d, coord, clamp=20) 91 | ray_target = None 92 | ray_target = ray_origin + (view_vector * 1000) 93 | ray_target.normalized() 94 | 95 | result, location, normal, index, object, matrix = context.scene.ray_cast(context.view_layer.depsgraph, 96 | ray_origin, 97 | ray_target) 98 | 99 | 100 | if bpy.context.mode == 'OBJECT': 101 | if result and not object.select_get(): 102 | return object.name 103 | 104 | return 'World' 105 | 106 | @classmethod 107 | def poll(cls, context): 108 | return context.mode == 'OBJECT' and len(context.selected_objects) > 0 109 | 110 | def execute(self, context): 111 | 112 | target_rotation = Vector((0.0, 0.0, 0.0)) 113 | target_scale = Vector((1.0, 1.0, 1.0)) 114 | 115 | # Change mode if target is World 116 | if self.target == 'World': 117 | self.relative_to = 'OPT_1' 118 | 119 | target_rotation.x = 0.0 120 | target_rotation.y = 0.0 121 | target_rotation.z = 0.0 122 | 123 | target_scale.x = 1.0 124 | target_scale.y = 1.0 125 | target_scale.z = 1.0 126 | 127 | else: 128 | self.relative_to = 'OPT_4' 129 | 130 | target_rotation = bpy.data.objects[self.target].rotation_euler 131 | target_scale = bpy.data.objects[self.target].scale 132 | 133 | itools.select(self.target, item=False) 134 | itools.active_set(self.target, item=False) 135 | 136 | # Use default align operator to handle alignment 137 | bpy.ops.object.align(bb_quality=self.bb_quality, align_mode=self.align_mode, 138 | relative_to=self.relative_to, align_axis=self.align_axis) 139 | 140 | # Extend align operator 141 | for obj in self.selected: 142 | if 'X' in self.rotation_axis: 143 | bpy.data.objects[obj].rotation_euler.x = target_rotation.x 144 | 145 | if 'Y' in self.rotation_axis: 146 | bpy.data.objects[obj].rotation_euler.y = target_rotation.y 147 | 148 | if 'Z' in self.rotation_axis: 149 | bpy.data.objects[obj].rotation_euler.z = target_rotation.z 150 | 151 | if 'X' in self.scale_axis: 152 | bpy.data.objects[obj].scale.x = target_scale.x 153 | 154 | if 'Y' in self.scale_axis: 155 | bpy.data.objects[obj].scale.y = target_scale.y 156 | 157 | if 'Z' in self.scale_axis: 158 | bpy.data.objects[obj].scale.z = target_scale.z 159 | 160 | # Deselect Target 161 | if self.target != 'World': 162 | itools.select(self.target, item=False, deselect=True) 163 | 164 | # Make first selected object active again 165 | for obj in self.selected: 166 | itools.active_set(obj, item=False) 167 | 168 | return {'FINISHED'} 169 | 170 | def invoke(self, context, event): 171 | # self.align_axis = {'X', 'Y', 'Z'} 172 | self.selected = itools.get_selected('OBJECT', item=False) 173 | self.target = self.mouse_raycast(context, event) 174 | 175 | return self.execute(context) 176 | 177 | def draw(self, context): 178 | layout = self.layout 179 | col = layout.column() 180 | 181 | row1 = col.row() 182 | row1.label(text="High Quality") 183 | row1.prop(self, "bb_quality", text="") 184 | 185 | row2 = col.row() 186 | row2.label(text="Align Mode") 187 | row2.prop(self, "align_mode", text="") 188 | 189 | row3 = col.row() 190 | row3.label(text="Relative To") 191 | row3.prop(self, "relative_to", text="") 192 | 193 | row4 = col.row() 194 | row4.label(text="Location") 195 | row4.prop(self, "align_axis") 196 | 197 | row5 = col.row() 198 | row5.label(text="Rotation") 199 | row5.prop(self, "rotation_axis") 200 | 201 | row6 = col.row() 202 | row6.label(text="Scale") 203 | row6.prop(self, "scale_axis") 204 | -------------------------------------------------------------------------------- /op/quick_lattice.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d 3 | from bpy.props import BoolProperty, EnumProperty 4 | from mathutils import Vector 5 | from .. utils import itools as itools 6 | 7 | 8 | def set_lattice_resolution(resolution): 9 | bpy.context.object.data.points_u = resolution 10 | bpy.context.object.data.points_v = resolution 11 | bpy.context.object.data.points_w = resolution 12 | 13 | 14 | class QuickLattice(bpy.types.Operator): 15 | bl_idname = "mesh.quick_lattice" 16 | bl_label = "Quick Lattice" 17 | bl_description = "Setup a Quick Lattice" 18 | bl_options = {'REGISTER', 'UNDO'} 19 | 20 | mouseX = 0.0 21 | initial_pos_x = 0.0 22 | sym_count = 0.0 23 | sym_axis = 0 24 | initial_sym_axis = 0 25 | initial_sym_count = 0 26 | offset_obj = "Empty" 27 | selection = "Empty" 28 | senitivity = 0.01 29 | modkey = 0 30 | 31 | def setup_lattice(self, context, selection): 32 | if selection != []: 33 | if context.mode == 'OBJECT': 34 | verts = selection.data.vertices 35 | vert_positions = [ 36 | vert.co @ selection.matrix_world for vert in verts] 37 | rotation = bpy.data.objects[selection.name].rotation_euler 38 | 39 | elif context.mode == 'EDIT_MESH': 40 | bmesh = itools.get_bmesh() 41 | minimum = Vector() 42 | maximum = Vector() 43 | mode = itools.get_mode() 44 | selectionMode = ( 45 | tuple(bpy.context.scene.tool_settings.mesh_select_mode)) 46 | 47 | if mode == 'VERT': 48 | verts = itools.get_selected() 49 | 50 | elif mode == 'EDGE': 51 | edges = itools.get_selected() 52 | verts = [edge.verts for edge in edges] 53 | verts = [vert for vert_pair in verts for vert in vert_pair] 54 | verts = list(set(verts)) 55 | 56 | elif mode == 'FACE': 57 | faces = itools.get_selected() 58 | verts = [face.verts for face in faces] 59 | verts = [vert for vert_pair in verts for vert in vert_pair] 60 | verts = list(set(verts)) 61 | 62 | vert_indexes = [vert.index for vert in verts] 63 | vert_positions = [(selection.matrix_world @ vert.co) 64 | for vert in verts] 65 | 66 | # Make vertex group, assign verts and update viewlayer 67 | itools.set_mode('OBJECT') 68 | 69 | # Remove old vertex group if it existed 70 | vg = selection.vertex_groups.get("lattice_group") 71 | if vg != None: 72 | selection.vertex_groups.remove(vg) 73 | 74 | vg = selection.vertex_groups.new(name="lattice_group") 75 | vg.add(vert_indexes, 1.0, 'ADD') 76 | bpy.context.view_layer.update() 77 | rotation = Vector() 78 | 79 | # Calculate positions 80 | minimum = Vector() 81 | maximum = Vector() 82 | 83 | for axis in range(3): 84 | poslist = [pos[axis] for pos in vert_positions] 85 | maximum[axis] = max(poslist) 86 | minimum[axis] = min(poslist) 87 | 88 | location = (maximum + minimum) / 2 89 | dimensions = maximum - minimum 90 | 91 | # Make sure no axis is 0 as this caused the bug where you couldnt move the lattice. 92 | for axis in range(3): 93 | print(axis) 94 | if dimensions[axis] == 0: 95 | dimensions[axis] = 0.001 96 | 97 | # Add Lattice 98 | bpy.ops.object.add( 99 | type='LATTICE', enter_editmode=False, location=(0, 0, 0)) 100 | lattice = bpy.context.active_object 101 | lattice.data.use_outside = True 102 | lattice.name = selection.name + ".Lattice" 103 | lattice.data.interpolation_type_u = 'KEY_LINEAR' 104 | lattice.data.interpolation_type_v = 'KEY_LINEAR' 105 | lattice.data.interpolation_type_w = 'KEY_LINEAR' 106 | lattice.location = location 107 | lattice.scale = dimensions 108 | lattice.rotation_euler = rotation 109 | 110 | bpy.context.view_layer.objects.active = selection 111 | mod = selection.modifiers.new(name="Lattice", type='LATTICE') 112 | mod.object = lattice 113 | 114 | # Make new vertex group 115 | mod.vertex_group = "lattice_group" 116 | bpy.context.view_layer.objects.active = lattice 117 | 118 | # Deselect obj, select lattice and make it active, switch to edit mode 119 | bpy.data.objects[selection.name].select_set(False) 120 | bpy.data.objects[lattice.name].select_set(True) 121 | bpy.ops.object.editmode_toggle() 122 | 123 | def apply_lattice(self, context, lattice): 124 | if context.mode == 'EDIT_MESH': 125 | bpy.ops.object.editmode_toggle() 126 | obj = bpy.data.objects[lattice.name[:-8]] 127 | bpy.data.objects[lattice.name].select_set(False) 128 | bpy.data.objects[obj.name].select_set(True) 129 | bpy.context.view_layer.objects.active = obj 130 | 131 | # Fix for blender 2.90 132 | version = bpy.app.version_string[:4] 133 | 134 | try: 135 | version = float(version) 136 | except ValueError: 137 | version = float(version[:-1]) 138 | 139 | if version >= 2.90: 140 | bpy.ops.object.modifier_apply(modifier="Lattice") 141 | else: 142 | bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Lattice") 143 | 144 | # Delete vertex group 145 | vg = obj.vertex_groups.get("lattice_group") 146 | if vg != None: 147 | obj.vertex_groups.remove(vg) 148 | 149 | # Delete lattice 150 | bpy.data.objects[obj.name].select_set(False) 151 | bpy.data.objects.remove(bpy.data.objects[lattice.name]) 152 | bpy.data.objects[obj.name].select_set(True) 153 | bpy.ops.object.mode_set(mode='EDIT') 154 | 155 | def get_lattice(self, context, obj): 156 | lattice = obj.name + ".Lattice" 157 | if bpy.data.objects.get(lattice) is None: 158 | return False 159 | else: 160 | bpy.data.objects[obj.name].select_set(False) 161 | bpy.data.objects[lattice].select_set(True) 162 | context.view_layer.objects.active = bpy.data.objects[lattice] 163 | bpy.ops.object.editmode_toggle() 164 | return True 165 | 166 | @classmethod 167 | def poll(cls, context): 168 | mode = itools.get_mode() 169 | cond_a = mode in ['VERT', 'EDGE', 'FACE'] and len( 170 | itools.get_selected()) > 0 171 | cond_b = mode == 'OBJECT' and len(context.selected_objects) == 1 172 | 173 | if len(context.selected_objects) > 0: 174 | cond_c = context.selected_objects[0].type == 'LATTICE' 175 | 176 | else: 177 | cond_c = False 178 | 179 | return cond_a or cond_b or cond_c 180 | 181 | def execute(self, context): 182 | selection = bpy.context.active_object 183 | if selection.name.endswith(".Lattice"): 184 | self.apply_lattice(context, selection) 185 | elif self.get_lattice(context, selection): 186 | lattice = bpy.context.active_object 187 | else: 188 | self.setup_lattice(context, selection) 189 | return{'FINISHED'} 190 | 191 | 192 | class LatticeResolution2x2x2(bpy.types.Operator): 193 | bl_idname = "mesh.lattice_resolution_2x2x2" 194 | bl_label = "Lattice Resolution 2x2x2" 195 | bl_description = "Set Latice Resolution to 2x2x2" 196 | bl_options = {'REGISTER', 'UNDO'} 197 | 198 | def execute(self, context): 199 | set_lattice_resolution(2) 200 | return{'FINISHED'} 201 | 202 | 203 | class LatticeResolution3x3x3(bpy.types.Operator): 204 | bl_idname = "mesh.lattice_resolution_3x3x3" 205 | bl_label = "Lattice Resolution 3x3x3" 206 | bl_description = "Set Latice Resolution to 3x3x3" 207 | bl_options = {'REGISTER', 'UNDO'} 208 | 209 | def execute(self, context): 210 | set_lattice_resolution(3) 211 | return{'FINISHED'} 212 | 213 | 214 | class LatticeResolution4x4x4(bpy.types.Operator): 215 | bl_idname = "mesh.lattice_resolution_4x4x4" 216 | bl_label = "Lattice Resolution 4x4x4" 217 | bl_description = "Set Latice Resolution to 4x4x4" 218 | bl_options = {'REGISTER', 'UNDO'} 219 | 220 | def execute(self, context): 221 | set_lattice_resolution(4) 222 | return{'FINISHED'} 223 | -------------------------------------------------------------------------------- /op/quick_pipe.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math 3 | import blf 4 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d 5 | from ..utils.user_prefs import get_radsym_hide_pivot 6 | from ..utils import itools as itools 7 | from bpy.props import EnumProperty, IntProperty 8 | 9 | 10 | class QuickPipe(bpy.types.Operator): 11 | bl_idname = "mesh.quick_pipe" 12 | bl_label = "Quick Pipe" 13 | bl_description = "Generates a Pipe from selection" 14 | bl_options = {'REGISTER', 'UNDO', 'GRAB_CURSOR', "BLOCKING"} 15 | 16 | mouse_x = 0.0 17 | mouse_mult = 1.0 18 | initial_pos_x = 0.0 19 | depth = 0 20 | resolution = 0 21 | initial_resolution = 0 22 | initial_depth = 0 23 | original_resolution = 0 24 | original_depth = 0 25 | selection = "Empty" 26 | senitivity = 0.01 27 | modkey = 0 28 | using_settings = False 29 | ignore_initial_depth = False 30 | ignore_initial_resolution = False 31 | change_resolution = False 32 | change_depth = False 33 | first_run = False 34 | 35 | def draw_ui(self, context, event): 36 | width = bpy.context.area.width 37 | 38 | font_id = 0 39 | blf.color(font_id, 1, 1, 1, 1) 40 | blf.position(font_id, width / 2 - 100, 140, 0) 41 | blf.size(font_id, 25) 42 | blf.draw(font_id, "Depth: ") 43 | 44 | blf.color(font_id, 0, 0.8, 1, 1) 45 | blf.position(font_id, width / 2, 140, 0) 46 | blf.size(font_id, 25) 47 | blf.draw(font_id, str(self.depth)[0:4]) 48 | 49 | blf.color(font_id, 1, 1, 1, 1) 50 | blf.position(font_id, width / 2 - 100, 100, 0) 51 | blf.draw(font_id, "Resolution: ") 52 | 53 | blf.color(font_id, 0, .8, 1, 1) 54 | blf.position(font_id, width / 2 + 40, 100, 0) 55 | blf.size(font_id, 25) 56 | blf.draw(font_id, str(self.resolution)) 57 | 58 | 59 | def setup_pipe(self, context, selection): 60 | if selection != []: 61 | #Select object: 62 | itools.set_mode('OBJECT') 63 | base_obj = itools.get_selected('OBJECT') 64 | 65 | #Separate edge: 66 | itools.set_mode('EDGE') 67 | bpy.ops.mesh.duplicate_move() 68 | bpy.ops.mesh.separate(type='SELECTED') 69 | bpy.ops.mesh.delete(type='EDGE') 70 | itools.set_mode('OBJECT') 71 | new_sel = itools.get_selected('OBJECT') 72 | pipe_obj = new_sel[1] 73 | 74 | #Convert to curve 75 | itools.select(pipe_obj.name, mode='OBJECT', item=False, replace=True) 76 | context.view_layer.objects.active = pipe_obj 77 | bpy.ops.object.convert(target='CURVE') 78 | pipe_obj.data.bevel_depth = 0.5 79 | bpy.ops.object.shade_smooth() 80 | pipe_obj.name = 'Pipe' 81 | 82 | self.selection = pipe_obj.name 83 | 84 | self.first_run = True 85 | 86 | 87 | dg = bpy.context.evaluated_depsgraph_get() 88 | dg.update() 89 | 90 | def calculate_depth(self, context, selection): 91 | if self.change_depth: 92 | if self.using_settings: 93 | self.depth = self.ui_count 94 | 95 | else: 96 | if self.ignore_initial_depth: 97 | self.depth = ((self.mouse_x - self.initial_pos_x) * self.senitivity * self.mouse_mult) 98 | else: 99 | self.depth = self.initial_depth + ((self.mouse_x - self.initial_pos_x) * self.senitivity * self.mouse_mult) 100 | 101 | if self.depth < 0: 102 | self.depth = 0 103 | self.initial_pos_x = self.mouse_x 104 | self.ignore_initial_depth = True 105 | 106 | bpy.context.view_layer.objects.active = selection 107 | 108 | bpy.context.object.data.bevel_depth = self.depth 109 | self.change_depth = False 110 | 111 | def calculate_resolution(self, context, selection): 112 | if self.change_resolution: 113 | if self.using_settings: 114 | self.resolution = self.ui_count 115 | 116 | else: 117 | if self.ignore_initial_resolution: 118 | self.resolution = int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 119 | else: 120 | self.resolution = self.initial_resolution + int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 121 | 122 | if self.resolution < 1: 123 | self.resolution = 1 124 | self.initial_pos_x = self.mouse_x 125 | self.ignore_initial_resolution = True 126 | 127 | bpy.context.view_layer.objects.active = selection 128 | bpy.context.object.data.bevel_resolution = self.resolution 129 | self.change_resolution = False 130 | 131 | def recover_settings(self, context, selection): 132 | self.initial_resolution = bpy.context.object.data.bevel_resolution 133 | self.initial_depth = bpy.context.object.data.bevel_depth 134 | 135 | self.resolution = self.initial_resolution 136 | self.depth = self.initial_depth 137 | 138 | if not self.first_run: 139 | self.original_resolution = self.initial_resolution 140 | self.original_depth = self.initial_depth 141 | 142 | def restore_settings(self, context, selection): 143 | bpy.context.object.data.bevel_resolution = self.original_resolution 144 | bpy.context.object.data.bevel_depth = self.original_depth 145 | 146 | def __init__(self): 147 | print("Start") 148 | 149 | def __del__(self): 150 | print("End") 151 | 152 | """ 153 | @classmethod 154 | def poll(cls, context): 155 | return ((context.mode == 'OBJECT' and bpy.context.object.modifiers.find("Cylindrical Sides") > -1) or 156 | bpy.context.mode == 'EDIT_MESH') 157 | """ 158 | 159 | def execute(self, context): 160 | #self.sync_ui_settings() 161 | self.calculate_depth(context, bpy.data.objects[self.selection]) 162 | self.calculate_resolution(context, bpy.data.objects[self.selection]) 163 | 164 | return{'FINISHED'} 165 | 166 | def modal(self, context, event): 167 | if event.type == 'MOUSEMOVE': # Apply 168 | if event.ctrl: 169 | if self.modkey != 1: 170 | self.modkey = 1 171 | self.initial_pos_x = event.mouse_x 172 | self.initial_resolution = self.resolution 173 | self.change_resolution = True 174 | 175 | else: 176 | if self.modkey != 0: 177 | self.modkey = 0 178 | self.initial_pos_x = event.mouse_x 179 | self.initial_depth = self.depth 180 | if event.shift: 181 | self.mouse_mult = 0.5 182 | else: 183 | self.mouse_mult = 1.0 184 | 185 | 186 | self.change_depth = True 187 | 188 | self.mouse_x = event.mouse_x 189 | self.execute(context) 190 | 191 | elif event.type == 'LEFTMOUSE': # Confirm 192 | if event.value == 'RELEASE': 193 | self.using_settings = True 194 | context.area.header_text_set(text=None) 195 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 196 | 197 | #Switch between modes to force update UI 198 | bpy.ops.object.mode_set(mode='EDIT') 199 | bpy.ops.object.mode_set(mode='OBJECT') 200 | return {'FINISHED'} 201 | 202 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Cancel 203 | if not self.first_run: 204 | self.restore_settings(context, bpy.data.objects[self.selection]) 205 | 206 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 207 | context.area.header_text_set(text=None) 208 | 209 | #Switch between modes to force update UI 210 | bpy.ops.object.mode_set(mode='EDIT') 211 | bpy.ops.object.mode_set(mode='OBJECT') 212 | 213 | if self.first_run: 214 | bpy.ops.object.delete(use_global=False, confirm=False) 215 | return {'CANCELLED'} 216 | 217 | #Tooltip 218 | context.area.header_text_set("LMB: confirm, RMB:Cancel, Mouse Left/Right for depth, CTRL + Mouse Left/ Right to change resolution, Shift for Precision Mode") 219 | return {'RUNNING_MODAL'} 220 | 221 | def invoke(self, context, event): 222 | self.initial_pos_x = event.mouse_x 223 | self.selection = bpy.context.active_object.name 224 | 225 | if not "Pipe" in self.selection: 226 | self.setup_pipe(context, bpy.data.objects[self.selection]) 227 | 228 | self.recover_settings(context, bpy.data.objects[self.selection]) 229 | 230 | self.draw_handler = bpy.types.SpaceView3D.draw_handler_add(self.draw_ui, (self, context), 'WINDOW','POST_PIXEL') 231 | 232 | self.execute(context) 233 | context.window_manager.modal_handler_add(self) 234 | return {'RUNNING_MODAL'} 235 | -------------------------------------------------------------------------------- /op/radial_symmetry.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import math 3 | import blf 4 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d 5 | from ..utils.user_prefs import get_radsym_hide_pivot 6 | from bpy.props import EnumProperty, IntProperty 7 | import datetime 8 | 9 | 10 | class QuickRadialSymmetry(bpy.types.Operator): 11 | bl_idname = "mesh.radial_symmetry" 12 | bl_label = "Radial Symmetry" 13 | bl_description = "Setup a Quick Radial Symmetry" 14 | bl_options = {'REGISTER', 'UNDO', "GRAB_CURSOR", "BLOCKING"} 15 | 16 | """ 17 | Editing menu disabled until I figure out solution to bug 18 | ui_axis: EnumProperty( 19 | name="Symmetry Axis:", 20 | description="Axis to use for the symmetry", 21 | items=( 22 | ('X', "X", ""), 23 | ('Y', "Y", ""), 24 | ('Z', "Z", ""), 25 | ), 26 | options={'ENUM_FLAG'}, 27 | ) 28 | 29 | ui_count: IntProperty( 30 | name="Number :", 31 | description="Number of iterations", 32 | default=1, 33 | min=1, 34 | ) 35 | """ 36 | 37 | mouse_x = 0.0 38 | initial_pos_x = 0.0 39 | sym_count = 0 40 | sym_axis = 0 41 | initial_sym_axis = 0 42 | initial_sym_count = 0 43 | original_sym_axis = 0 44 | original_sym_count = 0 45 | offset_obj = "Empty" 46 | selection = "Empty" 47 | senitivity = 0.01 48 | modkey = 0 49 | using_settings = False 50 | ignore_initial_sym_count = False 51 | change_axis = False 52 | change_rotation = False 53 | change_iteration = False 54 | symmetry_center = "" 55 | first_run = False 56 | 57 | def draw_ui(self, context, event): 58 | width = bpy.context.area.width 59 | 60 | font_id = 0 61 | blf.color(font_id, 1, 1, 1, 1) 62 | blf.position(font_id, width / 2 - 100, 140, 0) 63 | blf.size(font_id, 25) 64 | blf.draw(font_id, "Count: ") 65 | 66 | blf.color(font_id, 0, 0.8, 1, 1) 67 | blf.position(font_id, width / 2, 140, 0) 68 | blf.size(font_id, 25) 69 | blf.draw(font_id, str(self.sym_count)) 70 | 71 | blf.color(font_id, 1, 1, 1, 1) 72 | blf.position(font_id, width / 2 - 100, 100, 0) 73 | blf.draw(font_id, "Axis: ") 74 | 75 | blf.color(font_id, 0, .8, 1, 1) 76 | blf.position(font_id, width / 2, 100, 0) 77 | blf.size(font_id, 25) 78 | blf.draw(font_id, str(self.ui_axis)[2]) 79 | 80 | blf.color(font_id, 1, 1, 1, 1) 81 | blf.position(font_id, width / 2 - 100, 60, 0) 82 | blf.draw(font_id, "Show Origin: ") 83 | 84 | blf.color(font_id, 0, .8, 1, 1) 85 | blf.position(font_id, width / 2 + 60, 60, 0) 86 | blf.size(font_id, 25) 87 | blf.draw(font_id, str(not bpy.data.objects[self.offset_obj].hide_viewport)) 88 | 89 | def setup_symmetry(self, context, selection): 90 | if selection != []: 91 | sel_pivot = selection.location 92 | new_obj = bpy.data.objects.new('new_obj', None) 93 | bpy.ops.object.empty_add(type='ARROWS', location=sel_pivot) 94 | self.symmetry_center = bpy.context.active_object 95 | self.symmetry_center.rotation_euler = (0, 0, math.radians(120)) 96 | self.symmetry_center.name = selection.name + ".SymmetryPivot" 97 | self.symmetry_center.select_set(False) 98 | 99 | #Parent the pivot to the object and manage visibility 100 | self.symmetry_center.parent = selection 101 | self.symmetry_center.hide_viewport = get_radsym_hide_pivot() 102 | selection.select_set(True) 103 | 104 | #Clear Pivot Transform 105 | self.symmetry_center.location = (0,0,0) 106 | 107 | # Create modifier and assign name 108 | mod = selection.modifiers.new(name="Radial Symmetry", type='ARRAY') 109 | mod.relative_offset_displace[0] = 0 110 | mod.count = 3 111 | mod.offset_object = bpy.data.objects[self.symmetry_center.name] 112 | mod.use_object_offset = True 113 | 114 | # Update both depsgraph and viewlayer 115 | bpy.context.view_layer.objects.active = selection 116 | bpy.context.view_layer.update() 117 | 118 | dg = bpy.context.evaluated_depsgraph_get() 119 | dg.update() 120 | 121 | def calculate_iterations(self, context, selection): 122 | if self.change_iteration: 123 | if self.using_settings: 124 | self.sym_count = self.ui_count 125 | 126 | else: 127 | if self.ignore_initial_sym_count: 128 | self.sym_count = int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 129 | else: 130 | self.sym_count = self.initial_sym_count + int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 131 | 132 | if self.sym_count < 1: 133 | self.sym_count = 1 134 | self.initial_pos_x = self.mouse_x 135 | self.ignore_initial_sym_count = True 136 | 137 | bpy.context.view_layer.objects.active = selection 138 | 139 | selection.modifiers["Radial Symmetry"].count = self.sym_count 140 | self.change_iteration = False 141 | 142 | def calculate_axis(self, context): 143 | if self.change_axis: 144 | self.sym_axis = int((self.initial_sym_axis + (self.mouse_x - self.initial_pos_x) * self.senitivity) % 3) 145 | self.change_axis = False 146 | 147 | def calculate_rotation(self, axis): 148 | if self.change_rotation: 149 | if axis == 0: 150 | bpy.data.objects[self.offset_obj].rotation_euler = (math.radians(360 / self.sym_count), 0, 0) 151 | 152 | elif axis == 1: 153 | bpy.data.objects[self.offset_obj].rotation_euler = (0, math.radians(360 / self.sym_count), 0) 154 | 155 | elif axis == 2: 156 | bpy.data.objects[self.offset_obj].rotation_euler = (0, 0, math.radians(360 / self.sym_count)) 157 | self.change_rotation = False 158 | 159 | def sync_ui_settings(self): 160 | global axis 161 | # Use UI settings to drive parameters 162 | 163 | if self.using_settings: 164 | if 'X' in self.ui_axis: 165 | self.sym_axis = 0 166 | 167 | elif 'Y' in self.ui_axis: 168 | self.sym_axis = 1 169 | 170 | elif 'Z' in self.ui_axis: 171 | self.sym_axis = 2 172 | 173 | self.sym_count = self.ui_count 174 | 175 | self.change_iteration = True 176 | self.change_rotation = True 177 | 178 | # Sync UI information 179 | else: 180 | if self.sym_axis == 0: 181 | self.ui_axis = {'X'} 182 | 183 | elif self.sym_axis == 1: 184 | self.ui_axis = {'Y'} 185 | 186 | elif self.sym_axis == 2: 187 | self.ui_axis = {'Z'} 188 | 189 | self.ui_count = self.sym_count 190 | 191 | def recover_settings(self, context, selection): 192 | mod = selection.modifiers["Radial Symmetry"] 193 | self.initial_sym_count = mod.count 194 | self.offset_obj = mod.offset_object.name 195 | rotation = mod.offset_object.rotation_euler 196 | 197 | if rotation[0] > 0: 198 | self.initial_sym_axis = 0 199 | 200 | elif rotation[1] > 0: 201 | self.initial_sym_axis = 1 202 | 203 | elif rotation[2] > 0: 204 | self.initial_sym_axis = 2 205 | 206 | self.sym_axis = self.initial_sym_axis 207 | self.sym_count = self.initial_sym_count 208 | 209 | if not self.first_run: 210 | self.original_sym_axis = self.initial_sym_axis 211 | self.original_sym_count = self.initial_sym_count 212 | 213 | def restore_settings(self, context, selection): 214 | selection.modifiers["Radial Symmetry"].count = self.original_sym_count 215 | 216 | if self.original_sym_axis == 0: 217 | bpy.data.objects[self.offset_obj].rotation_euler = (math.radians(360 / self.original_sym_count), 0, 0) 218 | 219 | elif self.original_sym_axis == 1: 220 | bpy.data.objects[self.offset_obj].rotation_euler = (0, math.radians(360 / self.original_sym_count), 0) 221 | 222 | elif self.original_sym_axis == 2: 223 | bpy.data.objects[self.offset_obj].rotation_euler = (0, 0, math.radians(360 / self.original_sym_count)) 224 | 225 | def __init__(self): 226 | print("Start") 227 | 228 | def __del__(self): 229 | print("End") 230 | 231 | @classmethod 232 | def poll(cls, context): 233 | return context.mode == 'OBJECT' and len(context.selected_objects) > 0 234 | 235 | def execute(self, context): 236 | self.sync_ui_settings() 237 | self.calculate_iterations(context, bpy.data.objects[self.selection]) 238 | self.calculate_axis(context) 239 | self.calculate_rotation(self.sym_axis) 240 | return{'FINISHED'} 241 | 242 | def modal(self, context, event): 243 | if event.type == 'MOUSEMOVE': # Apply 244 | if event.ctrl: 245 | if self.modkey != 1: 246 | self.modkey = 1 247 | self.initial_pos_x = event.mouse_x 248 | self.initial_sym_count = self.sym_count 249 | self.change_axis = True 250 | 251 | else: 252 | if self.modkey != 0: 253 | self.modkey = 0 254 | self.initial_pos_x = event.mouse_x 255 | self.initial_sym_axis = self.sym_axis 256 | self.change_iteration = True 257 | 258 | self.mouse_x = event.mouse_x 259 | self.execute(context) 260 | self.change_rotation = True 261 | 262 | elif event.type in {'H', 'Z'}: # Show Pivot 263 | if event.value == 'RELEASE': 264 | if bpy.data.objects[self.offset_obj].hide_viewport: 265 | bpy.data.objects[self.offset_obj].hide_viewport = False 266 | else: 267 | bpy.data.objects[self.offset_obj].hide_viewport = True 268 | 269 | elif event.type == 'LEFTMOUSE': # Confirm 270 | if event.value == 'RELEASE': 271 | self.using_settings = True 272 | context.area.header_text_set(text=None) 273 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 274 | 275 | #Switch between modes to force update UI 276 | bpy.ops.object.mode_set(mode='EDIT') 277 | bpy.ops.object.mode_set(mode='OBJECT') 278 | return {'FINISHED'} 279 | 280 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Cancel 281 | if not self.first_run: 282 | self.restore_settings(context, bpy.data.objects[self.selection]) 283 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 284 | context.area.header_text_set(text=None) 285 | 286 | #Switch between modes to force update UI 287 | bpy.ops.object.mode_set(mode='EDIT') 288 | bpy.ops.object.mode_set(mode='OBJECT') 289 | 290 | if self.first_run: 291 | bpy.ops.object.modifier_remove(modifier="Radial Symmetry") 292 | 293 | return {'CANCELLED'} 294 | 295 | #Tooltip 296 | context.area.header_text_set("LMB: confirm, RMB:Cancel, Mouse Left/Right for number of instances, CTRL + Mouse Left/ Right to change Symmetry Axis H: Show / Hide Origin") 297 | return {'RUNNING_MODAL'} 298 | 299 | def invoke(self, context, event): 300 | self.initial_pos_x = event.mouse_x 301 | self.selection = bpy.context.active_object.name 302 | 303 | if bpy.data.objects[self.selection].modifiers.find("Radial Symmetry") < 0: 304 | self.setup_symmetry(context, bpy.data.objects[self.selection]) 305 | self.first_run = True 306 | 307 | self.recover_settings(context, bpy.data.objects[self.selection]) 308 | 309 | self.draw_handler = bpy.types.SpaceView3D.draw_handler_add(self.draw_ui, (self, context), 'WINDOW','POST_PIXEL') 310 | 311 | self.execute(context) 312 | context.window_manager.modal_handler_add(self) 313 | return {'RUNNING_MODAL'} 314 | 315 | """ 316 | Editing menu disabled until I figure out solution to bug 317 | def draw(self, context): 318 | layout = self.layout 319 | col = layout.column() 320 | 321 | row1 = col.row() 322 | row1.label(text="Count") 323 | row1.prop(self, "ui_count", text="") 324 | 325 | row2 = col.row() 326 | row2.label(text="Axis") 327 | row2.prop(self, "ui_axis") 328 | """ 329 | -------------------------------------------------------------------------------- /op/rebase_cylinder.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .. utils import itools as itools 3 | import math 4 | import blf 5 | from bpy_extras.view3d_utils import region_2d_to_vector_3d, region_2d_to_origin_3d 6 | from ..utils.user_prefs import get_radsym_hide_pivot 7 | from bpy.props import EnumProperty, IntProperty 8 | import datetime 9 | 10 | 11 | class RebaseCylinder(bpy.types.Operator): 12 | bl_idname = "mesh.rebase_cylinder" 13 | bl_label = "Rebase Cylinder" 14 | bl_description = "Reconstruct cylinder with a different number of sides" 15 | bl_options = {'REGISTER', 'UNDO', "GRAB_CURSOR", "BLOCKING"} 16 | 17 | mouse_x = 0.0 18 | initial_pos_x = 0.0 19 | sides_count = 0 20 | rebase_axis = 0 21 | merge_distance = 0.0 22 | initial_rebase_axis = 0 23 | initial_sides_count = 0 24 | initial_merge_distance = 0.0 25 | original_rebase_axis = 0 26 | original_sides_count = 0 27 | original_merge_distance = 0.0 28 | selection = "Empty" 29 | senitivity = 0.01 30 | modkey = 0 31 | using_settings = False 32 | ignore_initial_sym_count = False 33 | change_axis = False 34 | change_iteration = False 35 | symmetry_center = "" 36 | first_run = False 37 | 38 | def draw_ui(self, context, event): 39 | width = bpy.context.area.width 40 | 41 | font_id = 0 42 | blf.color(font_id, 1, 1, 1, 1) 43 | blf.position(font_id, width / 2 - 100, 140, 0) 44 | blf.size(font_id, 25) 45 | blf.draw(font_id, "Count: ") 46 | 47 | blf.color(font_id, 0, 0.8, 1, 1) 48 | blf.position(font_id, width / 2, 140, 0) 49 | blf.size(font_id, 25) 50 | blf.draw(font_id, str(self.sides_count)) 51 | 52 | blf.color(font_id, 1, 1, 1, 1) 53 | blf.position(font_id, width / 2 - 100, 100, 0) 54 | blf.draw(font_id, "Axis: ") 55 | 56 | blf.color(font_id, 0, .8, 1, 1) 57 | blf.position(font_id, width / 2, 100, 0) 58 | blf.size(font_id, 25) 59 | blf.draw(font_id, str(self.ui_axis)[2]) 60 | 61 | blf.color(font_id, 1, 1, 1, 1) 62 | blf.position(font_id, width / 2 - 100, 60, 0) 63 | blf.draw(font_id, "Merge Dist: ") 64 | 65 | blf.color(font_id, 0, .8, 1, 1) 66 | blf.position(font_id, width / 2 + 60, 60, 0) 67 | blf.size(font_id, 25) 68 | blf.draw(font_id, str(self.merge_distance)[0:6]) 69 | 70 | def setup_rebase(self, context, selection): 71 | if selection != []: 72 | # Separate Selected Edge 73 | bpy.ops.mesh.separate(type='SELECTED') 74 | new_selection = bpy.context.selected_objects 75 | mesh_to_select = list(filter(lambda x: x.name != selection.name, new_selection)) 76 | bpy.ops.object.editmode_toggle() 77 | bpy.ops.object.select_all(action='DESELECT') 78 | bpy.data.objects[mesh_to_select[0].name].select_set(state=True) 79 | bpy.context.view_layer.objects.active = mesh_to_select[0] 80 | self.selection = mesh_to_select[0].name 81 | 82 | # Create modifier and assign name 83 | bpy.ops.object.modifier_add(type='SCREW') 84 | mod = bpy.context.object.modifiers["Screw"] 85 | mod.name = "Cylindrical Sides" 86 | mod.use_merge_vertices = True 87 | 88 | # Update both depsgraph and viewlayer 89 | bpy.context.view_layer.objects.active = selection 90 | bpy.context.view_layer.update() 91 | 92 | self.first_run = True 93 | 94 | dg = bpy.context.evaluated_depsgraph_get() 95 | dg.update() 96 | 97 | def calculate_iterations(self, context, selection): 98 | if self.change_iteration: 99 | if self.using_settings: 100 | self.sides_count = self.ui_count 101 | 102 | else: 103 | if self.ignore_initial_sym_count: 104 | self.sides_count = int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 105 | else: 106 | self.sides_count = self.initial_sides_count + int(((self.mouse_x - self.initial_pos_x) * self.senitivity)) 107 | 108 | if self.sides_count < 1: 109 | self.sides_count = 1 110 | self.initial_pos_x = self.mouse_x 111 | self.ignore_initial_sym_count = True 112 | 113 | bpy.context.view_layer.objects.active = selection 114 | 115 | selection.modifiers["Cylindrical Sides"].steps = self.sides_count 116 | self.change_iteration = False 117 | 118 | def calculate_axis(self, context, selection): 119 | if self.change_axis: 120 | self.rebase_axis = int((self.initial_rebase_axis + (self.mouse_x - self.initial_pos_x) * self.senitivity) % 3) 121 | 122 | if self.rebase_axis == 0: 123 | selection.modifiers["Cylindrical Sides"].axis = "X" 124 | 125 | elif self.rebase_axis == 1: 126 | selection.modifiers["Cylindrical Sides"].axis = "Y" 127 | 128 | elif self.rebase_axis == 2: 129 | selection.modifiers["Cylindrical Sides"].axis = "Z" 130 | 131 | self.change_axis = False 132 | 133 | def calculate_merge_distance(self, context, selection): 134 | if self.merge_distance == 0.01: 135 | self.merge_distance = 0.001 136 | 137 | elif self.merge_distance == 0.001: 138 | self.merge_distance = 0.0001 139 | 140 | else: 141 | self.merge_distance = 0.01 142 | 143 | selection.modifiers["Cylindrical Sides"].merge_threshold = self.merge_distance 144 | 145 | 146 | def sync_ui_settings(self): 147 | global axis 148 | # Use UI settings to drive parameters 149 | if self.using_settings: 150 | if 'X' in self.ui_axis: 151 | self.rebase_axis = 0 152 | 153 | elif 'Y' in self.ui_axis: 154 | self.rebase_axis = 1 155 | 156 | elif 'Z' in self.ui_axis: 157 | self.rebase_axis = 2 158 | 159 | self.sides_count = self.ui_count 160 | 161 | self.change_iteration = True 162 | self.change_rotation = True 163 | 164 | # Sync UI information 165 | else: 166 | if self.rebase_axis == 0: 167 | self.ui_axis = {'X'} 168 | 169 | elif self.rebase_axis == 1: 170 | self.ui_axis = {'Y'} 171 | 172 | elif self.rebase_axis == 2: 173 | self.ui_axis = {'Z'} 174 | 175 | self.ui_count = self.sides_count 176 | 177 | def recover_settings(self, context, selection): 178 | mod = selection.modifiers["Cylindrical Sides"] 179 | self.initial_sides_count = mod.steps 180 | self.initial_merge_distance = mod.merge_threshold 181 | 182 | if mod.axis == 'X': 183 | self.initial_rebase_axis = 0 184 | 185 | elif mod.axis == 'Y': 186 | self.initial_rebase_axis = 1 187 | 188 | elif mod.axis == 'Z': 189 | self.initial_rebase_axis = 2 190 | 191 | self.rebase_axis = self.initial_rebase_axis 192 | self.sides_count = self.initial_sides_count 193 | self.merge_distance = self.initial_merge_distance 194 | 195 | if not self.first_run: 196 | self.original_rebase_axis = self.initial_rebase_axis 197 | self.original_sides_count = self.initial_sides_count 198 | self.original_merge_distance = self.initial_merge_distance 199 | 200 | 201 | def restore_settings(self, context, selection): 202 | mod = selection.modifiers["Cylindrical Sides"] 203 | mod.steps = self.original_sides_count 204 | 205 | if self.original_rebase_axis == 0: 206 | mod.axis = "X" 207 | 208 | elif self.original_rebase_axis == 1: 209 | mod.axis = "Y" 210 | 211 | elif self.original_rebase_axis == 2: 212 | mod.axis = "Z" 213 | 214 | mod.merge_threshold = self.original_merge_distance 215 | 216 | def __init__(self): 217 | print("Start") 218 | 219 | def __del__(self): 220 | print("End") 221 | 222 | @classmethod 223 | def poll(cls, context): 224 | if bpy.context.object != None: 225 | return ((context.mode == 'OBJECT' and bpy.context.object.modifiers.find("Cylindrical Sides") > -1) or 226 | bpy.context.mode == 'EDIT_MESH') 227 | else: 228 | return False 229 | 230 | def execute(self, context): 231 | self.sync_ui_settings() 232 | self.calculate_iterations(context, bpy.data.objects[self.selection]) 233 | self.calculate_axis(context, bpy.data.objects[self.selection]) 234 | 235 | return{'FINISHED'} 236 | 237 | def modal(self, context, event): 238 | if event.type == 'MOUSEMOVE': # Apply 239 | if event.ctrl: 240 | if self.modkey != 1: 241 | self.modkey = 1 242 | self.initial_pos_x = event.mouse_x 243 | self.initial_sides_count = self.sides_count 244 | self.change_axis = True 245 | 246 | else: 247 | if self.modkey != 0: 248 | self.modkey = 0 249 | self.initial_pos_x = event.mouse_x 250 | self.initial_rebase_axis = self.rebase_axis 251 | self.change_iteration = True 252 | 253 | self.mouse_x = event.mouse_x 254 | self.execute(context) 255 | 256 | elif event.type == 'LEFTMOUSE': # Confirm 257 | if event.value == 'RELEASE': 258 | self.using_settings = True 259 | context.area.header_text_set(text=None) 260 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 261 | 262 | #Switch between modes to force update UI 263 | bpy.ops.object.mode_set(mode='EDIT') 264 | bpy.ops.object.mode_set(mode='OBJECT') 265 | return {'FINISHED'} 266 | 267 | elif event.type == 'M': # Change Merge threshold 268 | if event.value == 'RELEASE': 269 | self.calculate_merge_distance(context, bpy.data.objects[self.selection]) 270 | 271 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Cancel 272 | if not self.first_run: 273 | self.restore_settings(context, bpy.data.objects[self.selection]) 274 | 275 | bpy.types.SpaceView3D.draw_handler_remove(self.draw_handler, 'WINDOW') 276 | context.area.header_text_set(text=None) 277 | 278 | #Switch between modes to force update UI 279 | bpy.ops.object.mode_set(mode='EDIT') 280 | bpy.ops.object.mode_set(mode='OBJECT') 281 | 282 | if self.first_run: 283 | bpy.ops.object.delete(use_global=False, confirm=False) 284 | return {'CANCELLED'} 285 | 286 | #Tooltip 287 | context.area.header_text_set("LMB: confirm, RMB:Cancel, Mouse Left/Right for number of instances, CTRL + Mouse Left/ Right to change Axis, M changes merge threshold precission") 288 | return {'RUNNING_MODAL'} 289 | 290 | def invoke(self, context, event): 291 | self.initial_pos_x = event.mouse_x 292 | self.selection = bpy.context.active_object.name 293 | 294 | if bpy.context.object.modifiers.find("Cylindrical Sides") < 0: 295 | self.setup_rebase(context, bpy.data.objects[self.selection]) 296 | 297 | self.recover_settings(context, bpy.data.objects[self.selection]) 298 | 299 | self.draw_handler = bpy.types.SpaceView3D.draw_handler_add(self.draw_ui, (self, context), 'WINDOW','POST_PIXEL') 300 | 301 | self.execute(context) 302 | context.window_manager.modal_handler_add(self) 303 | return {'RUNNING_MODAL'} 304 | 305 | """ 306 | bpy.context.object.modifiers["Cylindrical Sides"].use_merge_vertices = True 307 | bpy.context.object.modifiers["Cylindrical Sides"].merge_threshold = 0.001 308 | """ -------------------------------------------------------------------------------- /op/selection.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | import itertools 4 | import time 5 | 6 | ITERATION_LIMIT = 400 7 | 8 | 9 | def select_face_loops(ring=False): 10 | if ring: 11 | bpy.ops.mesh.loop_multi_select(ring=True) 12 | 13 | else: 14 | bpy.ops.mesh.loop_multi_select(ring=False) 15 | 16 | itools.set_mode('EDGE') 17 | bpy.ops.mesh.select_more() 18 | itools.set_mode('FACE') 19 | bpy.ops.mesh.select_less() 20 | 21 | 22 | # Review this function later 23 | def select_vert_loops(ring=False): 24 | bm = itools.get_bmesh() 25 | vert = itools.get_selected('VERT', item=False) 26 | edges = [edge.index for edge in bm.verts[vert[0]].link_edges] 27 | itools.select(edges, 'EDGE', item=False) 28 | 29 | if ring: 30 | bpy.ops.mesh.loop_multi_select(ring=True) 31 | 32 | else: 33 | bpy.ops.mesh.loop_multi_select(ring=False) 34 | 35 | 36 | def distance_between_elements(elements, mode, ring=False): 37 | itools.select(elements, mode, item=False, replace=True) 38 | bpy.ops.mesh.shortest_path_select() 39 | selection = itools.get_selected(mode, item=False) 40 | 41 | if ring: 42 | distance = len(selection) - 3 43 | else: 44 | distance = len(selection) - 2 45 | 46 | if distance > 0: 47 | return distance 48 | else: 49 | return 0 50 | 51 | 52 | def organize_elements_by_loop(elements, mode, ring=False): 53 | selected_elements = [] 54 | elements_to_check = elements 55 | 56 | while len(elements_to_check) > 0: 57 | itools.select([elements_to_check[0]], mode, item=False, replace=True) 58 | 59 | if mode == 'VERT': 60 | select_vert_loops(ring=ring) 61 | elif mode == 'EDGE': 62 | bpy.ops.mesh.loop_multi_select(ring=ring) 63 | elif mode == 'FACE': 64 | select_face_loops(ring=ring) 65 | 66 | element_loop = itools.get_selected(mode, item=False) 67 | selected_elements.append(itools.list_intersection(element_loop, elements_to_check)) 68 | elements_to_check = itools.list_difference(elements_to_check, element_loop) 69 | 70 | return selected_elements 71 | 72 | 73 | def is_step_selection(selection, mode, ring=False): 74 | if len(selection) > 2: 75 | selection_results = [] 76 | results = [] 77 | # OPTIMIZATION: ONLY CHECK FIRST 3 78 | if len(selection) > 3: 79 | selection = selection[:3] 80 | 81 | for a in selection: 82 | min = [] 83 | other_elements = itools.list_difference(selection, [a]) 84 | 85 | for b in other_elements: 86 | distance = distance_between_elements([a, b], mode, ring) 87 | 88 | if len(min) == 0 or min[1] > distance: 89 | min = [[a, b], distance] 90 | 91 | results.append(min) 92 | 93 | distances = list(set([x[1] for x in results])) 94 | itools.select(selection, item=False, replace=True, add_to_history=True) 95 | 96 | if len(distances) == 1: 97 | return [True, distances[0]] 98 | 99 | else: 100 | return [False, 0] 101 | 102 | else: 103 | return [False, 0] 104 | 105 | 106 | # Think of new way to complete step selection for future updates to fix bugs with 107 | # step selections in loops and rings that are not cyclical 108 | def complete_step_selection(mode): 109 | iteration = 0 110 | last_selection = [] 111 | selected_elements = itools.get_selected(mode, item=False) 112 | 113 | while not selected_elements == last_selection and iteration < ITERATION_LIMIT: 114 | bpy.ops.mesh.select_next_item() 115 | last_selection = selected_elements 116 | selected_elements = itools.get_selected(mode, item=False) 117 | iteration += 1 118 | 119 | 120 | def smart_loop(ring=False): 121 | final_selection = [] 122 | mode = itools.get_mode() 123 | selection = itools.get_selected(mode, item=False) 124 | organized_loops = organize_elements_by_loop(selection, mode, ring) 125 | 126 | for loop in organized_loops: 127 | step_selection_result = is_step_selection(loop, mode, ring) 128 | 129 | if step_selection_result[0]: 130 | complete_step_selection(mode) 131 | 132 | elif len(loop) == 2: 133 | distance = distance_between_elements([loop[0], loop[1]], mode, ring) 134 | 135 | if distance > 0: 136 | itools.select(loop, mode, item=False, replace=True, add_to_history=True) 137 | 138 | if ring: 139 | bpy.ops.mesh.shortest_path_select(use_face_step=True) 140 | 141 | else: 142 | bpy.ops.mesh.shortest_path_select() 143 | 144 | elif distance == 0: 145 | itools.select(loop, mode, item=False, replace=True, add_to_history=True) 146 | bpy.ops.mesh.loop_multi_select(ring=ring) 147 | 148 | else: 149 | if mode == 'EDGE': 150 | itools.select(loop, mode, item=False, replace=True, add_to_history=True) 151 | bpy.ops.mesh.loop_multi_select(ring=ring) 152 | 153 | final_selection += itools.get_selected(mode, item=False) 154 | 155 | itools.select(final_selection, mode, item=False, replace=True, add_to_history=True) 156 | 157 | 158 | def show_message(message="", title="Message Box", icon='INFO'): 159 | 160 | def draw(self, context): 161 | self.layout.label(text=message) 162 | 163 | bpy.context.window_manager.popup_menu(draw, title=title, icon=icon) 164 | 165 | 166 | class SmartSelectLoop(bpy.types.Operator): 167 | """ 168 | BUGS: 169 | *Step Face Loop only goes in one direction for faces 170 | *Sometimes top and bottom is ignored on loops of spheres, investigate 171 | *Complete step selection only works in one direction 172 | """ 173 | bl_idname = "mesh.smart_select_loop" 174 | bl_label = "Smart Select Loop" 175 | bl_description = "Context sensitive smart loop selection" 176 | bl_options = {'REGISTER', 'UNDO'} 177 | 178 | def execute(self, context): 179 | smart_loop() 180 | return{'FINISHED'} 181 | 182 | 183 | class SmartSelectRing(bpy.types.Operator): 184 | """ 185 | BUGS: 186 | *Step Face Loop only goes in one direction for faces 187 | """ 188 | bl_idname = "mesh.smart_select_ring" 189 | bl_label = "Smart Select Ring" 190 | bl_description = "Context sensitive smart ring selection" 191 | bl_options = {'REGISTER', 'UNDO'} 192 | 193 | def execute(self, context): 194 | smart_loop(ring=True) 195 | return{'FINISHED'} 196 | 197 | 198 | def select_loop_directional(edge, directional=True, direction=0): 199 | """ 200 | Future selection algorithm, not fully implemented, still in developments 201 | *Bugs: Improve selection of edges that dont have two faces 202 | Selects more than intended 203 | FEATURES: 204 | *Select border if the selection is in a border 205 | """ 206 | counter = 0 207 | iterations = 0 208 | selection = [edge] 209 | selected = selection 210 | new_selection = selection 211 | iterate = True 212 | directionality_loop = True 213 | mesh = itools.get_bmesh() 214 | print("SELECT LOOP DIRECTIONAL STARTS") 215 | now = time.time() 216 | # update_indexes(mesh, edges=True) 217 | while directionality_loop and counter < 2: 218 | while iterations < ITERATION_LIMIT and iterate: 219 | print("") 220 | print("----------------------------") 221 | print(iterations) 222 | print("Current Edge") 223 | print(selection) 224 | if direction == 0: 225 | new_selection = [selection[0].link_loops[0].link_loop_next.link_loop_radial_next.link_loop_next.edge] 226 | 227 | else: 228 | new_selection = [selection[0].link_loops[0].link_loop_prev.link_loop_radial_next.link_loop_prev.edge] 229 | 230 | if new_selection[0].select: 231 | # print("CHANGE DIRECTION") 232 | if direction == 0: 233 | new_selection = [selection[0].link_loops[0].link_loop_prev.link_loop_radial_next.link_loop_prev.edge] 234 | else: 235 | new_selection = [selection[0].link_loops[0].link_loop_next.link_loop_radial_next.link_loop_next.edge] 236 | # Check if new selection is still selected after correcting direction 237 | if new_selection[0].select: 238 | print("COMPLETE LAP") 239 | iterate = False 240 | if len(itools.list_intersection(list(new_selection[0].verts), list(selection[0].verts))) < 1: 241 | # Correct selection for cases where theres holes close by 242 | print("HOLES CLOSEBY, CORRECTING") 243 | new_selection = [selection[0].link_loops[0].link_loop_radial_next.link_loop_prev.link_loop_radial_next.link_loop_prev.edge] 244 | if len([face for face in new_selection[0].link_faces if (selection[0] in face.edges)]) > 0: 245 | # Make sure you cant accidentally select a loop on top or below of it 246 | print("LOOP WILL JUMP ROW") 247 | new_selection = selection 248 | iterate = False 249 | if len([face for face in new_selection[0].link_faces if len(list(face.verts)) != 4]) != 0: 250 | # End selection on ngons or triangles 251 | print("END LOOP") 252 | iterate = False 253 | selection = new_selection 254 | new_selection[0].select = True 255 | iterations += 1 256 | # If not directional reset and start the other way 257 | if not directional: 258 | iterate = True 259 | iterations = 0 260 | direction = 1 261 | selection = [edge] 262 | new_selection = selection 263 | else: 264 | directionality_loop = False 265 | counter += 1 266 | end = time.time() 267 | print("SELECT LOOP DIRECTIONAL ENDS TIME: %s", time) 268 | -------------------------------------------------------------------------------- /op/smart_delete.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | from ..utils import mesh as mesh 4 | from ..utils.user_prefs import get_enable_dissolve_faces, get_enable_dissolve_verts 5 | 6 | 7 | class SmartDelete(bpy.types.Operator): 8 | bl_idname = "mesh.smart_delete" 9 | bl_label = "Smart Delete" 10 | bl_description = "Context Sensitive Deletion" 11 | bl_options = {'REGISTER', 'UNDO'} 12 | 13 | def smart_delete(cls, context): 14 | mode = itools.get_mode() 15 | 16 | if mode == 'OBJECT': 17 | bpy.ops.object.delete() 18 | 19 | elif mode in ['VERT', 'EDGE', 'FACE']: 20 | bm = itools.get_bmesh() 21 | 22 | if mode == 'VERT': 23 | selection = itools.get_selected() 24 | verts_connectivity2 = [vert for vert in selection if len([edge for edge in vert.link_edges]) == 2] 25 | 26 | if len(verts_connectivity2) == len(selection): 27 | bpy.ops.mesh.dissolve_verts() 28 | 29 | else: 30 | if get_enable_dissolve_verts(): 31 | bpy.ops.mesh.dissolve_verts() 32 | else: 33 | bpy.ops.mesh.delete(type='VERT') 34 | 35 | elif mode == 'EDGE': 36 | selection = itools.get_selected() 37 | if get_enable_dissolve_faces(): 38 | if mesh.is_border(selection): 39 | for edge in selection: 40 | for face in edge.link_faces: 41 | face.select = 1 42 | bpy.ops.mesh.delete(type='FACE') 43 | 44 | else: 45 | bpy.ops.mesh.dissolve_edges() 46 | 47 | else: 48 | for edge in selection: 49 | for face in edge.link_faces: 50 | face.select = 1 51 | bpy.ops.mesh.delete(type='FACE') 52 | 53 | elif mode == 'FACE': 54 | bpy.ops.mesh.delete(type='FACE') 55 | 56 | elif mode == 'EDIT_CURVE': 57 | bpy.ops.curve.delete(type='VERT') 58 | 59 | return{'FINISHED'} 60 | 61 | def execute(self, context): 62 | self.smart_delete(context) 63 | return {'FINISHED'} 64 | -------------------------------------------------------------------------------- /op/smart_extrude.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.view3d_utils import region_2d_to_location_3d 3 | from ..utils import itools as itools 4 | from ..utils.mesh import is_border_edge 5 | from .smart_transform import mouse_2d_to_3d 6 | from mathutils import Vector 7 | 8 | 9 | class SmartExtrudeModal(bpy.types.Operator): 10 | bl_idname = "mesh.smart_extrude_modal" 11 | bl_label = "Smart Extrude Legacy" 12 | bl_description = "Context Sensitive Extrude operation, Legacy feature use at own risk" 13 | bl_options = {'REGISTER', 'UNDO'} 14 | 15 | initial_mouse_pos = Vector((0, 0, 0)) 16 | translation_accumulator = Vector((0, 0, 0)) 17 | initial_pos = Vector((0, 0, 0)) 18 | sensitivity = 1 19 | 20 | def calculate_translation(self, context, event): 21 | translation = Vector((0, 0, 0)) 22 | for area in context.screen.areas: 23 | if area.type == "VIEW_3D": 24 | new_mouse_pos = mouse_2d_to_3d(context, event) 25 | 26 | else: 27 | new_mouse_pos = self.initial_mouse_pos 28 | 29 | increment = (new_mouse_pos - self.initial_mouse_pos) * self.sensitivity 30 | increment_abs = [abs(value) for value in increment] 31 | axis = list(increment_abs).index(max(increment_abs)) 32 | 33 | if axis == 0: 34 | translation[0] = increment[0] - self.translation_accumulator[0] 35 | translation[1] = -self.translation_accumulator[1] 36 | translation[2] = -self.translation_accumulator[2] 37 | 38 | elif axis == 1: 39 | translation[0] = -self.translation_accumulator[0] 40 | translation[1] = increment[1] - self.translation_accumulator[1] 41 | translation[2] = -self.translation_accumulator[2] 42 | 43 | elif axis == 2: 44 | translation[0] = -self.translation_accumulator[0] 45 | translation[1] = -self.translation_accumulator[1] 46 | translation[2] = increment[2] - self.translation_accumulator[2] 47 | 48 | self.translation_accumulator += translation 49 | bpy.ops.transform.translate(value=translation, orient_type='GLOBAL') 50 | return True 51 | 52 | def context_sensitive_extend(self, context): 53 | if context.mode == 'OBJECT': 54 | if len(context.selected_objects) > 0: 55 | initial_pos = context.selected_objects[0].location 56 | bpy.ops.object.duplicate() 57 | else: 58 | return {'FINISHED'} 59 | 60 | elif context.mode == 'EDIT_MESH': 61 | bm = itools.get_bmesh() 62 | mode = itools.get_mode() 63 | if mode == 'EDGE': 64 | selection = itools.get_selected('EDGE', item=True) 65 | if all(is_border_edge(edge) for edge in selection): 66 | bpy.ops.mesh.extrude_edges_move(MESH_OT_extrude_edges_indiv=None, TRANSFORM_OT_translate=None) 67 | else: 68 | return {'FINISHED'} 69 | else: 70 | bpy.ops.mesh.duplicate(mode=1) 71 | elif context.mode == 'EDIT_CURVE': 72 | bpy.ops.curve.extrude_move(CURVE_OT_extrude={"mode": 'TRANSLATION'}, 73 | TRANSFORM_OT_translate={"value": (0, 0, 0)}) 74 | 75 | def __init__(self): 76 | self.initial_mouse_pos = Vector((0, 0, 0)) 77 | self.translation_accumulator = Vector((0, 0, 0)) 78 | self.initial_pos = Vector((0, 0, 0)) 79 | print("Start") 80 | 81 | def __del__(self): 82 | print("End") 83 | 84 | def execute(self, context): 85 | return {'FINISHED'} 86 | 87 | def modal(self, context, event): 88 | if event.type == 'MOUSEMOVE': # Apply 89 | self.calculate_translation(context, event) 90 | self.execute(context) 91 | 92 | elif event.type == 'LEFTMOUSE': # Confirm 93 | if event.value == 'RELEASE': 94 | return {'FINISHED'} 95 | 96 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Confirm 97 | bpy.ops.transform.translate(value=(Vector((0, 0, 0)) - self.translation_accumulator), 98 | orient_type='GLOBAL') 99 | SmartDelete.smart_delete(context) 100 | return {'CANCELLED'} 101 | 102 | return {'RUNNING_MODAL'} 103 | 104 | def invoke(self, context, event): 105 | self.initial_mouse_pos = mouse_2d_to_3d(context, event) 106 | self.context_sensitive_extend(context) 107 | self.execute(context) 108 | context.window_manager.modal_handler_add(self) 109 | return {'RUNNING_MODAL'} 110 | 111 | 112 | class SmartExtrude(bpy.types.Operator): 113 | bl_idname = "mesh.smart_extrude" 114 | bl_label = "Smart Extrude" 115 | bl_description = "Context Sensitive Extrude operation" 116 | bl_options = {'REGISTER', 'UNDO'} 117 | 118 | def context_sensitive_extrude(self, context): 119 | if context.mode == 'OBJECT': 120 | if len(context.selected_objects) > 0: 121 | initial_pos = context.selected_objects[0].location 122 | bpy.ops.object.duplicate() 123 | 124 | elif context.mode == 'EDIT_MESH': 125 | bm = itools.get_bmesh() 126 | mode = itools.get_mode() 127 | if mode == 'EDGE': 128 | selection = itools.get_selected('EDGE', item=True) 129 | if all(is_border_edge(edge) for edge in selection): 130 | bpy.ops.mesh.extrude_edges_move(MESH_OT_extrude_edges_indiv=None, TRANSFORM_OT_translate=None) 131 | else: 132 | return {'FINISHED'} 133 | else: 134 | bpy.ops.mesh.duplicate(mode=1) 135 | elif context.mode == 'EDIT_CURVE': 136 | bpy.ops.curve.extrude_move(CURVE_OT_extrude={"mode": 'TRANSLATION'}, 137 | TRANSFORM_OT_translate={"value": (0, 0, 0)}) 138 | 139 | 140 | active_tool = bpy.context.workspace.tools.from_space_view3d_mode(bpy.context.mode, create=False).idname 141 | areas = bpy.context.workspace.screens[0].areas 142 | 143 | for area in areas: 144 | for space in area.spaces: 145 | if space.type == 'VIEW_3D': 146 | if space.show_gizmo_object_translate or active_tool == 'builtin.move': 147 | bpy.ops.transform.translate('INVOKE_DEFAULT') 148 | 149 | elif space.show_gizmo_object_rotate or active_tool == 'builtin.rotate': 150 | bpy.ops.transform.rotate('INVOKE_DEFAULT') 151 | 152 | elif space.show_gizmo_object_scale or active_tool == 'builtin.scale': 153 | bpy.ops.transform.resize('INVOKE_DEFAULT') 154 | 155 | def execute(self, context): 156 | self.context_sensitive_extrude(context) 157 | return {'FINISHED'} 158 | -------------------------------------------------------------------------------- /op/smart_modify.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | from ..utils import mesh as mesh 4 | 5 | 6 | class SmartModify(bpy.types.Operator): 7 | bl_idname = "mesh.smart_modify" 8 | bl_label = "Smart Modify Pie" 9 | bl_description = "Context sensitive modification pie menu" 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def smart_modify(self): 13 | context = bpy.context.area.ui_type 14 | 15 | if context == 'UV': 16 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SM_uv") 17 | else: 18 | mode = itools.get_mode() 19 | if mode == 'OBJECT': 20 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SM_object") 21 | elif mode in ['VERT', 'EDGE', 'FACE']: 22 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SM_mesh") 23 | elif mode == 'EDIT_CURVE': 24 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SM_curve") 25 | elif mode == 'EDIT_LATTICE': 26 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SM_lattice") 27 | 28 | def execute(self, context): 29 | self.smart_modify() 30 | return{'FINISHED'} 31 | -------------------------------------------------------------------------------- /op/smart_transform.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy_extras.view3d_utils import region_2d_to_location_3d 3 | from mathutils import Vector 4 | 5 | 6 | def mouse_2d_to_3d(context, event): 7 | x, y = event.mouse_region_x, event.mouse_region_y 8 | location = region_2d_to_location_3d(context.region, context.space_data.region_3d, (x, y), (0, 0, 0)) 9 | return Vector(location) 10 | 11 | class CSMove(bpy.types.Operator): 12 | bl_idname = "mesh.cs_move" 13 | bl_label = "CS Move" 14 | bl_description = "Context Sensitive Move Tool" 15 | bl_options = {'REGISTER', 'UNDO'} 16 | 17 | def smart_move(self,context): 18 | area = bpy.context.area 19 | for space in area.spaces: 20 | if space.type != 'VIEW_3D': 21 | continue 22 | # Make sure active tool is set to select 23 | context_override = bpy.context.copy() 24 | context_override["space_data"] = area.spaces[0] 25 | context_override["area"] = area 26 | 27 | with context.temp_override(**context_override): 28 | bpy.ops.wm.tool_set_by_id(name="builtin.select_box") 29 | 30 | if space.show_gizmo_object_translate: 31 | bpy.ops.transform.translate('INVOKE_DEFAULT') 32 | else: 33 | space.show_gizmo_object_translate = True 34 | space.show_gizmo_object_rotate = False 35 | space.show_gizmo_object_scale = False 36 | 37 | def execute(self, context): 38 | self.smart_move(context) 39 | return{'FINISHED'} 40 | 41 | 42 | class CSRotate(bpy.types.Operator): 43 | bl_idname = "mesh.cs_rotate" 44 | bl_label = "CS Rotate" 45 | bl_description = "Context Sensitive Rotate Tool" 46 | bl_options = {'REGISTER', 'UNDO'} 47 | 48 | def smart_rotate(self, context): 49 | 50 | area = bpy.context.area 51 | for space in area.spaces: 52 | if space.type != 'VIEW_3D': 53 | continue 54 | # Make sure active tool is set to select 55 | context_override = bpy.context.copy() 56 | context_override["space_data"] = area.spaces[0] 57 | context_override["area"] = area 58 | 59 | with context.temp_override(**context_override): 60 | bpy.ops.wm.tool_set_by_id(name="builtin.select_box") 61 | 62 | if space.show_gizmo_object_rotate: 63 | bpy.ops.transform.rotate('INVOKE_DEFAULT') 64 | else: 65 | space.show_gizmo_object_translate = False 66 | space.show_gizmo_object_rotate = True 67 | space.show_gizmo_object_scale = False 68 | 69 | def execute(self, context): 70 | self.smart_rotate(context) 71 | return{'FINISHED'} 72 | 73 | #TODO: Optimize CSMove, Rotate and Scale into a single class with inheritance 74 | class CSScale(bpy.types.Operator): 75 | bl_idname = "mesh.cs_scale" 76 | bl_label = "CS Scale" 77 | bl_description = "Context Sensitive Scale Tool" 78 | bl_options = {'REGISTER', 'UNDO'} 79 | 80 | def smart_scale(self, context): 81 | areas = bpy.context.workspace.screens[0].areas 82 | 83 | area = bpy.context.area 84 | for space in area.spaces: 85 | if space.type != 'VIEW_3D': 86 | continue 87 | # Make sure active tool is set to select 88 | context_override = bpy.context.copy() 89 | context_override["space_data"] = area.spaces[0] 90 | context_override["area"] = area 91 | 92 | with context.temp_override(**context_override): 93 | bpy.ops.wm.tool_set_by_id(name="builtin.select_box") 94 | 95 | if space.show_gizmo_object_scale: 96 | bpy.ops.transform.resize('INVOKE_DEFAULT') 97 | else: 98 | space.show_gizmo_object_translate = False 99 | space.show_gizmo_object_rotate = False 100 | space.show_gizmo_object_scale = True 101 | 102 | def execute(self, context): 103 | self.smart_scale(context) 104 | return{'FINISHED'} 105 | 106 | 107 | class SmartTranslate(bpy.types.Operator): 108 | bl_idname = "mesh.smart_translate_modal" 109 | bl_label = "Smart Translate Legacy" 110 | bl_description = "Smart Translate Tool, Legacy feature use at own risk" 111 | bl_options = {'REGISTER', 'UNDO'} 112 | 113 | initial_mouse_pos = Vector((0, 0, 0)) 114 | translation_accumulator = Vector((0, 0, 0)) 115 | initial_pos = Vector((0, 0, 0)) 116 | sensitivity = 1 117 | 118 | def calculate_translation(self, context, event): 119 | translation = Vector((0, 0, 0)) 120 | for area in context.screen.areas: 121 | if area.type == "VIEW_3D": 122 | new_mouse_pos = mouse_2d_to_3d(context, event) 123 | 124 | else: 125 | new_mouse_pos = self.initial_mouse_pos 126 | 127 | increment = (new_mouse_pos - self.initial_mouse_pos) * self.sensitivity 128 | increment_abs = [abs(value) for value in increment] 129 | axis = list(increment_abs).index(max(increment_abs)) 130 | 131 | if axis == 0: 132 | translation[0] = increment[0] - self.translation_accumulator[0] 133 | translation[1] = -self.translation_accumulator[1] 134 | translation[2] = -self.translation_accumulator[2] 135 | 136 | elif axis == 1: 137 | translation[0] = -self.translation_accumulator[0] 138 | translation[1] = increment[1] - self.translation_accumulator[1] 139 | translation[2] = -self.translation_accumulator[2] 140 | 141 | elif axis == 2: 142 | translation[0] = -self.translation_accumulator[0] 143 | translation[1] = -self.translation_accumulator[1] 144 | translation[2] = increment[2] - self.translation_accumulator[2] 145 | 146 | self.translation_accumulator += translation 147 | bpy.ops.transform.translate(value=translation, orient_type='GLOBAL') 148 | return True 149 | 150 | def __init__(self): 151 | self.initial_mouse_pos = Vector((0, 0, 0)) 152 | self.translation_accumulator = Vector((0, 0, 0)) 153 | self.initial_pos = Vector((0, 0, 0)) 154 | print("Start") 155 | 156 | def __del__(self): 157 | print("End") 158 | 159 | def execute(self, context): 160 | return {'FINISHED'} 161 | 162 | def modal(self, context, event): 163 | if event.type == 'MOUSEMOVE': # Apply 164 | self.calculate_translation(context, event) 165 | self.execute(context) 166 | 167 | elif event.type == 'MIDDLEMOUSE': # Confirm 168 | if event.value == 'RELEASE': 169 | return {'FINISHED'} 170 | 171 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Confirm 172 | bpy.ops.transform.translate(value=-self.translation_accumulator, orient_type='GLOBAL') 173 | return {'CANCELLED'} 174 | 175 | return {'RUNNING_MODAL'} 176 | 177 | def invoke(self, context, event): 178 | self.initial_mouse_pos = mouse_2d_to_3d(context, event) 179 | self.execute(context) 180 | context.window_manager.modal_handler_add(self) 181 | return {'RUNNING_MODAL'} 182 | -------------------------------------------------------------------------------- /op/super_smart_create.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from ..utils import itools as itools 3 | from ..utils import mesh as mesh 4 | from ..utils.user_prefs import get_f2_active, get_ssc_switch_modes 5 | 6 | class SuperSmartCreate(bpy.types.Operator): 7 | bl_idname = "mesh.super_smart_create" 8 | bl_label = "Super Smart Create" 9 | bl_description = "Context sensitive creation" 10 | bl_options = {'REGISTER', 'UNDO'} 11 | 12 | def split_edge_select_vert(self): 13 | bpy.ops.mesh.subdivide() 14 | itools.update_indexes('ALL') 15 | 16 | if get_ssc_switch_modes(): 17 | selection = itools.get_selected() 18 | new_selection = [vert for edge in selection for vert in edge.verts 19 | if all(edge in selection for edge in vert.link_edges)] 20 | new_selection = itools.remove_duplicates(new_selection) 21 | 22 | itools.set_mode('VERT') 23 | itools.select(new_selection, replace=True) 24 | 25 | def split_edges_make_loop(self, selection): 26 | new_verts = [] 27 | 28 | for edge in selection: 29 | itools.set_mode('EDGE') 30 | itools.select(edge, replace=True) 31 | self.split_edge_select_vert() 32 | new_verts.append(itools.get_selected('VERT', item=False)[0]) 33 | 34 | itools.set_mode('VERT') 35 | itools.select(new_verts, replace=True, item=False) 36 | bpy.ops.mesh.vert_connect() 37 | itools.set_mode('EDGE') 38 | 39 | def connect_verts_to_last(self, selection): 40 | bm = itools.get_bmesh() 41 | ordered_selection = [] 42 | 43 | for vert in selection: 44 | if vert not in bm.select_history: 45 | ordered_selection.append(vert) 46 | 47 | for item in bm.select_history: 48 | ordered_selection.append(item) 49 | 50 | for vert in ordered_selection: 51 | itools.select([vert, ordered_selection[-1]], replace=True) 52 | bpy.ops.mesh.vert_connect() 53 | 54 | itools.select(selection, replace=True) 55 | 56 | def quad_fill(self): 57 | selection = itools.get_selected('EDGE') 58 | bpy.ops.mesh.delete(type='FACE') 59 | itools.select(selection, 'EDGE', replace=True) 60 | bpy.ops.mesh.fill_grid() 61 | 62 | def super_smart_create(self): 63 | 64 | mode = itools.get_mode() 65 | 66 | if mode == 'OBJECT': 67 | if len(itools.get_selected()) > 0: 68 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SSC_Duplicate") 69 | 70 | else: 71 | bpy.ops.wm.call_menu_pie(name="VIEW3D_MT_PIE_SSC_New_Obj") 72 | 73 | # if Vertex is selected 74 | elif mode == 'VERT': 75 | bm = itools.get_bmesh() 76 | selection = itools.get_selected() 77 | 78 | if len(selection) == 0: 79 | bpy.ops.mesh.knife_tool('INVOKE_DEFAULT') 80 | 81 | elif len(selection) == 1 or (mesh.verts_share_edge(selection) and mesh.are_border_verts(selection)): 82 | if get_f2_active(): 83 | bpy.ops.mesh.f2('INVOKE_DEFAULT') 84 | 85 | elif mesh.verts_share_face(selection): 86 | self.connect_verts_to_last(selection) 87 | 88 | else: 89 | bpy.ops.mesh.vert_connect() 90 | 91 | # if Edge is selected 92 | elif mode == 'EDGE': 93 | bm = itools.get_bmesh() 94 | selection = itools.get_selected() 95 | 96 | if len(selection) == 0: 97 | bpy.ops.mesh.loopcut_slide('INVOKE_DEFAULT') 98 | 99 | elif len(selection) == 1: 100 | self.split_edge_select_vert() 101 | 102 | elif mesh.is_border(selection): 103 | bpy.ops.mesh.edge_face_add() 104 | if get_ssc_switch_modes(): 105 | itools.set_mode('FACE') 106 | 107 | elif mesh.is_ring(selection): 108 | self.split_edges_make_loop(selection) 109 | 110 | elif mesh.is_adjacent(selection, mode) and mesh.is_partial_border(selection): 111 | bpy.ops.mesh.edge_face_add() 112 | itools.set_mode('EDGE') 113 | 114 | elif mesh.is_partial_border(selection): 115 | bpy.ops.mesh.bridge_edge_loops() 116 | itools.set_mode('EDGE') 117 | 118 | # if Face is selected 119 | elif mode == 'FACE': 120 | bm = itools.get_bmesh() 121 | selection = itools.get_selected() 122 | 123 | if len(selection) == 1: 124 | self.quad_fill() 125 | 126 | elif len(selection) > 1 and len(mesh.organize_faces_by_continuity(selection)) == 2: 127 | try: 128 | bpy.ops.mesh.bridge_edge_loops() 129 | 130 | except: 131 | print("Cant bridge selection") 132 | 133 | else: 134 | bpy.ops.mesh.subdivide() 135 | 136 | # if curve selected 137 | elif mode == 'EDIT_CURVE': 138 | selection = itools.get_selected() 139 | 140 | if len(selection) == 1: 141 | bpy.ops.curve.extrude_move(TRANSFORM_OT_translate={"value": (0, 0, 0)}) 142 | 143 | elif len(selection) == 2: 144 | try: 145 | bpy.ops.curve.make_segment() 146 | 147 | except: 148 | bpy.ops.curve.subdivide() 149 | bpy.ops.curve.select_less() 150 | 151 | def execute(self, context): 152 | self.super_smart_create() 153 | return{'FINISHED'} 154 | -------------------------------------------------------------------------------- /op/uv_functions.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | import math 4 | from ..utils import itools as itools 5 | 6 | 7 | def selected_uv_verts_pos(): 8 | bm = itools.get_bmesh() 9 | uv_layer = bm.loops.layers.uv.verify() 10 | verts_loc = [loop[uv_layer].uv for face in bm.faces for loop in face.loops if loop[uv_layer].select] 11 | return verts_loc 12 | 13 | 14 | def sharp_to_seams(context, selection=[]): 15 | mode = itools.get_mode() 16 | me = context.object.data 17 | 18 | itools.set_mode('EDGE') 19 | 20 | if len(selection) < 1: 21 | bm = itools.get_bmesh() 22 | selection = [edge for edge in bm.edges] 23 | 24 | for edge in selection: 25 | edge.seam = False 26 | 27 | for edge in selection: 28 | if not edge.smooth: 29 | edge.seam = True 30 | 31 | itools.set_mode('OBJECT') 32 | itools.set_mode(mode) 33 | 34 | 35 | class QuickRotateUv90Pos(bpy.types.Operator): 36 | bl_idname = "uv.rotate_90_pos" 37 | bl_label = "Rotate UV 90 +" 38 | bl_description = "Rotate Uvs +90 degrees" 39 | bl_options = {'REGISTER', 'UNDO'} 40 | 41 | def execute(self, context): 42 | original_pos = selected_uv_verts_pos() 43 | print(original_pos) 44 | bpy.ops.transform.rotate(value=math.radians(90), orient_axis='Z') 45 | new_pos = selected_uv_verts_pos() 46 | return{'FINISHED'} 47 | 48 | 49 | class QuickRotateUv90Neg(bpy.types.Operator): 50 | bl_idname = "uv.rotate_90_neg" 51 | bl_label = "Rotate UV 90 -" 52 | bl_description = "Rotate Uvs -90 degrees" 53 | bl_options = {'REGISTER', 'UNDO'} 54 | 55 | def execute(self, context): 56 | bpy.ops.transform.rotate(value=math.radians(-90), orient_axis='Z') 57 | return{'FINISHED'} 58 | 59 | 60 | class SeamsFromSharps(bpy.types.Operator): 61 | bl_idname = "uv.seams_from_sharps" 62 | bl_label = "Seams From Sharps" 63 | bl_description = "Convets hard edges to seams" 64 | bl_options = {'REGISTER', 'UNDO'} 65 | 66 | def execute(self, context): 67 | mode = itools.get_mode() 68 | selection = [] 69 | 70 | if mode in ['VERT', 'EDGE', 'FACE']: 71 | bm = itools.get_bmesh() 72 | itools.set_mode('EDGE') 73 | selection = itools.get_selected() 74 | 75 | sharp_to_seams(context, selection) 76 | itools.set_mode(mode) 77 | return{'FINISHED'} 78 | 79 | 80 | class UvsFromSharps(bpy.types.Operator): 81 | bl_idname = "uv.uvs_from_sharps" 82 | bl_label = "UVs From Sharps" 83 | bl_description = "Convets hard edges to seams and unwraps the model" 84 | bl_options = {'REGISTER', 'UNDO'} 85 | 86 | def execute(self, context): 87 | mode = itools.get_mode() 88 | selection = [] 89 | 90 | itools.set_mode('EDGE') 91 | bm = itools.get_bmesh() 92 | selection = itools.get_selected() 93 | sharp_to_seams(context, selection) 94 | 95 | if len(selection) < 1: 96 | bpy.ops.mesh.select_all(action='SELECT') 97 | 98 | bpy.ops.uv.unwrap(method='ANGLE_BASED', margin=0.02) 99 | bpy.ops.mesh.select_all(action='SELECT') 100 | return{'FINISHED'} 101 | -------------------------------------------------------------------------------- /ui/menus.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class VIEW3D_MT_object_mode_itools(bpy.types.Menu): 5 | bl_label = "Interactive Tools" 6 | 7 | def draw(self, context): 8 | layout = self.layout 9 | 10 | layout.operator("mesh.super_smart_create", text="Super Smart Create") 11 | layout.operator("mesh.smart_delete", text="Smart Delete") 12 | layout.operator("mesh.smart_modify", text="Smart Modify") 13 | layout.operator("mesh.smart_extrude", text="Smart Extrude") 14 | 15 | layout.separator() 16 | layout.operator("mesh.quick_pivot", text="Quick Origin") 17 | layout.operator("mesh.simple_edit_pivot", text="Edit Origin") 18 | layout.operator("mesh.transform_orientation_pie_pie", text="Quick Transform Orientation") 19 | layout.operator('mesh.quick_align', text="Quick Align") 20 | layout.operator('mesh.quick_lattice', text="Quick Lattice") 21 | layout.operator('mesh.rebase_cylinder', text="Edit Rebased Cylinder") 22 | layout.operator('mesh.radial_symmetry', text="Radial Symmetry") 23 | 24 | layout.separator() 25 | layout.operator('uv.seams_from_sharps', text="Seams From Sharps") 26 | layout.operator('uv.uvs_from_sharps', text="Uvs From Sharps") 27 | 28 | layout.separator() 29 | layout.operator("object.transform_apply", text="Apply Transforms") 30 | layout.operator("object.convert", text="Convert To...") 31 | layout.operator("object.duplicates_make_real", text="Make Instances Real") 32 | 33 | 34 | class VIEW3D_MT_edit_mesh_itools(bpy.types.Menu): 35 | bl_label = "Interactive Tools" 36 | 37 | def draw(self, context): 38 | layout = self.layout 39 | 40 | layout.operator("mesh.super_smart_create", text="Super Smart Create") 41 | layout.operator("mesh.smart_delete", text="Smart Delete") 42 | layout.operator("mesh.smart_modify", text="Smart Modify") 43 | layout.operator("mesh.smart_extrude", text="Smart Extrude") 44 | 45 | layout.separator() 46 | layout.operator("mesh.quick_pivot", text="Quick Origin") 47 | layout.operator("mesh.transform_orientation_pie_pie", text="Quick Transform Orientation") 48 | layout.operator('mesh.quick_pipe', text="Quick Pipe") 49 | layout.operator('mesh.quick_lattice', text="Quick Lattice") 50 | layout.operator('mesh.rebase_cylinder', text="Rebase Cylinder") 51 | 52 | layout.separator() 53 | layout.operator('uv.seams_from_islands', text="Seams From Islands") 54 | layout.operator('uv.seams_from_sharps', text="Seams From Sharps") 55 | layout.operator('uv.uvs_from_sharps', text="Uvs From Sharps") 56 | 57 | 58 | class VIEW3D_MT_edit_lattice_itools(bpy.types.Menu): 59 | bl_label = "Interactive Tools" 60 | 61 | def draw(self, context): 62 | layout = self.layout 63 | layout.operator("mesh.quick_lattice", text="Apply Lattice") 64 | layout.operator("mesh.lattice_resolution_2x2x2", text="Preset 2X2X2") 65 | layout.operator("mesh.lattice_resolution_3x3x3", text="Preset 3X3X3") 66 | layout.operator("mesh.lattice_resolution_4x4x4", text="Preset 4X4X4") 67 | 68 | 69 | class VIEW3D_MT_edit_uvs_itools(bpy.types.Menu): 70 | bl_label = "Interactive Tools" 71 | 72 | def draw(self, context): 73 | layout = self.layout 74 | layout.operator('uv.seams_from_islands', text="Seams From Islands") 75 | layout.operator('uv.seams_from_sharps', text="Seams From Sharps") 76 | layout.operator('uv.uvs_from_sharps', text="UVs From Sharps") 77 | layout.operator("mesh.smart_modify", text="Smart Modify") 78 | 79 | 80 | def menu_object_mode_itools(self, context): 81 | self.layout.menu("VIEW3D_MT_object_mode_itools") 82 | self.layout.separator() 83 | 84 | 85 | def menu_edit_mesh_itools(self, context): 86 | self.layout.menu("VIEW3D_MT_edit_mesh_itools") 87 | self.layout.separator() 88 | 89 | 90 | def menu_edit_lattice_itools(self, context): 91 | self.layout.menu("VIEW3D_MT_edit_lattice_itools") 92 | self.layout.separator() 93 | 94 | 95 | def menu_edit_uvs_itools(self, context): 96 | self.layout.menu("VIEW3D_MT_edit_uvs_itools") 97 | self.layout.separator() 98 | 99 | 100 | def load_menus_itools(): 101 | bpy.types.VIEW3D_MT_object_context_menu.prepend(menu_object_mode_itools) 102 | bpy.types.VIEW3D_MT_edit_mesh_context_menu.prepend(menu_edit_mesh_itools) 103 | bpy.types.VIEW3D_MT_edit_lattice_context_menu.prepend(menu_edit_lattice_itools) 104 | bpy.types.IMAGE_MT_uvs_context_menu.prepend(menu_edit_uvs_itools) 105 | 106 | 107 | def unload_menus_itools(): 108 | bpy.types.VIEW3D_MT_object_context_menu.remove(menu_object_mode_itools) 109 | bpy.types.VIEW3D_MT_edit_mesh_context_menu.remove(menu_edit_mesh_itools) 110 | bpy.types.VIEW3D_MT_edit_lattice_context_menu.remove(menu_edit_lattice_itools) 111 | bpy.types.IMAGE_MT_uvs_context_menu.remove(menu_edit_uvs_itools) 112 | -------------------------------------------------------------------------------- /ui/pannels.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.utils import register_class, unregister_class 3 | from ..utils.user_prefs import get_enable_legacy_tools 4 | 5 | 6 | class VIEW3D_PT_Itools(bpy.types.Panel): 7 | bl_idname = "VIEW3D_PT_Itools" 8 | bl_label = "Interactive Tools" 9 | bl_category = "Interactive Tools" 10 | bl_space_type = "VIEW_3D" 11 | bl_region_type = "UI" 12 | 13 | def draw(self, context): 14 | layout = self.layout 15 | 16 | layout.label(text="Modes Cycling") 17 | row = layout.row() 18 | row.operator('mesh.selection_mode_cycle', text="Selection Mode Cycle", icon="RESTRICT_SELECT_OFF") 19 | row = layout.row() 20 | row.operator('mesh.transform_mode_cycle', text="Transform Mode Cycle", icon="OUTLINER_OB_EMPTY") 21 | row = layout.row() 22 | row.operator('mesh.transform_orientation_cycle', text="Transform Orientation Cycle", icon="OBJECT_ORIGIN") 23 | 24 | layout.label(text="Selection") 25 | row = layout.row(align=True) 26 | row.operator('mesh.quick_selection_vert', text="QS Vert", icon="VERTEXSEL") 27 | row.operator('mesh.quick_selection_edge', text="QS Edge", icon="EDGESEL") 28 | row.operator('mesh.quick_selection_face', text="QS Face", icon="FACESEL") 29 | 30 | row = layout.row(align=True) 31 | row.operator('mesh.smart_select_loop', text="Smart Loop") 32 | row.operator('mesh.smart_select_ring', text="Smart Ring") 33 | 34 | layout.label(text="Transform") 35 | row = layout.row(align=True) 36 | row.operator('mesh.cs_move', text="CS Move", icon="ORIENTATION_VIEW") 37 | row.operator('mesh.cs_rotate', text="CS Rotate", icon="ORIENTATION_GIMBAL") 38 | row.operator('mesh.cs_scale', text="CS Scale", icon="OBJECT_DATAMODE") 39 | 40 | layout.label(text="Tools") 41 | row = layout.row() 42 | row.operator('mesh.super_smart_create', text="Super Smart Create", icon="PLUS") 43 | row = layout.row() 44 | row.operator('mesh.smart_delete', text="Smart Delete", icon="TRASH") 45 | row = layout.row() 46 | row.operator('mesh.smart_extrude', text="Smart Extrude", icon="EMPTY_SINGLE_ARROW") 47 | row = layout.row(align=True) 48 | row.operator('mesh.quick_pivot', text="Quick Origin") 49 | row.operator('mesh.simple_edit_pivot', text="Edit Origin") 50 | row = layout.row() 51 | row.operator('mesh.quick_align', text="Quick Align") 52 | row = layout.row(align=True) 53 | row.operator('mesh.quick_pipe', text="Quick Pipe") 54 | row.operator('mesh.quick_lattice', text="Quick Lattice") 55 | row = layout.row(align=True) 56 | row.operator('mesh.rebase_cylinder', text="Rebase Cylinder") 57 | row.operator('mesh.radial_symmetry', text="Radial Symmetry") 58 | row = layout.row(align=True) 59 | row.operator('mesh.context_sensitive_slide', text="CS Slide") 60 | row.operator('mesh.context_sensitive_bevel', text="CS Bevel") 61 | row = layout.row(align=True) 62 | row.operator('mesh.quick_hplp_namer', text="Quick Hp Lp Namer") 63 | 64 | layout.label(text="Pie Menus") 65 | row = layout.row() 66 | row.operator('mesh.smart_modify', text="Smart Modify Pie") 67 | row = layout.row() 68 | row.operator('mesh.transform_options_pie', text="Transform Options Pie") 69 | 70 | layout.label(text="Toggles") 71 | row = layout.row() 72 | row.operator('mesh.modifier_toggle', text="Modifiers On / Off", icon="MODIFIER_ON") 73 | row = layout.row() 74 | row.operator('mesh.target_weld_toggle', text="Target Weld On / Off") 75 | row = layout.row(align=True) 76 | row.operator('mesh.wire_toggle', text="Wireframe On/Off") 77 | row.operator('mesh.wire_shaded_toggle', text="Wire / Shaded") 78 | 79 | layout.label(text="UV Utilities") 80 | row = layout.row() 81 | row.operator('uv.rotate_90_pos', text="Rotate 90 +", icon="LOOP_FORWARDS") 82 | row.operator('uv.rotate_90_neg', text="Rotate 90 -", icon="LOOP_BACK") 83 | row = layout.row() 84 | row.operator('uv.seams_from_islands', text="Seams From Islands") 85 | row = layout.row() 86 | row.operator('uv.seams_from_sharps', text="Seams From Sharps") 87 | row = layout.row() 88 | row.operator('uv.uvs_from_sharps', text="Uvs From Sharps") 89 | 90 | if get_enable_legacy_tools(): 91 | layout.label(text="Legacy Tools") 92 | row = layout.row() 93 | row.operator('mesh.smart_extrude_modal', text="Smart Extrude Legacy") 94 | row = layout.row() 95 | row.operator('mesh.smart_translate_modal', text="Smart Translate Legacy") 96 | -------------------------------------------------------------------------------- /ui/pies.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from bpy.types import Menu 3 | from .. utils.user_prefs import get_qblocker_active, get_ssc_qblocker_integration, get_ssc_bezierutilities_integration, get_set_flow_active, get_bezierutilities_active, get_loop_tools_active, get_textools_active 4 | 5 | 6 | class VIEW3D_MT_PIE_SSC_Duplicate(Menu): 7 | # bl_idname = "mesh.ssc_duplicate_menu" 8 | bl_label = "Object Duplication" 9 | 10 | def draw(self, context): 11 | layout = self.layout 12 | pie = layout.menu_pie() 13 | 14 | # 4 - LEFT 15 | pie.operator("object.duplicate", text="Duplicate") 16 | # 6 - RIGHT 17 | pie.operator("object.duplicate", text="Duplicate Linked").linked = True 18 | # 2 - BOTTOM 19 | 20 | # 8 - TOP 21 | 22 | # 7 - TOP - LEFT 23 | 24 | # 9 - TOP - RIGHT 25 | 26 | # 1 - BOTTOM - LEFT 27 | 28 | # 3 - BOTTOM - RIGHT 29 | 30 | 31 | class VIEW3D_MT_PIE_SSC_New_Obj(Menu): 32 | # bl_idname = "mesh.ssc_new_obj_menu" 33 | bl_label = "Object Creation" 34 | 35 | def draw(self, context): 36 | layout = self.layout 37 | pie = layout.menu_pie() 38 | 39 | qblocker = get_qblocker_active() and get_ssc_qblocker_integration() 40 | bezierutils = get_bezierutilities_active() and get_ssc_bezierutilities_integration() 41 | 42 | # 4 - LEFT 43 | if bezierutils: 44 | submenu = pie.column() 45 | container = submenu.box() 46 | column = container.column() 47 | row = column.row(align = True) 48 | row.operator("curve.primitive_bezier_curve_add", text="Curve", icon="IPO_EASE_IN") 49 | row.operator("curve.flexitool_create", text="Curve Flexitools", icon="IPO_EASE_IN") 50 | 51 | else: 52 | pie.operator("curve.primitive_bezier_curve_add", text="Add Curve", icon="IPO_EASE_IN") 53 | # 6 - RIGHT 54 | 55 | if qblocker: 56 | submenu = pie.column() 57 | container = submenu.box() 58 | column = container.column() 59 | row = column.row(align=True) 60 | row.operator("mesh.primitive_cube_add", text="Cube", icon="MESH_CUBE") 61 | row.operator("object.box_create", text="Cube QBlocker", icon="MESH_CUBE") 62 | 63 | else: 64 | pie.operator("mesh.primitive_cube_add", text="Cube", icon="MESH_CUBE") 65 | 66 | # 2 - BOTTOM 67 | if qblocker: 68 | submenu = pie.column() 69 | container = submenu.box() 70 | column = container.column() 71 | row = column.row(align=True) 72 | row.operator("mesh.primitive_cylinder_add", text="Cylinder", icon="MESH_CYLINDER") 73 | row.operator("object.cylinder_create", text="Cylinder QBlocker", icon="MESH_CYLINDER") 74 | 75 | else: 76 | pie.operator("mesh.primitive_cylinder_add", text="Cylinder", icon="MESH_CYLINDER") 77 | 78 | # 8 - TOP 79 | if qblocker: 80 | submenu = pie.column() 81 | container = submenu.box() 82 | column = container.column() 83 | row = column.row(align=True) 84 | row.operator("mesh.primitive_uv_sphere_add", text="Sphere", icon="MESH_UVSPHERE") 85 | row.operator("object.sphere_create", text="Sphere QBlocker", icon="MESH_UVSPHERE") 86 | 87 | else: 88 | pie.operator("mesh.primitive_uv_sphere_add", text="Sphere", icon="MESH_UVSPHERE") 89 | 90 | # 7 - TOP - LEFT 91 | pie.operator("object.light_add", text="Light", icon="OUTLINER_OB_LIGHT").type = 'POINT' 92 | 93 | # 9 - TOP - RIGHT 94 | pie.operator("object.camera_add", text="Camera", icon="OUTLINER_OB_CAMERA") 95 | # 1 - BOTTOM - LEFT 96 | pie.operator("object.gpencil_add", text="Gpencil", icon="OUTLINER_OB_GREASEPENCIL").type = 'EMPTY' 97 | 98 | # 3 - BOTTOM - RIGHT 99 | pie.operator("object.empty_add", text="Empty", icon="OUTLINER_OB_EMPTY").type = 'PLAIN_AXES' 100 | 101 | 102 | class VIEW3D_MT_PIE_SM_object(Menu): 103 | # bl_idname = "mesh.ssc_new_obj_menu" 104 | bl_label = "Smart Modify" 105 | 106 | def draw(self, context): 107 | layout = self.layout 108 | pie = layout.menu_pie() 109 | 110 | # 4 - LEFT 111 | #Visibility 112 | submenu = pie.column() 113 | container = submenu.box() 114 | column = container.column() 115 | row = column.row(align=False) 116 | row.label(text="Visibility") 117 | row = column.row(align=True) 118 | row.operator("object.children_visibility", text="Show ", icon = "HIDE_OFF").hide = False 119 | row.operator("object.children_visibility", text="Hide Children", icon = "HIDE_ON").hide = True 120 | row = column.row(align=True) 121 | row.operator("object.hide_view_clear", text="Show All", icon = "HIDE_OFF") 122 | 123 | #Parenting 124 | row = column.row(align=False) 125 | row.label(text="Parenting") 126 | row = column.row(align=True) 127 | row.operator("object.parent_set", text="Set Parent") 128 | row = column.row(align=True) 129 | row.operator("object.parent_clear", text="Clear Parent") 130 | row = column.row(align=False) 131 | 132 | #Collections 133 | row.label(text="Collections") 134 | row = column.row(align=False) 135 | row.operator("object.move_to_collection", text="Move To Collection", icon = "DECORATE_DRIVER") 136 | row = column.row(align=False) 137 | row.operator("object.link_to_collection", text="Link To Collection", icon = "DECORATE_LINKED") 138 | 139 | # 6 - RIGHT 140 | submenu = pie.column() 141 | container = submenu.box() 142 | column = container.column() 143 | row = column.row(align=True) 144 | row.operator("mesh.quick_lattice", text="Quick Lattice") 145 | row = column.row(align=True) 146 | row.operator("mesh.radial_symmetry", text="Radial Symmetry") 147 | row = column.row(align=True) 148 | row.operator("mesh.rebase_cylinder", text="Rebase Cylinder") 149 | # 2 - BOTTOM 150 | pie.operator("object.convert", text="Visual Geo To Mesh").target='MESH' 151 | 152 | # 8 - TOP 153 | 154 | # 7 - TOP - LEFT 155 | # Align World Submenu 156 | submenu = pie.column() 157 | container = submenu.box() 158 | column = container.column() 159 | row = column.row(align=True) 160 | op = row.operator("mesh.quick_align", text="Align World") 161 | op.relative_to = 'OPT_1' 162 | op.align_axis = {'X', 'Y', 'Z'} 163 | 164 | row = column.row(align=True) 165 | op = row.operator("mesh.quick_align", text="X") 166 | op.relative_to = 'OPT_1' 167 | op.align_axis = {'X'} 168 | 169 | op = row.operator("mesh.quick_align", text="Y") 170 | op.relative_to = 'OPT_1' 171 | op.align_axis = {'Y'} 172 | 173 | op = row.operator("mesh.quick_align", text="Z") 174 | op.relative_to = 'OPT_1' 175 | op.align_axis = {'Z'} 176 | 177 | 178 | # 9 - TOP - RIGHT 179 | 180 | # 1 - BOTTOM - LEFT 181 | 182 | # 3 - BOTTOM - RIGHT 183 | 184 | 185 | class VIEW3D_MT_PIE_SM_lattice(Menu): 186 | # bl_idname = "mesh.ssc_new_obj_menu" 187 | bl_label = "Smart Modify" 188 | 189 | def draw(self, context): 190 | layout = self.layout 191 | pie = layout.menu_pie() 192 | 193 | # 4 - LEFT 194 | pie.operator("mesh.lattice_resolution_2x2x2", text="Resolution 2x2x2") 195 | 196 | # 6 - RIGHT 197 | pie.operator("mesh.lattice_resolution_4x4x4", text="Resolution 4x4x4") 198 | 199 | # 2 - BOTTOM 200 | pie.operator("mesh.lattice_resolution_3x3x3", text="Resolution 3x3x3") 201 | 202 | # 8 - TOP 203 | pie.operator("mesh.quick_lattice", text="Apply Lattice") 204 | 205 | 206 | class VIEW3D_MT_PIE_SM_curve(Menu): 207 | # bl_idname = "mesh.ssc_new_obj_menu" 208 | bl_label = "Smart Modify" 209 | 210 | def draw(self, context): 211 | layout = self.layout 212 | pie = layout.menu_pie() 213 | 214 | # 4 - LEFT 215 | pie.operator("curve.normals_make_consistent", text="Recalc Normal") 216 | 217 | # 6 - RIGHT 218 | pie.operator("curve.subdivide", text="Subdivide") 219 | 220 | # 2 - BOTTOM 221 | submenu = pie.column() 222 | container = submenu.box() 223 | column = container.column() 224 | 225 | row = column.row(align=True) 226 | row.label(text="Set Spline Type...") 227 | row = column.row(align=True) 228 | 229 | row.operator("curve.handle_type_set", text="Automatic").type = 'AUTOMATIC' 230 | row.operator("curve.handle_type_set", text="Vector").type = 'VECTOR' 231 | 232 | row = column.row(align=True) 233 | row.operator("curve.handle_type_set", text="Aligned").type = 'ALIGNED' 234 | row.operator("curve.handle_type_set", text="Free Align").type = 'FREE_ALIGN' 235 | 236 | row = column.row(align=True) 237 | row.operator("curve.handle_type_set", text="Toggle Free/Align").type = 'TOGGLE_FREE_ALIGN' 238 | 239 | # 8 - TOP 240 | submenu = pie.column() 241 | container = submenu.box() 242 | column = container.column() 243 | 244 | row = column.row(align=True) 245 | row.label(text="Set Spline Type...") 246 | row = column.row(align=True) 247 | 248 | row.operator("curve.spline_type_set", text="Poly").type = 'POLY' 249 | row.operator("curve.spline_type_set", text="Bezier").type = 'BEZIER' 250 | row.operator("curve.spline_type_set", text="Nurbs").type = 'NURBS' 251 | 252 | 253 | class VIEW3D_MT_PIE_SM_uv(Menu): 254 | # bl_idname = "mesh.ssc_new_obj_menu" 255 | bl_label = "Smart Modify" 256 | 257 | def draw(self, context): 258 | layout = self.layout 259 | pie = layout.menu_pie() 260 | 261 | # 4 - LEFT 262 | submenu = pie.column() 263 | container = submenu.box() 264 | 265 | column = container.column() 266 | row = column.row(align=True) 267 | row.operator("uv.textools_island_align_edge", text="Align Edge", icon="MOD_EDGESPLIT") 268 | row = column.row(align=True) 269 | row.operator("uv.textools_island_align_world", text="Align World", icon="WORLD_DATA") 270 | 271 | 272 | # 6 - RIGHT 273 | pie.operator("uv.textools_rectify", text="Rectify", icon="UV_FACESEL") 274 | 275 | # 2 - BOTTOM 276 | submenu = pie.column() 277 | container = submenu.box() 278 | column = container.column() 279 | 280 | row = column.row(align=True) 281 | row.label(text="Seams From...") 282 | 283 | row = column.row(align=True) 284 | row.operator("uv.seams_from_islands", text="Islands") 285 | 286 | row.operator("uv.seams_from_sharps", text="Sharps") 287 | 288 | # 8 - TOP 289 | submenu = pie.column() 290 | container = submenu.box() 291 | column = container.column() 292 | 293 | row = column.row(align=True) 294 | row.operator("uv.textools_island_rotate_90", text="Rotate -90", icon="LOOP_BACK").angle = -1.5708 295 | row.operator("uv.textools_align", text="Align Top", icon="TRIA_UP").direction = "top" 296 | row.operator("uv.textools_island_rotate_90", text="Rotate +90", icon="LOOP_FORWARDS").angle = 1.5708 297 | 298 | row = column.row(align=True) 299 | row.operator("uv.textools_align", text="Align Left", icon="TRIA_LEFT").direction = "left" 300 | row.operator("uv.textools_align", text="Align Bottom", icon="TRIA_DOWN").direction = "bottom" 301 | row.operator("uv.textools_align", text="Align Right", icon="TRIA_RIGHT").direction = "right" 302 | 303 | # 7 - TOP - LEFT 304 | 305 | # 9 - TOP - RIGHT 306 | 307 | # 1 - BOTTOM - LEFT 308 | 309 | # 3 - BOTTOM - RIGHT 310 | 311 | 312 | class VIEW3D_MT_PIE_SM_mesh(Menu): 313 | # bl_idname = "mesh.ssc_new_obj_menu" 314 | bl_label = "Smart Modify" 315 | 316 | def draw(self, context): 317 | layout = self.layout 318 | pie = layout.menu_pie() 319 | 320 | # 4 - LEFT 321 | 322 | submenu = pie.column() 323 | container = submenu.box() 324 | column = container.column() 325 | row = column.row(align=True) 326 | row.operator("mesh.quick_flatten", text="Flatten Avg Normal").mode = 1 327 | 328 | row = column.row(align=True) 329 | # row.label(text="Global") 330 | row.operator("mesh.quick_flatten", text="X").mode = 2 331 | row.operator("mesh.quick_flatten", text="Y").mode = 3 332 | row.operator("mesh.quick_flatten", text="Z").mode = 4 333 | 334 | # 6 - RIGHT 335 | submenu = pie.column() 336 | container = submenu.box() 337 | column = container.column() 338 | row = column.row(align=True) 339 | row.operator("mesh.quick_lattice", text="Quick Lattice") 340 | 341 | row = column.row(align=True) 342 | row.operator("mesh.quick_pipe", text="Quick Pipe") 343 | 344 | 345 | # 2 - BOTTOM 346 | # Flow Submenu 347 | submenu = pie.column() 348 | container = submenu.box() 349 | column = container.column() 350 | if get_loop_tools_active(): 351 | row = column.row(align=True) 352 | row.operator("mesh.looptools_circle", text="Make Circle") 353 | 354 | if get_set_flow_active(): 355 | op = row.operator("mesh.set_edge_flow", text="Set Flow") 356 | op.tension = 180 357 | op.iterations = 1 358 | 359 | if get_loop_tools_active(): 360 | row.operator("mesh.looptools_space", text="Space") 361 | row = column.row(align=True) 362 | 363 | row.operator("mesh.looptools_curve", text="Make Curve") 364 | row.operator("mesh.looptools_relax", text="Relax") 365 | 366 | # 8 - TOP 367 | pie.operator("mesh.quick_visual_geo_to_mesh", text="Visual Geo To Mesh") 368 | 369 | # 7 - TOP - LEFT 370 | pie.operator("mesh.flip_normals", text="Flip Normal") 371 | 372 | # 9 - TOP - RIGHT 373 | pie.operator("mesh.rebase_cylinder", text="Rebase Cylinder") 374 | # 1 - BOTTOM - LEFT 375 | 376 | # 3 - BOTTOM - RIGHT 377 | 378 | # Align World Submenu 379 | """ 380 | submenu = pie.column() 381 | container = submenu.box() 382 | column = container.column() 383 | row = column.row(align = True) 384 | op = row.operator("mesh.quick_align", text="Align World") 385 | op.relative_to = 'OPT_1' 386 | op.align_axis = {'X', 'Y', 'Z'} 387 | 388 | row = column.row(align = True) 389 | op = row.operator("mesh.quick_align", text="X") 390 | op.relative_to = 'OPT_1' 391 | op.align_axis = {'X'} 392 | 393 | op = row.operator("mesh.quick_align", text="Y") 394 | op.relative_to = 'OPT_1' 395 | op.align_axis = {'Y'} 396 | 397 | op = row.operator("mesh.quick_align", text="Z") 398 | op.relative_to = 'OPT_1' 399 | op.align_axis = {'Z'} 400 | """ 401 | # Flatten Submenu 402 | 403 | 404 | ''' 405 | TO Do: Local Alignment 406 | 407 | row = column.row(align = True) 408 | row.label(text="Local ") 409 | row.operator("mesh.quick_flatten", text = "X").mode = 2 410 | row.operator("mesh.quick_flatten", text = "Y").mode = 3 411 | row.operator("mesh.quick_flatten", text = "Z").mode = 4 412 | ''' 413 | 414 | 415 | class VIEW3D_MT_PIE_SM_looptools(Menu): 416 | # bl_idname = "mesh.ssc_new_obj_menu" 417 | bl_label = "Loop Tools" 418 | 419 | def draw(self, context): 420 | layout = self.layout 421 | pie = layout.menu_pie() 422 | 423 | # 4 - LEFT 424 | pie.operator("mesh.looptools_circle", text="Make Circle") 425 | 426 | # 6 - RIGHT 427 | pie.operator("mesh.looptools_flatten", text="Flatten") 428 | 429 | # 2 - BOTTOM 430 | pie.operator("mesh.looptools_curve", text="Make Curve") 431 | 432 | # 8 - TOP 433 | if get_set_flow_active(): 434 | op4 = pie.operator("mesh.set_edge_flow", text="Set Flow") 435 | op4.tension = 180 436 | op4.iterations = 1 437 | 438 | # 7 - TOP - LEFT 439 | pie.operator("mesh.looptools_relax", text="Relax") 440 | 441 | # 9 - TOP - RIGHT 442 | pie.operator("mesh.looptools_space", text="Space") 443 | 444 | 445 | class VIEW3D_MT_PIE_TransformOptions(Menu): 446 | bl_label = "Transform Options" 447 | 448 | def draw(self, context): 449 | layout = self.layout 450 | pie = layout.menu_pie() 451 | 452 | # 4 - LEFT 453 | if (bpy.context.scene.tool_settings.snap_elements == {'VERTEX'} and bpy.context.scene.tool_settings.snap_target == 'CLOSEST'): 454 | op = pie.operator("mesh.snap_presets_op", text="Vert Closest", icon="SNAP_VERTEX", depress=True).mode = 3 455 | else: 456 | op = pie.operator("mesh.snap_presets_op", text="Vert Closest", icon="SNAP_VERTEX").mode = 3 457 | 458 | # 6 - RIGHT 459 | if(bpy.context.scene.tool_settings.snap_elements == {'VERTEX'} and 460 | bpy.context.scene.tool_settings.snap_target == 'CENTER'): 461 | pie.operator("mesh.snap_presets_op", text="Vert Center", icon="SNAP_VERTEX", depress=True).mode = 2 462 | else: 463 | pie.operator("mesh.snap_presets_op", text="Vert Center", icon="SNAP_VERTEX").mode = 2 464 | 465 | # 2 - BOTTOM 466 | if(bpy.context.scene.tool_settings.snap_elements == {'FACE'} and 467 | bpy.context.scene.tool_settings.use_snap_align_rotation == True and 468 | bpy.context.scene.tool_settings.use_snap_project == True): 469 | pie.operator("mesh.snap_presets_op", text="Face Normal", icon="SNAP_FACE",depress=True).mode = 4 470 | else: 471 | pie.operator("mesh.snap_presets_op", text="Face Normal", icon="SNAP_FACE").mode = 4 472 | 473 | # 8 - TOP 474 | box = pie.split() 475 | 476 | #Top Left Column 477 | column = box.column() 478 | self.draw_proportional_editing(column) 479 | 480 | #Top Center 481 | column = box.column() 482 | column.scale_x = 1.5 483 | self.draw_orientations_submenu(column) 484 | 485 | #Top Right Column 486 | column = box.column() 487 | self.draw_transform_pivot_submenu(column) 488 | 489 | # 7 - TOP - LEFT 490 | pie.separator() 491 | 492 | # 9 - TOP - RIGHT 493 | pie.separator() 494 | 495 | # 1 - BOTTOM - LEFT 496 | if (bpy.context.scene.tool_settings.snap_elements == {'INCREMENT'} and bpy.context.scene.tool_settings.use_snap_grid_absolute == True): 497 | pie.operator("mesh.snap_presets_op", text="Grid Absolute", icon="SNAP_INCREMENT",depress=True).mode = 1 498 | else: 499 | pie.operator("mesh.snap_presets_op", text="Grid Absolute", icon="SNAP_INCREMENT").mode = 1 500 | 501 | # 3 - BOTTOM - RIGHT 502 | if (bpy.context.scene.tool_settings.snap_elements == {'EDGE_MIDPOINT'} and bpy.context.scene.tool_settings.snap_target == 'MEDIAN'): 503 | pie.operator("mesh.snap_presets_op", text="Edge Center", icon="SNAP_MIDPOINT",depress=True).mode = 5 504 | else: 505 | pie.operator("mesh.snap_presets_op", text="Edge Center", icon="SNAP_MIDPOINT").mode = 5 506 | 507 | def draw_orientations_submenu(self, ui_space): 508 | submenu = ui_space.column() 509 | container = submenu.box() 510 | column = container.column() 511 | 512 | row = column.row(align=True) 513 | row.label(text="Default Orientations") 514 | row = column.row(align=True) 515 | if bpy.context.scene.transform_orientation_slots[0].type == 'GLOBAL': 516 | row.operator("mesh.transform_orientation_op", text="Global", icon="ORIENTATION_GLOBAL", depress=True).mode = 8 517 | else: 518 | row.operator("mesh.transform_orientation_op", text="Global", icon="ORIENTATION_GLOBAL").mode = 8 519 | 520 | if bpy.context.scene.transform_orientation_slots[0].type == 'LOCAL': 521 | row.operator("mesh.transform_orientation_op", text="Local", icon="ORIENTATION_LOCAL", depress=True).mode = 9 522 | else: 523 | row.operator("mesh.transform_orientation_op", text="Local", icon="ORIENTATION_LOCAL").mode = 9 524 | 525 | if bpy.context.scene.transform_orientation_slots[0].type == 'CURSOR': 526 | row.operator("mesh.transform_orientation_op", text="Cursor", icon="ORIENTATION_CURSOR", depress=True).mode = 13 527 | else: 528 | row.operator("mesh.transform_orientation_op", text="Cursor", icon="ORIENTATION_CURSOR").mode = 13 529 | 530 | row = column.row(align=True) 531 | if bpy.context.scene.transform_orientation_slots[0].type == 'NORMAL': 532 | row.operator("mesh.transform_orientation_op", text="Normal", icon="ORIENTATION_NORMAL", depress=True).mode = 10 533 | else: 534 | row.operator("mesh.transform_orientation_op", text="Normal", icon="ORIENTATION_NORMAL").mode = 10 535 | 536 | if bpy.context.scene.transform_orientation_slots[0].type == 'VIEW': 537 | row.operator("mesh.transform_orientation_op", text="View", icon="ORIENTATION_VIEW", depress=True).mode = 12 538 | else: 539 | row.operator("mesh.transform_orientation_op", text="View", icon="ORIENTATION_VIEW").mode = 12 540 | 541 | if bpy.context.scene.transform_orientation_slots[0].type == 'GIMBAL': 542 | row.operator("mesh.transform_orientation_op", text="Gimbal", icon="ORIENTATION_GIMBAL", depress=True).mode = 11 543 | else: 544 | row.operator("mesh.transform_orientation_op", text="Gimbal", icon="ORIENTATION_GIMBAL").mode = 11 545 | 546 | row = column.row(align=True) 547 | row.label(text="Custom Orientations") 548 | 549 | row = column.row(align=True) 550 | row.operator("mesh.transform_orientation_op", text="Set Custom 1", icon='TRIA_DOWN').mode = 1 551 | if bpy.context.scene.transform_orientation_slots[0].type == 'Custom 1': 552 | row.operator("mesh.transform_orientation_op", text="Use Custom 1", icon='TRIA_UP', depress=True).mode = 4 553 | else: 554 | row.operator("mesh.transform_orientation_op", text="Use Custom 1", icon='TRIA_UP').mode = 4 555 | 556 | row = column.row(align=True) 557 | row.operator("mesh.transform_orientation_op", text="Set Custom 2", icon='TRIA_DOWN').mode = 2 558 | if bpy.context.scene.transform_orientation_slots[0].type == 'Custom 2': 559 | row.operator("mesh.transform_orientation_op", text="Use Custom 2", icon='TRIA_UP', depress=True).mode = 5 560 | else: 561 | row.operator("mesh.transform_orientation_op", text="Use Custom 2", icon='TRIA_UP').mode = 5 562 | 563 | row = column.row(align=True) 564 | row.operator("mesh.transform_orientation_op", text="Set Custom 3", icon='TRIA_DOWN').mode = 3 565 | if bpy.context.scene.transform_orientation_slots[0].type == 'Custom 3': 566 | row.operator("mesh.transform_orientation_op", text="Use Custom 3", icon='TRIA_UP', depress=True).mode = 6 567 | else: 568 | row.operator("mesh.transform_orientation_op", text="Use Custom 3", icon='TRIA_UP').mode = 6 569 | 570 | row = column.row(align=True) 571 | row.separator() 572 | 573 | row = column.row(align=True) 574 | row.operator("mesh.transform_orientation_op", text="Reset Working Pivot", icon='FILE_REFRESH').mode = 7 575 | 576 | def draw_transform_pivot_submenu(self, ui_space): 577 | submenu = ui_space.column() 578 | container = submenu.box() 579 | column = container.column() 580 | 581 | row = column.row(align=True) 582 | row.label(text="Transform Pivot Point") 583 | row = column.row(align=True) 584 | 585 | if bpy.context.scene.tool_settings.transform_pivot_point == 'MEDIAN_POINT': 586 | row.operator("mesh.transform_pivot_point_op", text="Median Point", icon="PIVOT_MEDIAN", depress=True).mode = 1 587 | else: 588 | row.operator("mesh.transform_pivot_point_op", text="Median Point", icon="PIVOT_MEDIAN").mode = 1 589 | 590 | row = column.row(align=True) 591 | if bpy.context.scene.tool_settings.transform_pivot_point == 'ACTIVE_ELEMENT': 592 | row.operator("mesh.transform_pivot_point_op", text="Active Element", icon="PIVOT_ACTIVE", depress=True).mode = 2 593 | else: 594 | row.operator("mesh.transform_pivot_point_op", text="Active Element", icon="PIVOT_ACTIVE").mode = 2 595 | 596 | row = column.row(align=True) 597 | if bpy.context.scene.tool_settings.transform_pivot_point == 'INDIVIDUAL_ORIGINS': 598 | row.operator("mesh.transform_pivot_point_op", text="Individual Origins", icon="PIVOT_INDIVIDUAL", depress=True).mode = 3 599 | else: 600 | row.operator("mesh.transform_pivot_point_op", text="Individual Origins", icon="PIVOT_INDIVIDUAL").mode = 3 601 | 602 | row = column.row(align=True) 603 | if bpy.context.scene.tool_settings.transform_pivot_point == 'CURSOR': 604 | row.operator("mesh.transform_pivot_point_op", text="3D Cursor", icon="PIVOT_CURSOR", depress=True).mode = 4 605 | else: 606 | row.operator("mesh.transform_pivot_point_op", text="3D Cursor", icon="PIVOT_CURSOR").mode = 4 607 | 608 | row = column.row(align=True) 609 | if bpy.context.scene.tool_settings.transform_pivot_point == 'BOUNDING_BOX_CENTER': 610 | row.operator("mesh.transform_pivot_point_op", text="Bounding Box Center", icon="PIVOT_BOUNDBOX", depress=True).mode = 5 611 | else: 612 | row.operator("mesh.transform_pivot_point_op", text="Bounding Box Center", icon="PIVOT_BOUNDBOX").mode = 5 613 | 614 | def draw_proportional_editing(self, ui_space): 615 | submenu = ui_space.column() 616 | container = submenu.box() 617 | column = container.column() 618 | 619 | row = column.row(align=True) 620 | row.label(text="Proportional Editing Falloffs") 621 | row = column.row(align=True) 622 | 623 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'SMOOTH': 624 | row.operator("mesh.prop_edit_op", text="Smooth", icon = "SMOOTHCURVE", depress=True).mode = 1 625 | else: 626 | row.operator("mesh.prop_edit_op", text="Smooth", icon = "SMOOTHCURVE").mode = 1 627 | 628 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'SPHERE': 629 | row.operator("mesh.prop_edit_op", text="Sphere", icon = "SPHERECURVE", depress=True).mode = 2 630 | else: 631 | row.operator("mesh.prop_edit_op", text="Sphere", icon = "SPHERECURVE").mode = 2 632 | 633 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'ROOT': 634 | row.operator("mesh.prop_edit_op", text="Root", icon = "ROOTCURVE", depress=True).mode = 3 635 | else: 636 | row.operator("mesh.prop_edit_op", text="Root", icon = "ROOTCURVE").mode = 3 637 | 638 | row = column.row(align=True) 639 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'INVERSE_SQUARE': 640 | row.operator("mesh.prop_edit_op", text="Inverse Square", icon = "INVERSESQUARECURVE", depress=True).mode = 4 641 | else: 642 | row.operator("mesh.prop_edit_op", text="Inverse", icon = "INVERSESQUARECURVE").mode = 4 643 | 644 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'SHARP': 645 | row.operator("mesh.prop_edit_op", text="Sharp", icon = "SHARPCURVE", depress=True).mode = 5 646 | else: 647 | row.operator("mesh.prop_edit_op", text="Sharp", icon = "SHARPCURVE").mode = 5 648 | 649 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'LINEAR': 650 | row.operator("mesh.prop_edit_op", text="Linear", icon = "LINCURVE", depress=True).mode = 6 651 | else: 652 | row.operator("mesh.prop_edit_op", text="Linear", icon = "LINCURVE").mode = 6 653 | 654 | row = column.row(align=True) 655 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'CONSTANT': 656 | row.operator("mesh.prop_edit_op", text="Constant", icon = "NOCURVE", depress=True).mode = 7 657 | else: 658 | row.operator("mesh.prop_edit_op", text="Constant", icon = "NOCURVE").mode = 7 659 | 660 | if bpy.context.scene.tool_settings.proportional_edit_falloff == 'RANDOM': 661 | row.operator("mesh.prop_edit_op", text="Random", icon = "RNDCURVE", depress=True).mode = 8 662 | else: 663 | row.operator("mesh.prop_edit_op", text="Random", icon = "RNDCURVE").mode = 8 664 | 665 | row = column.row(align=True) 666 | row.separator() 667 | 668 | row = column.row(align=True) 669 | if bpy.context.scene.tool_settings.use_proportional_connected: 670 | row.operator("mesh.prop_edit_op", text="Connected Only", icon = "PROP_ON", depress=True).mode = 10 671 | else: 672 | row.operator("mesh.prop_edit_op", text="Connected Only", icon = "PROP_OFF").mode = 10 673 | row = column.row(align=True) 674 | 675 | if bpy.context.scene.tool_settings.use_proportional_projected: 676 | row.operator("mesh.prop_edit_op", text="Projected", icon = "PROP_ON", depress=True).mode = 11 677 | else: 678 | row.operator("mesh.prop_edit_op", text="Projected", icon = "PROP_OFF").mode = 11 679 | 680 | row = column.row(align=True) 681 | row.separator() 682 | 683 | row = column.row(align=True) 684 | if bpy.context.scene.tool_settings.use_proportional_edit_objects: 685 | row.operator("mesh.prop_edit_op", text="Deactivate", icon = "PROP_ON", depress=True).mode = 9 686 | else: 687 | row.operator("mesh.prop_edit_op", text="Acrivate", icon = "PROP_OFF").mode = 9 688 | -------------------------------------------------------------------------------- /utils/debug.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | from .. utils import itools as itools 3 | from .. utils import mesh as mesh 4 | from .. utils import user_prefs as up 5 | from .. utils import dictionaries as dic 6 | from .. op.super_smart_create import SuperSmartCreate 7 | from .. op import selection as sel 8 | 9 | 10 | class MaxivzToolsDebug_PT_Panel(bpy.types.Panel): 11 | bl_idname = "MaxivzToolsDebug_PT_Panel" 12 | bl_label = "Debug" 13 | bl_category = "Maxivz Tools" 14 | bl_space_type = "VIEW_3D" 15 | bl_region_type = "UI" 16 | 17 | def draw(self, context): 18 | layout = self.layout 19 | row0 = layout.row() 20 | row0.operator('itools.debug', text="Debug") 21 | 22 | 23 | class DebugOp(bpy.types.Operator): 24 | bl_idname = "itools.debug" 25 | bl_label = "Debug" 26 | bl_description = "Debug Button" 27 | bl_options = {'REGISTER', 'UNDO'} 28 | 29 | def execute(self, context): 30 | prop = up.get_keymaps_by_key() 31 | print("Preference Settings : ", prop) 32 | return {'FINISHED'} 33 | 34 | 35 | class DebugOpModal(bpy.types.Operator): 36 | bl_idname = "itools.debug_modal" 37 | bl_label = "Debug Modal" 38 | bl_description = "Debug Button Modal" 39 | bl_options = {'REGISTER', 'UNDO'} 40 | 41 | mode = 0 42 | 43 | def __init__(self): 44 | print("Start") 45 | 46 | def __del__(self): 47 | print("End") 48 | 49 | def execute(self, context): 50 | context.object.location.x = self.value / 100.0 51 | return {'FINISHED'} 52 | 53 | def modal(self, context, event): 54 | value = get_property("activate_debug") 55 | if value: 56 | print("Option Active") 57 | else: 58 | print("Option Not Active") 59 | return {'FINISHED'} 60 | if event.type == 'MOUSEMOVE': # Apply 61 | self.value = event.mouse_x 62 | self.execute(context) 63 | elif event.type == 'LEFTMOUSE': # Confirm 64 | return {'FINISHED'} 65 | elif event.type in {'RIGHTMOUSE', 'ESC'}: # Cancel 66 | context.object.location.x = self.init_loc_x 67 | return {'CANCELLED'} 68 | 69 | return {'RUNNING_MODAL'} 70 | 71 | def invoke(self, context, event): 72 | return {'RUNNING_MODAL'} 73 | -------------------------------------------------------------------------------- /utils/dictionaries.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | itools_dic = {"selected_verts": [], 4 | "selected_edges": [], 5 | "selected_faces": []} 6 | 7 | 8 | def write(data_block, values, obj=""): 9 | if obj == "": 10 | obj = bpy.context.active_object 11 | 12 | if "itools" not in bpy.context.object: 13 | obj['itools'] = itools_dic 14 | 15 | obj['itools'][data_block] = values 16 | 17 | 18 | def read(data_block, obj=""): 19 | if obj == "": 20 | obj = bpy.context.active_object 21 | 22 | if "itools" in bpy.context.object: 23 | return obj['itools'][data_block] 24 | 25 | else: 26 | return "" 27 | -------------------------------------------------------------------------------- /utils/itools.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | from collections import OrderedDict, Counter 4 | 5 | 6 | MAX_ITERATIONS = 400 7 | DOUBLECLICK_TIME = 0.1 8 | 9 | 10 | def list_union(a, b): 11 | return list(set(a) | set(b)) 12 | 13 | 14 | def list_intersection(a, b): 15 | temp = set(b) 16 | result = [item for item in a if item in temp] 17 | return result 18 | # return list(set(a) & set(b)) 19 | 20 | 21 | def list_difference(a, b): 22 | temp = set(b) 23 | result = [item for item in a if item not in temp] 24 | return result 25 | # return list(set(a) - set(b)) 26 | 27 | 28 | def list_reduce_ordered(items): 29 | new_list = list(OrderedDict.fromkeys(items)) 30 | return new_list 31 | 32 | 33 | def get_mode(): 34 | mode = bpy.context.mode 35 | if mode == 'EDIT_MESH': 36 | selection_mode = (tuple(bpy.context.scene.tool_settings.mesh_select_mode)) 37 | if selection_mode[0]: 38 | return 'VERT' 39 | elif selection_mode[1]: 40 | return 'EDGE' 41 | elif selection_mode[2]: 42 | return 'FACE' 43 | 44 | if mode == 'EDIT_GPENCIL': 45 | return bpy.context.scene.tool_settings.gpencil_selectmode_edit 46 | 47 | return mode 48 | 49 | 50 | def set_mode(mode, grow=False): 51 | actual_mode = get_mode() 52 | if mode == 'OBJECT' and actual_mode != 'OBJECT': 53 | bpy.ops.object.mode_set(mode='OBJECT') 54 | 55 | elif mode in ['VERT', 'EDGE', 'FACE']: 56 | if actual_mode == 'OBJECT': 57 | bpy.ops.object.mode_set(mode='EDIT') 58 | bpy.ops.mesh.select_mode(type=mode, use_expand=grow) 59 | 60 | 61 | def get_bmesh(): 62 | if get_mode() in ['VERT', 'EDGE', 'FACE']: 63 | return bmesh.from_edit_mesh(bpy.context.edit_object.data) 64 | else: 65 | print("Must be in obj mode to get bmesh") 66 | 67 | 68 | def to_index(items): 69 | return [element.index for element in items] 70 | 71 | 72 | # Return item or index for selected mesh elements or names for objects 73 | # Add selection order by using print([a.index for a in bm.select_history]) 74 | def get_selected(mode='', item=True, ordered=False, all=False): 75 | selection = [] 76 | if not mode: 77 | mode = get_mode() 78 | 79 | if mode == 'OBJECT': 80 | if item: 81 | return [obj for obj in bpy.context.selected_objects] 82 | 83 | else: 84 | return [obj.name for obj in bpy.context.selected_objects] 85 | 86 | elif mode in ['VERT', 'EDGE', 'FACE']: 87 | bm = get_bmesh() 88 | 89 | if ordered: 90 | if mode == 'VERT': 91 | selection = [vert for vert in bm.select_history if isinstance(vert, bmesh.types.BMVert)] 92 | elif mode == 'EDGE': 93 | selection = [edge for edge in bm.select_history if isinstance(edge, bmesh.types.BMEdge)] 94 | elif mode == 'FACE': 95 | selection = [face for face in bm.select_history if isinstance(face, bmesh.types.BMFace)] 96 | 97 | if all: 98 | if mode == 'VERT': 99 | selection = [vert for vert in bm.verts] 100 | elif mode == 'EDGE': 101 | selection = [edge for edge in bm.edges] 102 | elif mode == 'FACE': 103 | selection = [face for face in bm.faces] 104 | 105 | else: 106 | if mode == 'VERT': 107 | selection = [vert for vert in bm.verts if vert.select] 108 | elif mode == 'EDGE': 109 | selection = [edge for edge in bm.edges if edge.select] 110 | elif mode == 'FACE': 111 | selection = [face for face in bm.faces if face.select] 112 | 113 | if item: 114 | return selection 115 | else: 116 | return [element.index for element in selection] 117 | 118 | elif mode == 'EDIT_CURVE': 119 | curves = bpy.context.active_object.data.splines 120 | points = [] 121 | 122 | if all: 123 | for curve in curves: 124 | if curve.type == 'BEZIER': 125 | points.append([point for point in curve.bezier_points]) 126 | 127 | else: 128 | points.append([point for point in curve.points]) 129 | 130 | else: 131 | for curve in curves: 132 | if curve.type == 'BEZIER': 133 | points.append([point for point in curve.bezier_points 134 | if point.select_control_point]) 135 | 136 | else: 137 | points.append([point for point in curve.points 138 | if point.select]) 139 | 140 | points = [item for sublist in points for item in sublist] 141 | return points 142 | 143 | else: 144 | return [] 145 | 146 | 147 | # Returns active object name 148 | def active_get(item=True): 149 | if item: 150 | return bpy.context.active_object 151 | else: 152 | return bpy.context.active_object.name 153 | 154 | 155 | # Sets active object based on name 156 | def active_set(obj, item=True): 157 | if item: 158 | print(obj) 159 | bpy.context.view_layer.objects.active = obj 160 | else: 161 | bpy.context.view_layer.objects.active = bpy.data.objects[obj] 162 | 163 | 164 | # Make selection based on indexes for selected mesh elements or names for objects 165 | def select(target, mode='', item=True, replace=False, deselect=False, add_to_history=False, safe_mode=False): 166 | if not mode: 167 | mode = get_mode() 168 | 169 | if safe_mode: 170 | existing_items = get_selected(mode=mode, item=item, all=True) 171 | 172 | selection_value = True 173 | 174 | if deselect: 175 | selection_value = False 176 | 177 | if type(target) != list: 178 | target = [target] 179 | 180 | if mode == 'OBJECT': 181 | if replace: 182 | bpy.ops.object.select_all(action='DESELECT') 183 | 184 | if item: 185 | for obj in target: 186 | target.select_set(selection_value) 187 | 188 | else: 189 | for obj in target: 190 | bpy.data.objects[obj].select_set(selection_value) 191 | 192 | elif mode in ['VERT', 'EDGE', 'FACE']: 193 | bm = get_bmesh() 194 | 195 | if replace: 196 | bpy.ops.mesh.select_all(action='DESELECT') 197 | 198 | if item: 199 | target = [item.index for item in target] 200 | 201 | if safe_mode: 202 | if mode == 'VERT': 203 | for vert in target: 204 | if vert in existing_items: 205 | bm.verts[vert].select = selection_value 206 | if add_to_history: 207 | bm.select_history.add(bm.verts[vert]) 208 | 209 | elif mode == 'EDGE': 210 | for edge in target: 211 | if edge in existing_items: 212 | bm.edges[edge].select = selection_value 213 | if add_to_history: 214 | bm.select_history.add(bm.edges[edge]) 215 | 216 | elif mode == 'FACE': 217 | for face in target: 218 | if face in existing_items: 219 | bm.faces[face].select = selection_value 220 | if add_to_history: 221 | bm.select_history.add(bm.faces[face]) 222 | 223 | else: 224 | if mode == 'VERT': 225 | for vert in target: 226 | bm.verts[vert].select = selection_value 227 | if add_to_history: 228 | bm.select_history.add(bm.verts[vert]) 229 | 230 | elif mode == 'EDGE': 231 | for edge in target: 232 | bm.edges[edge].select = selection_value 233 | if add_to_history: 234 | bm.select_history.add(bm.edges[edge]) 235 | 236 | elif mode == 'FACE': 237 | for face in target: 238 | bm.faces[face].select = selection_value 239 | if add_to_history: 240 | bm.select_history.add(bm.faces[face]) 241 | 242 | elif mode == 'EDIT_CURVE': 243 | print("Curve") 244 | curves = bpy.context.active_object.data.splines 245 | points = [] 246 | for curve in curves: 247 | if safe_mode: 248 | if curve.type == 'BEZIER': 249 | for point in curve.bezier_points: 250 | if point in existing_items: 251 | point.select_control_point = True 252 | else: 253 | for point in curve.points: 254 | if point in existing_items: 255 | point.select = True 256 | else: 257 | if curve.type == 'BEZIER': 258 | for point in curve.bezier_points: 259 | point.select_control_point = True 260 | else: 261 | for point in curve.points: 262 | point.select = True 263 | 264 | 265 | def convert_selection(selection, to): 266 | mode = get_mode() 267 | 268 | if mode == 'VERT': 269 | if to == 'EDGE': 270 | new_selection = [edge for vert in selection 271 | for edge in vert.link_edges] 272 | 273 | elif to == 'FACE': 274 | new_selection = [face for vert in selection 275 | for face in vert.link_faces] 276 | 277 | new_selection = [item for item, count 278 | in Counter(new_selection).items() 279 | if count > 1] 280 | 281 | elif mode == 'EDGE': 282 | if to == 'VERT': 283 | new_selection = [vert for edge in selection 284 | for vert in edge.verts] 285 | 286 | elif to == 'FACE': 287 | new_selection = [face for edge in selection 288 | for face in edge.link_faces] 289 | 290 | elif mode == 'FACE': 291 | if to == 'VERT': 292 | new_selection = [vert for face in selection 293 | for vert in face.verts] 294 | 295 | elif to == 'EDGE': 296 | new_selection = [edge for face in selection 297 | for edge in face.edges] 298 | 299 | new_selection = remove_duplicates(new_selection) 300 | 301 | return new_selection 302 | 303 | 304 | def update_indexes(mode=''): 305 | bm = get_bmesh() 306 | if not mode: 307 | print("Try to get mode") 308 | mode = get_mode() 309 | print(mode) 310 | 311 | if 'VERT' or 'ALL' in mode: 312 | bm.verts.index_update() 313 | bm.verts.ensure_lookup_table() 314 | 315 | if 'EDGE' or 'ALL' in mode: 316 | bm.edges.index_update() 317 | bm.edges.ensure_lookup_table() 318 | 319 | if 'FACE' or 'ALL' in mode: 320 | bm.faces.index_update() 321 | bm.faces.ensure_lookup_table() 322 | 323 | bmesh.update_edit_mesh(bpy.context.edit_object.data) 324 | 325 | 326 | def remove_duplicates(target): 327 | return list(set(target)) 328 | 329 | 330 | def get_children(obj_name): 331 | children = [] 332 | for ob in bpy.data.objects: 333 | if ob.parent != None: 334 | if ob.parent.name == obj_name: 335 | children.append(ob) 336 | return children 337 | -------------------------------------------------------------------------------- /utils/load.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | dirlist = os.listdir(path='.') 4 | classes = () 5 | for dir in dirlist: 6 | if dir in ['op','utils','ui']: 7 | #Find .py files 8 | files = os.listdir(path=dir) 9 | files = list(filter(lambda x: x[-3:] == '.py', files)) 10 | print(files) 11 | 12 | #Find class list in files 13 | for f in files: 14 | try: 15 | f = f[:-3] 16 | from . dir import f 17 | print(f.classes_to_load) 18 | except: 19 | print("Couldnt find variables") -------------------------------------------------------------------------------- /utils/mesh.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import bmesh 3 | from functools import reduce 4 | from . import itools as itools 5 | 6 | # Shared Mesh utilities and operations 7 | 8 | 9 | def verts_share_edge(verts): 10 | if len(verts) == 2: 11 | return len(itools.list_intersection(verts[0].link_edges, 12 | verts[1].link_edges)) == 1 13 | 14 | else: 15 | return False 16 | 17 | 18 | # Aproximation, might not work all the times. Will replace later 19 | def verts_share_face(verts): 20 | face_list = [] 21 | for vert in verts: 22 | face_list.append(vert.link_faces) 23 | face_list = reduce(lambda x, y: itools.list_intersection(x, y), face_list) 24 | if len(face_list) > 0: 25 | return True 26 | else: 27 | return False 28 | 29 | 30 | def is_corner_vert(vert): 31 | return len([face for face in vert.link_faces]) > 2 32 | 33 | 34 | def is_border_vert(vert): 35 | return len([edge for edge in vert.link_edges if len(edge.link_faces) == 1]) > 1 36 | 37 | 38 | def are_border_verts(verts): 39 | return all(is_border_vert(vert) for vert in verts) 40 | 41 | 42 | def is_border_edge(edge): 43 | return all(is_border_vert(vert) for vert in edge.verts) 44 | 45 | 46 | def is_border(selection): 47 | # every edge must be adjacent with two other edges, if its a closed 48 | # border the number of adjacent edges should be at least 2 X number edges 49 | adjacent_edges = [neightbour for edge in selection for verts in edge.verts 50 | for neightbour in verts.link_edges if neightbour in selection and neightbour != edge] 51 | return (all(is_border_edge(edge) for edge in selection) and len(adjacent_edges) >= len(selection) * 2) 52 | 53 | 54 | def is_partial_border(selection): 55 | return (all(is_border_edge(edge) for edge in selection)) 56 | 57 | 58 | def is_adjacent(selection, mode): 59 | if mode == 'EDGE': 60 | vert_list = [edge.verts for edge in selection] 61 | common_vert = reduce(lambda x, y: itools.list_intersection(x, y), vert_list) 62 | return len(common_vert) == 1 63 | 64 | elif mode == 'FACE': 65 | edge_list = [edge for face in selection for edge in face.edges] 66 | print("Edge list :", edge_list) 67 | vert_list = [edge.verts for edge in edge_list] 68 | print("Vert list :", vert_list) 69 | common_vert = reduce(lambda x, y: itools.list_intersection(x, y), vert_list) 70 | return len(common_vert) > 0 71 | 72 | 73 | def organize_faces_by_continuity(selection): 74 | groups = [] 75 | ordered_groups = [] 76 | temp = [] 77 | 78 | for face in selection: 79 | adjacent_faces = [] 80 | adjacent_faces = [face2.index for edge in face.edges 81 | for face2 in edge.link_faces 82 | if face2 in selection] 83 | adjacent_faces.append(face.index) 84 | adjacent_faces = list(set(adjacent_faces)) 85 | groups.append(adjacent_faces) 86 | 87 | while len(groups) > 0: 88 | temp = groups[0] 89 | for group in groups[1:]: 90 | intersection = itools.list_intersection(temp, group) 91 | if len(intersection) > 0: 92 | temp = itools.list_union(temp, group) 93 | 94 | ordered_groups.append(temp) 95 | 96 | groups = [group for group in groups 97 | if not any(element in temp 98 | for element in group)] 99 | 100 | return ordered_groups 101 | 102 | 103 | def is_ring(selection): 104 | """ 105 | # Aproximation that should work 98% for now 106 | # Gets false positives when corners are selected like this: I_ or _I 107 | """ 108 | neightbour_numbers = [edge for edge in selection 109 | if len([face for face in edge.link_faces if any(edge2 for edge2 in face.edges 110 | if edge2 in selection and edge2 != edge)]) > 0] 111 | return len(neightbour_numbers) == len(selection) 112 | -------------------------------------------------------------------------------- /utils/user_prefs.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | import addon_utils 3 | from bpy.types import Operator, AddonPreferences, Menu 4 | from bpy.props import StringProperty, IntProperty, BoolProperty, EnumProperty 5 | from os.path import basename, dirname 6 | import rna_keymap_ui 7 | 8 | 9 | def addon_installed(addon_name): 10 | #Thanks machin3 for the help to make this work for 4.2! 11 | for mod in addon_utils.modules(): 12 | name = mod.bl_info["name"] 13 | 14 | if name == addon_name: 15 | return True 16 | 17 | return False 18 | 19 | 20 | def add_keymap(function_name, key, modifiers=[], kname='3D View Generic', context='VIEW_3D'): 21 | mod_alt = False 22 | mod_ctrl = False 23 | mod_shift = False 24 | 25 | if 'ALT' in modifiers: 26 | mod_alt = True 27 | 28 | if 'CTRL' in modifiers: 29 | mod_ctrl = True 30 | 31 | if 'SHIFT' in modifiers: 32 | mod_shift = True 33 | 34 | wm = bpy.context.window_manager 35 | kc = wm.keyconfigs.user 36 | if kc: 37 | km = kc.keymaps.new(name=kname, 38 | space_type=context, 39 | region_type='WINDOW') 40 | kmi = km.keymap_items.new(function_name, 41 | type=key, value='PRESS', 42 | ctrl=mod_ctrl, shift=mod_shift, 43 | alt=mod_alt) 44 | 45 | addon_keymaps.append((km, kmi)) 46 | 47 | 48 | def add_hotkey_ui(name, km, kc, row): 49 | kmi = get_hotkey_entry_item(km, name) 50 | if kmi: 51 | row.context_pointer_set("keymap", km) 52 | rna_keymap_ui.draw_kmi([], kc, km, kmi, row, 0) 53 | 54 | else: 55 | row.label(text="No hotkey entry found") 56 | add_keymap(name, 'NONE') 57 | 58 | 59 | def activate_keymap(key): 60 | print("Get keymap") 61 | 62 | 63 | def addon_active_prop(addon_active, addon, row, url='none'): 64 | if addon_active: 65 | row.operator('menu.placeholder', text=addon) 66 | 67 | else: 68 | if url != 'none': 69 | row.operator("wm.url_open", text='Get '+addon, icon="ERROR").url = url 70 | 71 | else: 72 | row.operator('menu.placeholder', text=addon, icon="ERROR") 73 | 74 | 75 | def get_addon_name(): 76 | name = __package__ 77 | return name.split(".")[0] 78 | 79 | 80 | def get_property(target): 81 | addon_name = get_addon_name() 82 | addon = bpy.context.preferences.addons.get(addon_name) 83 | if addon: 84 | prefs = addon.preferences 85 | try: 86 | value = prefs[target] 87 | except: 88 | print("Can't Find Value :", target) 89 | value = False 90 | else: 91 | value = False 92 | 93 | return value 94 | 95 | 96 | def get_keymaps_by_key(): 97 | wm = bpy.context.window_manager 98 | for km in wm.keyconfigs.user.keymaps: 99 | print("Keymap :", km) 100 | for kmi in km: 101 | print("Keymap Item :", kmi) 102 | 103 | 104 | def get_keymap(name): 105 | print("Get keymap") 106 | 107 | 108 | def get_addon_preferences(): 109 | name = get_addon_name() 110 | addon_preferences = bpy.context.preferences.addons[name].preferences 111 | return addon_preferences 112 | 113 | 114 | def get_hotkey_entry_item(km, kmi_name): 115 | for i, km_item in enumerate(km.keymap_items): 116 | if km.keymap_items.keys()[i] == kmi_name: 117 | return km_item 118 | return None 119 | 120 | 121 | # 122 | # Get addon preferences: 123 | # 124 | def get_set_flow_active(): 125 | return set_flow_active 126 | 127 | 128 | def get_f2_active(): 129 | return f2_active 130 | 131 | 132 | def get_loop_tools_active(): 133 | return loop_tools_active 134 | 135 | 136 | def get_qblocker_active(): 137 | return qblocker_active 138 | 139 | 140 | def get_bezierutilities_active(): 141 | return bezierutilities_active 142 | 143 | 144 | def get_textools_active(): 145 | return textools_active 146 | 147 | 148 | def get_ssc_switch_modes(): 149 | prefs = get_addon_preferences() 150 | return prefs.ssc_switch_modes 151 | 152 | 153 | def get_ssc_qblocker_integration(): 154 | prefs = get_addon_preferences() 155 | return prefs.ssc_qblocker_integration 156 | 157 | 158 | def get_ssc_bezierutilities_integration(): 159 | prefs = get_addon_preferences() 160 | return prefs.ssc_bezierutilities_integration 161 | 162 | 163 | def get_enable_sticky_selection(): 164 | prefs = get_addon_preferences() 165 | return prefs.enable_sticky_selection 166 | 167 | 168 | def get_enable_show_faces(): 169 | prefs = get_addon_preferences() 170 | return prefs.enable_show_faces 171 | 172 | 173 | def get_enable_dissolve_verts(): 174 | prefs = get_addon_preferences() 175 | return prefs.enable_dissolve_verts 176 | 177 | 178 | def get_enable_dissolve_faces(): 179 | prefs = get_addon_preferences() 180 | return prefs.enable_dissolve_faces 181 | 182 | 183 | def get_radsym_hide_pivot(): 184 | prefs = get_addon_preferences() 185 | return prefs.radsym_hide_pivot 186 | 187 | 188 | def get_quickhplp_lp_suffix(): 189 | prefs = get_addon_preferences() 190 | return prefs.quickhplp_lp_suffix 191 | 192 | 193 | def get_quickhplp_hp_suffix(): 194 | prefs = get_addon_preferences() 195 | return prefs.quickhplp_hp_suffix 196 | 197 | 198 | def get_enable_wireshaded_cs(): 199 | prefs = get_addon_preferences() 200 | return prefs.enable_wireshaded_cs 201 | 202 | def get_transform_mode_cycle_cyclic(): 203 | prefs = get_addon_preferences() 204 | return prefs.transform_mode_cycle_cyclic 205 | 206 | def get_enable_hotkey_editor(): 207 | prefs = get_addon_preferences() 208 | return prefs.enable_hotkey_editor 209 | 210 | def unregister_keymaps(): 211 | wm = bpy.context.window_manager 212 | kc = wm.keyconfigs.user 213 | 214 | print("Keymaps Disabled") 215 | print(len(addon_keymaps)) 216 | for km, kmi in addon_keymaps: 217 | print(kmi) 218 | 219 | for km, kmi in addon_keymaps: 220 | print(kmi) 221 | kc.keymaps.keymap_items.remove(kmi) 222 | km.keymap_items.remove(kmi) 223 | 224 | addon_keymaps.clear() 225 | 226 | 227 | def get_enable_legacy_origin(): 228 | prefs = get_addon_preferences() 229 | return prefs.enable_legacy_origin 230 | 231 | 232 | def get_enable_legacy_tools(): 233 | prefs = get_addon_preferences() 234 | return prefs.enable_legacy_tools 235 | 236 | # Store keymaps to access after registration 237 | addon_keymaps = [] 238 | 239 | 240 | # Check for integrations: 241 | f2_active = addon_installed("F2") 242 | loop_tools_active = addon_installed("LoopTools") 243 | qblocker_active = addon_installed("QBlocker") 244 | bezierutilities_active = addon_installed("blenderbezierutils") 245 | textools_active = addon_installed("TexTools") 246 | set_flow_active = addon_installed("EdgeFlow") 247 | 248 | 249 | class MenuPlaceholder(bpy.types.Operator): 250 | bl_idname = "menu.placeholder" 251 | bl_label = "" 252 | bl_description = "Please enable this addon manually" 253 | bl_options = {'REGISTER', 'UNDO'} 254 | 255 | def execute(self, context): 256 | print("addon keymaps : ", addon_keymaps) 257 | return {'FINISHED'} 258 | 259 | 260 | class AddonPreferences(AddonPreferences): 261 | bl_idname = get_addon_name() 262 | print("NAME : %s", bl_idname) 263 | 264 | # Properties 265 | cateogries: EnumProperty(name="Categories", 266 | items=[("GENERAL", "General Settings", ""), 267 | ("KEYMAPS", "Keymaps", ""), ], 268 | default="GENERAL") 269 | 270 | ssc_switch_modes: BoolProperty(name="Super Smart Create Switch Modes", 271 | description="Enables Switching to optimal selection mode after certain operations in Super Smart Create", 272 | default=True) 273 | 274 | ssc_qblocker_integration: BoolProperty(name="Super Smart Create QBlocker Integration", 275 | description="Use QBlocker for primitive creation, needs QBlocker to be used", 276 | default=False) 277 | 278 | ssc_bezierutilities_integration: BoolProperty(name="Super Smart Create Bezier Utilities Integration", 279 | description="Use Flexi Bezier Tool for spline creation, needs Beier Utilities to be used", 280 | default=False) 281 | 282 | enable_sticky_selection: BoolProperty(name="Selection Sticky Mode", 283 | description="Enables Sticky Selection when using Quick Select Modes and Selection Cycle", 284 | default=False) 285 | 286 | enable_show_faces: BoolProperty(name="Selection Hilight Faces", 287 | description="Enables Face Hilighting when using Quick Select and Selection Cycle Modes", 288 | default=True) 289 | 290 | enable_dissolve_verts: BoolProperty(name="Smart Delete Dissolve Verts", 291 | description="Verts will be dissolved, if disabled they will be deleted", 292 | default=True) 293 | 294 | enable_dissolve_faces: BoolProperty(name="Smart Delete Dissolve Edges", 295 | description="Non-border edges will be dissolved, if disabled they will be deleted", 296 | default=True) 297 | 298 | radsym_hide_pivot: BoolProperty(name="Hide Radial Symmetry Pivot", 299 | description="Hide Radial Symmetry Pivot on symmetry creation", 300 | default=True) 301 | 302 | quickhplp_lp_suffix: StringProperty(name="Low Poly suffix", 303 | description="Suffix to use for Low Poly Meshes", 304 | default="_low") 305 | 306 | quickhplp_hp_suffix: StringProperty(name="High Poly suffix", 307 | description="Suffix to use for High Poly Meshes", 308 | default="_high") 309 | 310 | enable_wireshaded_cs: BoolProperty(name="Wireframe / Shaded Context Sensitive Mode", 311 | description="Enables context sensitive mode for the Wireframe / Shaded Tool", 312 | default=True) 313 | 314 | transform_mode_cycle_cyclic: BoolProperty(name="Transform Mode Cycle Cyclic", 315 | description="Enables switching to the transform tool right after the Scale Tool", 316 | default=True) 317 | 318 | enable_legacy_origin: BoolProperty(name="Legacy Origin Edit Mode", 319 | description="Enable Legacy Origin Edit Mode", 320 | default=False) 321 | 322 | enable_legacy_tools: BoolProperty(name="Enable Legacy Tools", 323 | description="Enable Legacy Tools that are no longer in active development or supported. Use at own risk", 324 | default=False) 325 | 326 | enable_hotkey_editor: BoolProperty(name="Enable Experimental Hotkey Editor", 327 | description="Enables the hotkey editor in Itools Preference Pannel, this is a experimental feature, enable at own risk", 328 | default=False) 329 | 330 | def draw(self, context): 331 | layout = self.layout 332 | 333 | col = layout.column(align=True) 334 | row = col.row() 335 | 336 | row.prop(self, "cateogries", expand=True) 337 | 338 | 339 | box = col.box() 340 | 341 | if self.cateogries == "GENERAL": 342 | self.draw_general(box) 343 | 344 | elif get_enable_hotkey_editor() and self.cateogries == "KEYMAPS": 345 | self.draw_keymaps(box) 346 | 347 | else: 348 | self.draw_keymaps_disabled(box) 349 | 350 | def draw_general(self, context): 351 | column = context.column() 352 | layout = column 353 | 354 | #Super Smart Create 355 | box = layout.box() 356 | row = box.row(align=True) 357 | row.label(text="Super Smart Create:") 358 | row = box.row(align=True) 359 | 360 | row = box.row(align=True) 361 | row.prop(self, "ssc_switch_modes", toggle=False) 362 | 363 | if qblocker_active: 364 | row = box.row(align=True) 365 | row.prop(self, "ssc_qblocker_integration", toggle=True) 366 | else: 367 | row = box.row(align=True) 368 | row.label(text="Qblocker Integration unavailable, install the addon to enable") 369 | 370 | if bezierutilities_active: 371 | row = box.row(align=True) 372 | row.prop(self, "ssc_bezierutilities_integration", toggle=True) 373 | else: 374 | row = box.row(align=True) 375 | row.label(text="Bezier Utilities Integration unavailable, install the addon to enable") 376 | 377 | #Selection 378 | box = layout.box() 379 | row = box.row(align=True) 380 | row.label(text="Selection:") 381 | row = box.row(align=True) 382 | 383 | row = box.row(align=True) 384 | row.prop(self, "enable_sticky_selection", toggle=False) 385 | 386 | row = box.row(align=True) 387 | row.prop(self, "enable_show_faces", toggle=False) 388 | 389 | #Smart Delete 390 | box = layout.box() 391 | row = box.row(align=True) 392 | row.label(text="Smart Delete:") 393 | row = box.row(align=True) 394 | 395 | row = box.row(align=True) 396 | row.prop(self, "enable_dissolve_verts", toggle=False) 397 | 398 | row = box.row(align=True) 399 | row.prop(self, "enable_dissolve_faces", toggle=False) 400 | 401 | #Quick Lp Hp Name 402 | box = layout.box() 403 | row = box.row(align=True) 404 | row.label(text="Quick Lp Hp Name:") 405 | row = box.row(align=True) 406 | 407 | row = box.row(align=True) 408 | row.prop(self, "quickhplp_lp_suffix", toggle=False) 409 | 410 | row = box.row(align=True) 411 | row.prop(self, "quickhplp_hp_suffix", toggle=False) 412 | 413 | #Other 414 | box = layout.box() 415 | row = box.row(align=True) 416 | row.label(text="Other:") 417 | row = box.row(align=True) 418 | 419 | row = box.row(align=True) 420 | row.prop(self, "radsym_hide_pivot", toggle=False) 421 | 422 | row = box.row(align=True) 423 | row.prop(self, "enable_wireshaded_cs", toggle=False) 424 | 425 | row = box.row(align=True) 426 | row.prop(self, "transform_mode_cycle_cyclic", toggle=False) 427 | 428 | row = box.row(align=True) 429 | row.prop(self, "enable_legacy_tools", toggle=False) 430 | 431 | if bpy.app.version >= (2,82,0): 432 | row = box.row(align=True) 433 | row.prop(self, "enable_legacy_origin", toggle=False) 434 | 435 | row = box.row(align=True) 436 | row.prop(self, "enable_hotkey_editor", toggle=False) 437 | 438 | 439 | # 440 | # Recommended Addons:: 441 | # 442 | box = layout.box() 443 | row = box.row(align=True) 444 | row = box.row(align=True) 445 | row.label(text="Recommended addons:") 446 | 447 | row = box.row(align=True) 448 | addon_active_prop(f2_active, "F2", row) 449 | addon_active_prop(loop_tools_active, "Loop Tools", row) 450 | addon_active_prop(set_flow_active, "Set Flow", row, 451 | url='https://github.com/BenjaminSauder/EdgeFlow') 452 | row = box.row(align=True) 453 | addon_active_prop(qblocker_active, "QBlocker", row, 454 | url='https://gumroad.com/l/gOEV') 455 | addon_active_prop(bezierutilities_active, "Bezier Utilities", row, 456 | url='https://github.com/Shriinivas/blenderbezierutils') 457 | addon_active_prop(bezierutilities_active, "TexTools", row, 458 | url='https://github.com/SavMartin/TexTools-Blender') 459 | 460 | row = box.row(align=True) 461 | row.label(text="To take full advantage of this addon make sure the following addons are enabled.") 462 | 463 | def draw_keymaps_disabled(self, context): 464 | column = context.column() 465 | layout = column 466 | 467 | # Hotkey setup: 468 | wm = bpy.context.window_manager 469 | kc = wm.keyconfigs.user 470 | km = kc.keymaps['3D View Generic'] 471 | 472 | row = layout.row(align=True) 473 | row.label(text="Feature currentlly disabled, to be released in future versions.") 474 | 475 | 476 | def draw_keymaps(self, context): 477 | column = context.column() 478 | layout = column 479 | 480 | # Hotkey setup: 481 | wm = bpy.context.window_manager 482 | kc = wm.keyconfigs.user 483 | km = kc.keymaps['3D View Generic'] 484 | 485 | row = layout.row(align=True) 486 | row.label(text="This pannel is a Beta feature, use at own risk.") 487 | row = layout.row(align=True) 488 | row.label(text="This pannel needs to be opened every time Blender is run for it to enable the set hotkeys") 489 | row = layout.row(align=True) 490 | row.label(text="Dont remove keymaps, disable or modify them instead.") 491 | 492 | 493 | # 494 | # Modes Cycling, double space: 495 | # 496 | row = layout.row(align=True) 497 | row = layout.row(align=True) 498 | row.label(text="Modes Cycling :") 499 | 500 | # Selection Mode Cycle: 501 | row = layout.row(align=True) 502 | add_hotkey_ui('mesh.selection_mode_cycle', km, kc, row) 503 | 504 | # Transform Mode Cycle: 505 | row = layout.row(align=True) 506 | add_hotkey_ui('mesh.transform_mode_cycle', km, kc, row) 507 | 508 | # Transform Orientation Cycle: 509 | row = layout.row(align=True) 510 | add_hotkey_ui('mesh.transform_orientation_cycle', km, kc, row) 511 | 512 | # 513 | # Selection, double space: 514 | # 515 | row = layout.row(align=True) 516 | row = layout.row(align=True) 517 | row.label(text="Selection :") 518 | 519 | # Quick Vert: 520 | row = layout.row(align=True) 521 | add_hotkey_ui('mesh.quick_selection_vert', km, kc, row) 522 | 523 | # Quick Edge: 524 | row = layout.row(align=True) 525 | add_hotkey_ui('mesh.quick_selection_edge', km, kc, row) 526 | 527 | # Quick Face: 528 | row = layout.row(align=True) 529 | add_hotkey_ui('mesh.quick_selection_face', km, kc, row) 530 | 531 | # Smart Select Loop: 532 | row = layout.row(align=True) 533 | add_hotkey_ui('mesh.smart_select_loop', km, kc, row) 534 | 535 | # Smart Select Ring: 536 | row = layout.row(align=True) 537 | add_hotkey_ui('mesh.smart_select_ring', km, kc, row) 538 | 539 | # 540 | # Smart Tools, double space: 541 | # 542 | row = layout.row(align=True) 543 | row = layout.row(align=True) 544 | row.label(text="Smart Tools :") 545 | 546 | # Super Smart Create Hotkey: 547 | row = layout.row(align=True) 548 | add_hotkey_ui('mesh.super_smart_create', km, kc, row) 549 | 550 | # Smart Delete Hotkey: 551 | row = layout.row(align=True) 552 | add_hotkey_ui('mesh.smart_delete', km, kc, row) 553 | 554 | # Smart Extrude: 555 | row = layout.row(align=True) 556 | add_hotkey_ui('mesh.smart_extrude_modal', km, kc, row) 557 | 558 | # Smart Extrude: 559 | row = layout.row(align=True) 560 | add_hotkey_ui('mesh.smart_modify', km, kc, row) 561 | 562 | # Smart Translate: 563 | row = layout.row(align=True) 564 | add_hotkey_ui('mesh.smart_translate_modal', km, kc, row) 565 | 566 | # 567 | # Utilities, double space: 568 | # 569 | row = layout.row(align=True) 570 | row = layout.row(align=True) 571 | row.label(text="Utilities :") 572 | 573 | # Quick Origin 574 | row = layout.row(align=True) 575 | add_hotkey_ui('mesh.quick_pivot', km, kc, row) 576 | 577 | # Edit Origin 578 | row = layout.row(align=True) 579 | add_hotkey_ui('mesh.simple_edit_pivot', km, kc, row) 580 | 581 | # Quick Transform Orientation 582 | row = layout.row(align=True) 583 | add_hotkey_ui('mesh.transform_options_pie', km, kc, row) 584 | 585 | # Quick Align 586 | row = layout.row(align=True) 587 | add_hotkey_ui('mesh.quick_align', km, kc, row) 588 | 589 | # Quick Lattice 590 | row = layout.row(align=True) 591 | add_hotkey_ui('mesh.quick_lattice', km, kc, row) 592 | 593 | # Rebase Cylinder 594 | row = layout.row(align=True) 595 | add_hotkey_ui('mesh.rebase_cylinder', km, kc, row) 596 | 597 | # Radial Symmetry 598 | row = layout.row(align=True) 599 | add_hotkey_ui('mesh.radial_symmetry', km, kc, row) 600 | 601 | # CS Slide 602 | row = layout.row(align=True) 603 | add_hotkey_ui('mesh.context_sensitive_slide', km, kc, row) 604 | 605 | # CS Bevel 606 | row = layout.row(align=True) 607 | add_hotkey_ui('mesh.context_sensitive_bevel', km, kc, row) 608 | 609 | # 610 | # Toggles, double space: 611 | # 612 | row = layout.row(align=True) 613 | row = layout.row(align=True) 614 | row.label(text="Toggles :") 615 | 616 | # Modifiers On/Off 617 | row = layout.row(align=True) 618 | add_hotkey_ui('mesh.modifier_toggle', km, kc, row) 619 | 620 | # Target Weld On/Off 621 | row = layout.row(align=True) 622 | add_hotkey_ui('mesh.target_weld_toggle', km, kc, row) 623 | 624 | # Wireframe On/Off 625 | row = layout.row(align=True) 626 | add_hotkey_ui('mesh.wire_toggle', km, kc, row) 627 | 628 | # Wireframe / Shaded Toggle 629 | row = layout.row(align=True) 630 | add_hotkey_ui('mesh.wire_shaded_toggle', km, kc, row) 631 | 632 | # 633 | # UV Utilities, double space: 634 | # 635 | row = layout.row(align=True) 636 | row = layout.row(align=True) 637 | row.label(text="UV Utilities :") 638 | 639 | # Rotate 90 + 640 | row = layout.row(align=True) 641 | add_hotkey_ui('uv.rotate_90_pos', km, kc, row) 642 | 643 | # Rotate 90 - 644 | row = layout.row(align=True) 645 | add_hotkey_ui('uv.rotate_90_neg', km, kc, row) 646 | 647 | # Seams From Sharps 648 | row = layout.row(align=True) 649 | add_hotkey_ui('uv.seams_from_sharps', km, kc, row) 650 | 651 | # UVs From Sharps 652 | row = layout.row(align=True) 653 | add_hotkey_ui('uv.uvs_from_sharps', km, kc, row) 654 | 655 | def draw_misc(self, context): 656 | layout = self.layout 657 | layout.label(text="This is still work in progress") 658 | layout.prop(self, "boolean") 659 | 660 | 661 | class OBJECT_OT_addon_prefs_example(Operator): 662 | """Display example preferences""" 663 | bl_idname = "object.addon_prefs_example" 664 | bl_label = "Add-on Preferences Example" 665 | bl_options = {'REGISTER', 'UNDO'} 666 | 667 | def execute(self, context): 668 | preferences = context.preferences 669 | addon_prefs = preferences.addons[__name__].preferences 670 | 671 | info = ("Path: %s, Number: %d, Boolean %r" % 672 | (addon_prefs.filepath, addon_prefs.number, addon_prefs.boolean)) 673 | 674 | self.report({'INFO'}, info) 675 | print(info) 676 | 677 | return {'FINISHED'} 678 | --------------------------------------------------------------------------------