//init WALLS = []; OBJDATA = []; ROOM = []; HISTORY = []; wallSize = 20; partitionSize = 8; let drag = 'off'; let action = 0; let magnetic = 0; let construc = 0; let Rcirclebinder = 8; let mode = 'select_mode'; let modeOption; let linElement = $('#lin'); taille_w = linElement.width(); taille_h = linElement.height(); let offset = linElement.offset(); // Debug: log initial floorplan image (background) width, height, and offset (x,y) try { const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null; if (bgEl) { const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; }; const fw = parseNum(bgEl.getAttribute('width')); const fh = parseNum(bgEl.getAttribute('height')); const fx = parseNum(bgEl.getAttribute('x')); const fy = parseNum(bgEl.getAttribute('y')); console.info('[init] floorplan image', { width: fw, height: fh, offset: { x: fx, y: fy } }); } } catch (_) {} grid = 20; showRib = true; showArea = true; meter = 60; grid_snap = 'off'; colorbackground = "#ffffff"; colorline = "#fff"; colorroom = "#f0daaf"; colorWall = "#666"; pox = 0; poy = 0; segment = 0; xpath = 0; ypath = 0; let width_viewbox = taille_w; let height_viewbox = taille_h; let ratio_viewbox = height_viewbox / width_viewbox; let originX_viewbox = 0; let originY_viewbox = 0; let zoom = 9; let factor = 1; // ************************************************************************** // ***************** LOAD / SAVE LOCALSTORAGE ************************ // ************************************************************************** function initHistory(boot = false) { HISTORY.index = 0; // Preserve existing history across sessions to allow restoring background image metrics. // Do not clear here; new plan initializers below will explicitly clear when appropriate. // if (!boot && localStorage.getItem('history')) localStorage.removeItem('history'); if (localStorage.getItem('history') && boot === "recovery") { let historyTemp = JSON.parse(localStorage.getItem('history')); load(historyTemp.length - 1, "boot"); save("boot"); } if (boot === "newSquare") { if (localStorage.getItem('history')) localStorage.removeItem('history'); HISTORY.push({ "objData": [], "wallData": [{ "thick": 20, "start": { "x": 540, "y": 194 }, "end": { "x": 540, "y": 734 }, "type": "normal", "parent": 3, "child": 1, "angle": 1.5707963267948966, "equations": { "up": { "A": "v", "B": 550 }, "down": { "A": "v", "B": 530 }, "base": { "A": "v", "B": 540 } }, "coords": [{ "x": 550, "y": 204 }, { "x": 530, "y": 184 }, { "x": 530, "y": 744 }, { "x": 550, "y": 724 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 540, "y": 734 }, "end": { "x": 1080, "y": 734 }, "type": "normal", "parent": 0, "child": 2, "angle": 0, "equations": { "up": { "A": "h", "B": 724 }, "down": { "A": "h", "B": 744 }, "base": { "A": "h", "B": 734 } }, "coords": [{ "x": 550, "y": 724 }, { "x": 530, "y": 744 }, { "x": 1090, "y": 744 }, { "x": 1070, "y": 724 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1080, "y": 734 }, "end": { "x": 1080, "y": 194 }, "type": "normal", "parent": 1, "child": 3, "angle": -1.5707963267948966, "equations": { "up": { "A": "v", "B": 1070 }, "down": { "A": "v", "B": 1090 }, "base": { "A": "v", "B": 1080 } }, "coords": [{ "x": 1070, "y": 724 }, { "x": 1090, "y": 744 }, { "x": 1090, "y": 184 }, { "x": 1070, "y": 204 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1080, "y": 194 }, "end": { "x": 540, "y": 194 }, "type": "normal", "parent": 2, "child": 0, "angle": 3.141592653589793, "equations": { "up": { "A": "h", "B": 204 }, "down": { "A": "h", "B": 184 }, "base": { "A": "h", "B": 194 } }, "coords": [{ "x": 1070, "y": 204 }, { "x": 1090, "y": 184 }, { "x": 530, "y": 184 }, { "x": 550, "y": 204 }], "graph": { "0": {}, "context": {}, "length": 1 } }], "roomData": [{ "coords": [{ "x": 540, "y": 734 }, { "x": 1080, "y": 734 }, { "x": 1080, "y": 194 }, { "x": 540, "y": 194 }, { "x": 540, "y": 734 }], "coordsOutside": [{ "x": 1090, "y": 744 }, { "x": 1090, "y": 184 }, { "x": 530, "y": 184 }, { "x": 530, "y": 744 }, { "x": 1090, "y": 744 }], "coordsInside": [{ "x": 1070, "y": 724 }, { "x": 1070, "y": 204 }, { "x": 550, "y": 204 }, { "x": 550, "y": 724 }, { "x": 1070, "y": 724 }], "inside": [], "way": ["0", "2", "3", "1", "0"], "area": 270400, "surface": "", "name": "", "color": "gradientWhite", "showSurface": true, "action": "add" }] }); HISTORY[0] = JSON.stringify(HISTORY[0]); localStorage.setItem('history', JSON.stringify(HISTORY)); load(0); save(); } if (boot === "newL") { if (localStorage.getItem('history')) localStorage.removeItem('history'); HISTORY.push({ "objData": [], "wallData": [{ "thick": 20, "start": { "x": 447, "y": 458 }, "end": { "x": 447, "y": 744 }, "type": "normal", "parent": 5, "child": 1, "angle": 1.5707963267948966, "equations": { "up": { "A": "v", "B": 457 }, "down": { "A": "v", "B": 437 }, "base": { "A": "v", "B": 447 } }, "coords": [{ "x": 457, "y": 468 }, { "x": 437, "y": 448 }, { "x": 437, "y": 754 }, { "x": 457, "y": 734 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 447, "y": 744 }, "end": { "x": 1347, "y": 744 }, "type": "normal", "parent": 0, "child": 2, "angle": 0, "equations": { "up": { "A": "h", "B": 734 }, "down": { "A": "h", "B": 754 }, "base": { "A": "h", "B": 744 } }, "coords": [{ "x": 457, "y": 734 }, { "x": 437, "y": 754 }, { "x": 1357, "y": 754 }, { "x": 1337, "y": 734 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1347, "y": 744 }, "end": { "x": 1347, "y": 144 }, "type": "normal", "parent": 1, "child": 3, "angle": -1.5707963267948966, "equations": { "up": { "A": "v", "B": 1337 }, "down": { "A": "v", "B": 1357 }, "base": { "A": "v", "B": 1347 } }, "coords": [{ "x": 1337, "y": 734 }, { "x": 1357, "y": 754 }, { "x": 1357, "y": 134 }, { "x": 1337, "y": 154 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1347, "y": 144 }, "end": { "x": 1020, "y": 144 }, "type": "normal", "parent": 2, "child": 4, "angle": 3.141592653589793, "equations": { "up": { "A": "h", "B": 154 }, "down": { "A": "h", "B": 134 }, "base": { "A": "h", "B": 144 } }, "coords": [{ "x": 1337, "y": 154 }, { "x": 1357, "y": 134 }, { "x": 1010, "y": 134 }, { "x": 1030, "y": 154 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1020, "y": 144 }, "end": { "x": 1020, "y": 458 }, "type": "normal", "parent": 3, "child": 5, "angle": 1.5707963267948966, "equations": { "up": { "A": "v", "B": 1030 }, "down": { "A": "v", "B": 1010 }, "base": { "A": "v", "B": 1020 } }, "coords": [{ "x": 1030, "y": 154 }, { "x": 1010, "y": 134 }, { "x": 1010, "y": 448 }, { "x": 1030, "y": 468 }], "graph": { "0": {}, "context": {}, "length": 1 } }, { "thick": 20, "start": { "x": 1020, "y": 458 }, "end": { "x": 447, "y": 458 }, "type": "normal", "parent": 4, "child": 0, "angle": 3.141592653589793, "equations": { "up": { "A": "h", "B": 468 }, "down": { "A": "h", "B": 448 }, "base": { "A": "h", "B": 458 } }, "coords": [{ "x": 1030, "y": 468 }, { "x": 1010, "y": 448 }, { "x": 437, "y": 448 }, { "x": 457, "y": 468 }], "graph": { "0": {}, "context": {}, "length": 1 } }], "roomData": [{ "coords": [{ "x": 447, "y": 744 }, { "x": 1347, "y": 744 }, { "x": 1347, "y": 144 }, { "x": 1020, "y": 144 }, { "x": 1020, "y": 458 }, { "x": 447, "y": 458 }, { "x": 447, "y": 744 }], "coordsOutside": [{ "x": 1357, "y": 754 }, { "x": 1357, "y": 134 }, { "x": 1010, "y": 134 }, { "x": 1010, "y": 448 }, { "x": 437, "y": 448 }, { "x": 437, "y": 754 }, { "x": 1357, "y": 754 }], "coordsInside": [{ "x": 1337, "y": 734 }, { "x": 1337, "y": 154 }, { "x": 1030, "y": 154 }, { "x": 1030, "y": 468 }, { "x": 457, "y": 468 }, { "x": 457, "y": 734 }, { "x": 1337, "y": 734 }], "inside": [], "way": ["0", "2", "3", "4", "5", "1", "0"], "area": 330478, "surface": "", "name": "", "color": "gradientWhite", "showSurface": true, "action": "add" }] }); HISTORY[0] = JSON.stringify(HISTORY[0]); localStorage.setItem('history', JSON.stringify(HISTORY)); load(0); save(); } } document.getElementById('redo').addEventListener("click", function () { if (HISTORY.index < HISTORY.length) { load(HISTORY.index); HISTORY.index++; $('#undo').removeClass('disabled'); if (HISTORY.index === HISTORY.length) { $('#redo').addClass('disabled'); } } }); document.getElementById('undo').addEventListener("click", function () { if (HISTORY.index > 0) { $('#undo').removeClass('disabled'); if (HISTORY.index - 1 > 0) { HISTORY.index--; load(HISTORY.index - 1); $('#redo').removeClass('disabled'); } } if (HISTORY.index === 1) $('#undo').addClass('disabled'); }); function save(boot = false) { // Only clear history on explicit boolean true; avoid clearing when called with strings like "boot" if (boot === true) localStorage.removeItem('history'); // If background image sizing is in progress, defer save to avoid capturing default geometry try { if (typeof window !== 'undefined' && window.__bgSizing) { if (typeof console !== 'undefined' && console.debug) { console.debug('[save] deferred: background image sizing in progress'); } setTimeout(function(){ try { save(boot); } catch(_){} }, 50); return false; } } catch(_) {} // FOR CYCLIC OBJ INTO LOCALSTORAGE !!! for (let k in WALLS) { if (WALLS[k].child != null) { WALLS[k].child = WALLS.indexOf(WALLS[k].child); } if (WALLS[k].parent != null) { WALLS[k].parent = WALLS.indexOf(WALLS[k].parent); } } // Gather background image state if present; if element is absent, carry over from previous snapshot to avoid reset const __bgImgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null; // Read previous snapshot (if any) to preserve background image when not present in DOM let __prevSnap = null; try { if (HISTORY && HISTORY.length > 0) { __prevSnap = JSON.parse(HISTORY[HISTORY.length - 1]); } } catch(_) {} // Fallback: read from localStorage history if in-memory HISTORY is empty or unparsable if (!__prevSnap) { try { const __ls = localStorage.getItem('history'); if (__ls) { const __arr = JSON.parse(__ls); if (Array.isArray(__arr) && __arr.length > 0) { __prevSnap = JSON.parse(__arr[__arr.length - 1]); } } } catch(_) {} } const backgroundImage = (__bgImgEl) ? (function(el){ const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; }; const x = parseNum(el.getAttribute('x'), 0); const y = parseNum(el.getAttribute('y'), 0); const width = parseNum(el.getAttribute('width'), 0); const height = parseNum(el.getAttribute('height'), 0); let opacity = parseNum(el.getAttribute('opacity'), 1); if (!isFinite(opacity) || opacity <= 0) opacity = 1; const href = el.getAttribute('href') || el.getAttribute('xlink:href') || ''; // Try to attach fileName from currentBackgroundImage reference if available let fileName = null; try { if (window.currentBackgroundImage && window.currentBackgroundImage.fileName) fileName = window.currentBackgroundImage.fileName; } catch(_) {} const snapshot = { x, y, width, height, opacity, href, fileName }; try { if (typeof console !== 'undefined' && console.debug) console.debug('[save] backgroundImage snapshot', snapshot); } catch(_) {} return snapshot; })(__bgImgEl) : (__prevSnap && __prevSnap.backgroundImage ? __prevSnap.backgroundImage : null); const snapshot = { objData: OBJDATA, wallData: WALLS, roomData: ROOM, furnitureData: getFurnitureData(), backgroundImage }; // If caller requests suppression and we'd drop background image (DOM missing) while previous snapshot has one, skip pushing try { if (typeof window !== 'undefined' && window.__suppressSaveIfNoBg) { const hasDomBg = !!__bgImgEl; const hadPrevBg = !!(__prevSnap && __prevSnap.backgroundImage); if (!hasDomBg && hadPrevBg) { if (typeof console !== 'undefined' && console.debug) { console.debug('[save] suppressed: no DOM backgroundImage while previous snapshot had one'); } // Clear the suppression flag for subsequent saves window.__suppressSaveIfNoBg = false; return false; } // Clear flag if we proceed window.__suppressSaveIfNoBg = false; } } catch(_) {} if (JSON.stringify(snapshot) === HISTORY[HISTORY.length - 1]) { for (let k in WALLS) { if (WALLS[k].child != null) { WALLS[k].child = WALLS[WALLS[k].child]; } if (WALLS[k].parent != null) { WALLS[k].parent = WALLS[WALLS[k].parent]; } } return false; } if (HISTORY.index < HISTORY.length) { HISTORY.splice(HISTORY.index, (HISTORY.length - HISTORY.index)); $('#redo').addClass('disabled'); } HISTORY.push(JSON.stringify(snapshot)); localStorage.setItem('history', JSON.stringify(HISTORY)); HISTORY.index++; // Log when state is saved to history along with current floorplan image metrics try { const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null; if (bgEl) { const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; }; const fw = parseNum(bgEl.getAttribute('width')); const fh = parseNum(bgEl.getAttribute('height')); const fx = parseNum(bgEl.getAttribute('x')); const fy = parseNum(bgEl.getAttribute('y')); console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, width: fw, height: fh, offset: { x: fx, y: fy } }); } else if (backgroundImage) { console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, width: backgroundImage.width, height: backgroundImage.height, offset: { x: backgroundImage.x, y: backgroundImage.y }, note: 'carried over previous backgroundImage' }); } else { console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, note: 'no backgroundImage' }); } } catch (_) {} if (HISTORY.index > 1) $('#undo').removeClass('disabled'); for (let k in WALLS) { if (WALLS[k].child != null) { WALLS[k].child = WALLS[WALLS[k].child]; } if (WALLS[k].parent != null) { WALLS[k].parent = WALLS[WALLS[k].parent]; } } return true; } function load(index = HISTORY.index, boot = false) { if (HISTORY.length === 0 && !boot) return false; for (let k in OBJDATA) { OBJDATA[k].graph.remove(); } OBJDATA = []; let historyTemp = []; historyTemp = JSON.parse(localStorage.getItem('history')); historyTemp = JSON.parse(historyTemp[index]); for (let k in historyTemp.objData) { let OO = historyTemp.objData[k]; // if (OO.family === 'energy') OO.family = 'byObject'; let obj = new editor.obj2D(OO.family, OO.class, OO.type, { x: OO.x, y: OO.y }, OO.angle, OO.angleSign, OO.size, OO.hinge = 'normal', OO.thick, OO.value); obj.limit = OO.limit; OBJDATA.push(obj); $('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph); obj.update(); } WALLS = historyTemp.wallData; for (let k in WALLS) { if (WALLS[k].child != null) { WALLS[k].child = WALLS[WALLS[k].child]; } if (WALLS[k].parent != null) { WALLS[k].parent = WALLS[WALLS[k].parent]; } } ROOM = historyTemp.roomData; // Load furniture data if it exists if (historyTemp.furnitureData) { loadSavedFurnitureData(historyTemp.furnitureData); } // Restore background image properties if present and an image exists in DOM try { if (historyTemp.backgroundImage) { const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null; if (bgEl) { // Only restore if it's the same image as saved (match href) const currentHref = bgEl.getAttribute('href') || bgEl.getAttribute('xlink:href') || ''; const savedHref = historyTemp.backgroundImage.href || ''; try { if (console && console.debug) console.debug('[load] backgroundImage found', { currentHref, savedHref, props: historyTemp.backgroundImage }); } catch(_) {} if (savedHref && currentHref && currentHref === savedHref) { try { if (console && console.debug) console.debug('[load] applying saved backgroundImage props'); } catch(_) {} if (typeof adjustBackgroundImage === 'function') { adjustBackgroundImage(historyTemp.backgroundImage); } else { const props = historyTemp.backgroundImage; if (props.x !== undefined) bgEl.setAttribute('x', props.x); if (props.y !== undefined) bgEl.setAttribute('y', props.y); if (props.width !== undefined) bgEl.setAttribute('width', props.width); if (props.height !== undefined) bgEl.setAttribute('height', props.height); if (props.opacity !== undefined) bgEl.setAttribute('opacity', props.opacity); } } else { try { if (console && console.debug) console.debug('[load] skipping apply: href mismatch or missing'); } catch(_) {} } } else { try { if (console && console.debug) console.debug('[load] no #backgroundImage element to restore to'); } catch(_) {} } } } catch (e) { console.warn('Error restoring background image properties:', e); } // Update UI: filename display and Floorplan mode button based on background image presence try { const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null; const nameEl = (typeof document !== 'undefined') ? document.getElementById('floorplan_filename') : null; const btn = (typeof document !== 'undefined') ? document.getElementById('floorplan_mode_btn') : null; if (bgEl) { // Enable button and show saved filename if available if (btn) btn.disabled = false; if (nameEl) { const savedName = (historyTemp.backgroundImage && historyTemp.backgroundImage.fileName) ? historyTemp.backgroundImage.fileName : ''; nameEl.textContent = savedName; } } else { // No background image element yet: show last used filename if available but keep button disabled if (nameEl) { const savedName = (historyTemp.backgroundImage && historyTemp.backgroundImage.fileName) ? historyTemp.backgroundImage.fileName : ''; nameEl.textContent = savedName; } if (btn) { btn.disabled = true; btn.innerText = 'Floorplan mode'; } if (window.__floorplanMode && typeof exitFloorplanMode === 'function') { exitFloorplanMode(); } } } catch(_) {} editor.architect(WALLS); editor.showScaleBox(); rib(); // Ensure we default to select mode after loading a snapshot try { if (typeof $ !== 'undefined') { $('#boxinfo').html('Mode "select"'); // Clear any lingering binders if (typeof binder !== 'undefined') { try { if (binder.graph) $(binder.graph).remove(); else if (binder.remove) binder.remove(); } catch (e) {} $('#boxbind').empty(); binder = undefined; } // Update buttons UI without triggering a save (avoid fonc_button which calls save()) if (typeof raz_button === 'function') raz_button(); $('#select_mode').removeClass('btn-default').addClass('btn-success'); } mode = 'select_mode'; } catch(_) {} } $('svg').each(function () { $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox) }); // ************************************************************************** // ***************** FUNCTIONS ON BUTTON click ************************ // ************************************************************************** document.getElementById('report_mode').addEventListener("click", function () { if (typeof (globalArea) === "undefined") return false; mode = "report_mode"; $('#panel').hide(); $('#reportTools').show(200) document.getElementById('reportTotalSurface').innerHTML = "Total surface : " + (globalArea / 3600).toFixed(1) + " m²"; $('#reportTotalSurface').show(1000); document.getElementById('reportNumberSurface').innerHTML = "Number of rooms : " + ROOM.length + ""; $('#reportNumberSurface').show(1000); let number = 1; let reportRoom = '
\n'; for (let k in ROOM) { let nameRoom = "Room n°" + number + " (sans nom)"; if (ROOM[k].name != "") nameRoom = ROOM[k].name; reportRoom += '

' + nameRoom + '

\n'; reportRoom += '

Surface : ' + ((ROOM[k].area) / 3600).toFixed(2) + '

\n'; number++; } reportRoom += '

\n'; reportRoom += '
\n'; let switchNumber = 0; let plugNumber = 0; let lampNumber = 0; for (let k in OBJDATA) { if (OBJDATA[k].class === 'energy') { if (OBJDATA[k].type === 'switch' || OBJDATA[k].type === 'doubleSwitch' || OBJDATA[k].type === 'dimmer') switchNumber++; if (OBJDATA[k].type === 'plug' || OBJDATA[k].type === 'plug20' || OBJDATA[k].type === 'plug32') plugNumber++; if (OBJDATA[k].type === 'wallLight' || OBJDATA[k].type === 'roofLight') lampNumber++; } } reportRoom += '

Switch number : ' + switchNumber + '

'; reportRoom += '

Electric outlet number : ' + plugNumber + '

'; reportRoom += '

Light point number : ' + lampNumber + '

'; reportRoom += '
'; reportRoom += '
\n'; reportRoom += '

Energy distribution per room

\n'; number = 1; reportRoom += '
\n'; reportRoom += '

Label

\n'; reportRoom += '
Swi.
\n'; reportRoom += '
Elec. out.
\n'; reportRoom += '
Light.
\n'; reportRoom += '
Watts Max
\n'; reportRoom += '
'; let roomEnergy = []; for (let k in ROOM) { reportRoom += '
\n'; let nameRoom = "Room n°" + number + " (no name)"; if (ROOM[k].name != "") nameRoom = ROOM[k].name; reportRoom += '

' + nameRoom + '

\n'; switchNumber = 0; plugNumber = 0; let plug20 = 0; let plug32 = 0; lampNumber = 0; let wattMax = 0; let plug = false; for (let i in OBJDATA) { if (OBJDATA[i].class === 'energy') { if (OBJDATA[i].type === 'switch' || OBJDATA[i].type === 'doubleSwitch' || OBJDATA[i].type === 'dimmer') { if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) { if (isObjectsEquals(ROOM[k], roomTarget)) switchNumber++; } } if (OBJDATA[i].type === 'plug' || OBJDATA[i].type === 'plug20' || OBJDATA[i].type === 'plug32') { if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) { if (isObjectsEquals(ROOM[k], roomTarget)) { plugNumber++; if (OBJDATA[i].type === 'plug' && !plug) { wattMax += 3520; plug = true; } if (OBJDATA[i].type === 'plug20') { wattMax += 4400; plug20++; } if (OBJDATA[i].type === 'plug32') { wattMax += 7040; plug32++; } } } } if (OBJDATA[i].type === 'wallLight' || OBJDATA[i].type === 'roofLight') { if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) { if (isObjectsEquals(ROOM[k], roomTarget)) { lampNumber++; wattMax += 100; } } } } } roomEnergy.push({ switch: switchNumber, plug: plugNumber, plug20: plug20, plug32: plug32, light: lampNumber }); reportRoom += '
' + switchNumber + '
\n'; reportRoom += '
' + plugNumber + '
\n'; reportRoom += '
' + lampNumber + '
\n'; reportRoom += '
' + wattMax + '
\n'; number++; reportRoom += '
'; } reportRoom += '

Standard details NF C 15-100

\n'; number = 1; for (let k in ROOM) { reportRoom += '
\n'; let nfc = true; let nameRoom = "Room n°" + number + " (no name)"; if (ROOM[k].name != "") nameRoom = ROOM[k].name; reportRoom += '

' + nameRoom + '

\n'; if (ROOM[k].name === "") { reportRoom += '

The room has no label, Home Rough Editor cannot provide you with information.

\n'; } else { if (ROOM[k].name === "Salon") { for (let g in ROOM) { if (ROOM[g].name === "Salle à manger") { roomEnergy[k].light += roomEnergy[g].light; roomEnergy[k].plug += roomEnergy[g].plug; roomEnergy[k].switch += roomEnergy[g].switch; } } reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 controlled light point (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (roomEnergy[k].plug < 5) { reportRoom += '

This room must have at least 5 power outlets (actually ' + roomEnergy[k].plug + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } if (ROOM[k].name === "Salle à manger") { reportRoom += '

This room is linked to the living room / living room according to the standard.

\n'; } if (ROOM[k].name.substr(0, 7) === "Chambre") { reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 controlled light point (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (roomEnergy[k].plug < 3) { reportRoom += '

This room must have at least 3 power outlets (actually ' + roomEnergy[k].plug + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } if (ROOM[k].name === "SdB") { reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 light point (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (roomEnergy[k].plug < 2) { reportRoom += '

This room must have at least 2 power outlets (actually ' + roomEnergy[k].plug + ').

\n'; nfc = false; } if (roomEnergy[k].switch === 0) { reportRoom += '

This room must have at least 1 switch (actually ' + roomEnergy[k].switch + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } if (ROOM[k].name === "Couloir") { reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 controlled light point (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (roomEnergy[k].plug < 1) { reportRoom += '

This room must have at least 1 power outlet (actually ' + roomEnergy[k].plug + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } if (ROOM[k].name === "Toilette") { reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 light point. (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } if (ROOM[k].name === "Cuisine") { reportRoom += '
'; if (roomEnergy[k].light === 0) { reportRoom += '

This room must have at least 1 controlled light point (actually ' + roomEnergy[k].light + ').

\n'; nfc = false; } if (roomEnergy[k].plug < 6) { reportRoom += '

This room must have at least 6 power outlets (actually ' + roomEnergy[k].plug + ').

\n'; nfc = false; } if (roomEnergy[k].plug32 === 0) { reportRoom += '

This room must have at least 1 32A power outlet (actually ' + roomEnergy[k].plug32 + ').

\n'; nfc = false; } if (roomEnergy[k].plug20 < 2) { reportRoom += '

This room must have at least 2 20A power outlets (actually ' + roomEnergy[k].plug20 + ').

\n'; nfc = false; } if (nfc) reportRoom += ''; reportRoom += '
'; } } number++; reportRoom += '
'; } document.getElementById('reportRooms').innerHTML = reportRoom; $('#reportRooms').show(1000); }); document.getElementById('wallWidth').addEventListener("input", function () { let sliderValue = this.value; binder.wall.thick = sliderValue; binder.wall.type = "normal"; editor.architect(WALLS); let objWall = editor.objFromWall(binder.wall); // LIST OBJ ON EDGE for (let w = 0; w < objWall.length; w++) { objWall[w].thick = sliderValue; objWall[w].update(); } rib(); document.getElementById("wallWidthVal").textContent = sliderValue; }); document.getElementById("bboxTrash").addEventListener("click", function () { binder.obj.graph.remove(); binder.graph.remove(); OBJDATA.splice(OBJDATA.indexOf(binder.obj), 1); $('#objBoundingBox').hide(100); $('#panel').show(200); fonc_button('select_mode'); $('#boxinfo').html('Deleted object'); delete binder; rib(); }); document.getElementById("bboxStepsAdd").addEventListener("click", function () { let newValue = document.getElementById("bboxStepsVal").textContent; if (newValue < 15) { newValue++; binder.obj.value = newValue; binder.obj.update(); document.getElementById("bboxStepsVal").textContent = newValue; } }); document.getElementById("bboxStepsMinus").addEventListener("click", function () { let newValue = document.getElementById("bboxStepsVal").textContent; if (newValue > 2) { newValue--; binder.obj.value = newValue; binder.obj.update(); document.getElementById("bboxStepsVal").textContent = newValue; } }); document.getElementById('bboxWidth').addEventListener("input", function () { let sliderValue = this.value; let objTarget = binder.obj; objTarget.size = (sliderValue / 100) * meter; objTarget.update(); binder.size = (sliderValue / 100) * meter; binder.update(); document.getElementById("bboxWidthVal").textContent = sliderValue; }); document.getElementById('bboxHeight').addEventListener("input", function () { let sliderValue = this.value; let objTarget = binder.obj; objTarget.thick = (sliderValue / 100) * meter; objTarget.update(); binder.thick = (sliderValue / 100) * meter; binder.update(); document.getElementById("bboxHeightVal").textContent = sliderValue; }); document.getElementById('bboxRotation').addEventListener("input", function () { let sliderValue = this.value; let objTarget = binder.obj; objTarget.angle = sliderValue; objTarget.update(); binder.angle = sliderValue; binder.update(); document.getElementById("bboxRotationVal").textContent = sliderValue; }); document.getElementById('doorWindowWidth').addEventListener("input", function () { let sliderValue = this.value; let objTarget = binder.obj; let wallBind = editor.rayCastingWalls(objTarget, WALLS); if (wallBind.length > 1) { wallBind = wallBind[wallBind.length - 1]; } let limits = limitObj(wallBind.equations.base, sliderValue, objTarget); if (qSVG.btwn(limits[1].x, wallBind.start.x, wallBind.end.x) && qSVG.btwn(limits[1].y, wallBind.start.y, wallBind.end.y) && qSVG.btwn(limits[0].x, wallBind.start.x, wallBind.end.x) && qSVG.btwn(limits[0].y, wallBind.start.y, wallBind.end.y)) { objTarget.size = sliderValue; objTarget.limit = limits; objTarget.update(); binder.size = sliderValue; binder.limit = limits; binder.update(); document.getElementById("doorWindowWidthVal").textContent = sliderValue; } inWallRib(wallBind); }); document.getElementById("objToolsHinge").addEventListener("click", function () { let objTarget = binder.obj; let hingeStatus = objTarget.hinge; // normal - reverse if (hingeStatus === 'normal') { objTarget.hinge = 'reverse'; } else objTarget.hinge = 'normal'; objTarget.update(); }); window.addEventListener("load", function () { document.getElementById('panel').style.transform = "translateX(200px)"; document.getElementById('panel').addEventListener("transitionend", function () { document.getElementById('moveBox').style.transform = "translateX(-165px)"; document.getElementById('zoomBox').style.transform = "translateX(-165px)"; }); if (!localStorage.getItem('history')) { $('#recover').html("

Select a plan type."); } const myModal = new bootstrap.Modal($('#myModal')) myModal.show(); }); document.getElementById('sizePolice').addEventListener("input", function () { document.getElementById('labelBox').style.fontSize = this.value + 'px'; }); $('#textToLayer').on('hidden.bs.modal', function (e) { fonc_button('select_mode'); action = 0; let textToMake = document.getElementById('labelBox').textContent; if (textToMake != "" && textToMake != "Your text") { binder = new editor.obj2D("free", "text", document.getElementById('labelBox').style.color, snap, 0, 0, 0, "normal", 0, { text: textToMake, size: document.getElementById('sizePolice').value }); binder.update(); OBJDATA.push(binder); binder.graph.remove(); $('#boxText').append(OBJDATA[OBJDATA.length - 1].graph); OBJDATA[OBJDATA.length - 1].update(); delete binder; $('#boxinfo').html('Added text'); save(); } else { $('#boxinfo').html('Selection mode'); } document.getElementById('labelBox').textContent = "Your text"; document.getElementById('labelBox').style.color = "#333333"; document.getElementById('labelBox').style.fontSize = "15px"; document.getElementById('sizePolice').value = 15; }); if (!Array.prototype.includes) { Object.defineProperty(Array.prototype, 'includes', { value: function (searchElement, fromIndex) { if (this === null) { throw new TypeError('"this" is null or not defined'); } let o = Object(this); let len = o.length >>> 0; if (len === 0) { return false; } let n = fromIndex | 0; let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); while (k < len) { if (o[k] === searchElement) { return true; } k++; } return false; } }); } function isObjectsEquals(a, b, message = false) { if (message) console.log(message) let isOK = true; for (let prop in a) { if (a[prop] !== b[prop]) { isOK = false; break; } } return isOK; }; function throttle(callback, delay) { let last; let timer; return function () { let context = this; let now = +new Date(); let args = arguments; if (last && now < last + delay) { // le délai n'est pas écoulé on reset le timer clearTimeout(timer); timer = setTimeout(function () { last = now; callback.apply(context, args); }, delay); } else { last = now; callback.apply(context, args); } }; } linElement.mousewheel(throttle(function (event) { event.preventDefault(); if (event.deltaY > 0) { zoom_maker('zoomin', 200); } else { zoom_maker('zoomout', 200); } }, 100)); document.getElementById("showRib").addEventListener("click", function () { if (document.getElementById("showRib").checked) { $('#boxScale').show(200); $('#boxRib').show(200); showRib = true; } else { $('#boxScale').hide(100); $('#boxRib').hide(100); showRib = false; } }); document.getElementById("showArea").addEventListener("click", function () { if (document.getElementById("showArea").checked) { $('#boxArea').show(200); } else { $('#boxArea').hide(100); } }); document.getElementById("showLayerRoom").addEventListener("click", function () { if (document.getElementById("showLayerRoom").checked) { $('#boxRoom').show(200); } else { $('#boxRoom').hide(100); } }); document.getElementById("showLayerEnergy").addEventListener("click", function () { if (document.getElementById("showLayerEnergy").checked) { $('#boxEnergy').show(200); } else { $('#boxEnergy').hide(100); } }); document.getElementById("showLayerBackground").addEventListener("click", function () { const backgroundImage = document.getElementById('backgroundImage'); if (backgroundImage) { if (document.getElementById("showLayerBackground").checked) { backgroundImage.style.display = 'block'; } else { backgroundImage.style.display = 'none'; } } }); // document.getElementById("showLayerFurniture").addEventListener("click", function () { // if (document.getElementById("showLayerFurniture").checked) { // $('#boxFurniture').show(200); // } // else { // $('#boxFurniture').hide(100); // } // }); document.getElementById("applySurface").addEventListener("click", function () { $('#roomTools').hide(100); $('#panel').show(200); binder.remove(); delete binder; let id = $('#roomIndex').val(); //COLOR let data = $('#roomBackground').val(); ROOM[id].color = data; //ROOM NAME let roomName = $('#roomName').val(); if (roomName === 'None') { roomName = ''; } ROOM[id].name = roomName; //ROOM SURFACE let area = $('#roomSurface').val(); ROOM[id].surface = area; //SHOW SURFACE let show = document.querySelector("#seeArea").checked; ROOM[id].showSurface = show; //ACTION PARAM let action = document.querySelector('input[type=radio]:checked').value; ROOM[id].action = action; if (action === 'sub') { ROOM[id].color = 'hatch'; } if (action != 'sub' && data === 'hatch') { ROOM[id].color = 'gradientNeutral'; } $('#boxRoom').empty(); $('#boxSurface').empty(); editor.roomMaker(Rooms); $('#boxinfo').html('Updated room'); fonc_button('select_mode'); }); document.getElementById("resetRoomTools").addEventListener("click", function () { $('#roomTools').hide(100); $('#panel').show(200); binder.remove(); delete binder; $('#boxinfo').html('Updated room'); fonc_button('select_mode'); }); document.getElementById("wallTrash").addEventListener("click", function () { let wall = binder.wall; for (let k in WALLS) { if (isObjectsEquals(WALLS[k].child, wall)) WALLS[k].child = null; if (isObjectsEquals(WALLS[k].parent, wall)) { WALLS[k].parent = null; } } WALLS.splice(WALLS.indexOf(wall), 1); $('#wallTools').hide(100); wall.graph.remove(); binder.graph.remove(); editor.architect(WALLS); rib(); mode = "select_mode"; $('#panel').show(200); }); let textEditorColorBtn = document.querySelectorAll('.textEditorColor'); for (let k = 0; k < textEditorColorBtn.length; k++) { textEditorColorBtn[k].addEventListener('click', function () { document.getElementById('labelBox').style.color = this.style.color; }); } let zoomBtn = document.querySelectorAll('.zoom'); for (let k = 0; k < zoomBtn.length; k++) { zoomBtn[k].addEventListener("click", function () { let lens = this.getAttribute('data-zoom'); zoom_maker(lens, 200, 50); }) } let roomColorBtn = document.querySelectorAll(".roomColor"); for (let k = 0; k < roomColorBtn.length; k++) { roomColorBtn[k].addEventListener("click", function () { let data = this.getAttribute('data-type'); $('#roomBackground').val(data); binder.attr({ 'fill': 'url(#' + data + ')' }); }); } let objTrashBtn = document.querySelectorAll(".objTrash"); for (let k = 0; k < objTrashBtn.length; k++) { objTrashBtn[k].addEventListener("click", function () { $('#objTools').hide('100'); let obj = binder.obj; obj.graph.remove(); OBJDATA.splice(OBJDATA.indexOf(obj), 1); fonc_button('select_mode'); $('#boxinfo').html('Selection mode'); $('#panel').show('200'); binder.graph.remove(); delete binder; rib(); $('#panel').show('300'); }); } let dropdownMenu = document.querySelectorAll(".dropdown-menu li a"); for (let k = 0; k < dropdownMenu.length; k++) { dropdownMenu[k].addEventListener("click", function () { let selText = this.textContent; $(this).parents('.btn-group').find('.dropdown-toggle').html(selText + ' '); if (selText != 'None') $('#roomName').val(selText); else $('#roomName').val(''); }); } // TRY MATRIX CALC FOR BBOX REAL COORDS WITH TRAS + ROT. function matrixCalc(el, message = false) { if (message) console.log("matrixCalc called by -> " + message); let m = el.getCTM(); let bb = el.getBBox(); let tpts = [ matrixXY(m, bb.x, bb.y), matrixXY(m, bb.x + bb.width, bb.y), matrixXY(m, bb.x + bb.width, bb.y + bb.height), matrixXY(m, bb.x, bb.y + bb.height)]; return tpts; } function matrixXY(m, x, y) { return { x: x * m.a + y * m.c + m.e, y: x * m.b + y * m.d + m.f }; } function realBboxShow(coords) { for (let k in coords) { debugPoint(coords[k]); } } function limitObj(equation, size, coords, message = false) { if (message) { console.log(message); } let Px = coords.x; let Py = coords.y; let Aq = equation.A; let Bq = equation.B; let pos1, pos2; if (Aq === 'v') { pos1 = { x: Px, y: Py - size / 2 }; pos2 = { x: Px, y: Py + size / 2 }; } else if (Aq === 'h') { pos1 = { x: Px - size / 2, y: Py }; pos2 = { x: Px + size / 2, y: Py }; } else { let A = 1 + Aq * Aq; let B = (-2 * Px) + (2 * Aq * Bq) + (-2 * Py * Aq); let C = (Px * Px) + (Bq * Bq) - (2 * Py * Bq) + (Py * Py) - (size * size) / 4; // -N let Delta = (B * B) - (4 * A * C); let posX1 = (-B - (Math.sqrt(Delta))) / (2 * A); let posX2 = (-B + (Math.sqrt(Delta))) / (2 * A); pos1 = { x: posX1, y: (Aq * posX1) + Bq }; pos2 = { x: posX2, y: (Aq * posX2) + Bq }; } return [pos1, pos2]; } function zoom_maker(lens, xmove, xview) { if (lens === 'zoomout' && zoom > 1 && zoom < 17) { zoom--; width_viewbox += xmove; let ratioWidthZoom = taille_w / width_viewbox; height_viewbox = width_viewbox * ratio_viewbox; myDiv = document.getElementById("scaleVal"); myDiv.style.width = 60 * ratioWidthZoom + 'px'; originX_viewbox = originX_viewbox - (xmove / 2); originY_viewbox = originY_viewbox - (xmove / 2 * ratio_viewbox); } if (lens === 'zoomin' && zoom < 14 && zoom > 0) { zoom++; let oldWidth = width_viewbox; let ratioWidthZoom = taille_w / width_viewbox; let newWidth = width_viewbox - xmove; if (newWidth < 100) newWidth = 100; width_viewbox = newWidth; height_viewbox = width_viewbox * ratio_viewbox; originX_viewbox = originX_viewbox + ((oldWidth - width_viewbox) / 2); originY_viewbox = originY_viewbox + (((oldWidth - width_viewbox) / 2) * ratio_viewbox); myDiv = document.getElementById("scaleVal"); myDiv.style.width = 60 * ratioWidthZoom + 'px'; } if (lens === 'zoominit') { width_viewbox = taille_w; height_viewbox = width_viewbox * ratio_viewbox; originX_viewbox = 0; originY_viewbox = 0; zoom = 9; factor = 1; } if (lens === 'zoomright') { originX_viewbox += xview; } if (lens === 'zoomleft') { originX_viewbox -= xview; } if (lens === 'zoomtop') { originY_viewbox -= xview; } if (lens === 'zoombottom') { originY_viewbox += xview; } if (lens === 'zoomdrag') { originX_viewbox -= xmove; originY_viewbox -= xview; } // Update pixel-to-SVG factor after any viewBox change factor = width_viewbox / taille_w; $('svg').each(function () { $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox) }); } // Center the current floorplan in view by fitting the WALLS bounding box to the viewport function centerFloorplanView(padding = 40) { try { if (!Array.isArray(WALLS) || WALLS.length === 0) return false; // Compute bounding box from wall endpoints let minX, minY, maxX, maxY; for (let i = 0; i < WALLS.length; i++) { const s = WALLS[i].start; const e = WALLS[i].end; if (!i) { minX = Math.min(s.x, e.x); minY = Math.min(s.y, e.y); maxX = Math.max(s.x, e.x); maxY = Math.max(s.y, e.y); } else { minX = Math.min(minX, s.x, e.x); minY = Math.min(minY, s.y, e.y); maxX = Math.max(maxX, s.x, e.x); maxY = Math.max(maxY, s.y, e.y); } } // Handle degenerate cases if (minX === undefined || minY === undefined || maxX === undefined || maxY === undefined) return false; const bboxW = Math.max(1, maxX - minX); const bboxH = Math.max(1, maxY - minY); // Target size keeping aspect ratio const viewAspect = ratio_viewbox; // height/width const targetWidth = bboxW + 2 * padding; const targetHeight = bboxH + 2 * padding; const widthFromHeight = targetHeight / viewAspect; const fitWidth = Math.max(targetWidth, widthFromHeight); const fitHeight = fitWidth * viewAspect; width_viewbox = fitWidth; height_viewbox = fitHeight; const cx = (minX + maxX) / 2; const cy = (minY + maxY) / 2; originX_viewbox = cx - (width_viewbox / 2); originY_viewbox = cy - (height_viewbox / 2); // Update pixel-to-SVG factor based on new viewBox size factor = width_viewbox / taille_w; // Update scale indicator if present const scaleEl = document.getElementById('scaleVal'); if (scaleEl) { const ratioWidthZoom = taille_w / width_viewbox; scaleEl.style.width = (60 * ratioWidthZoom) + 'px'; } // Apply viewBox to all SVGs $('svg').each(function () { $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox); }); return true; } catch (e) { console.error('Error centering view:', e); return false; } } tactile = false; function calcul_snap(event, state) { if (event.touches) { let touches = event.changedTouches; console.log("toto") eX = touches[0].pageX; eY = touches[0].pageY; tactile = true; } else { eX = event.pageX; eY = event.pageY; } x_mouse = (eX * factor) - (offset.left * factor) + originX_viewbox; y_mouse = (eY * factor) - (offset.top * factor) + originY_viewbox; if (state === 'on') { x_grid = Math.round(x_mouse / grid) * grid; y_grid = Math.round(y_mouse / grid) * grid; } if (state === 'off') { x_grid = x_mouse; y_grid = y_mouse; } return { x: x_grid, y: y_grid, xMouse: x_mouse, yMouse: y_mouse }; } minMoveGrid = function (mouse) { return Math.abs(Math.abs(pox - mouse.x) + Math.abs(poy - mouse.y)); } function intersectionOff() { if (typeof (lineIntersectionP) != 'undefined') { lineIntersectionP.remove(); delete lineIntersectionP; } } function intersection(snap, range = Infinity, except = ['']) { // ORANGE LINES 90° NEAR SEGMENT let bestEqPoint = {}; let equation = {}; bestEqPoint.distance = range; if (typeof (lineIntersectionP) != 'undefined') { lineIntersectionP.remove(); delete lineIntersectionP; } lineIntersectionP = qSVG.create("boxbind", "path", { // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+ d: "", "stroke": "transparent", "stroke-width": 0.5, "stroke-opacity": "1", fill: "none" }); for (index = 0; index < WALLS.length; index++) { if (except.indexOf(WALLS[index]) === -1) { let x1 = WALLS[index].start.x; let y1 = WALLS[index].start.y; let x2 = WALLS[index].end.x; let y2 = WALLS[index].end.y; // EQUATION 90° of segment nf/nf-1 at X2/Y2 Point if (Math.abs(y2 - y1) === 0) { equation.C = 'v'; // C/D equation 90° Coef = -1/E equation.D = x1; equation.E = 'h'; // E/F equation Segment equation.F = y1; equation.G = 'v'; // G/H equation 90° Coef = -1/E equation.H = x2; equation.I = 'h'; // I/J equation Segment equation.J = y2; } else if (Math.abs(x2 - x1) === 0) { equation.C = 'h'; // C/D equation 90° Coef = -1/E equation.D = y1; equation.E = 'v'; // E/F equation Segment equation.F = x1; equation.G = 'h'; // G/H equation 90° Coef = -1/E equation.H = y2; equation.I = 'v'; // I/J equation Segment equation.J = x2; } else { equation.C = (x1 - x2) / (y2 - y1); equation.D = y1 - (x1 * equation.C); equation.E = (y2 - y1) / (x2 - x1); equation.F = y1 - (x1 * equation.E); equation.G = (x1 - x2) / (y2 - y1); equation.H = y2 - (x2 * equation.C); equation.I = (y2 - y1) / (x2 - x1); equation.J = y2 - (x2 * equation.E); } equation.A = equation.C; equation.B = equation.D; eq = qSVG.nearPointOnEquation(equation, snap); if (eq.distance < bestEqPoint.distance) { setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 1); } equation.A = equation.E; equation.B = equation.F; eq = qSVG.nearPointOnEquation(equation, snap); if (eq.distance < bestEqPoint.distance) { setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 1); } equation.A = equation.G; equation.B = equation.H; eq = qSVG.nearPointOnEquation(equation, snap); if (eq.distance < bestEqPoint.distance) { setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 2); } equation.A = equation.I; equation.B = equation.J; eq = qSVG.nearPointOnEquation(equation, snap); if (eq.distance < bestEqPoint.distance) { setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 2); } } // END INDEXOF EXCEPT TEST } // END LOOP FOR if (bestEqPoint.distance < range) { if (bestEqPoint.way === 2) { lineIntersectionP.attr({ // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+ d: "M" + bestEqPoint.x1 + "," + bestEqPoint.y1 + " L" + bestEqPoint.x2 + "," + bestEqPoint.y2 + " L" + bestEqPoint.x + "," + bestEqPoint.y, "stroke": "#d7ac57" }); } else { lineIntersectionP.attr({ // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+ d: "M" + bestEqPoint.x2 + "," + bestEqPoint.y2 + " L" + bestEqPoint.x1 + "," + bestEqPoint.y1 + " L" + bestEqPoint.x + "," + bestEqPoint.y, "stroke": "#d7ac57" }); } return ({ x: bestEqPoint.x, y: bestEqPoint.y, wall: WALLS[bestEqPoint.node], distance: bestEqPoint.distance }); } else { return false; } } function debugPoint(point, name, color = "#00ff00") { qSVG.create('boxDebug', 'circle', { cx: point.x, cy: point.y, r: 7, fill: color, id: name, class: "visu" }); } function showVertex() { for (let i = 0; i < vertex.length; i++) { debugPoint(vertex[i], i); } } function showJunction() { for (let i = 0; i < junction.length; i++) { debugPoint({ x: junction[i].values[0], y: junction[i].values[1] }, i); } } $('.visu').mouseover(function () { console.log(this.id) }); let sizeText = []; let showAllSizeStatus = 0; function hideAllSize() { $('#boxbind').empty(); sizeText = []; showAllSizeStatus = 0; } function allRib() { $('#boxRib').empty(); for (let i in WALLS) { inWallRib(WALLS[i], 'all'); } } function inWallRib(wall, option = false) { if (!option) $('#boxRib').empty(); ribMaster = []; ribMaster.push([]); ribMaster.push([]); let inter; let distance; let cross; let angleTextValue = wall.angle * (180 / Math.PI); let objWall = editor.objFromWall(wall); // LIST OBJ ON EDGE if (objWall.length == 0) return ribMaster[0].push({ wall: wall, crossObj: false, side: 'up', coords: wall.coords[0], distance: 0 }); ribMaster[1].push({ wall: wall, crossObj: false, side: 'down', coords: wall.coords[1], distance: 0 }); let objTarget = null for (let ob in objWall) { objTarget = objWall[ob]; objTarget.up = [ qSVG.nearPointOnEquation(wall.equations.up, objTarget.limit[0]), qSVG.nearPointOnEquation(wall.equations.up, objTarget.limit[1]) ]; objTarget.down = [ qSVG.nearPointOnEquation(wall.equations.down, objTarget.limit[0]), qSVG.nearPointOnEquation(wall.equations.down, objTarget.limit[1]) ]; distance = qSVG.measure(wall.coords[0], objTarget.up[0]) / meter; ribMaster[0].push({ wall: objTarget, crossObj: ob, side: 'up', coords: objTarget.up[0], distance: distance.toFixed(2) }); distance = qSVG.measure(wall.coords[0], objTarget.up[1]) / meter; ribMaster[0].push({ wall: objTarget, crossObj: ob, side: 'up', coords: objTarget.up[1], distance: distance.toFixed(2) }); distance = qSVG.measure(wall.coords[1], objTarget.down[0]) / meter; ribMaster[1].push({ wall: objTarget, crossObj: ob, side: 'down', coords: objTarget.down[0], distance: distance.toFixed(2) }); distance = qSVG.measure(wall.coords[1], objTarget.down[1]) / meter; ribMaster[1].push({ wall: objTarget, crossObj: ob, side: 'down', coords: objTarget.down[1], distance: distance.toFixed(2) }); } distance = qSVG.measure(wall.coords[0], wall.coords[3]) / meter; ribMaster[0].push({ wall: objTarget, crossObj: false, side: 'up', coords: wall.coords[3], distance: distance }); distance = qSVG.measure(wall.coords[1], wall.coords[2]) / meter; ribMaster[1].push({ wall: objTarget, crossObj: false, side: 'down', coords: wall.coords[2], distance: distance }); ribMaster[0].sort(function (a, b) { return (a.distance - b.distance).toFixed(2); }); ribMaster[1].sort(function (a, b) { return (a.distance - b.distance).toFixed(2); }); for (let t in ribMaster) { for (let n = 1; n < ribMaster[t].length; n++) { let found = true; let shift = -5; let valueText = Math.abs(ribMaster[t][n - 1].distance - ribMaster[t][n].distance); let angleText = angleTextValue; if (found) { if (ribMaster[t][n - 1].side === 'down') { shift = -shift + 10; } if (angleText > 89 || angleText < -89) { angleText -= 180; if (ribMaster[t][n - 1].side === 'down') { shift = -5; } else shift = -shift + 10; } sizeText[n] = document.createElementNS('http://www.w3.org/2000/svg', 'text'); let startText = qSVG.middle(ribMaster[t][n - 1].coords.x, ribMaster[t][n - 1].coords.y, ribMaster[t][n].coords.x, ribMaster[t][n].coords.y); // Check for valid coordinates before setting attributes if (startText && !isNaN(startText.x) && !isNaN(startText.y)) { sizeText[n].setAttributeNS(null, 'x', startText.x); sizeText[n].setAttributeNS(null, 'y', (startText.y) + shift); sizeText[n].setAttributeNS(null, 'text-anchor', 'middle'); sizeText[n].setAttributeNS(null, 'font-family', 'roboto'); sizeText[n].setAttributeNS(null, 'stroke', '#ffffff'); sizeText[n].textContent = valueText.toFixed(2); if (sizeText[n].textContent < 1) { sizeText[n].setAttributeNS(null, 'font-size', '0.8em'); sizeText[n].textContent = sizeText[n].textContent.substring(1, sizeText[n].textContent.length); } else sizeText[n].setAttributeNS(null, 'font-size', '1em'); sizeText[n].setAttributeNS(null, 'stroke-width', '0.27px'); sizeText[n].setAttributeNS(null, 'fill', '#666666'); sizeText[n].setAttribute("transform", "rotate(" + angleText + " " + startText.x + "," + (startText.y) + ")"); } else { // Skip creating text element if coordinates are invalid console.warn('Invalid coordinates for size text element:', startText); } $('#boxRib').append(sizeText[n]); } } } } function rib(shift = 5) { // return false; let ribMaster = []; ribMaster.push([]); ribMaster.push([]); let inter; let distance; let cross; for (let i in WALLS) { if (WALLS[i].equations.base) { ribMaster[0].push([]); pushToRibMaster(ribMaster, 0, i, i, i, 'up', WALLS[i].coords[0], 0); ribMaster[1].push([]); pushToRibMaster(ribMaster, 1, i, i, i, 'down', WALLS[i].coords[1], 0); for (let p in WALLS) { if (i != p && WALLS[p].equations.base) { cross = qSVG.intersectionOfEquations(WALLS[i].equations.base, WALLS[p].equations.base, "object"); if (qSVG.btwn(cross.x, WALLS[i].start.x, WALLS[i].end.x, 'round') && qSVG.btwn(cross.y, WALLS[i].start.y, WALLS[i].end.y, 'round')) { inter = qSVG.intersectionOfEquations(WALLS[i].equations.up, WALLS[p].equations.up, "object"); if (qSVG.btwn(inter.x, WALLS[i].coords[0].x, WALLS[i].coords[3].x, 'round') && qSVG.btwn(inter.y, WALLS[i].coords[0].y, WALLS[i].coords[3].y, 'round') && qSVG.btwn(inter.x, WALLS[p].coords[0].x, WALLS[p].coords[3].x, 'round') && qSVG.btwn(inter.y, WALLS[p].coords[0].y, WALLS[p].coords[3].y, 'round')) { distance = qSVG.measure(WALLS[i].coords[0], inter) / meter; pushToRibMaster(ribMaster, 0, i, i, p, 'up', inter, distance.toFixed(2)); } inter = qSVG.intersectionOfEquations(WALLS[i].equations.up, WALLS[p].equations.down, "object"); if (qSVG.btwn(inter.x, WALLS[i].coords[0].x, WALLS[i].coords[3].x, 'round') && qSVG.btwn(inter.y, WALLS[i].coords[0].y, WALLS[i].coords[3].y, 'round') && qSVG.btwn(inter.x, WALLS[p].coords[1].x, WALLS[p].coords[2].x, 'round') && qSVG.btwn(inter.y, WALLS[p].coords[1].y, WALLS[p].coords[2].y, 'round')) { distance = qSVG.measure(WALLS[i].coords[0], inter) / meter; pushToRibMaster(ribMaster, 0, i, i, p, 'up', inter, distance.toFixed(2)); } inter = qSVG.intersectionOfEquations(WALLS[i].equations.down, WALLS[p].equations.up, "object"); if (qSVG.btwn(inter.x, WALLS[i].coords[1].x, WALLS[i].coords[2].x, 'round') && qSVG.btwn(inter.y, WALLS[i].coords[1].y, WALLS[i].coords[2].y, 'round') && qSVG.btwn(inter.x, WALLS[p].coords[0].x, WALLS[p].coords[3].x, 'round') && qSVG.btwn(inter.y, WALLS[p].coords[0].y, WALLS[p].coords[3].y, 'round')) { distance = qSVG.measure(WALLS[i].coords[1], inter) / meter; pushToRibMaster(ribMaster, 1, i, i, p, 'down', inter, distance.toFixed(2)); } inter = qSVG.intersectionOfEquations(WALLS[i].equations.down, WALLS[p].equations.down, "object"); if (qSVG.btwn(inter.x, WALLS[i].coords[1].x, WALLS[i].coords[2].x, 'round') && qSVG.btwn(inter.y, WALLS[i].coords[1].y, WALLS[i].coords[2].y, 'round') && qSVG.btwn(inter.x, WALLS[p].coords[1].x, WALLS[p].coords[2].x, 'round') && qSVG.btwn(inter.y, WALLS[p].coords[1].y, WALLS[p].coords[2].y, 'round')) { distance = qSVG.measure(WALLS[i].coords[1], inter) / meter; pushToRibMaster(ribMaster, 1, i, i, p, 'down', inter, distance.toFixed(2)); } } } } distance = qSVG.measure(WALLS[i].coords[0], WALLS[i].coords[3]) / meter; pushToRibMaster(ribMaster, 0, i, i, i, 'up', WALLS[i].coords[3], distance.toFixed(2)); distance = qSVG.measure(WALLS[i].coords[1], WALLS[i].coords[2]) / meter; pushToRibMaster(ribMaster, 1, i, i, i, 'down', WALLS[i].coords[2], distance.toFixed(2)); } } for (let a in ribMaster[0]) { ribMaster[0][a].sort(function (a, b) { return (a.distance - b.distance).toFixed(2); }); } for (let a in ribMaster[1]) { ribMaster[1][a].sort(function (a, b) { return (a.distance - b.distance).toFixed(2); }); } let sizeText = []; if (shift === 5) $('#boxRib').empty(); for (let t in ribMaster) { for (let a in ribMaster[t]) { for (let n = 1; n < ribMaster[t][a].length; n++) { if (ribMaster[t][a][n - 1].wallIndex === ribMaster[t][a][n].wallIndex) { let edge = ribMaster[t][a][n].wallIndex; let found = true; let valueText = Math.abs(ribMaster[t][a][n - 1].distance - ribMaster[t][a][n].distance); // CLEAR TOO LITTLE VALUE if (valueText < 0.15) { found = false; } // CLEAR (thick) BETWEEN CROSS EDGE if (found && ribMaster[t][a][n - 1].crossEdge === ribMaster[t][a][n].crossEdge && ribMaster[t][a][n].crossEdge != ribMaster[t][a][n].wallIndex) { found = false; } // CLEAR START INTO EDGE if (found && ribMaster[t][a].length > 2 && n === 1) { let polygon = []; for (let pp = 0; pp < 4; pp++) { polygon.push({ x: WALLS[ribMaster[t][a][n].crossEdge].coords[pp].x, y: WALLS[ribMaster[t][a][n].crossEdge].coords[pp].y }); // FOR Z } if (qSVG.rayCasting(ribMaster[t][a][0].coords, polygon)) { found = false; } } // CLEAR END INTO EDGE if (found && ribMaster[t][a].length > 2 && n === ribMaster[t][a].length - 1) { let polygon = []; for (let pp = 0; pp < 4; pp++) { polygon.push({ x: WALLS[ribMaster[t][a][n - 1].crossEdge].coords[pp].x, y: WALLS[ribMaster[t][a][n - 1].crossEdge].coords[pp].y }); // FOR Z } if (qSVG.rayCasting(ribMaster[t][a][ribMaster[t][a].length - 1].coords, polygon)) { found = false; } } if (found) { let angleText = WALLS[ribMaster[t][a][n].wallIndex].angle * (180 / Math.PI); let shiftValue = -shift; if (ribMaster[t][a][n - 1].side === 'down') { shiftValue = -shiftValue + 10; } if (angleText > 90 || angleText < -89) { angleText -= 180; if (ribMaster[t][a][n - 1].side === 'down') { shiftValue = -shift; } else shiftValue = -shiftValue + 10; } sizeText[n] = document.createElementNS('http://www.w3.org/2000/svg', 'text'); let startText = qSVG.middle(ribMaster[t][a][n - 1].coords.x, ribMaster[t][a][n - 1].coords.y, ribMaster[t][a][n].coords.x, ribMaster[t][a][n].coords.y); // Check for valid coordinates before setting attributes if (startText && !isNaN(startText.x) && !isNaN(startText.y)) { sizeText[n].setAttributeNS(null, 'x', startText.x); sizeText[n].setAttributeNS(null, 'y', (startText.y) + (shiftValue)); sizeText[n].setAttributeNS(null, 'text-anchor', 'middle'); sizeText[n].setAttributeNS(null, 'font-family', 'roboto'); sizeText[n].setAttributeNS(null, 'stroke', '#ffffff'); sizeText[n].textContent = valueText.toFixed(2); if (sizeText[n].textContent < 1) { sizeText[n].setAttributeNS(null, 'font-size', '0.73em'); sizeText[n].textContent = sizeText[n].textContent.substring(1, sizeText[n].textContent.length); } else sizeText[n].setAttributeNS(null, 'font-size', '0.9em'); sizeText[n].setAttributeNS(null, 'stroke-width', '0.2px'); sizeText[n].setAttributeNS(null, 'fill', '#555555'); sizeText[n].setAttribute("transform", "rotate(" + angleText + " " + startText.x + "," + (startText.y) + ")"); } else { // Skip creating text element if coordinates are invalid console.warn('Invalid coordinates for size text element:', startText); } $('#boxRib').append(sizeText[n]); } } } } } } function cursor(tool) { if (tool === 'grab') tool = "url('https://wiki.openmrs.org/s/en_GB/7502/b9217199c27dd617c8d51f6186067d7767c5001b/_/images/icons/emoticons/add.png') 8 8, auto"; if (tool === 'scissor') tool = "url('https://maxcdn.icons8.com/windows10/PNG/64/Hands/hand_scissors-64.png'), auto"; if (tool === 'trash') tool = "url('https://cdn4.iconfinder.com/data/icons/common-toolbar/36/Cancel-32.png'), auto"; if (tool === 'validation') tool = "url('https://images.fatguymedia.com/wp-content/uploads/2015/09/check.png'), auto"; linElement.css('cursor', tool); } function fullscreen() { // go full-screen let i = document.body; if (i.requestFullscreen) { i.requestFullscreen(); } else if (i.webkitRequestFullscreen) { i.webkitRequestFullscreen(); } else if (i.mozRequestFullScreen) { i.mozRequestFullScreen(); } else if (i.msRequestFullscreen) { i.msRequestFullscreen(); } } function outFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); } else if (document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if (document.webkitExitFullscreen) { document.webkitExitFullscreen(); } } document.addEventListener("fullscreenchange", function () { if ( !document.fullscreenElement && !document.webkitFullscreenElement && !document.mozFullScreenElement && !document.msFullscreenElement) { $('#nofull_mode').display = 'none'; $('#full_mode').show(); } }); function raz_button() { $('#rect_mode').removeClass('btn-success'); $('#rect_mode').addClass('btn-default'); $('#select_mode').removeClass('btn-success'); $('#select_mode').addClass('btn-default'); $('#line_mode').removeClass('btn-success'); $('#line_mode').addClass('btn-default'); $('#partition_mode').removeClass('btn-success'); $('#partition_mode').addClass('btn-default'); $('#door_mode').removeClass('btn-success'); $('#door_mode').addClass('btn-default'); $('#node_mode').removeClass('btn-success'); $('#node_mode').addClass('btn-default'); $('#text_mode').removeClass('btn-success'); $('#text_mode').addClass('btn-default'); $('#room_mode').removeClass('btn-success'); $('#room_mode').addClass('btn-default'); $('#distance_mode').removeClass('btn-success'); $('#distance_mode').addClass('btn-default'); $('#object_mode').removeClass('btn-success'); $('#object_mode').addClass('btn-default'); $('#stair_mode').removeClass('btn-success'); $('#stair_mode').addClass('btn-default'); } function fonc_button(modesetting, option) { save(); $('.sub').hide(); raz_button(); if (option != 'simpleStair') { $('#' + modesetting).removeClass('btn-default'); $('#' + modesetting).addClass('btn-success'); } mode = modesetting; modeOption = option; if (typeof (lineIntersectionP) != 'undefined') { lineIntersectionP.remove(); delete lineIntersectionP; } } $('#distance_mode').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Add a measurement'); fonc_button('distance_mode'); }); $('#room_mode').click(function () { linElement.css('cursor', 'pointer'); $('#boxinfo').html('Config. of rooms'); fonc_button('room_mode'); }); $('#select_mode').click(function () { $('#boxinfo').html('Mode "select"'); if (typeof (binder) != 'undefined') { try { if (binder.graph) $(binder.graph).remove(); } catch (e) {} $('#boxbind').empty(); binder = undefined; } fonc_button('select_mode'); }); $('#line_mode').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Creation of wall(s)'); multi = 0; action = 0; // snap = calcul_snap(event, grid_snap); // // pox = snap.x; // poy = snap.y; fonc_button('line_mode'); }); $('#partition_mode').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Creation of thin wall(s)'); multi = 0; fonc_button('partition_mode'); }); $('#rect_mode').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Room(s) creation'); fonc_button('rect_mode'); }); $('.door').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Add a door'); $('#door_list').hide(200); fonc_button('door_mode', this.id); }); $('.window').click(function () { linElement.css('cursor', 'crosshair'); $('#boxinfo').html('Add a window'); $('#door_list').hide(200); $('#window_list').hide(200); fonc_button('door_mode', this.id); }); $('.object').click(function () { cursor('move'); $('#boxinfo').html('Add an object'); fonc_button('object_mode', this.id); }); $('#stair_mode').click(function () { cursor('move'); $('#boxinfo').html('Add stair'); fonc_button('object_mode', 'simpleStair'); }); $('#node_mode').click(function () { $('#boxinfo') .html('Cut a wall
Warning : Cutting the wall of a room can cancel its ' + 'configuration'); fonc_button('node_mode'); }); $('#text_mode').click(function () { $('#boxinfo').html('Add text
Place the cursor to the desired location, then ' + 'type your text.'); fonc_button('text_mode'); }); $('#grid_mode').click(function () { if (grid_snap === 'on') { grid_snap = 'off'; $('#boxinfo').html('Help grid off'); $('#grid_mode').removeClass('btn-success'); $('#grid_mode').addClass('btn-warning'); $('#grid_mode').html('GRID OFF'); $('#boxgrid').css('opacity', '0.5'); } else { grid_snap = 'on'; $('#boxinfo').html('Help grid on'); $('#grid_mode').removeClass('btn-warning'); $('#grid_mode').addClass('btn-success'); $('#grid_mode').html('GRID ON '); $('#boxgrid').css('opacity', '1'); } }); // RETURN PATH(s) ARRAY FOR OBJECT + PROPERTY params => bindBox (false = open sideTool), move, resize, rotate function carpentryCalc(classObj, typeObj, sizeObj, thickObj, dividerObj = 10) { // Validate input parameters to prevent NaN propagation if (isNaN(sizeObj) || sizeObj <= 0) sizeObj = 60; if (isNaN(thickObj) || thickObj <= 0) thickObj = 20; if (isNaN(dividerObj) || dividerObj <= 0) dividerObj = 10; let construc = []; construc.params = {}; construc.params.bindBox = false; construc.params.move = false; construc.params.resize = false; construc.params.resizeLimit = {}; construc.params.resizeLimit.width = { min: false, max: false }; construc.params.resizeLimit.height = { min: false, max: false }; construc.params.rotate = false; if (classObj === 'socle') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#5cba79", "#5cba79", ''); } if (classObj === 'doorWindow') { if (typeObj === 'simple') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "none", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + (-sizeObj - thickObj / 2) + " A" + sizeObj + "," + sizeObj + " 0 0,1 " + sizeObj / 2 + "," + (-thickObj / 2), "none", colorWall, ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 40, max: 120 }; } if (typeObj === 'double') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "none", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + (-sizeObj / 2 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,1 0," + (-thickObj / 2), "none", colorWall, ''); pushToConstruc(construc, "M " + (sizeObj / 2) + "," + (-thickObj / 2) + " L " + (sizeObj / 2) + "," + (-sizeObj / 2 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,0 0," + (-thickObj / 2), "none", colorWall, ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 40, max: 160 }; } if (typeObj === 'pocket') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-(thickObj / 2) - 4) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-(thickObj / 2) - 4) + " Z", "#ccc", "none", 'none'); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " M " + (sizeObj / 2) + "," + (thickObj / 2) + " L " + (sizeObj / 2) + "," + (-thickObj / 2), "none", "#494646", '5 5'); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + (-thickObj / 2 - 5) + " L " + (+sizeObj / 2) + "," + (-thickObj / 2 - 5) + " L " + (+sizeObj / 2) + "," + (-thickObj / 2) + " Z", "url(#hatch)", "#494646", ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 60, max: 200 }; } if (typeObj === 'aperture') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "#494646", '5,5'); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-(thickObj / 2)) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + ((-sizeObj / 2) + 5) + "," + thickObj / 2 + " L " + ((-sizeObj / 2) + 5) + "," + (-(thickObj / 2)) + " Z", "none", "#494646", 'none'); pushToConstruc(construc, "M " + ((sizeObj / 2) - 5) + "," + (-(thickObj / 2)) + " L " + ((sizeObj / 2) - 5) + "," + thickObj / 2 + " L " + (sizeObj / 2) + "," + thickObj / 2 + " L " + (sizeObj / 2) + "," + (-(thickObj / 2)) + " Z", "none", "#494646", 'none'); construc.params.resize = true; construc.params.resizeLimit.width = { min: 40, max: 500 }; } if (typeObj === 'fix') { pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " + sizeObj / 2 + ",2 L " + sizeObj / 2 + ",-2 Z", "#ccc", "none", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 30, max: 300 }; } if (typeObj === 'flap') { pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " + sizeObj / 2 + ",2 L " + sizeObj / 2 + ",-2 Z", "#ccc", "none", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((-sizeObj / 2) + ((sizeObj) * 0.866)) + "," + ((-sizeObj / 2) - (thickObj / 2)) + " A" + sizeObj + "," + sizeObj + " 0 0,1 " + sizeObj / 2 + "," + (-thickObj / 2), "none", colorWall, ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 20, max: 100 }; } if (typeObj === 'twin') { pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " + sizeObj / 2 + ",2 L " + sizeObj / 2 + ",-2 Z", "#000", "none", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "#fff", "#fff", '', 0.7); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#000", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((-sizeObj / 2) + ((sizeObj / 2) * 0.866)) + "," + (-sizeObj / 4 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,1 0," + (-thickObj / 2), "none", colorWall, ''); pushToConstruc(construc, "M " + (sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((sizeObj / 2) + ((-sizeObj / 2) * 0.866)) + "," + (-sizeObj / 4 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,0 0," + (-thickObj / 2), "none", colorWall, ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 40, max: 200 }; } if (typeObj === 'bay') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", ''); pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",0 L 2,0 L 2,2 L 3,2 L 3,-2 Z", "#ccc", "none", ''); pushToConstruc(construc, "M -2,1 L -2,3 L " + sizeObj / 2 + ",3 L " + sizeObj / 2 + ",1 L -1,1 L -1,-1 L -2,-1 Z", "#ccc", "none", ''); construc.params.resize = true; construc.params.resizeLimit.width = { min: 60, max: 300 }; } } if (classObj === 'measure') { construc.params.bindBox = true; pushToConstruc(construc, "M-" + (sizeObj / 2) + ",0 l10,-10 l0,8 l" + (sizeObj - 20) + ",0 l0,-8 l10,10 l-10,10 l0,-8 l-" + (sizeObj - 20) + ",0 l0,8 Z", "#729eeb", "none", ''); } if (classObj === 'boundingBox') { pushToConstruc(construc, "M" + (-sizeObj / 2 - 10) + "," + (-thickObj / 2 - 10) + " L" + (sizeObj / 2 + 10) + "," + (-thickObj / 2 - 10) + " L" + (sizeObj / 2 + 10) + "," + (thickObj / 2 + 10) + " L" + (-sizeObj / 2 - 10) + "," + (thickObj / 2 + 10) + " Z", 'none', "#aaa", ''); // construc.push({'path':"M"+dividerObj[0].x+","+dividerObj[0].y+" L"+dividerObj[1].x+","+dividerObj[1].y+" L"+dividerObj[2].x+", // "+dividerObj[2].y+" L"+dividerObj[3].x+","+dividerObj[3].y+" Z", 'fill':'none', 'stroke':"#000", 'strokeDashArray': ''}); } //typeObj = color dividerObj = text if (classObj === 'text') { construc.params.bindBox = true; construc.params.move = true; construc.params.rotate = true; construc.push({ 'text': dividerObj.text, 'x': '0', 'y': '0', 'fill': typeObj, 'stroke': typeObj, 'fontSize': dividerObj.size + 'px', "strokeWidth": "0px" }); } if (classObj === 'stair') { construc.params.bindBox = true; construc.params.move = true; construc.params.resize = true; construc.params.rotate = true; construc.params.width = 60; construc.params.height = 180; if (typeObj === 'simpleStair') { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#fff", "#000", ''); let heightStep = thickObj / (dividerObj); for (let i = 1; i < dividerObj + 1; i++) { pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + ((-thickObj / 2) + (i * heightStep)) + " L " + (sizeObj / 2) + "," + ((-thickObj / 2) + (i * heightStep)), "none", "#000", 'none'); } construc.params.resizeLimit.width = { min: 40, max: 200 }; construc.params.resizeLimit.height = { min: 40, max: 400 }; } } if (classObj === 'energy') { construc.params.bindBox = true; construc.params.move = true; construc.params.resize = false; construc.params.rotate = false; if (typeObj === 'gtl') { pushToConstruc(construc, "m -20,-20 l 40,0 l0,40 l-40,0 Z", "#fff", "#333", ''); construc.push({ 'text': "GTL", 'x': '0', 'y': '5', 'fill': "#333333", 'stroke': "none", 'fontSize': '0.9em', "strokeWidth": "0.4px" }); construc.params.width = 40; construc.params.height = 40; construc.family = 'stick'; } if (typeObj === 'switch') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", ''); pushToConstruc(construc, qSVG.circlePath(-2, 4, 5), "none", "#333", ''); pushToConstruc(construc, "m 0,0 5,-9", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'doubleSwitch') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", ''); pushToConstruc(construc, qSVG.circlePath(0, 0, 4), "none", "#333", ''); pushToConstruc(construc, "m 2,-3 5,-8 3,2", "none", "#333", ''); pushToConstruc(construc, "m -2,3 -5,8 -3,-2", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'dimmer') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", ''); pushToConstruc(construc, qSVG.circlePath(-2, 4, 5), "none", "#333", ''); pushToConstruc(construc, "m 0,0 5,-9", "none", "#333", ''); pushToConstruc(construc, "M -2,-6 L 10,-4 L-2,-2 Z", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'plug') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", ''); pushToConstruc(construc, "m 0,3 v 7", "none", "#333", ''); pushToConstruc(construc, "m -10,4 h 20", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'plug20') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", ''); pushToConstruc(construc, "m 0,3 v 7", "none", "#333", ''); pushToConstruc(construc, "m -10,4 h 20", "none", "#333", ''); construc.push({ 'text': "20A", 'x': '0', 'y': '-5', 'fill': "#333333", 'stroke': "none", 'fontSize': '0.65em', "strokeWidth": "0.4px" }); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'plug32') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", ''); pushToConstruc(construc, "m 0,3 v 7", "none", "#333", ''); pushToConstruc(construc, "m -10,4 h 20", "none", "#333", ''); construc.push({ 'text': "32A", 'x': '0', 'y': '-5', 'fill': "#333333", 'stroke': "none", 'fontSize': '0.65em', "strokeWidth": "0.4px" }); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'roofLight') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "M -8,-8 L 8,8 M -8,8 L 8,-8", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'free'; } if (typeObj === 'wallLight') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "M -8,-8 L 8,8 M -8,8 L 8,-8", "none", "#333", ''); pushToConstruc(construc, "M -10,10 L 10,10", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'www') { pushToConstruc(construc, "m -20,-20 l 40,0 l0,40 l-40,0 Z", "#fff", "#333", ''); construc.push({ 'text': "@", 'x': '0', 'y': '4', 'fill': "#333333", 'stroke': "none", 'fontSize': '1.2em', "strokeWidth": "0.4px" }); construc.params.width = 40; construc.params.height = 40; construc.family = 'free'; } if (typeObj === 'rj45') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "m-10,5 l0,-10 m20,0 l0,10", "none", "#333", ''); pushToConstruc(construc, "m 0,5 v 7", "none", "#333", ''); pushToConstruc(construc, "m -10,5 h 20", "none", "#333", ''); construc.push({ 'text': "RJ45", 'x': '0', 'y': '-5', 'fill': "#333333", 'stroke': "none", 'fontSize': '0.5em', "strokeWidth": "0.4px" }); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'tv') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "m-10,5 l0-10 m20,0 l0,10", "none", "#333", ''); pushToConstruc(construc, "m-7,-5 l0,7 l14,0 l0,-7", "none", "#333", ''); pushToConstruc(construc, "m 0,5 v 7", "none", "#333", ''); pushToConstruc(construc, "m -10,5 h 20", "none", "#333", ''); construc.push({ 'text': "TV", 'x': '0', 'y': '-5', 'fill': "#333333", 'stroke': "none", 'fontSize': '0.5em', "strokeWidth": "0.4px" }); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'heater') { pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", ''); pushToConstruc(construc, "m-15,-4 l30,0", "none", "#333", ''); pushToConstruc(construc, "m-14,-8 l28,0", "none", "#333", ''); pushToConstruc(construc, "m-11,-12 l22,0", "none", "#333", ''); pushToConstruc(construc, "m-16,0 l32,0", "none", "#333", ''); pushToConstruc(construc, "m-15,4 l30,0", "none", "#333", ''); pushToConstruc(construc, "m-14,8 l28,0", "none", "#333", ''); pushToConstruc(construc, "m-11,12 l22,0", "none", "#333", ''); construc.params.width = 36; construc.params.height = 36; construc.family = 'stick'; } if (typeObj === 'radiator') { pushToConstruc(construc, "m -20,-10 l 40,0 l0,20 l-40,0 Z", "#fff", "#333", ''); pushToConstruc(construc, "M -15,-10 L -15,10", "#fff", "#333", ''); pushToConstruc(construc, "M -10,-10 L -10,10", "#fff", "#333", ''); pushToConstruc(construc, "M -5,-10 L -5,10", "#fff", "#333", ''); pushToConstruc(construc, "M -0,-10 L -0,10", "#fff", "#333", ''); pushToConstruc(construc, "M 5,-10 L 5,10", "#fff", "#333", ''); pushToConstruc(construc, "M 10,-10 L 10,10", "#fff", "#333", ''); pushToConstruc(construc, "M 15,-10 L 15,10", "#fff", "#333", ''); construc.params.width = 40; construc.params.height = 20; construc.family = 'stick'; } } if (classObj === 'furniture') { construc.params.bindBox = true; construc.params.move = true; construc.params.resize = true; construc.params.rotate = true; } return construc; } function setBestEqPoint(bestEqPoint, distance, index, x, y, x1, y1, x2, y2, way) { bestEqPoint.distance = distance; bestEqPoint.node = index; bestEqPoint.x = x; bestEqPoint.y = y; bestEqPoint.x1 = x1; bestEqPoint.y1 = y1; bestEqPoint.x2 = x2; bestEqPoint.y2 = y2; bestEqPoint.way = way; } function pushToRibMaster(ribMaster, firstIndex, secondIndex, wallIndex, crossEdge, side, coords, distance) { ribMaster[firstIndex][secondIndex].push({ wallIndex: wallIndex, crossEdge: crossEdge, side: side, coords: coords, distance: distance }); } function pushToConstruc(construc, path, fill, stroke, strokeDashArray, opacity = 1) { construc.push({ 'path': path, 'fill': fill, 'stroke': stroke, 'strokeDashArray': strokeDashArray, 'opacity': opacity }); } // Export button event handler document.getElementById('export_mode').addEventListener('click', function() { // Generate filename with current date const now = new Date(); const dateStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0'); const filename = 'floorplan_' + dateStr; // Call the export function if (exportFloorplanJSON(filename, true)) { $('#boxinfo').html('Floorplan exported successfully!'); } else { $('#boxinfo').html('Export failed. Please try again.'); } }); // Blender export button event handler document.getElementById('export_blender_mode').addEventListener('click', function() { // Generate filename with current date const now = new Date(); const dateStr = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0') + '-' + String(now.getDate()).padStart(2, '0'); const filename = 'floorplan_blender_' + dateStr; // Call the Blender export function if (exportForBlender(filename, 2.8, 0.08)) { $('#boxinfo').html('Floorplan exported for Blender successfully!'); } else { $('#boxinfo').html('Blender export failed. Please try again.'); } }); // Import button event handler document.getElementById('import_mode').addEventListener('click', function() { // Show confirmation dialog before importing (will clear current work) if (WALLS.length > 0 || OBJDATA.length > 0 || ROOM.length > 0) { if (!confirm('Importing will replace your current floorplan. Are you sure you want to continue?')) { $('#boxinfo').html('Import cancelled'); return; } } // Trigger the file selection dialog triggerImportDialog(); }); // Import from AI button event handler document.getElementById('import_ai_mode').addEventListener('click', function () { // Show confirmation dialog before importing (will clear current work) if (typeof WALLS !== 'undefined' && (WALLS.length > 0 || (typeof OBJDATA !== 'undefined' && OBJDATA.length > 0) || (typeof ROOM !== 'undefined' && ROOM.length > 0))) { if (!confirm('Importing will replace your current floorplan. Are you sure you want to continue?')) { if (typeof $('#boxinfo') !== 'undefined') $('#boxinfo').html('Import cancelled'); return; } } // Trigger the AI import dialog if (typeof triggerAIImportDialog === 'function') { triggerAIImportDialog(); } else { console.error('triggerAIImportDialog is not available'); if (typeof $('#boxinfo') !== 'undefined') $('#boxinfo').html('AI import is not available'); } }); // Import image button event handler document.getElementById('import_image_mode').addEventListener('click', function() { // Trigger the image import dialog triggerImageImportDialog(); }); // Background image opacity slider event handler (guard against missing element) (function(){ const opacitySlider = document.getElementById('backgroundImageOpacitySlider'); if (opacitySlider) { opacitySlider.addEventListener('input', function() { const opacityValue = this.value; if (typeof setBackgroundImageOpacity === 'function') setBackgroundImageOpacity(opacityValue); const label = document.getElementById('backgroundImageOpacityVal'); if (label) label.textContent = opacityValue; }); } })(); // Background image remove button event handler (guard against missing element) (function(){ const removeBtn = document.getElementById('backgroundImageRemove'); if (removeBtn) { removeBtn.addEventListener('click', function() { if (confirm('Are you sure you want to remove the background image?')) { if (typeof removeBackgroundImage === 'function') removeBackgroundImage(); if (typeof hideBackgroundImageTools === 'function') hideBackgroundImageTools(); if (typeof $ !== 'undefined') $('#boxinfo').html('Background image removed'); } }); } })(); // Combined Import (JSON + Image) modal handlers (function(){ const jsonInput = document.getElementById('combined_json_input'); const imageInput = document.getElementById('combined_image_input'); const importBtn = document.getElementById('combined_import_btn'); const jsonName = document.getElementById('combined_json_name'); const imageName = document.getElementById('combined_image_name'); const errMsg = document.getElementById('combined_error_msg'); const okMsg = document.getElementById('combined_success_msg'); if (!(jsonInput && imageInput && importBtn)) return; // Modal not present function resetMessages() { if (errMsg) errMsg.textContent = ''; if (okMsg) okMsg.textContent = ''; } function validateEnable() { const jf = jsonInput.files && jsonInput.files[0]; const imf = imageInput.files && imageInput.files[0]; let ok = true; resetMessages(); if (jf) { const validJson = /\.json$/i.test(jf.name); if (!validJson) { ok = false; if (errMsg) errMsg.textContent = 'Selected JSON file is not a .json'; } } else { ok = false; } // Image is optional; if provided, validate type if (imf) { const validImg = /\.(png|jpe?g)$/i.test(imf.name); if (!validImg) { ok = false; if (errMsg) errMsg.textContent = 'Selected image must be PNG or JPG'; } } importBtn.disabled = !ok; } jsonInput.addEventListener('change', function(){ if (jsonName) jsonName.textContent = this.files[0] ? this.files[0].name : ''; validateEnable(); }); imageInput.addEventListener('change', function(){ if (imageName) imageName.textContent = this.files[0] ? this.files[0].name : ''; validateEnable(); }); importBtn.addEventListener('click', async function(){ resetMessages(); // Confirm replacing current work if any try { if ((typeof WALLS !== 'undefined' && WALLS.length) || (typeof OBJDATA !== 'undefined' && OBJDATA.length) || (typeof ROOM !== 'undefined' && ROOM.length)) { if (!confirm('Importing will replace your current floorplan. Continue?')) { if (typeof $ !== 'undefined') $('#boxinfo').html('Import cancelled'); return; } } } catch(e) { /* ignore */ } const jf = jsonInput.files[0]; const imf = imageInput.files[0]; importBtn.disabled = true; importBtn.textContent = 'Importing...'; try { // Use AI importer exclusively per request const jsonOk = await (typeof importAIFloorplanJSON === 'function' ? importAIFloorplanJSON(jf) : Promise.resolve(false)); if (!jsonOk) { if (errMsg) errMsg.textContent = 'Failed to import floorplan JSON using AI format.'; importBtn.textContent = 'Import'; validateEnable(); return; } // Image is optional: only try importing if provided let imgOk = true; if (imf) { imgOk = await (typeof importBackgroundImage === 'function' ? importBackgroundImage(imf) : Promise.resolve(false)); if (!imgOk) { if (errMsg) errMsg.textContent = 'Floorplan JSON loaded, but background image import failed.'; importBtn.textContent = 'Import'; validateEnable(); return; } } if (okMsg) okMsg.textContent = imf ? 'Imported successfully!' : 'Floorplan JSON imported successfully.'; if (typeof $ !== 'undefined') $('#boxinfo').html(imf ? 'Floorplan and image imported successfully' : 'Floorplan JSON imported successfully'); // Close modal after short delay and clear inputs setTimeout(function(){ const modalEl = document.getElementById('combinedImportModal'); if (modalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) { const instance = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl); instance.hide(); } jsonInput.value = ''; imageInput.value = ''; if (jsonName) jsonName.textContent = ''; if (imageName) imageName.textContent = ''; importBtn.textContent = 'Import'; validateEnable(); }, 400); } catch (e) { console.error('Combined import error:', e); if (errMsg) errMsg.textContent = 'Unexpected error during import.'; if (typeof $ !== 'undefined') $('#boxinfo').html('Import failed'); importBtn.textContent = 'Import'; validateEnable(); } }); })();