From d0825a136ff65b36069ff8b078b9fd97adeed0df Mon Sep 17 00:00:00 2001 From: Anthony LaTorre Date: Wed, 1 Jun 2011 17:00:37 -0400 Subject: first step towards moving to a new mesh/solid/geometry structure --- detectors/lbne.py | 5 ++- geometry.py | 126 +++++++++++++++++------------------------------------- mesh.py | 113 ++++++++++++++++++++++++++++++++++++++++++++++++ solid.py | 58 +++++++++++++++++++++++++ src/intersect.h | 6 +-- stl.py | 43 ------------------- transform.py | 18 +++++--- view.py | 12 ++++-- 8 files changed, 239 insertions(+), 142 deletions(-) create mode 100644 mesh.py create mode 100644 solid.py delete mode 100644 stl.py diff --git a/detectors/lbne.py b/detectors/lbne.py index a9136dd..81f5dd7 100644 --- a/detectors/lbne.py +++ b/detectors/lbne.py @@ -10,9 +10,10 @@ dir = os.path.split(os.path.realpath(__file__))[0] sys.path.append(dir + '/..') import models -from stl import read_stl +from mesh import * from transform import rotate -from geometry import Geometry, Solid +from solid import Solid +from geometry import Geometry from materials import glass, h2o endcap_spacing = .485 diff --git a/geometry.py b/geometry.py index f50fe89..d1bec2a 100644 --- a/geometry.py +++ b/geometry.py @@ -48,103 +48,57 @@ def morton_order(mesh, bits): return interleave(mean_positions, bits) -class Solid(object): - """ - Object which stores a closed triangle mesh associated with a physically - distinct object. - - Args: - - mesh, array - A closed triangle mesh. - - material1, Material - The material inside within the mesh. - - material2, Material - The material outside the mesh. - - surface1, Surface, - The surface on the inside of the mesh. - - surface2, Surface, - The surface on the outside of the mesh. - - .. warning:: - It is possible to define logically inconsistent geometries unless - you are careful. For example, solid A may define its inside material - as water but contain solid B which defines its outside material as air. - In this case, a photon traveling out of solid B will reflect/refract - assuming it's going into air, but upon reaching solid A's boundary will - calculate attenuation factors assuming it just traveled through water. - """ - def __init__(self, mesh, material1, material2, \ - surface1=None, surface2=None): - if len(mesh.shape) != 3 or mesh.shape[1] != 3 or mesh.shape[2] != 3: - raise Exception('shape mismatch; mesh must be a triangle mesh') +class Geometry(object): + def __init__(self, solids=[]): + self.solids = solids - self.mesh = mesh - self.material1 = material1 - self.material2 = material2 - self.surface1 = surface1 - self.surface2 = surface2 + def add_solid(self, solid): + self.solids.append(solid) - def __len__(self): - return self.mesh.shape[0] + def build(self, bits=8): + self.mesh = np.concatenate([solid.build() for solid in self.solids]) -class Geometry(object): - """Object which stores the global mesh for a geometry.""" + zvalues_mesh = morton_order(self.mesh, bits) + reorder = np.argsort(zvalues_mesh) + zvalues_mesh = zvalues_mesh[reorder] - def __init__(self): - self.solids = [] - self.materials = [] - self.surfaces = [] + if (np.diff(zvalues_mesh) < 0).any(): + raise Exception('zvalues_mesh out of order.') - def add_solid(self, solid): - """Add a solid to the geometry.""" - self.solids.append(solid) + self.mesh = self.mesh[reorder] - if solid.material1 not in self.materials: - self.materials.append(solid.material1) + material1 = np.concatenate([solid.material1 for solid in self.solids]) + material2 = np.concatenate([solid.material2 for solid in self.solids]) + + self.materials = \ + list(np.unique(np.concatenate([material1, material2]))) - if solid.material2 not in self.materials: - self.materials.append(solid.material2) + self.material1_index = np.empty(len(self.mesh), dtype=np.uint32) + self.material2_index = np.empty(len(self.mesh), dtype=np.uint32) - if solid.surface1 not in self.surfaces: - self.surfaces.append(solid.surface1) + for i, material in enumerate(material1[reorder]): + self.material1_index[i] = self.materials.index(material) - if solid.surface2 not in self.surfaces: - self.surfaces.append(solid.surface1) + for i, material in enumerate(material2[reorder]): + self.material2_index[i] = self.materials.index(material) - def build(self, bits=8): - """Build the bounding volume hierarchy of the geometry.""" - mesh = np.concatenate([solid.mesh for solid in self.solids]) - - # lookup solid/material/surface index from triangle index - solid_index = \ - np.concatenate([np.tile(self.solids.index(solid), \ - len(solid)) for solid in self.solids]) - material1_index = \ - np.concatenate([np.tile(self.materials.index(solid.material1), \ - len(solid)) for solid in self.solids]) - material2_index = \ - np.concatenate([np.tile(self.materials.index(solid.material2), \ - len(solid)) for solid in self.solids]) - surface1_index = \ - np.concatenate([np.tile(self.surfaces.index(solid.surface1), \ - len(solid)) for solid in self.solids]) - surface2_index = \ - np.concatenate([np.tile(self.surfaces.index(solid.surface2), \ - len(solid)) for solid in self.solids]) - - zvalues_mesh = morton_order(mesh, bits) - reorder = np.argsort(zvalues_mesh) - zvalues_mesh = zvalues_mesh[reorder] + surface1 = np.concatenate([solid.surface1 for solid in self.solids]) + surface2 = np.concatenate([solid.surface2 for solid in self.solids]) - if (np.diff(zvalues_mesh) < 0).any(): - raise Exception('zvalues_mesh out of order') - - self.mesh = mesh[reorder] - self.solid_index = solid_index[reorder] - self.material1_index = material1_index[reorder] - self.material2_index = material2_index[reorder] - self.surface1_index = surface1_index[reorder] - self.surface2_index = surface2_index[reorder] + self.surfaces = list(np.unique(np.concatenate([surface1, surface2]))) + + self.surface1_index = np.empty(len(self.mesh), dtype=np.uint32) + self.surface2_index = np.empty(len(self.mesh), dtype=np.uint32) + + for i, surface in enumerate(surface1[reorder]): + self.surface1_index[i] = self.surfaces.index(surface) + + for i, surface in enumerate(surface2[reorder]): + self.surface2_index[i] = self.surfaces.index(surface) + + self.solid_index = \ + np.concatenate([np.tile(i, len(solid.mesh)) \ + for i in range(len(self.solids))])[reorder] unique_zvalues = np.unique(zvalues_mesh) zvalues = np.empty(unique_zvalues.size, dtype=np.uint64) diff --git a/mesh.py b/mesh.py new file mode 100644 index 0000000..a4b9048 --- /dev/null +++ b/mesh.py @@ -0,0 +1,113 @@ +import numpy as np +import string +import struct + +class Mesh(object): + def __init__(self, vertices, triangles): + vertices = np.asarray(vertices, dtype=float) + triangles = np.asarray(triangles, dtype=int) + + if len(vertices.shape) != 2 or vertices.shape[1] != 3: + raise ValueError('shape mismatch') + + if len(triangles.shape) != 2 or triangles.shape[1] != 3: + raise ValueError('shape mismatch') + + if (triangles < 0).any(): + raise ValueError('indices in `triangles` must be positive.') + + if (triangles >= len(vertices)).any(): + raise ValueError('indices in `triangles` must be less than the ' + 'length of the vertex array.') + + self.vertices = vertices + self.triangles = triangles + + def build(self): + return self.vertices[self.triangles] + + def __add__(self, other): + vertices = np.concatenate((self.vertices, other.vertices)) + triangles = np.concatenate((self.triangles, other.triangles+len(self.vertices))) + + return Mesh(vertices, triangles) + + def __len__(self): + return len(self.triangles) + +def mesh_from_stl(filename): + f = open(filename) + buf = f.read(200) + f.close() + + for char in buf: + if char not in string.printable: + return mesh_from_binary_stl(filename) + + return mesh_from_ascii_stl(filename) + +def mesh_from_ascii_stl(filename): + f = open(filename) + + vertices = [] + triangles = [] + vertex_map = {} + + while True: + line = f.readline() + + if line == '': + break + + if not line.strip().startswith('vertex'): + continue + + triangle = [None]*3 + for i in range(3): + vertex = tuple([float(s) for s in line.strip().split()[1:]]) + + if vertex not in vertex_map: + vertices.append(vertex) + vertex_map[vertex] = len(vertices) - 1 + + triangle[i] = vertex_map[vertex] + + if i < 3: + line = f.readline() + + triangles.append(triangle) + + f.close() + + return Mesh(np.array(vertices), np.array(triangles, dtype=np.uint32)) + +def mesh_from_binary_stl(filename): + f = open(filename) + + vertices = [] + triangles = [] + vertex_map = {} + + f.read(80) + ntriangles = struct.unpack(' 1.0f) return false; float u2 = ((m.a12*m.a20 - m.a10*m.a22)*b.x + (m.a00*m.a22 - m.a02*m.a20)*b.y + (m.a02*m.a10 - m.a00*m.a12)*b.z)/determinant; - if (u2 < 0.0f) + if (u2 < 0.0f || u2 > 1.0f) return false; float u3 = ((m.a10*m.a21 - m.a11*m.a20)*b.x + (m.a01*m.a20 - m.a00*m.a21)*b.y + (m.a00*m.a11 - m.a01*m.a10)*b.z)/determinant; - if (u3 < 0.0f || (1-u1-u2) < 0.0f) + if (u3 < 0.0f || (1.0f-u1-u2) < 0.0f) return false; distance = u3; diff --git a/stl.py b/stl.py deleted file mode 100644 index a458708..0000000 --- a/stl.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import string -import struct - -def read_stl(filename): - """Return a triangle mesh from `filename`.""" - f = open(filename) - buf = f.read(200) - f.close() - - for char in buf: - if char not in string.printable: - return read_binary_stl(filename) - - return read_ascii_stl(filename) - -def read_ascii_stl(filename): - f = open(filename) - - vertex = [] - for line in f: - if not line.strip().startswith('vertex'): - continue - vertex.append([float(s) for s in line.strip().split()[1:]]) - - f.close() - return np.array(vertex).reshape(len(vertex)//3,3,3) - -def read_binary_stl(filename): - f = open(filename) - - f.read(80) - triangles = struct.unpack('