1# -*- coding:utf-8 -*- 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# <pep8 compliant> 22 23# ---------------------------------------------------------- 24# Author: Stephen Leger (s-leger) 25# 26# ---------------------------------------------------------- 27# noinspection PyUnresolvedReferences 28import bpy 29# noinspection PyUnresolvedReferences 30from bpy.props import BoolProperty, StringProperty 31from mathutils import Vector, Matrix 32from mathutils.geometry import ( 33 intersect_line_plane 34 ) 35from bpy_extras.view3d_utils import ( 36 region_2d_to_origin_3d, 37 region_2d_to_vector_3d 38 ) 39 40 41class ArchipackCollectionManager(): 42 43 @staticmethod 44 def link_object_to_scene(context, o): 45 coll_main = context.scene.collection.children.get("Archipack") 46 if coll_main is None: 47 coll_main = bpy.data.collections.new(name="Archipack") 48 context.scene.collection.children.link(coll_main) 49 coll_main.objects.link(o) 50 51 @staticmethod 52 def unlink_object_from_scene(o): 53 for coll in o.users_collection: 54 coll.objects.unlink(o) 55 56 57class ArchipackObject(ArchipackCollectionManager): 58 """ 59 Shared property of archipack's objects PropertyGroup 60 provide basic support for copy to selected 61 and datablock access / filtering by object 62 """ 63 64 def iskindof(self, o, typ): 65 """ 66 return true if object contains databloc of typ name 67 """ 68 return o.data is not None and typ in o.data 69 70 @classmethod 71 def filter(cls, o): 72 """ 73 Filter object with this class in data 74 return 75 True when object contains this datablock 76 False otherwise 77 usage: 78 class_name.filter(object) from outside world 79 self.__class__.filter(object) from instance 80 """ 81 try: 82 return cls.__name__ in o.data 83 except: 84 pass 85 return False 86 87 @classmethod 88 def datablock(cls, o): 89 """ 90 Retrieve datablock from base object 91 return 92 datablock when found 93 None when not found 94 usage: 95 class_name.datablock(object) from outside world 96 self.__class__.datablock(object) from instance 97 """ 98 try: 99 return getattr(o.data, cls.__name__)[0] 100 except: 101 pass 102 return None 103 104 def find_in_selection(self, context, auto_update=True): 105 """ 106 find witch selected object this datablock instance belongs to 107 store context to be able to restore after oops 108 provide support for "copy to selected" 109 return 110 object or None when instance not found in selected objects 111 """ 112 if auto_update is False: 113 return None 114 115 active = context.active_object 116 selected = context.selected_objects[:] 117 118 for o in selected: 119 120 if self.__class__.datablock(o) == self: 121 self.previously_selected = selected 122 self.previously_active = active 123 return o 124 125 return None 126 127 def restore_context(self, context): 128 # restore context 129 bpy.ops.object.select_all(action="DESELECT") 130 131 try: 132 for o in self.previously_selected: 133 o.select_set(state=True) 134 except: 135 pass 136 if self.previously_active is not None: 137 self.previously_active.select_set(state=True) 138 context.view_layer.objects.active = self.previously_active 139 self.previously_selected = None 140 self.previously_active = None 141 142 def move_object(self, o, p): 143 """ 144 When firstpoint is moving we must move object according 145 p is new x, y location in world coordsys 146 """ 147 p = Vector((p.x, p.y, o.matrix_world.translation.z)) 148 # p is in o coordsys 149 if o.parent: 150 o.location = p @ o.parent.matrix_world.inverted() 151 o.matrix_world.translation = p 152 else: 153 o.location = p 154 o.matrix_world.translation = p 155 156 157class ArchipackCreateTool(ArchipackCollectionManager): 158 """ 159 Shared property of archipack's create tool Operator 160 """ 161 auto_manipulate : BoolProperty( 162 name="Auto manipulate", 163 description="Enable object's manipulators after create", 164 options={'SKIP_SAVE'}, 165 default=True 166 ) 167 filepath : StringProperty( 168 options={'SKIP_SAVE'}, 169 name="Preset", 170 description="Full filename of python preset to load at create time", 171 default="" 172 ) 173 174 @property 175 def archipack_category(self): 176 """ 177 return target object name from ARCHIPACK_OT_object_name 178 """ 179 return self.bl_idname[13:] 180 181 def load_preset(self, d): 182 """ 183 Load python preset 184 d: archipack object datablock 185 preset: full filename.py with path 186 """ 187 d.auto_update = False 188 fallback = True 189 if self.filepath != "": 190 try: 191 bpy.ops.script.python_file_run(filepath=self.filepath) 192 fallback = False 193 except: 194 pass 195 if fallback: 196 # fallback to load preset on background process 197 try: 198 with open(self.filepath) as f: 199 lines = f.read() 200 cmp = compile(lines, self.filepath, 'exec') 201 exec(cmp) 202 except: 203 print("Archipack unable to load preset file : %s" % (self.filepath)) 204 pass 205 d.auto_update = True 206 207 def add_material(self, o, material='DEFAULT', category=None): 208 # skip if preset already add material 209 if "archipack_material" in o: 210 return 211 try: 212 if category is None: 213 category = self.archipack_category 214 if bpy.ops.archipack.material.poll(): 215 bpy.ops.archipack.material(category=category, material=material) 216 except: 217 print("Archipack %s materials not found" % (self.archipack_category)) 218 pass 219 220 def manipulate(self): 221 if self.auto_manipulate: 222 try: 223 op = getattr(bpy.ops.archipack, self.archipack_category + "_manipulate") 224 if op.poll(): 225 op('INVOKE_DEFAULT') 226 except: 227 print("Archipack bpy.ops.archipack.%s_manipulate not found" % (self.archipack_category)) 228 pass 229 230 231class ArchipackDrawTool(ArchipackCollectionManager): 232 """ 233 Draw tools 234 """ 235 def mouse_to_plane(self, context, event, origin=Vector((0, 0, 0)), normal=Vector((0, 0, 1))): 236 """ 237 convert mouse pos to 3d point over plane defined by origin and normal 238 """ 239 region = context.region 240 rv3d = context.region_data 241 co2d = (event.mouse_region_x, event.mouse_region_y) 242 view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d) 243 ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d) 244 pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, 245 origin, normal, False) 246 # fix issue with parallel plane 247 if pt is None: 248 pt = intersect_line_plane(ray_origin_mouse, ray_origin_mouse + view_vector_mouse, 249 origin, view_vector_mouse, False) 250 return pt 251 252 def mouse_to_scene_raycast(self, context, event): 253 """ 254 convert mouse pos to 3d point over plane defined by origin and normal 255 """ 256 region = context.region 257 rv3d = context.region_data 258 co2d = (event.mouse_region_x, event.mouse_region_y) 259 view_vector_mouse = region_2d_to_vector_3d(region, rv3d, co2d) 260 ray_origin_mouse = region_2d_to_origin_3d(region, rv3d, co2d) 261 res, pos, normal, face_index, object, matrix_world = context.scene.ray_cast( 262 depsgraph=context.view_layer.depsgraph, 263 origin=ray_origin_mouse, 264 direction=view_vector_mouse) 265 return res, pos, normal, face_index, object, matrix_world 266 267 def mouse_hover_wall(self, context, event): 268 """ 269 convert mouse pos to matrix at bottom of surrounded wall, y oriented outside wall 270 """ 271 res, pt, y, i, o, tM = self.mouse_to_scene_raycast(context, event) 272 if res and o.data is not None and 'archipack_wall2' in o.data: 273 z = Vector((0, 0, 1)) 274 d = o.data.archipack_wall2[0] 275 y = -y 276 pt += (0.5 * d.width) * y.normalized() 277 x = y.cross(z) 278 return True, Matrix([ 279 [x.x, y.x, z.x, pt.x], 280 [x.y, y.y, z.y, pt.y], 281 [x.z, y.z, z.z, o.matrix_world.translation.z], 282 [0, 0, 0, 1] 283 ]), o, d.width, y, 0 # d.z_offset 284 return False, Matrix(), None, 0, Vector(), 0 285