Created
October 8, 2018 16:18
-
-
Save romanvolodin/3bfd349795443118a2298819349a0b3c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
from math import ( | |
acos, | |
atan2, | |
cos, | |
radians, | |
sin, | |
) | |
import bpy | |
import bmesh | |
from bmesh.types import ( | |
BMVert, | |
BMEdge, | |
BMFace, | |
) | |
from mathutils import ( | |
Vector, | |
Matrix, | |
) | |
def add_empty(name): | |
obj = bpy.data.objects.new(name, None) | |
obj.empty_draw_type = 'ARROWS' | |
obj.empty_draw_size = 0.25 | |
scene = bpy.context.scene | |
scene.objects.link(obj) | |
scene.update() | |
return obj | |
def calc_angle_signed_between_two_vectors(a, b): | |
a_norm = a.normalized() | |
b_norm = b.normalized() | |
n = get_positive_normal_from_two_vectors(a_norm, b_norm) | |
angle = atan2( | |
a_norm.cross(b_norm).dot(n), | |
a_norm.dot(b_norm) | |
) | |
return angle | |
def calc_rotation_matrix(x_local, y_local, z_local): | |
x_global = Vector((1, 0, 0)) | |
y_global = Vector((0, 1, 0)) | |
z_global = Vector((0, 0, 1)) | |
global_to_local = Matrix( | |
( | |
(x_local.dot(x_global), x_local.dot(y_global), x_local.dot(z_global), 0), | |
(y_local.dot(x_global), y_local.dot(y_global), y_local.dot(z_global), 0), | |
(z_local.dot(x_global), z_local.dot(y_global), z_local.dot(z_global), 0), | |
(0, 0, 0, 1), | |
) | |
) | |
return global_to_local.transposed() | |
def clear_screen(): | |
import os | |
os.system("clear") | |
def calc_angle_between_two_vectors(a, b): | |
angle = acos( | |
a.dot(b) / (a.length * b.length) | |
) | |
return angle | |
def get_normal_from_two_vectors(X, Z): | |
A = Matrix( | |
( | |
(X.y, Z.y), | |
(X.z, Z.z), | |
) | |
).determinant() | |
B = Matrix( | |
( | |
(X.x, Z.x), | |
(X.z, Z.z), | |
) | |
).determinant() | |
C = Matrix( | |
( | |
(X.x, Z.x), | |
(X.y, Z.y), | |
) | |
).determinant() | |
return Vector((A, B, C,)) | |
def get_positive_normal_from_two_vectors(X, Z): | |
A = Matrix( | |
( | |
(X.y, Z.y), | |
(X.z, Z.z), | |
) | |
).determinant() | |
B = -Matrix( | |
( | |
(X.x, Z.x), | |
(X.z, Z.z), | |
) | |
).determinant() | |
C = Matrix( | |
( | |
(X.x, Z.x), | |
(X.y, Z.y), | |
) | |
).determinant() | |
if A < 0: | |
return Vector((-A, -B, -C)) | |
if A == 0: | |
if B < 0: | |
return Vector((-A, -B, -C)) | |
if B == 0: | |
if C < 0: | |
return Vector((-A, -B, -C)) | |
return Vector((A, B, C)) | |
def load_data(filepath): | |
try: | |
with open(filepath, 'r', encoding='utf-8') as json_file: | |
return json.load(json_file) | |
except json.decoder.JSONDecodeError: | |
return | |
def fill_wall_corners_positions(width, height): | |
return [ | |
(0, 0, 0), | |
(width, 0, 0), | |
(width, 0, height), | |
(0, 0, height), | |
] | |
def fill_wall_corners_vertices(bm, width, height): | |
corner_positions = { | |
"wall_bottom_left": (0, 0, 0), | |
"wall_bottom_right": (width, 0, 0), | |
"wall_top_right": (width, 0, height), | |
"wall_top_left": (0, 0, height), | |
} | |
for layer_name, vertex_position in corner_positions.items(): | |
vertex = bm.verts.new(vertex_position) | |
layer = bm.verts.layers.int.new(layer_name) | |
vertex[layer] = 1 | |
return bm | |
def get_tallest_window(window_shapes): | |
return max( | |
window_shapes, | |
key=lambda shape: shape['height'] | |
) | |
def sum_window_widths(window_shapes): | |
return sum( | |
[shape['width'] for shape in window_shapes] | |
) | |
def get_arc_points(start_angle=0.0, | |
sector=180.0, | |
count=8, | |
radius=1.0, | |
center=(0.0, 0.0, 0.0), | |
height=1.0): | |
step = sector / count | |
vertices = [] | |
for i, v in enumerate(range(count + 1)): | |
rad = radians(step * i) + radians(start_angle) | |
x = radius * cos(rad) + center[0] | |
y = center[1] | |
z = radius * height * sin(rad) + center[2] | |
vertices.append((x, y, z)) | |
return vertices | |
def get_window_bottom_verts(window_left_side, | |
window_right_side, | |
window_bottom_side): | |
return [ | |
(window_left_side, 0, window_bottom_side), | |
(window_right_side, 0, window_bottom_side), | |
] | |
def get_window_top_verts(window_type, | |
window_left_side, | |
window_right_side, | |
window_top_side, | |
top_height): | |
if window_type == "RECTANGLE": | |
return [ | |
(window_right_side, 0, window_top_side), | |
(window_left_side, 0, window_top_side) | |
] | |
if window_type == "ROUND": | |
radius = (window_right_side - window_left_side) / 2 | |
center_x = window_left_side + radius | |
center_z = window_top_side - (radius * top_height) | |
center = (center_x, 0, center_z) | |
verts = get_arc_points( | |
radius=radius, | |
center=center, | |
height=top_height, | |
) | |
return verts | |
def fill_windows_vertex_positions(wall_width, | |
wall_height, | |
window_shapes, | |
margin_x, | |
margin_y, | |
space_between): | |
verts = [] | |
window_count = len(window_shapes) | |
between_count = window_count - 1 | |
common_window_widths = sum_window_widths(window_shapes) | |
block_width = common_window_widths + (space_between * between_count) | |
block_height = get_tallest_window(window_shapes)['height'] | |
margin_vertical = (wall_height - block_height) * margin_y | |
margin_horiz = (wall_width - block_width) * margin_x | |
for shape in window_shapes: | |
window_vertices = [] | |
window_type = shape['type'] | |
window_width = shape['width'] | |
window_height = shape['height'] | |
top_height = shape['top_height'] | |
window_bottom_side = margin_vertical + (block_height - window_height) | |
window_top_side = window_bottom_side + window_height | |
window_left_side = margin_horiz | |
window_right_side = window_left_side + window_width | |
window_vertices.extend( | |
get_window_bottom_verts(window_left_side, | |
window_right_side, | |
window_bottom_side) | |
) | |
window_vertices.extend( | |
get_window_top_verts(window_type, | |
window_left_side, | |
window_right_side, | |
window_top_side, | |
top_height) | |
) | |
margin_horiz += window_width + space_between | |
verts.append(window_vertices) | |
return verts | |
def fill_bm_verts(bm_object, positions): | |
for pos in positions: | |
bm_object.verts.new(pos) | |
return bm_object | |
def move_geo_to_center(bm_object, width, height): | |
for v in bm_object.verts: | |
v.co.x -= width / 2 | |
v.co.z -= height / 2 | |
return bm_object | |
def get_windows_edge_normals(windows_vertex_positions): | |
normals = [] | |
bm = bmesh.new() | |
for window in windows_vertex_positions: | |
window_bm_verts = [] | |
for v in window: | |
bm_vert = bm.verts.new(v) | |
window_bm_verts.append(bm_vert) | |
window_face = bm.faces.new(window_bm_verts) | |
extruded = bmesh.ops.extrude_face_region(bm, geom=[window_face]) | |
bmesh.ops.translate( | |
bm, | |
vec=Vector((0, 1, 0)), | |
verts=[v for v in extruded["geom"] if isinstance(v, BMVert)] | |
) | |
bm.faces.remove(window_face) | |
bmesh.ops.recalc_face_normals(bm, faces=bm.faces) | |
bm.normal_update() | |
window_normals = [] | |
for bm_vertex in window_bm_verts: | |
window_normals.append( | |
(bm_vertex.co, bm_vertex.normal) | |
) | |
normals.append(window_normals) | |
# bm = move_geo_to_center(bm, width, height) | |
bmesh_to_object(bm, 'windows', scene) | |
bm.free() | |
return normals | |
def add_object_profile_to_bmesh(bm_object, obj, scene): | |
old_verts = set(bm_object.verts) | |
old_edges = set(bm_object.edges) | |
old_faces = set(bm_object.faces) | |
bm_object.from_object(obj, scene) | |
updated_verts = set(bm_object.verts) | |
updated_edges = set(bm_object.edges) | |
updated_faces = set(bm_object.faces) | |
new_verts = updated_verts.difference(old_verts) | |
new_edges = updated_edges.difference(old_edges) | |
new_faces = updated_faces.difference(old_faces) | |
profile = { | |
'verts': list(new_verts), | |
'edges': list(new_edges), | |
'faces': list(new_faces), | |
} | |
return profile | |
def bmesh_to_object(bm_object, name, scene): | |
mesh = bpy.data.meshes.new(name) | |
bm_object.to_mesh(mesh) | |
obj = bpy.data.objects.new(name, mesh) | |
scene.objects.link(obj) | |
scene.objects.active = obj | |
obj.select = True | |
return obj | |
def add_bottom_polygon(bm, wall_bm_verts, windows_bm_verts): | |
bottom_polygon_verts = [ | |
wall_bm_verts[0], | |
wall_bm_verts[1], | |
] | |
for wind in windows_bm_verts[::-1]: | |
bottom_polygon_verts.extend( | |
(wind[1], wind[0]) | |
) | |
bm.faces.new(bottom_polygon_verts) | |
def add_top_polygon(bm, wall_bm_verts, windows_bm_verts): | |
top_polygon_verts = [ | |
wall_bm_verts[1], | |
wall_bm_verts[2], | |
wall_bm_verts[3], | |
wall_bm_verts[0], | |
] | |
for windw in windows_bm_verts: | |
top_polygon_verts.append(windw[0]) | |
for bm_v in windw[2::][::-1]: | |
top_polygon_verts.append(bm_v) | |
top_polygon_verts.append(windw[1]) | |
bm.faces.new(top_polygon_verts) | |
def fill_wall_bm_verts(wall_vertex_positions): | |
wall_bm_verts = [] | |
for v in wall_vertex_positions: | |
bv = bm.verts.new(v) | |
wall_bm_verts.append(bv) | |
return wall_bm_verts | |
def fill_windows_bm_verts(windows_vertex_positions): | |
windows_bm_verts = [] | |
for win in windows_vertex_positions: | |
window_bm_verts = [] | |
for v in win: | |
bv = bm.verts.new(v) | |
window_bm_verts.append(bv) | |
windows_bm_verts.append(window_bm_verts) | |
return windows_bm_verts | |
if __name__ == "__main__": | |
clear_screen() | |
print("~" * 40) | |
filepath = ("/project/tech/scripts/blender/bmesh/window_builder/5_win.json") | |
scene = bpy.context.scene | |
windows_data = load_data(filepath) | |
width = windows_data['width'] | |
height = windows_data['height'] | |
margin_x = windows_data['margin_x'] | |
margin_y = windows_data['margin_y'] | |
space_between = windows_data['space_between'] | |
window_shapes = windows_data['window_shapes'] | |
wall_vertex_positions = fill_wall_corners_positions(width, height) | |
windows_vertex_positions = fill_windows_vertex_positions( | |
width, | |
height, | |
window_shapes, | |
margin_x, | |
margin_y, | |
space_between, | |
) | |
bm = bmesh.new() | |
wall_bm_verts = fill_wall_bm_verts(wall_vertex_positions) | |
windows_bm_verts = fill_windows_bm_verts(windows_vertex_positions) | |
add_top_polygon(bm, wall_bm_verts, windows_bm_verts) | |
add_bottom_polygon(bm, wall_bm_verts, windows_bm_verts) | |
bm.normal_update() | |
# bm = move_geo_to_center(bm, width, height) | |
bmesh_to_object(bm, 'wall', scene) | |
bm.free() | |
########################################################################### | |
# | |
# D E C O R | |
# | |
########################################################################### | |
target_obj = bpy.data.objects["profile"] | |
windows_edge_normals = get_windows_edge_normals(windows_vertex_positions) | |
for i, window_normals in enumerate(windows_edge_normals, start=1): | |
print("before", i) | |
for num, normal in enumerate(window_normals): | |
vert_co, vert_normal = normal | |
print( | |
" co: {}\n" | |
"normal {}".format(vert_co, vert_normal) | |
) | |
for i, window_normals in enumerate(windows_edge_normals, start=1): | |
print("after", i) | |
bm = bmesh.new() | |
last_profile = None | |
for num, normal in enumerate(window_normals): | |
vert_co, vert_normal = normal | |
print( | |
" co: {}\n" | |
"normal {}".format(vert_co, vert_normal) | |
) | |
wall_normal = Vector((0, -1, 0)) | |
kind_of_Y = get_normal_from_two_vectors(wall_normal, vert_normal) | |
rotation_matrix = calc_rotation_matrix( | |
wall_normal, | |
kind_of_Y, | |
vert_normal | |
) | |
new_profile = add_object_profile_to_bmesh(bm, target_obj, scene) | |
bmesh.ops.rotate( | |
bm, | |
verts=new_profile['verts'], | |
cent=(0.0, 0.0, 0.0), | |
matrix=rotation_matrix | |
) | |
bmesh.ops.translate( | |
bm, | |
verts=new_profile['verts'], | |
vec=vert_co | |
) | |
if last_profile is not None: | |
bmesh.ops.bridge_loops( | |
bm, | |
edges=last_profile['edges'] + new_profile['edges'] | |
) | |
last_profile = new_profile | |
if num == 0: | |
first_profile = new_profile | |
bmesh.ops.bridge_loops( | |
bm, | |
edges=last_profile['edges'] + first_profile['edges'] | |
) | |
bmesh_to_object(bm, 'decor', scene) | |
bm.free() | |
scene.update() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment