lavavu-osmesa 1.8.43__cp311-cp311-manylinux_2_24_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. lavavu/LavaVuPython.py +578 -0
  2. lavavu/_LavaVuPython.cpython-311-x86_64-linux-gnu.so +0 -0
  3. lavavu/__init__.py +15 -0
  4. lavavu/__main__.py +12 -0
  5. lavavu/amalgamate.py +11 -0
  6. lavavu/aserver.py +357 -0
  7. lavavu/control.py +1699 -0
  8. lavavu/convert.py +853 -0
  9. lavavu/dict.json +2485 -0
  10. lavavu/font.bin +0 -0
  11. lavavu/html/LavaVu-amalgamated.css +282 -0
  12. lavavu/html/OK-min.js +99 -0
  13. lavavu/html/baseviewer.js +307 -0
  14. lavavu/html/control.css +104 -0
  15. lavavu/html/control.js +335 -0
  16. lavavu/html/dat-gui-light-theme.css +68 -0
  17. lavavu/html/dat.gui.min.js +2 -0
  18. lavavu/html/draw.js +2259 -0
  19. lavavu/html/drawbox.js +1030 -0
  20. lavavu/html/emscripten-template.js +184 -0
  21. lavavu/html/emscripten.css +92 -0
  22. lavavu/html/favicon.ico +0 -0
  23. lavavu/html/gl-matrix-min.js +47 -0
  24. lavavu/html/gui.css +25 -0
  25. lavavu/html/menu.js +609 -0
  26. lavavu/html/server.js +226 -0
  27. lavavu/html/stats.min.js +5 -0
  28. lavavu/html/styles.css +58 -0
  29. lavavu/html/webview-template.html +43 -0
  30. lavavu/html/webview.html +43 -0
  31. lavavu/lavavu.py +5635 -0
  32. lavavu/points.py +191 -0
  33. lavavu/server.py +343 -0
  34. lavavu/shaders/default.frag +20 -0
  35. lavavu/shaders/default.vert +17 -0
  36. lavavu/shaders/fontShader.frag +25 -0
  37. lavavu/shaders/fontShader.vert +18 -0
  38. lavavu/shaders/lineShader.frag +43 -0
  39. lavavu/shaders/lineShader.vert +28 -0
  40. lavavu/shaders/pointShader.frag +132 -0
  41. lavavu/shaders/pointShader.vert +57 -0
  42. lavavu/shaders/triShader.frag +170 -0
  43. lavavu/shaders/triShader.vert +55 -0
  44. lavavu/shaders/volumeShader.frag +463 -0
  45. lavavu/shaders/volumeShader.vert +5 -0
  46. lavavu/tracers.py +124 -0
  47. lavavu/vutils.py +213 -0
  48. lavavu_osmesa-1.8.43.dist-info/LICENSE.md +179 -0
  49. lavavu_osmesa-1.8.43.dist-info/METADATA +33 -0
  50. lavavu_osmesa-1.8.43.dist-info/RECORD +138 -0
  51. lavavu_osmesa-1.8.43.dist-info/WHEEL +5 -0
  52. lavavu_osmesa-1.8.43.dist-info/entry_points.txt +3 -0
  53. lavavu_osmesa-1.8.43.dist-info/top_level.txt +1 -0
  54. lavavu_osmesa.libs/libLLVM-3-6d00db57.8.so.1 +0 -0
  55. lavavu_osmesa.libs/libOSMesa-29f606eb.so.8.0.0 +0 -0
  56. lavavu_osmesa.libs/libXau-6ab8808d.so.6.0.0 +0 -0
  57. lavavu_osmesa.libs/libXdmcp-911ecd1c.so.6.0.0 +0 -0
  58. lavavu_osmesa.libs/libXfixes-af4baa9b.so.3.1.0 +0 -0
  59. lavavu_osmesa.libs/libavcodec-10cacdd4.so.57.64.101 +0 -0
  60. lavavu_osmesa.libs/libavformat-afa92877.so.57.56.101 +0 -0
  61. lavavu_osmesa.libs/libavutil-837eb790.so.55.34.101 +0 -0
  62. lavavu_osmesa.libs/libbluray-0b5d9dbd.so.1.10.0 +0 -0
  63. lavavu_osmesa.libs/libbsd-4a4ec721.so.0.8.3 +0 -0
  64. lavavu_osmesa.libs/libbz2-9ebec8ea.so.1.0.4 +0 -0
  65. lavavu_osmesa.libs/libcairo-4217ca50.so.2.11400.8 +0 -0
  66. lavavu_osmesa.libs/libchromaprint-c4f82352.so.1.4.2 +0 -0
  67. lavavu_osmesa.libs/libcom_err-b4bd5c72.so.2.1 +0 -0
  68. lavavu_osmesa.libs/libcrystalhd-e4ea0de0.so.3.6 +0 -0
  69. lavavu_osmesa.libs/libdrm-28f5b5e7.so.2.4.0 +0 -0
  70. lavavu_osmesa.libs/libedit-7f8577df.so.2.0.55 +0 -0
  71. lavavu_osmesa.libs/libffi-1c6807d3.so.6.0.4 +0 -0
  72. lavavu_osmesa.libs/libfontconfig-e9a06300.so.1.8.0 +0 -0
  73. lavavu_osmesa.libs/libfreetype-851758a3.so.6.12.3 +0 -0
  74. lavavu_osmesa.libs/libgcrypt-0005395c.so.20.1.6 +0 -0
  75. lavavu_osmesa.libs/libglapi-cf95372b.so.0.0.0 +0 -0
  76. lavavu_osmesa.libs/libgme-5f850ce8.so.0.6.0 +0 -0
  77. lavavu_osmesa.libs/libgmp-742f5e74.so.10.3.2 +0 -0
  78. lavavu_osmesa.libs/libgnutls-1ec2cd13.so.30.13.1 +0 -0
  79. lavavu_osmesa.libs/libgomp-ce6cf6a9.so.1.0.0 +0 -0
  80. lavavu_osmesa.libs/libgpg-error-24781f22.so.0.21.0 +0 -0
  81. lavavu_osmesa.libs/libgsm-aa736f52.so.1.0.12 +0 -0
  82. lavavu_osmesa.libs/libgssapi_krb5-e296a08d.so.2.2 +0 -0
  83. lavavu_osmesa.libs/libhogweed-9d325a8d.so.4.3 +0 -0
  84. lavavu_osmesa.libs/libicudata-79cf9efa.so.57.1 +0 -0
  85. lavavu_osmesa.libs/libicui18n-03536ef3.so.57.1 +0 -0
  86. lavavu_osmesa.libs/libicuuc-5743fca1.so.57.1 +0 -0
  87. lavavu_osmesa.libs/libidn-fd653b64.so.11.6.16 +0 -0
  88. lavavu_osmesa.libs/libjbig-b30cd8bd.so.0 +0 -0
  89. lavavu_osmesa.libs/libjpeg-8afa139c.so.62.2.0 +0 -0
  90. lavavu_osmesa.libs/libk5crypto-93afd15e.so.3.1 +0 -0
  91. lavavu_osmesa.libs/libkeyutils-46318358.so.1.5 +0 -0
  92. lavavu_osmesa.libs/libkrb5-061fe33b.so.3.3 +0 -0
  93. lavavu_osmesa.libs/libkrb5support-86ac49ad.so.0.1 +0 -0
  94. lavavu_osmesa.libs/liblzma-9c0610aa.so.5.2.2 +0 -0
  95. lavavu_osmesa.libs/libmp3lame-249ae4b0.so.0.0.0 +0 -0
  96. lavavu_osmesa.libs/libmpg123-13a39b0e.so.0.42.3 +0 -0
  97. lavavu_osmesa.libs/libncurses-09dfda50.so.5.9 +0 -0
  98. lavavu_osmesa.libs/libnettle-2482db45.so.6.3 +0 -0
  99. lavavu_osmesa.libs/libnuma-c8473f23.so.1.0.0 +0 -0
  100. lavavu_osmesa.libs/libogg-b6ceea65.so.0.8.2 +0 -0
  101. lavavu_osmesa.libs/libopenjp2-a0c5d12e.so.2.1.2 +0 -0
  102. lavavu_osmesa.libs/libopenmpt-58b855da.so.0.0.20 +0 -0
  103. lavavu_osmesa.libs/libopus-37a3229e.so.0.5.3 +0 -0
  104. lavavu_osmesa.libs/libp11-kit-d06ac4a7.so.0.2.0 +0 -0
  105. lavavu_osmesa.libs/libpixman-1-7369dbb3.so.0.34.0 +0 -0
  106. lavavu_osmesa.libs/libpng16-121b9de7.so.16.28.0 +0 -0
  107. lavavu_osmesa.libs/libshine-b48eced9.so.3.0.1 +0 -0
  108. lavavu_osmesa.libs/libsnappy-1125e350.so.1.3.0 +0 -0
  109. lavavu_osmesa.libs/libsoxr-e0d4d3e4.so.0.1.1 +0 -0
  110. lavavu_osmesa.libs/libspeex-6f258c6c.so.1.5.0 +0 -0
  111. lavavu_osmesa.libs/libssh-gcrypt-90c7dd19.so.4.5.1 +0 -0
  112. lavavu_osmesa.libs/libswresample-cb1bf771.so.2.3.100 +0 -0
  113. lavavu_osmesa.libs/libswscale-876dcddb.so.4.2.100 +0 -0
  114. lavavu_osmesa.libs/libtasn1-44938221.so.6.5.3 +0 -0
  115. lavavu_osmesa.libs/libtheoradec-02a81176.so.1.1.4 +0 -0
  116. lavavu_osmesa.libs/libtheoraenc-b508ccf1.so.1.1.2 +0 -0
  117. lavavu_osmesa.libs/libtiff-e2a5092b.so.5.2.6 +0 -0
  118. lavavu_osmesa.libs/libtinfo-33626a82.so.5.9 +0 -0
  119. lavavu_osmesa.libs/libtwolame-6fcc2d32.so.0.0.0 +0 -0
  120. lavavu_osmesa.libs/libva-0f7a824e.so.1.3904.0 +0 -0
  121. lavavu_osmesa.libs/libva-drm-178eb7ac.so.1.3904.0 +0 -0
  122. lavavu_osmesa.libs/libva-x11-d220c497.so.1.3904.0 +0 -0
  123. lavavu_osmesa.libs/libvdpau-0391b780.so.1.0.0 +0 -0
  124. lavavu_osmesa.libs/libvorbis-378bad15.so.0.4.8 +0 -0
  125. lavavu_osmesa.libs/libvorbisenc-d605e0e1.so.2.0.11 +0 -0
  126. lavavu_osmesa.libs/libvorbisfile-5692671f.so.3.3.7 +0 -0
  127. lavavu_osmesa.libs/libvpx-560f7a88.so.4.1.0 +0 -0
  128. lavavu_osmesa.libs/libwavpack-8339806a.so.1.2.0 +0 -0
  129. lavavu_osmesa.libs/libwebp-63152597.so.6.0.2 +0 -0
  130. lavavu_osmesa.libs/libwebpmux-38ab8eb3.so.2.0.2 +0 -0
  131. lavavu_osmesa.libs/libx264-1c898326.so.148 +0 -0
  132. lavavu_osmesa.libs/libx265-51947861.so.95 +0 -0
  133. lavavu_osmesa.libs/libxcb-ffedb2d4.so.1.1.0 +0 -0
  134. lavavu_osmesa.libs/libxcb-render-f7692d1d.so.0.0.0 +0 -0
  135. lavavu_osmesa.libs/libxcb-shm-5702672e.so.0.0.0 +0 -0
  136. lavavu_osmesa.libs/libxml2-b8b306ab.so.2.9.4 +0 -0
  137. lavavu_osmesa.libs/libxvidcore-a773eded.so.4.3 +0 -0
  138. lavavu_osmesa.libs/libzvbi-2db02ce4.so.0.13.2 +0 -0
lavavu/convert.py ADDED
@@ -0,0 +1,853 @@
1
+ from __future__ import print_function
2
+ """
3
+ Warning! EXPERIMENTAL:
4
+ these features and functions are under development, will have bugs,
5
+ and may be heavily modified in the future
6
+
7
+ Tools for converting between 3D data types
8
+
9
+ - Points to Volume
10
+ - Triangles to OBJ file
11
+ """
12
+ import numpy
13
+ import os
14
+ import sys
15
+
16
+ def try_import(module_name, second_try=False):
17
+ """
18
+ Attempts to import a module, then runs pip install if not found and attempts again
19
+ """
20
+ import importlib
21
+ try:
22
+ m = importlib.import_module(module_name)
23
+ globals()[module_name] = m
24
+ return m
25
+ except (ImportError) as e:
26
+ if not second_try:
27
+ import subprocess
28
+ subprocess.check_call([sys.executable, "-m", "pip", "install", module_name])
29
+ return try_import(module_name, True)
30
+ else:
31
+ raise(e)
32
+
33
+ def min_max_range(verts):
34
+ """
35
+ Get bounding box from a list of vertices
36
+ returns (min, max, range)
37
+ """
38
+ vmin = numpy.min(verts, axis=0)
39
+ vmax = numpy.max(verts, axis=0)
40
+ vrange = vmax - vmin
41
+ print("Bounding box ", (vmin, vmax), "Range ", vrange)
42
+ return (vmin, vmax, vrange)
43
+
44
+ def default_sample_grid(vrange, res=8):
45
+ """Calculate sample grid fine enough to capture point details
46
+ resolution of smallest dimension will be minres elements
47
+ """
48
+ #If provided a full resolution, use that, otherwise will be interpreted as min res
49
+ if isinstance(res, (list,tuple)):
50
+ if len(list(res)) == 3:
51
+ return list(res)
52
+ res = res[0]
53
+ #Use bounding box range min
54
+ minr = numpy.min(vrange)
55
+ #res parameter is minimum resolution
56
+ factor = float(res) / float(minr)
57
+ RES = [int(factor*(vrange[0])), int(factor*(vrange[1])), int(factor*(vrange[2]))]
58
+ print("Sample grid RES:",RES)
59
+ return RES
60
+
61
+ def points_to_volume(verts, res=8, kdtree=False, normed=False, clamp=None, boundingbox=None):
62
+ """
63
+ Convert object vertices to a volume by interpolating points to a grid
64
+
65
+ - Vertex data only is used, so treated as a point cloud
66
+ - Can also be used to sample an irregular grid to a regular so volume render matches the grid dimensions
67
+ - Result is a density field that can be volume rendered
68
+
69
+ Default is to use numpy.histogramdd method, pass kdtree=True to use scipy.spatial.KDTree
70
+
71
+ TODO: support colour data too, converted density field becomes alpha channel
72
+
73
+ Returns
74
+ -------
75
+ values: numpy array of float32
76
+ The converted density field
77
+ boundingbox : 2,3
78
+ the minimum 3d vertex of the bounding box
79
+ the maximum 3d vertex of the bounding box
80
+
81
+ """
82
+ if kdtree:
83
+ return points_to_volume_tree(verts, res)
84
+ else:
85
+ return points_to_volume_histogram(verts, res, normed, clamp, boundingbox)
86
+
87
+ def points_to_volume_histogram(verts, res=8, normed=False, clamp=None, boundingbox=None):
88
+ """
89
+ Using numpy.histogramdd to create 3d histogram volume grid
90
+ (Easily the fastest, but less control over output)
91
+ """
92
+ #Reshape to 3d vertices
93
+ verts = verts.reshape((-1,3))
94
+
95
+ #Get bounding box of swarm
96
+ vmin, vmax, vrange = min_max_range(verts)
97
+
98
+ #Minimum resolution to get ok sampling
99
+ if boundingbox is not None:
100
+ vmin = boundingbox[0]
101
+ vmax = boundingbox[1]
102
+ vrange = numpy.array(boundingbox[1]) - numpy.array(boundingbox[0])
103
+ RES = default_sample_grid(vrange, res)
104
+
105
+ #H, edges = numpy.histogramdd(verts, bins=RES)
106
+ if boundingbox is None:
107
+ H, edges = numpy.histogramdd(verts, bins=RES, normed=normed) #density=True for newer numpy
108
+ else:
109
+ rg = ((vmin[0], vmax[0]), (vmin[1], vmax[1]), (vmin[2], vmax[2])) #provide bounding box as range
110
+ H, edges = numpy.histogramdd(verts, bins=RES, range=rg, normed=normed) #density=True for newer numpy
111
+
112
+ #Reverse ordering X,Y,Z to Z,Y,X for volume data
113
+ values = H.transpose()
114
+
115
+ #Clamp [0,0.1] - optional (for a more binary output, points vs no points)
116
+ if clamp is not None:
117
+ values = numpy.clip(values, a_min=clamp[0], a_max=clamp[1])
118
+
119
+ #Normalise [0,1] if using probability density
120
+ if normed:
121
+ values = values / numpy.max(values)
122
+
123
+ return (values, vmin, vmax)
124
+
125
+ def points_to_volume_tree(verts, res=8):
126
+ """
127
+ Using scipy.spatial.KDTree to find nearest points on grid
128
+
129
+ Much slower, but more control
130
+
131
+ TODO: control parameters
132
+ """
133
+ #Reshape to 3d vertices
134
+ verts = verts.reshape((-1,3))
135
+
136
+ #Get bounding box of swarm
137
+ lmin, lmax, lrange = min_max_range(verts)
138
+ print("Volume bounds: ",lmin.tolist(), lmax.tolist(), lrange.tolist())
139
+
140
+ #Push out the edges a bit, will create a smoother boundary when we filter
141
+ #lmin -= 0.1*lrange
142
+ #lmax += 0.1*lrange
143
+ values = numpy.full(shape=(verts.shape[0]), dtype=numpy.float32, fill_value=1.0)
144
+
145
+ #Minimum resolution to get ok sampling
146
+ RES = default_sample_grid(lrange, res)
147
+
148
+ #Push out the edges a bit, will create a smoother boundary when we filter
149
+ cell = lrange / RES #Cell size
150
+ print("CELL:",cell)
151
+ lmin -= 2.0*cell
152
+ lmax += 2.0*cell
153
+
154
+ x = numpy.linspace(lmin[0], lmax[0] , RES[0])
155
+ y = numpy.linspace(lmin[1], lmax[1] , RES[1])
156
+ z = numpy.linspace(lmin[2], lmax[2] , RES[2])
157
+ print(x.shape, y.shape, z.shape)
158
+ Z, Y, X = numpy.meshgrid(z, y, x, indexing='ij') #ij=matrix indexing, xy=cartesian
159
+ XYZ = numpy.vstack((X,Y,Z)).reshape([3, -1]).T
160
+
161
+ print(lmin,lmax, XYZ.shape)
162
+
163
+ #KDtree for finding nearest neighbour points
164
+ from scipy import spatial
165
+ #sys.setrecursionlimit(1000)
166
+ #tree = spatial.KDTree(XYZ)
167
+ print("Building KDTree")
168
+ tree = spatial.cKDTree(XYZ)
169
+
170
+ #Outside distance to apply to grid points
171
+ MAXDIST = max(lrange) / max(RES) #Max cell size diagonal
172
+ print("Tree query, maxdist:",MAXDIST,max(lrange))
173
+
174
+ #Query all points, result is tuple: distances, indices
175
+ distances, indices = tree.query(verts, k=1) #Just get a single nearest neighbour for now
176
+
177
+ print("Calculate distances")
178
+ #Convert distances to [0,1] where 1=on grid and <= 0 is outside max range
179
+ distances = (MAXDIST - distances) / MAXDIST
180
+ print("Zero out of range distances")
181
+ distances *= (distances>0) #Zero negative elements by multiplication in-place
182
+
183
+ #Add the distances to the values at their nearest grid point indices
184
+ print("Compose distance field")
185
+ values = numpy.zeros(shape=(XYZ.shape[0]), dtype=numpy.float32)
186
+ values[indices] += distances
187
+ #Clip value max
188
+ print("Clip distance field")
189
+ values = values.clip(max=1.0)
190
+
191
+ #Reshape to actual grid dims Z,Y,X... (not required but allows slicing)
192
+ XYZ = XYZ.reshape(RES[::-1] + [3])
193
+ values = values.reshape(RES[::-1])
194
+ print(XYZ.shape, values.shape)
195
+
196
+ return (values, lmin, lmax)
197
+
198
+
199
+ def points_to_volume_3D(vol, objects, res=8, kdtree=False, blur=0, pad=None, normed=False, clamp=None):
200
+ """
201
+ Interpolate points to grid and load into passed volume object
202
+
203
+ Given a list of objects and a volume object, convert a point cloud
204
+ from another object (or objects - list is supported) into a volume using
205
+ points_to_volume()
206
+ """
207
+ lv = vol.parent #Get the viewer from passed object
208
+ lv.hide(objects) #Hide the converted objects
209
+
210
+ #Get vertices from lavavu objects
211
+ pverts, bb_all = lv.get_all_vertices(objects)
212
+
213
+ #blur = False
214
+ #Use bounding box of full model?
215
+ #vmin, vmax, vrange = min_max_range([lv["min"], lv["max"]])
216
+ #bb_all == (vmin, vmax)
217
+ vdata, vmin, vmax = points_to_volume(pverts, res, kdtree, normed, clamp, bb_all)
218
+
219
+ if blur > 0:
220
+ if pad == None:
221
+ pad = int(blur*2)
222
+ if pad > 0:
223
+ print("Pad edges before blur", pad)
224
+ vdata = numpy.pad(vdata, pad, mode='constant')
225
+ print("Filter/blur distance field, sigma=%d" % blur)
226
+ from scipy.ndimage.filters import gaussian_filter
227
+ values = gaussian_filter(vdata, sigma=blur) #, mode='nearest')
228
+ else:
229
+ values = vdata #Need extra space at edges to use blur, so skip, or use pad()
230
+
231
+ print(numpy.min(values), numpy.max(values))
232
+ print(numpy.min(values), numpy.max(values))
233
+
234
+ vol.values(values)
235
+ vol.vertices((vmin, vmax))
236
+
237
+ def points_to_volume_4D(vol, objects, res=8, kdtree=False, blur=False, normed=False, clamp=None):
238
+ """
239
+ Interpolate points to grid at each timestep
240
+
241
+ Given a list of objects and a volume object, convert a time-varying point cloud
242
+ from another object (or objects - list is supported) into a volume using
243
+ points_to_volume_3D()
244
+ """
245
+ lv = vol.parent #Get the viewer from passed object
246
+
247
+ for step in lv.steps:
248
+ print("TIMESTEP:",step)
249
+ lv.timestep(step)
250
+
251
+ points_to_volume_3D(vol, objects, res, kdtree, blur, normed, clamp)
252
+
253
+ def colour2rgb(c):
254
+ return [c & 255, (c >> 8) & 255, (c >> 16) & 255]
255
+
256
+ def colour2hex(rgb):
257
+ def padhex2(i):
258
+ s = hex(int(i))
259
+ return s[2:].zfill(2)
260
+ return "#" + padhex2(rgb[0]) + padhex2(rgb[1]) + padhex2(rgb[2])
261
+
262
+ def _get_objects(source):
263
+ """
264
+ Returns a list of objects
265
+
266
+ If source is lavavu.Viewer() list contains all objects
267
+ If source is lavavu.Object() list contains that single object
268
+ """
269
+ if source.__class__.__name__ == 'Viewer':
270
+ return source.objects.list
271
+ elif not isinstance(source, list):
272
+ return [source]
273
+ else:
274
+ return source
275
+
276
+ def export_OBJ(filepath, source, verbose=False, vertexcolours=True):
277
+ """
278
+ Export given object(s) to an OBJ file
279
+ Supports only triangle mesh object data
280
+
281
+ If source is lavavu.Viewer() exports all objects
282
+ If source is lavavu.Object() exports single object
283
+
284
+ Set vertexcolours to support writing R,G,B values with vertices
285
+ (not part of OBJ standard but supported in some software)
286
+ """
287
+ mtlfilename = os.path.splitext(filepath)[0] + '.mtl'
288
+ objects = _get_objects(source)
289
+ with open(filepath, 'w') as f, open(mtlfilename, 'w') as m:
290
+ f.write("# OBJ file\n")
291
+ offset = 1
292
+ for obj in objects:
293
+ if obj["visible"]:
294
+ f.write("g %s\n" % obj.name)
295
+ offset = _write_OBJ(f, m, filepath, obj, offset, verbose, vertexcolours)
296
+
297
+ def _write_MTL(m, name, texture=None, diffuse=[1.0, 1.0, 1.0], ambient=None, specular=None, opacity=1.0, illum=None):
298
+ #http://paulbourke.net/dataformats/mtl/
299
+ #print("Writing MTL ", texture, diffuse, opacity, name)
300
+ mtl = "newmtl %s\n" % name
301
+ mtl += "Kd %.06f %.06ff %.06f\n" % (diffuse[0], diffuse[1], diffuse[2])
302
+ if ambient:
303
+ mtl += "Ka %.06f %.06ff %.06f\n" % (ambient[0], ambient[1], ambient[2])
304
+ if specular:
305
+ if illum is None:
306
+ illum = 2 #Highlight on
307
+ mtl += "Ks %.06f %.06ff %.06f\n" % (specular[0], specular[1], specular[2])
308
+ if len(specular) > 3:
309
+ mtl += "Ns %.06f\n" % specular[3]
310
+ mtl += "d %f\n" % (1.0 - opacity) #Dissolve: inverse of opacity
311
+ if illum is None:
312
+ illum = 1 #Default=1 = colour on, ambient on
313
+ mtl += "illum %d\n" % illum
314
+
315
+ if texture:
316
+ mtl += "map_Kd %s\n" % texture
317
+
318
+ mtl += '\n'
319
+ m.write(mtl)
320
+ mtl_line = "usemtl %s\n" % name
321
+ return mtl_line
322
+
323
+ def _write_OBJ(f, m, filepath, obj, offset=1, verbose=False, vertexcolours=True):
324
+ mtl_line = ""
325
+ cmaptexcoords = []
326
+ colourdict = None
327
+ import re
328
+ name = re.sub(r"\s+", "", obj["name"])
329
+ colourcount = sum([len(c) for c in obj.data.colours])
330
+ #print(f"{colourcount=}")
331
+ if m and obj["texture"]:
332
+ fn = obj["texture"]
333
+ if fn == 'colourmap':
334
+ fn = 'texture.png'
335
+ #Write palette.png
336
+ obj.parent.palette('image', 'texture')
337
+ if verbose:
338
+ print("Writing texture mtl ", fn)
339
+ mtl_line = _write_MTL(m, name, texture=fn, opacity=obj["opacity"])
340
+
341
+ elif m and colourcount > 0 and not vertexcolours:
342
+ #Define material for each (slow for high colour/vertex count - could sort faces by material before writing?)
343
+ if colourcount < 10000:
344
+ colourdict = {}
345
+ #SLOW!
346
+
347
+ #Get unique https://stackoverflow.com/a/33197029/866759
348
+ allcolours = numpy.concatenate(obj.data.colours)
349
+ unique = numpy.unique(allcolours)
350
+
351
+ for c in unique:
352
+ rgb = colour2rgb(c)
353
+ cs = colour2hex(rgb)
354
+ colourdict[c] = (cs,rgb)
355
+ if verbose:
356
+ print("Writing mtl lib (colour list), unique colours:",len(unique))
357
+ for c in colourdict:
358
+ cs, rgb = colourdict[c]
359
+ mtl_line = _write_MTL(m, cs, diffuse=[rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0], opacity=obj["opacity"])
360
+ else:
361
+ #Support this full RGB palette?
362
+ #TODO: generate texcoord data using formula below
363
+ #18bit RGB = 262144 colours
364
+ #Top-left = (0,0,0)
365
+ #Bottom-right = (255,255,255)
366
+ #(8x8 tiles of 64x64)
367
+ #G - slow, Z index = G
368
+ #R - midd, Y axis = R
369
+ #B - fast, X axis = B
370
+ #X = B//4
371
+ #Y = R//4
372
+ #Z = G//4
373
+ #zx = Z % 8
374
+ #zy = Z // 8
375
+ #x = zx * 64 + X
376
+ #y = zy * 64 + Y
377
+ #u = x / 512
378
+ #v = y / 512
379
+
380
+ #https://upload.wikimedia.org/wikipedia/commons/3/34/RGB_18bits_palette.png
381
+ palimg = "iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAIAAAB7GkOtAAAABGdBTUEAANjr9RwUqgAAACBjSFJNAACH"\
382
+ "CgAAjAoAAPYWAACEzwAAczsAAOxVAAA6lwAAHU1girkoAAAGc0lEQVR42u3dQYoDMQxFwTZY93Duf8mQ"\
383
+ "A8hg9yIIVTHbgU9vHsrG8/mZL/7iv/8+n2G//fbbb//V/vkA0JAAAAgAAAIAgAAAIAAACAAAAgCAAAAg"\
384
+ "AAAIAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgDARQDCRwBwAQAgAAAIAAACAIAAACAAAAgAAAIAgAAA"\
385
+ "IAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIAQB4Aj8IDuAAAEAAABAAAAQBAAAAQAAAEAAABAEAA"\
386
+ "ABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAGATAI/CA/QMwBrVC2a//fbbb//dfj8BAfS8AAQA"\
387
+ "QAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAADOAxA+AoALAAAB"\
388
+ "AEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAA8gB4FB7ABQCA"\
389
+ "AAAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAJsAeBQeoGcA"\
390
+ "1qheMPvt77s/fH/7X+33ExBAzwtAAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAAB"\
391
+ "AEAAABAAAAQAAAEA4DwA4SMAuAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAA"\
392
+ "AQBAAAAQAAAEAAABACAPgEfhAVwAAAgAAAIAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIA"\
393
+ "gAAAIAAACAAAAgCAAACwCYBH4QF6BuAzqhfMfvvtt9/+u/1+AgLoeQEIAIAAACAAAAgAAAIAgAAAIAAA"\
394
+ "CAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAAnAcgfAQAFwAAAgCAAAAgAAAIAAACAIAAACAA"\
395
+ "AAgAAAIAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAACAAAOQB8Cg8gAsAAAEAQAAAEAAABAAAAQBAAAAQ"\
396
+ "AAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAA2AfAoPEDPAKxRvWD222+//fbf7fcTEEDP"\
397
+ "C0AAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQDgPADhIwC4"\
398
+ "AAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAIA+AR+EB"\
399
+ "XAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAALAJgEfh"\
400
+ "AXoGYI3qBauten+n/fbbX3i/n4AAel4AAgAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgCAAAAg"\
401
+ "AAAIAAACAIAAACAAAAgAAOcBCB8BwAUAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIAgAAA"\
402
+ "IAAACAAAAgCAAAAgAAAIAAB5ADwKD+ACAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAA"\
403
+ "ABAAAAQAAAEAQAAAEAAABACATQA8Cg/QMwCfUb1g9ttvv/323+33ExBAzwtAAAAEAAABAEAAABAAAAQA"\
404
+ "AAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEA4DwA4SMAuAAAEAAABAAAAQBAAAAQAAAE"\
405
+ "AAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABACAPgEfhAVwAAAgAAAIAgAAAIAAACAAA"\
406
+ "AgCAAAAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgCAAACwCYBH4QF6BmCN6gWz33777f+PKP/9"\
407
+ "/QQE0PMCEAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAADgP"\
408
+ "QPgIAC4AAAQAAAEAQAAAEAAABAAAAQBAAAAQAAAEAAABAEAAABAAAAQAAAEAQAAAEAAABAAAAQBAAADI"\
409
+ "A+BReAAXAAACAIAAACAAAAgAAAIAgAAAIAAACAAAAgCAAAAgAAAIAAACAIAAACAAAAgAAAIAgAAAIAAA"\
410
+ "bALgUXiAlr7Y4BnEOVAUKwAAAABJRU5ErkJggg=="
411
+ texfn = os.path.splitext(filepath)[0] + '.png'
412
+ import base64
413
+ with open(texfn, "wb") as fp:
414
+ fp.write(base64.b64decode(palimg))
415
+ mtl_line = _write_MTL(m, "palette_rgb", texture=texfn, opacity=obj["opacity"])
416
+ elif m and "colour" in obj:
417
+ #print("Writing mtl lib (default colour)")
418
+ c = obj.parent.parse_colour(obj["colour"])
419
+ mtl_line = _write_MTL(m, 'default-' + name, diffuse=c, opacity=obj["opacity"])
420
+
421
+ for o,data in enumerate(obj):
422
+ if verbose: print("[%s] element %d of %d" % (obj.name, o+1, len(obj.data.vertices)))
423
+ verts = data.vertices.reshape((-1,3))
424
+ if len(verts) == 0:
425
+ if verbose: print("No vertices")
426
+ continue
427
+ f.write("o Surface_%d\n" % o)
428
+ #f.write("o %s\n" % obj.name)
429
+ if m: f.write("mtllib " + os.path.splitext(os.path.basename(filepath))[0] + ".mtl\n")
430
+ indices = data.indices.reshape((-1,3))
431
+ normals = data.normals.reshape((-1,3))
432
+ texcoords = data.texcoords.reshape((-1,2))
433
+ #Calculate texcoords from colour values?
434
+ if len(texcoords) == 0 and obj["texture"] == 'colourmap':
435
+ label = obj["colourby"]
436
+ if isinstance(label,int):
437
+ #Use the given label index
438
+ sets = list(datasets.keys())
439
+ label = sets[label]
440
+ elif len(label) == 0:
441
+ #Use the default label
442
+ label = 'values'
443
+ valdata = data[label]
444
+ if len(valdata) >= o+1:
445
+ #Found matching value array
446
+ v = valdata
447
+ #Normalise [0,1]
448
+ texcoords = (v - numpy.min(v)) / numpy.ptp(v)
449
+ #Add 2nd dimension (not actually necessary,
450
+ #tex coords can by 1d but breaks some loaders (meshlab)
451
+ zeros = numpy.zeros((len(texcoords)))
452
+ texcoords = numpy.vstack((texcoords,zeros)).reshape([2, -1]).transpose()
453
+
454
+ #Colours?
455
+ cv0 = ""
456
+ vperc = 1
457
+ if len(data.colours):
458
+ vperc = int(verts.shape[0] / len(data.colours))
459
+
460
+ if verbose: print("- Writing vertices:",verts.shape)
461
+ for vi,v in enumerate(verts):
462
+ #Vertex colour with vertex? (only if flag passed)
463
+ if vertexcolours and len(data.colours):
464
+ #if vi%10==0: print(len(data.colours), len(verts), vperc, vi)
465
+ c = data.colours[len(data.colours) // vperc - 1]
466
+ rgb = colour2rgb(c)
467
+ f.write("v %.6f %.6f %.6f %.6f %.6f %.6f\n" % (v[0], v[1], v[2], rgb[0]/255.0, rgb[1]/255.0, rgb[2]/255.0))
468
+ else:
469
+ f.write("v %.6f %.6f %.6f\n" % (v[0], v[1], v[2]))
470
+ if verbose: print("- Writing normals:",normals.shape)
471
+ for n in normals:
472
+ f.write("vn %.6f %.6f %.6f\n" % (n[0], n[1], n[2]))
473
+ if verbose: print("- Writing texcoords:",texcoords.shape)
474
+ if len(texcoords.shape) == 2:
475
+ for t in texcoords:
476
+ f.write("vt %.6f %.6f\n" % (t[0], t[1]))
477
+ else:
478
+ for t in texcoords:
479
+ f.write("vt %.6f\n" % t)
480
+
481
+ #Face elements v/t/n v/t v//n
482
+ f.write(mtl_line)
483
+ if len(normals) and len(texcoords):
484
+ if verbose: print("- Writing faces (v/t/n):",indices.shape)
485
+ elif len(texcoords):
486
+ if verbose: print("- Writing faces (v/t):",indices.shape)
487
+ elif len(normals):
488
+ if verbose: print("- Writing faces (v//n):",indices.shape)
489
+ else:
490
+ if verbose: print("- Writing faces (v):",indices.shape)
491
+ if verbose: print("- Colours :",data.colours.shape)
492
+ if verbose: print("- Indices :",indices.shape)
493
+
494
+ for n,i in enumerate(indices):
495
+ if verbose and n%1000==0: print(".", end=''); sys.stdout.flush()
496
+ i0 = i[0]+offset
497
+ i1 = i[1]+offset
498
+ i2 = i[2]+offset
499
+ #Use mtl colours?
500
+ if colourdict:
501
+ ci = int(i[0] / vperc)
502
+ cv1 = data.colours[ci]
503
+ if cv0 != cv1 and cv1 in colourdict:
504
+ f.write("usemtl " + colourdict[cv1][0] + "\n")
505
+ cv0 = cv1
506
+
507
+ if len(normals) and len(texcoords):
508
+ f.write("f %d/%d/%d %d/%d/%d %d/%d/%d\n" % (i0, i0, i0, i1, i1, i1, i2, i2, i2))
509
+ elif len(texcoords):
510
+ f.write("f %d/%d %d/%d %d/%d\n" % (i0, i0, i1, i1, i2, i2))
511
+ elif len(normals):
512
+ f.write("f %d//%d %d//%d %d//%d\n" % (i0, i0, i1, i1, i2, i2))
513
+ else:
514
+ f.write("f %d %d %d\n" % (i0, i1, i2))
515
+ if verbose: print()
516
+
517
+ offset += verts.shape[0]
518
+ return offset
519
+
520
+ def export_PLY(filepath, source, binary=True):
521
+ """
522
+ Export given object(s) to a PLY file
523
+ Supports points or triangle mesh object data
524
+
525
+ If source is lavavu.Viewer() exports all objects
526
+ If source is lavavu.Object() exports single object
527
+
528
+ Parameters
529
+ ----------
530
+ filepath : str
531
+ Output file to write
532
+ source : lavavu.Viewer or lavavu.Object
533
+ Where to get object data to export
534
+ binary : boolean
535
+ Write vertex/face data as binary, default True
536
+ """
537
+ objects = _get_objects(source)
538
+ with open(filepath, mode='wb') as f:
539
+ voffset = 0
540
+ foffset = 0
541
+ #First count vertices, faces
542
+ fc = 0
543
+ vc = 0
544
+ for obj in objects:
545
+ for o,data in enumerate(obj):
546
+ vc += len(data.vertices) #Vertices now in shape (N,3) so len returns correct count
547
+ fc += len(data.indices) // 3
548
+
549
+ #Pass the counts first
550
+ vertex = None
551
+ face = None
552
+ print(vc, " vertices, ", fc, " faces")
553
+ for obj in objects:
554
+ for o,data in enumerate(obj):
555
+ print("[%s] element %d of %d, type %s" % (obj.name, o+1, len(obj.data), data.type))
556
+ #print("OFFSETS:",voffset,foffset)
557
+ verts = data.vertices.reshape((-1,3))
558
+ if len(verts) == 0:
559
+ print("No vertices")
560
+ return
561
+ if len(verts) != vc:
562
+ print("Vertex count error!", len(verts), vc, verts.shape)
563
+ return
564
+ indices = data.indices.reshape((-1,3))
565
+ normals = data.normals.reshape((-1,3))
566
+ texcoords = data.texcoords.reshape((-1,2))
567
+
568
+ vperc = 0
569
+ cperf = 0
570
+ if len(data.colours):
571
+ vperc = int(verts.shape[0] / len(data.colours))
572
+ C = data.colours
573
+ #print("COLOURS:",len(C),C.shape, verts.shape[0], verts.shape[0] / len(data.colours), vperc)
574
+
575
+ if data.type != 'points':
576
+ #Per face colours, or less
577
+ if face is None:
578
+ if vperc and vperc < len(verts):
579
+ #cperf = int(indices.shape[0] / len(data.colours))
580
+ face = numpy.zeros(shape=(fc), dtype=[('vertex_indices', 'i4', (3,)), ('red', 'u1'), ('green', 'u1'), ('blue', 'u1')])
581
+ else:
582
+ face = numpy.zeros(shape=(fc), dtype=[('vertex_indices', 'i4', (3,))])
583
+ print("FACE:",face.dtype)
584
+
585
+ for i,idx in enumerate(indices):
586
+ if i%1000==0: print(".", end=''); sys.stdout.flush()
587
+ if vperc and vperc < len(verts):
588
+ #Have colour, but less than vertices, apply to faces
589
+ ci = idx[0] // vperc
590
+ c = data.colours[ci]
591
+ rgb = colour2rgb(c)
592
+ face[i+foffset] = ([idx[0]+voffset, idx[1]+voffset, idx[2]+voffset], rgb[0], rgb[1], rgb[2])
593
+ else:
594
+ face[i+foffset] = ([idx[0]+voffset, idx[1]+voffset, idx[2]+voffset])
595
+ print()
596
+
597
+ #Construct and write vertex elements
598
+ if vertex is None:
599
+ #Setup vertex array based on first object element
600
+ D = [('x', 'f4'), ('y', 'f4'), ('z', 'f4')]
601
+ if normals.shape[0] == verts.shape[0]:
602
+ D += [('nx', 'f4'), ('ny', 'f4'), ('nz', 'f4')]
603
+ if texcoords.shape[0] == verts.shape[0]:
604
+ D += [('s', 'f4'), ('t', 'f4')]
605
+ if vperc and vperc == 1:
606
+ D += [('red', 'u1'), ('green', 'u1'), ('blue', 'u1')]
607
+ print("VERTEX:",D)
608
+ vertex = numpy.zeros(shape=(vc), dtype=D)
609
+
610
+ for i,v in enumerate(verts):
611
+ #if i%1000==0:
612
+ # print("vert index",i,vperc)
613
+ if i%1000==0: print(".", end=''); sys.stdout.flush()
614
+ E = [v[0], v[1], v[2]]
615
+ if normals.shape[0] == verts.shape[0]:
616
+ N = normals[i]
617
+ E += [N[0], N[1], N[2]]
618
+ if texcoords.shape[0] == verts.shape[0]:
619
+ T = texcoords[i]
620
+ E += [T[0], T[1]]
621
+ if vperc and vperc == 1:
622
+ c = data.colours[i]
623
+ rgb = colour2rgb(c)
624
+ E += [rgb[0], rgb[1], rgb[2]]
625
+ vertex[i+voffset] = tuple(E)
626
+ print()
627
+
628
+ #Update offsets : number of vertices / faces added
629
+ voffset += verts.shape[0]
630
+ foffset += indices.shape[0]
631
+
632
+ import plyfile
633
+ #vertex = numpy.array(vertex, dtype=vertex.dtype)
634
+ els = []
635
+ els.append(plyfile.PlyElement.describe(vertex, 'vertex'))
636
+ if face is not None:
637
+ els.append(plyfile.PlyElement.describe(face, 'face'))
638
+
639
+ #Write, text or binary
640
+ if binary:
641
+ print("Writing binary PLY data")
642
+ plyfile.PlyData(els).write(f)
643
+ else:
644
+ print("Writing ascii PLY data")
645
+ plyfile.PlyData(els, text=True).write(f)
646
+
647
+ def _get_PLY_colours(element):
648
+ """
649
+ Extract colour data from PLY element and return as a numpy rgba array
650
+ """
651
+ r = None
652
+ g = None
653
+ b = None
654
+ a = None
655
+ #print(element.properties)
656
+ for prop in element.properties:
657
+ #print(prop,prop.name,prop.dtype)
658
+ if 'red' in prop.name: r = element[prop.name]
659
+ if 'green' in prop.name: g = element[prop.name]
660
+ if 'blue' in prop.name: b = element[prop.name]
661
+ if 'alpha' in prop.name: a = element[prop.name]
662
+
663
+ if r is not None and g is not None and b is not None:
664
+ if a is None:
665
+ a = numpy.full(r.shape, 255)
666
+ #return numpy.array([r, g, b, a])
667
+ return numpy.vstack((r,g,b,a)).reshape([4, -1]).transpose()
668
+
669
+ return None
670
+
671
+ def plot_PLY(lv, filename):
672
+ """
673
+ Plot triangles from a PlyData instance. Assumptions:
674
+ has a 'vertex' element with 'x', 'y', and 'z' properties,
675
+ has a 'face' element with an integral list property 'vertex_indices',
676
+ all of whose elements have length 3.
677
+ """
678
+ import plyfile
679
+ plydata = plyfile.PlyData.read(filename)
680
+
681
+ x = plydata['vertex']['x']
682
+ y = plydata['vertex']['y']
683
+ z = plydata['vertex']['z']
684
+ V = numpy.vstack((x,y,z)).reshape([3, -1]).transpose()
685
+ #V = numpy.array([x, y, z])
686
+ #print("VERTS:", V.shape)
687
+
688
+ vp = []
689
+ for prop in plydata['vertex'].properties:
690
+ vp.append(prop.name)
691
+ print(prop.name)
692
+
693
+ N = None
694
+ if 'nx' in vp and 'ny' in vp and 'nz' in vp:
695
+ nx = plydata['vertex']['nx']
696
+ ny = plydata['vertex']['ny']
697
+ nz = plydata['vertex']['nz']
698
+ N = numpy.vstack((nx,ny,nz)).reshape([3, -1]).transpose()
699
+
700
+ T = None
701
+ if 's' in vp and 't' in vp:
702
+ s = plydata['vertex']['s']
703
+ t = plydata['vertex']['t']
704
+ T = numpy.vstack((s,t)).reshape([2, -1]).transpose()
705
+
706
+ C = _get_PLY_colours(plydata['vertex'])
707
+
708
+ if 'face' in plydata:
709
+ #print(plydata['face'])
710
+ #Face colours?
711
+ if C is None:
712
+ C = _get_PLY_colours(plydata['face'])
713
+
714
+ tri_idx = plydata['face']['vertex_indices']
715
+ idx_dtype = tri_idx[0].dtype
716
+ tri_idx = numpy.asarray(tri_idx).flatten()
717
+ #print(type(tri_idx),idx_dtype,tri_idx.shape)
718
+ #print(tri_idx)
719
+
720
+ triangles = numpy.array([t.tolist() for t in tri_idx]).flatten()
721
+ #triangles = numpy.fromiter(tri_idx, [('data', idx_dtype, (3,))], count=len(tri_idx))['data']
722
+
723
+ return lv.triangles(vertices=V, indices=triangles, colours=C, normals=N, texcoords=T)
724
+ else:
725
+ return lv.points(vertices=V, colours=C, normals=N, texcoords=T)
726
+
727
+ def export_any(filepath, source, name=None):
728
+ """
729
+ Export given object(s) to a file format supproted by trimesh, eg: GLTF or GLB file
730
+ See: https://trimsh.org/trimesh.exchange.html
731
+ Requires "trimesh" module
732
+ Supports triangle mesh object data
733
+
734
+ If source is lavavu.Viewer() exports all objects
735
+ If source is lavavu.Object() exports single object
736
+
737
+ Parameters
738
+ ----------
739
+ filepath : str
740
+ Output file to write
741
+ source : lavavu.Viewer or lavavu.Object
742
+ Where to get object data to export
743
+ """
744
+ #TODO: support points, lines
745
+ try_import('trimesh')
746
+
747
+ objects = _get_objects(source)
748
+ scene = trimesh.Scene()
749
+ facect = 0
750
+ for obj in objects:
751
+ #Use object name to export unless name provided
752
+ oname = name
753
+ if oname is None:
754
+ oname = obj["name"]
755
+ for i,e in enumerate(obj.data):
756
+ #print(e)
757
+ meshdict = {}
758
+ meshdict["vertices"] = e.vertices.reshape(-1,3)
759
+ meshdict["vertex_normals"] = e.normals.reshape(-1,3)
760
+ meshdict["faces"] = e.indices.reshape(-1,3)
761
+ facect += e.indices.shape[0]
762
+ if not len(meshdict["faces"]):
763
+ print("Empty",e)
764
+ continue
765
+ colours = e.colours
766
+ #print("ColourCount",len(colours))
767
+ if len(colours) == len(meshdict["vertices"]):
768
+ #View int32 into uint8 bytes
769
+ meshdict["vertex_colors"] = colours.view('uint8').reshape((-1,4))
770
+
771
+ mesh = trimesh.load_mesh(meshdict)
772
+ scene.add_geometry(mesh, geom_name=oname + '#' + str(i))
773
+
774
+ if len(colours) <= 1:
775
+ #If we don't set a default material colour, trimesh import will set default colour for every vertex
776
+ colour = obj.parent.parse_colour(obj["colour"])
777
+ colour = numpy.array(colour*255, dtype=numpy.uint8)
778
+ if len(colours) > 0:
779
+ colour = colours.view('uint8').reshape((-1,4))[0]
780
+ if obj["opacity"] < 1.0:
781
+ colour[3] *= obj["opacity"]
782
+ #print("Single colour: export as material", colour)
783
+ mesh.visual = trimesh.visual.TextureVisuals(material=trimesh.visual.material.PBRMaterial())
784
+ mesh.visual.material.baseColorFactor = colour
785
+
786
+ if facect > 0:
787
+ scene.export(file_obj=filepath)
788
+ else:
789
+ print("No triangle facets to export")
790
+
791
+ """
792
+ #Try GLTF ascii?
793
+ gltf = trimesh.exchange.gltf.export_gltf(scene)
794
+ import json
795
+ print("Mesh loaded, exporting GLTF")
796
+ gltf = trimesh.exchange.gltf.export_gltf(mesh) #, merge_buffers=True)
797
+ #print(gltf.keys())
798
+ #print(gltf['model.gltf'])
799
+ for fn in gltf:
800
+ mode = 'w'
801
+ #if ".bin" in fn: mode = 'wb'
802
+ with open(fn, 'wb') as f:
803
+ print("writing gltf component:" + fn)
804
+ f.write(gltf[fn])
805
+ """
806
+
807
+ def read_any(filepath, lv):
808
+ """
809
+ Load using trimesh, supports GLTF etc
810
+
811
+ See: https://trimsh.org/trimesh.exchange.html
812
+ Requires "trimesh" module
813
+ """
814
+ #TODO: support points, lines
815
+ try_import('trimesh')
816
+ scene = trimesh.load(filepath)
817
+ tris = None
818
+ for name in scene.geometry.keys():
819
+ geometry = scene.geometry[name]
820
+ idx = 0
821
+ if '#' in name:
822
+ name,idx = name.split('#')
823
+ idx = int(idx)
824
+
825
+ if idx == 0:
826
+ tris = lv.triangles(name)
827
+ else:
828
+ tris.append()
829
+
830
+ tris.vertices(geometry.vertices)
831
+ tris.normals(geometry.vertex_normals)
832
+ tris.indices(geometry.faces)
833
+
834
+ #adjacency_matrix = geometry.edges_sparse
835
+ #print(geometry)
836
+ #print(geometry.visual)
837
+
838
+ #Load vertex colours if available
839
+ if hasattr(geometry.visual, "vertex_colors"):
840
+ #print("HAVE VERTEX COLOURS",len(geometry.visual.vertex_colors))
841
+ tris.colours(geometry.visual.vertex_colors)
842
+ #Load single colour material
843
+ elif hasattr(geometry.visual, "material"):
844
+ #print("HAVE MATERIAL")
845
+ #print(geometry.visual.material)
846
+ #print(geometry.visual.material.baseColorFactor)
847
+ if idx == 0:
848
+ #Can set as prop, but only works for single el
849
+ tris["colour"] = geometry.visual.material.baseColorFactor
850
+ else:
851
+ tris.colours(geometry.visual.material.baseColorFactor)
852
+
853
+ return tris