1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # ----------------------------------------------------------
4 # Automatic generation of rooms
5 # Author: Antonio Vazquez (antonioya) and Eduardo Gutierrez
7 # ----------------------------------------------------------
8 # noinspection PyUnresolvedReferences
10 from math
import sin
, cos
, fabs
, radians
11 from mathutils
import Vector
12 from datetime
import datetime
15 from bpy
.types
import Operator
, PropertyGroup
, Object
, Panel
16 from bpy
.props
import StringProperty
, FloatProperty
, BoolProperty
, IntProperty
, FloatVectorProperty
, \
17 CollectionProperty
, EnumProperty
18 from bpy_extras
.io_utils
import ExportHelper
, ImportHelper
19 from .achm_tools
import *
22 # ----------------------------------------------------------
24 # ----------------------------------------------------------
25 class ARCHIMESH_OT_ExportRoom(Operator
, ExportHelper
):
26 bl_idname
= "io_export.roomdata"
27 bl_description
= 'Export Room data (.dat)'
31 # From ExportHelper. Filter filenames.
33 filter_glob
: StringProperty(
38 filepath
: StringProperty(
40 description
="File path used for exporting room data file",
41 maxlen
=1024, default
="",
44 # ----------------------------------------------------------
46 # ----------------------------------------------------------
47 # noinspection PyUnusedLocal
48 def execute(self
, context
):
49 print("Exporting:", self
.properties
.filepath
)
50 # noinspection PyBroadException
52 myobj
= bpy
.context
.active_object
53 mydata
= myobj
.RoomGenerator
[0]
55 # -------------------------------
56 # extract path and filename
57 # -------------------------------
58 (filepath
, filename
) = path
.split(self
.properties
.filepath
)
59 print('Exporting %s' % filename
)
60 # -------------------------------
62 # -------------------------------
63 realpath
= path
.realpath(path
.expanduser(self
.properties
.filepath
))
64 fout
= open(realpath
, 'w')
66 st
= datetime
.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S')
67 fout
.write("# Archimesh room export data\n")
68 fout
.write("# " + st
+ "\n")
69 fout
.write("#======================================================\n")
71 fout
.write("name=" + myobj
.name
+ "\n")
72 fout
.write("height=" + str(round(mydata
.room_height
, 3)) + "\n")
73 fout
.write("thickness=" + str(round(mydata
.wall_width
, 3)) + "\n")
74 fout
.write("inverse=" + str(mydata
.inverse
) + "\n")
75 fout
.write("ceiling=" + str(mydata
.ceiling
) + "\n")
76 fout
.write("floor=" + str(mydata
.floor
) + "\n")
77 fout
.write("close=" + str(mydata
.merge
) + "\n")
80 fout
.write("#\n# Walls\n#\n")
81 fout
.write("walls=" + str(mydata
.wall_num
) + "\n")
83 for w
in mydata
.walls
:
84 if i
< mydata
.wall_num
:
86 fout
.write("w=" + str(round(w
.w
, 3)))
87 # if w.a == True: # advance
88 fout
.write(",a=" + str(w
.a
) + ",")
89 fout
.write("r=" + str(round(w
.r
, 1)) + ",")
90 fout
.write("h=" + str(w
.h
) + ",")
91 fout
.write("m=" + str(round(w
.m
, 3)) + ",")
92 fout
.write("f=" + str(round(w
.f
, 3)) + ",")
93 fout
.write("c=" + str(w
.curved
) + ",")
94 fout
.write("cf=" + str(round(w
.curve_factor
, 1)) + ",")
95 fout
.write("cd=" + str(round(w
.curve_arc_deg
, 1)) + ",")
96 fout
.write("cs=" + str(w
.curve_steps
) + "\n")
101 fout
.write("#\n# Baseboard\n#\n")
102 fout
.write("baseboard=" + str(mydata
.baseboard
) + "\n")
103 fout
.write("baseh=" + str(round(mydata
.base_height
, 3)) + "\n")
104 fout
.write("baset=" + str(round(mydata
.base_width
, 3)) + "\n")
106 fout
.write("#\n# Wall Cover\n#\n")
107 fout
.write("shell=" + str(mydata
.shell
) + "\n")
108 fout
.write("shellh=" + str(round(mydata
.shell_height
, 3)) + "\n")
109 fout
.write("shellt=" + str(round(mydata
.shell_thick
, 3)) + "\n")
110 fout
.write("shellf=" + str(round(mydata
.shell_factor
, 3)) + "\n")
111 fout
.write("shellb=" + str(round(mydata
.shell_bfactor
, 3)) + "\n")
114 fout
.write("#\n# Materials\n#\n")
115 fout
.write("materials=" + str(mydata
.crt_mat
) + "\n")
118 self
.report({'INFO'}, realpath
+ "successfully exported")
120 self
.report({'ERROR'}, "Unable to export room data")
124 # ----------------------------------------------------------
126 # ----------------------------------------------------------
128 # noinspection PyUnusedLocal
129 def invoke(self
, context
, event
):
130 context
.window_manager
.fileselect_add(self
)
131 return {'RUNNING_MODAL'}
134 # ----------------------------------------------------------
136 # ----------------------------------------------------------
137 class ARCHIMESH_OT_ImportRoom(Operator
, ImportHelper
):
138 bl_idname
= "io_import.roomdata"
139 bl_description
= 'Import Room data (.dat)'
143 # From Helper. Filter filenames.
144 filename_ext
= ".dat"
145 filter_glob
: StringProperty(
150 filepath
: StringProperty(
152 description
="File path used for exporting room data file",
153 maxlen
=1024, default
="",
156 # ----------------------------------------------------------
158 # ----------------------------------------------------------
159 # noinspection PyUnusedLocal
160 def execute(self
, context
):
161 print("Importing:", self
.properties
.filepath
)
162 # noinspection PyBroadException
164 realpath
= path
.realpath(path
.expanduser(self
.properties
.filepath
))
165 finput
= open(realpath
)
166 line
= finput
.readline()
168 myobj
= bpy
.context
.active_object
169 mydata
= myobj
.RoomGenerator
[0]
170 # ----------------------------------
171 # Loop all records from file
172 # ----------------------------------
173 idx
= 0 # index of each wall
176 if "name=" in line
.lower():
177 myobj
.name
= line
[5:-1]
179 elif "height=" in line
.lower():
180 mydata
.room_height
= float(line
[7:-1])
182 elif "thickness=" in line
.lower():
183 mydata
.wall_width
= float(line
[10:-1])
185 elif "inverse=" in line
.lower():
186 if line
[8:-4].upper() == "T":
187 mydata
.inverse
= True
189 mydata
.inverse
= False
191 elif "ceiling=" in line
.lower():
192 if line
[8:-4].upper() == "T":
193 mydata
.ceiling
= True
195 mydata
.ceiling
= False
197 elif "floor=" in line
.lower():
198 if line
[6:-4].upper() == "T":
203 elif "close=" in line
.lower():
204 if line
[6:-4].upper() == "T":
208 elif "baseboard=" in line
.lower():
209 if line
[10:-4].upper() == "T":
210 mydata
.baseboard
= True
212 mydata
.baseboard
= False
213 elif "baseh=" in line
.lower():
214 mydata
.base_height
= float(line
[6:-1])
215 elif "baset=" in line
.lower():
216 mydata
.base_width
= float(line
[6:-1])
217 elif "shell=" in line
.lower():
218 if line
[6:-4].upper() == "T":
222 elif "shellh=" in line
.lower():
223 mydata
.shell_height
= float(line
[7:-1])
224 elif "shellt=" in line
.lower():
225 mydata
.shell_thick
= float(line
[6:-1])
226 elif "shellf=" in line
.lower():
227 mydata
.shell_factor
= float(line
[6:-1])
228 elif "shellb=" in line
.lower():
229 mydata
.shell_bfactor
= float(line
[6:-1])
230 elif "walls=" in line
.lower():
231 mydata
.wall_num
= int(line
[6:-1])
233 # ---------------------
235 # ---------------------
236 elif "w=" in line
.lower() and idx
< mydata
.wall_num
:
238 buf
= line
[:-1] + ","
243 mydata
.walls
[idx
].w
= float(e
[2:])
245 if "true" == param
[2:]:
246 mydata
.walls
[idx
].a
= True
248 mydata
.walls
[idx
].a
= False
250 mydata
.walls
[idx
].r
= float(e
[2:])
252 mydata
.walls
[idx
].h
= e
[2:]
254 mydata
.walls
[idx
].m
= float(e
[2:])
255 elif "f=" == param
[0:2]:
256 mydata
.walls
[idx
].f
= float(e
[2:])
258 if "true" == param
[2:]:
259 mydata
.walls
[idx
].curved
= True
261 mydata
.walls
[idx
].curved
= False
263 mydata
.walls
[idx
].curve_factor
= float(e
[3:])
265 mydata
.walls
[idx
].curve_arc_deg
= float(e
[3:])
267 mydata
.walls
[idx
].curve_steps
= int(e
[3:])
270 elif "materials=" in line
.lower():
271 if line
[10:-4].upper() == "T":
272 mydata
.crt_mat
= True
274 mydata
.crt_mat
= False
276 line
= finput
.readline()
279 self
.report({'INFO'}, realpath
+ "successfully imported")
281 self
.report({'ERROR'}, "Unable to import room data")
285 # ----------------------------------------------------------
287 # ----------------------------------------------------------
288 # noinspection PyUnusedLocal
289 def invoke(self
, context
, event
):
290 context
.window_manager
.fileselect_add(self
)
291 return {'RUNNING_MODAL'}
294 # ------------------------------------------------------------------
295 # Define operator class to create rooms
296 # ------------------------------------------------------------------
297 class ARCHIMESH_OT_Room(Operator
):
298 bl_idname
= "mesh.archimesh_room"
300 bl_description
= "Generate room with walls, baseboard, floor and ceiling"
302 bl_options
= {'REGISTER', 'UNDO'}
304 # -----------------------------------------------------
305 # Draw (create UI interface)
306 # -----------------------------------------------------
307 # noinspection PyUnusedLocal
308 def draw(self
, context
):
311 row
.label(text
="Use Properties panel (N) to define parms", icon
='INFO')
312 row
= layout
.row(align
=False)
313 row
.operator("io_import.roomdata", text
="Import", icon
='COPYDOWN')
315 # -----------------------------------------------------
317 # -----------------------------------------------------
318 def execute(self
, context
):
319 if bpy
.context
.mode
== "OBJECT":
320 create_room(self
, context
)
323 self
.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
327 # ------------------------------------------------------------------------------
328 # Create main object for the room. The other objects of room will be children of this.
329 # ------------------------------------------------------------------------------
330 # noinspection PyUnusedLocal
331 def create_room(self
, context
):
332 # deselect all objects
333 for o
in bpy
.data
.objects
:
336 # we create main object and mesh for walls
337 roommesh
= bpy
.data
.meshes
.new("Room")
338 roomobject
= bpy
.data
.objects
.new("Room", roommesh
)
339 roomobject
.location
= bpy
.context
.scene
.cursor
.location
340 bpy
.context
.collection
.objects
.link(roomobject
)
341 roomobject
.RoomGenerator
.add()
342 roomobject
.RoomGenerator
[0].walls
.add()
344 # we shape the walls and create other objects as children of 'RoomObject'.
345 shape_walls_and_create_children(roomobject
, roommesh
)
347 # we select, and activate, main object for the room.
348 bpy
.context
.view_layer
.objects
.active
= roomobject
349 roomobject
.select_set(True)
352 # -----------------------------------------------------
353 # Verify if solidify exist
354 # -----------------------------------------------------
355 def is_solidify(myobject
):
358 if myobject
.modifiers
is None:
361 for mod
in myobject
.modifiers
:
362 if mod
.type == 'SOLIDIFY':
366 except AttributeError:
370 # ------------------------------------------------------------------------------
371 # Update wall mesh and children objects (baseboard, floor and ceiling).
372 # ------------------------------------------------------------------------------
373 # noinspection PyUnusedLocal
374 def update_room(self
, context
):
375 # When we update, the active object is the main object of the room.
376 o
= bpy
.context
.active_object
378 oldname
= o
.data
.name
379 # Now we deselect that room object to not delete it.
381 # and we create a new mesh for the walls:
382 tmp_mesh
= bpy
.data
.meshes
.new("temp")
383 # deselect all objects
384 for obj
in bpy
.data
.objects
:
385 obj
.select_set(False)
386 # Remove children created by this addon:
387 for child
in o
.children
:
388 # noinspection PyBroadException
390 if child
["archimesh.room_object"]:
391 # noinspection PyBroadException
393 # remove child relationship
394 for grandchild
in child
.children
:
395 grandchild
.parent
= None
397 for mod
in child
.modifiers
:
398 bpy
.ops
.object.modifier_remove(mod
)
403 child
.select_set(True)
404 bpy
.ops
.object.delete()
405 bpy
.data
.meshes
.remove(old
)
408 # Finally we create all that again (except main object),
409 shape_walls_and_create_children(o
, tmp_mesh
, True)
411 # Remove data (mesh of active object),
412 bpy
.data
.meshes
.remove(oldmesh
)
413 tmp_mesh
.name
= oldname
414 # and select, and activate, the main object of the room.
416 bpy
.context
.view_layer
.objects
.active
= o
419 # -----------------------------------------------------
420 # Move Solidify to Top
421 # -----------------------------------------------------
422 def movetotopsolidify(myobject
):
425 if myobject
.modifiers
is not None:
426 for mod
in myobject
.modifiers
:
427 if mod
.type == 'SOLIDIFY':
430 if mymod
is not None:
431 while myobject
.modifiers
[0] != mymod
:
432 bpy
.ops
.object.modifier_move_up(modifier
=mymod
.name
)
433 except AttributeError:
437 # ------------------------------------------------------------------------------
438 # Generate walls, baseboard, floor, ceiling and materials.
439 # For walls, it only shapes mesh and creates modifier solidify (the modifier, only the first time).
440 # And, for the others, it creates object and mesh.
441 # ------------------------------------------------------------------------------
442 def shape_walls_and_create_children(myroom
, tmp_mesh
, update
=False):
443 rp
= myroom
.RoomGenerator
[0] # "rp" means "room properties".
448 # Create the walls (only mesh, because the object is 'myRoom', created before).
449 create_walls(rp
, tmp_mesh
, get_blendunits(rp
.room_height
))
450 myroom
.data
= tmp_mesh
452 select_vertices(myroom
, [0, 1])
457 remove_doubles(myroom
)
458 set_normals(myroom
, not rp
.inverse
) # inside/outside
460 if rp
.wall_width
> 0.0:
461 if update
is False or is_solidify(myroom
) is False:
462 set_modifier_solidify(myroom
, get_blendunits(rp
.wall_width
))
464 for mod
in myroom
.modifiers
:
465 if mod
.type == 'SOLIDIFY':
466 mod
.thickness
= rp
.wall_width
467 # Move to Top SOLIDIFY
468 movetotopsolidify(myroom
)
470 else: # clear not used SOLIDIFY
471 for mod
in myroom
.modifiers
:
472 if mod
.type == 'SOLIDIFY':
473 myroom
.modifiers
.remove(mod
)
477 baseboardmesh
= bpy
.data
.meshes
.new("Baseboard")
478 mybase
= bpy
.data
.objects
.new("Baseboard", baseboardmesh
)
479 mybase
.location
= (0, 0, 0)
480 bpy
.context
.collection
.objects
.link(mybase
)
481 mybase
.parent
= myroom
482 mybase
.select_set(True)
483 mybase
["archimesh.room_object"] = True
484 mybase
["archimesh.room_baseboard"] = True
486 create_walls(rp
, baseboardmesh
, get_blendunits(rp
.base_height
), True)
487 set_normals(mybase
, rp
.inverse
) # inside/outside room
489 set_modifier_solidify(mybase
, get_blendunits(rp
.base_width
))
490 # Move to Top SOLIDIFY
491 movetotopsolidify(mybase
)
493 select_vertices(mybase
, [0, 1])
499 if rp
.floor
and rp
.wall_num
> 1:
500 myfloor
= create_floor(rp
, "Floor", myroom
)
501 myfloor
["archimesh.room_object"] = True
502 myfloor
.parent
= myroom
507 if rp
.ceiling
and rp
.wall_num
> 1:
508 myceiling
= create_floor(rp
, "Ceiling", myroom
)
509 myceiling
["archimesh.room_object"] = True
510 myceiling
.parent
= myroom
512 unwrap_mesh(myceiling
)
517 myshell
= add_shell(myroom
, "Wall_cover", rp
)
518 myshell
["archimesh.room_object"] = True
519 myshell
["archimesh.room_shell"] = True
520 parentobject(myroom
, myshell
)
521 myshell
.rotation_euler
= myroom
.rotation_euler
522 if rp
.wall_width
> 0.0:
523 # Solidify (need for boolean)
524 set_modifier_solidify(myshell
, 0.01)
525 # Move to Top SOLIDIFY
526 movetotopsolidify(mybase
)
529 if rp
.crt_mat
and bpy
.context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
530 # Wall material (two faces)
531 mat
= create_diffuse_material("Wall_material", False, 0.765, 0.650, 0.588, 0.8, 0.621, 0.570, 0.1, True)
532 set_material(myroom
, mat
)
535 if rp
.baseboard
and mybase
is not None:
536 mat
= create_diffuse_material("Baseboard_material", False, 0.8, 0.8, 0.8)
537 set_material(mybase
, mat
)
540 if rp
.ceiling
and myceiling
is not None:
541 mat
= create_diffuse_material("Ceiling_material", False, 0.95, 0.95, 0.95)
542 set_material(myceiling
, mat
)
545 if rp
.floor
and myfloor
is not None:
546 mat
= create_brick_material("Floor_material", False, 0.711, 0.668, 0.668, 0.8, 0.636, 0.315)
547 set_material(myfloor
, mat
)
550 if rp
.shell
and myshell
is not None:
551 mat
= create_diffuse_material("Wall_cover_material", False, 0.507, 0.309, 0.076, 0.507, 0.309, 0.076)
552 set_material(myshell
, mat
)
555 for o
in bpy
.data
.objects
:
556 if o
.select_get() is True and o
.name
!= myroom
.name
:
560 # ------------------------------------------------------------------------------
561 # Create walls or baseboard (indicated with baseboard parameter).
562 # Some custom values are passed using the rp ("room properties" group) parameter (rp.myvariable).
563 # ------------------------------------------------------------------------------
564 def create_walls(rp
, mymesh
, height
, baseboard
=False):
565 myvertex
= [(0.0, 0.0, height
), (0.0, 0.0, 0.0)]
571 for i
in range(0, rp
.wall_num
):
575 prv
= rp
.walls
[i
- 1].a
and not rp
.walls
[i
- 1].curved
577 mydat
= make_wall(prv
, rp
.walls
[i
], baseboard
, lastface
,
578 lastx
, lasty
, height
, myvertex
, myfaces
)
583 # --------------------------------------
584 # saves vertex data for opengl
585 # --------------------------------------
589 for mf
in myfaces
[idf
]:
590 if myvertex
[mf
][2] == 0:
592 point_a
= myvertex
[mf
]
594 point_b
= myvertex
[mf
]
596 rp
.walls
[i
].glpoint_a
= point_a
597 rp
.walls
[i
].glpoint_b
= point_b
605 if baseboard
is False:
606 if rp
.walls
[rp
.wall_num
- 1].a
is not True:
607 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
609 if rp
.walls
[rp
.wall_num
- 1].curved
is True:
610 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
612 myfaces
.extend([(0, 1, lastface
, lastface
+ 1)])
614 myfaces
.extend([(0, 1, lastface
+ 1, lastface
)])
616 mymesh
.from_pydata(myvertex
, [], myfaces
)
617 mymesh
.update(calc_edges
=True)
620 # ------------------------------------------------------------------------------
622 # prv: If previous wall has 'curved' activate.
623 # lastFace: Number of faces of all before walls.
624 # lastX: X position of the end of the last wall.
625 # lastY: Y position of the end of the last wall.
626 # height: Height of the last wall, without peak.
627 # ------------------------------------------------------------------------------
628 def make_wall(prv
, wall
, baseboard
, lastface
, lastx
, lasty
, height
, myvertex
, myfaces
):
629 # size: Length of the wall.
630 # over: Height of the peak from "height".
631 # factor: Displacement of the peak (between -1 and 1; 0 is the middle of the wall).
639 # if angle negative, calculate real
640 # use add because the angle is negative
644 size
= get_blendunits(size
)
645 over
= get_blendunits(over
)
647 # Calculate size using angle
648 sizex
= cos(radians(angle
)) * size
649 sizey
= sin(radians(angle
)) * size
652 if advanced
is False or baseboard
is True:
653 # Cases of this first option: Baseboard or wall without peak and without curve.
654 if baseboard
is True and advanced
is True and wall
.curved
is True:
655 (myvertex
, myfaces
, sizex
, sizey
, lastface
) = make_curved_wall(myvertex
, myfaces
, size
, angle
,
656 lastx
, lasty
, height
, lastface
,
657 wall
.curve_factor
, int(wall
.curve_arc_deg
),
658 int(wall
.curve_arc_deg
/ wall
.curve_steps
),
661 myvertex
.extend([(lastx
+ sizex
, lasty
+ sizey
, height
),
662 (lastx
+ sizex
, lasty
+ sizey
, 0.0)])
663 if check_visibility(hide
, baseboard
):
664 if prv
is False or baseboard
is True:
665 # Previous no advance or advance with curve
666 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
668 # Previous advance without curve
669 myfaces
.extend([(lastface
, lastface
+ 1, lastface
+ 2, lastface
+ 3)])
672 # Case of this second option: Wall with advanced features (orientation, visibility and peak or curve).
673 # Orientation and visibility options ('angle' and 'hide' variables) are only visible in panel
674 # with advanced features, but are taken in account in any case.
676 # Wall with curve and without peak.
677 (myvertex
, myfaces
, sizex
, sizey
, lastface
) = make_curved_wall(myvertex
, myfaces
, size
, angle
,
678 lastx
, lasty
, height
, lastface
,
679 wall
.curve_factor
, int(wall
.curve_arc_deg
),
680 int(wall
.curve_arc_deg
/ wall
.curve_steps
),
683 # Wall with peak and without curve.
684 mid
= size
/ 2 + ((size
/ 2) * factor
)
685 midx
= cos(radians(angle
)) * mid
686 midy
= sin(radians(angle
)) * mid
688 myvertex
.extend([(lastx
+ midx
, lasty
+ midy
, height
+ over
),
689 (lastx
+ midx
, lasty
+ midy
, 0.0)])
690 if check_visibility(hide
, baseboard
):
691 if fabs(factor
) != 1:
693 # Previous no advance or advance with curve
694 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
696 # Previous advance without curve
697 myfaces
.extend([(lastface
, lastface
+ 1, lastface
+ 2, lastface
+ 3)])
699 myvertex
.extend([(lastx
+ sizex
, lasty
+ sizey
, 0.0),
700 (lastx
+ sizex
, lasty
+ sizey
, height
)])
701 if check_visibility(hide
, baseboard
):
702 if fabs(factor
) != 1:
703 myfaces
.extend([(lastface
+ 2, lastface
+ 5, lastface
+ 4, lastface
+ 3)])
706 myfaces
.extend([(lastface
, lastface
+ 5, lastface
+ 4, lastface
+ 1),
707 (lastface
, lastface
+ 2, lastface
+ 5)])
709 myfaces
.extend([(lastface
, lastface
+ 4, lastface
+ 5, lastface
+ 1),
710 (lastface
+ 1, lastface
+ 2, lastface
+ 5)])
717 return lastx
, lasty
, lastface
720 # ------------------------------------------------------------------------------
721 # Verify visibility of walls
722 # ------------------------------------------------------------------------------
723 def check_visibility(h
, base
):
744 # ------------------------------------------------------------------------------
745 # Create a curved wall.
746 # ------------------------------------------------------------------------------
747 def make_curved_wall(myvertex
, myfaces
, size
, wall_angle
, lastx
, lasty
, height
,
748 lastface
, curve_factor
, arc_angle
, step_angle
, hide
, baseboard
):
751 # Calculate size using angle
752 sizex
= cos(radians(wall_angle
)) * size
753 sizey
= sin(radians(wall_angle
)) * size
755 for step
in range(0, arc_angle
+ step_angle
, step_angle
):
756 curvex
= sizex
/ 2 - cos(radians(step
+ wall_angle
)) * size
/ 2
757 curvey
= sizey
/ 2 - sin(radians(step
+ wall_angle
)) * size
/ 2
758 curvey
= curvey
* curve_factor
759 myvertex
.extend([(lastx
+ curvex
, lasty
+ curvey
, height
),
760 (lastx
+ curvex
, lasty
+ curvey
, 0.0)])
761 if check_visibility(hide
, baseboard
):
762 myfaces
.extend([(lastface
, lastface
+ 2, lastface
+ 3, lastface
+ 1)])
764 return myvertex
, myfaces
, curvex
, curvey
, lastface
767 # ------------------------------------------------------------------------------
768 # Create floor or ceiling (create object and mesh)
770 # rm: "room properties" group
771 # typ: Name of new object and mesh ('Floor' or 'Ceiling')
772 # myRoom: Main object for the room
773 # ------------------------------------------------------------------------------
775 def create_floor(rp
, typ
, myroom
):
776 bpy
.context
.view_layer
.objects
.active
= myroom
782 obverts
= bpy
.context
.active_object
.data
.vertices
783 for vertex
in obverts
:
784 verts
.append(tuple(vertex
.co
))
790 myvertex
.extend([(e
[0], e
[1], e
[2])])
793 if round(e
[2], 5) == round(get_blendunits(rp
.room_height
), 5):
794 myvertex
.extend([(e
[0], e
[1], e
[2])])
799 for f
in range(0, i
):
804 mymesh
= bpy
.data
.meshes
.new(typ
)
805 myobject
= bpy
.data
.objects
.new(typ
, mymesh
)
807 myobject
.location
= (0, 0, 0)
808 bpy
.context
.collection
.objects
.link(myobject
)
810 mymesh
.from_pydata(myvertex
, [], myfaces
)
811 mymesh
.update(calc_edges
=True)
816 # ------------------------------------------------------------------
817 # Define property group class to create, or modify, room walls.
818 # ------------------------------------------------------------------
819 class WallProperties(PropertyGroup
):
823 default
=1, precision
=3,
824 description
='Length of the wall (negative to reverse direction)',
830 description
="Define advanced parameters of the wall",
835 curved
: BoolProperty(
837 description
="Enable curved wall parameters",
841 curve_factor
: FloatProperty(
844 default
=1, precision
=1,
845 description
='Curvature variation',
848 curve_arc_deg
: FloatProperty(
849 name
='Degrees', min=1, max=359,
850 default
=180, precision
=1,
851 description
='Degrees of the curve arc (must be >= steps)',
854 curve_steps
: IntProperty(
858 description
='Curve steps',
863 name
='Peak', min=0, max=50,
864 default
=0, precision
=3,
865 description
='Middle height variation',
869 name
='Factor', min=-1, max=1,
870 default
=0, precision
=3,
871 description
='Middle displacement',
877 default
=0, precision
=1,
878 description
='Wall Angle (-180 to +180)',
884 ('0', "Visible", ""),
885 ('1', "Baseboard", ""),
890 description
="Wall visibility",
894 # opengl internal data
895 glpoint_a
: FloatVectorProperty(
897 description
="Hidden property for opengl",
900 glpoint_b
: FloatVectorProperty(
902 description
="Hidden property for opengl",
906 bpy
.utils
.register_class(WallProperties
)
909 # ------------------------------------------------------------------
910 # Add a new room wall.
911 # First add a parameter group for that new wall, and then update the room.
912 # ------------------------------------------------------------------
913 def add_room_wall(self
, context
):
914 rp
= context
.object.RoomGenerator
[0]
915 for cont
in range(len(rp
.walls
) - 1, rp
.wall_num
):
917 # by default, we alternate the direction of the walls.
919 rp
.walls
[cont
].r
= 90
920 update_room(self
, context
)
923 # ------------------------------------
924 # Get if some vertex is highest
925 # ------------------------------------
926 def get_hight(verts
, faces_4
, faces_3
, face_index
, face_num
):
928 a
= faces_4
[face_num
][0]
929 b
= faces_4
[face_num
][1]
930 c
= faces_4
[face_num
][2]
931 d
= faces_4
[face_num
][3]
933 for face3
in faces_3
:
935 if idx3
!= face_index
:
936 # check x and y position (must be equal)
937 if verts
[idx3
][0] == verts
[face_index
][0] and verts
[idx3
][1] == verts
[face_index
][1]:
938 # only if z is > that previous z
939 if verts
[idx3
][2] > verts
[face_index
][2]:
940 # checking if the original vertex is in the same face
941 # must have 2 vertices on the original face
944 if e
== a
or e
== b
or e
== c
or e
== d
:
952 # ------------------------------------
954 # ------------------------------------
955 def sort_facelist(activefaces
, activenormals
):
956 totfaces
= len(activefaces
)
959 # -----------------------
961 # -----------------------
963 newlist
.append(activefaces
[0])
964 newnormal
.append(activenormals
[0])
965 return newlist
, newnormal
967 # -----------------------
968 # Look for first element
969 # -----------------------
971 for face
in activefaces
:
977 if c
>= 2 and face
not in newlist
:
979 newnormal
.append(activenormals
[idx
])
983 # -----------------------
984 # Look for second element
985 # -----------------------
987 for face
in activefaces
:
992 if c
>= 2 and face
not in newlist
:
994 newnormal
.append(activenormals
[idx
])
998 # -----------------------
1000 # -----------------------
1001 for x
in range(2, totfaces
):
1003 for face
in activefaces
:
1006 if i
== newlist
[x
- 1][0] or i
== newlist
[x
- 1][1] or i
== newlist
[x
- 1][2] or i
== newlist
[x
- 1][3]:
1008 if c
>= 2 and face
not in newlist
:
1009 newlist
.append(face
)
1010 newnormal
.append(activenormals
[idx
])
1013 return newlist
, newnormal
1016 # ------------------------------------
1017 # Get points of the walls
1019 # ------------------------------------
1020 def get_wall_points(selobject
):
1021 obverts
= selobject
.data
.vertices
1022 obfaces
= selobject
.data
.polygons
1031 # --------------------------
1032 # Recover all vertex
1033 # --------------------------
1034 for vertex
in obverts
:
1035 verts
.append(list(vertex
.co
))
1037 # --------------------------
1039 # --------------------------
1040 for face
in obfaces
:
1041 # get only 4 corners faces
1042 if len(list(face
.vertices
)) == 3:
1043 faces_3
.append(list(face
.vertices
))
1044 # --------------------------
1046 # --------------------------
1047 for face
in obfaces
:
1048 # get only 4 corners faces
1049 if len(list(face
.vertices
)) == 4:
1050 faces_4
.append(list(face
.vertices
))
1051 normals
.append(face
.normal
)
1052 # --------------------------
1054 # --------------------------
1056 for face
in faces_4
:
1058 for e
in face
: # e contains the number of vertex element
1059 if verts
[e
][2] == 0:
1061 # Only if Z > 0, recalculate
1062 if verts
[e
][2] != 0:
1063 mylist
.append(get_hight(verts
, faces_4
, faces_3
, e
, idx
))
1065 activefaces
.append(mylist
)
1066 activenormals
.append(normals
[idx
])
1069 # ------------------------
1071 # ------------------------
1072 newlist
, newnormal
= sort_facelist(activefaces
, activenormals
)
1074 return verts
, newlist
, newnormal
1077 # ------------------------------------
1078 # Create a shell of boards
1080 # objname: Name for new object
1081 # rp: room properties
1082 # ------------------------------------
1083 def add_shell(selobject
, objname
, rp
):
1088 verts
, activefaces
, activenormals
= get_wall_points(selobject
)
1090 # --------------------------
1092 # --------------------------
1095 for face
in activefaces
:
1102 if verts
[e
][2] == 0:
1109 if verts
[e
][2] != 0:
1110 if verts
[a1
][0] == verts
[e
][0] and verts
[a1
][1] == verts
[e
][1]:
1115 mydata
= create_cover_mesh(idx
, verts
, activefaces
, activenormals
, i
, a1
, a2
, b1
, b2
,
1117 rp
.shell_height
, rp
.shell_thick
, rp
.shell_factor
, rp
.shell_bfactor
)
1119 myvertex
.extend(mydata
[1])
1120 myfaces
.extend(mydata
[2])
1122 # --------------------------
1124 # --------------------------
1125 mesh
= bpy
.data
.meshes
.new(objname
)
1126 myobject
= bpy
.data
.objects
.new(objname
, mesh
)
1128 myobject
.location
= selobject
.location
1129 bpy
.context
.collection
.objects
.link(myobject
)
1131 mesh
.from_pydata(myvertex
, [], myfaces
)
1132 mesh
.update(calc_edges
=True)
1134 remove_doubles(myobject
)
1135 set_normals(myobject
)
1140 # ---------------------------------------------------------
1141 # Project point using face normals
1144 # pf: Comparison face +/-
1145 # ---------------------------------------------------------
1146 def project_point(idx
, point
, normals
, m
, pf
):
1147 v1
= Vector(normals
[idx
])
1148 if idx
+ pf
>= len(normals
):
1153 v2
= Vector(normals
[idx
+ pf
])
1156 vf
.normalize() # must be length equal to 1
1160 n1
= (vf
[0] * m
, vf
[1] * m
, vf
[2] * m
)
1161 p1
= (point
[0] + n1
[0], point
[1] + n1
[1], point
[2] + n1
[2])
1165 # ---------------------------------------------------------
1166 # Create wall cover mesh
1168 # Uses linear equation for cutting
1170 # Z = This value is the z axis value
1171 # so, we can replace t with ((Z-Z1) / (Z2-Z1))
1173 # X = X1 + ((X2 - X1) * t)
1175 # X = X1 + ((X2 - X1) * ((Z-Z1) / (Z2-Z1)))
1176 # Y = Y1 + ((Y2 - Y1) * ((Z-Z1) / (Z2-Z1)))
1178 # height refers to the height of the cover piece
1179 # width refers to the width of the cover piece
1180 # ---------------------------------------------------------
1183 def create_cover_mesh(idx
, verts
, activefaces
, normals
, i
, a1
, a2
, b1
, b2
, merge
, space
=0.005,
1184 height
=0.20, thickness
=0.025, shell_factor
=1, shell_bfactor
=1):
1213 # get high point of walls
1218 maxh
*= shell_factor
1219 minh
= maxh
* (1 - shell_bfactor
)
1223 if shell_factor
< 1:
1227 # --------------------------------------
1228 # Loop to generate each piece of cover
1229 # --------------------------------------
1230 zpos
= minh
# initial position
1233 # detect what face must use to compare
1234 face_num
= len(activefaces
) - 1
1235 if idx
== 0 and merge
is True:
1236 if is_in_nextface(idx
+ 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1242 elif idx
== face_num
and merge
is True:
1243 if is_in_nextface(face_num
, activefaces
, verts
, a1_x
, a1_y
) is False:
1250 if is_in_nextface(idx
+ 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1257 if idx
+ 1 >= len(activefaces
):
1258 if is_in_nextface(idx
- 1, activefaces
, verts
, a1_x
, a1_y
) is True:
1286 # ----------------------
1288 # ----------------------
1293 mypoint
= project_point(idx
, (a1_x
, a1_y
, zpos
), normals
, space
, side_a
)
1295 pvertex
.extend([mypoint
])
1299 mypoint
= project_point(idx
, (a1_x
, a1_y
, zpos
), normals
, space
+ thickness
, side_a
)
1300 pvertex
.extend([mypoint
])
1303 # get second point (vertical)
1304 mypoint
= project_point(idx
, (a2_x
, a2_y
, zpos
), normals
, space
, side_a
)
1307 mypoint
= project_point(idx
, (a2_x
, a2_y
, zpos
), normals
, space
+ thickness
, side_a
)
1314 mypoint
= project_point(idx
, (b1_x
, b1_y
, zpos
), normals
, space
, side_b
)
1315 pvertex
.extend([mypoint
])
1319 mypoint
= project_point(idx
, (b1_x
, b1_y
, zpos
), normals
, space
+ thickness
, side_b
)
1320 pvertex
.extend([mypoint
])
1323 # get second point (vertical)
1324 mypoint
= project_point(idx
, (b2_x
, b2_y
, zpos
), normals
, space
, side_b
)
1327 mypoint
= project_point(idx
, (b2_x
, b2_y
, zpos
), normals
, space
+ thickness
, side_b
)
1333 pfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2)])
1336 pfaces
.extend([(i
- 3, i
, i
+ 2, i
- 1)])
1340 # ----------------------
1342 # ----------------------
1344 # -------------------------------
1346 # -------------------------------
1380 x
= ax1
+ ((ax2
- ax1
) * ((zpos
- az1
) / (az2
- az1
)))
1381 y
= ay1
+ ((ay2
- ay1
) * ((zpos
- az1
) / (az2
- az1
)))
1382 pvertex
.extend([(x
, y
, zpos
)])
1386 x
= bx1
+ ((bx2
- bx1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1387 y
= by1
+ ((by2
- by1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1388 pvertex
.extend([(x
, y
, zpos
)])
1389 # -------------------------------
1391 # -------------------------------
1425 x
= ax1
+ ((ax2
- ax1
) * ((zpos
- az1
) / (az2
- az1
)))
1426 y
= ay1
+ ((ay2
- ay1
) * ((zpos
- az1
) / (az2
- az1
)))
1427 pvertex
.extend([(x
, y
, zpos
)])
1431 x
= bx1
+ ((bx2
- bx1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1432 y
= by1
+ ((by2
- by1
) * ((zpos
- bz1
) / (bz2
- bz1
)))
1433 pvertex
.extend([(x
, y
, zpos
)])
1436 pfaces
.extend([(i
, i
+ 1, i
+ 3, i
+ 2)])
1439 pfaces
.extend([(i
- 1, i
- 3, i
, i
+ 1)])
1441 pfaces
.extend([(i
- 1, i
- 2, i
, i
+ 1)])
1445 # avoid infinite loop
1455 return i
, pvertex
, pfaces
1458 # -------------------------------------------------------------
1459 # Detect if the vertex is face
1460 # -------------------------------------------------------------
1461 def is_in_nextface(idx
, activefaces
, verts
, x
, y
):
1462 if idx
>= len(activefaces
):
1465 for f
in activefaces
[idx
]:
1466 if verts
[f
][2] == 0: # only ground
1467 if verts
[f
][0] == x
and verts
[f
][1] == y
:
1473 # ------------------------------------------------------------------
1474 # Define property group class to create or modify a rooms.
1475 # ------------------------------------------------------------------
1476 class RoomProperties(PropertyGroup
):
1477 room_height
: FloatProperty(
1478 name
='Height', min=0.001, max=50,
1479 default
=2.4, precision
=3,
1480 description
='Room height', update
=update_room
,
1482 wall_width
: FloatProperty(
1483 name
='Thickness', min=0.000, max=10,
1484 default
=0.0, precision
=3,
1485 description
='Thickness of the walls', update
=update_room
,
1487 inverse
: BoolProperty(
1488 name
="Inverse", description
="Inverse normals to outside",
1492 crt_mat
: BoolProperty(
1493 name
="Create default Cycles materials",
1494 description
="Create default materials for Cycles render",
1499 wall_num
: IntProperty(
1500 name
='Number of Walls', min=1, max=50,
1502 description
='Number total of walls in the room', update
=add_room_wall
,
1505 baseboard
: BoolProperty(
1506 name
="Baseboard", description
="Create a baseboard automatically",
1511 base_width
: FloatProperty(
1512 name
='Width', min=-10, max=10,
1513 default
=0.015, precision
=3,
1514 description
='Baseboard width', update
=update_room
,
1516 base_height
: FloatProperty(
1517 name
='Height', min=0.05, max=20,
1518 default
=0.12, precision
=3,
1519 description
='Baseboard height', update
=update_room
,
1522 ceiling
: BoolProperty(
1523 name
="Ceiling", description
="Create a ceiling",
1524 default
=False, update
=update_room
,
1526 floor
: BoolProperty(
1527 name
="Floor", description
="Create a floor automatically",
1532 merge
: BoolProperty(
1533 name
="Close walls", description
="Close walls to create a full closed room",
1534 default
=False, update
=update_room
,
1537 walls
: CollectionProperty(
1538 type=WallProperties
,
1541 shell
: BoolProperty(
1542 name
="Wall cover", description
="Create a cover of boards",
1543 default
=False, update
=update_room
,
1545 shell_thick
: FloatProperty(
1546 name
='Thickness', min=0.001, max=1,
1547 default
=0.025, precision
=3,
1548 description
='Cover board thickness', update
=update_room
,
1550 shell_height
: FloatProperty(
1551 name
='Height', min=0.05, max=1,
1552 default
=0.20, precision
=3,
1553 description
='Cover board height', update
=update_room
,
1555 shell_factor
: FloatProperty(
1556 name
='Top', min=0.1, max=1,
1557 default
=1, precision
=1,
1558 description
='Percentage for top covering (1 Full)', update
=update_room
,
1560 shell_bfactor
: FloatProperty(
1561 name
='Bottom', min=0.1, max=1,
1562 default
=1, precision
=1,
1563 description
='Percentage for bottom covering (1 Full)', update
=update_room
,
1566 bpy
.utils
.register_class(RoomProperties
)
1567 Object
.RoomGenerator
= CollectionProperty(type=RoomProperties
)
1570 # -----------------------------------------------------
1571 # Add wall parameters to the panel.
1572 # -----------------------------------------------------
1573 def add_wall(idx
, box
, wall
):
1574 box
.label(text
="Wall " + str(idx
))
1578 # row.prop(wall, 'curved')
1581 srow
.prop(wall
, 'r')
1582 srow
.prop(wall
, 'h')
1585 srow
.prop(wall
, 'curved')
1587 if wall
.curved
is False:
1588 srow
.prop(wall
, 'm')
1589 srow
.prop(wall
, 'f')
1591 if wall
.curved
is True:
1592 srow
.prop(wall
, 'curve_factor')
1593 srow
.prop(wall
, 'curve_arc_deg')
1594 srow
.prop(wall
, 'curve_steps')
1597 # ------------------------------------------------------------------
1598 # Define panel class to modify rooms.
1599 # ------------------------------------------------------------------
1600 class ARCHIMESH_PT_RoomGenerator(Panel
):
1601 bl_idname
= "OBJECT_PT_room_generator"
1603 bl_space_type
= 'VIEW_3D'
1604 bl_region_type
= 'UI'
1605 bl_category
= 'Create'
1607 # -----------------------------------------------------
1609 # -----------------------------------------------------
1611 def poll(cls
, context
):
1615 if 'RoomGenerator' not in o
:
1620 # -----------------------------------------------------
1621 # Draw (create UI interface)
1622 # -----------------------------------------------------
1623 def draw(self
, context
):
1625 # If the selected object didn't be created with the group 'RoomGenerator', this panel is not created.
1626 # noinspection PyBroadException
1628 if 'RoomGenerator' not in o
:
1633 layout
= self
.layout
1634 if bpy
.context
.mode
== 'EDIT_MESH':
1635 layout
.label(text
='Warning: Operator does not work in edit mode.', icon
='ERROR')
1637 room
= o
.RoomGenerator
[0]
1639 row
.prop(room
, 'room_height')
1640 row
.prop(room
, 'wall_width')
1641 row
.prop(room
, 'inverse')
1644 if room
.wall_num
> 1:
1645 row
.prop(room
, 'ceiling')
1646 row
.prop(room
, 'floor')
1647 row
.prop(room
, 'merge')
1651 row
.prop(room
, 'wall_num')
1653 # Add menu for walls
1654 if room
.wall_num
> 0:
1655 for wall_index
in range(0, room
.wall_num
):
1657 add_wall(wall_index
+ 1, box
, room
.walls
[wall_index
])
1660 box
.prop(room
, 'baseboard')
1661 if room
.baseboard
is True:
1663 row
.prop(room
, 'base_width')
1664 row
.prop(room
, 'base_height')
1667 box
.prop(room
, 'shell')
1668 if room
.shell
is True:
1670 row
.prop(room
, 'shell_height')
1671 row
.prop(room
, 'shell_thick')
1673 row
.prop(room
, 'shell_factor', slider
=True)
1674 row
.prop(room
, 'shell_bfactor', slider
=True)
1677 if not context
.scene
.render
.engine
in {'CYCLES', 'BLENDER_EEVEE'}:
1679 box
.prop(room
, 'crt_mat')