Started Cage/CageGen/CageFork abstractions (example is working)

kludge-tacular, but working.
some parts are painful, but I have the fundamental structure
in place.
This commit is contained in:
Chris Hodapp 2019-11-24 05:03:35 +01:00
parent 2bfb202f03
commit d26cece387
3 changed files with 159 additions and 7 deletions

View File

@ -49,17 +49,10 @@
## Abstractions
- Encode the notions of "generator which transforms an
existing list of boundaries", "generator which transforms
another generator"
- This has a lot of functions parametrized over a lot
of functions. Need to work with this somehow. (e.g. should
it subdivide this boundary? should it merge opening/closing
boundaries?)
- Work directly with lists of boundaries. The only thing
I ever do with them is apply transforms to all of them, or
join adjacent ones with corresponding elements.
- Some generators produce boundaries that can be directly merged
and produce sensible geometry. Some generators produce
boundaries that are only usable when they are further
@ -70,6 +63,9 @@
they are all scaled in the correct way (some linearly, others
inversely perhaps), generated geometry that is more or less
identical except that it is higher-resolution?
- Use mixins to extend 3D transformations to things (matrices,
cages, meshes, existing transformations)
## ????
- Embed this in Blender?

123
cage.py Normal file
View File

@ -0,0 +1,123 @@
import itertools
import meshutil
import stl.mesh
import numpy
class Cage(object):
"""An ordered list of polygons (or polytopes, technically)."""
def __init__(self, verts, splits):
# Element i of 'self.splits' gives the row index in 'self.verts'
# in which polygon i begins.
self.splits = splits
# NumPy array of shape (N,3)
self.verts = verts
@classmethod
def from_arrays(cls, *arrs):
"""
Pass any number of array-like objects, with each one being a
nested array with 3 elements - e.g. [[0,0,0], [1,1,1], [2,2,2]] -
providing points.
Each array-like object is treated as vertices describing a
polygon/polytope.
"""
n = 0
splits = [0]*len(arrs)
for i,arr in enumerate(arrs):
splits[i] = n
n += len(arr)
verts = numpy.zeros((n,3), dtype=numpy.float64)
# Populate it accordingly:
i0 = 0
for arr in arrs:
i1 = i0 + len(arr)
verts[i0:i1, :] = arr
i0 = i1
return cls(verts, splits)
def polys(self):
"""Return iterable of polygons as (views of) NumPy arrays."""
count = len(self.splits)
for i,n0 in enumerate(self.splits):
if i+1 < count:
n1 = self.splits[i+1]
yield self.verts[n0:n1,:]
else:
yield self.verts[n0:,:]
def is_fork(self):
return False
def transform(self, xform):
"""Apply a Transform to all vertices, returning a new Cage."""
return Cage(xform.apply_to(self.verts), self.splits)
class CageFork(object):
"""A series of generators that all split off in such a way that their
initial polygons collectively cover all of some larger polygon, with
no overlap. The individual generators must produce either Cage, or
more CageFork.
"""
def __init__(self, gens):
self.gens = gens
def is_fork(self):
return True
class CageGen(object):
"""A generator, finite or infinite, that produces objects of type Cage.
It can also produce CageFork, but only a single one as the final value
of a finite generator."""
def __init__(self, gen):
self.gen = gen
def to_mesh(self, count=None, flip_order=False, loop=False, close_first=False,
close_last=False, join_fn=meshutil.join_boundary_simple):
# Get 'opening' polygons of generator:
cage_first = next(self.gen)
if cage_first.is_fork():
# TODO: Can it be a fork?
raise Exception("First element in CageGen can't be a fork.")
cage_last = cage_first
meshes = []
# Close off the first polygon if necessary:
if close_first:
for poly in cage_first.polys():
meshes.append(meshutil.close_boundary_simple(poly))
# Generate all polygons from there and connect them:
#print(self.gen)
for i, cage_cur in enumerate(self.gen):
#print("{}: {}".format(i, cage_cur))
if count is not None and i >= count:
break
# If it's a fork, then recursively generate all the geometry
# from them, depth-first:
if cage_cur.is_fork():
# TODO: Clean up these recursive calls; parameters are ugly.
# Some of them also make no sense in certain combinations
# (e.g. loop with fork)
for gen in cage_cur.gens:
m = gen.to_mesh(count=count - i, flip_order=flip_order, loop=loop,
close_first=close_first, close_last=close_last,
join_fn=join_fn)
meshes.append(m)
# A fork can be only the final element, so disregard anything
# after one and just quit:
break
if flip_order:
for b0,b1 in zip(cage_cur.polys(), cage_last.polys()):
m = join_fn(b0, b1)
meshes.append(m)
else:
for b0,b1 in zip(cage_cur.polys(), cage_last.polys()):
m = join_fn(b1, b0)
meshes.append(m)
cage_last = cage_cur
if loop:
for b0,b1 in zip(cage_last.polys(), cage_first.polys()):
if flip_order:
m = join_fn(b1, b0)
else:
m = join_fn(b0, b1)
meshes.append(m)
# TODO: close_last?
# or should this just look for whether or not the
# generator ends here (without a CageFork)?
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
return mesh

View File

@ -8,6 +8,7 @@ import trimesh
import meshutil
import meshgen
import cage
# I should be moving some of these things out into more of a
# standard library than an 'examples' script
@ -95,6 +96,37 @@ def ram_horn2():
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
return mesh
# Rewriting the above rewrite in terms of Cage
def ram_horn3():
center = meshutil.Transform().translate(-0.5, -0.5, 0)
cage0 = cage.Cage.from_arrays([
[0, 0, 0],
[1, 0, 0],
[1, 1, 0],
[0, 1, 0],
]).transform(center)
xf0_to_1 = meshutil.Transform().translate(0, 0, 1)
cage1 = cage0.transform(xf0_to_1)
opening_boundary = lambda i: meshutil.Transform() \
.translate(0,0,-1) \
.scale(0.5) \
.translate(0.25,0.25,1) \
.rotate([0,0,1], i*numpy.pi/2)
def recur(xf):
while True:
cage2 = cage1.transform(xf)
yield cage2
incr = meshutil.Transform() \
.scale(0.9) \
.rotate([-1,0,1], 0.3) \
.translate(0,0,0.8)
xf = incr.compose(xf)
gens = [cage.CageGen(recur(opening_boundary(i))) for i in range(4)]
cg = cage.CageGen(itertools.chain([cage0, cage1, cage.CageFork(gens)]))
# TODO: if this is just a list it seems silly to require itertools
mesh = cg.to_mesh(count=128, close_first=True, close_last=True)
return mesh
def branch_test():
b0 = numpy.array([
[0, 0, 0],
@ -262,6 +294,7 @@ def main():
fns = {
ram_horn: "ramhorn.stl",
ram_horn2: "ramhorn2.stl",
ram_horn3: "ramhorn3.stl",
twist: "twist.stl",
twist_nonlinear: "twist_nonlinear.stl",
twist_from_gen: "twist_from_gen.stl",