Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import React from "react"
import type { Preview } from "@storybook/react"
import type { StoryFn } from "@storybook/react"
import type { Preview, StoryFn } from "@storybook/react"

const withContainer = (Story: StoryFn) => (
<div style={{ width: "100vw", height: "100vh" }}>
Expand All @@ -10,6 +8,7 @@ const withContainer = (Story: StoryFn) => (

const preview: Preview = {
parameters: {
layout: "fullscreen",
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
Expand Down
38 changes: 19 additions & 19 deletions src/CadViewer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type React from "react"
import { useState, useCallback, useRef, useEffect } from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import * as THREE from "three"

// Constants for camera initialization - defined once, reused across renders
Expand All @@ -13,28 +12,29 @@ const readStoredCameraType = (): "perspective" | "orthographic" | undefined => {
? stored
: undefined
}

import { CadViewerJscad } from "./CadViewerJscad"
import CadViewerManifold from "./CadViewerManifold"
import { useContextMenu } from "./hooks/useContextMenu"
import { useCameraPreset } from "./hooks/useCameraPreset"
import { useGlobalDownloadGltf } from "./hooks/useGlobalDownloadGltf"
import { ContextMenu } from "./components/ContextMenu"
import { KeyboardShortcutsDialog } from "./components/KeyboardShortcutsDialog"
import { AppearanceProvider } from "./contexts/appearance-context"
import {
useRegisteredHotkey,
registerHotkeyViewer,
} from "./hooks/useRegisteredHotkey"
CameraControllerProvider,
useCameraController,
} from "./contexts/CameraControllerContext"
import {
LayerVisibilityProvider,
useLayerVisibility,
} from "./contexts/LayerVisibilityContext"
import { RenderingModeProvider } from "./contexts/RenderingModeContext"
import {
CameraControllerProvider,
useCameraController,
} from "./contexts/CameraControllerContext"
import { ToastProvider, useToast } from "./contexts/ToastContext"
import { ContextMenu } from "./components/ContextMenu"
import { KeyboardShortcutsDialog } from "./components/KeyboardShortcutsDialog"
import type { CameraController, CameraPreset } from "./hooks/cameraAnimation"
import { useCameraPreset } from "./hooks/useCameraPreset"
import { useContextMenu } from "./hooks/useContextMenu"
import { useGlobalDownloadGltf } from "./hooks/useGlobalDownloadGltf"
import {
registerHotkeyViewer,
useRegisteredHotkey,
} from "./hooks/useRegisteredHotkey"

const CadViewerInner = (props: any) => {
const [engine, setEngine] = useState<"jscad" | "manifold">(() => {
Expand All @@ -46,14 +46,14 @@ const CadViewerInner = (props: any) => {
useState(false)
const [autoRotate, setAutoRotate] = useState(() => {
const stored = window.localStorage.getItem("cadViewerAutoRotate")
return stored === "false" ? false : true
return stored !== "false"
})
const [autoRotateUserToggled, setAutoRotateUserToggled] = useState(() => {
const stored = window.localStorage.getItem("cadViewerAutoRotateUserToggled")
return stored === "true"
})
const [cameraPreset, setCameraPreset] = useState<CameraPreset>("Custom")
const { cameraType, setCameraType } = useCameraController()
const { cameraType } = useCameraController()
const { visibility, setLayerVisibility } = useLayerVisibility()
const { showToast } = useToast()

Expand Down Expand Up @@ -313,11 +313,11 @@ export const CadViewer = (props: any) => {
initialCameraType={readStoredCameraType()}
>
<LayerVisibilityProvider>
<RenderingModeProvider>
<AppearanceProvider>
<ToastProvider>
<CadViewerInner {...props} />
</ToastProvider>
</RenderingModeProvider>
</AppearanceProvider>
</LayerVisibilityProvider>
</CameraControllerProvider>
)
Expand Down
37 changes: 11 additions & 26 deletions src/CadViewerContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type * as React from "react"
import { forwardRef, useEffect, useMemo, useState } from "react"
import * as THREE from "three"
import { zIndexMap } from "../lib/utils/z-index-map"
import packageJson from "../package.json"
import { useAppearance } from "./contexts/appearance-context"
import { useCameraController } from "./contexts/CameraControllerContext"
import type { CameraController } from "./hooks/cameraAnimation"
import { CameraAnimatorWithContext } from "./hooks/cameraAnimation"
import { useCameraSession } from "./hooks/useCameraSession"
import { Canvas } from "./react-three/Canvas"
import { Lights } from "./react-three/Lights"
import { OrbitControls } from "./react-three/OrbitControls"
import { Grid } from "./react-three/Grid"
import { useFrame, useThree } from "./react-three/ThreeContext"
import { Lights } from "./react-three/Lights"
import { CameraAnimatorWithContext } from "./hooks/cameraAnimation"
import { useCameraController } from "./contexts/CameraControllerContext"
import { useRenderingMode } from "./contexts/RenderingModeContext"
import { useCameraSession } from "./hooks/useCameraSession"
import type { CameraController } from "./hooks/cameraAnimation"
import { OrientationCubeCanvas } from "./three-components/OrientationCubeCanvas"
import { zIndexMap } from "../lib/utils/z-index-map"

export type {
CameraController,
CameraPreset,
Expand Down Expand Up @@ -65,7 +65,7 @@ export const CadViewerContainer = forwardRef<

const { mainCameraRef, handleControlsChange, controller } =
useCameraController()
const { shadowsEnabled } = useRenderingMode()
const { darkBackgroundEnabled, lightingEnabled } = useAppearance()
const {
handleCameraCreated,
handleControlsChange: handleSessionControlsChange,
Expand All @@ -77,15 +77,6 @@ export const CadViewerContainer = forwardRef<
}
}, [controller, onCameraControllerReady])

const gridSectionSize = useMemo(() => {
if (!boardDimensions) return 10
const width = boardDimensions.width ?? 0
const height = boardDimensions.height ?? 0
const largest = Math.max(width, height)
const desired = largest * 1.5
return desired > 10 ? desired : 10
}, [boardDimensions])

const orbitTarget = useMemo(() => {
if (!boardCenter) return undefined
return [boardCenter.x, boardCenter.y, 0] as [number, number, number]
Expand Down Expand Up @@ -125,14 +116,8 @@ export const CadViewerContainer = forwardRef<
<Lights
boardDimensions={boardDimensions}
boardCenter={boardCenter}
shadowsEnabled={shadowsEnabled}
/>
<Grid
rotation={[Math.PI / 2, 0, 0]}
infiniteGrid={true}
cellSize={3}
sectionSize={gridSectionSize}
args={[gridSectionSize, gridSectionSize]}
darkBackgroundEnabled={darkBackgroundEnabled}
shadowsEnabled={lightingEnabled}
/>
{children}
</Canvas>
Expand Down
25 changes: 10 additions & 15 deletions src/CadViewerManifold.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import * as THREE from "three"
import { AnyCadComponent } from "./AnyCadComponent"
import { CadViewerContainer } from "./CadViewerContainer"
import { useLayerVisibility } from "./contexts/LayerVisibilityContext"
import { useRenderingMode } from "./contexts/RenderingModeContext"
import type { CameraController } from "./hooks/cameraAnimation"
import { useConvertChildrenToCircuitJson } from "./hooks/use-convert-children-to-soup"
import { useManifoldBoardBuilder } from "./hooks/useManifoldBoardBuilder"
Expand Down Expand Up @@ -77,7 +76,7 @@ export const BoardMeshes = ({
useEffect(() => {
if (!rootObject) return

geometryMeshes.forEach((mesh) => {
for (const mesh of geometryMeshes) {
let shouldShow = true
if (mesh.name === "board-geom") {
shouldShow = visibility.boardBody
Expand All @@ -91,32 +90,32 @@ export const BoardMeshes = ({
if (shouldShow) {
rootObject.add(mesh)
}
})
}

return () => {
geometryMeshes.forEach((mesh) => {
for (const mesh of geometryMeshes) {
if (mesh.parent === rootObject) {
rootObject.remove(mesh)
}
disposeMesh(mesh)
})
}
}
}, [rootObject, geometryMeshes, visibility])

useEffect(() => {
if (!rootObject) return

textureMeshes.forEach((mesh) => {
for (const mesh of textureMeshes) {
rootObject.add(mesh)
})
}

return () => {
textureMeshes.forEach((mesh) => {
for (const mesh of textureMeshes) {
if (mesh.parent === rootObject) {
rootObject.remove(mesh)
}
disposeMesh(mesh)
})
}
}
}, [rootObject, textureMeshes])

Expand Down Expand Up @@ -157,7 +156,6 @@ const CadViewerManifold: React.FC<CadViewerManifoldProps> = ({
string | null
>(null)
const { visibility } = useLayerVisibility()
const { shadowsEnabled } = useRenderingMode()

useEffect(() => {
if (
Expand Down Expand Up @@ -248,11 +246,8 @@ try {

const geometryMeshes = useMemo(() => createGeometryMeshes(geoms), [geoms])
const textureMeshes = useMemo(
() =>
createTextureMeshes(textures, boardData, pcbThickness, isFauxBoard, {
shadowsEnabled,
}),
[textures, boardData, pcbThickness, isFauxBoard, shadowsEnabled],
() => createTextureMeshes(textures, boardData, pcbThickness, isFauxBoard),
[textures, boardData, pcbThickness, isFauxBoard],
)

const cadComponents = useMemo(
Expand Down
24 changes: 24 additions & 0 deletions src/board-surface-textures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Values tuned for a manufactured, ENIG-finished FR-4 board under soft studio
* lighting. The solder mask has a fine satin texture, while the finished
* copper stays deliberately less reflective than polished metal.
*/
export const REALISTIC_BOARD_SURFACE_MATERIAL = {
bumpScale: 0.12,
normalScale: 0.08,
// This value is carried by the roughness map directly. Keep the clear coat
// restrained so the mask looks satin, not mirror-polished.
roughness: 0.7,
roughnessBias: 0.015,
roughnessVariance: 0.025,
clearcoat: 0.08,
clearcoatRoughness: 0.55,
detailStrength: 0.035,
}

export const PAD_COPPER_TEXTURE_MATERIAL = {
roughness: 0.42,
metalness: 0.5,
roughnessVariance: 0.035,
detailStrength: 0.028,
}
38 changes: 33 additions & 5 deletions src/components/AppearanceMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import type React from "react"
import { useState } from "react"
import { zIndexMap } from "../../lib/utils/z-index-map"
import { useAppearance } from "../contexts/appearance-context"
import { useLayerVisibility } from "../contexts/LayerVisibilityContext"
import { useRenderingMode } from "../contexts/RenderingModeContext"
import type React from "react"
import * as DropdownMenu from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon } from "./Icons"
import { zIndexMap } from "../../lib/utils/z-index-map"

const itemStyles: React.CSSProperties = {
padding: "6px 8px",
Expand Down Expand Up @@ -62,7 +62,12 @@ const iconContainerStyles: React.CSSProperties = {

export const AppearanceMenu = () => {
const { visibility, setLayerVisibility } = useLayerVisibility()
const { lightingEnabled, setLightingEnabled } = useRenderingMode()
const {
darkBackgroundEnabled,
setDarkBackgroundEnabled,
lightingEnabled,
setLightingEnabled,
} = useAppearance()
const [appearanceSubOpen, setAppearanceSubOpen] = useState(false)
const [hoveredItem, setHoveredItem] = useState<string | null>(null)

Expand Down Expand Up @@ -105,6 +110,29 @@ export const AppearanceMenu = () => {
collisionPadding={10}
avoidCollisions={true}
>
<DropdownMenu.Item
style={{
...itemStyles,
backgroundColor:
hoveredItem === "darkBackground" ? "#404040" : "transparent",
}}
onSelect={(e) => e.preventDefault()}
onPointerDown={(e) => {
e.preventDefault()
setDarkBackgroundEnabled(!darkBackgroundEnabled)
}}
onMouseEnter={() => setHoveredItem("darkBackground")}
onMouseLeave={() => setHoveredItem(null)}
onTouchStart={() => setHoveredItem("darkBackground")}
>
<span style={iconContainerStyles}>
{darkBackgroundEnabled && <CheckIcon />}
</span>
<span style={{ display: "flex", alignItems: "center" }}>
Dark Background
</span>
</DropdownMenu.Item>

<DropdownMenu.Item
style={{
...itemStyles,
Expand Down
Loading
Loading