mirror of
https://github.com/blender/blender-addons.git
synced 2025-08-01 16:06:15 +00:00
1680 lines
57 KiB
Python
1680 lines
57 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# ----------------------------------------------------------
|
|
# Automatic generation of rooms
|
|
# Author: Antonio Vazquez (antonioya) and Eduardo Gutierrez
|
|
#
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnresolvedReferences
|
|
import bpy
|
|
from math import sin, cos, fabs, radians
|
|
from mathutils import Vector
|
|
from datetime import datetime
|
|
from time import time
|
|
from os import path
|
|
from bpy.types import Operator, PropertyGroup, Object, Panel
|
|
from bpy.props import StringProperty, FloatProperty, BoolProperty, IntProperty, FloatVectorProperty, \
|
|
CollectionProperty, EnumProperty
|
|
from bpy_extras.io_utils import ExportHelper, ImportHelper
|
|
from .achm_tools import *
|
|
|
|
|
|
# ----------------------------------------------------------
|
|
# Export menu UI
|
|
# ----------------------------------------------------------
|
|
class ARCHIMESH_OT_ExportRoom(Operator, ExportHelper):
|
|
bl_idname = "io_export.roomdata"
|
|
bl_description = 'Export Room data (.dat)'
|
|
bl_category = 'View'
|
|
bl_label = "Export"
|
|
|
|
# From ExportHelper. Filter filenames.
|
|
filename_ext = ".dat"
|
|
filter_glob: StringProperty(
|
|
default="*.dat",
|
|
options={'HIDDEN'},
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
name="File Path",
|
|
description="File path used for exporting room data file",
|
|
maxlen=1024, default="",
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Execute
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def execute(self, context):
|
|
print("Exporting:", self.properties.filepath)
|
|
# noinspection PyBroadException
|
|
try:
|
|
myobj = bpy.context.active_object
|
|
mydata = myobj.RoomGenerator[0]
|
|
|
|
# -------------------------------
|
|
# extract path and filename
|
|
# -------------------------------
|
|
(filepath, filename) = path.split(self.properties.filepath)
|
|
print('Exporting %s' % filename)
|
|
# -------------------------------
|
|
# Open output file
|
|
# -------------------------------
|
|
realpath = path.realpath(path.expanduser(self.properties.filepath))
|
|
fout = open(realpath, 'w')
|
|
|
|
st = datetime.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S')
|
|
fout.write("# Archimesh room export data\n")
|
|
fout.write("# " + st + "\n")
|
|
fout.write("#======================================================\n")
|
|
|
|
fout.write("name=" + myobj.name + "\n")
|
|
fout.write("height=" + str(round(mydata.room_height, 3)) + "\n")
|
|
fout.write("thickness=" + str(round(mydata.wall_width, 3)) + "\n")
|
|
fout.write("inverse=" + str(mydata.inverse) + "\n")
|
|
fout.write("ceiling=" + str(mydata.ceiling) + "\n")
|
|
fout.write("floor=" + str(mydata.floor) + "\n")
|
|
fout.write("close=" + str(mydata.merge) + "\n")
|
|
|
|
# Walls
|
|
fout.write("#\n# Walls\n#\n")
|
|
fout.write("walls=" + str(mydata.wall_num) + "\n")
|
|
i = 0
|
|
for w in mydata.walls:
|
|
if i < mydata.wall_num:
|
|
i += 1
|
|
fout.write("w=" + str(round(w.w, 3)))
|
|
# if w.a == True: # advance
|
|
fout.write(",a=" + str(w.a) + ",")
|
|
fout.write("r=" + str(round(w.r, 1)) + ",")
|
|
fout.write("h=" + str(w.h) + ",")
|
|
fout.write("m=" + str(round(w.m, 3)) + ",")
|
|
fout.write("f=" + str(round(w.f, 3)) + ",")
|
|
fout.write("c=" + str(w.curved) + ",")
|
|
fout.write("cf=" + str(round(w.curve_factor, 1)) + ",")
|
|
fout.write("cd=" + str(round(w.curve_arc_deg, 1)) + ",")
|
|
fout.write("cs=" + str(w.curve_steps) + "\n")
|
|
# else:
|
|
# fOut.write("\n")
|
|
|
|
# Baseboard
|
|
fout.write("#\n# Baseboard\n#\n")
|
|
fout.write("baseboard=" + str(mydata.baseboard) + "\n")
|
|
fout.write("baseh=" + str(round(mydata.base_height, 3)) + "\n")
|
|
fout.write("baset=" + str(round(mydata.base_width, 3)) + "\n")
|
|
# Shell
|
|
fout.write("#\n# Wall Cover\n#\n")
|
|
fout.write("shell=" + str(mydata.shell) + "\n")
|
|
fout.write("shellh=" + str(round(mydata.shell_height, 3)) + "\n")
|
|
fout.write("shellt=" + str(round(mydata.shell_thick, 3)) + "\n")
|
|
fout.write("shellf=" + str(round(mydata.shell_factor, 3)) + "\n")
|
|
fout.write("shellb=" + str(round(mydata.shell_bfactor, 3)) + "\n")
|
|
|
|
# Materials
|
|
fout.write("#\n# Materials\n#\n")
|
|
fout.write("materials=" + str(mydata.crt_mat) + "\n")
|
|
|
|
fout.close()
|
|
self.report({'INFO'}, realpath + "successfully exported")
|
|
except:
|
|
self.report({'ERROR'}, "Unable to export room data")
|
|
|
|
return {'FINISHED'}
|
|
|
|
# ----------------------------------------------------------
|
|
# Invoke
|
|
# ----------------------------------------------------------
|
|
|
|
# noinspection PyUnusedLocal
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
# ----------------------------------------------------------
|
|
# Import menu UI
|
|
# ----------------------------------------------------------
|
|
class ARCHIMESH_OT_ImportRoom(Operator, ImportHelper):
|
|
bl_idname = "io_import.roomdata"
|
|
bl_description = 'Import Room data (.dat)'
|
|
bl_category = 'View'
|
|
bl_label = "Import"
|
|
|
|
# From Helper. Filter filenames.
|
|
filename_ext = ".dat"
|
|
filter_glob: StringProperty(
|
|
default="*.dat",
|
|
options={'HIDDEN'},
|
|
)
|
|
|
|
filepath: StringProperty(
|
|
name="File Path",
|
|
description="File path used for exporting room data file",
|
|
maxlen=1024, default="",
|
|
)
|
|
|
|
# ----------------------------------------------------------
|
|
# Execute
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def execute(self, context):
|
|
print("Importing:", self.properties.filepath)
|
|
# noinspection PyBroadException
|
|
try:
|
|
realpath = path.realpath(path.expanduser(self.properties.filepath))
|
|
finput = open(realpath)
|
|
line = finput.readline()
|
|
|
|
myobj = bpy.context.active_object
|
|
mydata = myobj.RoomGenerator[0]
|
|
# ----------------------------------
|
|
# Loop all records from file
|
|
# ----------------------------------
|
|
idx = 0 # index of each wall
|
|
while line:
|
|
if line[:1] != '#':
|
|
if "name=" in line.lower():
|
|
myobj.name = line[5:-1]
|
|
|
|
elif "height=" in line.lower():
|
|
mydata.room_height = float(line[7:-1])
|
|
|
|
elif "thickness=" in line.lower():
|
|
mydata.wall_width = float(line[10:-1])
|
|
|
|
elif "inverse=" in line.lower():
|
|
if line[8:-4].upper() == "T":
|
|
mydata.inverse = True
|
|
else:
|
|
mydata.inverse = False
|
|
|
|
elif "ceiling=" in line.lower():
|
|
if line[8:-4].upper() == "T":
|
|
mydata.ceiling = True
|
|
else:
|
|
mydata.ceiling = False
|
|
|
|
elif "floor=" in line.lower():
|
|
if line[6:-4].upper() == "T":
|
|
mydata.floor = True
|
|
else:
|
|
mydata.floor = False
|
|
|
|
elif "close=" in line.lower():
|
|
if line[6:-4].upper() == "T":
|
|
mydata.merge = True
|
|
else:
|
|
mydata.merge = False
|
|
elif "baseboard=" in line.lower():
|
|
if line[10:-4].upper() == "T":
|
|
mydata.baseboard = True
|
|
else:
|
|
mydata.baseboard = False
|
|
elif "baseh=" in line.lower():
|
|
mydata.base_height = float(line[6:-1])
|
|
elif "baset=" in line.lower():
|
|
mydata.base_width = float(line[6:-1])
|
|
elif "shell=" in line.lower():
|
|
if line[6:-4].upper() == "T":
|
|
mydata.shell = True
|
|
else:
|
|
mydata.shell = False
|
|
elif "shellh=" in line.lower():
|
|
mydata.shell_height = float(line[7:-1])
|
|
elif "shellt=" in line.lower():
|
|
mydata.shell_thick = float(line[6:-1])
|
|
elif "shellf=" in line.lower():
|
|
mydata.shell_factor = float(line[6:-1])
|
|
elif "shellb=" in line.lower():
|
|
mydata.shell_bfactor = float(line[6:-1])
|
|
elif "walls=" in line.lower():
|
|
mydata.wall_num = int(line[6:-1])
|
|
|
|
# ---------------------
|
|
# Walls Data
|
|
# ---------------------
|
|
elif "w=" in line.lower() and idx < mydata.wall_num:
|
|
# get all pieces
|
|
buf = line[:-1] + ","
|
|
s = buf.split(",")
|
|
for e in s:
|
|
param = e.lower()
|
|
if "w=" in param:
|
|
mydata.walls[idx].w = float(e[2:])
|
|
elif "a=" in param:
|
|
if "true" == param[2:]:
|
|
mydata.walls[idx].a = True
|
|
else:
|
|
mydata.walls[idx].a = False
|
|
elif "r=" in param:
|
|
mydata.walls[idx].r = float(e[2:])
|
|
elif "h=" in param:
|
|
mydata.walls[idx].h = e[2:]
|
|
elif "m=" in param:
|
|
mydata.walls[idx].m = float(e[2:])
|
|
elif "f=" == param[0:2]:
|
|
mydata.walls[idx].f = float(e[2:])
|
|
elif "c=" in param:
|
|
if "true" == param[2:]:
|
|
mydata.walls[idx].curved = True
|
|
else:
|
|
mydata.walls[idx].curved = False
|
|
elif "cf=" in param:
|
|
mydata.walls[idx].curve_factor = float(e[3:])
|
|
elif "cd=" in param:
|
|
mydata.walls[idx].curve_arc_deg = float(e[3:])
|
|
elif "cs=" in param:
|
|
mydata.walls[idx].curve_steps = int(e[3:])
|
|
idx += 1
|
|
|
|
elif "materials=" in line.lower():
|
|
if line[10:-4].upper() == "T":
|
|
mydata.crt_mat = True
|
|
else:
|
|
mydata.crt_mat = False
|
|
|
|
line = finput.readline()
|
|
|
|
finput.close()
|
|
self.report({'INFO'}, realpath + "successfully imported")
|
|
except:
|
|
self.report({'ERROR'}, "Unable to import room data")
|
|
|
|
return {'FINISHED'}
|
|
|
|
# ----------------------------------------------------------
|
|
# Invoke
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def invoke(self, context, event):
|
|
context.window_manager.fileselect_add(self)
|
|
return {'RUNNING_MODAL'}
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define operator class to create rooms
|
|
# ------------------------------------------------------------------
|
|
class ARCHIMESH_OT_Room(Operator):
|
|
bl_idname = "mesh.archimesh_room"
|
|
bl_label = "Room"
|
|
bl_description = "Generate room with walls, baseboard, floor and ceiling"
|
|
bl_category = 'View'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
# -----------------------------------------------------
|
|
# Draw (create UI interface)
|
|
# -----------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
row.label(text="Use Properties panel (N) to define parms", icon='INFO')
|
|
row = layout.row(align=False)
|
|
row.operator("io_import.roomdata", text="Import", icon='COPYDOWN')
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
def execute(self, context):
|
|
if bpy.context.mode == "OBJECT":
|
|
create_room(self, context)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create main object for the room. The other objects of room will be children of this.
|
|
# ------------------------------------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def create_room(self, context):
|
|
# deselect all objects
|
|
for o in bpy.data.objects:
|
|
o.select_set(False)
|
|
|
|
# we create main object and mesh for walls
|
|
roommesh = bpy.data.meshes.new("Room")
|
|
roomobject = bpy.data.objects.new("Room", roommesh)
|
|
roomobject.location = bpy.context.scene.cursor.location
|
|
bpy.context.collection.objects.link(roomobject)
|
|
roomobject.RoomGenerator.add()
|
|
roomobject.RoomGenerator[0].walls.add()
|
|
|
|
# we shape the walls and create other objects as children of 'RoomObject'.
|
|
shape_walls_and_create_children(roomobject, roommesh)
|
|
|
|
# we select, and activate, main object for the room.
|
|
bpy.context.view_layer.objects.active = roomobject
|
|
roomobject.select_set(True)
|
|
|
|
|
|
# -----------------------------------------------------
|
|
# Verify if solidify exist
|
|
# -----------------------------------------------------
|
|
def is_solidify(myobject):
|
|
flag = False
|
|
try:
|
|
if myobject.modifiers is None:
|
|
return False
|
|
|
|
for mod in myobject.modifiers:
|
|
if mod.type == 'SOLIDIFY':
|
|
flag = True
|
|
break
|
|
return flag
|
|
except AttributeError:
|
|
return False
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Update wall mesh and children objects (baseboard, floor and ceiling).
|
|
# ------------------------------------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def update_room(self, context):
|
|
# When we update, the active object is the main object of the room.
|
|
o = bpy.context.active_object
|
|
oldmesh = o.data
|
|
oldname = o.data.name
|
|
# Now we deselect that room object to not delete it.
|
|
o.select_set(False)
|
|
# and we create a new mesh for the walls:
|
|
tmp_mesh = bpy.data.meshes.new("temp")
|
|
# deselect all objects
|
|
for obj in bpy.data.objects:
|
|
obj.select_set(False)
|
|
# Remove children created by this addon:
|
|
for child in o.children:
|
|
# noinspection PyBroadException
|
|
try:
|
|
if child["archimesh.room_object"]:
|
|
# noinspection PyBroadException
|
|
try:
|
|
# remove child relationship
|
|
for grandchild in child.children:
|
|
grandchild.parent = None
|
|
# remove modifiers
|
|
for mod in child.modifiers:
|
|
bpy.ops.object.modifier_remove(mod)
|
|
except:
|
|
pass
|
|
# clear data
|
|
old = child.data
|
|
child.select_set(True)
|
|
bpy.ops.object.delete()
|
|
bpy.data.meshes.remove(old)
|
|
except:
|
|
pass
|
|
# Finally we create all that again (except main object),
|
|
shape_walls_and_create_children(o, tmp_mesh, True)
|
|
o.data = tmp_mesh
|
|
# Remove data (mesh of active object),
|
|
bpy.data.meshes.remove(oldmesh)
|
|
tmp_mesh.name = oldname
|
|
# and select, and activate, the main object of the room.
|
|
o.select_set(True)
|
|
bpy.context.view_layer.objects.active = o
|
|
|
|
|
|
# -----------------------------------------------------
|
|
# Move Solidify to Top
|
|
# -----------------------------------------------------
|
|
def movetotopsolidify(myobject):
|
|
mymod = None
|
|
try:
|
|
if myobject.modifiers is not None:
|
|
for mod in myobject.modifiers:
|
|
if mod.type == 'SOLIDIFY':
|
|
mymod = mod
|
|
|
|
if mymod is not None:
|
|
while myobject.modifiers[0] != mymod:
|
|
bpy.ops.object.modifier_move_up(modifier=mymod.name)
|
|
except AttributeError:
|
|
return
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Generate walls, baseboard, floor, ceiling and materials.
|
|
# For walls, it only shapes mesh and creates modifier solidify (the modifier, only the first time).
|
|
# And, for the others, it creates object and mesh.
|
|
# ------------------------------------------------------------------------------
|
|
def shape_walls_and_create_children(myroom, tmp_mesh, update=False):
|
|
rp = myroom.RoomGenerator[0] # "rp" means "room properties".
|
|
mybase = None
|
|
myfloor = None
|
|
myceiling = None
|
|
myshell = None
|
|
# Create the walls (only mesh, because the object is 'myRoom', created before).
|
|
create_walls(rp, tmp_mesh, get_blendunits(rp.room_height))
|
|
myroom.data = tmp_mesh
|
|
# Mark Seams
|
|
select_vertices(myroom, [0, 1])
|
|
mark_seam(myroom)
|
|
# Unwrap
|
|
unwrap_mesh(myroom)
|
|
|
|
remove_doubles(myroom)
|
|
set_normals(myroom, not rp.inverse) # inside/outside
|
|
|
|
if rp.wall_width > 0.0:
|
|
if update is False or is_solidify(myroom) is False:
|
|
set_modifier_solidify(myroom, get_blendunits(rp.wall_width))
|
|
else:
|
|
for mod in myroom.modifiers:
|
|
if mod.type == 'SOLIDIFY':
|
|
mod.thickness = rp.wall_width
|
|
# Move to Top SOLIDIFY
|
|
movetotopsolidify(myroom)
|
|
|
|
else: # clear not used SOLIDIFY
|
|
for mod in myroom.modifiers:
|
|
if mod.type == 'SOLIDIFY':
|
|
myroom.modifiers.remove(mod)
|
|
|
|
# Create baseboard
|
|
if rp.baseboard:
|
|
baseboardmesh = bpy.data.meshes.new("Baseboard")
|
|
mybase = bpy.data.objects.new("Baseboard", baseboardmesh)
|
|
mybase.location = (0, 0, 0)
|
|
bpy.context.collection.objects.link(mybase)
|
|
mybase.parent = myroom
|
|
mybase.select_set(True)
|
|
mybase["archimesh.room_object"] = True
|
|
mybase["archimesh.room_baseboard"] = True
|
|
|
|
create_walls(rp, baseboardmesh, get_blendunits(rp.base_height), True)
|
|
set_normals(mybase, rp.inverse) # inside/outside room
|
|
if rp.base_width:
|
|
set_modifier_solidify(mybase, get_blendunits(rp.base_width))
|
|
# Move to Top SOLIDIFY
|
|
movetotopsolidify(mybase)
|
|
# Mark Seams
|
|
select_vertices(mybase, [0, 1])
|
|
mark_seam(mybase)
|
|
# Unwrap
|
|
unwrap_mesh(mybase)
|
|
|
|
# Create floor
|
|
if rp.floor and rp.wall_num > 1:
|
|
myfloor = create_floor(rp, "Floor", myroom)
|
|
myfloor["archimesh.room_object"] = True
|
|
myfloor.parent = myroom
|
|
# Unwrap
|
|
unwrap_mesh(myfloor)
|
|
|
|
# Create ceiling
|
|
if rp.ceiling and rp.wall_num > 1:
|
|
myceiling = create_floor(rp, "Ceiling", myroom)
|
|
myceiling["archimesh.room_object"] = True
|
|
myceiling.parent = myroom
|
|
# Unwrap
|
|
unwrap_mesh(myceiling)
|
|
|
|
# Create Shell
|
|
#
|
|
if rp.shell:
|
|
myshell = add_shell(myroom, "Wall_cover", rp)
|
|
myshell["archimesh.room_object"] = True
|
|
myshell["archimesh.room_shell"] = True
|
|
parentobject(myroom, myshell)
|
|
myshell.rotation_euler = myroom.rotation_euler
|
|
if rp.wall_width > 0.0:
|
|
# Solidify (need for boolean)
|
|
set_modifier_solidify(myshell, 0.01)
|
|
# Move to Top SOLIDIFY
|
|
movetotopsolidify(mybase)
|
|
|
|
# Create materials
|
|
if rp.crt_mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
|
|
# Wall material (two faces)
|
|
mat = create_diffuse_material("Wall_material", False, 0.765, 0.650, 0.588, 0.8, 0.621, 0.570, 0.1, True)
|
|
set_material(myroom, mat)
|
|
|
|
# Baseboard material
|
|
if rp.baseboard and mybase is not None:
|
|
mat = create_diffuse_material("Baseboard_material", False, 0.8, 0.8, 0.8)
|
|
set_material(mybase, mat)
|
|
|
|
# Ceiling material
|
|
if rp.ceiling and myceiling is not None:
|
|
mat = create_diffuse_material("Ceiling_material", False, 0.95, 0.95, 0.95)
|
|
set_material(myceiling, mat)
|
|
|
|
# Floor material
|
|
if rp.floor and myfloor is not None:
|
|
mat = create_brick_material("Floor_material", False, 0.711, 0.668, 0.668, 0.8, 0.636, 0.315)
|
|
set_material(myfloor, mat)
|
|
|
|
# Shell material
|
|
if rp.shell and myshell is not None:
|
|
mat = create_diffuse_material("Wall_cover_material", False, 0.507, 0.309, 0.076, 0.507, 0.309, 0.076)
|
|
set_material(myshell, mat)
|
|
|
|
# deactivate others
|
|
for o in bpy.data.objects:
|
|
if o.select_get() is True and o.name != myroom.name:
|
|
o.select_set(False)
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create walls or baseboard (indicated with baseboard parameter).
|
|
# Some custom values are passed using the rp ("room properties" group) parameter (rp.myvariable).
|
|
# ------------------------------------------------------------------------------
|
|
def create_walls(rp, mymesh, height, baseboard=False):
|
|
myvertex = [(0.0, 0.0, height), (0.0, 0.0, 0.0)]
|
|
myfaces = []
|
|
lastface = 0
|
|
lastx = lasty = 0
|
|
idf = 0
|
|
# Iterate the walls
|
|
for i in range(0, rp.wall_num):
|
|
if 0 == i:
|
|
prv = False
|
|
else:
|
|
prv = rp.walls[i - 1].a and not rp.walls[i - 1].curved
|
|
|
|
mydat = make_wall(prv, rp.walls[i], baseboard, lastface,
|
|
lastx, lasty, height, myvertex, myfaces)
|
|
lastx = mydat[0]
|
|
lasty = mydat[1]
|
|
lastface = mydat[2]
|
|
|
|
# --------------------------------------
|
|
# saves vertex data for opengl
|
|
# --------------------------------------
|
|
point_a = None
|
|
point_b = None
|
|
try:
|
|
for mf in myfaces[idf]:
|
|
if myvertex[mf][2] == 0:
|
|
if point_a is None:
|
|
point_a = myvertex[mf]
|
|
else:
|
|
point_b = myvertex[mf]
|
|
|
|
rp.walls[i].glpoint_a = point_a
|
|
rp.walls[i].glpoint_b = point_b
|
|
except IndexError:
|
|
pass
|
|
|
|
idf = len(myfaces)
|
|
|
|
# Close room
|
|
if rp.merge is True:
|
|
if baseboard is False:
|
|
if rp.walls[rp.wall_num - 1].a is not True:
|
|
myfaces.extend([(0, 1, lastface + 1, lastface)])
|
|
else:
|
|
if rp.walls[rp.wall_num - 1].curved is True:
|
|
myfaces.extend([(0, 1, lastface + 1, lastface)])
|
|
else:
|
|
myfaces.extend([(0, 1, lastface, lastface + 1)])
|
|
else:
|
|
myfaces.extend([(0, 1, lastface + 1, lastface)])
|
|
|
|
mymesh.from_pydata(myvertex, [], myfaces)
|
|
mymesh.update(calc_edges=True)
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Make a Wall
|
|
# prv: If previous wall has 'curved' activate.
|
|
# lastFace: Number of faces of all before walls.
|
|
# lastX: X position of the end of the last wall.
|
|
# lastY: Y position of the end of the last wall.
|
|
# height: Height of the last wall, without peak.
|
|
# ------------------------------------------------------------------------------
|
|
def make_wall(prv, wall, baseboard, lastface, lastx, lasty, height, myvertex, myfaces):
|
|
# size: Length of the wall.
|
|
# over: Height of the peak from "height".
|
|
# factor: Displacement of the peak (between -1 and 1; 0 is the middle of the wall).
|
|
advanced = wall.a
|
|
size = wall.w
|
|
over = wall.m
|
|
factor = wall.f
|
|
angle = wall.r
|
|
hide = wall.h
|
|
|
|
# if angle negative, calculate real
|
|
# use add because the angle is negative
|
|
if angle < 0:
|
|
angle += 360
|
|
# Verify Units
|
|
size = get_blendunits(size)
|
|
over = get_blendunits(over)
|
|
|
|
# Calculate size using angle
|
|
sizex = cos(radians(angle)) * size
|
|
sizey = sin(radians(angle)) * size
|
|
|
|
# Create faces
|
|
if advanced is False or baseboard is True:
|
|
# Cases of this first option: Baseboard or wall without peak and without curve.
|
|
if baseboard is True and advanced is True and wall.curved is True:
|
|
(myvertex, myfaces, sizex, sizey, lastface) = make_curved_wall(myvertex, myfaces, size, angle,
|
|
lastx, lasty, height, lastface,
|
|
wall.curve_factor, int(wall.curve_arc_deg),
|
|
int(wall.curve_arc_deg / wall.curve_steps),
|
|
hide, baseboard)
|
|
else:
|
|
myvertex.extend([(lastx + sizex, lasty + sizey, height),
|
|
(lastx + sizex, lasty + sizey, 0.0)])
|
|
if check_visibility(hide, baseboard):
|
|
if prv is False or baseboard is True:
|
|
# Previous no advance or advance with curve
|
|
myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
|
|
else:
|
|
# Previous advance without curve
|
|
myfaces.extend([(lastface, lastface + 1, lastface + 2, lastface + 3)])
|
|
lastface += 2
|
|
else:
|
|
# Case of this second option: Wall with advanced features (orientation, visibility and peak or curve).
|
|
# Orientation and visibility options ('angle' and 'hide' variables) are only visible in panel
|
|
# with advanced features, but are taken in account in any case.
|
|
if wall.curved:
|
|
# Wall with curve and without peak.
|
|
(myvertex, myfaces, sizex, sizey, lastface) = make_curved_wall(myvertex, myfaces, size, angle,
|
|
lastx, lasty, height, lastface,
|
|
wall.curve_factor, int(wall.curve_arc_deg),
|
|
int(wall.curve_arc_deg / wall.curve_steps),
|
|
hide, baseboard)
|
|
else:
|
|
# Wall with peak and without curve.
|
|
mid = size / 2 + ((size / 2) * factor)
|
|
midx = cos(radians(angle)) * mid
|
|
midy = sin(radians(angle)) * mid
|
|
# first face
|
|
myvertex.extend([(lastx + midx, lasty + midy, height + over),
|
|
(lastx + midx, lasty + midy, 0.0)])
|
|
if check_visibility(hide, baseboard):
|
|
if fabs(factor) != 1:
|
|
if prv is False:
|
|
# Previous no advance or advance with curve
|
|
myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
|
|
else:
|
|
# Previous advance without curve
|
|
myfaces.extend([(lastface, lastface + 1, lastface + 2, lastface + 3)])
|
|
# second face
|
|
myvertex.extend([(lastx + sizex, lasty + sizey, 0.0),
|
|
(lastx + sizex, lasty + sizey, height)])
|
|
if check_visibility(hide, baseboard):
|
|
if fabs(factor) != 1:
|
|
myfaces.extend([(lastface + 2, lastface + 5, lastface + 4, lastface + 3)])
|
|
else:
|
|
if prv is False:
|
|
myfaces.extend([(lastface, lastface + 5, lastface + 4, lastface + 1),
|
|
(lastface, lastface + 2, lastface + 5)])
|
|
else:
|
|
myfaces.extend([(lastface, lastface + 4, lastface + 5, lastface + 1),
|
|
(lastface + 1, lastface + 2, lastface + 5)])
|
|
|
|
lastface += 4
|
|
|
|
lastx += sizex
|
|
lasty += sizey
|
|
|
|
return lastx, lasty, lastface
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Verify visibility of walls
|
|
# ------------------------------------------------------------------------------
|
|
def check_visibility(h, base):
|
|
# Visible
|
|
if h == '0':
|
|
return True
|
|
# Wall
|
|
if h == '2':
|
|
if base is True:
|
|
return False
|
|
else:
|
|
return True
|
|
# Baseboard
|
|
if h == '1':
|
|
if base is True:
|
|
return True
|
|
else:
|
|
return False
|
|
# Hidden
|
|
if h == '3':
|
|
return False
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create a curved wall.
|
|
# ------------------------------------------------------------------------------
|
|
def make_curved_wall(myvertex, myfaces, size, wall_angle, lastx, lasty, height,
|
|
lastface, curve_factor, arc_angle, step_angle, hide, baseboard):
|
|
curvex = None
|
|
curvey = None
|
|
# Calculate size using angle
|
|
sizex = cos(radians(wall_angle)) * size
|
|
sizey = sin(radians(wall_angle)) * size
|
|
|
|
for step in range(0, arc_angle + step_angle, step_angle):
|
|
curvex = sizex / 2 - cos(radians(step + wall_angle)) * size / 2
|
|
curvey = sizey / 2 - sin(radians(step + wall_angle)) * size / 2
|
|
curvey = curvey * curve_factor
|
|
myvertex.extend([(lastx + curvex, lasty + curvey, height),
|
|
(lastx + curvex, lasty + curvey, 0.0)])
|
|
if check_visibility(hide, baseboard):
|
|
myfaces.extend([(lastface, lastface + 2, lastface + 3, lastface + 1)])
|
|
lastface += 2
|
|
return myvertex, myfaces, curvex, curvey, lastface
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create floor or ceiling (create object and mesh)
|
|
# Parameters:
|
|
# rm: "room properties" group
|
|
# typ: Name of new object and mesh ('Floor' or 'Ceiling')
|
|
# myRoom: Main object for the room
|
|
# ------------------------------------------------------------------------------
|
|
|
|
def create_floor(rp, typ, myroom):
|
|
bpy.context.view_layer.objects.active = myroom
|
|
|
|
myvertex = []
|
|
myfaces = []
|
|
verts = []
|
|
|
|
obverts = bpy.context.active_object.data.vertices
|
|
for vertex in obverts:
|
|
verts.append(tuple(vertex.co))
|
|
# Loop only selected
|
|
i = 0
|
|
for e in verts:
|
|
if typ == "Floor":
|
|
if e[2] == 0.0:
|
|
myvertex.extend([(e[0], e[1], e[2])])
|
|
i += 1
|
|
else: # ceiling
|
|
if round(e[2], 5) == round(get_blendunits(rp.room_height), 5):
|
|
myvertex.extend([(e[0], e[1], e[2])])
|
|
i += 1
|
|
|
|
# Create faces
|
|
fa = []
|
|
for f in range(0, i):
|
|
fa.extend([f])
|
|
|
|
myfaces.extend([fa])
|
|
|
|
mymesh = bpy.data.meshes.new(typ)
|
|
myobject = bpy.data.objects.new(typ, mymesh)
|
|
|
|
myobject.location = (0, 0, 0)
|
|
bpy.context.collection.objects.link(myobject)
|
|
|
|
mymesh.from_pydata(myvertex, [], myfaces)
|
|
mymesh.update(calc_edges=True)
|
|
|
|
return myobject
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define property group class to create, or modify, room walls.
|
|
# ------------------------------------------------------------------
|
|
class WallProperties(PropertyGroup):
|
|
w: FloatProperty(
|
|
name='Length',
|
|
min=-150, max=150,
|
|
default=1, precision=3,
|
|
description='Length of the wall (negative to reverse direction)',
|
|
update=update_room,
|
|
)
|
|
|
|
a: BoolProperty(
|
|
name="Advanced",
|
|
description="Define advanced parameters of the wall",
|
|
default=False,
|
|
update=update_room,
|
|
)
|
|
|
|
curved: BoolProperty(
|
|
name="Curved",
|
|
description="Enable curved wall parameters",
|
|
default=False,
|
|
update=update_room,
|
|
)
|
|
curve_factor: FloatProperty(
|
|
name='Factor',
|
|
min=-5, max=5,
|
|
default=1, precision=1,
|
|
description='Curvature variation',
|
|
update=update_room,
|
|
)
|
|
curve_arc_deg: FloatProperty(
|
|
name='Degrees', min=1, max=359,
|
|
default=180, precision=1,
|
|
description='Degrees of the curve arc (must be >= steps)',
|
|
update=update_room,
|
|
)
|
|
curve_steps: IntProperty(
|
|
name='Steps',
|
|
min=2, max=50,
|
|
default=12,
|
|
description='Curve steps',
|
|
update=update_room,
|
|
)
|
|
|
|
m: FloatProperty(
|
|
name='Peak', min=0, max=50,
|
|
default=0, precision=3,
|
|
description='Middle height variation',
|
|
update=update_room,
|
|
)
|
|
f: FloatProperty(
|
|
name='Factor', min=-1, max=1,
|
|
default=0, precision=3,
|
|
description='Middle displacement',
|
|
update=update_room,
|
|
)
|
|
r: FloatProperty(
|
|
name='Angle',
|
|
min=-180, max=180,
|
|
default=0, precision=1,
|
|
description='Wall Angle (-180 to +180)',
|
|
update=update_room,
|
|
)
|
|
|
|
h: EnumProperty(
|
|
items=(
|
|
('0', "Visible", ""),
|
|
('1', "Baseboard", ""),
|
|
('2', "Wall", ""),
|
|
('3', "Hidden", ""),
|
|
),
|
|
name="",
|
|
description="Wall visibility",
|
|
update=update_room,
|
|
)
|
|
|
|
# opengl internal data
|
|
glpoint_a: FloatVectorProperty(
|
|
name="glpointa",
|
|
description="Hidden property for opengl",
|
|
default=(0, 0, 0),
|
|
)
|
|
glpoint_b: FloatVectorProperty(
|
|
name="glpointb",
|
|
description="Hidden property for opengl",
|
|
default=(0, 0, 0),
|
|
)
|
|
|
|
bpy.utils.register_class(WallProperties)
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Add a new room wall.
|
|
# First add a parameter group for that new wall, and then update the room.
|
|
# ------------------------------------------------------------------
|
|
def add_room_wall(self, context):
|
|
rp = context.object.RoomGenerator[0]
|
|
for cont in range(len(rp.walls) - 1, rp.wall_num):
|
|
rp.walls.add()
|
|
# by default, we alternate the direction of the walls.
|
|
if 1 == cont % 2:
|
|
rp.walls[cont].r = 90
|
|
update_room(self, context)
|
|
|
|
|
|
# ------------------------------------
|
|
# Get if some vertex is highest
|
|
# ------------------------------------
|
|
def get_hight(verts, faces_4, faces_3, face_index, face_num):
|
|
rtn = face_index
|
|
a = faces_4[face_num][0]
|
|
b = faces_4[face_num][1]
|
|
c = faces_4[face_num][2]
|
|
d = faces_4[face_num][3]
|
|
|
|
for face3 in faces_3:
|
|
for idx3 in face3:
|
|
if idx3 != face_index:
|
|
# check x and y position (must be equal)
|
|
if verts[idx3][0] == verts[face_index][0] and verts[idx3][1] == verts[face_index][1]:
|
|
# only if z is > that previous z
|
|
if verts[idx3][2] > verts[face_index][2]:
|
|
# checking if the original vertex is in the same face
|
|
# must have 2 vertices on the original face
|
|
t = 0
|
|
for e in face3:
|
|
if e == a or e == b or e == c or e == d:
|
|
t += 1
|
|
if t >= 2:
|
|
rtn = idx3
|
|
|
|
return rtn
|
|
|
|
|
|
# ------------------------------------
|
|
# Sort list of faces
|
|
# ------------------------------------
|
|
def sort_facelist(activefaces, activenormals):
|
|
totfaces = len(activefaces)
|
|
newlist = []
|
|
newnormal = []
|
|
# -----------------------
|
|
# Only one face
|
|
# -----------------------
|
|
if totfaces == 1:
|
|
newlist.append(activefaces[0])
|
|
newnormal.append(activenormals[0])
|
|
return newlist, newnormal
|
|
|
|
# -----------------------
|
|
# Look for first element
|
|
# -----------------------
|
|
idx = 0
|
|
for face in activefaces:
|
|
c = 0
|
|
for i in face:
|
|
if i == 0 or i == 1:
|
|
c += 1
|
|
|
|
if c >= 2 and face not in newlist:
|
|
newlist.append(face)
|
|
newnormal.append(activenormals[idx])
|
|
break
|
|
idx += 1
|
|
|
|
# -----------------------
|
|
# Look for second element
|
|
# -----------------------
|
|
idx = 0
|
|
for face in activefaces:
|
|
c = 0
|
|
for i in face:
|
|
if i == 2 or i == 3:
|
|
c += 1
|
|
if c >= 2 and face not in newlist:
|
|
newlist.append(face)
|
|
newnormal.append(activenormals[idx])
|
|
break
|
|
idx += 1
|
|
|
|
# -----------------------
|
|
# Add next faces
|
|
# -----------------------
|
|
for x in range(2, totfaces):
|
|
idx = 0
|
|
for face in activefaces:
|
|
c = 0
|
|
for i in face:
|
|
if i == newlist[x - 1][0] or i == newlist[x - 1][1] or i == newlist[x - 1][2] or i == newlist[x - 1][3]:
|
|
c += 1
|
|
if c >= 2 and face not in newlist:
|
|
newlist.append(face)
|
|
newnormal.append(activenormals[idx])
|
|
idx += 1
|
|
|
|
return newlist, newnormal
|
|
|
|
|
|
# ------------------------------------
|
|
# Get points of the walls
|
|
# selobject: room
|
|
# ------------------------------------
|
|
def get_wall_points(selobject):
|
|
obverts = selobject.data.vertices
|
|
obfaces = selobject.data.polygons
|
|
|
|
verts = []
|
|
faces_3 = []
|
|
faces_4 = []
|
|
normals = []
|
|
activefaces = []
|
|
activenormals = []
|
|
|
|
# --------------------------
|
|
# Recover all vertex
|
|
# --------------------------
|
|
for vertex in obverts:
|
|
verts.append(list(vertex.co))
|
|
|
|
# --------------------------
|
|
# Recover 3 faces
|
|
# --------------------------
|
|
for face in obfaces:
|
|
# get only 4 corners faces
|
|
if len(list(face.vertices)) == 3:
|
|
faces_3.append(list(face.vertices))
|
|
# --------------------------
|
|
# Recover 4 faces
|
|
# --------------------------
|
|
for face in obfaces:
|
|
# get only 4 corners faces
|
|
if len(list(face.vertices)) == 4:
|
|
faces_4.append(list(face.vertices))
|
|
normals.append(face.normal)
|
|
# --------------------------
|
|
# Replace highest
|
|
# --------------------------
|
|
idx = 0
|
|
for face in faces_4:
|
|
mylist = []
|
|
for e in face: # e contains the number of vertex element
|
|
if verts[e][2] == 0:
|
|
mylist.append(e)
|
|
# Only if Z > 0, recalculate
|
|
if verts[e][2] != 0:
|
|
mylist.append(get_hight(verts, faces_4, faces_3, e, idx))
|
|
|
|
activefaces.append(mylist)
|
|
activenormals.append(normals[idx])
|
|
idx += 1
|
|
|
|
# ------------------------
|
|
# Sort faces
|
|
# ------------------------
|
|
newlist, newnormal = sort_facelist(activefaces, activenormals)
|
|
|
|
return verts, newlist, newnormal
|
|
|
|
|
|
# ------------------------------------
|
|
# Create a shell of boards
|
|
# selobject: room
|
|
# objname: Name for new object
|
|
# rp: room properties
|
|
# ------------------------------------
|
|
def add_shell(selobject, objname, rp):
|
|
|
|
myvertex = []
|
|
myfaces = []
|
|
|
|
verts, activefaces, activenormals = get_wall_points(selobject)
|
|
|
|
# --------------------------
|
|
# Get line points
|
|
# --------------------------
|
|
i = 0
|
|
idx = 0
|
|
for face in activefaces:
|
|
a1 = None
|
|
b1 = None
|
|
a2 = None
|
|
b2 = None
|
|
# Bottom
|
|
for e in face:
|
|
if verts[e][2] == 0:
|
|
if a1 is None:
|
|
a1 = e
|
|
else:
|
|
b1 = e
|
|
# Top
|
|
for e in face:
|
|
if verts[e][2] != 0:
|
|
if verts[a1][0] == verts[e][0] and verts[a1][1] == verts[e][1]:
|
|
a2 = e
|
|
else:
|
|
b2 = e
|
|
# Create the mesh
|
|
mydata = create_cover_mesh(idx, verts, activefaces, activenormals, i, a1, a2, b1, b2,
|
|
rp.merge, 0.005,
|
|
rp.shell_height, rp.shell_thick, rp.shell_factor, rp.shell_bfactor)
|
|
i = mydata[0]
|
|
myvertex.extend(mydata[1])
|
|
myfaces.extend(mydata[2])
|
|
idx += 1
|
|
# --------------------------
|
|
# Create the mesh
|
|
# --------------------------
|
|
mesh = bpy.data.meshes.new(objname)
|
|
myobject = bpy.data.objects.new(objname, mesh)
|
|
|
|
myobject.location = selobject.location
|
|
bpy.context.collection.objects.link(myobject)
|
|
|
|
mesh.from_pydata(myvertex, [], myfaces)
|
|
mesh.update(calc_edges=True)
|
|
|
|
remove_doubles(myobject)
|
|
set_normals(myobject)
|
|
|
|
return myobject
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
# Project point using face normals
|
|
#
|
|
# m: Magnitud
|
|
# pf: Comparison face +/-
|
|
# ---------------------------------------------------------
|
|
def project_point(idx, point, normals, m, pf):
|
|
v1 = Vector(normals[idx])
|
|
if idx + pf >= len(normals):
|
|
vf = v1
|
|
elif idx + pf < 0:
|
|
vf = v1
|
|
else:
|
|
v2 = Vector(normals[idx + pf])
|
|
if v1 != v2:
|
|
vf = v1 + v2
|
|
vf.normalize() # must be length equal to 1
|
|
else:
|
|
vf = v1
|
|
|
|
n1 = (vf[0] * m, vf[1] * m, vf[2] * m)
|
|
p1 = (point[0] + n1[0], point[1] + n1[1], point[2] + n1[2])
|
|
return p1
|
|
|
|
|
|
# ---------------------------------------------------------
|
|
# Create wall cover mesh
|
|
#
|
|
# Uses linear equation for cutting
|
|
#
|
|
# Z = This value is the z axis value
|
|
# so, we can replace t with ((Z-Z1) / (Z2-Z1))
|
|
#
|
|
# X = X1 + ((X2 - X1) * t)
|
|
#
|
|
# X = X1 + ((X2 - X1) * ((Z-Z1) / (Z2-Z1)))
|
|
# Y = Y1 + ((Y2 - Y1) * ((Z-Z1) / (Z2-Z1)))
|
|
#
|
|
# height refers to the height of the cover piece
|
|
# width refers to the width of the cover piece
|
|
# ---------------------------------------------------------
|
|
|
|
|
|
def create_cover_mesh(idx, verts, activefaces, normals, i, a1, a2, b1, b2, merge, space=0.005,
|
|
height=0.20, thickness=0.025, shell_factor=1, shell_bfactor=1):
|
|
pvertex = []
|
|
pfaces = []
|
|
|
|
a1_x = verts[a1][0]
|
|
a1_y = verts[a1][1]
|
|
a1_z = verts[a1][2]
|
|
|
|
a2_x = verts[a2][0]
|
|
a2_y = verts[a2][1]
|
|
a2_z = verts[a2][2]
|
|
|
|
b1_x = verts[b1][0]
|
|
b1_y = verts[b1][1]
|
|
b1_z = verts[b1][2]
|
|
|
|
b2_x = verts[b2][0]
|
|
b2_y = verts[b2][1]
|
|
b2_z = verts[b2][2]
|
|
|
|
# Get highest
|
|
if a2_z >= b2_z:
|
|
top = a2_z
|
|
limit = b2_z
|
|
else:
|
|
top = b2_z
|
|
limit = a2_z
|
|
|
|
# apply factor
|
|
# get high point of walls
|
|
maxh = 0
|
|
for v in verts:
|
|
if v[2] > maxh:
|
|
maxh = v[2]
|
|
maxh *= shell_factor
|
|
minh = maxh * (1 - shell_bfactor)
|
|
if minh < 0:
|
|
minh = 0
|
|
|
|
if shell_factor < 1:
|
|
if top > maxh:
|
|
top = maxh
|
|
|
|
# --------------------------------------
|
|
# Loop to generate each piece of cover
|
|
# --------------------------------------
|
|
zpos = minh # initial position
|
|
f = 0
|
|
f2 = 0
|
|
# detect what face must use to compare
|
|
face_num = len(activefaces) - 1
|
|
if idx == 0 and merge is True:
|
|
if is_in_nextface(idx + 1, activefaces, verts, a1_x, a1_y) is True:
|
|
side_a = 1
|
|
side_b = face_num
|
|
else:
|
|
side_a = face_num
|
|
side_b = 1
|
|
elif idx == face_num and merge is True:
|
|
if is_in_nextface(face_num, activefaces, verts, a1_x, a1_y) is False:
|
|
side_b = -face_num
|
|
side_a = -1
|
|
else:
|
|
side_b = -1
|
|
side_a = -face_num
|
|
else:
|
|
if is_in_nextface(idx + 1, activefaces, verts, a1_x, a1_y) is True:
|
|
side_a = 1
|
|
side_b = -1
|
|
else:
|
|
side_a = -1
|
|
side_b = 1
|
|
# Last wall
|
|
if idx + 1 >= len(activefaces):
|
|
if is_in_nextface(idx - 1, activefaces, verts, a1_x, a1_y) is True:
|
|
side_a = -1
|
|
side_b = 1
|
|
else:
|
|
side_a = 1
|
|
side_b = -1
|
|
|
|
na1_x = 0
|
|
na1_y = 0
|
|
na2_x = 0
|
|
na2_y = 0
|
|
|
|
nb1_x = 0
|
|
nb1_y = 0
|
|
nb2_x = 0
|
|
nb2_y = 0
|
|
|
|
nc1_x = 0
|
|
nc1_y = 0
|
|
nc2_x = 0
|
|
nc2_y = 0
|
|
|
|
nd1_x = 0
|
|
nd1_y = 0
|
|
nd2_x = 0
|
|
nd2_y = 0
|
|
|
|
while zpos <= top:
|
|
# ----------------------
|
|
# Full cover piece
|
|
# ----------------------
|
|
if zpos <= limit:
|
|
# ----------------
|
|
# Point A
|
|
# ----------------
|
|
mypoint = project_point(idx, (a1_x, a1_y, zpos), normals, space, side_a)
|
|
|
|
pvertex.extend([mypoint])
|
|
na1_x = mypoint[0]
|
|
na1_y = mypoint[1]
|
|
# external point
|
|
mypoint = project_point(idx, (a1_x, a1_y, zpos), normals, space + thickness, side_a)
|
|
pvertex.extend([mypoint])
|
|
nc1_x = mypoint[0]
|
|
nc1_y = mypoint[1]
|
|
# get second point (vertical)
|
|
mypoint = project_point(idx, (a2_x, a2_y, zpos), normals, space, side_a)
|
|
na2_x = mypoint[0]
|
|
na2_y = mypoint[1]
|
|
mypoint = project_point(idx, (a2_x, a2_y, zpos), normals, space + thickness, side_a)
|
|
nc2_x = mypoint[0]
|
|
nc2_y = mypoint[1]
|
|
|
|
# ----------------
|
|
# Point B
|
|
# ----------------
|
|
mypoint = project_point(idx, (b1_x, b1_y, zpos), normals, space, side_b)
|
|
pvertex.extend([mypoint])
|
|
nb1_x = mypoint[0]
|
|
nb1_y = mypoint[1]
|
|
# external point
|
|
mypoint = project_point(idx, (b1_x, b1_y, zpos), normals, space + thickness, side_b)
|
|
pvertex.extend([mypoint])
|
|
nd1_x = mypoint[0]
|
|
nd1_y = mypoint[1]
|
|
# get second point (vertical)
|
|
mypoint = project_point(idx, (b2_x, b2_y, zpos), normals, space, side_b)
|
|
nb2_x = mypoint[0]
|
|
nb2_y = mypoint[1]
|
|
mypoint = project_point(idx, (b2_x, b2_y, zpos), normals, space + thickness, side_b)
|
|
nd2_x = mypoint[0]
|
|
nd2_y = mypoint[1]
|
|
|
|
# Faces
|
|
if zpos != top:
|
|
pfaces.extend([(i, i + 1, i + 3, i + 2)])
|
|
|
|
if f >= 1:
|
|
pfaces.extend([(i - 3, i, i + 2, i - 1)])
|
|
|
|
i += 4
|
|
f += 1
|
|
# ----------------------
|
|
# Cut pieces
|
|
# ----------------------
|
|
else:
|
|
# -------------------------------
|
|
# Internal Points
|
|
# -------------------------------
|
|
# Get highest
|
|
if a2_z >= b2_z:
|
|
ax1 = na1_x
|
|
ay1 = na1_y
|
|
az1 = a1_z
|
|
ax2 = na2_x
|
|
ay2 = na2_y
|
|
az2 = a2_z
|
|
|
|
bx1 = na2_x
|
|
by1 = na2_y
|
|
bz1 = a2_z
|
|
bx2 = nb2_x
|
|
by2 = nb2_y
|
|
bz2 = b2_z
|
|
else:
|
|
ax1 = na2_x
|
|
ay1 = na2_y
|
|
az1 = a2_z
|
|
ax2 = nb2_x
|
|
ay2 = nb2_y
|
|
az2 = b2_z
|
|
|
|
bx1 = nb1_x
|
|
by1 = nb1_y
|
|
bz1 = b1_z
|
|
bx2 = nb2_x
|
|
by2 = nb2_y
|
|
bz2 = b2_z
|
|
|
|
# ----------------
|
|
# Point A
|
|
# ----------------
|
|
x = ax1 + ((ax2 - ax1) * ((zpos - az1) / (az2 - az1)))
|
|
y = ay1 + ((ay2 - ay1) * ((zpos - az1) / (az2 - az1)))
|
|
pvertex.extend([(x, y, zpos)])
|
|
# ----------------
|
|
# Point B
|
|
# ----------------
|
|
x = bx1 + ((bx2 - bx1) * ((zpos - bz1) / (bz2 - bz1)))
|
|
y = by1 + ((by2 - by1) * ((zpos - bz1) / (bz2 - bz1)))
|
|
pvertex.extend([(x, y, zpos)])
|
|
# -------------------------------
|
|
# External Points
|
|
# -------------------------------
|
|
# Get highest
|
|
if a2_z >= b2_z:
|
|
ax1 = nc1_x
|
|
ay1 = nc1_y
|
|
az1 = a1_z
|
|
ax2 = nc2_x
|
|
ay2 = nc2_y
|
|
az2 = a2_z
|
|
|
|
bx1 = nc2_x
|
|
by1 = nc2_y
|
|
bz1 = a2_z
|
|
bx2 = nd2_x
|
|
by2 = nd2_y
|
|
bz2 = b2_z
|
|
else:
|
|
ax1 = nc2_x
|
|
ay1 = nc2_y
|
|
az1 = a2_z
|
|
ax2 = nd2_x
|
|
ay2 = nd2_y
|
|
az2 = b2_z
|
|
|
|
bx1 = nd1_x
|
|
by1 = nd1_y
|
|
bz1 = b1_z
|
|
bx2 = nd2_x
|
|
by2 = nd2_y
|
|
bz2 = b2_z
|
|
|
|
# ----------------
|
|
# Point A
|
|
# ----------------
|
|
x = ax1 + ((ax2 - ax1) * ((zpos - az1) / (az2 - az1)))
|
|
y = ay1 + ((ay2 - ay1) * ((zpos - az1) / (az2 - az1)))
|
|
pvertex.extend([(x, y, zpos)])
|
|
# ----------------
|
|
# Point B
|
|
# ----------------
|
|
x = bx1 + ((bx2 - bx1) * ((zpos - bz1) / (bz2 - bz1)))
|
|
y = by1 + ((by2 - by1) * ((zpos - bz1) / (bz2 - bz1)))
|
|
pvertex.extend([(x, y, zpos)])
|
|
# Faces
|
|
if zpos != top:
|
|
pfaces.extend([(i, i + 1, i + 3, i + 2)])
|
|
|
|
if f2 == 0:
|
|
pfaces.extend([(i - 1, i - 3, i, i + 1)])
|
|
else:
|
|
pfaces.extend([(i - 1, i - 2, i, i + 1)])
|
|
|
|
i += 4
|
|
f2 += 1
|
|
# avoid infinite loop
|
|
if zpos == top:
|
|
break
|
|
|
|
# add new piece
|
|
zpos += height
|
|
# cut oversized
|
|
if zpos > top:
|
|
zpos = top
|
|
|
|
return i, pvertex, pfaces
|
|
|
|
|
|
# -------------------------------------------------------------
|
|
# Detect if the vertex is face
|
|
# -------------------------------------------------------------
|
|
def is_in_nextface(idx, activefaces, verts, x, y):
|
|
if idx >= len(activefaces):
|
|
return False
|
|
|
|
for f in activefaces[idx]:
|
|
if verts[f][2] == 0: # only ground
|
|
if verts[f][0] == x and verts[f][1] == y:
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define property group class to create or modify a rooms.
|
|
# ------------------------------------------------------------------
|
|
class RoomProperties(PropertyGroup):
|
|
room_height: FloatProperty(
|
|
name='Height', min=0.001, max=50,
|
|
default=2.4, precision=3,
|
|
description='Room height', update=update_room,
|
|
)
|
|
wall_width: FloatProperty(
|
|
name='Thickness', min=0.000, max=10,
|
|
default=0.0, precision=3,
|
|
description='Thickness of the walls', update=update_room,
|
|
)
|
|
inverse: BoolProperty(
|
|
name="Inverse", description="Inverse normals to outside",
|
|
default=False,
|
|
update=update_room,
|
|
)
|
|
crt_mat: BoolProperty(
|
|
name="Create default Cycles materials",
|
|
description="Create default materials for Cycles render",
|
|
default=True,
|
|
update=update_room,
|
|
)
|
|
|
|
wall_num: IntProperty(
|
|
name='Number of Walls', min=1, max=50,
|
|
default=1,
|
|
description='Number total of walls in the room', update=add_room_wall,
|
|
)
|
|
|
|
baseboard: BoolProperty(
|
|
name="Baseboard", description="Create a baseboard automatically",
|
|
default=True,
|
|
update=update_room,
|
|
)
|
|
|
|
base_width: FloatProperty(
|
|
name='Width', min=-10, max=10,
|
|
default=0.015, precision=3,
|
|
description='Baseboard width', update=update_room,
|
|
)
|
|
base_height: FloatProperty(
|
|
name='Height', min=0.05, max=20,
|
|
default=0.12, precision=3,
|
|
description='Baseboard height', update=update_room,
|
|
)
|
|
|
|
ceiling: BoolProperty(
|
|
name="Ceiling", description="Create a ceiling",
|
|
default=False, update=update_room,
|
|
)
|
|
floor: BoolProperty(
|
|
name="Floor", description="Create a floor automatically",
|
|
default=False,
|
|
update=update_room,
|
|
)
|
|
|
|
merge: BoolProperty(
|
|
name="Close walls", description="Close walls to create a full closed room",
|
|
default=False, update=update_room,
|
|
)
|
|
|
|
walls: CollectionProperty(
|
|
type=WallProperties,
|
|
)
|
|
|
|
shell: BoolProperty(
|
|
name="Wall cover", description="Create a cover of boards",
|
|
default=False, update=update_room,
|
|
)
|
|
shell_thick: FloatProperty(
|
|
name='Thickness', min=0.001, max=1,
|
|
default=0.025, precision=3,
|
|
description='Cover board thickness', update=update_room,
|
|
)
|
|
shell_height: FloatProperty(
|
|
name='Height', min=0.05, max=1,
|
|
default=0.20, precision=3,
|
|
description='Cover board height', update=update_room,
|
|
)
|
|
shell_factor: FloatProperty(
|
|
name='Top', min=0.1, max=1,
|
|
default=1, precision=1,
|
|
description='Percentage for top covering (1 Full)', update=update_room,
|
|
)
|
|
shell_bfactor: FloatProperty(
|
|
name='Bottom', min=0.1, max=1,
|
|
default=1, precision=1,
|
|
description='Percentage for bottom covering (1 Full)', update=update_room,
|
|
)
|
|
|
|
bpy.utils.register_class(RoomProperties)
|
|
Object.RoomGenerator = CollectionProperty(type=RoomProperties)
|
|
|
|
|
|
# -----------------------------------------------------
|
|
# Add wall parameters to the panel.
|
|
# -----------------------------------------------------
|
|
def add_wall(idx, box, wall):
|
|
box.label(text="Wall " + str(idx))
|
|
row = box.row()
|
|
row.prop(wall, 'w')
|
|
row.prop(wall, 'a')
|
|
# row.prop(wall, 'curved')
|
|
if wall.a is True:
|
|
srow = box.row()
|
|
srow.prop(wall, 'r')
|
|
srow.prop(wall, 'h')
|
|
|
|
srow = box.row()
|
|
srow.prop(wall, 'curved')
|
|
|
|
if wall.curved is False:
|
|
srow.prop(wall, 'm')
|
|
srow.prop(wall, 'f')
|
|
|
|
if wall.curved is True:
|
|
srow.prop(wall, 'curve_factor')
|
|
srow.prop(wall, 'curve_arc_deg')
|
|
srow.prop(wall, 'curve_steps')
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define panel class to modify rooms.
|
|
# ------------------------------------------------------------------
|
|
class ARCHIMESH_PT_RoomGenerator(Panel):
|
|
bl_idname = "OBJECT_PT_room_generator"
|
|
bl_label = "Room"
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = 'Create'
|
|
|
|
# -----------------------------------------------------
|
|
# Verify if visible
|
|
# -----------------------------------------------------
|
|
@classmethod
|
|
def poll(cls, context):
|
|
o = context.object
|
|
if o is None:
|
|
return False
|
|
if 'RoomGenerator' not in o:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
# -----------------------------------------------------
|
|
# Draw (create UI interface)
|
|
# -----------------------------------------------------
|
|
def draw(self, context):
|
|
o = context.object
|
|
# If the selected object didn't be created with the group 'RoomGenerator', this panel is not created.
|
|
# noinspection PyBroadException
|
|
try:
|
|
if 'RoomGenerator' not in o:
|
|
return
|
|
except:
|
|
return
|
|
|
|
layout = self.layout
|
|
if bpy.context.mode == 'EDIT_MESH':
|
|
layout.label(text='Warning: Operator does not work in edit mode.', icon='ERROR')
|
|
else:
|
|
room = o.RoomGenerator[0]
|
|
row = layout.row()
|
|
row.prop(room, 'room_height')
|
|
row.prop(room, 'wall_width')
|
|
row.prop(room, 'inverse')
|
|
|
|
row = layout.row()
|
|
if room.wall_num > 1:
|
|
row.prop(room, 'ceiling')
|
|
row.prop(room, 'floor')
|
|
row.prop(room, 'merge')
|
|
|
|
# Wall number
|
|
row = layout.row()
|
|
row.prop(room, 'wall_num')
|
|
|
|
# Add menu for walls
|
|
if room.wall_num > 0:
|
|
for wall_index in range(0, room.wall_num):
|
|
box = layout.box()
|
|
add_wall(wall_index + 1, box, room.walls[wall_index])
|
|
|
|
box = layout.box()
|
|
box.prop(room, 'baseboard')
|
|
if room.baseboard is True:
|
|
row = box.row()
|
|
row.prop(room, 'base_width')
|
|
row.prop(room, 'base_height')
|
|
|
|
box = layout.box()
|
|
box.prop(room, 'shell')
|
|
if room.shell is True:
|
|
row = box.row()
|
|
row.prop(room, 'shell_height')
|
|
row.prop(room, 'shell_thick')
|
|
row = box.row()
|
|
row.prop(room, 'shell_factor', slider=True)
|
|
row.prop(room, 'shell_bfactor', slider=True)
|
|
|
|
box = layout.box()
|
|
if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
|
|
box.enabled = False
|
|
box.prop(room, 'crt_mat')
|