Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add GeometryColorSelector #9

Merged
merged 3 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 5 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
rel="stylesheet"
href="https://app.autodeskforma.eu/design-system/v2/forma/styles/base.css"
/>
<link rel="stylesheet" href="./src/styles.css"/>
<link rel="stylesheet" href="./src/styles.css" />
<script
type="module"
src="https://app.autodeskforma.eu/design-system/v2/weave/components/button/weave-button.js"
Expand All @@ -14,6 +14,10 @@
type="module"
src="https://app.autodeskforma.eu/design-system/v2/weave/components/dropdown/weave-select.js"
></script>
<script
type="module"
src="https://app.autodeskforma.eu/design-system/v2/weave/components/checkbox/weave-checkbox.js"
></script>
<title>Shadow study</title>
</head>
<body>
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
},
"dependencies": {
"file-saver": "^2.0.5",
"forma-embedded-view-sdk": "0.44.0",
"forma-embedded-view-sdk": "0.58.0",
"jszip": "3.10.1",
"lodash": "^4.17.21",
"luxon": "^3.4.3",
"preact": "^10.17.1"
"preact": "^10.19.6"
},
"devDependencies": {
"@preact/preset-vite": "^2.5.0",
"@preact/preset-vite": "^2.8.2",
"@types/file-saver": "^2.0.2",
"@types/lodash": "4.14.198",
"@types/luxon": "^3.3.2",
"prettier": "3.0.3",
"typescript": "^5.0.2",
"vite": "^4.5.2"
"typescript": "^5.4.2",
"vite": "^5.1.6"
}
}
2 changes: 2 additions & 0 deletions src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -32,6 +33,7 @@ export default function App() {
/>
<IntervalSelector interval={interval} setInterval={setInterval} />
<ResolutionSelector resolution={resolution} setResolution={setResolution} />
<GeometryColorSelector />
<PreviewButton
month={month}
day={day}
Expand Down
176 changes: 176 additions & 0 deletions src/components/GeometryColorSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { Forma } from "forma-embedded-view-sdk/auto";
import { useEffect, useState } from "preact/hooks";
import { FormaElement, Urn } from "forma-embedded-view-sdk/elements/types";

/**
*
* @param urn The root urn of the element hierarchy to search
* @param elements Record that must contain all elements part of the hierarchy
* @param filterPredicate Predicate to filter the elements you want to get the paths for
* @param path Pased recursively to build the path of the elements. Should be "root" when calling the function on a proposal URN.
* @returns a list of paths to the elements that match the filterPredicate
*/
function getPathOfElements(
urn: Urn,
elements: Record<Urn, FormaElement>,
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<string, string>();
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 = <F extends (...args: any[]) => ReturnType<F>>(func: F, waitFor: number) => {
let timeout: number | undefined;

return (...args: Parameters<F>): Promise<ReturnType<F>> =>
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<string[]>([]);
const [rootUrn, setRootUrn] = useState<Urn | undefined>();
const [geometryColor, setGeometryColor] = useState("#ffffff");
const [terrainColor, setTerrainColor] = useState("#ffffff");

useEffect(() => {
Forma.proposal.getRootUrn().then((rootUrn) => {
setRootUrn(rootUrn as Urn);
});
Forma.proposal.subscribe(async ({ rootUrn }) => {
await Forma.proposal.awaitProposalPersisted();
setRootUrn(rootUrn as Urn);
});
}, []);

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 (
<>
<div class="row">
<div class="row-title" style={{ width: "50%" }}>
Color geometry:
</div>
<div class="row-item">
<weave-checkbox
checked={shouldPaintGeometry}
onChange={(e) => setShouldPaintGeometry(e.detail.checked)}
></weave-checkbox>
<input
type="color"
class={"color-picker"}
value={geometryColor}
onInput={debounce((e: Event) => {
if (e.target instanceof HTMLInputElement) setGeometryColor(e.target?.value);
}, 50)}
/>
</div>
</div>
<div class="row">
<div class="row-title" style={{ width: "50%" }}>
Color terrain:
</div>
<div class="row-item">
<weave-checkbox
checked={shouldPaintTerrain}
onChange={(e) => setShouldPaintTerrain(e.detail.checked)}
></weave-checkbox>
<input
type="color"
class={"color-picker"}
value={geometryColor}
onInput={debounce((e: Event) => {
if (e.target instanceof HTMLInputElement) setTerrainColor(e.target?.value);
}, 50)}
/>
</div>
</div>
</>
);
}
51 changes: 30 additions & 21 deletions src/lib/weave.d.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
export declare module "preact/src/jsx" {
namespace JSXInternal {
interface IntrinsicElements {
"weave-button": JSX.HTMLAttributes<HTMLElement> & {
type?: "button" | "submit" | "reset";
variant?: "outlined" | "flat" | "solid";
density?: "high" | "medium";
iconposition?: "left" | "right";
};
"weave-select": JSX.HTMLAttributes<HTMLElement> & {
placeholder?: any;
value: any;
children: JSX.Element[];
onChange: (e: CustomEvent<{ value: string; text: string }>) => void;
};
"weave-select-option": JSX.HTMLAttributes<HTMLElement> & {
disabled?: true;
value: any;
children?: JSX.Element | string;
};
}
namespace JSX {
interface IntrinsicElements {
"weave-button": JSX.HTMLAttributes<HTMLElement> & {
type?: "button" | "submit" | "reset";
variant?: "outlined" | "flat" | "solid";
density?: "high" | "medium";
iconposition?: "left" | "right";
};
"weave-select": JSX.HTMLAttributes<HTMLElement> & {
placeholder?: any;
value: any;
children: JSX.Element[];
onChange: (e: CustomEvent<{ value: string; text: string }>) => void;
};
"weave-select-option": JSX.HTMLAttributes<HTMLElement> & {
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;
};
}
}
10 changes: 10 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
7 changes: 3 additions & 4 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
}
Loading
Loading