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 ## 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 - This has a lot of functions parametrized over a lot
of functions. Need to work with this somehow. (e.g. should of functions. Need to work with this somehow. (e.g. should
it subdivide this boundary? should it merge opening/closing it subdivide this boundary? should it merge opening/closing
boundaries?) 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 - Some generators produce boundaries that can be directly merged
and produce sensible geometry. Some generators produce and produce sensible geometry. Some generators produce
boundaries that are only usable when they are further boundaries that are only usable when they are further
@ -70,6 +63,9 @@
they are all scaled in the correct way (some linearly, others they are all scaled in the correct way (some linearly, others
inversely perhaps), generated geometry that is more or less inversely perhaps), generated geometry that is more or less
identical except that it is higher-resolution? identical except that it is higher-resolution?
- Use mixins to extend 3D transformations to things (matrices,
cages, meshes, existing transformations)
## ???? ## ????
- Embed this in Blender? - 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 meshutil
import meshgen import meshgen
import cage
# I should be moving some of these things out into more of a # I should be moving some of these things out into more of a
# standard library than an 'examples' script # standard library than an 'examples' script
@ -95,6 +96,37 @@ def ram_horn2():
mesh = meshutil.FaceVertexMesh.concat_many(meshes) mesh = meshutil.FaceVertexMesh.concat_many(meshes)
return mesh 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(): def branch_test():
b0 = numpy.array([ b0 = numpy.array([
[0, 0, 0], [0, 0, 0],
@ -262,6 +294,7 @@ def main():
fns = { fns = {
ram_horn: "ramhorn.stl", ram_horn: "ramhorn.stl",
ram_horn2: "ramhorn2.stl", ram_horn2: "ramhorn2.stl",
ram_horn3: "ramhorn3.stl",
twist: "twist.stl", twist: "twist.stl",
twist_nonlinear: "twist_nonlinear.stl", twist_nonlinear: "twist_nonlinear.stl",
twist_from_gen: "twist_from_gen.stl", twist_from_gen: "twist_from_gen.stl",