File: phiFromStl.py

package info (click to toggle)
yade 2026.1.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 34,448 kB
  • sloc: cpp: 97,645; python: 52,173; sh: 677; makefile: 162
file content (50 lines) | stat: -rw-r--r-- 3,438 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def phiFromStl(stlFile, nMin=80, extendCoeff=1):
	'''Return a (phi,grid) pair that can almost be passed to levelSetBody() (just think of turning phi numpy array into a list with .tolist()). Requires to "pip install trimesh rtree scipy". It is an alternative to using phiIniFromPts + FastMarchingMethod, which is much more computationally expensive (RAM requirements in particular may reach 10-100 of GB for a MB .stl and a usual 1e3 - 1e4 gridpoints grid) but certainly leads to less 0-values in LevelSet.distField
    :param string stlFile: full path to the .stl (file object also accepted ?). Has to conform inertial frame, it is possible to use mkStlInertial for this purpose
    :param nMin: minimum number of gridpoints discretizing the stl (just its mesh, whatever used *extendCoeff*) along an axis. The final grid will actually have a couple of more gridpoints than extendCoeff*nMin on that axis.
    :param extendCoeff: multiplicative coefficient to extend the grid beyond the mesh bounding box (if useful)
    '''
	try:
		numpy  # this one is for sure present alongside a YADE installation
	except NameError:  # we just need to be sure to have it imported
		import numpy
	try:
		import trimesh
	except ModuleNotFoundError:  # using except just for a print, not really avoiding the error
		print('****Python module trimesh is required, see the doc****')
		import trimesh  # we still repeat the same command for the error to appear to the user
	try:
		import rtree
	except ModuleNotFoundError:  # same philosophy as with trimesh
		print('****Python module rtree is required, see the doc****')
		import rtree
	mesh = trimesh.load_mesh(stlFile, process=False)  # process=False (as a trimesh.Trimesh attribute) for avoiding automatic merge of vertices
	aabb = mesh.bounding_box
	min_corner, max_corner = aabb.vertices.min(axis=0), aabb.vertices.max(axis=0)
	xmin, ymin, zmin = extendCoeff * min_corner
	xmax, ymax, zmax = extendCoeff * max_corner

	# Now copying-pasting the grid construction of phiIniFromPts (TODO: make a separate function and make the grid inertial ie with center of mass as a gridpoint)
	lxPt = xmax - xmin
	lyPt = ymax - ymin
	lzPt = zmax - zmin
	lMinPt = min(lxPt, lyPt, lzPt)

	# Deduced grid properties:
	spac = lMinPt / (nMin * extendCoeff)
	Nx, Ny, Nz = ceil(lxPt / spac), ceil(lyPt / spac), ceil(lzPt / spac)  # these are gridpoints strictly fitting the cloud
	nGP = Vector3i(Nx, Ny, Nz) + Vector3i(2, 2, 2)  # these will be the points of the actual RegularGrid, extended by one gp on each side:
	gridMin = Vector3(xmin, ymin, zmin) - spac * Vector3(1, 1, 1)
	grid = RegularGrid(min=gridMin, spacing=spac, nGP=nGP)

	# Preparing trimesh job:
	gpArray = numpy.array([[[grid.gridPoint(i, j, k) for k in range(nGP[2])] for j in range(nGP[1])] for i in range(nGP[0])])
	gpArrayReshaped = gpArray.reshape(
	        -1, 3
	)  # converting in correct format (with as many lines as necessary and 3 columns for x,y,z) as expected by trimesh below
	# Running trimesh to conclude:
	phiValues = -trimesh.proximity.signed_distance(
	        mesh, gpArrayReshaped
	)  # "-" for being positive outside, negative inside; with proximity.closest_point as the under-the-hood-instrumental function
	# phiValues is now a 1D-array with nGP values, we will convert its dimensions below
	return (phiValues.reshape(gpArray.shape[:-1]), grid)  # phiValues transformed to a 3D array (conforming gridpoints gpArray in terms of shape)