Skip to content
Open
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
2 changes: 2 additions & 0 deletions src/components/layout/Toolbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
COMPAS ThreeJs
</h1>
<TransformGroup />
<AddObjectGroup />
<ViewGroup />
<DisplayGroup />
</div>
</template>

<script setup lang="ts">
import TransformGroup from "@/components/tools/transforms/TransformGroup.vue";
import AddObjectGroup from "@/components/tools/objects/AddObjectGroup.vue";
import ViewGroup from "@/components/tools/views/ViewGroup.vue";
import DisplayGroup from "@/components/tools/display/DisplayGroup.vue";
import { useViewerRuntime } from "@/viewer/viewer_context";
Expand Down
173 changes: 173 additions & 0 deletions src/components/tools/objects/AddObjectButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<template>
<TooltipProvider :delay-duration="600">
<Popover v-model:open="isOpen" :modal="true">
<PopoverTrigger as-child>
<Button variant="secondary" size="icon">
<Tooltip>
<TooltipTrigger as-child>
<span
class="inline-flex h-full w-full items-center justify-center"
>
<Shapes />
</span>
</TooltipTrigger>
<TooltipContent class="z-1000" side="bottom">
<p>Add object</p>
</TooltipContent>
</Tooltip>
</Button>
</PopoverTrigger>

<PopoverContent
class="theme z-[4000] w-64 rounded-xl p-3 text-secondary-foreground"
side="bottom"
align="start"
>
<div class="add-object-form">
<Select v-model="shapeType">
<SelectTrigger class="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent class="z-[4000]">
<SelectItem
v-for="option in SHAPE_TYPES"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</SelectItem>
</SelectContent>
</Select>

<div v-if="shapeType === 'box'" class="param-grid">
<label class="param-label">
Size X
<NumberField v-model="boxSize.xsize" :min="0.01" :step="0.1">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</label>
<label class="param-label">
Size Y
<NumberField v-model="boxSize.ysize" :min="0.01" :step="0.1">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</label>
<label class="param-label">
Size Z
<NumberField v-model="boxSize.zsize" :min="0.01" :step="0.1">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</label>
</div>

<div v-else-if="shapeType === 'sphere'" class="param-grid">
<label class="param-label">
Radius
<NumberField v-model="sphereRadius" :min="0.01" :step="0.1">
<NumberFieldContent>
<NumberFieldDecrement />
<NumberFieldInput />
<NumberFieldIncrement />
</NumberFieldContent>
</NumberField>
</label>
</div>

<Button variant="secondary" class="w-full" @click="handleAdd">
Add
</Button>
</div>
</PopoverContent>
</Popover>
</TooltipProvider>
</template>

<script setup lang="ts">
import { ref } from "vue";
import { Shapes } from "lucide-vue-next";
import { useViewerRuntime } from "@/viewer/viewer_context";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
NumberField,
NumberFieldContent,
NumberFieldDecrement,
NumberFieldIncrement,
NumberFieldInput,
} from "@/components/ui/number-field";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";

type ShapeType = "box" | "sphere" | "point";

const SHAPE_TYPES: { value: ShapeType; label: string }[] = [
{ value: "box", label: "Box" },
{ value: "sphere", label: "Sphere" },
{ value: "point", label: "Point" },
];

const runtime = useViewerRuntime();
const isOpen = ref(false);
const shapeType = ref<ShapeType>("box");
const boxSize = ref({ xsize: 1, ysize: 1, zsize: 1 });
const sphereRadius = ref(1);

function handleAdd() {
const params: Record<string, number> =
shapeType.value === "box"
? { ...boxSize.value }
: shapeType.value === "sphere"
? { radius: sphereRadius.value }
: {};
runtime.createGeometry(shapeType.value, params);
isOpen.value = false;
}
</script>

<style scoped>
.add-object-form {
display: flex;
flex-direction: column;
gap: 10px;
}

.param-grid {
display: flex;
flex-direction: column;
gap: 6px;
}

.param-label {
display: flex;
flex-direction: column;
gap: 4px;
font-size: 0.8rem;
}
</style>
10 changes: 10 additions & 0 deletions src/components/tools/objects/AddObjectGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<template>
<div class="toolbar-group">
<AddObjectButton />
<MaterialButton />
</div>
</template>

<script setup lang="ts">
import { AddObjectButton, MaterialButton } from "./index";
</script>
180 changes: 180 additions & 0 deletions src/components/tools/objects/MaterialButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<template>
<TooltipProvider :delay-duration="600">
<Popover v-model:open="isOpen" :modal="true">
<PopoverTrigger as-child>
<Button
variant="secondary"
size="icon"
:disabled="!pickedObjectGuid.value"
>
<Tooltip>
<TooltipTrigger as-child>
<span
class="inline-flex h-full w-full items-center justify-center"
>
<Palette />
</span>
</TooltipTrigger>
<TooltipContent class="z-1000" side="bottom">
<p>Material</p>
</TooltipContent>
</Tooltip>
</Button>
</PopoverTrigger>

<PopoverContent
class="theme z-[4000] w-64 rounded-xl p-3 text-secondary-foreground"
side="bottom"
align="start"
>
<div class="material-form">
<label class="param-label">
Color
<input
type="color"
:value="color"
class="color-input"
@input="handleColorInput"
/>
</label>

<label class="param-label">
Metalness
<div class="slider-row">
<Slider
:model-value="[metalness]"
:min="0"
:max="1"
:step="0.05"
@update:model-value="(value) => handleMetalness(value?.[0])"
/>
<span class="slider-value">{{ metalness.toFixed(2) }}</span>
</div>
</label>

<label class="param-label">
Roughness
<div class="slider-row">
<Slider
:model-value="[roughness]"
:min="0"
:max="1"
:step="0.05"
@update:model-value="(value) => handleRoughness(value?.[0])"
/>
<span class="slider-value">{{ roughness.toFixed(2) }}</span>
</div>
</label>
</div>
</PopoverContent>
</Popover>
</TooltipProvider>
</template>

<script setup lang="ts">
import { ref, watch } from "vue";
import { Palette } from "lucide-vue-next";
import { useViewerRuntime } from "@/viewer/viewer_context";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Slider } from "@/components/ui/slider";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";

const runtime = useViewerRuntime();
const { pickedObjectGuid } = runtime.store;

const isOpen = ref(false);
const color = ref("#ffffff");
const metalness = ref(0);
const roughness = ref(1);

function refreshFromSnapshot() {
const guid = pickedObjectGuid.value;
const snapshot = guid ? runtime.getMaterialSnapshot(guid) : null;
color.value = snapshot?.color ?? "#ffffff";
metalness.value = snapshot?.metalness ?? 0;
roughness.value = snapshot?.roughness ?? 1;
}

watch(isOpen, (open) => {
if (open) refreshFromSnapshot();
});

watch(
() => pickedObjectGuid.value,
(guid) => {
if (!guid) {
isOpen.value = false;
return;
}
if (isOpen.value) refreshFromSnapshot();
},
);

function handleColorInput(event: Event) {
const value = (event.target as HTMLInputElement).value;
color.value = value;
const guid = pickedObjectGuid.value;
if (guid) runtime.setMaterial(guid, { color: value });
}

function handleMetalness(value: number | undefined) {
if (value === undefined) return;
metalness.value = value;
const guid = pickedObjectGuid.value;
if (guid) runtime.setMaterial(guid, { metalness: value });
}

function handleRoughness(value: number | undefined) {
if (value === undefined) return;
roughness.value = value;
const guid = pickedObjectGuid.value;
if (guid) runtime.setMaterial(guid, { roughness: value });
}
</script>

<style scoped>
.material-form {
display: flex;
flex-direction: column;
gap: 12px;
}

.param-label {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 0.8rem;
}

.color-input {
width: 100%;
height: 28px;
padding: 0;
border: none;
border-radius: 6px;
background: none;
cursor: pointer;
}

.slider-row {
display: flex;
align-items: center;
gap: 8px;
}

.slider-value {
min-width: 2.5em;
text-align: right;
font-variant-numeric: tabular-nums;
}
</style>
3 changes: 3 additions & 0 deletions src/components/tools/objects/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as AddObjectButton } from "./AddObjectButton.vue";
export { default as MaterialButton } from "./MaterialButton.vue";
export { default as AddObjectGroup } from "./AddObjectGroup.vue";
Loading
Loading