Chris Hodapp d26cece387 Started Cage/CageGen/CageFork abstractions (example is working)
kludge-tacular, but working.
some parts are painful, but I have the fundamental structure
in place.
2019-11-24 05:03:35 +01:00

124 lines
4.7 KiB
Python

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