Group object. You’ll learn how to build reusable components like furniture, fixtures, and custom architectural features.
Creating a simple table
Let’s create a custom table element using cuboid shapes:Copy
Ask AI
import { OpenPlans } from '@opengeometry/openplans';
import * as THREE from 'three';
const openPlans = new OpenPlans(document.getElementById('app'));
await openPlans.setupOpenGeometry();
// Create a group to hold the table parts
const table = new THREE.Group();
table.name = 'CustomTable';
// Table top
const tableTop = openPlans.cuboid({
center: { x: 0, y: 0.75, z: 0 },
width: 1.5,
height: 0.05,
depth: 0.8,
color: 0x8B4513 // Brown
});
// Table legs (4 corners)
const legHeight = 0.75;
const legSize = 0.05;
const leg1 = openPlans.cuboid({
center: { x: -0.7, y: legHeight / 2, z: -0.35 },
width: legSize,
height: legHeight,
depth: legSize,
color: 0x654321
});
const leg2 = openPlans.cuboid({
center: { x: 0.7, y: legHeight / 2, z: -0.35 },
width: legSize,
height: legHeight,
depth: legSize,
color: 0x654321
});
const leg3 = openPlans.cuboid({
center: { x: -0.7, y: legHeight / 2, z: 0.35 },
width: legSize,
height: legHeight,
depth: legSize,
color: 0x654321
});
const leg4 = openPlans.cuboid({
center: { x: 0.7, y: legHeight / 2, z: 0.35 },
width: legSize,
height: legHeight,
depth: legSize,
color: 0x654321
});
// Add all parts to the table group
table.add(tableTop);
table.add(leg1);
table.add(leg2);
table.add(leg3);
table.add(leg4);
// Position the table in the room
table.position.set(1, 0, 0);
// Add to scene (access through OpenPlans internals)
openPlans.openThree.scene.add(table);
Creating a column
Use cylinder shapes to create architectural columns:Copy
Ask AI
// Create a decorative column
const column = new THREE.Group();
column.name = 'DecorativeColumn';
// Base
const base = openPlans.cuboid({
center: { x: 0, y: 0.1, z: 0 },
width: 0.5,
height: 0.2,
depth: 0.5,
color: 0xD3D3D3 // Light gray
});
// Column shaft (cylinder)
const shaft = openPlans.cylinder({
center: { x: 0, y: 1.5, z: 0 },
radius: 0.2,
height: 2.6,
radialSegments: 32,
color: 0xFFFFFF // White
});
// Capital (top)
const capital = openPlans.cuboid({
center: { x: 0, y: 2.9, z: 0 },
width: 0.5,
height: 0.2,
depth: 0.5,
color: 0xD3D3D3
});
column.add(base);
column.add(shaft);
column.add(capital);
// Position column
column.position.set(-2, 0, -1);
openPlans.openThree.scene.add(column);
Creating a window with shutters
Extend the window element with custom shutters:Copy
Ask AI
// Create base window
const window = openPlans.baseSingleWindow({
windowPosition: [0, 0, 0],
windowHeight: 1.5,
sillHeight: 0.9,
dimensions: {
start: { x: -0.6, y: 0, z: 0 },
end: { x: 0.6, y: 0, z: 0 },
length: 1.2
},
windowColor: 0x87CEEB,
frameColor: 0xFFFFFF
});
// Add shutters (left and right panels)
const shutterWidth = 0.55;
const shutterHeight = 1.5;
const shutterThickness = 0.03;
const shutterOffsetZ = 0.2;
const leftShutter = openPlans.cuboid({
center: { x: -0.65, y: 0.9 + shutterHeight / 2, z: shutterOffsetZ },
width: shutterWidth,
height: shutterHeight,
depth: shutterThickness,
color: 0x2E8B57 // Sea green
});
const rightShutter = openPlans.cuboid({
center: { x: 0.65, y: 0.9 + shutterHeight / 2, z: shutterOffsetZ },
width: shutterWidth,
height: shutterHeight,
depth: shutterThickness,
color: 0x2E8B57
});
// Group window with shutters
const windowWithShutters = new THREE.Group();
windowWithShutters.add(window);
windowWithShutters.add(leftShutter);
windowWithShutters.add(rightShutter);
// Position in room
windowWithShutters.position.set(2, 0, 0);
openPlans.openThree.scene.add(windowWithShutters);
Creating a reusable component class
For better code organization, create a reusable class:Copy
Ask AI
class CustomChair {
group: THREE.Group;
openPlans: OpenPlans;
constructor(openPlans: OpenPlans, position: [number, number, number]) {
this.openPlans = openPlans;
this.group = new THREE.Group();
this.group.name = 'CustomChair';
this.createChair();
this.group.position.set(position[0], position[1], position[2]);
openPlans.openThree.scene.add(this.group);
}
private createChair() {
// Seat
const seat = this.openPlans.cuboid({
center: { x: 0, y: 0.45, z: 0 },
width: 0.45,
height: 0.05,
depth: 0.45,
color: 0x654321
});
// Backrest
const backrest = this.openPlans.cuboid({
center: { x: 0, y: 0.75, z: -0.2 },
width: 0.45,
height: 0.6,
depth: 0.05,
color: 0x654321
});
// Legs (simplified - 4 legs)
const legPositions = [
[-0.18, 0.225, -0.18],
[0.18, 0.225, -0.18],
[-0.18, 0.225, 0.18],
[0.18, 0.225, 0.18]
];
legPositions.forEach(pos => {
const leg = this.openPlans.cuboid({
center: { x: pos[0], y: pos[1], z: pos[2] },
width: 0.04,
height: 0.45,
depth: 0.04,
color: 0x3E2723
});
this.group.add(leg);
});
this.group.add(seat);
this.group.add(backrest);
}
setPosition(x: number, y: number, z: number) {
this.group.position.set(x, y, z);
}
setRotation(angleY: number) {
this.group.rotation.y = angleY;
}
dispose() {
this.group.removeFromParent();
this.group.clear();
}
}
// Usage:
const chair1 = new CustomChair(openPlans, [0.5, 0, 0.5]);
const chair2 = new CustomChair(openPlans, [-0.5, 0, 0.5]);
chair2.setRotation(Math.PI); // Rotate 180 degrees
Creating a complete desk setup
Combine multiple custom elements:Copy
Ask AI
class DeskSetup {
group: THREE.Group;
openPlans: OpenPlans;
constructor(openPlans: OpenPlans, position: [number, number, number]) {
this.openPlans = openPlans;
this.group = new THREE.Group();
this.group.name = 'DeskSetup';
this.createDesk();
this.group.position.set(position[0], position[1], position[2]);
openPlans.openThree.scene.add(this.group);
}
private createDesk() {
// Desk surface
const desktop = this.openPlans.cuboid({
center: { x: 0, y: 0.75, z: 0 },
width: 1.4,
height: 0.05,
depth: 0.7,
color: 0x8B7355
});
// Left drawer unit
const leftDrawer = this.openPlans.cuboid({
center: { x: -0.5, y: 0.4, z: 0 },
width: 0.3,
height: 0.6,
depth: 0.6,
color: 0x6B5D52
});
// Right drawer unit
const rightDrawer = this.openPlans.cuboid({
center: { x: 0.5, y: 0.4, z: 0 },
width: 0.3,
height: 0.6,
depth: 0.6,
color: 0x6B5D52
});
// Monitor (simplified)
const monitor = this.openPlans.cuboid({
center: { x: 0, y: 1.05, z: -0.15 },
width: 0.5,
height: 0.3,
depth: 0.05,
color: 0x1a1a1a
});
// Monitor stand
const stand = this.openPlans.cuboid({
center: { x: 0, y: 0.8, z: -0.15 },
width: 0.15,
height: 0.05,
depth: 0.15,
color: 0x333333
});
this.group.add(desktop);
this.group.add(leftDrawer);
this.group.add(rightDrawer);
this.group.add(monitor);
this.group.add(stand);
}
}
// Create desk setup
const desk = new DeskSetup(openPlans, [1.5, 0, -1]);
Adding custom properties and methods
Copy
Ask AI
class CustomBookshelf {
group: THREE.Group;
openPlans: OpenPlans;
shelves: THREE.Mesh[];
numShelves: number;
constructor(
openPlans: OpenPlans,
position: [number, number, number],
options: {
width?: number,
height?: number,
depth?: number,
numShelves?: number,
color?: number
} = {}
) {
this.openPlans = openPlans;
this.numShelves = options.numShelves || 5;
this.shelves = [];
this.group = new THREE.Group();
this.group.name = 'CustomBookshelf';
const width = options.width || 1.2;
const height = options.height || 2.0;
const depth = options.depth || 0.35;
const color = options.color || 0x8B4513;
this.createBookshelf(width, height, depth, color);
this.group.position.set(position[0], position[1], position[2]);
openPlans.openThree.scene.add(this.group);
}
private createBookshelf(
width: number,
height: number,
depth: number,
color: number
) {
const shelfThickness = 0.03;
const sideThickness = 0.05;
// Left side
const leftSide = this.openPlans.cuboid({
center: { x: -width / 2 + sideThickness / 2, y: height / 2, z: 0 },
width: sideThickness,
height: height,
depth: depth,
color: color
});
// Right side
const rightSide = this.openPlans.cuboid({
center: { x: width / 2 - sideThickness / 2, y: height / 2, z: 0 },
width: sideThickness,
height: height,
depth: depth,
color: color
});
this.group.add(leftSide);
this.group.add(rightSide);
// Create shelves
const shelfSpacing = height / (this.numShelves);
for (let i = 0; i <= this.numShelves; i++) {
const shelfY = i * shelfSpacing;
const shelf = this.openPlans.cuboid({
center: { x: 0, y: shelfY, z: 0 },
width: width - 2 * sideThickness,
height: shelfThickness,
depth: depth,
color: color
});
this.shelves.push(shelf);
this.group.add(shelf);
}
}
setColor(color: number) {
this.group.traverse((child) => {
if (child instanceof THREE.Mesh) {
(child.material as THREE.MeshBasicMaterial).color.setHex(color);
}
});
}
getShelfHeight(shelfIndex: number): number {
if (shelfIndex < this.shelves.length) {
return this.shelves[shelfIndex].position.y;
}
return 0;
}
}
// Usage
const bookshelf = new CustomBookshelf(
openPlans,
[-2, 0, 1],
{
width: 1.5,
height: 2.2,
depth: 0.4,
numShelves: 6,
color: 0x654321
}
);
// Change color later
bookshelf.setColor(0x8B4513);
Best practices
When should I create custom elements vs. using primitives directly?
When should I create custom elements vs. using primitives directly?
Create custom elements when:
- You need to reuse the same complex shape multiple times
- The element has its own behavior or state
- You want to encapsulate positioning and configuration logic
- Creating one-off simple shapes
- Prototyping or testing
- The element doesn’t need to be reusable
How do I make custom elements selectable?
How do I make custom elements selectable?
Add selection logic to your custom element class:
Copy
Ask AI
class SelectableElement {
selected: boolean = false;
originalColors: Map<THREE.Mesh, number> = new Map();
select() {
this.selected = true;
this.group.traverse((child) => {
if (child instanceof THREE.Mesh) {
const material = child.material as THREE.MeshBasicMaterial;
this.originalColors.set(child, material.color.getHex());
material.color.setHex(0x4460FF); // Highlight color
}
});
}
deselect() {
this.selected = false;
this.group.traverse((child) => {
if (child instanceof THREE.Mesh) {
const originalColor = this.originalColors.get(child);
if (originalColor !== undefined) {
(child.material as THREE.MeshBasicMaterial).color.setHex(originalColor);
}
}
});
this.originalColors.clear();
}
}
Can I export custom elements to JSON?
Can I export custom elements to JSON?
Yes! Implement serialization methods:
Copy
Ask AI
class CustomElement {
toJSON() {
return {
type: 'CustomChair',
position: this.group.position.toArray(),
rotation: this.group.rotation.toArray(),
// ... other properties
};
}
static fromJSON(openPlans: OpenPlans, data: any) {
const element = new CustomChair(openPlans, data.position);
element.setRotation(data.rotation[1]);
return element;
}
}