Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
114 changes: 114 additions & 0 deletions src/CadViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,32 @@ import { CadViewerJscad } from "./CadViewerJscad"
import CadViewerManifold from "./CadViewerManifold"
import { useContextMenu } from "./hooks/useContextMenu"
import { useGlobalDownloadGltf } from "./hooks/useGlobalDownloadGltf"
import { LayerVisibilitySubmenu } from "./LayerVisibilitySubmenu"
import { getPresentLayers, type LayerVisibility } from "./utils/layerDetection"
import packageJson from "../package.json"

const defaultLayerVisibility: LayerVisibility = {
board: true,
fCu: true,
bCu: true,
fSilkscreen: true,
bSilkscreen: true,
cadComponents: true,
}

export const CadViewer = (props: any) => {
const [engine, setEngine] = useState<"jscad" | "manifold">("manifold")
const containerRef = useRef<HTMLDivElement | null>(null)
const submenuRef = useRef<HTMLDivElement | null>(null)
const [autoRotate, setAutoRotate] = useState(true)
const [autoRotateUserToggled, setAutoRotateUserToggled] = useState(false)
const [layerVisibility, setLayerVisibility] = useState<LayerVisibility>(
defaultLayerVisibility,
)
const [layersSubmenuVisible, setLayersSubmenuVisible] = useState(false)
const [presentLayers, setPresentLayers] = useState<Partial<LayerVisibility>>(
{},
)

const {
menuVisible,
Expand All @@ -19,6 +38,56 @@ export const CadViewer = (props: any) => {
setMenuVisible,
} = useContextMenu({ containerRef })

// Override menuRef.contains to include both main menu and submenu
useEffect(() => {
if (menuRef.current) {
const originalContains = menuRef.current.contains
menuRef.current.contains = (node: Node) => {
const isInMainMenu = originalContains.call(menuRef.current!, node)
const isInSubmenu = submenuRef.current?.contains(node) || false
return isInMainMenu || isInSubmenu
}
}
}, [menuVisible]) // Re-run when menu becomes visible

// Handle clicks outside both menus to close them
useEffect(() => {
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
const target = e.target as Node
const isClickInsideMainMenu =
menuRef.current && menuRef.current.contains(target)
const isClickInsideSubmenu =
submenuRef.current && submenuRef.current.contains(target)

if (!isClickInsideMainMenu && !isClickInsideSubmenu) {
setMenuVisible(false)
setLayersSubmenuVisible(false)
} else {
// If click is inside either menu, prevent the useContextMenu's click outside from running
e.stopPropagation()
}
}

if (menuVisible) {
// Add our handler first (higher priority)
document.addEventListener("mousedown", handleClickOutside, true) // use capture phase
document.addEventListener("touchstart", handleClickOutside, true)
return () => {
document.removeEventListener("mousedown", handleClickOutside, true)
document.removeEventListener("touchstart", handleClickOutside, true)
}
}
}, [menuVisible])

// Update present layers when circuitJson changes
useEffect(() => {
if (props.circuitJson) {
setPresentLayers(getPresentLayers(props.circuitJson))
} else {
setPresentLayers({})
}
}, [props.circuitJson])

const autoRotateUserToggledRef = useRef(autoRotateUserToggled)
autoRotateUserToggledRef.current = autoRotateUserToggled

Expand All @@ -35,6 +104,17 @@ export const CadViewer = (props: any) => {

const downloadGltf = useGlobalDownloadGltf()

const toggleLayerVisibility = useCallback((layer: keyof LayerVisibility) => {
setLayerVisibility((prev) => ({
...prev,
[layer]: !prev[layer],
}))
}, [])

const showAllLayers = useCallback(() => {
setLayerVisibility(defaultLayerVisibility)
}, [])

const handleMenuClick = (newEngine: "jscad" | "manifold") => {
setEngine(newEngine)
setMenuVisible(false)
Expand Down Expand Up @@ -76,12 +156,14 @@ export const CadViewer = (props: any) => {
{...props}
autoRotateDisabled={props.autoRotateDisabled || !autoRotate}
onUserInteraction={handleUserInteraction}
layerVisibility={layerVisibility}
/>
) : (
<CadViewerManifold
{...props}
autoRotateDisabled={props.autoRotateDisabled || !autoRotate}
onUserInteraction={handleUserInteraction}
layerVisibility={layerVisibility}
/>
)}
<div
Expand Down Expand Up @@ -199,6 +281,28 @@ export const CadViewer = (props: any) => {
>
Download GLTF
</div>
<div
style={{
padding: "12px 18px",
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: 10,
color: "#f5f6fa",
fontWeight: 500,
borderRadius: 6,
transition: "background 0.1s",
position: "relative",
}}
onClick={() => setLayersSubmenuVisible(!layersSubmenuVisible)}
onMouseOver={(e) => (e.currentTarget.style.background = "#2d313a")}
onMouseOut={(e) => {
e.currentTarget.style.background = "transparent"
}}
>
<span>Toggle Layers</span>
<span style={{ marginLeft: "auto", fontSize: 12 }}>▶</span>
</div>
<div
style={{
display: "flex",
Expand All @@ -221,6 +325,16 @@ export const CadViewer = (props: any) => {
</div>
</div>
)}
{layersSubmenuVisible && (
<LayerVisibilitySubmenu
ref={submenuRef}
layerVisibility={layerVisibility}
presentLayers={presentLayers}
onToggleLayer={toggleLayerVisibility}
onShowAllLayers={showAllLayers}
position={menuPos}
/>
)}
</div>
)
}
68 changes: 47 additions & 21 deletions src/CadViewerJscad.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ interface Props {
circuitJson?: AnyCircuitElement[]
autoRotateDisabled?: boolean
clickToInteractEnabled?: boolean
layerVisibility?: {
board: boolean
platedHoles: boolean
smtPads: boolean
vias: boolean
copperPours: boolean
topTrace: boolean
bottomTrace: boolean
topSilkscreen: boolean
bottomSilkscreen: boolean
cadComponents: boolean
}
onUserInteraction?: () => void
}

Expand All @@ -40,6 +52,18 @@ export const CadViewerJscad = forwardRef<
children,
autoRotateDisabled,
clickToInteractEnabled,
layerVisibility = {
board: true,
platedHoles: true,
smtPads: true,
vias: true,
copperPours: true,
topTrace: true,
bottomTrace: true,
topSilkscreen: true,
bottomSilkscreen: true,
cadComponents: true,
},
onUserInteraction,
},
ref,
Expand Down Expand Up @@ -114,28 +138,30 @@ export const CadViewerJscad = forwardRef<
boardCenter={boardCenter}
onUserInteraction={onUserInteraction}
>
{boardStls.map(({ stlData, color }, index) => (
<STLModel
key={`board-${index - boardStls.length}`}
stlData={stlData}
color={color}
opacity={index === 0 ? 0.95 : 1}
/>
))}
{cad_components.map((cad_component) => (
<ThreeErrorBoundary
key={cad_component.cad_component_id}
fallback={({ error }) => (
<Error3d cad_component={cad_component} error={error} />
)}
>
<AnyCadComponent
key={cad_component.cad_component_id}
cad_component={cad_component}
circuitJson={internalCircuitJson}
{layerVisibility.board &&
boardStls.map(({ stlData, color }, index) => (
<STLModel
key={`board-${index - boardStls.length}`}
stlData={stlData}
color={color}
opacity={index === 0 ? 0.95 : 1}
/>
</ThreeErrorBoundary>
))}
))}
{layerVisibility.cadComponents &&
cad_components.map((cad_component) => (
<ThreeErrorBoundary
key={cad_component.cad_component_id}
fallback={({ error }) => (
<Error3d cad_component={cad_component} error={error} />
)}
>
<AnyCadComponent
key={cad_component.cad_component_id}
cad_component={cad_component}
circuitJson={internalCircuitJson}
/>
</ThreeErrorBoundary>
))}
</CadViewerContainer>
)
},
Expand Down
Loading