@tscircuit/3d-viewer 0.0.1
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.
- package/.storybook/main.ts +29 -0
- package/.storybook/preview.tsx +24 -0
- package/LICENSE +21 -0
- package/README.md +5 -0
- package/ai-reference/soup-reference.md +1301 -0
- package/bun.lockb +0 -0
- package/index.html +13 -0
- package/package.json +50 -0
- package/src/App.tsx +17 -0
- package/src/App2.tsx +48 -0
- package/src/App3.tsx +209 -0
- package/src/App4.tsx +88 -0
- package/src/CadViewer.tsx +56 -0
- package/src/CadViewerContainer.tsx +73 -0
- package/src/GeomContext.ts +3 -0
- package/src/bug-pads-and-traces.json +1516 -0
- package/src/geoms/constants.ts +9 -0
- package/src/geoms/plated-hole.ts +45 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/render/hooks.tsx +62 -0
- package/src/hooks/render/index.tsx +440 -0
- package/src/hooks/use-convert-children-to-soup.ts +10 -0
- package/src/hooks/use-stls-from-geom.ts +54 -0
- package/src/index.tsx +1 -0
- package/src/main.tsx +9 -0
- package/src/modules.d.ts +3 -0
- package/src/plated-hole-board.json +213 -0
- package/src/soup-to-3d/index.ts +162 -0
- package/src/themes.ts +7 -0
- package/src/three-components/GeomModel.tsx +13 -0
- package/src/three-components/MixedStlModel.tsx +61 -0
- package/src/three-components/STLModel.tsx +33 -0
- package/src/three-components/cube-with-labeled-sides.tsx +116 -0
- package/src/vite-env.d.ts +1 -0
- package/stories/Simple.stories.tsx +10 -0
- package/stories/assets/soic-with-traces.json +1802 -0
- package/tsconfig.json +39 -0
- package/vendor/@jscadui/format-three/index.ts +252 -0
- package/vite.config.ts +17 -0
package/tsconfig.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Base Options recommended for all projects
|
|
4
|
+
"esModuleInterop": true,
|
|
5
|
+
"skipLibCheck": true,
|
|
6
|
+
"target": "es2022",
|
|
7
|
+
"baseUrl": ".",
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"moduleDetection": "force",
|
|
11
|
+
"moduleResolution": "Bundler",
|
|
12
|
+
"isolatedModules": true,
|
|
13
|
+
"verbatimModuleSyntax": false,
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
// Enable strict type checking so you can catch bugs early
|
|
16
|
+
"strict": true,
|
|
17
|
+
"noUncheckedIndexedAccess": true,
|
|
18
|
+
"noImplicitOverride": true,
|
|
19
|
+
"noImplicitAny": false,
|
|
20
|
+
// We are not transpiling, so preserve our source code and do not emit files
|
|
21
|
+
"module": "preserve",
|
|
22
|
+
"jsx": "react-jsx",
|
|
23
|
+
"noEmit": true,
|
|
24
|
+
// Include the DOM types
|
|
25
|
+
"lib": [
|
|
26
|
+
"es2022",
|
|
27
|
+
"dom",
|
|
28
|
+
"dom.iterable"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
// Include the necessary files for your project
|
|
32
|
+
"include": [
|
|
33
|
+
"**/*.ts",
|
|
34
|
+
"**/*.tsx"
|
|
35
|
+
],
|
|
36
|
+
"exclude": [
|
|
37
|
+
"node_modules"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
export function CommonToThree({
|
|
3
|
+
MeshPhongMaterial,
|
|
4
|
+
LineBasicMaterial,
|
|
5
|
+
BufferGeometry,
|
|
6
|
+
BufferAttribute,
|
|
7
|
+
Mesh,
|
|
8
|
+
InstancedMesh,
|
|
9
|
+
Line,
|
|
10
|
+
LineSegments,
|
|
11
|
+
Color,
|
|
12
|
+
Vector3,
|
|
13
|
+
}) {
|
|
14
|
+
const flatShading = false
|
|
15
|
+
const materials: any = {
|
|
16
|
+
mesh: {
|
|
17
|
+
def: new MeshPhongMaterial({ color: 0x0084d1, flatShading }),
|
|
18
|
+
make: (params) => new MeshPhongMaterial({ flatShading, ...params }),
|
|
19
|
+
},
|
|
20
|
+
line: {
|
|
21
|
+
def: new LineBasicMaterial({ color: 0x0000ff }),
|
|
22
|
+
make: (params) => new LineBasicMaterial(params),
|
|
23
|
+
},
|
|
24
|
+
lines: null,
|
|
25
|
+
}
|
|
26
|
+
materials.lines = materials.line
|
|
27
|
+
materials.instance = materials.mesh // todo support instances for lines
|
|
28
|
+
|
|
29
|
+
function _CSG2Three(obj, { smooth = false }) {
|
|
30
|
+
const {
|
|
31
|
+
vertices,
|
|
32
|
+
indices,
|
|
33
|
+
normals,
|
|
34
|
+
color,
|
|
35
|
+
colors,
|
|
36
|
+
isTransparent = false,
|
|
37
|
+
opacity,
|
|
38
|
+
} = obj
|
|
39
|
+
let { transforms } = obj
|
|
40
|
+
const objType = obj.type || "mesh"
|
|
41
|
+
|
|
42
|
+
const materialDef = materials[objType]
|
|
43
|
+
if (!materialDef) {
|
|
44
|
+
console.error(`material not found for type ${objType}`, obj)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
let material = materialDef.def
|
|
48
|
+
const isInstanced = obj.type === "instance"
|
|
49
|
+
if ((color || colors) && !isInstanced) {
|
|
50
|
+
const c = color || colors
|
|
51
|
+
const opts: any = {
|
|
52
|
+
vertexColors: !!colors,
|
|
53
|
+
opacity: c[3] === undefined ? 1 : c[3],
|
|
54
|
+
transparent:
|
|
55
|
+
(color && c[3] !== 1 && c[3] !== undefined) || isTransparent,
|
|
56
|
+
}
|
|
57
|
+
if (opacity) opts.opacity = opacity
|
|
58
|
+
if (!colors) opts.color = _CSG2Three.makeColor(color)
|
|
59
|
+
material = materialDef.make(opts)
|
|
60
|
+
if (opacity) {
|
|
61
|
+
material.transparent = true
|
|
62
|
+
material.opacity = opacity
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let geo = new BufferGeometry()
|
|
67
|
+
geo.setAttribute("position", new BufferAttribute(vertices, 3))
|
|
68
|
+
if (indices) geo.setIndex(new BufferAttribute(indices, 1))
|
|
69
|
+
if (normals) geo.setAttribute("normal", new BufferAttribute(normals, 3))
|
|
70
|
+
if (smooth)
|
|
71
|
+
geo = toCreasedNormals({ Vector3, BufferAttribute }, geo, Math.PI / 10)
|
|
72
|
+
if (colors)
|
|
73
|
+
geo.setAttribute(
|
|
74
|
+
"color",
|
|
75
|
+
new BufferAttribute(colors, isTransparent ? 4 : 3)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
let mesh
|
|
79
|
+
switch (objType) {
|
|
80
|
+
case "mesh":
|
|
81
|
+
mesh = new Mesh(geo, material)
|
|
82
|
+
break
|
|
83
|
+
case "instance":
|
|
84
|
+
const { list } = obj
|
|
85
|
+
mesh = new InstancedMesh(
|
|
86
|
+
geo,
|
|
87
|
+
materials.mesh.make({ color: 0x0084d1 }),
|
|
88
|
+
list.length
|
|
89
|
+
)
|
|
90
|
+
list.forEach((item, i) => {
|
|
91
|
+
copyTransformToArray(
|
|
92
|
+
item.transforms,
|
|
93
|
+
mesh.instanceMatrix.array,
|
|
94
|
+
i * 16
|
|
95
|
+
)
|
|
96
|
+
})
|
|
97
|
+
transforms = null
|
|
98
|
+
break
|
|
99
|
+
case "line":
|
|
100
|
+
mesh = new Line(geo, material)
|
|
101
|
+
break
|
|
102
|
+
case "lines":
|
|
103
|
+
// https://threejs.org/docs/#api/en/materials/LineBasicMaterial
|
|
104
|
+
mesh = new LineSegments(geo, material)
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
if (transforms && !isInstanced) mesh.applyMatrix4({ elements: transforms })
|
|
108
|
+
return mesh
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// shortcut for setMatrixAt for InstancedMesh
|
|
112
|
+
function copyTransformToArray(te, array: any[] = [], offset = 0) {
|
|
113
|
+
array[offset] = te[0]
|
|
114
|
+
array[offset + 1] = te[1]
|
|
115
|
+
array[offset + 2] = te[2]
|
|
116
|
+
array[offset + 3] = te[3]
|
|
117
|
+
|
|
118
|
+
array[offset + 4] = te[4]
|
|
119
|
+
array[offset + 5] = te[5]
|
|
120
|
+
array[offset + 6] = te[6]
|
|
121
|
+
array[offset + 7] = te[7]
|
|
122
|
+
|
|
123
|
+
array[offset + 8] = te[8]
|
|
124
|
+
array[offset + 9] = te[9]
|
|
125
|
+
array[offset + 10] = te[10]
|
|
126
|
+
array[offset + 11] = te[11]
|
|
127
|
+
|
|
128
|
+
array[offset + 12] = te[12]
|
|
129
|
+
array[offset + 13] = te[13]
|
|
130
|
+
array[offset + 14] = te[14]
|
|
131
|
+
array[offset + 15] = te[15]
|
|
132
|
+
|
|
133
|
+
return array
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_CSG2Three.makeColor = (c) => new Color(c[0], c[1], c[2])
|
|
137
|
+
_CSG2Three.materials = materials
|
|
138
|
+
_CSG2Three.setDefColor = (c) => {
|
|
139
|
+
materials.mesh.def = new MeshPhongMaterial({
|
|
140
|
+
color: _CSG2Three.makeColor(c),
|
|
141
|
+
flatShading,
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return _CSG2Three
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** from threejs examples BufferGeometryUtils
|
|
149
|
+
* @param {BufferGeometry} geometry
|
|
150
|
+
* @param {number} tolerance
|
|
151
|
+
* @return {BufferGeometry>}
|
|
152
|
+
*/
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Modifies the supplied geometry if it is non-indexed, otherwise creates a new,
|
|
156
|
+
* non-indexed geometry. Returns the geometry with smooth normals everywhere except
|
|
157
|
+
* faces that meet at an angle greater than the crease angle.
|
|
158
|
+
*
|
|
159
|
+
* @param {BufferGeometry} geometry
|
|
160
|
+
* @param {number} [creaseAngle]
|
|
161
|
+
* @return {BufferGeometry}
|
|
162
|
+
*/
|
|
163
|
+
function toCreasedNormals(
|
|
164
|
+
{ Vector3, BufferAttribute },
|
|
165
|
+
geometry,
|
|
166
|
+
creaseAngle = Math.PI / 3 /* 60 degrees */
|
|
167
|
+
) {
|
|
168
|
+
const creaseDot = Math.cos(creaseAngle)
|
|
169
|
+
const hashMultiplier = (1 + 1e-10) * 1e2
|
|
170
|
+
|
|
171
|
+
// reusable vectors
|
|
172
|
+
const verts = [new Vector3(), new Vector3(), new Vector3()]
|
|
173
|
+
const tempVec1 = new Vector3()
|
|
174
|
+
const tempVec2 = new Vector3()
|
|
175
|
+
const tempNorm = new Vector3()
|
|
176
|
+
const tempNorm2 = new Vector3()
|
|
177
|
+
|
|
178
|
+
// hashes a vector
|
|
179
|
+
function hashVertex(v) {
|
|
180
|
+
const x = ~~(v.x * hashMultiplier)
|
|
181
|
+
const y = ~~(v.y * hashMultiplier)
|
|
182
|
+
const z = ~~(v.z * hashMultiplier)
|
|
183
|
+
return `${x},${y},${z}`
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed
|
|
187
|
+
// and returns the original geometry
|
|
188
|
+
const resultGeometry = geometry.index ? geometry.toNonIndexed() : geometry
|
|
189
|
+
const posAttr = resultGeometry.attributes.position
|
|
190
|
+
const vertexMap = {}
|
|
191
|
+
|
|
192
|
+
// find all the normals shared by commonly located vertices
|
|
193
|
+
for (let i = 0, l = posAttr.count / 3; i < l; i++) {
|
|
194
|
+
const i3 = 3 * i
|
|
195
|
+
const a = verts[0].fromBufferAttribute(posAttr, i3 + 0)
|
|
196
|
+
const b = verts[1].fromBufferAttribute(posAttr, i3 + 1)
|
|
197
|
+
const c = verts[2].fromBufferAttribute(posAttr, i3 + 2)
|
|
198
|
+
|
|
199
|
+
tempVec1.subVectors(c, b)
|
|
200
|
+
tempVec2.subVectors(a, b)
|
|
201
|
+
|
|
202
|
+
// add the normal to the map for all vertices
|
|
203
|
+
const normal = new Vector3().crossVectors(tempVec1, tempVec2).normalize()
|
|
204
|
+
for (let n = 0; n < 3; n++) {
|
|
205
|
+
const vert = verts[n]
|
|
206
|
+
const hash = hashVertex(vert)
|
|
207
|
+
if (!(hash in vertexMap)) {
|
|
208
|
+
vertexMap[hash] = []
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
vertexMap[hash].push(normal)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// average normals from all vertices that share a common location if they are within the
|
|
216
|
+
// provided crease threshold
|
|
217
|
+
const normalArray = new Float32Array(posAttr.count * 3)
|
|
218
|
+
const normAttr = new BufferAttribute(normalArray, 3, false)
|
|
219
|
+
for (let i = 0, l = posAttr.count / 3; i < l; i++) {
|
|
220
|
+
// get the face normal for this vertex
|
|
221
|
+
const i3 = 3 * i
|
|
222
|
+
const a = verts[0].fromBufferAttribute(posAttr, i3 + 0)
|
|
223
|
+
const b = verts[1].fromBufferAttribute(posAttr, i3 + 1)
|
|
224
|
+
const c = verts[2].fromBufferAttribute(posAttr, i3 + 2)
|
|
225
|
+
|
|
226
|
+
tempVec1.subVectors(c, b)
|
|
227
|
+
tempVec2.subVectors(a, b)
|
|
228
|
+
|
|
229
|
+
tempNorm.crossVectors(tempVec1, tempVec2).normalize()
|
|
230
|
+
|
|
231
|
+
// average all normals that meet the threshold and set the normal value
|
|
232
|
+
for (let n = 0; n < 3; n++) {
|
|
233
|
+
const vert = verts[n]
|
|
234
|
+
const hash = hashVertex(vert)
|
|
235
|
+
const otherNormals = vertexMap[hash]
|
|
236
|
+
tempNorm2.set(0, 0, 0)
|
|
237
|
+
|
|
238
|
+
for (let k = 0, lk = otherNormals.length; k < lk; k++) {
|
|
239
|
+
const otherNorm = otherNormals[k]
|
|
240
|
+
if (tempNorm.dot(otherNorm) > creaseDot) {
|
|
241
|
+
tempNorm2.add(otherNorm)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
tempNorm2.normalize()
|
|
246
|
+
normAttr.setXYZ(i3 + n, tempNorm2.x, tempNorm2.y, tempNorm2.z)
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
resultGeometry.setAttribute("normal", normAttr)
|
|
251
|
+
return resultGeometry
|
|
252
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineConfig } from "vite"
|
|
2
|
+
import react from "@vitejs/plugin-react"
|
|
3
|
+
import tsconfigPaths from "vite-tsconfig-paths"
|
|
4
|
+
|
|
5
|
+
// https://vitejs.dev/config/
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [react(), tsconfigPaths()],
|
|
8
|
+
server: {
|
|
9
|
+
proxy: {
|
|
10
|
+
"/easyeda-models": {
|
|
11
|
+
target: "https://modules.easyeda.com/3dmodel/",
|
|
12
|
+
changeOrigin: true,
|
|
13
|
+
rewrite: (path: string) => path.replace(/^\/easyeda-models/, ""),
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
})
|