From ea64900fefde48c72b85665239c5976e04747cb6 Mon Sep 17 00:00:00 2001 From: Chris Hodapp Date: Thu, 10 Oct 2019 01:15:34 +0200 Subject: [PATCH] Fix (stupid) bug from last commit; small refactor --- README.md | 49 +++++++++++++++++++++ Scratch.ipynb | 39 +---------------- examples.py | 115 +++++++------------------------------------------- meshgen.py | 97 ++++++++++++++++++++++++++++++++++++++++++ meshutil.py | 4 +- 5 files changed, 164 insertions(+), 140 deletions(-) create mode 100644 README.md create mode 100644 meshgen.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..f189312 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +To-do items, wanted features, bugs: + +- Examples of branching. This will probably need recursion via functions + (or an explicit stack some other way). +- I need to figure out winding order. It is consistent through seemingly + everything, except for reflection and close_boundary_simple. + (When there are two parallel boundaries joined with something like + join_boundary_simple, traversing these boundaries in their actual order + to generate triangles - like in close_boundary_simple - will produce + opposite winding order on each. Imagine a transparent clock: seen from the + front, it moves clockwise, but seen from the back, it moves + counter-clockwise.) +- Make it easier to build up meshes a bit at a time? +- Factor out recursive/iterative stuff to be a bit more concise +- Embed this in Blender? +- File that bug that I've seen in trimesh/three.js + (see trimesh_fail.ipynb) + +- Parametrize gen_twisted_boundary over boundaries and +do my nested spiral +- 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. +- 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. +- Why do I get the weird zig-zag pattern on the triangles, +despite larger numbers of them? Is it something in how I +twist the frames? + - How can I compute the *torsion* on a quad? I think it + comes down to this: torsion applied across the quad I'm + triangulating leading to neither diagonal being a + particularly good choice. Subdividing the boundary seems + to help, but other triangulation methods (e.g. turning a + quad to 4 triangles by adding the centroid) could be good + too. + - Facets/edges are just oriented the wrong way... +- I need an actual example of branching/forking. If I simply +split a boundary into sub-boundaries per the rules I already +have in my notes, then this still lets me split any way I want +to without having to worry about joining N boundaries instead +of 2, doesn't it? + +Other notes: +- Picking at random the diagonal on the quad to triangulate with + does seem to turn 'error' just to noise, and in its own way this + is preferable. diff --git a/Scratch.ipynb b/Scratch.ipynb index 719be43..f34ce28 100644 --- a/Scratch.ipynb +++ b/Scratch.ipynb @@ -1,43 +1,5 @@ { "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To do:\n", - "\n", - "- Parametrize gen_twisted_boundary over boundaries and\n", - "do my nested spiral\n", - "- Rewrite ram_horn in terms of newer abstractions\n", - "- Encode the notions of \"generator which transforms an\n", - "existing list of boundaries\", \"generator which transforms\n", - "another generator\"\n", - "- Work directly with lists of boundaries. The only thing\n", - "I ever do with them is apply transforms to all of them, or\n", - "join adjacent ones with corresponding elements.\n", - "- Why do I get the weird zig-zag pattern on the triangles,\n", - "despite larger numbers of them? Is it something in how I\n", - "twist the frames?\n", - " - How can I compute the *torsion* on a quad? I think it\n", - " comes down to this: torsion applied across the quad I'm\n", - " triangulating leading to neither diagonal being a\n", - " particularly good choice. Subdividing the boundary seems\n", - " to help, but other triangulation methods (e.g. turning a\n", - " quad to 4 triangles by adding the centroid) could be good\n", - " too.\n", - " - Facets/edges are just oriented the wrong way...\n", - "- I need an actual example of branching/forking. If I simply\n", - "split a boundary into sub-boundaries per the rules I already\n", - "have in my notes, then this still lets me split any way I want\n", - "to without having to worry about joining N boundaries instead\n", - "of 2, doesn't it?\n", - "\n", - "Other notes:\n", - "- Picking at random the diagonal on the quad to triangulate with\n", - " does seem to turn 'error' just to noise, and in its own way this\n", - " is preferable. " - ] - }, { "cell_type": "code", "execution_count": 1, @@ -50,6 +12,7 @@ "import random\n", "\n", "import meshutil\n", + "import meshgen\n", "import examples" ] }, diff --git a/examples.py b/examples.py index fb69fb9..eaabb4a 100755 --- a/examples.py +++ b/examples.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 -import meshutil import stl.mesh import numpy import trimesh +import meshutil +import meshgen + # I should be moving some of these things out into more of a # standard library than an 'examples' script @@ -59,7 +61,7 @@ def ram_horn(): def ram_horn_gen(b, xf): while True: b1 = xf.apply_to(b) - yield b1 + yield [b1] incr = meshutil.Transform() \ .scale(0.9) \ .rotate([-1,0,1], 0.3) \ @@ -76,7 +78,8 @@ def ram_horn2(): xf0_to_1 = meshutil.Transform().translate(0,0,1) b1 = xf0_to_1.apply_to(b0) meshes = [] - #meshes.append(meshutil.join_boundary_simple(b0, b1)) + meshes.append(meshutil.join_boundary_simple(b0, b1)) + meshes.append(meshutil.close_boundary_simple(b0)) for i in range(4): # Opening boundary: xf = meshutil.Transform() \ @@ -84,13 +87,9 @@ def ram_horn2(): .scale(0.5) \ .translate(0.25,0.25,1) \ .rotate([0,0,1], i*numpy.pi/2) - b = xf.apply_to(b1) - gen = ram_horn_gen(b, xf) - mesh = gen2mesh(gen, count=128) - print(mesh) + gen = ram_horn_gen(b1, xf) + mesh = meshgen.gen2mesh(gen, count=128, close_last=True) meshes.append(mesh) - # Close final boundary: - meshes.append(meshutil.close_boundary_simple(b_sub1[::-1,:])) mesh = meshutil.FaceVertexMesh.concat_many(meshes) return mesh @@ -161,103 +160,19 @@ def twist_nonlinear(dx0 = 2, dz=0.2, count=3, scale=0.99, layers=100): mesh = meshutil.FaceVertexMesh.concat_many(meshes) return mesh -# Generate a frame with 'count' boundaries in the XZ plane. -# Each one rotates by 'ang' as it moves by 'dz'. -# dx0 is center-point distance from each to the origin. -def gen_twisted_boundary(count=4, dx0=2, dz=0.2, ang=0.1): - b = numpy.array([ - [0, 0, 0], - [1, 0, 0], - [1, 0, 1], - [0, 0, 1], - ], dtype=numpy.float64) - [0.5, 0, 0.5] - b = meshutil.subdivide_boundary(b) - b = meshutil.subdivide_boundary(b) - b = meshutil.subdivide_boundary(b) - # Generate 'seed' transformations: - xfs = [meshutil.Transform().translate(dx0, 0, 0).rotate([0,1,0], numpy.pi * 2 * i / count) - for i in range(count)] - # (we'll increment the transforms in xfs as we go) - while True: - xfs_new = [] - bs = [] - for i, xf in enumerate(xfs): - # Generate a boundary from running transform: - b_i = xf.apply_to(b) - bs.append(b_i) - # Increment transform i: - xf2 = xf.rotate([0,1,0], ang) - xfs_new.append(xf2) - xfs = xfs_new - yield bs - -# This is to see how well it works to compose generators: -def gen_inc_y(gen, dy=0.1): - xf = meshutil.Transform() - for bs in gen: - bs2 = [xf.apply_to(b) for b in bs] - yield bs2 - xf = xf.translate(0, dy, 0) - -# Wrap a boundary generator around a (sorta) torus that is along XY. -# producing a mesh. -# 'frames' sets resolution, 'rad' sets radius (the boundary's origin -# sweeps through this radius - it's not 'inner' or 'outer' radius). -# -# generator should produce lists of boundaries which are oriented -# roughly in XZ. This will get 'frames' elements from it if -# possible. -def gen_torus_xy(gen, rad=2, frames=100): - ang = numpy.pi*2 / frames - xf = meshutil.Transform().translate(rad, 0, 0) - for i,bs in enumerate(gen): - if i >= frames: - break - bs2 = [xf.apply_to(b) for b in bs] - yield bs2 - xf = xf.rotate([0,0,1], ang) - -# String together boundaries from a generator. -# If count is nonzero, run only this many iterations. -def gen2mesh(gen, count=0, flip_order=False, loop=False, join_fn=meshutil.join_boundary_optim): - # Get first list of boundaries: - bs_first = next(gen) - bs_last = bs_first - # TODO: Begin and end with close_boundary_simple - meshes = [] - for i,bs_cur in enumerate(gen): - if count > 0 and i >= count: - break - for j,b in enumerate(bs_cur): - if flip_order: - m = join_fn(b, bs_last[j]) - else: - m = join_fn(bs_last[j], b) - meshes.append(m) - bs_last = bs_cur - if loop: - for b0,b1 in zip(bs_last, bs_first): - if flip_order: - m = join_fn(b1, b0) - else: - m = join_fn(b0, b1) - meshes.append(m) - mesh = meshutil.FaceVertexMesh.concat_many(meshes) - return mesh - def twist_from_gen(): - gen = gen_inc_y(gen_twisted_boundary()) - mesh = gen2mesh(gen, 100, True) + gen = meshgen.gen_inc_y(meshgen.gen_twisted_boundary()) + mesh = meshgen.gen2mesh(gen, 100, True) return mesh # frames = How many step to build this from: # turn = How many full turns to make in inner twist # count = How many inner twists to have -def twisty_torus(frames = 5000, turns = 4, count = 4, rad = 4): +def twisty_torus(frames = 200, turns = 4, count = 4, rad = 4): # In order to make this line up properly: angle = numpy.pi * 2 * turns / frames - gen = gen_torus_xy(gen_twisted_boundary(count=count, ang=angle), rad=rad, frames=frames) - return gen2mesh(gen, 0, flip_order=True, loop=True) + gen = meshgen.gen_torus_xy(meshgen.gen_twisted_boundary(count=count, ang=angle), rad=rad, frames=frames) + return meshgen.gen2mesh(gen, 0, flip_order=True, loop=True) # frames = How many step to build this from: # turn = How many full turns to make in inner twist @@ -265,8 +180,8 @@ def twisty_torus(frames = 5000, turns = 4, count = 4, rad = 4): def twisty_torus_opt(frames = 200, turns = 4, count = 4, rad = 4): # In order to make this line up properly: angle = numpy.pi * 2 * turns / frames - gen = gen_torus_xy(gen_twisted_boundary(count=count, ang=angle), rad=rad, frames=frames) - return gen2mesh(gen, 0, flip_order=True, loop=True, join_fn=meshutil.join_boundary_optim) + gen = meshgen.gen_torus_xy(meshgen.gen_twisted_boundary(count=count, ang=angle), rad=rad, frames=frames) + return meshgen.gen2mesh(gen, 0, flip_order=True, loop=True, join_fn=meshutil.join_boundary_optim) def main(): fns = { diff --git a/meshgen.py b/meshgen.py new file mode 100644 index 0000000..17acd81 --- /dev/null +++ b/meshgen.py @@ -0,0 +1,97 @@ +import meshutil +import stl.mesh +import numpy +import trimesh + +# Generate a frame with 'count' boundaries in the XZ plane. +# Each one rotates by 'ang' as it moves by 'dz'. +# dx0 is center-point distance from each to the origin. +def gen_twisted_boundary(count=4, dx0=2, dz=0.2, ang=0.1): + b = numpy.array([ + [0, 0, 0], + [1, 0, 0], + [1, 0, 1], + [0, 0, 1], + ], dtype=numpy.float64) - [0.5, 0, 0.5] + b = meshutil.subdivide_boundary(b) + b = meshutil.subdivide_boundary(b) + b = meshutil.subdivide_boundary(b) + # Generate 'seed' transformations: + xfs = [meshutil.Transform().translate(dx0, 0, 0).rotate([0,1,0], numpy.pi * 2 * i / count) + for i in range(count)] + # (we'll increment the transforms in xfs as we go) + while True: + xfs_new = [] + bs = [] + for i, xf in enumerate(xfs): + # Generate a boundary from running transform: + b_i = xf.apply_to(b) + bs.append(b_i) + # Increment transform i: + xf2 = xf.rotate([0,1,0], ang) + xfs_new.append(xf2) + xfs = xfs_new + yield bs + +# This is to see how well it works to compose generators: +def gen_inc_y(gen, dy=0.1): + xf = meshutil.Transform() + for bs in gen: + bs2 = [xf.apply_to(b) for b in bs] + yield bs2 + xf = xf.translate(0, dy, 0) + +# Wrap a boundary generator around a (sorta) torus that is along XY. +# producing a mesh. +# 'frames' sets resolution, 'rad' sets radius (the boundary's origin +# sweeps through this radius - it's not 'inner' or 'outer' radius). +# +# generator should produce lists of boundaries which are oriented +# roughly in XZ. This will get 'frames' elements from it if +# possible. +def gen_torus_xy(gen, rad=2, frames=100): + ang = numpy.pi*2 / frames + xf = meshutil.Transform().translate(rad, 0, 0) + for i,bs in enumerate(gen): + if i >= frames: + break + bs2 = [xf.apply_to(b) for b in bs] + yield bs2 + xf = xf.rotate([0,0,1], ang) + +# String together boundaries from a generator. +# If count is nonzero, run only this many iterations. +def gen2mesh(gen, count=0, flip_order=False, loop=False, + close_first = False, + close_last = False, + join_fn=meshutil.join_boundary_optim): + # Get first list of boundaries: + bs_first = next(gen) + bs_last = bs_first + # TODO: Begin and end with close_boundary_simple + meshes = [] + if close_first: + for b in bs_first: + meshes.append(meshutil.close_boundary_simple(b)) + for i,bs_cur in enumerate(gen): + if count > 0 and i >= count: + break + for j,b in enumerate(bs_cur): + if flip_order: + m = join_fn(b, bs_last[j]) + else: + m = join_fn(bs_last[j], b) + meshes.append(m) + bs_last = bs_cur + if loop: + for b0,b1 in zip(bs_last, bs_first): + if flip_order: + m = join_fn(b1, b0) + else: + m = join_fn(b0, b1) + meshes.append(m) + if close_last: + for b in bs_last: + meshes.append(meshutil.close_boundary_simple(b)) + mesh = meshutil.FaceVertexMesh.concat_many(meshes) + return mesh diff --git a/meshutil.py b/meshutil.py index 0b4e902..7364be3 100644 --- a/meshutil.py +++ b/meshutil.py @@ -203,7 +203,7 @@ def subdivide_boundary(bound): b2[2*i+1,:] = mids[i,:] return b2 -def join_boundary_simple(bound1, bound2): +def join_boundary_simple(bound1, bound2, random_diag=False): # bound1 & bound2 are both arrays of shape (N,3), representing # the points of a boundary. This joins the two boundaries by # simply connecting quads (made of 2 triangles) straight across. @@ -218,7 +218,7 @@ def join_boundary_simple(bound1, bound2): for i in range(n): v0 = i v1 = (i + 1) % n - if random.random() < 0.5: + if random_diag and random.random() < 0.5: fs[2*i] = [n + v1, n + v0, v0] fs[2*i + 1] = [v1, n + v1, v0] else: