func.js 111 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676
  1. //init
  2. WALLS = [];
  3. OBJDATA = [];
  4. ROOM = [];
  5. HISTORY = [];
  6. wallSize = 20;
  7. partitionSize = 8;
  8. let drag = 'off';
  9. let action = 0;
  10. let magnetic = 0;
  11. let construc = 0;
  12. let Rcirclebinder = 8;
  13. let mode = 'select_mode';
  14. let modeOption;
  15. let linElement = $('#lin');
  16. taille_w = linElement.width();
  17. taille_h = linElement.height();
  18. let offset = linElement.offset();
  19. // Debug: log initial floorplan image (background) width, height, and offset (x,y)
  20. try {
  21. const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null;
  22. if (bgEl) {
  23. const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; };
  24. const fw = parseNum(bgEl.getAttribute('width'));
  25. const fh = parseNum(bgEl.getAttribute('height'));
  26. const fx = parseNum(bgEl.getAttribute('x'));
  27. const fy = parseNum(bgEl.getAttribute('y'));
  28. console.info('[init] floorplan image', { width: fw, height: fh, offset: { x: fx, y: fy } });
  29. }
  30. } catch (_) {}
  31. grid = 20;
  32. showRib = true;
  33. showArea = true;
  34. meter = 60;
  35. grid_snap = 'off';
  36. colorbackground = "#ffffff";
  37. colorline = "#fff";
  38. colorroom = "#f0daaf";
  39. colorWall = "#666";
  40. pox = 0;
  41. poy = 0;
  42. segment = 0;
  43. xpath = 0;
  44. ypath = 0;
  45. let width_viewbox = taille_w;
  46. let height_viewbox = taille_h;
  47. let ratio_viewbox = height_viewbox / width_viewbox;
  48. let originX_viewbox = 0;
  49. let originY_viewbox = 0;
  50. let zoom = 9;
  51. let factor = 1;
  52. // **************************************************************************
  53. // ***************** LOAD / SAVE LOCALSTORAGE ************************
  54. // **************************************************************************
  55. function initHistory(boot = false) {
  56. HISTORY.index = 0;
  57. // Preserve existing history across sessions to allow restoring background image metrics.
  58. // Do not clear here; new plan initializers below will explicitly clear when appropriate.
  59. // if (!boot && localStorage.getItem('history')) localStorage.removeItem('history');
  60. if (localStorage.getItem('history') && boot === "recovery") {
  61. let historyTemp = JSON.parse(localStorage.getItem('history'));
  62. load(historyTemp.length - 1, "boot");
  63. save("boot");
  64. }
  65. if (boot === "newSquare") {
  66. if (localStorage.getItem('history')) localStorage.removeItem('history');
  67. HISTORY.push({
  68. "objData": [],
  69. "wallData": [{
  70. "thick": 20,
  71. "start": { "x": 540, "y": 194 },
  72. "end": { "x": 540, "y": 734 },
  73. "type": "normal",
  74. "parent": 3,
  75. "child": 1,
  76. "angle": 1.5707963267948966,
  77. "equations": { "up": { "A": "v", "B": 550 }, "down": { "A": "v", "B": 530 }, "base": { "A": "v", "B": 540 } },
  78. "coords": [{ "x": 550, "y": 204 }, { "x": 530, "y": 184 }, { "x": 530, "y": 744 }, { "x": 550, "y": 724 }],
  79. "graph": { "0": {}, "context": {}, "length": 1 }
  80. }, {
  81. "thick": 20,
  82. "start": { "x": 540, "y": 734 },
  83. "end": { "x": 1080, "y": 734 },
  84. "type": "normal",
  85. "parent": 0,
  86. "child": 2,
  87. "angle": 0,
  88. "equations": { "up": { "A": "h", "B": 724 }, "down": { "A": "h", "B": 744 }, "base": { "A": "h", "B": 734 } },
  89. "coords": [{ "x": 550, "y": 724 }, { "x": 530, "y": 744 }, { "x": 1090, "y": 744 }, { "x": 1070, "y": 724 }],
  90. "graph": { "0": {}, "context": {}, "length": 1 }
  91. }, {
  92. "thick": 20,
  93. "start": { "x": 1080, "y": 734 },
  94. "end": { "x": 1080, "y": 194 },
  95. "type": "normal",
  96. "parent": 1,
  97. "child": 3,
  98. "angle": -1.5707963267948966,
  99. "equations": {
  100. "up": { "A": "v", "B": 1070 },
  101. "down": { "A": "v", "B": 1090 },
  102. "base": { "A": "v", "B": 1080 }
  103. },
  104. "coords": [{ "x": 1070, "y": 724 }, { "x": 1090, "y": 744 }, { "x": 1090, "y": 184 }, { "x": 1070, "y": 204 }],
  105. "graph": { "0": {}, "context": {}, "length": 1 }
  106. }, {
  107. "thick": 20,
  108. "start": { "x": 1080, "y": 194 },
  109. "end": { "x": 540, "y": 194 },
  110. "type": "normal",
  111. "parent": 2,
  112. "child": 0,
  113. "angle": 3.141592653589793,
  114. "equations": { "up": { "A": "h", "B": 204 }, "down": { "A": "h", "B": 184 }, "base": { "A": "h", "B": 194 } },
  115. "coords": [{ "x": 1070, "y": 204 }, { "x": 1090, "y": 184 }, { "x": 530, "y": 184 }, { "x": 550, "y": 204 }],
  116. "graph": { "0": {}, "context": {}, "length": 1 }
  117. }],
  118. "roomData": [{
  119. "coords": [{ "x": 540, "y": 734 }, { "x": 1080, "y": 734 }, { "x": 1080, "y": 194 }, {
  120. "x": 540,
  121. "y": 194
  122. }, { "x": 540, "y": 734 }],
  123. "coordsOutside": [{ "x": 1090, "y": 744 }, { "x": 1090, "y": 184 }, { "x": 530, "y": 184 }, {
  124. "x": 530,
  125. "y": 744
  126. }, { "x": 1090, "y": 744 }],
  127. "coordsInside": [{ "x": 1070, "y": 724 }, { "x": 1070, "y": 204 }, { "x": 550, "y": 204 }, {
  128. "x": 550,
  129. "y": 724
  130. }, { "x": 1070, "y": 724 }],
  131. "inside": [],
  132. "way": ["0", "2", "3", "1", "0"],
  133. "area": 270400,
  134. "surface": "",
  135. "name": "",
  136. "color": "gradientWhite",
  137. "showSurface": true,
  138. "action": "add"
  139. }]
  140. });
  141. HISTORY[0] = JSON.stringify(HISTORY[0]);
  142. localStorage.setItem('history', JSON.stringify(HISTORY));
  143. load(0);
  144. save();
  145. }
  146. if (boot === "newL") {
  147. if (localStorage.getItem('history')) localStorage.removeItem('history');
  148. HISTORY.push({
  149. "objData": [],
  150. "wallData": [{
  151. "thick": 20,
  152. "start": { "x": 447, "y": 458 },
  153. "end": { "x": 447, "y": 744 },
  154. "type": "normal",
  155. "parent": 5,
  156. "child": 1,
  157. "angle": 1.5707963267948966,
  158. "equations": { "up": { "A": "v", "B": 457 }, "down": { "A": "v", "B": 437 }, "base": { "A": "v", "B": 447 } },
  159. "coords": [{ "x": 457, "y": 468 }, { "x": 437, "y": 448 }, { "x": 437, "y": 754 }, { "x": 457, "y": 734 }],
  160. "graph": { "0": {}, "context": {}, "length": 1 }
  161. }, {
  162. "thick": 20,
  163. "start": { "x": 447, "y": 744 },
  164. "end": { "x": 1347, "y": 744 },
  165. "type": "normal",
  166. "parent": 0,
  167. "child": 2,
  168. "angle": 0,
  169. "equations": { "up": { "A": "h", "B": 734 }, "down": { "A": "h", "B": 754 }, "base": { "A": "h", "B": 744 } },
  170. "coords": [{ "x": 457, "y": 734 }, { "x": 437, "y": 754 }, { "x": 1357, "y": 754 }, { "x": 1337, "y": 734 }],
  171. "graph": { "0": {}, "context": {}, "length": 1 }
  172. }, {
  173. "thick": 20,
  174. "start": { "x": 1347, "y": 744 },
  175. "end": { "x": 1347, "y": 144 },
  176. "type": "normal",
  177. "parent": 1,
  178. "child": 3,
  179. "angle": -1.5707963267948966,
  180. "equations": {
  181. "up": { "A": "v", "B": 1337 },
  182. "down": { "A": "v", "B": 1357 },
  183. "base": { "A": "v", "B": 1347 }
  184. },
  185. "coords": [{ "x": 1337, "y": 734 }, { "x": 1357, "y": 754 }, { "x": 1357, "y": 134 }, { "x": 1337, "y": 154 }],
  186. "graph": { "0": {}, "context": {}, "length": 1 }
  187. }, {
  188. "thick": 20,
  189. "start": { "x": 1347, "y": 144 },
  190. "end": { "x": 1020, "y": 144 },
  191. "type": "normal",
  192. "parent": 2,
  193. "child": 4,
  194. "angle": 3.141592653589793,
  195. "equations": { "up": { "A": "h", "B": 154 }, "down": { "A": "h", "B": 134 }, "base": { "A": "h", "B": 144 } },
  196. "coords": [{ "x": 1337, "y": 154 }, { "x": 1357, "y": 134 }, { "x": 1010, "y": 134 }, { "x": 1030, "y": 154 }],
  197. "graph": { "0": {}, "context": {}, "length": 1 }
  198. }, {
  199. "thick": 20,
  200. "start": { "x": 1020, "y": 144 },
  201. "end": { "x": 1020, "y": 458 },
  202. "type": "normal",
  203. "parent": 3,
  204. "child": 5,
  205. "angle": 1.5707963267948966,
  206. "equations": {
  207. "up": { "A": "v", "B": 1030 },
  208. "down": { "A": "v", "B": 1010 },
  209. "base": { "A": "v", "B": 1020 }
  210. },
  211. "coords": [{ "x": 1030, "y": 154 }, { "x": 1010, "y": 134 }, { "x": 1010, "y": 448 }, { "x": 1030, "y": 468 }],
  212. "graph": { "0": {}, "context": {}, "length": 1 }
  213. }, {
  214. "thick": 20,
  215. "start": { "x": 1020, "y": 458 },
  216. "end": { "x": 447, "y": 458 },
  217. "type": "normal",
  218. "parent": 4,
  219. "child": 0,
  220. "angle": 3.141592653589793,
  221. "equations": { "up": { "A": "h", "B": 468 }, "down": { "A": "h", "B": 448 }, "base": { "A": "h", "B": 458 } },
  222. "coords": [{ "x": 1030, "y": 468 }, { "x": 1010, "y": 448 }, { "x": 437, "y": 448 }, { "x": 457, "y": 468 }],
  223. "graph": { "0": {}, "context": {}, "length": 1 }
  224. }],
  225. "roomData": [{
  226. "coords": [{ "x": 447, "y": 744 }, { "x": 1347, "y": 744 }, { "x": 1347, "y": 144 }, {
  227. "x": 1020,
  228. "y": 144
  229. }, { "x": 1020, "y": 458 }, { "x": 447, "y": 458 }, { "x": 447, "y": 744 }],
  230. "coordsOutside": [{ "x": 1357, "y": 754 }, { "x": 1357, "y": 134 }, { "x": 1010, "y": 134 }, {
  231. "x": 1010,
  232. "y": 448
  233. }, { "x": 437, "y": 448 }, { "x": 437, "y": 754 }, { "x": 1357, "y": 754 }],
  234. "coordsInside": [{ "x": 1337, "y": 734 }, { "x": 1337, "y": 154 }, { "x": 1030, "y": 154 }, {
  235. "x": 1030,
  236. "y": 468
  237. }, { "x": 457, "y": 468 }, { "x": 457, "y": 734 }, { "x": 1337, "y": 734 }],
  238. "inside": [],
  239. "way": ["0", "2", "3", "4", "5", "1", "0"],
  240. "area": 330478,
  241. "surface": "",
  242. "name": "",
  243. "color": "gradientWhite",
  244. "showSurface": true,
  245. "action": "add"
  246. }]
  247. });
  248. HISTORY[0] = JSON.stringify(HISTORY[0]);
  249. localStorage.setItem('history', JSON.stringify(HISTORY));
  250. load(0);
  251. save();
  252. }
  253. }
  254. document.getElementById('redo').addEventListener("click", function () {
  255. if (HISTORY.index < HISTORY.length) {
  256. load(HISTORY.index);
  257. HISTORY.index++;
  258. $('#undo').removeClass('disabled');
  259. if (HISTORY.index === HISTORY.length) {
  260. $('#redo').addClass('disabled');
  261. }
  262. }
  263. });
  264. document.getElementById('undo').addEventListener("click", function () {
  265. if (HISTORY.index > 0) {
  266. $('#undo').removeClass('disabled');
  267. if (HISTORY.index - 1 > 0) {
  268. HISTORY.index--;
  269. load(HISTORY.index - 1);
  270. $('#redo').removeClass('disabled');
  271. }
  272. }
  273. if (HISTORY.index === 1) $('#undo').addClass('disabled');
  274. });
  275. function save(boot = false) {
  276. // Only clear history on explicit boolean true; avoid clearing when called with strings like "boot"
  277. if (boot === true) localStorage.removeItem('history');
  278. // If background image sizing is in progress, defer save to avoid capturing default geometry
  279. try {
  280. if (typeof window !== 'undefined' && window.__bgSizing) {
  281. if (typeof console !== 'undefined' && console.debug) {
  282. console.debug('[save] deferred: background image sizing in progress');
  283. }
  284. setTimeout(function(){ try { save(boot); } catch(_){} }, 50);
  285. return false;
  286. }
  287. } catch(_) {}
  288. // FOR CYCLIC OBJ INTO LOCALSTORAGE !!!
  289. for (let k in WALLS) {
  290. if (WALLS[k].child != null) {
  291. WALLS[k].child = WALLS.indexOf(WALLS[k].child);
  292. }
  293. if (WALLS[k].parent != null) {
  294. WALLS[k].parent = WALLS.indexOf(WALLS[k].parent);
  295. }
  296. }
  297. // Gather background image state if present; if element is absent, carry over from previous snapshot to avoid reset
  298. const __bgImgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null;
  299. // Read previous snapshot (if any) to preserve background image when not present in DOM
  300. let __prevSnap = null;
  301. try { if (HISTORY && HISTORY.length > 0) { __prevSnap = JSON.parse(HISTORY[HISTORY.length - 1]); } } catch(_) {}
  302. // Fallback: read from localStorage history if in-memory HISTORY is empty or unparsable
  303. if (!__prevSnap) {
  304. try {
  305. const __ls = localStorage.getItem('history');
  306. if (__ls) {
  307. const __arr = JSON.parse(__ls);
  308. if (Array.isArray(__arr) && __arr.length > 0) {
  309. __prevSnap = JSON.parse(__arr[__arr.length - 1]);
  310. }
  311. }
  312. } catch(_) {}
  313. }
  314. const backgroundImage = (__bgImgEl) ? (function(el){
  315. const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; };
  316. const x = parseNum(el.getAttribute('x'), 0);
  317. const y = parseNum(el.getAttribute('y'), 0);
  318. const width = parseNum(el.getAttribute('width'), 0);
  319. const height = parseNum(el.getAttribute('height'), 0);
  320. let opacity = parseNum(el.getAttribute('opacity'), 1);
  321. if (!isFinite(opacity) || opacity <= 0) opacity = 1;
  322. const href = el.getAttribute('href') || el.getAttribute('xlink:href') || '';
  323. // Try to attach fileName from currentBackgroundImage reference if available
  324. let fileName = null;
  325. try { if (window.currentBackgroundImage && window.currentBackgroundImage.fileName) fileName = window.currentBackgroundImage.fileName; } catch(_) {}
  326. const snapshot = { x, y, width, height, opacity, href, fileName };
  327. try { if (typeof console !== 'undefined' && console.debug) console.debug('[save] backgroundImage snapshot', snapshot); } catch(_) {}
  328. return snapshot;
  329. })(__bgImgEl) : (__prevSnap && __prevSnap.backgroundImage ? __prevSnap.backgroundImage : null);
  330. const snapshot = { objData: OBJDATA, wallData: WALLS, roomData: ROOM, furnitureData: getFurnitureData(), backgroundImage };
  331. // If caller requests suppression and we'd drop background image (DOM missing) while previous snapshot has one, skip pushing
  332. try {
  333. if (typeof window !== 'undefined' && window.__suppressSaveIfNoBg) {
  334. const hasDomBg = !!__bgImgEl;
  335. const hadPrevBg = !!(__prevSnap && __prevSnap.backgroundImage);
  336. if (!hasDomBg && hadPrevBg) {
  337. if (typeof console !== 'undefined' && console.debug) {
  338. console.debug('[save] suppressed: no DOM backgroundImage while previous snapshot had one');
  339. }
  340. // Clear the suppression flag for subsequent saves
  341. window.__suppressSaveIfNoBg = false;
  342. return false;
  343. }
  344. // Clear flag if we proceed
  345. window.__suppressSaveIfNoBg = false;
  346. }
  347. } catch(_) {}
  348. if (JSON.stringify(snapshot) === HISTORY[HISTORY.length - 1]) {
  349. for (let k in WALLS) {
  350. if (WALLS[k].child != null) {
  351. WALLS[k].child = WALLS[WALLS[k].child];
  352. }
  353. if (WALLS[k].parent != null) {
  354. WALLS[k].parent = WALLS[WALLS[k].parent];
  355. }
  356. }
  357. return false;
  358. }
  359. if (HISTORY.index < HISTORY.length) {
  360. HISTORY.splice(HISTORY.index, (HISTORY.length - HISTORY.index));
  361. $('#redo').addClass('disabled');
  362. }
  363. HISTORY.push(JSON.stringify(snapshot));
  364. localStorage.setItem('history', JSON.stringify(HISTORY));
  365. HISTORY.index++;
  366. // Log when state is saved to history along with current floorplan image metrics
  367. try {
  368. const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null;
  369. if (bgEl) {
  370. const parseNum = (v, d=0) => { const n = parseFloat(v); return isFinite(n) ? n : d; };
  371. const fw = parseNum(bgEl.getAttribute('width'));
  372. const fh = parseNum(bgEl.getAttribute('height'));
  373. const fx = parseNum(bgEl.getAttribute('x'));
  374. const fy = parseNum(bgEl.getAttribute('y'));
  375. console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, width: fw, height: fh, offset: { x: fx, y: fy } });
  376. } else if (backgroundImage) {
  377. console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, width: backgroundImage.width, height: backgroundImage.height, offset: { x: backgroundImage.x, y: backgroundImage.y }, note: 'carried over previous backgroundImage' });
  378. } else {
  379. console.info('[save] snapshot pushed', { historyIndex: HISTORY.index, note: 'no backgroundImage' });
  380. }
  381. } catch (_) {}
  382. if (HISTORY.index > 1) $('#undo').removeClass('disabled');
  383. for (let k in WALLS) {
  384. if (WALLS[k].child != null) {
  385. WALLS[k].child = WALLS[WALLS[k].child];
  386. }
  387. if (WALLS[k].parent != null) {
  388. WALLS[k].parent = WALLS[WALLS[k].parent];
  389. }
  390. }
  391. return true;
  392. }
  393. function load(index = HISTORY.index, boot = false) {
  394. if (HISTORY.length === 0 && !boot) return false;
  395. for (let k in OBJDATA) {
  396. OBJDATA[k].graph.remove();
  397. }
  398. OBJDATA = [];
  399. let historyTemp = [];
  400. historyTemp = JSON.parse(localStorage.getItem('history'));
  401. historyTemp = JSON.parse(historyTemp[index]);
  402. for (let k in historyTemp.objData) {
  403. let OO = historyTemp.objData[k];
  404. // if (OO.family === 'energy') OO.family = 'byObject';
  405. let obj = new editor.obj2D(OO.family, OO.class, OO.type, {
  406. x: OO.x,
  407. y: OO.y
  408. }, OO.angle, OO.angleSign, OO.size, OO.hinge = 'normal', OO.thick, OO.value);
  409. obj.limit = OO.limit;
  410. OBJDATA.push(obj);
  411. $('#boxcarpentry').append(OBJDATA[OBJDATA.length - 1].graph);
  412. obj.update();
  413. }
  414. WALLS = historyTemp.wallData;
  415. for (let k in WALLS) {
  416. if (WALLS[k].child != null) {
  417. WALLS[k].child = WALLS[WALLS[k].child];
  418. }
  419. if (WALLS[k].parent != null) {
  420. WALLS[k].parent = WALLS[WALLS[k].parent];
  421. }
  422. }
  423. ROOM = historyTemp.roomData;
  424. // Load furniture data if it exists
  425. if (historyTemp.furnitureData) {
  426. loadSavedFurnitureData(historyTemp.furnitureData);
  427. }
  428. // Restore background image properties if present and an image exists in DOM
  429. try {
  430. if (historyTemp.backgroundImage) {
  431. const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null;
  432. if (bgEl) {
  433. // Only restore if it's the same image as saved (match href)
  434. const currentHref = bgEl.getAttribute('href') || bgEl.getAttribute('xlink:href') || '';
  435. const savedHref = historyTemp.backgroundImage.href || '';
  436. try { if (console && console.debug) console.debug('[load] backgroundImage found', { currentHref, savedHref, props: historyTemp.backgroundImage }); } catch(_) {}
  437. if (savedHref && currentHref && currentHref === savedHref) {
  438. try { if (console && console.debug) console.debug('[load] applying saved backgroundImage props'); } catch(_) {}
  439. if (typeof adjustBackgroundImage === 'function') {
  440. adjustBackgroundImage(historyTemp.backgroundImage);
  441. } else {
  442. const props = historyTemp.backgroundImage;
  443. if (props.x !== undefined) bgEl.setAttribute('x', props.x);
  444. if (props.y !== undefined) bgEl.setAttribute('y', props.y);
  445. if (props.width !== undefined) bgEl.setAttribute('width', props.width);
  446. if (props.height !== undefined) bgEl.setAttribute('height', props.height);
  447. if (props.opacity !== undefined) bgEl.setAttribute('opacity', props.opacity);
  448. }
  449. } else {
  450. try { if (console && console.debug) console.debug('[load] skipping apply: href mismatch or missing'); } catch(_) {}
  451. }
  452. } else {
  453. try { if (console && console.debug) console.debug('[load] no #backgroundImage element to restore to'); } catch(_) {}
  454. }
  455. }
  456. } catch (e) {
  457. console.warn('Error restoring background image properties:', e);
  458. }
  459. // Update UI: filename display and Floorplan mode button based on background image presence
  460. try {
  461. const bgEl = (typeof document !== 'undefined') ? document.getElementById('backgroundImage') : null;
  462. const nameEl = (typeof document !== 'undefined') ? document.getElementById('floorplan_filename') : null;
  463. const btn = (typeof document !== 'undefined') ? document.getElementById('floorplan_mode_btn') : null;
  464. if (bgEl) {
  465. // Enable button and show saved filename if available
  466. if (btn) btn.disabled = false;
  467. if (nameEl) {
  468. const savedName = (historyTemp.backgroundImage && historyTemp.backgroundImage.fileName) ? historyTemp.backgroundImage.fileName : '';
  469. nameEl.textContent = savedName;
  470. }
  471. } else {
  472. // No background image element yet: show last used filename if available but keep button disabled
  473. if (nameEl) {
  474. const savedName = (historyTemp.backgroundImage && historyTemp.backgroundImage.fileName) ? historyTemp.backgroundImage.fileName : '';
  475. nameEl.textContent = savedName;
  476. }
  477. if (btn) {
  478. btn.disabled = true;
  479. btn.innerText = 'Floorplan mode';
  480. }
  481. if (window.__floorplanMode && typeof exitFloorplanMode === 'function') {
  482. exitFloorplanMode();
  483. }
  484. }
  485. } catch(_) {}
  486. editor.architect(WALLS);
  487. editor.showScaleBox();
  488. rib();
  489. // Ensure we default to select mode after loading a snapshot
  490. try {
  491. if (typeof $ !== 'undefined') {
  492. $('#boxinfo').html('Mode "select"');
  493. // Clear any lingering binders
  494. if (typeof binder !== 'undefined') {
  495. try { if (binder.graph) $(binder.graph).remove(); else if (binder.remove) binder.remove(); } catch (e) {}
  496. $('#boxbind').empty();
  497. binder = undefined;
  498. }
  499. // Update buttons UI without triggering a save (avoid fonc_button which calls save())
  500. if (typeof raz_button === 'function') raz_button();
  501. $('#select_mode').removeClass('btn-default').addClass('btn-success');
  502. }
  503. mode = 'select_mode';
  504. } catch(_) {}
  505. }
  506. $('svg').each(function () {
  507. $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox)
  508. });
  509. // **************************************************************************
  510. // ***************** FUNCTIONS ON BUTTON click ************************
  511. // **************************************************************************
  512. document.getElementById('report_mode').addEventListener("click", function () {
  513. if (typeof (globalArea) === "undefined") return false;
  514. mode = "report_mode";
  515. $('#panel').hide();
  516. $('#reportTools').show(200)
  517. document.getElementById('reportTotalSurface').innerHTML = "Total surface : <b>" + (globalArea / 3600).toFixed(1) + "</b> m²";
  518. $('#reportTotalSurface').show(1000);
  519. document.getElementById('reportNumberSurface').innerHTML = "Number of rooms : <b>" + ROOM.length + "</b>";
  520. $('#reportNumberSurface').show(1000);
  521. let number = 1;
  522. let reportRoom = '<div class="row">\n';
  523. for (let k in ROOM) {
  524. let nameRoom = "Room n°" + number + " <small>(sans nom)</small>";
  525. if (ROOM[k].name != "") nameRoom = ROOM[k].name;
  526. reportRoom += '<div class="col-md-6"><p>' + nameRoom + '</p></div>\n';
  527. reportRoom += '<div class="col-md-6"><p>Surface : <b>' + ((ROOM[k].area) / 3600).toFixed(2) + '</b> m²</p></div>\n';
  528. number++;
  529. }
  530. reportRoom += '</div><hr/>\n';
  531. reportRoom += '<div>\n';
  532. let switchNumber = 0;
  533. let plugNumber = 0;
  534. let lampNumber = 0;
  535. for (let k in OBJDATA) {
  536. if (OBJDATA[k].class === 'energy') {
  537. if (OBJDATA[k].type === 'switch' || OBJDATA[k].type === 'doubleSwitch' || OBJDATA[k].type === 'dimmer') switchNumber++;
  538. if (OBJDATA[k].type === 'plug' || OBJDATA[k].type === 'plug20' || OBJDATA[k].type === 'plug32') plugNumber++;
  539. if (OBJDATA[k].type === 'wallLight' || OBJDATA[k].type === 'roofLight') lampNumber++;
  540. }
  541. }
  542. reportRoom += '<p>Switch number : ' + switchNumber + '</p>';
  543. reportRoom += '<p>Electric outlet number : ' + plugNumber + '</p>';
  544. reportRoom += '<p>Light point number : ' + lampNumber + '</p>';
  545. reportRoom += '</div>';
  546. reportRoom += '<div>\n';
  547. reportRoom += '<h2>Energy distribution per room</h2>\n';
  548. number = 1;
  549. reportRoom += '<div class="row">\n';
  550. reportRoom += '<div class="col-md-4"><p>Label</p></div>\n';
  551. reportRoom += '<div class="col-md-2"><small>Swi.</small></div>\n';
  552. reportRoom += '<div class="col-md-2"><small>Elec. out.</small></div>\n';
  553. reportRoom += '<div class="col-md-2"><small>Light.</small></div>\n';
  554. reportRoom += '<div class="col-md-2"><small>Watts Max</small></div>\n';
  555. reportRoom += '</div>';
  556. let roomEnergy = [];
  557. for (let k in ROOM) {
  558. reportRoom += '<div class="row">\n';
  559. let nameRoom = "Room n°" + number + " <small>(no name)</small>";
  560. if (ROOM[k].name != "") nameRoom = ROOM[k].name;
  561. reportRoom += '<div class="col-md-4"><p>' + nameRoom + '</p></div>\n';
  562. switchNumber = 0;
  563. plugNumber = 0;
  564. let plug20 = 0;
  565. let plug32 = 0;
  566. lampNumber = 0;
  567. let wattMax = 0;
  568. let plug = false;
  569. for (let i in OBJDATA) {
  570. if (OBJDATA[i].class === 'energy') {
  571. if (OBJDATA[i].type === 'switch' || OBJDATA[i].type === 'doubleSwitch' || OBJDATA[i].type === 'dimmer') {
  572. if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) {
  573. if (isObjectsEquals(ROOM[k], roomTarget)) switchNumber++;
  574. }
  575. }
  576. if (OBJDATA[i].type === 'plug' || OBJDATA[i].type === 'plug20' || OBJDATA[i].type === 'plug32') {
  577. if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) {
  578. if (isObjectsEquals(ROOM[k], roomTarget)) {
  579. plugNumber++;
  580. if (OBJDATA[i].type === 'plug' && !plug) {
  581. wattMax += 3520;
  582. plug = true;
  583. }
  584. if (OBJDATA[i].type === 'plug20') {
  585. wattMax += 4400;
  586. plug20++;
  587. }
  588. if (OBJDATA[i].type === 'plug32') {
  589. wattMax += 7040;
  590. plug32++;
  591. }
  592. }
  593. }
  594. }
  595. if (OBJDATA[i].type === 'wallLight' || OBJDATA[i].type === 'roofLight') {
  596. if (roomTarget = editor.rayCastingRoom(OBJDATA[i])) {
  597. if (isObjectsEquals(ROOM[k], roomTarget)) {
  598. lampNumber++;
  599. wattMax += 100;
  600. }
  601. }
  602. }
  603. }
  604. }
  605. roomEnergy.push({
  606. switch: switchNumber,
  607. plug: plugNumber,
  608. plug20: plug20,
  609. plug32: plug32,
  610. light: lampNumber
  611. });
  612. reportRoom += '<div class="col-md-2"><b>' + switchNumber + '</b></div>\n';
  613. reportRoom += '<div class="col-md-2"><b>' + plugNumber + '</b></div>\n';
  614. reportRoom += '<div class="col-md-2"><b>' + lampNumber + '</b></div>\n';
  615. reportRoom += '<div class="col-md-2"><b>' + wattMax + '</b></div>\n';
  616. number++;
  617. reportRoom += '</div>';
  618. }
  619. reportRoom += '<hr/><h2>Standard details NF C 15-100</h2>\n';
  620. number = 1;
  621. for (let k in ROOM) {
  622. reportRoom += '<div class="row">\n';
  623. let nfc = true;
  624. let nameRoom = "Room n°" + number + " <small>(no name)</small>";
  625. if (ROOM[k].name != "") nameRoom = ROOM[k].name;
  626. reportRoom += '<div class="col-md-4"><p>' + nameRoom + '</p></div>\n';
  627. if (ROOM[k].name === "") {
  628. reportRoom +=
  629. '<div class="col-md-8"><p><i class="fa fa-ban" aria-hidden="true" style="color:red"></i> The room has no label, Home Rough Editor cannot provide you with information.</p></div>\n';
  630. } else {
  631. if (ROOM[k].name === "Salon") {
  632. for (let g in ROOM) {
  633. if (ROOM[g].name === "Salle à manger") {
  634. roomEnergy[k].light += roomEnergy[g].light;
  635. roomEnergy[k].plug += roomEnergy[g].plug;
  636. roomEnergy[k].switch += roomEnergy[g].switch;
  637. }
  638. }
  639. reportRoom += '<div class="col-md-8">';
  640. if (roomEnergy[k].light === 0) {
  641. reportRoom +=
  642. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 controlled light point</b> <small>(actually ' +
  643. roomEnergy[k].light + ')</small>.</p>\n';
  644. nfc = false;
  645. }
  646. if (roomEnergy[k].plug < 5) {
  647. reportRoom +=
  648. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>5 power outlets</b> <small>(actually ' +
  649. roomEnergy[k].plug + ')</small>.</p>\n';
  650. nfc = false;
  651. }
  652. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  653. reportRoom += '</div>';
  654. }
  655. if (ROOM[k].name === "Salle à manger") {
  656. reportRoom +=
  657. '<div class="col-md-8"><p><i class="fa fa-info" aria-hidden="true" style="color:blue"></i> This room is linked to the <b>living room / living room</b> according to the standard.</p></div>\n';
  658. }
  659. if (ROOM[k].name.substr(0, 7) === "Chambre") {
  660. reportRoom += '<div class="col-md-8">';
  661. if (roomEnergy[k].light === 0) {
  662. reportRoom +=
  663. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 controlled light point</b> <small>(actually ' +
  664. roomEnergy[k].light + ')</small>.</p>\n';
  665. nfc = false;
  666. }
  667. if (roomEnergy[k].plug < 3) {
  668. reportRoom +=
  669. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>3 power outlets</b> <small>(actually ' +
  670. roomEnergy[k].plug + ')</small>.</p>\n';
  671. nfc = false;
  672. }
  673. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  674. reportRoom += '</div>';
  675. }
  676. if (ROOM[k].name === "SdB") {
  677. reportRoom += '<div class="col-md-8">';
  678. if (roomEnergy[k].light === 0) {
  679. reportRoom +=
  680. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 light point</b> <small>(actually ' +
  681. roomEnergy[k].light + ')</small>.</p>\n';
  682. nfc = false;
  683. }
  684. if (roomEnergy[k].plug < 2) {
  685. reportRoom +=
  686. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>2 power outlets</b> <small>(actually ' +
  687. roomEnergy[k].plug + ')</small>.</p>\n';
  688. nfc = false;
  689. }
  690. if (roomEnergy[k].switch === 0) {
  691. reportRoom +=
  692. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 switch</b> <small>(actually ' +
  693. roomEnergy[k].switch + ')</small>.</p>\n';
  694. nfc = false;
  695. }
  696. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  697. reportRoom += '</div>';
  698. }
  699. if (ROOM[k].name === "Couloir") {
  700. reportRoom += '<div class="col-md-8">';
  701. if (roomEnergy[k].light === 0) {
  702. reportRoom +=
  703. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 controlled light point</b> <small>(actually ' +
  704. roomEnergy[k].light + ')</small>.</p>\n';
  705. nfc = false;
  706. }
  707. if (roomEnergy[k].plug < 1) {
  708. reportRoom +=
  709. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 power outlet</b> <small>(actually ' +
  710. roomEnergy[k].plug + ')</small>.</p>\n';
  711. nfc = false;
  712. }
  713. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  714. reportRoom += '</div>';
  715. }
  716. if (ROOM[k].name === "Toilette") {
  717. reportRoom += '<div class="col-md-8">';
  718. if (roomEnergy[k].light === 0) {
  719. reportRoom +=
  720. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 light point</b>. <small>(actually ' +
  721. roomEnergy[k].light + ')</small>.</p>\n';
  722. nfc = false;
  723. }
  724. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  725. reportRoom += '</div>';
  726. }
  727. if (ROOM[k].name === "Cuisine") {
  728. reportRoom += '<div class="col-md-8">';
  729. if (roomEnergy[k].light === 0) {
  730. reportRoom +=
  731. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 controlled light point</b> <small>(actually ' +
  732. roomEnergy[k].light + ')</small>.</p>\n';
  733. nfc = false;
  734. }
  735. if (roomEnergy[k].plug < 6) {
  736. reportRoom +=
  737. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>6 power outlets</b> <small>(actually ' +
  738. roomEnergy[k].plug + ')</small>.</p>\n';
  739. nfc = false;
  740. }
  741. if (roomEnergy[k].plug32 === 0) {
  742. reportRoom +=
  743. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>1 32A power outlet</b> <small>(actually ' +
  744. roomEnergy[k].plug32 + ')</small>.</p>\n';
  745. nfc = false;
  746. }
  747. if (roomEnergy[k].plug20 < 2) {
  748. reportRoom +=
  749. '<p><i class="fa fa-exclamation-triangle" style="color:orange" aria-hidden="true"></i> This room must have at least <b>2 20A power outlets</b> <small>(actually ' +
  750. roomEnergy[k].plug20 + ')</small>.</p>\n';
  751. nfc = false;
  752. }
  753. if (nfc) reportRoom += '<i class="fa fa-check" aria-hidden="true" style="color:green"></i>';
  754. reportRoom += '</div>';
  755. }
  756. }
  757. number++;
  758. reportRoom += '</div>';
  759. }
  760. document.getElementById('reportRooms').innerHTML = reportRoom;
  761. $('#reportRooms').show(1000);
  762. });
  763. document.getElementById('wallWidth').addEventListener("input", function () {
  764. let sliderValue = this.value;
  765. binder.wall.thick = sliderValue;
  766. binder.wall.type = "normal";
  767. editor.architect(WALLS);
  768. let objWall = editor.objFromWall(binder.wall); // LIST OBJ ON EDGE
  769. for (let w = 0; w < objWall.length; w++) {
  770. objWall[w].thick = sliderValue;
  771. objWall[w].update();
  772. }
  773. rib();
  774. document.getElementById("wallWidthVal").textContent = sliderValue;
  775. });
  776. document.getElementById("bboxTrash").addEventListener("click", function () {
  777. binder.obj.graph.remove();
  778. binder.graph.remove();
  779. OBJDATA.splice(OBJDATA.indexOf(binder.obj), 1);
  780. $('#objBoundingBox').hide(100);
  781. $('#panel').show(200);
  782. fonc_button('select_mode');
  783. $('#boxinfo').html('Deleted object');
  784. delete binder;
  785. rib();
  786. });
  787. document.getElementById("bboxStepsAdd").addEventListener("click", function () {
  788. let newValue = document.getElementById("bboxStepsVal").textContent;
  789. if (newValue < 15) {
  790. newValue++;
  791. binder.obj.value = newValue;
  792. binder.obj.update();
  793. document.getElementById("bboxStepsVal").textContent = newValue;
  794. }
  795. });
  796. document.getElementById("bboxStepsMinus").addEventListener("click", function () {
  797. let newValue = document.getElementById("bboxStepsVal").textContent;
  798. if (newValue > 2) {
  799. newValue--;
  800. binder.obj.value = newValue;
  801. binder.obj.update();
  802. document.getElementById("bboxStepsVal").textContent = newValue;
  803. }
  804. });
  805. document.getElementById('bboxWidth').addEventListener("input", function () {
  806. let sliderValue = this.value;
  807. let objTarget = binder.obj;
  808. objTarget.size = (sliderValue / 100) * meter;
  809. objTarget.update();
  810. binder.size = (sliderValue / 100) * meter;
  811. binder.update();
  812. document.getElementById("bboxWidthVal").textContent = sliderValue;
  813. });
  814. document.getElementById('bboxHeight').addEventListener("input", function () {
  815. let sliderValue = this.value;
  816. let objTarget = binder.obj;
  817. objTarget.thick = (sliderValue / 100) * meter;
  818. objTarget.update();
  819. binder.thick = (sliderValue / 100) * meter;
  820. binder.update();
  821. document.getElementById("bboxHeightVal").textContent = sliderValue;
  822. });
  823. document.getElementById('bboxRotation').addEventListener("input", function () {
  824. let sliderValue = this.value;
  825. let objTarget = binder.obj;
  826. objTarget.angle = sliderValue;
  827. objTarget.update();
  828. binder.angle = sliderValue;
  829. binder.update();
  830. document.getElementById("bboxRotationVal").textContent = sliderValue;
  831. });
  832. document.getElementById('doorWindowWidth').addEventListener("input", function () {
  833. let sliderValue = this.value;
  834. let objTarget = binder.obj;
  835. let wallBind = editor.rayCastingWalls(objTarget, WALLS);
  836. if (wallBind.length > 1) {
  837. wallBind = wallBind[wallBind.length - 1];
  838. }
  839. let limits = limitObj(wallBind.equations.base, sliderValue, objTarget);
  840. if (qSVG.btwn(limits[1].x, wallBind.start.x, wallBind.end.x) && qSVG.btwn(limits[1].y, wallBind.start.y, wallBind.end.y) &&
  841. qSVG.btwn(limits[0].x, wallBind.start.x, wallBind.end.x) && qSVG.btwn(limits[0].y, wallBind.start.y, wallBind.end.y)) {
  842. objTarget.size = sliderValue;
  843. objTarget.limit = limits;
  844. objTarget.update();
  845. binder.size = sliderValue;
  846. binder.limit = limits;
  847. binder.update();
  848. document.getElementById("doorWindowWidthVal").textContent = sliderValue;
  849. }
  850. inWallRib(wallBind);
  851. });
  852. document.getElementById("objToolsHinge").addEventListener("click", function () {
  853. let objTarget = binder.obj;
  854. let hingeStatus = objTarget.hinge; // normal - reverse
  855. if (hingeStatus === 'normal') {
  856. objTarget.hinge = 'reverse';
  857. } else objTarget.hinge = 'normal';
  858. objTarget.update();
  859. });
  860. window.addEventListener("load", function () {
  861. document.getElementById('panel').style.transform = "translateX(200px)";
  862. document.getElementById('panel').addEventListener("transitionend", function () {
  863. document.getElementById('moveBox').style.transform = "translateX(-165px)";
  864. document.getElementById('zoomBox').style.transform = "translateX(-165px)";
  865. });
  866. if (!localStorage.getItem('history')) {
  867. $('#recover').html("<p>Select a plan type.");
  868. }
  869. const myModal = new bootstrap.Modal($('#myModal'))
  870. myModal.show();
  871. });
  872. document.getElementById('sizePolice').addEventListener("input", function () {
  873. document.getElementById('labelBox').style.fontSize = this.value + 'px';
  874. });
  875. $('#textToLayer').on('hidden.bs.modal', function (e) {
  876. fonc_button('select_mode');
  877. action = 0;
  878. let textToMake = document.getElementById('labelBox').textContent;
  879. if (textToMake != "" && textToMake != "Your text") {
  880. binder = new editor.obj2D("free", "text", document.getElementById('labelBox').style.color, snap, 0, 0, 0, "normal", 0, {
  881. text: textToMake,
  882. size: document.getElementById('sizePolice').value
  883. });
  884. binder.update();
  885. OBJDATA.push(binder);
  886. binder.graph.remove();
  887. $('#boxText').append(OBJDATA[OBJDATA.length - 1].graph);
  888. OBJDATA[OBJDATA.length - 1].update();
  889. delete binder;
  890. $('#boxinfo').html('Added text');
  891. save();
  892. } else {
  893. $('#boxinfo').html('Selection mode');
  894. }
  895. document.getElementById('labelBox').textContent = "Your text";
  896. document.getElementById('labelBox').style.color = "#333333";
  897. document.getElementById('labelBox').style.fontSize = "15px";
  898. document.getElementById('sizePolice').value = 15;
  899. });
  900. if (!Array.prototype.includes) {
  901. Object.defineProperty(Array.prototype, 'includes', {
  902. value: function (searchElement, fromIndex) {
  903. if (this === null) {
  904. throw new TypeError('"this" is null or not defined');
  905. }
  906. let o = Object(this);
  907. let len = o.length >>> 0;
  908. if (len === 0) {
  909. return false;
  910. }
  911. let n = fromIndex | 0;
  912. let k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
  913. while (k < len) {
  914. if (o[k] === searchElement) {
  915. return true;
  916. }
  917. k++;
  918. }
  919. return false;
  920. }
  921. });
  922. }
  923. function isObjectsEquals(a, b, message = false) {
  924. if (message) console.log(message)
  925. let isOK = true;
  926. for (let prop in a) {
  927. if (a[prop] !== b[prop]) {
  928. isOK = false;
  929. break;
  930. }
  931. }
  932. return isOK;
  933. };
  934. function throttle(callback, delay) {
  935. let last;
  936. let timer;
  937. return function () {
  938. let context = this;
  939. let now = +new Date();
  940. let args = arguments;
  941. if (last && now < last + delay) {
  942. // le délai n'est pas écoulé on reset le timer
  943. clearTimeout(timer);
  944. timer = setTimeout(function () {
  945. last = now;
  946. callback.apply(context, args);
  947. }, delay);
  948. } else {
  949. last = now;
  950. callback.apply(context, args);
  951. }
  952. };
  953. }
  954. linElement.mousewheel(throttle(function (event) {
  955. event.preventDefault();
  956. if (event.deltaY > 0) {
  957. zoom_maker('zoomin', 200);
  958. } else {
  959. zoom_maker('zoomout', 200);
  960. }
  961. }, 100));
  962. document.getElementById("showRib").addEventListener("click", function () {
  963. if (document.getElementById("showRib").checked) {
  964. $('#boxScale').show(200);
  965. $('#boxRib').show(200);
  966. showRib = true;
  967. } else {
  968. $('#boxScale').hide(100);
  969. $('#boxRib').hide(100);
  970. showRib = false;
  971. }
  972. });
  973. document.getElementById("showArea").addEventListener("click", function () {
  974. if (document.getElementById("showArea").checked) {
  975. $('#boxArea').show(200);
  976. } else {
  977. $('#boxArea').hide(100);
  978. }
  979. });
  980. document.getElementById("showLayerRoom").addEventListener("click", function () {
  981. if (document.getElementById("showLayerRoom").checked) {
  982. $('#boxRoom').show(200);
  983. } else {
  984. $('#boxRoom').hide(100);
  985. }
  986. });
  987. document.getElementById("showLayerEnergy").addEventListener("click", function () {
  988. if (document.getElementById("showLayerEnergy").checked) {
  989. $('#boxEnergy').show(200);
  990. } else {
  991. $('#boxEnergy').hide(100);
  992. }
  993. });
  994. document.getElementById("showLayerBackground").addEventListener("click", function () {
  995. const backgroundImage = document.getElementById('backgroundImage');
  996. if (backgroundImage) {
  997. if (document.getElementById("showLayerBackground").checked) {
  998. backgroundImage.style.display = 'block';
  999. } else {
  1000. backgroundImage.style.display = 'none';
  1001. }
  1002. }
  1003. });
  1004. // document.getElementById("showLayerFurniture").addEventListener("click", function () {
  1005. // if (document.getElementById("showLayerFurniture").checked) {
  1006. // $('#boxFurniture').show(200);
  1007. // }
  1008. // else {
  1009. // $('#boxFurniture').hide(100);
  1010. // }
  1011. // });
  1012. document.getElementById("applySurface").addEventListener("click", function () {
  1013. $('#roomTools').hide(100);
  1014. $('#panel').show(200);
  1015. binder.remove();
  1016. delete binder;
  1017. let id = $('#roomIndex').val();
  1018. //COLOR
  1019. let data = $('#roomBackground').val();
  1020. ROOM[id].color = data;
  1021. //ROOM NAME
  1022. let roomName = $('#roomName').val();
  1023. if (roomName === 'None') {
  1024. roomName = '';
  1025. }
  1026. ROOM[id].name = roomName;
  1027. //ROOM SURFACE
  1028. let area = $('#roomSurface').val();
  1029. ROOM[id].surface = area;
  1030. //SHOW SURFACE
  1031. let show = document.querySelector("#seeArea").checked;
  1032. ROOM[id].showSurface = show;
  1033. //ACTION PARAM
  1034. let action = document.querySelector('input[type=radio]:checked').value;
  1035. ROOM[id].action = action;
  1036. if (action === 'sub') {
  1037. ROOM[id].color = 'hatch';
  1038. }
  1039. if (action != 'sub' && data === 'hatch') {
  1040. ROOM[id].color = 'gradientNeutral';
  1041. }
  1042. $('#boxRoom').empty();
  1043. $('#boxSurface').empty();
  1044. editor.roomMaker(Rooms);
  1045. $('#boxinfo').html('Updated room');
  1046. fonc_button('select_mode');
  1047. });
  1048. document.getElementById("resetRoomTools").addEventListener("click", function () {
  1049. $('#roomTools').hide(100);
  1050. $('#panel').show(200);
  1051. binder.remove();
  1052. delete binder;
  1053. $('#boxinfo').html('Updated room');
  1054. fonc_button('select_mode');
  1055. });
  1056. document.getElementById("wallTrash").addEventListener("click", function () {
  1057. let wall = binder.wall;
  1058. for (let k in WALLS) {
  1059. if (isObjectsEquals(WALLS[k].child, wall)) WALLS[k].child = null;
  1060. if (isObjectsEquals(WALLS[k].parent, wall)) {
  1061. WALLS[k].parent = null;
  1062. }
  1063. }
  1064. WALLS.splice(WALLS.indexOf(wall), 1);
  1065. $('#wallTools').hide(100);
  1066. wall.graph.remove();
  1067. binder.graph.remove();
  1068. editor.architect(WALLS);
  1069. rib();
  1070. mode = "select_mode";
  1071. $('#panel').show(200);
  1072. });
  1073. let textEditorColorBtn = document.querySelectorAll('.textEditorColor');
  1074. for (let k = 0; k < textEditorColorBtn.length; k++) {
  1075. textEditorColorBtn[k].addEventListener('click', function () {
  1076. document.getElementById('labelBox').style.color = this.style.color;
  1077. });
  1078. }
  1079. let zoomBtn = document.querySelectorAll('.zoom');
  1080. for (let k = 0; k < zoomBtn.length; k++) {
  1081. zoomBtn[k].addEventListener("click", function () {
  1082. let lens = this.getAttribute('data-zoom');
  1083. zoom_maker(lens, 200, 50);
  1084. })
  1085. }
  1086. let roomColorBtn = document.querySelectorAll(".roomColor");
  1087. for (let k = 0; k < roomColorBtn.length; k++) {
  1088. roomColorBtn[k].addEventListener("click", function () {
  1089. let data = this.getAttribute('data-type');
  1090. $('#roomBackground').val(data);
  1091. binder.attr({ 'fill': 'url(#' + data + ')' });
  1092. });
  1093. }
  1094. let objTrashBtn = document.querySelectorAll(".objTrash");
  1095. for (let k = 0; k < objTrashBtn.length; k++) {
  1096. objTrashBtn[k].addEventListener("click", function () {
  1097. $('#objTools').hide('100');
  1098. let obj = binder.obj;
  1099. obj.graph.remove();
  1100. OBJDATA.splice(OBJDATA.indexOf(obj), 1);
  1101. fonc_button('select_mode');
  1102. $('#boxinfo').html('Selection mode');
  1103. $('#panel').show('200');
  1104. binder.graph.remove();
  1105. delete binder;
  1106. rib();
  1107. $('#panel').show('300');
  1108. });
  1109. }
  1110. let dropdownMenu = document.querySelectorAll(".dropdown-menu li a");
  1111. for (let k = 0; k < dropdownMenu.length; k++) {
  1112. dropdownMenu[k].addEventListener("click", function () {
  1113. let selText = this.textContent;
  1114. $(this).parents('.btn-group').find('.dropdown-toggle').html(selText + ' <span class="caret"></span>');
  1115. if (selText != 'None') $('#roomName').val(selText);
  1116. else $('#roomName').val('');
  1117. });
  1118. }
  1119. // TRY MATRIX CALC FOR BBOX REAL COORDS WITH TRAS + ROT.
  1120. function matrixCalc(el, message = false) {
  1121. if (message) console.log("matrixCalc called by -> " + message);
  1122. let m = el.getCTM();
  1123. let bb = el.getBBox();
  1124. let tpts = [
  1125. matrixXY(m, bb.x, bb.y),
  1126. matrixXY(m, bb.x + bb.width, bb.y),
  1127. matrixXY(m, bb.x + bb.width, bb.y + bb.height),
  1128. matrixXY(m, bb.x, bb.y + bb.height)];
  1129. return tpts;
  1130. }
  1131. function matrixXY(m, x, y) {
  1132. return { x: x * m.a + y * m.c + m.e, y: x * m.b + y * m.d + m.f };
  1133. }
  1134. function realBboxShow(coords) {
  1135. for (let k in coords) {
  1136. debugPoint(coords[k]);
  1137. }
  1138. }
  1139. function limitObj(equation, size, coords, message = false) {
  1140. if (message) {
  1141. console.log(message);
  1142. }
  1143. let Px = coords.x;
  1144. let Py = coords.y;
  1145. let Aq = equation.A;
  1146. let Bq = equation.B;
  1147. let pos1, pos2;
  1148. if (Aq === 'v') {
  1149. pos1 = { x: Px, y: Py - size / 2 };
  1150. pos2 = { x: Px, y: Py + size / 2 };
  1151. } else if (Aq === 'h') {
  1152. pos1 = { x: Px - size / 2, y: Py };
  1153. pos2 = { x: Px + size / 2, y: Py };
  1154. } else {
  1155. let A = 1 + Aq * Aq;
  1156. let B = (-2 * Px) + (2 * Aq * Bq) + (-2 * Py * Aq);
  1157. let C = (Px * Px) + (Bq * Bq) - (2 * Py * Bq) + (Py * Py) - (size * size) / 4; // -N
  1158. let Delta = (B * B) - (4 * A * C);
  1159. let posX1 = (-B - (Math.sqrt(Delta))) / (2 * A);
  1160. let posX2 = (-B + (Math.sqrt(Delta))) / (2 * A);
  1161. pos1 = { x: posX1, y: (Aq * posX1) + Bq };
  1162. pos2 = { x: posX2, y: (Aq * posX2) + Bq };
  1163. }
  1164. return [pos1, pos2];
  1165. }
  1166. function zoom_maker(lens, xmove, xview) {
  1167. if (lens === 'zoomout' && zoom > 1 && zoom < 17) {
  1168. zoom--;
  1169. width_viewbox += xmove;
  1170. let ratioWidthZoom = taille_w / width_viewbox;
  1171. height_viewbox = width_viewbox * ratio_viewbox;
  1172. myDiv = document.getElementById("scaleVal");
  1173. myDiv.style.width = 60 * ratioWidthZoom + 'px';
  1174. originX_viewbox = originX_viewbox - (xmove / 2);
  1175. originY_viewbox = originY_viewbox - (xmove / 2 * ratio_viewbox);
  1176. }
  1177. if (lens === 'zoomin' && zoom < 14 && zoom > 0) {
  1178. zoom++;
  1179. let oldWidth = width_viewbox;
  1180. let ratioWidthZoom = taille_w / width_viewbox;
  1181. let newWidth = width_viewbox - xmove;
  1182. if (newWidth < 100) newWidth = 100;
  1183. width_viewbox = newWidth;
  1184. height_viewbox = width_viewbox * ratio_viewbox;
  1185. originX_viewbox = originX_viewbox + ((oldWidth - width_viewbox) / 2);
  1186. originY_viewbox = originY_viewbox + (((oldWidth - width_viewbox) / 2) * ratio_viewbox);
  1187. myDiv = document.getElementById("scaleVal");
  1188. myDiv.style.width = 60 * ratioWidthZoom + 'px';
  1189. }
  1190. if (lens === 'zoominit') {
  1191. width_viewbox = taille_w;
  1192. height_viewbox = width_viewbox * ratio_viewbox;
  1193. originX_viewbox = 0;
  1194. originY_viewbox = 0;
  1195. zoom = 9;
  1196. factor = 1;
  1197. }
  1198. if (lens === 'zoomright') {
  1199. originX_viewbox += xview;
  1200. }
  1201. if (lens === 'zoomleft') {
  1202. originX_viewbox -= xview;
  1203. }
  1204. if (lens === 'zoomtop') {
  1205. originY_viewbox -= xview;
  1206. }
  1207. if (lens === 'zoombottom') {
  1208. originY_viewbox += xview;
  1209. }
  1210. if (lens === 'zoomdrag') {
  1211. originX_viewbox -= xmove;
  1212. originY_viewbox -= xview;
  1213. }
  1214. // Update pixel-to-SVG factor after any viewBox change
  1215. factor = width_viewbox / taille_w;
  1216. $('svg').each(function () {
  1217. $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox)
  1218. });
  1219. }
  1220. // Center the current floorplan in view by fitting the WALLS bounding box to the viewport
  1221. function centerFloorplanView(padding = 40) {
  1222. try {
  1223. if (!Array.isArray(WALLS) || WALLS.length === 0) return false;
  1224. // Compute bounding box from wall endpoints
  1225. let minX, minY, maxX, maxY;
  1226. for (let i = 0; i < WALLS.length; i++) {
  1227. const s = WALLS[i].start;
  1228. const e = WALLS[i].end;
  1229. if (!i) {
  1230. minX = Math.min(s.x, e.x);
  1231. minY = Math.min(s.y, e.y);
  1232. maxX = Math.max(s.x, e.x);
  1233. maxY = Math.max(s.y, e.y);
  1234. } else {
  1235. minX = Math.min(minX, s.x, e.x);
  1236. minY = Math.min(minY, s.y, e.y);
  1237. maxX = Math.max(maxX, s.x, e.x);
  1238. maxY = Math.max(maxY, s.y, e.y);
  1239. }
  1240. }
  1241. // Handle degenerate cases
  1242. if (minX === undefined || minY === undefined || maxX === undefined || maxY === undefined) return false;
  1243. const bboxW = Math.max(1, maxX - minX);
  1244. const bboxH = Math.max(1, maxY - minY);
  1245. // Target size keeping aspect ratio
  1246. const viewAspect = ratio_viewbox; // height/width
  1247. const targetWidth = bboxW + 2 * padding;
  1248. const targetHeight = bboxH + 2 * padding;
  1249. const widthFromHeight = targetHeight / viewAspect;
  1250. const fitWidth = Math.max(targetWidth, widthFromHeight);
  1251. const fitHeight = fitWidth * viewAspect;
  1252. width_viewbox = fitWidth;
  1253. height_viewbox = fitHeight;
  1254. const cx = (minX + maxX) / 2;
  1255. const cy = (minY + maxY) / 2;
  1256. originX_viewbox = cx - (width_viewbox / 2);
  1257. originY_viewbox = cy - (height_viewbox / 2);
  1258. // Update pixel-to-SVG factor based on new viewBox size
  1259. factor = width_viewbox / taille_w;
  1260. // Update scale indicator if present
  1261. const scaleEl = document.getElementById('scaleVal');
  1262. if (scaleEl) {
  1263. const ratioWidthZoom = taille_w / width_viewbox;
  1264. scaleEl.style.width = (60 * ratioWidthZoom) + 'px';
  1265. }
  1266. // Apply viewBox to all SVGs
  1267. $('svg').each(function () {
  1268. $(this)[0].setAttribute('viewBox', originX_viewbox + ' ' + originY_viewbox + ' ' + width_viewbox + ' ' + height_viewbox);
  1269. });
  1270. return true;
  1271. } catch (e) {
  1272. console.error('Error centering view:', e);
  1273. return false;
  1274. }
  1275. }
  1276. tactile = false;
  1277. function calcul_snap(event, state) {
  1278. if (event.touches) {
  1279. let touches = event.changedTouches;
  1280. console.log("toto")
  1281. eX = touches[0].pageX;
  1282. eY = touches[0].pageY;
  1283. tactile = true;
  1284. } else {
  1285. eX = event.pageX;
  1286. eY = event.pageY;
  1287. }
  1288. x_mouse = (eX * factor) - (offset.left * factor) + originX_viewbox;
  1289. y_mouse = (eY * factor) - (offset.top * factor) + originY_viewbox;
  1290. if (state === 'on') {
  1291. x_grid = Math.round(x_mouse / grid) * grid;
  1292. y_grid = Math.round(y_mouse / grid) * grid;
  1293. }
  1294. if (state === 'off') {
  1295. x_grid = x_mouse;
  1296. y_grid = y_mouse;
  1297. }
  1298. return {
  1299. x: x_grid,
  1300. y: y_grid,
  1301. xMouse: x_mouse,
  1302. yMouse: y_mouse
  1303. };
  1304. }
  1305. minMoveGrid = function (mouse) {
  1306. return Math.abs(Math.abs(pox - mouse.x) + Math.abs(poy - mouse.y));
  1307. }
  1308. function intersectionOff() {
  1309. if (typeof (lineIntersectionP) != 'undefined') {
  1310. lineIntersectionP.remove();
  1311. delete lineIntersectionP;
  1312. }
  1313. }
  1314. function intersection(snap, range = Infinity, except = ['']) {
  1315. // ORANGE LINES 90° NEAR SEGMENT
  1316. let bestEqPoint = {};
  1317. let equation = {};
  1318. bestEqPoint.distance = range;
  1319. if (typeof (lineIntersectionP) != 'undefined') {
  1320. lineIntersectionP.remove();
  1321. delete lineIntersectionP;
  1322. }
  1323. lineIntersectionP = qSVG.create("boxbind", "path", { // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+
  1324. d: "",
  1325. "stroke": "transparent",
  1326. "stroke-width": 0.5,
  1327. "stroke-opacity": "1",
  1328. fill: "none"
  1329. });
  1330. for (index = 0; index < WALLS.length; index++) {
  1331. if (except.indexOf(WALLS[index]) === -1) {
  1332. let x1 = WALLS[index].start.x;
  1333. let y1 = WALLS[index].start.y;
  1334. let x2 = WALLS[index].end.x;
  1335. let y2 = WALLS[index].end.y;
  1336. // EQUATION 90° of segment nf/nf-1 at X2/Y2 Point
  1337. if (Math.abs(y2 - y1) === 0) {
  1338. equation.C = 'v'; // C/D equation 90° Coef = -1/E
  1339. equation.D = x1;
  1340. equation.E = 'h'; // E/F equation Segment
  1341. equation.F = y1;
  1342. equation.G = 'v'; // G/H equation 90° Coef = -1/E
  1343. equation.H = x2;
  1344. equation.I = 'h'; // I/J equation Segment
  1345. equation.J = y2;
  1346. } else if (Math.abs(x2 - x1) === 0) {
  1347. equation.C = 'h'; // C/D equation 90° Coef = -1/E
  1348. equation.D = y1;
  1349. equation.E = 'v'; // E/F equation Segment
  1350. equation.F = x1;
  1351. equation.G = 'h'; // G/H equation 90° Coef = -1/E
  1352. equation.H = y2;
  1353. equation.I = 'v'; // I/J equation Segment
  1354. equation.J = x2;
  1355. } else {
  1356. equation.C = (x1 - x2) / (y2 - y1);
  1357. equation.D = y1 - (x1 * equation.C);
  1358. equation.E = (y2 - y1) / (x2 - x1);
  1359. equation.F = y1 - (x1 * equation.E);
  1360. equation.G = (x1 - x2) / (y2 - y1);
  1361. equation.H = y2 - (x2 * equation.C);
  1362. equation.I = (y2 - y1) / (x2 - x1);
  1363. equation.J = y2 - (x2 * equation.E);
  1364. }
  1365. equation.A = equation.C;
  1366. equation.B = equation.D;
  1367. eq = qSVG.nearPointOnEquation(equation, snap);
  1368. if (eq.distance < bestEqPoint.distance) {
  1369. setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 1);
  1370. }
  1371. equation.A = equation.E;
  1372. equation.B = equation.F;
  1373. eq = qSVG.nearPointOnEquation(equation, snap);
  1374. if (eq.distance < bestEqPoint.distance) {
  1375. setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 1);
  1376. }
  1377. equation.A = equation.G;
  1378. equation.B = equation.H;
  1379. eq = qSVG.nearPointOnEquation(equation, snap);
  1380. if (eq.distance < bestEqPoint.distance) {
  1381. setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 2);
  1382. }
  1383. equation.A = equation.I;
  1384. equation.B = equation.J;
  1385. eq = qSVG.nearPointOnEquation(equation, snap);
  1386. if (eq.distance < bestEqPoint.distance) {
  1387. setBestEqPoint(bestEqPoint, eq.distance, index, eq.x, eq.y, x1, y1, x2, y2, 2);
  1388. }
  1389. } // END INDEXOF EXCEPT TEST
  1390. } // END LOOP FOR
  1391. if (bestEqPoint.distance < range) {
  1392. if (bestEqPoint.way === 2) {
  1393. lineIntersectionP.attr({ // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+
  1394. d: "M" + bestEqPoint.x1 + "," + bestEqPoint.y1 + " L" + bestEqPoint.x2 + "," + bestEqPoint.y2 + " L" + bestEqPoint.x + "," +
  1395. bestEqPoint.y,
  1396. "stroke": "#d7ac57"
  1397. });
  1398. } else {
  1399. lineIntersectionP.attr({ // ORANGE TEMP LINE FOR ANGLE 0 90 45 -+
  1400. d: "M" + bestEqPoint.x2 + "," + bestEqPoint.y2 + " L" + bestEqPoint.x1 + "," + bestEqPoint.y1 + " L" + bestEqPoint.x + "," +
  1401. bestEqPoint.y,
  1402. "stroke": "#d7ac57"
  1403. });
  1404. }
  1405. return ({
  1406. x: bestEqPoint.x,
  1407. y: bestEqPoint.y,
  1408. wall: WALLS[bestEqPoint.node],
  1409. distance: bestEqPoint.distance
  1410. });
  1411. } else {
  1412. return false;
  1413. }
  1414. }
  1415. function debugPoint(point, name, color = "#00ff00") {
  1416. qSVG.create('boxDebug', 'circle', {
  1417. cx: point.x,
  1418. cy: point.y,
  1419. r: 7,
  1420. fill: color,
  1421. id: name,
  1422. class: "visu"
  1423. });
  1424. }
  1425. function showVertex() {
  1426. for (let i = 0; i < vertex.length; i++) {
  1427. debugPoint(vertex[i], i);
  1428. }
  1429. }
  1430. function showJunction() {
  1431. for (let i = 0; i < junction.length; i++) {
  1432. debugPoint({ x: junction[i].values[0], y: junction[i].values[1] }, i);
  1433. }
  1434. }
  1435. $('.visu').mouseover(function () {
  1436. console.log(this.id)
  1437. });
  1438. let sizeText = [];
  1439. let showAllSizeStatus = 0;
  1440. function hideAllSize() {
  1441. $('#boxbind').empty();
  1442. sizeText = [];
  1443. showAllSizeStatus = 0;
  1444. }
  1445. function allRib() {
  1446. $('#boxRib').empty();
  1447. for (let i in WALLS) {
  1448. inWallRib(WALLS[i], 'all');
  1449. }
  1450. }
  1451. function inWallRib(wall, option = false) {
  1452. if (!option) $('#boxRib').empty();
  1453. ribMaster = [];
  1454. ribMaster.push([]);
  1455. ribMaster.push([]);
  1456. let inter;
  1457. let distance;
  1458. let cross;
  1459. let angleTextValue = wall.angle * (180 / Math.PI);
  1460. let objWall = editor.objFromWall(wall); // LIST OBJ ON EDGE
  1461. if (objWall.length == 0) return
  1462. ribMaster[0].push({ wall: wall, crossObj: false, side: 'up', coords: wall.coords[0], distance: 0 });
  1463. ribMaster[1].push({ wall: wall, crossObj: false, side: 'down', coords: wall.coords[1], distance: 0 });
  1464. let objTarget = null
  1465. for (let ob in objWall) {
  1466. objTarget = objWall[ob];
  1467. objTarget.up = [
  1468. qSVG.nearPointOnEquation(wall.equations.up, objTarget.limit[0]),
  1469. qSVG.nearPointOnEquation(wall.equations.up, objTarget.limit[1])
  1470. ];
  1471. objTarget.down = [
  1472. qSVG.nearPointOnEquation(wall.equations.down, objTarget.limit[0]),
  1473. qSVG.nearPointOnEquation(wall.equations.down, objTarget.limit[1])
  1474. ];
  1475. distance = qSVG.measure(wall.coords[0], objTarget.up[0]) / meter;
  1476. ribMaster[0].push({
  1477. wall: objTarget,
  1478. crossObj: ob,
  1479. side: 'up',
  1480. coords: objTarget.up[0],
  1481. distance: distance.toFixed(2)
  1482. });
  1483. distance = qSVG.measure(wall.coords[0], objTarget.up[1]) / meter;
  1484. ribMaster[0].push({
  1485. wall: objTarget,
  1486. crossObj: ob,
  1487. side: 'up',
  1488. coords: objTarget.up[1],
  1489. distance: distance.toFixed(2)
  1490. });
  1491. distance = qSVG.measure(wall.coords[1], objTarget.down[0]) / meter;
  1492. ribMaster[1].push({
  1493. wall: objTarget,
  1494. crossObj: ob,
  1495. side: 'down',
  1496. coords: objTarget.down[0],
  1497. distance: distance.toFixed(2)
  1498. });
  1499. distance = qSVG.measure(wall.coords[1], objTarget.down[1]) / meter;
  1500. ribMaster[1].push({
  1501. wall: objTarget,
  1502. crossObj: ob,
  1503. side: 'down',
  1504. coords: objTarget.down[1],
  1505. distance: distance.toFixed(2)
  1506. });
  1507. }
  1508. distance = qSVG.measure(wall.coords[0], wall.coords[3]) / meter;
  1509. ribMaster[0].push({ wall: objTarget, crossObj: false, side: 'up', coords: wall.coords[3], distance: distance });
  1510. distance = qSVG.measure(wall.coords[1], wall.coords[2]) / meter;
  1511. ribMaster[1].push({ wall: objTarget, crossObj: false, side: 'down', coords: wall.coords[2], distance: distance });
  1512. ribMaster[0].sort(function (a, b) {
  1513. return (a.distance - b.distance).toFixed(2);
  1514. });
  1515. ribMaster[1].sort(function (a, b) {
  1516. return (a.distance - b.distance).toFixed(2);
  1517. });
  1518. for (let t in ribMaster) {
  1519. for (let n = 1; n < ribMaster[t].length; n++) {
  1520. let found = true;
  1521. let shift = -5;
  1522. let valueText = Math.abs(ribMaster[t][n - 1].distance - ribMaster[t][n].distance);
  1523. let angleText = angleTextValue;
  1524. if (found) {
  1525. if (ribMaster[t][n - 1].side === 'down') {
  1526. shift = -shift + 10;
  1527. }
  1528. if (angleText > 89 || angleText < -89) {
  1529. angleText -= 180;
  1530. if (ribMaster[t][n - 1].side === 'down') {
  1531. shift = -5;
  1532. } else shift = -shift + 10;
  1533. }
  1534. sizeText[n] = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  1535. let startText = qSVG.middle(ribMaster[t][n - 1].coords.x, ribMaster[t][n - 1].coords.y, ribMaster[t][n].coords.x,
  1536. ribMaster[t][n].coords.y);
  1537. // Check for valid coordinates before setting attributes
  1538. if (startText && !isNaN(startText.x) && !isNaN(startText.y)) {
  1539. sizeText[n].setAttributeNS(null, 'x', startText.x);
  1540. sizeText[n].setAttributeNS(null, 'y', (startText.y) + shift);
  1541. sizeText[n].setAttributeNS(null, 'text-anchor', 'middle');
  1542. sizeText[n].setAttributeNS(null, 'font-family', 'roboto');
  1543. sizeText[n].setAttributeNS(null, 'stroke', '#ffffff');
  1544. sizeText[n].textContent = valueText.toFixed(2);
  1545. if (sizeText[n].textContent < 1) {
  1546. sizeText[n].setAttributeNS(null, 'font-size', '0.8em');
  1547. sizeText[n].textContent = sizeText[n].textContent.substring(1, sizeText[n].textContent.length);
  1548. } else sizeText[n].setAttributeNS(null, 'font-size', '1em');
  1549. sizeText[n].setAttributeNS(null, 'stroke-width', '0.27px');
  1550. sizeText[n].setAttributeNS(null, 'fill', '#666666');
  1551. sizeText[n].setAttribute("transform", "rotate(" + angleText + " " + startText.x + "," + (startText.y) + ")");
  1552. } else {
  1553. // Skip creating text element if coordinates are invalid
  1554. console.warn('Invalid coordinates for size text element:', startText);
  1555. }
  1556. $('#boxRib').append(sizeText[n]);
  1557. }
  1558. }
  1559. }
  1560. }
  1561. function rib(shift = 5) {
  1562. // return false;
  1563. let ribMaster = [];
  1564. ribMaster.push([]);
  1565. ribMaster.push([]);
  1566. let inter;
  1567. let distance;
  1568. let cross;
  1569. for (let i in WALLS) {
  1570. if (WALLS[i].equations.base) {
  1571. ribMaster[0].push([]);
  1572. pushToRibMaster(ribMaster, 0, i, i, i, 'up', WALLS[i].coords[0], 0);
  1573. ribMaster[1].push([]);
  1574. pushToRibMaster(ribMaster, 1, i, i, i, 'down', WALLS[i].coords[1], 0);
  1575. for (let p in WALLS) {
  1576. if (i != p && WALLS[p].equations.base) {
  1577. cross = qSVG.intersectionOfEquations(WALLS[i].equations.base, WALLS[p].equations.base, "object");
  1578. if (qSVG.btwn(cross.x, WALLS[i].start.x, WALLS[i].end.x, 'round') &&
  1579. qSVG.btwn(cross.y, WALLS[i].start.y, WALLS[i].end.y, 'round')) {
  1580. inter = qSVG.intersectionOfEquations(WALLS[i].equations.up, WALLS[p].equations.up, "object");
  1581. if (qSVG.btwn(inter.x, WALLS[i].coords[0].x, WALLS[i].coords[3].x, 'round') &&
  1582. qSVG.btwn(inter.y, WALLS[i].coords[0].y, WALLS[i].coords[3].y, 'round') &&
  1583. qSVG.btwn(inter.x, WALLS[p].coords[0].x, WALLS[p].coords[3].x, 'round') &&
  1584. qSVG.btwn(inter.y, WALLS[p].coords[0].y, WALLS[p].coords[3].y, 'round')) {
  1585. distance = qSVG.measure(WALLS[i].coords[0], inter) / meter;
  1586. pushToRibMaster(ribMaster, 0, i, i, p, 'up', inter, distance.toFixed(2));
  1587. }
  1588. inter = qSVG.intersectionOfEquations(WALLS[i].equations.up, WALLS[p].equations.down, "object");
  1589. if (qSVG.btwn(inter.x, WALLS[i].coords[0].x, WALLS[i].coords[3].x, 'round') &&
  1590. qSVG.btwn(inter.y, WALLS[i].coords[0].y, WALLS[i].coords[3].y, 'round') &&
  1591. qSVG.btwn(inter.x, WALLS[p].coords[1].x, WALLS[p].coords[2].x, 'round') &&
  1592. qSVG.btwn(inter.y, WALLS[p].coords[1].y, WALLS[p].coords[2].y, 'round')) {
  1593. distance = qSVG.measure(WALLS[i].coords[0], inter) / meter;
  1594. pushToRibMaster(ribMaster, 0, i, i, p, 'up', inter, distance.toFixed(2));
  1595. }
  1596. inter = qSVG.intersectionOfEquations(WALLS[i].equations.down, WALLS[p].equations.up, "object");
  1597. if (qSVG.btwn(inter.x, WALLS[i].coords[1].x, WALLS[i].coords[2].x, 'round') &&
  1598. qSVG.btwn(inter.y, WALLS[i].coords[1].y, WALLS[i].coords[2].y, 'round') &&
  1599. qSVG.btwn(inter.x, WALLS[p].coords[0].x, WALLS[p].coords[3].x, 'round') &&
  1600. qSVG.btwn(inter.y, WALLS[p].coords[0].y, WALLS[p].coords[3].y, 'round')) {
  1601. distance = qSVG.measure(WALLS[i].coords[1], inter) / meter;
  1602. pushToRibMaster(ribMaster, 1, i, i, p, 'down', inter, distance.toFixed(2));
  1603. }
  1604. inter = qSVG.intersectionOfEquations(WALLS[i].equations.down, WALLS[p].equations.down, "object");
  1605. if (qSVG.btwn(inter.x, WALLS[i].coords[1].x, WALLS[i].coords[2].x, 'round') &&
  1606. qSVG.btwn(inter.y, WALLS[i].coords[1].y, WALLS[i].coords[2].y, 'round') &&
  1607. qSVG.btwn(inter.x, WALLS[p].coords[1].x, WALLS[p].coords[2].x, 'round') &&
  1608. qSVG.btwn(inter.y, WALLS[p].coords[1].y, WALLS[p].coords[2].y, 'round')) {
  1609. distance = qSVG.measure(WALLS[i].coords[1], inter) / meter;
  1610. pushToRibMaster(ribMaster, 1, i, i, p, 'down', inter, distance.toFixed(2));
  1611. }
  1612. }
  1613. }
  1614. }
  1615. distance = qSVG.measure(WALLS[i].coords[0], WALLS[i].coords[3]) / meter;
  1616. pushToRibMaster(ribMaster, 0, i, i, i, 'up', WALLS[i].coords[3], distance.toFixed(2));
  1617. distance = qSVG.measure(WALLS[i].coords[1], WALLS[i].coords[2]) / meter;
  1618. pushToRibMaster(ribMaster, 1, i, i, i, 'down', WALLS[i].coords[2], distance.toFixed(2));
  1619. }
  1620. }
  1621. for (let a in ribMaster[0]) {
  1622. ribMaster[0][a].sort(function (a, b) {
  1623. return (a.distance - b.distance).toFixed(2);
  1624. });
  1625. }
  1626. for (let a in ribMaster[1]) {
  1627. ribMaster[1][a].sort(function (a, b) {
  1628. return (a.distance - b.distance).toFixed(2);
  1629. });
  1630. }
  1631. let sizeText = [];
  1632. if (shift === 5) $('#boxRib').empty();
  1633. for (let t in ribMaster) {
  1634. for (let a in ribMaster[t]) {
  1635. for (let n = 1; n < ribMaster[t][a].length; n++) {
  1636. if (ribMaster[t][a][n - 1].wallIndex === ribMaster[t][a][n].wallIndex) {
  1637. let edge = ribMaster[t][a][n].wallIndex;
  1638. let found = true;
  1639. let valueText = Math.abs(ribMaster[t][a][n - 1].distance - ribMaster[t][a][n].distance);
  1640. // CLEAR TOO LITTLE VALUE
  1641. if (valueText < 0.15) {
  1642. found = false;
  1643. }
  1644. // CLEAR (thick) BETWEEN CROSS EDGE
  1645. if (found && ribMaster[t][a][n - 1].crossEdge === ribMaster[t][a][n].crossEdge && ribMaster[t][a][n].crossEdge !=
  1646. ribMaster[t][a][n].wallIndex) {
  1647. found = false;
  1648. }
  1649. // CLEAR START INTO EDGE
  1650. if (found && ribMaster[t][a].length > 2 && n === 1) {
  1651. let polygon = [];
  1652. for (let pp = 0; pp < 4; pp++) {
  1653. polygon.push({
  1654. x: WALLS[ribMaster[t][a][n].crossEdge].coords[pp].x,
  1655. y: WALLS[ribMaster[t][a][n].crossEdge].coords[pp].y
  1656. }); // FOR Z
  1657. }
  1658. if (qSVG.rayCasting(ribMaster[t][a][0].coords, polygon)) {
  1659. found = false;
  1660. }
  1661. }
  1662. // CLEAR END INTO EDGE
  1663. if (found && ribMaster[t][a].length > 2 && n === ribMaster[t][a].length - 1) {
  1664. let polygon = [];
  1665. for (let pp = 0; pp < 4; pp++) {
  1666. polygon.push({
  1667. x: WALLS[ribMaster[t][a][n - 1].crossEdge].coords[pp].x,
  1668. y: WALLS[ribMaster[t][a][n - 1].crossEdge].coords[pp].y
  1669. }); // FOR Z
  1670. }
  1671. if (qSVG.rayCasting(ribMaster[t][a][ribMaster[t][a].length - 1].coords, polygon)) {
  1672. found = false;
  1673. }
  1674. }
  1675. if (found) {
  1676. let angleText = WALLS[ribMaster[t][a][n].wallIndex].angle * (180 / Math.PI);
  1677. let shiftValue = -shift;
  1678. if (ribMaster[t][a][n - 1].side === 'down') {
  1679. shiftValue = -shiftValue + 10;
  1680. }
  1681. if (angleText > 90 || angleText < -89) {
  1682. angleText -= 180;
  1683. if (ribMaster[t][a][n - 1].side === 'down') {
  1684. shiftValue = -shift;
  1685. } else shiftValue = -shiftValue + 10;
  1686. }
  1687. sizeText[n] = document.createElementNS('http://www.w3.org/2000/svg', 'text');
  1688. let startText = qSVG.middle(ribMaster[t][a][n - 1].coords.x, ribMaster[t][a][n - 1].coords.y, ribMaster[t][a][n].coords.x,
  1689. ribMaster[t][a][n].coords.y);
  1690. // Check for valid coordinates before setting attributes
  1691. if (startText && !isNaN(startText.x) && !isNaN(startText.y)) {
  1692. sizeText[n].setAttributeNS(null, 'x', startText.x);
  1693. sizeText[n].setAttributeNS(null, 'y', (startText.y) + (shiftValue));
  1694. sizeText[n].setAttributeNS(null, 'text-anchor', 'middle');
  1695. sizeText[n].setAttributeNS(null, 'font-family', 'roboto');
  1696. sizeText[n].setAttributeNS(null, 'stroke', '#ffffff');
  1697. sizeText[n].textContent = valueText.toFixed(2);
  1698. if (sizeText[n].textContent < 1) {
  1699. sizeText[n].setAttributeNS(null, 'font-size', '0.73em');
  1700. sizeText[n].textContent = sizeText[n].textContent.substring(1, sizeText[n].textContent.length);
  1701. } else sizeText[n].setAttributeNS(null, 'font-size', '0.9em');
  1702. sizeText[n].setAttributeNS(null, 'stroke-width', '0.2px');
  1703. sizeText[n].setAttributeNS(null, 'fill', '#555555');
  1704. sizeText[n].setAttribute("transform", "rotate(" + angleText + " " + startText.x + "," + (startText.y) + ")");
  1705. } else {
  1706. // Skip creating text element if coordinates are invalid
  1707. console.warn('Invalid coordinates for size text element:', startText);
  1708. }
  1709. $('#boxRib').append(sizeText[n]);
  1710. }
  1711. }
  1712. }
  1713. }
  1714. }
  1715. }
  1716. function cursor(tool) {
  1717. if (tool === 'grab') tool =
  1718. "url('https://wiki.openmrs.org/s/en_GB/7502/b9217199c27dd617c8d51f6186067d7767c5001b/_/images/icons/emoticons/add.png') 8 8, auto";
  1719. if (tool === 'scissor') tool = "url('https://maxcdn.icons8.com/windows10/PNG/64/Hands/hand_scissors-64.png'), auto";
  1720. if (tool === 'trash') tool = "url('https://cdn4.iconfinder.com/data/icons/common-toolbar/36/Cancel-32.png'), auto";
  1721. if (tool === 'validation') tool = "url('https://images.fatguymedia.com/wp-content/uploads/2015/09/check.png'), auto";
  1722. linElement.css('cursor', tool);
  1723. }
  1724. function fullscreen() {
  1725. // go full-screen
  1726. let i = document.body;
  1727. if (i.requestFullscreen) {
  1728. i.requestFullscreen();
  1729. } else if (i.webkitRequestFullscreen) {
  1730. i.webkitRequestFullscreen();
  1731. } else if (i.mozRequestFullScreen) {
  1732. i.mozRequestFullScreen();
  1733. } else if (i.msRequestFullscreen) {
  1734. i.msRequestFullscreen();
  1735. }
  1736. }
  1737. function outFullscreen() {
  1738. if (document.exitFullscreen) {
  1739. document.exitFullscreen();
  1740. } else if (document.mozCancelFullScreen) {
  1741. document.mozCancelFullScreen();
  1742. } else if (document.webkitExitFullscreen) {
  1743. document.webkitExitFullscreen();
  1744. }
  1745. }
  1746. document.addEventListener("fullscreenchange", function () {
  1747. if (
  1748. !document.fullscreenElement &&
  1749. !document.webkitFullscreenElement &&
  1750. !document.mozFullScreenElement &&
  1751. !document.msFullscreenElement) {
  1752. $('#nofull_mode').display = 'none';
  1753. $('#full_mode').show();
  1754. }
  1755. });
  1756. function raz_button() {
  1757. $('#rect_mode').removeClass('btn-success');
  1758. $('#rect_mode').addClass('btn-default');
  1759. $('#select_mode').removeClass('btn-success');
  1760. $('#select_mode').addClass('btn-default');
  1761. $('#line_mode').removeClass('btn-success');
  1762. $('#line_mode').addClass('btn-default');
  1763. $('#partition_mode').removeClass('btn-success');
  1764. $('#partition_mode').addClass('btn-default');
  1765. $('#door_mode').removeClass('btn-success');
  1766. $('#door_mode').addClass('btn-default');
  1767. $('#node_mode').removeClass('btn-success');
  1768. $('#node_mode').addClass('btn-default');
  1769. $('#text_mode').removeClass('btn-success');
  1770. $('#text_mode').addClass('btn-default');
  1771. $('#room_mode').removeClass('btn-success');
  1772. $('#room_mode').addClass('btn-default');
  1773. $('#distance_mode').removeClass('btn-success');
  1774. $('#distance_mode').addClass('btn-default');
  1775. $('#object_mode').removeClass('btn-success');
  1776. $('#object_mode').addClass('btn-default');
  1777. $('#stair_mode').removeClass('btn-success');
  1778. $('#stair_mode').addClass('btn-default');
  1779. }
  1780. function fonc_button(modesetting, option) {
  1781. save();
  1782. $('.sub').hide();
  1783. raz_button();
  1784. if (option != 'simpleStair') {
  1785. $('#' + modesetting).removeClass('btn-default');
  1786. $('#' + modesetting).addClass('btn-success');
  1787. }
  1788. mode = modesetting;
  1789. modeOption = option;
  1790. if (typeof (lineIntersectionP) != 'undefined') {
  1791. lineIntersectionP.remove();
  1792. delete lineIntersectionP;
  1793. }
  1794. }
  1795. $('#distance_mode').click(function () {
  1796. linElement.css('cursor', 'crosshair');
  1797. $('#boxinfo').html('Add a measurement');
  1798. fonc_button('distance_mode');
  1799. });
  1800. $('#room_mode').click(function () {
  1801. linElement.css('cursor', 'pointer');
  1802. $('#boxinfo').html('Config. of rooms');
  1803. fonc_button('room_mode');
  1804. });
  1805. $('#select_mode').click(function () {
  1806. $('#boxinfo').html('Mode "select"');
  1807. if (typeof (binder) != 'undefined') {
  1808. try { if (binder.graph) $(binder.graph).remove(); } catch (e) {}
  1809. $('#boxbind').empty();
  1810. binder = undefined;
  1811. }
  1812. fonc_button('select_mode');
  1813. });
  1814. $('#line_mode').click(function () {
  1815. linElement.css('cursor', 'crosshair');
  1816. $('#boxinfo').html('Creation of wall(s)');
  1817. multi = 0;
  1818. action = 0;
  1819. // snap = calcul_snap(event, grid_snap);
  1820. //
  1821. // pox = snap.x;
  1822. // poy = snap.y;
  1823. fonc_button('line_mode');
  1824. });
  1825. $('#partition_mode').click(function () {
  1826. linElement.css('cursor', 'crosshair');
  1827. $('#boxinfo').html('Creation of thin wall(s)');
  1828. multi = 0;
  1829. fonc_button('partition_mode');
  1830. });
  1831. $('#rect_mode').click(function () {
  1832. linElement.css('cursor', 'crosshair');
  1833. $('#boxinfo').html('Room(s) creation');
  1834. fonc_button('rect_mode');
  1835. });
  1836. $('.door').click(function () {
  1837. linElement.css('cursor', 'crosshair');
  1838. $('#boxinfo').html('Add a door');
  1839. $('#door_list').hide(200);
  1840. fonc_button('door_mode', this.id);
  1841. });
  1842. $('.window').click(function () {
  1843. linElement.css('cursor', 'crosshair');
  1844. $('#boxinfo').html('Add a window');
  1845. $('#door_list').hide(200);
  1846. $('#window_list').hide(200);
  1847. fonc_button('door_mode', this.id);
  1848. });
  1849. $('.object').click(function () {
  1850. cursor('move');
  1851. $('#boxinfo').html('Add an object');
  1852. fonc_button('object_mode', this.id);
  1853. });
  1854. $('#stair_mode').click(function () {
  1855. cursor('move');
  1856. $('#boxinfo').html('Add stair');
  1857. fonc_button('object_mode', 'simpleStair');
  1858. });
  1859. $('#node_mode').click(function () {
  1860. $('#boxinfo')
  1861. .html('Cut a wall<br/><span style=\"font-size:0.7em\">Warning : Cutting the wall of a room can cancel its ' +
  1862. 'configuration</span>');
  1863. fonc_button('node_mode');
  1864. });
  1865. $('#text_mode').click(function () {
  1866. $('#boxinfo').html('Add text<br/><span style=\"font-size:0.7em\">Place the cursor to the desired location, then ' +
  1867. 'type your text.</span>');
  1868. fonc_button('text_mode');
  1869. });
  1870. $('#grid_mode').click(function () {
  1871. if (grid_snap === 'on') {
  1872. grid_snap = 'off';
  1873. $('#boxinfo').html('Help grid off');
  1874. $('#grid_mode').removeClass('btn-success');
  1875. $('#grid_mode').addClass('btn-warning');
  1876. $('#grid_mode').html('GRID OFF');
  1877. $('#boxgrid').css('opacity', '0.5');
  1878. } else {
  1879. grid_snap = 'on';
  1880. $('#boxinfo').html('Help grid on');
  1881. $('#grid_mode').removeClass('btn-warning');
  1882. $('#grid_mode').addClass('btn-success');
  1883. $('#grid_mode').html('GRID ON <i class="fa fa-th" aria-hidden="true"></i>');
  1884. $('#boxgrid').css('opacity', '1');
  1885. }
  1886. });
  1887. // RETURN PATH(s) ARRAY FOR OBJECT + PROPERTY params => bindBox (false = open sideTool), move, resize, rotate
  1888. function carpentryCalc(classObj, typeObj, sizeObj, thickObj, dividerObj = 10) {
  1889. // Validate input parameters to prevent NaN propagation
  1890. if (isNaN(sizeObj) || sizeObj <= 0) sizeObj = 60;
  1891. if (isNaN(thickObj) || thickObj <= 0) thickObj = 20;
  1892. if (isNaN(dividerObj) || dividerObj <= 0) dividerObj = 10;
  1893. let construc = [];
  1894. construc.params = {};
  1895. construc.params.bindBox = false;
  1896. construc.params.move = false;
  1897. construc.params.resize = false;
  1898. construc.params.resizeLimit = {};
  1899. construc.params.resizeLimit.width = { min: false, max: false };
  1900. construc.params.resizeLimit.height = { min: false, max: false };
  1901. construc.params.rotate = false;
  1902. if (classObj === 'socle') {
  1903. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," +
  1904. thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) +
  1905. " Z", "#5cba79", "#5cba79", '');
  1906. }
  1907. if (classObj === 'doorWindow') {
  1908. if (typeObj === 'simple') {
  1909. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1910. " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "none",
  1911. '');
  1912. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," +
  1913. (-sizeObj - thickObj / 2) + " A" + sizeObj + "," + sizeObj + " 0 0,1 " + sizeObj / 2 + "," + (-thickObj / 2), "none", colorWall,
  1914. '');
  1915. construc.params.resize = true;
  1916. construc.params.resizeLimit.width = { min: 40, max: 120 };
  1917. }
  1918. if (typeObj === 'double') {
  1919. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1920. " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "none",
  1921. '');
  1922. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," +
  1923. (-sizeObj / 2 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,1 0," + (-thickObj / 2), "none", colorWall,
  1924. '');
  1925. pushToConstruc(construc, "M " + (sizeObj / 2) + "," + (-thickObj / 2) + " L " + (sizeObj / 2) + "," +
  1926. (-sizeObj / 2 - thickObj / 2) + " A" + sizeObj / 2 + "," + sizeObj / 2 + " 0 0,0 0," + (-thickObj / 2), "none", colorWall,
  1927. '');
  1928. construc.params.resize = true;
  1929. construc.params.resizeLimit.width = { min: 40, max: 160 };
  1930. }
  1931. if (typeObj === 'pocket') {
  1932. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-(thickObj / 2) - 4) + " L " + (-sizeObj / 2) + "," +
  1933. thickObj / 2 + " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-(thickObj / 2) - 4) + " Z", "#ccc",
  1934. "none",
  1935. 'none');
  1936. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1937. " M " + (sizeObj / 2) + "," + (thickObj / 2) + " L " + (sizeObj / 2) + "," + (-thickObj / 2), "none", "#494646",
  1938. '5 5');
  1939. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," +
  1940. (-thickObj / 2 - 5) + " L " + (+sizeObj / 2) + "," + (-thickObj / 2 - 5) + " L " + (+sizeObj / 2) +
  1941. "," + (-thickObj / 2) + " Z", "url(#hatch)", "#494646", '');
  1942. construc.params.resize = true;
  1943. construc.params.resizeLimit.width = { min: 60, max: 200 };
  1944. }
  1945. if (typeObj === 'aperture') {
  1946. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1947. " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#ccc", "#494646",
  1948. '5,5');
  1949. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-(thickObj / 2)) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1950. " L " + ((-sizeObj / 2) + 5) + "," + thickObj / 2 + " L " + ((-sizeObj / 2) + 5) + "," + (-(thickObj / 2)) + " Z", "none",
  1951. "#494646",
  1952. 'none');
  1953. pushToConstruc(construc, "M " + ((sizeObj / 2) - 5) + "," + (-(thickObj / 2)) + " L " + ((sizeObj / 2) - 5) + "," + thickObj / 2 +
  1954. " L " + (sizeObj / 2) + "," + thickObj / 2 + " L " + (sizeObj / 2) + "," + (-(thickObj / 2)) + " Z", "none", "#494646",
  1955. 'none');
  1956. construc.params.resize = true;
  1957. construc.params.resizeLimit.width = { min: 40, max: 500 };
  1958. }
  1959. if (typeObj === 'fix') {
  1960. pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " +
  1961. sizeObj / 2 + ",2 L " + sizeObj / 2 + ",-2 Z", "#ccc", "none", '');
  1962. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1963. " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", '');
  1964. construc.params.resize = true;
  1965. construc.params.resizeLimit.width = { min: 30, max: 300 };
  1966. }
  1967. if (typeObj === 'flap') {
  1968. pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " +
  1969. sizeObj / 2 + ",2 L " + sizeObj / 2 + ",-2 Z", "#ccc", "none", '');
  1970. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1971. " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", '');
  1972. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((-sizeObj / 2) +
  1973. ((sizeObj) * 0.866)) + "," + ((-sizeObj / 2) - (thickObj / 2)) + " A" + sizeObj + "," +
  1974. sizeObj + " 0 0,1 " + sizeObj / 2 + "," + (-thickObj / 2), "none", colorWall, '');
  1975. construc.params.resize = true;
  1976. construc.params.resizeLimit.width = { min: 20, max: 100 };
  1977. }
  1978. if (typeObj === 'twin') {
  1979. pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",2 L " + sizeObj / 2 +
  1980. ",2 L " + sizeObj / 2 + ",-2 Z", "#000", "none", '');
  1981. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1982. " L " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "#fff", "#fff", '', 0.7);
  1983. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1984. " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#000", '');
  1985. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((-sizeObj / 2) +
  1986. ((sizeObj / 2) * 0.866)) + "," + (-sizeObj / 4 - thickObj / 2) + " A" +
  1987. sizeObj / 2 + "," + sizeObj / 2 + " 0 0,1 0," + (-thickObj / 2), "none", colorWall, '');
  1988. pushToConstruc(construc, "M " + (sizeObj / 2) + "," + (-thickObj / 2) + " L " + ((sizeObj / 2) +
  1989. ((-sizeObj / 2) * 0.866)) + "," + (-sizeObj / 4 - thickObj / 2) + " A" +
  1990. sizeObj / 2 + "," + sizeObj / 2 + " 0 0,0 0," + (-thickObj / 2), "none", colorWall, '');
  1991. construc.params.resize = true;
  1992. construc.params.resizeLimit.width = { min: 40, max: 200 };
  1993. }
  1994. if (typeObj === 'bay') {
  1995. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 +
  1996. " M " + sizeObj / 2 + "," + thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2), "none", "#ccc", '');
  1997. pushToConstruc(construc, "M " + (-sizeObj / 2) + ",-2 L " + (-sizeObj / 2) + ",0 L 2,0 L 2,2 L 3,2 L 3,-2 Z", "#ccc", "none", '');
  1998. pushToConstruc(construc, "M -2,1 L -2,3 L " + sizeObj / 2 + ",3 L " + sizeObj / 2 + ",1 L -1,1 L -1,-1 L -2,-1 Z", "#ccc", "none", '');
  1999. construc.params.resize = true;
  2000. construc.params.resizeLimit.width = { min: 60, max: 300 };
  2001. }
  2002. }
  2003. if (classObj === 'measure') {
  2004. construc.params.bindBox = true;
  2005. pushToConstruc(construc, "M-" + (sizeObj / 2) + ",0 l10,-10 l0,8 l" + (sizeObj - 20) +
  2006. ",0 l0,-8 l10,10 l-10,10 l0,-8 l-" + (sizeObj - 20) + ",0 l0,8 Z", "#729eeb", "none", '');
  2007. }
  2008. if (classObj === 'boundingBox') {
  2009. pushToConstruc(construc,
  2010. "M" + (-sizeObj / 2 - 10) + "," + (-thickObj / 2 - 10) + " L" + (sizeObj / 2 + 10) + "," + (-thickObj / 2 - 10) + " L" +
  2011. (sizeObj / 2 + 10) + "," + (thickObj / 2 + 10) + " L" + (-sizeObj / 2 - 10) + "," + (thickObj / 2 + 10) + " Z", 'none',
  2012. "#aaa", '');
  2013. // construc.push({'path':"M"+dividerObj[0].x+","+dividerObj[0].y+" L"+dividerObj[1].x+","+dividerObj[1].y+" L"+dividerObj[2].x+",
  2014. // "+dividerObj[2].y+" L"+dividerObj[3].x+","+dividerObj[3].y+" Z", 'fill':'none', 'stroke':"#000", 'strokeDashArray': ''});
  2015. }
  2016. //typeObj = color dividerObj = text
  2017. if (classObj === 'text') {
  2018. construc.params.bindBox = true;
  2019. construc.params.move = true;
  2020. construc.params.rotate = true;
  2021. construc.push({
  2022. 'text': dividerObj.text,
  2023. 'x': '0',
  2024. 'y': '0',
  2025. 'fill': typeObj,
  2026. 'stroke': typeObj,
  2027. 'fontSize': dividerObj.size + 'px',
  2028. "strokeWidth": "0px"
  2029. });
  2030. }
  2031. if (classObj === 'stair') {
  2032. construc.params.bindBox = true;
  2033. construc.params.move = true;
  2034. construc.params.resize = true;
  2035. construc.params.rotate = true;
  2036. construc.params.width = 60;
  2037. construc.params.height = 180;
  2038. if (typeObj === 'simpleStair') {
  2039. pushToConstruc(construc,
  2040. "M " + (-sizeObj / 2) + "," + (-thickObj / 2) + " L " + (-sizeObj / 2) + "," + thickObj / 2 + " L " + sizeObj / 2 + "," +
  2041. thickObj / 2 + " L " + sizeObj / 2 + "," + (-thickObj / 2) + " Z", "#fff", "#000", '');
  2042. let heightStep = thickObj / (dividerObj);
  2043. for (let i = 1; i < dividerObj + 1; i++) {
  2044. pushToConstruc(construc, "M " + (-sizeObj / 2) + "," + ((-thickObj / 2) + (i * heightStep)) + " L " + (sizeObj / 2) + "," +
  2045. ((-thickObj / 2) + (i * heightStep)), "none", "#000", 'none');
  2046. }
  2047. construc.params.resizeLimit.width = { min: 40, max: 200 };
  2048. construc.params.resizeLimit.height = { min: 40, max: 400 };
  2049. }
  2050. }
  2051. if (classObj === 'energy') {
  2052. construc.params.bindBox = true;
  2053. construc.params.move = true;
  2054. construc.params.resize = false;
  2055. construc.params.rotate = false;
  2056. if (typeObj === 'gtl') {
  2057. pushToConstruc(construc, "m -20,-20 l 40,0 l0,40 l-40,0 Z", "#fff", "#333", '');
  2058. construc.push({
  2059. 'text': "GTL",
  2060. 'x': '0',
  2061. 'y': '5',
  2062. 'fill': "#333333",
  2063. 'stroke': "none",
  2064. 'fontSize': '0.9em',
  2065. "strokeWidth": "0.4px"
  2066. });
  2067. construc.params.width = 40;
  2068. construc.params.height = 40;
  2069. construc.family = 'stick';
  2070. }
  2071. if (typeObj === 'switch') {
  2072. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", '');
  2073. pushToConstruc(construc, qSVG.circlePath(-2, 4, 5), "none", "#333", '');
  2074. pushToConstruc(construc, "m 0,0 5,-9", "none", "#333", '');
  2075. construc.params.width = 36;
  2076. construc.params.height = 36;
  2077. construc.family = 'stick';
  2078. }
  2079. if (typeObj === 'doubleSwitch') {
  2080. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", '');
  2081. pushToConstruc(construc, qSVG.circlePath(0, 0, 4), "none", "#333", '');
  2082. pushToConstruc(construc, "m 2,-3 5,-8 3,2", "none", "#333", '');
  2083. pushToConstruc(construc, "m -2,3 -5,8 -3,-2", "none", "#333", '');
  2084. construc.params.width = 36;
  2085. construc.params.height = 36;
  2086. construc.family = 'stick';
  2087. }
  2088. if (typeObj === 'dimmer') {
  2089. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#333", '');
  2090. pushToConstruc(construc, qSVG.circlePath(-2, 4, 5), "none", "#333", '');
  2091. pushToConstruc(construc, "m 0,0 5,-9", "none", "#333", '');
  2092. pushToConstruc(construc, "M -2,-6 L 10,-4 L-2,-2 Z", "none", "#333", '');
  2093. construc.params.width = 36;
  2094. construc.params.height = 36;
  2095. construc.family = 'stick';
  2096. }
  2097. if (typeObj === 'plug') {
  2098. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2099. pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", '');
  2100. pushToConstruc(construc, "m 0,3 v 7", "none", "#333", '');
  2101. pushToConstruc(construc, "m -10,4 h 20", "none", "#333", '');
  2102. construc.params.width = 36;
  2103. construc.params.height = 36;
  2104. construc.family = 'stick';
  2105. }
  2106. if (typeObj === 'plug20') {
  2107. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2108. pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", '');
  2109. pushToConstruc(construc, "m 0,3 v 7", "none", "#333", '');
  2110. pushToConstruc(construc, "m -10,4 h 20", "none", "#333", '');
  2111. construc.push({
  2112. 'text': "20A",
  2113. 'x': '0',
  2114. 'y': '-5',
  2115. 'fill': "#333333",
  2116. 'stroke': "none",
  2117. 'fontSize': '0.65em',
  2118. "strokeWidth": "0.4px"
  2119. });
  2120. construc.params.width = 36;
  2121. construc.params.height = 36;
  2122. construc.family = 'stick';
  2123. }
  2124. if (typeObj === 'plug32') {
  2125. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2126. pushToConstruc(construc, "M 10,-6 a 10,10 0 0 1 -5,8 10,10 0 0 1 -10,0 10,10 0 0 1 -5,-8", "none", "#333", '');
  2127. pushToConstruc(construc, "m 0,3 v 7", "none", "#333", '');
  2128. pushToConstruc(construc, "m -10,4 h 20", "none", "#333", '');
  2129. construc.push({
  2130. 'text': "32A",
  2131. 'x': '0',
  2132. 'y': '-5',
  2133. 'fill': "#333333",
  2134. 'stroke': "none",
  2135. 'fontSize': '0.65em',
  2136. "strokeWidth": "0.4px"
  2137. });
  2138. construc.params.width = 36;
  2139. construc.params.height = 36;
  2140. construc.family = 'stick';
  2141. }
  2142. if (typeObj === 'roofLight') {
  2143. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2144. pushToConstruc(construc, "M -8,-8 L 8,8 M -8,8 L 8,-8", "none", "#333", '');
  2145. construc.params.width = 36;
  2146. construc.params.height = 36;
  2147. construc.family = 'free';
  2148. }
  2149. if (typeObj === 'wallLight') {
  2150. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2151. pushToConstruc(construc, "M -8,-8 L 8,8 M -8,8 L 8,-8", "none", "#333", '');
  2152. pushToConstruc(construc, "M -10,10 L 10,10", "none", "#333", '');
  2153. construc.params.width = 36;
  2154. construc.params.height = 36;
  2155. construc.family = 'stick';
  2156. }
  2157. if (typeObj === 'www') {
  2158. pushToConstruc(construc, "m -20,-20 l 40,0 l0,40 l-40,0 Z", "#fff", "#333", '');
  2159. construc.push({
  2160. 'text': "@",
  2161. 'x': '0',
  2162. 'y': '4',
  2163. 'fill': "#333333",
  2164. 'stroke': "none",
  2165. 'fontSize': '1.2em',
  2166. "strokeWidth": "0.4px"
  2167. });
  2168. construc.params.width = 40;
  2169. construc.params.height = 40;
  2170. construc.family = 'free';
  2171. }
  2172. if (typeObj === 'rj45') {
  2173. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2174. pushToConstruc(construc, "m-10,5 l0,-10 m20,0 l0,10", "none", "#333", '');
  2175. pushToConstruc(construc, "m 0,5 v 7", "none", "#333", '');
  2176. pushToConstruc(construc, "m -10,5 h 20", "none", "#333", '');
  2177. construc.push({
  2178. 'text': "RJ45",
  2179. 'x': '0',
  2180. 'y': '-5',
  2181. 'fill': "#333333",
  2182. 'stroke': "none",
  2183. 'fontSize': '0.5em',
  2184. "strokeWidth": "0.4px"
  2185. });
  2186. construc.params.width = 36;
  2187. construc.params.height = 36;
  2188. construc.family = 'stick';
  2189. }
  2190. if (typeObj === 'tv') {
  2191. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2192. pushToConstruc(construc, "m-10,5 l0-10 m20,0 l0,10", "none", "#333", '');
  2193. pushToConstruc(construc, "m-7,-5 l0,7 l14,0 l0,-7", "none", "#333", '');
  2194. pushToConstruc(construc, "m 0,5 v 7", "none", "#333", '');
  2195. pushToConstruc(construc, "m -10,5 h 20", "none", "#333", '');
  2196. construc.push({
  2197. 'text': "TV",
  2198. 'x': '0',
  2199. 'y': '-5',
  2200. 'fill': "#333333",
  2201. 'stroke': "none",
  2202. 'fontSize': '0.5em',
  2203. "strokeWidth": "0.4px"
  2204. });
  2205. construc.params.width = 36;
  2206. construc.params.height = 36;
  2207. construc.family = 'stick';
  2208. }
  2209. if (typeObj === 'heater') {
  2210. pushToConstruc(construc, qSVG.circlePath(0, 0, 16), "#fff", "#000", '');
  2211. pushToConstruc(construc, "m-15,-4 l30,0", "none", "#333", '');
  2212. pushToConstruc(construc, "m-14,-8 l28,0", "none", "#333", '');
  2213. pushToConstruc(construc, "m-11,-12 l22,0", "none", "#333", '');
  2214. pushToConstruc(construc, "m-16,0 l32,0", "none", "#333", '');
  2215. pushToConstruc(construc, "m-15,4 l30,0", "none", "#333", '');
  2216. pushToConstruc(construc, "m-14,8 l28,0", "none", "#333", '');
  2217. pushToConstruc(construc, "m-11,12 l22,0", "none", "#333", '');
  2218. construc.params.width = 36;
  2219. construc.params.height = 36;
  2220. construc.family = 'stick';
  2221. }
  2222. if (typeObj === 'radiator') {
  2223. pushToConstruc(construc, "m -20,-10 l 40,0 l0,20 l-40,0 Z", "#fff", "#333", '');
  2224. pushToConstruc(construc, "M -15,-10 L -15,10", "#fff", "#333", '');
  2225. pushToConstruc(construc, "M -10,-10 L -10,10", "#fff", "#333", '');
  2226. pushToConstruc(construc, "M -5,-10 L -5,10", "#fff", "#333", '');
  2227. pushToConstruc(construc, "M -0,-10 L -0,10", "#fff", "#333", '');
  2228. pushToConstruc(construc, "M 5,-10 L 5,10", "#fff", "#333", '');
  2229. pushToConstruc(construc, "M 10,-10 L 10,10", "#fff", "#333", '');
  2230. pushToConstruc(construc, "M 15,-10 L 15,10", "#fff", "#333", '');
  2231. construc.params.width = 40;
  2232. construc.params.height = 20;
  2233. construc.family = 'stick';
  2234. }
  2235. }
  2236. if (classObj === 'furniture') {
  2237. construc.params.bindBox = true;
  2238. construc.params.move = true;
  2239. construc.params.resize = true;
  2240. construc.params.rotate = true;
  2241. }
  2242. return construc;
  2243. }
  2244. function setBestEqPoint(bestEqPoint, distance, index, x, y, x1, y1, x2, y2, way) {
  2245. bestEqPoint.distance = distance;
  2246. bestEqPoint.node = index;
  2247. bestEqPoint.x = x;
  2248. bestEqPoint.y = y;
  2249. bestEqPoint.x1 = x1;
  2250. bestEqPoint.y1 = y1;
  2251. bestEqPoint.x2 = x2;
  2252. bestEqPoint.y2 = y2;
  2253. bestEqPoint.way = way;
  2254. }
  2255. function pushToRibMaster(ribMaster, firstIndex, secondIndex, wallIndex, crossEdge, side, coords, distance) {
  2256. ribMaster[firstIndex][secondIndex].push({
  2257. wallIndex: wallIndex,
  2258. crossEdge: crossEdge,
  2259. side: side,
  2260. coords: coords,
  2261. distance: distance
  2262. });
  2263. }
  2264. function pushToConstruc(construc, path, fill, stroke, strokeDashArray, opacity = 1) {
  2265. construc.push({
  2266. 'path': path,
  2267. 'fill': fill,
  2268. 'stroke': stroke,
  2269. 'strokeDashArray': strokeDashArray,
  2270. 'opacity': opacity
  2271. });
  2272. }
  2273. // Export button event handler
  2274. document.getElementById('export_mode').addEventListener('click', function() {
  2275. // Generate filename with current date
  2276. const now = new Date();
  2277. const dateStr = now.getFullYear() + '-' +
  2278. String(now.getMonth() + 1).padStart(2, '0') + '-' +
  2279. String(now.getDate()).padStart(2, '0');
  2280. const filename = 'floorplan_' + dateStr;
  2281. // Call the export function
  2282. if (exportFloorplanJSON(filename, true)) {
  2283. $('#boxinfo').html('Floorplan exported successfully!');
  2284. } else {
  2285. $('#boxinfo').html('Export failed. Please try again.');
  2286. }
  2287. });
  2288. // Blender export button event handler
  2289. document.getElementById('export_blender_mode').addEventListener('click', function() {
  2290. // Generate filename with current date
  2291. const now = new Date();
  2292. const dateStr = now.getFullYear() + '-' +
  2293. String(now.getMonth() + 1).padStart(2, '0') + '-' +
  2294. String(now.getDate()).padStart(2, '0');
  2295. const filename = 'floorplan_blender_' + dateStr;
  2296. // Call the Blender export function
  2297. if (exportForBlender(filename, 2.8, 0.08)) {
  2298. $('#boxinfo').html('Floorplan exported for Blender successfully!');
  2299. } else {
  2300. $('#boxinfo').html('Blender export failed. Please try again.');
  2301. }
  2302. });
  2303. // Import button event handler
  2304. document.getElementById('import_mode').addEventListener('click', function() {
  2305. // Show confirmation dialog before importing (will clear current work)
  2306. if (WALLS.length > 0 || OBJDATA.length > 0 || ROOM.length > 0) {
  2307. if (!confirm('Importing will replace your current floorplan. Are you sure you want to continue?')) {
  2308. $('#boxinfo').html('Import cancelled');
  2309. return;
  2310. }
  2311. }
  2312. // Trigger the file selection dialog
  2313. triggerImportDialog();
  2314. });
  2315. // Import from AI button event handler
  2316. document.getElementById('import_ai_mode').addEventListener('click', function () {
  2317. // Show confirmation dialog before importing (will clear current work)
  2318. if (typeof WALLS !== 'undefined' && (WALLS.length > 0 || (typeof OBJDATA !== 'undefined' && OBJDATA.length > 0) || (typeof ROOM !== 'undefined' && ROOM.length > 0))) {
  2319. if (!confirm('Importing will replace your current floorplan. Are you sure you want to continue?')) {
  2320. if (typeof $('#boxinfo') !== 'undefined') $('#boxinfo').html('Import cancelled');
  2321. return;
  2322. }
  2323. }
  2324. // Trigger the AI import dialog
  2325. if (typeof triggerAIImportDialog === 'function') {
  2326. triggerAIImportDialog();
  2327. } else {
  2328. console.error('triggerAIImportDialog is not available');
  2329. if (typeof $('#boxinfo') !== 'undefined') $('#boxinfo').html('AI import is not available');
  2330. }
  2331. });
  2332. // Import image button event handler
  2333. document.getElementById('import_image_mode').addEventListener('click', function() {
  2334. // Trigger the image import dialog
  2335. triggerImageImportDialog();
  2336. });
  2337. // Background image opacity slider event handler (guard against missing element)
  2338. (function(){
  2339. const opacitySlider = document.getElementById('backgroundImageOpacitySlider');
  2340. if (opacitySlider) {
  2341. opacitySlider.addEventListener('input', function() {
  2342. const opacityValue = this.value;
  2343. if (typeof setBackgroundImageOpacity === 'function') setBackgroundImageOpacity(opacityValue);
  2344. const label = document.getElementById('backgroundImageOpacityVal');
  2345. if (label) label.textContent = opacityValue;
  2346. });
  2347. }
  2348. })();
  2349. // Background image remove button event handler (guard against missing element)
  2350. (function(){
  2351. const removeBtn = document.getElementById('backgroundImageRemove');
  2352. if (removeBtn) {
  2353. removeBtn.addEventListener('click', function() {
  2354. if (confirm('Are you sure you want to remove the background image?')) {
  2355. if (typeof removeBackgroundImage === 'function') removeBackgroundImage();
  2356. if (typeof hideBackgroundImageTools === 'function') hideBackgroundImageTools();
  2357. if (typeof $ !== 'undefined') $('#boxinfo').html('Background image removed');
  2358. }
  2359. });
  2360. }
  2361. })();
  2362. // Combined Import (JSON + Image) modal handlers
  2363. (function(){
  2364. const jsonInput = document.getElementById('combined_json_input');
  2365. const imageInput = document.getElementById('combined_image_input');
  2366. const importBtn = document.getElementById('combined_import_btn');
  2367. const jsonName = document.getElementById('combined_json_name');
  2368. const imageName = document.getElementById('combined_image_name');
  2369. const errMsg = document.getElementById('combined_error_msg');
  2370. const okMsg = document.getElementById('combined_success_msg');
  2371. if (!(jsonInput && imageInput && importBtn)) return; // Modal not present
  2372. function resetMessages() {
  2373. if (errMsg) errMsg.textContent = '';
  2374. if (okMsg) okMsg.textContent = '';
  2375. }
  2376. function validateEnable() {
  2377. const jf = jsonInput.files && jsonInput.files[0];
  2378. const imf = imageInput.files && imageInput.files[0];
  2379. let ok = true;
  2380. resetMessages();
  2381. if (jf) {
  2382. const validJson = /\.json$/i.test(jf.name);
  2383. if (!validJson) { ok = false; if (errMsg) errMsg.textContent = 'Selected JSON file is not a .json'; }
  2384. } else { ok = false; }
  2385. // Image is optional; if provided, validate type
  2386. if (imf) {
  2387. const validImg = /\.(png|jpe?g)$/i.test(imf.name);
  2388. if (!validImg) { ok = false; if (errMsg) errMsg.textContent = 'Selected image must be PNG or JPG'; }
  2389. }
  2390. importBtn.disabled = !ok;
  2391. }
  2392. jsonInput.addEventListener('change', function(){
  2393. if (jsonName) jsonName.textContent = this.files[0] ? this.files[0].name : '';
  2394. validateEnable();
  2395. });
  2396. imageInput.addEventListener('change', function(){
  2397. if (imageName) imageName.textContent = this.files[0] ? this.files[0].name : '';
  2398. validateEnable();
  2399. });
  2400. importBtn.addEventListener('click', async function(){
  2401. resetMessages();
  2402. // Confirm replacing current work if any
  2403. try {
  2404. if ((typeof WALLS !== 'undefined' && WALLS.length) || (typeof OBJDATA !== 'undefined' && OBJDATA.length) || (typeof ROOM !== 'undefined' && ROOM.length)) {
  2405. if (!confirm('Importing will replace your current floorplan. Continue?')) {
  2406. if (typeof $ !== 'undefined') $('#boxinfo').html('Import cancelled');
  2407. return;
  2408. }
  2409. }
  2410. } catch(e) { /* ignore */ }
  2411. const jf = jsonInput.files[0];
  2412. const imf = imageInput.files[0];
  2413. importBtn.disabled = true;
  2414. importBtn.textContent = 'Importing...';
  2415. try {
  2416. // Use AI importer exclusively per request
  2417. const jsonOk = await (typeof importAIFloorplanJSON === 'function' ? importAIFloorplanJSON(jf) : Promise.resolve(false));
  2418. if (!jsonOk) {
  2419. if (errMsg) errMsg.textContent = 'Failed to import floorplan JSON using AI format.';
  2420. importBtn.textContent = 'Import';
  2421. validateEnable();
  2422. return;
  2423. }
  2424. // Image is optional: only try importing if provided
  2425. let imgOk = true;
  2426. if (imf) {
  2427. imgOk = await (typeof importBackgroundImage === 'function' ? importBackgroundImage(imf) : Promise.resolve(false));
  2428. if (!imgOk) {
  2429. if (errMsg) errMsg.textContent = 'Floorplan JSON loaded, but background image import failed.';
  2430. importBtn.textContent = 'Import';
  2431. validateEnable();
  2432. return;
  2433. }
  2434. }
  2435. if (okMsg) okMsg.textContent = imf ? 'Imported successfully!' : 'Floorplan JSON imported successfully.';
  2436. if (typeof $ !== 'undefined') $('#boxinfo').html(imf ? 'Floorplan and image imported successfully' : 'Floorplan JSON imported successfully');
  2437. // Close modal after short delay and clear inputs
  2438. setTimeout(function(){
  2439. const modalEl = document.getElementById('combinedImportModal');
  2440. if (modalEl && typeof bootstrap !== 'undefined' && bootstrap.Modal) {
  2441. const instance = bootstrap.Modal.getInstance(modalEl) || new bootstrap.Modal(modalEl);
  2442. instance.hide();
  2443. }
  2444. jsonInput.value = '';
  2445. imageInput.value = '';
  2446. if (jsonName) jsonName.textContent = '';
  2447. if (imageName) imageName.textContent = '';
  2448. importBtn.textContent = 'Import';
  2449. validateEnable();
  2450. }, 400);
  2451. } catch (e) {
  2452. console.error('Combined import error:', e);
  2453. if (errMsg) errMsg.textContent = 'Unexpected error during import.';
  2454. if (typeof $ !== 'undefined') $('#boxinfo').html('Import failed');
  2455. importBtn.textContent = 'Import';
  2456. validateEnable();
  2457. }
  2458. });
  2459. })();