Update for changes in Blender's API
[blender-addons.git] / archimesh / achm_main_panel.py
blobd12c2a6ba666f54ee1e1e5271d0445cfed517386
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 #####
19 # <pep8 compliant>
21 # ----------------------------------------------------------
22 # Main panel for different Archimesh general actions
23 # Author: Antonio Vazquez (antonioya)
25 # ----------------------------------------------------------
26 # noinspection PyUnresolvedReferences
27 import bpy
28 # noinspection PyUnresolvedReferences
29 import bgl
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):
40 flag = False
41 for mod in myobject.modifiers:
42 if mod.type == 'BOOLEAN':
43 if mod.object == childobject:
44 flag = True
45 break
46 return flag
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 # ------------------------------
59 # Execute
60 # ------------------------------
61 # noinspection PyMethodMayBeStatic
62 def execute(self, context):
63 scene = context.scene
64 listobj = []
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
71 try:
72 if obj["archimesh.hole_enable"]:
73 if obj.select is True or scene.archimesh_select_only is False:
74 listobj.extend([obj])
75 except:
76 continue
77 # ---------------------------
78 # Get the baseboard object
79 # ---------------------------
80 mybaseboard = None
81 for child in context.object.children:
82 # noinspection PyBroadException
83 try:
84 if child["archimesh.room_baseboard"]:
85 mybaseboard = child
86 except:
87 continue
88 # ---------------------------
89 # Get the shell object
90 # ---------------------------
91 myshell = None
92 for child in context.object.children:
93 # noinspection PyBroadException
94 try:
95 if child["archimesh.room_shell"]:
96 myshell = child
97 except:
98 continue
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 # -----------------------------
115 for obj in listobj:
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
129 try:
130 if child["archimesh.ctrl_hole"]:
131 # apply scale
132 t = parentobj.RoomGenerator[0].wall_width
133 if t > 0:
134 child.scale.y = (t + 0.45) / (child.dimensions.y / child.scale.y) # Add some gap
135 else:
136 child.scale.y = 1
137 # add boolean modifier
138 if isboolean(myroom, child) is False:
139 set_modifier_boolean(myroom, child)
140 except:
141 # print("Unexpected error:" + str(sys.exc_info()))
142 pass
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
150 try:
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)
156 except:
157 pass
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
171 try:
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)
177 except:
178 pass
180 return {'FINISHED'}
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 # ------------------------------
193 # Execute
194 # ------------------------------
195 def execute(self, context):
196 # Enable for debugging code
197 debugmode = False
199 scene = context.scene
200 mypoints = None
201 clearangles = None
203 if debugmode is True:
204 print("======================================================================")
205 print("== ==")
206 print("== Grease pencil strokes analysis ==")
207 print("== ==")
208 print("======================================================================")
210 # -----------------------------------
211 # Get grease pencil points
212 # -----------------------------------
213 # noinspection PyBroadException
214 try:
216 # noinspection PyBroadException
217 try:
218 pencil = bpy.context.object.grease_pencil.layers.active
219 except:
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]
228 mypoints = []
229 idx = 0
230 x = 0
231 y = 0
232 orientation = None
233 old_orientation = None
235 for point in allpoints:
236 if idx == 0:
237 x = point[0]
238 y = point[1]
239 else:
240 abs_x = abs(point[0] - x)
241 abs_y = abs(point[1] - y)
243 if abs_y > abs_x:
244 orientation = "V"
245 else:
246 orientation = "H"
248 if old_orientation == orientation:
249 x = point[0]
250 y = point[1]
251 else:
252 mypoints.extend([(x, y)])
253 x = point[0]
254 y = point[1]
255 old_orientation = orientation
257 idx += 1
258 # Last point
259 mypoints.extend([(x, y)])
261 if debugmode is True:
262 print("\nPoints\n====================")
263 i = 0
264 for p in mypoints:
265 print(str(i) + ":" + str(p))
266 i += 1
267 # -----------------------------------
268 # Calculate distance between points
269 # -----------------------------------
270 if debugmode is True:
271 print("\nDistance\n====================")
272 i = len(mypoints)
273 distlist = []
274 for e in range(1, i):
275 d = sqrt(
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":
279 d *= 3.2808399
281 distlist.extend([d])
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====================")
292 i = len(mypoints)
293 anglelist = []
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))
297 a = asin(sinv)
298 # Clamp to 90 or 0 degrees
299 if fabs(a) > pi / 4:
300 b = pi / 2
301 else:
302 b = 0
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
310 if b == 0:
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:
322 clearangles = []
323 cleardistan = []
324 i = len(anglelist)
325 oldangle = anglelist[0]
326 olddist = 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]
333 else:
334 olddist += distlist[e]
335 # last
336 clearangles.extend([oldangle])
337 cleardistan.extend([olddist])
339 # ----------------------------
340 # Create the room
341 # ----------------------------
342 if len(mypoints) > 1 and len(clearangles) > 0:
343 # Move cursor
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
348 # Add room mesh
349 bpy.ops.mesh.archimesh_room()
350 myroom = context.object
351 mydata = myroom.RoomGenerator[0]
352 # Number of walls
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
358 i = len(mypoints)
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
364 else:
365 mydata.walls[e].w = round(fabs(cleardistan[e]), 2)
366 mydata.walls[e].r = (fabs(clearangles[e]) * 180 * -1) / pi # from radians
368 else:
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")
378 else:
379 self.report({'WARNING'}, "Archimesh: Not enough grease pencil strokes for creating room.")
381 return {'FINISHED'}
382 except:
383 self.report({'WARNING'}, "Archimesh: No grease pencil strokes. Do strokes in top view before creating room")
384 return {'CANCELLED'}
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 # ------------------------------
399 # Draw UI
400 # ------------------------------
401 def draw(self, context):
402 layout = self.layout
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
411 try:
412 if 'RoomGenerator' in myobj:
413 box = layout.box()
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")
419 # Export/Import
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')
423 except:
424 pass
426 # -------------------------------------------------------------------------
427 # If the selected object isn't a kitchen
428 # this button is not created.
429 # -------------------------------------------------------------------------
430 # noinspection PyBroadException
431 try:
432 if myobj["archimesh.sku"] is not None:
433 box = layout.box()
434 box.label("Kitchen Tools", icon='MODIFIER')
435 # Export
436 row = box.row(align=False)
437 row.operator("io_export.kitchen_inventory", text="Export inventory", icon='PASTEDOWN')
438 except:
439 pass
441 # ------------------------------
442 # Elements Buttons
443 # ------------------------------
444 box = layout.box()
445 box.label("Elements", icon='GROUP')
446 row = box.row()
447 row.operator("mesh.archimesh_room")
448 row.operator("mesh.archimesh_column")
449 row = box.row()
450 row.operator("mesh.archimesh_door")
451 row = box.row()
452 row.operator("mesh.archimesh_window")
453 row.operator("mesh.archimesh_winpanel")
454 row = box.row()
455 row.operator("mesh.archimesh_kitchen")
456 row.operator("mesh.archimesh_shelves")
457 row = box.row()
458 row.operator("mesh.archimesh_stairs")
459 row.operator("mesh.archimesh_roof")
461 # ------------------------------
462 # Prop Buttons
463 # ------------------------------
464 box = layout.box()
465 box.label("Props", icon='LAMP_DATA')
466 row = box.row()
467 row.operator("mesh.archimesh_books")
468 row.operator("mesh.archimesh_lamp")
469 row = box.row()
470 row.operator("mesh.archimesh_venetian")
471 row.operator("mesh.archimesh_roller")
472 row = box.row()
473 row.operator("mesh.archimesh_japan")
475 # ------------------------------
476 # OpenGL Buttons
477 # ------------------------------
478 box = layout.box()
479 box.label("Display hints", icon='QUESTION')
480 row = box.row()
481 if context.window_manager.archimesh_run_opengl is False:
482 icon = 'PLAY'
483 txt = 'Show'
484 else:
485 icon = "PAUSE"
486 txt = 'Hide'
487 row.operator("archimesh.runopenglbutton", text=txt, icon=icon)
488 row = box.row()
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')
492 row = box.row()
493 row.prop(scene, "archimesh_text_color", text="")
494 row.prop(scene, "archimesh_walltext_color", text="")
495 row = box.row()
496 row.prop(scene, "archimesh_font_size")
497 row.prop(scene, "archimesh_wfont_size")
498 row = box.row()
499 row.prop(scene, "archimesh_hint_space")
500 # ------------------------------
501 # Grease pencil tools
502 # ------------------------------
503 box = layout.box()
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 # ----------------------------------
528 @staticmethod
529 def handle_add(self, context):
530 if AchmRunHintDisplayButton._handle is None:
531 AchmRunHintDisplayButton._handle = SpaceView3D.draw_handler_add(draw_callback_px, (self, context),
532 'WINDOW',
533 'POST_PIXEL')
534 context.window_manager.archimesh_run_opengl = True
536 # ------------------------------------
537 # Disable gl drawing removing handler
538 # ------------------------------------
539 # noinspection PyUnusedLocal
540 @staticmethod
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()
555 else:
556 self.handle_remove(self, context)
557 context.area.tag_redraw()
559 return {'FINISHED'}
560 else:
561 self.report({'WARNING'},
562 "View3D not found, cannot run operator")
564 return {'CANCELLED'}
567 # -------------------------------------------------------------
568 # Handler for drawing OpenGl
569 # -------------------------------------------------------------
570 # noinspection PyUnusedLocal
571 def draw_callback_px(self, context):
572 draw_main(context)