Move older Python code into python_cage_meshgen
This commit is contained in:
84
python_extrude_meshgen/README.md
Normal file
84
python_extrude_meshgen/README.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# To-do items, wanted features, bugs:
|
||||
|
||||
## Cool
|
||||
- More complicated: Examples of *merging*. I'm not sure on the theory
|
||||
behind this.
|
||||
|
||||
## Annoying/boring
|
||||
- https://en.wikipedia.org/wiki/Polygon_triangulation - do this to
|
||||
fix my wave example!
|
||||
- http://www.polygontriangulation.com/2018/07/triangulation-algorithm.html
|
||||
- Clean up examples.ram_horn_branch(). The way I clean it up might
|
||||
help inform some cleaner designs.
|
||||
- I really need to standardize some of the behavior of fundamental
|
||||
operations (with regard to things like sizes they generate). This
|
||||
is behavior that, if it changes, will change a lot of things that I'm
|
||||
trying to keep consistent so that my examples still work.
|
||||
- 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.)
|
||||
- File that bug that I've seen in trimesh/three.js
|
||||
(see trimesh_fail.ipynb)
|
||||
- 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...
|
||||
- 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.
|
||||
- Integrate parallel_transport work and reuse what I can
|
||||
- /mnt/dev/graphics_misc/isosurfaces_2018_2019 - perhaps include my
|
||||
spiral isosurface stuff from here
|
||||
|
||||
## Abstractions
|
||||
|
||||
- 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?)
|
||||
- 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
|
||||
transformed (and would produce degenerate geometry). What sort
|
||||
of nomenclature captures this?
|
||||
|
||||
- How can I capture the idea of a group of parameters which, if
|
||||
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)
|
||||
- I can transform a Cage. Why not a CageGen?
|
||||
|
||||
## ????
|
||||
- Embed this in Blender?
|
||||
|
||||
## Future thoughts
|
||||
|
||||
- What if I had a function that could generate a Cage as if
|
||||
from a parametric formula and smoothly vary its orientation?
|
||||
My existing tools could easily turn this to a mesh. If I could vary
|
||||
the detail of the Cage itself (if needed), then I could also
|
||||
generate a mesh at an arbitrary level of detail simply by sampling at
|
||||
finer and finer points on the parameter space. (This might also tie
|
||||
into the Parallel Transport work.)
|
||||
- What are the limitations of using Cages?
|
||||
- Current system is very "generative". Could I do basically L-system
|
||||
if I have rules for how a much is *refined*? What about IFS?
|
||||
- Do this in Rust once I understand WTF I am doing
|
||||
|
||||
## Other thoughts
|
||||
|
||||
- Why do I never use the term "extruding" to describe what I'm doing?
|
||||
660
python_extrude_meshgen/Scratch.ipynb
Normal file
660
python_extrude_meshgen/Scratch.ipynb
Normal file
File diff suppressed because one or more lines are too long
282
python_extrude_meshgen/cage.py
Normal file
282
python_extrude_meshgen/cage.py
Normal file
@@ -0,0 +1,282 @@
|
||||
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 subdivide_deprecated(self):
|
||||
# assume self.verts has shape (4,3).
|
||||
# Midpoints of every segment:
|
||||
mids = (self.verts + numpy.roll(self.verts, -1, axis=0)) / 2
|
||||
# Centroid:
|
||||
centroid = numpy.mean(self.verts, axis=0)
|
||||
# Now, every single new boundary has: one vertex of 'bound', an
|
||||
# adjacent midpoint, a centroid, and the other adjacent midpoint.
|
||||
arrs = [
|
||||
[self.verts[0,:], mids[0,:], centroid, mids[3,:]],
|
||||
[mids[0,:], self.verts[1,:], mids[1,:], centroid],
|
||||
[centroid, mids[1,:], self.verts[2,:], mids[2,:]],
|
||||
[mids[3,:], centroid, mids[2,:], self.verts[3,:]],
|
||||
]
|
||||
# The above respects winding order and should not add any rotation.
|
||||
# I'm sure it has a pattern I can factor out, but I've not tried
|
||||
# yet.
|
||||
cages = [Cage(numpy.array(a), self.splits) for a in arrs]
|
||||
trans_verts = numpy.zeros((2*len(self.verts),3), dtype=self.verts.dtype)
|
||||
for i,(v,m) in enumerate(zip(self.verts, mids)):
|
||||
trans_verts[2*i] = v
|
||||
trans_verts[2*i+1] = m
|
||||
trans_edges = [[7, 0, 1], [1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
return cages, trans_verts, trans_edges
|
||||
def subdivide_x_deprecated(self):
|
||||
mids = (self.verts + numpy.roll(self.verts, -1, axis=0)) / 2
|
||||
centroid = numpy.mean(self.verts, axis=0)
|
||||
arrs = [
|
||||
[self.verts[0,:], mids[0,:], mids[2,:], self.verts[3,:]],
|
||||
[mids[0,:], self.verts[1,:], self.verts[2,:], mids[2,:]],
|
||||
]
|
||||
cages = [Cage(numpy.array(a), self.splits) for a in arrs]
|
||||
trans_verts = numpy.zeros((2*len(self.verts),3), dtype=self.verts.dtype)
|
||||
for i,(v,m) in enumerate(zip(self.verts, mids)):
|
||||
trans_verts[2*i] = v
|
||||
trans_verts[2*i+1] = m
|
||||
trans_edges = [[7, 0, 1], [1, 2, 3], [3, 4, 5], [5, 6, 7]]
|
||||
return cages, trans_verts, trans_edges
|
||||
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)
|
||||
def classify_overlap(self, cages):
|
||||
"""Classifies each vertex in a list of cages according to some rules.
|
||||
|
||||
(This is mostly used in order to verify that certain rules are
|
||||
followed when a mesh is undergoing forking/branching.)
|
||||
|
||||
Returns:
|
||||
v -- List of length len(cages). v[i] is a numpy array of shape (N,)
|
||||
where N is the number of vertices in cages[i] (i.e. rows of
|
||||
cages[i].verts). Element v[i][j] gives a classification of
|
||||
X=l[i].verts[j] that will take values below:
|
||||
|
||||
0 -- None of the below apply to X.
|
||||
1 -- X lies on an edge in this Cage (i.e. self).
|
||||
2 -- X equals another (different) vertex somewhere in 'cages', and
|
||||
case 1 does not apply.
|
||||
3 -- X equals a vertex in self.verts.
|
||||
"""
|
||||
v = [numpy.zeros((cage.verts.shape[0],), dtype=numpy.uint8)
|
||||
for cage in cages]
|
||||
# for cage i of all the cages...
|
||||
for i, cage in enumerate(cages):
|
||||
# for vertex j within cage i...
|
||||
for j, vert in enumerate(cage.verts):
|
||||
# Check against every vert in our own (self.verts):
|
||||
for vert2 in self.verts:
|
||||
if numpy.allclose(vert, vert2):
|
||||
v[i][j] = 3
|
||||
break
|
||||
if v[i][j] > 0:
|
||||
continue
|
||||
# Check against every edge of our own polygons:
|
||||
for poly in self.polys():
|
||||
for k,_ in enumerate(poly):
|
||||
# Below is because 'poly' is cyclic (last vertex
|
||||
# has an edge to the first):
|
||||
k2 = (k + 1) % len(poly)
|
||||
# Find distance from 'vert' to each vertex of the edge:
|
||||
d1 = numpy.linalg.norm(poly[k,:] - vert)
|
||||
d2 = numpy.linalg.norm(poly[k2,:] - vert)
|
||||
# Find the edge's length:
|
||||
d = numpy.linalg.norm(poly[k2,:] - poly[k,:])
|
||||
# These are equal if and only if the vertex lies along
|
||||
# that edge:
|
||||
if numpy.isclose(d, d1 + d2):
|
||||
v[i][j] = 1
|
||||
break
|
||||
if v[i][j] > 0:
|
||||
break
|
||||
if v[i][j] > 0:
|
||||
continue
|
||||
# Check against every *other* vert in cages:
|
||||
for i2, cage2 in enumerate(cages):
|
||||
for j2, vert2 in enumerate(cage.verts):
|
||||
if i == i2 and j == j2:
|
||||
# same cage, same vertex - ignore:
|
||||
continue
|
||||
if numpy.allclose(vert, vert2):
|
||||
v[i][j] = 2
|
||||
break
|
||||
if v[i][j] > 0:
|
||||
break
|
||||
return v
|
||||
|
||||
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.
|
||||
|
||||
Transition vertices and edges are here to help adapt this CageFork
|
||||
to an earlier Cage, which may require subdividing its edges.
|
||||
|
||||
Vertices (in 'verts') should proceed in the same direction around the
|
||||
cage, and start at the same vertex. Edges (in 'edges') should have N
|
||||
elements, one for each of N vertices in the 'starting' Cage (the one
|
||||
that we must adapt *from*), and edges[i] should itself be a list in
|
||||
which each element is a (row) index of 'verts'. edges[i] specifies,
|
||||
in correct order, which vertices in 'verts' should connect to vertex
|
||||
i of the 'starting' Cage. In its entirety, it also gives the
|
||||
'transition' Cage (hence, order matters in the inner lists).
|
||||
|
||||
As an example, if a starting cage is [0, 0, 0], [1, 0, 0], [1, 1, 0],
|
||||
[0, 1, 0] and the CageFork simply subdivides into 4 equal-size cages,
|
||||
then 'verts' might be [[0, 0, 0], [0.5, 0, 0], [1, 0, 0], [1, 0.5, 0],
|
||||
[1, 1, 0], [0.5, 1, 0], [0, 1, 0], [0, 0.5, 0]] - note that it begins
|
||||
at the same vertex, subdivides each edge, and (including the cyclic
|
||||
nature) ends at the same vertex. 'edges' then would be:
|
||||
[[7, 0, 1], [1, 2, 3], [3, 4, 5], [5, 6, 7]]. Note that every vertex
|
||||
in the starting cage connects to 3 vertices in 'verts' and overlaps
|
||||
with the previous and next vertex.
|
||||
|
||||
(Sorry. This is explained badly and I know it.)
|
||||
|
||||
Parameters:
|
||||
gens -- explained above
|
||||
verts -- Numpy array with 'transition' vertices, shape (M,3)
|
||||
edges -- List of 'transition' edges
|
||||
"""
|
||||
def __init__(self, gens, verts, edges):
|
||||
self.gens = gens
|
||||
self.verts = verts
|
||||
self.edges = edges
|
||||
def is_fork(self):
|
||||
return True
|
||||
def transition_from(self, cage):
|
||||
"""Generate a transitional mesh to adapt the given starting Cage"""
|
||||
#print("DEBUG: Transition from {} to {}".format(cage.verts, self.verts))
|
||||
vs = numpy.concatenate([cage.verts, self.verts])
|
||||
# Indices 0...offset-1 are from cage, rest are from self.verts
|
||||
offset = cage.verts.shape[0]
|
||||
# We have one face for total sub-elements in self.edges:
|
||||
count = sum([len(e) for e in self.edges])
|
||||
fs = numpy.zeros((count, 3), dtype=int)
|
||||
face_idx = 0
|
||||
for j, adjs in enumerate(self.edges):
|
||||
for k, adj in enumerate(adjs[:-1]):
|
||||
adj_next = adjs[(k + 1) % len(adjs)]
|
||||
# Proceed in direction of cage.verts:
|
||||
fs[face_idx] = [j, offset + adj_next, offset + adj]
|
||||
face_idx += 1
|
||||
fs[face_idx] = [j, (j + 1) % len(cage.verts), offset + adjs[-1]]
|
||||
face_idx += 1
|
||||
return meshutil.FaceVertexMesh(vs, fs)
|
||||
|
||||
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)
|
||||
#print("DEBUG: to_mesh(count={}), cage_first={}".format(count, cage_first.verts))
|
||||
# TODO: Avoid 'next' here so that we can use a list, not solely a
|
||||
# generator/iterator.
|
||||
if cage_first.is_fork():
|
||||
# TODO: Can it be a fork? Does that make sense?
|
||||
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("DEBUG: i={}, cage_cur={}, cage_last={}".format(i, cage_cur, cage_last.verts))
|
||||
#print("{}: {}".format(i, cage_cur))
|
||||
if count is not None and i >= count:
|
||||
# We stop recursing here, so close things off if needed:
|
||||
if close_last:
|
||||
for poly in cage_last.polys():
|
||||
meshes.append(meshutil.close_boundary_simple(poly, reverse=True))
|
||||
# TODO: Fix the winding order hack here.
|
||||
break
|
||||
# If it's a fork, then recursively generate all the geometry
|
||||
# from them, depth-first:
|
||||
if cage_cur.is_fork():
|
||||
# First, transition the cage properly:
|
||||
mesh_trans = cage_cur.transition_from(cage_last)
|
||||
meshes.append(mesh_trans)
|
||||
# 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=False, 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)
|
||||
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
|
||||
return mesh
|
||||
401
python_extrude_meshgen/examples.py
Executable file
401
python_extrude_meshgen/examples.py
Executable file
@@ -0,0 +1,401 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import itertools
|
||||
|
||||
import math
|
||||
import numpy
|
||||
import stl.mesh
|
||||
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
|
||||
|
||||
# The first "working" example I had of the recursive 3D geometry
|
||||
# that actually kept the manifold throughout:
|
||||
def ram_horn():
|
||||
b0 = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
], dtype=numpy.float64) - [0.5, 0.5, 0]
|
||||
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.close_boundary_simple(b0))
|
||||
for i in range(4):
|
||||
# Opening boundary:
|
||||
b = b1
|
||||
xf = meshutil.Transform() \
|
||||
.translate(0,0,-1) \
|
||||
.scale(0.5) \
|
||||
.translate(0.25,0.25,1) \
|
||||
.rotate([0,0,1], i*numpy.pi/2)
|
||||
for layer in range(128):
|
||||
b_sub0 = xf.apply_to(b)
|
||||
incr = meshutil.Transform() \
|
||||
.scale(0.9) \
|
||||
.rotate([-1,0,1], 0.3) \
|
||||
.translate(0,0,0.8)
|
||||
b_sub1 = incr.compose(xf).apply_to(b)
|
||||
m = meshutil.join_boundary_simple(b_sub0, b_sub1)
|
||||
meshes.append(m)
|
||||
xf = incr.compose(xf)
|
||||
# Close final boundary:
|
||||
meshes.append(meshutil.close_boundary_simple(b_sub1[::-1,:]))
|
||||
# ::-1 is to reverse the boundary's order to fix winding order.
|
||||
# Not sure of the "right" way to fix winding order here.
|
||||
# The boundary vertices go in an identical order... it's just
|
||||
# that clockwise/counter-clockwise flip.
|
||||
|
||||
# I keep confusing the 'incremental' transform with the
|
||||
# transform to get b_open in the first place
|
||||
|
||||
# I don't need to subdivide *geometry*.
|
||||
# I need to subdivide *space* and then put geometry in it.
|
||||
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
|
||||
return mesh
|
||||
|
||||
# Rewriting the above in terms of generators & iterated transforms
|
||||
def ram_horn_gen(b, xf):
|
||||
while True:
|
||||
b1 = xf.apply_to(b)
|
||||
yield [b1]
|
||||
incr = meshutil.Transform() \
|
||||
.scale(0.9) \
|
||||
.rotate([-1,0,1], 0.3) \
|
||||
.translate(0,0,0.8)
|
||||
xf = incr.compose(xf)
|
||||
|
||||
def ram_horn2():
|
||||
b0 = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
], dtype=numpy.float64) - [0.5, 0.5, 0]
|
||||
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.close_boundary_simple(b0))
|
||||
for i in range(4):
|
||||
# Opening boundary:
|
||||
xf = meshutil.Transform() \
|
||||
.translate(0,0,-1) \
|
||||
.scale(0.5) \
|
||||
.translate(0.25,0.25,1) \
|
||||
.rotate([0,0,1], i*numpy.pi/2)
|
||||
gen = ram_horn_gen(b1, xf)
|
||||
mesh = meshgen.gen2mesh(gen, count=128, close_last=True)
|
||||
meshes.append(mesh)
|
||||
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)
|
||||
incr = meshutil.Transform() \
|
||||
.scale(0.9) \
|
||||
.rotate([-1,0,1], 0.3) \
|
||||
.translate(0,0,0.8)
|
||||
def recur(xf):
|
||||
while True:
|
||||
cage2 = cage1.transform(xf)
|
||||
yield cage2
|
||||
xf = incr.compose(xf)
|
||||
# TODO: I think there is a way to express 'recur' in the form of
|
||||
# itertools.accumulate, and it might be clearer. This function is
|
||||
# just iteratively re-composing 'incr' into a seed transformation,
|
||||
# and applying this transformation (at every stage) to the same
|
||||
# mesh.
|
||||
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 ram_horn_branch():
|
||||
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)
|
||||
incr = meshutil.Transform() \
|
||||
.scale(0.9) \
|
||||
.rotate([-1,0,1], 0.3) \
|
||||
.translate(0,0,0.8)
|
||||
def recur(xf, cage1, count):
|
||||
for i in range(count):
|
||||
if i > 0:
|
||||
c = cage1.transform(xf)
|
||||
yield c
|
||||
xf0 = xf
|
||||
xf = incr.compose(xf)
|
||||
def xf_sub(i):
|
||||
# (dx,dy) should be normalized, but I reused from something else
|
||||
dx = 1 if i == 0 or i == 1 else -1
|
||||
dy = 1 if i == 0 or i == 3 else -1
|
||||
return meshutil.Transform().translate(0, 0, 0.5).rotate([-dy,dx,0], -numpy.pi/6)
|
||||
subdiv, trans_vs, trans_es = cage1.subdivide_deprecated()
|
||||
gens = [cage.CageGen(itertools.chain(
|
||||
[cage_sub.transform(xf)],
|
||||
recur(xf_sub(i).compose(xf), cage_sub, 8)))
|
||||
for i,cage_sub in
|
||||
enumerate(subdiv)]
|
||||
yield cage.CageFork(gens, xf.apply_to(trans_vs), trans_es)
|
||||
cg = cage.CageGen(itertools.chain(
|
||||
[cage0],
|
||||
recur(meshutil.Transform(), cage0, 8),
|
||||
))
|
||||
# TODO: if this is just a list it seems silly to require itertools
|
||||
mesh = cg.to_mesh(count=32, close_first=True, close_last=True)
|
||||
return mesh
|
||||
|
||||
def dream_pendant():
|
||||
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)
|
||||
incr = meshutil.Transform() \
|
||||
.scale(0.95, 1.0, 0.95) \
|
||||
.rotate([0,1,0], 0.2) \
|
||||
.translate(0,0,0.9)
|
||||
def recur(xf, cage1, count):
|
||||
for i in range(count):
|
||||
if i > 0:
|
||||
c = cage1.transform(xf)
|
||||
yield c
|
||||
xf0 = xf
|
||||
xf = incr.compose(xf)
|
||||
def xf_rot(a):
|
||||
return meshutil.Transform().rotate([0,1,0], a)
|
||||
subdiv, trans_vs, trans_es = cage1.subdivide_x_deprecated()
|
||||
gens = [cage.CageGen(itertools.chain(
|
||||
[cage_sub.transform(xf)],
|
||||
recur(xf_rot(ang).compose(xf), cage_sub, 5)))
|
||||
for cage_sub,ang in
|
||||
zip(subdiv, [-0.2, 0.7])]
|
||||
yield cage.CageFork(gens, xf.apply_to(trans_vs), trans_es)
|
||||
cg = cage.CageGen(itertools.chain(
|
||||
[cage0],
|
||||
recur(meshutil.Transform(), cage0, 3),
|
||||
))
|
||||
# TODO: if this is just a list it seems silly to require itertools
|
||||
mesh1 = cg.to_mesh(count=32, close_first=False, close_last=True)
|
||||
mesh2 = mesh1.transform(meshutil.Transform().rotate([0,1,0], math.pi))
|
||||
return meshutil.FaceVertexMesh.concat_many([mesh1, mesh2])
|
||||
|
||||
def branch_test():
|
||||
b0 = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
], dtype=numpy.float64) - [0.5, 0.5, 0]
|
||||
parts = [meshutil.Transform().scale(0.5).translate(dx, dy, 1)
|
||||
for dx in (-0.25,+0.25) for dy in (-0.25,+0.25)]
|
||||
xf = meshutil.Transform().translate(0,0,0.1).scale(0.95)
|
||||
def gen():
|
||||
b = b0
|
||||
for i in range(10):
|
||||
b = xf.apply_to(b)
|
||||
yield [b]
|
||||
return meshgen.gen2mesh(gen(), close_first=True, close_last=True)
|
||||
|
||||
# Interlocking twists.
|
||||
# ang/dz control resolution. dx0 controls radius. count controls
|
||||
# how many twists. scale controls speed they shrink at.
|
||||
def twist(ang=0.1, dz=0.2, dx0=2, count=4, scale=0.98):
|
||||
b = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
], dtype=numpy.float64) - [0.5, 0.5, 0]
|
||||
meshes = []
|
||||
for i in range(count):
|
||||
xf = meshutil.Transform() \
|
||||
.translate(dx0, 0, 0) \
|
||||
.rotate([0,0,1], numpy.pi * 2 * i / count)
|
||||
b0 = xf.apply_to(b)
|
||||
meshes.append(meshutil.close_boundary_simple(b0))
|
||||
for layer in range(256):
|
||||
b_sub0 = xf.apply_to(b)
|
||||
incr = meshutil.Transform() \
|
||||
.rotate([0,0,1], ang) \
|
||||
.translate(0,0,dz) \
|
||||
.scale(scale)
|
||||
b_sub1 = xf.compose(incr).apply_to(b)
|
||||
m = meshutil.join_boundary_simple(b_sub0, b_sub1)
|
||||
meshes.append(m)
|
||||
xf = xf.compose(incr)
|
||||
# Close final boundary:
|
||||
meshes.append(meshutil.close_boundary_simple(b_sub1[::-1,:]))
|
||||
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
|
||||
return mesh
|
||||
|
||||
def twist_nonlinear(dx0 = 2, dz=0.2, count=3, scale=0.99, layers=100):
|
||||
# This can be a function rather than a constant:
|
||||
angs = numpy.power(numpy.linspace(0.4, 2.0, layers), 2.0) / 10.0
|
||||
ang_fn = lambda i: angs[i]
|
||||
# (could it also be a function of space rather than which layer?)
|
||||
|
||||
b = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 1, 0],
|
||||
[0, 1, 0],
|
||||
], dtype=numpy.float64) - [0.5, 0.5, 0]
|
||||
meshes = []
|
||||
for i in range(count):
|
||||
xf = meshutil.Transform() \
|
||||
.translate(dx0, 0, 0) \
|
||||
.rotate([0,0,1], numpy.pi * 2 * i / count)
|
||||
b0 = xf.apply_to(b)
|
||||
meshes.append(meshutil.close_boundary_simple(b0))
|
||||
for layer in range(layers):
|
||||
b_sub0 = xf.apply_to(b)
|
||||
ang = ang_fn(layer)
|
||||
incr = meshutil.Transform() \
|
||||
.rotate([0,0,1], ang) \
|
||||
.translate(0,0,dz) \
|
||||
.scale(scale)
|
||||
b_sub1 = xf.compose(incr).apply_to(b)
|
||||
m = meshutil.join_boundary_simple(b_sub0, b_sub1)
|
||||
meshes.append(m)
|
||||
xf = xf.compose(incr)
|
||||
# Close final boundary:
|
||||
meshes.append(meshutil.close_boundary_simple(b_sub1[::-1,:]))
|
||||
mesh = meshutil.FaceVertexMesh.concat_many(meshes)
|
||||
return mesh
|
||||
|
||||
def twist_from_gen():
|
||||
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)
|
||||
bs = [b]
|
||||
# since it needs a generator:
|
||||
gen_inner = itertools.repeat(bs)
|
||||
gen = meshgen.gen_inc_y(meshgen.gen_twisted_boundary(gen_inner))
|
||||
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 = 200, turns = 4, count = 4, rad = 4):
|
||||
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)
|
||||
bs = [b]
|
||||
# since it needs a generator:
|
||||
gen_inner = itertools.repeat(bs)
|
||||
# In order to make this line up properly:
|
||||
angle = numpy.pi * 2 * turns / frames
|
||||
gen = meshgen.gen_torus_xy(meshgen.gen_twisted_boundary(gen=gen_inner, count=count, ang=angle), rad=rad, frames=frames)
|
||||
return meshgen.gen2mesh(gen, 0, flip_order=True, loop=True)
|
||||
|
||||
def spiral_nested_2():
|
||||
# Slow.
|
||||
b = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 0, 1],
|
||||
[0, 0, 1],
|
||||
], dtype=numpy.float64) - [0.5, 0, 0.5]
|
||||
b *= 0.3
|
||||
b = meshutil.subdivide_boundary(b)
|
||||
b = meshutil.subdivide_boundary(b)
|
||||
bs = [b]
|
||||
# since it needs a generator:
|
||||
gen1 = itertools.repeat(bs)
|
||||
gen2 = meshgen.gen_twisted_boundary(gen1, ang=-0.2, dx0=0.5)
|
||||
gen3 = meshgen.gen_twisted_boundary(gen2, ang=0.05, dx0=1)
|
||||
gen = meshgen.gen_inc_y(gen3, dy=0.1)
|
||||
return meshgen.gen2mesh(
|
||||
gen, count=250, flip_order=True, close_first=True, close_last=True)
|
||||
|
||||
def spiral_nested_3():
|
||||
# Slower.
|
||||
b = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 0, 1],
|
||||
[0, 0, 1],
|
||||
], dtype=numpy.float64) - [0.5, 0, 0.5]
|
||||
b *= 0.3
|
||||
b = meshutil.subdivide_boundary(b)
|
||||
b = meshutil.subdivide_boundary(b)
|
||||
bs = [b]
|
||||
# since it needs a generator:
|
||||
gen1 = itertools.repeat(bs)
|
||||
gen2 = meshgen.gen_twisted_boundary(gen1, ang=-0.2, dx0=0.5)
|
||||
gen3 = meshgen.gen_twisted_boundary(gen2, ang=0.07, dx0=1)
|
||||
gen4 = meshgen.gen_twisted_boundary(gen3, ang=-0.03, dx0=3)
|
||||
gen = meshgen.gen_inc_y(gen4, dy=0.1)
|
||||
return meshgen.gen2mesh(
|
||||
gen, count=500, flip_order=True, close_first=True, close_last=True)
|
||||
|
||||
def main():
|
||||
fns = {
|
||||
ram_horn: "ramhorn.stl",
|
||||
ram_horn2: "ramhorn2.stl",
|
||||
# TODO: Fix
|
||||
#ram_horn3: "ramhorn3.stl",
|
||||
ram_horn_branch: "ramhorn_branch.stl",
|
||||
dream_pendant: "dream_pendant.stl",
|
||||
twist: "twist.stl",
|
||||
twist_nonlinear: "twist_nonlinear.stl",
|
||||
twist_from_gen: "twist_from_gen.stl",
|
||||
twisty_torus: "twisty_torus.stl",
|
||||
spiral_nested_2: "spiral_nested_2.stl",
|
||||
spiral_nested_3: "spiral_nested_3.stl",
|
||||
}
|
||||
for f in fns:
|
||||
fname = fns[f]
|
||||
print("Generate {}...".format(fname))
|
||||
mesh = f()
|
||||
nv = mesh.v.shape[0]
|
||||
nf = mesh.f.shape[0]
|
||||
print("Saving {} verts & {} faces...".format(nv, nf))
|
||||
mesh.to_stl_mesh().save(fname)
|
||||
print("Done.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
99
python_extrude_meshgen/meshgen.py
Normal file
99
python_extrude_meshgen/meshgen.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import itertools
|
||||
|
||||
import meshutil
|
||||
import stl.mesh
|
||||
import numpy
|
||||
import trimesh
|
||||
|
||||
# Generate a frame with 'count' boundaries in the XZ plane.
|
||||
# Each one rotates by 'ang' at each step.
|
||||
# dx0 is center-point distance from each to the origin.
|
||||
#
|
||||
# This doesn't generate usable geometry on its own.
|
||||
def gen_twisted_boundary(gen=None, count=4, dx0=2, ang=0.1):
|
||||
if gen is None:
|
||||
b = numpy.array([
|
||||
[0, 0, 0],
|
||||
[1, 0, 0],
|
||||
[1, 0, 1],
|
||||
[0, 0, 1],
|
||||
], dtype=numpy.float64) - [0.5, 0, 0.5]
|
||||
gen = itertools.repeat([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)
|
||||
for bs in gen:
|
||||
xfs_new = []
|
||||
bs2 = []
|
||||
for i, xf in enumerate(xfs):
|
||||
# Generate a boundary from running transform:
|
||||
bs2 += [xf.apply_to(b) for b in bs]
|
||||
# Increment transform i:
|
||||
xf2 = xf.rotate([0,1,0], ang)
|
||||
xfs_new.append(xf2)
|
||||
xfs = xfs_new
|
||||
yield bs2
|
||||
|
||||
# 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_simple):
|
||||
# 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
|
||||
257
python_extrude_meshgen/meshutil.py
Normal file
257
python_extrude_meshgen/meshutil.py
Normal file
@@ -0,0 +1,257 @@
|
||||
import stl.mesh
|
||||
import numpy
|
||||
import quaternion
|
||||
import random
|
||||
|
||||
import quat
|
||||
|
||||
# (left/right, bottom/top, back/front)
|
||||
# I'm using X to the right, Y up, Z inward
|
||||
# (not that it really matters except for variable names)
|
||||
lbf = numpy.array([0,0,0])
|
||||
rbf = numpy.array([1,0,0])
|
||||
ltf = numpy.array([0,1,0])
|
||||
rtf = numpy.array([1,1,0])
|
||||
lbb = numpy.array([0,0,1])
|
||||
rbb = numpy.array([1,0,1])
|
||||
ltb = numpy.array([0,1,1])
|
||||
rtb = numpy.array([1,1,1])
|
||||
|
||||
class FaceVertexMesh(object):
|
||||
def __init__(self, v, f):
|
||||
# v & f should both be of shape (N,3)
|
||||
self.v = v
|
||||
self.f = f
|
||||
def concat(self, other_mesh):
|
||||
v2 = numpy.concatenate([self.v, other_mesh.v])
|
||||
# Note index shift!
|
||||
f2 = numpy.concatenate([self.f, other_mesh.f + self.v.shape[0]])
|
||||
m2 = FaceVertexMesh(v2, f2)
|
||||
return m2
|
||||
def transform(self, xform):
|
||||
# Just transform vertices. Indices don't change.
|
||||
return FaceVertexMesh(xform.apply_to(self.v), self.f)
|
||||
def to_stl_mesh(self):
|
||||
data = numpy.zeros(self.f.shape[0], dtype=stl.mesh.Mesh.dtype)
|
||||
v = data["vectors"]
|
||||
for i, (iv0, iv1, iv2) in enumerate(self.f):
|
||||
v[i] = [self.v[iv0], self.v[iv1], self.v[iv2]]
|
||||
return stl.mesh.Mesh(data)
|
||||
@classmethod
|
||||
def Empty(cls):
|
||||
return FaceVertexMesh(numpy.zeros((0,3)), numpy.zeros((0,3), dtype=int))
|
||||
@classmethod
|
||||
def concat_many(cls, meshes):
|
||||
nv = 0
|
||||
nf = 0
|
||||
for m in meshes:
|
||||
nv += m.v.shape[0]
|
||||
nf += m.f.shape[0]
|
||||
v = numpy.zeros((nv,3), dtype=numpy.float64)
|
||||
f = numpy.zeros((nf,3), dtype=int)
|
||||
vi = 0
|
||||
fi = 0
|
||||
for m in meshes:
|
||||
vj = vi + m.v.shape[0]
|
||||
fj = fi + m.f.shape[0]
|
||||
v[vi:vj,:] = m.v
|
||||
f[fi:fj,:] = m.f + vi
|
||||
vi = vj
|
||||
fi = fj
|
||||
return FaceVertexMesh(v, f)
|
||||
|
||||
class Transform(object):
|
||||
def __init__(self, mtx=None):
|
||||
if mtx is None:
|
||||
self.mtx = numpy.identity(4)
|
||||
else:
|
||||
self.mtx = mtx
|
||||
def _compose(self, mtx2):
|
||||
# Note pre-multiply. Earlier transforms are done first.
|
||||
return Transform(mtx2 @ self.mtx)
|
||||
def compose(self, xform):
|
||||
return self._compose(xform.mtx)
|
||||
def scale(self, *a, **kw):
|
||||
return self._compose(mtx_scale(*a, **kw))
|
||||
def translate(self, *a, **kw):
|
||||
return self._compose(mtx_translate(*a, **kw))
|
||||
def rotate(self, *a, **kw):
|
||||
return self._compose(mtx_rotate(*a, **kw))
|
||||
def reflect(self, *a, **kw):
|
||||
return self._compose(mtx_reflect(*a, **kw))
|
||||
def identity(self, *a, **kw):
|
||||
return self._compose(mtx_identity(*a, **kw))
|
||||
def apply_to(self, vs):
|
||||
# Homogeneous coords, so append a column of ones. vh is then shape (N,4):
|
||||
vh = numpy.hstack([vs, numpy.ones((vs.shape[0], 1), dtype=vs.dtype)])
|
||||
# As we have row vectors, we're doing basically (A*x)^T=(x^T)*(A^T)
|
||||
# hence transposing the matrix, while vectors are already transposed.
|
||||
return (vh @ self.mtx.T)[:,0:3]
|
||||
|
||||
def mtx_scale(sx, sy=None, sz=None):
|
||||
if sy is None:
|
||||
sy = sx
|
||||
if sz is None:
|
||||
sz = sx
|
||||
return numpy.array([
|
||||
[sx, 0, 0, 0],
|
||||
[0, sy, 0, 0],
|
||||
[0, 0, sz, 0],
|
||||
[0, 0, 0, 1],
|
||||
])
|
||||
|
||||
def mtx_translate(x, y, z):
|
||||
return numpy.array([
|
||||
[1, 0, 0, x],
|
||||
[0, 1, 0, y],
|
||||
[0, 0, 1, z],
|
||||
[0, 0, 0, 1],
|
||||
])
|
||||
|
||||
def mtx_rotate(axis, angle):
|
||||
q = quat.rotation_quaternion(axis, angle)
|
||||
return quat.quat2mat(q)
|
||||
|
||||
def mtx_reflect(axis):
|
||||
# axis must be norm-1
|
||||
axis = numpy.array(axis)
|
||||
axis = axis / numpy.linalg.norm(axis)
|
||||
a,b,c = axis[0], axis[1], axis[2]
|
||||
return numpy.array([
|
||||
[1-2*a*a, -2*a*b, -2*a*c, 0],
|
||||
[-2*a*b, 1-2*b*b, -2*b*c, 0],
|
||||
[-2*a*c, -2*b*c, 1-2*c*c, 0],
|
||||
[0, 0, 0, 1],
|
||||
])
|
||||
|
||||
def mtx_identity():
|
||||
return numpy.eye(4)
|
||||
|
||||
def cube(open_xz=False):
|
||||
verts = numpy.array([
|
||||
lbf, rbf, ltf, rtf,
|
||||
lbb, rbb, ltb, rtb,
|
||||
], dtype=numpy.float64)
|
||||
if open_xz:
|
||||
faces = numpy.zeros((8,3), dtype=int)
|
||||
else:
|
||||
faces = numpy.zeros((12,3), dtype=int)
|
||||
faces[0,:] = [0, 3, 1]
|
||||
faces[1,:] = [0, 2, 3]
|
||||
faces[2,:] = [1, 7, 5]
|
||||
faces[3,:] = [1, 3, 7]
|
||||
faces[4,:] = [5, 6, 4]
|
||||
faces[5,:] = [5, 7, 6]
|
||||
faces[6,:] = [4, 2, 0]
|
||||
faces[7,:] = [4, 6, 2]
|
||||
if not open_xz:
|
||||
faces[8,:] = [2, 7, 3]
|
||||
faces[9,:] = [2, 6, 7]
|
||||
faces[10,:] = [0, 1, 5]
|
||||
faces[11,:] = [0, 5, 4]
|
||||
# winding order?
|
||||
return FaceVertexMesh(verts, faces)
|
||||
|
||||
def cube_distort(angle, open_xz=False):
|
||||
q = quat.rotation_quaternion(numpy.array([-1,0,1]), angle)
|
||||
ltf2 = quat.conjugate_by(ltf, q)[0,:]
|
||||
rtf2 = quat.conjugate_by(rtf, q)[0,:]
|
||||
ltb2 = quat.conjugate_by(ltb, q)[0,:]
|
||||
rtb2 = quat.conjugate_by(rtb, q)[0,:]
|
||||
# TODO: Just make these functions work right with single vectors
|
||||
verts = numpy.array([
|
||||
lbf, rbf, ltf2, rtf2,
|
||||
lbb, rbb, ltb2, rtb2,
|
||||
], dtype=numpy.float64)
|
||||
if open_xz:
|
||||
faces = numpy.zeros((8,3), dtype=int)
|
||||
else:
|
||||
faces = numpy.zeros((12,3), dtype=int)
|
||||
faces[0,:] = [0, 3, 1]
|
||||
faces[1,:] = [0, 2, 3]
|
||||
faces[2,:] = [1, 7, 5]
|
||||
faces[3,:] = [1, 3, 7]
|
||||
faces[4,:] = [5, 6, 4]
|
||||
faces[5,:] = [5, 7, 6]
|
||||
faces[6,:] = [4, 2, 0]
|
||||
faces[7,:] = [4, 6, 2]
|
||||
if not open_xz:
|
||||
faces[8,:] = [2, 7, 3]
|
||||
faces[9,:] = [2, 6, 7]
|
||||
faces[10,:] = [0, 1, 5]
|
||||
faces[11,:] = [0, 5, 4]
|
||||
# winding order?
|
||||
return FaceVertexMesh(verts, faces)
|
||||
|
||||
def split_boundary(bound):
|
||||
# assume bound1 has shape (4,3).
|
||||
# Midpoints of every segment:
|
||||
mids = (bound + numpy.roll(bound, 1, axis=0)) / 2
|
||||
mids_adj = numpy.roll(mids, -1, axis=0)
|
||||
# Centroid:
|
||||
centroid = numpy.mean(bound, axis=0)
|
||||
# Now, every single new boundary has: one vertex of 'bound', an
|
||||
# adjacent midpoint, a centroid, and the other adjacent midpoint.
|
||||
bounds = [
|
||||
numpy.array([bound[i,:], mids[i,:], centroid, mids_adj[i,:]])
|
||||
for i in range(4)
|
||||
]
|
||||
return bounds
|
||||
|
||||
def subdivide_boundary(bound):
|
||||
# assume bound1 has shape (4,3).
|
||||
# Midpoints of every segment:
|
||||
mids = (bound + numpy.roll(bound, -1, axis=0)) / 2
|
||||
b2 = numpy.zeros((bound.shape[0]*2, bound.shape[1]))
|
||||
for i,row in enumerate(bound):
|
||||
b2[2*i,:] = bound[i,:]
|
||||
b2[2*i+1,:] = mids[i,:]
|
||||
return b2
|
||||
|
||||
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.
|
||||
#
|
||||
# Winding will proceed in the direction of the first boundary.
|
||||
#
|
||||
# Returns FaceVertexMesh.
|
||||
n = bound1.shape[0]
|
||||
vs = numpy.concatenate([bound1, bound2])
|
||||
# Indices 0...N-1 are from bound1, N...2*N-1 are from bound2
|
||||
fs = numpy.zeros((2*n, 3), dtype=int)
|
||||
for i in range(n):
|
||||
v0 = i
|
||||
v1 = (i + 1) % n
|
||||
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:
|
||||
fs[2*i] = [n + v1, n + v0, v1]
|
||||
fs[2*i + 1] = [v1, n + v0, v0]
|
||||
return FaceVertexMesh(vs, fs)
|
||||
|
||||
def join_boundary_optim(bound1, bound2):
|
||||
# bound1 and bound2 must stay in order, but we can rotate
|
||||
# the starting point to whatever we want. Use distance as
|
||||
# a metric:
|
||||
errs = [numpy.linalg.norm(bound1 - numpy.roll(bound2, i, axis=0))
|
||||
for i,_ in enumerate(bound1)]
|
||||
# What shift gives the lowest distance?
|
||||
i = numpy.argmin(errs)
|
||||
return join_boundary_simple(bound1, numpy.roll(bound2, i, axis=0))
|
||||
|
||||
def close_boundary_simple(bound, reverse=False):
|
||||
# This will fail for any non-convex boundary!
|
||||
centroid = numpy.mean(bound, axis=0)
|
||||
vs = numpy.concatenate([bound, centroid[numpy.newaxis,:]])
|
||||
n = bound.shape[0]
|
||||
# note that n is new the index of the centroid
|
||||
fs = numpy.zeros((n+1, 3), dtype=int)
|
||||
if reverse:
|
||||
for i in range(n):
|
||||
fs[i] = [(i+1) % n, n, i]
|
||||
else:
|
||||
for i in range(n):
|
||||
fs[i] = [i, n, (i+1) % n]
|
||||
return FaceVertexMesh(vs, fs)
|
||||
33
python_extrude_meshgen/quat.py
Normal file
33
python_extrude_meshgen/quat.py
Normal file
@@ -0,0 +1,33 @@
|
||||
import numpy
|
||||
import quaternion
|
||||
|
||||
def conjugate_by(vec, quat):
|
||||
"""Turn 'vec' to a quaternion, conjugate it by 'quat', and return it."""
|
||||
q2 = quat * vec2quat(vec) * quat.conjugate()
|
||||
return quaternion.as_float_array(q2)[:,1:]
|
||||
|
||||
def rotation_quaternion(axis, angle):
|
||||
"""Returns a quaternion for rotating by some axis and angle.
|
||||
|
||||
Inputs:
|
||||
axis -- numpy array of shape (3,), with axis to rotate around
|
||||
angle -- angle in radians by which to rotate
|
||||
"""
|
||||
qc = numpy.cos(angle / 2)
|
||||
qs = numpy.sin(angle / 2)
|
||||
qv = qs * numpy.array(axis)
|
||||
return numpy.quaternion(qc, qv[0], qv[1], qv[2])
|
||||
|
||||
def vec2quat(vs):
|
||||
qs = numpy.zeros(vs.shape[0], dtype=numpy.quaternion)
|
||||
quaternion.as_float_array(qs)[:,1:4] = vs
|
||||
return qs
|
||||
|
||||
def quat2mat(q):
|
||||
s = 1
|
||||
return numpy.array([
|
||||
[1-2*s*(q.y**2+q.z**2), 2*s*(q.x*q.y-q.z*q.w), 2*s*(q.x*q.z+q.y*q.w), 0],
|
||||
[2*s*(q.x*q.y+q.z*q.w), 1-2*s*(q.x**2+q.z**2), 2*s*(q.y*q.z-q.x*q.w), 0],
|
||||
[2*s*(q.x*q.z-q.y*q.w), 2*s*(q.y*q.z+q.x*q.w), 1-2*s*(q.x**2+q.y**2), 0],
|
||||
[0, 0, 0, 1],
|
||||
])
|
||||
263
python_extrude_meshgen/trimesh_fail.ipynb
Normal file
263
python_extrude_meshgen/trimesh_fail.ipynb
Normal file
@@ -0,0 +1,263 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"face_normals didn't match triangles, ignoring!\n",
|
||||
"/home/hodapp/.local/lib/python3.6/site-packages/IPython/core/display.py:694: UserWarning: Consider using IPython.display.IFrame instead\n",
|
||||
" warnings.warn(\"Consider using IPython.display.IFrame instead\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/html": [
|
||||
"<iframe srcdoc=\"<!DOCTYPE html>\n",
|
||||
"<html lang="en">\n",
|
||||
" <head>\n",
|
||||
" <title>trimesh: threejs viewer</title>\n",
|
||||
" <meta charset="utf-8">\n",
|
||||
" <meta name="viewport" content="width=device-width, \n",
|
||||
"\t\t user-scalable=no, \n",
|
||||
"\t\t minimum-scale=1.0, \n",
|
||||
"\t\t maximum-scale=1.0">\n",
|
||||
" <style>\n",
|
||||
" body {\n",
|
||||
" margin: 0px;\n",
|
||||
" overflow: hidden;\n",
|
||||
" }\n",
|
||||
" </style>\n",
|
||||
" </head>\n",
|
||||
" <body>\n",
|
||||
" <div id="container"></div>\n",
|
||||
" <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/106/three.min.js"></script>\n",
|
||||
" <script>THREE.TrackballControls=function(object,domElement){var _this=this;var STATE={NONE:-1,ROTATE:0,ZOOM:1,PAN:2,TOUCH_ROTATE:3,TOUCH_ZOOM_PAN:4};this.object=object;this.domElement=(domElement!==undefined)?domElement:document;this.enabled=true;this.screen={left:0,top:0,width:0,height:0};this.rotateSpeed=1.0;this.zoomSpeed=1.2;this.panSpeed=0.3;this.noRotate=false;this.noZoom=false;this.noPan=false;this.staticMoving=false;this.dynamicDampingFactor=0.2;this.minDistance=0;this.maxDistance=Infinity;this.keys=[65 ,83 ,68 ];this.target=new THREE.Vector3();var EPS=0.000001;var lastPosition=new THREE.Vector3();var _state=STATE.NONE,_prevState=STATE.NONE,_eye=new THREE.Vector3(),_movePrev=new THREE.Vector2(),_moveCurr=new THREE.Vector2(),_lastAxis=new THREE.Vector3(),_lastAngle=0,_zoomStart=new THREE.Vector2(),_zoomEnd=new THREE.Vector2(),_touchZoomDistanceStart=0,_touchZoomDistanceEnd=0,_panStart=new THREE.Vector2(),_panEnd=new THREE.Vector2();this.target0=this.target.clone();this.position0=this.object.position.clone();this.up0=this.object.up.clone();var changeEvent={type:'change'};var startEvent={type:'start'};var endEvent={type:'end'};this.handleResize=function(){if(this.domElement===document){this.screen.left=0;this.screen.top=0;this.screen.width=window.innerWidth;this.screen.height=window.innerHeight;}else{var box=this.domElement.getBoundingClientRect();var d=this.domElement.ownerDocument.documentElement;this.screen.left=box.left+window.pageXOffset-d.clientLeft;this.screen.top=box.top+window.pageYOffset-d.clientTop;this.screen.width=box.width;this.screen.height=box.height;}};var getMouseOnScreen=(function(){var vector=new THREE.Vector2();return function getMouseOnScreen(pageX,pageY){vector.set((pageX-_this.screen.left)/_this.screen.width,(pageY-_this.screen.top)/_this.screen.height);return vector;};}());var getMouseOnCircle=(function(){var vector=new THREE.Vector2();return function getMouseOnCircle(pageX,pageY){vector.set(((pageX-_this.screen.width*0.5-_this.screen.left)/(_this.screen.width*0.5)),((_this.screen.height+2*(_this.screen.top-pageY))/_this.screen.width));return vector;};}());this.rotateCamera=(function(){var axis=new THREE.Vector3(),quaternion=new THREE.Quaternion(),eyeDirection=new THREE.Vector3(),objectUpDirection=new THREE.Vector3(),objectSidewaysDirection=new THREE.Vector3(),moveDirection=new THREE.Vector3(),angle;return function rotateCamera(){moveDirection.set(_moveCurr.x-_movePrev.x,_moveCurr.y-_movePrev.y,0);angle=moveDirection.length();if(angle){_eye.copy(_this.object.position).sub(_this.target);eyeDirection.copy(_eye).normalize();objectUpDirection.copy(_this.object.up).normalize();objectSidewaysDirection.crossVectors(objectUpDirection,eyeDirection).normalize();objectUpDirection.setLength(_moveCurr.y-_movePrev.y);objectSidewaysDirection.setLength(_moveCurr.x-_movePrev.x);moveDirection.copy(objectUpDirection.add(objectSidewaysDirection));axis.crossVectors(moveDirection,_eye).normalize();angle*=_this.rotateSpeed;quaternion.setFromAxisAngle(axis,angle);_eye.applyQuaternion(quaternion);_this.object.up.applyQuaternion(quaternion);_lastAxis.copy(axis);_lastAngle=angle;}else if(!_this.staticMoving&&_lastAngle){_lastAngle*=Math.sqrt(1.0-_this.dynamicDampingFactor);_eye.copy(_this.object.position).sub(_this.target);quaternion.setFromAxisAngle(_lastAxis,_lastAngle);_eye.applyQuaternion(quaternion);_this.object.up.applyQuaternion(quaternion);}\n",
|
||||
"_movePrev.copy(_moveCurr);};}());this.zoomCamera=function(){var factor;if(_state===STATE.TOUCH_ZOOM_PAN){factor=_touchZoomDistanceStart/_touchZoomDistanceEnd;_touchZoomDistanceStart=_touchZoomDistanceEnd;_eye.multiplyScalar(factor);}else{factor=1.0+(_zoomEnd.y-_zoomStart.y)*_this.zoomSpeed;if(factor!==1.0&&factor>0.0){_eye.multiplyScalar(factor);}\n",
|
||||
"if(_this.staticMoving){_zoomStart.copy(_zoomEnd);}else{_zoomStart.y+=(_zoomEnd.y-_zoomStart.y)*this.dynamicDampingFactor;}}};this.panCamera=(function(){var mouseChange=new THREE.Vector2(),objectUp=new THREE.Vector3(),pan=new THREE.Vector3();return function panCamera(){mouseChange.copy(_panEnd).sub(_panStart);if(mouseChange.lengthSq()){mouseChange.multiplyScalar(_eye.length()*_this.panSpeed);pan.copy(_eye).cross(_this.object.up).setLength(mouseChange.x);pan.add(objectUp.copy(_this.object.up).setLength(mouseChange.y));_this.object.position.add(pan);_this.target.add(pan);if(_this.staticMoving){_panStart.copy(_panEnd);}else{_panStart.add(mouseChange.subVectors(_panEnd,_panStart).multiplyScalar(_this.dynamicDampingFactor));}}};}());this.checkDistances=function(){if(!_this.noZoom||!_this.noPan){if(_eye.lengthSq()>_this.maxDistance*_this.maxDistance){_this.object.position.addVectors(_this.target,_eye.setLength(_this.maxDistance));_zoomStart.copy(_zoomEnd);}\n",
|
||||
"if(_eye.lengthSq()<_this.minDistance*_this.minDistance){_this.object.position.addVectors(_this.target,_eye.setLength(_this.minDistance));_zoomStart.copy(_zoomEnd);}}};this.update=function(){_eye.subVectors(_this.object.position,_this.target);if(!_this.noRotate){_this.rotateCamera();}\n",
|
||||
"if(!_this.noZoom){_this.zoomCamera();}\n",
|
||||
"if(!_this.noPan){_this.panCamera();}\n",
|
||||
"_this.object.position.addVectors(_this.target,_eye);_this.checkDistances();_this.object.lookAt(_this.target);if(lastPosition.distanceToSquared(_this.object.position)>EPS){_this.dispatchEvent(changeEvent);lastPosition.copy(_this.object.position);}};this.reset=function(){_state=STATE.NONE;_prevState=STATE.NONE;_this.target.copy(_this.target0);_this.object.position.copy(_this.position0);_this.object.up.copy(_this.up0);_eye.subVectors(_this.object.position,_this.target);_this.object.lookAt(_this.target);_this.dispatchEvent(changeEvent);lastPosition.copy(_this.object.position);};function keydown(event){if(_this.enabled===false)return;window.removeEventListener('keydown',keydown);_prevState=_state;if(_state!==STATE.NONE){return;}else if(event.keyCode===_this.keys[STATE.ROTATE]&&!_this.noRotate){_state=STATE.ROTATE;}else if(event.keyCode===_this.keys[STATE.ZOOM]&&!_this.noZoom){_state=STATE.ZOOM;}else if(event.keyCode===_this.keys[STATE.PAN]&&!_this.noPan){_state=STATE.PAN;}}\n",
|
||||
"function keyup(event){if(_this.enabled===false)return;_state=_prevState;window.addEventListener('keydown',keydown,false);}\n",
|
||||
"function mousedown(event){if(_this.enabled===false)return;event.preventDefault();event.stopPropagation();if(_state===STATE.NONE){_state=event.button;}\n",
|
||||
"if(_state===STATE.ROTATE&&!_this.noRotate){_moveCurr.copy(getMouseOnCircle(event.pageX,event.pageY));_movePrev.copy(_moveCurr);}else if(_state===STATE.ZOOM&&!_this.noZoom){_zoomStart.copy(getMouseOnScreen(event.pageX,event.pageY));_zoomEnd.copy(_zoomStart);}else if(_state===STATE.PAN&&!_this.noPan){_panStart.copy(getMouseOnScreen(event.pageX,event.pageY));_panEnd.copy(_panStart);}\n",
|
||||
"document.addEventListener('mousemove',mousemove,false);document.addEventListener('mouseup',mouseup,false);_this.dispatchEvent(startEvent);}\n",
|
||||
"function mousemove(event){if(_this.enabled===false)return;event.preventDefault();event.stopPropagation();if(_state===STATE.ROTATE&&!_this.noRotate){_movePrev.copy(_moveCurr);_moveCurr.copy(getMouseOnCircle(event.pageX,event.pageY));}else if(_state===STATE.ZOOM&&!_this.noZoom){_zoomEnd.copy(getMouseOnScreen(event.pageX,event.pageY));}else if(_state===STATE.PAN&&!_this.noPan){_panEnd.copy(getMouseOnScreen(event.pageX,event.pageY));}}\n",
|
||||
"function mouseup(event){if(_this.enabled===false)return;event.preventDefault();event.stopPropagation();_state=STATE.NONE;document.removeEventListener('mousemove',mousemove);document.removeEventListener('mouseup',mouseup);_this.dispatchEvent(endEvent);}\n",
|
||||
"function mousewheel(event){if(_this.enabled===false)return;if(_this.noZoom===true)return;event.preventDefault();event.stopPropagation();switch(event.deltaMode){case 2:_zoomStart.y-=event.deltaY*0.025;break;case 1:_zoomStart.y-=event.deltaY*0.01;break;default:_zoomStart.y-=event.deltaY*0.00025;break;}\n",
|
||||
"_this.dispatchEvent(startEvent);_this.dispatchEvent(endEvent);}\n",
|
||||
"function touchstart(event){if(_this.enabled===false)return;event.preventDefault();switch(event.touches.length){case 1:_state=STATE.TOUCH_ROTATE;_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX,event.touches[0].pageY));_movePrev.copy(_moveCurr);break;default:_state=STATE.TOUCH_ZOOM_PAN;var dx=event.touches[0].pageX-event.touches[1].pageX;var dy=event.touches[0].pageY-event.touches[1].pageY;_touchZoomDistanceEnd=_touchZoomDistanceStart=Math.sqrt(dx*dx+dy*dy);var x=(event.touches[0].pageX+event.touches[1].pageX)/2;var y=(event.touches[0].pageY+event.touches[1].pageY)/2;_panStart.copy(getMouseOnScreen(x,y));_panEnd.copy(_panStart);break;}\n",
|
||||
"_this.dispatchEvent(startEvent);}\n",
|
||||
"function touchmove(event){if(_this.enabled===false)return;event.preventDefault();event.stopPropagation();switch(event.touches.length){case 1:_movePrev.copy(_moveCurr);_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX,event.touches[0].pageY));break;default:var dx=event.touches[0].pageX-event.touches[1].pageX;var dy=event.touches[0].pageY-event.touches[1].pageY;_touchZoomDistanceEnd=Math.sqrt(dx*dx+dy*dy);var x=(event.touches[0].pageX+event.touches[1].pageX)/2;var y=(event.touches[0].pageY+event.touches[1].pageY)/2;_panEnd.copy(getMouseOnScreen(x,y));break;}}\n",
|
||||
"function touchend(event){if(_this.enabled===false)return;switch(event.touches.length){case 0:_state=STATE.NONE;break;case 1:_state=STATE.TOUCH_ROTATE;_moveCurr.copy(getMouseOnCircle(event.touches[0].pageX,event.touches[0].pageY));_movePrev.copy(_moveCurr);break;}\n",
|
||||
"_this.dispatchEvent(endEvent);}\n",
|
||||
"function contextmenu(event){if(_this.enabled===false)return;event.preventDefault();}\n",
|
||||
"this.dispose=function(){this.domElement.removeEventListener('contextmenu',contextmenu,false);this.domElement.removeEventListener('mousedown',mousedown,false);this.domElement.removeEventListener('wheel',mousewheel,false);this.domElement.removeEventListener('touchstart',touchstart,false);this.domElement.removeEventListener('touchend',touchend,false);this.domElement.removeEventListener('touchmove',touchmove,false);document.removeEventListener('mousemove',mousemove,false);document.removeEventListener('mouseup',mouseup,false);window.removeEventListener('keydown',keydown,false);window.removeEventListener('keyup',keyup,false);};this.domElement.addEventListener('contextmenu',contextmenu,false);this.domElement.addEventListener('mousedown',mousedown,false);this.domElement.addEventListener('wheel',mousewheel,false);this.domElement.addEventListener('touchstart',touchstart,false);this.domElement.addEventListener('touchend',touchend,false);this.domElement.addEventListener('touchmove',touchmove,false);window.addEventListener('keydown',keydown,false);window.addEventListener('keyup',keyup,false);this.handleResize();this.update();};THREE.TrackballControls.prototype=Object.create(THREE.EventDispatcher.prototype);THREE.TrackballControls.prototype.constructor=THREE.TrackballControls;THREE.GLTFLoader=(function(){function GLTFLoader(manager){this.manager=(manager!==undefined)?manager:THREE.DefaultLoadingManager;this.dracoLoader=null;this.ddsLoader=null;}\n",
|
||||
"GLTFLoader.prototype={constructor:GLTFLoader,crossOrigin:'anonymous',load:function(url,onLoad,onProgress,onError){var scope=this;var resourcePath;if(this.resourcePath!==undefined){resourcePath=this.resourcePath;}else if(this.path!==undefined){resourcePath=this.path;}else{resourcePath=THREE.LoaderUtils.extractUrlBase(url);}\n",
|
||||
"scope.manager.itemStart(url);var _onError=function(e){if(onError){onError(e);}else{console.error(e);}\n",
|
||||
"scope.manager.itemError(url);scope.manager.itemEnd(url);};var loader=new THREE.FileLoader(scope.manager);loader.setPath(this.path);loader.setResponseType('arraybuffer');if(scope.crossOrigin==='use-credentials'){loader.setWithCredentials(true);}\n",
|
||||
"loader.load(url,function(data){try{scope.parse(data,resourcePath,function(gltf){onLoad(gltf);scope.manager.itemEnd(url);},_onError);}catch(e){_onError(e);}},onProgress,_onError);},setCrossOrigin:function(value){this.crossOrigin=value;return this;},setPath:function(value){this.path=value;return this;},setResourcePath:function(value){this.resourcePath=value;return this;},setDRACOLoader:function(dracoLoader){this.dracoLoader=dracoLoader;return this;},setDDSLoader:function(ddsLoader){this.ddsLoader=ddsLoader;return this;},parse:function(data,path,onLoad,onError){var content;var extensions={};if(typeof data==='string'){content=data;}else{var magic=THREE.LoaderUtils.decodeText(new Uint8Array(data,0,4));if(magic===BINARY_EXTENSION_HEADER_MAGIC){try{extensions[EXTENSIONS.KHR_BINARY_GLTF]=new GLTFBinaryExtension(data);}catch(error){if(onError)onError(error);return;}\n",
|
||||
"content=extensions[EXTENSIONS.KHR_BINARY_GLTF].content;}else{content=THREE.LoaderUtils.decodeText(new Uint8Array(data));}}\n",
|
||||
"var json=JSON.parse(content);if(json.asset===undefined||json.asset.version[0]<2){if(onError)onError(new Error('THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.'));return;}\n",
|
||||
"if(json.extensionsUsed){for(var i=0;i<json.extensionsUsed.length;++i){var extensionName=json.extensionsUsed[i];var extensionsRequired=json.extensionsRequired||[];switch(extensionName){case EXTENSIONS.KHR_LIGHTS_PUNCTUAL:extensions[extensionName]=new GLTFLightsExtension(json);break;case EXTENSIONS.KHR_MATERIALS_UNLIT:extensions[extensionName]=new GLTFMaterialsUnlitExtension();break;case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:extensions[extensionName]=new GLTFMaterialsPbrSpecularGlossinessExtension();break;case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION:extensions[extensionName]=new GLTFDracoMeshCompressionExtension(json,this.dracoLoader);break;case EXTENSIONS.MSFT_TEXTURE_DDS:extensions[EXTENSIONS.MSFT_TEXTURE_DDS]=new GLTFTextureDDSExtension(this.ddsLoader);break;case EXTENSIONS.KHR_TEXTURE_TRANSFORM:extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]=new GLTFTextureTransformExtension();break;default:if(extensionsRequired.indexOf(extensionName)>=0){console.warn('THREE.GLTFLoader: Unknown extension "'+extensionName+'".');}}}}\n",
|
||||
"var parser=new GLTFParser(json,extensions,{path:path||this.resourcePath||'',crossOrigin:this.crossOrigin,manager:this.manager});parser.parse(onLoad,onError);}};function GLTFRegistry(){var objects={};return{get:function(key){return objects[key];},add:function(key,object){objects[key]=object;},remove:function(key){delete objects[key];},removeAll:function(){objects={};}};}\n",
|
||||
"var EXTENSIONS={KHR_BINARY_GLTF:'KHR_binary_glTF',KHR_DRACO_MESH_COMPRESSION:'KHR_draco_mesh_compression',KHR_LIGHTS_PUNCTUAL:'KHR_lights_punctual',KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS:'KHR_materials_pbrSpecularGlossiness',KHR_MATERIALS_UNLIT:'KHR_materials_unlit',KHR_TEXTURE_TRANSFORM:'KHR_texture_transform',MSFT_TEXTURE_DDS:'MSFT_texture_dds'};function GLTFTextureDDSExtension(ddsLoader){if(!ddsLoader){throw new Error('THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader');}\n",
|
||||
"this.name=EXTENSIONS.MSFT_TEXTURE_DDS;this.ddsLoader=ddsLoader;}\n",
|
||||
"function GLTFLightsExtension(json){this.name=EXTENSIONS.KHR_LIGHTS_PUNCTUAL;var extension=(json.extensions&&json.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL])||{};this.lightDefs=extension.lights||[];}\n",
|
||||
"GLTFLightsExtension.prototype.loadLight=function(lightIndex){var lightDef=this.lightDefs[lightIndex];var lightNode;var color=new THREE.Color(0xffffff);if(lightDef.color!==undefined)color.fromArray(lightDef.color);var range=lightDef.range!==undefined?lightDef.range:0;switch(lightDef.type){case'directional':lightNode=new THREE.DirectionalLight(color);lightNode.target.position.set(0,0,-1);lightNode.add(lightNode.target);break;case'point':lightNode=new THREE.PointLight(color);lightNode.distance=range;break;case'spot':lightNode=new THREE.SpotLight(color);lightNode.distance=range;lightDef.spot=lightDef.spot||{};lightDef.spot.innerConeAngle=lightDef.spot.innerConeAngle!==undefined?lightDef.spot.innerConeAngle:0;lightDef.spot.outerConeAngle=lightDef.spot.outerConeAngle!==undefined?lightDef.spot.outerConeAngle:Math.PI/4.0;lightNode.angle=lightDef.spot.outerConeAngle;lightNode.penumbra=1.0-lightDef.spot.innerConeAngle/lightDef.spot.outerConeAngle;lightNode.target.position.set(0,0,-1);lightNode.add(lightNode.target);break;default:throw new Error('THREE.GLTFLoader: Unexpected light type, "'+lightDef.type+'".');}\n",
|
||||
"lightNode.position.set(0,0,0);lightNode.decay=2;if(lightDef.intensity!==undefined)lightNode.intensity=lightDef.intensity;lightNode.name=lightDef.name||('light_'+lightIndex);return Promise.resolve(lightNode);};function GLTFMaterialsUnlitExtension(){this.name=EXTENSIONS.KHR_MATERIALS_UNLIT;}\n",
|
||||
"GLTFMaterialsUnlitExtension.prototype.getMaterialType=function(){return THREE.MeshBasicMaterial;};GLTFMaterialsUnlitExtension.prototype.extendParams=function(materialParams,materialDef,parser){var pending=[];materialParams.color=new THREE.Color(1.0,1.0,1.0);materialParams.opacity=1.0;var metallicRoughness=materialDef.pbrMetallicRoughness;if(metallicRoughness){if(Array.isArray(metallicRoughness.baseColorFactor)){var array=metallicRoughness.baseColorFactor;materialParams.color.fromArray(array);materialParams.opacity=array[3];}\n",
|
||||
"if(metallicRoughness.baseColorTexture!==undefined){pending.push(parser.assignTexture(materialParams,'map',metallicRoughness.baseColorTexture));}}\n",
|
||||
"return Promise.all(pending);};var BINARY_EXTENSION_HEADER_MAGIC='glTF';var BINARY_EXTENSION_HEADER_LENGTH=12;var BINARY_EXTENSION_CHUNK_TYPES={JSON:0x4E4F534A,BIN:0x004E4942};function GLTFBinaryExtension(data){this.name=EXTENSIONS.KHR_BINARY_GLTF;this.content=null;this.body=null;var headerView=new DataView(data,0,BINARY_EXTENSION_HEADER_LENGTH);this.header={magic:THREE.LoaderUtils.decodeText(new Uint8Array(data.slice(0,4))),version:headerView.getUint32(4,true),length:headerView.getUint32(8,true)};if(this.header.magic!==BINARY_EXTENSION_HEADER_MAGIC){throw new Error('THREE.GLTFLoader: Unsupported glTF-Binary header.');}else if(this.header.version<2.0){throw new Error('THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.');}\n",
|
||||
"var chunkView=new DataView(data,BINARY_EXTENSION_HEADER_LENGTH);var chunkIndex=0;while(chunkIndex<chunkView.byteLength){var chunkLength=chunkView.getUint32(chunkIndex,true);chunkIndex+=4;var chunkType=chunkView.getUint32(chunkIndex,true);chunkIndex+=4;if(chunkType===BINARY_EXTENSION_CHUNK_TYPES.JSON){var contentArray=new Uint8Array(data,BINARY_EXTENSION_HEADER_LENGTH+chunkIndex,chunkLength);this.content=THREE.LoaderUtils.decodeText(contentArray);}else if(chunkType===BINARY_EXTENSION_CHUNK_TYPES.BIN){var byteOffset=BINARY_EXTENSION_HEADER_LENGTH+chunkIndex;this.body=data.slice(byteOffset,byteOffset+chunkLength);}\n",
|
||||
"chunkIndex+=chunkLength;}\n",
|
||||
"if(this.content===null){throw new Error('THREE.GLTFLoader: JSON content not found.');}}\n",
|
||||
"function GLTFDracoMeshCompressionExtension(json,dracoLoader){if(!dracoLoader){throw new Error('THREE.GLTFLoader: No DRACOLoader instance provided.');}\n",
|
||||
"this.name=EXTENSIONS.KHR_DRACO_MESH_COMPRESSION;this.json=json;this.dracoLoader=dracoLoader;}\n",
|
||||
"GLTFDracoMeshCompressionExtension.prototype.decodePrimitive=function(primitive,parser){var json=this.json;var dracoLoader=this.dracoLoader;var bufferViewIndex=primitive.extensions[this.name].bufferView;var gltfAttributeMap=primitive.extensions[this.name].attributes;var threeAttributeMap={};var attributeNormalizedMap={};var attributeTypeMap={};for(var attributeName in gltfAttributeMap){var threeAttributeName=ATTRIBUTES[attributeName]||attributeName.toLowerCase();threeAttributeMap[threeAttributeName]=gltfAttributeMap[attributeName];}\n",
|
||||
"for(attributeName in primitive.attributes){var threeAttributeName=ATTRIBUTES[attributeName]||attributeName.toLowerCase();if(gltfAttributeMap[attributeName]!==undefined){var accessorDef=json.accessors[primitive.attributes[attributeName]];var componentType=WEBGL_COMPONENT_TYPES[accessorDef.componentType];attributeTypeMap[threeAttributeName]=componentType;attributeNormalizedMap[threeAttributeName]=accessorDef.normalized===true;}}\n",
|
||||
"return parser.getDependency('bufferView',bufferViewIndex).then(function(bufferView){return new Promise(function(resolve){dracoLoader.decodeDracoFile(bufferView,function(geometry){for(var attributeName in geometry.attributes){var attribute=geometry.attributes[attributeName];var normalized=attributeNormalizedMap[attributeName];if(normalized!==undefined)attribute.normalized=normalized;}\n",
|
||||
"resolve(geometry);},threeAttributeMap,attributeTypeMap);});});};function GLTFTextureTransformExtension(){this.name=EXTENSIONS.KHR_TEXTURE_TRANSFORM;}\n",
|
||||
"GLTFTextureTransformExtension.prototype.extendTexture=function(texture,transform){texture=texture.clone();if(transform.offset!==undefined){texture.offset.fromArray(transform.offset);}\n",
|
||||
"if(transform.rotation!==undefined){texture.rotation=transform.rotation;}\n",
|
||||
"if(transform.scale!==undefined){texture.repeat.fromArray(transform.scale);}\n",
|
||||
"if(transform.texCoord!==undefined){console.warn('THREE.GLTFLoader: Custom UV sets in "'+this.name+'" extension not yet supported.');}\n",
|
||||
"texture.needsUpdate=true;return texture;};function GLTFMaterialsPbrSpecularGlossinessExtension(){return{name:EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS,specularGlossinessParams:['color','map','lightMap','lightMapIntensity','aoMap','aoMapIntensity','emissive','emissiveIntensity','emissiveMap','bumpMap','bumpScale','normalMap','displacementMap','displacementScale','displacementBias','specularMap','specular','glossinessMap','glossiness','alphaMap','envMap','envMapIntensity','refractionRatio',],getMaterialType:function(){return THREE.ShaderMaterial;},extendParams:function(materialParams,materialDef,parser){var pbrSpecularGlossiness=materialDef.extensions[this.name];var shader=THREE.ShaderLib['standard'];var uniforms=THREE.UniformsUtils.clone(shader.uniforms);var specularMapParsFragmentChunk=['#ifdef USE_SPECULARMAP','\tuniform sampler2D specularMap;','#endif'].join('\\n');var glossinessMapParsFragmentChunk=['#ifdef USE_GLOSSINESSMAP','\tuniform sampler2D glossinessMap;','#endif'].join('\\n');var specularMapFragmentChunk=['vec3 specularFactor = specular;','#ifdef USE_SPECULARMAP','\tvec4 texelSpecular = texture2D( specularMap, vUv );','\ttexelSpecular = sRGBToLinear( texelSpecular );','\t// reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture','\tspecularFactor *= texelSpecular.rgb;','#endif'].join('\\n');var glossinessMapFragmentChunk=['float glossinessFactor = glossiness;','#ifdef USE_GLOSSINESSMAP','\tvec4 texelGlossiness = texture2D( glossinessMap, vUv );','\t// reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture','\tglossinessFactor *= texelGlossiness.a;','#endif'].join('\\n');var lightPhysicalFragmentChunk=['PhysicalMaterial material;','material.diffuseColor = diffuseColor.rgb;','material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );','material.specularColor = specularFactor.rgb;',].join('\\n');var fragmentShader=shader.fragmentShader.replace('uniform float roughness;','uniform vec3 specular;').replace('uniform float metalness;','uniform float glossiness;').replace('#include <roughnessmap_pars_fragment>',specularMapParsFragmentChunk).replace('#include <metalnessmap_pars_fragment>',glossinessMapParsFragmentChunk).replace('#include <roughnessmap_fragment>',specularMapFragmentChunk).replace('#include <metalnessmap_fragment>',glossinessMapFragmentChunk).replace('#include <lights_physical_fragment>',lightPhysicalFragmentChunk);delete uniforms.roughness;delete uniforms.metalness;delete uniforms.roughnessMap;delete uniforms.metalnessMap;uniforms.specular={value:new THREE.Color().setHex(0x111111)};uniforms.glossiness={value:0.5};uniforms.specularMap={value:null};uniforms.glossinessMap={value:null};materialParams.vertexShader=shader.vertexShader;materialParams.fragmentShader=fragmentShader;materialParams.uniforms=uniforms;materialParams.defines={'STANDARD':''};materialParams.color=new THREE.Color(1.0,1.0,1.0);materialParams.opacity=1.0;var pending=[];if(Array.isArray(pbrSpecularGlossiness.diffuseFactor)){var array=pbrSpecularGlossiness.diffuseFactor;materialParams.color.fromArray(array);materialParams.opacity=array[3];}\n",
|
||||
"if(pbrSpecularGlossiness.diffuseTexture!==undefined){pending.push(parser.assignTexture(materialParams,'map',pbrSpecularGlossiness.diffuseTexture));}\n",
|
||||
"materialParams.emissive=new THREE.Color(0.0,0.0,0.0);materialParams.glossiness=pbrSpecularGlossiness.glossinessFactor!==undefined?pbrSpecularGlossiness.glossinessFactor:1.0;materialParams.specular=new THREE.Color(1.0,1.0,1.0);if(Array.isArray(pbrSpecularGlossiness.specularFactor)){materialParams.specular.fromArray(pbrSpecularGlossiness.specularFactor);}\n",
|
||||
"if(pbrSpecularGlossiness.specularGlossinessTexture!==undefined){var specGlossMapDef=pbrSpecularGlossiness.specularGlossinessTexture;pending.push(parser.assignTexture(materialParams,'glossinessMap',specGlossMapDef));pending.push(parser.assignTexture(materialParams,'specularMap',specGlossMapDef));}\n",
|
||||
"return Promise.all(pending);},createMaterial:function(params){var material=new THREE.ShaderMaterial({defines:params.defines,vertexShader:params.vertexShader,fragmentShader:params.fragmentShader,uniforms:params.uniforms,fog:true,lights:true,opacity:params.opacity,transparent:params.transparent});material.isGLTFSpecularGlossinessMaterial=true;material.color=params.color;material.map=params.map===undefined?null:params.map;material.lightMap=null;material.lightMapIntensity=1.0;material.aoMap=params.aoMap===undefined?null:params.aoMap;material.aoMapIntensity=1.0;material.emissive=params.emissive;material.emissiveIntensity=1.0;material.emissiveMap=params.emissiveMap===undefined?null:params.emissiveMap;material.bumpMap=params.bumpMap===undefined?null:params.bumpMap;material.bumpScale=1;material.normalMap=params.normalMap===undefined?null:params.normalMap;if(params.normalScale)material.normalScale=params.normalScale;material.displacementMap=null;material.displacementScale=1;material.displacementBias=0;material.specularMap=params.specularMap===undefined?null:params.specularMap;material.specular=params.specular;material.glossinessMap=params.glossinessMap===undefined?null:params.glossinessMap;material.glossiness=params.glossiness;material.alphaMap=null;material.envMap=params.envMap===undefined?null:params.envMap;material.envMapIntensity=1.0;material.refractionRatio=0.98;material.extensions.derivatives=true;return material;},cloneMaterial:function(source){var target=source.clone();target.isGLTFSpecularGlossinessMaterial=true;var params=this.specularGlossinessParams;for(var i=0,il=params.length;i<il;i++){var value=source[params[i]];target[params[i]]=(value&&value.isColor)?value.clone():value;}\n",
|
||||
"return target;},refreshUniforms:function(renderer,scene,camera,geometry,material){if(material.isGLTFSpecularGlossinessMaterial!==true){return;}\n",
|
||||
"var uniforms=material.uniforms;var defines=material.defines;uniforms.opacity.value=material.opacity;uniforms.diffuse.value.copy(material.color);uniforms.emissive.value.copy(material.emissive).multiplyScalar(material.emissiveIntensity);uniforms.map.value=material.map;uniforms.specularMap.value=material.specularMap;uniforms.alphaMap.value=material.alphaMap;uniforms.lightMap.value=material.lightMap;uniforms.lightMapIntensity.value=material.lightMapIntensity;uniforms.aoMap.value=material.aoMap;uniforms.aoMapIntensity.value=material.aoMapIntensity;var uvScaleMap;if(material.map){uvScaleMap=material.map;}else if(material.specularMap){uvScaleMap=material.specularMap;}else if(material.displacementMap){uvScaleMap=material.displacementMap;}else if(material.normalMap){uvScaleMap=material.normalMap;}else if(material.bumpMap){uvScaleMap=material.bumpMap;}else if(material.glossinessMap){uvScaleMap=material.glossinessMap;}else if(material.alphaMap){uvScaleMap=material.alphaMap;}else if(material.emissiveMap){uvScaleMap=material.emissiveMap;}\n",
|
||||
"if(uvScaleMap!==undefined){if(uvScaleMap.isWebGLRenderTarget){uvScaleMap=uvScaleMap.texture;}\n",
|
||||
"if(uvScaleMap.matrixAutoUpdate===true){uvScaleMap.updateMatrix();}\n",
|
||||
"uniforms.uvTransform.value.copy(uvScaleMap.matrix);}\n",
|
||||
"if(material.envMap){uniforms.envMap.value=material.envMap;uniforms.envMapIntensity.value=material.envMapIntensity;uniforms.flipEnvMap.value=material.envMap.isCubeTexture?-1:1;uniforms.reflectivity.value=material.reflectivity;uniforms.refractionRatio.value=material.refractionRatio;uniforms.maxMipLevel.value=renderer.properties.get(material.envMap).__maxMipLevel;}\n",
|
||||
"uniforms.specular.value.copy(material.specular);uniforms.glossiness.value=material.glossiness;uniforms.glossinessMap.value=material.glossinessMap;uniforms.emissiveMap.value=material.emissiveMap;uniforms.bumpMap.value=material.bumpMap;uniforms.normalMap.value=material.normalMap;uniforms.displacementMap.value=material.displacementMap;uniforms.displacementScale.value=material.displacementScale;uniforms.displacementBias.value=material.displacementBias;if(uniforms.glossinessMap.value!==null&&defines.USE_GLOSSINESSMAP===undefined){defines.USE_GLOSSINESSMAP='';defines.USE_ROUGHNESSMAP='';}\n",
|
||||
"if(uniforms.glossinessMap.value===null&&defines.USE_GLOSSINESSMAP!==undefined){delete defines.USE_GLOSSINESSMAP;delete defines.USE_ROUGHNESSMAP;}}};}\n",
|
||||
"function GLTFCubicSplineInterpolant(parameterPositions,sampleValues,sampleSize,resultBuffer){THREE.Interpolant.call(this,parameterPositions,sampleValues,sampleSize,resultBuffer);}\n",
|
||||
"GLTFCubicSplineInterpolant.prototype=Object.create(THREE.Interpolant.prototype);GLTFCubicSplineInterpolant.prototype.constructor=GLTFCubicSplineInterpolant;GLTFCubicSplineInterpolant.prototype.copySampleValue_=function(index){var result=this.resultBuffer,values=this.sampleValues,valueSize=this.valueSize,offset=index*valueSize*3+valueSize;for(var i=0;i!==valueSize;i++){result[i]=values[offset+i];}\n",
|
||||
"return result;};GLTFCubicSplineInterpolant.prototype.beforeStart_=GLTFCubicSplineInterpolant.prototype.copySampleValue_;GLTFCubicSplineInterpolant.prototype.afterEnd_=GLTFCubicSplineInterpolant.prototype.copySampleValue_;GLTFCubicSplineInterpolant.prototype.interpolate_=function(i1,t0,t,t1){var result=this.resultBuffer;var values=this.sampleValues;var stride=this.valueSize;var stride2=stride*2;var stride3=stride*3;var td=t1-t0;var p=(t-t0)/td;var pp=p*p;var ppp=pp*p;var offset1=i1*stride3;var offset0=offset1-stride3;var s2=-2*ppp+3*pp;var s3=ppp-pp;var s0=1-s2;var s1=s3-pp+p;for(var i=0;i!==stride;i++){var p0=values[offset0+i+stride];var m0=values[offset0+i+stride2]*td;var p1=values[offset1+i+stride];var m1=values[offset1+i]*td;result[i]=s0*p0+s1*m0+s2*p1+s3*m1;}\n",
|
||||
"return result;};var WEBGL_CONSTANTS={FLOAT:5126,FLOAT_MAT3:35675,FLOAT_MAT4:35676,FLOAT_VEC2:35664,FLOAT_VEC3:35665,FLOAT_VEC4:35666,LINEAR:9729,REPEAT:10497,SAMPLER_2D:35678,POINTS:0,LINES:1,LINE_LOOP:2,LINE_STRIP:3,TRIANGLES:4,TRIANGLE_STRIP:5,TRIANGLE_FAN:6,UNSIGNED_BYTE:5121,UNSIGNED_SHORT:5123};var WEBGL_COMPONENT_TYPES={5120:Int8Array,5121:Uint8Array,5122:Int16Array,5123:Uint16Array,5125:Uint32Array,5126:Float32Array};var WEBGL_FILTERS={9728:THREE.NearestFilter,9729:THREE.LinearFilter,9984:THREE.NearestMipMapNearestFilter,9985:THREE.LinearMipMapNearestFilter,9986:THREE.NearestMipMapLinearFilter,9987:THREE.LinearMipMapLinearFilter};var WEBGL_WRAPPINGS={33071:THREE.ClampToEdgeWrapping,33648:THREE.MirroredRepeatWrapping,10497:THREE.RepeatWrapping};var WEBGL_TYPE_SIZES={'SCALAR':1,'VEC2':2,'VEC3':3,'VEC4':4,'MAT2':4,'MAT3':9,'MAT4':16};var ATTRIBUTES={POSITION:'position',NORMAL:'normal',TANGENT:'tangent',TEXCOORD_0:'uv',TEXCOORD_1:'uv2',COLOR_0:'color',WEIGHTS_0:'skinWeight',JOINTS_0:'skinIndex',};var PATH_PROPERTIES={scale:'scale',translation:'position',rotation:'quaternion',weights:'morphTargetInfluences'};var INTERPOLATION={CUBICSPLINE:undefined,LINEAR:THREE.InterpolateLinear,STEP:THREE.InterpolateDiscrete};var ALPHA_MODES={OPAQUE:'OPAQUE',MASK:'MASK',BLEND:'BLEND'};var MIME_TYPE_FORMATS={'image/png':THREE.RGBAFormat,'image/jpeg':THREE.RGBFormat};function resolveURL(url,path){if(typeof url!=='string'||url==='')return'';if(/^(https?:)?\\/\\//i.test(url))return url;if(/^data:.*,.*$/i.test(url))return url;if(/^blob:.*$/i.test(url))return url;return path+url;}\n",
|
||||
"var defaultMaterial;function createDefaultMaterial(){defaultMaterial=defaultMaterial||new THREE.MeshStandardMaterial({color:0xFFFFFF,emissive:0x000000,metalness:1,roughness:1,transparent:false,depthTest:true,side:THREE.FrontSide});return defaultMaterial;}\n",
|
||||
"function addUnknownExtensionsToUserData(knownExtensions,object,objectDef){for(var name in objectDef.extensions){if(knownExtensions[name]===undefined){object.userData.gltfExtensions=object.userData.gltfExtensions||{};object.userData.gltfExtensions[name]=objectDef.extensions[name];}}}\n",
|
||||
"function assignExtrasToUserData(object,gltfDef){if(gltfDef.extras!==undefined){if(typeof gltfDef.extras==='object'){Object.assign(object.userData,gltfDef.extras);}else{console.warn('THREE.GLTFLoader: Ignoring primitive type .extras, '+gltfDef.extras);}}}\n",
|
||||
"function addMorphTargets(geometry,targets,parser){var hasMorphPosition=false;var hasMorphNormal=false;for(var i=0,il=targets.length;i<il;i++){var target=targets[i];if(target.POSITION!==undefined)hasMorphPosition=true;if(target.NORMAL!==undefined)hasMorphNormal=true;if(hasMorphPosition&&hasMorphNormal)break;}\n",
|
||||
"if(!hasMorphPosition&&!hasMorphNormal)return Promise.resolve(geometry);var pendingPositionAccessors=[];var pendingNormalAccessors=[];for(var i=0,il=targets.length;i<il;i++){var target=targets[i];if(hasMorphPosition){var pendingAccessor=target.POSITION!==undefined?parser.getDependency('accessor',target.POSITION):geometry.attributes.position;pendingPositionAccessors.push(pendingAccessor);}\n",
|
||||
"if(hasMorphNormal){var pendingAccessor=target.NORMAL!==undefined?parser.getDependency('accessor',target.NORMAL):geometry.attributes.normal;pendingNormalAccessors.push(pendingAccessor);}}\n",
|
||||
"return Promise.all([Promise.all(pendingPositionAccessors),Promise.all(pendingNormalAccessors)]).then(function(accessors){var morphPositions=accessors[0];var morphNormals=accessors[1];for(var i=0,il=morphPositions.length;i<il;i++){if(geometry.attributes.position===morphPositions[i])continue;morphPositions[i]=cloneBufferAttribute(morphPositions[i]);}\n",
|
||||
"for(var i=0,il=morphNormals.length;i<il;i++){if(geometry.attributes.normal===morphNormals[i])continue;morphNormals[i]=cloneBufferAttribute(morphNormals[i]);}\n",
|
||||
"for(var i=0,il=targets.length;i<il;i++){var target=targets[i];var attributeName='morphTarget'+i;if(hasMorphPosition){if(target.POSITION!==undefined){var positionAttribute=morphPositions[i];positionAttribute.name=attributeName;var position=geometry.attributes.position;for(var j=0,jl=positionAttribute.count;j<jl;j++){positionAttribute.setXYZ(j,positionAttribute.getX(j)+position.getX(j),positionAttribute.getY(j)+position.getY(j),positionAttribute.getZ(j)+position.getZ(j));}}}\n",
|
||||
"if(hasMorphNormal){if(target.NORMAL!==undefined){var normalAttribute=morphNormals[i];normalAttribute.name=attributeName;var normal=geometry.attributes.normal;for(var j=0,jl=normalAttribute.count;j<jl;j++){normalAttribute.setXYZ(j,normalAttribute.getX(j)+normal.getX(j),normalAttribute.getY(j)+normal.getY(j),normalAttribute.getZ(j)+normal.getZ(j));}}}}\n",
|
||||
"if(hasMorphPosition)geometry.morphAttributes.position=morphPositions;if(hasMorphNormal)geometry.morphAttributes.normal=morphNormals;return geometry;});}\n",
|
||||
"function updateMorphTargets(mesh,meshDef){mesh.updateMorphTargets();if(meshDef.weights!==undefined){for(var i=0,il=meshDef.weights.length;i<il;i++){mesh.morphTargetInfluences[i]=meshDef.weights[i];}}\n",
|
||||
"if(meshDef.extras&&Array.isArray(meshDef.extras.targetNames)){var targetNames=meshDef.extras.targetNames;if(mesh.morphTargetInfluences.length===targetNames.length){mesh.morphTargetDictionary={};for(var i=0,il=targetNames.length;i<il;i++){mesh.morphTargetDictionary[targetNames[i]]=i;}}else{console.warn('THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.');}}}\n",
|
||||
"function createPrimitiveKey(primitiveDef){var dracoExtension=primitiveDef.extensions&&primitiveDef.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION];var geometryKey;if(dracoExtension){geometryKey='draco:'+dracoExtension.bufferView\n",
|
||||
"+':'+dracoExtension.indices\n",
|
||||
"+':'+createAttributesKey(dracoExtension.attributes);}else{geometryKey=primitiveDef.indices+':'+createAttributesKey(primitiveDef.attributes)+':'+primitiveDef.mode;}\n",
|
||||
"return geometryKey;}\n",
|
||||
"function createAttributesKey(attributes){var attributesKey='';var keys=Object.keys(attributes).sort();for(var i=0,il=keys.length;i<il;i++){attributesKey+=keys[i]+':'+attributes[keys[i]]+';';}\n",
|
||||
"return attributesKey;}\n",
|
||||
"function cloneBufferAttribute(attribute){if(attribute.isInterleavedBufferAttribute){var count=attribute.count;var itemSize=attribute.itemSize;var array=attribute.array.slice(0,count*itemSize);for(var i=0,j=0;i<count;++i){array[j++]=attribute.getX(i);if(itemSize>=2)array[j++]=attribute.getY(i);if(itemSize>=3)array[j++]=attribute.getZ(i);if(itemSize>=4)array[j++]=attribute.getW(i);}\n",
|
||||
"return new THREE.BufferAttribute(array,itemSize,attribute.normalized);}\n",
|
||||
"return attribute.clone();}\n",
|
||||
"function GLTFParser(json,extensions,options){this.json=json||{};this.extensions=extensions||{};this.options=options||{};this.cache=new GLTFRegistry();this.primitiveCache={};this.textureLoader=new THREE.TextureLoader(this.options.manager);this.textureLoader.setCrossOrigin(this.options.crossOrigin);this.fileLoader=new THREE.FileLoader(this.options.manager);this.fileLoader.setResponseType('arraybuffer');if(this.options.crossOrigin==='use-credentials'){this.fileLoader.setWithCredentials(true);}}\n",
|
||||
"GLTFParser.prototype.parse=function(onLoad,onError){var parser=this;var json=this.json;var extensions=this.extensions;this.cache.removeAll();this.markDefs();Promise.all([this.getDependencies('scene'),this.getDependencies('animation'),this.getDependencies('camera'),]).then(function(dependencies){var result={scene:dependencies[0][json.scene||0],scenes:dependencies[0],animations:dependencies[1],cameras:dependencies[2],asset:json.asset,parser:parser,userData:{}};addUnknownExtensionsToUserData(extensions,result,json);assignExtrasToUserData(result,json);onLoad(result);}).catch(onError);};GLTFParser.prototype.markDefs=function(){var nodeDefs=this.json.nodes||[];var skinDefs=this.json.skins||[];var meshDefs=this.json.meshes||[];var meshReferences={};var meshUses={};for(var skinIndex=0,skinLength=skinDefs.length;skinIndex<skinLength;skinIndex++){var joints=skinDefs[skinIndex].joints;for(var i=0,il=joints.length;i<il;i++){nodeDefs[joints[i]].isBone=true;}}\n",
|
||||
"for(var nodeIndex=0,nodeLength=nodeDefs.length;nodeIndex<nodeLength;nodeIndex++){var nodeDef=nodeDefs[nodeIndex];if(nodeDef.mesh!==undefined){if(meshReferences[nodeDef.mesh]===undefined){meshReferences[nodeDef.mesh]=meshUses[nodeDef.mesh]=0;}\n",
|
||||
"meshReferences[nodeDef.mesh]++;if(nodeDef.skin!==undefined){meshDefs[nodeDef.mesh].isSkinnedMesh=true;}}}\n",
|
||||
"this.json.meshReferences=meshReferences;this.json.meshUses=meshUses;};GLTFParser.prototype.getDependency=function(type,index){var cacheKey=type+':'+index;var dependency=this.cache.get(cacheKey);if(!dependency){switch(type){case'scene':dependency=this.loadScene(index);break;case'node':dependency=this.loadNode(index);break;case'mesh':dependency=this.loadMesh(index);break;case'accessor':dependency=this.loadAccessor(index);break;case'bufferView':dependency=this.loadBufferView(index);break;case'buffer':dependency=this.loadBuffer(index);break;case'material':dependency=this.loadMaterial(index);break;case'texture':dependency=this.loadTexture(index);break;case'skin':dependency=this.loadSkin(index);break;case'animation':dependency=this.loadAnimation(index);break;case'camera':dependency=this.loadCamera(index);break;case'light':dependency=this.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL].loadLight(index);break;default:throw new Error('Unknown type: '+type);}\n",
|
||||
"this.cache.add(cacheKey,dependency);}\n",
|
||||
"return dependency;};GLTFParser.prototype.getDependencies=function(type){var dependencies=this.cache.get(type);if(!dependencies){var parser=this;var defs=this.json[type+(type==='mesh'?'es':'s')]||[];dependencies=Promise.all(defs.map(function(def,index){return parser.getDependency(type,index);}));this.cache.add(type,dependencies);}\n",
|
||||
"return dependencies;};GLTFParser.prototype.loadBuffer=function(bufferIndex){var bufferDef=this.json.buffers[bufferIndex];var loader=this.fileLoader;if(bufferDef.type&&bufferDef.type!=='arraybuffer'){throw new Error('THREE.GLTFLoader: '+bufferDef.type+' buffer type is not supported.');}\n",
|
||||
"if(bufferDef.uri===undefined&&bufferIndex===0){return Promise.resolve(this.extensions[EXTENSIONS.KHR_BINARY_GLTF].body);}\n",
|
||||
"var options=this.options;return new Promise(function(resolve,reject){loader.load(resolveURL(bufferDef.uri,options.path),resolve,undefined,function(){reject(new Error('THREE.GLTFLoader: Failed to load buffer "'+bufferDef.uri+'".'));});});};GLTFParser.prototype.loadBufferView=function(bufferViewIndex){var bufferViewDef=this.json.bufferViews[bufferViewIndex];return this.getDependency('buffer',bufferViewDef.buffer).then(function(buffer){var byteLength=bufferViewDef.byteLength||0;var byteOffset=bufferViewDef.byteOffset||0;return buffer.slice(byteOffset,byteOffset+byteLength);});};GLTFParser.prototype.loadAccessor=function(accessorIndex){var parser=this;var json=this.json;var accessorDef=this.json.accessors[accessorIndex];if(accessorDef.bufferView===undefined&&accessorDef.sparse===undefined){return Promise.resolve(null);}\n",
|
||||
"var pendingBufferViews=[];if(accessorDef.bufferView!==undefined){pendingBufferViews.push(this.getDependency('bufferView',accessorDef.bufferView));}else{pendingBufferViews.push(null);}\n",
|
||||
"if(accessorDef.sparse!==undefined){pendingBufferViews.push(this.getDependency('bufferView',accessorDef.sparse.indices.bufferView));pendingBufferViews.push(this.getDependency('bufferView',accessorDef.sparse.values.bufferView));}\n",
|
||||
"return Promise.all(pendingBufferViews).then(function(bufferViews){var bufferView=bufferViews[0];var itemSize=WEBGL_TYPE_SIZES[accessorDef.type];var TypedArray=WEBGL_COMPONENT_TYPES[accessorDef.componentType];var elementBytes=TypedArray.BYTES_PER_ELEMENT;var itemBytes=elementBytes*itemSize;var byteOffset=accessorDef.byteOffset||0;var byteStride=accessorDef.bufferView!==undefined?json.bufferViews[accessorDef.bufferView].byteStride:undefined;var normalized=accessorDef.normalized===true;var array,bufferAttribute;if(byteStride&&byteStride!==itemBytes){var ibCacheKey='InterleavedBuffer:'+accessorDef.bufferView+':'+accessorDef.componentType;var ib=parser.cache.get(ibCacheKey);if(!ib){array=new TypedArray(bufferView);ib=new THREE.InterleavedBuffer(array,byteStride/elementBytes);parser.cache.add(ibCacheKey,ib);}\n",
|
||||
"bufferAttribute=new THREE.InterleavedBufferAttribute(ib,itemSize,byteOffset/elementBytes,normalized);}else{if(bufferView===null){array=new TypedArray(accessorDef.count*itemSize);}else{array=new TypedArray(bufferView,byteOffset,accessorDef.count*itemSize);}\n",
|
||||
"bufferAttribute=new THREE.BufferAttribute(array,itemSize,normalized);}\n",
|
||||
"if(accessorDef.sparse!==undefined){var itemSizeIndices=WEBGL_TYPE_SIZES.SCALAR;var TypedArrayIndices=WEBGL_COMPONENT_TYPES[accessorDef.sparse.indices.componentType];var byteOffsetIndices=accessorDef.sparse.indices.byteOffset||0;var byteOffsetValues=accessorDef.sparse.values.byteOffset||0;var sparseIndices=new TypedArrayIndices(bufferViews[1],byteOffsetIndices,accessorDef.sparse.count*itemSizeIndices);var sparseValues=new TypedArray(bufferViews[2],byteOffsetValues,accessorDef.sparse.count*itemSize);if(bufferView!==null){bufferAttribute.setArray(bufferAttribute.array.slice());}\n",
|
||||
"for(var i=0,il=sparseIndices.length;i<il;i++){var index=sparseIndices[i];bufferAttribute.setX(index,sparseValues[i*itemSize]);if(itemSize>=2)bufferAttribute.setY(index,sparseValues[i*itemSize+1]);if(itemSize>=3)bufferAttribute.setZ(index,sparseValues[i*itemSize+2]);if(itemSize>=4)bufferAttribute.setW(index,sparseValues[i*itemSize+3]);if(itemSize>=5)throw new Error('THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.');}}\n",
|
||||
"return bufferAttribute;});};GLTFParser.prototype.loadTexture=function(textureIndex){var parser=this;var json=this.json;var options=this.options;var textureLoader=this.textureLoader;var URL=window.URL||window.webkitURL;var textureDef=json.textures[textureIndex];var textureExtensions=textureDef.extensions||{};var source;if(textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS]){source=json.images[textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS].source];}else{source=json.images[textureDef.source];}\n",
|
||||
"var sourceURI=source.uri;var isObjectURL=false;if(source.bufferView!==undefined){sourceURI=parser.getDependency('bufferView',source.bufferView).then(function(bufferView){isObjectURL=true;var blob=new Blob([bufferView],{type:source.mimeType});sourceURI=URL.createObjectURL(blob);return sourceURI;});}\n",
|
||||
"return Promise.resolve(sourceURI).then(function(sourceURI){var loader=THREE.Loader.Handlers.get(sourceURI);if(!loader){loader=textureExtensions[EXTENSIONS.MSFT_TEXTURE_DDS]?parser.extensions[EXTENSIONS.MSFT_TEXTURE_DDS].ddsLoader:textureLoader;}\n",
|
||||
"return new Promise(function(resolve,reject){loader.load(resolveURL(sourceURI,options.path),resolve,undefined,reject);});}).then(function(texture){if(isObjectURL===true){URL.revokeObjectURL(sourceURI);}\n",
|
||||
"texture.flipY=false;if(textureDef.name!==undefined)texture.name=textureDef.name;if(source.mimeType in MIME_TYPE_FORMATS){texture.format=MIME_TYPE_FORMATS[source.mimeType];}\n",
|
||||
"var samplers=json.samplers||{};var sampler=samplers[textureDef.sampler]||{};texture.magFilter=WEBGL_FILTERS[sampler.magFilter]||THREE.LinearFilter;texture.minFilter=WEBGL_FILTERS[sampler.minFilter]||THREE.LinearMipMapLinearFilter;texture.wrapS=WEBGL_WRAPPINGS[sampler.wrapS]||THREE.RepeatWrapping;texture.wrapT=WEBGL_WRAPPINGS[sampler.wrapT]||THREE.RepeatWrapping;return texture;});};GLTFParser.prototype.assignTexture=function(materialParams,mapName,mapDef){var parser=this;return this.getDependency('texture',mapDef.index).then(function(texture){if(!texture.isCompressedTexture){switch(mapName){case'aoMap':case'emissiveMap':case'metalnessMap':case'normalMap':case'roughnessMap':texture.format=THREE.RGBFormat;break;}}\n",
|
||||
"if(parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]){var transform=mapDef.extensions!==undefined?mapDef.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM]:undefined;if(transform){texture=parser.extensions[EXTENSIONS.KHR_TEXTURE_TRANSFORM].extendTexture(texture,transform);}}\n",
|
||||
"materialParams[mapName]=texture;});};GLTFParser.prototype.assignFinalMaterial=function(mesh){var geometry=mesh.geometry;var material=mesh.material;var extensions=this.extensions;var useVertexTangents=geometry.attributes.tangent!==undefined;var useVertexColors=geometry.attributes.color!==undefined;var useFlatShading=geometry.attributes.normal===undefined;var useSkinning=mesh.isSkinnedMesh===true;var useMorphTargets=Object.keys(geometry.morphAttributes).length>0;var useMorphNormals=useMorphTargets&&geometry.morphAttributes.normal!==undefined;if(mesh.isPoints){var cacheKey='PointsMaterial:'+material.uuid;var pointsMaterial=this.cache.get(cacheKey);if(!pointsMaterial){pointsMaterial=new THREE.PointsMaterial();THREE.Material.prototype.copy.call(pointsMaterial,material);pointsMaterial.color.copy(material.color);pointsMaterial.map=material.map;pointsMaterial.lights=false;this.cache.add(cacheKey,pointsMaterial);}\n",
|
||||
"material=pointsMaterial;}else if(mesh.isLine){var cacheKey='LineBasicMaterial:'+material.uuid;var lineMaterial=this.cache.get(cacheKey);if(!lineMaterial){lineMaterial=new THREE.LineBasicMaterial();THREE.Material.prototype.copy.call(lineMaterial,material);lineMaterial.color.copy(material.color);lineMaterial.lights=false;this.cache.add(cacheKey,lineMaterial);}\n",
|
||||
"material=lineMaterial;}\n",
|
||||
"if(useVertexTangents||useVertexColors||useFlatShading||useSkinning||useMorphTargets){var cacheKey='ClonedMaterial:'+material.uuid+':';if(material.isGLTFSpecularGlossinessMaterial)cacheKey+='specular-glossiness:';if(useSkinning)cacheKey+='skinning:';if(useVertexTangents)cacheKey+='vertex-tangents:';if(useVertexColors)cacheKey+='vertex-colors:';if(useFlatShading)cacheKey+='flat-shading:';if(useMorphTargets)cacheKey+='morph-targets:';if(useMorphNormals)cacheKey+='morph-normals:';var cachedMaterial=this.cache.get(cacheKey);if(!cachedMaterial){cachedMaterial=material.isGLTFSpecularGlossinessMaterial?extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].cloneMaterial(material):material.clone();if(useSkinning)cachedMaterial.skinning=true;if(useVertexTangents)cachedMaterial.vertexTangents=true;if(useVertexColors)cachedMaterial.vertexColors=THREE.VertexColors;if(useFlatShading)cachedMaterial.flatShading=true;if(useMorphTargets)cachedMaterial.morphTargets=true;if(useMorphNormals)cachedMaterial.morphNormals=true;this.cache.add(cacheKey,cachedMaterial);}\n",
|
||||
"material=cachedMaterial;}\n",
|
||||
"if(material.aoMap&&geometry.attributes.uv2===undefined&&geometry.attributes.uv!==undefined){console.log('THREE.GLTFLoader: Duplicating UVs to support aoMap.');geometry.addAttribute('uv2',new THREE.BufferAttribute(geometry.attributes.uv.array,2));}\n",
|
||||
"if(material.isGLTFSpecularGlossinessMaterial){mesh.onBeforeRender=extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].refreshUniforms;}\n",
|
||||
"mesh.material=material;};GLTFParser.prototype.loadMaterial=function(materialIndex){var parser=this;var json=this.json;var extensions=this.extensions;var materialDef=json.materials[materialIndex];var materialType;var materialParams={};var materialExtensions=materialDef.extensions||{};var pending=[];if(materialExtensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS]){var sgExtension=extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS];materialType=sgExtension.getMaterialType();pending.push(sgExtension.extendParams(materialParams,materialDef,parser));}else if(materialExtensions[EXTENSIONS.KHR_MATERIALS_UNLIT]){var kmuExtension=extensions[EXTENSIONS.KHR_MATERIALS_UNLIT];materialType=kmuExtension.getMaterialType();pending.push(kmuExtension.extendParams(materialParams,materialDef,parser));}else{materialType=THREE.MeshStandardMaterial;var metallicRoughness=materialDef.pbrMetallicRoughness||{};materialParams.color=new THREE.Color(1.0,1.0,1.0);materialParams.opacity=1.0;if(Array.isArray(metallicRoughness.baseColorFactor)){var array=metallicRoughness.baseColorFactor;materialParams.color.fromArray(array);materialParams.opacity=array[3];}\n",
|
||||
"if(metallicRoughness.baseColorTexture!==undefined){pending.push(parser.assignTexture(materialParams,'map',metallicRoughness.baseColorTexture));}\n",
|
||||
"materialParams.metalness=metallicRoughness.metallicFactor!==undefined?metallicRoughness.metallicFactor:1.0;materialParams.roughness=metallicRoughness.roughnessFactor!==undefined?metallicRoughness.roughnessFactor:1.0;if(metallicRoughness.metallicRoughnessTexture!==undefined){pending.push(parser.assignTexture(materialParams,'metalnessMap',metallicRoughness.metallicRoughnessTexture));pending.push(parser.assignTexture(materialParams,'roughnessMap',metallicRoughness.metallicRoughnessTexture));}}\n",
|
||||
"if(materialDef.doubleSided===true){materialParams.side=THREE.DoubleSide;}\n",
|
||||
"var alphaMode=materialDef.alphaMode||ALPHA_MODES.OPAQUE;if(alphaMode===ALPHA_MODES.BLEND){materialParams.transparent=true;}else{materialParams.transparent=false;if(alphaMode===ALPHA_MODES.MASK){materialParams.alphaTest=materialDef.alphaCutoff!==undefined?materialDef.alphaCutoff:0.5;}}\n",
|
||||
"if(materialDef.normalTexture!==undefined&&materialType!==THREE.MeshBasicMaterial){pending.push(parser.assignTexture(materialParams,'normalMap',materialDef.normalTexture));materialParams.normalScale=new THREE.Vector2(1,1);if(materialDef.normalTexture.scale!==undefined){materialParams.normalScale.set(materialDef.normalTexture.scale,materialDef.normalTexture.scale);}}\n",
|
||||
"if(materialDef.occlusionTexture!==undefined&&materialType!==THREE.MeshBasicMaterial){pending.push(parser.assignTexture(materialParams,'aoMap',materialDef.occlusionTexture));if(materialDef.occlusionTexture.strength!==undefined){materialParams.aoMapIntensity=materialDef.occlusionTexture.strength;}}\n",
|
||||
"if(materialDef.emissiveFactor!==undefined&&materialType!==THREE.MeshBasicMaterial){materialParams.emissive=new THREE.Color().fromArray(materialDef.emissiveFactor);}\n",
|
||||
"if(materialDef.emissiveTexture!==undefined&&materialType!==THREE.MeshBasicMaterial){pending.push(parser.assignTexture(materialParams,'emissiveMap',materialDef.emissiveTexture));}\n",
|
||||
"return Promise.all(pending).then(function(){var material;if(materialType===THREE.ShaderMaterial){material=extensions[EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS].createMaterial(materialParams);}else{material=new materialType(materialParams);}\n",
|
||||
"if(materialDef.name!==undefined)material.name=materialDef.name;if(material.map)material.map.encoding=THREE.sRGBEncoding;if(material.emissiveMap)material.emissiveMap.encoding=THREE.sRGBEncoding;if(material.specularMap)material.specularMap.encoding=THREE.sRGBEncoding;assignExtrasToUserData(material,materialDef);if(materialDef.extensions)addUnknownExtensionsToUserData(extensions,material,materialDef);return material;});};function addPrimitiveAttributes(geometry,primitiveDef,parser){var attributes=primitiveDef.attributes;var pending=[];function assignAttributeAccessor(accessorIndex,attributeName){return parser.getDependency('accessor',accessorIndex).then(function(accessor){geometry.addAttribute(attributeName,accessor);});}\n",
|
||||
"for(var gltfAttributeName in attributes){var threeAttributeName=ATTRIBUTES[gltfAttributeName]||gltfAttributeName.toLowerCase();if(threeAttributeName in geometry.attributes)continue;pending.push(assignAttributeAccessor(attributes[gltfAttributeName],threeAttributeName));}\n",
|
||||
"if(primitiveDef.indices!==undefined&&!geometry.index){var accessor=parser.getDependency('accessor',primitiveDef.indices).then(function(accessor){geometry.setIndex(accessor);});pending.push(accessor);}\n",
|
||||
"assignExtrasToUserData(geometry,primitiveDef);return Promise.all(pending).then(function(){return primitiveDef.targets!==undefined?addMorphTargets(geometry,primitiveDef.targets,parser):geometry;});}\n",
|
||||
"GLTFParser.prototype.loadGeometries=function(primitives){var parser=this;var extensions=this.extensions;var cache=this.primitiveCache;function createDracoPrimitive(primitive){return extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION].decodePrimitive(primitive,parser).then(function(geometry){return addPrimitiveAttributes(geometry,primitive,parser);});}\n",
|
||||
"var pending=[];for(var i=0,il=primitives.length;i<il;i++){var primitive=primitives[i];var cacheKey=createPrimitiveKey(primitive);var cached=cache[cacheKey];if(cached){pending.push(cached.promise);}else{var geometryPromise;if(primitive.extensions&&primitive.extensions[EXTENSIONS.KHR_DRACO_MESH_COMPRESSION]){geometryPromise=createDracoPrimitive(primitive);}else{geometryPromise=addPrimitiveAttributes(new THREE.BufferGeometry(),primitive,parser);}\n",
|
||||
"cache[cacheKey]={primitive:primitive,promise:geometryPromise};pending.push(geometryPromise);}}\n",
|
||||
"return Promise.all(pending);};GLTFParser.prototype.loadMesh=function(meshIndex){var parser=this;var json=this.json;var meshDef=json.meshes[meshIndex];var primitives=meshDef.primitives;var pending=[];for(var i=0,il=primitives.length;i<il;i++){var material=primitives[i].material===undefined?createDefaultMaterial():this.getDependency('material',primitives[i].material);pending.push(material);}\n",
|
||||
"return Promise.all(pending).then(function(originalMaterials){return parser.loadGeometries(primitives).then(function(geometries){var meshes=[];for(var i=0,il=geometries.length;i<il;i++){var geometry=geometries[i];var primitive=primitives[i];var mesh;var material=originalMaterials[i];if(primitive.mode===WEBGL_CONSTANTS.TRIANGLES||primitive.mode===WEBGL_CONSTANTS.TRIANGLE_STRIP||primitive.mode===WEBGL_CONSTANTS.TRIANGLE_FAN||primitive.mode===undefined){mesh=meshDef.isSkinnedMesh===true?new THREE.SkinnedMesh(geometry,material):new THREE.Mesh(geometry,material);if(mesh.isSkinnedMesh===true&&!mesh.geometry.attributes.skinWeight.normalized){mesh.normalizeSkinWeights();}\n",
|
||||
"if(primitive.mode===WEBGL_CONSTANTS.TRIANGLE_STRIP){mesh.drawMode=THREE.TriangleStripDrawMode;}else if(primitive.mode===WEBGL_CONSTANTS.TRIANGLE_FAN){mesh.drawMode=THREE.TriangleFanDrawMode;}}else if(primitive.mode===WEBGL_CONSTANTS.LINES){mesh=new THREE.LineSegments(geometry,material);}else if(primitive.mode===WEBGL_CONSTANTS.LINE_STRIP){mesh=new THREE.Line(geometry,material);}else if(primitive.mode===WEBGL_CONSTANTS.LINE_LOOP){mesh=new THREE.LineLoop(geometry,material);}else if(primitive.mode===WEBGL_CONSTANTS.POINTS){mesh=new THREE.Points(geometry,material);}else{throw new Error('THREE.GLTFLoader: Primitive mode unsupported: '+primitive.mode);}\n",
|
||||
"if(Object.keys(mesh.geometry.morphAttributes).length>0){updateMorphTargets(mesh,meshDef);}\n",
|
||||
"mesh.name=meshDef.name||('mesh_'+meshIndex);if(geometries.length>1)mesh.name+='_'+i;assignExtrasToUserData(mesh,meshDef);parser.assignFinalMaterial(mesh);meshes.push(mesh);}\n",
|
||||
"if(meshes.length===1){return meshes[0];}\n",
|
||||
"var group=new THREE.Group();for(var i=0,il=meshes.length;i<il;i++){group.add(meshes[i]);}\n",
|
||||
"return group;});});};GLTFParser.prototype.loadCamera=function(cameraIndex){var camera;var cameraDef=this.json.cameras[cameraIndex];var params=cameraDef[cameraDef.type];if(!params){console.warn('THREE.GLTFLoader: Missing camera parameters.');return;}\n",
|
||||
"if(cameraDef.type==='perspective'){camera=new THREE.PerspectiveCamera(THREE.Math.radToDeg(params.yfov),params.aspectRatio||1,params.znear||1,params.zfar||2e6);}else if(cameraDef.type==='orthographic'){camera=new THREE.OrthographicCamera(params.xmag/-2,params.xmag/2,params.ymag/2,params.ymag/-2,params.znear,params.zfar);}\n",
|
||||
"if(cameraDef.name!==undefined)camera.name=cameraDef.name;assignExtrasToUserData(camera,cameraDef);return Promise.resolve(camera);};GLTFParser.prototype.loadSkin=function(skinIndex){var skinDef=this.json.skins[skinIndex];var skinEntry={joints:skinDef.joints};if(skinDef.inverseBindMatrices===undefined){return Promise.resolve(skinEntry);}\n",
|
||||
"return this.getDependency('accessor',skinDef.inverseBindMatrices).then(function(accessor){skinEntry.inverseBindMatrices=accessor;return skinEntry;});};GLTFParser.prototype.loadAnimation=function(animationIndex){var json=this.json;var animationDef=json.animations[animationIndex];var pendingNodes=[];var pendingInputAccessors=[];var pendingOutputAccessors=[];var pendingSamplers=[];var pendingTargets=[];for(var i=0,il=animationDef.channels.length;i<il;i++){var channel=animationDef.channels[i];var sampler=animationDef.samplers[channel.sampler];var target=channel.target;var name=target.node!==undefined?target.node:target.id;var input=animationDef.parameters!==undefined?animationDef.parameters[sampler.input]:sampler.input;var output=animationDef.parameters!==undefined?animationDef.parameters[sampler.output]:sampler.output;pendingNodes.push(this.getDependency('node',name));pendingInputAccessors.push(this.getDependency('accessor',input));pendingOutputAccessors.push(this.getDependency('accessor',output));pendingSamplers.push(sampler);pendingTargets.push(target);}\n",
|
||||
"return Promise.all([Promise.all(pendingNodes),Promise.all(pendingInputAccessors),Promise.all(pendingOutputAccessors),Promise.all(pendingSamplers),Promise.all(pendingTargets)]).then(function(dependencies){var nodes=dependencies[0];var inputAccessors=dependencies[1];var outputAccessors=dependencies[2];var samplers=dependencies[3];var targets=dependencies[4];var tracks=[];for(var i=0,il=nodes.length;i<il;i++){var node=nodes[i];var inputAccessor=inputAccessors[i];var outputAccessor=outputAccessors[i];var sampler=samplers[i];var target=targets[i];if(node===undefined)continue;node.updateMatrix();node.matrixAutoUpdate=true;var TypedKeyframeTrack;switch(PATH_PROPERTIES[target.path]){case PATH_PROPERTIES.weights:TypedKeyframeTrack=THREE.NumberKeyframeTrack;break;case PATH_PROPERTIES.rotation:TypedKeyframeTrack=THREE.QuaternionKeyframeTrack;break;case PATH_PROPERTIES.position:case PATH_PROPERTIES.scale:default:TypedKeyframeTrack=THREE.VectorKeyframeTrack;break;}\n",
|
||||
"var targetName=node.name?node.name:node.uuid;var interpolation=sampler.interpolation!==undefined?INTERPOLATION[sampler.interpolation]:THREE.InterpolateLinear;var targetNames=[];if(PATH_PROPERTIES[target.path]===PATH_PROPERTIES.weights){node.traverse(function(object){if(object.isMesh===true&&object.morphTargetInfluences){targetNames.push(object.name?object.name:object.uuid);}});}else{targetNames.push(targetName);}\n",
|
||||
"var outputArray=outputAccessor.array;if(outputAccessor.normalized){var scale;if(outputArray.constructor===Int8Array){scale=1/127;}else if(outputArray.constructor===Uint8Array){scale=1/255;}else if(outputArray.constructor==Int16Array){scale=1/32767;}else if(outputArray.constructor===Uint16Array){scale=1/65535;}else{throw new Error('THREE.GLTFLoader: Unsupported output accessor component type.');}\n",
|
||||
"var scaled=new Float32Array(outputArray.length);for(var j=0,jl=outputArray.length;j<jl;j++){scaled[j]=outputArray[j]*scale;}\n",
|
||||
"outputArray=scaled;}\n",
|
||||
"for(var j=0,jl=targetNames.length;j<jl;j++){var track=new TypedKeyframeTrack(targetNames[j]+'.'+PATH_PROPERTIES[target.path],inputAccessor.array,outputArray,interpolation);if(sampler.interpolation==='CUBICSPLINE'){track.createInterpolant=function InterpolantFactoryMethodGLTFCubicSpline(result){return new GLTFCubicSplineInterpolant(this.times,this.values,this.getValueSize()/3,result);};track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline=true;}\n",
|
||||
"tracks.push(track);}}\n",
|
||||
"var name=animationDef.name!==undefined?animationDef.name:'animation_'+animationIndex;return new THREE.AnimationClip(name,undefined,tracks);});};GLTFParser.prototype.loadNode=function(nodeIndex){var json=this.json;var extensions=this.extensions;var parser=this;var meshReferences=json.meshReferences;var meshUses=json.meshUses;var nodeDef=json.nodes[nodeIndex];return(function(){if(nodeDef.isBone===true){return Promise.resolve(new THREE.Bone());}else if(nodeDef.mesh!==undefined){return parser.getDependency('mesh',nodeDef.mesh).then(function(mesh){var node;if(meshReferences[nodeDef.mesh]>1){var instanceNum=meshUses[nodeDef.mesh]++;node=mesh.clone();node.name+='_instance_'+instanceNum;node.onBeforeRender=mesh.onBeforeRender;for(var i=0,il=node.children.length;i<il;i++){node.children[i].name+='_instance_'+instanceNum;node.children[i].onBeforeRender=mesh.children[i].onBeforeRender;}}else{node=mesh;}\n",
|
||||
"if(nodeDef.weights!==undefined){node.traverse(function(o){if(!o.isMesh)return;for(var i=0,il=nodeDef.weights.length;i<il;i++){o.morphTargetInfluences[i]=nodeDef.weights[i];}});}\n",
|
||||
"return node;});}else if(nodeDef.camera!==undefined){return parser.getDependency('camera',nodeDef.camera);}else if(nodeDef.extensions&&nodeDef.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL]&&nodeDef.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL].light!==undefined){return parser.getDependency('light',nodeDef.extensions[EXTENSIONS.KHR_LIGHTS_PUNCTUAL].light);}else{return Promise.resolve(new THREE.Object3D());}}()).then(function(node){if(nodeDef.name!==undefined){node.userData.name=nodeDef.name;node.name=THREE.PropertyBinding.sanitizeNodeName(nodeDef.name);}\n",
|
||||
"assignExtrasToUserData(node,nodeDef);if(nodeDef.extensions)addUnknownExtensionsToUserData(extensions,node,nodeDef);if(nodeDef.matrix!==undefined){var matrix=new THREE.Matrix4();matrix.fromArray(nodeDef.matrix);node.applyMatrix(matrix);}else{if(nodeDef.translation!==undefined){node.position.fromArray(nodeDef.translation);}\n",
|
||||
"if(nodeDef.rotation!==undefined){node.quaternion.fromArray(nodeDef.rotation);}\n",
|
||||
"if(nodeDef.scale!==undefined){node.scale.fromArray(nodeDef.scale);}}\n",
|
||||
"return node;});};GLTFParser.prototype.loadScene=function(){function buildNodeHierachy(nodeId,parentObject,json,parser){var nodeDef=json.nodes[nodeId];return parser.getDependency('node',nodeId).then(function(node){if(nodeDef.skin===undefined)return node;var skinEntry;return parser.getDependency('skin',nodeDef.skin).then(function(skin){skinEntry=skin;var pendingJoints=[];for(var i=0,il=skinEntry.joints.length;i<il;i++){pendingJoints.push(parser.getDependency('node',skinEntry.joints[i]));}\n",
|
||||
"return Promise.all(pendingJoints);}).then(function(jointNodes){var meshes=node.isGroup===true?node.children:[node];for(var i=0,il=meshes.length;i<il;i++){var mesh=meshes[i];var bones=[];var boneInverses=[];for(var j=0,jl=jointNodes.length;j<jl;j++){var jointNode=jointNodes[j];if(jointNode){bones.push(jointNode);var mat=new THREE.Matrix4();if(skinEntry.inverseBindMatrices!==undefined){mat.fromArray(skinEntry.inverseBindMatrices.array,j*16);}\n",
|
||||
"boneInverses.push(mat);}else{console.warn('THREE.GLTFLoader: Joint "%s" could not be found.',skinEntry.joints[j]);}}\n",
|
||||
"mesh.bind(new THREE.Skeleton(bones,boneInverses),mesh.matrixWorld);}\n",
|
||||
"return node;});}).then(function(node){parentObject.add(node);var pending=[];if(nodeDef.children){var children=nodeDef.children;for(var i=0,il=children.length;i<il;i++){var child=children[i];pending.push(buildNodeHierachy(child,node,json,parser));}}\n",
|
||||
"return Promise.all(pending);});}\n",
|
||||
"return function loadScene(sceneIndex){var json=this.json;var extensions=this.extensions;var sceneDef=this.json.scenes[sceneIndex];var parser=this;var scene=new THREE.Scene();if(sceneDef.name!==undefined)scene.name=sceneDef.name;assignExtrasToUserData(scene,sceneDef);if(sceneDef.extensions)addUnknownExtensionsToUserData(extensions,scene,sceneDef);var nodeIds=sceneDef.nodes||[];var pending=[];for(var i=0,il=nodeIds.length;i<il;i++){pending.push(buildNodeHierachy(nodeIds[i],scene,json,parser));}\n",
|
||||
"return Promise.all(pending).then(function(){return scene;});};}();return GLTFLoader;})();var camera,controls,scene,renderer,tracklight;function autoFit(obj,camera,controls){const boundingBox=new THREE.Box3().setFromObject(obj);const boundingSphere=new THREE.Sphere();boundingBox.getBoundingSphere((target=boundingSphere));const scale=1.0;const angularSize=camera.fov*Math.PI/180*scale;const distanceToCamera=boundingSphere.radius/Math.tan(angularSize);const len=Math.sqrt(Math.pow(distanceToCamera,2)+\n",
|
||||
"Math.pow(distanceToCamera,2)+\n",
|
||||
"Math.pow(distanceToCamera,2));camera.position.set(len,len,len);controls.update();camera.lookAt(boundingSphere.center);controls.target.set(boundingSphere.center.x,boundingSphere.center.y,boundingSphere.center.z);camera.updateProjectionMatrix();}\n",
|
||||
"function centerControls(obj,camera,controls){const boundingBox=new THREE.Box3().setFromObject(obj);const boundingSphere=new THREE.Sphere();boundingBox.getBoundingSphere((target=boundingSphere));controls.update();controls.target.set(boundingSphere.center.x,boundingSphere.center.y,boundingSphere.center.z);}\n",
|
||||
"function init(){scene=new THREE.Scene();scene.background=new THREE.Color(0xffffff);tracklight=new THREE.DirectionalLight(0xffffff,1.75);scene.add(tracklight);base64_data="Z2xURgIAAAAkBgAAmAMAAEpTT057InNjZW5lIjogMCwgInNjZW5lcyI6IFt7Im5vZGVzIjogWzBdfV0sICJhc3NldCI6IHsidmVyc2lvbiI6ICIyLjAiLCAiZ2VuZXJhdG9yIjogImh0dHBzOi8vZ2l0aHViLmNvbS9taWtlZGgvdHJpbWVzaCJ9LCAiYWNjZXNzb3JzIjogW3siYnVmZmVyVmlldyI6IDAsICJjb21wb25lbnRUeXBlIjogNTEyNSwgImNvdW50IjogOTYsICJtYXgiOiBbMTldLCAibWluIjogWzBdLCAidHlwZSI6ICJTQ0FMQVIifSwgeyJidWZmZXJWaWV3IjogMSwgImNvbXBvbmVudFR5cGUiOiA1MTI2LCAiY291bnQiOiAyMCwgInR5cGUiOiAiVkVDMyIsICJieXRlT2Zmc2V0IjogMCwgIm1heCI6IFswLjUsIDEuMDk2MDg2NTAyMDc1MTk1MywgMC41XSwgIm1pbiI6IFstMC41LCAtMC41LCAtMC41XX1dLCAibWVzaGVzIjogW3sibmFtZSI6ICJ0cmltZXNoX2ZhaWwuc3RsIiwgInByaW1pdGl2ZXMiOiBbeyJhdHRyaWJ1dGVzIjogeyJQT1NJVElPTiI6IDF9LCAiaW5kaWNlcyI6IDAsICJtb2RlIjogNH1dfV0sICJjYW1lcmFzIjogW3sibmFtZSI6ICJjYW1lcmFfVU9TUVZaIiwgInR5cGUiOiAicGVyc3BlY3RpdmUiLCAicGVyc3BlY3RpdmUiOiB7ImFzcGVjdFJhdGlvIjogMS4zMzMzMzMzMzMzMzMzMzMzLCAieWZvdiI6IDAuNzg1Mzk4MTYzMzk3NDQ4MywgInpuZWFyIjogMC4wMX19XSwgIm5vZGVzIjogW3sibmFtZSI6ICJ3b3JsZCIsICJjaGlsZHJlbiI6IFsxXX0sIHsibmFtZSI6ICJ0cmltZXNoX2ZhaWwuc3RsX1RUVVJPUklSWFJFUCIsICJtZXNoIjogMH1dLCAiYnVmZmVycyI6IFt7ImJ5dGVMZW5ndGgiOiA2MjR9XSwgImJ1ZmZlclZpZXdzIjogW3siYnVmZmVyIjogMCwgImJ5dGVPZmZzZXQiOiAwLCAiYnl0ZUxlbmd0aCI6IDM4NH0sIHsiYnVmZmVyIjogMCwgImJ5dGVPZmZzZXQiOiAzODQsICJieXRlTGVuZ3RoIjogMjQwfV19ICAgIHACAABCSU4ABwAAAA4AAAALAAAABwAAAAkAAAAOAAAACwAAAA8AAAAMAAAACwAAAA4AAAAPAAAADAAAAAoAAAAIAAAADAAAAA8AAAAKAAAACAAAAAkAAAAHAAAACAAAAAoAAAAJAAAAAAAAABMAAAANAAAAAAAAAAUAAAATAAAADQAAAAMAAAAPAAAADQAAABMAAAADAAAADwAAABEAAAACAAAADwAAAAMAAAARAAAAAgAAAAUAAAAAAAAAAgAAABEAAAAFAAAABQAAAAMAAAATAAAABQAAABEAAAADAAAAAAAAAA0AAAAPAAAAAAAAAA8AAAACAAAAAAAAABAAAAABAAAAAAAAAAYAAAAQAAAAAQAAAAQAAAAOAAAAAQAAABAAAAAEAAAADgAAABIAAAANAAAADgAAAAQAAAASAAAADQAAAAYAAAAAAAAADQAAABIAAAAGAAAABgAAAAQAAAAQAAAABgAAABIAAAAEAAAAAAAAAAEAAAAOAAAAAAAAAA4AAAANAAAAAAAAAAAAAD8AAAAAAAAAJQAAAD8AAAC/AAAAAAAAAD8AAAA/NYbkPpBMjD81huQ+NYbkPpBMjD81huS+dnVMvXIKfz92dUy9dnVMvXIKfz92dUw9AAAAvwAAAL8AAAC/AAAAvwAAAL8AAAA/AAAAvwAAAD8AAAC/AAAAvwAAAD8AAAA/AAAAPwAAAL8AAAC/AAAAPwAAAL8AAAA/AAAAPwAAAD8AAAAAAAAAPwAAAD8AAAC/AAAAPwAAAD8AAAA/uLBWveXohT+dzeW+uLBWveXohT+dzeU+nc3lPuXohT+4sFY9nc3lPuXohT+4sFa9";;renderer=new THREE.WebGLRenderer({antialias:true});renderer.setPixelRatio(window.devicePixelRatio);renderer.setSize(window.innerWidth,window.innerHeight);document.body.appendChild(renderer.domElement);loader=new THREE.GLTFLoader();loader.load("data:text/plain;base64,"+base64_data,function(gltf){scene.add(gltf.scene);camera=gltf.cameras[0];controls=new THREE.TrackballControls(camera);controls.rotateSpeed=1.0;controls.zoomSpeed=1.2;controls.panSpeed=0.8;controls.noZoom=false;controls.noPan=false;controls.staticMoving=true;controls.dynamicDampingFactor=0.3;controls.keys=[65,83,68];controls.addEventListener("change",render);centerControls(scene,camera,controls);render();window.addEventListener("resize",onWindowResize,false);animate();});}\n",
|
||||
"function onWindowResize(){camera.aspect=window.innerWidth/window.innerHeight;camera.updateProjectionMatrix();renderer.setSize(window.innerWidth,window.innerHeight);controls.handleResize();render();}\n",
|
||||
"function animate(){requestAnimationFrame(animate);controls.update();}\n",
|
||||
"function render(){tracklight.position.copy(camera.position);renderer.render(scene,camera);}\n",
|
||||
"init();</script></body>\n",
|
||||
"</html>\" width=\"100%\" height=\"500px\" style=\"border:none;\"></iframe>"
|
||||
],
|
||||
"text/plain": [
|
||||
"<IPython.core.display.HTML object>"
|
||||
]
|
||||
},
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import trimesh\n",
|
||||
"m = trimesh.load_mesh(\"trimesh_fail.stl\")\n",
|
||||
"m.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.6.8"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
BIN
python_extrude_meshgen/trimesh_fail.stl
Normal file
BIN
python_extrude_meshgen/trimesh_fail.stl
Normal file
Binary file not shown.
Reference in New Issue
Block a user