├── .gitignore ├── Addons └── PolyQuilt │ ├── QMesh │ ├── ElementItem.py │ ├── QMesh.py │ ├── QMeshHighlight.py │ ├── QMeshOperators.py │ ├── QSnap.py │ └── __init__.py │ ├── __init__.py │ ├── gizmo_preselect.py │ ├── icons │ ├── addon.poly_quilt_brush_icon.dat │ ├── addon.poly_quilt_delete_icon.dat │ ├── addon.poly_quilt_extrude_icon.dat │ ├── addon.poly_quilt_frame_icon.dat │ ├── addon.poly_quilt_icon.dat │ ├── addon.poly_quilt_knife_icon.dat │ ├── addon.poly_quilt_loopcut_icon.dat │ ├── addon.poly_quilt_poly_icon.dat │ ├── addon.poly_quilt_seam_icon.dat │ ├── icon_brush_delete.png │ ├── icon_brush_move.png │ ├── icon_brush_relax.png │ ├── icon_geom_edge.png │ ├── icon_geom_polygon.png │ ├── icon_geom_quad.png │ ├── icon_geom_triangle.png │ ├── icon_geom_vert.png │ ├── icon_move_free.png │ ├── icon_move_normal.png │ ├── icon_move_tangent.png │ ├── icon_move_x.png │ ├── icon_move_y.png │ ├── icon_move_z.png │ ├── icon_opt_backcull.png │ ├── icon_opt_mirror.png │ └── icon_opt_x0.png │ ├── pq_icon.py │ ├── pq_keymap_editor.py │ ├── pq_operator.py │ ├── pq_operator_add_empty_object.py │ ├── pq_preferences.py │ ├── pq_tool.py │ ├── pq_tool_ui.py │ ├── subtools │ ├── __init__.py │ ├── maintool_brush.py │ ├── maintool_default.py │ ├── maintool_delete.py │ ├── maintool_edgeloop_dissolve.py │ ├── maintool_extrude.py │ ├── maintool_hold.py │ ├── maintool_knife.py │ ├── maintool_loopcut.py │ ├── maintool_lowpoly.py │ ├── subtool.py │ ├── subtool_autoquad.py │ ├── subtool_brush_delete.py │ ├── subtool_brush_move.py │ ├── subtool_brush_relax.py │ ├── subtool_brush_size.py │ ├── subtool_delete.py │ ├── subtool_edge_extrude.py │ ├── subtool_edge_extrude_multi.py │ ├── subtool_edge_slice.py │ ├── subtool_edge_slide.py │ ├── subtool_edgeloop_cut.py │ ├── subtool_edgeloop_extrude.py │ ├── subtool_fin_slice.py │ ├── subtool_knife.py │ ├── subtool_makepoly.py │ ├── subtool_move.py │ ├── subtool_polypen.py │ ├── subtool_seam.py │ ├── subtool_seam_loop.py │ └── subtool_vert_extrude.py │ ├── translation.py │ └── utils │ ├── __init__.py │ ├── addon_updater.py │ ├── dpi.py │ ├── draw_util.py │ ├── mouse_event_util.py │ └── pqutil.py ├── Docs └── en │ ├── docs │ ├── download.md │ ├── index.md │ └── manual │ │ ├── Brush.md │ │ ├── facecut.md │ │ ├── geometry.md │ │ ├── insert_loop.md │ │ ├── knife.md │ │ ├── move.md │ │ ├── operation.md │ │ ├── remove.md │ │ └── remove_loop.md │ └── mkdocs.yml ├── README.md ├── Resources ├── blender_icons_geom.py ├── icon_geom.blend └── makeicon.bat ├── _config.yml └── mkpackage.py /.gitignore: -------------------------------------------------------------------------------- 1 | /Addons/PolyQuilt/QMesh/__pycache__ 2 | /Addons/PolyQuilt/subtools/__pycache__ 3 | /Addons/PolyQuilt/utils/__pycache__ 4 | /Addons/PolyQuilt/__pycache__ 5 | /Addons/PolyQuilt/.vscode 6 | /Docs/en/site 7 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/QMesh/QMesh.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import bmesh 16 | import math 17 | import copy 18 | import mathutils 19 | import bpy_extras 20 | import collections 21 | from mathutils import * 22 | from .QSnap import QSnap 23 | import numpy as np 24 | from ..utils import pqutil 25 | from ..utils import draw_util 26 | from ..utils.dpi import * 27 | from .ElementItem import ElementItem 28 | from .QMeshOperators import QMeshOperators 29 | from .QMeshHighlight import QMeshHighlight 30 | 31 | __all__ = ['QMesh','SelectStack'] 32 | 33 | class QMesh(QMeshOperators) : 34 | 35 | def __init__(self , obj , preferences) : 36 | super().__init__(obj, preferences) 37 | self.highlight = QMeshHighlight(self) 38 | self.invalid = False 39 | 40 | def UpdateMesh( self , updateHighLight = True ) : 41 | super().UpdateMesh() 42 | if updateHighLight : 43 | self.highlight.setDirty() 44 | 45 | def CheckValid( self , context ) : 46 | val = super()._CheckValid(context) 47 | if val == False or self.invalid : 48 | self.highlight.setDirty() 49 | self.reload_obj(context) 50 | self.invalid = False 51 | return val 52 | 53 | def UpdateView( self ,context , forced = False ): 54 | self.highlight.UpdateView(context) 55 | 56 | def PickElement( self , coord , radius : float , ignore = [] , edgering = False , backface_culling = None , elements = ['FACE','EDGE','VERT'] ) -> ElementItem : 57 | if backface_culling == None : 58 | backface_culling = self.get_shading(bpy.context).show_backface_culling 59 | rv3d = bpy.context.region_data 60 | matrix = rv3d.perspective_matrix 61 | radius = radius * dpm() 62 | 63 | hitElement = ElementItem.Empty() 64 | 65 | ignoreFaces = [ i for i in ignore if isinstance( i , bmesh.types.BMFace ) ] 66 | 67 | # Hitする頂点を探す 68 | hitVert = ElementItem.Empty() 69 | if 'VERT' in elements : 70 | ignoreVerts = [ i for i in ignore if isinstance( i , bmesh.types.BMVert ) ] 71 | candidateVerts = self.highlight.CollectVerts( coord , radius , ignoreVerts , edgering , backface_culling = backface_culling ) 72 | for vert in candidateVerts : 73 | # 各点からRayを飛ばす 74 | if QSnap.is_target( vert.hitPosition ) : 75 | hitTemp = self.highlight.PickFace( vert.coord , ignoreFaces , backface_culling = False ) 76 | if hitTemp.isEmpty : 77 | # 何の面にもヒットしないなら採択 78 | hitVert = vert 79 | break 80 | else : 81 | if vert.element in hitTemp.element.verts : 82 | # ヒットした面に含まれているなら採択 83 | hitVert = vert 84 | break 85 | else : 86 | # ヒットしたポイントより後ろなら採択 87 | v1 = matrix @ vert.hitPosition 88 | v2 = matrix @ hitTemp.hitPosition 89 | if v1.z <= v2.z : 90 | hitVert = vert 91 | break 92 | 93 | # Todo:ヒットするエッジを探す 94 | hitEdge = ElementItem.Empty() 95 | if 'EDGE' in elements : 96 | ignoreEdges = [ i for i in ignore if isinstance( i , bmesh.types.BMEdge ) ] 97 | candidateEdges = self.highlight.CollectEdge( coord , radius , ignoreEdges , backface_culling = backface_culling , edgering= edgering ) 98 | 99 | for edge in candidateEdges : 100 | if QSnap.is_target( edge.hitPosition ) : 101 | hitTemp = self.highlight.PickFace( edge.coord , ignoreFaces , backface_culling = False ) 102 | 103 | if hitTemp.isEmpty : 104 | hitEdge = edge 105 | break 106 | else: 107 | if edge.element in hitTemp.element.edges : 108 | # ヒットした面に含まれているなら採択 109 | hitEdge = edge 110 | break 111 | else : 112 | # ヒットしたポイントより後ろなら採択 113 | v1 = matrix @ edge.hitPosition 114 | v2 = matrix @ hitTemp.hitPosition 115 | if v1.z <= v2.z : 116 | hitEdge = edge 117 | break 118 | 119 | 120 | if hitVert.isEmpty and hitEdge.isEmpty : 121 | if 'FACE' in elements : 122 | # hitする面を探す 123 | hitFace = self.highlight.PickFace( coord , ignoreFaces , backface_culling = backface_culling ) 124 | # 候補頂点/エッジがないなら面を返す 125 | if hitFace.isNotEmpty : 126 | if QSnap.is_target( hitFace.hitPosition ) : 127 | hitElement = hitFace 128 | elif hitVert.isNotEmpty and hitEdge.isNotEmpty : 129 | if hitVert.element in hitEdge.element.verts : 130 | return hitVert 131 | v1 = matrix @ hitVert.hitPosition.to_4d() 132 | v2 = matrix @ hitEdge.hitPosition.to_4d() 133 | if v1.z <= v2.z : 134 | hitElement = hitVert 135 | else : 136 | hitElement = hitEdge 137 | elif hitVert.isNotEmpty : 138 | hitElement = hitVert 139 | elif hitEdge.isNotEmpty : 140 | hitElement = hitEdge 141 | 142 | return hitElement 143 | 144 | 145 | class SelectStack : 146 | def __init__(self, context , bm) : 147 | self.context = context 148 | self.bm = bm 149 | self.mesh_select_mode = context.tool_settings.mesh_select_mode[0:3] 150 | 151 | def push( self ) : 152 | self.mesh_select_mode = self.context.tool_settings.mesh_select_mode[0:3] 153 | self.vert_selection = [ v.select for v in self.bm.verts ] 154 | self.face_selection = [ f.select for f in self.bm.faces ] 155 | self.edge_selection = [ e.select for e in self.bm.edges ] 156 | self.select_history = self.bm.select_history[:] 157 | self.mesh_select_mode = self.context.tool_settings.mesh_select_mode[0:3] 158 | 159 | def select_mode( self , vert , edge , face ) : 160 | self.context.tool_settings.mesh_select_mode = (vert , edge , face) 161 | 162 | 163 | def pop( self ) : 164 | for select , v in zip( self.vert_selection , self.bm.verts ) : 165 | v.select = select 166 | for select , f in zip( self.face_selection , self.bm.faces ) : 167 | f.select = select 168 | for select , e in zip( self.edge_selection , self.bm.edges ) : 169 | e.select = select 170 | 171 | self.bm.select_history = self.select_history 172 | 173 | del self.vert_selection 174 | del self.face_selection 175 | del self.edge_selection 176 | 177 | self.context.tool_settings.mesh_select_mode = self.mesh_select_mode 178 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/QMesh/QSnap.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import bmesh 16 | import math 17 | import copy 18 | import mathutils 19 | import bpy_extras 20 | import collections 21 | from mathutils import * 22 | from .QMeshOperators import * 23 | from ..utils import pqutil 24 | 25 | class QSnap : 26 | instance = None 27 | ref = 0 28 | 29 | @classmethod 30 | def add_ref( cls , context ) : 31 | if cls.ref == 0 : 32 | cls.instance = cls(context) 33 | cls.update(context) 34 | cls.ref = cls.ref + 1 35 | 36 | @classmethod 37 | def remove_ref( cls ) : 38 | cls.ref = cls.ref - 1 39 | if cls.ref == 0 : 40 | if cls.instance : 41 | del cls.instance 42 | cls.instance = None 43 | 44 | @classmethod 45 | def is_active( cls ) : 46 | return cls.instance != None 47 | 48 | @classmethod 49 | def update(cls,context) : 50 | if cls.instance : 51 | cls.instance.__update(context) 52 | 53 | def __init__( self , context, snap_objects = 'Visible' ) : 54 | self.objects_array = None 55 | self.bvh_list = None 56 | 57 | def __update( self , context ) : 58 | if context.scene.tool_settings.use_snap \ 59 | and 'FACE' in context.scene.tool_settings.snap_elements : 60 | if self.bvh_list == None : 61 | self.create_tree(context) 62 | else : 63 | if set( self.bvh_list.keys() ) != set(self.snap_objects(context)) : 64 | self.remove_tree() 65 | self.create_tree(context) 66 | else : 67 | if self.bvh_list != None : 68 | self.remove_tree() 69 | 70 | @staticmethod 71 | def snap_objects( context ) : 72 | active_obj = context.active_object 73 | objects = context.visible_objects 74 | # objects = context.selected_objects 75 | objects_array = [obj for obj in objects if obj != active_obj and obj.type == 'MESH'] 76 | return objects_array 77 | 78 | def create_tree( self , context ) : 79 | if self.bvh_list == None : 80 | self.bvh_list = {} 81 | for obj in self.snap_objects(context): 82 | bvh = mathutils.bvhtree.BVHTree.FromObject(obj, context.evaluated_depsgraph_get() , epsilon = 0.0 ) 83 | self.bvh_list[obj] = bvh 84 | 85 | def remove_tree( self ) : 86 | if self.bvh_list != None : 87 | for bvh in self.bvh_list.values(): 88 | del bvh 89 | self.bvh_list = None 90 | 91 | 92 | @classmethod 93 | def view_adjust( cls , world_pos : mathutils.Vector ) -> mathutils.Vector : 94 | if cls.instance != None : 95 | ray = pqutil.Ray.from_world_to_screen( bpy.context , world_pos ) 96 | if ray == None : 97 | return world_pos 98 | location , norm , obj = cls.instance.__raycast( ray ) 99 | if location != None : 100 | return location 101 | return world_pos 102 | 103 | @classmethod 104 | def adjust_point( cls , world_pos : mathutils.Vector , is_fix_to_x_zero = False) : 105 | if cls.instance != None : 106 | location , norm , index = cls.instance.__find_nearest( world_pos ) 107 | if is_fix_to_x_zero and QMeshOperators.is_x_zero_pos(location) : 108 | location.x = 0 109 | return location 110 | return world_pos 111 | 112 | @classmethod 113 | def adjust_local( cls , matrix_world : mathutils.Matrix , local_pos : mathutils.Vector , is_fix_to_x_zero ) : 114 | if cls.instance != None : 115 | location , norm , index = cls.instance.__find_nearest( matrix_world @ local_pos ) 116 | lp = matrix_world.inverted() @ location 117 | if is_fix_to_x_zero and QMeshOperators.is_x_zero_pos(local_pos) : 118 | lp.x = 0 119 | return lp 120 | return local_pos 121 | 122 | @classmethod 123 | def adjust_local_to_world( cls , matrix_world : mathutils.Matrix , local_pos : mathutils.Vector , is_fix_to_x_zero ) : 124 | if cls.instance != None : 125 | location , norm , index = cls.instance.__find_nearest( matrix_world @ local_pos ) 126 | lp = location 127 | if is_fix_to_x_zero and QMeshOperators.is_x_zero_pos(local_pos) : 128 | lp.x = 0 129 | return lp 130 | return local_pos 131 | 132 | 133 | @classmethod 134 | def adjust_verts( cls , obj , verts , is_fix_to_x_zero ) : 135 | if cls.instance != None and cls.instance.bvh_list : 136 | dist = bpy.context.scene.tool_settings.double_threshold 137 | find_nearest = cls.instance.__find_nearest 138 | matrix = obj.matrix_world 139 | for vert in verts : 140 | location , norm , index = find_nearest( matrix @ vert.co ) 141 | if location != None : 142 | lp = obj.matrix_world.inverted() @ location 143 | if is_fix_to_x_zero and QMeshOperators.is_x_zero_pos(vert.co) : 144 | lp.x = 0 145 | vert.co = lp 146 | 147 | @classmethod 148 | def is_target( cls , world_pos : mathutils.Vector) -> bool : 149 | dist = bpy.context.scene.tool_settings.double_threshold 150 | if cls.instance != None : 151 | ray = pqutil.Ray.from_world_to_screen( bpy.context , world_pos ) 152 | if ray == None : 153 | return False 154 | hit , normal , face = cls.instance.__raycast( ray ) 155 | if hit != None : 156 | v2h = (ray.origin - hit).length 157 | v2w = (ray.origin - world_pos).length 158 | 159 | if abs(v2h - v2w) <= dist : 160 | return True 161 | else : 162 | ray2 = pqutil.Ray( hit + ray.vector * dist , ray.vector ) 163 | hit2 , normal2 , face2 = cls.instance.__raycast( ray2 ) 164 | if not hit2 : 165 | return False 166 | h2h = ( ray2.origin - hit2 ).length 167 | w2h0 = ( ray2.origin - world_pos ).length 168 | w2h1 = ( world_pos - hit2 ).length 169 | if w2h0 < h2h : 170 | if w2h0 < w2h1 : 171 | return True 172 | return False 173 | return True 174 | 175 | def __raycast( self , ray : pqutil.Ray ) : 176 | min_dist = math.inf 177 | location = None 178 | normal = None 179 | index = None 180 | if self.bvh_list : 181 | for obj , bvh in self.bvh_list.items(): 182 | local_ray = ray.world_to_object( obj ) 183 | hit = bvh.ray_cast( local_ray.origin , local_ray.vector ) 184 | if None not in hit : 185 | if hit[3] < min_dist : 186 | matrix = obj.matrix_world 187 | location = pqutil.transform_position( hit[0] , matrix ) 188 | normal = pqutil.transform_normal( hit[1] , matrix ) 189 | index = hit[2] + obj.pass_index * 10000000 190 | min_dist = hit[3] 191 | 192 | return location , normal , index 193 | 194 | def __smart_find( self , ray : pqutil.Ray ) : 195 | location_i , normal_i , obj_i = self.__raycast_double( ray ) 196 | if location_i == None : 197 | a,b,c = self.__find_nearest( ray.origin ) 198 | return a,b,c 199 | location_r , normal_r , obj_r = self.__find_nearest( ray.origin ) 200 | if location_r == None : 201 | return location_i , normal_i , obj_i 202 | if (location_r - ray.origin).length <= (location_i - ray.origin).length : 203 | return location_r , normal_r , obj_r 204 | else : 205 | return location_i , normal_i , obj_i 206 | 207 | def __raycast_double( self , ray : pqutil.Ray ) : 208 | # ターゲットからビュー方向にレイを飛ばす 209 | location_r , normal_r , face_r = self.__raycast( ray ) 210 | location_i , normal_i , face_i = self.__raycast( ray.invert ) 211 | 212 | if None in [face_i,face_r] : 213 | if face_i != None : 214 | return location_i , normal_i , face_i 215 | elif face_r != None : 216 | return location_r , normal_r , face_r 217 | else : 218 | if (location_r - ray.origin).length <= (location_i - ray.origin).length : 219 | return location_r , normal_r , face_r 220 | else : 221 | return location_i , normal_i , face_i 222 | return None , None , None 223 | 224 | def __find_nearest( self, pos : mathutils.Vector ) : 225 | min_dist = math.inf 226 | location = pos 227 | normal = None 228 | index = None 229 | hits = [] 230 | if self.bvh_list : 231 | for obj , bvh in self.bvh_list.items(): 232 | lp = obj.matrix_world.inverted() @ pos 233 | hit = bvh.find_nearest( lp ) 234 | if None not in hit : 235 | wp = pqutil.transform_position( hit[0] , obj.matrix_world ) 236 | dist = ( pos - wp ).length 237 | if min_dist > dist : 238 | min_dist = dist 239 | location = wp 240 | normal = pqutil.transform_normal( hit[1] , obj.matrix_world ) 241 | index = hit[2] + obj.pass_index * 10000000 242 | 243 | return location , normal , index 244 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/QMesh/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # This program is free software; you can redistribute it and/or modify 3 | # it under the terms of the GNU General Public License as published by 4 | # the Free Software Foundation; either version 3 of the License, or 5 | # (at your option) any later version. 6 | # 7 | # This program is distributed in the hope that it will be useful, but 8 | # WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program. If not, see . 14 | 15 | from .QMesh import QMesh , SelectStack 16 | from .QSnap import QSnap 17 | from .ElementItem import ElementItem 18 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/__init__.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | bl_info = { 15 | "name" : "PolyQuilt", 16 | "author" : "Sakana3", 17 | "version": (1, 3, 1), 18 | "blender" : (2, 83, 0), 19 | "location": "View3D > Mesh > PolyQuilt", 20 | "description": "Lowpoly Tool", 21 | "warning" : "", 22 | "wiki_url": "", 23 | "category": "Mesh", 24 | } 25 | 26 | import bpy 27 | from bpy.utils.toolsystem import ToolDef 28 | from .pq_operator import * 29 | from .pq_operator_add_empty_object import * 30 | from .pq_icon import * 31 | from .pq_tool import PolyQuiltTools 32 | from .pq_tool_ui import VIEW3D_PT_tools_polyquilt_options 33 | from .pq_keymap_editor import PQ_OT_DirtyKeymap 34 | from .gizmo_preselect import * 35 | from .pq_preferences import * 36 | from .translation import pq_translation_dict 37 | 38 | classes = ( 39 | MESH_OT_poly_quilt , 40 | MESH_OT_poly_quilt_brush_size , 41 | MESH_OT_poly_quilt_daemon , 42 | PQ_OT_SetupUnityLikeKeymap , 43 | PolyQuiltPreferences , 44 | PQ_OT_CheckAddonUpdate , 45 | PQ_OT_UpdateAddon , 46 | VIEW3D_PT_tools_polyquilt_options , 47 | PQ_OT_DirtyKeymap , 48 | ) + gizmo_preselect.all_gizmos 49 | 50 | 51 | def register(): 52 | bpy.app.translations.register(__name__, pq_translation_dict) 53 | register_icons() 54 | register_updater(bl_info) 55 | 56 | # 空メッシュ追加 57 | bpy.utils.register_class(pq_operator_add_empty_object.OBJECT_OT_add_object) 58 | bpy.utils.register_manual_map(pq_operator_add_empty_object.add_object_manual_map) 59 | bpy.types.VIEW3D_MT_mesh_add.append(pq_operator_add_empty_object.add_object_button) 60 | 61 | for cls in classes: 62 | bpy.utils.register_class(cls) 63 | 64 | for tool in PolyQuiltTools : 65 | bpy.utils.register_tool(tool['tool'] , after = tool['after'] , group = tool['group'] ) 66 | 67 | def unregister(): 68 | for tool in PolyQuiltTools : 69 | bpy.utils.unregister_tool(tool['tool']) 70 | 71 | for cls in reversed(classes): 72 | bpy.utils.unregister_class(cls) 73 | 74 | bpy.utils.unregister_class(pq_operator_add_empty_object.OBJECT_OT_add_object) 75 | bpy.utils.unregister_manual_map(pq_operator_add_empty_object.add_object_manual_map) 76 | bpy.types.VIEW3D_MT_mesh_add.remove(pq_operator_add_empty_object.add_object_button) 77 | 78 | unregister_icons() 79 | bpy.app.translations.unregister(__name__) 80 | 81 | if __name__ == "__main__": 82 | register() 83 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/gizmo_preselect.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import mathutils 16 | import time 17 | from .QMesh import * 18 | from .utils import draw_util 19 | from .subtools import * 20 | from .pq_tool import * 21 | 22 | 23 | 24 | class PQ_Gizmo_Preselect( bpy.types.Gizmo): 25 | bl_idname = "MESH_GT_PQ_Preselect" 26 | 27 | def __init__(self) : 28 | self.bmo = None 29 | self.currentElement = None 30 | self.preferences = bpy.context.preferences.addons[__package__].preferences 31 | self.DrawHighlight = None 32 | self.region = None 33 | self.subtool = None 34 | self.tool_table = [None,None,None,None] 35 | self.tool = None 36 | 37 | def __del__(self) : 38 | pass 39 | 40 | def setup(self): 41 | self.bmo = None 42 | self.currentElement = ElementItem.Empty() 43 | 44 | def init( self , context , tool ) : 45 | self.tool = tool 46 | self.maintool = maintools[tool.pq_main_tool] 47 | self.subtool = self.maintool 48 | self.region = context.region_data 49 | self.bmo = QMesh( context.active_object , self.preferences ) 50 | self.keyitem = None 51 | if self.subtool : 52 | context.window.cursor_set( self.subtool.GetCursor() ) 53 | 54 | def exit( self , context, cancel) : 55 | pass 56 | 57 | def test_select(self, context, location): 58 | if PQ_GizmoGroup_Base.running_polyquilt : 59 | self.DrawHighlight = None 60 | return -1 61 | 62 | if self.currentElement == None : 63 | self.currentElement = ElementItem.Empty() 64 | 65 | self.mouse_pos = mathutils.Vector(location) 66 | if context.region == self.region : 67 | return -1 68 | if self.bmo == None : 69 | self.bmo = QMesh( context.active_object , self.preferences ) 70 | self.bmo.CheckValid( context ) 71 | self.bmo.UpdateView(context) 72 | QSnap.update(context) 73 | 74 | if self.subtool != None : 75 | element = self.subtool.pick_element( self.bmo , location , self.preferences ) 76 | element.set_snap_div( self.preferences.loopcut_division ) 77 | if self.subtool.UpdateHighlight( self , element ) : 78 | context.area.tag_redraw() 79 | else : 80 | element = ElementItem.Empty() 81 | 82 | self.currentElement = element 83 | 84 | if self.subtool != None : 85 | self.DrawHighlight = self.subtool.DrawHighlight( self , self.currentElement ) 86 | else : 87 | self.DrawHighlight = None 88 | 89 | return -1 90 | 91 | def draw(self, context): 92 | if PQ_GizmoGroup_Base.running_polyquilt : 93 | self.DrawHighlight = None 94 | 95 | if self.DrawHighlight != None : 96 | self.DrawHighlight() 97 | 98 | def refresh( self , context ) : 99 | if self.bmo != None : 100 | self.bmo.invalid = True 101 | self.currentElement = ElementItem.Empty() 102 | self.DrawHighlight = None 103 | 104 | def recive_event( self , context , event ) : 105 | subtool = self.maintool 106 | 107 | self.keyitem = self.get_keyitem( event.shift , event.ctrl , event.alt, event.oskey ) 108 | if self.keyitem and hasattr( self.keyitem.properties , "tool_mode" ) : 109 | subtool = maintools[ self.keyitem.properties.tool_mode ] 110 | 111 | if self.subtool != subtool : 112 | self.subtool = subtool 113 | if context.area : 114 | context.area.tag_redraw() 115 | if self.subtool : 116 | PQ_GizmoGroup_Base.set_cursor( subtool.GetCursor() ) 117 | else : 118 | PQ_GizmoGroup_Base.set_cursor( ) 119 | 120 | if context.region_data == self.region and self.subtool: 121 | self.subtool.recive_event( self , context , event ) 122 | 123 | def get_keyitem( self , shift , ctrl , alt, oskey ) : 124 | keymap = bpy.context.window_manager.keyconfigs.user.keymaps["3D View Tool: Edit Mesh, " + self.tool.bl_label] 125 | keyitems = [ item for item in keymap.keymap_items if item.idname == 'mesh.poly_quilt' ] 126 | for item in keymap.keymap_items : 127 | if item.idname == 'mesh.poly_quilt' and item.active : 128 | if [ item.shift , item.ctrl , item.alt, item.oskey ] == [ shift , ctrl , alt, oskey ] : 129 | if item.active : 130 | return item 131 | return None 132 | 133 | def get_attr( self , attr ) : 134 | if self.keyitem : 135 | if self.keyitem.properties.is_property_set(attr) : 136 | return getattr( self.keyitem.properties , attr ) 137 | 138 | for tool in bpy.context.workspace.tools : 139 | if "mesh_tool.poly_quilt" in tool.idname : 140 | props = tool.operator_properties("mesh.poly_quilt") 141 | return getattr( props , attr ) 142 | 143 | return None 144 | 145 | class PQ_GizmoGroup_Base(bpy.types.GizmoGroup): 146 | my_tool = ToolPolyQuiltBase 147 | bl_idname = "MESH_GGT_PQ_Preselect" 148 | bl_label = "PolyQuilt Preselect Gizmo" 149 | bl_options = {'3D'} 150 | bl_region_type = 'WINDOW' 151 | bl_space_type = 'VIEW_3D' 152 | bl_idname = my_tool.bl_widget 153 | child_gizmos = [] 154 | cursor = 'DEFAULT' 155 | 156 | running_polyquilt = False 157 | 158 | def __init__(self) : 159 | self.gizmo = None 160 | 161 | def __del__(self) : 162 | if hasattr( self , "gizmo" ) : 163 | PQ_GizmoGroup_Base.child_gizmos.remove( self.gizmo ) 164 | if not PQ_GizmoGroup_Base.child_gizmos : 165 | QSnap.remove_ref() 166 | 167 | @classmethod 168 | def poll(cls, context): 169 | if context.mode != 'EDIT_MESH' : 170 | return False 171 | # 自分を使っているツールを探す。 172 | workspace = context.workspace 173 | for tool in workspace.tools: 174 | if tool.widget == cls.bl_idname: 175 | break 176 | else: 177 | context.window_manager.gizmo_group_type_unlink_delayed(cls.bl_idname) 178 | return False 179 | if not PQ_GizmoGroup_Base.running_polyquilt : 180 | context.window.cursor_set( cls.cursor ) 181 | return True 182 | 183 | def setup(self, context): 184 | QSnap.add_ref(context) 185 | self.gizmo = self.gizmos.new(PQ_Gizmo_Preselect.bl_idname) 186 | self.gizmo.init(context , self.my_tool ) 187 | PQ_GizmoGroup_Base.child_gizmos.append(self.gizmo) 188 | 189 | def refresh( self , context ) : 190 | if hasattr( self , "gizmo" ) : 191 | self.gizmo.refresh(context) 192 | 193 | @classmethod 194 | def set_cursor(cls, cursor = 'DEFAULT' ): 195 | cls.cursor = cursor 196 | 197 | @classmethod 198 | def get_gizmo(cls, region ): 199 | gizmo = [ i for i in cls.child_gizmos if i.region == region ] 200 | if gizmo : 201 | return gizmo[0] 202 | return None 203 | 204 | @classmethod 205 | def recive_event( cls , context , event ) : 206 | for gizmo in cls.child_gizmos : 207 | gizmo.recive_event( context , event) 208 | 209 | @classmethod 210 | def depsgraph_update_post( cls , scene ) : 211 | for gizmo in cls.child_gizmos : 212 | gizmo.refresh( bpy.context ) 213 | 214 | 215 | class PQ_GizmoGroup_Preselect(PQ_GizmoGroup_Base): 216 | my_tool = ToolPolyQuilt 217 | bl_idname = my_tool.bl_widget 218 | bl_label = "PolyQuilt Preselect Gizmo" 219 | 220 | class PQ_GizmoGroup_Lowpoly(PQ_GizmoGroup_Base): 221 | my_tool = ToolPolyQuiltPoly 222 | bl_idname = my_tool.bl_widget 223 | bl_label = "PolyQuilt Lowpoly Gizmo" 224 | 225 | class PQ_GizmoGroup_Knife(PQ_GizmoGroup_Base): 226 | my_tool = ToolPolyQuiltKnife 227 | bl_idname = my_tool.bl_widget 228 | bl_label = "PolyQuilt Knife Gizmo" 229 | 230 | class PQ_GizmoGroup_Delete(PQ_GizmoGroup_Base): 231 | my_tool = ToolPolyQuiltDelete 232 | bl_idname = my_tool.bl_widget 233 | bl_label = "PolyQuilt Delete Gizmo" 234 | 235 | class PQ_GizmoGroup_Extrude(PQ_GizmoGroup_Base): 236 | my_tool = ToolPolyQuiltExtrude 237 | bl_idname = my_tool.bl_widget 238 | bl_label = "PolyQuilt Extrude Gizmo" 239 | 240 | class PQ_GizmoGroup_LoopCut(PQ_GizmoGroup_Base): 241 | my_tool = ToolPolyQuiltLoopCut 242 | bl_idname = my_tool.bl_widget 243 | bl_label = "PolyQuilt LoopCut Gizmo" 244 | 245 | class PQ_GizmoGroup_Brush(PQ_GizmoGroup_Base): 246 | my_tool = ToolPolyQuiltBrush 247 | bl_idname = my_tool.bl_widget 248 | bl_label = "PolyQuilt Brush Gizmo" 249 | 250 | class PQ_GizmoGroup_Seam(PQ_GizmoGroup_Base): 251 | my_tool = ToolPolyQuiltSeam 252 | bl_idname = my_tool.bl_widget 253 | bl_label = "PolyQuilt Seam Gizmo" 254 | 255 | 256 | all_gizmos = ( PQ_Gizmo_Preselect , PQ_GizmoGroup_Preselect , PQ_GizmoGroup_Lowpoly , PQ_GizmoGroup_Knife , PQ_GizmoGroup_Delete, PQ_GizmoGroup_Extrude, PQ_GizmoGroup_LoopCut, PQ_GizmoGroup_Brush, PQ_GizmoGroup_Seam ) 257 | 258 | 259 | # ursor (enum in ['DEFAULT', 'NONE', 'WAIT', 'CROSSHAIR', 'MOVE_X', 'MOVE_Y', 'KNIFE', 'TEXT', 'PAINT_BRUSH', 'PAINT_CROSS', 'DOT', 'ERASER', 'HAND', 'SCROLL_X', 'SCROLL_Y', 'SCROLL_XY', 'EYEDROPPER'], (optional)) – cursor 260 | 261 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_brush_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_brush_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_delete_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_delete_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_extrude_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_extrude_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_frame_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_frame_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_knife_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_knife_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_loopcut_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_loopcut_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_poly_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_poly_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/addon.poly_quilt_seam_icon.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/addon.poly_quilt_seam_icon.dat -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_brush_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_brush_delete.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_brush_move.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_brush_move.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_brush_relax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_brush_relax.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_geom_edge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_geom_edge.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_geom_polygon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_geom_polygon.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_geom_quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_geom_quad.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_geom_triangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_geom_triangle.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_geom_vert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_geom_vert.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_free.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_free.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_normal.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_tangent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_tangent.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_x.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_y.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_y.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_move_z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_move_z.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_opt_backcull.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_opt_backcull.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_opt_mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_opt_mirror.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/icons/icon_opt_x0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Addons/PolyQuilt/icons/icon_opt_x0.png -------------------------------------------------------------------------------- /Addons/PolyQuilt/pq_icon.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import os 16 | import bpy.utils.previews 17 | 18 | __all__ = ['register_icons','unregister_icons','custom_icon'] 19 | 20 | icons = [ "icon_geom_vert" , "icon_geom_edge" , "icon_geom_triangle" , "icon_geom_quad" , "icon_geom_polygon" , 21 | "icon_move_free" , "icon_move_x" , "icon_move_y" , "icon_move_z" , "icon_move_normal", "icon_move_tangent" , 22 | "icon_opt_backcull" , "icon_opt_mirror" , "icon_opt_x0" , 23 | "icon_brush_move" , "icon_brush_relax", "icon_brush_delete" ] 24 | 25 | custom_icons = {} 26 | 27 | def register_icons(): 28 | global custom_icons 29 | custom_icons = bpy.utils.previews.new() 30 | my_icons_dir = os.path.join(os.path.dirname(__file__), "icons") 31 | for icon in icons : 32 | custom_icons.load( icon , os.path.join(my_icons_dir, icon + ".png" ) , 'IMAGE') 33 | 34 | def unregister_icons(): 35 | global custom_icons 36 | bpy.utils.previews.remove(custom_icons) 37 | custom_icons = None 38 | 39 | def custom_icon( name ) : 40 | global custom_icons 41 | return custom_icons[ name].icon_id 42 | 43 | def custom_icon_t( name ) : 44 | global custom_icons 45 | return custom_icons[ name] 46 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/pq_keymap_editor.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import os 15 | import bpy 16 | from bpy.types import WorkSpaceTool , Panel 17 | from bpy.utils.toolsystem import ToolDef 18 | from .pq_icon import * 19 | import inspect 20 | import rna_keymap_ui 21 | from bpy.types import AddonPreferences 22 | 23 | def draw_tool_keymap( layout ,keyconfing,keymapname ) : 24 | keymap = keyconfing.keymaps[keymapname] 25 | layout.context_pointer_set('keymap', keymap) 26 | cnt = 0 27 | 28 | 29 | for item in reversed(keymap.keymap_items) : 30 | cnt = max( cnt , (item.oskey,item.shift,item.ctrl,item.alt).count(True) ) 31 | 32 | for item in reversed(keymap.keymap_items) : 33 | if True in (item.oskey,item.shift,item.ctrl,item.alt) : 34 | it = layout.row( ) 35 | # it.prop(item , "active" , text = "" ) 36 | if item.idname == 'mesh.poly_quilt' : 37 | # for i3d in keyconfing.keymaps["Mesh"].keymap_items : 38 | # if i3d.type == 'LEFTMOUSE' and i3d.shift == item.shift and i3d.ctrl == item.ctrl and i3d.alt == item.alt and i3d.oskey == item.oskey : 39 | # ic = layout.row(align = True) 40 | # ic.template_event_from_keymap_item(i3d) 41 | # ic.label( icon = 'ERROR' , text = i3d.name ) 42 | # for i3d in keyconfing.keymaps["3D View"].keymap_items : 43 | # if i3d.type == 'LEFTMOUSE' and i3d.shift == item.shift and i3d.ctrl == item.ctrl and i3d.alt == item.alt and i3d.oskey == item.oskey : 44 | # ic = layout.row(align = True) 45 | # ic.template_event_from_keymap_item(i3d) 46 | # ic.label( icon = 'ERROR' , text = i3d.name ) 47 | 48 | # ic = it.row(align = True) 49 | # ic.prop( item , icon = 'ERROR' ) 50 | 51 | ic = it.row(align = True) 52 | ic.ui_units_x = cnt + 2 53 | ic.prop(item , "active" , text = "" , emboss = True ) 54 | ic.template_event_from_keymap_item(item) 55 | 56 | ic = it.row(align = True) 57 | ic.prop(item.properties , "tool_mode" , text = "" , emboss = True ) 58 | 59 | # op = it.popover(panel="VIEW3D_PT_tools_polyquilt_keymap_properties" , text = item.properties.tool_mode ) 60 | # op.item_id = 0 61 | 62 | if( item.properties.tool_mode == 'LOWPOLY' ) : 63 | im = ic.row() 64 | im.active = item.properties.is_property_set("geometry_type") 65 | im.prop(item.properties, "geometry_type" , text = "" , emboss = True , expand = False , icon_only = False ) 66 | 67 | if( item.properties.tool_mode == 'BRUSH' ) : 68 | im = ic.row() 69 | im.active = item.properties.is_property_set("brush_type") 70 | im.prop(item.properties, "brush_type" , text = "" , emboss = True , expand = False , icon_only = False ) 71 | 72 | if( item.properties.tool_mode == 'LOOPCUT' ) : 73 | im = ic.row() 74 | im.active = item.properties.is_property_set("loopcut_mode") 75 | im.prop(item.properties, "loopcut_mode" , text = "" , emboss = True , expand = False , icon_only = False ) 76 | 77 | 78 | if (not item.is_user_defined) and item.is_user_modified: 79 | it.operator("preferences.keyitem_restore", text="", icon='BACK').item_id = item.id 80 | elif item.is_user_defined : 81 | it.operator("preferences.keyitem_remove", text="", icon='X').item_id = item.id 82 | 83 | # layout.operator("preferences.keyitem_add", text="Add New", text_ctxt=i18n_contexts.id_windowmanager, icon='ADD') 84 | layout.operator(PQ_OT_DirtyKeymap.bl_idname) 85 | # popover_kw = {"space_type": 'VIEW_3D', "region_type": 'UI', "category": "Tool"} 86 | # op = layout.popover_group(context=".poly_quilt_keymap_properties", **popover_kw) 87 | # if it.active : 88 | # it.context_pointer_set( "brush_type" , item.properties ) 89 | # it = layout.column() 90 | # rna_keymap_ui.draw_kmi( 91 | # [], keyconfing, keymap, item, it, 0) 92 | # it.template_keymap_item_properties(item) 93 | # for m in inspect.getmembers(item.properties): 94 | # print(m) 95 | 96 | 97 | def draw_tool_keymap_ui( context , _layout , text , tool) : 98 | 99 | preferences = context.preferences.addons[__package__].preferences 100 | 101 | column = _layout.box().column() 102 | row = column.row() 103 | row.prop( preferences, "keymap_setting_expanded", text="", 104 | icon='TRIA_DOWN' if preferences.keymap_setting_expanded else 'TRIA_RIGHT') 105 | 106 | row.label(text =text + " Setting") 107 | 108 | if preferences.keymap_setting_expanded : 109 | keyconfing = context.window_manager.keyconfigs.user 110 | draw_tool_keymap( column, keyconfing,"3D View Tool: Edit Mesh, " + tool.bl_label ) 111 | 112 | class PQ_OT_DirtyKeymap(bpy.types.Operator) : 113 | bl_idname = "addon.polyquilt_dirty_keymap" 114 | bl_label = "Save Keymap" 115 | 116 | def execute(self, context): 117 | for keymap in [ k for k in context.window_manager.keyconfigs.user.keymaps if "PolyQuilt" in k.name ] : 118 | keymap.show_expanded_items = keymap.show_expanded_items 119 | for item in reversed(keymap.keymap_items) : 120 | if True in (item.oskey,item.shift,item.ctrl,item.alt) : 121 | if item.idname == 'mesh.poly_quilt' : 122 | item.active = item.active 123 | 124 | context.preferences.is_dirty = True 125 | # bpy.ops.wm.save_userpref() 126 | return {'FINISHED'} 127 | 128 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/pq_operator_add_empty_object.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | import bpy 14 | from bpy.types import Operator 15 | from bpy.props import FloatVectorProperty 16 | from bpy_extras.object_utils import AddObjectHelper, object_data_add 17 | 18 | class OBJECT_OT_add_object(Operator, AddObjectHelper): 19 | """Create a new Empty Mesh Object""" 20 | bl_idname = "mesh.add_empty_mesh_object" 21 | bl_label = "Add Empty Mesh Object" 22 | bl_options = {'REGISTER', 'UNDO'} 23 | 24 | def invoke(self, context , event): 25 | mesh = bpy.data.meshes.new(name="New Empty Mesh") 26 | object_data_add(context, mesh, operator=self) 27 | return {'FINISHED'} 28 | 29 | def add_object_button(self, context): 30 | self.layout.operator( 31 | OBJECT_OT_add_object.bl_idname, 32 | text="Empty Mesh Object", 33 | icon='EMPTY_DATA') 34 | 35 | # This allows you to right click on a button and link to the manual 36 | def add_object_manual_map(): 37 | url_manual_prefix = "https://docs.blender.org/manual/en/dev/" 38 | url_manual_mapping = ( 39 | ("bpy.ops.mesh.add_object", "editors/3dview/object"), 40 | ) 41 | return url_manual_prefix, url_manual_mapping 42 | 43 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/pq_tool.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import os 15 | import bpy 16 | from bpy.types import WorkSpaceTool , Panel 17 | from bpy.utils.toolsystem import ToolDef 18 | from .pq_icon import * 19 | from .pq_tool_ui import * 20 | from .pq_keymap_editor import draw_tool_keymap_ui 21 | 22 | class ToolPolyQuiltBase(WorkSpaceTool): 23 | pq_main_tool = 'MASTER' 24 | pq_description = 'Master Tool' 25 | 26 | bl_space_type='VIEW_3D' 27 | bl_context_mode='EDIT_MESH' 28 | bl_widget = "MESH_GGT_PQ_Preselect" 29 | 30 | @staticmethod 31 | def tool_keymaps( main_tool , shift = ["NONE"] , ctrl = ["NONE"] , alt = ["NONE"] ) : 32 | def keyitem( mods , tool ) : 33 | key = {"type": 'LEFTMOUSE', "value": 'PRESS', "shift" : 's' in mods , "ctrl" : 'c' in mods , "alt" : 'a' in mods , "oskey": 'o' in mods } 34 | prop = {"properties": [("tool_mode", tool[0] )]} 35 | if len(tool) > 1 and tool[0] == 'BRUSH' : 36 | prop["properties"].append( ('brush_type' , tool[1] ) ) 37 | item = ("mesh.poly_quilt", key , prop ) 38 | return item 39 | 40 | return ( 41 | keyitem( "" , main_tool ) , 42 | keyitem( "s" , shift ) , 43 | keyitem( "c" , ctrl ) , 44 | keyitem( "a" , alt ) , 45 | keyitem( "cs" , ['NONE'] ) , 46 | keyitem( "sa" , ['NONE'] ) , 47 | keyitem( "ca" , ['NONE'] ) , 48 | keyitem( "os" , ['NONE'] ) , 49 | keyitem( "oc" , ['NONE'] ) , 50 | keyitem( "oa" , ['NONE'] ) , 51 | 52 | ("mesh.poly_quilt_daemon", {"type": 'MOUSEMOVE', "value": 'ANY' }, {"properties": []}), 53 | ) 54 | 55 | @classmethod 56 | def draw_settings( cls ,context, layout, tool): 57 | reg = context.region.type 58 | 59 | # keyconfigs = context.window_manager.keyconfigs.user 60 | # keymap = keyconfigs.keymaps["3D View Tool: Edit Mesh, " + cls.bl_label ] 61 | # tools = [ item.properties.tool_mode for item in keymap.keymap_items if item.idname == 'mesh.poly_quilt' and hasattr( item.properties , "tool_mode" ) ] 62 | tools = [ "MASTER" ] 63 | 64 | if reg == 'UI' : 65 | draw_settings_ui( context , layout , tool , ui = tools) 66 | draw_tool_keymap_ui( context , layout , cls.pq_description , cls) 67 | elif reg == 'WINDOW' : 68 | draw_settings_ui( context , layout , tool , ui = tools) 69 | draw_tool_keymap_ui( context , layout , cls.pq_description , cls ) 70 | elif reg == 'TOOL_HEADER' : 71 | draw_settings_toolheader( context , layout , tool , ui = tools ) 72 | 73 | class ToolPolyQuilt(ToolPolyQuiltBase): 74 | pq_main_tool = 'MASTER' 75 | pq_description = 'Master Tool' 76 | 77 | # The prefix of the idname should be your add-on name. 78 | bl_idname = "mesh_tool.poly_quilt" 79 | bl_label = "PolyQuilt" 80 | bl_description = ( "Lowpoly Tool" ) 81 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_icon") 82 | bl_widget = "MESH_GGT_PQ_Preselect" 83 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH'] ) 84 | 85 | class ToolPolyQuiltPoly(ToolPolyQuiltBase): 86 | pq_main_tool = 'LOWPOLY' 87 | pq_description = 'LowPoly Tool' 88 | 89 | # The prefix of the idname should be your add-on name. 90 | bl_idname = "mesh_tool.poly_quilt_poly" 91 | bl_label = "PolyQuilt:Poly" 92 | bl_description = ( "Lowpoly Tool" ) 93 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_poly_icon") 94 | bl_widget = "MESH_GGT_PQ_Lowpoly" 95 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH'] ) 96 | 97 | class ToolPolyQuiltKnife(ToolPolyQuiltBase): 98 | pq_main_tool = 'KNIFE' 99 | pq_description = 'Knife Tool' 100 | 101 | # The prefix of the idname should be your add-on name. 102 | bl_idname = "mesh_tool.poly_quilt_knife" 103 | bl_label = "PolyQuilt:Knife" 104 | bl_description = ( "Quick Knife Tool" ) 105 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_knife_icon") 106 | bl_widget = "MESH_GGT_PQ_Knife" 107 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH'] ) 108 | 109 | 110 | class ToolPolyQuiltDelete(ToolPolyQuiltBase): 111 | pq_main_tool = 'DELETE' 112 | pq_description = 'Delete Tool' 113 | 114 | # The prefix of the idname should be your add-on name. 115 | bl_idname = "mesh_tool.poly_quilt_delete" 116 | bl_label = "PolyQuilt:Delete" 117 | bl_description = ( "Quick Delete Tool" ) 118 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_delete_icon") 119 | bl_widget = "MESH_GGT_PQ_Delete" 120 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH','DELETE'] ) 121 | 122 | class ToolPolyQuiltExtrude(ToolPolyQuiltBase): 123 | pq_main_tool = 'EXTRUDE' 124 | pq_description = 'Extrude Tool' 125 | 126 | # The prefix of the idname should be your add-on name. 127 | bl_idname = "mesh_tool.poly_quilt_extrude" 128 | bl_label = "PolyQuilt:Extrude" 129 | bl_description = ( "Edge Extrude Tool" ) 130 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_extrude_icon") 131 | bl_widget = "MESH_GGT_PQ_Extrude" 132 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH'] ) 133 | 134 | class ToolPolyQuiltLoopCut(ToolPolyQuiltBase): 135 | pq_main_tool = 'LOOPCUT' 136 | pq_description = 'LoopCut Tool' 137 | 138 | # The prefix of the idname should be your add-on name. 139 | bl_idname = "mesh_tool.poly_quilt_loopcut" 140 | bl_label = "PolyQuilt:LoopCut" 141 | bl_description = ( "LoopCut Tool" ) 142 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_loopcut_icon") 143 | bl_widget = "MESH_GGT_PQ_LoopCut" 144 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool] , shift = ['BRUSH']) 145 | 146 | class ToolPolyQuiltBrush(ToolPolyQuiltBase): 147 | pq_main_tool = 'BRUSH' 148 | pq_description = 'Brush Tool' 149 | 150 | # The prefix of the idname should be your add-on name. 151 | bl_idname = "mesh_tool.poly_quilt_brush" 152 | bl_label = "PolyQuilt:Brush" 153 | bl_description = ( "Brush Tool" ) 154 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_brush_icon") 155 | bl_widget = "MESH_GGT_PQ_Brush" 156 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool], shift = ['BRUSH'] ) 157 | 158 | class ToolPolyQuiltSeam(ToolPolyQuiltBase): 159 | pq_main_tool = 'MARK_SEAM' 160 | pq_description = 'Seam Tool' 161 | 162 | # The prefix of the idname should be your add-on name. 163 | bl_idname = "mesh_tool.poly_quilt_seam" 164 | bl_label = "PolyQuilt:Seam" 165 | bl_description = ( "Seam Tool" ) 166 | bl_icon = os.path.join(os.path.join(os.path.dirname(__file__), "icons") , "addon.poly_quilt_seam_icon") 167 | bl_widget = "MESH_GGT_PQ_Seam" 168 | bl_keymap = ToolPolyQuiltBase.tool_keymaps( [pq_main_tool], ctrl = ['MARK_SEAM_LOOP'] ) 169 | 170 | PolyQuiltTools = ( 171 | { 'tool' : ToolPolyQuilt , 'after' : {"builtin.poly_build"} , 'group' : True }, 172 | { 'tool' : ToolPolyQuiltPoly , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 173 | { 'tool' : ToolPolyQuiltExtrude , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 174 | { 'tool' : ToolPolyQuiltLoopCut , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 175 | { 'tool' : ToolPolyQuiltKnife , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 176 | { 'tool' : ToolPolyQuiltDelete , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 177 | { 'tool' : ToolPolyQuiltBrush , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 178 | { 'tool' : ToolPolyQuiltSeam , 'after' : {"mesh_tool.poly_quilt"} , 'group' : False }, 179 | ) 180 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/pq_tool_ui.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import os 15 | import bpy 16 | from bpy.types import WorkSpaceTool , Panel 17 | from bpy.utils.toolsystem import ToolDef 18 | from .pq_icon import * 19 | import inspect 20 | import rna_keymap_ui 21 | from bpy.app.translations import pgettext_iface as iface_ 22 | from bpy.app.translations import contexts as i18n_contexts 23 | from bpy.types import AddonPreferences 24 | def draw_settings_ui(context, layout, tool , ui ): 25 | props = tool.operator_properties("mesh.poly_quilt") 26 | preferences = bpy.context.preferences.addons[__package__].preferences 27 | 28 | hoge = bpy.props.EnumProperty( 29 | name="LoopCut Mode", 30 | description="LoopCut Mode", 31 | items=[('EQUAL' , "Equal", "" ), 32 | ('EVEN' , "Even", "" ) ], 33 | default='EQUAL', 34 | ) 35 | 36 | # layout.label(text="Make",text_ctxt="Make", translate=True, icon='NORMALS_FACE') 37 | 38 | if "MASTER" in ui or "LOWPOLY" in ui : 39 | col = layout.column(align=True) 40 | col.prop(props, "geometry_type" , text = "Geom" , expand = True , icon_only = False ) 41 | 42 | col = layout.column(align=True) 43 | col.prop(props, "plane_pivot" , text = "Pivot" , expand = True , icon_only = False ) 44 | 45 | col = layout.column(align=True) 46 | col.prop(props, "move_type" , text = "Move" , expand = True , icon_only = False ) 47 | 48 | row = layout.row(align=True) 49 | row.prop(props, "snap_mode" , text = "Snap" , expand = True , icon_only = False ) 50 | 51 | # layout.prop(context.active_object.data, "use_mirror_x", toggle = toggle , icon_only = False, icon_value = custom_icon("icon_opt_mirror") ) 52 | layout.prop(context.active_object.data, "use_mirror_x", toggle = True , icon_only = False , icon = "MOD_MIRROR" ) 53 | layout.prop( preferences, "fix_to_x_zero", toggle = True , text = "Fix X=0" , icon_only = False, icon_value = custom_icon("icon_opt_x0") ) 54 | 55 | if "MASTER" in ui or "EXTRUDE" in ui : 56 | row = layout.row(align=True) 57 | row.prop(props, "extrude_mode" , text = "EXTRUDE" , expand = True ) 58 | 59 | if "MASTER" in ui or "LOOPCUT" in ui : 60 | layout.separator() 61 | row = layout.row(align=True) 62 | row.prop(props, "loopcut_mode" , text = "LOOPCUT" , expand = True ) 63 | 64 | row = layout.row(align=True) 65 | row.prop( preferences, "loopcut_division" , text = "Edge Snap Div" , expand = True, slider = True ) 66 | 67 | layout.separator() 68 | col = layout.column(align=True) 69 | col.prop( preferences, "vertex_dissolve_angle" , text = "Vertex Dissolve Angle", expand = True, slider = True , icon_only = False ) 70 | 71 | if "MASTER" in ui or "BRUSH" in ui : 72 | layout.separator() 73 | col = layout.column(align=True) 74 | col.prop( props, "brush_type" , text = "Brush", toggle = True , expand = True, icon_only = False ) 75 | 76 | col.prop( preferences, "brush_size" , text = "Brush Size" , expand = True, slider = True , icon_only = False ) 77 | col.prop( preferences, "brush_strength" , text = "Brush Strength" , expand = True, slider = True , icon_only = False ) 78 | # shading = get_shading() 79 | # if shading.type == 'SOLID': 80 | # layout.prop( shading , "show_backface_culling", icon_value = custom_icon("icon_opt_backcull")) 81 | 82 | # tool_settings = context.tool_settings 83 | # layout.prop(tool_settings, "use_edge_path_live_unwrap") 84 | # layout.prop(tool_settings, "use_mesh_automerge") 85 | # layout.prop(tool_settings, "double_threshold") 86 | # layout.prop(tool_settings, "edge_path_mode") 87 | 88 | def draw_settings_toolheader(context, layout, tool , ui = ['GEOM','BRUSH','OPTION'] ): 89 | props = tool.operator_properties("mesh.poly_quilt") 90 | 91 | if "MASTER" in ui or "LOWPOLY" in ui : 92 | row = layout.row( align=True) 93 | row.label( text = "Geom" ) 94 | row.prop(props, "geometry_type" , text = "Geom" , expand = True , icon_only = True ) 95 | 96 | if "MASTER" in ui or "BRUSH" in ui : 97 | row = layout.row( align=True) 98 | row.label( text = "Brush" ) 99 | row.prop( props , "brush_type" , text = "Brush", toggle = True , expand = True, icon_only = True ) 100 | 101 | # Expand panels from the side-bar as popovers. 102 | popover_kw = {"space_type": 'VIEW_3D', "region_type": 'UI', "category": "Tool"} 103 | op = layout.popover_group(context=".poly_quilt_option", **popover_kw) 104 | 105 | 106 | class VIEW3D_PT_tools_polyquilt_options( Panel): 107 | bl_space_type = 'VIEW_3D' 108 | bl_region_type = 'UI' 109 | 110 | bl_category = "Tool" 111 | bl_context = ".poly_quilt_option" # dot on purpose (access from topbar) 112 | bl_label = "Options" 113 | bl_options = {'DEFAULT_CLOSED'} 114 | # bl_ui_units_x = 8 115 | 116 | def draw(self, context): 117 | layout = self.layout 118 | 119 | # Active Tool 120 | # ----------- 121 | from bl_ui.space_toolsystem_common import ToolSelectPanelHelper 122 | tool = ToolSelectPanelHelper.tool_active_from_context(context) 123 | # print(tool.idname) 124 | props = tool.operator_properties("mesh.poly_quilt") 125 | preferences = bpy.context.preferences.addons[__package__].preferences 126 | 127 | col = layout.column() 128 | col.label( text = "Pivot" ) 129 | col.prop(props, "plane_pivot" , text = "Pivot" , expand = True ) 130 | 131 | col = layout.column() 132 | col.label( text = "Move" ) 133 | row = layout.row() 134 | row.ui_units_x = 3.25 135 | row.prop(props, "move_type" , text = "" , expand = True , icon_only = True ) 136 | 137 | col = layout.column() 138 | col.label( text = "Snap" ) 139 | row = layout.row(align=True) 140 | row.prop(props, "snap_mode" , text = "Snap" , expand = True , icon_only = False ) 141 | 142 | layout.label( text = "Fix X=0" ) 143 | layout.prop( preferences, "fix_to_x_zero", toggle = True , text = "" , icon_only = True, icon_value = custom_icon("icon_opt_x0") ) 144 | 145 | layout.label( text = "Extrude" ) 146 | layout.prop(props, "extrude_mode" , text = "EXTRUDE" , expand = True ) 147 | 148 | layout.label( text = "LOOPCUT" ) 149 | layout.prop(props, "loopcut_mode" , text = "LOOPCUT" , expand = True ) 150 | col = layout.column() 151 | col.label( text = "Edge Snap Div" ) 152 | col.prop( preferences, "loopcut_division" , text = "Edge Snap Div" , expand = True, slider = True , icon_only = False ) 153 | 154 | col.label( text = "Vertex Dissolve Angle" ) 155 | col.prop( preferences, "vertex_dissolve_angle" , text = "Vertex Dissolve Angle", expand = True, slider = True , icon_only = False ) 156 | 157 | col.label( text = "Brush" ) 158 | col.prop( preferences, "brush_size" , text = "Brush Size" , expand = True, slider = True , icon_only = False ) 159 | col.prop( preferences, "brush_strength" , text = "Brush Strength" , expand = True, slider = True , icon_only = False ) 160 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | # This program is free software; you can redistribute it and/or modify 3 | # it under the terms of the GNU General Public License as published by 4 | # the Free Software Foundation; either version 3 of the License, or 5 | # (at your option) any later version. 6 | # 7 | # This program is distributed in the hope that it will be useful, but 8 | # WITHOUT ANY WARRANTY; without even the implied warranty of 9 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 10 | # General Public License for more details. 11 | # 12 | # You should have received a copy of the GNU General Public License 13 | # along with this program. If not, see . 14 | 15 | from .maintool_default import MainToolDefault 16 | from .maintool_hold import MainToolHold 17 | from .maintool_brush import * 18 | from .maintool_lowpoly import MainToolLowPoly 19 | from .maintool_knife import MainToolKnife 20 | from .maintool_delete import MainToolDelete 21 | from .maintool_extrude import MainToolExtrude 22 | from .maintool_loopcut import MainToolLoopCut 23 | from .maintool_edgeloop_dissolve import MainToolEdgeLoopDissolve 24 | from .subtool_seam import SubToolSeam 25 | from .subtool_seam_loop import SubToolSeamLoop 26 | 27 | maintools = { 28 | 'NONE' : None , 29 | 'MASTER' : MainToolDefault , 30 | # 'HOLD' : MainToolHold , 31 | 'LOWPOLY' : MainToolLowPoly , 32 | 'BRUSH' : MainToolBrush , 33 | # 'BRUSH_DELETE' : MainToolBrushDelete , 34 | # 'BRUSH_RELAX' : MainToolBrushRelax , 35 | # 'BRUSH_MOVE' : MainToolBrushMove , 36 | 'EXTRUDE' : MainToolExtrude , 37 | 'KNIFE' : MainToolKnife , 38 | 'DELETE' : MainToolDelete , 39 | 'LOOPCUT' : MainToolLoopCut , 40 | 'EDGELOOP_DISSOLVE' : MainToolEdgeLoopDissolve , 41 | 'MARK_SEAM' : SubToolSeam , 42 | 'MARK_SEAM_LOOP' : SubToolSeamLoop , 43 | } 44 | 45 | 46 | def enum_tool_callback(scene, context ): 47 | return ( ( tool , cls.name if cls else "None" , "" ,"", index ) for index , (tool,cls) in enumerate(maintools.items()) ) -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_brush.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..utils.dpi import * 24 | from ..QMesh import * 25 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 26 | from .subtool import * 27 | from .subtool_makepoly import * 28 | from .subtool_knife import * 29 | from .subtool_edge_slice import * 30 | from .subtool_edgeloop_cut import * 31 | from .subtool_edge_extrude import * 32 | from .subtool_brush_relax import * 33 | from .subtool_brush_size import * 34 | from .subtool_brush_move import * 35 | from .subtool_brush_delete import * 36 | from .subtool_move import * 37 | from .subtool_fin_slice import * 38 | from .subtool_autoquad import * 39 | from ..utils.dpi import * 40 | 41 | 42 | 43 | 44 | class MainToolBrush(MainTool) : 45 | name = "Brush" 46 | 47 | def __init__(self,op,currentTarget, button) : 48 | super().__init__(op,currentTarget, button) 49 | brush_tbl = { 50 | 'SMOOTH' : SubToolBrushRelax , 51 | 'MOVE' : SubToolBrushMove , 52 | 'DELETE' : SubToolBrushDelete , 53 | } 54 | 55 | brush = op.brush_type 56 | brush_type = brush_tbl[ brush ] 57 | 58 | self.callback = { 59 | MBEventType.Release : [] , 60 | MBEventType.Click : [SubToolAutoQuad] , 61 | MBEventType.LongClick : [] , 62 | MBEventType.LongPressDrag : [SubToolBrushSize] , 63 | MBEventType.Drag : [brush_type] , 64 | } 65 | 66 | @staticmethod 67 | def LMBEventCallback(self , event ): 68 | self.debugStr = str(event.type) 69 | if event.type in self.callback.keys() : 70 | tools = [ t( event.event , self) for t in self.callback[event.type] if t.Check( self , self.currentTarget ) ] 71 | if tools : 72 | self.SetSubTool( tools ) 73 | self.isExit = True 74 | 75 | @classmethod 76 | def DrawHighlight( cls , gizmo , element ) : 77 | if SubToolAutoQuad.Check( None , element ) : 78 | drawAutoQuad = SubToolAutoQuad.DrawHighlight(gizmo,element) 79 | else : 80 | drawAutoQuad = None 81 | 82 | def Draw() : 83 | radius = gizmo.preferences.brush_size * dpm() 84 | strength = gizmo.preferences.brush_strength 85 | if drawAutoQuad : 86 | drawAutoQuad() 87 | with draw_util.push_pop_projection2D() : 88 | draw_util.draw_circle2D( gizmo.mouse_pos , radius * strength , color = (1,0.25,0.25,0.25), fill = False , subdivide = 64 , dpi= False ) 89 | draw_util.draw_circle2D( gizmo.mouse_pos , radius , color = (1,1,1,0.5), fill = False , subdivide = 64 , dpi= False ) 90 | return Draw 91 | 92 | @classmethod 93 | def UpdateHighlight( cls , gizmo , element ) : 94 | return True 95 | 96 | def OnDraw( self , context ) : 97 | radius = self.preferences.brush_size * dpm() 98 | strength = self.preferences.brush_strength 99 | draw_util.draw_circle2D( self.mouse_pos , radius * strength , color = (1,0.25,0.25,0.25), fill = False , subdivide = 64 , dpi= False ) 100 | draw_util.draw_circle2D( self.mouse_pos , radius , color = (1,1,1,0.5), fill = False , subdivide = 64 , dpi= False ) 101 | 102 | self.LMBEvent.Draw( self.mouse_pos ) 103 | 104 | if self.LMBEvent.is_hold : 105 | draw_util.DrawFont( "Strenght = " + '{:.0f}'.format(self.preferences.brush_strength * 100) , 10 , self.mouse_pos , (0,0) ) 106 | draw_util.DrawFont( "Radius = " + '{:.0f}'.format(self.preferences.brush_size * dpm() ) , 10 , self.mouse_pos , (0,-8) ) 107 | 108 | def OnDraw3D( self , context ) : 109 | if not self.LMBEvent.presureComplite : 110 | if SubToolAutoQuad.Check( self , self.currentTarget ) : 111 | draw = SubToolAutoQuad.DrawHighlight(self,self.currentTarget) 112 | if draw : 113 | draw() 114 | 115 | @classmethod 116 | def GetCursor(cls) : 117 | return 'CROSSHAIR' 118 | 119 | @classmethod 120 | def recive_event( cls , gizmo , context , event ) : 121 | if any( [ event.shift , event.ctrl , event.alt, event.oskey ] ) : 122 | if event.type == 'WHEELUPMOUSE' : 123 | cls.change_brush_size( gizmo.preferences , context ,-50 , 0 ) 124 | 125 | if event.type == 'WHEELDOWNMOUSE' : 126 | cls.change_brush_size( gizmo.preferences , context , 50 , 0 ) 127 | 128 | return {'PASS_THROUGH'} 129 | 130 | @classmethod 131 | def change_brush_size( cls , preferences , context , brush_size_value , brush_strong_value ): 132 | if context.area.type == 'VIEW_3D' : 133 | a = (preferences.brush_size * preferences.brush_size) / 40000.0 + 0.1 134 | preferences.brush_size = preferences.brush_size + brush_size_value * a 135 | strength = min( max( 0 , preferences.brush_strength + brush_strong_value ) , 1 ) 136 | preferences.brush_strength = strength 137 | context.area.tag_redraw() 138 | 139 | class MainToolBrushDelete(MainToolBrush) : 140 | name = "BrushDeleteSubTool" 141 | 142 | def __init__(self,op,currentTarget, button) : 143 | super().__init__(op,currentTarget, button) 144 | self.callback = { 145 | MBEventType.Release : [] , 146 | MBEventType.Click : [] , 147 | MBEventType.LongClick : [] , 148 | MBEventType.LongPressDrag : [SubToolBrushSize] , 149 | MBEventType.Drag : [SubToolBrushDelete] , 150 | } 151 | 152 | def OnDraw3D( self , context ) : 153 | pass 154 | 155 | @classmethod 156 | def DrawHighlight( cls , gizmo , element ) : 157 | return SubToolBrushDelete.DrawHighlight(gizmo,element) 158 | 159 | class MainToolBrushRelax(MainToolBrush) : 160 | name = "BrushRelaxSubTool" 161 | 162 | def __init__(self,op,currentTarget, button) : 163 | super().__init__(op,currentTarget, button) 164 | self.callback = { 165 | MBEventType.Release : [] , 166 | MBEventType.Click : [] , 167 | MBEventType.LongClick : [] , 168 | MBEventType.LongPressDrag : [SubToolBrushSize] , 169 | MBEventType.Drag : [SubToolBrushRelax] , 170 | } 171 | 172 | def OnDraw3D( self , context ) : 173 | pass 174 | 175 | @classmethod 176 | def DrawHighlight( cls , gizmo , element ) : 177 | return SubToolBrushRelax.DrawHighlight(gizmo,element) 178 | 179 | class MainToolBrushMove(MainToolBrush) : 180 | name = "BrushMoveSubTool" 181 | 182 | def __init__(self,op,currentTarget, button) : 183 | super().__init__(op,currentTarget, button) 184 | self.callback = { 185 | MBEventType.Release : [] , 186 | MBEventType.Click : [] , 187 | MBEventType.LongClick : [] , 188 | MBEventType.LongPressDrag : [SubToolBrushSize] , 189 | MBEventType.Drag : [SubToolBrushMove] , 190 | } 191 | 192 | def OnDraw3D( self , context ) : 193 | pass 194 | 195 | @classmethod 196 | def DrawHighlight( cls , gizmo , element ) : 197 | return SubToolBrushMove.DrawHighlight(gizmo,element) 198 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_default.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edgeloop_cut import * 30 | from .subtool_edge_extrude import * 31 | from .subtool_vert_extrude import * 32 | from .subtool_move import * 33 | from .subtool_fin_slice import * 34 | from .subtool_polypen import * 35 | 36 | class MainToolDefault(MainTool) : 37 | name = "Master Tool" 38 | 39 | def __init__(self,op,currentTarget, button) : 40 | super().__init__(op,currentTarget, button) 41 | 42 | @staticmethod 43 | def LMBEventCallback(self , event ): 44 | self.debugStr = str(event.type) 45 | 46 | if event.type == MBEventType.Release : 47 | self.isExit = True 48 | 49 | elif event.type == MBEventType.Click : 50 | if self.currentTarget.isVert or self.currentTarget.isEmpty or self.currentTarget.isEdge: 51 | self.SetSubTool( SubToolMakePoly(self.operator,self.currentTarget , self.mouse_pos ) ) 52 | 53 | elif event.type == MBEventType.LongClick : 54 | if self.currentTarget.isVert : 55 | self.bmo.dissolve_vert( self.currentTarget.element , False , False , dissolve_vert_angle=self.preferences.vertex_dissolve_angle ) 56 | elif self.currentTarget.isEdge : 57 | self.bmo.dissolve_edge( self.currentTarget.element , use_verts = False , use_face_split = False , dissolve_vert_angle=self.preferences.vertex_dissolve_angle ) 58 | elif self.currentTarget.isFace : 59 | self.bmo.Remove( self.currentTarget.element ) 60 | self.bmo.UpdateMesh() 61 | self.currentTarget = ElementItem.Empty() 62 | 63 | elif event.type == MBEventType.LongPressDrag : 64 | if self.currentTarget.isEdge : 65 | tools = [] 66 | if SubToolPolyPen.Check( self ,self.currentTarget) : 67 | tools.append(SubToolPolyPen(self.operator,self.currentTarget)) 68 | else : 69 | if len(self.currentTarget.element.link_faces) > 0 : 70 | tools.append(SubToolEdgeSlice(self.operator,self.currentTarget, self.mouse_pos)) 71 | if SubToolEdgeloopCut.Check( self ,self.currentTarget) : 72 | tools.append(SubToolEdgeloopCut(self.operator,self.currentTarget)) 73 | if SubToolEdgeExtrude.Check( self ,self.currentTarget) : 74 | tools.append(SubToolEdgeExtrude(self.operator,self.currentTarget,False)) 75 | self.SetSubTool( tools ) 76 | elif self.currentTarget.isVert : 77 | tools = [] 78 | tools.append(SubToolFinSlice(self.operator,self.currentTarget )) 79 | if SubToolVertExtrude.Check( self ,self.currentTarget ) : 80 | tools.append(SubToolVertExtrude(self.operator,self.currentTarget)) 81 | self.SetSubTool( tools ) 82 | elif self.currentTarget.isEmpty : 83 | self.SetSubTool( SubToolKnife(self.operator,self.currentTarget , self.LMBEvent.PressPos ) ) 84 | 85 | elif event.type == MBEventType.Drag : 86 | if self.currentTarget.isEdge : 87 | if self.currentTarget.can_extrude() : 88 | self.SetSubTool( SubToolEdgeExtrude(self.operator,self.currentTarget , False ) ) 89 | else : 90 | self.SetSubTool( SubToolMove(self.operator,self.currentTarget , self.mouse_pos ) ) 91 | elif self.currentTarget.isNotEmpty : 92 | self.SetSubTool( SubToolMove(self.operator,self.currentTarget , self.mouse_pos ) ) 93 | else : 94 | self.isExit = self.do_empty_space(event) 95 | 96 | 97 | @classmethod 98 | def DrawHighlight( cls , gizmo , element ) : 99 | if element != None and gizmo.bmo != None : 100 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.highlight_color , gizmo.preferences , True ) 101 | return None 102 | 103 | def OnDraw( self , context ) : 104 | if self.LMBEvent.isPresure : 105 | if self.currentTarget.isNotEmpty : 106 | self.LMBEvent.Draw( self.currentTarget.coord ) 107 | else: 108 | self.LMBEvent.Draw( None ) 109 | 110 | def OnDraw3D( self , context ) : 111 | if self.currentTarget.isNotEmpty : 112 | color = self.color_highlight() 113 | if self.LMBEvent.is_hold : 114 | color = self.color_delete() 115 | self.currentTarget.Draw( self.bmo.obj , color , self.preferences ) 116 | 117 | def OnExit( self ) : 118 | pass 119 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_delete.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_knife import * 27 | from .subtool_edge_slice import * 28 | from .subtool_edgeloop_cut import * 29 | from .subtool_delete import * 30 | 31 | class MainToolDelete(MainTool) : 32 | name = "Delete" 33 | 34 | def __init__(self,op,currentTarget, button) : 35 | super().__init__(op,currentTarget, button , no_hold = True ) 36 | 37 | @staticmethod 38 | def LMBEventCallback(self , event ): 39 | self.debugStr = str(event.type) 40 | 41 | if event.type == MBEventType.Release : 42 | self.isExit = True 43 | elif event.type == MBEventType.Down or event.type == MBEventType.Click or event.type == MBEventType.LongClick: 44 | self.SetSubTool(SubToolDelete( self , self.currentTarget)) 45 | elif event.type == MBEventType.Drag or event.type == MBEventType.LongPressDrag : 46 | self.SetSubTool(SubToolDelete( self , self.currentTarget)) 47 | 48 | @classmethod 49 | def DrawHighlight( cls , gizmo , element ) : 50 | return SubToolDelete.DrawHighlight( gizmo , element ) 51 | 52 | def OnDraw( self , context ) : 53 | pass 54 | 55 | def OnDraw3D( self , context ) : 56 | if self.currentTarget.isNotEmpty : 57 | self.currentTarget.Draw( self.bmo.obj , self.preferences.delete_color , self.preferences , edge_pivot = False ) 58 | 59 | def OnExit( self ) : 60 | pass 61 | 62 | @classmethod 63 | def GetCursor(cls) : 64 | return 'ERASER' 65 | 66 | @staticmethod 67 | def pick_element( qmesh , location , preferences ) : 68 | element = qmesh.PickElement( location , preferences.distance_to_highlight ) 69 | return element 70 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_edgeloop_dissolve.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_knife import * 27 | from .subtool_edge_slice import * 28 | from .subtool_edgeloop_cut import * 29 | from .subtool_delete import * 30 | 31 | class MainToolEdgeLoopDissolve(MainTool) : 32 | name = "Dissolve Loop" 33 | 34 | def __init__(self,op,currentTarget, button) : 35 | super().__init__(op,currentTarget, button , no_hold = True ) 36 | self.currentTarget = currentTarget 37 | self.removes , v = self.bmo.calc_edge_loop( self.currentTarget.element ) 38 | 39 | @staticmethod 40 | def Check( root , target ) : 41 | return target.isEdge 42 | 43 | def OnUpdate( self , context , event ) : 44 | if event.type == 'MOUSEMOVE': 45 | self.currentTarget = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight , elements = ['EDGE'] ) 46 | if self.currentTarget.isEdge : 47 | self.removes , v = self.bmo.calc_edge_loop( self.currentTarget.element ) 48 | else : 49 | self.removes = [] 50 | elif event.type == self.buttonType : 51 | if event.value == 'RELEASE' : 52 | if self.removes : 53 | self.bmo.dissolve_edges( self.removes , use_verts = False , use_face_split = False , dissolve_vert_angle=0 ) 54 | self.bmo.UpdateMesh() 55 | return 'FINISHED' 56 | elif event.type == 'RIGHTMOUSE': 57 | if event.value == 'RELEASE' : 58 | return 'FINISHED' 59 | return 'RUNNING_MODAL' 60 | 61 | @classmethod 62 | def DrawHighlight( cls , gizmo , element ) : 63 | e , v = gizmo.bmo.calc_edge_loop( element.element ) 64 | alpha = gizmo.preferences.highlight_face_alpha 65 | vertex_size = gizmo.preferences.highlight_vertex_size 66 | width = gizmo.preferences.highlight_line_width 67 | color = gizmo.preferences.delete_color 68 | return draw_util.drawElementsHilight3DFunc( gizmo.bmo.obj , e , vertex_size , width , alpha , color ) 69 | 70 | def OnDraw( self , context ) : 71 | pass 72 | 73 | def OnDraw3D( self , context ) : 74 | if self.currentTarget.isEdge : 75 | alpha = self.preferences.highlight_face_alpha 76 | vertex_size = self.preferences.highlight_vertex_size 77 | width = self.preferences.highlight_line_width 78 | color = self.preferences.delete_color 79 | draw_util.drawElementsHilight3D( self.bmo.obj , self.removes , vertex_size , width , alpha , color ) 80 | 81 | def OnExit( self ) : 82 | pass 83 | 84 | @classmethod 85 | def GetCursor(cls) : 86 | return 'ERASER' 87 | 88 | @staticmethod 89 | def pick_element( qmesh , location , preferences ) : 90 | element = qmesh.PickElement( location , preferences.distance_to_highlight, elements = ['EDGE'] ) 91 | return element 92 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_extrude.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edgeloop_cut import * 30 | from .subtool_edge_extrude import * 31 | from .subtool_vert_extrude import * 32 | from .subtool_move import * 33 | from .subtool_fin_slice import * 34 | from .subtool_polypen import * 35 | 36 | class MainToolExtrude(MainTool) : 37 | name = "Edge Extrude" 38 | 39 | def __init__(self,op,currentTarget, button) : 40 | super().__init__(op,currentTarget, button , no_hold = True ) 41 | 42 | @staticmethod 43 | def LMBEventCallback(self , event ): 44 | self.debugStr = str(event.type) 45 | 46 | if event.type == MBEventType.Release : 47 | self.isExit = True 48 | 49 | elif event.type == MBEventType.Click or event.type == MBEventType.LongClick : 50 | if self.currentTarget.isVert or self.currentTarget.isEmpty or self.currentTarget.isEdge: 51 | self.SetSubTool( SubToolMakePoly(self.operator,self.currentTarget , self.mouse_pos ) ) 52 | 53 | elif event.type == MBEventType.Drag or event.type == MBEventType.LongPressDrag : 54 | if self.currentTarget.isEdge : 55 | self.SetSubTool( SubToolEdgeExtrude(self.operator,self.currentTarget,False)) 56 | elif self.currentTarget.isVert : 57 | self.SetSubTool( SubToolVertExtrude(self.operator,self.currentTarget)) 58 | 59 | @classmethod 60 | def DrawHighlight( cls , gizmo , element ) : 61 | if element != None and gizmo.bmo != None : 62 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.highlight_color , gizmo.preferences , marker = True ) 63 | return None 64 | 65 | @staticmethod 66 | def pick_element( qmesh , location , preferences ) : 67 | element = qmesh.PickElement( location , preferences.distance_to_highlight, edgering = True, elements = ['EDGE','VERT'] ) 68 | # element.set_snap_div( preferences.loopcut_division ) 69 | return element 70 | 71 | def OnDraw( self , context ) : 72 | pass 73 | 74 | def OnDraw3D( self , context ) : 75 | self.currentTarget.Draw( self.bmo.obj , self.color_highlight() , self.preferences ) 76 | 77 | def OnExit( self ) : 78 | pass 79 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_hold.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edge_slide import * 30 | from .subtool_edgeloop_cut import * 31 | from .subtool_edge_extrude import * 32 | from .subtool_edge_extrude_multi import * 33 | from .subtool_vert_extrude import * 34 | from .subtool_autoquad import * 35 | from .subtool_move import * 36 | from .subtool_fin_slice import * 37 | 38 | class MainToolHold(MainTool) : 39 | name = "Hold" 40 | 41 | def __init__(self,op,currentTarget, button) : 42 | super().__init__(op,currentTarget, button) 43 | 44 | @staticmethod 45 | def LMBEventCallback(self , event ): 46 | self.debugStr = str(event.type) 47 | if event.type == MBEventType.Down : 48 | pass 49 | 50 | elif event.type == MBEventType.Release : 51 | self.isExit = True 52 | 53 | elif event.type == MBEventType.Click : 54 | if self.currentTarget.isVert or self.currentTarget.isEdge or self.currentTarget.isEmpty: 55 | if SubToolAutoQuad.Check( self , self.currentTarget) : 56 | self.SetSubTool( SubToolAutoQuad( self )) 57 | self.isExit = True 58 | 59 | elif event.type == MBEventType.LongClick : 60 | if self.currentTarget.isVert : 61 | self.bmo.dissolve_vert( self.currentTarget.element , False , False ) 62 | elif self.currentTarget.isEdge : 63 | self.bmo.dissolve_edge( self.currentTarget.element , False , False ) 64 | elif self.currentTarget.isFace : 65 | self.bmo.Remove( self.currentTarget.element ) 66 | self.bmo.UpdateMesh() 67 | self.currentTarget = ElementItem.Empty() 68 | 69 | elif event.type == MBEventType.Drag : 70 | if self.currentTarget.isEdge : 71 | tools = [] 72 | tools.append(SubToolEdgeSlide(self.operator,self.currentTarget)) 73 | self.SetSubTool( tools ) 74 | elif self.currentTarget.isVert : 75 | tools = [] 76 | if SubToolVertExtrude.Check( self ,self.currentTarget ) : 77 | tools.append(SubToolVertExtrude(self.operator,self.currentTarget)) 78 | if tools : 79 | self.SetSubTool( tools ) 80 | elif self.currentTarget.isEmpty : 81 | self.SetSubTool( SubToolKnife(self.operator,self.currentTarget , self.LMBEvent.PressPos ) ) 82 | 83 | elif event.type == MBEventType.LongPressDrag : 84 | if self.currentTarget.isEdge : 85 | tools = [] 86 | if len(self.currentTarget.element.link_faces) > 0 : 87 | tools.append(SubToolEdgeSlice(self.operator,self.currentTarget, self.mouse_pos)) 88 | if SubToolEdgeloopCut.Check(self ,self.currentTarget) : 89 | tools.append(SubToolEdgeloopCut(self.operator,self.currentTarget)) 90 | if SubToolEdgeExtrudeMulti.Check(self ,self.currentTarget) : 91 | tools.append(SubToolEdgeExtrudeMulti(self.operator,self.currentTarget,True)) 92 | self.SetSubTool( tools ) 93 | elif self.currentTarget.isVert : 94 | tools = [] 95 | if SubToolEdgeExtrudeMulti.Check( self ,self.currentTarget ) : 96 | tools.append(SubToolEdgeExtrudeMulti(self.operator,self.currentTarget)) 97 | self.SetSubTool( tools ) 98 | elif self.currentTarget.isEmpty : 99 | self.SetSubTool( SubToolKnife(self.operator,self.currentTarget , self.LMBEvent.PressPos ) ) 100 | 101 | 102 | 103 | @classmethod 104 | def DrawHighlight( cls , gizmo , element ) : 105 | if SubToolAutoQuad.Check( None , element ) : 106 | drawAutoQuad = SubToolAutoQuad.DrawHighlight(gizmo,element) 107 | else : 108 | drawAutoQuad = None 109 | 110 | if element.isEdge : 111 | edges , verts = gizmo.bmo.findEdgeLoop( element.element ) 112 | else : 113 | edges = [] 114 | 115 | width = gizmo.preferences.highlight_line_width 116 | color = gizmo.preferences.highlight_color 117 | def Draw() : 118 | if drawAutoQuad: 119 | drawAutoQuad() 120 | for edge in edges : 121 | vs = [ gizmo.bmo.obj.matrix_world @ v.co for v in edge.verts ] 122 | draw_util.draw_lines3D( bpy.context , vs , color , width , primitiveType = 'LINES' , hide_alpha = 0.5 ) 123 | return Draw 124 | 125 | def OnDraw( self , context ) : 126 | if self.LMBEvent.isPresure : 127 | if self.currentTarget.isNotEmpty : 128 | self.LMBEvent.Draw( self.currentTarget.coord ) 129 | else: 130 | self.LMBEvent.Draw( None ) 131 | 132 | def OnDraw3D( self , context ) : 133 | width = self.preferences.highlight_line_width 134 | color = self.preferences.highlight_color 135 | if self.LMBEvent.isPresure : 136 | color = self.preferences.makepoly_color 137 | else : 138 | if self.currentTarget.isEdge : 139 | if SubToolAutoQuad.Check( None , self.currentTarget ) : 140 | drawAutoQuad = SubToolAutoQuad.DrawHighlight( self,self.currentTarget ) 141 | if drawAutoQuad : 142 | drawAutoQuad() 143 | if self.currentTarget.isEdge : 144 | edges , verts = self.bmo.findEdgeLoop( self.currentTarget.element ) 145 | else : 146 | edges = [] 147 | for edge in edges : 148 | vs = [ self.bmo.obj.matrix_world @ v.co for v in edge.verts ] 149 | draw_util.draw_lines3D( bpy.context , vs , color , width , primitiveType = 'LINES' , hide_alpha = 0.5 ) 150 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_knife.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edgeloop_cut import * 30 | from .subtool_edge_extrude import * 31 | from .subtool_vert_extrude import * 32 | from .subtool_move import * 33 | from .subtool_fin_slice import * 34 | from .subtool_polypen import * 35 | 36 | class MainToolKnife(MainTool) : 37 | name = "Knife" 38 | 39 | def __init__(self,op,currentTarget, button) : 40 | super().__init__(op,currentTarget, button , no_hold = True ) 41 | 42 | @staticmethod 43 | def LMBEventCallback(self , event ): 44 | self.debugStr = str(event.type) 45 | 46 | if event.type == MBEventType.Release : 47 | self.isExit = True 48 | elif event.type == MBEventType.Click or event.type == MBEventType.LongClick: 49 | if self.currentTarget.isVert or self.currentTarget.isEmpty or self.currentTarget.isEdge: 50 | self.SetSubTool( SubToolMakePoly(self.operator,self.currentTarget , self.mouse_pos ) ) 51 | elif event.type == MBEventType.Drag or event.type == MBEventType.LongPressDrag: 52 | self.SetSubTool( SubToolKnife(self.operator,self.currentTarget , self.LMBEvent.PressPos ) ) 53 | 54 | @classmethod 55 | def DrawHighlight( cls , gizmo , element ) : 56 | if element != None and gizmo.bmo != None : 57 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.highlight_color , gizmo.preferences , False ) 58 | return None 59 | 60 | def OnDraw( self , context ) : 61 | pass 62 | 63 | def OnDraw3D( self , context ) : 64 | if self.currentTarget.isNotEmpty : 65 | color = self.color_highlight() 66 | self.currentTarget.Draw( self.bmo.obj , color , self.preferences ) 67 | 68 | def OnExit( self ) : 69 | pass 70 | 71 | @classmethod 72 | def GetCursor(cls) : 73 | return 'KNIFE' -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_loopcut.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edgeloop_cut import * 30 | from .subtool_edge_extrude import * 31 | from .subtool_vert_extrude import * 32 | from .subtool_move import * 33 | from .subtool_fin_slice import * 34 | from .subtool_polypen import * 35 | 36 | class MainToolLoopCut(MainTool) : 37 | name = "Loop Cut" 38 | 39 | def __init__(self,op,currentTarget, button) : 40 | super().__init__(op,currentTarget, button , no_hold = True ) 41 | 42 | @staticmethod 43 | def LMBEventCallback(self , event ): 44 | last_mouse_pos = event.mouse_pos 45 | self.debugStr = str(event.type) 46 | 47 | if event.type == MBEventType.Release : 48 | self.isExit = True 49 | 50 | elif event.type == MBEventType.Down : 51 | if SubToolEdgeSlice.Check( self , self.currentTarget ): 52 | self.SetSubTool( SubToolEdgeSlice(self.operator,self.currentTarget, self.mouse_pos ) ) 53 | 54 | @classmethod 55 | def DrawHighlight( cls , gizmo , target ) : 56 | if SubToolEdgeSlice.Check( None , target ) : 57 | return SubToolEdgeSlice.DrawHighlight( gizmo , target ) 58 | return None 59 | 60 | @staticmethod 61 | def pick_element( qmesh , location , preferences ) : 62 | element = qmesh.PickElement( location , preferences.distance_to_highlight, edgering = False, elements = ['EDGE'] ) 63 | element.set_snap_div( preferences.loopcut_division ) 64 | return element 65 | 66 | def OnDraw( self , context ) : 67 | pass 68 | 69 | def OnDraw3D( self , context ) : 70 | self.currentTarget.Draw( self.bmo.obj , self.color_highlight() , self.preferences ) 71 | 72 | def OnExit( self ) : 73 | pass 74 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/maintool_lowpoly.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import bmesh 18 | import bpy_extras 19 | import collections 20 | import copy 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 25 | from .subtool import * 26 | from .subtool_makepoly import * 27 | from .subtool_knife import * 28 | from .subtool_edge_slice import * 29 | from .subtool_edgeloop_cut import * 30 | from .subtool_edge_extrude import * 31 | from .subtool_vert_extrude import * 32 | from .subtool_move import * 33 | from .subtool_fin_slice import * 34 | from .subtool_polypen import * 35 | 36 | class MainToolLowPoly(MainTool) : 37 | name = "Make Polygon" 38 | 39 | def __init__(self,op,currentTarget, button) : 40 | super().__init__(op,currentTarget, button , no_hold = True ) 41 | 42 | @staticmethod 43 | def LMBEventCallback(self , event ): 44 | self.debugStr = str(event.type) 45 | 46 | if event.type == MBEventType.Release : 47 | self.isExit = True 48 | 49 | elif event.type == MBEventType.Click or event.type == MBEventType.LongClick : 50 | if self.currentTarget.isVert or self.currentTarget.isEmpty or self.currentTarget.isEdge: 51 | self.SetSubTool( SubToolMakePoly(self.operator,self.currentTarget , self.mouse_pos ) ) 52 | 53 | elif event.type == MBEventType.Drag or event.type == MBEventType.LongPressDrag : 54 | if self.currentTarget.isNotEmpty : 55 | self.SetSubTool( SubToolMove(self.operator,self.currentTarget , self.mouse_pos ) ) 56 | else : 57 | self.isExit = self.do_empty_space(event) 58 | 59 | @classmethod 60 | def DrawHighlight( cls , gizmo , element ) : 61 | if element != None and gizmo.bmo != None : 62 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.highlight_color , gizmo.preferences , False ) 63 | return None 64 | 65 | def OnDraw( self , context ) : 66 | pass 67 | 68 | def OnDraw3D( self , context ) : 69 | self.currentTarget.Draw( self.bmo.obj , self.color_highlight() , self.preferences ) 70 | 71 | def OnExit( self ) : 72 | pass 73 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import blf 16 | import math 17 | import mathutils 18 | import bmesh 19 | from enum import Enum , auto 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils.mouse_event_util import ButtonEventUtil, MBEventType 24 | import time 25 | from ..QMesh import * 26 | 27 | class SubToolRoot : 28 | name = "None" 29 | __timer_handle = None 30 | 31 | def __init__(self,op, button = None) : 32 | self.operator = op 33 | self.bmo : QMesh = op.bmo 34 | self.debugStr = "" 35 | self.subTool = [] 36 | self.__enterySubTool = None 37 | self.step = 0 38 | self.mouse_pos = mathutils.Vector((0,0)) 39 | self.preferences = op.preferences 40 | self.activeSubTool = None 41 | self.buttonType = button 42 | 43 | @staticmethod 44 | def Check( root ,target ) : 45 | return True 46 | 47 | def Active(self) : 48 | return self if self.activeSubTool == None else self.activeSubTool 49 | 50 | @classmethod 51 | def GetCursor(cls) : 52 | return 'DEFAULT' 53 | 54 | def CurrentCursor( self ) : 55 | if self.activeSubTool is None : 56 | return self.GetCursor() 57 | return self.activeSubTool.CurrentCursor() 58 | 59 | def SetSubTool( self , subTool ) : 60 | if isinstance( subTool , list) : 61 | self.__enterySubTool = subTool 62 | else : 63 | self.__enterySubTool = [ subTool ] 64 | 65 | def OnInit( self , context ) : 66 | pass 67 | 68 | def OnExit( self ) : 69 | pass 70 | 71 | def OnForcus( self , context , event ) : 72 | return True 73 | 74 | def OnUpdate( self , context , event ) : 75 | return 'FINISHED' 76 | 77 | def OnDraw( self , context ) : 78 | pass 79 | 80 | def OnDraw3D( self , context ) : 81 | pass 82 | 83 | def Update( self , context , event ) : 84 | 85 | ret = None 86 | self.mouse_pos = mathutils.Vector((event.mouse_region_x, event.mouse_region_y)) 87 | 88 | if self.__enterySubTool != None : 89 | self.subTool = self.__enterySubTool 90 | self.__enterySubTool = None 91 | for subTool in self.subTool : 92 | self.OnEnterSubTool( context , subTool) 93 | 94 | self.activeSubTool = None 95 | if self.subTool : 96 | for subTool in self.subTool : 97 | ret = subTool.Update(context , event) 98 | if ret == 'RUNNING_MODAL' : 99 | self.activeSubTool = subTool 100 | break 101 | elif ret == 'FINISHED' : 102 | break 103 | elif ret == 'PASS_THROUGH' : 104 | ret = None 105 | 106 | if ret == 'FINISHED' : 107 | for subTool in self.subTool : 108 | subTool.OnExit() 109 | self.OnExitSubTool( context , subTool) 110 | 111 | if ret == 'PASS_THROUGH' : 112 | ret = 'RUNNING_MODAL' 113 | 114 | if ret == None : 115 | if self.OnForcus(context , event) : 116 | ret = self.OnUpdate(context,event) 117 | else : 118 | return 'PASS_THROUGH' 119 | 120 | if ret != 'RUNNING_MODAL' : 121 | self.subTool = [] 122 | self.OnExit() 123 | 124 | self.step += 1 125 | return ret 126 | 127 | def check_animated( self , context ) : 128 | if self.activeSubTool : 129 | return self.activeSubTool.is_animated(context) 130 | else : 131 | return self.is_animated(context) 132 | return False 133 | 134 | def is_animated( self , context ) : 135 | return False 136 | 137 | def Draw2D( self , context ) : 138 | if self.activeSubTool : 139 | self.activeSubTool.Draw2D(context ) 140 | else : 141 | self.OnDraw(context) 142 | 143 | def Draw3D( self , context ) : 144 | if self.activeSubTool : 145 | self.activeSubTool.Draw3D(context ) 146 | else : 147 | self.OnDraw3D(context) 148 | 149 | def OnEnterSubTool( self ,context,subTool ): 150 | pass 151 | 152 | def OnExitSubTool( self ,context,subTool ): 153 | return 'RUNNING_MODAL' 154 | 155 | def color_highlight( self , alpha = 1.0 ) : 156 | col = self.preferences.highlight_color 157 | return (col[0],col[1],col[2],col[3] * alpha) 158 | 159 | def color_create( self , alpha = 1.0 ) : 160 | col = self.preferences.makepoly_color 161 | return (col[0],col[1],col[2],col[3] * alpha) 162 | 163 | def color_split( self , alpha = 1.0 ) : 164 | col = self.preferences.split_color 165 | return (col[0],col[1],col[2],col[3] * alpha) 166 | 167 | def color_delete( self ,alpha = 1.0 ) : 168 | col = self.preferences.delete_color 169 | return (col[0],col[1],col[2],col[3] * alpha) 170 | 171 | @classmethod 172 | def DrawHighlight( cls , gizmo , element ) : 173 | if element != None and gizmo.bmo != None : 174 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.highlight_color , gizmo.preferences ) 175 | return None 176 | 177 | @classmethod 178 | def UpdateHighlight( cls , gizmo , element ) : 179 | if gizmo.currentElement.element != element.element : 180 | return True 181 | elif element.isEdge : 182 | if element.coord != gizmo.currentElement.coord : 183 | return True 184 | return False 185 | 186 | @classmethod 187 | def recive_event( cls , gizmo , context , event ) : 188 | pass 189 | 190 | class MainTool(SubToolRoot) : 191 | def __init__(self,op,currentTarget, button , no_hold = False ) : 192 | super().__init__(op, button) 193 | self.currentTarget = currentTarget 194 | self.LMBEvent = ButtonEventUtil( button ,self, self.LMBEventCallback , op , True , no_hold ) 195 | self.isExit = False 196 | 197 | def is_animated( self , context ) : 198 | return self.LMBEvent.is_animated() 199 | 200 | @staticmethod 201 | def LMBEventCallback(self , event ): 202 | pass 203 | 204 | def OnUpdate( self , context , event ) : 205 | if self.isExit : 206 | return 'FINISHED' 207 | 208 | self.LMBEvent.Update(context,event) 209 | 210 | return 'RUNNING_MODAL' 211 | 212 | def OnEnterSubTool( self ,context,subTool ): 213 | self.currentTarget = ElementItem.Empty() 214 | self.LMBEvent.Reset(context) 215 | 216 | def OnExitSubTool( self ,context,subTool ): 217 | self.currentTarget = ElementItem.Empty() 218 | 219 | def do_empty_space( self , event ) : 220 | if self.preferences.space_drag_op == "ORBIT" : 221 | bpy.ops.view3d.rotate('INVOKE_DEFAULT', use_cursor_init=True) 222 | return True 223 | elif self.preferences.space_drag_op == "PAN" : 224 | bpy.ops.view3d.move('INVOKE_DEFAULT', use_cursor_init=True) 225 | return True 226 | elif self.preferences.space_drag_op == "DOLLY" : 227 | bpy.ops.view3d.zoom('INVOKE_DEFAULT', use_cursor_init=True) 228 | return True 229 | elif self.preferences.space_drag_op == "KNIFE" : 230 | self.SetSubTool( SubToolKnife(self.operator,self.currentTarget , self.LMBEvent.PressPos ) ) 231 | elif self.preferences.space_drag_op == "SELECT_BOX" : 232 | bpy.context.window.cursor_warp( event.PressPrevPos.x , event.PressPrevPos.y ) 233 | bpy.ops.view3d.select_box('INVOKE_DEFAULT' ,wait_for_input=False, mode='SET') 234 | bpy.context.window.cursor_warp( event.event.mouse_prev_x ,event.event.mouse_prev_y ) 235 | return True 236 | elif self.preferences.space_drag_op == "SELECT_LASSO" : 237 | bpy.context.window.cursor_warp( event.PressPrevPos.x , event.PressPrevPos.y ) 238 | bpy.ops.view3d.select_lasso('INVOKE_DEFAULT' , path = [], mode='SET') 239 | bpy.context.window.cursor_warp( event.event.mouse_prev_x ,event.event.mouse_prev_y ) 240 | return True 241 | 242 | return True 243 | 244 | @classmethod 245 | def UpdateHighlight( cls , gizmo , element ) : 246 | return True 247 | 248 | @staticmethod 249 | def pick_element( qmesh , location , preferences ) : 250 | element = qmesh.PickElement( location , preferences.distance_to_highlight ) 251 | element.set_snap_div( preferences.loopcut_division ) 252 | return element 253 | 254 | 255 | class SubTool(SubToolRoot) : 256 | def __init__( self, op ) : 257 | super().__init__(op ) 258 | 259 | class SubToolEx(SubTool) : 260 | def __init__( self, root ) : 261 | super().__init__( root.operator ) 262 | self.rootTool = root 263 | self.currentTarget = root.currentTarget 264 | self.startMousePos = root.mouse_pos.copy() 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_brush_delete.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubToolEx 26 | from ..utils.dpi import * 27 | 28 | class SubToolBrushDelete(SubToolEx) : 29 | name = "DeleteBrushTool" 30 | 31 | def __init__(self, event , root ) : 32 | super().__init__( root ) 33 | self.radius = self.preferences.brush_size * dpm() 34 | self.strength = self.preferences.brush_strength 35 | self.mirror_tbl = {} 36 | matrix = self.bmo.obj.matrix_world 37 | self.remove_faces = self.collect_faces( bpy.context , self.startMousePos ) 38 | if self.bmo.is_mirror_mode : 39 | mirror = { self.bmo.find_mirror( f ) for f in self.remove_faces } 40 | mirror = { m for m in mirror if m != None } 41 | self.remove_faces = self.remove_faces | mirror 42 | 43 | @staticmethod 44 | def Check( root , target ) : 45 | return True 46 | 47 | @classmethod 48 | def DrawHighlight( cls , gizmo , element ) : 49 | def Draw() : 50 | radius = gizmo.preferences.brush_size * dpm() 51 | strength = gizmo.preferences.brush_strength 52 | color = gizmo.preferences.delete_color 53 | with draw_util.push_pop_projection2D() : 54 | draw_util.draw_circle2D( gizmo.mouse_pos , radius * strength , color = color, fill = False , subdivide = 64 , dpi= False ) 55 | draw_util.draw_circle2D( gizmo.mouse_pos , radius , color = color, fill = False , subdivide = 64 , dpi= False ) 56 | return Draw 57 | 58 | def OnUpdate( self , context , event ) : 59 | if event.type == 'MOUSEMOVE': 60 | faces = self.collect_faces( context , self.mouse_pos ) 61 | if self.bmo.is_mirror_mode : 62 | mirror = { self.bmo.find_mirror( f ) for f in faces if f not in self.remove_faces } 63 | mirror = { m for m in mirror if m != None } 64 | self.remove_faces = self.remove_faces | mirror 65 | self.remove_faces = self.remove_faces | faces 66 | 67 | elif event.type == self.rootTool.buttonType : 68 | if event.value == 'RELEASE' : 69 | if self.remove_faces : 70 | self.bmo.delete_faces( list( self.remove_faces ) ) 71 | self.bmo.UpdateMesh() 72 | return 'FINISHED' 73 | return 'CANCELLED' 74 | elif event.value == 'RELEASE' : 75 | self.repeat = False 76 | 77 | return 'RUNNING_MODAL' 78 | 79 | def OnDraw( self , context ) : 80 | color = self.preferences.delete_color 81 | draw_util.draw_circle2D( self.mouse_pos , self.radius , color , fill = False , subdivide = 64 , dpi= False , width = 1.0 ) 82 | 83 | def OnDraw3D( self , context ) : 84 | alpha = self.preferences.highlight_face_alpha 85 | vertex_size = self.preferences.highlight_vertex_size 86 | width = self.preferences.highlight_line_width 87 | color = self.preferences.delete_color 88 | draw_util.drawElementsHilight3D( self.bmo.obj , self.remove_faces , vertex_size , width , alpha , color ) 89 | 90 | def collect_faces( self , context , coord ) : 91 | radius = self.radius 92 | bm = self.bmo.bm 93 | 94 | select_stack = SelectStack( context , bm ) 95 | select_stack.push() 96 | select_stack.select_mode(False,False,True) 97 | bpy.ops.view3d.select_circle( x = coord.x , y = coord.y , radius = radius , wait_for_input=False, mode='SET' ) 98 | faces = { f for f in self.bmo.bm.faces if f.select } 99 | select_stack.pop() 100 | return faces 101 | 102 | @classmethod 103 | def GetCursor(cls) : 104 | return 'ERASER' 105 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_brush_move.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubToolEx 26 | from ..utils.dpi import * 27 | 28 | class SubToolBrushMove(SubToolEx) : 29 | name = "MoveBrushTool" 30 | 31 | def __init__(self, event , root ) : 32 | super().__init__( root ) 33 | self.radius = self.preferences.brush_size * dpm() 34 | self.strength = self.preferences.brush_strength 35 | self.mirror_tbl = {} 36 | matrix = self.bmo.obj.matrix_world 37 | self.verts = self.CollectVerts( bpy.context , self.startMousePos ) 38 | 39 | if self.bmo.is_mirror_mode : 40 | self.mirrors = { vert : self.bmo.find_mirror( vert ) for vert in self.verts } 41 | else : 42 | self.mirrors = {} 43 | 44 | @staticmethod 45 | def Check( root , target ) : 46 | return True 47 | 48 | def OnUpdate( self , context , event ) : 49 | if event.type == 'MOUSEMOVE': 50 | self.UpdateVerts(context) 51 | elif event.type == self.rootTool.buttonType : 52 | if event.value == 'RELEASE' : 53 | if self.verts : 54 | self.bmo.UpdateMesh() 55 | return 'FINISHED' 56 | return 'CANCELLED' 57 | elif event.value == 'RELEASE' : 58 | self.repeat = False 59 | 60 | return 'RUNNING_MODAL' 61 | 62 | @classmethod 63 | def DrawHighlight( cls , gizmo , element ) : 64 | def Draw() : 65 | radius = gizmo.preferences.brush_size * dpm() 66 | strength = gizmo.preferences.brush_strength 67 | with draw_util.push_pop_projection2D() : 68 | draw_util.draw_circle2D( gizmo.mouse_pos , radius * strength , color = (1,0.25,0.25,0.25), fill = False , subdivide = 64 , dpi= False ) 69 | draw_util.draw_circle2D( gizmo.mouse_pos , radius , color = (1,1,1,0.5), fill = False , subdivide = 64 , dpi= False ) 70 | return Draw 71 | 72 | def OnDraw( self , context ) : 73 | radius = self.preferences.brush_size * dpm() 74 | strength = self.preferences.brush_strength 75 | 76 | draw_util.draw_circle2D( self.mouse_pos , radius * strength , color = (1,0.25,0.25,0.25), fill = False , subdivide = 64 , dpi= False ) 77 | draw_util.draw_circle2D( self.startMousePos , self.radius , color = (0.75,0.75,1,1), fill = False , subdivide = 64 , dpi= False , width = 1.0 ) 78 | 79 | def OnDraw3D( self , context ) : 80 | pass 81 | 82 | def CollectVerts( self , context , coord ) : 83 | rv3d = context.region_data 84 | region = context.region 85 | halfW = region.width / 2.0 86 | halfH = region.height / 2.0 87 | matrix_world = self.bmo.obj.matrix_world 88 | matrix = rv3d.perspective_matrix @ matrix_world 89 | radius = self.radius 90 | bm = self.bmo.bm 91 | verts = bm.verts 92 | 93 | select_stack = SelectStack( context , bm ) 94 | 95 | select_stack.push() 96 | select_stack.select_mode(True,False,False) 97 | bpy.ops.view3d.select_circle( x = coord.x , y = coord.y , radius = radius , wait_for_input=False, mode='SET' ) 98 | # bm.select_flush(False) 99 | 100 | is_target = QSnap.is_target 101 | new_vec = mathutils.Vector 102 | pw = (self.strength * self.strength ) * 8 103 | 104 | def ProjVert( vt ) : 105 | co = vt.co 106 | if not is_target(matrix_world @ co) : 107 | return None 108 | 109 | pv = matrix @ co.to_4d() 110 | w = pv.w 111 | if w < 0.0 : 112 | return None 113 | p = new_vec( (pv.x * halfW / w + halfW , pv.y * halfH / w + halfH ) ) 114 | r = (coord - p).length 115 | if r > radius : 116 | return None 117 | 118 | x = (radius - r) / radius 119 | r = (1-x) ** pw 120 | return ( p , r , matrix_world @ co , co ) 121 | 122 | coords = { vert : ProjVert(vert) for vert in verts if vert.select } 123 | 124 | select_stack.pop() 125 | 126 | return { v : x for v,x in coords.items() if x } 127 | 128 | def UpdateVerts( self , context ) : 129 | is_fix_zero = self.preferences.fix_to_x_zero or self.bmo.is_mirror_mode 130 | region = context.region 131 | rv3d = context.region_data 132 | move = self.mouse_pos - self.startMousePos 133 | matrix = self.bmo.obj.matrix_world 134 | matrix_inv = self.bmo.obj.matrix_world.inverted() 135 | region_2d_to_location_3d = pqutil.region_2d_to_location_3d 136 | is_x_zero_pos = self.bmo.is_x_zero_pos 137 | zero_pos = self.bmo.zero_pos 138 | mirror_pos = self.bmo.mirror_pos 139 | 140 | for v,(p,r,co,orig) in self.verts.items() : 141 | coord = p + move 142 | x = region_2d_to_location_3d( region = region , rv3d = rv3d , coord = coord , depth_location = co) 143 | x = co.lerp( x , 1 - r ) 144 | x = QSnap.adjust_point( x ) 145 | x = matrix_inv @ x 146 | if is_fix_zero and is_x_zero_pos(orig) : 147 | x.x = 0 148 | v.co = x 149 | 150 | if self.bmo.is_mirror_mode : 151 | for vert , mirror in self.mirrors.items() : 152 | if mirror != None : 153 | if mirror in self.verts.keys() : 154 | ms = self.verts[mirror][1] 155 | vs = self.verts[vert][1] 156 | if vs <= ms : 157 | mirror.co = mirror_pos(vert.co) 158 | else : 159 | vert.co = mirror_pos(mirror.co) 160 | else : 161 | mirror.co = mirror_pos(vert.co) 162 | 163 | self.bmo.UpdateMesh() 164 | 165 | @classmethod 166 | def GetCursor(cls) : 167 | return 'SCROLL_XY' -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_brush_relax.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubToolEx 26 | from ..utils.dpi import * 27 | 28 | class SubToolBrushRelax(SubToolEx) : 29 | name = "RelaxBrushTool" 30 | 31 | def __init__(self, event , root) : 32 | super().__init__(root) 33 | self.radius = self.preferences.brush_size * dpm() 34 | self.occlusion_tbl = {} 35 | self.mirror_tbl = {} 36 | self.dirty = False 37 | if self.currentTarget.isEmpty or ( self.currentTarget.isEdge and self.currentTarget.element.is_boundary ) : 38 | self.effective_boundary = True 39 | else : 40 | self.effective_boundary = False 41 | 42 | @staticmethod 43 | def Check( root , target ) : 44 | return True 45 | 46 | @classmethod 47 | def DrawHighlight( cls , gizmo , element ) : 48 | def Draw() : 49 | radius = gizmo.preferences.brush_size * dpm() 50 | strength = gizmo.preferences.brush_strength 51 | with draw_util.push_pop_projection2D() : 52 | draw_util.draw_circle2D( gizmo.mouse_pos , radius * strength , color = (1,0.25,0.25,0.25), fill = False , subdivide = 64 , dpi= False ) 53 | draw_util.draw_circle2D( gizmo.mouse_pos , radius , color = (1,1,1,0.5), fill = False , subdivide = 64 , dpi= False ) 54 | return Draw 55 | 56 | def OnUpdate( self , context , event ) : 57 | if event.type == 'MOUSEMOVE': 58 | self.DoRelax( context ,self.mouse_pos ) 59 | elif event.type == self.rootTool.buttonType : 60 | if event.value == 'RELEASE' : 61 | if self.dirty : 62 | self.bmo.UpdateMesh() 63 | return 'FINISHED' 64 | return 'CANCELLED' 65 | elif event.value == 'RELEASE' : 66 | self.repeat = False 67 | 68 | return 'RUNNING_MODAL' 69 | 70 | def OnDraw( self , context ) : 71 | width = 2.0 if self.effective_boundary else 1.0 72 | draw_util.draw_circle2D( self.mouse_pos , self.radius , color = (1,1,1,1), fill = False , subdivide = 64 , dpi= False , width = width ) 73 | pass 74 | 75 | def OnDraw3D( self , context ) : 76 | pass 77 | 78 | def CollectVerts( self , context , coord ) : 79 | rv3d = context.region_data 80 | region = context.region 81 | halfW = region.width / 2.0 82 | halfH = region.height / 2.0 83 | matrix_world = self.bmo.obj.matrix_world 84 | matrix = rv3d.perspective_matrix @ matrix_world 85 | radius = self.radius 86 | bm = self.bmo.bm 87 | verts = bm.verts 88 | 89 | select_stack = SelectStack( context , bm ) 90 | 91 | select_stack.push() 92 | select_stack.select_mode(True,False,False) 93 | bpy.ops.view3d.select_circle( x = coord.x , y = coord.y , radius = radius , wait_for_input=False, mode='SET' ) 94 | # bm.select_flush(False) 95 | 96 | occlusion_tbl_get = self.occlusion_tbl.get 97 | is_target = QSnap.is_target 98 | new_vec = mathutils.Vector 99 | def ProjVert( vt ) : 100 | co = vt.co 101 | is_occlusion = occlusion_tbl_get(vt) 102 | if is_occlusion == None : 103 | is_occlusion = is_target(matrix_world @ co) 104 | self.occlusion_tbl[vt] = is_occlusion 105 | 106 | if not is_occlusion : 107 | return None 108 | 109 | pv = matrix @ co.to_4d() 110 | w = pv.w 111 | if w < 0.0 : 112 | return None 113 | p = new_vec( (pv.x * halfW / w + halfW , pv.y * halfH / w + halfH ) ) 114 | r = (coord - p).length 115 | if r > radius : 116 | return None 117 | 118 | x = (radius - r) / radius 119 | return ( vt , x ** 2 , co.copy()) 120 | 121 | coords = [ ProjVert(vert) for vert in verts if vert.select ] 122 | 123 | select_stack.pop() 124 | 125 | return { c[0]: [c[1],c[2]] for c in coords if c != None } 126 | 127 | def MirrorVert( self , context , coords ) : 128 | # ミラー頂点を検出 129 | find_mirror = self.bmo.find_mirror 130 | mirrors = { vert : find_mirror( vert ) for vert , coord in coords.items() } 131 | 132 | # 重複する場合は 133 | for (vert , coord) , mirror in zip( coords.items() , mirrors.values() ) : 134 | if mirror != None : 135 | cur = coord[0] 136 | dst = coords[mirror][0] 137 | coord[0] = cur if dst <= cur else dst 138 | 139 | # 重複しないものを列挙 140 | mirrors = { vert : [coords[vert][0] , mirror.co.copy() ] for vert , mirror in mirrors.items() if mirror != None and mirror not in coords } 141 | 142 | coords.update(mirrors) 143 | return coords 144 | 145 | def DoRelax( self , context , coord ) : 146 | is_fix_zero = self.preferences.fix_to_x_zero or self.bmo.is_mirror_mode 147 | coords = self.CollectVerts( context, coord ) 148 | if coords : 149 | self.dirty = True 150 | if self.bmo.is_mirror_mode : 151 | mirrors = { vert : self.bmo.find_mirror( vert ) for vert , coord in coords.items() } 152 | 153 | if not self.effective_boundary : 154 | bmesh.ops.smooth_vert( self.bmo.bm , verts = [ c for c in coords.keys() if not c.is_boundary ] , factor = self.preferences.brush_strength , 155 | mirror_clip_x = is_fix_zero, mirror_clip_y = False, mirror_clip_z = False, clip_dist = 0.0001 , 156 | use_axis_x = True, use_axis_y = True, use_axis_z = True) 157 | else : 158 | boundary = { c for c in coords.keys() if c.is_boundary } 159 | 160 | result = {} 161 | for v in boundary : 162 | if len(v.link_faces) != 1 : 163 | tv = mathutils.Vector( (0,0,0) ) 164 | le = [ e.other_vert( v ).co for e in v.link_edges if e.is_boundary ] 165 | for te in le : 166 | tv = tv + te 167 | result[v] = tv * (1/ len(le) ) 168 | for v , co in result.items() : 169 | v.co = co 170 | 171 | bmesh.ops.smooth_vert( self.bmo.bm , verts = list( coords.keys() - boundary ) , factor = self.preferences.brush_strength , 172 | mirror_clip_x = is_fix_zero, mirror_clip_y = False, mirror_clip_z = False, clip_dist = 0.0001 , 173 | use_axis_x = True, use_axis_y = True, use_axis_z = True) 174 | 175 | # bmesh.ops.smooth_laplacian_vert(self.bmo.bm , verts = hits , lambda_factor = 1.0 , lambda_border = 0.0 , 176 | # use_x = True, use_y = True, use_z = True, preserve_volume = False ) 177 | 178 | matrix_world = self.bmo.obj.matrix_world 179 | is_x_zero_pos = self.bmo.is_x_zero_pos 180 | zero_pos = self.bmo.zero_pos 181 | mirror_pos = self.bmo.mirror_pos 182 | # matrix_world_inv = matrix_world.inverted() 183 | for v , (f,orig) in coords.items() : 184 | p = QSnap.adjust_local( matrix_world , v.co , is_fix_zero ) 185 | s = orig.lerp( p , f ) 186 | if is_fix_zero and is_x_zero_pos(s) : 187 | s = zero_pos(s) 188 | v.co = s 189 | 190 | if self.bmo.is_mirror_mode : 191 | for vert , mirror in mirrors.items() : 192 | if mirror != None : 193 | if mirror in coords : 194 | ms = coords[mirror][0] 195 | vs = coords[vert][0] 196 | if vs >= ms : 197 | mirror.co = mirror_pos(vert.co) 198 | else : 199 | vert.co = mirror_pos(mirror.co) 200 | else : 201 | mirror.co = mirror_pos(vert.co) 202 | 203 | # self.bmo.bm.normal_update() 204 | # self.bmo.obj.data.update_gpu_tag() 205 | # self.bmo.obj.data.update_tag() 206 | # self.bmo.obj.update_from_editmode() 207 | # self.bmo.obj.update_tag() 208 | bmesh.update_edit_mesh(self.bmo.obj.data , loop_triangles = False,destructive = False ) 209 | 210 | @classmethod 211 | def GetCursor(cls) : 212 | return 'CROSSHAIR' -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_brush_size.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubToolEx 26 | from ..utils.dpi import * 27 | 28 | class SubToolBrushSize(SubToolEx) : 29 | name = "BrushSizeTool" 30 | 31 | def __init__(self, event , root ) : 32 | super().__init__(root) 33 | self.preMousePos = self.startMousePos 34 | self.start_radius = self.preferences.brush_size * dpm() 35 | self.radius = self.start_radius 36 | self.strength = self.preferences.brush_strength 37 | self.start_strength = self.strength 38 | self.PressPrevPos = mathutils.Vector( (event.mouse_prev_x , event.mouse_prev_y) ) 39 | 40 | def OnUpdate( self , context , event ) : 41 | if event.type == 'MOUSEMOVE': 42 | vec = self.mouse_pos - self.preMousePos 43 | self.preMousePos = self.mouse_pos 44 | nrm = vec.normalized() 45 | ang = 0.8 46 | if nrm.x > ang or nrm.x < -ang : 47 | self.radius = self.radius + vec.x 48 | self.radius = min( max( 50 , self.radius ) , 500 ) 49 | self.preferences.brush_size = self.radius / dpm() 50 | 51 | if nrm.y > ang or nrm.y < -ang : 52 | self.strength = self.strength + vec.y / self.radius 53 | self.strength = min( max( 0 , self.strength ) , 1 ) 54 | self.preferences.brush_strength = self.strength 55 | 56 | elif event.type == self.rootTool.buttonType : 57 | if event.value == 'RELEASE' : 58 | bpy.context.window.cursor_warp( self.PressPrevPos.x , self.PressPrevPos.y ) 59 | return 'FINISHED' 60 | 61 | return 'RUNNING_MODAL' 62 | 63 | def OnDraw( self , context ) : 64 | draw_util.draw_circle2D( self.startMousePos , self.radius * self.strength , color = (1,0.25,0.25,0.5), fill = False , subdivide = 64 , dpi= False ) 65 | draw_util.draw_circle2D( self.startMousePos , self.radius , color = (1,1,1,1), fill = False , subdivide = 64 , dpi= False ) 66 | draw_util.DrawFont( "Strenght = " + '{:.0f}'.format(self.strength * 100) , 10 , self.startMousePos , (0,0) ) 67 | draw_util.DrawFont( "Radius = " + '{:.0f}'.format(self.radius ) , 10 , self.startMousePos , (0,-8) ) 68 | 69 | def OnDraw3D( self , context ) : 70 | pass 71 | 72 | def resetMouse(self, context, event): 73 | context.window.cursor_warp(context.region.x + context.region.width / 2 - 0.5*(event.mouse_x - event.mouse_prev_x), \ 74 | context.region.y + context.region.height / 2 - 0.5*(event.mouse_y - event.mouse_prev_y)) 75 | 76 | @classmethod 77 | def GetCursor(cls) : 78 | return 'NONE' 79 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_delete.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubToolEx 26 | from ..utils.dpi import * 27 | 28 | class SubToolDelete(SubToolEx) : 29 | name = "DeleteTool" 30 | 31 | def __init__(self,root,currentTarget) : 32 | super().__init__( root ) 33 | self.currentTarget = currentTarget 34 | self.startTarget = currentTarget 35 | self.removes = ( [ currentTarget.element ] , [] ) 36 | 37 | @staticmethod 38 | def Check( root , target ) : 39 | return target.isNotEmpty 40 | 41 | def OnUpdate( self , context , event ) : 42 | if event.type == 'MOUSEMOVE': 43 | preTarget = self.currentTarget 44 | self.currentTarget = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight , elements = [self.startTarget.type_name] ) 45 | if self.currentTarget.isNotEmpty : 46 | vt = None 47 | if self.startTarget.isEdge : 48 | vt = self.bmo.highlight.check_hit_element_vert( self.startTarget.element , self.mouse_pos , self.preferences.distance_to_highlight * dpm()) 49 | ed = None 50 | if self.startTarget.isFace : 51 | ed = self.bmo.highlight.check_hit_element_edge( self.startTarget.element , self.mouse_pos , self.preferences.distance_to_highlight * dpm()) 52 | if vt : 53 | e , v = self.bmo.calc_edge_loop( self.startTarget.element ) 54 | self.removes = (e,v) 55 | self.currentTarget = self.startTarget 56 | elif ed : 57 | self.removes = ( self.bmo.calc_loop_face(ed) , [] ) 58 | elif self.startTarget.element != self.currentTarget.element and self.startTarget.type == self.currentTarget.type : 59 | self.removes = self.bmo.calc_shortest_pass( self.bmo.bm , self.startTarget.element , self.currentTarget.element ) 60 | else : 61 | self.removes = ([self.startTarget.element],[]) 62 | else : 63 | self.removes = ([self.startTarget.element],[]) 64 | elif event.type == self.rootTool.buttonType : 65 | if event.value == 'RELEASE' : 66 | self.RemoveElement(self.currentTarget ) 67 | return 'FINISHED' 68 | elif event.type == 'RIGHTMOUSE': 69 | if event.value == 'RELEASE' : 70 | return 'FINISHED' 71 | return 'RUNNING_MODAL' 72 | 73 | @classmethod 74 | def DrawHighlight( cls , gizmo , element ) : 75 | if element != None and gizmo.bmo != None : 76 | return element.DrawFunc( gizmo.bmo.obj , gizmo.preferences.delete_color , gizmo.preferences , marker = False , edge_pivot = False ) 77 | return None 78 | 79 | def OnDraw( self , context ) : 80 | pass 81 | 82 | def OnDraw3D( self , context ) : 83 | if self.removes[0] : 84 | alpha = self.preferences.highlight_face_alpha 85 | vertex_size = self.preferences.highlight_vertex_size 86 | width = self.preferences.highlight_line_width 87 | color = self.preferences.delete_color 88 | draw_util.drawElementsHilight3D( self.bmo.obj , self.removes[0] , vertex_size , width , alpha , color ) 89 | if self.bmo.is_mirror_mode : 90 | mirrors = [ self.bmo.find_mirror(m) for m in self.removes[0] ] 91 | mirrors = [ m for m in mirrors if m ] 92 | if mirrors : 93 | draw_util.drawElementsHilight3D( self.bmo.obj , mirrors , vertex_size , width , alpha * 0.5 , color ) 94 | 95 | if self.startTarget.element == self.currentTarget.element : 96 | self.startTarget.Draw( self.bmo.obj , self.preferences.delete_color , self.preferences , marker = False , edge_pivot = True ) 97 | else : 98 | self.startTarget.Draw( self.bmo.obj , self.preferences.delete_color , self.preferences , marker = False , edge_pivot = False ) 99 | if self.currentTarget.isNotEmpty : 100 | if self.startTarget.element != self.currentTarget.element : 101 | self.currentTarget.Draw( self.bmo.obj , self.preferences.delete_color , self.preferences , marker = False , edge_pivot = False ) 102 | 103 | @classmethod 104 | def GetCursor(cls) : 105 | return 'ERASER' 106 | 107 | def RemoveElement( self , element ) : 108 | def dissolve_edges( edges ) : 109 | single_edges = [ e for e in edges if len(e.link_faces) == 0 ] 110 | edges = [ e for e in edges if len(e.link_faces) > 0 ] 111 | 112 | if single_edges : 113 | self.bmo.delete_edges( list(single_edges) ) 114 | 115 | if all( e.is_boundary for e in edges ) : 116 | faces = set() 117 | for e in edges : 118 | for f in e.link_faces : 119 | faces.add(f) 120 | self.bmo.delete_faces( list(faces) ) 121 | else : 122 | self.bmo.dissolve_edges( edges , use_verts = False , use_face_split = False , dissolve_vert_angle=self.preferences.vertex_dissolve_angle ) 123 | 124 | if element.isNotEmpty : 125 | if self.removes[0] and self.removes[1] : 126 | self.bmo.do_edge_loop_cut( self.removes[0] , self.removes[1] ) 127 | elif element.isVert : 128 | edges = [ r for r in self.removes[0] if isinstance( r , bmesh.types.BMEdge ) ] 129 | if edges : 130 | dissolve_edges( edges ) 131 | else : 132 | self.bmo.dissolve_vert( element.element , False , False , dissolve_vert_angle=self.preferences.vertex_dissolve_angle ) 133 | elif element.isEdge : 134 | dissolve_edges( self.removes[0] ) 135 | elif element.isFace : 136 | self.bmo.delete_faces( self.removes[0] ) 137 | self.bmo.UpdateMesh() 138 | 139 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_edge_slide.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import bpy_extras 20 | import collections 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.dpi import * 25 | from .subtool import SubTool 26 | 27 | class SubToolEdgeSlide(SubTool) : 28 | name = "EdgeLoopSlideTool" 29 | 30 | def __init__(self,op, target : ElementItem ) : 31 | super().__init__(op) 32 | self.currentEdge = target.element 33 | self.edges , self.vetrs = self.bmo.findEdgeLoop( self.currentEdge ) 34 | l2w = self.bmo.local_to_world_pos 35 | t = [ [ l2w( e.other_vert(v).co) for e in self.vetrs[v] ] for v in self.currentEdge.verts ] 36 | v0 = l2w(self.currentEdge.verts[0].co) 37 | v1 = l2w(self.currentEdge.verts[1].co) 38 | rate = ( v0 - target.hitPosition ).length / ( v0 - v1 ).length 39 | self.center_pos = target.hitPosition 40 | self.target_poss = [ t[0][i].lerp( t[1][i] , rate ) for i in range( len(self.currentEdge.link_faces) ) ] 41 | self.initial_poss = { v : v.co.copy() for v in self.vetrs } 42 | self.other_poss = { v : [ e.other_vert(v).co.copy() for e in edges] for v,edges in self.vetrs.items() } 43 | self.rates = [0,0] 44 | 45 | def OnUpdate( self , context , event ) : 46 | if event.type == 'MOUSEMOVE': 47 | self.rates = self.calcRate( context , self.mouse_pos ) 48 | max_rate = max( self.rates ) 49 | if max_rate <= sys.float_info.epsilon : 50 | for vert in self.vetrs : 51 | vert.co = self.initial_poss[vert] 52 | else : 53 | for vert in self.vetrs : 54 | edges = self.vetrs[vert] 55 | for i in range( len(edges)) : 56 | if self.rates[i] >= sys.float_info.epsilon and self.rates[i] == max_rate : 57 | p = self.initial_poss[vert].lerp( self.other_poss[vert][i] , self.rates[i] ) 58 | vert.co = p 59 | self.bmo.UpdateMesh() 60 | 61 | elif event.type == 'RIGHTMOUSE' : 62 | if event.value == 'PRESS' : 63 | pass 64 | elif event.value == 'RELEASE' : 65 | pass 66 | elif event.type == 'LEFTMOUSE' : 67 | if event.value == 'RELEASE' : 68 | return 'FINISHED' 69 | return 'RUNNING_MODAL' 70 | 71 | def OnDraw( self , context ) : 72 | pass 73 | 74 | def calcRate( self , context , location ) : 75 | ray = pqutil.Ray.from_screen( context , location ) 76 | r = [ ray.hit_to_line( self.center_pos , p ) for p in self.target_poss ] 77 | return r 78 | 79 | def OnDraw3D( self , context ) : 80 | for t in self.target_poss : 81 | draw_util.draw_lines3D( context , [self.center_pos,t] , width = 1 , color = self.color_create() , primitiveType = 'LINES' , hide_alpha = 1 ) 82 | 83 | for (t,r) in zip( self.target_poss , self.rates ) : 84 | draw_util.draw_pivots3D( ( self.center_pos.lerp( t , r ) ,) , self.preferences.highlight_vertex_size / 2 , self.color_split(0.5) ) 85 | 86 | lines = [] 87 | for e in self.edges : 88 | lines.append( self.bmo.local_to_world_pos( e.verts[0].co ) ) 89 | lines.append( self.bmo.local_to_world_pos( e.verts[1].co ) ) 90 | draw_util.draw_lines3D( context , lines , width = 3 , color = self.color_highlight() , primitiveType = 'LINES' , hide_alpha = 1 ) 91 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_edgeloop_cut.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import bpy_extras 20 | import collections 21 | from ..utils import pqutil 22 | from ..utils import draw_util 23 | from ..QMesh import * 24 | from ..utils.dpi import * 25 | from .subtool import SubTool 26 | 27 | class SubToolEdgeloopCut(SubTool) : 28 | name = "EdgeLoopCutTool" 29 | 30 | def __init__(self,op, target : ElementItem ) : 31 | super().__init__(op) 32 | self.currentEdge = target 33 | self.is_forcus = False 34 | self.EdgeLoops = None 35 | self.VertLoops = None 36 | 37 | def Check( root ,target : ElementItem ) : 38 | if len( target.element.link_faces ) == 2 : 39 | return True 40 | return False 41 | 42 | def OnForcus( self , context , event ) : 43 | if event.type == 'MOUSEMOVE': 44 | self.is_forcus = False 45 | p0 = self.bmo.local_to_2d( self.currentEdge.element.verts[0].co ) 46 | p1 = self.bmo.local_to_2d( self.currentEdge.element.verts[1].co ) 47 | if self.bmo.is_snap2D( self.mouse_pos , p0 ) or self.bmo.is_snap2D( self.mouse_pos , p1 ) : 48 | self.is_forcus = True 49 | if self.EdgeLoops == None : 50 | self.EdgeLoops , self.VertLoops = self.bmo.calc_edge_loop( self.currentEdge.element ) 51 | return self.is_forcus 52 | 53 | def OnUpdate( self , context , event ) : 54 | if event.type == 'RIGHTMOUSE' : 55 | if event.value == 'PRESS' : 56 | pass 57 | elif event.value == 'RELEASE' : 58 | pass 59 | elif event.type == 'LEFTMOUSE' : 60 | if event.value == 'RELEASE' : 61 | if self.EdgeLoops != None : 62 | # bpy.ops.mesh.select_all(action='DESELECT') 63 | self.bmo.do_edge_loop_cut( self.EdgeLoops , self.VertLoops ) 64 | self.bmo.UpdateMesh() 65 | self.currentTarget = ElementItem.Empty() 66 | return 'FINISHED' 67 | return 'CANCELLED' 68 | return 'RUNNING_MODAL' 69 | 70 | def OnDraw( self , context ) : 71 | pass 72 | 73 | def OnDraw3D( self , context ) : 74 | if self.EdgeLoops != None : 75 | alpha = self.preferences.highlight_face_alpha 76 | vertex_size = self.preferences.highlight_vertex_size 77 | width = self.preferences.highlight_line_width 78 | color = self.color_delete() 79 | draw_util.drawElementsHilight3D( self.bmo.obj , self.EdgeLoops , vertex_size ,width,alpha, color ) 80 | 81 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_fin_slice.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from ..utils.dpi import * 26 | from .subtool import SubTool 27 | 28 | class SubToolFinSlice(SubTool) : 29 | name = "FinSliceTool" 30 | 31 | def __init__(self,op, target ) : 32 | super().__init__(op) 33 | self.currentTarget = target 34 | self.slice_rate = 0.0 35 | self.slice_dist = 0.0 36 | 37 | def OnForcus( self , context , event ) : 38 | if event.type == 'MOUSEMOVE': 39 | self.slice_rate , self.slice_dist = self.CalcRate(context,self.mouse_pos) 40 | return self.slice_rate > 0 41 | 42 | def OnUpdate( self , context , event ) : 43 | if event.type == 'RIGHTMOUSE' : 44 | return 'FINISHED' 45 | elif event.type == 'LEFTMOUSE' : 46 | if event.value == 'RELEASE' : 47 | if self.slice_rate == 1 : 48 | self.DoSplit() 49 | elif self.slice_rate > 0 : 50 | self.DoSlice() 51 | return 'FINISHED' 52 | return 'RUNNING_MODAL' 53 | 54 | 55 | def OnDraw( self , context ) : 56 | pass 57 | 58 | def OnDraw3D( self , context ) : 59 | self.currentTarget.Draw( self.bmo.obj , self.color_split() , self.preferences ) 60 | 61 | for edge in self.currentTarget.element.link_edges : 62 | v0 = self.bmo.local_to_world_pos(edge.verts[0].co) 63 | v1 = self.bmo.local_to_world_pos(edge.verts[1].co) 64 | draw_util.draw_lines3D( context , (v0,v1) , self.color_split(0.25) , self.preferences.highlight_line_width , 1.0 , primitiveType = 'LINES' ) 65 | if self.slice_rate == 1.0 : 66 | p = self.bmo.local_to_world_pos(edge.other_vert(self.currentTarget.element).co) 67 | draw_util.draw_pivots3D( ( p , ) , 1 , self.color_split() ) 68 | 69 | rate = self.slice_rate 70 | virts , edges = self.collect_geom( self.currentTarget.element , self.currentTarget.mirror ) 71 | 72 | for vert in virts : 73 | for face in vert.link_faces : 74 | links = [ e for e in face.edges if e in edges ] 75 | if len(links) == 2 : 76 | for v in virts : 77 | p0 = self.bmo.local_to_world_pos(v.co) 78 | if v in links[0].verts : 79 | p1 = self.bmo.local_to_world_pos(links[0].other_vert(v).co) 80 | v0 = p0 *(1-rate) + p1 * rate 81 | if v in links[1].verts : 82 | p1 = self.bmo.local_to_world_pos(links[1].other_vert(v).co) 83 | v1 = p0 *(1-rate) + p1 * rate 84 | draw_util.draw_lines3D( context , (v0,v1) , self.color_split() , self.preferences.highlight_line_width , 1.0 , primitiveType = 'LINES' ) 85 | 86 | def collect_geom( self , element , mirror ) : 87 | if self.bmo.is_mirror_mode and mirror is not None : 88 | s0 = set(element.link_edges) 89 | s1 = set(mirror.link_edges) 90 | edges = ( s0 | s1 ) - ( s0 & s1 ) 91 | virts = [ element, mirror] 92 | else : 93 | edges = element.link_edges 94 | virts = [element] 95 | return virts , edges 96 | 97 | def CalcRate( self , context , coord ): 98 | rate = 0.0 99 | dist = 0.0 100 | ray = pqutil.Ray.from_screen( context , coord ).world_to_object( self.bmo.obj ) 101 | d = self.preferences.distance_to_highlight* dpm() 102 | for edge in self.currentTarget.element.link_edges : 103 | r = pqutil.CalcRateEdgeRay( self.bmo.obj , context , edge , self.currentTarget.element , coord , ray , d ) 104 | if r > rate : 105 | rate = r 106 | dist = d * (edge.verts[0].co - edge.verts[1].co).length 107 | return rate , dist 108 | 109 | def DoSplit( self ) : 110 | verts , edges = self.collect_geom( self.currentTarget.element , self.currentTarget.mirror ) 111 | 112 | for vert in verts : 113 | for face in vert.link_faces : 114 | links = [ e for e in face.edges if e in edges ] 115 | if len(links) == 2 : 116 | v0 = links[0].other_vert(vert) 117 | v1 = links[1].other_vert(vert) 118 | 119 | if self.bmo.bm.edges.get( (v0 , v1) ) == None : 120 | bmesh.utils.face_split( face , v0 , v1 ) 121 | self.bmo.UpdateMesh() 122 | 123 | def DoSlice( self ) : 124 | _slice = {} 125 | _edges = [] 126 | 127 | def append( vert , other_vert ) : 128 | for edge in vert.link_edges : 129 | if other_vert is not None : 130 | if edge not in other_vert.link_edges : 131 | _edges.append( edge ) 132 | else : 133 | _edges.append( edge ) 134 | _slice[ edge ] = self.slice_rate if ( edge.verts[0] == vert ) else (1.0 - self.slice_rate) 135 | 136 | if self.bmo.is_mirror_mode and self.currentTarget.mirror is not None : 137 | append( self.currentTarget.element , self.currentTarget.mirror ) 138 | append( self.currentTarget.mirror , self.currentTarget.element ) 139 | else : 140 | append( self.currentTarget.element , None ) 141 | 142 | ret = bmesh.ops.subdivide_edges( 143 | self.bmo.bm , 144 | edges = _edges , 145 | edge_percents = _slice , 146 | smooth = 0 , 147 | smooth_falloff = 'SMOOTH' , 148 | use_smooth_even = False , 149 | fractal = 0.0 , 150 | along_normal = 0.0 , 151 | cuts = 1 , 152 | quad_corner_type = 'STRAIGHT_CUT' , 153 | use_single_edge = False , 154 | use_grid_fill=False, 155 | use_only_quads = False , 156 | seed = 0 , 157 | use_sphere = False 158 | ) 159 | 160 | for e in ret['geom_inner'] : 161 | e.select_set(True) 162 | if QSnap.is_active() : 163 | QSnap.adjust_verts( self.bmo.obj , [ v for v in ret['geom_inner'] if isinstance( v , bmesh.types.BMVert ) ] , self.preferences.fix_to_x_zero ) 164 | 165 | self.bmo.UpdateMesh() 166 | 167 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_knife.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import blf 17 | import math 18 | import mathutils 19 | import bmesh 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import SubTool 26 | 27 | class SubToolKnife(SubTool) : 28 | name = "KnifeTool2" 29 | 30 | def __init__(self,op, currentElement : ElementItem ,startPos) : 31 | super().__init__(op) 32 | if currentElement.isNotEmpty and not currentElement.isFace : 33 | self.startPos = currentElement.coord 34 | else : 35 | self.startPos = startPos 36 | self.endPos = self.startPos 37 | self.cut_edges = {} 38 | self.cut_edges_mirror = {} 39 | self.startElement = currentElement 40 | self.goalElement = ElementItem.Empty() 41 | 42 | def OnUpdate( self , context , event ) : 43 | self.goalElement = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight, elements = ['EDGE','VERT'] ) 44 | if self.goalElement.isNotEmpty and not self.goalElement.isFace : 45 | self.goalElement.set_snap_div( self.preferences.loopcut_division ) 46 | self.endPos = self.goalElement.coord 47 | else : 48 | self.endPos = self.mouse_pos 49 | 50 | if event.type == 'MOUSEMOVE': 51 | if( self.startPos - self.endPos ).length > 2 : 52 | self.CalcKnife( context,self.startPos,self.endPos ) 53 | elif event.type == 'RIGHTMOUSE' : 54 | if event.value == 'RELEASE' : 55 | return 'FINISHED' 56 | elif event.type == 'LEFTMOUSE' : 57 | if event.value == 'RELEASE' : 58 | if self.cut_edges or self.cut_edges_mirror : 59 | self.DoKnife(context,self.startPos,self.endPos) 60 | self.bmo.UpdateMesh() 61 | return 'FINISHED' 62 | return 'CANCELLED' 63 | return 'RUNNING_MODAL' 64 | 65 | def OnDraw( self , context ) : 66 | draw_util.draw_lines2D( (self.startPos,self.endPos) , self.color_delete() , self.preferences.highlight_line_width ) 67 | 68 | def OnDraw3D( self , context ) : 69 | if self.goalElement.isNotEmpty : 70 | self.goalElement.Draw( self.bmo.obj , self.color_highlight() , self.preferences ) 71 | 72 | if self.cut_edges : 73 | draw_util.draw_pivots3D( list(self.cut_edges.values()) , 1 , self.color_delete() ) 74 | if self.cut_edges_mirror : 75 | draw_util.draw_pivots3D( list(self.cut_edges_mirror.values()) , 1 , self.color_delete(0.5) ) 76 | 77 | def CalcKnife( self ,context,startPos , endPos ) : 78 | slice_plane , plane0 , plane1 = self.make_slice_planes(context,startPos , endPos) 79 | self.cut_edges = self.calc_slice( slice_plane , plane0 , plane1 ) 80 | if self.bmo.is_mirror_mode : 81 | slice_plane.x_mirror() 82 | plane0.x_mirror() 83 | plane1.x_mirror() 84 | self.cut_edges_mirror = self.calc_slice( slice_plane , plane0 , plane1 ) 85 | 86 | def make_slice_planes( self , context,startPos , endPos ): 87 | slice_plane_world = pqutil.Plane.from_screen_slice( context,startPos , endPos ) 88 | slice_plane_object = slice_plane_world.world_to_object( self.bmo.obj ) 89 | 90 | ray0 = pqutil.Ray.from_screen( context , startPos ).world_to_object( self.bmo.obj ) 91 | vec0 = slice_plane_object.vector.cross(ray0.vector).normalized() 92 | ofs0 = vec0 * 0.001 93 | plane0 = pqutil.Plane( ray0.origin - ofs0 , vec0 ) 94 | ray1 = pqutil.Ray.from_screen( context ,endPos ).world_to_object( self.bmo.obj ) 95 | vec1 = slice_plane_object.vector.cross(ray1.vector).normalized() 96 | ofs1 = vec1 * 0.001 97 | plane1 = pqutil.Plane( ray1.origin + ofs1 , vec1 ) 98 | 99 | return slice_plane_object , plane0 , plane1 100 | 101 | def calc_slice( self ,slice_plane , plane0 , plane1 ) : 102 | edges = [ edge for edge in self.bmo.edges if edge.hide is False ] 103 | slice_plane_intersect_line = slice_plane.intersect_line 104 | plane0_distance_point = plane0.distance_point 105 | plane1_distance_point = plane1.distance_point 106 | epsilon = sys.float_info.epsilon 107 | 108 | def chk( edge ) : 109 | p0 = edge.verts[0].co 110 | p1 = edge.verts[1].co 111 | p = slice_plane_intersect_line( p0 , p1 ) 112 | 113 | if p != None : 114 | a0 = plane0_distance_point( p ) 115 | a1 = plane1_distance_point( p ) 116 | if (a0 > epsilon and a1 > epsilon ) or (a0 < -epsilon and a1 < -epsilon ): 117 | return None 118 | return p 119 | 120 | matrix = self.bmo.obj.matrix_world 121 | cut_edges = { edge : chk( edge ) for edge in edges } 122 | cut_edges = { e : matrix @ p for e,p in cut_edges.items() if p != None } 123 | # for add_edge in add_edges : 124 | # cut_edges[add_edge] = slice_plane_intersect_line( add_edge.verts[0].co , add_edge.verts[1].co ) 125 | return cut_edges 126 | 127 | def DoKnife( self ,context,startPos , endPos ) : 128 | bm = self.bmo.bm 129 | threshold = bpy.context.scene.tool_settings.double_threshold 130 | plane , plane0 , plane1 = self.make_slice_planes(context,startPos , endPos) 131 | faces = [ face for face in self.bmo.faces if not face.hide ] 132 | # if self.startElement.isVert : 133 | # for edge in self.startElement.element.link_edges : 134 | # self.cut_edges[edge] = self.startElement.coord 135 | # if self.goalElement.isVert : 136 | # for edge in self.goalElement.element.link_edges : 137 | # self.cut_edges[edge] = self.goalElement.coord 138 | elements = list(self.cut_edges.keys()) + faces 139 | 140 | ret = bmesh.ops.bisect_plane(bm,geom=elements,dist=threshold,plane_co= plane.origin ,plane_no= plane.vector ,use_snap_center=True,clear_outer=False,clear_inner=False) 141 | for e in ret['geom_cut'] : 142 | e.select_set(True) 143 | if QSnap.is_active() : 144 | QSnap.adjust_verts( self.bmo.obj , [ v for v in ret['geom_cut'] if isinstance( v , bmesh.types.BMVert ) ] , self.preferences.fix_to_x_zero ) 145 | 146 | if self.bmo.is_mirror_mode : 147 | slice_plane , plane0 , plane1 = self.make_slice_planes(context,startPos , endPos) 148 | slice_plane.x_mirror() 149 | plane0.x_mirror() 150 | plane1.x_mirror() 151 | self.bmo.UpdateMesh() 152 | cut_edges_mirror = self.calc_slice( slice_plane , plane0 , plane1 ) 153 | if cut_edges_mirror : 154 | faces = [ face for face in self.bmo.faces if face.hide is False ] 155 | elements = list(cut_edges_mirror.keys()) + faces[:] 156 | ret = bmesh.ops.bisect_plane(bm,geom=elements,dist=threshold,plane_co= slice_plane.origin ,plane_no= slice_plane.vector ,use_snap_center=False,clear_outer=False,clear_inner=False) 157 | for e in ret['geom_cut'] : 158 | e.select_set(True) 159 | if QSnap.is_active() : 160 | QSnap.adjust_verts( self.bmo.obj , [ v for v in ret['geom_cut'] if isinstance( v , bmesh.types.BMVert ) ] , self.preferences.fix_to_x_zero ) 161 | 162 | @classmethod 163 | def GetCursor(cls) : 164 | return 'KNIFE' 165 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_polypen.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import bpy_extras 20 | import collections 21 | import mathutils 22 | import copy 23 | from ..utils import pqutil 24 | from ..utils import draw_util 25 | from ..QMesh import * 26 | from ..utils.dpi import * 27 | from .subtool import SubTool 28 | from collections import namedtuple 29 | 30 | class SubToolPolyPen(SubTool) : 31 | name = "PolyPenTool" 32 | 33 | def __init__(self , op , target : ElementItem ) : 34 | super().__init__(op) 35 | self.currentEdge = target 36 | self.startPos = target.hitPosition.copy() 37 | self.targetPos = target.hitPosition.copy() 38 | self.startMousePos = copy.copy(target.coord) 39 | 40 | self.startEdge = target.element 41 | self.startData = [ self.CalcHead( target.verts ) ] 42 | self.endData = None 43 | 44 | @staticmethod 45 | def Check( root ,target ) : 46 | return target.isEdge and target.can_extrude() 47 | 48 | def OnUpdate( self , context , event ) : 49 | if event.type == 'MOUSEMOVE': 50 | dist = self.preferences.distance_to_highlight 51 | hitEdge = self.bmo.PickElement( self.mouse_pos , dist , edgering=True , backface_culling = True , elements=['EDGE'], ignore=[self.startEdge] ) 52 | if hitEdge.isEdge : 53 | self.endData = self.AdsorptionEdge( self.startData[-1].WorldPos[0] , self.startData[-1].WorldPos[1] , hitEdge.element ) 54 | else : 55 | while( True ) : 56 | sd = self.startData[-1] 57 | v = ( self.mouse_pos - sd.Center ) 58 | pos = sd.Center + v.normalized() * min( sd.Witdh , v.length ) 59 | self.endData = self.CalcEnd( pos ) 60 | self.ReCalcFin(pos) 61 | if self.startData != None and self.endData != None : 62 | if v.length >= sd.Witdh : 63 | self.MakePoly() 64 | continue 65 | break 66 | 67 | elif event.type == 'RIGHTMOUSE' : 68 | if event.value == 'PRESS' : 69 | pass 70 | elif event.value == 'RELEASE' : 71 | pass 72 | elif event.type == 'LEFTMOUSE' : 73 | if event.value == 'RELEASE' : 74 | if self.endData != None : 75 | if (self.startData[-1].Center - self.mouse_pos).length / self.startData[-1].Witdh > 0.2 : 76 | self.MakePoly() 77 | return 'FINISHED' 78 | return 'RUNNING_MODAL' 79 | 80 | def OnDraw( self , context ) : 81 | pass 82 | 83 | def OnDraw3D( self , context ) : 84 | startData = self.startData[-1] 85 | if startData != None and self.endData != None : 86 | alpha = (startData.Center - self.mouse_pos).length / startData.Witdh 87 | if alpha >= 0.2 : 88 | verts = [ startData.WorldPos[0] , startData.WorldPos[1] , self.endData.WorldPos[1] , self.endData.WorldPos[0] ] 89 | draw_util.draw_Poly3D( context , verts , color = self.color_create(alpha / 2) ) 90 | verts.append( verts[-1] ) 91 | draw_util.draw_lines3D( context , verts , color = self.color_create(alpha ) , width = 2 ) 92 | 93 | def CalcHead( self , verts , center = None ) : 94 | wpos = [ self.bmo.obj.matrix_world @ v.co for v in verts ] 95 | planes = [ pqutil.Plane.from_screen( bpy.context , v ) for v in wpos ] 96 | vpos = [ pqutil.location_3d_to_region_2d(v) for v in wpos ] 97 | if center == None : 98 | center = ( vpos[0] + vpos[1] ) / 2 99 | vec = ( vpos[0] - vpos[1] ) 100 | width = vec.length 101 | nrm = vec.normalized() 102 | 103 | perpendicular = mathutils.Vector( (0,0,1) ).cross( mathutils.Vector( (nrm.x,nrm.y,0) ) ).normalized() 104 | 105 | Ret = namedtuple('Ret', ('Verts','WorldPos', 'Plane' , 'ViewPos' , 'Center' , 'Witdh', 'Perpendicular' )) 106 | ret = Ret(Verts=verts[:],WorldPos=wpos,Plane=planes, ViewPos = vpos , Center = center , Witdh = width , Perpendicular = perpendicular ) 107 | return ret 108 | 109 | def CalcEnd( self , mouse_pos ) : 110 | Ret = namedtuple('Ret', ('WorldPos', 'ViewPos' , 'Center', 'Verts' )) 111 | 112 | start = self.startData[-1] 113 | context = bpy.context 114 | 115 | q = self.CalcRot( start , mouse_pos ) 116 | 117 | f = [ v - start.Center for v in start.ViewPos ] 118 | ft = [ q.to_matrix() @ v.to_3d() for v in f ] 119 | pt = [ mouse_pos + v.xy for v in ft ] 120 | rt = [ pqutil.Ray.from_screen( context , v ) for v in pt ] 121 | vt = [ p.intersect_ray( r ) for r,p in zip(rt , start.Plane) ] 122 | vt = [ QSnap.view_adjust( p ) for p in vt ] 123 | 124 | ret = Ret(WorldPos = vt , ViewPos = pt , Center = mouse_pos , Verts = [None,None] ) 125 | return ret 126 | 127 | 128 | def ReCalcFin( self , mouse_pos ) : 129 | if len( self.startData ) < 2 : 130 | return 131 | context = bpy.context 132 | finP = self.startData[-2] 133 | fin1 = self.startData[-1] 134 | 135 | q = self.CalcRot( finP , self.endData.Center ) 136 | 137 | f = [ v - finP.Center for v in finP.ViewPos ] 138 | ft = [ q.to_matrix() @ v.to_3d() for v in f ] 139 | pt = [ fin1.Center + v.xy for v in ft ] 140 | rt = [ pqutil.Ray.from_screen( context , v ) for v in pt ] 141 | vt = [ p.intersect_ray( r ) for r,p in zip(rt , finP.Plane) ] 142 | vt = [ QSnap.view_adjust( p ) for p in vt ] 143 | 144 | for v , p in zip( fin1.Verts , vt ) : 145 | if self.bmo.is_mirror_mode : 146 | mirror = self.bmo.find_mirror(v) 147 | 148 | self.bmo.set_positon( v , p , is_world = True ) 149 | 150 | if self.bmo.is_mirror_mode and mirror != None : 151 | self.bmo.set_positon( mirror , self.bmo.mirror_world_pos( p ) , is_world = True ) 152 | self.bmo.UpdateMesh() 153 | 154 | self.startData[-1] = self.CalcHead( fin1.Verts , fin1.Center ) 155 | 156 | def MakePoly( self ) : 157 | startData = self.startData[-1] 158 | verts = [ startData.Verts[0] , startData.Verts[1] ] 159 | nv = [] 160 | 161 | for p , vt in zip( self.endData.WorldPos[::-1] , self.endData.Verts[::-1] ) : 162 | if vt != None : 163 | v = vt 164 | else : 165 | v = self.bmo.AddVertexWorld( p ) 166 | verts.append( v ) 167 | nv.append( v ) 168 | 169 | normal = pqutil.getViewDir() 170 | 171 | face = self.bmo.AddFace( verts , normal ) 172 | face.normal_update() 173 | self.bmo.UpdateMesh() 174 | 175 | for e in face.edges : 176 | if set( e.verts ) == set( nv ) : 177 | self.startEdge = e 178 | self.startData.append( self.CalcHead( nv[::-1] ) ) 179 | self.endData = None 180 | 181 | 182 | def AdsorptionEdge( self , p0 , p1 , edge ) : 183 | Ret = namedtuple('Ret', ('WorldPos', 'ViewPos' , 'Center' , 'Verts' )) 184 | 185 | st0 = pqutil.location_3d_to_region_2d(p0) 186 | st1 = pqutil.location_3d_to_region_2d(p1) 187 | se0 = pqutil.location_3d_to_region_2d(self.bmo.local_to_world_pos(edge.verts[0].co)) 188 | se1 = pqutil.location_3d_to_region_2d(self.bmo.local_to_world_pos(edge.verts[1].co)) 189 | if (st0-se0).length + (st1-se1).length > (st0-se1).length + (st1-se0).length : 190 | wp = [ edge.verts[1] , edge.verts[0] ] 191 | else : 192 | wp = [ edge.verts[0] , edge.verts[1] ] 193 | 194 | vp = [ pqutil.location_3d_to_region_2d(v.co) for v in wp ] 195 | 196 | ret = Ret(WorldPos = [ self.bmo.obj.matrix_world @ v.co for v in wp] , ViewPos = vp , Center = (vp[0]+vp[1]) / 2 , Verts = wp ) 197 | 198 | return ret 199 | 200 | def CalcRot( self , start , v0 ) : 201 | vec = (v0 - start.Center ) 202 | nrm = vec.normalized() 203 | 204 | r0 = math.atan2( nrm.x , nrm.y ) 205 | if nrm.dot(start.Perpendicular) > 0 : 206 | r1 = math.atan2( start.Perpendicular.x , start.Perpendicular.y ) 207 | else : 208 | r1 = math.atan2( -start.Perpendicular.x , -start.Perpendicular.y ) 209 | 210 | q0 = mathutils.Quaternion( mathutils.Vector( (0,0,1) ) , r0 ) 211 | q1 = mathutils.Quaternion( mathutils.Vector( (0,0,1) ) , r1 ) 212 | 213 | q = q0.rotation_difference(q1) 214 | 215 | return q -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_seam.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import MainTool 26 | from ..utils.dpi import * 27 | 28 | class SubToolSeam(MainTool) : 29 | name = "Mark Seam" 30 | 31 | def __init__(self,op,currentTarget, button) : 32 | super().__init__(op,currentTarget, button , no_hold = True ) 33 | 34 | self.currentTarget = currentTarget 35 | self.startTarget = currentTarget 36 | self.removes = ( [ currentTarget.element ] , [] ) 37 | 38 | @staticmethod 39 | def Check( root , target ) : 40 | return target.isEdge or target.isVert 41 | 42 | @staticmethod 43 | def pick_element( qmesh , location , preferences ) : 44 | element = qmesh.PickElement( location , preferences.distance_to_highlight , elements = ["EDGE","VERT"] ) 45 | return element 46 | 47 | def OnUpdate( self , context , event ) : 48 | if event.type == 'MOUSEMOVE': 49 | preTarget = self.currentTarget 50 | self.currentTarget = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight , elements = ["VERT","EDGE"] ) 51 | if self.currentTarget.isNotEmpty : 52 | vt = None 53 | if self.startTarget.isEdge : 54 | vt = self.bmo.highlight.check_hit_element_vert( self.startTarget.element , self.mouse_pos , self.preferences.distance_to_highlight * dpm()) 55 | if vt : 56 | e = self.find_seam_loop( self.bmo , self.startTarget.element ) 57 | self.removes = (e,[]) 58 | self.currentTarget = self.startTarget 59 | elif self.startTarget.element != self.currentTarget.element : 60 | self.removes = self.bmo.calc_shortest_pass( self.bmo.bm , self.startTarget.element , self.currentTarget.element ) 61 | else : 62 | self.removes = ([self.startTarget.element],[]) 63 | else : 64 | self.removes = ([self.startTarget.element],[]) 65 | elif event.type == self.buttonType : 66 | if event.value == 'RELEASE' : 67 | self.SeamElement(self.currentTarget ) 68 | return 'FINISHED' 69 | elif event.type == 'RIGHTMOUSE': 70 | if event.value == 'RELEASE' : 71 | return 'FINISHED' 72 | return 'RUNNING_MODAL' 73 | 74 | @classmethod 75 | def DrawHighlight( cls , gizmo , element ) : 76 | if element != None and gizmo.bmo != None : 77 | if element.isEdge and element.element.seam : 78 | color = (0,0,0,1) 79 | else : 80 | color = bpy.context.preferences.themes["Default"].view_3d.edge_seam 81 | return element.DrawFunc( gizmo.bmo.obj , (color[0],color[1],color[2],1) , gizmo.preferences , marker = False , edge_pivot = False , width = 5 ) 82 | return None 83 | 84 | def OnDraw( self , context ) : 85 | pass 86 | 87 | def OnDraw3D( self , context ) : 88 | if self.removes[0] : 89 | alpha = self.preferences.highlight_face_alpha 90 | vertex_size = self.preferences.highlight_vertex_size 91 | width = 5 92 | if all( isinstance( e , bmesh.types.BMEdge ) and e.seam for e in self.removes[0] ) : 93 | color = (0,0,0,1) 94 | else : 95 | color = bpy.context.preferences.themes["Default"].view_3d.edge_seam 96 | color = (color[0],color[1],color[2],1) 97 | 98 | draw_util.drawElementsHilight3D( self.bmo.obj , self.removes[0] , vertex_size , width , alpha , color ) 99 | if self.bmo.is_mirror_mode : 100 | mirrors = [ self.bmo.find_mirror(m) for m in self.removes[0] ] 101 | mirrors = [ m for m in mirrors if m ] 102 | if mirrors : 103 | draw_util.drawElementsHilight3D( self.bmo.obj , mirrors , vertex_size , width , alpha * 0.5 , color ) 104 | 105 | if self.startTarget.element == self.currentTarget.element : 106 | self.startTarget.Draw( self.bmo.obj , self.preferences.highlight_color , self.preferences , marker = False , edge_pivot = True ) 107 | else : 108 | self.startTarget.Draw( self.bmo.obj , self.preferences.highlight_color , self.preferences , marker = False , edge_pivot = False ) 109 | if self.currentTarget.isNotEmpty : 110 | if self.startTarget.element != self.currentTarget.element : 111 | self.currentTarget.Draw( self.bmo.obj , self.preferences.highlight_color , self.preferences , marker = False , edge_pivot = False ) 112 | 113 | @classmethod 114 | def GetCursor(cls) : 115 | return 'CROSSHAIR' 116 | 117 | def SeamElement( self , element ) : 118 | edges = [ e for e in self.removes[0] if isinstance( e , bmesh.types.BMEdge ) ] 119 | seam = True 120 | if all( [ e.seam for e in edges] ) : 121 | seam = False 122 | 123 | for edge in edges : 124 | edge.seam = seam 125 | if self.bmo.is_mirror_mode : 126 | mirror = self.bmo.find_mirror( edge ) 127 | if mirror : 128 | mirror.seam = seam 129 | 130 | if bpy.context.tool_settings.use_edge_path_live_unwrap : 131 | bpy.ops.uv.unwrap() 132 | self.bmo.UpdateMesh() 133 | 134 | @classmethod 135 | def find_seam_loop( cls , bmo , edge ) : 136 | loop = [edge] 137 | 138 | if not edge.seam : 139 | def check_func( edge , vert ) : 140 | return any( [ e.seam for e in vert.link_edges if e != edge ] ) == False 141 | loop , v = bmo.calc_edge_loop( edge , check_func ) 142 | else : 143 | def find_loop( start , head ) : 144 | while(True) : 145 | link_seams = [ e for e in head.link_edges if e != start and e.seam ] 146 | if len( [ e for e in link_seams ] ) != 1 : 147 | return 148 | start = link_seams[0] 149 | if start in loop : 150 | break 151 | loop.append( start ) 152 | head = start.other_vert(head) 153 | 154 | find_loop( edge , edge.verts[0] ) 155 | find_loop( edge , edge.verts[1] ) 156 | 157 | return loop 158 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_seam_loop.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import copy 20 | import bpy_extras 21 | import collections 22 | from ..utils import pqutil 23 | from ..utils import draw_util 24 | from ..QMesh import * 25 | from .subtool import MainTool 26 | from ..utils.dpi import * 27 | from .subtool_seam import SubToolSeam 28 | 29 | class SubToolSeamLoop(SubToolSeam) : 30 | name = "Mark Seam Loop" 31 | 32 | @staticmethod 33 | def Check( root , target ) : 34 | return target.isEdge 35 | 36 | @staticmethod 37 | def pick_element( qmesh , location , preferences ) : 38 | element = qmesh.PickElement( location , preferences.distance_to_highlight , elements = ["EDGE"] ) 39 | return element 40 | 41 | def OnUpdate( self , context , event ) : 42 | if event.type == 'MOUSEMOVE': 43 | preTarget = self.currentTarget 44 | self.currentTarget = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight , elements = ["VERT","EDGE"] ) 45 | if self.currentTarget.isEdge : 46 | e = self.find_seam_loop( self.bmo , self.currentTarget.element ) 47 | self.removes = (e,[]) 48 | self.startTarget = self.currentTarget 49 | else : 50 | self.removes = ([self.currentTarget.element],[]) 51 | elif event.type == self.buttonType : 52 | if event.value == 'RELEASE' : 53 | if self.currentTarget.isEdge : 54 | e = self.find_seam_loop( self.bmo , self.currentTarget.element ) 55 | self.removes = (e,[]) 56 | self.startTarget = self.currentTarget 57 | else : 58 | self.removes = ([self.currentTarget.element],[]) 59 | self.SeamElement(self.currentTarget ) 60 | return 'FINISHED' 61 | elif event.type == 'RIGHTMOUSE': 62 | if event.value == 'RELEASE' : 63 | return 'FINISHED' 64 | return 'RUNNING_MODAL' 65 | 66 | @classmethod 67 | def DrawHighlight( cls , gizmo , element ) : 68 | if element != None and gizmo.bmo != None : 69 | edges = cls.find_seam_loop( gizmo.bmo , element.element ) 70 | if edges : 71 | alpha = gizmo.preferences.highlight_face_alpha 72 | vertex_size = gizmo.preferences.highlight_vertex_size 73 | width = 5 74 | if not element.element.seam : 75 | color = bpy.context.preferences.themes["Default"].view_3d.edge_seam 76 | color = (color[0],color[1],color[2],1) 77 | else : 78 | color = ( 0, 0 , 0 ,1) 79 | 80 | mirrors = [] 81 | if gizmo.bmo.is_mirror_mode : 82 | mirrors = [ gizmo.bmo.find_mirror(m) for m in edges ] 83 | mirrors = [ m for m in mirrors if m ] 84 | def func() : 85 | draw_util.drawElementsHilight3D( gizmo.bmo.obj , edges , vertex_size , width , alpha , color ) 86 | if mirrors : 87 | draw_util.drawElementsHilight3D( gizmo.bmo.obj , mirrors , vertex_size , width , alpha * 0.5 , color ) 88 | return func 89 | return None 90 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/subtools/subtool_vert_extrude.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import sys 15 | import bpy 16 | import math 17 | import mathutils 18 | import bmesh 19 | import bpy_extras 20 | import collections 21 | import mathutils 22 | import copy 23 | from ..utils import pqutil 24 | from ..utils import draw_util 25 | from ..QMesh import * 26 | from ..utils.dpi import * 27 | from .subtool import SubTool 28 | 29 | class SubToolVertExtrude(SubTool) : 30 | name = "VertExtrudeTool" 31 | 32 | def __init__(self,op, target ) : 33 | super().__init__(op) 34 | self.currentVert = target 35 | self.startPos = target.hitPosition 36 | self.targetPos = target.hitPosition 37 | self.screen_space_plane = pqutil.Plane.from_screen( bpy.context , target.hitPosition ) 38 | self.move_plane = self.screen_space_plane 39 | self.startMousePos = copy.copy(target.coord) 40 | self.snapTarget = ElementItem.Empty() 41 | self.is_snap_center = False 42 | self.ignore = [] 43 | for face in self.currentVert.element.link_faces : 44 | self.ignore.extend(face.verts) 45 | self.ignore = set(self.ignore ) 46 | 47 | @staticmethod 48 | def Check( root ,target ) : 49 | edges = [ e for e in target.element.link_edges if len(e.link_faces) == 1 ] 50 | if len(edges) == 2 : 51 | if set(edges[0].link_faces) == set(edges[1].link_faces) : 52 | return False 53 | return True 54 | return False 55 | 56 | def OnUpdate( self , context , event ) : 57 | if event.type == 'MOUSEMOVE': 58 | rayS = pqutil.Ray.from_screen( context , self.startMousePos ) 59 | rayG = pqutil.Ray.from_screen( context , self.mouse_pos ) 60 | vS = self.move_plane.intersect_ray( rayS ) 61 | vG = self.move_plane.intersect_ray( rayG ) 62 | move = (vG - vS) 63 | self.targetPos = self.startPos + move 64 | self.targetPos = QSnap.view_adjust(self.targetPos) 65 | self.snapTarget = self.bmo.PickElement( self.mouse_pos , self.preferences.distance_to_highlight , edgering=True , backface_culling = True , elements=['VERT'] , ignore = self.ignore ) 66 | if self.snapTarget.isEmpty : 67 | self.is_snap_center = self.bmo.is_x0_snap( self.targetPos ) 68 | if self.is_snap_center : 69 | self.targetPos = self.bmo.zero_pos_w2l(self.targetPos) 70 | else : 71 | self.is_snap_center = False 72 | 73 | elif event.type == 'RIGHTMOUSE' : 74 | if event.value == 'PRESS' : 75 | pass 76 | elif event.value == 'RELEASE' : 77 | pass 78 | elif event.type == 'LEFTMOUSE' : 79 | if event.value == 'RELEASE' : 80 | self.MakePoly() 81 | return 'FINISHED' 82 | return 'RUNNING_MODAL' 83 | 84 | def OnDraw( self , context ) : 85 | size = self.preferences.highlight_vertex_size 86 | if self.snapTarget.isVert : 87 | pos = pqutil.location_3d_to_region_2d( self.bmo.local_to_world_pos( self.snapTarget.element.co ) ) 88 | draw_util.draw_circle2D( pos , size , (1,1,1,1) , False ) 89 | if self.is_snap_center : 90 | p = self.bmo.zero_pos_w2l( self.targetPos ) 91 | pos = pqutil.location_3d_to_region_2d( p ) 92 | draw_util.draw_circle2D( pos , size , (1,1,1,1) , False ) 93 | 94 | def OnDraw3D( self , context ) : 95 | vert = self.currentVert.element 96 | edges = [ edge for edge in vert.link_edges if edge.is_boundary ] 97 | 98 | p0 = self.bmo.local_to_world_pos(vert.co) 99 | p1 = self.bmo.local_to_world_pos(edges[0].other_vert(vert).co) 100 | p3 = self.bmo.local_to_world_pos(edges[1].other_vert(vert).co) 101 | if self.snapTarget.isVert : 102 | p2 = self.bmo.local_to_world_pos(self.snapTarget.element.co) 103 | else : 104 | p2 = self.targetPos 105 | 106 | lines = (p0,p1,p2,p3,p0) 107 | polys = (p0,p1,p2,p3) 108 | 109 | draw_util.draw_Poly3D( self.bmo.obj , polys , self.color_create(0.5), hide_alpha = 0.25 ) 110 | draw_util.draw_lines3D( context , lines , self.color_create(1.0) , 2 , primitiveType = 'LINE_STRIP' , hide_alpha = 0 ) 111 | 112 | if self.bmo.is_mirror_mode : 113 | lines = [ self.bmo.mirror_pos_w2l(p) for p in lines ] 114 | polys = [ self.bmo.mirror_pos_w2l(p) for p in polys ] 115 | draw_util.draw_Poly3D( self.bmo.obj , polys , self.color_create(0.5), hide_alpha = 0.25 ) 116 | draw_util.draw_lines3D( context , lines , self.color_create(1.0) , 1 , primitiveType = 'LINE_STRIP' , hide_alpha = 0 ) 117 | 118 | def MakePoly( self ) : 119 | vert = self.currentVert.element 120 | edges = [ edge for edge in vert.link_edges if edge.is_boundary ] 121 | if self.snapTarget.isVert : 122 | v0 = self.snapTarget.element 123 | else : 124 | v0 = self.bmo.AddVertexWorld( self.targetPos ) 125 | self.bmo.UpdateMesh() 126 | v1 = edges[0].other_vert(vert) 127 | v2 = edges[1].other_vert(vert) 128 | 129 | verts = [v2,vert,v1,v0] 130 | 131 | normal = None 132 | edge = edges[0] 133 | if edge.link_faces : 134 | for loop in edge.link_faces[0].loops : 135 | if edge == loop.edge : 136 | if loop.vert == vert : 137 | verts.reverse() 138 | break 139 | else : 140 | normal = pqutil.getViewDir() 141 | 142 | self.bmo.AddFace( verts , normal ) 143 | self.bmo.UpdateMesh() 144 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/translation.py: -------------------------------------------------------------------------------- 1 | pq_translation_dict = { 2 | "ja_JP" : 3 | { 4 | ("*", "Fix X=0") : "X=0を固定" , 5 | ("*", "Tool settings:") : "ツールセッティング" , 6 | ("*", "Long Press Time") : "長押し判定の時間" , 7 | ("*", "Distance to Highlight") : "ハイライト距離" , 8 | ("*", "Highlight Vertex Size") : "ハイライト点のサイズ" , 9 | ("*", "Highlight Line Width") : "ハイライト線の太さ" , 10 | ("*", "Highlight Face Alpha") : "ハイライト面のアルファ" , 11 | ("*", "Color settings:") : "カラーセッティング" , 12 | ("*", "HighlightColor") : "ハイライト色" , 13 | ("*", "MakePolyColor") : "面作成色" , 14 | ("*", "SplitColor") : "分割色" , 15 | ("*", "DeleteColor") : "削除色" , 16 | ("*", "Edge Snap Div") : "辺スナップ分割数" , 17 | ("*", "Vertex Dissolve Angle") : "頂点融解角度" , 18 | ("*", "Setup GameEngine like Keymaps") : "ゲームエンジン風キーマップをセットアップ" , 19 | ("*", "Empty Mesh Object") : "空メッシュ" , 20 | ("*", "Brush Strength") : "ブラシ強度" , 21 | ("*", "Flex") : "自由" , 22 | ("*", "Lowpoly") : "ローポリ" , 23 | ("*", "Knife") : "ナイフ" , 24 | ("*", "LoopCut") : "ループカット" , 25 | ("*", "Brush_Delete") : "削除ブラシ" , 26 | ("*", "Space Drag Operation") : "スペースでドラッグした時の操作" , 27 | ("*", "Loop Cut") : "ループカット" , 28 | ("*", "Dissolve Loop") : "ループ消去" , 29 | ("*", "Make Polygon") : "ポリゴン作成" , 30 | ("*", "Master Tool") : "マスターツール" , 31 | ("*", "Edge Extrude") : "エッジ押し出し" , 32 | ("*", "Mark Seam") : "シームをマーク" , 33 | ("*", "Mark Seam Loop") : "シームループをマーク" , 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/utils/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | 3 | # ##### BEGIN GPL LICENSE BLOCK ##### 4 | # 5 | # This program is free software; you can redistribute it and/or 6 | # modify it under the terms of the GNU General Public License 7 | # as published by the Free Software Foundation; either version 2 8 | # of the License, or (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to the Free Software Foundation, 17 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 | # 19 | # ##### END GPL LICENSE BLOCK ##### 20 | 21 | from . import dpi 22 | from . import addon_updater 23 | from . import pqutil 24 | from . import mouse_event_util 25 | 26 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/utils/dpi.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | 16 | def dpi() : 17 | return bpy.context.preferences.system.dpi 18 | 19 | def dpc() : 20 | return dpi() / 2.54 21 | 22 | def dpm() : 23 | return dpc() / 10 24 | -------------------------------------------------------------------------------- /Addons/PolyQuilt/utils/mouse_event_util.py: -------------------------------------------------------------------------------- 1 | # This program is free software; you can redistribute it and/or modify 2 | # it under the terms of the GNU General Public License as published by 3 | # the Free Software Foundation; either version 3 of the License, or 4 | # (at your option) any later version. 5 | # 6 | # This program is distributed in the hope that it will be useful, but 7 | # WITHOUT ANY WARRANTY; without even the implied warranty of 8 | # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 9 | # General Public License for more details. 10 | # 11 | # You should have received a copy of the GNU General Public License 12 | # along with this program. If not, see . 13 | 14 | import bpy 15 | import math 16 | import mathutils 17 | import time 18 | import collections 19 | from enum import Enum , auto 20 | from . import draw_util 21 | 22 | __all__ = ['MBEventType','ButtonEventUtil'] 23 | 24 | class MBEventType(Enum) : 25 | Noting = auto() 26 | Click = auto() 27 | Drag = auto() 28 | Down = auto() 29 | Move = auto() 30 | LongPress = auto() 31 | LongClick = auto() 32 | LongPressDrag = auto() 33 | Release = auto() 34 | 35 | class ButtonEventUtil : 36 | def __init__( self , button : str , cls , func , op , use_hold_lock = False , no_hold = False ) : 37 | self.op = op 38 | self.button : str = button 39 | self.eventFunc = func 40 | self.eventClass = cls 41 | self.Press : bool = False 42 | self.Presure : bool = False 43 | self.PressTime : float = 0.0 44 | self.type : MBEventType = MBEventType.Noting 45 | self.mouse_pos = mathutils.Vector((0.0,0.0)) 46 | self.event = None 47 | self.PressPos = mathutils.Vector((0.0,0.0)) 48 | self.PressPrevPos = mathutils.Vector((0.0,0.0)) 49 | self.presureCompOnce = False 50 | self.preferences = op.preferences 51 | self.no_hold = no_hold 52 | 53 | @property 54 | def presureValue(self) -> float : 55 | if self.Presure == False : 56 | return 0.0 57 | else : 58 | lpt = self.preferences.longpress_time 59 | cur = time.time()-self.PressTime 60 | m = max( lpt / 3.0 , 0.125 ) 61 | t = ( cur - m ) / (lpt - m ) 62 | return min( 1.0 , max( 0.0 , t ) ) 63 | 64 | @property 65 | def presureComplite(self) -> bool : 66 | return self.presureValue >= 1.0 67 | 68 | @property 69 | def isPresure(self) -> bool : 70 | return self.presureValue >= 0.0001 71 | 72 | @property 73 | def is_hold(self) -> bool : 74 | hold = self.presureCompOnce 75 | return hold 76 | 77 | def is_animated( self ) : 78 | if self.Presure and not self.presureCompOnce : 79 | return True 80 | return False 81 | 82 | def Update( self , context , event ) : 83 | self.mouse_pos = mathutils.Vector((event.mouse_region_x, event.mouse_region_y)) 84 | if event.type == self.button: 85 | if event.value == 'PRESS': 86 | if self.Press is False : 87 | self.PressPos = self.mouse_pos 88 | self.PressTime = time.time() 89 | if not self.no_hold : 90 | self.Press = True 91 | self.Presure = True 92 | self.presureCompOnce = False 93 | self.PressPrevPos = mathutils.Vector((event.mouse_prev_x, event.mouse_prev_y)) 94 | self.OnEvent( event , MBEventType.Down ) 95 | else : 96 | self.OnEvent( event , MBEventType.Press ) 97 | elif event.value == 'RELEASE': 98 | if not self.no_hold : 99 | if self.presureComplite : 100 | self.presureCompOnce = True 101 | if self.Presure : 102 | if self.is_hold : 103 | self.OnEvent( event , MBEventType.LongClick ) 104 | else : 105 | self.OnEvent( event , MBEventType.Click ) 106 | self.Presure = False 107 | self.Press = False 108 | else : 109 | self.OnEvent( event , MBEventType.Click ) 110 | self.OnEvent( event , MBEventType.Release ) 111 | self.PressTime = 0.0 112 | self.presureCompOnce = False 113 | elif event.type == 'MOUSEMOVE': 114 | drag_threshold = context.preferences.inputs.drag_threshold_mouse 115 | if not self.no_hold : 116 | if self.Press : 117 | if self.presureComplite : 118 | self.presureCompOnce = True 119 | if event.is_tablet : 120 | drag_threshold = context.preferences.inputs.drag_threshold_tablet 121 | if (time.time()-self.PressTime ) > 0.15 and (self.mouse_pos-self.PressPos ).length > drag_threshold: 122 | self.Presure = False 123 | if self.Presure is False : 124 | if self.is_hold : 125 | self.OnEvent( event , MBEventType.LongPressDrag ) 126 | else : 127 | self.OnEvent( event , MBEventType.Drag ) 128 | else : 129 | if (time.time()-self.PressTime ) > 0.15 and (self.mouse_pos-self.PressPos ).length > drag_threshold: 130 | self.OnEvent( event , MBEventType.Drag ) 131 | 132 | self.OnEvent( event , MBEventType.Move ) 133 | elif event.type == 'TIMER': 134 | if self.presureComplite : 135 | self.presureCompOnce = True 136 | self.OnEvent( event , MBEventType.LongPress ) 137 | 138 | def OnEvent( self ,event , type : MBEventType ) : 139 | self.type = type 140 | if self.eventFunc != None : 141 | self.event = event 142 | self.eventFunc( self.eventClass , self) 143 | self.event = None 144 | 145 | def Reset(self, context ) : 146 | self.Presure = False 147 | self.Press = False 148 | self.presureCompOnce = False 149 | 150 | def Draw( self , coord = None , text = None ) : 151 | if self.presureValue > 0.001 : 152 | if coord != None : 153 | draw_util.draw_donuts2D( coord , 4 , 1 , self.presureValue, (1,1,1,self.presureValue) ) 154 | if self.presureComplite and text != None : 155 | draw_util.DrawFont(text, 12 , coord , (0,4) ) 156 | else: 157 | draw_util.draw_donuts2D( self.PressPos , 4 , 1 , self.presureValue, (1,1,1,self.presureValue) ) 158 | if self.presureComplite and text != None : 159 | draw_util.DrawFont( text , 12 , self.PressPos , (0,4) ) 160 | return self.presureComplite 161 | 162 | -------------------------------------------------------------------------------- /Docs/en/docs/download.md: -------------------------------------------------------------------------------- 1 | # Download and Install 2 | 3 | ## Install 4 | 5 | - [Download](##download) the zip to your local storage 6 | - Open Preferences form edit menu 7 | - Change to Add-ons Tab 8 | - Click Install and select zip 9 | - Activate PolyQuilt 10 | - Into Editmode and Select PolyQuilt from Toolbar 11 | - Enjoy! 12 | 13 | ## Update 14 | 15 | Online updater is built in. You can automatically update to the latest version by selecting **Check PolyQuilt add-on update** from ExtraSetting in the add-on menu. 16 | 17 | !!! Note 18 | Reboot required after update 19 | 20 | ## Download 21 | 22 | ### [PolyQuilt 1.1.5](https://github.com/sakana3/PolyQuilt/releases/download/1.1.5/PolyQuilt_v1.1.5.zip) 23 | 24 | -------------------------------------------------------------------------------- /Docs/en/docs/index.md: -------------------------------------------------------------------------------- 1 | # Welcome to PolyQuilt 2 | 3 | ## What is PolyQuilt? 4 | 5 | PolyQuilt is an add-on for Blender 2.8 that supports low-poly modeling. 6 | In addition to simple modeling capabilities, it supports fast and intuitive polygon modeling with minimal interaction by assigning various functions to mouse actions such as clicking, dragging and holding. 7 | It also works well with tablets and tablet PCs with digitizers. 8 | 9 | I hope this add-on will make your modeling life happy! 10 | 11 | ## If you are in trouble 12 | 13 | Please send bug reports or requests to a [Twitter](https://twitter.com/sakanaya) or [Github](https://github.com/sakana3/PolyQuilt) or [BlenderArtist PolyQuilt thread](https://blenderartists.org/t/polyquilt-addon-for-blender-2-8/1168918 14 | ). 15 | 16 | 17 | -------------------------------------------------------------------------------- /Docs/en/docs/manual/Brush.md: -------------------------------------------------------------------------------- 1 | # Brush Tools 2 | 3 | ## Change size and intensity 4 | 5 | ## Move Brush 6 | 7 | ## Relax Brush 8 | -------------------------------------------------------------------------------- /Docs/en/docs/manual/facecut.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Docs/en/docs/manual/facecut.md -------------------------------------------------------------------------------- /Docs/en/docs/manual/geometry.md: -------------------------------------------------------------------------------- 1 | # Draw Geometry 2 | 3 | ## Vertex 4 | 5 | ### Click 6 | 7 | ## Edge 8 | 9 | ### Click and Draw 10 | 11 | ## Face 12 | 13 | ### Click and Draw 14 | ### Extrude 15 | ### AutoQuad 16 | 17 | -------------------------------------------------------------------------------- /Docs/en/docs/manual/insert_loop.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Docs/en/docs/manual/insert_loop.md -------------------------------------------------------------------------------- /Docs/en/docs/manual/knife.md: -------------------------------------------------------------------------------- 1 | # Knife 2 | 3 | 4 | -------------------------------------------------------------------------------- /Docs/en/docs/manual/move.md: -------------------------------------------------------------------------------- 1 | # Move and Snap -------------------------------------------------------------------------------- /Docs/en/docs/manual/operation.md: -------------------------------------------------------------------------------- 1 | # Basic Operation 2 | 3 | PolyQuilt allows you to perform a variety of polygon edits using a combination of highlighted elements, mouse actions, and modifier keys. 4 | 5 | ## Action List 6 | 7 | | Opetation |Description | 8 | |:-:|:-| 9 | | Click |Press and release the left mouse button in a short period of time| 10 | | Drag|Press and move the left mouse button| 11 | | Hold|Press the left mouse button for a long time. When the marker becomes a circle, holding is completed.| 12 | | Hold Drag|Move the mouse from the hold state.| 13 | 14 | ## Modifier key 15 | 16 | | Key |Description | 17 | |:-:|:-| 18 | | Alt | Delete Geometry | 19 | | Ctrl | Preparing | 20 | | Shift | AutoQuad and Brushs | 21 | | OS Key | Lock Hold | 22 | 23 | ## Caution 24 | 25 | Until you get used to it, holding may cause a lot of malfunction. If there are many mistakes in operation, it is better to set the hold time longer from the setting screen. The default is 0.4 seconds. 26 | -------------------------------------------------------------------------------- /Docs/en/docs/manual/remove.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Docs/en/docs/manual/remove.md -------------------------------------------------------------------------------- /Docs/en/docs/manual/remove_loop.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Docs/en/docs/manual/remove_loop.md -------------------------------------------------------------------------------- /Docs/en/mkdocs.yml: -------------------------------------------------------------------------------- 1 | # Project information 2 | site_name: 'PolyQuilt' 3 | site_description: 'Lowpoly modeling assist blender-addon PolyQuilt' 4 | site_author: 'sakana3' 5 | site_url: 'https://sakana3.github.io/PolyQuilt/' 6 | 7 | # Repository 8 | repo_name: 'sakana3/PolyQuilt' 9 | repo_url: 'https://github.com/sakana3/PolyQuilt' 10 | 11 | # Copyright 12 | copyright: 'Copyright © 2016 - 2017 Martin Donath' 13 | 14 | # Configuration 15 | theme: 16 | name: 'material' 17 | language: 'en' 18 | feature: 19 | tabs: true 20 | palette: 21 | primary: 'Teal' 22 | accent: 'Red' 23 | font: 24 | text: 'Roboto' 25 | code: 'Roboto Mono' 26 | 27 | # Customization 28 | extra: 29 | manifest: 'manifest.webmanifest' 30 | social: 31 | - type: 'github' 32 | link: 'https://github.com/sakana3/PolyQuilt' 33 | - type: 'twitter' 34 | link: 'https://twitter.com/sakanaya' 35 | - type: 'globe' 36 | link: 'https://blenderartists.org/t/polyquilt-addon-for-blender-2-8/1168918/' 37 | 38 | # Page tree 39 | nav: 40 | - About: index.md 41 | - Download and Install: download.md 42 | - Manual : 43 | - Basic operation: manual/operation.md 44 | - Move and Snap : manual/move.md 45 | - Make Geometry: manual/geometry.md 46 | - Remove Geometry: manual/remove.md 47 | - Face Cut: manual/facecut.md 48 | - Insert Loop: manual/insert_loop.md 49 | - Remove Loop: manual/remove_loop.md 50 | - Knife : manual/knife.md 51 | - Brush Tools : manual/brush.md 52 | 53 | # Extensions 54 | markdown_extensions: 55 | - markdown.extensions.admonition 56 | - markdown.extensions.codehilite: 57 | guess_lang: false 58 | - markdown.extensions.def_list 59 | - markdown.extensions.footnotes 60 | - markdown.extensions.meta 61 | - markdown.extensions.toc: 62 | permalink: true 63 | - pymdownx.arithmatex 64 | - pymdownx.betterem: 65 | smart_enable: all 66 | - pymdownx.caret 67 | - pymdownx.critic 68 | - pymdownx.details 69 | - pymdownx.emoji: 70 | emoji_index: !!python/name:pymdownx.emoji.twemoji 71 | emoji_generator: !!python/name:pymdownx.emoji.to_svg 72 | - pymdownx.inlinehilite 73 | - pymdownx.keys 74 | - pymdownx.magiclink: 75 | repo_url_shorthand: true 76 | user: squidfunk 77 | repo: mkdocs-material 78 | - pymdownx.mark 79 | - pymdownx.smartsymbols 80 | - pymdownx.superfences 81 | - pymdownx.tasklist: 82 | custom_checkbox: true 83 | - pymdownx.tilde 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I am currently working on an English document. [Click here](https://github.com/sakana3/PolyQuilt/releases/download/1.3.1/PolyQuilt_v1.3.1.zip) to download the latest version. 2 | If you are using Blender2.82 and earlier. [Click here](#OldVersion) 3 | 4 | # PolyQuilt(ポリキルト) 5 | --- 6 | PolyQuiltはローポリモデリングをサポートするBlender2.8用アドオンです。シンプルでオーソドックスでオールドスクールな面張り機能に加えてドラッグや長押し等のマウスオペレーションに様々な機能を割り振ることでツールの切り替えを最小限にしてポリゴンモデリングを支援します。 7 | またタブレットやデジタイザ付きタブレットPCとの相性も良く、操作量を減らしつつ直感的にモデリングを行う事ができます。 8 | 9 | # 導入方法 10 | 11 | 最新版ダウンロードは[こちら](https://github.com/sakana3/PolyQuilt/releases/download/1.3.1/PolyQuilt_v1.3.1.zip)から(Blender2.83 LTS以降) 12 | Blender2.82以前の方は[こちら](#OldVersion)から 13 | 14 | ダウンロードして編集→設定→アドオン→インストールよりダウンロード先を指定してインストールしてください。インストールした段階ではまだ使えませんのでその後検索バーよりPolyQuiltを検索しチェックボックスをOnにしてください。 15 | 16 | 編集モードに入るとPolyBuildの下にアイコンが追加されているのでこれをクリックでPolyQuiltがアクティブツールになります。 17 | 18 | # 機能一覧 19 | 20 | 各種機能はクリック、ドラッグ、長押し、長押し後ドラッグの操作に割り振られています。空間、点辺面の要素との組み合わせで以下の機能が割り振られています。 21 | Altでホールドと同等の動作になります。Altキーダブルクリックで長押しをロックします。 22 | 23 | ||クリック|ドラッグ|ホールド|ホールドドラッグ| 24 | |:-:|:-:|:-:|:-:|:-:| 25 | |空間|[頂点作成](#頂点作成)
→[面張り](#面張り)
→[ループカット](#ループカット)|ビュー回転(暫定)||[ナイフ](#ナイフ)|| 26 | |点|[面張り](#面張り)|[移動(結合)](#移動)|[削除/融解](#削除/融解)|[扇カット](#扇カット)
[エッジ押し出し](#エッジ押し出し)| 27 | |辺|[頂点挿入→面張り](#面張り)|[移動](#移動)|[削除/融解](#削除/融解)|[ループ挿入](#ループ挿入)
[エッジ押し出し](#エッジ押し出し)
[ループカット](#ループカット)| 28 | |面||[移動](#移動)|[削除](#削除/融解)|| 29 | 30 | |Shiftを押しながら| 機能 | 31 | |:-:|:-:| 32 | |Shift + クリック| AutoQuad | 33 | |Shift + ドラッグ| Brush | 34 | |Shift + ミドルボタン+ドラッグ| 2ndBrush | 35 | |Shift + ホールドドラック| ブラシサイズ/強度 +/- | 36 | |Shift + Wheel Up/ Down| ブラシサイズ +/- | 37 | |Shift + Ctrl + Wheel Up/ Down| ブラシ強度 +/- | 38 | 39 | --- 40 | ## 移動 41 | 42 | 点、線、面の上でマウスドラッグでビュー平面状の移動ができます。エッジの頂点は別エッジの頂点に近づける事で結合する事ができます。 43 | 44 | ## 頂点作成 45 | 何もない空間をクリックで頂点が作成されます。通常はそのまま面張りモードになりますがジオメトリを点モードにすれば連続で点を作れます。 46 | 47 | ## 面張り 48 | 49 | 何もないエリアか点の上でクリックで面張りを開始します。クリックする度に点、線、面が追加されます。最後に配置した点の上でクリック(ダブルクリックと同意)か右クリックで終了しまた新たな面を作ることができます。 50 | 51 | 画面上部のツールメニューより作成するジオメトリを点、線、三角形、四角形、ポリゴンから選ぶ事ができます。 52 | 53 | また同一面の頂点でクリックすると面の分断になります。 54 | 55 | ## 削除/融解 56 | 57 | 点、線、面の上で長押しでその要素を消去or融解します。共有する線や面がある場合は融解、そうでない場合は削除になります。 58 | 59 | ## ナイフ 60 | 61 | 空間で長押し後にドラッグでナイフを実行できます。 62 | 63 | ## ループカット 64 | 65 | 辺の上で長押し後ドラッグで辺をループカットできます。 66 | 67 | ## 扇カット 68 | 69 | 点の上で長押し後ドラッグで辺を扇状にループカットできます。 70 | 71 | ## エッジ押し出し 72 | 73 | 辺または点の上で長押し後ドラッグでエッジの押し出しをします。 74 | 75 | ## ループ挿入 76 | 77 | 辺ループを挿入します 78 | 79 | ## ループカット 80 | 81 | 辺ループをカットします 82 | 83 | # 環境設定 84 | 85 | ドキュメント準備中 86 | 87 | ## Extra Setting 88 | 特別な設定群です。上級者向け 89 | 90 | ### Check PolyQuilt add-on update 91 | 92 | ポリキルトを更新をチェックし最新版にアップデートする事ができます。上手くいかない場合は従来通りの方法でお願いします。 93 | 94 | ### ゲームエンジン風キーマップをセットアップ 95 | 96 | 右マウスボタン+ドラッグでの視点変更にキーマップをセットアップします。標準で割り振られているコンテキストメニューはクリックで開く為、操作が被る事なくUnityやUE4といった主要ゲームエンジン風の操作を行う事ができます。キーマップを書き換える為、独自カスタムや他のアドオンとの衝突する場合があるので注意してください。 97 | 98 | # フィードバック 99 | 100 | フィードバックはGithubの[Issues](https://github.com/sakana3/PolyQuilt/issues)かTwitterアカウント[sakana3](https://twitter.com/sakanaya) まで宜しくお願い致します。 101 | 102 | # 実装予定 103 | 104 | - [ ] 分離(Rip) 105 | - [ ] LoopCutの末端三角形対応 106 | - [ ] 選択 107 | - [x] ハンドリトポ編集 108 | 109 | # OldVersion 110 | For Blender2.82 and earlier. [Click here](https://github.com/sakana3/PolyQuilt/releases/download/1.2.0/PolyQuilt_v1.2.0.zip) 111 | -------------------------------------------------------------------------------- /Resources/icon_geom.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakana3/PolyQuilt/5ed6c9ac20b04008e726299ea4e3a36080024f11/Resources/icon_geom.blend -------------------------------------------------------------------------------- /Resources/makeicon.bat: -------------------------------------------------------------------------------- 1 | blender.exe icon_geom.blend --background --python blender_icons_geom.py -- --output-dir=./icons 2 | 3 | pause -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | -------------------------------------------------------------------------------- /mkpackage.py: -------------------------------------------------------------------------------- 1 | # coding: UTF-8 2 | import os 3 | import re 4 | import zipfile 5 | import shutil 6 | 7 | modulename = "PolyQuilt" 8 | package_folder = os.getcwd() + "/Addons/PolyQuilt" 9 | 10 | version = "0.0.0" 11 | with open( package_folder + "/__init__.py", encoding= "utf-8" ) as f: 12 | line = f.readline() 13 | while line : 14 | if "\"version\"" in line : 15 | vtext = line.split('(')[-1].split(')')[0] 16 | vtext = [ v.replace(" ","") for v in vtext.split(',') ] 17 | version = '.'.join(vtext) 18 | line = f.readline() 19 | 20 | filename = modulename + "_v" + version 21 | 22 | shutil.make_archive( filename , 'zip', root_dir= package_folder ) 23 | 24 | --------------------------------------------------------------------------------