import bpy from math import atan2, pi import mathutils from mathutils import noise import heapq from random import random maze_rows = 15 maze_cols = 15 # Delete default floor if present # Our delete all function would have worked fine too if "Cube" in bpy.data.objects: bpy.data.objects["Cube"].select_set(True) bpy.ops.object.delete() floor_size = max((maze_rows, maze_cols)) + 2 bpy.ops.mesh.primitive_grid_add(location=(floor_size/2 - 1.5, floor_size/2 - 1.5, 0), size=floor_size, x_subdivisions=100, y_subdivisions=100, calc_uvs=True) floor = bpy.context.active_object # Floor uses stone texture material = bpy.data.materials.new(name=f"FaceMaterial") material.use_nodes = True texture = bpy.data.images.load("floor.jpg") floor.data.materials.append(material) bsdf_node = material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) for i, poly in enumerate(floor.data.polygons): uv_layer = floor.data.uv_layers.active.data for j, loop_index in enumerate(poly.loop_indices): vco = floor.data.vertices[poly.vertices[j]].co x,y = (0.5 + vco.x, 0.5 + vco.y) uv_layer[loop_index].uv = (x, y) material = bpy.data.materials.new(name=f"WallMaterial") material.use_nodes = True texture = bpy.data.images.load("brick.jpg") bsdf_node = material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) edge_material = bpy.data.materials.new(name=f"EdgeMaterial") edge_material.use_nodes = True texture = bpy.data.images.load("brick_2.png") bsdf_node = edge_material.node_tree.nodes.get("Principled BSDF") texture_node = edge_material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture edge_material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) roof_material = bpy.data.materials.new(name=f"RoofMaterial") roof_material.use_nodes = True texture = bpy.data.images.load("roof.jpg") bsdf_node = roof_material.node_tree.nodes.get("Principled BSDF") texture_node = roof_material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture roof_material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) def top_panel(verts): zees = [v.z for v in verts] return zees.count(zees[0]) == 4 def x_aligned_panel(verts): exes = [v.x for v in verts] return exes.count(exes[0]) == 4 def make_wall_panel(location, x_aligned, rim=False, flat=False): bpy.ops.mesh.primitive_cube_add(size=1, calc_uvs=True) wall = bpy.context.active_object wall.scale = (0.1, 1, 1) if x_aligned else (1, 0.1, .999) if rim: wall.scale.z *= 1.1 if flat: wall.scale = (1, 1, 0.1) wall.location = location if rim: wall.data.materials.append(edge_material) else: wall.data.materials.append(material) uv_layer = wall.data.uv_layers.active.data for poly in wall.data.polygons: verts = [wall.data.vertices[poly.vertices[i]].co for i in range(4)] if top_panel(verts) and x_aligned: for i, co in enumerate(((1, 1), (0.9, 1), (0.9, 0), (1, 0))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif top_panel(verts): for i, co in enumerate(((1, 1), (0, 1), (0, 0.9), (1, 0.9))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif x_aligned_panel(verts) and not x_aligned: for i, co in enumerate(((1, 1), (1, 0), (0.9, 0), (0.9, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif not x_aligned_panel(verts) and x_aligned: for i, co in enumerate(((1, 1), (1, 0), (0.9, 0), (0.9, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) else: for i, co in enumerate(((1, 1), (1, 0), (0, 0), (0, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) return wall # Assume trusses are parallel to the x axis # Is location the lowest point, middle, or highest? # pitch_height is the rise over 1 unit def make_roof_panel(location, slope_direction, pitch_height, half = False): if slope_direction == 0: make_roof_panel((location[0] - 0.25, location[1], location[2]), -1, pitch_height, True) make_roof_panel((location[0] + 0.25, location[1], location[2]), 1, pitch_height, True) return bpy.ops.mesh.primitive_cube_add(size=1, calc_uvs=True) roof = bpy.context.active_object center_height = pitch_height / 2 # Because we're roofing a 1 by 1 square if half: center_height /= 2 roof.location = (location[0], location[1], location[2] + center_height) if half: roof.scale = 0.55, 1, 0.1 else: roof.scale = (1.1, 1, 0.1) roof.data.materials.append(roof_material) uv_layer = roof.data.uv_layers.active.data one = 0.5 if half else 1 for poly in roof.data.polygons: verts = [roof.data.vertices[poly.vertices[i]].co for i in range(4)] if top_panel(verts): for i, co in enumerate(((0, one), (0, 0), (1, 0), (1, one))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) else: for i, co in enumerate(((0, 1), (0, 0.9), (one, 0.9), (one, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) if slope_direction > 0: # Toward +x direction roof.rotation_euler = (0, atan2(pitch_height, 1), 0) elif slope_direction < 0: # Toward +x direction roof.rotation_euler = (0, -atan2(pitch_height, 1), 0) return roof make_roof_panel((0, 0, 1), -1, 0.4) make_roof_panel((2, 0, 1), 1, 0.4) make_roof_panel((1, 0, 1.4), 0, 0.4) # This one makes eves for roofs # def make_eve( ) unvisited = dict() for row in range(maze_rows): for col in range(maze_cols): unvisited[(row, col)] = 1 # Change this if you want a different maze # Modify noise in some way (add to the coordinates, different z, etc) def edge_weight(n1, n2): middle = ((n1[0] + n2[0]) / 2, (n1[1] + n2[1]) / 2) return noise.noise((20 + middle[0], middle[1], 0)) start_point = (8, 8) # places_to_go format: node 1, node 2, weight places_to_go = [] waiting_edges = dict() def neighbors_costs(p): neighbors = [ (p[0], p[1] + 1), (p[0], p[1] - 1), (p[0] + 1, p[1]), (p[0] - 1, p[1])] for n in neighbors: nx, ny = n if nx < 0 or nx == maze_cols: continue if ny < 0 or ny == maze_rows: continue if not n in unvisited: continue # Caveat here: It assumes the no two edges have the same weight ew = edge_weight(p, n) waiting_edges[ew] = (p, n) places_to_go.append(edge_weight(p, n)) cancel_list = [] del unvisited[start_point] neighbors_costs(start_point) print(places_to_go) # Fully explored condition # while len(unvisited) > 0: # A specific goal while (10, 10) in unvisited: heapq.heapify(places_to_go) key = heapq.heappop(places_to_go) next_edge = waiting_edges[key] # We should probably delete it from waiting_edges if not next_edge[1] in unvisited: continue del unvisited[next_edge[1]] neighbors_costs(next_edge[1]) cancel_list.append(next_edge) def check_cancel_list(v1, v2): if (v1, v2) in cancel_list: return True if (v2, v1) in cancel_list: return True # If we want some cycles # if random() > 0.9: # return True return False def check_unvisited_or_outside(v): return v[0] < 0 or v[1] < 0 or v[0] >= maze_cols or v[1] >= maze_rows or v in unvisited def is_outside_wall(v1, v2): if check_unvisited_or_outside(v1) and not check_unvisited_or_outside(v2): return True if check_unvisited_or_outside(v2) and not check_unvisited_or_outside(v1): return True return False def outside_building(v1, v2): return v1 in unvisited and v2 in unvisited for row in range(0, maze_rows * 2 - 1): if row % 2: # x-aligned row for segment in range(maze_cols): # This links together (segment, row/2) and (segment, 1 + row/2) v1 = (segment, int(row/2)) v2 = (segment, 1 + int(row/2)) if check_cancel_list(v1, v2): continue if outside_building(v1, v2): continue if is_outside_wall(v1, v2): make_wall_panel((row/2, segment, 0.5), True, rim=True) else: make_wall_panel((row/2, segment, 0.5), True) else: for segment in range(maze_cols - 1): # This links together (segment, row/2) and (segment, 1 + row/2) v1 = (segment, int(row/2)) v2 = (1 + segment, int(row/2)) if check_cancel_list(v1, v2): continue if outside_building(v1, v2): continue if is_outside_wall(v1, v2): make_wall_panel((row/2, segment + 0.5, 0.5), False, rim=True) else: make_wall_panel((row/2, segment + 0.5, 0.5), False) #for location in unvisited: # make_wall_panel((location[0], location[1], 1.0), True, flat=True) #material = bpy.data.materials.new(name=f"EdgeMaterial") #material.use_nodes = True #texture = bpy.data.images.load("brick_2.png") #bsdf_node = material.node_tree.nodes.get("Principled BSDF") #texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") #texture_node.image = texture #material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) # ## Should return true for any panels inside the maze, false for rim panels #def skip(row, segment): # if row == -1 or row == maze_rows * 2 - 1: # return False # if segment == -1 or (segment == maze_cols - 1 and 0 == row % 2): # return False # return True # #for row in range(-1, maze_rows * 2): # if row % 2: # x-aligned row # for segment in range(0, maze_cols): # if skip(row, segment): # continue; # make_wall_panel((row/2, segment, 0.5), True, True) # else: # for segment in range(-1, maze_cols): # if skip(row, segment): # continue; # wall = make_wall_panel((row/2, segment + 0.5, 0.5), False, True) # # This next section seals the corners # if segment in [maze_cols-1, -1] and row == (maze_rows * 2)-2: # wall.location.x += 0.0247 # wall.scale.x += 0.05 # elif segment in [maze_cols-1, -1] and row == 0: # wall.location.x -= 0.0247 # wall.scale.x += 0.05 # def set_material_view_and_zoom(zoom_level=1.0): for area in bpy.context.screen.areas: if area.type == "VIEW_3D": for space in area.spaces: if space.type == "VIEW_3D": space.shading.type = "MATERIAL" space.region_3d.view_distance *= zoom_level set_material_view_and_zoom()