document.querySelector('#lin').addEventListener("mouseup", _MOUSEUP);
document.querySelector('#lin').addEventListener("mousemove", throttle(function (event) { _MOUSEMOVE(event); }, 30));
document.querySelector('#lin').addEventListener("mousedown", _MOUSEDOWN, true);
$(document).on('click', '#lin', function (event) {
event.preventDefault();
});
document.querySelector('#panel').addEventListener('mousemove', function (event) {
if ((mode == 'line_mode' || mode == 'partition_mode') && action == 1) {
action = 0;
if (typeof (binder) != 'undefined') {
binder.remove();
delete binder;
}
$('#linetemp').remove();
$('#line_construc').remove();
lengthTemp.remove();
delete lengthTemp;
}
});
window.addEventListener('resize', function (event) {
width_viewbox = $('#lin').width();
height_viewbox = $('#lin').height();
document.querySelector('#lin').setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox)
});
// *****************************************************************************************************
// ****************************** KEYPRESS on KEYBOARD *********************************
// *****************************************************************************************************
document.addEventListener("keydown", function (event) {
if (mode != "text_mode") {
if (event.keyCode == '37') {
//LEFT
zoom_maker('zoomleft', 100, 30);
}
if (event.keyCode == '38') {
//UP
zoom_maker('zoomtop', 100, 30);
}
if (event.keyCode == '39') {
//RIGHT
zoom_maker('zoomright', 100, 30);
}
if (event.keyCode == '40') {
//DOWN
zoom_maker('zoombottom', 100, 30);
}
if (event.keyCode == '107') {
//+
zoom_maker('zoomin', 20, 50);
}
if (event.keyCode == '109') {
//-
zoom_maker('zoomout', 20, 50);
}
}
// else {
// if (action == 1) {
// binder.textContent = binder.textContent + event.key;
// console.log(field.value);
// }
// }
});
// *****************************************************************************************************
// ****************************** MOUSE MOVE *******************************************
// *****************************************************************************************************
function _MOUSEMOVE(event) {
event.preventDefault();
$('.sub').hide(100);
// Cleanup debug markers so they don't linger when moving away from key points
$('#boxDebug').empty();
// In floorplan mode, do not show hover highlights over walls/nodes (binder visuals)
if (window.__floorplanMode && mode === 'select_mode') {
try {
if (typeof (binder) !== 'undefined') {
if (binder.remove) binder.remove();
else if (binder.graph && binder.graph.remove) binder.graph.remove();
delete binder;
}
} catch (_) { /* no-op */ }
// Also ensure the binder container is clear
try { $('#boxbind').empty(); } catch (_) { /* no-op */ }
// Stop processing to avoid recreating highlights
return;
}
//**************************************************************************
//******************** TEXTE MODE **************************************
//**************************************************************************
if (mode == 'text_mode') {
snap = calcul_snap(event, grid_snap);
if (action == 0) cursor('text');
else {
cursor('none');
}
}
//**************************************************************************
//************** OBJECT MODE **************************************
//**************************************************************************
if (mode == 'object_mode') {
snap = calcul_snap(event, grid_snap);
if (typeof (binder) == 'undefined') {
$('#object_list').hide(200);
if (modeOption == 'simpleStair') binder = new editor.obj2D("free", "stair", "simpleStair", snap, 0, 0, 0, "normal", 0, 15);
else {
var typeObj = modeOption;
binder = new editor.obj2D("free", "energy", typeObj, snap, 0, 0, 0, "normal", 0);
}
$('#boxbind').append(binder.graph);
}
else {
if ((binder.family != 'stick' && binder.family != 'collision') || WALLS.length == 0) {
binder.x = snap.x;
binder.y = snap.y;
binder.oldX = binder.x;
binder.oldY = binder.y;
binder.update();
}
if (binder.family == 'collision') {
var found = false;
if (editor.rayCastingWalls({ x: binder.bbox.left, y: binder.bbox.top })) found = true;
if (!found && editor.rayCastingWalls({ x: binder.bbox.left, y: binder.bbox.bottom })) found = true;
if (!found && editor.rayCastingWalls({ x: binder.bbox.right, y: binder.bbox.top })) found = true;
if (!found && editor.rayCastingWalls({ x: binder.bbox.right, y: binder.bbox.bottom })) found = true;
if (!found) {
binder.x = snap.x;
binder.y = snap.y;
binder.oldX = binder.x;
binder.oldY = binder.y;
binder.update();
}
else {
binder.x = binder.oldX;
binder.y = binder.oldY;
binder.update();
}
}
if (binder.family == 'stick') {
pos = editor.stickOnWall(snap);
binder.oldX = pos.x;
binder.oldY = pos.y;
var angleWall = qSVG.angleDeg(pos.wall.start.x, pos.wall.start.y, pos.wall.end.x, pos.wall.end.y);
var v1 = qSVG.vectorXY({ x: pos.wall.start.x, y: pos.wall.start.y }, { x: pos.wall.end.x, y: pos.wall.end.y });
var v2 = qSVG.vectorXY({ x: pos.wall.end.x, y: pos.wall.end.y }, snap);
binder.x = pos.x - Math.sin(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
binder.y = pos.y - Math.cos(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
var newAngle = qSVG.vectorDeter(v1, v2);
if (Math.sign(newAngle) == 1) {
angleWall += 180;
binder.x = pos.x + Math.sin(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
binder.y = pos.y + Math.cos(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
}
binder.angle = angleWall;
binder.update();
}
}
}
//**************************************************************************
//************** DISTANCE MODE **************************************
//**************************************************************************
if (mode == 'distance_mode') {
snap = calcul_snap(event, grid_snap);
if (typeof (binder) == 'undefined') {
cross = qSVG.create("boxbind", "path", {
d: "M-3000,0 L3000,0 M0,-3000 L0,3000",
"stroke-width": 0.5,
"stroke-opacity": "0.8",
stroke: "#e2b653",
fill: "#e2b653"
});
binder = new editor.obj2D("free", "measure", "", { x: 0, y: 0 }, 0, 0, 0, "normal", 0, "");
labelMeasure = qSVG.create("none", "text", {
x: 0,
y: - 10,
'font-size': '1.2em',
stroke: "#ffffff",
"stroke-width": "0.4px",
'font-family': 'roboto',
'text-anchor': 'middle',
fill: "#3672d9"
});
binder.graph.append(labelMeasure);
$('#boxbind').append(binder.graph);
}
else {
x = snap.x;
y = snap.y;
cross.attr({
"transform": "translate(" + (snap.x) + "," + (snap.y) + ")"
});
if (action == 1) {
var startText = qSVG.middle(pox, poy, x, y);
var angleText = qSVG.angle(pox, poy, x, y);
var valueText = qSVG.measure({
x: pox,
y: poy
}, {
x: x,
y: y
});
binder.size = valueText;
binder.x = startText.x;
binder.y = startText.y;
binder.angle = angleText.deg;
valueText = (valueText / meter).toFixed(2) + ' m';
//labelMeasure.context.textContent = valueText;
labelMeasure[0].textContent = valueText;
binder.update();
}
}
}
//**************************************************************************
//************** ROOM MODE *****************************************
//**************************************************************************
if (mode == 'room_mode') {
snap = calcul_snap(event, grid_snap);
var roomTarget;
if (roomTarget = editor.rayCastingRoom(snap)) {
if (typeof (binder) != 'undefined') {
binder.remove();
delete binder;
}
var pathSurface = roomTarget.coords;
var pathCreate = "M" + pathSurface[0].x + "," + pathSurface[0].y;
for (var p = 1; p < pathSurface.length - 1; p++) {
pathCreate = pathCreate + " " + "L" + pathSurface[p].x + "," + pathSurface[p].y;
}
pathCreate = pathCreate + "Z";
if (roomTarget.inside.length > 0) {
for (var ins = 0; ins < roomTarget.inside.length; ins++) {
pathCreate = pathCreate + " M" + Rooms.polygons[roomTarget.inside[ins]].coords[Rooms.polygons[roomTarget.inside[ins]].coords.length - 1].x + "," + Rooms.polygons[roomTarget.inside[ins]].coords[Rooms.polygons[roomTarget.inside[ins]].coords.length - 1].y;
for (var free = Rooms.polygons[roomTarget.inside[ins]].coords.length - 2; free > -1; free--) {
pathCreate = pathCreate + " L" + Rooms.polygons[roomTarget.inside[ins]].coords[free].x + "," + Rooms.polygons[roomTarget.inside[ins]].coords[free].y;
}
}
}
binder = qSVG.create('boxbind', 'path', {
id: 'roomSelected',
d: pathCreate,
fill: '#c9c14c',
'fill-opacity': 0.5,
stroke: '#c9c14c',
'fill-rule': 'evenodd',
'stroke-width': 3
});
binder.type = 'room';
binder.area = roomTarget.area;
binder.id = ROOM.indexOf(roomTarget);
}
else {
if (typeof (binder) != 'undefined') {
binder.remove();
delete binder;
}
}
}
//**************************************************************************
//************** DOOR/WINDOW MODE *********************************
//**************************************************************************
if (mode == 'door_mode') {
snap = calcul_snap(event, grid_snap);
if (wallSelect = editor.nearWall(snap)) {
var wall = wallSelect.wall;
if (wall.type != 'separate') {
if (typeof (binder) == 'undefined') {
// family, classe, type, pos, angle, angleSign, size, hinge, thick
binder = new editor.obj2D("inWall", "doorWindow", modeOption, wallSelect, 0, 0, 60, "normal", wall.thick);
var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
var newAngle = qSVG.vectorDeter(v1, v2);
if (Math.sign(newAngle) == 1) {
angleWall += 180;
binder.angleSign = 1;
}
var startCoords = qSVG.middle(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
binder.x = startCoords.x;
binder.y = startCoords.y;
binder.angle = angleWall;
binder.update();
$('#boxbind').append(binder.graph);
}
else {
var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
var newAngle = qSVG.vectorDeter(v1, v2);
binder.angleSign = 0;
if (Math.sign(newAngle) == 1) {
binder.angleSign = 1;
angleWall += 180;
}
var limits = limitObj(wall.equations.base, binder.size, wallSelect);
if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y) && qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
binder.x = wallSelect.x;
binder.y = wallSelect.y;
binder.angle = angleWall;
binder.thick = wall.thick;
binder.limit = limits;
binder.update();
}
if ((wallSelect.x == wall.start.x && wallSelect.y == wall.start.y) || (wallSelect.x == wall.end.x && wallSelect.y == wall.end.y)) {
if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y)) {
binder.x = limits[0].x;
binder.y = limits[0].y;
}
if (qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
binder.x = limits[1].x;
binder.y = limits[1].y;
}
binder.limit = limits;
binder.angle = angleWall;
binder.thick = wall.thick;
binder.update();
}
}
}
}
else {
if (typeof (binder) != 'undefined') {
binder.graph.remove();
delete binder;
}
}
} // END DOOR MODE
//**************************************************************************
//************** NODE MODE *****************************************
//**************************************************************************
if (mode == 'node_mode') {
snap = calcul_snap(event, grid_snap);
if (typeof (binder) == 'undefined') {
if (addNode = editor.nearWall(snap, 30)) {
var x2 = addNode.wall.end.x;
var y2 = addNode.wall.end.y;
var x1 = addNode.wall.start.x;
var y1 = addNode.wall.start.y;
angleWall = qSVG.angle(x1, y1, x2, y2);
binder = qSVG.create('boxbind', 'path', {
id: "circlebinder",
d: "M-20,-10 L-13,0 L-20,10 Z M-13,0 L13,0 M13,0 L20,-10 L20,10 Z",
stroke: "#5cba79",
fill: "#5cba79",
"stroke-width": "1.5px"
});
binder.attr({
"transform": "translate(" + (addNode.x) + "," + (addNode.y) + ") rotate(" + (angleWall.deg + 90) + ",0,0)"
});
binder.data = addNode;
binder.x1 = x1;
binder.x2 = x2;
binder.y1 = y1;
binder.y2 = y2;
}
} else {
if (addNode = editor.nearWall(snap, 30)) {
if (addNode) {
var x2 = addNode.wall.end.x;
var y2 = addNode.wall.end.y;
var x1 = addNode.wall.start.x;
var y1 = addNode.wall.start.y;
angleWall = qSVG.angle(x1, y1, x2, y2);
binder.attr({
"transform": "translate(" + (addNode.x) + "," + (addNode.y) + ") rotate(" + (angleWall.deg + 90) + ",0,0)"
});
binder.data = addNode;
}
else {
binder.remove();
delete binder;
}
} else {
binder.remove();
delete binder;
}
}
} // END NODE MODE
//********************************** SELECT MODE ***************************************************************
if (mode == 'select_mode' && drag == 'off') { // FIRST TEST ON SELECT MODE (and drag OFF) to detect MOUSEOVER DOOR
snap = calcul_snap(event, 'off');
var objTarget = false;
for (var i = 0; i < OBJDATA.length; i++) {
var objX1 = OBJDATA[i].bbox.left;
var objX2 = OBJDATA[i].bbox.right;
var objY1 = OBJDATA[i].bbox.top;
var objY2 = OBJDATA[i].bbox.bottom;
var realBboxCoords = OBJDATA[i].realBbox;
if (qSVG.rayCasting(snap, realBboxCoords)) {
objTarget = OBJDATA[i];
}
}
if (objTarget !== false) {
if (typeof (binder) != 'undefined' && (binder.type == 'segment')) {
binder.graph.remove();
delete binder;
cursor('default');
}
if (objTarget.params.bindBox) { // OBJ -> BOUNDINGBOX TOOL
if (typeof (binder) == 'undefined') {
binder = new editor.obj2D("free", "boundingBox", "", objTarget.bbox.origin, objTarget.angle, 0, objTarget.size, "normal", objTarget.thick, objTarget.realBbox);
binder.update();
binder.obj = objTarget;
binder.type = 'boundingBox';
binder.oldX = binder.x;
binder.oldY = binder.y;
$('#boxbind').append(binder.graph);
if (!objTarget.params.move) cursor('trash'); // LIKE MEASURE ON PLAN
if (objTarget.params.move) cursor('move');
}
}
else { // DOOR, WINDOW, APERTURE.. -- OBJ WITHOUT BINDBOX (params.bindBox = False) -- !!!!
if (typeof (binder) == 'undefined') {
var wallList = editor.rayCastingWall(objTarget);
if (wallList.length > 1) wallList = wallList[0];
inWallRib(wallList);
var thickObj = wallList.thick;
var sizeObj = objTarget.size;
binder = new editor.obj2D("inWall", "socle", "", objTarget, objTarget.angle, 0, sizeObj, "normal", thickObj);
binder.update();
binder.oldXY = { x: objTarget.x, y: objTarget.y }; // FOR OBJECT MENU
$('#boxbind').append(binder.graph);
}
else {
// Guard against cases where binder may not have a graph (e.g., binder is a simple SVG element from other modes)
var hasBinderGraph = (typeof binder.graph !== 'undefined') &&
(typeof binder.graph.get === 'function') &&
binder.graph.get(0) &&
binder.graph.get(0).firstChild;
if (hasBinderGraph && event.target == binder.graph.get(0).firstChild) {
cursor('move');
binder.graph.get(0).firstChild.setAttribute("class", "circle_css_2");
binder.type = "obj";
binder.obj = objTarget;
}
else {
cursor('default');
if (hasBinderGraph) {
binder.graph.get(0).firstChild.setAttribute("class", "circle_css_1");
}
binder.type = false;
}
}
}
}
else {
if (typeof (binder) != 'undefined') {
if (typeof (binder.graph) != 'undefined') binder.graph.remove();
if (binder.type == 'node') binder.remove();
delete binder;
cursor('default');
rib();
}
}
// BIND CIRCLE IF nearNode and GROUP ALL SAME XY SEG POINTS
if (wallNode = editor.nearWallNode(snap, 20)) {
if (typeof (binder) == 'undefined' || binder.type == 'segment') {
binder = qSVG.create('boxbind', 'circle', {
id: "circlebinder",
class: "circle_css_2",
cx: wallNode.x,
cy: wallNode.y,
r: Rcirclebinder
});
binder.data = wallNode;
binder.type = "node";
if ($('#linebinder').length) $('#linebinder').remove();
} else {
// REMAKE CIRCLE_CSS ON BINDER AND TAKE DATA SEG GROUP
// if (typeof(binder) != 'undefined') {
// binder.attr({
// class: "circle_css_2"
// });
// }
}
cursor('move');
} else {
if (typeof (binder) != "undefined" && binder.type == 'node') {
binder.remove();
delete binder;
hideAllSize();
cursor('default');
rib();
}
}
// BIND WALL WITH NEARPOINT function ---> WALL BINDER CREATION
if (wallBind = editor.rayCastingWalls(snap, WALLS)) {
if (wallBind.length > 1) wallBind = wallBind[wallBind.length - 1];
if (wallBind && typeof (binder) == 'undefined') {
var objWall = editor.objFromWall(wallBind);
if (objWall.length > 0) editor.inWallRib2(wallBind);
binder = {};
binder.wall = wallBind;
inWallRib(binder.wall);
var line = qSVG.create('none', 'line', {
x1: binder.wall.start.x, y1: binder.wall.start.y, x2: binder.wall.end.x, y2: binder.wall.end.y,
"stroke-width": 5,
stroke: "#5cba79"
});
var ball1 = qSVG.create('none', 'circle', {
class: "circle_css",
cx: binder.wall.start.x, cy: binder.wall.start.y,
r: Rcirclebinder / 1.8
});
var ball2 = qSVG.create('none', 'circle', {
class: "circle_css",
cx: binder.wall.end.x, cy: binder.wall.end.y,
r: Rcirclebinder / 1.8
});
binder.graph = qSVG.create('none', 'g');
binder.graph.append(line);
binder.graph.append(ball1);
binder.graph.append(ball2);
$('#boxbind').append(binder.graph);
binder.type = "segment";
cursor('pointer');
}
} else {
if (wallBind = editor.nearWall(snap, 6)) {
if (wallBind && typeof (binder) == 'undefined') {
wallBind = wallBind.wall;
var objWall = editor.objFromWall(wallBind);
if (objWall.length > 0) editor.inWallRib2(wallBind);
binder = {};
binder.wall = wallBind;
inWallRib(binder.wall);
var line = qSVG.create('none', 'line', {
x1: binder.wall.start.x, y1: binder.wall.start.y, x2: binder.wall.end.x, y2: binder.wall.end.y,
"stroke-width": 5,
stroke: "#5cba79"
});
var ball1 = qSVG.create('none', 'circle', {
class: "circle_css",
cx: binder.wall.start.x, cy: binder.wall.start.y,
r: Rcirclebinder / 1.8
});
var ball2 = qSVG.create('none', 'circle', {
class: "circle_css",
cx: binder.wall.end.x, cy: binder.wall.end.y,
r: Rcirclebinder / 1.8
});
binder.graph = qSVG.create('none', 'g');
binder.graph.append(line);
binder.graph.append(ball1);
binder.graph.append(ball2);
$('#boxbind').append(binder.graph);
binder.type = "segment";
cursor('pointer');
}
}
else {
if (typeof (binder) != "undefined" && binder.type == 'segment') {
binder.graph.remove();
delete binder;
hideAllSize();
cursor('default');
rib();
}
}
}
} // END mode == 'select_mode' && drag == 'off'
//**************************************************************************
//************** FURNITURE PLACEMENT MODE ***************************
//**************************************************************************
if (mode == 'furniture_placement_mode') {
snap = calcul_snap(event, grid_snap);
updateFurnitureCursor(snap.x, snap.y);
cursor('none');
}
// ------------------------------ LINE MODE ------------------------------------------------------
if ((mode == 'line_mode' || mode == 'partition_mode') && action == 0) {
snap = calcul_snap(event, 'off');
cursor('grab');
pox = snap.x;
poy = snap.y;
if (helpConstruc = intersection(snap, 25)) {
if (helpConstruc.distance < 10) {
pox = helpConstruc.x;
poy = helpConstruc.y;
cursor('grab');
} else {
cursor('crosshair');
}
}
if (wallNode = editor.nearWallNode(snap, 20)) {
pox = wallNode.x;
poy = wallNode.y;
cursor('grab');
if (typeof (binder) == 'undefined') {
binder = qSVG.create('boxbind', 'circle', {
id: "circlebinder",
class: "circle_css_2",
cx: wallNode.x,
cy: wallNode.y,
r: Rcirclebinder / 1.5
});
}
intersectionOff();
} else {
if (!helpConstruc) cursor('crosshair');
if (typeof (binder) != "undefined") {
if (binder.graph) binder.graph.remove();
else binder.remove();
delete binder;
}
}
}
// ******************************************************************************************************
// ************************** ACTION = 1 LINE MODE => WALL CREATE *********************
// ******************************************************************************************************
if (action == 1 && (mode == 'line_mode' || mode == 'partition_mode')) {
snap = calcul_snap(event, grid_snap);
x = snap.x;
y = snap.y;
starter = minMoveGrid(snap);
if (!$('#line_construc').length) {
if (wallNode = editor.nearWallNode(snap, 20)) {
pox = wallNode.x;
poy = wallNode.y;
wallStartConstruc = false;
if (wallNode.bestWall == WALLS.length - 1) {
cursor('validation');
}
else {
cursor('grab');
}
} else {
cursor('crosshair');
}
}
if (starter > grid) {
if (!$('#line_construc').length) {
var ws = 20;
if (mode == 'partition_mode') ws = 10;
lineconstruc = qSVG.create("boxbind", "line", {
id: "line_construc",
x1: pox,
y1: poy,
x2: x,
y2: y,
"stroke-width": ws,
"stroke-linecap": "butt",
"stroke-opacity": 0.7,
stroke: "#9fb2e2"
});
svgadd = qSVG.create("boxbind", "line", { // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+
id: "linetemp",
x1: pox,
y1: poy,
x2: x,
y2: y,
"stroke": "transparent",
"stroke-width": 0.5,
"stroke-opacity": "0.9"
});
} else { // THE LINES AND BINDER ARE CREATED
$('#linetemp').attr({
x2: x,
y2: y
});
if (helpConstrucEnd = intersection(snap, 10)) {
x = helpConstrucEnd.x;
y = helpConstrucEnd.y;
}
if (wallEndConstruc = editor.nearWall(snap, 12)) { // TO SNAP SEGMENT TO FINALIZE X2Y2
x = wallEndConstruc.x;
y = wallEndConstruc.y;
cursor('grab');
} else {
cursor('crosshair');
}
// nearNode helped to attach the end of the construc line
if (wallNode = editor.nearWallNode(snap, 20)) {
if (typeof (binder) == 'undefined') {
binder = qSVG.create('boxbind', 'circle', {
id: "circlebinder",
class: "circle_css_2",
cx: wallNode.x,
cy: wallNode.y,
r: Rcirclebinder / 1.5
});
}
$('#line_construc').attr({
x2: wallNode.x,
y2: wallNode.y
});
x = wallNode.x;
y = wallNode.y;
wallEndConstruc = true;
intersectionOff();
if (wallNode.bestWall == WALLS.length - 1 && document.getElementById("multi").checked) {
cursor('validation');
}
else {
cursor('grab');
}
} else {
if (typeof (binder) != "undefined") {
binder.remove();
delete binder;
}
if (wallEndConstruc === false) cursor('crosshair');
}
// LINETEMP AND LITLLE SNAPPING FOR HELP TO CONSTRUC ANGLE 0 90 45 *****************************************
var fltt = qSVG.angle(pox, poy, x, y);
var flt = Math.abs(fltt.deg);
var coeff = fltt.deg / flt; // -45 -> -1 45 -> 1
var phi = poy - (coeff * pox);
var Xdiag = (y - phi) / coeff;
if (typeof (binder) == 'undefined') {
// HELP FOR H LINE
var found = false;
if (flt < 15 && Math.abs(poy - y) < 25) {
y = poy;
found = true;
} // HELP FOR V LINE
if (flt > 75 && Math.abs(pox - x) < 25) {
x = pox;
found = true;
} // HELP FOR DIAG LINE
if (flt < 55 && flt > 35 && Math.abs(Xdiag - x) < 20) {
x = Xdiag;
found = true;
}
if (found) $('#line_construc').attr({ "stroke-opacity": 1 });
else $('#line_construc').attr({ "stroke-opacity": 0.7 });
}
$('#line_construc').attr({
x2: x,
y2: y
});
// SHOW WALL SIZE -------------------------------------------------------------------------
var startText = qSVG.middle(pox, poy, x, y);
var angleText = qSVG.angle(pox, poy, x, y);
var valueText = ((qSVG.measure({
x: pox,
y: poy
}, {
x: x,
y: y
})) / 60).toFixed(2);
if (typeof (lengthTemp) == 'undefined') {
lengthTemp = document.createElementNS('http://www.w3.org/2000/svg', 'text');
lengthTemp.setAttributeNS(null, 'x', startText.x);
lengthTemp.setAttributeNS(null, 'y', (startText.y) - 15);
lengthTemp.setAttributeNS(null, 'text-anchor', 'middle');
lengthTemp.setAttributeNS(null, 'stroke', 'none');
lengthTemp.setAttributeNS(null, 'stroke-width', '0.6px');
lengthTemp.setAttributeNS(null, 'fill', '#777777');
lengthTemp.textContent = valueText + 'm';
$('#boxbind').append(lengthTemp);
}
if (typeof (lengthTemp) != 'undefined' && valueText > 0.1) {
lengthTemp.setAttributeNS(null, 'x', startText.x);
lengthTemp.setAttributeNS(null, 'y', (startText.y) - 15);
lengthTemp.setAttribute("transform", "rotate(" + angleText.deg + " " + startText.x + "," + startText.y + ")");
lengthTemp.textContent = valueText + ' m';
}
if (typeof (lengthTemp) != 'undefined' && valueText < 0.1) {
lengthTemp.textContent = "";
}
}
}
} // END LINE MODE DETECT && ACTION = 1
//ONMOVE
// **************************************************************************************************
// ____ ___ _ _ ____ _____ ____
// | __ )_ _| \ | | _ \| ____| _ \
// | _ \| || \| | | | | _| | |_) |
// | |_) | || |\ | |_| | |___| _ <
// |____/___|_| \_|____/|_____|_| \_\
//
// **************************************************************************************************
if (mode == 'bind_mode') {
snap = calcul_snap(event, grid_snap);
if (binder.type == 'node') {
var coords = snap;
var magnetic = false;
for (var k in wallListRun) {
if (isObjectsEquals(wallListRun[k].end, binder.data)) {
if (Math.abs(wallListRun[k].start.x - snap.x) < 20) { coords.x = wallListRun[k].start.x; magnetic = "H"; }
if (Math.abs(wallListRun[k].start.y - snap.y) < 20) { coords.y = wallListRun[k].start.y; magnetic = "V"; }
}
if (isObjectsEquals(wallListRun[k].start, binder.data)) {
if (Math.abs(wallListRun[k].end.x - snap.x) < 20) { coords.x = wallListRun[k].end.x; magnetic = "H"; }
if (Math.abs(wallListRun[k].end.y - snap.y) < 20) { coords.y = wallListRun[k].end.y; magnetic = "V"; }
}
}
if (nodeMove = editor.nearWallNode(snap, 15, wallListRun)) {
coords.x = nodeMove.x;
coords.y = nodeMove.y;
$('#circlebinder').attr({ "class": "circleGum", cx: coords.x, cy: coords.y });
cursor('grab');
} else {
if (magnetic != false) {
if (magnetic == "H") snap.x = coords.x;
else snap.y = coords.y;
}
if (helpConstruc = intersection(snap, 10, wallListRun)) {
coords.x = helpConstruc.x;
coords.y = helpConstruc.y;
snap.x = helpConstruc.x;
snap.y = helpConstruc.y;
if (magnetic != false) {
if (magnetic == "H") snap.x = coords.x;
else snap.y = coords.y;
}
cursor('grab');
} else {
cursor('move');
}
binder.remove()
//$('#circlebinder').attr({"class": "circle_css", cx: coords.x, cy: coords.y});
}
for (var k in wallListRun) {
if (isObjectsEquals(wallListRun[k].start, binder.data)) {
wallListRun[k].start.x = coords.x;
wallListRun[k].start.y = coords.y;
}
if (isObjectsEquals(wallListRun[k].end, binder.data)) {
wallListRun[k].end.x = coords.x;
wallListRun[k].end.y = coords.y;
}
}
binder.data = coords;
editor.wallsComputing(WALLS, false); // UPDATE FALSE
for (var k in wallListObj) {
var wall = wallListObj[k].wall;
var objTarget = wallListObj[k].obj;
var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
var limits = limitObj(wall.equations.base, 2 * wallListObj[k].distance, wallListObj[k].from); // COORDS OBJ AFTER ROTATION
var indexLimits = 0;
if (qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) indexLimits = 1;
// NEW COORDS OBJDATA[obj]
objTarget.x = limits[indexLimits].x;
objTarget.y = limits[indexLimits].y;
objTarget.angle = angleWall;
if (objTarget.angleSign == 1) objTarget.angle = angleWall + 180;
var limitBtwn = limitObj(wall.equations.base, objTarget.size, objTarget); // OBJ SIZE OK BTWN xy1/xy2
if (qSVG.btwn(limitBtwn[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limitBtwn[0].y, wall.start.y, wall.end.y) && qSVG.btwn(limitBtwn[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limitBtwn[1].y, wall.start.y, wall.end.y)) {
objTarget.limit = limitBtwn;
objTarget.update();
}
else {
objTarget.graph.remove();
delete objTarget;
OBJDATA.splice(wall.indexObj, 1);
wallListObj.splice(k, 1);
}
}
// for (k in toClean)
$('#boxRoom').empty();
$('#boxSurface').empty();
Rooms = qSVG.polygonize(WALLS);
editor.roomMaker(Rooms);
}
// WALL MOVING ----BINDER TYPE SEGMENT-------- FUNCTION FOR H,V and Calculate Vectorial Translation
if (binder.type == 'segment' && action == 1) {
rib();
if (equation2.A == 'v') { equation2.B = snap.x; }
else if (equation2.A == 'h') { equation2.B = snap.y; }
else { equation2.B = snap.y - (snap.x * equation2.A); }
var intersection1 = qSVG.intersectionOfEquations(equation1, equation2, "obj");
var intersection2 = qSVG.intersectionOfEquations(equation2, equation3, "obj");
var intersection3 = qSVG.intersectionOfEquations(equation1, equation3, "obj");
if (binder.wall.parent != null) {
if (isObjectsEquals(binder.wall.parent.end, binder.wall.start)) binder.wall.parent.end = intersection1;
else if (isObjectsEquals(binder.wall.parent.start, binder.wall.start)) binder.wall.parent.start = intersection1;
else binder.wall.parent.end = intersection1;
}
if (binder.wall.child != null) {
if (isObjectsEquals(binder.wall.child.start, binder.wall.end)) binder.wall.child.start = intersection2;
else if (isObjectsEquals(binder.wall.child.end, binder.wall.end)) binder.wall.child.end = intersection2;
else binder.wall.child.start = intersection2;
}
binder.wall.start = intersection1;
binder.wall.end = intersection2;
binder.graph.remove()
// binder.graph[0].children[0].setAttribute("x1",intersection1.x);
// binder.graph[0].children[0].setAttribute("x2",intersection2.x);
// binder.graph[0].children[0].setAttribute("y1",intersection1.y);
// binder.graph[0].children[0].setAttribute("y2",intersection2.y);
// binder.graph[0].children[1].setAttribute("cx",intersection1.x);
// binder.graph[0].children[1].setAttribute("cy",intersection1.y);
// binder.graph[0].children[2].setAttribute("cx",intersection2.x);
// binder.graph[0].children[2].setAttribute("cy",intersection2.y);
// THE EQ FOLLOWED BY eq (PARENT EQ1 --- CHILD EQ3)
if (equation1.follow != undefined) {
if (!qSVG.rayCasting(intersection1, equation1.backUp.coords)) { // IF OUT OF WALL FOLLOWED
var distanceFromStart = qSVG.gap(equation1.backUp.start, intersection1);
var distanceFromEnd = qSVG.gap(equation1.backUp.end, intersection1);
if (distanceFromStart > distanceFromEnd) { // NEAR FROM End
equation1.follow.end = intersection1;
}
else {
equation1.follow.start = intersection1;
}
}
else {
equation1.follow.end = equation1.backUp.end;
equation1.follow.start = equation1.backUp.start;
}
}
if (equation3.follow != undefined) {
if (!qSVG.rayCasting(intersection2, equation3.backUp.coords)) { // IF OUT OF WALL FOLLOWED
var distanceFromStart = qSVG.gap(equation3.backUp.start, intersection2);
var distanceFromEnd = qSVG.gap(equation3.backUp.end, intersection2);
if (distanceFromStart > distanceFromEnd) { // NEAR FROM End
equation3.follow.end = intersection2;
}
else {
equation3.follow.start = intersection2;
}
}
else {
equation3.follow.end = equation3.backUp.end;
equation3.follow.start = equation3.backUp.start;
}
}
// EQ FOLLOWERS WALL MOVING
for (var i = 0; i < equationFollowers.length; i++) {
var intersectionFollowers = qSVG.intersectionOfEquations(equationFollowers[i].eq, equation2, "obj");
if (qSVG.btwn(intersectionFollowers.x, binder.wall.start.x, binder.wall.end.x, 'round') && qSVG.btwn(intersectionFollowers.y, binder.wall.start.y, binder.wall.end.y, 'round')) {
var size = qSVG.measure(equationFollowers[i].wall.start, equationFollowers[i].wall.end);
if (equationFollowers[i].type == "start") {
equationFollowers[i].wall.start = intersectionFollowers;
}
if (equationFollowers[i].type == "end") {
equationFollowers[i].wall.end = intersectionFollowers;
// Note: Wall deletion is now deferred until mouse release to prevent premature deletion
}
}
}
// WALL COMPUTING, BLOCK FAMILY OF BINDERWALL IF NULL (START OR END) !!!!!
editor.wallsComputing(WALLS, "move");
Rooms = qSVG.polygonize(WALLS);
// Re-append all door/window objects to ensure they stay in boxcarpentry after wall computing
for (var objIdx = 0; objIdx < OBJDATA.length; objIdx++) {
var obj = OBJDATA[objIdx];
if (obj && obj.graph && obj.family === 'inWall') {
obj.graph.remove();
$('#boxcarpentry').append(obj.graph);
}
}
// OBJDATA(s) FOLLOW 90° EDGE SELECTED
for (var rp = 0; rp < equationsObj.length; rp++) {
var objTarget = equationsObj[rp].obj;
var intersectionObj = qSVG.intersectionOfEquations(equationsObj[rp].eq, equation2);
// NEW COORDS OBJDATA[o]
objTarget.x = intersectionObj[0];
objTarget.y = intersectionObj[1];
var limits = limitObj(equation2, objTarget.size, objTarget);
if (qSVG.btwn(limits[0].x, binder.wall.start.x, binder.wall.end.x) && qSVG.btwn(limits[0].y, binder.wall.start.y, binder.wall.end.y) && qSVG.btwn(limits[1].x, binder.wall.start.x, binder.wall.end.x) && qSVG.btwn(limits[1].y, binder.wall.start.y, binder.wall.end.y)) {
objTarget.limit = limits;
objTarget.update();
// Re-append to ensure doors/windows stay on top after wall movement
if (objTarget.graph) {
objTarget.graph.remove();
$('#boxcarpentry').append(objTarget.graph);
}
}
}
// HANDLE ALL INWALL OBJECTS - INCLUDING DETACHED ONES
// First pass: handle objects currently attached to walls
for (var k in WALLS) {
var objWall = editor.objFromWall(WALLS[k]); // LIST OBJ ON EDGE
for (var ob in objWall) {
var objTarget = objWall[ob];
var eq = editor.createEquationFromWall(WALLS[k]);
var limits = limitObj(eq, objTarget.size, objTarget);
if (!qSVG.btwn(limits[0].x, WALLS[k].start.x, WALLS[k].end.x) || !qSVG.btwn(limits[0].y, WALLS[k].start.y, WALLS[k].end.y) || !qSVG.btwn(limits[1].x, WALLS[k].start.x, WALLS[k].end.x) || !qSVG.btwn(limits[1].y, WALLS[k].start.y, WALLS[k].end.y)) {
// Try to resize the door/window to fit the wall instead of deleting it
var wallLength = qSVG.measure(WALLS[k].start, WALLS[k].end);
var maxObjectSize = wallLength - 20; // Leave some margin
if (maxObjectSize > 20) { // Minimum viable size for door/window
objTarget.size = maxObjectSize;
var newLimits = limitObj(eq, objTarget.size, objTarget);
// Check if resized object fits
if (qSVG.btwn(newLimits[0].x, WALLS[k].start.x, WALLS[k].end.x) && qSVG.btwn(newLimits[0].y, WALLS[k].start.y, WALLS[k].end.y) && qSVG.btwn(newLimits[1].x, WALLS[k].start.x, WALLS[k].end.x) && qSVG.btwn(newLimits[1].y, WALLS[k].start.y, WALLS[k].end.y)) {
objTarget.limit = newLimits;
objTarget.update();
// Re-append to maintain proper layering
if (objTarget.graph) {
objTarget.graph.remove();
$('#boxcarpentry').append(objTarget.graph);
}
} else {
// If resizing doesn't work, delete the object
var indexObj = OBJDATA.indexOf(objTarget);
objTarget.graph.remove();
if (indexObj !== -1) {
OBJDATA.splice(indexObj, 1);
}
}
} else {
// Wall too small, delete the object
var indexObj = OBJDATA.indexOf(objTarget);
objTarget.graph.remove();
if (indexObj !== -1) {
OBJDATA.splice(indexObj, 1);
}
}
}
}
}
// Second pass: handle detached inWall objects (not found by objFromWall)
var objectsToRemove = [];
for (var objIdx = 0; objIdx < OBJDATA.length; objIdx++) {
var obj = OBJDATA[objIdx];
if (obj && obj.family === 'inWall') {
var isAttachedToWall = false;
var bestWall = null;
var bestDistance = Infinity;
var bestPosition = null;
// Check if object is attached to any wall
for (var wallIdx in WALLS) {
var wall = WALLS[wallIdx];
var eq = qSVG.createEquation(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
var nearPoint = qSVG.nearPointOnEquation(eq, obj);
if (nearPoint.distance < 0.01 && qSVG.btwn(obj.x, wall.start.x, wall.end.x) && qSVG.btwn(obj.y, wall.start.y, wall.end.y)) {
isAttachedToWall = true;
break;
}
// Track closest wall for potential re-attachment
if (nearPoint.distance < bestDistance && qSVG.btwn(nearPoint.x, wall.start.x, wall.end.x) && qSVG.btwn(nearPoint.y, wall.start.y, wall.end.y)) {
bestDistance = nearPoint.distance;
bestWall = wall;
bestPosition = { x: nearPoint.x, y: nearPoint.y };
}
}
// If object is detached, try to re-attach or remove it
if (!isAttachedToWall) {
if (bestWall && bestDistance < 50) { // Within 50px of a wall
var wallLength = qSVG.measure(bestWall.start, bestWall.end);
var maxObjectSize = wallLength - 20;
// Check if there's enough space on the wall
if (maxObjectSize >= obj.size || (maxObjectSize > 20 && obj.size > maxObjectSize)) {
// Re-attach to the closest wall
obj.x = bestPosition.x;
obj.y = bestPosition.y;
// Resize if necessary
if (obj.size > maxObjectSize && maxObjectSize > 20) {
obj.size = maxObjectSize;
}
// Update object position and limits
var eq = editor.createEquationFromWall(bestWall);
var limits = limitObj(eq, obj.size, obj);
obj.limit = limits;
obj.update();
// Re-append to maintain proper layering
if (obj.graph) {
obj.graph.remove();
$('#boxcarpentry').append(obj.graph);
}
} else {
// Wall too small, mark for removal
objectsToRemove.push(objIdx);
}
} else {
// No suitable wall found, mark for removal
objectsToRemove.push(objIdx);
}
}
}
}
// Remove objects that couldn't be re-attached (in reverse order to maintain indices)
for (var i = objectsToRemove.length - 1; i >= 0; i--) {
var objIndex = objectsToRemove[i];
if (OBJDATA[objIndex] && OBJDATA[objIndex].graph) {
OBJDATA[objIndex].graph.remove();
}
OBJDATA.splice(objIndex, 1);
}
equationsObj = []; // REINIT eqObj -> MAYBE ONE OR PLUS OF OBJDATA REMOVED !!!!
var objWall = editor.objFromWall(binder.wall); // LIST OBJ ON EDGE
for (var ob = 0; ob < objWall.length; ob++) {
var objTarget = objWall[ob];
equationsObj.push({ obj: objTarget, wall: binder.wall, eq: qSVG.perpendicularEquation(equation2, objTarget.x, objTarget.y) });
}
$('#boxRoom').empty();
$('#boxSurface').empty();
editor.roomMaker(Rooms);
$('#lin').css('cursor', 'pointer');
}
// **********************************************************************
// ---------------------- BOUNDING BOX ------------------------------
// **********************************************************************
// binder.obj.params.move ---> FOR MEASURE DONT MOVE
if (binder.type == 'boundingBox' && action == 1 && binder.obj.params.move) {
binder.x = snap.x;
binder.y = snap.y;
binder.obj.x = snap.x;
binder.obj.y = snap.y;
binder.obj.update();
binder.update();
}
// **********************************************************************
// OBJ MOVING
// **********************************************************************
if (binder.type == 'obj' && action == 1) {
if (wallSelect = editor.nearWall(snap)) {
if (wallSelect.wall.type != 'separate') {
inWallRib(wallSelect.wall);
var objTarget = binder.obj;
var wall = wallSelect.wall;
var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
var newAngle = qSVG.vectorDeter(v1, v2);
binder.angleSign = 0;
objTarget.angleSign = 0;
if (Math.sign(newAngle) == 1) {
angleWall += 180;
binder.angleSign = 1;
objTarget.angleSign = 1;
}
var limits = limitObj(wall.equations.base, binder.size, wallSelect);
if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y) && qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
binder.x = wallSelect.x;
binder.y = wallSelect.y;
binder.angle = angleWall;
binder.thick = wall.thick;
objTarget.x = wallSelect.x;
objTarget.y = wallSelect.y;
objTarget.angle = angleWall;
objTarget.thick = wall.thick;
objTarget.limit = limits;
binder.update();
objTarget.update();
}
if ((wallSelect.x == wall.start.x && wallSelect.y == wall.start.y) || (wallSelect.x == wall.end.x && wallSelect.y == wall.end.y)) {
if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y)) {
binder.x = limits[0].x;
binder.y = limits[0].y;
objTarget.x = limits[0].x;
objTarget.y = limits[0].y;
objTarget.limit = limits;
}
if (qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
binder.x = limits[1].x;
binder.y = limits[1].y;
objTarget.x = limits[1].x;
objTarget.y = limits[1].y;
objTarget.limit = limits;
}
binder.angle = angleWall;
binder.thick = wall.thick;
objTarget.angle = angleWall;
objTarget.thick = wall.thick;
binder.update();
objTarget.update();
}
}
}
} // END OBJ MOVE
if (binder.type != 'obj' && binder.type != 'segment') rib();
}
// ENDBIND ACTION MOVE **************************************************************************
// ---DRAG VIEWBOX PANNING -------------------------------------------------------
// Check if background image tools are open - if so, disable global panning
const backgroundImageToolsOpen = document.getElementById('backgroundImageTools') &&
document.getElementById('backgroundImageTools').style.display !== 'none' &&
window.getComputedStyle(document.getElementById('backgroundImageTools')).display !== 'none';
// Permit panning when floorplan mode is active even if background image tools are open
const floorplanMode = !!window.__floorplanMode;
if ((mode == 'select_mode' || mode == 'furniture_placement_mode' || mode == 'furniture_mode') && drag == 'on' && (!backgroundImageToolsOpen || floorplanMode) && !window.draggingFurnitureItem && !window.draggingBackgroundImage) {
snap = calcul_snap(event, grid_snap);
$('#lin').css('cursor', 'move');
distX = (snap.xMouse - pox) * factor;
distY = (snap.yMouse - poy) * factor;
// pox = event.pageX;
// poy = event.pageY;
zoom_maker('zoomdrag', distX, distY);
}
} // END MOUSEMOVE
// *****************************************************************************************************
// *****************************************************************************************************
// *****************************************************************************************************
// ****************************** MOUSE DOWN *****************************************
// *****************************************************************************************************
// *****************************************************************************************************
// *****************************************************************************************************
function _MOUSEDOWN(event) {
event.preventDefault();
// In floorplan mode, block edits in select mode entirely
if (window.__floorplanMode && mode === 'select_mode') {
// Ensure no binder remains
try {
if (typeof (binder) !== 'undefined') {
if (binder.remove) binder.remove();
else if (binder.graph && binder.graph.remove) binder.graph.remove();
delete binder;
}
$('#boxbind').empty();
} catch (_) { /* no-op */ }
return;
}
// *******************************************************************
// ************************** DISTANCE MODE **********************
// *******************************************************************
if (mode == 'distance_mode') {
if (action == 0) {
action = 1;
snap = calcul_snap(event, grid_snap);
pox = snap.x;
poy = snap.y;
}
}
// *******************************************************************
// ************************* LINE/WALL MODE **********************
// *******************************************************************
if (mode == 'line_mode' || mode == 'partition_mode') {
if (action == 0) {
snap = calcul_snap(event, grid_snap);
pox = snap.x;
poy = snap.y;
if (wallStartConstruc = editor.nearWall(snap, 12)) { // TO SNAP SEGMENT TO FINALIZE X2Y2
pox = wallStartConstruc.x;
poy = wallStartConstruc.y;
}
}
else {
// FINALIZE LINE_++
construc = 1;
}
action = 1;
}
if (mode == 'edit_door_mode') { // ACTION 1 ACTIVATE EDITION OF THE DOOR
action = 1;
$('#lin').css('cursor', 'pointer');
}
// *******************************************************************
// ******************** FURNITURE PLACEMENT MODE ****************
// *******************************************************************
if (mode == 'furniture_placement_mode') {
snap = calcul_snap(event, grid_snap);
// Begin potential panning; defer placement to mouseup if it's a click (not a drag)
drag = 'on';
pox = snap.xMouse;
poy = snap.yMouse;
// Remember initial position for click detection
window._pendingFurniturePlacement = { x: snap.x, y: snap.y, xMouse: snap.xMouse, yMouse: snap.yMouse };
event.stopPropagation();
return;
}
// *******************************************************************
// ******************** FURNITURE MODE (PAN) ********************
// *******************************************************************
if (mode == 'furniture_mode') {
// Start panning on mousedown in furniture mode (unless background tools block)
const bgTools = document.getElementById('backgroundImageTools') &&
document.getElementById('backgroundImageTools').style.display !== 'none' &&
window.getComputedStyle(document.getElementById('backgroundImageTools')).display !== 'none';
// Avoid starting pan when clicking on a furniture item (drag is handled in furniture.js)
const overFurniture = event.target && (event.target.closest && event.target.closest('.furniture-item'));
if (!bgTools && !overFurniture) {
snap = calcul_snap(event, grid_snap);
drag = 'on';
pox = snap.xMouse;
poy = snap.yMouse;
}
}
// *******************************************************************
// ********************** SELECT MODE + BIND *********************
// *******************************************************************
if (mode == 'select_mode') {
if (typeof (binder) != 'undefined' && (binder.type == 'segment' || binder.type == 'node' || binder.type == 'obj' || binder.type == 'boundingBox')) {
// In floorplan mode: disable wall/object editing interactions
if (window.__floorplanMode) {
// Do not enter bind mode or start edits when aligning to image
if (typeof $ !== 'undefined') $('#boxinfo').html('Floorplan mode: editing disabled');
return; // prevent switching to bind_mode
}
mode = 'bind_mode';
if (binder.type == 'obj') {
action = 1;
}
if (binder.type == 'boundingBox') {
action = 1;
}
// INIT FOR HELP BINDER NODE MOVING H V (MOUSE DOWN)
if (binder.type == 'node') {
$('#boxScale').hide(100);
var node = binder.data;
pox = node.x;
poy = node.y;
var nodeControl = { x: pox, y: poy };
// DETERMINATE DISTANCE OF OPPOSED NODE ON EDGE(s) PARENT(s) OF THIS NODE !!!! NODE 1 -- NODE 2 SYSTE% :-(
wallListObj = []; // SUPER VAR -- WARNING
var objWall;
wallListRun = [];
for (var ee = WALLS.length - 1; ee > -1; ee--) { // SEARCH MOST YOUNG WALL COORDS IN NODE BINDER
if (isObjectsEquals(WALLS[ee].start, nodeControl) || isObjectsEquals(WALLS[ee].end, nodeControl)) {
wallListRun.push(WALLS[ee]);
break;
}
}
if (wallListRun[0].child != null) {
if (isObjectsEquals(wallListRun[0].child.start, nodeControl) || isObjectsEquals(wallListRun[0].child.end, nodeControl)) wallListRun.push(wallListRun[0].child);
}
if (wallListRun[0].parent != null) {
if (isObjectsEquals(wallListRun[0].parent.start, nodeControl) || isObjectsEquals(wallListRun[0].parent.end, nodeControl)) wallListRun.push(wallListRun[0].parent);
}
for (var k in wallListRun) {
if (isObjectsEquals(wallListRun[k].start, nodeControl) || isObjectsEquals(wallListRun[k].end, nodeControl)) {
var nodeTarget = wallListRun[k].start;
if (isObjectsEquals(wallListRun[k].start, nodeControl)) {
nodeTarget = wallListRun[k].end;
}
objWall = editor.objFromWall(wallListRun[k]); // LIST OBJ ON EDGE -- NOT INDEX !!!
wall = wallListRun[k];
for (var ob = 0; ob < objWall.length; ob++) {
var objTarget = objWall[ob];
var distance = qSVG.measure(objTarget, nodeTarget);
wallListObj.push({ wall: wall, from: nodeTarget, distance: distance, obj: objTarget, indexObj: ob });
}
}
}
magnetic = 0;
action = 1;
}
if (binder.type == 'segment') {
$('#boxScale').hide(100);
var wall = binder.wall;
binder.before = binder.wall.start;
equation2 = editor.createEquationFromWall(wall);
if (wall.parent != null) {
equation1 = editor.createEquationFromWall(wall.parent);
var angle12 = qSVG.angleBetweenEquations(equation1.A, equation2.A);
if (angle12 < 20 || angle12 > 160) {
var found = true;
for (var k in WALLS) {
if (qSVG.rayCasting(wall.start, WALLS[k].coords) && !isObjectsEquals(WALLS[k], wall.parent) && !isObjectsEquals(WALLS[k], wall)) {
if (wall.parent.parent != null && isObjectsEquals(wall, wall.parent.parent)) wall.parent.parent = null;
if (wall.parent.child != null && isObjectsEquals(wall, wall.parent.child)) wall.parent.child = null;
wall.parent = null;
found = false;
break;
}
}
if (found) {
var newWall;
if (isObjectsEquals(wall.parent.end, wall.start, "1")) {
newWall = new editor.wall(wall.parent.end, wall.start, "normal", wall.thick);
WALLS.push(newWall);
newWall.parent = wall.parent;
newWall.child = wall;
wall.parent.child = newWall;
wall.parent = newWall;
equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
}
else if (isObjectsEquals(wall.parent.start, wall.start, "2")) {
newWall = new editor.wall(wall.parent.start, wall.start, "normal", wall.thick);
WALLS.push(newWall);
newWall.parent = wall.parent;
newWall.child = wall;
wall.parent.parent = newWall;
wall.parent = newWall;
equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
}
// CREATE NEW WALL
}
}
}
if (wall.parent == null) {
var foundEq = false;
for (var k in WALLS) {
if (qSVG.rayCasting(wall.start, WALLS[k].coords) && !isObjectsEquals(WALLS[k].coords, wall.coords)) {
var angleFollow = qSVG.angleBetweenEquations(WALLS[k].equations.base.A, equation2.A);
if (angleFollow < 20 || angleFollow > 160) break;
equation1 = editor.createEquationFromWall(WALLS[k]);
equation1.follow = WALLS[k];
equation1.backUp = {
coords: WALLS[k].coords,
start: WALLS[k].start,
end: WALLS[k].end,
child: WALLS[k].child,
parent: WALLS[k].parent
};
foundEq = true;
break;
}
}
if (!foundEq) equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
}
if (wall.child != null) {
equation3 = editor.createEquationFromWall(wall.child);
var angle23 = qSVG.angleBetweenEquations(equation3.A, equation2.A);
if (angle23 < 20 || angle23 > 160) {
var found = true;
for (var k in WALLS) {
if (qSVG.rayCasting(wall.end, WALLS[k].coords) && !isObjectsEquals(WALLS[k], wall.child) && !isObjectsEquals(WALLS[k], wall)) {
if (wall.child.parent != null && isObjectsEquals(wall, wall.child.parent)) wall.child.parent = null;
if (wall.child.child != null && isObjectsEquals(wall, wall.child.child)) wall.child.child = null;
wall.child = null;
found = false;
break;
}
}
if (found) {
if (isObjectsEquals(wall.child.start, wall.end)) {
var newWall = new editor.wall(wall.end, wall.child.start, "new", wall.thick);
WALLS.push(newWall);
newWall.parent = wall;
newWall.child = wall.child;
wall.child.parent = newWall;
wall.child = newWall;
equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
}
else if (isObjectsEquals(wall.child.end, wall.end)) {
var newWall = new editor.wall(wall.end, wall.child.end, "normal", wall.thick);
WALLS.push(newWall);
newWall.parent = wall;
newWall.child = wall.child;
wall.child.child = newWall;
wall.child = newWall;
equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
}
// CREATE NEW WALL
}
}
}
if (wall.child == null) {
var foundEq = false;
for (var k in WALLS) {
if (qSVG.rayCasting(wall.end, WALLS[k].coords) && !isObjectsEquals(WALLS[k].coords, wall.coords, "4")) {
var angleFollow = qSVG.angleBetweenEquations(WALLS[k].equations.base.A, equation2.A);
if (angleFollow < 20 || angleFollow > 160) break;
equation3 = editor.createEquationFromWall(WALLS[k]);
equation3.follow = WALLS[k];
equation3.backUp = {
coords: WALLS[k].coords,
start: WALLS[k].start,
end: WALLS[k].end,
child: WALLS[k].child,
parent: WALLS[k].parent
};
foundEq = true;
break;
}
}
if (!foundEq) equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
}
equationFollowers = [];
for (var k in WALLS) {
if (WALLS[k].child == null && qSVG.rayCasting(WALLS[k].end, wall.coords) && !isObjectsEquals(wall, WALLS[k])) {
equationFollowers.push({
wall: WALLS[k],
eq: editor.createEquationFromWall(WALLS[k]),
type: "end"
});
}
if (WALLS[k].parent == null && qSVG.rayCasting(WALLS[k].start, wall.coords) && !isObjectsEquals(wall, WALLS[k])) {
equationFollowers.push({
wall: WALLS[k],
eq: editor.createEquationFromWall(WALLS[k]),
type: "start"
});
}
}
equationsObj = [];
var objWall = editor.objFromWall(wall); // LIST OBJ ON EDGE
for (var ob = 0; ob < objWall.length; ob++) {
var objTarget = objWall[ob];
equationsObj.push({ obj: objTarget, wall: wall, eq: qSVG.perpendicularEquation(equation2, objTarget.x, objTarget.y) });
}
action = 1;
}
}
else {
action = 0;
drag = 'on';
snap = calcul_snap(event, grid_snap);
pox = snap.xMouse;
poy = snap.yMouse;
}
}
}
//******************************************************************************************************
//******************* ***** ****** ************************************************************
//******************* ***** ****** **** ************************************************************
//******************* ***** ****** **** ************************************************************
//******************* ***** ****** ************************************************************
//******************* ****** ******************************************************************
//********************************** ******************************************************************
function _MOUSEUP(event) {
if (showRib) $('#boxScale').show(200);
drag = 'off';
cursor('default');
// Handle deferred placement from furniture_placement_mode
if (window._pendingFurniturePlacement) {
try {
const snapUp = calcul_snap(event, grid_snap);
const dx = Math.abs(snapUp.xMouse - window._pendingFurniturePlacement.xMouse);
const dy = Math.abs(snapUp.yMouse - window._pendingFurniturePlacement.yMouse);
const moved = Math.sqrt(dx*dx + dy*dy);
if (moved < 5 && mode === 'furniture_placement_mode') {
// Consider it a click: place furniture at original snapped coords
placeFurnitureItem(window._pendingFurniturePlacement.x, window._pendingFurniturePlacement.y);
}
} catch (_) { /* no-op */ }
window._pendingFurniturePlacement = null;
}
if (mode == 'select_mode') {
if (typeof (binder) != 'undefined') {
// Handle different binder types properly
if (binder.type == 'node' && typeof binder.remove === 'function') {
binder.remove();
} else if (binder.graph && typeof binder.graph.remove === 'function') {
binder.graph.remove();
} else if (typeof binder.remove === 'function') {
binder.remove();
}
delete binder;
save();
}
}
//**************************************************************************
//******************** TEXTE MODE **************************************
//**************************************************************************
if (mode == 'text_mode') {
if (action == 0) {
action = 1;
const textModal = new bootstrap.Modal($('#textToLayer'))
textModal.show();
mode == 'edit_text_mode';
}
}
//**************************************************************************
//************** OBJECT MODE **************************************
//**************************************************************************
if (mode == 'object_mode') {
OBJDATA.push(binder);
binder.graph.remove();
var targetBox = 'boxcarpentry';
if (OBJDATA[OBJDATA.length - 1].class == 'energy') targetBox = 'boxEnergy';
if (OBJDATA[OBJDATA.length - 1].class == 'furniture') targetBox = 'boxFurniture';
$('#' + targetBox).append(OBJDATA[OBJDATA.length - 1].graph);
delete binder;
$('#boxinfo').html('Object added');
fonc_button('select_mode');
save();
}
// *******************************************************************
// ************************** DISTANCE MODE **********************
// *******************************************************************
if (mode == 'distance_mode') {
if (action == 1) {
action = 0;
// MODIFY BBOX FOR BINDER ZONE (TXT)
var bbox = labelMeasure.get(0).getBoundingClientRect();
bbox.x = (bbox.x * factor) - (offset.left * factor) + originX_viewbox;
bbox.y = (bbox.y * factor) - (offset.top * factor) + originY_viewbox;
bbox.origin = { x: bbox.x + (bbox.width / 2), y: bbox.y + (bbox.height / 2) };
binder.bbox = bbox;
binder.realBbox = [
{ x: binder.bbox.x, y: binder.bbox.y }, { x: binder.bbox.x + binder.bbox.width, y: binder.bbox.y }, { x: binder.bbox.x + binder.bbox.width, y: binder.bbox.y + binder.bbox.height }, { x: binder.bbox.x, y: binder.bbox.y + binder.bbox.height }];
binder.size = binder.bbox.width;
binder.thick = binder.bbox.height;
binder.graph.append(labelMeasure);
OBJDATA.push(binder);
binder.graph.remove();
$('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph);
delete binder;
delete labelMeasure;
cross.remove();
delete cross;
$('#boxinfo').html('Measure added');
fonc_button('select_mode');
save();
}
}
// *******************************************************************
// ************************** ROOM MODE **************************
// *******************************************************************
if (mode == 'room_mode') {
if (typeof (binder) == "undefined") {
return false;
}
var area = binder.area / 3600;
binder.attr({ 'fill': 'none', 'stroke': '#ddf00a', 'stroke-width': 7 });
$('.size').html(area.toFixed(2) + " m²");
$('#roomIndex').val(binder.id);
if (ROOM[binder.id].surface != '') $('#roomSurface').val(ROOM[binder.id].surface);
else $('#roomSurface').val('');
document.querySelector('#seeArea').checked = ROOM[binder.id].showSurface;
document.querySelector('#roomBackground').value = ROOM[binder.id].color;
var roomName = ROOM[binder.id].name;
document.querySelector('#roomName').value = roomName;
if (ROOM[binder.id].name != '') {
document.querySelector('#roomLabel').innerHTML = roomName + ' ';
}
else {
document.querySelector('#roomLabel').innerHTML = 'None ';
}
var actionToDo = ROOM[binder.id].action;
document.querySelector('#' + actionToDo + 'Action').checked = true;
$('#panel').hide(100);
$('#roomTools').show('300')
$('#lin').css('cursor', 'default');
$('#boxinfo').html('Config. the room');
mode = 'edit_room_mode';
save();
}
// *******************************************************************
// ************************** NODE MODE **************************
// *******************************************************************
if (mode == 'node_mode') {
if (typeof (binder) != 'undefined') { // ALSO ON MOUSEUP WITH HAVE CIRCLEBINDER ON ADDPOINT
var newWall = new editor.wall({ x: binder.data.x, y: binder.data.y }, binder.data.wall.end, "normal", binder.data.wall.thick);
WALLS.push(newWall);
binder.data.wall.end = { x: binder.data.x, y: binder.data.y };
binder.remove();
delete binder;
editor.architect(WALLS);
save();
}
fonc_button('select_mode');
}
// ******************************************************************* ***** **** ******* ****** ****** *****
// ************************** OBJ MODE *************************** * * ******* ***** ****** ****** **
// ******************************************************************* ***** **** ****** ****** ****** ***
if (mode == 'door_mode') {
if (typeof (binder) == "undefined") {
$('#boxinfo').html('The plan currently contains no wall.');
fonc_button('select_mode');
return false;
}
OBJDATA.push(binder);
binder.graph.remove();
$('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph);
delete binder;
$('#boxinfo').html('Element added');
fonc_button('select_mode');
save();
}
// *******************************************************************
// ******************** LINE MODE MOUSE UP ***********************
// *******************************************************************
if (mode == 'line_mode' || mode == 'partition_mode') {
$('#linetemp').remove(); // DEL LINE HELP CONSTRUC 0 45 90
intersectionOff();
var sizeWall = qSVG.measure({ x: x, y: y }, { x: pox, y: poy });
sizeWall = sizeWall / meter;
if ($('#line_construc').length && sizeWall > 0.3) {
var sizeWall = wallSize;
if (mode == 'partition_mode') sizeWall = partitionSize;
var wall = new editor.wall({ x: pox, y: poy }, { x: x, y: y }, "normal", sizeWall);
WALLS.push(wall);
editor.architect(WALLS);
if (document.getElementById("multi").checked && !wallEndConstruc) {
cursor('validation');
action = 1;
}
else action = 0;
$('#boxinfo').html('Wall added Moy. ' + (qSVG.measure(
{ x: pox, y: poy }, { x: x, y: y }) / 60).toFixed(2) + ' m');
$('#line_construc').remove(); // DEL LINE CONSTRUC HELP TO VIEW NEW SEG PATH
lengthTemp.remove();
delete lengthTemp;
construc = 0;
if (wallEndConstruc) action = 0;
delete wallEndConstruc;
pox = x;
poy = y;
save();
}
else {
action = 0;
construc = 0;
$('#boxinfo').html('Select mode');
fonc_button('select_mode');
if (typeof (binder) != 'undefined') {
binder.remove();
delete binder;
}
snap = calcul_snap(event, grid_snap);
pox = snap.x;
poy = snap.y;
}
}
// **************************** END LINE MODE MOUSE UP **************************
//**************************************************************************************
//********************** BIND MODE MOUSE UP ************************************
//**************************************************************************************
if (mode == 'bind_mode') {
action = 0;
construc = 0; // CONSTRUC 0 TO FREE BINDER GROUP NODE WALL MOVING
if (typeof (binder) != 'undefined') {
fonc_button('select_mode');
if (binder.type == 'node') {
// Check for zero-length walls after node movement and clean them up
var wallsToDelete = [];
for (var k in WALLS) {
var wallSize = qSVG.measure(WALLS[k].start, WALLS[k].end);
if (wallSize < 1) {
wallsToDelete.push(WALLS[k]);
}
}
// Delete zero-length walls with proper cleanup
for (var d = 0; d < wallsToDelete.length; d++) {
var wallToDelete = wallsToDelete[d];
// Clean up parent/child references before deletion
for (var cleanK in WALLS) {
if (isObjectsEquals(WALLS[cleanK].child, wallToDelete)) WALLS[cleanK].child = null;
if (isObjectsEquals(WALLS[cleanK].parent, wallToDelete)) WALLS[cleanK].parent = null;
}
// Remove the wall's visual representation
if (wallToDelete.graph) wallToDelete.graph.remove();
// Remove from WALLS array
WALLS.splice(WALLS.indexOf(wallToDelete), 1);
}
// Clean up any lingering UI elements
$('#circlebinder').remove();
$('#boxbind').empty();
// Rebuild architecture if walls were deleted
if (wallsToDelete.length > 0) {
editor.architect(WALLS);
}
} // END BINDER NODE
if (binder.type == 'segment') {
var found = false;
if (binder.wall.start == binder.before) {
found = true;
}
if (found) {
$('#panel').hide(100);
var objWall = editor.objFromWall(wallBind);
$('#boxinfo').html('Modify a wall
This wall can\'t become a separation (contains doors or windows) !');
if (objWall.length > 0) $('#separate').hide();
else if (binder.wall.type == 'separate') {
$('#separate').hide();
$('#rangeThick').hide();
$('#recombine').show();
$('#cutWall').hide();
document.getElementById('titleWallTools').textContent = "Modify the separation";
}
else {
$('#cutWall').show();
$('#separate').show();
$('#rangeThick').show();
$('#recombine').hide();
document.getElementById('titleWallTools').textContent = "Modify the wall";
$('#boxinfo').html('Modify the wall');
}
$('#wallTools').show(200);
document.getElementById('wallWidth').setAttribute('min', 7);
document.getElementById('wallWidth').setAttribute('max', 50);
document.getElementById('wallWidthScale').textContent = "7-50";
document.getElementById("wallWidth").value = binder.wall.thick;
document.getElementById("wallWidthVal").textContent = binder.wall.thick;
mode = 'edit_wall_mode';
}
delete equation1;
delete equation2;
delete equation3;
delete intersectionFollowers;
}
if (binder.type == 'obj') {
var moveObj = Math.abs(binder.oldXY.x - binder.x) + Math.abs(binder.oldXY.y - binder.y);
if (moveObj < 1) {
$('#panel').hide(100);
$('#objTools').show('200')
$('#lin').css('cursor', 'default');
$('#boxinfo').html('Config. the door/window');
console.log('obj ??')
document.getElementById('doorWindowWidth').setAttribute('min', binder.obj.params.resizeLimit.width.min);
document.getElementById('doorWindowWidth').setAttribute('max', binder.obj.params.resizeLimit.width.max);
document.getElementById('doorWindowWidthScale').textContent = binder.obj.params.resizeLimit.width.min + "-" + binder.obj.params.resizeLimit.width.max;
document.getElementById("doorWindowWidth").value = binder.obj.size;
document.getElementById("doorWindowWidthVal").textContent = binder.obj.size;
mode = 'edit_door_mode';
}
else {
mode = "select_mode";
action = 0;
binder.graph.remove();
delete binder;
}
}
if (typeof (binder) != 'undefined' && binder.type == 'boundingBox') {
var moveObj = Math.abs(binder.oldX - binder.x) + Math.abs(binder.oldY - binder.y);
var objTarget = binder.obj;
if (!objTarget.params.move) {
// TO REMOVE MEASURE ON PLAN
objTarget.graph.remove();
OBJDATA.splice(OBJDATA.indexOf(objTarget), 1);
$('#boxinfo').html('Measure deleted !');
}
if (moveObj < 1 && objTarget.params.move) {
if (!objTarget.params.resize) $('#objBoundingBoxScale').hide();
else $('#objBoundingBoxScale').show();
if (!objTarget.params.rotate) $('#objBoundingBoxRotation').hide();
else $('#objBoundingBoxRotation').show();
$('#panel').hide(100);
console.log(objTarget.params.resizeLimit.width.min)
$('#objBoundingBox').show('200')
$('#lin').css('cursor', 'default');
$('#boxinfo').html('Modify the object');
console.log(objTarget)
document.getElementById('bboxWidth').setAttribute('min', objTarget.params.resizeLimit.width.min);
document.getElementById('bboxWidth').setAttribute('max', objTarget.params.resizeLimit.width.max);
document.getElementById('bboxWidthScale').textContent = objTarget.params.resizeLimit.width.min + "-" + objTarget.params.resizeLimit.height.max;
document.getElementById('bboxHeight').setAttribute('min', objTarget.params.resizeLimit.height.min);
document.getElementById('bboxHeight').setAttribute('max', objTarget.params.resizeLimit.height.max);
document.getElementById('bboxHeightScale').textContent = objTarget.params.resizeLimit.height.min + "-" + objTarget.params.resizeLimit.height.max;
$('#stepsCounter').hide();
if (objTarget.class == 'stair') {
document.getElementById("bboxStepsVal").textContent = objTarget.value;
$('#stepsCounter').show();
}
document.getElementById("bboxWidth").value = objTarget.width * 100;
document.getElementById("bboxWidthVal").textContent = objTarget.width * 100;
document.getElementById("bboxHeight").value = objTarget.height * 100;
document.getElementById("bboxHeightVal").textContent = objTarget.height * 100;
document.getElementById("bboxRotation").value = objTarget.angle;
document.getElementById("bboxRotationVal").textContent = objTarget.angle;
mode = 'edit_boundingBox_mode';
}
else {
mode = "select_mode";
action = 0;
binder.graph.remove();
delete binder;
}
}
if (mode == 'bind_mode') {
binder.remove();
delete binder;
}
} // END BIND IS DEFINED
save();
} // END BIND MODE
if (mode != 'edit_room_mode') {
editor.showScaleBox();
rib();
}
}