engine.js 79 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980
  1. document.querySelector('#lin').addEventListener("mouseup", _MOUSEUP);
  2. document.querySelector('#lin').addEventListener("mousemove", throttle(function (event) { _MOUSEMOVE(event); }, 30));
  3. document.querySelector('#lin').addEventListener("mousedown", _MOUSEDOWN, true);
  4. $(document).on('click', '#lin', function (event) {
  5. event.preventDefault();
  6. });
  7. document.querySelector('#panel').addEventListener('mousemove', function (event) {
  8. if ((mode == 'line_mode' || mode == 'partition_mode') && action == 1) {
  9. action = 0;
  10. if (typeof (binder) != 'undefined') {
  11. binder.remove();
  12. delete binder;
  13. }
  14. $('#linetemp').remove();
  15. $('#line_construc').remove();
  16. lengthTemp.remove();
  17. delete lengthTemp;
  18. }
  19. });
  20. window.addEventListener('resize', function (event) {
  21. width_viewbox = $('#lin').width();
  22. height_viewbox = $('#lin').height();
  23. document.querySelector('#lin').setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox)
  24. });
  25. // *****************************************************************************************************
  26. // ****************************** KEYPRESS on KEYBOARD *********************************
  27. // *****************************************************************************************************
  28. document.addEventListener("keydown", function (event) {
  29. if (mode != "text_mode") {
  30. if (event.keyCode == '37') {
  31. //LEFT
  32. zoom_maker('zoomleft', 100, 30);
  33. }
  34. if (event.keyCode == '38') {
  35. //UP
  36. zoom_maker('zoomtop', 100, 30);
  37. }
  38. if (event.keyCode == '39') {
  39. //RIGHT
  40. zoom_maker('zoomright', 100, 30);
  41. }
  42. if (event.keyCode == '40') {
  43. //DOWN
  44. zoom_maker('zoombottom', 100, 30);
  45. }
  46. if (event.keyCode == '107') {
  47. //+
  48. zoom_maker('zoomin', 20, 50);
  49. }
  50. if (event.keyCode == '109') {
  51. //-
  52. zoom_maker('zoomout', 20, 50);
  53. }
  54. }
  55. // else {
  56. // if (action == 1) {
  57. // binder.textContent = binder.textContent + event.key;
  58. // console.log(field.value);
  59. // }
  60. // }
  61. });
  62. // *****************************************************************************************************
  63. // ****************************** MOUSE MOVE *******************************************
  64. // *****************************************************************************************************
  65. function _MOUSEMOVE(event) {
  66. event.preventDefault();
  67. $('.sub').hide(100);
  68. // Cleanup debug markers so they don't linger when moving away from key points
  69. $('#boxDebug').empty();
  70. // In floorplan mode, do not show hover highlights over walls/nodes (binder visuals)
  71. if (window.__floorplanMode && mode === 'select_mode') {
  72. try {
  73. if (typeof (binder) !== 'undefined') {
  74. if (binder.remove) binder.remove();
  75. else if (binder.graph && binder.graph.remove) binder.graph.remove();
  76. delete binder;
  77. }
  78. } catch (_) { /* no-op */ }
  79. // Also ensure the binder container is clear
  80. try { $('#boxbind').empty(); } catch (_) { /* no-op */ }
  81. // Stop processing to avoid recreating highlights
  82. return;
  83. }
  84. //**************************************************************************
  85. //******************** TEXTE MODE **************************************
  86. //**************************************************************************
  87. if (mode == 'text_mode') {
  88. snap = calcul_snap(event, grid_snap);
  89. if (action == 0) cursor('text');
  90. else {
  91. cursor('none');
  92. }
  93. }
  94. //**************************************************************************
  95. //************** OBJECT MODE **************************************
  96. //**************************************************************************
  97. if (mode == 'object_mode') {
  98. snap = calcul_snap(event, grid_snap);
  99. if (typeof (binder) == 'undefined') {
  100. $('#object_list').hide(200);
  101. if (modeOption == 'simpleStair') binder = new editor.obj2D("free", "stair", "simpleStair", snap, 0, 0, 0, "normal", 0, 15);
  102. else {
  103. var typeObj = modeOption;
  104. binder = new editor.obj2D("free", "energy", typeObj, snap, 0, 0, 0, "normal", 0);
  105. }
  106. $('#boxbind').append(binder.graph);
  107. }
  108. else {
  109. if ((binder.family != 'stick' && binder.family != 'collision') || WALLS.length == 0) {
  110. binder.x = snap.x;
  111. binder.y = snap.y;
  112. binder.oldX = binder.x;
  113. binder.oldY = binder.y;
  114. binder.update();
  115. }
  116. if (binder.family == 'collision') {
  117. var found = false;
  118. if (editor.rayCastingWalls({ x: binder.bbox.left, y: binder.bbox.top })) found = true;
  119. if (!found && editor.rayCastingWalls({ x: binder.bbox.left, y: binder.bbox.bottom })) found = true;
  120. if (!found && editor.rayCastingWalls({ x: binder.bbox.right, y: binder.bbox.top })) found = true;
  121. if (!found && editor.rayCastingWalls({ x: binder.bbox.right, y: binder.bbox.bottom })) found = true;
  122. if (!found) {
  123. binder.x = snap.x;
  124. binder.y = snap.y;
  125. binder.oldX = binder.x;
  126. binder.oldY = binder.y;
  127. binder.update();
  128. }
  129. else {
  130. binder.x = binder.oldX;
  131. binder.y = binder.oldY;
  132. binder.update();
  133. }
  134. }
  135. if (binder.family == 'stick') {
  136. pos = editor.stickOnWall(snap);
  137. binder.oldX = pos.x;
  138. binder.oldY = pos.y;
  139. var angleWall = qSVG.angleDeg(pos.wall.start.x, pos.wall.start.y, pos.wall.end.x, pos.wall.end.y);
  140. var v1 = qSVG.vectorXY({ x: pos.wall.start.x, y: pos.wall.start.y }, { x: pos.wall.end.x, y: pos.wall.end.y });
  141. var v2 = qSVG.vectorXY({ x: pos.wall.end.x, y: pos.wall.end.y }, snap);
  142. binder.x = pos.x - Math.sin(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
  143. binder.y = pos.y - Math.cos(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
  144. var newAngle = qSVG.vectorDeter(v1, v2);
  145. if (Math.sign(newAngle) == 1) {
  146. angleWall += 180;
  147. binder.x = pos.x + Math.sin(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
  148. binder.y = pos.y + Math.cos(pos.wall.angle * (360 / 2 * Math.PI)) * binder.thick / 2;
  149. }
  150. binder.angle = angleWall;
  151. binder.update();
  152. }
  153. }
  154. }
  155. //**************************************************************************
  156. //************** DISTANCE MODE **************************************
  157. //**************************************************************************
  158. if (mode == 'distance_mode') {
  159. snap = calcul_snap(event, grid_snap);
  160. if (typeof (binder) == 'undefined') {
  161. cross = qSVG.create("boxbind", "path", {
  162. d: "M-3000,0 L3000,0 M0,-3000 L0,3000",
  163. "stroke-width": 0.5,
  164. "stroke-opacity": "0.8",
  165. stroke: "#e2b653",
  166. fill: "#e2b653"
  167. });
  168. binder = new editor.obj2D("free", "measure", "", { x: 0, y: 0 }, 0, 0, 0, "normal", 0, "");
  169. labelMeasure = qSVG.create("none", "text", {
  170. x: 0,
  171. y: - 10,
  172. 'font-size': '1.2em',
  173. stroke: "#ffffff",
  174. "stroke-width": "0.4px",
  175. 'font-family': 'roboto',
  176. 'text-anchor': 'middle',
  177. fill: "#3672d9"
  178. });
  179. binder.graph.append(labelMeasure);
  180. $('#boxbind').append(binder.graph);
  181. }
  182. else {
  183. x = snap.x;
  184. y = snap.y;
  185. cross.attr({
  186. "transform": "translate(" + (snap.x) + "," + (snap.y) + ")"
  187. });
  188. if (action == 1) {
  189. var startText = qSVG.middle(pox, poy, x, y);
  190. var angleText = qSVG.angle(pox, poy, x, y);
  191. var valueText = qSVG.measure({
  192. x: pox,
  193. y: poy
  194. }, {
  195. x: x,
  196. y: y
  197. });
  198. binder.size = valueText;
  199. binder.x = startText.x;
  200. binder.y = startText.y;
  201. binder.angle = angleText.deg;
  202. valueText = (valueText / meter).toFixed(2) + ' m';
  203. //labelMeasure.context.textContent = valueText;
  204. labelMeasure[0].textContent = valueText;
  205. binder.update();
  206. }
  207. }
  208. }
  209. //**************************************************************************
  210. //************** ROOM MODE *****************************************
  211. //**************************************************************************
  212. if (mode == 'room_mode') {
  213. snap = calcul_snap(event, grid_snap);
  214. var roomTarget;
  215. if (roomTarget = editor.rayCastingRoom(snap)) {
  216. if (typeof (binder) != 'undefined') {
  217. binder.remove();
  218. delete binder;
  219. }
  220. var pathSurface = roomTarget.coords;
  221. var pathCreate = "M" + pathSurface[0].x + "," + pathSurface[0].y;
  222. for (var p = 1; p < pathSurface.length - 1; p++) {
  223. pathCreate = pathCreate + " " + "L" + pathSurface[p].x + "," + pathSurface[p].y;
  224. }
  225. pathCreate = pathCreate + "Z";
  226. if (roomTarget.inside.length > 0) {
  227. for (var ins = 0; ins < roomTarget.inside.length; ins++) {
  228. 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;
  229. for (var free = Rooms.polygons[roomTarget.inside[ins]].coords.length - 2; free > -1; free--) {
  230. pathCreate = pathCreate + " L" + Rooms.polygons[roomTarget.inside[ins]].coords[free].x + "," + Rooms.polygons[roomTarget.inside[ins]].coords[free].y;
  231. }
  232. }
  233. }
  234. binder = qSVG.create('boxbind', 'path', {
  235. id: 'roomSelected',
  236. d: pathCreate,
  237. fill: '#c9c14c',
  238. 'fill-opacity': 0.5,
  239. stroke: '#c9c14c',
  240. 'fill-rule': 'evenodd',
  241. 'stroke-width': 3
  242. });
  243. binder.type = 'room';
  244. binder.area = roomTarget.area;
  245. binder.id = ROOM.indexOf(roomTarget);
  246. }
  247. else {
  248. if (typeof (binder) != 'undefined') {
  249. binder.remove();
  250. delete binder;
  251. }
  252. }
  253. }
  254. //**************************************************************************
  255. //************** DOOR/WINDOW MODE *********************************
  256. //**************************************************************************
  257. if (mode == 'door_mode') {
  258. snap = calcul_snap(event, grid_snap);
  259. if (wallSelect = editor.nearWall(snap)) {
  260. var wall = wallSelect.wall;
  261. if (wall.type != 'separate') {
  262. if (typeof (binder) == 'undefined') {
  263. // family, classe, type, pos, angle, angleSign, size, hinge, thick
  264. binder = new editor.obj2D("inWall", "doorWindow", modeOption, wallSelect, 0, 0, 60, "normal", wall.thick);
  265. var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  266. var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
  267. var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
  268. var newAngle = qSVG.vectorDeter(v1, v2);
  269. if (Math.sign(newAngle) == 1) {
  270. angleWall += 180;
  271. binder.angleSign = 1;
  272. }
  273. var startCoords = qSVG.middle(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  274. binder.x = startCoords.x;
  275. binder.y = startCoords.y;
  276. binder.angle = angleWall;
  277. binder.update();
  278. $('#boxbind').append(binder.graph);
  279. }
  280. else {
  281. var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  282. var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
  283. var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
  284. var newAngle = qSVG.vectorDeter(v1, v2);
  285. binder.angleSign = 0;
  286. if (Math.sign(newAngle) == 1) {
  287. binder.angleSign = 1;
  288. angleWall += 180;
  289. }
  290. var limits = limitObj(wall.equations.base, binder.size, wallSelect);
  291. 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)) {
  292. binder.x = wallSelect.x;
  293. binder.y = wallSelect.y;
  294. binder.angle = angleWall;
  295. binder.thick = wall.thick;
  296. binder.limit = limits;
  297. binder.update();
  298. }
  299. if ((wallSelect.x == wall.start.x && wallSelect.y == wall.start.y) || (wallSelect.x == wall.end.x && wallSelect.y == wall.end.y)) {
  300. if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y)) {
  301. binder.x = limits[0].x;
  302. binder.y = limits[0].y;
  303. }
  304. if (qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
  305. binder.x = limits[1].x;
  306. binder.y = limits[1].y;
  307. }
  308. binder.limit = limits;
  309. binder.angle = angleWall;
  310. binder.thick = wall.thick;
  311. binder.update();
  312. }
  313. }
  314. }
  315. }
  316. else {
  317. if (typeof (binder) != 'undefined') {
  318. binder.graph.remove();
  319. delete binder;
  320. }
  321. }
  322. } // END DOOR MODE
  323. //**************************************************************************
  324. //************** NODE MODE *****************************************
  325. //**************************************************************************
  326. if (mode == 'node_mode') {
  327. snap = calcul_snap(event, grid_snap);
  328. if (typeof (binder) == 'undefined') {
  329. if (addNode = editor.nearWall(snap, 30)) {
  330. var x2 = addNode.wall.end.x;
  331. var y2 = addNode.wall.end.y;
  332. var x1 = addNode.wall.start.x;
  333. var y1 = addNode.wall.start.y;
  334. angleWall = qSVG.angle(x1, y1, x2, y2);
  335. binder = qSVG.create('boxbind', 'path', {
  336. id: "circlebinder",
  337. d: "M-20,-10 L-13,0 L-20,10 Z M-13,0 L13,0 M13,0 L20,-10 L20,10 Z",
  338. stroke: "#5cba79",
  339. fill: "#5cba79",
  340. "stroke-width": "1.5px"
  341. });
  342. binder.attr({
  343. "transform": "translate(" + (addNode.x) + "," + (addNode.y) + ") rotate(" + (angleWall.deg + 90) + ",0,0)"
  344. });
  345. binder.data = addNode;
  346. binder.x1 = x1;
  347. binder.x2 = x2;
  348. binder.y1 = y1;
  349. binder.y2 = y2;
  350. }
  351. } else {
  352. if (addNode = editor.nearWall(snap, 30)) {
  353. if (addNode) {
  354. var x2 = addNode.wall.end.x;
  355. var y2 = addNode.wall.end.y;
  356. var x1 = addNode.wall.start.x;
  357. var y1 = addNode.wall.start.y;
  358. angleWall = qSVG.angle(x1, y1, x2, y2);
  359. binder.attr({
  360. "transform": "translate(" + (addNode.x) + "," + (addNode.y) + ") rotate(" + (angleWall.deg + 90) + ",0,0)"
  361. });
  362. binder.data = addNode;
  363. }
  364. else {
  365. binder.remove();
  366. delete binder;
  367. }
  368. } else {
  369. binder.remove();
  370. delete binder;
  371. }
  372. }
  373. } // END NODE MODE
  374. //********************************** SELECT MODE ***************************************************************
  375. if (mode == 'select_mode' && drag == 'off') { // FIRST TEST ON SELECT MODE (and drag OFF) to detect MOUSEOVER DOOR
  376. snap = calcul_snap(event, 'off');
  377. var objTarget = false;
  378. for (var i = 0; i < OBJDATA.length; i++) {
  379. var objX1 = OBJDATA[i].bbox.left;
  380. var objX2 = OBJDATA[i].bbox.right;
  381. var objY1 = OBJDATA[i].bbox.top;
  382. var objY2 = OBJDATA[i].bbox.bottom;
  383. var realBboxCoords = OBJDATA[i].realBbox;
  384. if (qSVG.rayCasting(snap, realBboxCoords)) {
  385. objTarget = OBJDATA[i];
  386. }
  387. }
  388. if (objTarget !== false) {
  389. if (typeof (binder) != 'undefined' && (binder.type == 'segment')) {
  390. binder.graph.remove();
  391. delete binder;
  392. cursor('default');
  393. }
  394. if (objTarget.params.bindBox) { // OBJ -> BOUNDINGBOX TOOL
  395. if (typeof (binder) == 'undefined') {
  396. binder = new editor.obj2D("free", "boundingBox", "", objTarget.bbox.origin, objTarget.angle, 0, objTarget.size, "normal", objTarget.thick, objTarget.realBbox);
  397. binder.update();
  398. binder.obj = objTarget;
  399. binder.type = 'boundingBox';
  400. binder.oldX = binder.x;
  401. binder.oldY = binder.y;
  402. $('#boxbind').append(binder.graph);
  403. if (!objTarget.params.move) cursor('trash'); // LIKE MEASURE ON PLAN
  404. if (objTarget.params.move) cursor('move');
  405. }
  406. }
  407. else { // DOOR, WINDOW, APERTURE.. -- OBJ WITHOUT BINDBOX (params.bindBox = False) -- !!!!
  408. if (typeof (binder) == 'undefined') {
  409. var wallList = editor.rayCastingWall(objTarget);
  410. if (wallList.length > 1) wallList = wallList[0];
  411. inWallRib(wallList);
  412. var thickObj = wallList.thick;
  413. var sizeObj = objTarget.size;
  414. binder = new editor.obj2D("inWall", "socle", "", objTarget, objTarget.angle, 0, sizeObj, "normal", thickObj);
  415. binder.update();
  416. binder.oldXY = { x: objTarget.x, y: objTarget.y }; // FOR OBJECT MENU
  417. $('#boxbind').append(binder.graph);
  418. }
  419. else {
  420. // Guard against cases where binder may not have a graph (e.g., binder is a simple SVG element from other modes)
  421. var hasBinderGraph = (typeof binder.graph !== 'undefined') &&
  422. (typeof binder.graph.get === 'function') &&
  423. binder.graph.get(0) &&
  424. binder.graph.get(0).firstChild;
  425. if (hasBinderGraph && event.target == binder.graph.get(0).firstChild) {
  426. cursor('move');
  427. binder.graph.get(0).firstChild.setAttribute("class", "circle_css_2");
  428. binder.type = "obj";
  429. binder.obj = objTarget;
  430. }
  431. else {
  432. cursor('default');
  433. if (hasBinderGraph) {
  434. binder.graph.get(0).firstChild.setAttribute("class", "circle_css_1");
  435. }
  436. binder.type = false;
  437. }
  438. }
  439. }
  440. }
  441. else {
  442. if (typeof (binder) != 'undefined') {
  443. if (typeof (binder.graph) != 'undefined') binder.graph.remove();
  444. if (binder.type == 'node') binder.remove();
  445. delete binder;
  446. cursor('default');
  447. rib();
  448. }
  449. }
  450. // BIND CIRCLE IF nearNode and GROUP ALL SAME XY SEG POINTS
  451. if (wallNode = editor.nearWallNode(snap, 20)) {
  452. if (typeof (binder) == 'undefined' || binder.type == 'segment') {
  453. binder = qSVG.create('boxbind', 'circle', {
  454. id: "circlebinder",
  455. class: "circle_css_2",
  456. cx: wallNode.x,
  457. cy: wallNode.y,
  458. r: Rcirclebinder
  459. });
  460. binder.data = wallNode;
  461. binder.type = "node";
  462. if ($('#linebinder').length) $('#linebinder').remove();
  463. } else {
  464. // REMAKE CIRCLE_CSS ON BINDER AND TAKE DATA SEG GROUP
  465. // if (typeof(binder) != 'undefined') {
  466. // binder.attr({
  467. // class: "circle_css_2"
  468. // });
  469. // }
  470. }
  471. cursor('move');
  472. } else {
  473. if (typeof (binder) != "undefined" && binder.type == 'node') {
  474. binder.remove();
  475. delete binder;
  476. hideAllSize();
  477. cursor('default');
  478. rib();
  479. }
  480. }
  481. // BIND WALL WITH NEARPOINT function ---> WALL BINDER CREATION
  482. if (wallBind = editor.rayCastingWalls(snap, WALLS)) {
  483. if (wallBind.length > 1) wallBind = wallBind[wallBind.length - 1];
  484. if (wallBind && typeof (binder) == 'undefined') {
  485. var objWall = editor.objFromWall(wallBind);
  486. if (objWall.length > 0) editor.inWallRib2(wallBind);
  487. binder = {};
  488. binder.wall = wallBind;
  489. inWallRib(binder.wall);
  490. var line = qSVG.create('none', 'line', {
  491. x1: binder.wall.start.x, y1: binder.wall.start.y, x2: binder.wall.end.x, y2: binder.wall.end.y,
  492. "stroke-width": 5,
  493. stroke: "#5cba79"
  494. });
  495. var ball1 = qSVG.create('none', 'circle', {
  496. class: "circle_css",
  497. cx: binder.wall.start.x, cy: binder.wall.start.y,
  498. r: Rcirclebinder / 1.8
  499. });
  500. var ball2 = qSVG.create('none', 'circle', {
  501. class: "circle_css",
  502. cx: binder.wall.end.x, cy: binder.wall.end.y,
  503. r: Rcirclebinder / 1.8
  504. });
  505. binder.graph = qSVG.create('none', 'g');
  506. binder.graph.append(line);
  507. binder.graph.append(ball1);
  508. binder.graph.append(ball2);
  509. $('#boxbind').append(binder.graph);
  510. binder.type = "segment";
  511. cursor('pointer');
  512. }
  513. } else {
  514. if (wallBind = editor.nearWall(snap, 6)) {
  515. if (wallBind && typeof (binder) == 'undefined') {
  516. wallBind = wallBind.wall;
  517. var objWall = editor.objFromWall(wallBind);
  518. if (objWall.length > 0) editor.inWallRib2(wallBind);
  519. binder = {};
  520. binder.wall = wallBind;
  521. inWallRib(binder.wall);
  522. var line = qSVG.create('none', 'line', {
  523. x1: binder.wall.start.x, y1: binder.wall.start.y, x2: binder.wall.end.x, y2: binder.wall.end.y,
  524. "stroke-width": 5,
  525. stroke: "#5cba79"
  526. });
  527. var ball1 = qSVG.create('none', 'circle', {
  528. class: "circle_css",
  529. cx: binder.wall.start.x, cy: binder.wall.start.y,
  530. r: Rcirclebinder / 1.8
  531. });
  532. var ball2 = qSVG.create('none', 'circle', {
  533. class: "circle_css",
  534. cx: binder.wall.end.x, cy: binder.wall.end.y,
  535. r: Rcirclebinder / 1.8
  536. });
  537. binder.graph = qSVG.create('none', 'g');
  538. binder.graph.append(line);
  539. binder.graph.append(ball1);
  540. binder.graph.append(ball2);
  541. $('#boxbind').append(binder.graph);
  542. binder.type = "segment";
  543. cursor('pointer');
  544. }
  545. }
  546. else {
  547. if (typeof (binder) != "undefined" && binder.type == 'segment') {
  548. binder.graph.remove();
  549. delete binder;
  550. hideAllSize();
  551. cursor('default');
  552. rib();
  553. }
  554. }
  555. }
  556. } // END mode == 'select_mode' && drag == 'off'
  557. //**************************************************************************
  558. //************** FURNITURE PLACEMENT MODE ***************************
  559. //**************************************************************************
  560. if (mode == 'furniture_placement_mode') {
  561. snap = calcul_snap(event, grid_snap);
  562. updateFurnitureCursor(snap.x, snap.y);
  563. cursor('none');
  564. }
  565. // ------------------------------ LINE MODE ------------------------------------------------------
  566. if ((mode == 'line_mode' || mode == 'partition_mode') && action == 0) {
  567. snap = calcul_snap(event, 'off');
  568. cursor('grab');
  569. pox = snap.x;
  570. poy = snap.y;
  571. if (helpConstruc = intersection(snap, 25)) {
  572. if (helpConstruc.distance < 10) {
  573. pox = helpConstruc.x;
  574. poy = helpConstruc.y;
  575. cursor('grab');
  576. } else {
  577. cursor('crosshair');
  578. }
  579. }
  580. if (wallNode = editor.nearWallNode(snap, 20)) {
  581. pox = wallNode.x;
  582. poy = wallNode.y;
  583. cursor('grab');
  584. if (typeof (binder) == 'undefined') {
  585. binder = qSVG.create('boxbind', 'circle', {
  586. id: "circlebinder",
  587. class: "circle_css_2",
  588. cx: wallNode.x,
  589. cy: wallNode.y,
  590. r: Rcirclebinder / 1.5
  591. });
  592. }
  593. intersectionOff();
  594. } else {
  595. if (!helpConstruc) cursor('crosshair');
  596. if (typeof (binder) != "undefined") {
  597. if (binder.graph) binder.graph.remove();
  598. else binder.remove();
  599. delete binder;
  600. }
  601. }
  602. }
  603. // ******************************************************************************************************
  604. // ************************** ACTION = 1 LINE MODE => WALL CREATE *********************
  605. // ******************************************************************************************************
  606. if (action == 1 && (mode == 'line_mode' || mode == 'partition_mode')) {
  607. snap = calcul_snap(event, grid_snap);
  608. x = snap.x;
  609. y = snap.y;
  610. starter = minMoveGrid(snap);
  611. if (!$('#line_construc').length) {
  612. if (wallNode = editor.nearWallNode(snap, 20)) {
  613. pox = wallNode.x;
  614. poy = wallNode.y;
  615. wallStartConstruc = false;
  616. if (wallNode.bestWall == WALLS.length - 1) {
  617. cursor('validation');
  618. }
  619. else {
  620. cursor('grab');
  621. }
  622. } else {
  623. cursor('crosshair');
  624. }
  625. }
  626. if (starter > grid) {
  627. if (!$('#line_construc').length) {
  628. var ws = 20;
  629. if (mode == 'partition_mode') ws = 10;
  630. lineconstruc = qSVG.create("boxbind", "line", {
  631. id: "line_construc",
  632. x1: pox,
  633. y1: poy,
  634. x2: x,
  635. y2: y,
  636. "stroke-width": ws,
  637. "stroke-linecap": "butt",
  638. "stroke-opacity": 0.7,
  639. stroke: "#9fb2e2"
  640. });
  641. svgadd = qSVG.create("boxbind", "line", { // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+
  642. id: "linetemp",
  643. x1: pox,
  644. y1: poy,
  645. x2: x,
  646. y2: y,
  647. "stroke": "transparent",
  648. "stroke-width": 0.5,
  649. "stroke-opacity": "0.9"
  650. });
  651. } else { // THE LINES AND BINDER ARE CREATED
  652. $('#linetemp').attr({
  653. x2: x,
  654. y2: y
  655. });
  656. if (helpConstrucEnd = intersection(snap, 10)) {
  657. x = helpConstrucEnd.x;
  658. y = helpConstrucEnd.y;
  659. }
  660. if (wallEndConstruc = editor.nearWall(snap, 12)) { // TO SNAP SEGMENT TO FINALIZE X2Y2
  661. x = wallEndConstruc.x;
  662. y = wallEndConstruc.y;
  663. cursor('grab');
  664. } else {
  665. cursor('crosshair');
  666. }
  667. // nearNode helped to attach the end of the construc line
  668. if (wallNode = editor.nearWallNode(snap, 20)) {
  669. if (typeof (binder) == 'undefined') {
  670. binder = qSVG.create('boxbind', 'circle', {
  671. id: "circlebinder",
  672. class: "circle_css_2",
  673. cx: wallNode.x,
  674. cy: wallNode.y,
  675. r: Rcirclebinder / 1.5
  676. });
  677. }
  678. $('#line_construc').attr({
  679. x2: wallNode.x,
  680. y2: wallNode.y
  681. });
  682. x = wallNode.x;
  683. y = wallNode.y;
  684. wallEndConstruc = true;
  685. intersectionOff();
  686. if (wallNode.bestWall == WALLS.length - 1 && document.getElementById("multi").checked) {
  687. cursor('validation');
  688. }
  689. else {
  690. cursor('grab');
  691. }
  692. } else {
  693. if (typeof (binder) != "undefined") {
  694. binder.remove();
  695. delete binder;
  696. }
  697. if (wallEndConstruc === false) cursor('crosshair');
  698. }
  699. // LINETEMP AND LITLLE SNAPPING FOR HELP TO CONSTRUC ANGLE 0 90 45 *****************************************
  700. var fltt = qSVG.angle(pox, poy, x, y);
  701. var flt = Math.abs(fltt.deg);
  702. var coeff = fltt.deg / flt; // -45 -> -1 45 -> 1
  703. var phi = poy - (coeff * pox);
  704. var Xdiag = (y - phi) / coeff;
  705. if (typeof (binder) == 'undefined') {
  706. // HELP FOR H LINE
  707. var found = false;
  708. if (flt < 15 && Math.abs(poy - y) < 25) {
  709. y = poy;
  710. found = true;
  711. } // HELP FOR V LINE
  712. if (flt > 75 && Math.abs(pox - x) < 25) {
  713. x = pox;
  714. found = true;
  715. } // HELP FOR DIAG LINE
  716. if (flt < 55 && flt > 35 && Math.abs(Xdiag - x) < 20) {
  717. x = Xdiag;
  718. found = true;
  719. }
  720. if (found) $('#line_construc').attr({ "stroke-opacity": 1 });
  721. else $('#line_construc').attr({ "stroke-opacity": 0.7 });
  722. }
  723. $('#line_construc').attr({
  724. x2: x,
  725. y2: y
  726. });
  727. // SHOW WALL SIZE -------------------------------------------------------------------------
  728. var startText = qSVG.middle(pox, poy, x, y);
  729. var angleText = qSVG.angle(pox, poy, x, y);
  730. var valueText = ((qSVG.measure({
  731. x: pox,
  732. y: poy
  733. }, {
  734. x: x,
  735. y: y
  736. })) / 60).toFixed(2);
  737. if (typeof (lengthTemp) == 'undefined') {
  738. lengthTemp = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  739. lengthTemp.setAttributeNS(null, 'x', startText.x);
  740. lengthTemp.setAttributeNS(null, 'y', (startText.y) - 15);
  741. lengthTemp.setAttributeNS(null, 'text-anchor', 'middle');
  742. lengthTemp.setAttributeNS(null, 'stroke', 'none');
  743. lengthTemp.setAttributeNS(null, 'stroke-width', '0.6px');
  744. lengthTemp.setAttributeNS(null, 'fill', '#777777');
  745. lengthTemp.textContent = valueText + 'm';
  746. $('#boxbind').append(lengthTemp);
  747. }
  748. if (typeof (lengthTemp) != 'undefined' && valueText > 0.1) {
  749. lengthTemp.setAttributeNS(null, 'x', startText.x);
  750. lengthTemp.setAttributeNS(null, 'y', (startText.y) - 15);
  751. lengthTemp.setAttribute("transform", "rotate(" + angleText.deg + " " + startText.x + "," + startText.y + ")");
  752. lengthTemp.textContent = valueText + ' m';
  753. }
  754. if (typeof (lengthTemp) != 'undefined' && valueText < 0.1) {
  755. lengthTemp.textContent = "";
  756. }
  757. }
  758. }
  759. } // END LINE MODE DETECT && ACTION = 1
  760. //ONMOVE
  761. // **************************************************************************************************
  762. // ____ ___ _ _ ____ _____ ____
  763. // | __ )_ _| \ | | _ \| ____| _ \
  764. // | _ \| || \| | | | | _| | |_) |
  765. // | |_) | || |\ | |_| | |___| _ <
  766. // |____/___|_| \_|____/|_____|_| \_\
  767. //
  768. // **************************************************************************************************
  769. if (mode == 'bind_mode') {
  770. snap = calcul_snap(event, grid_snap);
  771. if (binder.type == 'node') {
  772. var coords = snap;
  773. var magnetic = false;
  774. for (var k in wallListRun) {
  775. if (isObjectsEquals(wallListRun[k].end, binder.data)) {
  776. if (Math.abs(wallListRun[k].start.x - snap.x) < 20) { coords.x = wallListRun[k].start.x; magnetic = "H"; }
  777. if (Math.abs(wallListRun[k].start.y - snap.y) < 20) { coords.y = wallListRun[k].start.y; magnetic = "V"; }
  778. }
  779. if (isObjectsEquals(wallListRun[k].start, binder.data)) {
  780. if (Math.abs(wallListRun[k].end.x - snap.x) < 20) { coords.x = wallListRun[k].end.x; magnetic = "H"; }
  781. if (Math.abs(wallListRun[k].end.y - snap.y) < 20) { coords.y = wallListRun[k].end.y; magnetic = "V"; }
  782. }
  783. }
  784. if (nodeMove = editor.nearWallNode(snap, 15, wallListRun)) {
  785. coords.x = nodeMove.x;
  786. coords.y = nodeMove.y;
  787. $('#circlebinder').attr({ "class": "circleGum", cx: coords.x, cy: coords.y });
  788. cursor('grab');
  789. } else {
  790. if (magnetic != false) {
  791. if (magnetic == "H") snap.x = coords.x;
  792. else snap.y = coords.y;
  793. }
  794. if (helpConstruc = intersection(snap, 10, wallListRun)) {
  795. coords.x = helpConstruc.x;
  796. coords.y = helpConstruc.y;
  797. snap.x = helpConstruc.x;
  798. snap.y = helpConstruc.y;
  799. if (magnetic != false) {
  800. if (magnetic == "H") snap.x = coords.x;
  801. else snap.y = coords.y;
  802. }
  803. cursor('grab');
  804. } else {
  805. cursor('move');
  806. }
  807. binder.remove()
  808. //$('#circlebinder').attr({"class": "circle_css", cx: coords.x, cy: coords.y});
  809. }
  810. for (var k in wallListRun) {
  811. if (isObjectsEquals(wallListRun[k].start, binder.data)) {
  812. wallListRun[k].start.x = coords.x;
  813. wallListRun[k].start.y = coords.y;
  814. }
  815. if (isObjectsEquals(wallListRun[k].end, binder.data)) {
  816. wallListRun[k].end.x = coords.x;
  817. wallListRun[k].end.y = coords.y;
  818. }
  819. }
  820. binder.data = coords;
  821. editor.wallsComputing(WALLS, false); // UPDATE FALSE
  822. for (var k in wallListObj) {
  823. var wall = wallListObj[k].wall;
  824. var objTarget = wallListObj[k].obj;
  825. var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  826. var limits = limitObj(wall.equations.base, 2 * wallListObj[k].distance, wallListObj[k].from); // COORDS OBJ AFTER ROTATION
  827. var indexLimits = 0;
  828. 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;
  829. // NEW COORDS OBJDATA[obj]
  830. objTarget.x = limits[indexLimits].x;
  831. objTarget.y = limits[indexLimits].y;
  832. objTarget.angle = angleWall;
  833. if (objTarget.angleSign == 1) objTarget.angle = angleWall + 180;
  834. var limitBtwn = limitObj(wall.equations.base, objTarget.size, objTarget); // OBJ SIZE OK BTWN xy1/xy2
  835. 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)) {
  836. objTarget.limit = limitBtwn;
  837. objTarget.update();
  838. }
  839. else {
  840. objTarget.graph.remove();
  841. delete objTarget;
  842. OBJDATA.splice(wall.indexObj, 1);
  843. wallListObj.splice(k, 1);
  844. }
  845. }
  846. // for (k in toClean)
  847. $('#boxRoom').empty();
  848. $('#boxSurface').empty();
  849. Rooms = qSVG.polygonize(WALLS);
  850. editor.roomMaker(Rooms);
  851. }
  852. // WALL MOVING ----BINDER TYPE SEGMENT-------- FUNCTION FOR H,V and Calculate Vectorial Translation
  853. if (binder.type == 'segment' && action == 1) {
  854. rib();
  855. if (equation2.A == 'v') { equation2.B = snap.x; }
  856. else if (equation2.A == 'h') { equation2.B = snap.y; }
  857. else { equation2.B = snap.y - (snap.x * equation2.A); }
  858. var intersection1 = qSVG.intersectionOfEquations(equation1, equation2, "obj");
  859. var intersection2 = qSVG.intersectionOfEquations(equation2, equation3, "obj");
  860. var intersection3 = qSVG.intersectionOfEquations(equation1, equation3, "obj");
  861. if (binder.wall.parent != null) {
  862. if (isObjectsEquals(binder.wall.parent.end, binder.wall.start)) binder.wall.parent.end = intersection1;
  863. else if (isObjectsEquals(binder.wall.parent.start, binder.wall.start)) binder.wall.parent.start = intersection1;
  864. else binder.wall.parent.end = intersection1;
  865. }
  866. if (binder.wall.child != null) {
  867. if (isObjectsEquals(binder.wall.child.start, binder.wall.end)) binder.wall.child.start = intersection2;
  868. else if (isObjectsEquals(binder.wall.child.end, binder.wall.end)) binder.wall.child.end = intersection2;
  869. else binder.wall.child.start = intersection2;
  870. }
  871. binder.wall.start = intersection1;
  872. binder.wall.end = intersection2;
  873. binder.graph.remove()
  874. // binder.graph[0].children[0].setAttribute("x1",intersection1.x);
  875. // binder.graph[0].children[0].setAttribute("x2",intersection2.x);
  876. // binder.graph[0].children[0].setAttribute("y1",intersection1.y);
  877. // binder.graph[0].children[0].setAttribute("y2",intersection2.y);
  878. // binder.graph[0].children[1].setAttribute("cx",intersection1.x);
  879. // binder.graph[0].children[1].setAttribute("cy",intersection1.y);
  880. // binder.graph[0].children[2].setAttribute("cx",intersection2.x);
  881. // binder.graph[0].children[2].setAttribute("cy",intersection2.y);
  882. // THE EQ FOLLOWED BY eq (PARENT EQ1 --- CHILD EQ3)
  883. if (equation1.follow != undefined) {
  884. if (!qSVG.rayCasting(intersection1, equation1.backUp.coords)) { // IF OUT OF WALL FOLLOWED
  885. var distanceFromStart = qSVG.gap(equation1.backUp.start, intersection1);
  886. var distanceFromEnd = qSVG.gap(equation1.backUp.end, intersection1);
  887. if (distanceFromStart > distanceFromEnd) { // NEAR FROM End
  888. equation1.follow.end = intersection1;
  889. }
  890. else {
  891. equation1.follow.start = intersection1;
  892. }
  893. }
  894. else {
  895. equation1.follow.end = equation1.backUp.end;
  896. equation1.follow.start = equation1.backUp.start;
  897. }
  898. }
  899. if (equation3.follow != undefined) {
  900. if (!qSVG.rayCasting(intersection2, equation3.backUp.coords)) { // IF OUT OF WALL FOLLOWED
  901. var distanceFromStart = qSVG.gap(equation3.backUp.start, intersection2);
  902. var distanceFromEnd = qSVG.gap(equation3.backUp.end, intersection2);
  903. if (distanceFromStart > distanceFromEnd) { // NEAR FROM End
  904. equation3.follow.end = intersection2;
  905. }
  906. else {
  907. equation3.follow.start = intersection2;
  908. }
  909. }
  910. else {
  911. equation3.follow.end = equation3.backUp.end;
  912. equation3.follow.start = equation3.backUp.start;
  913. }
  914. }
  915. // EQ FOLLOWERS WALL MOVING
  916. for (var i = 0; i < equationFollowers.length; i++) {
  917. var intersectionFollowers = qSVG.intersectionOfEquations(equationFollowers[i].eq, equation2, "obj");
  918. 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')) {
  919. var size = qSVG.measure(equationFollowers[i].wall.start, equationFollowers[i].wall.end);
  920. if (equationFollowers[i].type == "start") {
  921. equationFollowers[i].wall.start = intersectionFollowers;
  922. }
  923. if (equationFollowers[i].type == "end") {
  924. equationFollowers[i].wall.end = intersectionFollowers;
  925. // Note: Wall deletion is now deferred until mouse release to prevent premature deletion
  926. }
  927. }
  928. }
  929. // WALL COMPUTING, BLOCK FAMILY OF BINDERWALL IF NULL (START OR END) !!!!!
  930. editor.wallsComputing(WALLS, "move");
  931. Rooms = qSVG.polygonize(WALLS);
  932. // Re-append all door/window objects to ensure they stay in boxcarpentry after wall computing
  933. for (var objIdx = 0; objIdx < OBJDATA.length; objIdx++) {
  934. var obj = OBJDATA[objIdx];
  935. if (obj && obj.graph && obj.family === 'inWall') {
  936. obj.graph.remove();
  937. $('#boxcarpentry').append(obj.graph);
  938. }
  939. }
  940. // OBJDATA(s) FOLLOW 90° EDGE SELECTED
  941. for (var rp = 0; rp < equationsObj.length; rp++) {
  942. var objTarget = equationsObj[rp].obj;
  943. var intersectionObj = qSVG.intersectionOfEquations(equationsObj[rp].eq, equation2);
  944. // NEW COORDS OBJDATA[o]
  945. objTarget.x = intersectionObj[0];
  946. objTarget.y = intersectionObj[1];
  947. var limits = limitObj(equation2, objTarget.size, objTarget);
  948. 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)) {
  949. objTarget.limit = limits;
  950. objTarget.update();
  951. // Re-append to ensure doors/windows stay on top after wall movement
  952. if (objTarget.graph) {
  953. objTarget.graph.remove();
  954. $('#boxcarpentry').append(objTarget.graph);
  955. }
  956. }
  957. }
  958. // HANDLE ALL INWALL OBJECTS - INCLUDING DETACHED ONES
  959. // First pass: handle objects currently attached to walls
  960. for (var k in WALLS) {
  961. var objWall = editor.objFromWall(WALLS[k]); // LIST OBJ ON EDGE
  962. for (var ob in objWall) {
  963. var objTarget = objWall[ob];
  964. var eq = editor.createEquationFromWall(WALLS[k]);
  965. var limits = limitObj(eq, objTarget.size, objTarget);
  966. 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)) {
  967. // Try to resize the door/window to fit the wall instead of deleting it
  968. var wallLength = qSVG.measure(WALLS[k].start, WALLS[k].end);
  969. var maxObjectSize = wallLength - 20; // Leave some margin
  970. if (maxObjectSize > 20) { // Minimum viable size for door/window
  971. objTarget.size = maxObjectSize;
  972. var newLimits = limitObj(eq, objTarget.size, objTarget);
  973. // Check if resized object fits
  974. 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)) {
  975. objTarget.limit = newLimits;
  976. objTarget.update();
  977. // Re-append to maintain proper layering
  978. if (objTarget.graph) {
  979. objTarget.graph.remove();
  980. $('#boxcarpentry').append(objTarget.graph);
  981. }
  982. } else {
  983. // If resizing doesn't work, delete the object
  984. var indexObj = OBJDATA.indexOf(objTarget);
  985. objTarget.graph.remove();
  986. if (indexObj !== -1) {
  987. OBJDATA.splice(indexObj, 1);
  988. }
  989. }
  990. } else {
  991. // Wall too small, delete the object
  992. var indexObj = OBJDATA.indexOf(objTarget);
  993. objTarget.graph.remove();
  994. if (indexObj !== -1) {
  995. OBJDATA.splice(indexObj, 1);
  996. }
  997. }
  998. }
  999. }
  1000. }
  1001. // Second pass: handle detached inWall objects (not found by objFromWall)
  1002. var objectsToRemove = [];
  1003. for (var objIdx = 0; objIdx < OBJDATA.length; objIdx++) {
  1004. var obj = OBJDATA[objIdx];
  1005. if (obj && obj.family === 'inWall') {
  1006. var isAttachedToWall = false;
  1007. var bestWall = null;
  1008. var bestDistance = Infinity;
  1009. var bestPosition = null;
  1010. // Check if object is attached to any wall
  1011. for (var wallIdx in WALLS) {
  1012. var wall = WALLS[wallIdx];
  1013. var eq = qSVG.createEquation(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  1014. var nearPoint = qSVG.nearPointOnEquation(eq, obj);
  1015. 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)) {
  1016. isAttachedToWall = true;
  1017. break;
  1018. }
  1019. // Track closest wall for potential re-attachment
  1020. if (nearPoint.distance < bestDistance && qSVG.btwn(nearPoint.x, wall.start.x, wall.end.x) && qSVG.btwn(nearPoint.y, wall.start.y, wall.end.y)) {
  1021. bestDistance = nearPoint.distance;
  1022. bestWall = wall;
  1023. bestPosition = { x: nearPoint.x, y: nearPoint.y };
  1024. }
  1025. }
  1026. // If object is detached, try to re-attach or remove it
  1027. if (!isAttachedToWall) {
  1028. if (bestWall && bestDistance < 50) { // Within 50px of a wall
  1029. var wallLength = qSVG.measure(bestWall.start, bestWall.end);
  1030. var maxObjectSize = wallLength - 20;
  1031. // Check if there's enough space on the wall
  1032. if (maxObjectSize >= obj.size || (maxObjectSize > 20 && obj.size > maxObjectSize)) {
  1033. // Re-attach to the closest wall
  1034. obj.x = bestPosition.x;
  1035. obj.y = bestPosition.y;
  1036. // Resize if necessary
  1037. if (obj.size > maxObjectSize && maxObjectSize > 20) {
  1038. obj.size = maxObjectSize;
  1039. }
  1040. // Update object position and limits
  1041. var eq = editor.createEquationFromWall(bestWall);
  1042. var limits = limitObj(eq, obj.size, obj);
  1043. obj.limit = limits;
  1044. obj.update();
  1045. // Re-append to maintain proper layering
  1046. if (obj.graph) {
  1047. obj.graph.remove();
  1048. $('#boxcarpentry').append(obj.graph);
  1049. }
  1050. } else {
  1051. // Wall too small, mark for removal
  1052. objectsToRemove.push(objIdx);
  1053. }
  1054. } else {
  1055. // No suitable wall found, mark for removal
  1056. objectsToRemove.push(objIdx);
  1057. }
  1058. }
  1059. }
  1060. }
  1061. // Remove objects that couldn't be re-attached (in reverse order to maintain indices)
  1062. for (var i = objectsToRemove.length - 1; i >= 0; i--) {
  1063. var objIndex = objectsToRemove[i];
  1064. if (OBJDATA[objIndex] && OBJDATA[objIndex].graph) {
  1065. OBJDATA[objIndex].graph.remove();
  1066. }
  1067. OBJDATA.splice(objIndex, 1);
  1068. }
  1069. equationsObj = []; // REINIT eqObj -> MAYBE ONE OR PLUS OF OBJDATA REMOVED !!!!
  1070. var objWall = editor.objFromWall(binder.wall); // LIST OBJ ON EDGE
  1071. for (var ob = 0; ob < objWall.length; ob++) {
  1072. var objTarget = objWall[ob];
  1073. equationsObj.push({ obj: objTarget, wall: binder.wall, eq: qSVG.perpendicularEquation(equation2, objTarget.x, objTarget.y) });
  1074. }
  1075. $('#boxRoom').empty();
  1076. $('#boxSurface').empty();
  1077. editor.roomMaker(Rooms);
  1078. $('#lin').css('cursor', 'pointer');
  1079. }
  1080. // **********************************************************************
  1081. // ---------------------- BOUNDING BOX ------------------------------
  1082. // **********************************************************************
  1083. // binder.obj.params.move ---> FOR MEASURE DONT MOVE
  1084. if (binder.type == 'boundingBox' && action == 1 && binder.obj.params.move) {
  1085. binder.x = snap.x;
  1086. binder.y = snap.y;
  1087. binder.obj.x = snap.x;
  1088. binder.obj.y = snap.y;
  1089. binder.obj.update();
  1090. binder.update();
  1091. }
  1092. // **********************************************************************
  1093. // OBJ MOVING
  1094. // **********************************************************************
  1095. if (binder.type == 'obj' && action == 1) {
  1096. if (wallSelect = editor.nearWall(snap)) {
  1097. if (wallSelect.wall.type != 'separate') {
  1098. inWallRib(wallSelect.wall);
  1099. var objTarget = binder.obj;
  1100. var wall = wallSelect.wall;
  1101. var angleWall = qSVG.angleDeg(wall.start.x, wall.start.y, wall.end.x, wall.end.y);
  1102. var v1 = qSVG.vectorXY({ x: wall.start.x, y: wall.start.y }, { x: wall.end.x, y: wall.end.y });
  1103. var v2 = qSVG.vectorXY({ x: wall.end.x, y: wall.end.y }, snap);
  1104. var newAngle = qSVG.vectorDeter(v1, v2);
  1105. binder.angleSign = 0;
  1106. objTarget.angleSign = 0;
  1107. if (Math.sign(newAngle) == 1) {
  1108. angleWall += 180;
  1109. binder.angleSign = 1;
  1110. objTarget.angleSign = 1;
  1111. }
  1112. var limits = limitObj(wall.equations.base, binder.size, wallSelect);
  1113. 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)) {
  1114. binder.x = wallSelect.x;
  1115. binder.y = wallSelect.y;
  1116. binder.angle = angleWall;
  1117. binder.thick = wall.thick;
  1118. objTarget.x = wallSelect.x;
  1119. objTarget.y = wallSelect.y;
  1120. objTarget.angle = angleWall;
  1121. objTarget.thick = wall.thick;
  1122. objTarget.limit = limits;
  1123. binder.update();
  1124. objTarget.update();
  1125. }
  1126. if ((wallSelect.x == wall.start.x && wallSelect.y == wall.start.y) || (wallSelect.x == wall.end.x && wallSelect.y == wall.end.y)) {
  1127. if (qSVG.btwn(limits[0].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[0].y, wall.start.y, wall.end.y)) {
  1128. binder.x = limits[0].x;
  1129. binder.y = limits[0].y;
  1130. objTarget.x = limits[0].x;
  1131. objTarget.y = limits[0].y;
  1132. objTarget.limit = limits;
  1133. }
  1134. if (qSVG.btwn(limits[1].x, wall.start.x, wall.end.x) && qSVG.btwn(limits[1].y, wall.start.y, wall.end.y)) {
  1135. binder.x = limits[1].x;
  1136. binder.y = limits[1].y;
  1137. objTarget.x = limits[1].x;
  1138. objTarget.y = limits[1].y;
  1139. objTarget.limit = limits;
  1140. }
  1141. binder.angle = angleWall;
  1142. binder.thick = wall.thick;
  1143. objTarget.angle = angleWall;
  1144. objTarget.thick = wall.thick;
  1145. binder.update();
  1146. objTarget.update();
  1147. }
  1148. }
  1149. }
  1150. } // END OBJ MOVE
  1151. if (binder.type != 'obj' && binder.type != 'segment') rib();
  1152. }
  1153. // ENDBIND ACTION MOVE **************************************************************************
  1154. // ---DRAG VIEWBOX PANNING -------------------------------------------------------
  1155. // Check if background image tools are open - if so, disable global panning
  1156. const backgroundImageToolsOpen = document.getElementById('backgroundImageTools') &&
  1157. document.getElementById('backgroundImageTools').style.display !== 'none' &&
  1158. window.getComputedStyle(document.getElementById('backgroundImageTools')).display !== 'none';
  1159. // Permit panning when floorplan mode is active even if background image tools are open
  1160. const floorplanMode = !!window.__floorplanMode;
  1161. if ((mode == 'select_mode' || mode == 'furniture_placement_mode' || mode == 'furniture_mode') && drag == 'on' && (!backgroundImageToolsOpen || floorplanMode) && !window.draggingFurnitureItem && !window.draggingBackgroundImage) {
  1162. snap = calcul_snap(event, grid_snap);
  1163. $('#lin').css('cursor', 'move');
  1164. distX = (snap.xMouse - pox) * factor;
  1165. distY = (snap.yMouse - poy) * factor;
  1166. // pox = event.pageX;
  1167. // poy = event.pageY;
  1168. zoom_maker('zoomdrag', distX, distY);
  1169. }
  1170. } // END MOUSEMOVE
  1171. // *****************************************************************************************************
  1172. // *****************************************************************************************************
  1173. // *****************************************************************************************************
  1174. // ****************************** MOUSE DOWN *****************************************
  1175. // *****************************************************************************************************
  1176. // *****************************************************************************************************
  1177. // *****************************************************************************************************
  1178. function _MOUSEDOWN(event) {
  1179. event.preventDefault();
  1180. // In floorplan mode, block edits in select mode entirely
  1181. if (window.__floorplanMode && mode === 'select_mode') {
  1182. // Ensure no binder remains
  1183. try {
  1184. if (typeof (binder) !== 'undefined') {
  1185. if (binder.remove) binder.remove();
  1186. else if (binder.graph && binder.graph.remove) binder.graph.remove();
  1187. delete binder;
  1188. }
  1189. $('#boxbind').empty();
  1190. } catch (_) { /* no-op */ }
  1191. return;
  1192. }
  1193. // *******************************************************************
  1194. // ************************** DISTANCE MODE **********************
  1195. // *******************************************************************
  1196. if (mode == 'distance_mode') {
  1197. if (action == 0) {
  1198. action = 1;
  1199. snap = calcul_snap(event, grid_snap);
  1200. pox = snap.x;
  1201. poy = snap.y;
  1202. }
  1203. }
  1204. // *******************************************************************
  1205. // ************************* LINE/WALL MODE **********************
  1206. // *******************************************************************
  1207. if (mode == 'line_mode' || mode == 'partition_mode') {
  1208. if (action == 0) {
  1209. snap = calcul_snap(event, grid_snap);
  1210. pox = snap.x;
  1211. poy = snap.y;
  1212. if (wallStartConstruc = editor.nearWall(snap, 12)) { // TO SNAP SEGMENT TO FINALIZE X2Y2
  1213. pox = wallStartConstruc.x;
  1214. poy = wallStartConstruc.y;
  1215. }
  1216. }
  1217. else {
  1218. // FINALIZE LINE_++
  1219. construc = 1;
  1220. }
  1221. action = 1;
  1222. }
  1223. if (mode == 'edit_door_mode') { // ACTION 1 ACTIVATE EDITION OF THE DOOR
  1224. action = 1;
  1225. $('#lin').css('cursor', 'pointer');
  1226. }
  1227. // *******************************************************************
  1228. // ******************** FURNITURE PLACEMENT MODE ****************
  1229. // *******************************************************************
  1230. if (mode == 'furniture_placement_mode') {
  1231. snap = calcul_snap(event, grid_snap);
  1232. // Begin potential panning; defer placement to mouseup if it's a click (not a drag)
  1233. drag = 'on';
  1234. pox = snap.xMouse;
  1235. poy = snap.yMouse;
  1236. // Remember initial position for click detection
  1237. window._pendingFurniturePlacement = { x: snap.x, y: snap.y, xMouse: snap.xMouse, yMouse: snap.yMouse };
  1238. event.stopPropagation();
  1239. return;
  1240. }
  1241. // *******************************************************************
  1242. // ******************** FURNITURE MODE (PAN) ********************
  1243. // *******************************************************************
  1244. if (mode == 'furniture_mode') {
  1245. // Start panning on mousedown in furniture mode (unless background tools block)
  1246. const bgTools = document.getElementById('backgroundImageTools') &&
  1247. document.getElementById('backgroundImageTools').style.display !== 'none' &&
  1248. window.getComputedStyle(document.getElementById('backgroundImageTools')).display !== 'none';
  1249. // Avoid starting pan when clicking on a furniture item (drag is handled in furniture.js)
  1250. const overFurniture = event.target && (event.target.closest && event.target.closest('.furniture-item'));
  1251. if (!bgTools && !overFurniture) {
  1252. snap = calcul_snap(event, grid_snap);
  1253. drag = 'on';
  1254. pox = snap.xMouse;
  1255. poy = snap.yMouse;
  1256. }
  1257. }
  1258. // *******************************************************************
  1259. // ********************** SELECT MODE + BIND *********************
  1260. // *******************************************************************
  1261. if (mode == 'select_mode') {
  1262. if (typeof (binder) != 'undefined' && (binder.type == 'segment' || binder.type == 'node' || binder.type == 'obj' || binder.type == 'boundingBox')) {
  1263. // In floorplan mode: disable wall/object editing interactions
  1264. if (window.__floorplanMode) {
  1265. // Do not enter bind mode or start edits when aligning to image
  1266. if (typeof $ !== 'undefined') $('#boxinfo').html('Floorplan mode: editing disabled');
  1267. return; // prevent switching to bind_mode
  1268. }
  1269. mode = 'bind_mode';
  1270. if (binder.type == 'obj') {
  1271. action = 1;
  1272. }
  1273. if (binder.type == 'boundingBox') {
  1274. action = 1;
  1275. }
  1276. // INIT FOR HELP BINDER NODE MOVING H V (MOUSE DOWN)
  1277. if (binder.type == 'node') {
  1278. $('#boxScale').hide(100);
  1279. var node = binder.data;
  1280. pox = node.x;
  1281. poy = node.y;
  1282. var nodeControl = { x: pox, y: poy };
  1283. // DETERMINATE DISTANCE OF OPPOSED NODE ON EDGE(s) PARENT(s) OF THIS NODE !!!! NODE 1 -- NODE 2 SYSTE% :-(
  1284. wallListObj = []; // SUPER VAR -- WARNING
  1285. var objWall;
  1286. wallListRun = [];
  1287. for (var ee = WALLS.length - 1; ee > -1; ee--) { // SEARCH MOST YOUNG WALL COORDS IN NODE BINDER
  1288. if (isObjectsEquals(WALLS[ee].start, nodeControl) || isObjectsEquals(WALLS[ee].end, nodeControl)) {
  1289. wallListRun.push(WALLS[ee]);
  1290. break;
  1291. }
  1292. }
  1293. if (wallListRun[0].child != null) {
  1294. if (isObjectsEquals(wallListRun[0].child.start, nodeControl) || isObjectsEquals(wallListRun[0].child.end, nodeControl)) wallListRun.push(wallListRun[0].child);
  1295. }
  1296. if (wallListRun[0].parent != null) {
  1297. if (isObjectsEquals(wallListRun[0].parent.start, nodeControl) || isObjectsEquals(wallListRun[0].parent.end, nodeControl)) wallListRun.push(wallListRun[0].parent);
  1298. }
  1299. for (var k in wallListRun) {
  1300. if (isObjectsEquals(wallListRun[k].start, nodeControl) || isObjectsEquals(wallListRun[k].end, nodeControl)) {
  1301. var nodeTarget = wallListRun[k].start;
  1302. if (isObjectsEquals(wallListRun[k].start, nodeControl)) {
  1303. nodeTarget = wallListRun[k].end;
  1304. }
  1305. objWall = editor.objFromWall(wallListRun[k]); // LIST OBJ ON EDGE -- NOT INDEX !!!
  1306. wall = wallListRun[k];
  1307. for (var ob = 0; ob < objWall.length; ob++) {
  1308. var objTarget = objWall[ob];
  1309. var distance = qSVG.measure(objTarget, nodeTarget);
  1310. wallListObj.push({ wall: wall, from: nodeTarget, distance: distance, obj: objTarget, indexObj: ob });
  1311. }
  1312. }
  1313. }
  1314. magnetic = 0;
  1315. action = 1;
  1316. }
  1317. if (binder.type == 'segment') {
  1318. $('#boxScale').hide(100);
  1319. var wall = binder.wall;
  1320. binder.before = binder.wall.start;
  1321. equation2 = editor.createEquationFromWall(wall);
  1322. if (wall.parent != null) {
  1323. equation1 = editor.createEquationFromWall(wall.parent);
  1324. var angle12 = qSVG.angleBetweenEquations(equation1.A, equation2.A);
  1325. if (angle12 < 20 || angle12 > 160) {
  1326. var found = true;
  1327. for (var k in WALLS) {
  1328. if (qSVG.rayCasting(wall.start, WALLS[k].coords) && !isObjectsEquals(WALLS[k], wall.parent) && !isObjectsEquals(WALLS[k], wall)) {
  1329. if (wall.parent.parent != null && isObjectsEquals(wall, wall.parent.parent)) wall.parent.parent = null;
  1330. if (wall.parent.child != null && isObjectsEquals(wall, wall.parent.child)) wall.parent.child = null;
  1331. wall.parent = null;
  1332. found = false;
  1333. break;
  1334. }
  1335. }
  1336. if (found) {
  1337. var newWall;
  1338. if (isObjectsEquals(wall.parent.end, wall.start, "1")) {
  1339. newWall = new editor.wall(wall.parent.end, wall.start, "normal", wall.thick);
  1340. WALLS.push(newWall);
  1341. newWall.parent = wall.parent;
  1342. newWall.child = wall;
  1343. wall.parent.child = newWall;
  1344. wall.parent = newWall;
  1345. equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
  1346. }
  1347. else if (isObjectsEquals(wall.parent.start, wall.start, "2")) {
  1348. newWall = new editor.wall(wall.parent.start, wall.start, "normal", wall.thick);
  1349. WALLS.push(newWall);
  1350. newWall.parent = wall.parent;
  1351. newWall.child = wall;
  1352. wall.parent.parent = newWall;
  1353. wall.parent = newWall;
  1354. equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
  1355. }
  1356. // CREATE NEW WALL
  1357. }
  1358. }
  1359. }
  1360. if (wall.parent == null) {
  1361. var foundEq = false;
  1362. for (var k in WALLS) {
  1363. if (qSVG.rayCasting(wall.start, WALLS[k].coords) && !isObjectsEquals(WALLS[k].coords, wall.coords)) {
  1364. var angleFollow = qSVG.angleBetweenEquations(WALLS[k].equations.base.A, equation2.A);
  1365. if (angleFollow < 20 || angleFollow > 160) break;
  1366. equation1 = editor.createEquationFromWall(WALLS[k]);
  1367. equation1.follow = WALLS[k];
  1368. equation1.backUp = {
  1369. coords: WALLS[k].coords,
  1370. start: WALLS[k].start,
  1371. end: WALLS[k].end,
  1372. child: WALLS[k].child,
  1373. parent: WALLS[k].parent
  1374. };
  1375. foundEq = true;
  1376. break;
  1377. }
  1378. }
  1379. if (!foundEq) equation1 = qSVG.perpendicularEquation(equation2, wall.start.x, wall.start.y);
  1380. }
  1381. if (wall.child != null) {
  1382. equation3 = editor.createEquationFromWall(wall.child);
  1383. var angle23 = qSVG.angleBetweenEquations(equation3.A, equation2.A);
  1384. if (angle23 < 20 || angle23 > 160) {
  1385. var found = true;
  1386. for (var k in WALLS) {
  1387. if (qSVG.rayCasting(wall.end, WALLS[k].coords) && !isObjectsEquals(WALLS[k], wall.child) && !isObjectsEquals(WALLS[k], wall)) {
  1388. if (wall.child.parent != null && isObjectsEquals(wall, wall.child.parent)) wall.child.parent = null;
  1389. if (wall.child.child != null && isObjectsEquals(wall, wall.child.child)) wall.child.child = null;
  1390. wall.child = null;
  1391. found = false;
  1392. break;
  1393. }
  1394. }
  1395. if (found) {
  1396. if (isObjectsEquals(wall.child.start, wall.end)) {
  1397. var newWall = new editor.wall(wall.end, wall.child.start, "new", wall.thick);
  1398. WALLS.push(newWall);
  1399. newWall.parent = wall;
  1400. newWall.child = wall.child;
  1401. wall.child.parent = newWall;
  1402. wall.child = newWall;
  1403. equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
  1404. }
  1405. else if (isObjectsEquals(wall.child.end, wall.end)) {
  1406. var newWall = new editor.wall(wall.end, wall.child.end, "normal", wall.thick);
  1407. WALLS.push(newWall);
  1408. newWall.parent = wall;
  1409. newWall.child = wall.child;
  1410. wall.child.child = newWall;
  1411. wall.child = newWall;
  1412. equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
  1413. }
  1414. // CREATE NEW WALL
  1415. }
  1416. }
  1417. }
  1418. if (wall.child == null) {
  1419. var foundEq = false;
  1420. for (var k in WALLS) {
  1421. if (qSVG.rayCasting(wall.end, WALLS[k].coords) && !isObjectsEquals(WALLS[k].coords, wall.coords, "4")) {
  1422. var angleFollow = qSVG.angleBetweenEquations(WALLS[k].equations.base.A, equation2.A);
  1423. if (angleFollow < 20 || angleFollow > 160) break;
  1424. equation3 = editor.createEquationFromWall(WALLS[k]);
  1425. equation3.follow = WALLS[k];
  1426. equation3.backUp = {
  1427. coords: WALLS[k].coords,
  1428. start: WALLS[k].start,
  1429. end: WALLS[k].end,
  1430. child: WALLS[k].child,
  1431. parent: WALLS[k].parent
  1432. };
  1433. foundEq = true;
  1434. break;
  1435. }
  1436. }
  1437. if (!foundEq) equation3 = qSVG.perpendicularEquation(equation2, wall.end.x, wall.end.y);
  1438. }
  1439. equationFollowers = [];
  1440. for (var k in WALLS) {
  1441. if (WALLS[k].child == null && qSVG.rayCasting(WALLS[k].end, wall.coords) && !isObjectsEquals(wall, WALLS[k])) {
  1442. equationFollowers.push({
  1443. wall: WALLS[k],
  1444. eq: editor.createEquationFromWall(WALLS[k]),
  1445. type: "end"
  1446. });
  1447. }
  1448. if (WALLS[k].parent == null && qSVG.rayCasting(WALLS[k].start, wall.coords) && !isObjectsEquals(wall, WALLS[k])) {
  1449. equationFollowers.push({
  1450. wall: WALLS[k],
  1451. eq: editor.createEquationFromWall(WALLS[k]),
  1452. type: "start"
  1453. });
  1454. }
  1455. }
  1456. equationsObj = [];
  1457. var objWall = editor.objFromWall(wall); // LIST OBJ ON EDGE
  1458. for (var ob = 0; ob < objWall.length; ob++) {
  1459. var objTarget = objWall[ob];
  1460. equationsObj.push({ obj: objTarget, wall: wall, eq: qSVG.perpendicularEquation(equation2, objTarget.x, objTarget.y) });
  1461. }
  1462. action = 1;
  1463. }
  1464. }
  1465. else {
  1466. action = 0;
  1467. drag = 'on';
  1468. snap = calcul_snap(event, grid_snap);
  1469. pox = snap.xMouse;
  1470. poy = snap.yMouse;
  1471. }
  1472. }
  1473. }
  1474. //******************************************************************************************************
  1475. //******************* ***** ****** ************************************************************
  1476. //******************* ***** ****** **** ************************************************************
  1477. //******************* ***** ****** **** ************************************************************
  1478. //******************* ***** ****** ************************************************************
  1479. //******************* ****** ******************************************************************
  1480. //********************************** ******************************************************************
  1481. function _MOUSEUP(event) {
  1482. if (showRib) $('#boxScale').show(200);
  1483. drag = 'off';
  1484. cursor('default');
  1485. // Handle deferred placement from furniture_placement_mode
  1486. if (window._pendingFurniturePlacement) {
  1487. try {
  1488. const snapUp = calcul_snap(event, grid_snap);
  1489. const dx = Math.abs(snapUp.xMouse - window._pendingFurniturePlacement.xMouse);
  1490. const dy = Math.abs(snapUp.yMouse - window._pendingFurniturePlacement.yMouse);
  1491. const moved = Math.sqrt(dx*dx + dy*dy);
  1492. if (moved < 5 && mode === 'furniture_placement_mode') {
  1493. // Consider it a click: place furniture at original snapped coords
  1494. placeFurnitureItem(window._pendingFurniturePlacement.x, window._pendingFurniturePlacement.y);
  1495. }
  1496. } catch (_) { /* no-op */ }
  1497. window._pendingFurniturePlacement = null;
  1498. }
  1499. if (mode == 'select_mode') {
  1500. if (typeof (binder) != 'undefined') {
  1501. // Handle different binder types properly
  1502. if (binder.type == 'node' && typeof binder.remove === 'function') {
  1503. binder.remove();
  1504. } else if (binder.graph && typeof binder.graph.remove === 'function') {
  1505. binder.graph.remove();
  1506. } else if (typeof binder.remove === 'function') {
  1507. binder.remove();
  1508. }
  1509. delete binder;
  1510. save();
  1511. }
  1512. }
  1513. //**************************************************************************
  1514. //******************** TEXTE MODE **************************************
  1515. //**************************************************************************
  1516. if (mode == 'text_mode') {
  1517. if (action == 0) {
  1518. action = 1;
  1519. const textModal = new bootstrap.Modal($('#textToLayer'))
  1520. textModal.show();
  1521. mode == 'edit_text_mode';
  1522. }
  1523. }
  1524. //**************************************************************************
  1525. //************** OBJECT MODE **************************************
  1526. //**************************************************************************
  1527. if (mode == 'object_mode') {
  1528. OBJDATA.push(binder);
  1529. binder.graph.remove();
  1530. var targetBox = 'boxcarpentry';
  1531. if (OBJDATA[OBJDATA.length - 1].class == 'energy') targetBox = 'boxEnergy';
  1532. if (OBJDATA[OBJDATA.length - 1].class == 'furniture') targetBox = 'boxFurniture';
  1533. $('#' + targetBox).append(OBJDATA[OBJDATA.length - 1].graph);
  1534. delete binder;
  1535. $('#boxinfo').html('Object added');
  1536. fonc_button('select_mode');
  1537. save();
  1538. }
  1539. // *******************************************************************
  1540. // ************************** DISTANCE MODE **********************
  1541. // *******************************************************************
  1542. if (mode == 'distance_mode') {
  1543. if (action == 1) {
  1544. action = 0;
  1545. // MODIFY BBOX FOR BINDER ZONE (TXT)
  1546. var bbox = labelMeasure.get(0).getBoundingClientRect();
  1547. bbox.x = (bbox.x * factor) - (offset.left * factor) + originX_viewbox;
  1548. bbox.y = (bbox.y * factor) - (offset.top * factor) + originY_viewbox;
  1549. bbox.origin = { x: bbox.x + (bbox.width / 2), y: bbox.y + (bbox.height / 2) };
  1550. binder.bbox = bbox;
  1551. binder.realBbox = [
  1552. { 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 }];
  1553. binder.size = binder.bbox.width;
  1554. binder.thick = binder.bbox.height;
  1555. binder.graph.append(labelMeasure);
  1556. OBJDATA.push(binder);
  1557. binder.graph.remove();
  1558. $('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph);
  1559. delete binder;
  1560. delete labelMeasure;
  1561. cross.remove();
  1562. delete cross;
  1563. $('#boxinfo').html('Measure added');
  1564. fonc_button('select_mode');
  1565. save();
  1566. }
  1567. }
  1568. // *******************************************************************
  1569. // ************************** ROOM MODE **************************
  1570. // *******************************************************************
  1571. if (mode == 'room_mode') {
  1572. if (typeof (binder) == "undefined") {
  1573. return false;
  1574. }
  1575. var area = binder.area / 3600;
  1576. binder.attr({ 'fill': 'none', 'stroke': '#ddf00a', 'stroke-width': 7 });
  1577. $('.size').html(area.toFixed(2) + " m²");
  1578. $('#roomIndex').val(binder.id);
  1579. if (ROOM[binder.id].surface != '') $('#roomSurface').val(ROOM[binder.id].surface);
  1580. else $('#roomSurface').val('');
  1581. document.querySelector('#seeArea').checked = ROOM[binder.id].showSurface;
  1582. document.querySelector('#roomBackground').value = ROOM[binder.id].color;
  1583. var roomName = ROOM[binder.id].name;
  1584. document.querySelector('#roomName').value = roomName;
  1585. if (ROOM[binder.id].name != '') {
  1586. document.querySelector('#roomLabel').innerHTML = roomName + ' <span class="caret"></span>';
  1587. }
  1588. else {
  1589. document.querySelector('#roomLabel').innerHTML = 'None <span class="caret"></span>';
  1590. }
  1591. var actionToDo = ROOM[binder.id].action;
  1592. document.querySelector('#' + actionToDo + 'Action').checked = true;
  1593. $('#panel').hide(100);
  1594. $('#roomTools').show('300')
  1595. $('#lin').css('cursor', 'default');
  1596. $('#boxinfo').html('Config. the room');
  1597. mode = 'edit_room_mode';
  1598. save();
  1599. }
  1600. // *******************************************************************
  1601. // ************************** NODE MODE **************************
  1602. // *******************************************************************
  1603. if (mode == 'node_mode') {
  1604. if (typeof (binder) != 'undefined') { // ALSO ON MOUSEUP WITH HAVE CIRCLEBINDER ON ADDPOINT
  1605. var newWall = new editor.wall({ x: binder.data.x, y: binder.data.y }, binder.data.wall.end, "normal", binder.data.wall.thick);
  1606. WALLS.push(newWall);
  1607. binder.data.wall.end = { x: binder.data.x, y: binder.data.y };
  1608. binder.remove();
  1609. delete binder;
  1610. editor.architect(WALLS);
  1611. save();
  1612. }
  1613. fonc_button('select_mode');
  1614. }
  1615. // ******************************************************************* ***** **** ******* ****** ****** *****
  1616. // ************************** OBJ MODE *************************** * * ******* ***** ****** ****** **
  1617. // ******************************************************************* ***** **** ****** ****** ****** ***
  1618. if (mode == 'door_mode') {
  1619. if (typeof (binder) == "undefined") {
  1620. $('#boxinfo').html('The plan currently contains no wall.');
  1621. fonc_button('select_mode');
  1622. return false;
  1623. }
  1624. OBJDATA.push(binder);
  1625. binder.graph.remove();
  1626. $('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph);
  1627. delete binder;
  1628. $('#boxinfo').html('Element added');
  1629. fonc_button('select_mode');
  1630. save();
  1631. }
  1632. // *******************************************************************
  1633. // ******************** LINE MODE MOUSE UP ***********************
  1634. // *******************************************************************
  1635. if (mode == 'line_mode' || mode == 'partition_mode') {
  1636. $('#linetemp').remove(); // DEL LINE HELP CONSTRUC 0 45 90
  1637. intersectionOff();
  1638. var sizeWall = qSVG.measure({ x: x, y: y }, { x: pox, y: poy });
  1639. sizeWall = sizeWall / meter;
  1640. if ($('#line_construc').length && sizeWall > 0.3) {
  1641. var sizeWall = wallSize;
  1642. if (mode == 'partition_mode') sizeWall = partitionSize;
  1643. var wall = new editor.wall({ x: pox, y: poy }, { x: x, y: y }, "normal", sizeWall);
  1644. WALLS.push(wall);
  1645. editor.architect(WALLS);
  1646. if (document.getElementById("multi").checked && !wallEndConstruc) {
  1647. cursor('validation');
  1648. action = 1;
  1649. }
  1650. else action = 0;
  1651. $('#boxinfo').html('Wall added <span style=\'font-size:0.6em\'>Moy. ' + (qSVG.measure(
  1652. { x: pox, y: poy }, { x: x, y: y }) / 60).toFixed(2) + ' m</span>');
  1653. $('#line_construc').remove(); // DEL LINE CONSTRUC HELP TO VIEW NEW SEG PATH
  1654. lengthTemp.remove();
  1655. delete lengthTemp;
  1656. construc = 0;
  1657. if (wallEndConstruc) action = 0;
  1658. delete wallEndConstruc;
  1659. pox = x;
  1660. poy = y;
  1661. save();
  1662. }
  1663. else {
  1664. action = 0;
  1665. construc = 0;
  1666. $('#boxinfo').html('Select mode');
  1667. fonc_button('select_mode');
  1668. if (typeof (binder) != 'undefined') {
  1669. binder.remove();
  1670. delete binder;
  1671. }
  1672. snap = calcul_snap(event, grid_snap);
  1673. pox = snap.x;
  1674. poy = snap.y;
  1675. }
  1676. }
  1677. // **************************** END LINE MODE MOUSE UP **************************
  1678. //**************************************************************************************
  1679. //********************** BIND MODE MOUSE UP ************************************
  1680. //**************************************************************************************
  1681. if (mode == 'bind_mode') {
  1682. action = 0;
  1683. construc = 0; // CONSTRUC 0 TO FREE BINDER GROUP NODE WALL MOVING
  1684. if (typeof (binder) != 'undefined') {
  1685. fonc_button('select_mode');
  1686. if (binder.type == 'node') {
  1687. // Check for zero-length walls after node movement and clean them up
  1688. var wallsToDelete = [];
  1689. for (var k in WALLS) {
  1690. var wallSize = qSVG.measure(WALLS[k].start, WALLS[k].end);
  1691. if (wallSize < 1) {
  1692. wallsToDelete.push(WALLS[k]);
  1693. }
  1694. }
  1695. // Delete zero-length walls with proper cleanup
  1696. for (var d = 0; d < wallsToDelete.length; d++) {
  1697. var wallToDelete = wallsToDelete[d];
  1698. // Clean up parent/child references before deletion
  1699. for (var cleanK in WALLS) {
  1700. if (isObjectsEquals(WALLS[cleanK].child, wallToDelete)) WALLS[cleanK].child = null;
  1701. if (isObjectsEquals(WALLS[cleanK].parent, wallToDelete)) WALLS[cleanK].parent = null;
  1702. }
  1703. // Remove the wall's visual representation
  1704. if (wallToDelete.graph) wallToDelete.graph.remove();
  1705. // Remove from WALLS array
  1706. WALLS.splice(WALLS.indexOf(wallToDelete), 1);
  1707. }
  1708. // Clean up any lingering UI elements
  1709. $('#circlebinder').remove();
  1710. $('#boxbind').empty();
  1711. // Rebuild architecture if walls were deleted
  1712. if (wallsToDelete.length > 0) {
  1713. editor.architect(WALLS);
  1714. }
  1715. } // END BINDER NODE
  1716. if (binder.type == 'segment') {
  1717. var found = false;
  1718. if (binder.wall.start == binder.before) {
  1719. found = true;
  1720. }
  1721. if (found) {
  1722. $('#panel').hide(100);
  1723. var objWall = editor.objFromWall(wallBind);
  1724. $('#boxinfo').html('Modify a wall<br/><span style="font-size:0.7em;color:#de9b43">This wall can\'t become a separation (contains doors or windows) !</span>');
  1725. if (objWall.length > 0) $('#separate').hide();
  1726. else if (binder.wall.type == 'separate') {
  1727. $('#separate').hide();
  1728. $('#rangeThick').hide();
  1729. $('#recombine').show();
  1730. $('#cutWall').hide();
  1731. document.getElementById('titleWallTools').textContent = "Modify the separation";
  1732. }
  1733. else {
  1734. $('#cutWall').show();
  1735. $('#separate').show();
  1736. $('#rangeThick').show();
  1737. $('#recombine').hide();
  1738. document.getElementById('titleWallTools').textContent = "Modify the wall";
  1739. $('#boxinfo').html('Modify the wall');
  1740. }
  1741. $('#wallTools').show(200);
  1742. document.getElementById('wallWidth').setAttribute('min', 7);
  1743. document.getElementById('wallWidth').setAttribute('max', 50);
  1744. document.getElementById('wallWidthScale').textContent = "7-50";
  1745. document.getElementById("wallWidth").value = binder.wall.thick;
  1746. document.getElementById("wallWidthVal").textContent = binder.wall.thick;
  1747. mode = 'edit_wall_mode';
  1748. }
  1749. delete equation1;
  1750. delete equation2;
  1751. delete equation3;
  1752. delete intersectionFollowers;
  1753. }
  1754. if (binder.type == 'obj') {
  1755. var moveObj = Math.abs(binder.oldXY.x - binder.x) + Math.abs(binder.oldXY.y - binder.y);
  1756. if (moveObj < 1) {
  1757. $('#panel').hide(100);
  1758. $('#objTools').show('200')
  1759. $('#lin').css('cursor', 'default');
  1760. $('#boxinfo').html('Config. the door/window');
  1761. console.log('obj ??')
  1762. document.getElementById('doorWindowWidth').setAttribute('min', binder.obj.params.resizeLimit.width.min);
  1763. document.getElementById('doorWindowWidth').setAttribute('max', binder.obj.params.resizeLimit.width.max);
  1764. document.getElementById('doorWindowWidthScale').textContent = binder.obj.params.resizeLimit.width.min + "-" + binder.obj.params.resizeLimit.width.max;
  1765. document.getElementById("doorWindowWidth").value = binder.obj.size;
  1766. document.getElementById("doorWindowWidthVal").textContent = binder.obj.size;
  1767. mode = 'edit_door_mode';
  1768. }
  1769. else {
  1770. mode = "select_mode";
  1771. action = 0;
  1772. binder.graph.remove();
  1773. delete binder;
  1774. }
  1775. }
  1776. if (typeof (binder) != 'undefined' && binder.type == 'boundingBox') {
  1777. var moveObj = Math.abs(binder.oldX - binder.x) + Math.abs(binder.oldY - binder.y);
  1778. var objTarget = binder.obj;
  1779. if (!objTarget.params.move) {
  1780. // TO REMOVE MEASURE ON PLAN
  1781. objTarget.graph.remove();
  1782. OBJDATA.splice(OBJDATA.indexOf(objTarget), 1);
  1783. $('#boxinfo').html('Measure deleted !');
  1784. }
  1785. if (moveObj < 1 && objTarget.params.move) {
  1786. if (!objTarget.params.resize) $('#objBoundingBoxScale').hide();
  1787. else $('#objBoundingBoxScale').show();
  1788. if (!objTarget.params.rotate) $('#objBoundingBoxRotation').hide();
  1789. else $('#objBoundingBoxRotation').show();
  1790. $('#panel').hide(100);
  1791. console.log(objTarget.params.resizeLimit.width.min)
  1792. $('#objBoundingBox').show('200')
  1793. $('#lin').css('cursor', 'default');
  1794. $('#boxinfo').html('Modify the object');
  1795. console.log(objTarget)
  1796. document.getElementById('bboxWidth').setAttribute('min', objTarget.params.resizeLimit.width.min);
  1797. document.getElementById('bboxWidth').setAttribute('max', objTarget.params.resizeLimit.width.max);
  1798. document.getElementById('bboxWidthScale').textContent = objTarget.params.resizeLimit.width.min + "-" + objTarget.params.resizeLimit.height.max;
  1799. document.getElementById('bboxHeight').setAttribute('min', objTarget.params.resizeLimit.height.min);
  1800. document.getElementById('bboxHeight').setAttribute('max', objTarget.params.resizeLimit.height.max);
  1801. document.getElementById('bboxHeightScale').textContent = objTarget.params.resizeLimit.height.min + "-" + objTarget.params.resizeLimit.height.max;
  1802. $('#stepsCounter').hide();
  1803. if (objTarget.class == 'stair') {
  1804. document.getElementById("bboxStepsVal").textContent = objTarget.value;
  1805. $('#stepsCounter').show();
  1806. }
  1807. document.getElementById("bboxWidth").value = objTarget.width * 100;
  1808. document.getElementById("bboxWidthVal").textContent = objTarget.width * 100;
  1809. document.getElementById("bboxHeight").value = objTarget.height * 100;
  1810. document.getElementById("bboxHeightVal").textContent = objTarget.height * 100;
  1811. document.getElementById("bboxRotation").value = objTarget.angle;
  1812. document.getElementById("bboxRotationVal").textContent = objTarget.angle;
  1813. mode = 'edit_boundingBox_mode';
  1814. }
  1815. else {
  1816. mode = "select_mode";
  1817. action = 0;
  1818. binder.graph.remove();
  1819. delete binder;
  1820. }
  1821. }
  1822. if (mode == 'bind_mode') {
  1823. binder.remove();
  1824. delete binder;
  1825. }
  1826. } // END BIND IS DEFINED
  1827. save();
  1828. } // END BIND MODE
  1829. if (mode != 'edit_room_mode') {
  1830. editor.showScaleBox();
  1831. rib();
  1832. }
  1833. }