1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ----------------------------------------------------------
6 # Automatic generation of rooms
7 # Author: Antonio Vazquez (antonioya) and Eduardo Gutierrez
9 # ----------------------------------------------------------
10 # noinspection PyUnresolvedReferences
12 from math
import sin
, cos
, fabs
, radians
13 from mathutils
import Vector
14 from datetime
import datetime
17 from bpy
.types
import Operator
, PropertyGroup
, Object
, Panel
18 from bpy
.props
import StringProperty
, FloatProperty
, BoolProperty
, IntProperty
, FloatVectorProperty
, \
19 CollectionProperty
, EnumProperty
20 from bpy_extras
.io_utils
import ExportHelper
, ImportHelper
21 from .achm_tools
import *
24 # ----------------------------------------------------------
26 # ----------------------------------------------------------
27 class ARCHIMESH_OT_ExportRoom(Operator
, ExportHelper
):
28 bl_idname
= "io_export.roomdata"
29 bl_description
= 'Export Room data (.dat)'
33 # From ExportHelper. Filter filenames.
35 filter_glob
: StringProperty(
40 filepath
: StringProperty(
42 description
="File path used for exporting room data file",
43 maxlen
=1024, default
="",
46 # ----------------------------------------------------------
48 # ----------------------------------------------------------
49 # noinspection PyUnusedLocal
50 def execute(self
, context
):
51 print("Exporting:", self
.properties
.filepath
)
52 # noinspection PyBroadException
54 myobj
= bpy
.context
.active_object
55 mydata
= myobj
.RoomGenerator
[0]
57 # -------------------------------
58 # extract path and filename
59 # -------------------------------
60 (filepath
, filename
) = path
.split(self
.properties
.filepath
)
61 print('Exporting %s' % filename
)
62 # -------------------------------
64 # -------------------------------
65 realpath
= path
.realpath(path
.expanduser(self
.properties
.filepath
))
66 fout
= open(realpath
, 'w')
68 st
= datetime
.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S')
69 fout
.write("# Archimesh room export data\n")
70 fout
.write("# " + st
+ "\n")
71 fout
.write("#======================================================\n")
73 fout
.write("name=" + myobj
.name
+ "\n")
74 fout
.write("height=" + str(round(mydata
.room_height
, 3)) + "\n")
75 fout
.write("thickness=" + str(round(mydata
.wall_width
, 3)) + "\n")
76 fout
.write("inverse=" + str(mydata
.inverse
) + "\n")
77 fout
.write("ceiling=" + str(mydata
.ceiling
) + "\n")
78 fout
.write("floor=" + str(mydata
.floor
) + "\n")
79 fout
.write("close=" + str(mydata
.merge
) + "\n")
82 fout
.write("#\n# Walls\n#\n")
83 fout
.write("walls=" + str(mydata
.wall_num
) + "\n")
85 for w
in mydata
.walls
:
86 if i
< mydata
.wall_num
:
88 fout
.write("w=" + str(round(w
.w
, 3)))
89 # if w.a == True: # advance
90 fout
.write(",a=" + str(w
.a
) + ",")
91 fout
.write("r=" + str(round(w
.r
, 1)) + ",")
92 fout
.write("h=" + str(w
.h
) + ",")
93 fout
.write("m=" + str(round(w
.m
, 3)) + ",")
94 fout
.write("f=" + str(round(w
.f
, 3)) + ",")
95 fout
.write("c=" + str(w
.curved
) + ",")
96 fout
.write("cf=" + str(round(w
.curve_factor
, 1)) + ",")
97 fout
.write("cd=" + str(round(w
.curve_arc_deg
, 1)) + ",")
98 fout
.write("cs=" + str(w
.curve_steps
) + "\n")
103 fout
.write("#\n# Baseboard\n#\n")
104 fout
.write("baseboard=" + str(mydata
.baseboard
) + "\n")
105 fout
.write("baseh=" + str(round(mydata
.base_height
, 3)) + "\n")
106 fout
.write("baset=" + str(round(mydata
.base_width
, 3)) + "\n")
108 fout
.write("#\n# Wall Cover\n#\n")
109 fout
.write("shell=" + str(mydata
.shell
) + "\n")
110 fout
.write("shellh=" + str(round(mydata
.shell_height
, 3)) + "\n")
111 fout
.write("shellt=" + str(round(mydata
.shell_thick
, 3)) + "\n")
112 fout
.write("shellf=" + str(round(mydata
.shell_factor
, 3)) + "\n")
113 fout
.write("shellb=" + str(round(mydata
.shell_bfactor
, 3)) + "\n")
116 fout
.write("#\n# Materials\n#\n")
117 fout
.write("materials=" + str(mydata
.crt_mat
) + "\n")
120 self
.report({'INFO'}, realpath
+ "successfully exported")
122 self
.report({'ERROR'}, "Unable to export room data")
126 # ----------------------------------------------------------
128 # ----------------------------------------------------------
130 # noinspection PyUnusedLocal
131 def invoke(self
, context
, event
):
132 context
.window_manager
.fileselect_add(self
)
133 return {'RUNNING_MODAL'}
136 # ----------------------------------------------------------
138 # ----------------------------------------------------------
139 class ARCHIMESH_OT_ImportRoom(Operator
, ImportHelper
):
140 bl_idname
= "io_import.roomdata"
141 bl_description
= 'Import Room data (.dat)'
145 # From Helper. Filter filenames.
146 filename_ext
= ".dat"
147 filter_glob
: StringProperty(
152 filepath
: StringProperty(
154 description
="File path used for exporting room data file",
155 maxlen
=1024, default
="",
158 # ----------------------------------------------------------
160 # ----------------------------------------------------------
161 # noinspection PyUnusedLocal
162 def execute(self
, context
):
163 print("Importing:", self
.properties
.filepath
)
164 # noinspection PyBroadException
166 realpath
= path
.realpath(path
.expanduser(self
.properties
.filepath
))
167 finput
= open(realpath
)
168 line
= finput
.readline()
170 myobj
= bpy
.context
.active_object
171 mydata
= myobj
.RoomGenerator
[0]
172 # ----------------------------------
173 # Loop all records from file
174 # ----------------------------------
175 idx
= 0 # index of each wall
178 if "name=" in line
.lower():
179 myobj
.name
= line
[5:-1]
181 elif "height=" in line
.lower():
182 mydata
.room_height
= float(line
[7:-1])
184 elif "thickness=" in line
.lower():
185 mydata
.wall_width
= float(line
[10:-1])
187 elif "inverse=" in line
.lower():
188 if line
[8:-4].upper() == "T":
189 mydata
.inverse
= True
191 mydata
.inverse
= False
193 elif "ceiling=" in line
.lower():
194 if line
[8:-4].upper() == "T":
195 mydata
.ceiling
= True
197 mydata
.ceiling
= False
199 elif "floor=" in line
.lower():
200 if line
[6:-4].upper() == "T":
205 elif "close=" in line
.lower():
206 if line
[6:-4].upper() == "T":
210 elif "baseboard=" in line
.lower():
211 if line
[10:-4].upper() == "T":
212 mydata
.baseboard
= True
214 mydata
.baseboard
= False
215 elif "baseh=" in line
.lower():
216 mydata
.base_height
= float(line
[6:-1])
217 elif "baset=" in line
.lower():
218 mydata
.base_width
= float(line
[6:-1])
219 elif "shell=" in line
.lower():
220 if line
[6:-4].upper() == "T":
224 elif "shellh=" in line
.lower():
225 mydata
.shell_height
= float(line
[7:-1])
226 elif "shellt=" in line
.lower():
227 mydata
.shell_thick
= float(line
[6:-1])
228 elif "shellf=" in line
.lower():
229 mydata
.shell_factor
= float(line
[6:-1])
230 elif "shellb=" in line
.lower():
231 mydata
.shell_bfactor
= float(line
[6:-1])
232 elif "walls=" in line
.lower():
233 mydata
.wall_num
= int(line
[6:-1])
235 # ---------------------
237 # ---------------------
238 elif "w=" in line
.lower() and idx
< mydata
.wall_num
:
240 buf
= line
[:-1] + ","
245 mydata
.walls
[idx
].w
= float(e
[2:])
247 if "true" == param
[2:]:
248 mydata
.walls
[idx
].a
= True
250 mydata
.walls
[idx
].a
= False
252 mydata
.walls
[idx
].r
= float(e
[2:])
254 mydata
.walls
[idx
].h
= e
[2:]
256 mydata
.walls
[idx
].m
= float(e
[2:])
257 elif "f=" == param
[0:2]:
258 mydata
.walls
[idx
].f
= float(e
[2:])
260 if "true" == param
[2:]:
261 mydata
.walls
[idx
].curved
= True
263 mydata
.walls
[idx
].curved
= False
265 mydata
.walls
[idx
].curve_factor
= float(e
[3:])
267 mydata
.walls
[idx
].curve_arc_deg
= float(e
[3:])
269 mydata
.walls
[idx
].curve_steps
= int(e
[3:])
272 elif "materials=" in line
.lower():
273 if line
[10:-4].upper() == "T":
274 mydata
.crt_mat
= True
276 mydata
.crt_mat
= False
278 line
= finput
.readline()
281 self
.report({'INFO'}, realpath
+ "successfully imported")
283 self
.report({'ERROR'}, "Unable to import room data")
287 # ----------------------------------------------------------
289 # ----------------------------------------------------------
290 # noinspection PyUnusedLocal
291 def invoke(self
, context
, event
):
292 context
.window_manager
.fileselect_add(self
)
293 return {'RUNNING_MODAL'}
296 # ------------------------------------------------------------------
297 # Define operator class to create rooms
298 # ------------------------------------------------------------------
299 class ARCHIMESH_OT_Room(Operator
):
300 bl_idname
= "mesh.archimesh_room"
302 bl_description
= "Generate room with walls, baseboard, floor and ceiling"
304 bl_options
= {'REGISTER', 'UNDO'}
306 # -----------------------------------------------------
307 # Draw (create UI interface)
308 # -----------------------------------------------------
309 # noinspection PyUnusedLocal
310 def draw(self
, context
):
313 row
.label(text
="Use Properties panel (N) to define parms", icon
='INFO')
314 row
= layout
.row(align
=False)
315 row
.operator("io_import.roomdata", text
="Import", icon
='COPYDOWN')
317 # -----------------------------------------------------
319 # -----------------------------------------------------
320 def execute(self
, context
):
321 if bpy
.context
.mode
== "OBJECT":
322 create_room(self
, context
)
325 self
.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
329 # ------------------------------------------------------------------------------
330 # Create main object for the room. The other objects of room will be children of this.
331 # ------------------------------------------------------------------------------
332 # noinspection PyUnusedLocal
333 def create_room(self
, context
):
334 # deselect all objects
335 for o
in bpy
.data
.objects
:
338 # we create main object and mesh for walls
339 roommesh
= bpy
.data
.meshes
.new("Room")
340 roomobject
= bpy
.data
.objects
.new("Room", roommesh
)
341 roomobject
.location
= bpy
.context
.scene
.cursor
.location
342 bpy
.context
.collection
.objects
.link(roomobject
)
343 roomobject
.RoomGenerator
.add()
344 roomobject
.RoomGenerator
[0].walls
.add()
346 # we shape the walls and create other objects as children of 'RoomObject'.
347 shape_walls_and_create_children(roomobject
, roommesh
)
349 # we select, and activate, main object for the room.
350 bpy
.context
.view_layer
.objects
.active
= roomobject
351 roomobject
.select_set(True)
354 # -----------------------------------------------------
355 # Verify if solidify exist
356 # -----------------------------------------------------
357 def is_solidify(myobject
):
360 if myobject
.modifiers
is None:
363 for mod
in myobject
.modifiers
:
364 if mod
.type == 'SOLIDIFY':
368 except AttributeError:
372 # ------------------------------------------------------------------------------
373 # Update wall mesh and children objects (baseboard, floor and ceiling).
374 # ------------------------------------------------------------------------------
375 # noinspection PyUnusedLocal
376 def update_room(self
, context
):
377 # When we update, the active object is the main object of the room.
378 o
= bpy
.context
.active_object
380 oldname
= o
.data
.name
381 # Now we deselect that room object to not delete it.
383 # and we create a new mesh for the walls:
384 tmp_mesh
= bpy
.data
.meshes
.new("temp")
385 # deselect all objects
386 for obj
in bpy
.data
.objects
:
387 obj
.select_set(False)
388 # Remove children created by this addon:
389 for child
in o
.children
:
390 # noinspection PyBroadException
392 if child
["archimesh.room_object"]:
393 # noinspection PyBroadException
395 # remove child relationship
396 for grandchild
in child
.children
:
397 grandchild
.parent
= None
399 for mod
in child
.modifiers
:
400 bpy
.ops
.object.modifier_remove(mod
)
405 child
.select_set(True)
406 bpy
.ops
.object.delete()
407 bpy
.data
.meshes
.remove(old
)
410 # Finally we create all that again (except main object),
411 shape_walls_and_create_children(o
, tmp_mesh
, True)
413 # Remove data (mesh of active object),
414 bpy
.data
.meshes
.remove(oldmesh
)
415 tmp_mesh
.name
= oldname
416 # and select, and activate, the main object of the room.
418 bpy
.context
.view_layer
.objects
.active
= o
421 # -----------------------------------------------------
422 # Move Solidify to Top
423 # -----------------------------------------------------
424 def movetotopsolidify(myobject
):
427 if myobject
.modifiers
is not None:
428 for mod
in myobject
.modifiers
:
429 if mod
.type == 'SOLIDIFY':
432 if mymod
is not None:
433 while myobject
.modifiers
[0] != mymod
:
434 bpy
.ops
.object.modifier_move_up(modifier
=mymod
.name
)
435 except AttributeError:
439 # ------------------------------------------------------------------------------
440 # Generate walls, baseboard, floor, ceiling and materials.
441 # For walls, it only shapes mesh and creates modifier solidify (the modifier, only the first time).
442 # And, for the others, it creates object and mesh.
443 # ------------------------------------------------------------------------------
444 def shape_walls_and_create_children(myroom
, tmp_mesh
, update
=False):
445 rp
= myroom
.RoomGenerator
[0] # "rp" means "room properties".
450 # Create the walls (only mesh, because the object is 'myRoom', created before).
451 create_walls(rp
, tmp_mesh
, get_blendunits(rp
.room_height
))
452 myroom
.data
= tmp_mesh
454 select_vertices(myroom
, [0, 1])
459 remove_doubles(myroom
)
460 set_normals(myroom
, not rp
.inverse
) # inside/outside
462 if rp
.wall_width
> 0.0:
463 if update
is False or is_solidify(myroom
) is False:
464 set_modifier_solidify(myroom
, get_blendunits(rp
.wall_width
))
466 for mod
in myroom
.modifiers
:
467 if mod
.type == 'SOLIDIFY':
468 mod
.thickness
= rp
.wall_width
469 # Move to Top SOLIDIFY
470 movetotopsolidify(myroom
)
472 else: # clear not used SOLIDIFY
473 for mod
in myroom
.modifiers
:
474 if mod
.type == 'SOLIDIFY':
475 myroom
.modifiers
.remove(mod
)
479 baseboardmesh
= bpy
.data
.meshes
.new("Baseboard")
480 mybase
= bpy
.data
.objects
.new("Baseboard", baseboardmesh
)
481 mybase
.location
= (0, 0, 0)
482 bpy
.context
.collection
.objects
.link(mybase
)
483 mybase
.parent
= myroom
484 mybase
.select_set(True)
485 mybase
["archimesh.room_object"] = True
486 mybase
["archimesh.room_baseboard"] = True
488 create_walls(rp
, baseboardmesh
, get_blendunits(rp
.base_height
), True)
489 set_normals(mybase
, rp
.inverse
) # inside/outside room
491 set_modifier_solidify(mybase
, get_blendunits(rp
.base_width
))
492 # Move to Top SOLIDIFY
493 movetotopsolidify(mybase
)
495 select_vertices(mybase
, [0, 1])
501 if rp
.floor
and rp
.wall_num
> 1:
502 myfloor
= create_floor(rp
, "Floor", myroom
)
503 myfloor
["archimesh.room_object"] = True
504 myfloor
.parent
= myroom
509 if rp
.ceiling
and rp
.wall_num
> 1:
510 myceiling
= create_floor(rp
, "Ceiling", myroom
)
511 myceiling
["archimesh.room_object"] = True
512 myceiling
.parent
= myroom
514 unwrap_mesh(myceiling
)
519 myshell
= add_shell(myroom
, "Wall_cover", rp
)
520 myshell
["archimesh.room_object"] = True
521 myshell
["archimesh.room_shell"] = True
522 parentobject(myroom
, myshell
)
523 myshell
.rotation_euler
= myroom
.rotation_euler
524 if rp
.wall_width
> 0.0:
525 # Solidify (need for boolean)
526 set_modifier_solidify(myshell
, 0.01)
527 # Move to Top SOLIDIFY
528 movetotopsolidify(mybase
)
531 if rp
.crt_mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
532 # Wall material (two faces)
533 mat
= create_diffuse_material("Wall_material", False, 0.765, 0.650, 0.588, 0.8, 0.621, 0.570, 0.1, True)
534 set_material(myroom
, mat
)
537 if rp
.baseboard
and mybase
is not None:
538 mat
= create_diffuse_material("Baseboard_material", False, 0.8, 0.8, 0.8)
539 set_material(mybase
, mat
)
542 if rp
.ceiling
and myceiling
is not None:
543 mat
= create_diffuse_material("Ceiling_material", False, 0.95, 0.95, 0.95)
544 set_material(myceiling
, mat
)
547 if rp
.floor
and myfloor
is not None:
548 mat
= create_brick_material("Floor_material", False, 0.711, 0.668, 0.668, 0.8, 0.636, 0.315)
549 set_material(myfloor
, mat
)
552 if rp
.shell
and myshell
is not None:
553 mat
= create_diffuse_material("Wall_cover_material", False, 0.507, 0.309, 0.076, 0.507, 0.309, 0.076)
554 set_material(myshell
, mat
)
557 for o
in bpy
.data
.objects
:
558 if o
.select_get() is True and o
.name
!= myroom
.name
:
562 # ------------------------------------------------------------------------------
563 # Create walls or baseboard (indicated with baseboard parameter).
564 # Some custom values are passed using the rp ("room properties" group) parameter (rp.myvariable).
565 # ------------------------------------------------------------------------------
566 def create_walls(rp
, mymesh
, height
, baseboard
=False):
567 myvertex
= [(0.0, 0.0, height
), (0.0, 0.0, 0.0)]
573 for i
in range(0, rp
.wall_num
):
577 prv
= rp
.walls
[i
- 1].a
and not rp
.walls
[i
- 1].curved
579 mydat
= make_wall(prv
, rp
.walls
[i
], baseboard
, lastface
,
580 lastx
, lasty
, height
, myvertex
, myfaces
)
585 # --------------------------------------
586 # saves vertex data for opengl
587 # --------------------------------------
591 for mf
in myfaces
[idf
]:
592 if myvertex
[mf
][2] == 0:
594 point_a
= myvertex
[mf
]
596 point_b
= myvertex
[mf
]
598 rp
.walls
[i
].glpoint_a
= point_a
599 rp
.walls
[i
].glpoint_b
= point_b
607 if baseboard
is False:
608 if rp
.walls
[rp
.wall_num
- 1].a
is not True:
609 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
611 if rp
.walls
[rp
.wall_num
- 1].curved
is True:
612 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
614 myfaces
.extend([(0, 1, lastface
, lastface
+ 1)])
616 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
618 mymesh
.from_pydata(myvertex
, [], myfaces
)
619 mymesh
.update(calc_edges
=True)
622 # ------------------------------------------------------------------------------
624 # prv: If previous wall has 'curved' activate.
625 # lastFace: Number of faces of all before walls.
626 # lastX: X position of the end of the last wall.
627 # lastY: Y position of the end of the last wall.
628 # height: Height of the last wall, without peak.
629 # ------------------------------------------------------------------------------
630 def make_wall(prv
, wall
, baseboard
, lastface
, lastx
, lasty
, height
, myvertex
, myfaces
):
631 # size: Length of the wall.
632 # over: Height of the peak from "height".
633 # factor: Displacement of the peak (between -1 and 1; 0 is the middle of the wall).
641 # if angle negative, calculate real
642 # use add because the angle is negative
646 size
= get_blendunits(size
)
647 over
= get_blendunits(over
)
649 # Calculate size using angle
650 sizex
= cos(radians(angle
)) * size
651 sizey
= sin(radians(angle
)) * size
654 if advanced
is False or baseboard
is True:
655 # Cases of this first option: Baseboard or wall without peak and without curve.
656 if baseboard
is True and advanced
is True and wall
.curved
is True:
657 (myvertex
, myfaces
, sizex
, sizey
, lastface
) = make_curved_wall(myvertex
, myfaces
, size
, angle
,
658 lastx
, lasty
, height
, lastface
,
659 wall
.curve_factor
, int(wall
.curve_arc_deg
),
660 int(wall
.curve_arc_deg
/ wall
.curve_steps
),
663 myvertex
.extend([(lastx
+ sizex
, lasty
+ sizey
, height
),
664 (lastx
+ sizex
, lasty
+ sizey
, 0.0)])
665 if check_visibility(hide
, baseboard
):
666 if prv
is False or baseboard
is True:
667 # Previous no advance or advance with curve
668 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
670 # Previous advance without curve
671 myfaces
.extend([(lastface
, lastface
+ 1, lastface
+ 2, lastface
+ 3)])
674 # Case of this second option: Wall with advanced features (orientation, visibility and peak or curve).
675 # Orientation and visibility options ('angle' and 'hide' variables) are only visible in panel
676 # with advanced features, but are taken in account in any case.
678 # Wall with curve and without peak.
679 (myvertex
, myfaces
, sizex
, sizey
, lastface
) = make_curved_wall(myvertex
, myfaces
, size
, angle
,
680 lastx
, lasty
, height
, lastface
,
681 wall
.curve_factor
, int(wall
.curve_arc_deg
),
682 int(wall
.curve_arc_deg
/ wall
.curve_steps
),
685 # Wall with peak and without curve.
686 mid
= size
/ 2 + ((size
/ 2) * factor
)
687 midx
= cos(radians(angle
)) * mid
688 midy
= sin(radians(angle
)) * mid
690 myvertex
.extend([(lastx
+ midx
, lasty
+ midy
, height
+ over
),
691 (lastx
+ midx
, lasty
+ midy
, 0.0)])
692 if check_visibility(hide
, baseboard
):
693 if fabs(factor
) != 1:
695 # Previous no advance or advance with curve
696 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
698 # Previous advance without curve
699 myfaces
.extend([(lastface
, lastface
+ 1, lastface
+ 2, lastface
+ 3)])
701 myvertex
.extend([(lastx
+ sizex
, lasty
+ sizey
, 0.0),
702 (lastx
+ sizex
, lasty
+ sizey
, height
)])
703 if check_visibility(hide
, baseboard
):
704 if fabs(factor
) != 1:
705 myfaces
.extend([(lastface
+ 2, lastface
+ 5, lastface
+ 4, lastface
+ 3)])
708 myfaces
.extend([(lastface
, lastface
+ 5, lastface
+ 4, lastface
+ 1),
709 (lastface
, lastface
+ 2, lastface
+ 5)])
711 myfaces
.extend([(lastface
, lastface
+ 4, lastface
+ 5, lastface
+ 1),
712 (lastface
+ 1, lastface
+ 2, lastface
+ 5)])
719 return lastx
, lasty
, lastface
722 # ------------------------------------------------------------------------------
723 # Verify visibility of walls
724 # ------------------------------------------------------------------------------
725 def check_visibility(h
, base
):
746 # ------------------------------------------------------------------------------
747 # Create a curved wall.
748 # ------------------------------------------------------------------------------
749 def make_curved_wall(myvertex
, myfaces
, size
, wall_angle
, lastx
, lasty
, height
,
750 lastface
, curve_factor
, arc_angle
, step_angle
, hide
, baseboard
):
753 # Calculate size using angle
754 sizex
= cos(radians(wall_angle
)) * size
755 sizey
= sin(radians(wall_angle
)) * size
757 for step
in range(0, arc_angle
+ step_angle
, step_angle
):
758 curvex
= sizex
/ 2 - cos(radians(step
+ wall_angle
)) * size
/ 2
759 curvey
= sizey
/ 2 - sin(radians(step
+ wall_angle
)) * size
/ 2
760 curvey
= curvey
* curve_factor
761 myvertex
.extend([(lastx
+ curvex
, lasty
+ curvey
, height
),
762 (lastx
+ curvex
, lasty
+ curvey
, 0.0)])
763 if check_visibility(hide
, baseboard
):
764 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
766 return myvertex
, myfaces
, curvex
, curvey
, lastface
769 # ------------------------------------------------------------------------------
770 # Create floor or ceiling (create object and mesh)
772 # rm: "room properties" group
773 # typ: Name of new object and mesh ('Floor' or 'Ceiling')
774 # myRoom: Main object for the room
775 # ------------------------------------------------------------------------------
777 def create_floor(rp
, typ
, myroom
):
778 bpy
.context
.view_layer
.objects
.active
= myroom
784 obverts
= bpy
.context
.active_object
.data
.vertices
785 for vertex
in obverts
:
786 verts
.append(tuple(vertex
.co
))
792 myvertex
.extend([(e
[0], e
[1], e
[2])])
795 if round(e
[2], 5) == round(get_blendunits(rp
.room_height
), 5):
796 myvertex
.extend([(e
[0], e
[1], e
[2])])
801 for f
in range(0, i
):
806 mymesh
= bpy
.data
.meshes
.new(typ
)
807 myobject
= bpy
.data
.objects
.new(typ
, mymesh
)
809 myobject
.location
= (0, 0, 0)
810 bpy
.context
.collection
.objects
.link(myobject
)
812 mymesh
.from_pydata(myvertex
, [], myfaces
)
813 mymesh
.update(calc_edges
=True)
818 # ------------------------------------------------------------------
819 # Define property group class to create, or modify, room walls.
820 # ------------------------------------------------------------------
821 class WallProperties(PropertyGroup
):
825 default
=1, precision
=3,
826 description
='Length of the wall (negative to reverse direction)',
832 description
="Define advanced parameters of the wall",
837 curved
: BoolProperty(
839 description
="Enable curved wall parameters",
843 curve_factor
: FloatProperty(
846 default
=1, precision
=1,
847 description
='Curvature variation',
850 curve_arc_deg
: FloatProperty(
851 name
='Degrees', min=1, max=359,
852 default
=180, precision
=1,
853 description
='Degrees of the curve arc (must be >= steps)',
856 curve_steps
: IntProperty(
860 description
='Curve steps',
865 name
='Peak', min=0, max=50,
866 default
=0, precision
=3,
867 description
='Middle height variation',
871 name
='Factor', min=-1, max=1,
872 default
=0, precision
=3,
873 description
='Middle displacement',
879 default
=0, precision
=1,
880 description
='Wall Angle (-180 to +180)',
886 ('0', "Visible", ""),
887 ('1', "Baseboard", ""),
892 description
="Wall visibility",
896 # opengl internal data
897 glpoint_a
: FloatVectorProperty(
899 description
="Hidden property for opengl",
902 glpoint_b
: FloatVectorProperty(
904 description
="Hidden property for opengl",
908 bpy
.utils
.register_class(WallProperties
)
911 # ------------------------------------------------------------------
912 # Add a new room wall.
913 # First add a parameter group for that new wall, and then update the room.
914 # ------------------------------------------------------------------
915 def add_room_wall(self
, context
):
916 rp
= context
.object.RoomGenerator
[0]
917 for cont
in range(len(rp
.walls
) - 1, rp
.wall_num
):
919 # by default, we alternate the direction of the walls.
921 rp
.walls
[cont
].r
= 90
922 update_room(self
, context
)
925 # ------------------------------------
926 # Get if some vertex is highest
927 # ------------------------------------
928 def get_hight(verts
, faces_4
, faces_3
, face_index
, face_num
):
930 a
= faces_4
[face_num
][0]
931 b
= faces_4
[face_num
][1]
932 c
= faces_4
[face_num
][2]
933 d
= faces_4
[face_num
][3]
935 for face3
in faces_3
:
937 if idx3
!= face_index
:
938 # check x and y position (must be equal)
939 if verts
[idx3
][0] == verts
[face_index
][0] and verts
[idx3
][1] == verts
[face_index
][1]:
940 # only if z is > that previous z
941 if verts
[idx3
][2] > verts
[face_index
][2]:
942 # checking if the original vertex is in the same face
943 # must have 2 vertices on the original face
946 if e
== a
or e
== b
or e
== c
or e
== d
:
954 # ------------------------------------
956 # ------------------------------------
957 def sort_facelist(activefaces
, activenormals
):
958 totfaces
= len(activefaces
)
961 # -----------------------
963 # -----------------------
965 newlist
.append(activefaces
[0])
966 newnormal
.append(activenormals
[0])
967 return newlist
, newnormal
969 # -----------------------
970 # Look for first element
971 # -----------------------
973 for face
in activefaces
:
979 if c
>= 2 and face
not in newlist
:
981 newnormal
.append(activenormals
[idx
])
985 # -----------------------
986 # Look for second element
987 # -----------------------
989 for face
in activefaces
:
994 if c
>= 2 and face
not in newlist
:
996 newnormal
.append(activenormals
[idx
])
1000 # -----------------------
1002 # -----------------------
1003 for x
in range(2, totfaces
):
1005 for face
in activefaces
:
1008 if i
== newlist
[x
- 1][0] or i
== newlist
[x
- 1][1] or i
== newlist
[x
- 1][2] or i
== newlist
[x
- 1][3]:
1010 if c
>= 2 and face
not in newlist
:
1011 newlist
.append(face
)
1012 newnormal
.append(activenormals
[idx
])
1015 return newlist
, newnormal
1018 # ------------------------------------
1019 # Get points of the walls
1021 # ------------------------------------
1022 def get_wall_points(selobject
):
1023 obverts
= selobject
.data
.vertices
1024 obfaces
= selobject
.data
.polygons
1033 # --------------------------
1034 # Recover all vertex
1035 # --------------------------
1036 for vertex
in obverts
:
1037 verts
.append(list(vertex
.co
))
1039 # --------------------------
1041 # --------------------------
1042 for face
in obfaces
:
1043 # get only 4 corners faces
1044 if len(list(face
.vertices
)) == 3:
1045 faces_3
.append(list(face
.vertices
))
1046 # --------------------------
1048 # --------------------------
1049 for face
in obfaces
:
1050 # get only 4 corners faces
1051 if len(list(face
.vertices
)) == 4:
1052 faces_4
.append(list(face
.vertices
))
1053 normals
.append(face
.normal
)
1054 # --------------------------
1056 # --------------------------
1058 for face
in faces_4
:
1060 for e
in face
: # e contains the number of vertex element
1061 if verts
[e
][2] == 0:
1063 # Only if Z > 0, recalculate
1064 if verts
[e
][2] != 0:
1065 mylist
.append(get_hight(verts
, faces_4
, faces_3
, e
, idx
))
1067 activefaces
.append(mylist
)
1068 activenormals
.append(normals
[idx
])
1071 # ------------------------
1073 # ------------------------
1074 newlist
, newnormal
= sort_facelist(activefaces
, activenormals
)
1076 return verts
, newlist
, newnormal
1079 # ------------------------------------
1080 # Create a shell of boards
1082 # objname: Name for new object
1083 # rp: room properties
1084 # ------------------------------------
1085 def add_shell(selobject
, objname
, rp
):
1090 verts
, activefaces
, activenormals
= get_wall_points(selobject
)
1092 # --------------------------
1094 # --------------------------
1097 for face
in activefaces
:
1104 if verts
[e
][2] == 0:
1111 if verts
[e
][2] != 0:
1112 if verts
[a1
][0] == verts
[e
][0] and verts
[a1
][1] == verts
[e
][1]:
1117 mydata
= create_cover_mesh(idx
, verts
, activefaces
, activenormals
, i
, a1
, a2
, b1
, b2
,
1119 rp
.shell_height
, rp
.shell_thick
, rp
.shell_factor
, rp
.shell_bfactor
)
1121 myvertex
.extend(mydata
[1])
1122 myfaces
.extend(mydata
[2])
1124 # --------------------------
1126 # --------------------------
1127 mesh
= bpy
.data
.meshes
.new(objname
)
1128 myobject
= bpy
.data
.objects
.new(objname
, mesh
)
1130 myobject
.location
= selobject
.location
1131 bpy
.context
.collection
.objects
.link(myobject
)
1133 mesh
.from_pydata(myvertex
, [], myfaces
)
1134 mesh
.update(calc_edges
=True)
1136 remove_doubles(myobject
)
1137 set_normals(myobject
)
1142 # ---------------------------------------------------------
1143 # Project point using face normals
1146 # pf: Comparison face +/-
1147 # ---------------------------------------------------------
1148 def project_point(idx
, point
, normals
, m
, pf
):
1149 v1
= Vector(normals
[idx
])
1150 if idx
+ pf
>= len(normals
):
1155 v2
= Vector(normals
[idx
+ pf
])
1158 vf
.normalize() # must be length equal to 1
1162 n1
= (vf
[0] * m
, vf
[1] * m
, vf
[2] * m
)
1163 p1
= (point
[0] + n1
[0], point
[1] + n1
[1], point
[2] + n1
[2])
1167 # ---------------------------------------------------------
1168 # Create wall cover mesh
1170 # Uses linear equation for cutting
1172 # Z = This value is the z axis value
1173 # so, we can replace t with ((Z-Z1) / (Z2-Z1))
1175 # X = X1 + ((X2 - X1) * t)
1177 # X = X1 + ((X2 - X1) * ((Z-Z1) / (Z2-Z1)))
1178 # Y = Y1 + ((Y2 - Y1) * ((Z-Z1) / (Z2-Z1)))
1180 # height refers to the height of the cover piece
1181 # width refers to the width of the cover piece
1182 # ---------------------------------------------------------
1185 def create_cover_mesh(idx
, verts
, activefaces
, normals
, i
, a1
, a2
, b1
, b2
, merge
, space
=0.005,
1186 height
=0.20, thickness
=0.025, shell_factor
=1, shell_bfactor
=1):
1215 # get high point of walls
1220 maxh
*= shell_factor
1221 minh
= maxh
* (1 - shell_bfactor
)
1225 if shell_factor
< 1:
1229 # --------------------------------------
1230 # Loop to generate each piece of cover
1231 # --------------------------------------
1232 zpos
= minh
# initial position
1235 # detect what face must use to compare
1236 face_num
= len(activefaces
) - 1
1237 if idx
== 0 and merge
is True:
1238 if is_in_nextface(idx
+ 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1244 elif idx
== face_num
and merge
is True:
1245 if is_in_nextface(face_num
, activefaces
, verts
, a1_x
, a1_y
) is False:
1252 if is_in_nextface(idx
+ 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1259 if idx
+ 1 >= len(activefaces
):
1260 if is_in_nextface(idx
- 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1288 # ----------------------
1290 # ----------------------
1295 mypoint
= project_point(idx
, (a1_x
, a1_y
, zpos
), normals
, space
, side_a
)
1297 pvertex
.extend([mypoint
])
1301 mypoint
= project_point(idx
, (a1_x
, a1_y
, zpos
), normals
, space
+ thickness
, side_a
)
1302 pvertex
.extend([mypoint
])
1305 # get second point (vertical)
1306 mypoint
= project_point(idx
, (a2_x
, a2_y
, zpos
), normals
, space
, side_a
)
1309 mypoint
= project_point(idx
, (a2_x
, a2_y
, zpos
), normals
, space
+ thickness
, side_a
)
1316 mypoint
= project_point(idx
, (b1_x
, b1_y
, zpos
), normals
, space
, side_b
)
1317 pvertex
.extend([mypoint
])
1321 mypoint
= project_point(idx
, (b1_x
, b1_y
, zpos
), normals
, space
+ thickness
, side_b
)
1322 pvertex
.extend([mypoint
])
1325 # get second point (vertical)
1326 mypoint
= project_point(idx
, (b2_x
, b2_y
, zpos
), normals
, space
, side_b
)
1329 mypoint
= project_point(idx
, (b2_x
, b2_y
, zpos
), normals
, space
+ thickness
, side_b
)
1335 pfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2)])
1338 pfaces
.extend([(i
- 3, i
, i
+ 2, i
- 1)])
1342 # ----------------------
1344 # ----------------------
1346 # -------------------------------
1348 # -------------------------------
1382 x
= ax1
+ ((ax2
- ax1
) * ((zpos
- az1
) / (az2
- az1
)))
1383 y
= ay1
+ ((ay2
- ay1
) * ((zpos
- az1
) / (az2
- az1
)))
1384 pvertex
.extend([(x
, y
, zpos
)])
1388 x
= bx1
+ ((bx2
- bx1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1389 y
= by1
+ ((by2
- by1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1390 pvertex
.extend([(x
, y
, zpos
)])
1391 # -------------------------------
1393 # -------------------------------
1427 x
= ax1
+ ((ax2
- ax1
) * ((zpos
- az1
) / (az2
- az1
)))
1428 y
= ay1
+ ((ay2
- ay1
) * ((zpos
- az1
) / (az2
- az1
)))
1429 pvertex
.extend([(x
, y
, zpos
)])
1433 x
= bx1
+ ((bx2
- bx1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1434 y
= by1
+ ((by2
- by1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1435 pvertex
.extend([(x
, y
, zpos
)])
1438 pfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2)])
1441 pfaces
.extend([(i
- 1, i
- 3, i
, i
+ 1)])
1443 pfaces
.extend([(i
- 1, i
- 2, i
, i
+ 1)])
1447 # avoid infinite loop
1457 return i
, pvertex
, pfaces
1460 # -------------------------------------------------------------
1461 # Detect if the vertex is face
1462 # -------------------------------------------------------------
1463 def is_in_nextface(idx
, activefaces
, verts
, x
, y
):
1464 if idx
>= len(activefaces
):
1467 for f
in activefaces
[idx
]:
1468 if verts
[f
][2] == 0: # only ground
1469 if verts
[f
][0] == x
and verts
[f
][1] == y
:
1475 # ------------------------------------------------------------------
1476 # Define property group class to create or modify a rooms.
1477 # ------------------------------------------------------------------
1478 class RoomProperties(PropertyGroup
):
1479 room_height
: FloatProperty(
1480 name
='Height', min=0.001, max=50,
1481 default
=2.4, precision
=3,
1482 description
='Room height', update
=update_room
,
1484 wall_width
: FloatProperty(
1485 name
='Thickness', min=0.000, max=10,
1486 default
=0.0, precision
=3,
1487 description
='Thickness of the walls', update
=update_room
,
1489 inverse
: BoolProperty(
1490 name
="Inverse", description
="Inverse normals to outside",
1494 crt_mat
: BoolProperty(
1495 name
="Create default Cycles materials",
1496 description
="Create default materials for Cycles render",
1501 wall_num
: IntProperty(
1502 name
='Number of Walls', min=1, max=50,
1504 description
='Number total of walls in the room', update
=add_room_wall
,
1507 baseboard
: BoolProperty(
1508 name
="Baseboard", description
="Create a baseboard automatically",
1513 base_width
: FloatProperty(
1514 name
='Width', min=-10, max=10,
1515 default
=0.015, precision
=3,
1516 description
='Baseboard width', update
=update_room
,
1518 base_height
: FloatProperty(
1519 name
='Height', min=0.05, max=20,
1520 default
=0.12, precision
=3,
1521 description
='Baseboard height', update
=update_room
,
1524 ceiling
: BoolProperty(
1525 name
="Ceiling", description
="Create a ceiling",
1526 default
=False, update
=update_room
,
1528 floor
: BoolProperty(
1529 name
="Floor", description
="Create a floor automatically",
1534 merge
: BoolProperty(
1535 name
="Close walls", description
="Close walls to create a full closed room",
1536 default
=False, update
=update_room
,
1539 walls
: CollectionProperty(
1540 type=WallProperties
,
1543 shell
: BoolProperty(
1544 name
="Wall cover", description
="Create a cover of boards",
1545 default
=False, update
=update_room
,
1547 shell_thick
: FloatProperty(
1548 name
='Thickness', min=0.001, max=1,
1549 default
=0.025, precision
=3,
1550 description
='Cover board thickness', update
=update_room
,
1552 shell_height
: FloatProperty(
1553 name
='Height', min=0.05, max=1,
1554 default
=0.20, precision
=3,
1555 description
='Cover board height', update
=update_room
,
1557 shell_factor
: FloatProperty(
1558 name
='Top', min=0.1, max=1,
1559 default
=1, precision
=1,
1560 description
='Percentage for top covering (1 Full)', update
=update_room
,
1562 shell_bfactor
: FloatProperty(
1563 name
='Bottom', min=0.1, max=1,
1564 default
=1, precision
=1,
1565 description
='Percentage for bottom covering (1 Full)', update
=update_room
,
1568 bpy
.utils
.register_class(RoomProperties
)
1569 Object
.RoomGenerator
= CollectionProperty(type=RoomProperties
)
1572 # -----------------------------------------------------
1573 # Add wall parameters to the panel.
1574 # -----------------------------------------------------
1575 def add_wall(idx
, box
, wall
):
1576 box
.label(text
="Wall " + str(idx
))
1580 # row.prop(wall, 'curved')
1583 srow
.prop(wall
, 'r')
1584 srow
.prop(wall
, 'h')
1587 srow
.prop(wall
, 'curved')
1589 if wall
.curved
is False:
1590 srow
.prop(wall
, 'm')
1591 srow
.prop(wall
, 'f')
1593 if wall
.curved
is True:
1594 srow
.prop(wall
, 'curve_factor')
1595 srow
.prop(wall
, 'curve_arc_deg')
1596 srow
.prop(wall
, 'curve_steps')
1599 # ------------------------------------------------------------------
1600 # Define panel class to modify rooms.
1601 # ------------------------------------------------------------------
1602 class ARCHIMESH_PT_RoomGenerator(Panel
):
1603 bl_idname
= "OBJECT_PT_room_generator"
1605 bl_space_type
= 'VIEW_3D'
1606 bl_region_type
= 'UI'
1607 bl_category
= 'Create'
1609 # -----------------------------------------------------
1611 # -----------------------------------------------------
1613 def poll(cls
, context
):
1617 if 'RoomGenerator' not in o
:
1622 # -----------------------------------------------------
1623 # Draw (create UI interface)
1624 # -----------------------------------------------------
1625 def draw(self
, context
):
1627 # If the selected object didn't be created with the group 'RoomGenerator', this panel is not created.
1628 # noinspection PyBroadException
1630 if 'RoomGenerator' not in o
:
1635 layout
= self
.layout
1636 if bpy
.context
.mode
== 'EDIT_MESH':
1637 layout
.label(text
='Warning: Operator does not work in edit mode.', icon
='ERROR')
1639 room
= o
.RoomGenerator
[0]
1641 row
.prop(room
, 'room_height')
1642 row
.prop(room
, 'wall_width')
1643 row
.prop(room
, 'inverse')
1646 if room
.wall_num
> 1:
1647 row
.prop(room
, 'ceiling')
1648 row
.prop(room
, 'floor')
1649 row
.prop(room
, 'merge')
1653 row
.prop(room
, 'wall_num')
1655 # Add menu for walls
1656 if room
.wall_num
> 0:
1657 for wall_index
in range(0, room
.wall_num
):
1659 add_wall(wall_index
+ 1, box
, room
.walls
[wall_index
])
1662 box
.prop(room
, 'baseboard')
1663 if room
.baseboard
is True:
1665 row
.prop(room
, 'base_width')
1666 row
.prop(room
, 'base_height')
1669 box
.prop(room
, 'shell')
1670 if room
.shell
is True:
1672 row
.prop(room
, 'shell_height')
1673 row
.prop(room
, 'shell_thick')
1675 row
.prop(room
, 'shell_factor', slider
=True)
1676 row
.prop(room
, 'shell_bfactor', slider
=True)
1679 if not context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
1681 box
.prop(room
, 'crt_mat')