diff --git a/data/ikea-catalog.json b/data/ikea-catalog.json new file mode 100644 index 0000000..aae59ee --- /dev/null +++ b/data/ikea-catalog.json @@ -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" } + ] + } + } + ] +} diff --git a/scripts/import-ikea-hf.js b/scripts/import-ikea-hf.js new file mode 100644 index 0000000..00cde93 --- /dev/null +++ b/scripts/import-ikea-hf.js @@ -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); +}); diff --git a/src/catalog.js b/src/catalog.js index 908baa8..81fb0c3 100644 --- a/src/catalog.js +++ b/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 ? `IKEA` : ''; + const series = isIkea && item.ikeaSeries ? `${item.ikeaSeries}` : ''; card.innerHTML = `
` + `
` + - `
${item.name}
` + - `
${dimStr}
` + + `
${badge}${item.name}
` + + `
${dimStr}${series}
` + `
` + ``; @@ -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(); } } diff --git a/src/index.html b/src/index.html index 1c5a9ba..5642509 100644 --- a/src/index.html +++ b/src/index.html @@ -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 diff --git a/src/renderer.js b/src/renderer.js index bf1995c..6ef54a0 100644 --- a/src/renderer.js +++ b/src/renderer.js @@ -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);