@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
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"type": "source_component",
|
|
4
|
+
"source_component_id": "simple_resistor_0",
|
|
5
|
+
"name": "R1",
|
|
6
|
+
"supplier_part_numbers": {},
|
|
7
|
+
"ftype": "simple_resistor",
|
|
8
|
+
"resistance": "10kohm"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"type": "schematic_component",
|
|
12
|
+
"source_component_id": "simple_resistor_0",
|
|
13
|
+
"schematic_component_id": "schematic_component_simple_resistor_0",
|
|
14
|
+
"rotation": 0,
|
|
15
|
+
"size": {
|
|
16
|
+
"width": 1,
|
|
17
|
+
"height": 0.3
|
|
18
|
+
},
|
|
19
|
+
"center": {
|
|
20
|
+
"x": 0,
|
|
21
|
+
"y": 0
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"type": "source_port",
|
|
26
|
+
"name": "left",
|
|
27
|
+
"source_port_id": "source_port_0",
|
|
28
|
+
"source_component_id": "simple_resistor_0"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"type": "schematic_port",
|
|
32
|
+
"schematic_port_id": "schematic_port_0",
|
|
33
|
+
"source_port_id": "source_port_0",
|
|
34
|
+
"center": {
|
|
35
|
+
"x": -0.5,
|
|
36
|
+
"y": 0
|
|
37
|
+
},
|
|
38
|
+
"facing_direction": "left",
|
|
39
|
+
"schematic_component_id": "schematic_component_simple_resistor_0"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"type": "pcb_port",
|
|
43
|
+
"pcb_port_id": "pcb_port_0",
|
|
44
|
+
"source_port_id": "source_port_0",
|
|
45
|
+
"pcb_component_id": "pcb_component_simple_resistor_0",
|
|
46
|
+
"x": 0,
|
|
47
|
+
"y": 0
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"type": "source_port",
|
|
51
|
+
"name": "right",
|
|
52
|
+
"source_port_id": "source_port_1",
|
|
53
|
+
"source_component_id": "simple_resistor_0"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"type": "schematic_port",
|
|
57
|
+
"schematic_port_id": "schematic_port_1",
|
|
58
|
+
"source_port_id": "source_port_1",
|
|
59
|
+
"center": {
|
|
60
|
+
"x": 0.5,
|
|
61
|
+
"y": 0
|
|
62
|
+
},
|
|
63
|
+
"facing_direction": "right",
|
|
64
|
+
"schematic_component_id": "schematic_component_simple_resistor_0"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"type": "pcb_port",
|
|
68
|
+
"pcb_port_id": "pcb_port_1",
|
|
69
|
+
"source_port_id": "source_port_1",
|
|
70
|
+
"pcb_component_id": "pcb_component_simple_resistor_0",
|
|
71
|
+
"x": 0,
|
|
72
|
+
"y": 0
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"type": "schematic_text",
|
|
76
|
+
"text": "R1",
|
|
77
|
+
"schematic_text_id": "schematic_text_0",
|
|
78
|
+
"schematic_component_id": "schematic_component_simple_resistor_0",
|
|
79
|
+
"anchor": "left",
|
|
80
|
+
"position": {
|
|
81
|
+
"x": -0.2,
|
|
82
|
+
"y": -0.5
|
|
83
|
+
},
|
|
84
|
+
"rotation": 0
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "schematic_text",
|
|
88
|
+
"text": "10kohm",
|
|
89
|
+
"schematic_text_id": "schematic_text_1",
|
|
90
|
+
"schematic_component_id": "schematic_component_simple_resistor_0",
|
|
91
|
+
"anchor": "left",
|
|
92
|
+
"position": {
|
|
93
|
+
"x": -0.2,
|
|
94
|
+
"y": -0.3
|
|
95
|
+
},
|
|
96
|
+
"rotation": 0
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"type": "pcb_component",
|
|
100
|
+
"source_component_id": "simple_resistor_0",
|
|
101
|
+
"pcb_component_id": "pcb_component_simple_resistor_0",
|
|
102
|
+
"layer": "top",
|
|
103
|
+
"center": {
|
|
104
|
+
"x": 0,
|
|
105
|
+
"y": 0
|
|
106
|
+
},
|
|
107
|
+
"rotation": 0,
|
|
108
|
+
"width": 4.2,
|
|
109
|
+
"height": 1.2
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"type": "pcb_plated_hole",
|
|
113
|
+
"x": -1.5,
|
|
114
|
+
"y": 0,
|
|
115
|
+
"layers": [
|
|
116
|
+
"top",
|
|
117
|
+
"bottom"
|
|
118
|
+
],
|
|
119
|
+
"hole_diameter": 1,
|
|
120
|
+
"outer_diameter": 1.2,
|
|
121
|
+
"port_hints": [
|
|
122
|
+
"1"
|
|
123
|
+
]
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"type": "pcb_plated_hole",
|
|
127
|
+
"x": 1.5,
|
|
128
|
+
"y": 0,
|
|
129
|
+
"layers": [
|
|
130
|
+
"top",
|
|
131
|
+
"bottom"
|
|
132
|
+
],
|
|
133
|
+
"hole_diameter": 1,
|
|
134
|
+
"outer_diameter": 1.2,
|
|
135
|
+
"port_hints": [
|
|
136
|
+
"2"
|
|
137
|
+
]
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"type": "pcb_silkscreen_path",
|
|
141
|
+
"layer": "top",
|
|
142
|
+
"pcb_component_id": "pcb_component_simple_resistor_0",
|
|
143
|
+
"pcb_silkscreen_path_id": "pcb_silkscreen_path_0",
|
|
144
|
+
"route": [
|
|
145
|
+
{
|
|
146
|
+
"x": -0.7,
|
|
147
|
+
"y": -0.8
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"x": -0.7,
|
|
151
|
+
"y": 0.8
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"x": -0.2333333333333333,
|
|
155
|
+
"y": 0.8
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
"x": -0.2155718909193002,
|
|
159
|
+
"y": 0.7107071991148124
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"x": -0.16499158227686106,
|
|
163
|
+
"y": 0.635008417723139
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"x": -0.0892928008851876,
|
|
167
|
+
"y": 0.5844281090806999
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"x": 1.4287545990052454e-17,
|
|
171
|
+
"y": 0.5666666666666668
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"x": 0.08929280088518761,
|
|
175
|
+
"y": 0.5844281090806999
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"x": 0.1649915822768611,
|
|
179
|
+
"y": 0.635008417723139
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
"x": 0.2155718909193002,
|
|
183
|
+
"y": 0.7107071991148124
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"x": 0.2333333333333333,
|
|
187
|
+
"y": 0.8
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"x": 0.7,
|
|
191
|
+
"y": 0.8
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"x": 0.7,
|
|
195
|
+
"y": -0.8
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"x": -0.7,
|
|
199
|
+
"y": -0.8
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
"stroke_width": 0.1
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"type": "pcb_board",
|
|
206
|
+
"center": {
|
|
207
|
+
"x": 0,
|
|
208
|
+
"y": 0
|
|
209
|
+
},
|
|
210
|
+
"width": 5,
|
|
211
|
+
"height": 5
|
|
212
|
+
}
|
|
213
|
+
]
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { Geom3 } from "@jscad/modeling/src/geometries/types"
|
|
2
|
+
import type { AnySoupElement, PCBPlatedHole } from "@tscircuit/soup"
|
|
3
|
+
import { su } from "@tscircuit/soup-util"
|
|
4
|
+
import { translate } from "@jscad/modeling/src/operations/transforms"
|
|
5
|
+
import { cuboid, cylinder, line } from "@jscad/modeling/src/primitives"
|
|
6
|
+
import { colorize } from "@jscad/modeling/src/colors"
|
|
7
|
+
import { subtract } from "@jscad/modeling/src/operations/booleans"
|
|
8
|
+
import { platedHole } from "../geoms/plated-hole"
|
|
9
|
+
import { M, colors } from "../geoms/constants"
|
|
10
|
+
import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions"
|
|
11
|
+
import { expand } from "@jscad/modeling/src/operations/expansions"
|
|
12
|
+
|
|
13
|
+
export const createBoardGeomFromSoup = (soup: AnySoupElement[]): Geom3[] => {
|
|
14
|
+
const board = su(soup).pcb_board.list()[0]
|
|
15
|
+
if (!board) {
|
|
16
|
+
throw new Error("No pcb_board found")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const plated_holes = su(soup).pcb_plated_hole.list()
|
|
20
|
+
const pads = su(soup).pcb_smtpad.list()
|
|
21
|
+
const traces = su(soup).pcb_trace.list()
|
|
22
|
+
const pcb_vias = su(soup).pcb_via.list()
|
|
23
|
+
|
|
24
|
+
// PCB Board
|
|
25
|
+
let boardGeom = cuboid({ size: [board.width, board.height, 1.2] })
|
|
26
|
+
|
|
27
|
+
const platedHoleGeoms: Geom3[] = []
|
|
28
|
+
const padGeoms: Geom3[] = []
|
|
29
|
+
const traceGeoms: Geom3[] = []
|
|
30
|
+
const ctx = {
|
|
31
|
+
pcbThickness: 1.2,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const addPlatedHole = (plated_hole: PCBPlatedHole) => {
|
|
35
|
+
if (!(plated_hole as any).shape) plated_hole.shape = "circle"
|
|
36
|
+
if (plated_hole.shape === "circle") {
|
|
37
|
+
const cyGeom = cylinder({
|
|
38
|
+
center: [plated_hole.x, plated_hole.y, 0],
|
|
39
|
+
radius: plated_hole.hole_diameter / 2 + M,
|
|
40
|
+
})
|
|
41
|
+
boardGeom = subtract(boardGeom, cyGeom)
|
|
42
|
+
|
|
43
|
+
const platedHoleGeom = platedHole(plated_hole, ctx)
|
|
44
|
+
platedHoleGeoms.push(platedHoleGeom)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const plated_hole of plated_holes) {
|
|
49
|
+
addPlatedHole(plated_hole)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const pad of pads) {
|
|
53
|
+
if (pad.shape === "rect") {
|
|
54
|
+
const padGeom = colorize(
|
|
55
|
+
colors.copper,
|
|
56
|
+
cuboid({
|
|
57
|
+
center: [pad.x, pad.y, 1.2 / 2 + M],
|
|
58
|
+
size: [pad.width, pad.height, M],
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
padGeoms.push(padGeom)
|
|
62
|
+
} else if (pad.shape === "circle") {
|
|
63
|
+
const padGeom = colorize(
|
|
64
|
+
colors.copper,
|
|
65
|
+
cylinder({
|
|
66
|
+
center: [pad.x, pad.y, 1.2 / 2 + M],
|
|
67
|
+
radius: pad.radius,
|
|
68
|
+
height: M,
|
|
69
|
+
})
|
|
70
|
+
)
|
|
71
|
+
padGeoms.push(padGeom)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const { route: mixedRoute } of traces) {
|
|
76
|
+
const subRoutes = mixedRoute.reduce(
|
|
77
|
+
(c, p) => {
|
|
78
|
+
// @ts-ignore
|
|
79
|
+
const lastLayer = c.current?.[c.current.length - 1]?.layer
|
|
80
|
+
if (
|
|
81
|
+
p.route_type === "via" ||
|
|
82
|
+
(p.route_type === "wire" && p.layer !== lastLayer)
|
|
83
|
+
) {
|
|
84
|
+
if (c.current.length > 2) {
|
|
85
|
+
c.allPrev.push(c.current)
|
|
86
|
+
}
|
|
87
|
+
c.current = p.route_type === "wire" ? [p] : []
|
|
88
|
+
return c
|
|
89
|
+
}
|
|
90
|
+
c.current.push(p)
|
|
91
|
+
return c
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
current: [] as typeof mixedRoute,
|
|
95
|
+
allPrev: [] as Array<typeof mixedRoute>,
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
for (const route of subRoutes.allPrev.concat([subRoutes.current])) {
|
|
99
|
+
// TODO break into segments based on layers
|
|
100
|
+
const linePath = line(route.map((p) => [p.x, p.y]))
|
|
101
|
+
|
|
102
|
+
const layer = route[0]!.route_type === "wire" ? route[0]!.layer : "top"
|
|
103
|
+
const layerSign = layer === "top" ? 1 : -1
|
|
104
|
+
// traceGeoms.push(traceGeom)
|
|
105
|
+
let traceGeom = translate(
|
|
106
|
+
[0, 0, (layerSign * 1.2) / 2],
|
|
107
|
+
extrudeLinear(
|
|
108
|
+
{ height: M * layerSign },
|
|
109
|
+
expand({ delta: 0.1, corners: "edge" }, linePath)
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
// HACK: Subtract all vias from every trace- this mostly is because the
|
|
114
|
+
// vias aren't inside the route- we should probably pre-filter to make sure
|
|
115
|
+
// that vias are only near the route
|
|
116
|
+
for (const via of pcb_vias) {
|
|
117
|
+
traceGeom = subtract(
|
|
118
|
+
traceGeom,
|
|
119
|
+
cylinder({
|
|
120
|
+
center: [via.x, via.y, 0],
|
|
121
|
+
radius: via.outer_diameter / 2,
|
|
122
|
+
height: 5,
|
|
123
|
+
})
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
traceGeom = colorize(colors.fr4GreenSolderWithMask, traceGeom)
|
|
128
|
+
|
|
129
|
+
traceGeoms.push(traceGeom)
|
|
130
|
+
}
|
|
131
|
+
for (const via of mixedRoute.filter((p) => p.route_type === "via")) {
|
|
132
|
+
if (via.route_type !== "via") continue // TODO remove when ts is smart
|
|
133
|
+
|
|
134
|
+
addPlatedHole({
|
|
135
|
+
x: via.x,
|
|
136
|
+
y: via.y,
|
|
137
|
+
hole_diameter: 0.8,
|
|
138
|
+
outer_diameter: 1.6,
|
|
139
|
+
shape: "circle",
|
|
140
|
+
layers: ["top", "bottom"],
|
|
141
|
+
type: "pcb_plated_hole",
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
for (const via of pcb_vias) {
|
|
147
|
+
addPlatedHole({
|
|
148
|
+
x: via.x,
|
|
149
|
+
y: via.y,
|
|
150
|
+
hole_diameter: via.hole_diameter,
|
|
151
|
+
outer_diameter: via.outer_diameter,
|
|
152
|
+
shape: "circle",
|
|
153
|
+
layers: ["top", "bottom"],
|
|
154
|
+
type: "pcb_plated_hole",
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Colorize to a PCB green color: #05A32E
|
|
159
|
+
boardGeom = colorize(colors.fr4Green, boardGeom)
|
|
160
|
+
|
|
161
|
+
return [boardGeom, ...platedHoleGeoms, ...padGeoms, ...traceGeoms]
|
|
162
|
+
}
|
package/src/themes.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Geom3 } from "@jscad/modeling/src/geometries/types"
|
|
2
|
+
|
|
3
|
+
export const GeomModel = ({ geom }: { geom: Geom3[] | Geom3 }) => {
|
|
4
|
+
if (!Array.isArray(geom)) {
|
|
5
|
+
geom = [geom]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// TODO useGeomToStl
|
|
9
|
+
|
|
10
|
+
// TODO STLModel
|
|
11
|
+
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useEffect, useState } from "react"
|
|
2
|
+
import { Euler, Vector3 } from "three"
|
|
3
|
+
import { MTLLoader, OBJLoader } from "three-stdlib"
|
|
4
|
+
|
|
5
|
+
export function MixedStlModel({
|
|
6
|
+
url,
|
|
7
|
+
position,
|
|
8
|
+
rotation,
|
|
9
|
+
}: {
|
|
10
|
+
url: string
|
|
11
|
+
position?: Vector3 | [number, number, number]
|
|
12
|
+
rotation?: Euler | [number, number, number]
|
|
13
|
+
}) {
|
|
14
|
+
// const group = useLoader(OBJLoader, url)
|
|
15
|
+
// const materials = useLoader(MTLLoader, url)
|
|
16
|
+
// const obj = useLoader(OBJLoader, url)
|
|
17
|
+
|
|
18
|
+
const [obj, setObj] = useState<any | null>(null)
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
async function loadUrlContent() {
|
|
21
|
+
const response = await fetch(url)
|
|
22
|
+
const text = await response.text()
|
|
23
|
+
|
|
24
|
+
// Extract all the sections of the file that have newmtl...endmtl to
|
|
25
|
+
// separate into mtlContent and objContent
|
|
26
|
+
|
|
27
|
+
const mtlContent = text
|
|
28
|
+
.match(/newmtl[\s\S]*?endmtl/g)
|
|
29
|
+
?.join("\n")!
|
|
30
|
+
.replace(/d 0\./g, "d 1.")!
|
|
31
|
+
const objContent = text.replace(/newmtl[\s\S]*?endmtl/g, "")
|
|
32
|
+
|
|
33
|
+
const mtlLoader = new MTLLoader()
|
|
34
|
+
mtlLoader.setMaterialOptions({
|
|
35
|
+
invertTrProperty: true,
|
|
36
|
+
})
|
|
37
|
+
const materials = mtlLoader.parse(
|
|
38
|
+
// Grayscale the colors, for some reason everything from JLCPCB is
|
|
39
|
+
// a bit red, it doesn't look right. The grayscale version looks OK,
|
|
40
|
+
// it's a HACK because we only take the second color rather than
|
|
41
|
+
// averaging the colors
|
|
42
|
+
mtlContent.replace(
|
|
43
|
+
/Kd\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)/g,
|
|
44
|
+
"Kd $2 $2 $2"
|
|
45
|
+
),
|
|
46
|
+
"test.mtl"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
const objLoader = new OBJLoader()
|
|
50
|
+
objLoader.setMaterials(materials)
|
|
51
|
+
setObj(objLoader.parse(objContent))
|
|
52
|
+
}
|
|
53
|
+
loadUrlContent()
|
|
54
|
+
}, [url])
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<group rotation={rotation} position={position}>
|
|
58
|
+
{obj && <primitive object={obj} />}
|
|
59
|
+
</group>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useLoader } from "@react-three/fiber"
|
|
2
|
+
import { useRef } from "react"
|
|
3
|
+
import * as THREE from "three"
|
|
4
|
+
import { MTLLoader, OBJLoader, STLLoader } from "three-stdlib"
|
|
5
|
+
|
|
6
|
+
export function STLModel({
|
|
7
|
+
stlUrl,
|
|
8
|
+
mtlUrl,
|
|
9
|
+
color,
|
|
10
|
+
opacity = 1,
|
|
11
|
+
}: {
|
|
12
|
+
stlUrl: string
|
|
13
|
+
color?: any
|
|
14
|
+
mtlUrl?: string
|
|
15
|
+
opacity?: number
|
|
16
|
+
}) {
|
|
17
|
+
const geom = useLoader(STLLoader, stlUrl)
|
|
18
|
+
const mesh = useRef<THREE.Mesh>()
|
|
19
|
+
|
|
20
|
+
// TODO handle mtl url
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<mesh ref={mesh as any}>
|
|
24
|
+
<primitive object={geom} attach="geometry" />
|
|
25
|
+
<meshStandardMaterial
|
|
26
|
+
color={color}
|
|
27
|
+
transparent={opacity !== 1}
|
|
28
|
+
opacity={opacity}
|
|
29
|
+
/>
|
|
30
|
+
{/* <Outlines thickness={0.05} color="black" opacity={0.25} /> */}
|
|
31
|
+
</mesh>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useRef } from "react"
|
|
2
|
+
import { Canvas, useFrame } from "@react-three/fiber"
|
|
3
|
+
import { OrbitControls, Text } from "@react-three/drei"
|
|
4
|
+
import * as THREE from "three"
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
interface Window {
|
|
8
|
+
TSCI_MAIN_CAMERA_ROTATION: THREE.Euler
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
window.TSCI_MAIN_CAMERA_ROTATION = new THREE.Euler(0, 0, 0)
|
|
12
|
+
|
|
13
|
+
function computePointInFront(rotationVector, distance) {
|
|
14
|
+
// Create a quaternion from the rotation vector
|
|
15
|
+
const quaternion = new THREE.Quaternion().setFromEuler(
|
|
16
|
+
new THREE.Euler(rotationVector.x, rotationVector.y, rotationVector.z)
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
// Create a vector pointing forward (along the negative z-axis)
|
|
20
|
+
const forwardVector = new THREE.Vector3(0, 0, 1)
|
|
21
|
+
|
|
22
|
+
// Apply the rotation to the forward vector
|
|
23
|
+
forwardVector.applyQuaternion(quaternion)
|
|
24
|
+
|
|
25
|
+
// Scale the rotated vector by the distance
|
|
26
|
+
const result = forwardVector.multiplyScalar(distance)
|
|
27
|
+
|
|
28
|
+
return result
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const CubeWithLabeledSides = ({}: any) => {
|
|
32
|
+
const ref = useRef<THREE.Mesh>()
|
|
33
|
+
const rotationTrackingRef = useRef({ lastRotation: new THREE.Euler() })
|
|
34
|
+
useFrame((state, delta) => {
|
|
35
|
+
if (!ref.current) return
|
|
36
|
+
|
|
37
|
+
const mainRot = window.TSCI_MAIN_CAMERA_ROTATION
|
|
38
|
+
|
|
39
|
+
// Use window.TSCI_CAMERA_ROTATION to compute the position of the camera
|
|
40
|
+
const cameraPosition = computePointInFront(mainRot, 2)
|
|
41
|
+
|
|
42
|
+
state.camera.position.copy(cameraPosition)
|
|
43
|
+
state.camera.lookAt(0, 0, 0)
|
|
44
|
+
})
|
|
45
|
+
return (
|
|
46
|
+
<mesh ref={ref as any} rotation={[Math.PI / 2, 0, 0]}>
|
|
47
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
48
|
+
<meshStandardMaterial color="white" />
|
|
49
|
+
<Text position={[0, 0, 0.51]} fontSize={0.25} color="black">
|
|
50
|
+
Front
|
|
51
|
+
</Text>
|
|
52
|
+
<Text
|
|
53
|
+
position={[0, 0, -0.51]}
|
|
54
|
+
fontSize={0.25}
|
|
55
|
+
color="black"
|
|
56
|
+
rotation={[0, Math.PI, 0]}
|
|
57
|
+
>
|
|
58
|
+
Back
|
|
59
|
+
</Text>
|
|
60
|
+
<Text
|
|
61
|
+
position={[0.51, 0, 0]}
|
|
62
|
+
fontSize={0.25}
|
|
63
|
+
color="black"
|
|
64
|
+
rotation={[0, Math.PI / 2, 0]}
|
|
65
|
+
>
|
|
66
|
+
Right
|
|
67
|
+
</Text>
|
|
68
|
+
<Text
|
|
69
|
+
position={[-0.51, 0, 0]}
|
|
70
|
+
fontSize={0.25}
|
|
71
|
+
color="black"
|
|
72
|
+
rotation={[0, -Math.PI / 2, 0]}
|
|
73
|
+
>
|
|
74
|
+
Left
|
|
75
|
+
</Text>
|
|
76
|
+
<Text
|
|
77
|
+
position={[0, 0.51, 0]}
|
|
78
|
+
fontSize={0.25}
|
|
79
|
+
color="black"
|
|
80
|
+
rotation={[-Math.PI / 2, 0, 0]}
|
|
81
|
+
>
|
|
82
|
+
Top
|
|
83
|
+
</Text>
|
|
84
|
+
<Text
|
|
85
|
+
position={[0, -0.51, 0]}
|
|
86
|
+
fontSize={0.25}
|
|
87
|
+
color="black"
|
|
88
|
+
rotation={[Math.PI / 2, 0, 0]}
|
|
89
|
+
>
|
|
90
|
+
Bottom
|
|
91
|
+
</Text>
|
|
92
|
+
<lineSegments
|
|
93
|
+
args={[new THREE.EdgesGeometry(new THREE.BoxGeometry(1, 1, 1))]}
|
|
94
|
+
material={
|
|
95
|
+
new THREE.LineBasicMaterial({
|
|
96
|
+
color: 0x000000,
|
|
97
|
+
linewidth: 2,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
/>
|
|
101
|
+
</mesh>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// const LabeledCubeScene = () => {
|
|
106
|
+
// return (
|
|
107
|
+
// <Canvas camera={{ position: [1.5, 1.5, 1.5] }}>
|
|
108
|
+
// <ambientLight intensity={0.5} />
|
|
109
|
+
// <pointLight position={[10, 10, 10]} />
|
|
110
|
+
// <LabeledCube />
|
|
111
|
+
// <OrbitControls />
|
|
112
|
+
// </Canvas>
|
|
113
|
+
// )
|
|
114
|
+
// }
|
|
115
|
+
|
|
116
|
+
// export default LabeledCubeScene
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { fn } from "@storybook/test"
|
|
2
|
+
import { CadViewer } from "src/CadViewer"
|
|
3
|
+
import bugsPadsAndTracesSoup from "./assets/soic-with-traces.json"
|
|
4
|
+
|
|
5
|
+
export const Default = () => <CadViewer soup={bugsPadsAndTracesSoup as any} />
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: "Simple",
|
|
9
|
+
component: Default,
|
|
10
|
+
}
|