diff --git a/__init__.py b/__init__.py index 00fbe7e..bed0f04 100644 --- a/__init__.py +++ b/__init__.py @@ -33,7 +33,7 @@ "name": "Architecture Lab", "author": "Insma Software - Maciej Klemarczyk (mklemarczyk)", "location": "View3D > Add > Mesh > ArchLab", - "version": (1, 0, 9), + "version": (1, 1, 0), "blender": (2, 7, 9), "description": "Creates rooms, doors, windows, and other architecture objects", "wiki_url": "https://github.com/insma/ArchitectureLab/wiki", @@ -52,6 +52,8 @@ importlib.reload(archlab_bldn_wall_tool) importlib.reload(archlab_dcrt_glass_tool) importlib.reload(archlab_dcrt_plate_tool) + importlib.reload(archlab_frnt_bench_tool) + importlib.reload(archlab_frnt_shelve_tool) importlib.reload(archlab_mesh_cube_tool) importlib.reload(archlab_mesh_cube_tool) importlib.reload(archlab_mesh_plane_tool) @@ -64,6 +66,8 @@ from . import archlab_bldn_wall_tool from . import archlab_dcrt_glass_tool from . import archlab_dcrt_plate_tool + from . import archlab_frnt_bench_tool + from . import archlab_frnt_shelve_tool from . import archlab_mesh_circle_tool from . import archlab_mesh_cube_tool from . import archlab_mesh_plane_tool @@ -82,6 +86,10 @@ archlab_dcrt_glass_tool.ArchLabGlassGeneratorPanel, archlab_dcrt_plate_tool.ArchLabPlate, archlab_dcrt_plate_tool.ArchLabPlateGeneratorPanel, + archlab_frnt_bench_tool.ArchLabBench, + archlab_frnt_bench_tool.ArchLabBenchGeneratorPanel, + archlab_frnt_shelve_tool.ArchLabShelve, + archlab_frnt_shelve_tool.ArchLabShelveGeneratorPanel, archlab_mesh_circle_tool.ArchLabCircle, archlab_mesh_circle_tool.ArchLabCircleGeneratorPanel, archlab_mesh_cube_tool.ArchLabCube, @@ -116,6 +124,17 @@ ) +# ---------------------------------------------------------- +# Furnitures menu +# ---------------------------------------------------------- +class ArchLabMeshFurnituresAdd(Menu): + bl_idname = "INFO_MT_archlab_mesh_furnitures_add" + bl_label = "Furnitures" + + def draw(self, context): + self.layout.operator("mesh.archlab_bench", text="Add Bench") + self.layout.operator("mesh.archlab_shelve", text="Add Shelve") + # ---------------------------------------------------------- # Decorations menu # ---------------------------------------------------------- @@ -156,9 +175,11 @@ def draw(self, context): self.layout.separator() self.layout.menu("INFO_MT_archlab_mesh_primitives_add", text="Primitives", icon="GROUP") self.layout.menu("INFO_MT_archlab_mesh_decorations_add", text="Decorations", icon="GROUP") + self.layout.menu("INFO_MT_archlab_mesh_furnitures_add", text="Furnitures", icon="GROUP") modules.extend([ ArchLabMeshCustomMenuAdd, + ArchLabMeshFurnituresAdd, ArchLabMeshDecorationsAdd, ArchLabMeshPrimitivesAdd ]) diff --git a/archlab_bldn_room_tool.py b/archlab_bldn_room_tool.py index 507ee85..a9aaeea 100644 --- a/archlab_bldn_room_tool.py +++ b/archlab_bldn_room_tool.py @@ -27,7 +27,8 @@ # ---------------------------------------------------------- import bpy from bpy.types import Operator, PropertyGroup, Object, Panel -from bpy.props import IntProperty, FloatProperty, CollectionProperty +from bpy.props import BoolProperty, IntProperty, FloatProperty, CollectionProperty +from math import sin from .archlab_utils import * # ------------------------------------------------------------------------------ @@ -46,10 +47,13 @@ def create_room(self, context): roomobject.ArchLabRoomGenerator.add() roomobject.ArchLabRoomGenerator[0].room_height = self.room_height + roomobject.ArchLabRoomGenerator[0].room_floor = self.room_floor + roomobject.ArchLabRoomGenerator[0].room_ceiling = self.room_ceiling roomobject.ArchLabRoomGenerator[0].room_wall_count = self.room_wall_count for wall in self.room_walls: wallprop = roomobject.ArchLabRoomGenerator[0].room_walls.add() wallprop.wall_width = wall.wall_width + wallprop.wall_depth = wall.wall_depth wallprop.wall_angle = wall.wall_angle # we shape the mesh. @@ -77,27 +81,11 @@ def shape_room_mesh(myroom, tmp_mesh, update=False): rp.room_walls.remove(prwc) # Create room mesh data - update_room_mesh_data(tmp_mesh, rp.room_height, rp.room_walls) + update_room_mesh_data(tmp_mesh, rp.room_height, rp.room_walls, rp.room_floor, rp.room_ceiling) myroom.data = tmp_mesh remove_doubles(myroom) - set_normals(myroom) - - if rp.room_wall_depth > 0.0: - if update is False or is_solidify(myroom) is False: - set_modifier_solidify(myroom, rp.room_wall_depth) - else: - for mod in myroom.modifiers: - if mod.type == 'SOLIDIFY': - mod.thickness = rp.room_wall_depth - mod.use_even_offset = True # The solidify have a problem with some wall angles - # Move to Top SOLIDIFY - movetotopsolidify(myroom) - - else: # clear not used SOLIDIFY - for mod in myroom.modifiers: - if mod.type == 'SOLIDIFY': - myroom.modifiers.remove(mod) + #set_normals(myroom) # deactivate others for o in bpy.data.objects: @@ -107,15 +95,15 @@ def shape_room_mesh(myroom, tmp_mesh, update=False): # ------------------------------------------------------------------------------ # Creates room mesh data. # ------------------------------------------------------------------------------ -def update_room_mesh_data(mymesh, height, walls): - myvertices = [] +def update_room_mesh_data(mymesh, height, walls, has_floor, has_ceiling): + myvertices = None myfaces = [] - if len(walls) > 0: - myvertices = [(0.0, 0.0, 0.0), (0.0, 0.0, height)] + lwalls = len(walls) lastwi = 0 - lastp = [0.0, 0.0, 0.0] - lastpnorm = [1.0, 0.0, 0.0] + lastdepth = 0 + lastp = (0.0, 0.0, 0.0) + lastpnorm = (1.0, 0.0, 0.0) for wall in walls: pnorm = rotate_point3d_rad(lastpnorm, anglez=wall.wall_angle) p1 = [ @@ -123,11 +111,71 @@ def update_room_mesh_data(mymesh, height, walls): lastp[1] + pnorm[1] * wall.wall_width, lastp[2] + pnorm[2] * wall.wall_width ] - myvertices.extend([(p1[0], p1[1], 0.0), (p1[0], p1[1], height)]) - myfaces.append((lastwi * 2 + 0, lastwi * 2 + 1, lastwi * 2 + 3, lastwi * 2 + 2)) + wdepth = wall.wall_depth /2 + wdp = (-pnorm[1] * wdepth, pnorm[0] * wdepth, 0.0) + if myvertices is None: # First wall + myvertices = [ + (-wdp[0], -wdp[1], 0.0), + (-wdp[0], -wdp[1], height), + ( wdp[0], wdp[1], 0.0), + ( wdp[0], wdp[1], height) + ] + myfaces.extend([ + [0, 1, 3, 2] + ]) + myfaces.extend([ + [lastwi * 4 + 0, lastwi * 4 + 2, lastwi * 4 + 6, lastwi * 4 + 4], # bottom + [lastwi * 4 + 0, lastwi * 4 + 4, lastwi * 4 + 5, lastwi * 4 + 1], # outer + [lastwi * 4 + 1, lastwi * 4 + 5, lastwi * 4 + 7, lastwi * 4 + 3], # top + [lastwi * 4 + 2, lastwi * 4 + 3, lastwi * 4 + 7, lastwi * 4 + 6] # inner + ]) + else: # Wall not first + sinwa = sin(wall.wall_angle) + crosswdp = (wdp[0], wdp[1], 0.0) + if not sinwa == 0: # angle = 0 + h1 = -lastpnorm * wdepth + h2 = pnorm * lastdepth + crosswdp = (h1 + h2) / sinwa + myvertices.extend([ + (lastp[0]-crosswdp[0], lastp[1]-crosswdp[1], 0.0), + (lastp[0]-crosswdp[0], lastp[1]-crosswdp[1], height), + (lastp[0]+crosswdp[0], lastp[1]+crosswdp[1], 0.0), + (lastp[0]+crosswdp[0], lastp[1]+crosswdp[1], height) + ]) + myfaces.extend([ + [lastwi * 4 + 0, lastwi * 4 + 2, lastwi * 4 + 6, lastwi * 4 + 4], # bottom + [lastwi * 4 + 0, lastwi * 4 + 4, lastwi * 4 + 5, lastwi * 4 + 1], # outer + [lastwi * 4 + 1, lastwi * 4 + 5, lastwi * 4 + 7, lastwi * 4 + 3], # top + [lastwi * 4 + 2, lastwi * 4 + 3, lastwi * 4 + 7, lastwi * 4 + 6] # inner + ]) + if lwalls == lastwi +1: #Last wall + myvertices.extend([ + (p1[0]-wdp[0], p1[1]-wdp[1], 0.0), + (p1[0]-wdp[0], p1[1]-wdp[1], height), + (p1[0]+wdp[0], p1[1]+wdp[1], 0.0), + (p1[0]+wdp[0], p1[1]+wdp[1], height) + ]) + myfaces.extend([ + [lastwi * 4 + 5, lastwi * 4 + 4, lastwi * 4 + 6, lastwi * 4 + 7] + ]) lastwi = lastwi + 1 - lastp = p1 lastpnorm = pnorm + lastp = p1 + lastdepth = wdepth + + if has_floor and lwalls > 1: + floorverts = [] + for wno in range(lwalls): + floorverts.append(wno * 4 + 2) + floorverts.append(lwalls * 4 + 2) + myfaces.append(floorverts) + + if has_ceiling and lwalls > 1: + ceilingverts = [] + for wno in range(lwalls, 0, -1): + ceilingverts.append(wno * 4 + 3) + ceilingverts.append(0 * 4 + 3) + myfaces.append(ceilingverts) mymesh.from_pydata(myvertices, [], myfaces) mymesh.update(calc_edges=True) @@ -202,10 +250,18 @@ def wall_width_property(callback=None): description='Wall width', update=callback, ) +def wall_depth_property(callback=None): + return FloatProperty( + name='Thickness', + soft_min=0.001, + default=0.025, precision=4, unit = 'LENGTH', + description='Thickness of the walls', update=callback, + ) + def wall_angle_property(callback=None): return FloatProperty( name='Angle', - soft_min=-3.14159, soft_max=3.14159, + soft_min=-2.79232, soft_max=2.79232, default=3.14159/2, precision=3, step=50, description='Angle of this wall with previous', update=callback, subtype='ANGLE', @@ -216,6 +272,7 @@ def wall_angle_property(callback=None): # ------------------------------------------------------------------ class ArchLabWallProperties(PropertyGroup): wall_width = wall_width_property(callback=update_room) + wall_depth = wall_depth_property(callback=update_room) wall_angle = wall_angle_property(callback=update_room) # ----------------------------------------------------- @@ -237,12 +294,18 @@ def room_wall_count_property(callback=None): description='Number of walls in the room', update=callback, ) -def room_wall_depth_property(callback=None): - return FloatProperty( - name='Thickness', - soft_min=0.001, - default=0.025, precision=4, unit = 'LENGTH', - description='Thickness of the walls', update=callback, +def room_floor_property(callback=None): + return BoolProperty( + name='Floor', + default=True, + description='Generates floor for the room', update=callback, + ) + +def room_ceiling_property(callback=None): + return BoolProperty( + name='Ceiling', + default=False, + description='Generates ceiling for the room', update=callback, ) def room_walls_property(callback=None): @@ -253,8 +316,10 @@ def room_walls_property(callback=None): # ------------------------------------------------------------------ class ArchLabRoomProperties(PropertyGroup): room_height = room_height_property(callback=update_room) + room_floor = room_floor_property(callback=update_room) + room_ceiling = room_ceiling_property(callback=update_room) + room_wall_count = room_wall_count_property(callback=update_room) room_wall_count = room_wall_count_property(callback=update_room) - room_wall_depth = room_wall_depth_property(callback=update_room) room_walls = room_walls_property(callback=update_room) bpy.utils.register_class(ArchLabWallProperties) @@ -307,7 +372,9 @@ def draw(self, context): row = layout.row() row.prop(room, 'room_height') row = layout.row() - row.prop(room, 'room_wall_depth') + row.prop(room, 'room_floor') + row = layout.row() + row.prop(room, 'room_ceiling') row = layout.row() row.prop(room, 'room_wall_count') for wt in range(len(room.room_walls)): @@ -316,6 +383,8 @@ def draw(self, context): row = box.row() row.prop(room.room_walls[wt], 'wall_width') row = box.row() + row.prop(room.room_walls[wt], 'wall_depth') + row = box.row() row.prop(room.room_walls[wt], 'wall_angle') # ------------------------------------------------------------------ @@ -330,8 +399,9 @@ class ArchLabRoom(Operator): # preset room_height = room_height_property() + room_floor = room_floor_property(callback=update_room) + room_ceiling = room_ceiling_property(callback=update_room) room_wall_count = room_wall_count_property() - room_wall_depth = room_wall_depth_property() room_walls = CollectionProperty(type=ArchLabWallProperties) # ----------------------------------------------------- @@ -344,7 +414,9 @@ def draw(self, context): row = layout.row() row.prop(self, 'room_height') row = layout.row() - row.prop(self, 'room_wall_depth') + row.prop(self, 'room_floor') + row = layout.row() + row.prop(self, 'room_ceiling') row = layout.row() row.prop(self, 'room_wall_count') for wt in range(len(self.room_walls)): @@ -353,6 +425,8 @@ def draw(self, context): row = box.row() row.prop(self.room_walls[wt], 'wall_width') row = box.row() + row.prop(self.room_walls[wt], 'wall_depth') + row = box.row() row.prop(self.room_walls[wt], 'wall_angle') else: row = layout.row() diff --git a/archlab_bldn_wall_tool.py b/archlab_bldn_wall_tool.py index a7a06b5..2f1f1a1 100644 --- a/archlab_bldn_wall_tool.py +++ b/archlab_bldn_wall_tool.py @@ -273,11 +273,11 @@ def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: - row = layout.row() - row.prop(self, 'wall_height') row = layout.row() row.prop(self, 'wall_width') row = layout.row() + row.prop(self, 'wall_height') + row = layout.row() row.prop(self, 'wall_depth') else: row = layout.row() diff --git a/archlab_frnt_bench_tool.py b/archlab_frnt_bench_tool.py new file mode 100644 index 0000000..dec33da --- /dev/null +++ b/archlab_frnt_bench_tool.py @@ -0,0 +1,248 @@ +# ##### BEGIN MIT LICENSE BLOCK ##### +# MIT License +# +# Copyright (c) 2018 Insma Software +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ##### END MIT LICENSE BLOCK ##### + +# ---------------------------------------------------------- +# Author: Maciej Klemarczyk (mklemarczyk) +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator, PropertyGroup, Object, Panel +from bpy.props import FloatProperty, CollectionProperty +from .archlab_utils import * +from .archlab_utils_mesh_generator import * + +# ------------------------------------------------------------------------------ +# Create main object for the bench. +# ------------------------------------------------------------------------------ +def create_bench(self, context): + # deselect all objects + for o in bpy.data.objects: + o.select = False + + # we create main object and mesh for bench + benchmesh = bpy.data.meshes.new("Bench") + benchobject = bpy.data.objects.new("Bench", benchmesh) + benchobject.location = bpy.context.scene.cursor_location + bpy.context.scene.objects.link(benchobject) + benchobject.ArchLabBenchGenerator.add() + + benchobject.ArchLabBenchGenerator[0].bench_height = self.bench_height + benchobject.ArchLabBenchGenerator[0].bench_width = self.bench_width + benchobject.ArchLabBenchGenerator[0].bench_depth = self.bench_depth + + # we shape the mesh. + shape_bench_mesh(benchobject, benchmesh) + + # we select, and activate, main object for the bench. + benchobject.select = True + bpy.context.scene.objects.active = benchobject + +# ------------------------------------------------------------------------------ +# Shapes mesh and creates modifier solidify (the modifier, only the first time). +# ------------------------------------------------------------------------------ +def shape_bench_mesh(mybench, tmp_mesh, update=False): + sp = mybench.ArchLabBenchGenerator[0] # "sp" means "bench properties". + # Create bench mesh data + update_bench_mesh_data(tmp_mesh, sp.bench_width, sp.bench_height, sp.bench_depth) + mybench.data = tmp_mesh + + remove_doubles(mybench) + set_normals(mybench) + + # deactivate others + for o in bpy.data.objects: + if o.select is True and o.name != mybench.name: + o.select = False + +# ------------------------------------------------------------------------------ +# Creates bench mesh data. +# ------------------------------------------------------------------------------ +def update_bench_mesh_data(mymesh, width, height, depth): + (myvertices, myedges, myfaces) = generate_mesh_from_library( + 'BenchN', + size=(width, depth, height) + ) + + mymesh.from_pydata(myvertices, myedges, myfaces) + mymesh.update(calc_edges=True) + +# ------------------------------------------------------------------------------ +# Update bench mesh. +# ------------------------------------------------------------------------------ +def update_bench(self, context): + # When we update, the active object is the main object of the bench. + o = bpy.context.active_object + oldmesh = o.data + oldname = o.data.name + # Now we deselect that bench object to not delete it. + o.select = False + # and we create a new mesh for the bench: + tmp_mesh = bpy.data.meshes.new("temp") + # deselect all objects + for obj in bpy.data.objects: + obj.select = False + # Finally we shape the main mesh again, + shape_bench_mesh(o, tmp_mesh, True) + o.data = tmp_mesh + # Remove data (mesh of active object), + bpy.data.meshes.remove(oldmesh) + tmp_mesh.name = oldname + # and select, and activate, the main object of the bench. + o.select = True + bpy.context.scene.objects.active = o + +# ----------------------------------------------------- +# Property definition creator +# ----------------------------------------------------- +def bench_height_property(callback=None): + return FloatProperty( + name='Height', + soft_min=0.001, + default=0.45, precision=3, unit = 'LENGTH', + description='Bench height', update=callback, + ) + +def bench_width_property(callback=None): + return FloatProperty( + name='Width', + soft_min=0.001, + default=1.20, precision=3, unit = 'LENGTH', + description='Bench width', update=callback, + ) + +def bench_depth_property(callback=None): + return FloatProperty( + name='Depth', + soft_min=0.001, + default=0.34, precision=3, unit = 'LENGTH', + description='Bench depth', update=callback, + ) + +# ------------------------------------------------------------------ +# Define property group class to create or modify a benchs. +# ------------------------------------------------------------------ +class ArchLabBenchProperties(PropertyGroup): + bench_height = bench_height_property(callback=update_bench) + bench_width = bench_width_property(callback=update_bench) + bench_depth = bench_depth_property(callback=update_bench) + +bpy.utils.register_class(ArchLabBenchProperties) +Object.ArchLabBenchGenerator = CollectionProperty(type=ArchLabBenchProperties) + +# ------------------------------------------------------------------ +# Define panel class to modify benchs. +# ------------------------------------------------------------------ +class ArchLabBenchGeneratorPanel(Panel): + bl_idname = "OBJECT_PT_bench_generator" + bl_label = "Bench" + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_category = 'ArchLab' + + # ----------------------------------------------------- + # Verify if visible + # ----------------------------------------------------- + @classmethod + def poll(cls, context): + o = context.object + act_op = context.active_operator + if o is None: + return False + if 'ArchLabBenchGenerator' not in o: + return False + if act_op is not None and act_op.bl_idname.endswith('archlab_bench'): + return False + else: + return True + + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + def draw(self, context): + o = context.object + # If the selected object didn't be created with the group 'ArchLabBenchGenerator', this panel is not created. + try: + if 'ArchLabBenchGenerator' not in o: + return + except: + return + + layout = self.layout + if bpy.context.mode == 'EDIT_MESH': + layout.label('Warning: Operator does not work in edit mode.', icon='ERROR') + else: + bench = o.ArchLabBenchGenerator[0] + row = layout.row() + row.prop(bench, 'bench_width') + row = layout.row() + row.prop(bench, 'bench_height') + row = layout.row() + row.prop(bench, 'bench_depth') + +# ------------------------------------------------------------------ +# Define operator class to create benchs +# ------------------------------------------------------------------ +class ArchLabBench(Operator): + bl_idname = "mesh.archlab_bench" + bl_label = "Add Bench" + bl_description = "Generate bench mesh" + bl_category = 'ArchLab' + bl_options = {'REGISTER', 'UNDO'} + + # preset + bench_height = bench_height_property() + bench_width = bench_width_property() + bench_depth = bench_depth_property() + + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + def draw(self, context): + layout = self.layout + space = bpy.context.space_data + if not space.local_view: + row = layout.row() + row.prop(self, 'bench_width') + row = layout.row() + row.prop(self, 'bench_height') + row = layout.row() + row.prop(self, 'bench_depth') + else: + row = layout.row() + row.label("Warning: Operator does not work in local view mode", icon='ERROR') + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if bpy.context.mode == "OBJECT": + space = bpy.context.space_data + if not space.local_view: + create_bench(self, context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "ArchLab: Option only valid in global view mode") + return {'CANCELLED'} + else: + self.report({'WARNING'}, "ArchLab: Option only valid in Object mode") + return {'CANCELLED'} diff --git a/archlab_frnt_shelve_tool.py b/archlab_frnt_shelve_tool.py new file mode 100644 index 0000000..2f10691 --- /dev/null +++ b/archlab_frnt_shelve_tool.py @@ -0,0 +1,473 @@ +# ##### BEGIN MIT LICENSE BLOCK ##### +# MIT License +# +# Copyright (c) 2018 Insma Software +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# ##### END MIT LICENSE BLOCK ##### + +# ---------------------------------------------------------- +# Author: Maciej Klemarczyk (mklemarczyk) +# ---------------------------------------------------------- +import bpy +from bpy.types import Operator, PropertyGroup, Object, Panel +from bpy.props import BoolProperty, FloatProperty, CollectionProperty +from .archlab_utils import * + +# ------------------------------------------------------------------------------ +# Create main object for the shelve. +# ------------------------------------------------------------------------------ +def create_shelve(self, context): + # deselect all objects + for o in bpy.data.objects: + o.select = False + + # we create shelve object and mesh + shelvemesh = bpy.data.meshes.new("Shelve") + shelveobject = bpy.data.objects.new("Shelve", shelvemesh) + shelveobject.location = bpy.context.scene.cursor_location + bpy.context.scene.objects.link(shelveobject) + shelveobject.ArchLabShelveGenerator.add() + + shelveobject.ArchLabShelveGenerator[0].shelve_height = self.shelve_height + shelveobject.ArchLabShelveGenerator[0].shelve_width = self.shelve_width + shelveobject.ArchLabShelveGenerator[0].shelve_depth = self.shelve_depth + shelveobject.ArchLabShelveGenerator[0].shelve_thickness = self.shelve_thickness + shelveobject.ArchLabShelveGenerator[0].shelve_armature = self.shelve_armature + + # we shape the mesh. + shape_shelve_mesh(shelveobject, shelvemesh) + + if self.shelve_armature: + # we create main object and mesh for shelve + shelvearmature = bpy.data.armatures.new("Shelve Armature") + shelvearmatureobject = bpy.data.objects.new("Shelve Armature", shelvearmature) + shelvearmatureobject.location = bpy.context.scene.cursor_location + shelvearmatureobject.parent = shelveobject + bpy.context.scene.objects.link(shelvearmatureobject) + + # we shape the armature. + shape_shelve_armature(shelveobject, shelvearmatureobject, shelvearmature) + + # we select, and activate, main object for the shelve. + shelveobject.select = True + bpy.context.scene.objects.active = shelveobject + +# ------------------------------------------------------------------------------ +# Shapes mesh and creates modifier solidify (the modifier, only the first time). +# ------------------------------------------------------------------------------ +def shape_shelve_mesh(myshelve, tmp_mesh, update=False): + sp = myshelve.ArchLabShelveGenerator[0] # "sp" means "shelve properties". + # Create shelve mesh data + update_shelve_mesh_data(tmp_mesh, sp.shelve_width, sp.shelve_height, sp.shelve_depth, sp.shelve_thickness) + myshelve.data = tmp_mesh + + remove_doubles(myshelve) + set_normals(myshelve) + + # Create Door vertex group + if not is_vertex_group(myshelve, 'Shelve Door'): + doorvg = myshelve.vertex_groups.new() + doorvg.name = 'Shelve Door' + doorvg.add(index=[8, 9, 11, 10], weight=1, type='ADD') + + if sp.shelve_thickness > 0.0: + if update is False or is_solidify(myshelve) is False: + set_modifier_solidify(myshelve, sp.shelve_thickness) + else: + for mod in myshelve.modifiers: + if mod.type == 'SOLIDIFY': + mod.thickness = sp.shelve_thickness + # Move to Top SOLIDIFY + movetotopsolidify(myshelve) + + else: # clear not used SOLIDIFY + for mod in myshelve.modifiers: + if mod.type == 'SOLIDIFY': + myshelve.modifiers.remove(mod) + + # deactivate others + for o in bpy.data.objects: + if o.select is True and o.name != myshelve.name: + o.select = False + +# ------------------------------------------------------------------------------ +# Shapes armature and creates modifier armature (the modifier, only the first time). +# ------------------------------------------------------------------------------ +def shape_shelve_armature(myshelve, myarmatureobj, myarmature, update=False): + sp = myshelve.ArchLabShelveGenerator[0] # "sp" means "shelve properties". + # Create shelve armature data + update_shelve_armature_data(myarmatureobj, myarmature, sp.shelve_width, sp.shelve_height, sp.shelve_depth, sp.shelve_thickness) + + if sp.shelve_armature: + if update is False or is_armature(myshelve) is False: + set_modifier_armature(myshelve, myarmatureobj) + else: + for mod in myshelve.modifiers: + if mod.type == 'ARMATURE': + mod.thickness = sp.shelve_thickness + # Move to Top ARMATURE + movetotoparmature(myshelve) + + else: # clear not used ARMATURE + for mod in myshelve.modifiers: + if mod.type == 'ARMATURE': + myshelve.modifiers.remove(mod) + +# ------------------------------------------------------------------------------ +# Creates shelve mesh data. +# ------------------------------------------------------------------------------ +def update_shelve_mesh_data(mymesh, width, height, depth, thickness): + basethick = thickness /2 + posx = width /2 + posy = depth /2 + posz = height +basethick + + myvertices = [ + (-posx, -posy, basethick), (posx, -posy, basethick), + (-posx, posy, basethick), (posx, posy, basethick), + (-posx, -posy, posz), (posx, -posy, posz), + (-posx, posy, posz), (posx, posy, posz)] + + thickdiff = thickness /2 + 0.001 + myvertices.extend([ + (-posx + thickdiff, -posy + thickdiff, basethick + thickdiff), + (posx - thickdiff, -posy + thickdiff, basethick + thickdiff), + (-posx + thickdiff, -posy + thickdiff, posz - thickdiff), + (posx - thickdiff, -posy + thickdiff, posz - thickdiff) + ]) + + myfaces = [ + (0, 1, 3, 2), + (0, 4, 6, 2), + (1, 5, 7, 3), + (2, 3, 7, 6), + (4, 5, 7, 6), + + (8, 9, 11, 10) + ] + + mymesh.from_pydata(myvertices, [], myfaces) + mymesh.update(calc_edges=True) + +# ------------------------------------------------------------------------------ +# Creates shelve armature data. +# ------------------------------------------------------------------------------ +def update_shelve_armature_data(myarmatureobj, myarmature, width, height, depth, thickness): + basethick = thickness /2 + posx = width /2 + posy = depth /2 + posz = height /2 +basethick + thickdiff = thickness /2 + 0.001 + + prev_o = bpy.context.scene.objects.active + bpy.context.scene.objects.active = myarmatureobj + myarmatureobj.select = True + bpy.ops.object.editmode_toggle() + + doorbone = myarmature.edit_bones.new('Shelve Door') + doorbone.head = (posx - thickdiff, -posy + thickdiff, posz ) + doorbone.tail = (-posx + thickdiff, -posy + thickdiff, posz) + + bpy.ops.object.editmode_toggle() + bpy.context.scene.objects.active = prev_o + + doorbone = myarmatureobj.pose.bones[0] + doorbone.rotation_mode = 'XYZ' + doorbone.lock_location[0] = True + doorbone.lock_location[1] = True + doorbone.lock_location[2] = True + doorbone.lock_rotation[0] = True + doorbone.lock_rotation[1] = True + doorbone.lock_scale[0] = True + doorbone.lock_scale[1] = True + doorbone.lock_scale[2] = True + +# ------------------------------------------------------------------------------ +# Update shelve mesh. +# ------------------------------------------------------------------------------ +def update_shelve(self, context): + # When we update, the active object is the main object of the shelve. + o = bpy.context.active_object + oldmesh = o.data + oldname = o.data.name + # Now we deselect that shelve object to not delete it. + o.select = False + # and we create a new mesh for the shelve: + tmp_mesh = bpy.data.meshes.new("temp") + # deselect all objects + for obj in bpy.data.objects: + obj.select = False + # Finally we shape the main mesh again, + shape_shelve_mesh(o, tmp_mesh, True) + o.data = tmp_mesh + # Remove data (mesh of active object), + bpy.data.meshes.remove(oldmesh) + tmp_mesh.name = oldname + # and select, and activate, the main object of the shelve. + o.select = True + bpy.context.scene.objects.active = o + +# ----------------------------------------------------- +# Verify if vertex group exist +# ----------------------------------------------------- +def is_vertex_group(myobject, vgname): + flag = False + try: + if myobject.vertex_groups is None: + return False + + for vg in myobject.vertex_groups: + if vg.name == vgname: + flag = True + break + return flag + except AttributeError: + return False + +# ----------------------------------------------------- +# Verify if armature exist +# ----------------------------------------------------- +def is_armature(myobject): + flag = False + try: + if myobject.modifiers is None: + return False + + for mod in myobject.modifiers: + if mod.type == 'ARMATURE': + flag = True + break + return flag + except AttributeError: + return False + +# ----------------------------------------------------- +# Move Armature to Top +# ----------------------------------------------------- +def movetotoparmature(myobject): + mymod = None + try: + if myobject.modifiers is not None: + for mod in myobject.modifiers: + if mod.type == 'ARMATURE': + mymod = mod + + if mymod is not None: + while myobject.modifiers[0] != mymod: + bpy.ops.object.modifier_move_up(modifier=mymod.name) + except AttributeError: + return + +# ----------------------------------------------------- +# Verify if solidify exist +# ----------------------------------------------------- +def is_solidify(myobject): + flag = False + try: + if myobject.modifiers is None: + return False + + for mod in myobject.modifiers: + if mod.type == 'SOLIDIFY': + flag = True + break + return flag + except AttributeError: + return False + +# ----------------------------------------------------- +# Move Solidify to Top +# ----------------------------------------------------- +def movetotopsolidify(myobject): + mymod = None + try: + if myobject.modifiers is not None: + for mod in myobject.modifiers: + if mod.type == 'SOLIDIFY': + mymod = mod + + if mymod is not None: + while myobject.modifiers[0] != mymod: + bpy.ops.object.modifier_move_up(modifier=mymod.name) + except AttributeError: + return + +# ----------------------------------------------------- +# Property definition creator +# ----------------------------------------------------- +def shelve_height_property(callback=None): + return FloatProperty( + name='Height', + soft_min=0.001, + default=0.65, precision=3, unit = 'LENGTH', + description='Shelve height', update=callback, + ) + +def shelve_width_property(callback=None): + return FloatProperty( + name='Width', + soft_min=0.001, + default=0.60, precision=3, unit = 'LENGTH', + description='Shelve width', update=callback, + ) + +def shelve_depth_property(callback=None): + return FloatProperty( + name='Depth', + soft_min=0.001, + default=0.40, precision=3, unit = 'LENGTH', + description='Shelve depth', update=callback, + ) + +def shelve_thickness_property(callback=None): + return FloatProperty( + name='Thickness', + soft_min=0.001, + default=0.015, precision=4, unit = 'LENGTH', + description='Thickness of the shelve', update=callback, + ) + +def shelve_armature_property(callback=None): + return BoolProperty( + name='Armature', + default=False, + description='Create armature for the shelve door', update=callback, + ) + +# ------------------------------------------------------------------ +# Define property group class to create or modify a shelves. +# ------------------------------------------------------------------ +class ArchLabShelveProperties(PropertyGroup): + shelve_height = shelve_height_property(callback=update_shelve) + shelve_width = shelve_width_property(callback=update_shelve) + shelve_depth = shelve_depth_property(callback=update_shelve) + shelve_thickness = shelve_thickness_property(callback=update_shelve) + shelve_armature = shelve_armature_property(callback=update_shelve) + +bpy.utils.register_class(ArchLabShelveProperties) +Object.ArchLabShelveGenerator = CollectionProperty(type=ArchLabShelveProperties) + +# ------------------------------------------------------------------ +# Define panel class to modify shelves. +# ------------------------------------------------------------------ +class ArchLabShelveGeneratorPanel(Panel): + bl_idname = "OBJECT_PT_shelve_generator" + bl_label = "Shelve" + bl_space_type = 'VIEW_3D' + bl_region_type = 'TOOLS' + bl_category = 'ArchLab' + + # ----------------------------------------------------- + # Verify if visible + # ----------------------------------------------------- + @classmethod + def poll(cls, context): + o = context.object + act_op = context.active_operator + if o is None: + return False + if 'ArchLabShelveGenerator' not in o: + return False + if act_op is not None and act_op.bl_idname.endswith('archlab_shelve'): + return False + if o.ArchLabShelveGenerator[0].shelve_armature: + return False + else: + return True + + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + def draw(self, context): + o = context.object + # If the selected object didn't be created with the group 'ArchLabShelveGenerator', this panel is not created. + try: + if 'ArchLabShelveGenerator' not in o: + return + except: + return + + layout = self.layout + if bpy.context.mode == 'EDIT_MESH': + layout.label('Warning: Operator does not work in edit mode.', icon='ERROR') + else: + shelve = o.ArchLabShelveGenerator[0] + row = layout.row() + row.prop(shelve, 'shelve_width') + row = layout.row() + row.prop(shelve, 'shelve_height') + row = layout.row() + row.prop(shelve, 'shelve_depth') + row = layout.row() + row.prop(shelve, 'shelve_thickness') + +# ------------------------------------------------------------------ +# Define operator class to create shelves +# ------------------------------------------------------------------ +class ArchLabShelve(Operator): + bl_idname = "mesh.archlab_shelve" + bl_label = "Add Shelve" + bl_description = "Generate shelve mesh" + bl_category = 'ArchLab' + bl_options = {'REGISTER', 'UNDO'} + + # preset + shelve_height = shelve_height_property() + shelve_width = shelve_width_property() + shelve_depth = shelve_depth_property() + shelve_thickness = shelve_thickness_property() + shelve_armature = shelve_armature_property() + + # ----------------------------------------------------- + # Draw (create UI interface) + # ----------------------------------------------------- + def draw(self, context): + layout = self.layout + space = bpy.context.space_data + if not space.local_view: + row = layout.row() + row.prop(self, 'shelve_width') + row = layout.row() + row.prop(self, 'shelve_height') + row = layout.row() + row.prop(self, 'shelve_depth') + row = layout.row() + row.prop(self, 'shelve_thickness') + row = layout.row() + row.prop(self, 'shelve_armature') + else: + row = layout.row() + row.label("Warning: Operator does not work in local view mode", icon='ERROR') + + # ----------------------------------------------------- + # Execute + # ----------------------------------------------------- + def execute(self, context): + if bpy.context.mode == "OBJECT": + space = bpy.context.space_data + if not space.local_view: + create_shelve(self, context) + return {'FINISHED'} + else: + self.report({'WARNING'}, "ArchLab: Option only valid in global view mode") + return {'CANCELLED'} + else: + self.report({'WARNING'}, "ArchLab: Option only valid in Object mode") + return {'CANCELLED'} diff --git a/archlab_mesh_cube_tool.py b/archlab_mesh_cube_tool.py index cd57039..d9d9192 100644 --- a/archlab_mesh_cube_tool.py +++ b/archlab_mesh_cube_tool.py @@ -218,11 +218,11 @@ def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: - row = layout.row() - row.prop(self, 'cube_height') row = layout.row() row.prop(self, 'cube_width') row = layout.row() + row.prop(self, 'cube_height') + row = layout.row() row.prop(self, 'cube_depth') else: row = layout.row() diff --git a/archlab_mesh_plane_tool.py b/archlab_mesh_plane_tool.py index a6ea77c..3ed365c 100644 --- a/archlab_mesh_plane_tool.py +++ b/archlab_mesh_plane_tool.py @@ -267,11 +267,11 @@ def draw(self, context): layout = self.layout space = bpy.context.space_data if not space.local_view: - row = layout.row() - row.prop(self, 'plane_height') row = layout.row() row.prop(self, 'plane_width') row = layout.row() + row.prop(self, 'plane_height') + row = layout.row() row.prop(self, 'plane_depth') else: row = layout.row() diff --git a/archlab_utils.py b/archlab_utils.py index fa682f9..1a51e51 100644 --- a/archlab_utils.py +++ b/archlab_utils.py @@ -28,7 +28,7 @@ import bpy from math import sin, cos, radians -from mathutils import Vector, Matrix +from mathutils import Vector, Matrix, Euler from os import path import json @@ -106,6 +106,17 @@ def set_material(ob, matname, index = 0): ob.data.materials.append(mat) return mat +# -------------------------------------------------------------------- +# Adds armature modifier +# -------------------------------------------------------------------- +def set_modifier_armature(myobject, armatureobject, modname = "Armature ArchLib"): + modid = myobject.modifiers.find(modname) + if (modid == -1): + mod = myobject.modifiers.new(name=modname, type="ARMATURE") + else: + mod = myobject.modifiers[modname] + mod.object = armatureobject + # -------------------------------------------------------------------- # Adds array modifier # -------------------------------------------------------------------- @@ -172,41 +183,10 @@ def rotate_point3d(pos, anglex=0.0, angley=0.0, anglez=0.0): # Rotates a point in 3D space with specified angle in radians # -------------------------------------------------------------------- def rotate_point3d_rad(pos, anglex=0.0, angley=0.0, anglez=0.0): - v1 = Vector(pos) - mat1 = Matrix([ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1] - ]) - if not anglez == 0.0: - cosa1 = cos(anglez) - sina1 = sin(anglez) - mat2 = Matrix([ - [cosa1, -sina1, 0], - [sina1, cosa1, 0], - [0, 0, 1] - ]) - mat1 = mat1 * mat2 - if not angley == 0.0: - cosa1 = cos(angley) - sina1 = sin(angley) - mat2 = Matrix([ - [cosa1, 0, -sina1], - [0, 1, 0], - [sina1, 0, cosa1] - ]) - mat1 = mat1 * mat2 - if not anglex == 0.0: - cosa1 = cos(anglex) - sina1 = sin(anglex) - mat2 = Matrix([ - [1, 0, 0], - [0, cosa1, -sina1], - [0, sina1, cosa1] - ]) - mat1 = mat1 * mat2 - v2 = mat1 * v1 - return v2 + genvector = Vector(pos) + myEuler = Euler((anglex, angley, anglez),'XYZ') + genvector.rotate(myEuler) + return genvector # -------------------------------------------------------------------- # Rotates a point in 2D space with specified angle @@ -320,7 +300,7 @@ def extract_vertices(): # -------------------------------------------------------------------- # Extracts edges from selected object # -------------------------------------------------------------------- -def extract_vertices(): +def extract_edges(): print("".join(["[(", "),(".join(",".join(str(v) for v in e.vertices) for e in bpy.context.object.data.edges), ")]"])) # -------------------------------------------------------------------- diff --git a/data/meshes.json b/data/meshes.json index 206f52d..2b72948 100644 --- a/data/meshes.json +++ b/data/meshes.json @@ -95,6 +95,14 @@ "Vertices": [[0.0076, 0.0000, 0.0025],[0.0076, 0.0000, 0.0051],[0.0925, 0.0000, 0.0170],[0.0668, 0.0000, 0.0025],[0.0904, 0.0000, 0.0170],[0.0925, 0.0000, 0.0153],[0.0702, 0.0000, 0.0051],[0.0698, 0.0000, 0.0000],[0.0713, 0.0000, 0.0000],[0.0668, 0.0000, 0.0051],[0.0686, 0.0000, 0.0025]], "Edges": [[10,3],[4,2],[3,0],[6,4],[2,5],[8,7],[5,8],[1,9],[7,10],[9,6]], "Faces": [] + }, + "BenchN": { + "Name": "Bench", + "ConstructMethod": "VEF", + "RealSize": [1.20, 0.34, 0.45], + "Vertices": [[-0.6000, -0.1700, 0.4500],[-0.6000, 0.1700, 0.4500],[0.6000, -0.1700, 0.4500],[0.6000, 0.1700, 0.4500],[-0.6000, -0.1700, 0.4250],[-0.6000, 0.1700, 0.4250],[0.6000, 0.1700, 0.4250],[0.6000, -0.1700, 0.4250],[-0.4000, -0.1700, 0.4500],[-0.4000, -0.1700, 0.4250],[-0.4000, 0.1700, 0.4500],[-0.4000, 0.1700, 0.4250],[0.4000, 0.1700, 0.4500],[0.4000, 0.1700, 0.4250],[0.4000, -0.1700, 0.4500],[0.4000, -0.1700, 0.4250],[-0.6000, -0.0300, 0.4500],[0.6000, -0.0300, 0.4500],[0.6000, -0.0300, 0.4250],[-0.6000, -0.0300, 0.4250],[-0.4000, -0.0300, 0.4500],[0.4000, -0.0300, 0.4500],[-0.6000, 0.0300, 0.4500],[-0.6000, 0.0300, 0.4250],[0.6000, 0.0300, 0.4500],[0.6000, 0.0300, 0.4250],[-0.4000, 0.0300, 0.0000],[-0.4000, 0.0300, 0.4500],[0.4000, 0.0300, 0.4500],[-0.6000, 0.1000, 0.4500],[-0.6000, 0.1000, 0.4250],[0.6000, 0.1000, 0.4500],[0.6000, 0.1000, 0.4250],[-0.4000, 0.1000, 0.4500],[0.4000, 0.1000, 0.4500],[0.4000, 0.1000, 0.0000],[0.6000, -0.1000, 0.4500],[0.6000, -0.1000, 0.4250],[-0.4000, -0.1000, 0.4500],[0.4000, -0.1000, 0.4500],[0.4000, -0.1000, 0.0000],[-0.6000, -0.1000, 0.4500],[-0.6000, -0.1000, 0.4250],[-0.3000, 0.1700, 0.4500],[-0.3000, 0.1700, 0.4250],[-0.3000, -0.1700, 0.4500],[-0.3000, -0.1700, 0.4250],[-0.3000, -0.0300, 0.4500],[-0.3000, -0.0300, 0.0000],[-0.3000, 0.0300, 0.0000],[-0.3000, 0.0300, 0.4500],[-0.3000, 0.1000, 0.4500],[-0.3000, -0.1000, 0.4500],[0.3000, -0.1700, 0.4500],[0.3000, -0.1700, 0.4250],[0.3000, 0.1700, 0.4500],[0.3000, 0.1700, 0.4250],[0.3000, -0.0300, 0.0000],[0.3000, -0.0300, 0.4500],[0.3000, 0.0300, 0.4500],[0.3000, 0.0300, 0.0000],[0.3000, 0.1000, 0.4500],[0.3000, -0.1000, 0.0000],[0.3000, -0.1000, 0.4500],[-0.3000, -0.1000, 0.4250],[-0.4000, -0.1000, 0.0000],[-0.3000, -0.1000, 0.0000],[-0.3000, 0.1000, 0.0000],[-0.4000, 0.1000, 0.0000],[-0.3000, 0.1000, 0.4250],[-0.4000, -0.1000, 0.4250],[-0.4000, -0.0300, 0.4250],[-0.4000, -0.0300, 0.0000],[-0.4000, 0.0300, 0.4250],[-0.4000, 0.1000, 0.4250],[0.3000, -0.1000, 0.4250],[0.4000, -0.0300, 0.0000],[0.4000, -0.1000, 0.4250],[0.4000, 0.1000, 0.4250],[0.4000, 0.0300, 0.0000],[0.3000, 0.1000, 0.4250],[0.3000, 0.1000, 0.0000],[-0.3000, -0.0300, 0.4250],[-0.3000, 0.0300, 0.4250],[0.4000, 0.0300, 0.4250],[0.4000, -0.0300, 0.4250],[0.3000, -0.0300, 0.4250],[0.3000, 0.0300, 0.4250]], + "Edges": [], + "Faces": [[30,29,1,5],[13,12,3,6],[37,36,2,7],[9,8,0,4],[38,41,0,8],[53,45,46,54],[43,55,56,44],[50,47,58,59],[5,1,10,11],[52,38,8,45],[46,45,8,9],[7,2,14,15],[36,39,14,2],[64,66,48,82],[56,55,12,13],[24,28,21,17],[50,27,20,47],[51,50,59,61],[42,19,71,70],[27,22,16,20],[25,24,17,18],[42,41,16,19],[32,31,24,25],[33,29,22,27],[32,25,84,78],[43,51,61,55],[51,33,27,50],[31,34,28,24],[81,35,79,60],[19,16,22,23],[83,49,67,69],[3,12,34,31],[43,10,33,51],[47,52,63,58],[10,1,29,33],[6,3,31,32],[23,22,29,30],[4,0,41,42],[57,76,40,62],[17,21,39,36],[47,20,38,52],[69,44,56,80],[18,37,77,85],[20,16,41,38],[18,17,36,37],[72,48,66,65],[87,80,81,60],[75,86,57,62],[83,69,80,87],[68,67,49,26],[82,83,87,86],[75,54,46,64],[11,10,43,44],[11,44,69,74],[21,58,63,39],[12,55,61,34],[34,61,59,28],[28,59,58,21],[75,64,82,86],[15,14,53,54],[39,63,53,14],[15,54,75,77],[66,64,70,65],[46,9,70,64],[68,74,69,67],[65,70,71,72],[26,73,74,68],[23,30,74,73],[40,77,75,62],[76,85,77,40],[35,78,84,79],[81,80,78,35],[56,13,78,80],[73,83,82,71],[26,49,83,73],[48,72,71,82],[87,84,85,86],[76,57,86,85],[60,79,84,87],[78,13,6,32],[77,37,7,15],[25,18,85,84],[70,9,4,42],[71,19,23,73],[74,30,5,11],[52,45,53,63]] } } }