Add IKEA furniture catalog with 41 items and tabbed browse UI

- Create data/ikea-catalog.json with 41 curated IKEA items across 23 series
  (KALLAX, BILLY, MALM, PAX, HEMNES, LACK, etc.) with verified dimensions
- Add source tabs (All/Standard/IKEA) to catalog panel for filtering
- Add IKEA series filter bar when viewing IKEA items
- Add IKEA badge and series label on item cards
- Add mergeCatalog() to renderer for loading additional catalog files
- Add scripts/import-ikea-hf.js for importing from HuggingFace dataset
This commit is contained in:
m
2026-02-07 12:58:52 +01:00
parent cf0fe586eb
commit ceea42ac1d
5 changed files with 1411 additions and 22 deletions

769
data/ikea-catalog.json Normal file
View File

@@ -0,0 +1,769 @@
{
"version": "1.0",
"source": "ikea",
"units": "meters",
"description": "Curated IKEA furniture catalog with verified dimensions",
"categories": [
"seating",
"tables",
"storage",
"beds",
"kitchen",
"office"
],
"items": [
{
"id": "ikea-kallax-1x4",
"name": "KALLAX Shelf 1x4",
"ikeaSeries": "KALLAX",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 0.42, "depth": 0.39, "height": 1.47 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.42, 1.47, 0.39], "position": [0, 0.735, 0], "color": "#ffffff" },
{ "name": "shelf1", "geometry": "box", "size": [0.38, 0.02, 0.37], "position": [0, 0.37, 0], "color": "#f0f0f0" },
{ "name": "shelf2", "geometry": "box", "size": [0.38, 0.02, 0.37], "position": [0, 0.735, 0], "color": "#f0f0f0" },
{ "name": "shelf3", "geometry": "box", "size": [0.38, 0.02, 0.37], "position": [0, 1.1, 0], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-kallax-2x2",
"name": "KALLAX Shelf 2x2",
"ikeaSeries": "KALLAX",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 0.77, "depth": 0.39, "height": 0.77 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.77, 0.77, 0.39], "position": [0, 0.385, 0], "color": "#ffffff" },
{ "name": "divV", "geometry": "box", "size": [0.02, 0.73, 0.37], "position": [0, 0.385, 0], "color": "#f0f0f0" },
{ "name": "divH", "geometry": "box", "size": [0.73, 0.02, 0.37], "position": [0, 0.385, 0], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-kallax-2x4",
"name": "KALLAX Shelf 2x4",
"ikeaSeries": "KALLAX",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 0.77, "depth": 0.39, "height": 1.47 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.77, 1.47, 0.39], "position": [0, 0.735, 0], "color": "#ffffff" },
{ "name": "divV", "geometry": "box", "size": [0.02, 1.43, 0.37], "position": [0, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divH1", "geometry": "box", "size": [0.73, 0.02, 0.37], "position": [0, 0.37, 0], "color": "#f0f0f0" },
{ "name": "divH2", "geometry": "box", "size": [0.73, 0.02, 0.37], "position": [0, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divH3", "geometry": "box", "size": [0.73, 0.02, 0.37], "position": [0, 1.1, 0], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-kallax-4x4",
"name": "KALLAX Shelf 4x4",
"ikeaSeries": "KALLAX",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer"],
"dimensions": { "width": 1.47, "depth": 0.39, "height": 1.47 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [1.47, 1.47, 0.39], "position": [0, 0.735, 0], "color": "#ffffff" },
{ "name": "divV1", "geometry": "box", "size": [0.02, 1.43, 0.37], "position": [-0.365, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divV2", "geometry": "box", "size": [0.02, 1.43, 0.37], "position": [0, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divV3", "geometry": "box", "size": [0.02, 1.43, 0.37], "position": [0.365, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divH1", "geometry": "box", "size": [1.43, 0.02, 0.37], "position": [0, 0.37, 0], "color": "#f0f0f0" },
{ "name": "divH2", "geometry": "box", "size": [1.43, 0.02, 0.37], "position": [0, 0.735, 0], "color": "#f0f0f0" },
{ "name": "divH3", "geometry": "box", "size": [1.43, 0.02, 0.37], "position": [0, 1.1, 0], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-billy-standard",
"name": "BILLY Bookcase",
"ikeaSeries": "BILLY",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 0.80, "depth": 0.28, "height": 2.02 },
"mesh": {
"type": "group",
"parts": [
{ "name": "left", "geometry": "box", "size": [0.02, 2.02, 0.28], "position": [-0.39, 1.01, 0], "color": "#ffffff" },
{ "name": "right", "geometry": "box", "size": [0.02, 2.02, 0.28], "position": [0.39, 1.01, 0], "color": "#ffffff" },
{ "name": "back", "geometry": "box", "size": [0.76, 2.0, 0.01], "position": [0, 1.01, -0.135], "color": "#f8f8f8" },
{ "name": "shelf1", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.01, 0], "color": "#f0f0f0" },
{ "name": "shelf2", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.4, 0], "color": "#f0f0f0" },
{ "name": "shelf3", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.8, 0], "color": "#f0f0f0" },
{ "name": "shelf4", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 1.2, 0], "color": "#f0f0f0" },
{ "name": "shelf5", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 1.6, 0], "color": "#f0f0f0" },
{ "name": "top", "geometry": "box", "size": [0.80, 0.02, 0.28], "position": [0, 2.01, 0], "color": "#ffffff" }
]
}
},
{
"id": "ikea-billy-narrow",
"name": "BILLY Bookcase Narrow",
"ikeaSeries": "BILLY",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer"],
"dimensions": { "width": 0.40, "depth": 0.28, "height": 2.02 },
"mesh": {
"type": "group",
"parts": [
{ "name": "left", "geometry": "box", "size": [0.02, 2.02, 0.28], "position": [-0.19, 1.01, 0], "color": "#ffffff" },
{ "name": "right", "geometry": "box", "size": [0.02, 2.02, 0.28], "position": [0.19, 1.01, 0], "color": "#ffffff" },
{ "name": "back", "geometry": "box", "size": [0.36, 2.0, 0.01], "position": [0, 1.01, -0.135], "color": "#f8f8f8" },
{ "name": "shelf1", "geometry": "box", "size": [0.36, 0.02, 0.26], "position": [0, 0.01, 0], "color": "#f0f0f0" },
{ "name": "shelf2", "geometry": "box", "size": [0.36, 0.02, 0.26], "position": [0, 0.4, 0], "color": "#f0f0f0" },
{ "name": "shelf3", "geometry": "box", "size": [0.36, 0.02, 0.26], "position": [0, 0.8, 0], "color": "#f0f0f0" },
{ "name": "shelf4", "geometry": "box", "size": [0.36, 0.02, 0.26], "position": [0, 1.2, 0], "color": "#f0f0f0" },
{ "name": "shelf5", "geometry": "box", "size": [0.36, 0.02, 0.26], "position": [0, 1.6, 0], "color": "#f0f0f0" },
{ "name": "top", "geometry": "box", "size": [0.40, 0.02, 0.28], "position": [0, 2.01, 0], "color": "#ffffff" }
]
}
},
{
"id": "ikea-billy-short",
"name": "BILLY Bookcase Short",
"ikeaSeries": "BILLY",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 0.80, "depth": 0.28, "height": 1.06 },
"mesh": {
"type": "group",
"parts": [
{ "name": "left", "geometry": "box", "size": [0.02, 1.06, 0.28], "position": [-0.39, 0.53, 0], "color": "#ffffff" },
{ "name": "right", "geometry": "box", "size": [0.02, 1.06, 0.28], "position": [0.39, 0.53, 0], "color": "#ffffff" },
{ "name": "back", "geometry": "box", "size": [0.76, 1.04, 0.01], "position": [0, 0.53, -0.135], "color": "#f8f8f8" },
{ "name": "shelf1", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.01, 0], "color": "#f0f0f0" },
{ "name": "shelf2", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.35, 0], "color": "#f0f0f0" },
{ "name": "shelf3", "geometry": "box", "size": [0.76, 0.02, 0.26], "position": [0, 0.7, 0], "color": "#f0f0f0" },
{ "name": "top", "geometry": "box", "size": [0.80, 0.02, 0.28], "position": [0, 1.05, 0], "color": "#ffffff" }
]
}
},
{
"id": "ikea-hemnes-6drawer",
"name": "HEMNES 6-Drawer Dresser",
"ikeaSeries": "HEMNES",
"category": "storage",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.08, "depth": 0.50, "height": 1.31 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.08, 1.31, 0.50], "position": [0, 0.655, 0], "color": "#f0ece4" },
{ "name": "drawer1", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [-0.27, 0.15, 0.24], "color": "#e8e4dc" },
{ "name": "drawer2", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [0.27, 0.15, 0.24], "color": "#e8e4dc" },
{ "name": "drawer3", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [-0.27, 0.37, 0.24], "color": "#e8e4dc" },
{ "name": "drawer4", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [0.27, 0.37, 0.24], "color": "#e8e4dc" },
{ "name": "drawer5", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [-0.27, 0.59, 0.24], "color": "#e8e4dc" },
{ "name": "drawer6", "geometry": "box", "size": [0.48, 0.16, 0.02], "position": [0.27, 0.59, 0.24], "color": "#e8e4dc" }
]
}
},
{
"id": "ikea-hemnes-3drawer",
"name": "HEMNES 3-Drawer Dresser",
"ikeaSeries": "HEMNES",
"category": "storage",
"rooms": ["schlafzimmer", "flur"],
"dimensions": { "width": 1.08, "depth": 0.50, "height": 0.96 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.08, 0.96, 0.50], "position": [0, 0.48, 0], "color": "#f0ece4" },
{ "name": "drawer1", "geometry": "box", "size": [1.0, 0.2, 0.02], "position": [0, 0.18, 0.24], "color": "#e8e4dc" },
{ "name": "drawer2", "geometry": "box", "size": [1.0, 0.2, 0.02], "position": [0, 0.44, 0.24], "color": "#e8e4dc" },
{ "name": "drawer3", "geometry": "box", "size": [1.0, 0.2, 0.02], "position": [0, 0.70, 0.24], "color": "#e8e4dc" }
]
}
},
{
"id": "ikea-hemnes-bookcase",
"name": "HEMNES Bookcase",
"ikeaSeries": "HEMNES",
"category": "storage",
"rooms": ["wohnzimmer", "arbeitszimmer"],
"dimensions": { "width": 0.90, "depth": 0.37, "height": 1.97 },
"mesh": {
"type": "group",
"parts": [
{ "name": "left", "geometry": "box", "size": [0.03, 1.97, 0.37], "position": [-0.435, 0.985, 0], "color": "#c4a87d" },
{ "name": "right", "geometry": "box", "size": [0.03, 1.97, 0.37], "position": [0.435, 0.985, 0], "color": "#c4a87d" },
{ "name": "back", "geometry": "box", "size": [0.84, 1.95, 0.01], "position": [0, 0.985, -0.18], "color": "#d4be97" },
{ "name": "shelf1", "geometry": "box", "size": [0.84, 0.02, 0.35], "position": [0, 0.01, 0], "color": "#c4a87d" },
{ "name": "shelf2", "geometry": "box", "size": [0.84, 0.02, 0.35], "position": [0, 0.5, 0], "color": "#c4a87d" },
{ "name": "shelf3", "geometry": "box", "size": [0.84, 0.02, 0.35], "position": [0, 1.0, 0], "color": "#c4a87d" },
{ "name": "shelf4", "geometry": "box", "size": [0.84, 0.02, 0.35], "position": [0, 1.5, 0], "color": "#c4a87d" },
{ "name": "top", "geometry": "box", "size": [0.90, 0.02, 0.37], "position": [0, 1.96, 0], "color": "#c4a87d" }
]
}
},
{
"id": "ikea-besta-tv",
"name": "BESTA TV Bench",
"ikeaSeries": "BESTA",
"category": "storage",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 1.80, "depth": 0.42, "height": 0.38 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.80, 0.38, 0.42], "position": [0, 0.19, 0], "color": "#ffffff" },
{ "name": "door1", "geometry": "box", "size": [0.58, 0.34, 0.02], "position": [-0.6, 0.19, 0.2], "color": "#f0f0f0" },
{ "name": "door2", "geometry": "box", "size": [0.58, 0.34, 0.02], "position": [0, 0.19, 0.2], "color": "#f0f0f0" },
{ "name": "door3", "geometry": "box", "size": [0.58, 0.34, 0.02], "position": [0.6, 0.19, 0.2], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-malm-6drawer",
"name": "MALM 6-Drawer Dresser",
"ikeaSeries": "MALM",
"category": "storage",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 0.80, "depth": 0.48, "height": 1.23 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.80, 1.23, 0.48], "position": [0, 0.615, 0], "color": "#ffffff" },
{ "name": "drawer1", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 0.12, 0.23], "color": "#f0f0f0" },
{ "name": "drawer2", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 0.32, 0.23], "color": "#f0f0f0" },
{ "name": "drawer3", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 0.52, 0.23], "color": "#f0f0f0" },
{ "name": "drawer4", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 0.72, 0.23], "color": "#f0f0f0" },
{ "name": "drawer5", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 0.92, 0.23], "color": "#f0f0f0" },
{ "name": "drawer6", "geometry": "box", "size": [0.74, 0.15, 0.02], "position": [0, 1.12, 0.23], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-malm-4drawer",
"name": "MALM 4-Drawer Dresser",
"ikeaSeries": "MALM",
"category": "storage",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 0.80, "depth": 0.48, "height": 1.00 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.80, 1.00, 0.48], "position": [0, 0.50, 0], "color": "#ffffff" },
{ "name": "drawer1", "geometry": "box", "size": [0.74, 0.18, 0.02], "position": [0, 0.14, 0.23], "color": "#f0f0f0" },
{ "name": "drawer2", "geometry": "box", "size": [0.74, 0.18, 0.02], "position": [0, 0.38, 0.23], "color": "#f0f0f0" },
{ "name": "drawer3", "geometry": "box", "size": [0.74, 0.18, 0.02], "position": [0, 0.62, 0.23], "color": "#f0f0f0" },
{ "name": "drawer4", "geometry": "box", "size": [0.74, 0.18, 0.02], "position": [0, 0.86, 0.23], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-pax-wardrobe",
"name": "PAX Wardrobe 100cm",
"ikeaSeries": "PAX",
"category": "storage",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.00, "depth": 0.58, "height": 2.01 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.00, 2.01, 0.58], "position": [0, 1.005, 0], "color": "#ffffff" },
{ "name": "door_l", "geometry": "box", "size": [0.48, 1.95, 0.02], "position": [-0.25, 1.005, 0.28], "color": "#f0f0f0" },
{ "name": "door_r", "geometry": "box", "size": [0.48, 1.95, 0.02], "position": [0.25, 1.005, 0.28], "color": "#f0f0f0" },
{ "name": "handle_l", "geometry": "box", "size": [0.02, 0.12, 0.03], "position": [-0.02, 1.005, 0.3], "color": "#888888" },
{ "name": "handle_r", "geometry": "box", "size": [0.02, 0.12, 0.03], "position": [0.02, 1.005, 0.3], "color": "#888888" }
]
}
},
{
"id": "ikea-pax-wardrobe-150",
"name": "PAX Wardrobe 150cm",
"ikeaSeries": "PAX",
"category": "storage",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.50, "depth": 0.58, "height": 2.01 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.50, 2.01, 0.58], "position": [0, 1.005, 0], "color": "#ffffff" },
{ "name": "door_l", "geometry": "box", "size": [0.48, 1.95, 0.02], "position": [-0.5, 1.005, 0.28], "color": "#f0f0f0" },
{ "name": "door_m", "geometry": "box", "size": [0.48, 1.95, 0.02], "position": [0, 1.005, 0.28], "color": "#f0f0f0" },
{ "name": "door_r", "geometry": "box", "size": [0.48, 1.95, 0.02], "position": [0.5, 1.005, 0.28], "color": "#f0f0f0" }
]
}
},
{
"id": "ikea-lack-side",
"name": "LACK Side Table",
"ikeaSeries": "LACK",
"category": "tables",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 0.55, "depth": 0.55, "height": 0.45 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [0.55, 0.05, 0.55], "position": [0, 0.425, 0], "color": "#1a1a1a" },
{ "name": "leg1", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.22, 0.2, -0.22], "color": "#1a1a1a" },
{ "name": "leg2", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.22, 0.2, -0.22], "color": "#1a1a1a" },
{ "name": "leg3", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.22, 0.2, 0.22], "color": "#1a1a1a" },
{ "name": "leg4", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.22, 0.2, 0.22], "color": "#1a1a1a" }
]
}
},
{
"id": "ikea-lack-coffee",
"name": "LACK Coffee Table",
"ikeaSeries": "LACK",
"category": "tables",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 0.90, "depth": 0.55, "height": 0.45 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [0.90, 0.05, 0.55], "position": [0, 0.425, 0], "color": "#1a1a1a" },
{ "name": "shelf", "geometry": "box", "size": [0.84, 0.02, 0.49], "position": [0, 0.07, 0], "color": "#1a1a1a" },
{ "name": "leg1", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.40, 0.2, -0.22], "color": "#1a1a1a" },
{ "name": "leg2", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.40, 0.2, -0.22], "color": "#1a1a1a" },
{ "name": "leg3", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.40, 0.2, 0.22], "color": "#1a1a1a" },
{ "name": "leg4", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.40, 0.2, 0.22], "color": "#1a1a1a" }
]
}
},
{
"id": "ikea-lack-tv",
"name": "LACK TV Bench",
"ikeaSeries": "LACK",
"category": "tables",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 0.90, "depth": 0.26, "height": 0.45 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [0.90, 0.04, 0.26], "position": [0, 0.43, 0], "color": "#1a1a1a" },
{ "name": "shelf", "geometry": "box", "size": [0.84, 0.02, 0.22], "position": [0, 0.07, 0], "color": "#1a1a1a" },
{ "name": "leg1", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.40, 0.2, -0.08], "color": "#1a1a1a" },
{ "name": "leg2", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.40, 0.2, -0.08], "color": "#1a1a1a" },
{ "name": "leg3", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [-0.40, 0.2, 0.08], "color": "#1a1a1a" },
{ "name": "leg4", "geometry": "box", "size": [0.05, 0.4, 0.05], "position": [0.40, 0.2, 0.08], "color": "#1a1a1a" }
]
}
},
{
"id": "ikea-lisabo-desk",
"name": "LISABO Desk",
"ikeaSeries": "LISABO",
"category": "tables",
"rooms": ["arbeitszimmer"],
"dimensions": { "width": 1.18, "depth": 0.45, "height": 0.74 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [1.18, 0.03, 0.45], "position": [0, 0.725, 0], "color": "#c4a87d" },
{ "name": "leg1", "geometry": "box", "size": [0.04, 0.71, 0.04], "position": [-0.54, 0.355, -0.18], "color": "#b09870" },
{ "name": "leg2", "geometry": "box", "size": [0.04, 0.71, 0.04], "position": [0.54, 0.355, -0.18], "color": "#b09870" },
{ "name": "leg3", "geometry": "box", "size": [0.04, 0.71, 0.04], "position": [-0.54, 0.355, 0.18], "color": "#b09870" },
{ "name": "leg4", "geometry": "box", "size": [0.04, 0.71, 0.04], "position": [0.54, 0.355, 0.18], "color": "#b09870" }
]
}
},
{
"id": "ikea-bekant-desk",
"name": "BEKANT Desk 160x80",
"ikeaSeries": "BEKANT",
"category": "office",
"rooms": ["arbeitszimmer"],
"dimensions": { "width": 1.60, "depth": 0.80, "height": 0.75 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [1.60, 0.03, 0.80], "position": [0, 0.735, 0], "color": "#f0ece4" },
{ "name": "leg1", "geometry": "box", "size": [0.06, 0.72, 0.06], "position": [-0.72, 0.36, -0.32], "color": "#cccccc" },
{ "name": "leg2", "geometry": "box", "size": [0.06, 0.72, 0.06], "position": [0.72, 0.36, -0.32], "color": "#cccccc" },
{ "name": "leg3", "geometry": "box", "size": [0.06, 0.72, 0.06], "position": [-0.72, 0.36, 0.32], "color": "#cccccc" },
{ "name": "leg4", "geometry": "box", "size": [0.06, 0.72, 0.06], "position": [0.72, 0.36, 0.32], "color": "#cccccc" }
]
}
},
{
"id": "ikea-melltorp-table",
"name": "MELLTORP Dining Table",
"ikeaSeries": "MELLTORP",
"category": "tables",
"rooms": ["esszimmer", "kueche"],
"dimensions": { "width": 1.25, "depth": 0.75, "height": 0.74 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [1.25, 0.03, 0.75], "position": [0, 0.725, 0], "color": "#ffffff" },
{ "name": "leg1", "geometry": "box", "size": [0.05, 0.71, 0.05], "position": [-0.56, 0.355, -0.31], "color": "#e0e0e0" },
{ "name": "leg2", "geometry": "box", "size": [0.05, 0.71, 0.05], "position": [0.56, 0.355, -0.31], "color": "#e0e0e0" },
{ "name": "leg3", "geometry": "box", "size": [0.05, 0.71, 0.05], "position": [-0.56, 0.355, 0.31], "color": "#e0e0e0" },
{ "name": "leg4", "geometry": "box", "size": [0.05, 0.71, 0.05], "position": [0.56, 0.355, 0.31], "color": "#e0e0e0" }
]
}
},
{
"id": "ikea-ekedalen-table",
"name": "EKEDALEN Dining Table",
"ikeaSeries": "EKEDALEN",
"category": "tables",
"rooms": ["esszimmer"],
"dimensions": { "width": 1.20, "depth": 0.80, "height": 0.75 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [1.20, 0.04, 0.80], "position": [0, 0.73, 0], "color": "#6b5640" },
{ "name": "leg1", "geometry": "box", "size": [0.06, 0.71, 0.06], "position": [-0.52, 0.355, -0.32], "color": "#5a4530" },
{ "name": "leg2", "geometry": "box", "size": [0.06, 0.71, 0.06], "position": [0.52, 0.355, -0.32], "color": "#5a4530" },
{ "name": "leg3", "geometry": "box", "size": [0.06, 0.71, 0.06], "position": [-0.52, 0.355, 0.32], "color": "#5a4530" },
{ "name": "leg4", "geometry": "box", "size": [0.06, 0.71, 0.06], "position": [0.52, 0.355, 0.32], "color": "#5a4530" }
]
}
},
{
"id": "ikea-poang-chair",
"name": "POANG Armchair",
"ikeaSeries": "POANG",
"category": "seating",
"rooms": ["wohnzimmer", "schlafzimmer"],
"dimensions": { "width": 0.68, "depth": 0.82, "height": 1.00 },
"mesh": {
"type": "group",
"parts": [
{ "name": "seat", "geometry": "box", "size": [0.55, 0.12, 0.55], "position": [0, 0.38, 0.08], "color": "#d4c4a0" },
{ "name": "back", "geometry": "box", "size": [0.55, 0.5, 0.08], "position": [0, 0.75, -0.30], "color": "#d4c4a0" },
{ "name": "frame_l", "geometry": "box", "size": [0.05, 0.95, 0.75], "position": [-0.30, 0.48, 0], "color": "#a08050" },
{ "name": "frame_r", "geometry": "box", "size": [0.05, 0.95, 0.75], "position": [0.30, 0.48, 0], "color": "#a08050" }
]
}
},
{
"id": "ikea-strandmon-chair",
"name": "STRANDMON Wing Chair",
"ikeaSeries": "STRANDMON",
"category": "seating",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 0.82, "depth": 0.96, "height": 1.01 },
"mesh": {
"type": "group",
"parts": [
{ "name": "seat", "geometry": "box", "size": [0.60, 0.15, 0.55], "position": [0, 0.38, 0.1], "color": "#5a7060" },
{ "name": "back", "geometry": "box", "size": [0.65, 0.55, 0.12], "position": [0, 0.73, -0.35], "color": "#5a7060" },
{ "name": "wing_l", "geometry": "box", "size": [0.12, 0.45, 0.30], "position": [-0.35, 0.70, -0.15], "color": "#5a7060" },
{ "name": "wing_r", "geometry": "box", "size": [0.12, 0.45, 0.30], "position": [0.35, 0.70, -0.15], "color": "#5a7060" },
{ "name": "arm_l", "geometry": "box", "size": [0.10, 0.20, 0.55], "position": [-0.36, 0.48, 0.1], "color": "#4a6050" },
{ "name": "arm_r", "geometry": "box", "size": [0.10, 0.20, 0.55], "position": [0.36, 0.48, 0.1], "color": "#4a6050" },
{ "name": "leg1", "geometry": "box", "size": [0.04, 0.15, 0.04], "position": [-0.30, 0.075, 0.35], "color": "#3a3020" },
{ "name": "leg2", "geometry": "box", "size": [0.04, 0.15, 0.04], "position": [0.30, 0.075, 0.35], "color": "#3a3020" },
{ "name": "leg3", "geometry": "box", "size": [0.04, 0.15, 0.04], "position": [-0.30, 0.075, -0.35], "color": "#3a3020" },
{ "name": "leg4", "geometry": "box", "size": [0.04, 0.15, 0.04], "position": [0.30, 0.075, -0.35], "color": "#3a3020" }
]
}
},
{
"id": "ikea-klippan-sofa",
"name": "KLIPPAN 2-Seat Sofa",
"ikeaSeries": "KLIPPAN",
"category": "seating",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 1.80, "depth": 0.88, "height": 0.66 },
"mesh": {
"type": "group",
"parts": [
{ "name": "base", "geometry": "box", "size": [1.80, 0.35, 0.88], "position": [0, 0.175, 0], "color": "#3a3a3a" },
{ "name": "back", "geometry": "box", "size": [1.80, 0.31, 0.15], "position": [0, 0.505, -0.365], "color": "#3a3a3a" },
{ "name": "arm_l", "geometry": "box", "size": [0.15, 0.50, 0.73], "position": [-0.825, 0.25, 0.075], "color": "#333333" },
{ "name": "arm_r", "geometry": "box", "size": [0.15, 0.50, 0.73], "position": [0.825, 0.25, 0.075], "color": "#333333" }
]
}
},
{
"id": "ikea-ektorp-sofa",
"name": "EKTORP 3-Seat Sofa",
"ikeaSeries": "EKTORP",
"category": "seating",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 2.18, "depth": 0.88, "height": 0.88 },
"mesh": {
"type": "group",
"parts": [
{ "name": "base", "geometry": "box", "size": [2.18, 0.42, 0.88], "position": [0, 0.21, 0], "color": "#e8e0d4" },
{ "name": "back", "geometry": "box", "size": [2.18, 0.46, 0.15], "position": [0, 0.65, -0.365], "color": "#e8e0d4" },
{ "name": "arm_l", "geometry": "box", "size": [0.18, 0.65, 0.88], "position": [-1.0, 0.325, 0], "color": "#ddd8cc" },
{ "name": "arm_r", "geometry": "box", "size": [0.18, 0.65, 0.88], "position": [1.0, 0.325, 0], "color": "#ddd8cc" },
{ "name": "cushion1", "geometry": "box", "size": [0.55, 0.10, 0.55], "position": [-0.56, 0.47, 0.1], "color": "#ece4d8" },
{ "name": "cushion2", "geometry": "box", "size": [0.55, 0.10, 0.55], "position": [0, 0.47, 0.1], "color": "#ece4d8" },
{ "name": "cushion3", "geometry": "box", "size": [0.55, 0.10, 0.55], "position": [0.56, 0.47, 0.1], "color": "#ece4d8" }
]
}
},
{
"id": "ikea-kivik-sofa",
"name": "KIVIK 3-Seat Sofa",
"ikeaSeries": "KIVIK",
"category": "seating",
"rooms": ["wohnzimmer"],
"dimensions": { "width": 2.28, "depth": 0.95, "height": 0.83 },
"mesh": {
"type": "group",
"parts": [
{ "name": "base", "geometry": "box", "size": [2.28, 0.40, 0.95], "position": [0, 0.20, 0], "color": "#8899aa" },
{ "name": "back", "geometry": "box", "size": [2.10, 0.43, 0.18], "position": [0, 0.615, -0.385], "color": "#8899aa" },
{ "name": "arm_l", "geometry": "box", "size": [0.20, 0.55, 0.95], "position": [-1.04, 0.275, 0], "color": "#7a8a9a" },
{ "name": "arm_r", "geometry": "box", "size": [0.20, 0.55, 0.95], "position": [1.04, 0.275, 0], "color": "#7a8a9a" }
]
}
},
{
"id": "ikea-markus-chair",
"name": "MARKUS Office Chair",
"ikeaSeries": "MARKUS",
"category": "office",
"rooms": ["arbeitszimmer"],
"dimensions": { "width": 0.62, "depth": 0.60, "height": 1.35 },
"mesh": {
"type": "group",
"parts": [
{ "name": "seat", "geometry": "box", "size": [0.50, 0.08, 0.48], "position": [0, 0.48, 0], "color": "#2a2a2a" },
{ "name": "back", "geometry": "box", "size": [0.48, 0.65, 0.06], "position": [0, 0.87, -0.24], "color": "#2a2a2a" },
{ "name": "headrest", "geometry": "box", "size": [0.30, 0.18, 0.06], "position": [0, 1.28, -0.24], "color": "#2a2a2a" },
{ "name": "pedestal", "geometry": "cylinder", "radius": 0.03, "height": 0.44, "position": [0, 0.22, 0], "color": "#666666" },
{ "name": "base", "geometry": "cylinder", "radius": 0.28, "height": 0.04, "position": [0, 0.02, 0], "color": "#444444" },
{ "name": "arm_l", "geometry": "box", "size": [0.04, 0.04, 0.22], "position": [-0.27, 0.55, 0.05], "color": "#444444" },
{ "name": "arm_r", "geometry": "box", "size": [0.04, 0.04, 0.22], "position": [0.27, 0.55, 0.05], "color": "#444444" }
]
}
},
{
"id": "ikea-malm-bed-queen",
"name": "MALM Bed Queen 160cm",
"ikeaSeries": "MALM",
"category": "beds",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.60, "depth": 2.09, "height": 0.92 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [1.60, 0.28, 2.09], "position": [0, 0.14, 0], "color": "#ffffff" },
{ "name": "mattress", "geometry": "box", "size": [1.50, 0.20, 1.98], "position": [0, 0.38, 0], "color": "#f5f0eb" },
{ "name": "headboard", "geometry": "box", "size": [1.60, 0.64, 0.04], "position": [0, 0.60, -1.025], "color": "#f0f0f0" },
{ "name": "pillow_l", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [-0.38, 0.52, -0.72], "color": "#ffffff" },
{ "name": "pillow_r", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [0.38, 0.52, -0.72], "color": "#ffffff" }
]
}
},
{
"id": "ikea-malm-bed-king",
"name": "MALM Bed King 180cm",
"ikeaSeries": "MALM",
"category": "beds",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.80, "depth": 2.09, "height": 0.92 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [1.80, 0.28, 2.09], "position": [0, 0.14, 0], "color": "#ffffff" },
{ "name": "mattress", "geometry": "box", "size": [1.70, 0.20, 1.98], "position": [0, 0.38, 0], "color": "#f5f0eb" },
{ "name": "headboard", "geometry": "box", "size": [1.80, 0.64, 0.04], "position": [0, 0.60, -1.025], "color": "#f0f0f0" },
{ "name": "pillow_l", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [-0.45, 0.52, -0.72], "color": "#ffffff" },
{ "name": "pillow_r", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [0.45, 0.52, -0.72], "color": "#ffffff" }
]
}
},
{
"id": "ikea-malm-bed-single",
"name": "MALM Bed Single 90cm",
"ikeaSeries": "MALM",
"category": "beds",
"rooms": ["kinderzimmer"],
"dimensions": { "width": 0.90, "depth": 2.09, "height": 0.92 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.90, 0.28, 2.09], "position": [0, 0.14, 0], "color": "#ffffff" },
{ "name": "mattress", "geometry": "box", "size": [0.82, 0.20, 1.98], "position": [0, 0.38, 0], "color": "#f5f0eb" },
{ "name": "headboard", "geometry": "box", "size": [0.90, 0.64, 0.04], "position": [0, 0.60, -1.025], "color": "#f0f0f0" },
{ "name": "pillow", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [0, 0.52, -0.72], "color": "#ffffff" }
]
}
},
{
"id": "ikea-hemnes-bed-queen",
"name": "HEMNES Bed Queen",
"ikeaSeries": "HEMNES",
"category": "beds",
"rooms": ["schlafzimmer"],
"dimensions": { "width": 1.63, "depth": 2.11, "height": 1.12 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [1.63, 0.30, 2.11], "position": [0, 0.15, 0], "color": "#c4a87d" },
{ "name": "mattress", "geometry": "box", "size": [1.53, 0.20, 2.0], "position": [0, 0.40, 0], "color": "#f5f0eb" },
{ "name": "headboard", "geometry": "box", "size": [1.63, 0.82, 0.06], "position": [0, 0.71, -1.025], "color": "#b09870" },
{ "name": "footboard", "geometry": "box", "size": [1.63, 0.36, 0.04], "position": [0, 0.48, 1.035], "color": "#b09870" },
{ "name": "pillow_l", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [-0.38, 0.54, -0.72], "color": "#ffffff" },
{ "name": "pillow_r", "geometry": "box", "size": [0.55, 0.08, 0.38], "position": [0.38, 0.54, -0.72], "color": "#ffffff" }
]
}
},
{
"id": "ikea-kura-bed",
"name": "KURA Reversible Bed",
"ikeaSeries": "KURA",
"category": "beds",
"rooms": ["kinderzimmer"],
"dimensions": { "width": 0.99, "depth": 2.09, "height": 1.16 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.99, 0.28, 2.09], "position": [0, 0.14, 0], "color": "#ffffff" },
{ "name": "mattress", "geometry": "box", "size": [0.90, 0.12, 1.98], "position": [0, 0.34, 0], "color": "#f5f0eb" },
{ "name": "rail_l", "geometry": "box", "size": [0.03, 0.76, 2.09], "position": [-0.48, 0.66, 0], "color": "#b4a48c" },
{ "name": "rail_r", "geometry": "box", "size": [0.03, 0.76, 2.09], "position": [0.48, 0.66, 0], "color": "#b4a48c" },
{ "name": "top_frame", "geometry": "box", "size": [0.99, 0.04, 2.09], "position": [0, 1.14, 0], "color": "#b4a48c" }
]
}
},
{
"id": "ikea-metod-base",
"name": "METOD Base Cabinet 60cm",
"ikeaSeries": "METOD",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 0.60, "depth": 0.60, "height": 0.80 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.60, 0.80, 0.60], "position": [0, 0.40, 0], "color": "#f0ece4" },
{ "name": "door", "geometry": "box", "size": [0.56, 0.70, 0.02], "position": [0, 0.40, 0.29], "color": "#e0dcd4" },
{ "name": "counter", "geometry": "box", "size": [0.60, 0.04, 0.62], "position": [0, 0.82, 0], "color": "#888888" },
{ "name": "handle", "geometry": "box", "size": [0.10, 0.02, 0.03], "position": [0, 0.62, 0.31], "color": "#aaaaaa" }
]
}
},
{
"id": "ikea-metod-base-80",
"name": "METOD Base Cabinet 80cm",
"ikeaSeries": "METOD",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 0.80, "depth": 0.60, "height": 0.80 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.80, 0.80, 0.60], "position": [0, 0.40, 0], "color": "#f0ece4" },
{ "name": "door_l", "geometry": "box", "size": [0.37, 0.70, 0.02], "position": [-0.19, 0.40, 0.29], "color": "#e0dcd4" },
{ "name": "door_r", "geometry": "box", "size": [0.37, 0.70, 0.02], "position": [0.19, 0.40, 0.29], "color": "#e0dcd4" },
{ "name": "counter", "geometry": "box", "size": [0.80, 0.04, 0.62], "position": [0, 0.82, 0], "color": "#888888" }
]
}
},
{
"id": "ikea-metod-wall",
"name": "METOD Wall Cabinet 60cm",
"ikeaSeries": "METOD",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 0.60, "depth": 0.37, "height": 0.80 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.60, 0.80, 0.37], "position": [0, 1.70, 0], "color": "#f0ece4" },
{ "name": "door", "geometry": "box", "size": [0.56, 0.76, 0.02], "position": [0, 1.70, 0.175], "color": "#e0dcd4" },
{ "name": "handle", "geometry": "box", "size": [0.10, 0.02, 0.03], "position": [0, 1.40, 0.20], "color": "#aaaaaa" }
]
}
},
{
"id": "ikea-metod-tall",
"name": "METOD Tall Cabinet 60cm",
"ikeaSeries": "METOD",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 0.60, "depth": 0.60, "height": 2.00 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [0.60, 2.00, 0.60], "position": [0, 1.00, 0], "color": "#f0ece4" },
{ "name": "door_top", "geometry": "box", "size": [0.56, 0.90, 0.02], "position": [0, 1.50, 0.29], "color": "#e0dcd4" },
{ "name": "door_bot", "geometry": "box", "size": [0.56, 0.90, 0.02], "position": [0, 0.50, 0.29], "color": "#e0dcd4" }
]
}
},
{
"id": "ikea-vadholma-island",
"name": "VADHOLMA Kitchen Island",
"ikeaSeries": "VADHOLMA",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 1.26, "depth": 0.79, "height": 0.90 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.26, 0.85, 0.79], "position": [0, 0.425, 0], "color": "#f0ece4" },
{ "name": "counter", "geometry": "box", "size": [1.30, 0.04, 0.83], "position": [0, 0.87, 0], "color": "#888888" },
{ "name": "shelf", "geometry": "box", "size": [1.16, 0.02, 0.69], "position": [0, 0.15, 0], "color": "#c4a87d" }
]
}
},
{
"id": "ikea-knoxhult-base",
"name": "KNOXHULT Base Cabinet 120cm",
"ikeaSeries": "KNOXHULT",
"category": "kitchen",
"rooms": ["kueche"],
"dimensions": { "width": 1.20, "depth": 0.61, "height": 0.85 },
"mesh": {
"type": "group",
"parts": [
{ "name": "body", "geometry": "box", "size": [1.20, 0.85, 0.61], "position": [0, 0.425, 0], "color": "#f0ece4" },
{ "name": "door_l", "geometry": "box", "size": [0.38, 0.70, 0.02], "position": [-0.38, 0.40, 0.295], "color": "#e0dcd4" },
{ "name": "door_r", "geometry": "box", "size": [0.38, 0.70, 0.02], "position": [0.38, 0.40, 0.295], "color": "#e0dcd4" },
{ "name": "drawer", "geometry": "box", "size": [0.38, 0.18, 0.02], "position": [0, 0.70, 0.295], "color": "#e0dcd4" },
{ "name": "counter", "geometry": "box", "size": [1.20, 0.04, 0.63], "position": [0, 0.83, 0], "color": "#888888" }
]
}
},
{
"id": "ikea-linnmon-alex-desk",
"name": "LINNMON/ALEX Desk",
"ikeaSeries": "LINNMON",
"category": "office",
"rooms": ["arbeitszimmer", "kinderzimmer"],
"dimensions": { "width": 1.50, "depth": 0.75, "height": 0.73 },
"mesh": {
"type": "group",
"parts": [
{ "name": "top", "geometry": "box", "size": [1.50, 0.04, 0.75], "position": [0, 0.71, 0], "color": "#f0ece4" },
{ "name": "drawer_unit", "geometry": "box", "size": [0.36, 0.58, 0.70], "position": [0.53, 0.29, -0.02], "color": "#ffffff" },
{ "name": "drawer1", "geometry": "box", "size": [0.32, 0.12, 0.02], "position": [0.53, 0.12, 0.33], "color": "#f0f0f0" },
{ "name": "drawer2", "geometry": "box", "size": [0.32, 0.12, 0.02], "position": [0.53, 0.28, 0.33], "color": "#f0f0f0" },
{ "name": "drawer3", "geometry": "box", "size": [0.32, 0.12, 0.02], "position": [0.53, 0.44, 0.33], "color": "#f0f0f0" },
{ "name": "leg1", "geometry": "box", "size": [0.04, 0.69, 0.04], "position": [-0.70, 0.345, -0.33], "color": "#cccccc" },
{ "name": "leg2", "geometry": "box", "size": [0.04, 0.69, 0.04], "position": [-0.70, 0.345, 0.33], "color": "#cccccc" }
]
}
},
{
"id": "ikea-sundvik-bed",
"name": "SUNDVIK Child Bed",
"ikeaSeries": "SUNDVIK",
"category": "beds",
"rooms": ["kinderzimmer"],
"dimensions": { "width": 0.80, "depth": 1.67, "height": 0.83 },
"mesh": {
"type": "group",
"parts": [
{ "name": "frame", "geometry": "box", "size": [0.80, 0.25, 1.67], "position": [0, 0.125, 0], "color": "#f0ece4" },
{ "name": "mattress", "geometry": "box", "size": [0.70, 0.12, 1.56], "position": [0, 0.31, 0], "color": "#f5f0eb" },
{ "name": "headboard", "geometry": "box", "size": [0.80, 0.58, 0.04], "position": [0, 0.54, -0.815], "color": "#e8e4dc" },
{ "name": "footboard", "geometry": "box", "size": [0.80, 0.40, 0.04], "position": [0, 0.45, 0.815], "color": "#e8e4dc" },
{ "name": "pillow", "geometry": "box", "size": [0.40, 0.06, 0.28], "position": [0, 0.40, -0.56], "color": "#ffffff" }
]
}
}
]
}

361
scripts/import-ikea-hf.js Normal file
View File

@@ -0,0 +1,361 @@
#!/usr/bin/env node
/**
* IKEA HuggingFace Dataset Importer
*
* Fetches product data from the tsazan/ikea-us-commercetxt dataset on HuggingFace
* and converts items with valid dimensions into our catalog JSON format.
*
* Usage:
* node scripts/import-ikea-hf.js [--limit N] [--output path]
*
* The HuggingFace dataset stores products in CommerceTXT format where each row
* is a line of text. Products are spread across multiple rows with sections like
* @PRODUCT, @SPECS, @IMAGES. This script streams through rows, reassembles
* product records, extracts dimensions, and generates procedural box meshes.
*/
const DATASET = 'tsazan/ikea-us-commercetxt';
const API_BASE = 'https://datasets-server.huggingface.co';
const BATCH_SIZE = 100;
// Category mapping from IKEA categories to our catalog categories
const CATEGORY_MAP = {
'sofas': 'seating',
'armchairs': 'seating',
'chairs': 'seating',
'dining chairs': 'seating',
'office chairs': 'office',
'desk chairs': 'office',
'desks': 'tables',
'dining tables': 'tables',
'coffee tables': 'tables',
'side tables': 'tables',
'console tables': 'tables',
'nightstands': 'tables',
'bedside tables': 'tables',
'bookcases': 'storage',
'shelving units': 'storage',
'shelf units': 'storage',
'dressers': 'storage',
'chests of drawers': 'storage',
'wardrobes': 'storage',
'tv stands': 'storage',
'tv benches': 'storage',
'sideboards': 'storage',
'cabinets': 'storage',
'beds': 'beds',
'bed frames': 'beds',
'kitchen cabinets': 'kitchen',
'kitchen islands': 'kitchen',
'base cabinets': 'kitchen',
'wall cabinets': 'kitchen',
};
// Room mapping based on category
const ROOM_MAP = {
'seating': ['wohnzimmer'],
'tables': ['wohnzimmer', 'esszimmer'],
'storage': ['wohnzimmer', 'arbeitszimmer'],
'beds': ['schlafzimmer'],
'kitchen': ['kueche'],
'office': ['arbeitszimmer'],
};
// Parse dimension string like '23⅝"' or '50¾"' to meters
function parseInchDim(str) {
if (!str) return null;
str = str.trim().replace(/"/g, '').replace(/'/g, '');
// Handle fractions like ⅝, ¾, ½, ¼, ⅜, ⅞
const fractions = { '⅛': 0.125, '¼': 0.25, '⅜': 0.375, '½': 0.5, '⅝': 0.625, '¾': 0.75, '⅞': 0.875 };
let value = 0;
for (const [frac, num] of Object.entries(fractions)) {
if (str.includes(frac)) {
str = str.replace(frac, '');
value += num;
}
}
const numPart = parseFloat(str);
if (!isNaN(numPart)) value += numPart;
// Convert inches to meters
return value > 0 ? Math.round(value * 0.0254 * 1000) / 1000 : null;
}
// Parse a dimensions line from @SPECS section
// Examples: "Width: 23⅝" and 50¾".", "Height: 29½"", "Depth: 15⅜""
function parseDimensions(specsLines) {
let width = null, height = null, depth = null;
for (const line of specsLines) {
const lower = line.toLowerCase();
// Try "Width: X" pattern
const wMatch = line.match(/Width:\s*([^,.\n]+)/i);
if (wMatch) {
// Take first value if multiple ("23⅝" and 50¾"")
const parts = wMatch[1].split(/\s+and\s+/);
width = parseInchDim(parts[parts.length - 1]); // take largest
}
const hMatch = line.match(/Height:\s*([^,.\n]+)/i);
if (hMatch) {
const parts = hMatch[1].split(/\s+and\s+/);
height = parseInchDim(parts[parts.length - 1]);
}
const dMatch = line.match(/Depth:\s*([^,.\n]+)/i);
if (dMatch) {
const parts = dMatch[1].split(/\s+and\s+/);
depth = parseInchDim(parts[parts.length - 1]);
}
// Also try "WxDxH" or "W"xD"xH"" pattern
const xMatch = line.match(/(\d+[⅛¼⅜½⅝¾⅞]?)"?\s*x\s*(\d+[⅛¼⅜½⅝¾⅞]?)"?\s*x\s*(\d+[⅛¼⅜½⅝¾⅞]?)"/i);
if (xMatch) {
width = width || parseInchDim(xMatch[1]);
depth = depth || parseInchDim(xMatch[2]);
height = height || parseInchDim(xMatch[3]);
}
}
if (width && height && depth) {
return { width, depth, height };
}
// At minimum need width and one other
if (width && (height || depth)) {
return {
width,
depth: depth || Math.round(width * 0.5 * 1000) / 1000,
height: height || Math.round(width * 0.8 * 1000) / 1000
};
}
return null;
}
// Generate a simple procedural box mesh from dimensions
function generateMesh(dims, category) {
const { width, depth, height } = dims;
const color = {
seating: '#7a8a9a',
tables: '#b09870',
storage: '#f0ece4',
beds: '#f5f0eb',
kitchen: '#e0dcd4',
office: '#cccccc',
}[category] || '#aaaaaa';
return {
type: 'group',
parts: [
{
name: 'body',
geometry: 'box',
size: [width, height, depth],
position: [0, height / 2, 0],
color
}
]
};
}
// Generate slug ID from product name
function slugify(name) {
return 'ikea-hf-' + name
.toLowerCase()
.replace(/[äöü]/g, c => ({ 'ä': 'ae', 'ö': 'oe', 'ü': 'ue' }[c]))
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '')
.slice(0, 50);
}
// Guess category from product name/context
function guessCategory(name, contextCategory) {
const lower = name.toLowerCase();
if (/sofa|couch|loveseat/i.test(lower)) return 'seating';
if (/chair|armchair|stool/i.test(lower)) return 'seating';
if (/desk|table/i.test(lower)) return 'tables';
if (/shelf|bookcase|shelving|kallax|billy/i.test(lower)) return 'storage';
if (/dresser|drawer|wardrobe|pax|malm.*drawer/i.test(lower)) return 'storage';
if (/tv.*bench|tv.*stand|besta|bestå/i.test(lower)) return 'storage';
if (/bed|mattress/i.test(lower)) return 'beds';
if (/cabinet|kitchen|metod|knoxhult/i.test(lower)) return 'kitchen';
if (/office/i.test(lower)) return 'office';
// Try context category
for (const [key, cat] of Object.entries(CATEGORY_MAP)) {
if (contextCategory && contextCategory.toLowerCase().includes(key)) return cat;
}
return 'storage'; // default
}
// Extract IKEA series name from product name
function extractSeries(name) {
// IKEA series are typically the first all-caps word
const match = name.match(/^([A-ZÅÄÖ]{2,})/);
return match ? match[1] : null;
}
async function fetchRows(offset, length) {
const url = `${API_BASE}/rows?dataset=${DATASET}&config=default&split=train&offset=${offset}&length=${length}`;
const resp = await fetch(url);
if (!resp.ok) throw new Error(`API error: ${resp.status}`);
const data = await resp.json();
return data.rows?.map(r => r.row?.text || '') || [];
}
async function importDataset(maxItems = 50) {
console.error(`Fetching IKEA products from HuggingFace (limit: ${maxItems})...`);
const items = [];
const seenIds = new Set();
let offset = 0;
let currentProduct = null;
let currentSection = null;
let currentCategory = null;
let specsLines = [];
let totalRows = 0;
// Process in batches
while (items.length < maxItems) {
let rows;
try {
rows = await fetchRows(offset, BATCH_SIZE);
} catch (e) {
console.error(` Fetch error at offset ${offset}: ${e.message}`);
break;
}
if (!rows || rows.length === 0) break;
totalRows += rows.length;
for (const line of rows) {
// Track sections
if (line.startsWith('# @CATEGORY')) {
currentSection = 'category';
continue;
}
if (line.startsWith('# @PRODUCT')) {
currentSection = 'product';
currentProduct = {};
specsLines = [];
continue;
}
if (line.startsWith('# @SPECS')) {
currentSection = 'specs';
continue;
}
if (line.startsWith('# @FILTERS')) {
currentSection = 'filters';
continue;
}
if (line.startsWith('# @ITEMS')) {
currentSection = 'items';
continue;
}
if (line.startsWith('# @IMAGES')) {
currentSection = 'images';
continue;
}
if (line === '---' || line.startsWith('# DISCLAIMER')) {
// End of product — process if we have one
if (currentProduct && currentProduct.name) {
const dims = parseDimensions(specsLines);
if (dims && dims.width > 0.1 && dims.height > 0.1) {
const category = guessCategory(currentProduct.name, currentCategory);
const id = slugify(currentProduct.name);
if (!seenIds.has(id)) {
seenIds.add(id);
items.push({
id,
name: currentProduct.name,
ikeaSeries: extractSeries(currentProduct.name),
sku: currentProduct.sku || null,
category,
rooms: ROOM_MAP[category] || [],
dimensions: dims,
mesh: generateMesh(dims, category)
});
if (items.length >= maxItems) break;
}
}
}
currentProduct = null;
currentSection = line.startsWith('# DISCLAIMER') ? 'disclaimer' : null;
specsLines = [];
continue;
}
// Parse line content based on section
if (currentSection === 'category') {
const nameMatch = line.match(/^Name:\s*(.+)/);
if (nameMatch) currentCategory = nameMatch[1].trim();
}
if (currentSection === 'product' && currentProduct) {
const nameMatch = line.match(/^Name:\s*(.+)/);
if (nameMatch) currentProduct.name = nameMatch[1].trim();
const skuMatch = line.match(/^SKU:\s*(.+)/);
if (skuMatch) currentProduct.sku = skuMatch[1].trim();
}
if (currentSection === 'specs') {
if (line.trim()) specsLines.push(line);
}
}
if (items.length >= maxItems) break;
offset += BATCH_SIZE;
// Safety limit: don't scan more than 100k rows
if (offset > 100000) {
console.error(` Reached scan limit at ${offset} rows`);
break;
}
}
console.error(` Scanned ${totalRows} rows, extracted ${items.length} items with dimensions`);
return items;
}
async function main() {
const args = process.argv.slice(2);
let limit = 100;
let outputPath = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--limit' && args[i + 1]) limit = parseInt(args[i + 1], 10);
if (args[i] === '--output' && args[i + 1]) outputPath = args[i + 1];
}
const items = await importDataset(limit);
const catalog = {
version: '1.0',
source: 'huggingface-ikea-us-commercetxt',
units: 'meters',
description: `Imported from HuggingFace dataset tsazan/ikea-us-commercetxt (${items.length} items)`,
categories: [...new Set(items.map(i => i.category))].sort(),
items
};
const json = JSON.stringify(catalog, null, 2);
if (outputPath) {
const fs = await import('fs');
fs.writeFileSync(outputPath, json);
console.error(`Wrote ${items.length} items to ${outputPath}`);
} else {
process.stdout.write(json);
}
}
main().catch(e => {
console.error('Error:', e.message);
process.exit(1);
});

View File

@@ -1,8 +1,9 @@
/** /**
* CatalogPanel — left sidebar for browsing furniture catalog. * CatalogPanel — left sidebar for browsing furniture catalog.
* *
* Shows categories, search, and item cards. Clicking an item * Shows source tabs (All / Standard / IKEA), categories, series filter,
* adds it to the center of the selected room via DesignState. * search, and item cards. Clicking an item adds it to the center of the
* selected room via DesignState.
*/ */
export class CatalogPanel { export class CatalogPanel {
constructor(container, { renderer, state, interaction }) { constructor(container, { renderer, state, interaction }) {
@@ -11,7 +12,9 @@ export class CatalogPanel {
this.state = state; this.state = state;
this.interaction = interaction; this.interaction = interaction;
this.selectedSource = 'all'; // 'all', 'standard', 'ikea'
this.selectedCategory = 'all'; this.selectedCategory = 'all';
this.selectedSeries = 'all';
this.searchQuery = ''; this.searchQuery = '';
this.selectedRoomId = null; this.selectedRoomId = null;
@@ -25,6 +28,11 @@ export class CatalogPanel {
this.container.innerHTML = ''; this.container.innerHTML = '';
this.container.className = 'catalog-panel'; this.container.className = 'catalog-panel';
// Source tabs
this._sourceBar = document.createElement('div');
this._sourceBar.className = 'catalog-source-tabs';
this.container.appendChild(this._sourceBar);
// Search // Search
const searchWrap = document.createElement('div'); const searchWrap = document.createElement('div');
searchWrap.className = 'catalog-search'; searchWrap.className = 'catalog-search';
@@ -40,6 +48,12 @@ export class CatalogPanel {
this._categoryBar.className = 'catalog-categories'; this._categoryBar.className = 'catalog-categories';
this.container.appendChild(this._categoryBar); this.container.appendChild(this._categoryBar);
// Series filter (IKEA only, hidden by default)
this._seriesBar = document.createElement('div');
this._seriesBar.className = 'catalog-series';
this._seriesBar.style.display = 'none';
this.container.appendChild(this._seriesBar);
// Items list // Items list
this._itemList = document.createElement('div'); this._itemList = document.createElement('div');
this._itemList.className = 'catalog-items'; this._itemList.className = 'catalog-items';
@@ -58,16 +72,57 @@ export class CatalogPanel {
this._createFormContainer.style.display = 'none'; this._createFormContainer.style.display = 'none';
this.container.appendChild(this._createFormContainer); this.container.appendChild(this._createFormContainer);
this._renderSourceTabs();
this._renderCategories(); this._renderCategories();
this._renderSeriesFilter();
this._renderItems(); this._renderItems();
} }
_renderSourceTabs() {
this._sourceBar.innerHTML = '';
const hasIkea = this._hasIkeaItems();
const sources = [
{ id: 'all', label: 'All' },
{ id: 'standard', label: 'Standard' },
];
if (hasIkea) {
sources.push({ id: 'ikea', label: 'IKEA' });
}
for (const src of sources) {
const btn = document.createElement('button');
btn.className = 'catalog-source-btn' + (src.id === this.selectedSource ? ' active' : '');
btn.textContent = src.label;
btn.addEventListener('click', () => {
this.selectedSource = src.id;
this.selectedSeries = 'all';
this._renderSourceTabs();
this._renderCategories();
this._renderSeriesFilter();
this._renderItems();
});
this._sourceBar.appendChild(btn);
}
// Item count badge
const count = this._getFilteredItems().length;
const badge = document.createElement('span');
badge.className = 'catalog-count';
badge.textContent = count;
this._sourceBar.appendChild(badge);
}
_renderCategories() { _renderCategories() {
const catalog = this.renderer.catalogData; const catalog = this.renderer.catalogData;
if (!catalog) return; if (!catalog) return;
this._categoryBar.innerHTML = ''; this._categoryBar.innerHTML = '';
const categories = ['all', ...catalog.categories];
// Get categories from filtered items
const items = this._getSourceFilteredItems();
const activeCats = new Set(items.map(it => it.category));
const categories = ['all', ...catalog.categories.filter(c => activeCats.has(c))];
const LABELS = { const LABELS = {
all: 'All', all: 'All',
@@ -90,12 +145,101 @@ export class CatalogPanel {
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
this.selectedCategory = cat; this.selectedCategory = cat;
this._renderCategories(); this._renderCategories();
this._renderSeriesFilter();
this._renderItems(); this._renderItems();
}); });
this._categoryBar.appendChild(btn); this._categoryBar.appendChild(btn);
} }
} }
_renderSeriesFilter() {
// Only show series filter when IKEA source is active
if (this.selectedSource !== 'ikea') {
this._seriesBar.style.display = 'none';
return;
}
const items = this._getSourceFilteredItems();
const seriesSet = new Set();
for (const it of items) {
if (it.ikeaSeries) seriesSet.add(it.ikeaSeries);
}
if (seriesSet.size < 2) {
this._seriesBar.style.display = 'none';
return;
}
this._seriesBar.style.display = '';
this._seriesBar.innerHTML = '';
const label = document.createElement('span');
label.className = 'catalog-series-label';
label.textContent = 'Series:';
this._seriesBar.appendChild(label);
const seriesList = ['all', ...Array.from(seriesSet).sort()];
for (const s of seriesList) {
const btn = document.createElement('button');
btn.className = 'catalog-series-btn' + (s === this.selectedSeries ? ' active' : '');
btn.textContent = s === 'all' ? 'All' : s;
btn.addEventListener('click', () => {
this.selectedSeries = s;
this._renderSeriesFilter();
this._renderItems();
});
this._seriesBar.appendChild(btn);
}
}
_hasIkeaItems() {
const catalog = this.renderer.catalogData;
if (!catalog) return false;
return catalog.items.some(it => it.id.startsWith('ikea-'));
}
/** Get items filtered by source tab only */
_getSourceFilteredItems() {
const catalog = this.renderer.catalogData;
if (!catalog) return [];
let items = catalog.items;
if (this.selectedSource === 'ikea') {
items = items.filter(it => it.id.startsWith('ikea-'));
} else if (this.selectedSource === 'standard') {
items = items.filter(it => !it.id.startsWith('ikea-'));
}
return items;
}
/** Get items with all filters applied */
_getFilteredItems() {
let items = this._getSourceFilteredItems();
// Filter by category
if (this.selectedCategory !== 'all') {
items = items.filter(it => it.category === this.selectedCategory);
}
// Filter by series (IKEA only)
if (this.selectedSeries !== 'all') {
items = items.filter(it => it.ikeaSeries === this.selectedSeries);
}
// Filter by search
if (this.searchQuery) {
const q = this.searchQuery.toLowerCase();
items = items.filter(it =>
it.name.toLowerCase().includes(q) ||
it.id.toLowerCase().includes(q) ||
it.category.toLowerCase().includes(q) ||
(it.ikeaSeries && it.ikeaSeries.toLowerCase().includes(q))
);
}
return items;
}
_renderItems() { _renderItems() {
const catalog = this.renderer.catalogData; const catalog = this.renderer.catalogData;
if (!catalog) { if (!catalog) {
@@ -103,22 +247,11 @@ export class CatalogPanel {
return; return;
} }
let items = catalog.items; const items = this._getFilteredItems();
// Filter by category // Update count badge
if (this.selectedCategory !== 'all') { const badge = this._sourceBar.querySelector('.catalog-count');
items = items.filter(it => it.category === this.selectedCategory); if (badge) badge.textContent = items.length;
}
// Filter by search
if (this.searchQuery) {
const q = this.searchQuery.toLowerCase();
items = items.filter(it =>
it.name.toLowerCase().includes(q) ||
it.id.toLowerCase().includes(q) ||
it.category.toLowerCase().includes(q)
);
}
this._itemList.innerHTML = ''; this._itemList.innerHTML = '';
@@ -142,13 +275,18 @@ export class CatalogPanel {
const color = item.mesh?.parts?.[0]?.color || '#888'; const color = item.mesh?.parts?.[0]?.color || '#888';
const dims = item.dimensions; const dims = item.dimensions;
const dimStr = `${dims.width}×${dims.depth}×${dims.height}m`; const dimStr = `${dims.width}\u00d7${dims.depth}\u00d7${dims.height}m`;
// Add IKEA badge for IKEA items
const isIkea = item.id.startsWith('ikea-');
const badge = isIkea ? `<span class="catalog-item-badge">IKEA</span>` : '';
const series = isIkea && item.ikeaSeries ? `<span class="catalog-item-series">${item.ikeaSeries}</span>` : '';
card.innerHTML = card.innerHTML =
`<div class="catalog-item-swatch" style="background:${color}"></div>` + `<div class="catalog-item-swatch" style="background:${color}"></div>` +
`<div class="catalog-item-info">` + `<div class="catalog-item-info">` +
`<div class="catalog-item-name">${item.name}</div>` + `<div class="catalog-item-name">${badge}${item.name}</div>` +
`<div class="catalog-item-dims">${dimStr}</div>` + `<div class="catalog-item-dims">${dimStr}${series}</div>` +
`</div>` + `</div>` +
`<button class="catalog-item-add" title="Add to room">+</button>`; `<button class="catalog-item-add" title="Add to room">+</button>`;
@@ -397,8 +535,11 @@ export class CatalogPanel {
this.selectedRoomId = roomId; this.selectedRoomId = roomId;
} }
/** Refresh the item list (e.g., after floor change). */ /** Refresh the full panel (e.g., after catalog merge or floor change). */
refresh() { refresh() {
this._renderSourceTabs();
this._renderCategories();
this._renderSeriesFilter();
this._renderItems(); this._renderItems();
} }
} }

View File

@@ -207,6 +207,89 @@
font-size: 12px; font-size: 12px;
} }
/* Source tabs */
.catalog-source-tabs {
display: flex;
align-items: center;
padding: 10px 12px 6px;
gap: 4px;
border-bottom: 1px solid #eee;
}
.catalog-source-btn {
padding: 4px 10px;
border: 1px solid #ccc;
border-radius: 4px;
background: #fff;
cursor: pointer;
font-size: 12px;
font-weight: 500;
}
.catalog-source-btn.active {
background: #4a90d9;
color: #fff;
border-color: #4a90d9;
}
.catalog-count {
margin-left: auto;
font-size: 11px;
color: #999;
background: #f0f0f0;
padding: 2px 7px;
border-radius: 10px;
}
/* Series filter */
.catalog-series {
padding: 4px 12px 6px;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
border-bottom: 1px solid #f0f0f0;
}
.catalog-series-label {
font-size: 10px;
color: #888;
text-transform: uppercase;
letter-spacing: 0.3px;
margin-right: 2px;
}
.catalog-series-btn {
padding: 2px 6px;
border: 1px solid #ddd;
border-radius: 3px;
background: #fff;
cursor: pointer;
font-size: 10px;
font-weight: 600;
letter-spacing: 0.3px;
}
.catalog-series-btn.active {
background: #0058a3;
color: #fff;
border-color: #0058a3;
}
/* IKEA badge */
.catalog-item-badge {
display: inline-block;
font-size: 8px;
font-weight: 700;
background: #0058a3;
color: #ffda1a;
padding: 1px 4px;
border-radius: 2px;
margin-right: 4px;
vertical-align: middle;
letter-spacing: 0.5px;
}
.catalog-item-series {
font-size: 9px;
color: #0058a3;
margin-left: 6px;
font-weight: 600;
}
/* Custom furniture creator */ /* Custom furniture creator */
.catalog-create-btn { .catalog-create-btn {
display: block; display: block;
@@ -375,6 +458,10 @@
houseRenderer.loadHouse('../data/sample-house.json').then(async (house) => { houseRenderer.loadHouse('../data/sample-house.json').then(async (house) => {
document.getElementById('house-name').textContent = house.name; document.getElementById('house-name').textContent = house.name;
await houseRenderer.loadCatalog('../data/furniture-catalog.json'); await houseRenderer.loadCatalog('../data/furniture-catalog.json');
// Merge IKEA catalog items into the main catalog
await houseRenderer.mergeCatalog('../data/ikea-catalog.json').catch(e =>
console.warn('IKEA catalog not loaded:', e.message)
);
const design = await houseRenderer.loadDesign('../designs/sample-house-design.json'); const design = await houseRenderer.loadDesign('../designs/sample-house-design.json');
// Initialize state and interaction manager // Initialize state and interaction manager

View File

@@ -120,6 +120,37 @@ export class HouseRenderer {
} }
} }
async mergeCatalog(url) {
try {
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to load catalog: ${res.status} ${res.statusText}`);
const extra = await res.json();
if (!this.catalogData) {
return this.loadCatalog(url);
}
// Merge categories
for (const cat of extra.categories || []) {
if (!this.catalogData.categories.includes(cat)) {
this.catalogData.categories.push(cat);
}
}
// Merge items, avoiding duplicates by id
for (const item of extra.items || []) {
if (!this._catalogIndex.has(item.id)) {
this.catalogData.items.push(item);
this._catalogIndex.set(item.id, item);
}
}
// Store extra catalog for tabbed access
if (!this._extraCatalogs) this._extraCatalogs = [];
this._extraCatalogs.push(extra);
return extra;
} catch (err) {
this._emitError('mergeCatalog', err);
throw err;
}
}
async loadDesign(url) { async loadDesign(url) {
try { try {
const res = await fetch(url); const res = await fetch(url);