@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/bun.lockb
ADDED
|
Binary file
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite + React + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tscircuit/3d-viewer",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"main": "./dist/index.cjs",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "bun run dev",
|
|
7
|
+
"dev": "bunx --bun vite",
|
|
8
|
+
"build": "tsup ./src/index.tsx --dts --sourcemap",
|
|
9
|
+
"preview": "vite preview",
|
|
10
|
+
"storybook": "storybook dev -p 6006",
|
|
11
|
+
"build-storybook": "storybook build"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@jscad/modeling": "^2.12.2",
|
|
15
|
+
"@jscad/regl-renderer": "^2.6.9",
|
|
16
|
+
"@jscad/stl-serializer": "^2.1.17",
|
|
17
|
+
"@react-three/drei": "^9.107.2",
|
|
18
|
+
"@react-three/fiber": "^8.16.8",
|
|
19
|
+
"@types/three": "^0.165.0",
|
|
20
|
+
"react": "^18.3.1",
|
|
21
|
+
"react-dom": "^18.3.1",
|
|
22
|
+
"react-use-gesture": "^9.1.3",
|
|
23
|
+
"three": "^0.165.0",
|
|
24
|
+
"three-stdlib": "^2.30.3"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "^1.8.3",
|
|
28
|
+
"@chromatic-com/storybook": "^1.5.0",
|
|
29
|
+
"@storybook/addon-essentials": "^8.1.10",
|
|
30
|
+
"@storybook/addon-interactions": "^8.1.10",
|
|
31
|
+
"@storybook/addon-links": "^8.1.10",
|
|
32
|
+
"@storybook/addon-onboarding": "^8.1.10",
|
|
33
|
+
"@storybook/blocks": "^8.1.10",
|
|
34
|
+
"@storybook/builder-vite": "^8.1.10",
|
|
35
|
+
"@storybook/react": "^8.1.10",
|
|
36
|
+
"@storybook/react-vite": "^8.1.10",
|
|
37
|
+
"@storybook/test": "^8.1.10",
|
|
38
|
+
"@tscircuit/soup": "^0.0.38",
|
|
39
|
+
"@tscircuit/soup-util": "^0.0.11",
|
|
40
|
+
"@types/react": "^18.3.3",
|
|
41
|
+
"@types/react-dom": "^18.3.0",
|
|
42
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
43
|
+
"storybook": "^8.1.10",
|
|
44
|
+
"strip-ansi": "^7.1.0",
|
|
45
|
+
"tsup": "^8.1.0",
|
|
46
|
+
"typescript": "^5.5.3",
|
|
47
|
+
"vite": "^5.3.1",
|
|
48
|
+
"vite-tsconfig-paths": "^4.3.2"
|
|
49
|
+
}
|
|
50
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { Renderer } from "./hooks/render"
|
|
3
|
+
import App2 from "./App2"
|
|
4
|
+
import App3 from "./App3"
|
|
5
|
+
import App4 from "./App4"
|
|
6
|
+
|
|
7
|
+
function App() {
|
|
8
|
+
return (
|
|
9
|
+
<div>
|
|
10
|
+
{/* <App2 /> */}
|
|
11
|
+
{/* <App3 /> */}
|
|
12
|
+
<App4 />
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default App
|
package/src/App2.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
intersect,
|
|
3
|
+
subtract,
|
|
4
|
+
union,
|
|
5
|
+
} from "@jscad/modeling/src/operations/booleans"
|
|
6
|
+
import { scale, translate } from "@jscad/modeling/src/operations/transforms"
|
|
7
|
+
import { cube, sphere } from "@jscad/modeling/src/primitives"
|
|
8
|
+
import * as renderingDefaults from "@jscad/regl-renderer/types/rendering/renderDefaults"
|
|
9
|
+
import { light } from "./themes"
|
|
10
|
+
import { useState } from "react"
|
|
11
|
+
import soup from "./plated-hole-board.json"
|
|
12
|
+
// import soup from "./bug-pads-and-traces.json"
|
|
13
|
+
|
|
14
|
+
// import { downloadGeometry } from "./helpers"
|
|
15
|
+
import { Renderer } from "./hooks/render"
|
|
16
|
+
import { createBoardGeomFromSoup } from "./soup-to-3d"
|
|
17
|
+
|
|
18
|
+
const shape = union(
|
|
19
|
+
subtract(cube({ size: 3 }), sphere({ radius: 2 })),
|
|
20
|
+
intersect(sphere({ radius: 1.3 }), cube({ size: 2.1 }))
|
|
21
|
+
)
|
|
22
|
+
const shape2 = translate([0, 0, 1.5], shape)
|
|
23
|
+
const shape3 = scale([3, 3, 3], shape2)
|
|
24
|
+
|
|
25
|
+
function App() {
|
|
26
|
+
const [solids] = useState<any[]>([shape3])
|
|
27
|
+
return (
|
|
28
|
+
<div className="App">
|
|
29
|
+
<Renderer
|
|
30
|
+
solids={createBoardGeomFromSoup(soup as any)}
|
|
31
|
+
height={500}
|
|
32
|
+
width={800}
|
|
33
|
+
options={{
|
|
34
|
+
renderingOptions: {
|
|
35
|
+
background: light.bg,
|
|
36
|
+
meshColor: light.color,
|
|
37
|
+
},
|
|
38
|
+
gridOptions: {
|
|
39
|
+
// color: light.grid1,
|
|
40
|
+
// subColor: light.grid2,
|
|
41
|
+
},
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default App
|
package/src/App3.tsx
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Canvas,
|
|
3
|
+
useFrame,
|
|
4
|
+
extend,
|
|
5
|
+
useThree,
|
|
6
|
+
useLoader,
|
|
7
|
+
} from "@react-three/fiber"
|
|
8
|
+
import { Suspense, useEffect, useMemo, useRef, useState } from "react"
|
|
9
|
+
import { OrbitControls, Grid, Outlines } from "@react-three/drei"
|
|
10
|
+
import * as THREE from "three"
|
|
11
|
+
import { CubeWithLabeledSides } from "./three-components/cube-with-labeled-sides"
|
|
12
|
+
import { createBoardGeomFromSoup } from "./soup-to-3d"
|
|
13
|
+
import soup from "./bug-pads-and-traces.json"
|
|
14
|
+
// import soup from "./plated-hole-board.json"
|
|
15
|
+
import stlSerializer from "@jscad/stl-serializer"
|
|
16
|
+
// import { STLLoader } from "three/examples/jsm/loaders/STLLoader"
|
|
17
|
+
import { MTLLoader, OBJLoader, STLLoader } from "three-stdlib"
|
|
18
|
+
|
|
19
|
+
extend({ OrbitControls })
|
|
20
|
+
|
|
21
|
+
function Box(props) {
|
|
22
|
+
const meshRef = useRef<THREE.Mesh>()
|
|
23
|
+
useFrame((state, delta) => {
|
|
24
|
+
if (!meshRef.current) return
|
|
25
|
+
meshRef.current.rotation.x += delta
|
|
26
|
+
meshRef.current.rotation.y += delta
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<mesh ref={meshRef} {...props}>
|
|
31
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
32
|
+
<meshStandardMaterial color="orange" />
|
|
33
|
+
</mesh>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function blobToBase64Url(blob: Blob) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const reader = new FileReader()
|
|
40
|
+
reader.onload = () => {
|
|
41
|
+
resolve(reader.result)
|
|
42
|
+
}
|
|
43
|
+
reader.onerror = reject
|
|
44
|
+
reader.readAsDataURL(blob)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const jscadGeom = createBoardGeomFromSoup(soup as any)
|
|
49
|
+
|
|
50
|
+
const stlPromises = jscadGeom.map((a) => {
|
|
51
|
+
const rawData = stlSerializer.serialize({ binary: true }, [a])
|
|
52
|
+
|
|
53
|
+
const blobData = new Blob(rawData)
|
|
54
|
+
|
|
55
|
+
const $urlForStl = blobToBase64Url(blobData)
|
|
56
|
+
|
|
57
|
+
return $urlForStl.then((url) => ({
|
|
58
|
+
url,
|
|
59
|
+
color: a.color,
|
|
60
|
+
}))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// const entities = entitiesFromSolids({}, ...soupToJscadShape(soup as any))
|
|
64
|
+
// console.log(entities)
|
|
65
|
+
|
|
66
|
+
function TestStl({
|
|
67
|
+
url,
|
|
68
|
+
color,
|
|
69
|
+
index,
|
|
70
|
+
}: {
|
|
71
|
+
index: number
|
|
72
|
+
url: string
|
|
73
|
+
color: any
|
|
74
|
+
}) {
|
|
75
|
+
const threeGeom = useLoader(STLLoader, url)
|
|
76
|
+
const mesh = useRef<THREE.Mesh>()
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<mesh ref={mesh as any}>
|
|
80
|
+
<primitive object={threeGeom} attach="geometry" />
|
|
81
|
+
<meshStandardMaterial
|
|
82
|
+
color={color}
|
|
83
|
+
transparent={index === 0}
|
|
84
|
+
opacity={index === 0 ? 0.8 : 1}
|
|
85
|
+
/>
|
|
86
|
+
{/* <Outlines thickness={0.05} color="black" opacity={0.25} /> */}
|
|
87
|
+
</mesh>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function TestObj({ url }: { url: string }) {
|
|
92
|
+
// const group = useLoader(OBJLoader, url)
|
|
93
|
+
// const materials = useLoader(MTLLoader, url)
|
|
94
|
+
// const obj = useLoader(OBJLoader, url)
|
|
95
|
+
|
|
96
|
+
const [content, setContent] = useState<string | null>(null)
|
|
97
|
+
const [obj, setObj] = useState<any | null>(null)
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
async function loadUrlContent() {
|
|
100
|
+
const response = await fetch(url)
|
|
101
|
+
const text = await response.text()
|
|
102
|
+
setContent(text)
|
|
103
|
+
|
|
104
|
+
// Extract all the sections of the file that have newmtl...endmtl to
|
|
105
|
+
// separate into mtlContent and objContent
|
|
106
|
+
|
|
107
|
+
const mtlContent = text
|
|
108
|
+
.match(/newmtl[\s\S]*?endmtl/g)
|
|
109
|
+
?.join("\n")!
|
|
110
|
+
.replace(/d 0\./g, "d 1.")!
|
|
111
|
+
const objContent = text.replace(/newmtl[\s\S]*?endmtl/g, "")
|
|
112
|
+
|
|
113
|
+
// console.log({ mtlContent, objContent })
|
|
114
|
+
console.log("MTL", mtlContent)
|
|
115
|
+
// console.log("OBJ", objContent)
|
|
116
|
+
|
|
117
|
+
const mtlLoader = new MTLLoader()
|
|
118
|
+
mtlLoader.setMaterialOptions({
|
|
119
|
+
invertTrProperty: true,
|
|
120
|
+
})
|
|
121
|
+
const materials = mtlLoader.parse(mtlContent, "test.mtl")
|
|
122
|
+
console.log(materials)
|
|
123
|
+
|
|
124
|
+
const objLoader = new OBJLoader()
|
|
125
|
+
objLoader.setMaterials(materials)
|
|
126
|
+
setObj(objLoader.parse(objContent))
|
|
127
|
+
}
|
|
128
|
+
loadUrlContent()
|
|
129
|
+
}, [url])
|
|
130
|
+
|
|
131
|
+
return <>{obj && <primitive object={obj} />}</>
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function Scene() {
|
|
135
|
+
const [stls, setStls] = useState<Array<{
|
|
136
|
+
url: string
|
|
137
|
+
color: number[]
|
|
138
|
+
}> | null>(null)
|
|
139
|
+
useEffect(() => {
|
|
140
|
+
async function loadStls() {
|
|
141
|
+
const stls = await Promise.all(stlPromises)
|
|
142
|
+
setStls(stls as any)
|
|
143
|
+
}
|
|
144
|
+
loadStls()
|
|
145
|
+
}, [])
|
|
146
|
+
return (
|
|
147
|
+
<>
|
|
148
|
+
<OrbitControls />
|
|
149
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
150
|
+
<pointLight position={[-10, -10, 10]} decay={0} intensity={Math.PI / 4} />
|
|
151
|
+
{/* <Box /> */}
|
|
152
|
+
{(stls ?? []).map((stl, i) => (
|
|
153
|
+
<TestStl index={i} {...stl} key={stl.url} />
|
|
154
|
+
))}
|
|
155
|
+
{/* <TestObj url="https://modules.easyeda.com/3dmodel/4ee8413127e64716b804db03d4b340ae" /> */}
|
|
156
|
+
<TestObj url="/easyeda-models/84af7f0f6529479fb6b1c809c61d205f" />
|
|
157
|
+
{/* <axesHelper args={[5]} /> */}
|
|
158
|
+
<Grid
|
|
159
|
+
rotation={[Math.PI / 2, 0, 0]}
|
|
160
|
+
infiniteGrid={true}
|
|
161
|
+
cellSize={1}
|
|
162
|
+
sectionSize={10}
|
|
163
|
+
/>
|
|
164
|
+
</>
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export const RotationTracker = () => {
|
|
169
|
+
useFrame(({ camera }) => {
|
|
170
|
+
window.TSCI_MAIN_CAMERA_ROTATION = camera.rotation
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
return <></>
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export default function App() {
|
|
177
|
+
return (
|
|
178
|
+
<div style={{ width: "100vw", height: "100vh", position: "relative" }}>
|
|
179
|
+
<div
|
|
180
|
+
style={{
|
|
181
|
+
position: "absolute",
|
|
182
|
+
top: 0,
|
|
183
|
+
left: 0,
|
|
184
|
+
width: 120,
|
|
185
|
+
height: 120,
|
|
186
|
+
}}
|
|
187
|
+
>
|
|
188
|
+
<Canvas
|
|
189
|
+
camera={{
|
|
190
|
+
up: [0, 0, 1],
|
|
191
|
+
// rotation: [-Math.PI / 2, 0, 0],
|
|
192
|
+
// lookAt: new THREE.Vector3(0, 0, 0),
|
|
193
|
+
position: [1, 1, 1],
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
197
|
+
<CubeWithLabeledSides />
|
|
198
|
+
</Canvas>
|
|
199
|
+
</div>
|
|
200
|
+
<Canvas
|
|
201
|
+
scene={{ up: [0, 0, 1] }}
|
|
202
|
+
camera={{ up: [0, 0, 1], position: [5, 5, 5] }}
|
|
203
|
+
>
|
|
204
|
+
<RotationTracker />
|
|
205
|
+
<Scene />
|
|
206
|
+
</Canvas>
|
|
207
|
+
</div>
|
|
208
|
+
)
|
|
209
|
+
}
|
package/src/App4.tsx
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Canvas,
|
|
3
|
+
useFrame,
|
|
4
|
+
extend,
|
|
5
|
+
useThree,
|
|
6
|
+
useLoader,
|
|
7
|
+
} from "@react-three/fiber"
|
|
8
|
+
import { Suspense, useEffect, useMemo, useRef, useState } from "react"
|
|
9
|
+
import { OrbitControls, Grid, Outlines } from "@react-three/drei"
|
|
10
|
+
import * as THREE from "three"
|
|
11
|
+
import { CubeWithLabeledSides } from "./three-components/cube-with-labeled-sides"
|
|
12
|
+
import { createBoardGeomFromSoup } from "./soup-to-3d"
|
|
13
|
+
import soup from "./bug-pads-and-traces.json"
|
|
14
|
+
// import soup from "./plated-hole-board.json"
|
|
15
|
+
import stlSerializer from "@jscad/stl-serializer"
|
|
16
|
+
// import { STLLoader } from "three/examples/jsm/loaders/STLLoader"
|
|
17
|
+
import { MTLLoader, OBJLoader, STLLoader } from "three-stdlib"
|
|
18
|
+
import { CommonToThree } from "vendor/@jscadui/format-three"
|
|
19
|
+
import { entitiesFromSolids } from "@jscad/regl-renderer"
|
|
20
|
+
|
|
21
|
+
extend({ OrbitControls })
|
|
22
|
+
|
|
23
|
+
const jscadGeom = createBoardGeomFromSoup(soup as any)
|
|
24
|
+
|
|
25
|
+
const threejs = CommonToThree(THREE)(entitiesFromSolids({}, ...jscadGeom), {
|
|
26
|
+
smooth: false,
|
|
27
|
+
})
|
|
28
|
+
console.log(threejs)
|
|
29
|
+
|
|
30
|
+
function Scene() {
|
|
31
|
+
return (
|
|
32
|
+
<>
|
|
33
|
+
<OrbitControls />
|
|
34
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
35
|
+
<pointLight position={[-10, -10, 10]} decay={0} intensity={Math.PI / 4} />
|
|
36
|
+
<primitive object={threejs} />
|
|
37
|
+
<Grid
|
|
38
|
+
rotation={[Math.PI / 2, 0, 0]}
|
|
39
|
+
infiniteGrid={true}
|
|
40
|
+
cellSize={1}
|
|
41
|
+
sectionSize={10}
|
|
42
|
+
/>
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const RotationTracker = () => {
|
|
48
|
+
useFrame(({ camera }) => {
|
|
49
|
+
window.TSCI_MAIN_CAMERA_ROTATION = camera.rotation
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
return <></>
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default function App() {
|
|
56
|
+
return (
|
|
57
|
+
<div style={{ width: "100vw", height: "100vh", position: "relative" }}>
|
|
58
|
+
<div
|
|
59
|
+
style={{
|
|
60
|
+
position: "absolute",
|
|
61
|
+
top: 0,
|
|
62
|
+
left: 0,
|
|
63
|
+
width: 120,
|
|
64
|
+
height: 120,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
<Canvas
|
|
68
|
+
camera={{
|
|
69
|
+
up: [0, 0, 1],
|
|
70
|
+
// rotation: [-Math.PI / 2, 0, 0],
|
|
71
|
+
// lookAt: new THREE.Vector3(0, 0, 0),
|
|
72
|
+
position: [1, 1, 1],
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
76
|
+
<CubeWithLabeledSides />
|
|
77
|
+
</Canvas>
|
|
78
|
+
</div>
|
|
79
|
+
<Canvas
|
|
80
|
+
scene={{ up: [0, 0, 1] }}
|
|
81
|
+
camera={{ up: [0, 0, 1], position: [5, 5, 5] }}
|
|
82
|
+
>
|
|
83
|
+
<RotationTracker />
|
|
84
|
+
<Scene />
|
|
85
|
+
</Canvas>
|
|
86
|
+
</div>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
2
|
+
import { useConvertChildrenToSoup } from "./hooks/use-convert-children-to-soup"
|
|
3
|
+
import { su } from "@tscircuit/soup-util"
|
|
4
|
+
import { useMemo } from "react"
|
|
5
|
+
import { createBoardGeomFromSoup } from "./soup-to-3d"
|
|
6
|
+
import { useStlsFromGeom } from "./hooks/use-stls-from-geom"
|
|
7
|
+
import { STLModel } from "./three-components/STLModel"
|
|
8
|
+
import { CadViewerContainer } from "./CadViewerContainer"
|
|
9
|
+
import { MixedStlModel } from "./three-components/MixedStlModel"
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
soup?: AnySoupElement[]
|
|
13
|
+
children?: any
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const CadViewer = ({ soup, children }: Props) => {
|
|
17
|
+
soup ??= useConvertChildrenToSoup(children, soup)
|
|
18
|
+
|
|
19
|
+
// TODO convert board
|
|
20
|
+
|
|
21
|
+
const boardGeom = useMemo(() => createBoardGeomFromSoup(soup), [soup])
|
|
22
|
+
|
|
23
|
+
const { stls, loading } = useStlsFromGeom(boardGeom)
|
|
24
|
+
|
|
25
|
+
const cad_components = su(soup).cad_component.list()
|
|
26
|
+
|
|
27
|
+
// TODO canvas/camera etc.
|
|
28
|
+
return (
|
|
29
|
+
<CadViewerContainer>
|
|
30
|
+
{stls.map(({ stlUrl, color }, index) => (
|
|
31
|
+
<STLModel
|
|
32
|
+
key={stlUrl}
|
|
33
|
+
stlUrl={stlUrl}
|
|
34
|
+
color={color}
|
|
35
|
+
opacity={index === 0 ? 0.95 : 1}
|
|
36
|
+
/>
|
|
37
|
+
))}
|
|
38
|
+
{/* <MixedStlModel url="/easyeda-models/84af7f0f6529479fb6b1c809c61d205f" /> */}
|
|
39
|
+
<MixedStlModel
|
|
40
|
+
url="/easyeda-models/dc694c23844346e9981bdbac7bb76421"
|
|
41
|
+
position={[0, 0, 0.5]}
|
|
42
|
+
rotation={[0, 0, Math.PI / 2]}
|
|
43
|
+
/>
|
|
44
|
+
<MixedStlModel
|
|
45
|
+
url="/easyeda-models/c7acac53bcbc44d68fbab8f60a747688"
|
|
46
|
+
position={[-5.65, 0, 0.5]}
|
|
47
|
+
rotation={[0, 0, Math.PI / 2]}
|
|
48
|
+
/>
|
|
49
|
+
<MixedStlModel
|
|
50
|
+
url="/easyeda-models/c7acac53bcbc44d68fbab8f60a747688"
|
|
51
|
+
position={[6.75, 0, 0.5]}
|
|
52
|
+
rotation={[0, 0, 0]}
|
|
53
|
+
/>
|
|
54
|
+
</CadViewerContainer>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Canvas,
|
|
3
|
+
useFrame,
|
|
4
|
+
extend,
|
|
5
|
+
useThree,
|
|
6
|
+
useLoader,
|
|
7
|
+
} from "@react-three/fiber"
|
|
8
|
+
import { Suspense, useEffect, useMemo, useRef, useState } from "react"
|
|
9
|
+
import { OrbitControls, Grid, Outlines } from "@react-three/drei"
|
|
10
|
+
import * as THREE from "three"
|
|
11
|
+
import { CubeWithLabeledSides } from "./three-components/cube-with-labeled-sides"
|
|
12
|
+
import { createBoardGeomFromSoup } from "./soup-to-3d"
|
|
13
|
+
import soup from "./bug-pads-and-traces.json"
|
|
14
|
+
// import soup from "./plated-hole-board.json"
|
|
15
|
+
import stlSerializer from "@jscad/stl-serializer"
|
|
16
|
+
// import { STLLoader } from "three/examples/jsm/loaders/STLLoader"
|
|
17
|
+
import { MTLLoader, OBJLoader, STLLoader } from "three-stdlib"
|
|
18
|
+
|
|
19
|
+
export const RotationTracker = () => {
|
|
20
|
+
useFrame(({ camera }) => {
|
|
21
|
+
window.TSCI_MAIN_CAMERA_ROTATION = camera.rotation
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
return <></>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CadViewerContainer = ({ children }: { children: any }) => {
|
|
28
|
+
return (
|
|
29
|
+
<div style={{ position: "relative", width: "100%", height: "100%" }}>
|
|
30
|
+
<div
|
|
31
|
+
style={{
|
|
32
|
+
position: "absolute",
|
|
33
|
+
top: 0,
|
|
34
|
+
left: 0,
|
|
35
|
+
width: 120,
|
|
36
|
+
height: 120,
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
<Canvas
|
|
40
|
+
camera={{
|
|
41
|
+
up: [0, 0, 1],
|
|
42
|
+
// rotation: [-Math.PI / 2, 0, 0],
|
|
43
|
+
// lookAt: new THREE.Vector3(0, 0, 0),
|
|
44
|
+
position: [1, 1, 1],
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
48
|
+
<CubeWithLabeledSides />
|
|
49
|
+
</Canvas>
|
|
50
|
+
</div>
|
|
51
|
+
<Canvas
|
|
52
|
+
scene={{ up: [0, 0, 1] }}
|
|
53
|
+
camera={{ up: [0, 0, 1], position: [5, 5, 5] }}
|
|
54
|
+
>
|
|
55
|
+
<RotationTracker />
|
|
56
|
+
<OrbitControls autoRotate autoRotateSpeed={1} />
|
|
57
|
+
<ambientLight intensity={Math.PI / 2} />
|
|
58
|
+
<pointLight
|
|
59
|
+
position={[-10, -10, 10]}
|
|
60
|
+
decay={0}
|
|
61
|
+
intensity={Math.PI / 4}
|
|
62
|
+
/>
|
|
63
|
+
<Grid
|
|
64
|
+
rotation={[Math.PI / 2, 0, 0]}
|
|
65
|
+
infiniteGrid={true}
|
|
66
|
+
cellSize={1}
|
|
67
|
+
sectionSize={10}
|
|
68
|
+
/>
|
|
69
|
+
{children}
|
|
70
|
+
</Canvas>
|
|
71
|
+
</div>
|
|
72
|
+
)
|
|
73
|
+
}
|