Skip to content

Instantly share code, notes, and snippets.

@mikedh
Created September 5, 2018 03:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikedh/bc6fbf1c69ee0aff2bcdcebf169552d9 to your computer and use it in GitHub Desktop.
Save mikedh/bc6fbf1c69ee0aff2bcdcebf169552d9 to your computer and use it in GitHub Desktop.
"""
bricks.py
-------------
Fun with meshes of bricks.
"""
import trimesh
import numpy as np
def simulated_brick(face_count, extents, noise, max_iter=10):
"""
Produce a mesh that is a rectangular solid with noise
Parameters
-------------
face_count : int
Approximate number of faces desired
extents : (n,3) float
Dimensions of brick
noise : float
Magnitude of vertex noise to apply
"""
# create the mesh as a simple box
mesh = trimesh.creation.box(extents=extents)
# subdivide until we have more faces than we want
for i in range(max_iter):
if len(mesh.vertices) > face_count:
break
mesh = mesh.subdivide()
# apply tesselation and random noise
mesh = mesh.permutate.noise(noise)
return mesh
if __name__ == '__main__':
# create a simulated brick
mesh = simulated_brick(face_count=10000,
extents=[6, 12, 2],
noise=.005)
###
# We need to label faces into planar regions
# there are a lot of ways to do this but the easiest way is to
# threshold angle between faces. It works only if the mesh
# is smooth- ish and local deviations don't make things suck
# face_adjacency is indexes of mesh.faces that are adjacent
adjacency_ok = mesh.face_adjacency_angles < np.radians(70)
adjacency = mesh.face_adjacency[adjacency_ok]
# get a sequence of face indexes that are connected
components = trimesh.graph.connected_components(adjacency)
# color the face group
for c in components:
mesh.visual.face_colors[c] = trimesh.visual.random_color()
print('showing with graph method')
mesh.show(smooth=False)
###
# Another option with bricks is to apply the transform from
# the oriented bounding box, which is the minimum volume
# rectangular solid which encloses the mesh. This lets us fix
# the arbitrary frame that the mesh is presumably in and will
# mean that every face should line up nicely with an axis
# move the mesh from an arbitrary frame to its axis aligned bounds
# centered at the origin and identical to the OBB
to_origin = np.linalg.inv(mesh.bounding_box_oriented.primitive.transform)
# move the mesh
mesh.apply_transform(to_origin)
# since it's a rectangular solid we know there are 6 plane
# we might want to associate a face with
plane_normals = np.vstack((-np.eye(3),
np.eye(3)))
plane_origins = np.tile(mesh.bounds, 3).reshape((-1, 3))
# find the vertex distance for each of 6 planes
# you can do this with tiling and avoid a loop but it's more confusing
distances = []
for origin, normal in zip(plane_origins, plane_normals):
distances.append(np.dot(mesh.vertices - origin, normal))
# stack to (len(mesh.vertices), 6) array
distances = np.abs(np.column_stack(distances))
# which plane index is closest
nearest = distances.argmin(axis=1)
# the nearest index for faces
label = nearest[mesh.faces]
# a face should only be associated with one of our planes
label_ok = label.ptp(axis=1) == 0
# group the label index
groups = trimesh.grouping.group(label[:, 0])
# remove faces which have vertices associated with
# multiple OBB face planes
culled = [g[label_ok[g]] for g in groups]
# reset all face colors
mesh.visual.face_colors = [100, 100, 100, 255]
for group in culled:
# set our group to a pretty color
mesh.visual.face_colors[group] = trimesh.visual.random_color()
print('showing with nearest OBB face method')
mesh.show(smooth=False)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment