From 7e5b4bed2da2add255ea63914f62cf6a26ecb70e Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Fri, 2 Jul 2021 18:09:36 -0400 Subject: [PATCH] Add some Blender mesh generation scraps --- blender_scraps/loader.py | 23 +++ blender_scraps/menger_cube_ish.py | 278 ++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) create mode 100644 blender_scraps/loader.py create mode 100644 blender_scraps/menger_cube_ish.py diff --git a/blender_scraps/loader.py b/blender_scraps/loader.py new file mode 100644 index 0000000..e81317f --- /dev/null +++ b/blender_scraps/loader.py @@ -0,0 +1,23 @@ +# Meant to be used with Blender's Python API +import sys +import importlib +import bpy + +ext_path = "/home/hodapp/source/automata_scratch/blender_scraps" +if ext_path not in sys.path: + sys.path.append(ext_path) + +import menger_cube_ish +menger_cube_ish = importlib.reload(menger_cube_ish) + +v,f = menger_cube_ish.cube_iterate(4) + +mesh = bpy.data.meshes.new('mesh_thing') +mesh.from_pydata(v, [], f) +mesh.update(calc_edges=True) +for edge in mesh.edges: + v = list(edge.vertices) + edge.crease = 0.9 + +obj = bpy.data.objects.new('obj_thing', mesh) +bpy.context.scene.collection.objects.link(obj) diff --git a/blender_scraps/menger_cube_ish.py b/blender_scraps/menger_cube_ish.py new file mode 100644 index 0000000..ef6a1f5 --- /dev/null +++ b/blender_scraps/menger_cube_ish.py @@ -0,0 +1,278 @@ +# 2021-06-28, Chris Hodapp +import numpy as np + +# Run this in Blender. See something like loader.py. + +# This is a royal clusterfucked jumble *but* it generates a cube +# something like the first stage of a Menger sponge. There is a lot +# of extra work being done here. + +# Known bugs: +# - Doesn't set the crease properly always? +# - I need to properly denote which faces *don't* need built +# (but I still need to run most code paths on all faces because +# of the structures they build up) +# - I handle only the main 4 vertices and no others + +# Adjust 'f' to set the size of the central hole. (f=1/3 is 'normal') +#f = 1/4 +def cube_thing(vert_accum, face_accum, base_idxs, vert_between=None, f=1/4): + xform = lambda v: v + np.array([2.0, 0.0, 0.0]) + v0,v1,v2,v3 = base_idxs + v4 = len(vert_accum) + v5 = v4+1 + v6 = v5+1 + v7 = v6+1 + vert_accum.extend([xform(vert_accum[i]) for i in base_idxs]) + faces = [ + (v0,v1,v2,v3), + (v4,v5,v1,v0), + (v5,v6,v2,v1), + (v7,v6,v5,v4), + (v3,v2,v6,v7), + (v0,v3,v7,v4), + ] + # key = vertex, value = list of adjacent faces (counter-clockwise) + vert_to_faces = { + v0: [5,1,0], + v1: [1,2,0], + v2: [0,2,4], + v3: [0,4,5], + v4: [3,1,5], + v5: [2,1,3], + v6: [4,2,3], + v7: [4,3,5], + } + + # key = (vert idx 1, vert idx 2). + # value = index of vertex factor 'f' between 1 & 2. + # (key is *not* symmetrical!) + if vert_between is None: + vert_between = {} + # faces_inner_vertex[i] will have the corresponding face-split vertices + # nearest to the vertices in faces[i] + face_inner_vertex = [None for _ in faces] + for f_idx, vert_idxs in enumerate(faces): + n = len(vert_idxs) + face_inner_vertex[f_idx] = [None]*len(vert_idxs) + # Split each edge: + print(f"{vert_idxs}:") + for i, v in enumerate(vert_idxs): + # v = 'current' vertex index. + # vp = 'previous' vertex, vn = 'next' vertex: + vp = vert_idxs[(i + n - 1) % n] + vn = vert_idxs[(i + 1) % n] + coord_v = vert_accum[v] + coord_vp = vert_accum[vp] + coord_vn = vert_accum[vn] + print(f"{v}:{coord_v} {vp}:{coord_vp} {vn}:{coord_vn}") + # Go f of way from v to vn: + if (v,vn) not in vert_between: + coord_vnf = (1-f)*coord_v + f*coord_vn + vert_between[(v, vn)] = len(vert_accum) + vert_accum.append(coord_vnf) + # and from v to vp: + if (v,vp) not in vert_between: + coord_vpf = (1-f)*coord_v + f*coord_vp + vert_between[(v, vp)] = len(vert_accum) + vert_accum.append(coord_vpf) + # Now, go 'across' those new edge splits too. + # In particular, if starting at vertex i, and vertex j is next, + # then join between: + # - i's split to its *previous* vertex + # - j's split to its *next* vertex + for i, vi in enumerate(vert_idxs): + j = (i + 1) % n + vj = vert_idxs[j] + vi_prev = vert_idxs[(i + n - 1) % n] + vj_next = vert_idxs[(j + 1) % n] + vi_split = vert_between[(vi, vi_prev)] + vj_split = vert_between[(vj, vj_next)] + coord = (1-f)*vert_accum[vi_split] + f*vert_accum[vj_split] + # vb = our new vertex between vi_split & vj_split: + vb = len(vert_accum) + vert_between[(vi_split, vj_split)] = vb + face_inner_vertex[f_idx][i] = vb + vert_accum.append(coord) + # note that this is also between two others: + vert_between[(vert_between[(vi,vj)], vert_between[(vi_prev,vj_next)])] = vb + # As we do this, make corner faces, because we know the vertices: + face_accum.append((vi, vert_between[(vi, vj)], vb, vi_split)) + # and make faces between those: + for i, vi in enumerate(vert_idxs): + vi_next = vert_idxs[(i + 1) % n] + vi_prev = vert_idxs[(i + n - 1) % n] + vi_prev2 = vert_idxs[(i + n - 2) % n] + vi_split = vert_between[(vi, vi_next)] + vi_split2 = vert_between[(vi_prev, vi_prev2)] + vb_i = vert_between[(vi_split, vi_split2)] + vb_i2 = vert_between[(vi_split2, vi_split)] + face_accum.append(( + vert_between[(vi, vi_prev)], + vb_i, + vb_i2, + vert_between[(vi_prev, vi)], + )) + + # key = corner vertex index, value = nearest 'inside' corner vertex + # (i.e. of the 8 'inside' vertices, the nearest to this corner - + # always diagonally away through the cube's interior) + vertex_to_inner = {} + + # face_idx_opp[i] gives the face that is *opposite* face i + face_idx_opp = [None for _ in faces] + # face_remap_opp[i] gives the vertices for face_idx_opp[i] - + # as indices into faces[face_idx_opp[i]], and in an order that moves + # in lockstep with faces[i], i.e. opposite winding order and always with + # 'opposite' vertices at the same index. + face_remap_opp = [None for _ in faces] + + # TODO: Feels a little wrong to hard-code? + for vi in range(8): + v = list(vert_to_faces.keys())[vi] + # f_inc = all faces incident on corner v + f_inc = vert_to_faces[v] + for i,f0_idx in enumerate(f_inc): + # Look at the *other* two incident faces: + f1_idx = f_inc[(i + 1) % 3] + f2_idx = f_inc[(i - 1) % 3] + f1 = faces[f1_idx] + f2 = faces[f2_idx] + # Find v in both these faces: + v_idx1 = f1.index(v) + v_idx2 = f2.index(v) + # We want the 'opposite' vertex: one adjacent to v, but not in face f0. + # It's adjacent to v in both f1 and f2, so choices are limited. + v1p = f1[(v_idx1 - 1) % len(f1)] + v1n = f1[(v_idx1 + 1) % len(f1)] + v2p = f2[(v_idx2 - 1) % len(f2)] + v2n = f2[(v_idx2 + 1) % len(f2)] + for v1 in (v1p, v1n): + for v2 in (v2p, v2n): + if v1 == v2: + v_opp = v1 + # Find the 'opposite' face to f0 too. (v_opp is incident on it, + # but it's the only face that isn't f1 or f2). + f_inc2 = vert_to_faces[v_opp] + for f_try in f_inc2: + if f_try == f1_idx or f_try == f2_idx: + continue + f_opp_idx = f_try + face_idx_opp[f0_idx] = f_opp_idx + face_idx_opp[f_opp_idx] = f0_idx + # Since they are *opposite* faces but they have consistent winding order, + # if we start at corresponding vertices (i.e. v and v_opp) and walk in + # *opposite* directions, we will walk to corresponding vertices. + f0 = faces[f0_idx] + f_opp = faces[f_opp_idx] + n = len(f0) + v_idx = f0.index(v) + v_opp_idx = f_opp.index(v_opp) + face_remap_opp[f0_idx] = [(v_opp_idx + v_idx - j) % n for j in range(n)] + + # I don't even know... mark out the redundant ways of reaching the + # same 8 inner vertices. + for vi in range(8): + v = list(vert_to_faces.keys())[vi] + # f_inc = all faces incident on corner v + f_inc = vert_to_faces[v] + for k,f0_idx in enumerate(f_inc): + # Again, for some face incident on v, find the opposite face and + # walk around its corresponding opposite vertices. + f0 = faces[f0_idx] + f_opp_idx = face_idx_opp[f0_idx] + remap = face_remap_opp[f0_idx] + f_opp = [faces[f_opp_idx][j] for j in remap] + # That is, f0[i] and f_opp[i] are opposite vertices (they have one + # edge between them and sit on opposite faces). + print(f"face {f0_idx}: {f0} opposite {f_opp}") + # Now, translate this to their corresponding nearest face-split vertices + # on their respective faces. + f0_split = face_inner_vertex[f0_idx] + f_opp_split = [face_inner_vertex[f_opp_idx][j] for j in remap] + print(f"face {f0_idx} inner: {f0_split} opposite {f_opp_split}") + # Make 'inner' vertices nearest f0_split & f_opp_split: + for i,(m,n) in enumerate(zip(f0_split, f_opp_split)): + equiv = [(m,n)] + # Find 'neighbor' faces to the i'th vertex: + for f1_idx in vert_to_faces[f0[i]]: + if f1_idx == f0_idx: + # Ignore the same face. + continue + f1_opp_idx = face_idx_opp[f1_idx] + print(f"f0_idx={f0_idx}, f0[i]={f0[i]}, f1_idx={f1_idx} opposite={f1_opp_idx}") + # Find f0[i] in that neighbor face f1... + f1_v_idx = faces[f1_idx].index(f0[i]) + # ...so we can find this neighbor face's face-split vertex + # that is nearest v: + v_f1_split = face_inner_vertex[f1_idx][f1_v_idx] + # and then find the vertex across from that: + remap2 = face_remap_opp[f1_idx][f1_v_idx] + v_opp_split = face_inner_vertex[f1_opp_idx][remap2] + print(f" nearest split vertex: {v_f1_split}, opp {v_opp_split}") + equiv.append((v_f1_split, v_opp_split)) + found = None + for m2,n2 in equiv: + if (m2,n2) in vert_between: + found = vert_between[(m2,n2)] + break + if found is None: + vert = (1-f)*vert_accum[m] + f*vert_accum[n] + found = len(vert_accum) + vert_accum.append(vert) + for m2,n2 in equiv: + vert_between[(m2,n2)] = found + print(f"equivalent between verts {m},{n}: {equiv}") + + # Pick a vertex: + vi = 3 + v = list(vert_to_faces.keys())[vi] + f_inc = vert_to_faces[v] + + for k,f0_idx in enumerate(f_inc): + # Again, for some face incident on v, find the opposite face and + # walk around its corresponding opposite vertices. + f0 = faces[f0_idx] + f_opp_idx = face_idx_opp[f0_idx] + remap = face_remap_opp[f0_idx] + f_opp = [faces[f_opp_idx][j] for j in remap] + # That is, f0[i] and f_opp[i] are opposite vertices (they have one + # edge between them and sit on opposite faces). + # Now, translate this to their corresponding nearest face-split vertices + # on their respective faces. + f0_split = face_inner_vertex[f0_idx] + f_opp_split = [face_inner_vertex[f_opp_idx][j] for j in remap] + for i,_ in enumerate(f0): + j = (i + 1) % len(f0) + face_accum.append(( + f0_split[i], + f0_split[j], + vert_between[(f0_split[j], f_opp_split[j])], + vert_between[(f0_split[i], f_opp_split[i])], + )) + face_accum.append(( + f_opp_split[j], + f_opp_split[i], + vert_between[(f_opp_split[i], f0_split[i])], + vert_between[(f_opp_split[j], f0_split[j])], + )) + return vert_accum, face_accum, (v4,v5,v6,v7), vert_between + +base_verts = [ + (-1, -1, -1), + (-1, -1, 1), + (-1, 1, 1), + (-1, 1, -1), +] +base_verts = [np.array(t) for t in base_verts] + +def cube_iterate(count=4): + f = [] + btw = {} + v = base_verts.copy() + idxs = [i for i,_ in enumerate(base_verts)] + for i in range(count): + print(i) + v,f,idxs,btw = cube_thing(v, f, idxs, btw) + v = [tuple(i) for i in v] + return v,f