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:
parent
2bfb202f03
commit
d26cece387
10
README.md
10
README.md
@ -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
123
cage.py
Normal 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
|
||||
33
examples.py
33
examples.py
@ -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",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user