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:
769
data/ikea-catalog.json
Normal file
769
data/ikea-catalog.json
Normal 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
361
scripts/import-ikea-hf.js
Normal 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);
|
||||
});
|
||||
185
src/catalog.js
185
src/catalog.js
@@ -1,8 +1,9 @@
|
||||
/**
|
||||
* CatalogPanel — left sidebar for browsing furniture catalog.
|
||||
*
|
||||
* Shows categories, search, and item cards. Clicking an item
|
||||
* adds it to the center of the selected room via DesignState.
|
||||
* Shows source tabs (All / Standard / IKEA), categories, series filter,
|
||||
* search, and item cards. Clicking an item adds it to the center of the
|
||||
* selected room via DesignState.
|
||||
*/
|
||||
export class CatalogPanel {
|
||||
constructor(container, { renderer, state, interaction }) {
|
||||
@@ -11,7 +12,9 @@ export class CatalogPanel {
|
||||
this.state = state;
|
||||
this.interaction = interaction;
|
||||
|
||||
this.selectedSource = 'all'; // 'all', 'standard', 'ikea'
|
||||
this.selectedCategory = 'all';
|
||||
this.selectedSeries = 'all';
|
||||
this.searchQuery = '';
|
||||
this.selectedRoomId = null;
|
||||
|
||||
@@ -25,6 +28,11 @@ export class CatalogPanel {
|
||||
this.container.innerHTML = '';
|
||||
this.container.className = 'catalog-panel';
|
||||
|
||||
// Source tabs
|
||||
this._sourceBar = document.createElement('div');
|
||||
this._sourceBar.className = 'catalog-source-tabs';
|
||||
this.container.appendChild(this._sourceBar);
|
||||
|
||||
// Search
|
||||
const searchWrap = document.createElement('div');
|
||||
searchWrap.className = 'catalog-search';
|
||||
@@ -40,6 +48,12 @@ export class CatalogPanel {
|
||||
this._categoryBar.className = 'catalog-categories';
|
||||
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
|
||||
this._itemList = document.createElement('div');
|
||||
this._itemList.className = 'catalog-items';
|
||||
@@ -58,16 +72,57 @@ export class CatalogPanel {
|
||||
this._createFormContainer.style.display = 'none';
|
||||
this.container.appendChild(this._createFormContainer);
|
||||
|
||||
this._renderSourceTabs();
|
||||
this._renderCategories();
|
||||
this._renderSeriesFilter();
|
||||
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() {
|
||||
const catalog = this.renderer.catalogData;
|
||||
if (!catalog) return;
|
||||
|
||||
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 = {
|
||||
all: 'All',
|
||||
@@ -90,12 +145,101 @@ export class CatalogPanel {
|
||||
btn.addEventListener('click', () => {
|
||||
this.selectedCategory = cat;
|
||||
this._renderCategories();
|
||||
this._renderSeriesFilter();
|
||||
this._renderItems();
|
||||
});
|
||||
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() {
|
||||
const catalog = this.renderer.catalogData;
|
||||
if (!catalog) {
|
||||
@@ -103,22 +247,11 @@ export class CatalogPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
let items = catalog.items;
|
||||
const items = this._getFilteredItems();
|
||||
|
||||
// Filter by category
|
||||
if (this.selectedCategory !== 'all') {
|
||||
items = items.filter(it => it.category === this.selectedCategory);
|
||||
}
|
||||
|
||||
// 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)
|
||||
);
|
||||
}
|
||||
// Update count badge
|
||||
const badge = this._sourceBar.querySelector('.catalog-count');
|
||||
if (badge) badge.textContent = items.length;
|
||||
|
||||
this._itemList.innerHTML = '';
|
||||
|
||||
@@ -142,13 +275,18 @@ export class CatalogPanel {
|
||||
const color = item.mesh?.parts?.[0]?.color || '#888';
|
||||
|
||||
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 =
|
||||
`<div class="catalog-item-swatch" style="background:${color}"></div>` +
|
||||
`<div class="catalog-item-info">` +
|
||||
`<div class="catalog-item-name">${item.name}</div>` +
|
||||
`<div class="catalog-item-dims">${dimStr}</div>` +
|
||||
`<div class="catalog-item-name">${badge}${item.name}</div>` +
|
||||
`<div class="catalog-item-dims">${dimStr}${series}</div>` +
|
||||
`</div>` +
|
||||
`<button class="catalog-item-add" title="Add to room">+</button>`;
|
||||
|
||||
@@ -397,8 +535,11 @@ export class CatalogPanel {
|
||||
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() {
|
||||
this._renderSourceTabs();
|
||||
this._renderCategories();
|
||||
this._renderSeriesFilter();
|
||||
this._renderItems();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,89 @@
|
||||
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 */
|
||||
.catalog-create-btn {
|
||||
display: block;
|
||||
@@ -375,6 +458,10 @@
|
||||
houseRenderer.loadHouse('../data/sample-house.json').then(async (house) => {
|
||||
document.getElementById('house-name').textContent = house.name;
|
||||
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');
|
||||
|
||||
// Initialize state and interaction manager
|
||||
|
||||
@@ -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) {
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
|
||||
Reference in New Issue
Block a user