# it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import math import bmesh import bpy.utils.previews import bpy bl_info = { "name": "Isometric Room Gen", "author": "SkdSam & Mr Steve", "description": "Generate Isometric Rooms", "blender": (3, 6, 0), "version": (1, 0, 6), "location": "", "warning": "", "doc_url": "https://superhivemarket.com/creators/skdsam", "tracker_url": "", "category": "3D View" } def string_to_int(value): if value.isdigit(): return int(value) return 0 def string_to_icon(value): if value in bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items.keys(): return bpy.types.UILayout.bl_rna.functions["prop"].parameters["icon"].enum_items[value].value return string_to_int(value) addon_keymaps = {} _icons = None class SNA_PT_IRG_3542C(bpy.types.Panel): bl_label = 'IRG' bl_idname = 'SNA_PT_IRG_3542C' bl_space_type = 'VIEW_3D' bl_region_type = 'UI' bl_context = '' bl_category = 'IRG' bl_order = 0 bl_ui_units_x = 0 @classmethod def poll(cls, context): return not (False) def draw_header(self, context): layout = self.layout def draw(self, context): layout = self.layout layout.prop(bpy.context.scene, 'sna_style', text=bpy.context.scene.sna_style, icon_value=0, emboss=True, expand=True) if bpy.context.scene.sna_style == "Square": col_F2E32 = layout.column(heading='', align=False) col_F2E32.alert = False col_F2E32.enabled = True col_F2E32.active = True col_F2E32.use_property_split = False col_F2E32.use_property_decorate = False col_F2E32.scale_x = 1.0 col_F2E32.scale_y = 1.0 col_F2E32.alignment = 'Expand'.upper() col_F2E32.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_8A9B7 = col_F2E32.row(heading='', align=True) row_8A9B7.alert = False row_8A9B7.enabled = True row_8A9B7.active = True row_8A9B7.use_property_split = False row_8A9B7.use_property_decorate = False row_8A9B7.scale_x = 1.0 row_8A9B7.scale_y = 1.0 row_8A9B7.alignment = 'Expand'.upper() row_8A9B7.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_8A9B7.prop(bpy.context.scene, 'sna_room_settings', text='Room Settings ', icon_value=( 11 if bpy.context.scene.sna_room_settings else 10), emboss=False, toggle=True) row_8A9B7.prop(bpy.context.scene, 'sna_room_settings', text='', icon_value=string_to_icon( 'MATCUBE'), emboss=bpy.context.scene.sna_room_settings, toggle=True) col_AF92C = col_F2E32.column(heading='', align=False) col_AF92C.alert = False col_AF92C.enabled = True col_AF92C.active = True col_AF92C.use_property_split = False col_AF92C.use_property_decorate = False col_AF92C.scale_x = 1.0 col_AF92C.scale_y = 1.0 col_AF92C.alignment = 'Expand'.upper() col_AF92C.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" if bpy.context.scene.sna_room_settings: box_480DE = col_AF92C.box() box_480DE.alert = False box_480DE.enabled = True box_480DE.active = True box_480DE.use_property_split = False box_480DE.use_property_decorate = False box_480DE.alignment = 'Expand'.upper() box_480DE.scale_x = 1.0 box_480DE.scale_y = 1.0 if not True: box_480DE.operator_context = "EXEC_DEFAULT" box_480DE.prop(bpy.context.scene, 'sna_room_width', text='Room Width', icon_value=0, emboss=True) box_480DE.prop(bpy.context.scene, 'sna_room_depth', text='Room Depth', icon_value=0, emboss=True) box_480DE.prop(bpy.context.scene, 'sna_room_height', text='Room Height', icon_value=0, emboss=True) box_480DE.prop(bpy.context.scene, 'sna_wall_thickness', text='Wall Thickness', icon_value=0, emboss=True) row_C816B = col_F2E32.row(heading='', align=True) row_C816B.alert = False row_C816B.enabled = True row_C816B.active = True row_C816B.use_property_split = False row_C816B.use_property_decorate = False row_C816B.scale_x = 1.0 row_C816B.scale_y = 1.0 row_C816B.alignment = 'Expand'.upper() row_C816B.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_C816B.prop(bpy.context.scene, 'sna_winows_settings', text='Window Settings', icon_value=( 11 if bpy.context.scene.sna_winows_settings else 10), emboss=False, toggle=True) row_C816B.prop(bpy.context.scene, 'sna_winows_settings', text='', icon_value=string_to_icon( 'MESH_PLANE'), emboss=bpy.context.scene.sna_winows_settings, toggle=True) if bpy.context.scene.sna_winows_settings: box_7092C = col_F2E32.box() box_7092C.alert = False box_7092C.enabled = True box_7092C.active = True box_7092C.use_property_split = False box_7092C.use_property_decorate = False box_7092C.alignment = 'Expand'.upper() box_7092C.scale_x = 1.0 box_7092C.scale_y = 1.0 if not True: box_7092C.operator_context = "EXEC_DEFAULT" box_7092C.prop(bpy.context.scene, 'sna_windows_enum', text='Window Placement', icon_value=0, emboss=True) if (bpy.context.scene.sna_windows_enum != 'NONE'): col_8CB93 = box_7092C.column(heading='', align=False) col_8CB93.alert = False col_8CB93.enabled = True col_8CB93.active = True col_8CB93.use_property_split = False col_8CB93.use_property_decorate = False col_8CB93.scale_x = 1.0 col_8CB93.scale_y = 1.0 col_8CB93.alignment = 'Expand'.upper() col_8CB93.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" col_8CB93.prop(bpy.context.scene, 'sna_window_style', text='Type', icon_value=0, emboss=True) col_8CB93.prop(bpy.context.scene, 'sna_windows_count', text='Window Count', icon_value=0, emboss=True) col_8CB93.prop(bpy.context.scene, 'sna_windows_width', text='Window Width %', icon_value=0, emboss=True) col_8CB93.prop(bpy.context.scene, 'sna_windows_height', text='Window Height %', icon_value=0, emboss=True) # 添加拱门设置 row_arch = col_F2E32.row(heading='', align=True) row_arch.alert = False row_arch.enabled = True row_arch.active = True row_arch.use_property_split = False row_arch.use_property_decorate = False row_arch.scale_x = 1.0 row_arch.scale_y = 1.0 row_arch.alignment = 'Expand'.upper() row_arch.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_arch.prop(bpy.context.scene, 'sna_arch_settings', text='Arch Settings ', icon_value=( 11 if bpy.context.scene.sna_arch_settings else 10), emboss=False, toggle=True) row_arch.prop(bpy.context.scene, 'sna_arch_settings', text='', icon_value=string_to_icon( 'MESH_CUBE'), emboss=bpy.context.scene.sna_arch_settings, toggle=True) if bpy.context.scene.sna_arch_settings: box_arch = col_F2E32.box() box_arch.alert = False box_arch.enabled = True box_arch.active = True box_arch.use_property_split = False box_arch.use_property_decorate = False box_arch.alignment = 'Expand'.upper() box_arch.scale_x = 1.0 box_arch.scale_y = 1.0 if not True: box_arch.operator_context = "EXEC_DEFAULT" box_arch.prop(bpy.context.scene, 'sna_arch_placement', text='Arch Placement', icon_value=0, emboss=True) if bpy.context.scene.sna_arch_placement != 'NONE': box_arch.prop(bpy.context.scene, 'sna_arch_width', text='Arch Width', icon_value=0, emboss=True) box_arch.prop(bpy.context.scene, 'sna_arch_height', text='Arch Height', icon_value=0, emboss=True) box_arch.prop(bpy.context.scene, 'sna_arch_thickness', text='Arch Thickness', icon_value=0, emboss=True) col_F2E32.separator(factor=1.0000016689300537) row_A6551 = col_F2E32.row(heading='', align=False) row_A6551.alert = False row_A6551.enabled = True row_A6551.active = True row_A6551.use_property_split = False row_A6551.use_property_decorate = False row_A6551.scale_x = 1.0 row_A6551.scale_y = 2.440000057220459 row_A6551.alignment = 'Expand'.upper() row_A6551.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" op = row_A6551.operator('sna.gen_room_1803a', text='Generate Room', icon_value=string_to_icon( 'SHAPEKEY_DATA'), emboss=True, depress=False) elif bpy.context.scene.sna_style == "Round": col_05349 = layout.column(heading='', align=False) col_05349.alert = False col_05349.enabled = True col_05349.active = True col_05349.use_property_split = False col_05349.use_property_decorate = False col_05349.scale_x = 1.0 col_05349.scale_y = 1.0 col_05349.alignment = 'Expand'.upper() col_05349.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_31CFF = col_05349.row(heading='', align=True) row_31CFF.alert = False row_31CFF.enabled = True row_31CFF.active = True row_31CFF.use_property_split = False row_31CFF.use_property_decorate = False row_31CFF.scale_x = 1.0 row_31CFF.scale_y = 1.0 row_31CFF.alignment = 'Expand'.upper() row_31CFF.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_31CFF.prop(bpy.context.scene, 'sna_round_room_settings', text='Room Settings ', icon_value=( 11 if bpy.context.scene.sna_round_room_settings else 10), emboss=False, toggle=True) row_31CFF.prop(bpy.context.scene, 'sna_round_room_settings', text='', icon_value=string_to_icon( 'MATCUBE'), emboss=bpy.context.scene.sna_round_room_settings, toggle=True) col_549D5 = col_05349.column(heading='', align=False) col_549D5.alert = False col_549D5.enabled = True col_549D5.active = True col_549D5.use_property_split = False col_549D5.use_property_decorate = False col_549D5.scale_x = 1.0 col_549D5.scale_y = 1.0 col_549D5.alignment = 'Expand'.upper() col_549D5.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" if bpy.context.scene.sna_round_room_settings: box_B3F1F = col_549D5.box() box_B3F1F.alert = False box_B3F1F.enabled = True box_B3F1F.active = True box_B3F1F.use_property_split = False box_B3F1F.use_property_decorate = False box_B3F1F.alignment = 'Expand'.upper() box_B3F1F.scale_x = 1.0 box_B3F1F.scale_y = 1.0 if not True: box_B3F1F.operator_context = "EXEC_DEFAULT" box_B3F1F.prop(bpy.context.scene, 'sna_room_diameter_round', text='Room Diameter', icon_value=0, emboss=True) box_B3F1F.prop(bpy.context.scene, 'sna_room_floor_thickness_round', text='Floor Depth', icon_value=0, emboss=True) box_B3F1F.prop(bpy.context.scene, 'sna_room_height_round', text='Room Height', icon_value=0, emboss=True) box_B3F1F.prop(bpy.context.scene, 'sna_wall_thickness_round', text='Wall Thickness', icon_value=0, emboss=True) row_6F6A0 = col_05349.row(heading='', align=True) row_6F6A0.alert = False row_6F6A0.enabled = True row_6F6A0.active = True row_6F6A0.use_property_split = False row_6F6A0.use_property_decorate = False row_6F6A0.scale_x = 1.0 row_6F6A0.scale_y = 1.0 row_6F6A0.alignment = 'Expand'.upper() row_6F6A0.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" row_6F6A0.prop(bpy.context.scene, 'sna_round_window_settings', text='Window Settings', icon_value=( 11 if bpy.context.scene.sna_round_window_settings else 10), emboss=False, toggle=True) row_6F6A0.prop(bpy.context.scene, 'sna_round_window_settings', text='', icon_value=string_to_icon( 'MESH_PLANE'), emboss=bpy.context.scene.sna_round_window_settings, toggle=True) if bpy.context.scene.sna_round_window_settings: box_E2A99 = col_05349.box() box_E2A99.alert = False box_E2A99.enabled = True box_E2A99.active = True box_E2A99.use_property_split = False box_E2A99.use_property_decorate = False box_E2A99.alignment = 'Expand'.upper() box_E2A99.scale_x = 1.0 box_E2A99.scale_y = 1.0 if not True: box_E2A99.operator_context = "EXEC_DEFAULT" box_E2A99.prop(bpy.context.scene, 'sna_window_style_round', text='Window Placement', icon_value=0, emboss=True) if (bpy.context.scene.sna_window_style_round != 'NONE'): col_765D0 = box_E2A99.column(heading='', align=False) col_765D0.alert = False col_765D0.enabled = True col_765D0.active = True col_765D0.use_property_split = False col_765D0.use_property_decorate = False col_765D0.scale_x = 1.0 col_765D0.scale_y = 1.0 col_765D0.alignment = 'Expand'.upper() col_765D0.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" col_765D0.prop(bpy.context.scene, 'sna_round_window_amount', text='Window Count', icon_value=0, emboss=True) col_765D0.prop(bpy.context.scene, 'sna_round_window_width', text='Window Width %', icon_value=0, emboss=True) col_765D0.prop(bpy.context.scene, 'sna_round_window_height', text='Window Height %', icon_value=0, emboss=True) col_05349.separator(factor=1.0000016689300537) row_63B4A = col_05349.row(heading='', align=False) row_63B4A.alert = False row_63B4A.enabled = True row_63B4A.active = True row_63B4A.use_property_split = False row_63B4A.use_property_decorate = False row_63B4A.scale_x = 1.0 row_63B4A.scale_y = 2.440000057220459 row_63B4A.alignment = 'Expand'.upper() row_63B4A.operator_context = "INVOKE_DEFAULT" if True else "EXEC_DEFAULT" op = row_63B4A.operator('sna.gen_round_room_a43ca', text='Generate Room', icon_value=string_to_icon( 'SHAPEKEY_DATA'), emboss=True, depress=False) class SNA_OT_Gen_Room_1803A(bpy.types.Operator): bl_idname = "sna.gen_room_1803a" bl_label = "Gen room" bl_description = "Create room" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): if bpy.app.version >= (3, 0, 0) and True: cls.poll_message_set('') return not False def execute(self, context): room_width = bpy.context.scene.sna_room_width room_depth = bpy.context.scene.sna_room_depth room_height = bpy.context.scene.sna_room_height wall_thickness = bpy.context.scene.sna_wall_thickness window_placement = bpy.context.scene.sna_windows_enum window_count = bpy.context.scene.sna_windows_count window_width_pct = bpy.context.scene.sna_windows_width window_height_pct = bpy.context.scene.sna_windows_height window_type = bpy.context.scene.sna_window_style import math # 确保大的值为room_depth,小的值为room_width if room_width > room_depth: # 交换值 room_width, room_depth = room_depth, room_width print(f"自动调整房间尺寸:宽度={room_width}m,深度={room_depth}m") # 在generate_room函数中,将拱门生成函数定义在调用之前 def generate_room( room_width, room_depth, room_height, wall_thickness, window_placement, window_count, window_width_pct, window_height_pct, window_type='SQUARE', window_divisions=2, sash_bottom_pct=0.55): """ 1) Make floor & walls as separate cubes (scale applied) 2) Carve windows from each wall (cutters & panes all apply scale) 3) Boolean-union walls into the floor 4) Join sash-frame bits, then weld stray verts 5) Add ceiling (天花板) 6) Add arch door (拱门) """ extra = 0.01 inset = (wall_thickness - extra) / 2.0 glass_thickness = extra ceiling_thickness = 0.10 # 天花板厚度10cm # 拱门生成函数(定义在generate_room内部,但在使用之前) def add_arch_to_room(room_obj, room_width, room_depth, room_height, wall_thickness): """创建独立的拱门墙面""" arch_placement = bpy.context.scene.sna_arch_placement # 如果选择"无",则不生成拱门 if arch_placement == 'NONE': print("未选择拱门位置,跳过拱门生成") return # 根据规则计算拱门参数 # 基础空间:5x3x2.8,拱门距背景墙1米,空间每加1米长,距离增加30cm base_distance = 1.0 # 基础距离1米 distance_increase = 0.3 # 每增加1米距离增加30cm extra_length = room_depth - 5.0 # 超出基础5米的长度(不是3米) arch_distance = base_distance + \ (extra_length * distance_increase) # 门洞宽度:基础1.2米,空间宽度每加1米,门洞宽度+30cm base_width = 1.2 # 基础宽度1.2米 width_increase = 0.3 # 每增加1米宽度增加30cm extra_width = room_width - 3.0 # 超出基础3米的宽度(不是5米) arch_width = base_width + (extra_width * width_increase) # 门洞高度:根据层高确定 arch_height = 2.4 # 默认2.4米 if room_height >= 3.2: arch_height = 2.7 elif room_height >= 3.0: arch_height = 2.6 elif room_height >= 2.8: arch_height = 2.4 # 拱门墙面厚度 arch_wall_thickness = 0.10 # 10cm厚 print(f"拱门墙面参数计算:") print(f" 房间尺寸: {room_width}x{room_depth}x{room_height}") print(f" 拱门墙面距离背景墙: {arch_distance}m") print(f" 拱门宽度: {arch_width}m") print(f" 拱门高度: {arch_height}m") print(f" 拱门墙面厚度: {arch_wall_thickness}m") # 创建拱门墙面(复制并移动选择的墙面) arch_wall = create_arch_wall_by_copy( room_width, room_depth, room_height, arch_wall_thickness, arch_placement, arch_distance) if arch_wall: # 在拱门墙面上挖洞 # 增加切割器厚度,确保能完全穿透拱门墙面 arch_cutter = create_arch_cutter( arch_width, arch_height, arch_wall_thickness + 0.05) # 增加厚度确保穿透 if arch_cutter: # 设置拱门切割器位置(相对于拱门墙面) if arch_placement == 'BACK': # 在后墙前面创建拱门墙面,在墙面上挖洞 # 拱门墙面位置:(0, room_depth - distance - wall_thickness, 0) # 切割器应该在拱门墙面中心:(room_width/2, room_depth - distance - wall_thickness, 0) arch_cutter.location = ( room_width/2, # X居中 room_depth - arch_distance - arch_wall_thickness + arch_wall_thickness/2, # Y在拱门墙面中心,多加半个墙体厚度 0 # Z从地面开始 ) # 参考COPY4的旋转设置:绕Y轴旋转90度,使其垂直于墙面 arch_cutter.rotation_euler = (0, 0, 0) # 使用布尔运算在拱门墙面上挖洞 print( f"开始布尔运算,拱门墙面: {arch_wall.name}, 切割器: {arch_cutter.name}") print( f"拱门墙面位置: {arch_wall.location}, 切割器位置: {arch_cutter.location}") print( f"拱门墙面尺寸: {arch_wall.dimensions}, 切割器尺寸: {arch_cutter.dimensions}") # 确保切割器完全穿透拱门墙面 mod = arch_wall.modifiers.new("ArchCut", 'BOOLEAN') mod.operation = 'DIFFERENCE' mod.object = arch_cutter bpy.context.view_layer.objects.active = arch_wall bpy.ops.object.modifier_apply(modifier=mod.name) print(f"布尔运算完成") # 删除拱门切割器对象 bpy.data.objects.remove(arch_cutter, do_unlink=True) print(f"拱门墙面已创建并挖洞,位置: {arch_placement}") def create_arch_wall_by_copy(room_width, room_depth, room_height, wall_thickness, placement, distance): """通过复制墙面创建拱门墙面""" # 创建墙面几何体 bm = bmesh.new() # 房间外轮廓尺寸(包含墙体) room_outer_width = room_width # 直接使用房间宽度,不加墙体厚度 room_outer_depth = room_depth + wall_thickness room_inner_height = room_height if placement == 'BACK': # 复制后墙并前移 # 后墙尺寸:宽度=房间宽度,高度=房间内高度,厚度=10cm wall_width = room_outer_width wall_height = room_inner_height wall_depth = wall_thickness # 拱门墙面位置:在Y=distance处(距背景墙distance米) # 创建墙面顶点(从原点开始,然后移动到正确位置) verts = [ (0, 0, 0), # 左下后(地面顶面) (wall_width, 0, 0), # 右下后(地面顶面) (wall_width, wall_depth, 0), # 右下前(地面顶面) (0, wall_depth, 0), # 左下前(地面顶面) (0, 0, room_inner_height), # 左上后(天花板底面) (wall_width, 0, room_inner_height), # 右上后(天花板底面) (wall_width, wall_depth, room_inner_height), # 右上前(天花板底面) (0, wall_depth, room_inner_height) # 左上前(天花板底面) ] # 添加顶点到bmesh bm_verts = [bm.verts.new(v) for v in verts] bm.verts.ensure_lookup_table() # 创建面 faces = [ [0, 1, 2, 3], # 底面 [4, 5, 6, 7], # 顶面 [0, 4, 7, 3], # 左面 [1, 5, 6, 2], # 右面 [3, 7, 6, 2], # 前面 [0, 4, 5, 1] # 后面 ] for face_verts in faces: bm.faces.new([bm_verts[i] for i in face_verts]) # 创建网格对象 mesh = bpy.data.meshes.new("ArchWallMesh") bm.to_mesh(mesh) bm.free() obj = bpy.data.objects.new("ArchWall", mesh) bpy.context.collection.objects.link(obj) # 设置拱门墙面位置 if placement == 'BACK': # 背景墙位置:Y=room_depth # 拱门墙面位置:Y=room_depth - distance # 调整:向原点方向移动半个墙面厚度,使背面距背景墙前面distance米 obj.location = (0, room_depth - distance - wall_thickness, 0) return obj def create_arch_cutter(width, height, thickness, segments=32): """创建拱门切割器(垂直XY面)""" bm = bmesh.new() radius = width / 2 wall_height = height - radius # 墙体部分高度 # 创建拱门轮廓点(在XZ平面上) profile_verts = [] # 底部矩形部分 - 从左下角开始,逆时针,从地面以下开始,确保完全穿透 profile_verts.extend([ (-radius, 0, -thickness/2), # 左下(地面以下) (radius, 0, -thickness/2), # 右下(地面以下) (radius, 0, wall_height), # 右上(墙体顶部) (-radius, 0, wall_height) # 左上(墙体顶部) ]) # 顶部半圆部分 - 从右到左创建半圆顶点 # 注意:这里需要从右到左,所以角度从0到π for i in range(segments + 1): angle = math.pi * i / segments # 从0到π x = radius * math.cos(angle) z = wall_height + radius * math.sin(angle) profile_verts.append((x, 0, z)) # 拉伸创建3D几何体(沿Y轴拉伸) extrude_profile_to_3d_vertical( bm, profile_verts, thickness, radius, wall_height) # 创建网格对象 mesh = bpy.data.meshes.new("ArchCutterMesh") bm.to_mesh(mesh) bm.free() obj = bpy.data.objects.new("ArchCutter", mesh) bpy.context.collection.objects.link(obj) return obj def extrude_profile_to_3d_vertical(bm, profile_verts, thickness, radius, wall_height): """将2D轮廓沿Y轴拉伸为3D几何体(垂直XY面)""" # 创建前后面 front_verts = [] back_verts = [] for x, y, z in profile_verts: front_verts.append(bm.verts.new((x, thickness/2, z))) back_verts.append(bm.verts.new((x, -thickness/2, z))) bm.verts.ensure_lookup_table() # 创建前面 bm.faces.new(front_verts) # 创建后面 bm.faces.new(back_verts[::-1]) # 反转顺序以保持法向一致 # 创建侧面 - 但不创建矩形和半圆之间的连接面 for i in range(len(profile_verts) - 1): v1_front = front_verts[i] v2_front = front_verts[i + 1] v1_back = back_verts[i] v2_back = back_verts[i + 1] # 检查是否是矩形和半圆之间的连接 # 矩形顶部:Z = wall_height, X = ±radius # 半圆底部:Z = wall_height, X = ±radius # 如果两个顶点都在矩形顶部,则不创建面 if (abs(v1_front.co.z - wall_height) < 0.001 and abs(v2_front.co.z - wall_height) < 0.001 and abs(v1_front.co.x) < radius + 0.001 and abs(v2_front.co.x) < radius + 0.001): continue # 跳过这个面 bm.faces.new([v1_front, v2_front, v2_back, v1_back]) # 连接首尾 v1_front = front_verts[-1] v2_front = front_verts[0] v1_back = back_verts[-1] v2_back = back_verts[0] bm.faces.new([v1_front, v2_front, v2_back, v1_back]) def create_semicircle_arch_geometry(width, height, thickness, segments=32): """创建半圆拱门几何体(保留用于其他用途)""" bm = bmesh.new() radius = width / 2 wall_height = height - radius # 墙体部分高度 # 创建拱门轮廓点 profile_verts = [] # 底部矩形部分 profile_verts.extend([ (-radius, 0, 0), (radius, 0, 0), (radius, wall_height, 0), (-radius, wall_height, 0) ]) # 顶部半圆部分 for i in range(segments + 1): angle = math.pi * i / segments x = radius * math.cos(angle) y = wall_height + radius * math.sin(angle) profile_verts.append((x, y, 0)) # 拉伸创建3D几何体 extrude_profile_to_3d(bm, profile_verts, thickness) # 创建网格对象 mesh = bpy.data.meshes.new("SemicircleArchMesh") bm.to_mesh(mesh) bm.free() obj = bpy.data.objects.new("SemicircleArch", mesh) bpy.context.collection.objects.link(obj) return obj def extrude_profile_to_3d(bm, profile_verts, thickness): """将2D轮廓拉伸为3D几何体(保留用于其他用途)""" # 创建前后面 front_verts = [] back_verts = [] for x, y, z in profile_verts: front_verts.append(bm.verts.new((x, y, thickness/2))) back_verts.append(bm.verts.new((x, y, -thickness/2))) bm.verts.ensure_lookup_table() # 创建前面 bm.faces.new(front_verts) # 创建后面 bm.faces.new(back_verts[::-1]) # 反转顺序以保持法向一致 # 创建侧面 for i in range(len(profile_verts) - 1): v1_front = front_verts[i] v2_front = front_verts[i + 1] v1_back = back_verts[i] v2_back = back_verts[i + 1] bm.faces.new([v1_front, v2_front, v2_back, v1_back]) # 连接首尾 v1_front = front_verts[-1] v2_front = front_verts[0] v1_back = back_verts[-1] v2_back = back_verts[0] bm.faces.new([v1_front, v2_front, v2_back, v1_back]) # — Glass material setup — def get_glass_mat(): mat = bpy.data.materials.get("IRG_GlassMaterial") if not mat: mat = bpy.data.materials.new("IRG_GlassMaterial") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links for n in list(nodes): nodes.remove(n) out = nodes.new(type='ShaderNodeOutputMaterial') mix = nodes.new(type='ShaderNodeMixShader') fres = nodes.new(type='ShaderNodeFresnel') refr = nodes.new(type='ShaderNodeBsdfRefraction') gloss = nodes.new(type='ShaderNodeBsdfGlossy') fres.inputs['IOR'].default_value = 1.5 refr.inputs['IOR'].default_value = 1.5 refr.inputs['Roughness'].default_value = 0.0 gloss.inputs['Roughness'].default_value = 0.0 fres.location = (-400, 200) refr.location = (-200, 300) gloss.location = (-200, 100) mix.location = (0, 200) out.location = (200, 200) links.new(fres.outputs['Fac'], mix.inputs['Fac']) links.new(refr.outputs['BSDF'], mix.inputs[1]) links.new(gloss.outputs['BSDF'], mix.inputs[2]) links.new(mix.outputs['Shader'], out.inputs['Surface']) return mat glass_mat = get_glass_mat() # 1) Create floor & walls, then apply scale def make_cube(name, loc, scale): bpy.ops.mesh.primitive_cube_add(location=loc) o = bpy.context.active_object o.name = name o.scale = scale bpy.ops.object.transform_apply( location=False, rotation=False, scale=True) return o # 房间内空尺寸(用户输入的尺寸) room_inner_width = room_width room_inner_depth = room_depth room_inner_height = room_height # 房间外轮廓尺寸(包含墙体) room_outer_width = room_inner_width + wall_thickness # 只有一边有墙 room_outer_depth = room_inner_depth + wall_thickness # 只有一边有墙 room_outer_height = room_inner_height + 2 * wall_thickness # 上下都有墙(地面+天花板) floor = make_cube("Floor", # 地面顶面在Z=0,底面在Z=-wall_thickness (room_outer_width/2, room_outer_depth/2, -wall_thickness/2), (room_outer_width/2, room_outer_depth/2, wall_thickness/2)) # 创建天花板 ceiling = make_cube("Ceiling", (room_outer_width/2, room_outer_depth/2, # 天花板底面在Z=room_height,顶面在Z=room_height+wall_thickness room_height + wall_thickness/2), (room_outer_width/2, room_outer_depth/2, wall_thickness/2)) back_wall = make_cube("BackWall", (room_outer_width/2, room_outer_depth - wall_thickness/2, room_height/2), (room_outer_width/2, wall_thickness/2, room_height/2)) # 生成X=0位置的侧墙,垂直XZ面 side_wall = make_cube("SideWall", (wall_thickness/2, room_outer_depth/2, room_height/2), (wall_thickness/2, room_outer_depth/2, room_height/2)) # 2) Window-cut helpers with scale‐apply def cube_cut(loc, sx, sy, sz): bpy.ops.mesh.primitive_cube_add(location=loc) o = bpy.context.active_object o.scale = (sx, sy, sz) bpy.ops.object.transform_apply( location=False, rotation=False, scale=True) return o def cyl_cut(loc, dia, depth, rot): bpy.ops.mesh.primitive_cylinder_add( vertices=64, radius=dia/2, depth=depth, location=loc, rotation=rot ) o = bpy.context.active_object # scale is baked into radius/depth above, so just apply bpy.ops.object.transform_apply( location=False, rotation=False, scale=True) return o # carve windows out of a wall def add_windows(wall, axis, wall_len): frames = [] glasses = [] margin = wall_len * 0.1 slot = (wall_len - 2*margin) / window_count for i in range(window_count): win_h = room_height * window_height_pct z0 = room_height/2 if axis == 'X': x0 = margin + (i+0.5)*slot y0 = room_depth - wall_thickness/2 else: y0 = margin + (i+0.5)*slot x0 = room_width - wall_thickness/2 win_w = slot * window_width_pct pos = (x0, y0, z0) depth_cut = wall_thickness + extra # Boolean cutter if window_type == 'ROUND': dia = min(win_w, win_h) rot = (math.pi/2, 0, 0) if axis == 'X' else ( math.pi/2, 0, math.pi/2) cutter = cyl_cut(pos, dia, depth_cut, rot) else: sx = win_w/2 if axis == 'X' else depth_cut/2 sy = depth_cut/2 if axis == 'X' else win_w/2 sz = win_h/2 cutter = cube_cut(pos, sx, sy, sz) # subtract cutter mod = wall.modifiers.new(f"Win_{axis}_{i}", 'BOOLEAN') mod.operation = 'DIFFERENCE' mod.object = cutter bpy.context.view_layer.objects.active = wall bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(cutter, do_unlink=True) # add glass panes, name & apply scale if window_type == 'ROUND': glass_loc = ( x0, y0-(wall_thickness/2-glass_thickness/2), z0 ) if axis == 'X' else ( x0-(wall_thickness/2-glass_thickness/2), y0, z0 ) g = cyl_cut(glass_loc, dia, glass_thickness, rot) g.name = "IRG_IsoWindow" g.data.materials.append(glass_mat) glasses.append(g) continue # sash or single pane if window_type == 'SASH' and window_divisions == 2: ft = min(win_w, win_h)*0.08 bottom_h = win_h * sash_bottom_pct top_h = win_h - bottom_h - ft # bottom pane bot_cz = z0 - win_h/2 + bottom_h/2 loc_b = (x0, y0-(wall_thickness/2-glass_thickness/2), bot_cz) \ if axis == 'X' else \ (x0-(wall_thickness/2-glass_thickness/2), y0, bot_cz) g_bot = cube_cut(loc_b, *((win_w/2, glass_thickness/2, bottom_h/2) if axis == 'X' else (glass_thickness/2, win_w/2, bottom_h/2))) g_bot.name = "IRG_IsoWindow" g_bot.data.materials.append(glass_mat) glasses.append(g_bot) # divider z_div = z0 - win_h/2 + bottom_h + ft/2 d = cube_cut((x0, y0, z_div), *((win_w/2, depth_cut/2, ft/2) if axis == 'X' else (depth_cut/2, win_w/2, ft/2))) frames.append(d) # top pane top_cz = z0 - win_h/2 + bottom_h + ft + top_h/2 loc_t = (x0, y0-(wall_thickness/2-glass_thickness/2), top_cz) \ if axis == 'X' else \ (x0-(wall_thickness/2-glass_thickness/2), y0, top_cz) g_top = cube_cut(loc_t, *((win_w/2, glass_thickness/2, top_h/2) if axis == 'X' else (glass_thickness/2, win_w/2, top_h/2))) g_top.name = "IRG_IsoWindow" g_top.data.materials.append(glass_mat) glasses.append(g_top) else: # single pane loc_g = (x0, y0-(wall_thickness/2-glass_thickness/2), z0) \ if axis == 'X' else \ (x0-(wall_thickness/2-glass_thickness/2), y0, z0) g = cube_cut(loc_g, *((win_w/2, glass_thickness/2, win_h/2) if axis == 'X' else (glass_thickness/2, win_w/2, win_h/2))) g.name = "IRG_IsoWindow" g.data.materials.append(glass_mat) glasses.append(g) return frames, glasses # carve windows frame_objs = [] if window_placement in {'BACK', 'BOTH'}: f, _ = add_windows(back_wall, 'X', room_width) frame_objs += f if window_placement in {'SIDE', 'BOTH'}: # 改为'X'轴,因为对面墙也是X轴方向 f, _ = add_windows(side_wall, 'X', room_width) frame_objs += f # 3) 取消墙面与地面合并,保持独立对象 # for wall in (back_wall, side_wall): # 注释掉墙面合并 # mod = floor.modifiers.new(f"Union_{wall.name}", 'BOOLEAN') # mod.operation = 'UNION' # mod.object = wall # bpy.context.view_layer.objects.active = floor # bpy.ops.object.modifier_apply(modifier=mod.name) # bpy.data.objects.remove(wall, do_unlink=True) # 4) 取消窗户框架合并,保持独立对象 # bpy.ops.object.select_all(action='DESELECT') # floor.select_set(True) # for o in frame_objs: # o.select_set(True) # bpy.context.view_layer.objects.active = floor # bpy.ops.object.join() # floor.name = "IRG_IsoRoom" # 5) Final weld + normals (只对地面进行) bpy.context.view_layer.objects.active = floor bpy.ops.object.mode_set(mode='EDIT') try: bpy.ops.mesh.merge_by_distance(distance=0.001) except AttributeError: bpy.ops.mesh.remove_doubles( threshold=0.001, use_unselected=False) bpy.ops.mesh.normals_make_consistent(inside=False) bpy.ops.object.mode_set(mode='OBJECT') # 6) 取消天花板与房间主体合并,保持独立对象 # bpy.ops.object.select_all(action='DESELECT') # floor.select_set(True) # ceiling.select_set(True) # bpy.context.view_layer.objects.active = floor # bpy.ops.object.join() # floor.name = "IRG_IsoRoom_WithCeiling" # 7) 添加拱门 if bpy.context.scene.sna_arch_settings: add_arch_to_room(floor, room_width, room_depth, room_height, wall_thickness) # 8) 最终清理(只对地面进行) bpy.context.view_layer.objects.active = floor bpy.ops.object.mode_set(mode='EDIT') try: bpy.ops.mesh.merge_by_distance(distance=0.001) except AttributeError: bpy.ops.mesh.remove_doubles( threshold=0.001, use_unselected=False) bpy.ops.mesh.normals_make_consistent(inside=False) bpy.ops.object.mode_set(mode='OBJECT') # 9) 将整个房间在X轴负方向上位移墙厚度的距离 floor.location.x -= wall_thickness back_wall.location.x -= wall_thickness side_wall.location.x -= wall_thickness ceiling.location.x -= wall_thickness print(f"房间已向X轴负方向位移 {wall_thickness}m") generate_room(room_width, room_depth, room_height, wall_thickness, window_placement, window_count, window_width_pct, window_height_pct, window_type, window_divisions=2, sash_bottom_pct=0.55) return {"FINISHED"} def invoke(self, context, event): return self.execute(context) class SNA_OT_Gen_Round_Room_A43Ca(bpy.types.Operator): bl_idname = "sna.gen_round_room_a43ca" bl_label = "Gen Round Room" bl_description = "Create Round Iso Room" bl_options = {"REGISTER", "UNDO"} @classmethod def poll(cls, context): if bpy.app.version >= (3, 0, 0) and True: cls.poll_message_set('') return not False def execute(self, context): diameter = bpy.context.scene.sna_room_diameter_round floor_thickness = bpy.context.scene.sna_room_floor_thickness_round wall_height = bpy.context.scene.sna_room_height_round wall_thickness = bpy.context.scene.sna_wall_thickness_round window_count = bpy.context.scene.sna_round_window_amount window_width_pct = bpy.context.scene.sna_round_window_width window_height_pct = bpy.context.scene.sna_round_window_height window_type = bpy.context.scene.sna_window_style_round def get_glass_mat(): mat = bpy.data.materials.get("IRG_GlassMaterial") if not mat: mat = bpy.data.materials.new("IRG_GlassMaterial") mat.use_nodes = True nodes = mat.node_tree.nodes links = mat.node_tree.links for n in list(nodes): nodes.remove(n) out = nodes.new('ShaderNodeOutputMaterial') mix = nodes.new('ShaderNodeMixShader') fres = nodes.new('ShaderNodeFresnel') refr = nodes.new('ShaderNodeBsdfRefraction') gloss = nodes.new('ShaderNodeBsdfGlossy') fres.inputs['IOR'].default_value = 1.5 refr.inputs['IOR'].default_value = 1.5 refr.inputs['Roughness'].default_value = 0.0 gloss.inputs['Roughness'].default_value = 0.0 links.new(fres.outputs['Fac'], mix.inputs['Fac']) links.new(refr.outputs['BSDF'], mix.inputs[1]) links.new(gloss.outputs['BSDF'], mix.inputs[2]) links.new(mix.outputs['Shader'], out.inputs['Surface']) return mat def make_curved_glass(name, θ_center, arc_angle, radius, height, segments, z_offset, mat=None): bm = bmesh.new() verts_top = [] verts_bottom = [] for i in range(segments + 1): θ = θ_center - arc_angle / 2 + i * (arc_angle / segments) x = radius * math.cos(θ) y = radius * math.sin(θ) verts_top.append(bm.verts.new((x, y, z_offset + height / 2))) verts_bottom.append(bm.verts.new( (x, y, z_offset - height / 2))) bm.verts.ensure_lookup_table() for i in range(segments): bm.faces.new( (verts_top[i], verts_bottom[i], verts_bottom[i+1], verts_top[i+1])) mesh = bpy.data.meshes.new(name) bm.to_mesh(mesh) bm.free() obj = bpy.data.objects.new(name, mesh) bpy.context.collection.objects.link(obj) obj.location.z = height / 2 if mat: obj.data.materials.append(mat) return obj def generate_round_room( diameter, floor_thickness, wall_height, wall_thickness, window_count, window_width_pct, window_height_pct, window_type='SQUARE', sash_bottom_pct=0.55 ): extra = 0.01 R_out = diameter / 2.0 R_mid = R_out - wall_thickness / 2.0 glass_off = extra / 2.0 glass_mat = get_glass_mat() def make_cylinder(name, radius, depth, loc, rot=(0, 0, 0), verts=64): bpy.ops.mesh.primitive_cylinder_add( vertices=verts, radius=radius, depth=depth, location=loc, rotation=rot) o = bpy.context.active_object o.name = name bpy.ops.object.transform_apply( location=False, rotation=False, scale=True) return o def make_cube(name, loc, half_extents, rot=(0, 0, 0)): bpy.ops.mesh.primitive_cube_add(location=loc) o = bpy.context.active_object o.name = name o.scale = half_extents o.rotation_euler = rot bpy.ops.object.transform_apply( location=False, rotation=True, scale=True) return o floor = make_cylinder( "Floor", R_out, floor_thickness, (0, 0, floor_thickness/2)) wall = make_cylinder("WallOuter", R_out, wall_height, (0, 0, wall_height/2)) inner = make_cylinder( "WallInner", R_out-wall_thickness, wall_height+extra, (0, 0, wall_height/2)) mod = wall.modifiers.new("HollowWall", 'BOOLEAN') mod.operation, mod.object = 'DIFFERENCE', inner bpy.context.view_layer.objects.active = wall bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(inner, do_unlink=True) cutter = make_cube( "FrontCutter", (0, R_out, wall_height/2), (2*R_out, R_out, wall_height)) mod = wall.modifiers.new("CutFront", 'BOOLEAN') mod.operation, mod.object = 'DIFFERENCE', cutter bpy.context.view_layer.objects.active = wall bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(cutter, do_unlink=True) panes = [] # Only generate windows if applicable if window_type.upper() in {'SQUARE', 'SASH'} and window_count > 0: start_ang, end_ang = math.pi, 2*math.pi seg = (end_ang - start_ang) / window_count for i in range(window_count): θ = start_ang + (i+0.5)*seg x, y = R_mid * math.cos(θ), R_mid * math.sin(θ) z = wall_height / 2 win_h = wall_height * window_height_pct win_ang = seg * window_width_pct chord = 2 * R_mid * math.sin(win_ang / 2) cut = make_cube( f"Cut_{i}", (x, y, z), (wall_thickness, chord/2 + extra, win_h/2 + extra), rot=(0, 0, θ)) mod = wall.modifiers.new(f"WinBool_{i}", 'BOOLEAN') mod.operation, mod.object = 'DIFFERENCE', cut bpy.context.view_layer.objects.active = wall bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(cut, do_unlink=True) objs = [] if window_type.upper() == 'SASH': ft = min(chord, win_h) * 0.08 bot_h = win_h * sash_bottom_pct top_h = win_h - bot_h - ft bz = z - win_h/2 bot = make_curved_glass( f"IRG_IsoRoom_Window_SashBot_{i}", θ, win_ang, R_out - glass_off, bot_h, 16, bz, glass_mat) objs.append(bot) dz = bz + bot_h bar = make_curved_glass( f"IRG_IsoRoom_Window_SashBar_{i}", θ, win_ang, R_out - glass_off, ft, 16, dz, None) objs.append(bar) tz = dz + ft top = make_curved_glass( f"IRG_IsoRoom_Window_SashTop_{i}", θ, win_ang, R_out - glass_off, top_h, 16, tz, glass_mat) objs.append(top) if window_type.upper() == 'SQUARE': pane = make_curved_glass( f"IRG_IsoRoom_Window_Glass_{i}", θ, win_ang, R_out - glass_off, win_h, 16, z - win_h/2, glass_mat) objs.append(pane) panes.extend(objs) mod = floor.modifiers.new("WallUnion", 'BOOLEAN') mod.operation, mod.object = 'UNION', wall bpy.context.view_layer.objects.active = floor bpy.ops.object.modifier_apply(modifier=mod.name) bpy.data.objects.remove(wall, do_unlink=True) me = floor.data bm = bmesh.new() bm.from_mesh(me) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.001) bm.normal_update() bm.to_mesh(me) bm.free() floor.name = "IRG_IsoRoom_Round" return floor, panes generate_round_room( diameter, floor_thickness, wall_height, wall_thickness, window_count, window_width_pct, window_height_pct, window_type ) return {"FINISHED"} def invoke(self, context, event): return self.execute(context) def register(): global _icons _icons = bpy.utils.previews.new() bpy.types.Scene.sna_room_width = bpy.props.FloatProperty( name='Room Width', description='', default=1.0, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_room_depth = bpy.props.FloatProperty( name='Room Depth', description='', default=1.0, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_room_height = bpy.props.FloatProperty( name='Room Height', description='', default=1.0, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_wall_thickness = bpy.props.FloatProperty( name='Wall Thickness', description='', default=0.10000000149011612, subtype='NONE', unit='NONE', min=0.009999999776482582, step=3, precision=2) bpy.types.Scene.sna_windows_enum = bpy.props.EnumProperty(name='Windows Enum', description='', items=[('NONE', 'NONE', 'None', 0, 0), ( 'BACK', 'BACK', 'Back Wall Only', 0, 1), ('SIDE', 'SIDE', 'Side Wall Only', 0, 2), ('BOTH', 'BOTH', 'Both Walls', 0, 3)]) bpy.types.Scene.sna_windows_count = bpy.props.IntProperty( name='Windows Count', description='', default=1, subtype='NONE', min=1, max=5) bpy.types.Scene.sna_windows_width = bpy.props.FloatProperty( name='Windows Width', description='', default=0.25, subtype='NONE', unit='NONE', min=0.10000000149011612, max=1.0, step=3, precision=2) bpy.types.Scene.sna_windows_height = bpy.props.FloatProperty( name='Windows Height', description='', default=0.25, subtype='NONE', unit='NONE', min=0.10000000149011612, max=1.0, step=3, precision=2) bpy.types.Scene.sna_window_style = bpy.props.EnumProperty(name='Window Style', description='', items=[( 'SQUARE', 'SQUARE', 'SQUARE', 0, 0), ('ROUND', 'ROUND', 'ROUND', 0, 1), ('SASH', 'SASH', 'SASH', 0, 2)]) bpy.types.Scene.sna_room_settings = bpy.props.BoolProperty( name='Room Settings', description='', default=False) bpy.types.Scene.sna_winows_settings = bpy.props.BoolProperty( name='Winows settings', description='', default=False) bpy.types.Scene.sna_style = bpy.props.EnumProperty(name='Style', description='', items=[( 'Square', 'Square', 'Square Room', 0, 0), ('Round', 'Round', 'Round Room', 0, 1)]) # 添加拱门相关属性(移除前墙选项) bpy.types.Scene.sna_arch_settings = bpy.props.BoolProperty( name='Arch Settings', description='', default=False) bpy.types.Scene.sna_arch_placement = bpy.props.EnumProperty( name='Arch Placement', description='', items=[('NONE', 'None', 'No Arch', 0, 0), ('BACK', 'Back Wall', 'Back Wall', 0, 1)]) bpy.types.Scene.sna_arch_width = bpy.props.FloatProperty( name='Arch Width', description='', default=1.2, subtype='NONE', unit='NONE', min=0.5, max=3.0, step=3, precision=2) bpy.types.Scene.sna_arch_height = bpy.props.FloatProperty( name='Arch Height', description='', default=2.4, subtype='NONE', unit='NONE', min=1.5, max=4.0, step=3, precision=2) bpy.types.Scene.sna_arch_thickness = bpy.props.FloatProperty( name='Arch Thickness', description='', default=0.10, subtype='NONE', unit='NONE', min=0.05, max=0.5, step=3, precision=2) bpy.types.Scene.sna_round_room_settings = bpy.props.BoolProperty( name='Round Room Settings', description='', default=False) bpy.types.Scene.sna_round_window_settings = bpy.props.BoolProperty( name='Round Window Settings', description='', default=False) bpy.types.Scene.sna_room_diameter_round = bpy.props.FloatProperty( name='Room diameter Round', description='', default=10.0, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_room_floor_thickness_round = bpy.props.FloatProperty( name='Room Floor thickness Round', description='', default=0.10000000149011612, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_room_height_round = bpy.props.FloatProperty( name='Room Height Round', description='', default=3.0, subtype='NONE', unit='NONE', min=0.10000000149011612, step=3, precision=2) bpy.types.Scene.sna_wall_thickness_round = bpy.props.FloatProperty( name='Wall Thickness round', description='', default=0.20000000298023224, subtype='NONE', unit='NONE', min=0.009999999776482582, step=3, precision=2) bpy.types.Scene.sna_window_style_round = bpy.props.EnumProperty(name='Window Style Round', description='', items=[( 'NONE', 'NONE', 'No Windows', 0, 0), ('SQUARE', 'SQUARE', 'Square Windows', 0, 1), ('SASH', 'SASH', 'Sash Windows', 0, 2)]) bpy.types.Scene.sna_round_window_amount = bpy.props.IntProperty( name='Round Window Amount', description='', default=6, subtype='NONE', min=1) bpy.types.Scene.sna_round_window_width = bpy.props.FloatProperty( name='Round Window Width', description='', default=0.699999988079071, subtype='NONE', unit='NONE', min=0.10000000149011612, max=1.0, step=3, precision=2) bpy.types.Scene.sna_round_window_height = bpy.props.FloatProperty( name='Round Window Height', description='', default=0.6000000238418579, subtype='NONE', unit='NONE', min=0.10000000149011612, max=1.0, step=3, precision=2) bpy.utils.register_class(SNA_PT_IRG_3542C) bpy.utils.register_class(SNA_OT_Gen_Room_1803A) bpy.utils.register_class(SNA_OT_Gen_Round_Room_A43Ca) def unregister(): global _icons bpy.utils.previews.remove(_icons) wm = bpy.context.window_manager kc = wm.keyconfigs.addon for km, kmi in addon_keymaps.values(): km.keymap_items.remove(kmi) addon_keymaps.clear() # 清理拱门相关属性 del bpy.types.Scene.sna_arch_thickness del bpy.types.Scene.sna_arch_height del bpy.types.Scene.sna_arch_width del bpy.types.Scene.sna_arch_placement del bpy.types.Scene.sna_arch_settings del bpy.types.Scene.sna_round_window_height del bpy.types.Scene.sna_round_window_width del bpy.types.Scene.sna_round_window_amount del bpy.types.Scene.sna_window_style_round del bpy.types.Scene.sna_wall_thickness_round del bpy.types.Scene.sna_room_height_round del bpy.types.Scene.sna_room_floor_thickness_round del bpy.types.Scene.sna_room_diameter_round del bpy.types.Scene.sna_round_window_settings del bpy.types.Scene.sna_round_room_settings del bpy.types.Scene.sna_style del bpy.types.Scene.sna_winows_settings del bpy.types.Scene.sna_room_settings del bpy.types.Scene.sna_window_style del bpy.types.Scene.sna_windows_height del bpy.types.Scene.sna_windows_width del bpy.types.Scene.sna_windows_count del bpy.types.Scene.sna_windows_enum del bpy.types.Scene.sna_wall_thickness del bpy.types.Scene.sna_room_height del bpy.types.Scene.sna_room_depth del bpy.types.Scene.sna_room_width bpy.utils.unregister_class(SNA_PT_IRG_3542C) bpy.utils.unregister_class(SNA_OT_Gen_Room_1803A) bpy.utils.unregister_class(SNA_OT_Gen_Round_Room_A43Ca)