1268 lines
62 KiB
Python
1268 lines
62 KiB
Python
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation; either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful, but
|
||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
# General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
||
import 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)
|