1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
21 # ----------------------------------------------------------
22 # Main panel for different Archimesh general actions
23 # Author: Antonio Vazquez (antonioya)
25 # ----------------------------------------------------------
26 # noinspection PyUnresolvedReferences
28 # noinspection PyUnresolvedReferences
30 from bpy
.types
import Operator
, Panel
, SpaceView3D
31 from math
import sqrt
, fabs
, pi
, asin
32 from .achm_tools
import *
33 from .achm_gltools
import *
36 # -----------------------------------------------------
37 # Verify if boolean already exist
38 # -----------------------------------------------------
39 def isboolean(myobject
, childobject
):
41 for mod
in myobject
.modifiers
:
42 if mod
.type == 'BOOLEAN':
43 if mod
.object == childobject
:
49 # ------------------------------------------------------
50 # Button: Action to link windows and doors
51 # ------------------------------------------------------
52 class AchmHoleAction(Operator
):
53 bl_idname
= "object.archimesh_cut_holes"
54 bl_label
= "Auto Holes"
55 bl_description
= "Enable windows and doors holes for any selected object (needs wall thickness)"
56 bl_category
= 'Archimesh'
58 # ------------------------------
60 # ------------------------------
61 # noinspection PyMethodMayBeStatic
62 def execute(self
, context
):
65 # ---------------------------------------------------------------------
66 # Save the list of selected objects because the select flag is missed
67 # only can be windows or doors
68 # ---------------------------------------------------------------------
69 for obj
in bpy
.context
.scene
.objects
:
70 # noinspection PyBroadException
72 if obj
["archimesh.hole_enable"]:
73 if obj
.select
is True or scene
.archimesh_select_only
is False:
77 # ---------------------------
78 # Get the baseboard object
79 # ---------------------------
81 for child
in context
.object.children
:
82 # noinspection PyBroadException
84 if child
["archimesh.room_baseboard"]:
88 # ---------------------------
89 # Get the shell object
90 # ---------------------------
92 for child
in context
.object.children
:
93 # noinspection PyBroadException
95 if child
["archimesh.room_shell"]:
100 # -----------------------------
101 # Remove all empty Boolean modifiers
102 # -----------------------------
103 for mod
in context
.object.modifiers
:
104 if mod
.type == 'BOOLEAN':
105 if mod
.object is None:
106 bpy
.ops
.object.modifier_remove(modifier
=mod
.name
)
108 # if thickness is 0, must be > 0
109 myroom
= context
.object
110 if myroom
.RoomGenerator
[0].wall_width
== 0:
111 self
.report({'WARNING'}, "Walls must have thickness for using autohole function. Change it and run again")
112 # -----------------------------
113 # Now apply Wall holes
114 # -----------------------------
116 parentobj
= context
.object
117 # Parent the empty to the room (the parent of frame)
118 if obj
.parent
is not None:
119 bpy
.ops
.object.select_all(action
='DESELECT')
120 parentobj
.select
= True
121 obj
.parent
.select
= True # parent of object
122 bpy
.ops
.object.parent_set(type='OBJECT', keep_transform
=False)
123 # ---------------------------------------
124 # Add the modifier to controller
125 # and the scale to use the same thickness
126 # ---------------------------------------
127 for child
in obj
.parent
.children
:
128 # noinspection PyBroadException
130 if child
["archimesh.ctrl_hole"]:
132 t
= parentobj
.RoomGenerator
[0].wall_width
134 child
.scale
.y
= (t
+ 0.45) / (child
.dimensions
.y
/ child
.scale
.y
) # Add some gap
137 # add boolean modifier
138 if isboolean(myroom
, child
) is False:
139 set_modifier_boolean(myroom
, child
)
141 # print("Unexpected error:" + str(sys.exc_info()))
144 # ---------------------------------------
145 # Now add the modifiers to baseboard
146 # ---------------------------------------
147 if mybaseboard
is not None:
148 for obj
in bpy
.context
.scene
.objects
:
149 # noinspection PyBroadException
151 if obj
["archimesh.ctrl_base"]:
152 if obj
.select
is True or scene
.archimesh_select_only
is False:
153 # add boolean modifier
154 if isboolean(mybaseboard
, obj
) is False:
155 set_modifier_boolean(mybaseboard
, obj
)
159 # ---------------------------------------
160 # Now add the modifiers to shell
161 # ---------------------------------------
162 if myshell
is not None:
163 # Remove all empty Boolean modifiers
164 for mod
in myshell
.modifiers
:
165 if mod
.type == 'BOOLEAN':
166 if mod
.object is None:
167 bpy
.ops
.object.modifier_remove(modifier
=mod
.name
)
169 for obj
in bpy
.context
.scene
.objects
:
170 # noinspection PyBroadException
172 if obj
["archimesh.ctrl_hole"]:
173 if obj
.select
is True or scene
.archimesh_select_only
is False:
174 # add boolean modifier
175 if isboolean(myshell
, obj
) is False:
176 set_modifier_boolean(myshell
, obj
)
183 # ------------------------------------------------------
184 # Button: Action to create room from grease pencil
185 # ------------------------------------------------------
186 class AchmPencilAction(Operator
):
187 bl_idname
= "object.archimesh_pencil_room"
188 bl_label
= "Room from Draw"
189 bl_description
= "Create a room base on grease pencil strokes (draw from top view (7 key))"
190 bl_category
= 'Archimesh'
192 # ------------------------------
194 # ------------------------------
195 def execute(self
, context
):
196 # Enable for debugging code
199 scene
= context
.scene
203 if debugmode
is True:
204 print("======================================================================")
206 print("== Grease pencil strokes analysis ==")
208 print("======================================================================")
210 # -----------------------------------
211 # Get grease pencil points
212 # -----------------------------------
213 # noinspection PyBroadException
216 # noinspection PyBroadException
218 pencil
= bpy
.context
.object.grease_pencil
.layers
.active
220 pencil
= bpy
.context
.scene
.grease_pencil
.layers
.active
222 if pencil
.active_frame
is not None:
223 for i
, stroke
in enumerate(pencil
.active_frame
.strokes
):
224 stroke_points
= pencil
.active_frame
.strokes
[i
].points
225 allpoints
= [(point
.co
.x
, point
.co
.y
)
226 for point
in stroke_points
]
233 old_orientation
= None
235 for point
in allpoints
:
240 abs_x
= abs(point
[0] - x
)
241 abs_y
= abs(point
[1] - y
)
248 if old_orientation
== orientation
:
252 mypoints
.extend([(x
, y
)])
255 old_orientation
= orientation
259 mypoints
.extend([(x
, y
)])
261 if debugmode
is True:
262 print("\nPoints\n====================")
265 print(str(i
) + ":" + str(p
))
267 # -----------------------------------
268 # Calculate distance between points
269 # -----------------------------------
270 if debugmode
is True:
271 print("\nDistance\n====================")
274 for e
in range(1, i
):
276 ((mypoints
[e
][0] - mypoints
[e
- 1][0]) ** 2) + ((mypoints
[e
][1] - mypoints
[e
- 1][1]) ** 2))
277 # Imperial units if needed
278 if bpy
.context
.scene
.unit_settings
.system
== "IMPERIAL":
283 if debugmode
is True:
284 print(str(e
- 1) + ":" + str(d
))
285 # -----------------------------------
286 # Calculate angle of walls
287 # clamped to right angles
288 # -----------------------------------
289 if debugmode
is True:
290 print("\nAngle\n====================")
294 for e
in range(1, i
):
295 sinv
= (mypoints
[e
][1] - mypoints
[e
- 1][1]) / sqrt(
296 ((mypoints
[e
][0] - mypoints
[e
- 1][0]) ** 2) + ((mypoints
[e
][1] - mypoints
[e
- 1][1]) ** 2))
298 # Clamp to 90 or 0 degrees
304 anglelist
.extend([b
])
305 # Reverse de distance using angles (inverse angle to axis) for Vertical lines
306 if a
< 0.0 and b
!= 0:
307 distlist
[e
- 1] *= -1 # reverse distance
309 # Reverse de distance for horizontal lines
311 if mypoints
[e
- 1][0] > mypoints
[e
][0]:
312 distlist
[e
- 1] *= -1 # reverse distance
314 if debugmode
is True:
315 print(str(e
- 1) + ":" + str((a
* 180) / pi
) + "...:" + str(
316 (b
* 180) / pi
) + "--->" + str(distlist
[e
- 1]))
318 # ---------------------------------------
319 # Verify duplications and reduce noise
320 # ---------------------------------------
321 if len(anglelist
) >= 1:
325 oldangle
= anglelist
[0]
327 for e
in range(0, i
):
328 if oldangle
!= anglelist
[e
]:
329 clearangles
.extend([oldangle
])
330 cleardistan
.extend([olddist
])
331 oldangle
= anglelist
[e
]
332 olddist
= distlist
[e
]
334 olddist
+= distlist
[e
]
336 clearangles
.extend([oldangle
])
337 cleardistan
.extend([olddist
])
339 # ----------------------------
341 # ----------------------------
342 if len(mypoints
) > 1 and len(clearangles
) > 0:
344 bpy
.context
.scene
.cursor_location
.x
= mypoints
[0][0]
345 bpy
.context
.scene
.cursor_location
.y
= mypoints
[0][1]
346 bpy
.context
.scene
.cursor_location
.z
= 0 # always on grid floor
349 bpy
.ops
.mesh
.archimesh_room()
350 myroom
= context
.object
351 mydata
= myroom
.RoomGenerator
[0]
353 mydata
.wall_num
= len(mypoints
) - 1
354 mydata
.ceiling
= scene
.archimesh_ceiling
355 mydata
.floor
= scene
.archimesh_floor
356 mydata
.merge
= scene
.archimesh_merge
359 for e
in range(0, i
- 1):
360 if clearangles
[e
] == pi
/ 2:
361 if cleardistan
[e
] > 0:
362 mydata
.walls
[e
].w
= round(fabs(cleardistan
[e
]), 2)
363 mydata
.walls
[e
].r
= (fabs(clearangles
[e
]) * 180) / pi
# from radians
365 mydata
.walls
[e
].w
= round(fabs(cleardistan
[e
]), 2)
366 mydata
.walls
[e
].r
= (fabs(clearangles
[e
]) * 180 * -1) / pi
# from radians
369 mydata
.walls
[e
].w
= round(cleardistan
[e
], 2)
370 mydata
.walls
[e
].r
= (fabs(clearangles
[e
]) * 180) / pi
# from radians
372 # Remove Grease pencil
373 if pencil
is not None:
374 for frame
in pencil
.frames
:
375 pencil
.frames
.remove(frame
)
377 self
.report({'INFO'}, "Archimesh: Room created from grease pencil strokes")
379 self
.report({'WARNING'}, "Archimesh: Not enough grease pencil strokes for creating room.")
383 self
.report({'WARNING'}, "Archimesh: No grease pencil strokes. Do strokes in top view before creating room")
387 # ------------------------------------------------------------------
388 # Define panel class for main functions.
389 # ------------------------------------------------------------------
390 class ArchimeshMainPanel(Panel
):
391 bl_idname
= "ARCHIMESH_PT_main"
392 bl_label
= "Archimesh"
393 bl_space_type
= "VIEW_3D"
394 bl_region_type
= "TOOLS"
395 bl_category
= "Create"
396 bl_context
= "objectmode"
398 # ------------------------------
400 # ------------------------------
401 def draw(self
, context
):
403 scene
= context
.scene
405 myobj
= context
.object
406 # -------------------------------------------------------------------------
407 # If the selected object didn't be created with the group 'RoomGenerator',
408 # this button is not created.
409 # -------------------------------------------------------------------------
410 # noinspection PyBroadException
412 if 'RoomGenerator' in myobj
:
414 box
.label("Room Tools", icon
='MODIFIER')
415 row
= box
.row(align
=False)
416 row
.operator("object.archimesh_cut_holes", icon
='GRID')
417 row
.prop(scene
, "archimesh_select_only")
420 row
= box
.row(align
=False)
421 row
.operator("io_import.roomdata", text
="Import", icon
='COPYDOWN')
422 row
.operator("io_export.roomdata", text
="Export", icon
='PASTEDOWN')
426 # -------------------------------------------------------------------------
427 # If the selected object isn't a kitchen
428 # this button is not created.
429 # -------------------------------------------------------------------------
430 # noinspection PyBroadException
432 if myobj
["archimesh.sku"] is not None:
434 box
.label("Kitchen Tools", icon
='MODIFIER')
436 row
= box
.row(align
=False)
437 row
.operator("io_export.kitchen_inventory", text
="Export inventory", icon
='PASTEDOWN')
441 # ------------------------------
443 # ------------------------------
445 box
.label("Elements", icon
='GROUP')
447 row
.operator("mesh.archimesh_room")
448 row
.operator("mesh.archimesh_column")
450 row
.operator("mesh.archimesh_door")
452 row
.operator("mesh.archimesh_window")
453 row
.operator("mesh.archimesh_winpanel")
455 row
.operator("mesh.archimesh_kitchen")
456 row
.operator("mesh.archimesh_shelves")
458 row
.operator("mesh.archimesh_stairs")
459 row
.operator("mesh.archimesh_roof")
461 # ------------------------------
463 # ------------------------------
465 box
.label("Props", icon
='LAMP_DATA')
467 row
.operator("mesh.archimesh_books")
468 row
.operator("mesh.archimesh_lamp")
470 row
.operator("mesh.archimesh_venetian")
471 row
.operator("mesh.archimesh_roller")
473 row
.operator("mesh.archimesh_japan")
475 # ------------------------------
477 # ------------------------------
479 box
.label("Display hints", icon
='QUESTION')
481 if context
.window_manager
.archimesh_run_opengl
is False:
487 row
.operator("archimesh.runopenglbutton", text
=txt
, icon
=icon
)
489 row
.prop(scene
, "archimesh_gl_measure", toggle
=True, icon
="ALIGN")
490 row
.prop(scene
, "archimesh_gl_name", toggle
=True, icon
="OUTLINER_OB_FONT")
491 row
.prop(scene
, "archimesh_gl_ghost", icon
='GHOST_ENABLED')
493 row
.prop(scene
, "archimesh_text_color", text
="")
494 row
.prop(scene
, "archimesh_walltext_color", text
="")
496 row
.prop(scene
, "archimesh_font_size")
497 row
.prop(scene
, "archimesh_wfont_size")
499 row
.prop(scene
, "archimesh_hint_space")
500 # ------------------------------
501 # Grease pencil tools
502 # ------------------------------
504 box
.label("Pencil Tools", icon
='MODIFIER')
505 row
= box
.row(align
=False)
506 row
.operator("object.archimesh_pencil_room", icon
='GREASEPENCIL')
507 row
= box
.row(align
=False)
508 row
.prop(scene
, "archimesh_ceiling")
509 row
.prop(scene
, "archimesh_floor")
510 row
.prop(scene
, "archimesh_merge")
513 # -------------------------------------------------------------
514 # Defines button for enable/disable the tip display
516 # -------------------------------------------------------------
517 class AchmRunHintDisplayButton(Operator
):
518 bl_idname
= "archimesh.runopenglbutton"
519 bl_label
= "Display hint data manager"
520 bl_description
= "Display aditional information in the viewport"
521 bl_category
= 'Archimesh'
523 _handle
= None # keep function handler
525 # ----------------------------------
526 # Enable gl drawing adding handler
527 # ----------------------------------
529 def handle_add(self
, context
):
530 if AchmRunHintDisplayButton
._handle
is None:
531 AchmRunHintDisplayButton
._handle
= SpaceView3D
.draw_handler_add(draw_callback_px
, (self
, context
),
534 context
.window_manager
.archimesh_run_opengl
= True
536 # ------------------------------------
537 # Disable gl drawing removing handler
538 # ------------------------------------
539 # noinspection PyUnusedLocal
541 def handle_remove(self
, context
):
542 if AchmRunHintDisplayButton
._handle
is not None:
543 SpaceView3D
.draw_handler_remove(AchmRunHintDisplayButton
._handle
, 'WINDOW')
544 AchmRunHintDisplayButton
._handle
= None
545 context
.window_manager
.archimesh_run_opengl
= False
547 # ------------------------------
548 # Execute button action
549 # ------------------------------
550 def execute(self
, context
):
551 if context
.area
.type == 'VIEW_3D':
552 if context
.window_manager
.archimesh_run_opengl
is False:
553 self
.handle_add(self
, context
)
554 context
.area
.tag_redraw()
556 self
.handle_remove(self
, context
)
557 context
.area
.tag_redraw()
561 self
.report({'WARNING'},
562 "View3D not found, cannot run operator")
567 # -------------------------------------------------------------
568 # Handler for drawing OpenGl
569 # -------------------------------------------------------------
570 # noinspection PyUnusedLocal
571 def draw_callback_px(self
, context
):