diff --git a/index.html b/index.html index 116b64c..c556c7d 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ rel="stylesheet" href="https://app.autodeskforma.eu/design-system/v2/forma/styles/base.css" /> - + + Shadow study diff --git a/package.json b/package.json index 73da9cd..a4bebef 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "file-saver": "^2.0.5", - "forma-embedded-view-sdk": "0.59.1", + "forma-embedded-view-sdk": "^0.60.1", "jszip": "3.10.1", "lodash": "^4.17.21", "luxon": "^3.4.4", diff --git a/src/app.tsx b/src/app.tsx index e8e213f..006a9d7 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -5,6 +5,7 @@ import IntervalSelector from "./components/IntervalSelector"; import ResolutionSelector from "./components/ResolutionSelector"; import TimeSelector from "./components/TimeSelector"; import PreviewButton from "./components/PreviewButton"; +import GeometryColorSelector from "./components/GeometryColorSelector"; export default function App() { const [month, setMonth] = useState(6); @@ -32,6 +33,7 @@ export default function App() { /> + , + filterPredicate: (element: FormaElement) => boolean, + path: string = "root", +): string[] { + const element = elements[urn]; + const paths = []; + if (filterPredicate(element)) { + paths.push(path); + } + + if (element.children) { + for (const child of element?.children) { + paths.push( + ...getPathOfElements(child.urn, elements, filterPredicate, `${path}/${child.key}`), + ); + } + } + + return paths; +} + +/** + * Color the ground texture with a given color + */ +async function colorGround(color: string) { + const bbox = await Forma.terrain.getBbox(); + const canvas = document.createElement("canvas"); + const width = bbox.max.x - bbox.min.x; + const height = bbox.max.y - bbox.min.y; + canvas.width = width; + canvas.height = height; + const context = canvas.getContext("2d"); + if (!context) { + return; + } + context.fillStyle = color; + context.fillRect(0, 0, width, height); + return await Forma.terrain.groundTexture.add({ + name: "shadow-study", + canvas: canvas, + position: { x: 0, y: 0, z: 0 }, + scale: { x: 1, y: 1 }, + }); +} + +/** + * Color elements with a given color + */ +function colorPaths(elementPaths: string[], color: string) { + const pathsToColor = new Map(); + for (const path of elementPaths) { + pathsToColor.set(path, color); + } + + Forma.render.elementColors.set({ pathsToColor }); +} + +/** + * Will debounce the function call to avoid calling it too often. + * Useful for avoiding color input events to be called too often. + */ +export const debounce = ReturnType>(func: F, waitFor: number) => { + let timeout: number | undefined; + + return (...args: Parameters): Promise> => + new Promise((resolve) => { + if (timeout) { + clearTimeout(timeout); + } + + timeout = setTimeout(() => resolve(func(...args)), waitFor); + }); +}; + +export default function GeometryColorSelector() { + const [shouldPaintGeometry, setShouldPaintGeometry] = useState(false); + const [shouldPaintTerrain, setShouldPaintTerrain] = useState(false); + + const [elementPaths, setElementPaths] = useState([]); + const [rootUrn, setRootUrn] = useState(); + const [geometryColor, setGeometryColor] = useState("#ffffff"); + const [terrainColor, setTerrainColor] = useState("#ffffff"); + + useEffect(() => { + Forma.proposal.getRootUrn().then((rootUrn) => { + setRootUrn(rootUrn as Urn); + }); + Forma.proposal.subscribe( + ({ rootUrn }) => { + setRootUrn(rootUrn as Urn); + }, + { debouncedPersistedOnly: true }, + ); + }, []); + + useEffect(() => { + if (rootUrn != null) { + Forma.elements.get({ urn: rootUrn as Urn, recursive: true }).then(({ elements }) => { + const paths = getPathOfElements(rootUrn as Urn, elements, () => true); + setElementPaths(paths); + }); + } + }, [rootUrn]); + + useEffect(() => { + if (shouldPaintTerrain && shouldPaintGeometry) { + colorGround(terrainColor).then(() => { + colorPaths(elementPaths, geometryColor); + }); + } else if (shouldPaintTerrain) { + colorGround(terrainColor); + Forma.render.elementColors.clearAll(); + } else if (shouldPaintGeometry) { + colorPaths(elementPaths, geometryColor); + Forma.terrain.groundTexture.remove({ name: "shadow-study" }); + } else { + Forma.render.elementColors.clearAll(); + Forma.terrain.groundTexture.remove({ name: "shadow-study" }); + } + }, [shouldPaintTerrain, shouldPaintGeometry, geometryColor, elementPaths, terrainColor]); + + return ( + <> +
+
+ Color geometry: +
+
+ setShouldPaintGeometry(e.detail.checked)} + > + { + if (e.target instanceof HTMLInputElement) setGeometryColor(e.target?.value); + }, 50)} + /> +
+
+
+
+ Color terrain: +
+
+ setShouldPaintTerrain(e.detail.checked)} + > + { + if (e.target instanceof HTMLInputElement) setTerrainColor(e.target?.value); + }, 50)} + /> +
+
+ + ); +} diff --git a/src/lib/weave.d.ts b/src/lib/weave.d.ts index fef76f0..385c66b 100644 --- a/src/lib/weave.d.ts +++ b/src/lib/weave.d.ts @@ -1,23 +1,32 @@ -export declare module "preact/src/jsx" { - namespace JSXInternal { - interface IntrinsicElements { - "weave-button": JSX.HTMLAttributes & { - type?: "button" | "submit" | "reset"; - variant?: "outlined" | "flat" | "solid"; - density?: "high" | "medium"; - iconposition?: "left" | "right"; - }; - "weave-select": JSX.HTMLAttributes & { - placeholder?: any; - value: any; - children: JSX.Element[]; - onChange: (e: CustomEvent<{ value: string; text: string }>) => void; - }; - "weave-select-option": JSX.HTMLAttributes & { - disabled?: true; - value: any; - children?: JSX.Element | string; - }; - } +namespace JSX { + interface IntrinsicElements { + "weave-button": JSX.HTMLAttributes & { + type?: "button" | "submit" | "reset"; + variant?: "outlined" | "flat" | "solid"; + density?: "high" | "medium"; + iconposition?: "left" | "right"; + }; + "weave-select": JSX.HTMLAttributes & { + placeholder?: any; + value: any; + children: JSX.Element[]; + onChange: (e: CustomEvent<{ value: string; text: string }>) => void; + }; + "weave-select-option": JSX.HTMLAttributes & { + disabled?: true; + value: any; + children?: JSX.Element | string; + }; + "weave-checkbox": { + onChange?: (e: CustomEvent) => void; + children?: JSX.Element | string; + style?: string; + checked: boolean; + showlabel?: boolean; + label?: string; + value?: string; + key?: string; + disabled?: boolean; + }; } } diff --git a/src/styles.css b/src/styles.css index c08e2f2..a992798 100644 --- a/src/styles.css +++ b/src/styles.css @@ -58,3 +58,13 @@ weave-select { color: #3c3c3cb2; } + +.color-picker { + margin-left: 10px; + width: 40%; + cursor: pointer; + border: none; + block-size: 20px; + padding: 0; + background-color: transparent; +} diff --git a/tsconfig.json b/tsconfig.json index b52d3d2..8ce2a2b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,20 +7,19 @@ "skipLibCheck": true, /* Bundler mode */ - "moduleResolution": "node", + "moduleResolution": "bundler", "allowImportingTsExtensions": true, - "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "jsxImportSource": "preact", - /* Linting */ + /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, "include": ["src"] -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 836b755..72f26cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -706,10 +706,10 @@ forma-elements@^1.5.2: resolved "https://registry.yarnpkg.com/forma-elements/-/forma-elements-1.5.3.tgz#9413acb1fa0e8b88a5cc6da2cfb42545c9d75895" integrity sha512-f5xSHypP5eETa1oFy8GYeRjmVI0/82ARuRtYTe8Smy0cZdGkRaDd5WFwNZT252KqxyOu/phUjDegZxZQxayIfA== -forma-embedded-view-sdk@0.59.1: - version "0.59.1" - resolved "https://registry.yarnpkg.com/forma-embedded-view-sdk/-/forma-embedded-view-sdk-0.59.1.tgz#7692a7e543e4c7fc695be91bf19f7d7022d28b30" - integrity sha512-UmFQJLFZYf+Vwg71CTmMW6/b8ybc1Xd+v3AFQ0CfofaAVNUhBDjUP4I+v97tk6Jpl0vM0Pt0CxPl8hOElwJRJg== +forma-embedded-view-sdk@^0.60.1: + version "0.60.1" + resolved "https://registry.yarnpkg.com/forma-embedded-view-sdk/-/forma-embedded-view-sdk-0.60.1.tgz#c6619dcc93d7709e920694b15a2e80eb14ecff41" + integrity sha512-G8v5PBvi4OFIUhU6+S5U0dPod/hVFAvatJciq2Cw0GPDt28voX0GRpq6JXrSXHIYdfYsnc6zRfwERyIHzZ1Isw== dependencies: "@types/geojson" "^7946.0.13" forma-elements "^1.5.2"