@threepipe/plugin-blend-importer 0.0.4 → 0.0.5

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.
@@ -1,13 +1,225 @@
1
- import {BufferAttribute, BufferGeometry, Vector3Tuple} from 'threepipe'
1
+ function getLayer(layers: any, i: number) {
2
+ if (!Array.isArray(layers)) return layers
3
+ return layers[i]
4
+ }
5
+ // https://projects.blender.org/blender/blender/pulls/108015/files
6
+ // https://projects.blender.org/blender/blender/commit/1b63a290c68636211b16c5e212a699e6b63031b9
7
+ // https://developer.blender.org/docs/release_notes/4.0/python_api/#breaking-changes
8
+ // https://developer.blender.org/docs/release_notes/4.0/#blend-files
9
+ // https://projects.blender.org/blender/blender/commit/7966cd16d6dc4e66d01f7bd68a090107c1a7978c
10
+ // https://projects.blender.org/blender/blender/issues/95967
11
+ // https://projects.blender.org/blender/blender/pulls/106638
12
+
13
+ // https://github.com/blender/blender/blob/55e2fd2929b7577e0785c128c8f8069efd990c07/source/blender/blenkernel/intern/mesh.cc#L413
14
+ export function createBufferGeometry(meshData: any, ctx: any) {
15
+
16
+ if (meshData.mpoly) return createBufferGeometryOld(meshData, ctx)
17
+ const geometry = new ctx.BufferGeometry()
18
+ geometry.name = meshData.aname || ''
19
+
20
+ // console.log(bakeGetters(meshData))
21
+
22
+ // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19
23
+
24
+ let vertices
25
+ let indices
26
+
27
+ // Extract vertex positions from vdata layers
28
+ if (meshData.vdata && meshData.vdata.layers && meshData.vdata.totlayer > 0) {
29
+ for (let i = 0; i < meshData.vdata.totlayer; i++) {
30
+ const layer = getLayer(meshData.vdata.layers, i)
31
+ // if (layer.type === 0) {
32
+ // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575
33
+ const data = layer.data || []
34
+ // if (layer.name === 'position') { // type = 48 (custom vec3)
35
+ if (data.length === meshData.totvert) {
36
+ if (vertices && (layer.name !== 'position' || vertices.name === 'position')) {
37
+ // console.warn('BlendLoader - multiple vertices, ignoring', layer)
38
+ continue
39
+ }
40
+ vertices = layer
41
+ } else {
42
+ // console.log('unknown vdata', layer)
43
+ }
44
+ }
45
+ }
46
+
47
+ // Blender loops are indexes in three.js/blender. Called dots in gltf exporter
48
+
49
+ // // Extract indexes from loop data layers
50
+ if (meshData.ldata && meshData.ldata.layers) {
51
+ for (let i = 0; i < meshData.ldata.totlayer; i++) {
52
+ const layer = getLayer(meshData.ldata.layers, i)
53
+ // if (layer.type === 0) {
54
+ const data = layer.data || []
55
+ // if (layer.name === '.corner_vert') { // type = 11
56
+ if (data.length === meshData.totloop) { // type = 11
57
+ if (indices && (layer.name !== '.corner_vert' || indices.name === '.corner_vert')) {
58
+ // console.warn('BlendLoader - multiple indices, ignoring', layer)
59
+ continue
60
+ }
61
+ indices = layer
62
+ } else {
63
+ // console.log('unknown ldata', layer)
64
+ }
65
+ }
66
+ }
67
+ // if (meshData.pdata && meshData.pdata.layers) {
68
+ // for (let i = 0; i < meshData.pdata.totlayer; i++) {
69
+ // const layer = getLayer(meshData.pdata.layers, i)
70
+ // // if (layer.type === 0) {
71
+ // // if (layer.name === '.corner_vert') { // type = 11
72
+ // // } else {
73
+ // console.log('unknown pdata', layer)
74
+ // // }
75
+ // }
76
+ // }
77
+ // if (meshData.fdata && meshData.fdata.layers) {
78
+ // for (let i = 0; i < meshData.fdata.totlayer; i++) {
79
+ // const layer = getLayer(meshData.fdata.layers, i)
80
+ // // if (layer.type === 0) {
81
+ // // if (layer.name === '.corner_vert') { // type = 11
82
+ // // } else {
83
+ // console.log('unknown fdata', layer)
84
+ // // }
85
+ // }
86
+ // }
87
+ // if (meshData.edata && meshData.edata.layers) {
88
+ // for (let i = 0; i < meshData.edata.totlayer; i++) {
89
+ // const layer = getLayer(meshData.edata.layers, i)
90
+ // // if (layer.type === 0) {
91
+ // // if (layer.name === '.corner_vert') { // type = 11
92
+ // // } else {
93
+ // console.log('unknown edata', layer)
94
+ // // }
95
+ // }
96
+ // }
97
+
98
+ let faceIndices: number[] = []
99
+ if (meshData.poly_offset_indices && meshData.totpoly > 0) {
100
+ faceIndices = [...new Int32Array(meshData.poly_offset_indices.__blender_file__.AB, meshData.poly_offset_indices.__data_address__, meshData.totpoly + 1)]
101
+ }
102
+ // if (faceSize !== 3 && faceSize !== 4 && !faceIndices.length) {
103
+ // console.error('not supported polygons with ', faceSize, 'vertices')
104
+ // return geometry
105
+ // }
106
+
107
+ if (vertices?.data && vertices.data.length > 0) {
108
+ const positions = new Float32Array(vertices.data.length * 3)
109
+ for (let j = 0; j < vertices.data.length; j++) {
110
+ const vertex = vertices.data[j]
111
+ if (vertex.x !== undefined) {
112
+ positions[j * 3] = vertex.x
113
+ positions[j * 3 + 1] = vertex.z
114
+ positions[j * 3 + 2] = -vertex.y
115
+ } else if (vertex.co !== undefined) {
116
+ positions[j * 3] = vertex.co[0]
117
+ positions[j * 3 + 1] = vertex.co[2]
118
+ positions[j * 3 + 2] = -vertex.co[1]
119
+ } else {
120
+ // console.log(bakeGetters(meshData))
121
+ // const t = [...new Int32Array(vertex.__blender_file__.AB, vertex.__data_address__, 8)]
122
+ // debugger
123
+ console.error('BlendLoader - unknown vertex', vertex)
124
+ break
125
+ }
126
+ }
127
+ geometry.setAttribute('position', new ctx.BufferAttribute(positions, 3))
128
+ }
2
129
 
3
- export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
130
+ if (indices?.data && indices.data.length > 0 && vertices?.data?.length) {
131
+ const faceSize = meshData.totloop / meshData.totpoly
132
+ if (faceIndices.length > 0) {
133
+ // Use face offset indices for variable-sized faces
134
+ let totalTriangles = 0
135
+ for (let i = 0; i < faceIndices.length - 1; i++) {
136
+ const faceVertCount = faceIndices[i + 1] - faceIndices[i]
137
+ totalTriangles += Math.max(0, faceVertCount - 2)
138
+ }
139
+
140
+ const indexes = new Uint32Array(totalTriangles * 3)
141
+ let t = 0
142
+
143
+ for (let i = 0; i < faceIndices.length - 1; i++) {
144
+ const faceStart = faceIndices[i]
145
+ const faceEnd = faceIndices[i + 1]
146
+ const faceVertCount = faceEnd - faceStart
147
+
148
+ if (faceVertCount >= 3) {
149
+ // todo better Triangulate the face using fan triangulation
150
+ const firstVert = indices.data[faceStart].i
151
+ for (let k = 1; k < faceVertCount - 1; k++) {
152
+ indexes[t++] = firstVert
153
+ indexes[t++] = indices.data[faceStart + k].i
154
+ indexes[t++] = indices.data[faceStart + k + 1].i
155
+ }
156
+ } else {
157
+ // debugger
158
+ }
159
+ }
160
+
161
+ // console.log(indexes)
162
+ geometry.setIndex(new ctx.BufferAttribute(indexes, 1))
163
+ } else if (faceSize === 3 || faceSize === 4) {
164
+ // Fall back to uniform face size approach
165
+ const isQuad = faceSize === 4
166
+ const faceCount = indices.length / faceSize
167
+ const indexes = new Uint32Array(faceCount * 3 * (isQuad ? 2 : 1))
168
+
169
+ if (faceSize !== 3 && faceSize !== 4) return geometry
170
+ for (let j = 0, t = 0; j < indices.length; j += faceSize) {
171
+ const a = indices[j].i
172
+ const b = indices[j + 1].i
173
+ const c = indices[j + 2].i
174
+ indexes[t++] = a
175
+ indexes[t++] = b
176
+ indexes[t++] = c
177
+
178
+ if (isQuad) {
179
+ const d = indices[j + 3].i
180
+ indexes[t++] = a
181
+ indexes[t++] = c
182
+ indexes[t++] = d
183
+ }
184
+ }
185
+ geometry.setIndex(new ctx.BufferAttribute(indexes, 1))
186
+ }
187
+ } else if (faceIndices) {
188
+ console.error('BlendLoader - no indices data found, but face indices are present', faceIndices)
189
+ }
190
+
191
+ // console.log(geometry.attributes.position)
192
+ // console.log(geometry.index)
193
+
194
+ // compute stuff not present
195
+ if (geometry.attributes.position && !geometry.attributes.normal)
196
+ geometry.computeVertexNormals()
197
+
198
+ // if (meshData.loc) { // maybe this is the bbox center?
199
+ // geometry.translate(meshData.loc[0], meshData.loc[2], -meshData.loc[1])
200
+ // }
201
+ // if (meshData.size) { // maybe this is the bbox size?
202
+ // geometry.scale(meshData.size[0], meshData.size[2], -meshData.size[1])
203
+ // }
204
+
205
+ return geometry
206
+ }
207
+ // // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L19
208
+ // // https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L1575
209
+ // // todo __set_morph_locs_attribute
210
+ // // todo __set_morph_tangent_attribute
211
+ // // todo pdata(points), edata(edge), fdata(face? legacy?)
212
+ // // meshData.eData = domain edge - https://github.com/blender/blender/blob/05dcc0377b62d8e026e1901dfbecbd4b06fda0b5/scripts/addons_core/io_scene_gltf2/blender/exp/primitive_extract.py#L189
213
+ // // todo normals, tangents
214
+
215
+ export function createBufferGeometryOld(mesh: any, ctx: any) {
4
216
  const
5
217
  faces = Array.isArray(mesh.mpoly) ? mesh.mpoly as any[] : [mesh.mpoly],
6
218
  loops = mesh.mloop,
7
219
  uv = mesh.mloopuv,
8
220
  vertices = mesh.mvert
9
221
 
10
- const geometry = new BufferGeometry()
222
+ const geometry = new ctx.BufferGeometry()
11
223
 
12
224
  if (!faces) return geometry
13
225
 
@@ -18,6 +230,7 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
18
230
  const positions = new Float32Array(size * 3)
19
231
 
20
232
  let currentIndex = 0
233
+ let computeNormals = false
21
234
 
22
235
  for (const face of faces) {
23
236
  const len = face.totloop
@@ -39,13 +252,17 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
39
252
  const vert = vertices[loop.v]
40
253
  indices[currentIndex] = currentIndex
41
254
 
42
- positions[currentIndex * 3 + 0] = vert.co[0] + origin[0]
43
- positions[currentIndex * 3 + 1] = vert.co[2] + origin[2]
44
- positions[currentIndex * 3 + 2] = -vert.co[1] + -origin[1]
255
+ positions[currentIndex * 3 + 0] = vert.co[0]
256
+ positions[currentIndex * 3 + 1] = vert.co[2]
257
+ positions[currentIndex * 3 + 2] = -vert.co[1]
45
258
 
46
- normals[currentIndex * 3 + 0] = vert.no[0]
47
- normals[currentIndex * 3 + 1] = vert.no[2]
48
- normals[currentIndex * 3 + 2] = -vert.no[1]
259
+ if (vert.no) {
260
+ normals[currentIndex * 3 + 0] = vert.no[0]
261
+ normals[currentIndex * 3 + 1] = vert.no[2]
262
+ normals[currentIndex * 3 + 2] = -vert.no[1]
263
+ } else {
264
+ computeNormals = true
265
+ }
49
266
 
50
267
  if (uv) {
51
268
  uvs[currentIndex * 2 + 0] = uv[index].uv[0]
@@ -59,10 +276,14 @@ export function createBufferGeometry(mesh: any, origin: Vector3Tuple) {
59
276
  }
60
277
  }
61
278
 
62
- geometry.setAttribute('position', new BufferAttribute(positions, 3))
63
- geometry.setIndex(new BufferAttribute(indices, 1))
64
- geometry.setAttribute('normal', new BufferAttribute(normals, 3))
65
- geometry.setAttribute('uv', new BufferAttribute(uvs, 2))
279
+ geometry.setAttribute('position', new ctx.BufferAttribute(positions, 3))
280
+ geometry.setIndex(new ctx.BufferAttribute(indices, 1))
281
+ geometry.setAttribute('normal', new ctx.BufferAttribute(normals, 3))
282
+ geometry.setAttribute('uv', new ctx.BufferAttribute(uvs, 2))
283
+
284
+ if (computeNormals) {
285
+ geometry.computeVertexNormals()
286
+ }
66
287
 
67
288
  return geometry
68
289
  }
@@ -1,26 +1,78 @@
1
+ import {createCamera} from './camera'
1
2
  import {createLight} from './light'
2
- import {createMesh} from './mesh'
3
+ import {createMesh, setCreateTransform} from './mesh'
3
4
  import {Object3D} from 'threepipe'
4
5
 
6
+ // https://github.com/blender/blender/blob/99a4c93081cc2310a09a14f20a1493e8974c10d2/source/blender/makesdna/DNA_object_types.h#L445
5
7
  const blenderObjectTypes = {
8
+ empty: 0,
6
9
  mesh: 1,
7
10
  lamp: 10,
11
+ camera: 11,
8
12
  }
9
13
 
10
- export async function createObjects(file: any) {
14
+ export async function createObjects(file: any, ctx: any) {
11
15
  const objects: (Object3D|null)[] = []
12
- const blendObjects = file.objects.Object
16
+ const childMap = new Map<any, Object3D[]>()
17
+ const objMap = new Map<any, Object3D>()
18
+ const blendObjects = file.objects.Object ?? []
19
+ const loaded = new WeakMap()
20
+ // console.log(bakeGetters(file))
13
21
  for (const object of blendObjects) {
22
+ if (loaded.has(object)) {
23
+ console.warn('BlendLoader - duplicate object')
24
+ continue
25
+ }
26
+ let obj: Object3D | undefined = undefined
14
27
  switch (object.type) {
28
+ case blenderObjectTypes.empty:
29
+ obj = new ctx.Object3D()
30
+ break
15
31
  case blenderObjectTypes.mesh:
16
- objects.push(createMesh(object))
32
+ obj = createMesh(object, loaded, ctx)
17
33
  break
18
34
  case blenderObjectTypes.lamp:
19
- objects.push(createLight(object))
35
+ obj = createLight(object, ctx)
36
+ break
37
+ case blenderObjectTypes.camera:
38
+ obj = createCamera(object, ctx)
20
39
  break
21
40
  default:
22
- console.warn('Unsupported object type', object.type)
41
+ obj = new ctx.Object3D()
42
+ console.warn('Unsupported object type', object.type, object, obj)
23
43
  }
44
+ if (obj) {
45
+ setCreateTransform(object, obj)
46
+ const exChildren = childMap.get(object)
47
+ if (exChildren) {
48
+ for (const exChild of exChildren) {
49
+ obj.add(exChild)
50
+ }
51
+ childMap.delete(object)
52
+ }
53
+ objMap.set(object, obj)
54
+ if (object.parent === object) {
55
+ console.error('invalid, parent same as object, adding to root', object)
56
+ objects.push(obj)
57
+ } else if (!object.parent) { // just in case
58
+ objects.push(obj)
59
+ } else {
60
+ const parent = objMap.get(object.parent)
61
+ if (!parent) {
62
+ const chi = childMap.get(object.parent) ?? []
63
+ chi.push(obj)
64
+ childMap.set(object, chi)
65
+ } else {
66
+ parent.add(obj)
67
+ }
68
+ }
69
+ loaded.set(object, obj)
70
+ }
71
+ }
72
+ if (childMap.size) {
73
+ const vals = [...childMap.values()].flat()
74
+ console.warn('unknown objects with parents, adding to root', vals, childMap)
75
+ objects.push(...vals)
24
76
  }
25
77
  return objects.filter(o => !!o) as Object3D[]
26
78
  }
@@ -1,4 +1,4 @@
1
- import {PointLight} from 'threepipe'
1
+ import {Light} from 'threepipe'
2
2
 
3
3
  const blenderLightTypes = {
4
4
  point: 0,
@@ -8,7 +8,7 @@ const blenderLightTypes = {
8
8
  area: 0,
9
9
  }
10
10
 
11
- export function createLight(lamp: any) {
11
+ export function createLight(lamp: any, ctx: any) {
12
12
  const ldata = lamp.data
13
13
 
14
14
  const position = [lamp.loc[0], lamp.loc[2], -lamp.loc[1]]
@@ -17,22 +17,26 @@ export function createLight(lamp: any) {
17
17
  const intensity = ldata.energy
18
18
  const distance = 0
19
19
 
20
- let light = null
20
+ let light: Light | undefined = undefined
21
21
 
22
22
  switch (ldata.type) {
23
23
  case blenderLightTypes.point:
24
- light = new PointLight(color, intensity, distance)
25
- light.position.fromArray(position, 0)
26
- light.castShadow = true
24
+ light = new ctx.PointLight(color, intensity, distance)
25
+ light!.position.fromArray(position, 0)
26
+ light!.castShadow = true
27
27
  break
28
28
  case blenderLightTypes.sun:
29
- light = new PointLight(color, intensity, distance)
30
- light.position.fromArray(position, 0)
31
- light.castShadow = true
32
- light.shadow.mapSize.width = 1024
33
- light.shadow.mapSize.height = 1024
34
- light.shadow.camera.near = 0.01
35
- light.shadow.camera.far = 500
29
+ light = new ctx.PointLight(color, intensity, distance)
30
+ light!.position.fromArray(position, 0)
31
+ light!.castShadow = true
32
+ if (light!.shadow) {
33
+ light!.shadow.mapSize.width = 1024
34
+ light!.shadow.mapSize.height = 1024
35
+ // @ts-expect-error ??/
36
+ light!.shadow.camera.near = 0.01
37
+ // @ts-expect-error ??/
38
+ light!.shadow.camera.far = 500
39
+ }
36
40
  break
37
41
  default:
38
42
  console.warn('Unsupported light type', ldata.type)
@@ -1,12 +1,12 @@
1
- import {MeshPhysicalMaterial} from 'threepipe'
2
-
3
1
  // todo see blender gltf exporter and convert to js. structure is the same
4
- export function createMaterial(mat: any) {
5
- const material = new MeshPhysicalMaterial()
2
+ // https://github.com/KhronosGroup/glTF-Blender-IO/blob/ed5100ab6c40472b7c3254fddfe0dd0d76d60644/addons/io_scene_gltf2/blender/exp/material/materials.py#L60
3
+ export function createMaterial(mat: any, ctx: any) {
4
+ const material = new ctx.MeshPhysicalMaterial()
6
5
  material.color.setRGB(mat.r, mat.g, mat.b)
7
6
  material.roughness = mat.roughness !== undefined ? mat.roughness : 0.4
8
7
  material.metalness = mat.metallic !== undefined ? mat.metallic : 0.0
9
- material.opacity = mat.alpha !== undefined ? mat.alpha : 0.0
8
+ // material.opacity = mat.alpha !== undefined ? mat.alpha : 0.0
9
+ material.opacity = 1
10
10
  material.transparent = material.opacity < 1.0
11
11
  return material
12
12
  }
@@ -1,26 +1,39 @@
1
- import {Mesh} from 'threepipe'
1
+ import {Object3D} from 'threepipe'
2
2
  import {createBufferGeometry} from './geometry'
3
3
  import {createMaterial} from './material'
4
4
 
5
- export function createMesh(object: any) {
6
- if (!object.data) return null
5
+ export function setCreateTransform(object: any, obj: Object3D) {
6
+ // obj.rotateZ(object.rot[2])
7
+ // obj.rotateY(object.rot[1])
8
+ // obj.rotateX(object.rot[0])
7
9
 
8
- const geometry = createBufferGeometry(object.data, [0, 0, 0])
10
+ obj.name = object.aname
11
+ obj.quaternion.set(object.quat[1], object.quat[3], -object.quat[2], object.quat[0]) // wxyz
12
+ obj.scale.set(object.size[0], object.size[1], object.size[2])
13
+ obj.position.set(object.loc[0], object.loc[2], -object.loc[1])
14
+ obj.updateMatrix()
15
+ }
16
+
17
+ export function createMesh(object: any, loaded: WeakMap<any, any>, ctx: any) {
18
+ if (!object.data) {
19
+ return undefined
20
+ }
21
+
22
+ const geometry = loaded.get(object.data) ?? createBufferGeometry(object.data, ctx)
23
+ loaded.set(object.data, geometry)
9
24
 
10
25
  const mat = object.data.mat[0]
11
26
 
12
- const material = mat ? createMaterial(mat) : undefined
13
- const mesh = new Mesh(geometry, material)
27
+ // const material = mat ? createMaterial(mat) : undefined
28
+ const material = mat ? loaded.get(mat) ?? createMaterial(mat, ctx) : new ctx.MeshPhysicalMaterial()
29
+ if (mat) loaded.set(mat, material)
30
+
31
+ // console.log(material, mat)
32
+ // material.side = DoubleSide
33
+ const mesh = new ctx.Mesh(geometry, material)
14
34
 
15
35
  mesh.castShadow = true
16
36
  mesh.receiveShadow = true
17
37
 
18
- mesh.rotateZ(object.rot[2])
19
- mesh.rotateY(object.rot[1])
20
- mesh.rotateX(object.rot[0])
21
-
22
- mesh.scale.fromArray(object.size, 0)
23
- mesh.position.fromArray([object.loc[0], object.loc[2], -object.loc[1]], 0)
24
-
25
38
  return mesh
26
39
  }