DrawFromCSV.js 101 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155
  1. /**
  2. * DrawFromCSV version 3.04
  3. * Copyright (c) 2021 of CVH.
  4. * All rights reserved.
  5. *
  6. * This file is a free extension of the QCAD project.
  7. * Permission to evaluate, copy, modify, and/or distribute this software
  8. * for any purpose with or without fee is hereby granted, provided
  9. * that the above copyright notice and this permission notice appear
  10. * in all copies.
  11. *
  12. * This script and all related files are donated to the QCAD project.
  13. * Copyright (c) 2011-2021 by Andrew Mustun. All rights reserved.
  14. *
  15. * QCAD is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * (at your option) any later version.
  19. *
  20. * QCAD is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with QCAD.
  27. */
  28. include("scripts/Misc/MiscIO/MiscIO.js");
  29. // Including File.js for a file dialog
  30. include("scripts/File/File.js");
  31. /**
  32. * \class DrawFromCSV
  33. * \brief This action draws entities declared in a CSV file and handles document layers.
  34. * Supports most basic QCAD shapes, 2 kinds of solids, text, attributes, custom properties ...
  35. * Allows different CSV styles, empty CSV lines, empty cells and commenting lines.
  36. * All details see: DrawCSV_doc.pdf
  37. * \ingroup ecma_misc_io
  38. * \author CVH © 2021
  39. */
  40. // Define the class constructor, which calls the base class constructor:
  41. function DrawFromCSV(guiAction) {
  42. MiscIO.call(this, guiAction);
  43. };
  44. // Deriving our class from the base class:
  45. DrawFromCSV.prototype = new MiscIO();
  46. DrawFromCSV.includeBasePath = includeBasePath;
  47. // BeginEvent handler:
  48. DrawFromCSV.prototype.beginEvent = function() {
  49. MiscIO.prototype.beginEvent.call(this);
  50. // # Issue Fixed # Warnings/Info messages by dialog, many
  51. // Retrieve actual preferences for Warnings/Info messages if any:
  52. var oldWarningsAsDialog;
  53. if (RSettings.hasValue("CommandLine/WarningsAsDialog")) {
  54. oldWarningsAsDialog = RSettings.getBoolValue("CommandLine/WarningsAsDialog", false);
  55. }
  56. var oldInfoAsDialog;
  57. if (RSettings.hasValue("CommandLine/InfoAsDialog")) {
  58. oldInfoAsDialog = RSettings.getBoolValue("CommandLine/InfoAsDialog", false);
  59. }
  60. // Temporarily clear settings only when those existed and were set:
  61. if (oldWarningsAsDialog === true) RSettings.setValue("CommandLine/WarningsAsDialog", false);
  62. if (oldInfoAsDialog === true) RSettings.setValue("CommandLine/InfoAsDialog", false);
  63. // Call main script without file name:
  64. // The user can choose a file from a file dialog
  65. this.DrawFromCSVFile(this.getDocumentInterface(), undefined);
  66. // Restore former settings only when those existed and were set:
  67. if (oldWarningsAsDialog === true) RSettings.setValue("CommandLine/WarningsAsDialog", true);
  68. if (oldInfoAsDialog === true) RSettings.setValue("CommandLine/InfoAsDialog", true);
  69. // # Issue Fixed # Button toggles between usage, fixed with releasing
  70. this.getGuiAction().setChecked(false);
  71. // Terminate beginEvent:
  72. this.terminate();
  73. return;
  74. };
  75. /**
  76. * Process a CSV file by file name or by file name from a dialog.
  77. * \author CVH © 2021
  78. *
  79. * \param di Document interface.
  80. * \param (fileName) Optional CSV file name.
  81. *
  82. * \return A list with affected entities ID or undefined.
  83. */
  84. DrawFromCSV.prototype.DrawFromCSVFile = function(di, fileName) {
  85. var decPnt; // Decimal point
  86. var lstSep; // List separator
  87. var msg; // Intermediate message strings
  88. var file, flags, ts; // User selected file & related
  89. var op, transaction, ids; // Operation to collect additions & returned transaction & affected list
  90. var textLine, textLineCount; // A single line of text & counter
  91. var fields, fieldsCount; // The text line split in separate fields & counter
  92. var xtrField1, xtrField2 // Additional fields
  93. var offsetRv; // Current offset vector
  94. var polyRounding; // Rounding radius for polylines
  95. var layer; // A layer object
  96. var layerOp; // A layer operation
  97. var zoomToAll; // Flag to zoom to all new after casting
  98. var selectAll; // Flag to select all new after casting
  99. var newData; // Newly created shape data
  100. var newEntity, newShape; // Newly created document entity/shape
  101. var anyError; // Error message
  102. var refRv, refRv1, refRv2, refRv3, refRv4; // Reference points RVectors
  103. var refs; // Collection of references
  104. var radius; // Radius value
  105. var angle1, angle2; // Angle values
  106. var bool; // A boolean value
  107. var circle1, circle2, ips, chord // Intersecting circles ArcTo
  108. var sizeX, sizeY; // X/Y size of square or rectangle
  109. var i; // Common iterator
  110. var bulge; // Bulge factor
  111. var txtHeight, txtWidth; // Text size attributes
  112. var txtVAlign, txtHAlign, txtDirection; // Text align attributes
  113. var txtLSStyle, txtLineSpace; // Text Line space attributes
  114. var boldBool, italicBool, simpleBool; // Text boolean values
  115. var attribText; // Entity textual custom attributes, unvalidated
  116. var customAttrib; // Entity custom attributes, default or validated
  117. var customProp; // Entity textual custom properties
  118. var keyValue; // A Key:Value pair
  119. var castCount; // Newly created document entities counter
  120. var graphicsView // View to zoom in
  121. this.di = di;
  122. this.doc = di.getDocument();
  123. // Without any, get a filename by dialog:
  124. if (isNull(fileName)) { // Without a filename as parameter >
  125. fileName = this.getFileName(); // .getFileName as in BlockListExport.js
  126. // Validate return value:
  127. if (isNull(fileName)) { // Without a filename >
  128. msg = qsTr("No import file selected.");
  129. EAction.handleUserWarning(msg); // Push warning to history (Win)
  130. qDebug(msg); // Push warning to stdout
  131. msg = qsTr("Command ended.");
  132. EAction.handleUserMessage(msg); // Push message to history (Win)
  133. qDebug(msg); // Push End to stdout
  134. return undefined; // Failed, no filename
  135. } // End without a filename
  136. } // End without parameter
  137. // -> Continue with a fileName
  138. // Try to open the file to read from:
  139. file = new QFile(fileName);
  140. flags = makeQIODeviceOpenMode(QIODevice.ReadOnly, QIODevice.Text);
  141. if (!file.open(flags)) { // Without a open file >
  142. msg = qsTr("No file opened.");
  143. EAction.handleUserWarning(msg); // Push warning to history (Win)
  144. qDebug(msg); // Push warning to stdout
  145. msg = qsTr("Command ended.");
  146. EAction.handleUserMessage(msg); // Push message to history (Win)
  147. qDebug(msg); // Push End to stdout
  148. return undefined; // Failed, no open file
  149. } // End without open file
  150. // -> Continue with an open file
  151. // Additional Command History script notification:
  152. EAction.handleUserMessage(qsTr("DrawFromCSV script (v3.03) by CVH"));
  153. // Get QCAD native decimal point or a dot by default:
  154. decPnt = RSettings.getStringValue("Input/DecimalPoint", ".");
  155. // This may alter at the point where a style is declared in the CSV.
  156. // Get QCAD native list separator or a comma by default:
  157. lstSep = RSettings.getStringValue("Input/CartesianCoordinateSeparator", ",");
  158. // This may alter at the point where a style is declared in the CSV.
  159. // Native list separator, persistent for user messages:
  160. this.nativeLstSep = lstSep;
  161. // Retrieve layer hierarchy separator, if any:
  162. this.layerSep = RLayer.getHierarchySeparator(); // Requires RLayer proxy
  163. // # Issue Fixed # Revert to 3 dots when not a string value (CE):
  164. // Approved by Andrew: PM Thu Nov 18, 2021 5:16 pm
  165. if (!isString(this.layerSep)) this.layerSep = " ... ";
  166. // Preset other initial values:
  167. this.currentLayerName = this.doc.getCurrentLayerName(); // Actual active layer name
  168. this.relativePos = new RVector(0.0, 0.0); // Reset relative position
  169. textLineCount = castCount = 0; // Reset counters
  170. offsetRv = new RVector(0.0, 0.0); // Reset offset vector
  171. polyRounding = 0.0; // Reset rounding radius
  172. zoomToAll = selectAll = false // Reset zoom and select output flags
  173. // Initiate a TextStream:
  174. ts = new QTextStream(file);
  175. setUtf8Codec(ts);
  176. // Start a new transaction group:
  177. this.doc.startTransactionGroup();
  178. // Initiate an operation for adding entities:
  179. op = new RAddObjectsOperation();
  180. // Allow all transactions, layers can be hidden, frozen and or locked:
  181. op.setAllowAll(true);
  182. // Set tool title used for undo/redo information:
  183. op.setText(qsTr("Cast CSV")); // Translated tool title
  184. // Use global transaction group:
  185. op.setTransactionGroup(this.doc.getTransactionGroup());
  186. // Report reading from file:
  187. msg = qsTr("Reading from file: %1").arg(fileName);
  188. EAction.handleUserInfo(msg); // Push info to history (Win)
  189. qDebug(msg); // Push info to stdout
  190. //debugger;
  191. // Stream input from file
  192. // - - - - - - - - - - - -
  193. // Process all definition lines:
  194. do {
  195. // Initial values every CSV text line:
  196. newEntity = "n/a"; // No entity
  197. this.lastCoordinate = undefined; // Empty = No last valid coordinate
  198. anyError = undefined; // Empty = No error
  199. xtrField1 = xtrField2 = undefined; // Empty = None
  200. customAttrib = { // Default = Current layer & all byLayer & 1
  201. layer : this.currentLayerName,
  202. color : new RColor(RColor.ByLayer),
  203. lineweight : RLineweight.WeightByLayer,
  204. linetype : this.doc.getLinetypeId("BYLAYER"),
  205. LinetypeScale : 1.0};
  206. // Read a text line trimmed from leading and trailing whitespace:
  207. // # ToDo # Allow and strip trailing remark
  208. textLine = ts.readLine().trim();
  209. textLineCount++;
  210. // # Issue Fixed # For all CSV styles any tab is redundant.
  211. // # Issue # What about tab delimited files ... No example.
  212. // Remove any tab:
  213. textLine = textLine.replace(/\t+/,"");
  214. // # Issue Fixed # For all CSV styles any trailing comma or semicolon is redundant.
  215. // Remove any trailing list separator:
  216. textLine = textLine.replace(/[\;\,]+$/,"");
  217. // # Issue Fixed # For all CSV styles any double quotation marks is redundant.
  218. // Remove any double quotation marks:
  219. textLine = textLine.replace(/\"+/g,"");
  220. // Detect CSV style:
  221. if (textLine.toUpperCase().startsWith("CSV")) { // With a CSV switch >
  222. // Process by short CSV style in uppercase:
  223. switch (textLine.slice(3).toUpperCase()) {
  224. case "COMMA/DOT":
  225. decPnt = ".";
  226. lstSep = ",";
  227. msg = qsTr("Switched to comma/dot CSV style.");
  228. break;
  229. case "SEMICOLON/DOT":
  230. decPnt = ".";
  231. lstSep = ";";
  232. msg = qsTr("Switched to semicolon/dot CSV style.");
  233. break;
  234. case "SEMICOLON/COMMA":
  235. decPnt = ",";
  236. lstSep = ";";
  237. msg = qsTr("Switched to semicolon/comma CSV style.");
  238. break;
  239. default:
  240. // Report unsupported CSV style switch:
  241. msg = qsTr("Unsupported or invalid CSV style, ignored. Text line: %1").arg(textLineCount);
  242. EAction.handleUserWarning(msg); // Push warning to history (Win)
  243. qDebug(msg); // Push warning to stdout
  244. continue; // Not valid, ignore line and skip
  245. } // End switch on short CSV style
  246. // -> Continue with a forced valid CSV style
  247. // Report CSV style switch:
  248. EAction.handleUserInfo(msg); // Push info to history (Win)
  249. qDebug(msg); // Push info to stdout
  250. // On a CSV style skip to next line:
  251. continue;
  252. } // End is CSV switch
  253. // -> Continue without a CSV switch
  254. // Detect comment line:
  255. if (textLine.startsWith(";")) {
  256. // On a comment line skip to next line
  257. continue;
  258. } // End is comment
  259. // -> Continue without a comment line
  260. // Detect empty line:
  261. if (textLine.length === 0) { // When empty >
  262. // On an empty line skip to next line
  263. continue;
  264. } // End is empty
  265. // -> Continue without an empty line
  266. // Remove any empty field and all whitespace adjacent a list separator:
  267. if (lstSep === ";") { // When using semicolon >
  268. textLine = textLine.replace(/\s*\;+\s*/g,";");
  269. } // End when semicolon
  270. else if (lstSep === ",") { // When using comma >
  271. textLine = textLine.replace(/\s*\,+\s*/g,",");
  272. } // End when comma
  273. else { // When using other >
  274. // Should not occur for now
  275. } // End when other
  276. // -> Continue without empty fields
  277. // Isolate extra fields:
  278. fields = textLine.split(lstSep + "\&\["); // Reserved: ',&[' & ';&['
  279. if (fields.length === 3) { // With two extra fields >
  280. // Store second extra field without closing bracket and trimmed:
  281. xtrField2 = fields[2].replace(/\]/g,"").trim();
  282. } // End with two extra
  283. if (fields.length > 1) { // With any extra fields >
  284. // Store first extra field without closing bracket and trimmed:
  285. xtrField1 = fields[1].replace(/\]/g,"").trim();
  286. } // End with any extra
  287. textLine = fields[0];
  288. // -> Continue without optional entries
  289. // Adapt decimal point for semicolon style CSV:
  290. if (lstSep === ";") { // when using semicolon >
  291. textLine = textLine.replace(/\,/g,".");
  292. if (!isNull(xtrField1)) xtrField1 = xtrField1.replace(/\,/g,".");
  293. if (!isNull(xtrField2)) xtrField2 = xtrField2.replace(/\,/g,".");
  294. // # Not an Issue # Husky used dot/semicolon
  295. // https://qcad.org/rsforum/viewtopic.php?t=8116
  296. } // End when semicolon
  297. // -> Continue with dots as decimal point
  298. // Split up definition line at list separators:
  299. fields = textLine.split(lstSep);
  300. fieldsCount = fields.length;
  301. //debugger;
  302. // Process definition by nominator key in uppercase:
  303. switch (fields[0].toUpperCase()) { // On nominator key in upper case
  304. case "NEWORIGIN":
  305. // Create new offset vector:
  306. refRv = this.getCoordinate(fields[1], fields[2]);
  307. // Set new offset vector if valid:
  308. if (!isNull(refRv)) { // With a valid vector >
  309. offsetRv = refRv;
  310. // Reset relative position:
  311. this.relativePos = new RVector(0.0, 0.0);
  312. } // End valid
  313. else { // Without a valid vector >
  314. msg = qsTr("Setting new relative origin failed, ignored. Text line: %1").arg(textLineCount);
  315. EAction.handleUserWarning(msg); // Push warning to history (Win)
  316. qDebug(msg); // Push warning to stdout
  317. } // End NOT valid
  318. break;
  319. case "POLYROUND":
  320. // Set new polyline rounding radius:
  321. // Requires RPolyline proxy:
  322. if (RPolyline.hasProxy()) { // With proxy >
  323. radius = parseFloat(fields[1]);
  324. // Set new polyline rounding when valid:
  325. if (this.validateAsRadius(radius)) { // With a valid radius >
  326. polyRounding = radius;
  327. } // End valid
  328. else { // Without a valid radius >
  329. msg = qsTr("Setting new rounding radius failed, ignored. Text line: %1").arg(textLineCount);
  330. EAction.handleUserWarning(msg); // Push warning to history (Win)
  331. qDebug(msg); // Push warning to stdout
  332. } // End NOT valid
  333. } // End hasProxy
  334. else { // Without proxy >
  335. msg = qsTr("Rounding polylines requires QCAD Professional, ignored. Text line: %1").arg(textLineCount);
  336. EAction.handleUserWarning(msg); // Push warning to history (Win)
  337. qDebug(msg); // Push warning to stdout
  338. } // End without proxy
  339. break;
  340. case "LAYER":
  341. // Switch to an existing layer:
  342. msg = ""; // Empty
  343. if (fieldsCount === 2) { // With 2 fields
  344. // Adapt naming to proper layer hierarchy separator:
  345. fields[1] = fields[1].replace(/\//g, this.layerSep);
  346. // Validate layer switch:
  347. if (this.doc.hasLayer(fields[1])) { // When the layer exists >
  348. this.currentLayerName = fields[1];
  349. } // End existing
  350. else { // When the layer doesn't exists >
  351. msg = qsTr("Can not switch to non-existing layers, ignored. Text line: %1%2 Layer: %3")
  352. .arg(textLineCount).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  353. } // End NOT existing
  354. } // End with 2 fields
  355. else { // With any but 2 fields >
  356. msg = qsTr("Not a correct set active \'Layer\' switch, ignored. Text line: %1").arg(textLineCount);
  357. } // End any
  358. // Report any message:
  359. if (msg.length > 0) { // With a message >
  360. EAction.handleUserWarning(msg); // Push warning to history (Win)
  361. qDebug(msg); // Push warning to stdout
  362. } // End message
  363. break;
  364. case "NEWLAYER":
  365. // Add a new or update an existing layer:
  366. msg = ""; // Empty
  367. if (fieldsCount === 7) { // With 7 fields
  368. this.newOrUpdateLayer(fields, textLineCount);
  369. }
  370. else { // With any but 7 fields >
  371. msg = qsTr("Not a correct \'New layer\' switch, ignored. Text line: %1").arg(textLineCount);
  372. EAction.handleUserWarning(msg); // Push warning to history (Win)
  373. qDebug(msg); // Push warning to stdout
  374. } // End any
  375. break;
  376. case "HIDELAYER":
  377. // Hide an existing layer:
  378. msg = ""; // Empty
  379. if (fieldsCount === 2) { // With 2 fields
  380. // Adapt naming to proper layer hierarchy separator:
  381. fields[1] = fields[1].replace(/\//g, this.layerSep);
  382. // Validate layer switch:
  383. if (this.doc.hasLayer(fields[1])) { // When the layer exists >
  384. // Get the layer from the document:
  385. layer = this.doc.queryLayer(fields[1]);
  386. // Apply changes:
  387. layer.setOff(true);
  388. // Initiate an operation for updating a layer:
  389. layerOp = new RAddObjectOperation(layer, false); // NOTuseCurrentAttributes, undoAble=true
  390. // Use global transaction group:
  391. layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  392. // Update the layer on the fly:
  393. di.applyOperation(layerOp);
  394. } // End existing
  395. else { // When the layer doesn't exists >
  396. msg = qsTr("Can not hide non-existing layers, ignored. Text line: %1%2 Layer: %3")
  397. .arg(textLineCount).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  398. } // End NOT existing
  399. } // End with 2 fields
  400. else { // With any but 2 fields >
  401. msg = qsTr("Not a correct \'HideLayer\' switch, ignored. Text line: %1").arg(textLineCount);
  402. } // End any
  403. // Report any message:
  404. if (msg.length > 0) { // With a message >
  405. EAction.handleUserWarning(msg); // Push warning to history (Win)
  406. qDebug(msg); // Push warning to stdout
  407. } // End message
  408. break;
  409. case "FREEZELAYER":
  410. // Freeze an existing layer:
  411. msg = ""; // Empty
  412. if (fieldsCount === 2) { // With 2 fields
  413. // Adapt naming to proper layer hierarchy separator:
  414. fields[1] = fields[1].replace(/\//g, this.layerSep);
  415. // Validate layer switch:
  416. if (this.doc.hasLayer(fields[1])) { // When the layer exists >
  417. // Get the layer from the document:
  418. layer = this.doc.queryLayer(fields[1]);
  419. // Apply changes:
  420. layer.setFrozen(true);
  421. // Initiate an operation for updating a layer:
  422. layerOp = new RAddObjectOperation(layer, false); // NOTuseCurrentAttributes, undoAble=true
  423. // Use global transaction group:
  424. layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  425. // Update the layer on the fly:
  426. di.applyOperation(layerOp);
  427. } // End existing
  428. else { // When the layer doesn't exists >
  429. msg = qsTr("Can not freeze non-existing layers, ignored. Text line: %1%2 Layer: %3")
  430. .arg(textLineCount).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  431. } // End NOT existing
  432. } // End with 2 fields
  433. else { // With any but 2 fields >
  434. msg = qsTr("Not a correct \'FreezeLayer\' switch, ignored. Text line: %1").arg(textLineCount);
  435. } // End any
  436. // Report any message:
  437. if (msg.length > 0) { // With a message >
  438. EAction.handleUserWarning(msg); // Push warning to history (Win)
  439. qDebug(msg); // Push warning to stdout
  440. } // End message
  441. break;
  442. case "LOCKLAYER":
  443. // Lock an existing layer:
  444. msg = ""; // Empty
  445. if (fieldsCount === 2) { // With 2 fields
  446. // Adapt naming to proper layer hierarchy separator:
  447. fields[1] = fields[1].replace(/\//g, this.layerSep);
  448. // Validate layer switch:
  449. if (this.doc.hasLayer(fields[1])) { // When the layer exists >
  450. // Get the layer from the document:
  451. layer = this.doc.queryLayer(fields[1]);
  452. // Apply changes:
  453. layer.setLocked(true);
  454. // Initiate an operation for updating a layer:
  455. layerOp = new RAddObjectOperation(layer, false); // NOTuseCurrentAttributes, undoAble=true
  456. // Use global transaction group:
  457. layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  458. // Update the layer on the fly:
  459. di.applyOperation(layerOp);
  460. } // End existing
  461. else { // When the layer doesn't exists >
  462. msg = qsTr("Can not lock non-existing layers, ignored. Text line: %1%2 Layer: %3")
  463. .arg(textLineCount).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  464. } // End NOT existing
  465. } // End with 2 fields
  466. else { // With any but 2 fields >
  467. msg = qsTr("Not a correct \'LockLayer\' switch, ignored. Text line: %1").arg(textLineCount);
  468. } // End any
  469. // Report any message:
  470. if (msg.length > 0) { // With a message >
  471. EAction.handleUserWarning(msg); // Push warning to history (Win)
  472. qDebug(msg); // Push warning to stdout
  473. } // End message
  474. break;
  475. case "ALLOWLAYER":
  476. // Show/Thaw/Unlock an existing layer:
  477. msg = ""; // Empty
  478. if (fieldsCount === 2) { // With 2 fields
  479. // Adapt naming to proper layer hierarchy separator:
  480. fields[1] = fields[1].replace(/\//g, this.layerSep);
  481. // Validate layer switch:
  482. if (this.doc.hasLayer(fields[1])) { // When the layer exists >
  483. // Get the layer from the document:
  484. layer = this.doc.queryLayer(fields[1]);
  485. // Apply changes:
  486. layer.setOff(false);
  487. layer.setFrozen(false);
  488. layer.setLocked(false);
  489. // Initiate an operation for updating a layer:
  490. layerOp = new RAddObjectOperation(layer, false); // NOTuseCurrentAttributes, undoAble=true
  491. // Use global transaction group:
  492. layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  493. // Update the layer on the fly:
  494. di.applyOperation(layerOp);
  495. } // End existing
  496. else { // When the layer doesn't exists >
  497. msg = qsTr("Can not allow non-existing layers, ignored. Text line: %1%2 Layer: %3")
  498. .arg(textLineCount).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  499. } // End NOT existing
  500. } // End with 2 fields
  501. else { // With any but 2 fields >
  502. msg = qsTr("Not a correct \'AllowLayer\' switch, ignored. Text line: %1").arg(textLineCount);
  503. } // End any
  504. // Report any message:
  505. if (msg.length > 0) { // With a message >
  506. EAction.handleUserWarning(msg); // Push warning to history (Win)
  507. qDebug(msg); // Push warning to stdout
  508. } // End message
  509. break;
  510. case "ZOOMTOALL":
  511. // Set zoom to all flag:
  512. zoomToAll = true;
  513. break;
  514. case "SELECTALL":
  515. // Set select all flag:
  516. selectAll = true;
  517. break;
  518. case "POINT":
  519. // Create new Point entity:
  520. try { // Try to create an entity >
  521. if (fieldsCount < 3) throw qsTr("Low field count.");
  522. if (fieldsCount !== 3) throw qsTr("Odd field count.");
  523. refRv = this.getCoordinate(fields[1], fields[2]); // Reference
  524. if (isNull(refRv)) throw qsTr("Rv Invalid.");
  525. newData = new RPointData(refRv);
  526. newEntity = new RPointEntity(this.doc, newData);
  527. // Set single reference as relative position:
  528. this.relativePos = refRv;
  529. }
  530. catch(err) { // On any error > reset entity
  531. anyError = err;
  532. newEntity = undefined;
  533. }
  534. break;
  535. case "LINE":
  536. // Create new Line entity:
  537. try { // Try to create an entity >
  538. if (fieldsCount < 5) throw qsTr("Low field count.");
  539. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  540. refRv1 = this.getCoordinate(fields[1], fields[2]); // Startpoint
  541. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  542. refRv2 = this.getCoordinate(fields[3], fields[4]); // Endpoint
  543. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  544. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  545. newShape = new RLine(refRv1, refRv2);
  546. newEntity = new RLineEntity(this.doc, new RLineData(newShape));
  547. // Set endpoint as relative position:
  548. this.relativePos = refRv2;
  549. }
  550. catch(err) { // On any error > reset entity
  551. anyError = err;
  552. newEntity = undefined;
  553. }
  554. break;
  555. case "LINETO":
  556. // Create new Line entity:
  557. try { // Try to create an entity >
  558. if (fieldsCount < 3) throw qsTr("Low field count.");
  559. if (fieldsCount !== 3) throw qsTr("Odd field count.");
  560. refRv1 = this.relativePos; // Startpoint
  561. refRv2 = this.getCoordinate(fields[1], fields[2]); // Endpoint
  562. if (isNull(refRv2)) throw qsTr("Rv Invalid.");
  563. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  564. newShape = new RLine(refRv1, refRv2);
  565. newEntity = new RLineEntity(this.doc, new RLineData(newShape));
  566. // Set endpoint as relative position:
  567. this.relativePos = refRv2;
  568. }
  569. catch(err) { // On any error > reset entity
  570. anyError = err;
  571. newEntity = undefined;
  572. }
  573. break;
  574. case "VECTOR":
  575. // Create new Line entity:
  576. try { // Try to create an entity >
  577. if (fieldsCount < 5) throw qsTr("Low field count.");
  578. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  579. refRv1 = this.getCoordinate(fields[1], fields[2]); // Startpoint
  580. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  581. radius = parseFloat(fields[3]);
  582. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  583. angle1 = this.getAngleValue(fields[4], false);
  584. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  585. refRv = new RVector();
  586. refRv.setPolar(radius, angle1);
  587. refRv2 = refRv1.operator_add(refRv); // Endpoint
  588. if (!isValidVector(refRv2)) throw qsTr("Rv2 Invalid.");
  589. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  590. newShape = new RLine(refRv1, refRv2);
  591. newEntity = new RLineEntity(this.doc, new RLineData(newShape));
  592. // Set endpoint as relative position:
  593. this.relativePos = refRv2;
  594. }
  595. catch(err) { // On any error > reset entity
  596. anyError = err;
  597. newEntity = undefined;
  598. }
  599. break;
  600. case "VECTORTO":
  601. // Create new Line entity:
  602. try { // Try to create an entity >
  603. if (fieldsCount < 3) throw qsTr("Low field count.");
  604. if (fieldsCount !== 3) throw qsTr("Odd field count.");
  605. refRv1 = this.relativePos; // Startpoint
  606. radius = parseFloat(fields[1]);
  607. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  608. angle1 = this.getAngleValue(fields[2], false);
  609. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  610. refRv = new RVector();
  611. refRv.setPolar(radius, angle1);
  612. refRv2 = this.relativePos.operator_add(refRv); // Endpoint
  613. if (!isValidVector(refRv2)) throw qsTr("Rv2 Invalid.");
  614. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  615. newShape = new RLine(refRv1, refRv2);
  616. newEntity = new RLineEntity(this.doc, new RLineData(newShape));
  617. // Set endpoint as relative position:
  618. this.relativePos = refRv2;
  619. }
  620. catch(err) { // On any error > reset entity
  621. anyError = err;
  622. newEntity = undefined;
  623. }
  624. break;
  625. case "RAY":
  626. // Create new Ray entity:
  627. try { // Try to create an entity >
  628. if (fieldsCount < 5) throw qsTr("Low field count.");
  629. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  630. refRv1 = this.getCoordinate(fields[1], fields[2]); // Base point
  631. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  632. refRv2 = this.getCoordinate(fields[3], fields[4]); // Direction point
  633. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  634. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  635. newShape = new RRay(refRv1, refRv2.operator_subtract(refRv1));
  636. newEntity = new RRayEntity(this.doc, new RRayData(newShape));
  637. // Set base point as relative position:
  638. this.relativePos = refRv1;
  639. }
  640. catch(err) { // On any error > reset entity
  641. anyError = err;
  642. newEntity = undefined;
  643. }
  644. break;
  645. case "XLINE":
  646. // Create new XLine entity:
  647. try { // Try to create an entity >
  648. if (fieldsCount < 5) throw qsTr("Low field count.");
  649. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  650. refRv1 = this.getCoordinate(fields[1], fields[2]); // Base point
  651. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  652. refRv2 = this.getCoordinate(fields[3], fields[4]); // Direction point
  653. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  654. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  655. newShape = new RXLine(refRv1, refRv2.operator_subtract(refRv1));
  656. newEntity = new RXLineEntity(this.doc, new RXLineData(newShape));
  657. // Set base point as relative position:
  658. this.relativePos = refRv1;
  659. }
  660. catch(err) { // On any error > reset entity
  661. anyError = err;
  662. newEntity = undefined;
  663. }
  664. break;
  665. case "ARC":
  666. // Create new Arc entity:
  667. try { // Try to create an entity >
  668. if (fieldsCount < 7) throw qsTr("Low field count.");
  669. if (fieldsCount !== 7) throw qsTr("Odd field count.");
  670. refRv = this.getCoordinate(fields[1], fields[2]); // Center
  671. if (isNull(refRv)) throw qsTr("Rv Invalid.");
  672. radius = parseFloat(fields[3]);
  673. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  674. angle1 = this.getAngleValue(fields[4], false);
  675. if (isNull(angle1)) throw qsTr("Angle1 Invalid.");
  676. angle2 = this.getAngleValue(fields[5], false);
  677. if (isNull(angle2)) throw qsTr("Angle2 Invalid.");
  678. if (RMath.fuzzyAngleCompare(angle1, angle2)) throw qsTr("Equal angles.");
  679. bool = fields[6].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  680. newShape = new RArc(refRv, radius, angle1, angle2, bool);
  681. newEntity = new RArcEntity(this.doc, new RArcData(newShape));
  682. // Set endpoint as relative position:
  683. this.relativePos = newShape.getEndPoint();
  684. }
  685. catch(err) { // On any error > reset entity
  686. anyError = err;
  687. newEntity = undefined;
  688. }
  689. break;
  690. case "ARC3P":
  691. // Create new Arc entity:
  692. try { // Try to create an entity >
  693. if (fieldsCount < 7) throw qsTr("Low field count.");
  694. if (fieldsCount !== 7) throw qsTr("Odd field count.");
  695. refRv1 = this.getCoordinate(fields[1], fields[2]); // Startpoint
  696. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  697. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  698. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  699. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  700. refRv3 = this.getCoordinate(fields[5], fields[6]); // Endpoint
  701. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  702. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  703. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  704. angle1 = refRv1.getAngleTo(refRv2);
  705. angle2 = refRv1.getAngleTo(refRv3); // Assuming refRv1 is an endpoint
  706. if (RMath.fuzzyAngleCompare(angle1, angle2)) throw qsTr("Near co-linear positions.");
  707. angle2 = refRv3.getAngleTo(refRv1); // Assuming refRv1 is a fitpoint
  708. if (RMath.fuzzyAngleCompare(angle1, angle2)) throw qsTr("Near co-linear positions.");
  709. newShape = RArc.createFrom3Points(refRv1, refRv2, refRv3);
  710. if (!newShape.isValid()) throw qsTr("Arc Invalid.");
  711. if (!this.validateAsRadius(newShape.getRadius())) throw qsTr("Radius Invalid.");
  712. newEntity = new RArcEntity(this.doc, new RArcData(newShape));
  713. // Set endpoint as relative position:
  714. this.relativePos = refRv3;
  715. }
  716. catch(err) { // On any error > reset entity
  717. anyError = err;
  718. newEntity = undefined;
  719. }
  720. break;
  721. case "ARCTO+A":
  722. // Create new Arc entity:
  723. try { // Try to create an entity >
  724. if (fieldsCount < 5) throw qsTr("Low field count.");
  725. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  726. refRv1 = this.relativePos; // Startpoint
  727. refRv2 = this.getCoordinate(fields[1], fields[2]); // Endpoint
  728. if (isNull(refRv2)) throw qsTr("Rv Invalid.");
  729. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  730. angle1 = this.getAngleValue(fields[3], true);
  731. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  732. bool = fields[4].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  733. // Bulge conversion:
  734. radius = refRv1.getDistanceTo2D(refRv2) / 2 / Math.sin(angle1 / 2);
  735. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  736. // Get center from intersecting circles:
  737. circle1 = new RCircle(refRv1, radius);
  738. circle2 = new RCircle(refRv2, radius);
  739. ips = circle1.getIntersectionPoints(circle2, false);
  740. chord = new RLine(refRv1, refRv2);
  741. if (ips.length === 2) { // With 2 solutions >
  742. // XNOR: When true == true or false == false
  743. if ((chord.getSideOfPoint(ips[0]) == RS.RightHand) == bool) {
  744. refRv = ips[0];
  745. }
  746. else {
  747. refRv = ips[1];
  748. }
  749. }
  750. else if (ips.length === 1) { // When center is in the middle >
  751. refRv = chord.getMiddlePoint();
  752. }
  753. else { // Without intersections >
  754. // Should not occur
  755. throw qsTr("Center Invalid.");
  756. }
  757. // With a center point:
  758. angle1 = refRv.getAngleTo(refRv1);
  759. angle2 = refRv.getAngleTo(refRv2);
  760. newShape = new RArc(refRv, radius, angle1, angle2, bool);
  761. newEntity = new RArcEntity(this.doc, new RArcData(newShape));
  762. // Set endpoint as relative position:
  763. this.relativePos = refRv2;
  764. }
  765. catch(err) { // On any error > reset entity
  766. anyError = err;
  767. newEntity = undefined;
  768. }
  769. break;
  770. case "ARCTO+R":
  771. // Create new Arc entity:
  772. try { // Try to create an entity >
  773. if (fieldsCount < 5) throw qsTr("Low field count.");
  774. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  775. refRv1 = this.relativePos; // Startpoint
  776. refRv2 = this.getCoordinate(fields[1], fields[2]); // Endpoint
  777. if (isNull(refRv2)) throw qsTr("Rv Invalid.");
  778. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  779. radius = parseFloat(fields[3]);
  780. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  781. if (2*radius < refRv1.getDistanceTo2D(refRv2)) throw qsTr("Radius too small.");
  782. bool = fields[4].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  783. // Get center from intersecting circles:
  784. circle1 = new RCircle(refRv1, radius);
  785. circle2 = new RCircle(refRv2, radius);
  786. ips = circle1.getIntersectionPoints(circle2, false);
  787. chord = new RLine(refRv1, refRv2);
  788. if (ips.length === 2) { // With 2 solutions:
  789. // XNOR: true == true; false == false
  790. if ((chord.getSideOfPoint(ips[0]) == RS.RightHand) == bool) {
  791. refRv = ips[0];
  792. }
  793. else {
  794. refRv = ips[1];
  795. }
  796. }
  797. else if (ips.length === 1) { // When the center is in the middle:
  798. refRv = chord.getMiddlePoint();
  799. }
  800. else { // Without intersections >
  801. // Should not occur
  802. throw qsTr("Center Invalid.");
  803. }
  804. // With a center point:
  805. angle1 = refRv.getAngleTo(refRv1);
  806. angle2 = refRv.getAngleTo(refRv2);
  807. newShape = new RArc(refRv, radius, angle1, angle2, bool);
  808. newEntity = new RArcEntity(this.doc, new RArcData(newShape));
  809. // Set endpoint as relative position:
  810. this.relativePos = refRv2;
  811. }
  812. catch(err) { // On any error > reset entity
  813. anyError = err;
  814. newEntity = undefined;
  815. }
  816. break;
  817. case "CIRCLE":
  818. // Create new Circle entity:
  819. try { // Try to create an entity >
  820. if (fieldsCount < 4) throw qsTr("Low field count.");
  821. if (fieldsCount !== 4) throw qsTr("Odd field count.");
  822. refRv = this.getCoordinate(fields[1], fields[2]); // Center
  823. if (isNull(refRv)) throw qsTr("Rv Invalid.");
  824. radius = parseFloat(fields[3]);
  825. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  826. newShape = new RCircle(refRv, radius);
  827. newEntity = new RCircleEntity(this.doc, new RCircleData(newShape));
  828. // Set center as relative position:
  829. this.relativePos = refRv;
  830. }
  831. catch(err) { // On any error > reset entity
  832. anyError = err;
  833. newEntity = undefined;
  834. }
  835. break;
  836. case "CIRCLE+P":
  837. // Create new Circle entity:
  838. try { // Try to create an entity >
  839. if (fieldsCount < 5) throw qsTr("Low field count.");
  840. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  841. refRv1 = this.getCoordinate(fields[1], fields[2]); // Center
  842. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  843. refRv2 = this.getCoordinate(fields[3], fields[4]); // Fit point
  844. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  845. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions.");
  846. radius = refRv1.getDistanceTo2D(refRv2);
  847. if (!this.validateAsRadius(radius)) throw qsTr("Radius Invalid.");
  848. newShape = new RCircle(refRv1, radius);
  849. newEntity = new RCircleEntity(this.doc, new RCircleData(newShape));
  850. // Set fit point as relative position:
  851. this.relativePos = refRv2;
  852. }
  853. catch(err) { // On any error > reset entity
  854. anyError = err;
  855. newEntity = undefined;
  856. }
  857. break;
  858. case "CIRCLE3P":
  859. // Create new Circle entity:
  860. try { // Try to create an entity >
  861. if (fieldsCount < 7) throw qsTr("Low field count.");
  862. if (fieldsCount !== 7) throw qsTr("Odd field count.");
  863. refRv1 = this.getCoordinate(fields[1], fields[2]); // First reference
  864. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  865. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  866. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  867. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  868. refRv3 = this.getCoordinate(fields[5], fields[6]); // Third reference
  869. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  870. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  871. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  872. angle1 = refRv1.getAngleTo(refRv2);
  873. angle2 = refRv1.getAngleTo(refRv3); // Assuming refRv1 is an endpoint
  874. if (RMath.fuzzyAngleCompare(angle1, angle2)) throw qsTr("Near co-linear positions.");
  875. angle2 = refRv3.getAngleTo(refRv1); // Assuming refRv1 is a fitpoint
  876. if (RMath.fuzzyAngleCompare(angle1, angle2)) throw qsTr("Near co-linear positions.");
  877. newShape = RCircle.createFrom3Points(refRv1, refRv2, refRv3);
  878. if (!newShape.isValid()) throw qsTr("Circle Invalid.");
  879. if (!this.validateAsRadius(newShape.getRadius())) throw qsTr("Radius Invalid.");
  880. newEntity = new RCircleEntity(this.doc, new RCircleData(newShape));
  881. // Set third reference as relative position:
  882. this.relativePos = refRv3;
  883. }
  884. catch(err) { // On any error > reset entity
  885. anyError = err;
  886. newEntity = undefined;
  887. }
  888. break;
  889. case "TRIANGLE": // Only Line-segments, No bulging
  890. // Create new closed Polyline entity:
  891. try { // Try to create an entity from a shape >
  892. if (fieldsCount < 7) throw qsTr("Low field count.");
  893. if (fieldsCount !== 7) throw qsTr("Odd field count.");
  894. refRv1 = this.getCoordinate(fields[1], fields[2]); // First reference
  895. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  896. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  897. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  898. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  899. refRv3 = this.getCoordinate(fields[5], fields[6]); // Third reference
  900. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  901. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  902. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  903. refs = new Array(refRv1, refRv2, refRv3);
  904. newShape = new RPolyline(refs, true);
  905. newShape = this.roundPolyline(newShape, polyRounding);
  906. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  907. // # General fix # Polylines with NaN widths.
  908. newEntity.setGlobalWidth(0.0);
  909. // Set first reference as relative position:
  910. this.relativePos = refRv1;
  911. }
  912. catch(err) { // On any error > reset entity
  913. anyError = err;
  914. newEntity = undefined;
  915. }
  916. break;
  917. case "RSOLID3":
  918. // Create new Solid entity:
  919. try { // Try to create an entity >
  920. if (fieldsCount < 7) throw qsTr("Low field count.");
  921. if (fieldsCount !== 7) throw qsTr("Odd field count.");
  922. refRv1 = this.getCoordinate(fields[1], fields[2]); // First reference
  923. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  924. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  925. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  926. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  927. refRv3 = this.getCoordinate(fields[5], fields[6]); // Third reference
  928. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  929. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  930. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  931. newData = new RSolidData(refRv1, refRv2, refRv3);
  932. newEntity = new RSolidEntity(this.doc, newData);
  933. // Set first reference as relative position:
  934. this.relativePos = refRv1;
  935. }
  936. catch(err) { // On any error > reset entity
  937. anyError = err;
  938. newEntity = undefined;
  939. }
  940. break;
  941. case "QUAD": // Only Line-segments, No bulging
  942. // Create new closed Polyline entity:
  943. try { // Try to create an entity from a shape >
  944. if (fieldsCount < 9) throw qsTr("Low field count.");
  945. if (fieldsCount !== 9) throw qsTr("Odd field count.");
  946. refRv1 = this.getCoordinate(fields[1], fields[2]); // First reference
  947. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  948. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  949. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  950. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  951. refRv3 = this.getCoordinate(fields[5], fields[6]); // Third reference
  952. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  953. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  954. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  955. refRv4 = this.getCoordinate(fields[7], fields[8]); // Forth reference
  956. if (isNull(refRv4)) throw qsTr("Rv4 Invalid.");
  957. if (refRv1.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 1-4.");
  958. if (refRv2.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 2-4.");
  959. if (refRv3.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 3-4.");
  960. refs = new Array(refRv1, refRv2, refRv3, refRv4);
  961. newShape = new RPolyline(refs, true);
  962. newShape = this.roundPolyline(newShape, polyRounding);
  963. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  964. // # General fix # Polylines with NaN widths.
  965. newEntity.setGlobalWidth(0.0);
  966. // Set first reference as relative position:
  967. this.relativePos = refRv1;
  968. }
  969. catch(err) { // On any error > reset entity
  970. anyError = err;
  971. newEntity = undefined;
  972. }
  973. break;
  974. case "RSOLID4":
  975. // Create new Solid entity:
  976. try { // Try to create an entity >
  977. if (fieldsCount < 9) throw qsTr("Low field count.");
  978. if (fieldsCount !== 9) throw qsTr("Odd field count.");
  979. refRv1 = this.getCoordinate(fields[1], fields[2]); // First reference
  980. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  981. refRv2 = this.getCoordinate(fields[3], fields[4]); // Second reference
  982. if (isNull(refRv2)) throw qsTr("Rv2 Invalid.");
  983. if (refRv1.equalsFuzzy2D(refRv2)) throw qsTr("Equal positions 1-2.");
  984. refRv3 = this.getCoordinate(fields[5], fields[6]); // Third reference
  985. if (isNull(refRv3)) throw qsTr("Rv3 Invalid.");
  986. if (refRv1.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 1-3.");
  987. if (refRv2.equalsFuzzy2D(refRv3)) throw qsTr("Equal positions 2-3.");
  988. refRv4 = this.getCoordinate(fields[7], fields[8]); // Forth reference
  989. if (isNull(refRv4)) throw qsTr("Rv4 Invalid.");
  990. if (refRv1.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 1-4.");
  991. if (refRv2.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 2-4.");
  992. if (refRv3.equalsFuzzy2D(refRv4)) throw qsTr("Equal positions 3-4.");
  993. newData = new RSolidData(refRv1, refRv2, refRv4, refRv3);
  994. newEntity = new RSolidEntity(this.doc, newData);
  995. // Set first reference as relative position:
  996. this.relativePos = refRv1;
  997. }
  998. catch(err) { // On any error > reset entity
  999. anyError = err;
  1000. newEntity = undefined;
  1001. }
  1002. break;
  1003. case "SQUARE": // Only Line-segments, No bulging
  1004. // Create new closed Polyline entity:
  1005. try { // Try to create an entity from a shape >
  1006. if (fieldsCount < 5) throw qsTr("Low field count.");
  1007. if (fieldsCount !== 5) throw qsTr("Odd field count.");
  1008. refRv1 = this.getCoordinate(fields[1], fields[2]); // Reference
  1009. if (isNull(refRv1)) throw qsTr("Rv Invalid.");
  1010. sizeX = sizeY = parseFloat(fields[3]);
  1011. if (!isNumber(sizeX) || sizeX < RS.PointTolerance) throw qsTr("Size Invalid.");
  1012. angle1 = this.getAngleValue(fields[4], false);
  1013. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  1014. refRv2 = new RVector(refRv1.x + sizeX, refRv1.y);
  1015. if (!isValidVector(refRv2)) throw qsTr("Rv2 Invalid.");
  1016. refRv3 = new RVector(refRv1.x + sizeX, refRv1.y + sizeY);
  1017. if (!isValidVector(refRv3)) throw qsTr("Rv3 Invalid.");
  1018. refRv4 = new RVector(refRv1.x, refRv1.y + sizeY);
  1019. if (!isValidVector(refRv4)) throw qsTr("Rv4 Invalid.");
  1020. refs = new Array(refRv1, refRv2, refRv3, refRv4);
  1021. newShape = new RPolyline(refs, true);
  1022. newShape = this.roundPolyline(newShape, polyRounding);
  1023. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  1024. // # General fix # Polylines with NaN widths.
  1025. newEntity.setGlobalWidth(0.0);
  1026. // Rotate the shape around refRv1:
  1027. newEntity.rotate(angle1, refRv1);
  1028. // Set first reference as relative position:
  1029. this.relativePos = refRv1;
  1030. }
  1031. catch(err) { // On any error > reset entity
  1032. anyError = err;
  1033. newEntity = undefined;
  1034. }
  1035. break;
  1036. case "RECTANGLE": // Only Line-segments, No bulging
  1037. // Create new closed Polyline entity:
  1038. try { // Try to create an entity from a shape >
  1039. if (fieldsCount < 6) throw qsTr("Low field count.");
  1040. if (fieldsCount !== 6) throw qsTr("Odd field count.");
  1041. refRv1 = this.getCoordinate(fields[1], fields[2]); // Reference
  1042. if (isNull(refRv1)) throw qsTr("Rv1 Invalid.");
  1043. sizeX = parseFloat(fields[3]);
  1044. if (!isNumber(sizeX) || sizeX < RS.PointTolerance) throw qsTr("Size in X Invalid.");
  1045. sizeY = parseFloat(fields[4]);
  1046. if (!isNumber(sizeY) || sizeY < RS.PointTolerance) throw qsTr("Size in Y Invalid.");
  1047. angle1 = this.getAngleValue(fields[5], false);
  1048. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  1049. refRv2 = new RVector(refRv1.x + sizeX, refRv1.y);
  1050. if (!isValidVector(refRv2)) throw qsTr("Rv2 Invalid.");
  1051. refRv3 = new RVector(refRv1.x + sizeX, refRv1.y + sizeY);
  1052. if (!isValidVector(refRv3)) throw qsTr("Rv3 Invalid.");
  1053. refRv4 = new RVector(refRv1.x, refRv1.y + sizeY);
  1054. if (!isValidVector(refRv4)) throw qsTr("Rv4 Invalid.");
  1055. refs = new Array(refRv1, refRv2, refRv3, refRv4);
  1056. newShape = new RPolyline(refs, true);
  1057. newShape = this.roundPolyline(newShape, polyRounding);
  1058. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  1059. // # General fix # Polylines with NaN widths.
  1060. newEntity.setGlobalWidth(0.0);
  1061. // Rotate the shape around refRv1:
  1062. newEntity.rotate(angle1, refRv1);
  1063. // Set first reference as relative position:
  1064. this.relativePos = refRv1;
  1065. }
  1066. catch(err) { // On any error > reset entity
  1067. anyError = err;
  1068. newEntity = undefined;
  1069. }
  1070. break;
  1071. case "POLYGON": // Only Line-segments, No bulging
  1072. // Create new closed Polyline entity:
  1073. try { // Try to create an entity from a shape >
  1074. if (fieldsCount < 11) throw qsTr("Polygon: less than 5 references.");
  1075. if ((fieldsCount - 11)%2 !== 0) throw qsTr("Odd field count.");
  1076. refs = []; // Empty
  1077. for (i=1; i<fieldsCount; i+=2) { // Rv 1=1 3=2 5=3 Rv=(i+1)/2
  1078. refRv = this.getCoordinate(fields[i], fields[i+1]); // References
  1079. if (isNull(refRv)) throw qsTr("Rv%1 Invalid.").arg((i+1)/2);
  1080. refs.push(refRv);
  1081. }
  1082. newShape = new RPolyline(refs, true);
  1083. newShape = this.roundPolyline(newShape, polyRounding);
  1084. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  1085. // # General fix # Polylines with NaN widths.
  1086. newEntity.setGlobalWidth(0.0);
  1087. // Set first reference as relative position:
  1088. this.relativePos = newShape.getStartPoint();
  1089. }
  1090. catch(err) { // On any error > reset entity
  1091. anyError = err;
  1092. newEntity = undefined;
  1093. }
  1094. break;
  1095. case "POLYLINE":
  1096. // Create new Polyline entity:
  1097. try { // Try to create an entity from a shape >
  1098. if (fieldsCount < 8) throw qsTr("Polyline, less than 2 references.");
  1099. if ((fieldsCount - 8)%3 !== 0) throw qsTr("Odd field count.");
  1100. newShape = new RPolyline();
  1101. bool = fields[1].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1102. newShape.setClosed(bool);
  1103. for (i=2; i<fieldsCount; i+=3) { // Rv 2=1 5=2 8=3 Rv=(i+1)/3
  1104. refRv = this.getCoordinate(fields[i], fields[i+1]); // References
  1105. if (isNull(refRv)) throw qsTr("Rv%1 Invalid.").arg((i+1)/3)
  1106. bulge = parseFloat(fields[i+2]);
  1107. if (!isNumber(bulge)) throw qsTr("Bulge%1 Invalid.").arg((i+1)/3);
  1108. newShape.appendVertex(refRv, bulge);
  1109. }
  1110. newShape = this.roundPolyline(newShape, polyRounding);
  1111. newEntity = new RPolylineEntity(this.doc, new RPolylineData(newShape));
  1112. // # General fix # Polylines with NaN widths.
  1113. newEntity.setGlobalWidth(0.0);
  1114. // Set first/last reference as relative position:
  1115. this.relativePos = (bool) ? newShape.getStartPoint() : refRv;
  1116. }
  1117. catch(err) { // On any error > reset entity
  1118. anyError = err;
  1119. newEntity = undefined;
  1120. }
  1121. break;
  1122. case "FITSPLINE":
  1123. // Create new fit point Spline entity:
  1124. try { // Try to create an entity from a shape >
  1125. if (!RSpline.hasProxy()) throw qsTr("Unsupported QCAD Professional feature.");
  1126. if (fieldsCount < 8) throw qsTr("3th degree fit point Spline, less than 3 references.");
  1127. if ((fieldsCount - 8)%2 !== 0) throw qsTr("Odd field count.");
  1128. refs = []; // Empty
  1129. for (i=2; i<fieldsCount; i+=2) { // Rv 2=1 4=2 6=3 Rv=i/2
  1130. refRv = this.getCoordinate(fields[i], fields[i+1]); // References
  1131. if (isNull(refRv)) throw qsTr("Rv%1 Invalid.").arg(i/2);
  1132. refs.push(refRv);
  1133. }
  1134. newShape = new RSpline();
  1135. newShape.setDegree(3); // Seems to be default
  1136. newShape.setFitPoints(refs);
  1137. bool = fields[1].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1138. newShape.setPeriodic(bool);
  1139. newEntity = new RSplineEntity(this.doc, new RSplineData(newShape));
  1140. // Set last reference as relative position:
  1141. this.relativePos = refRv;
  1142. }
  1143. catch(err) { // On any error > reset entity
  1144. anyError = err;
  1145. newEntity = undefined;
  1146. }
  1147. break;
  1148. case "CONTROLSPLINE":
  1149. // Create new control point Spline entity:
  1150. try { // Try to create an entity from a shape >
  1151. if (fieldsCount < 10) throw qsTr("3th degree control point Spline, less than 4 references.");
  1152. if ((fieldsCount - 10)%2 !== 0) throw qsTr("Odd field count.");
  1153. newShape = new RSpline();
  1154. newShape.setDegree(3); // Seems to be default
  1155. bool = fields[1].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1156. newShape.setPeriodic(bool);
  1157. for (i=2; i<fieldsCount; i+=2) { // Rv 2=1 4=2 6=3 Rv=i/2
  1158. refRv = this.getCoordinate(fields[i], fields[i+1]); // References
  1159. if (isNull(refRv)) throw qsTr("Rv%1 Invalid.").arg(i/2);
  1160. newShape.appendControlPoint(refRv);
  1161. }
  1162. newEntity = new RSplineEntity(this.doc, new RSplineData(newShape));
  1163. // Set last reference as relative position:
  1164. this.relativePos = refRv;
  1165. }
  1166. catch(err) { // On any error > reset entity
  1167. anyError = err;
  1168. newEntity = undefined;
  1169. }
  1170. break;
  1171. case "TEXT":
  1172. // create a text entity and add it to the operation:
  1173. try { // Try to create an entity from a shape >
  1174. if (fieldsCount < 16) throw qsTr("Low field count.");
  1175. if (fieldsCount !== 16) throw qsTr("Odd field count.");
  1176. refRv = this.getCoordinate(fields[1], fields[2]); // References
  1177. if (isNull(refRv)) throw qsTr("Rv Invalid.");
  1178. txtHeight = parseFloat(fields[3]);
  1179. if (!isNumber(txtHeight) || txtHeight <= 0.0) throw qsTr("Height Invalid.");
  1180. // # Issue Solved # Signature updated in 3.26.0, zero is 'none'
  1181. txtWidth = parseFloat(fields[4]);
  1182. if (!isNumber(txtWidth) || txtWidth < 0.0) throw qsTr("Width Invalid.");
  1183. txtVAlign = parseInt(fields[5]);
  1184. if (!isNumber(txtVAlign) || txtVAlign < 0 || txtVAlign > 3) throw qsTr("Vert. alignment Invalid.");
  1185. txtHAlign = parseInt(fields[6]);
  1186. if (!isNumber(txtHAlign) || txtHAlign < 0 || txtHAlign > 5) throw qsTr("Horz. alignment Invalid.");
  1187. txtDirection = parseInt(fields[7]);
  1188. if (!isNumber(txtDirection) || txtDirection < 0 || txtDirection > 2) throw qsTr("Drawing direction Invalid.");
  1189. txtLSStyle = parseInt(fields[8]);
  1190. if (!isNumber(txtLSStyle) || txtLSStyle < 0 || txtLSStyle > 1) throw qsTr("Line spacing style Invalid.");
  1191. txtLineSpace = parseInt(fields[9]);
  1192. if (!isNumber(txtLineSpace) || txtLineSpace <= 0.0) throw qsTr("Line spacing Invalid.");
  1193. if (fields[10].toUpperCase() === "FILESTAMP") fields[10] = "File: " + fileName;
  1194. if (fields[10].toUpperCase() === "DATESTAMP") fields[10] = "Date: " + new Date().toString();
  1195. boldBool = fields[12].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1196. italicBool = fields[13].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1197. angle1 = this.getAngleValue(fields[14], false);
  1198. if (isNull(angle1)) throw qsTr("Angle Invalid.");
  1199. simpleBool = fields[15].toUpperCase() === "TRUE"; // Ignoring anything else to default to false
  1200. newData = new RTextData(
  1201. refRv, // Position
  1202. refRv, // Alignment point
  1203. txtHeight,
  1204. txtWidth, // Text width since 3.26.0
  1205. txtVAlign,
  1206. txtHAlign,
  1207. txtDirection,
  1208. txtLSStyle, // Text line spacing style
  1209. txtLineSpace, // Line spacing factor
  1210. fields[10], // The text message itself with/without formatting
  1211. fields[11], // Font
  1212. boldBool,
  1213. italicBool,
  1214. angle1,
  1215. simpleBool
  1216. );
  1217. newEntity = new RTextEntity(this.doc, newData);
  1218. // Set single reference as relative position:
  1219. this.relativePos = refRv;
  1220. }
  1221. catch(err) { // On any error > reset entity
  1222. anyError = err;
  1223. newEntity = undefined;
  1224. }
  1225. break;
  1226. default:
  1227. // Method not listed among cases:
  1228. msg = qsTr("Unsupported method. Text line: %1").arg(textLineCount);
  1229. EAction.handleUserWarning(msg); // Push warning to history (Win)
  1230. qDebug(msg); // Push warning to stdout
  1231. } // End switch on nominator key
  1232. // Reset attributes and custom properties:
  1233. attribText = customProp = undefined; // Empty
  1234. // Diversify attributes and custom properties:
  1235. if (isEntity(newEntity) && !isNull(xtrField1)) {
  1236. xtrField1 = xtrField1.split(lstSep);
  1237. if (xtrField1[0].toUpperCase() === "ATTRIB") {
  1238. attribText = xtrField1;
  1239. }
  1240. else if (xtrField1[0].toUpperCase() === "CUSTOM") {
  1241. customProp = xtrField1;
  1242. }
  1243. else {
  1244. // Should not occur > reset entity
  1245. anyError = qsTr("Error with first optional list.");
  1246. newEntity = undefined;
  1247. }
  1248. }
  1249. if (isEntity(newEntity) && !isNull(xtrField2)) {
  1250. xtrField2 = xtrField2.split(lstSep);
  1251. if (xtrField2[0].toUpperCase() === "ATTRIB" && isNull(attribText)) {
  1252. attribText = xtrField2;
  1253. }
  1254. else if (xtrField2[0].toUpperCase() === "CUSTOM" && isNull(customProp)) {
  1255. customProp = xtrField2;
  1256. }
  1257. else {
  1258. // Should not occur > reset entity
  1259. anyError = qsTr("Error with second optional list.");
  1260. newEntity = undefined;
  1261. }
  1262. }
  1263. // Validate layer attributes if any:
  1264. if (!isNull(attribText)) {
  1265. // Overwrite customAttrib with 5 verified attributes:
  1266. customAttrib = this.validateAttributes(attribText, textLineCount);
  1267. }
  1268. // Finalize new drawing entity and add to operation:
  1269. if (isEntity(newEntity)) { // With a valid shape >
  1270. // Apply general properties:
  1271. newEntity.setLayerName(customAttrib.layer);
  1272. newEntity.setColor(customAttrib.color);
  1273. newEntity.setLineweight(customAttrib.lineweight);
  1274. newEntity.setLinetypeId(customAttrib.linetype);
  1275. newEntity.setLinetypeScale(customAttrib.LinetypeScale);
  1276. // Apply general offset:
  1277. newEntity.move(offsetRv);
  1278. // Apply custom properties from key/value pairs:
  1279. // customProp[0] matches 'CUSTOM', customProp are key/value pairs
  1280. if (!isNull(customProp) && customProp.length > 1) { // With any custom
  1281. for (i=1; i<customProp.length; i++) { // Cycle all custom props
  1282. keyValue = customProp[i].split(":");
  1283. if (keyValue.length !== 2) keyValue[1] = "";
  1284. if (!keyValue[0].trim().isEmpty()) { // With a key
  1285. newEntity.setCustomProperty("QCAD", keyValue[0].trim(), keyValue[1].trim());
  1286. } // End with key
  1287. } // Loop custom props
  1288. } // End with any custom
  1289. // Add to the operation:
  1290. op.addObject(newEntity, false, false); // NOTuseCurrentAttributes, NOTforceNew (all are new)
  1291. castCount++;
  1292. } // End with shape
  1293. else if (newEntity !== "n/a") { // Without a valid shape
  1294. // # Issue Fixed # Locally thrown errors are strings, internal errors are objects.
  1295. anyError = (isString(anyError)) ? anyError : anyError.message;
  1296. // All locally thrown errors are translated message
  1297. msg = qsTr("Not a correct entry, ignored. Text line: %1 > %2").arg(textLineCount).arg(anyError);
  1298. EAction.handleUserWarning(msg); // Push warning to history (Win)
  1299. qDebug(msg); // Push warning to stdout
  1300. } // End without shape
  1301. } while(!ts.atEnd()); // Loop when the text stream is not ended
  1302. //debugger;
  1303. // Synchronize RelativeZero:
  1304. di.setRelativeZero(this.relativePos.operator_add(offsetRv));
  1305. // Synchronize document current layer:
  1306. /*
  1307. this.doc.setCurrentLayer(this.currentLayerName);
  1308. // Or
  1309. di.setCurrentLayer(this.currentLayerName);
  1310. */
  1311. // # Issue # .setCurrentLayer is an extra action
  1312. // Also encountered with AddLayer.js: adding is 1 action, to undo requires 2 actions
  1313. // https://www.qcad.org/rsforum/viewtopic.php?t=8287
  1314. // Based on: ActivateLayerState class 'restore current layer'
  1315. // Initiate an operation for setting current layer:
  1316. // layerOp = new RAddObjectOperation();
  1317. // layerOp.setText(qsTr("Cast CSV")); // Translated tool title
  1318. // layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  1319. // Update the document variables:
  1320. var docVars = this.doc.queryDocumentVariables();
  1321. docVars.setCurrentLayerId(this.doc.getLayerId(this.currentLayerName));
  1322. // layerOp.addObject(docVars);
  1323. op.addObject(docVars);
  1324. // di.applyOperation(layerOp);
  1325. // # Issue # Functional but change doesn't show in Layer List Widget
  1326. // Change is visible after drawing tab swap, new document or after undo + redo
  1327. // # ToDo # HowTo set current layer without extra operation?
  1328. // di.regenerateScenes();
  1329. // di.repaintViews();
  1330. // di.clearPreview();
  1331. /*
  1332. // Based on: LayerList.js Fails
  1333. // 'this.blockSignals' [undefined] is not a function.
  1334. this.blockSignals(true);
  1335. di.setCurrentLayer(this.currentLayerName);
  1336. this.blockSignals(false);
  1337. */
  1338. /*
  1339. // Based on: LayerList.js Fails
  1340. // ReferenceError: Can't find variable: RLayerListQt
  1341. RLayerListQt.blockSignals(true);
  1342. di.setCurrentLayer(this.currentLayerName);
  1343. RLayerListQt.blockSignals(false);
  1344. */
  1345. // Apply all entity operations in one go:
  1346. transaction = di.applyOperation(op); // No fault without any
  1347. // Retrieve the affected entities:
  1348. ids = transaction.getAffectedObjects();
  1349. // Apply optional selection with any entities:
  1350. if (selectAll) { // When the select all flag is set >
  1351. if (ids.length > 0) { // With new entities >
  1352. di.selectEntities(ids, false); // NOTaddToCurrent
  1353. } // End with new
  1354. } // End with flag
  1355. // Apply optional zoom with any entities:
  1356. if (zoomToAll) { // When the zoom all flag is set >
  1357. if (ids.length > 0) { // With new entities >
  1358. graphicsView = di.getGraphicsViewWithFocus();
  1359. // Simple.js ... di.getLastKnownViewWithFocus();
  1360. if (!isNull(graphicsView)) {
  1361. getRGraphicsView(graphicsView).zoomToEntities(ids, 30); // Margin in pixels
  1362. }
  1363. } // End with new
  1364. } // End with flag
  1365. // Report actions and terminate command:
  1366. file.close();
  1367. if (castCount > 0) {
  1368. msg = qsTr("Casted entries: %1 out of %2 text lines").arg(castCount).arg(textLineCount);
  1369. EAction.handleUserInfo(msg); // Push message to history (Win)
  1370. msg = qsTr("Importing CSV complete. Command ended.");
  1371. }
  1372. if (castCount === 0) {
  1373. msg = qsTr("Nothing casted.");
  1374. EAction.handleUserWarning(msg); // Push warning to history (Win)
  1375. msg = qsTr("Command ended.");
  1376. }
  1377. EAction.handleUserMessage(msg); // Push message to history (Win)
  1378. qDebug(msg); // Push End to stdout
  1379. // Return the affected entities if any:
  1380. return (ids.length > 0) ? ids : undefined;
  1381. };
  1382. /**
  1383. * Returns a file name and path from a file Dialog.
  1384. * Adapted copy from BlockListExport.js; BlockListExport.prototype.getFileName = function().
  1385. * \author Andrew Mustun
  1386. *
  1387. * \return Full path to the selected file or 'undefined' when none.
  1388. */
  1389. DrawFromCSV.prototype.getFileName = function() {
  1390. // # Known issues # Persistent behavior. Empty files with suffix(-es).
  1391. // # ToDo # Enhance .getFileName
  1392. // Requires: this.doc Current document.
  1393. var drawingFileName = this.doc.getFileName();
  1394. var fi;
  1395. var fileName;
  1396. var initialPath = "";
  1397. // # Issue # General idea should be:
  1398. // With a drawing name the name.csv in the last folder or homeDir
  1399. // Without a drawing name the last file or the windowTitle in the last folder or homeDir
  1400. if (drawingFileName.length === 0) {
  1401. var path = RSettings.getStringValue("DrawFromCSV/Path", QDir.homePath());
  1402. fi = new QFileInfo(path);
  1403. initialPath = fi.absoluteFilePath() + QDir.separator +
  1404. stripDirtyFlag(EAction.getMdiChild().windowTitle) + ".csv";
  1405. }
  1406. else {
  1407. fi = new QFileInfo(drawingFileName);
  1408. initialPath = fi.path() + QDir.separator + fi.completeBaseName() + ".csv";
  1409. }
  1410. var appWin = EAction.getMainWindow();
  1411. var ret = File.getOpenFileName(appWin,
  1412. qsTr("Draw from CSV"),
  1413. initialPath,
  1414. [qsTr("CSV") + " (*.csv)", qsTr("All Files") + " (*)" ],
  1415. true); // Not prefixing 'All files'
  1416. if (isNull(ret)) {
  1417. return undefined; // Failed, no filename
  1418. }
  1419. fileName = ret[0];
  1420. if (fileName === "") {
  1421. return undefined; // Failed, empty filename
  1422. }
  1423. if (drawingFileName.length === 0) {
  1424. RSettings.setValue("DrawFromCSV/Path", new QFileInfo(fileName).absolutePath());
  1425. }
  1426. return fileName;
  1427. };
  1428. /**
  1429. * Gets the absolute 2D coordinate vector from a textual coordinate pair.
  1430. * Relative when xField starts with '@'.
  1431. * Relative/polar when relative and yField starts with '<'
  1432. * Validates both entries as numbers or polar notations as radius/angle.
  1433. * \author CVH © 2021
  1434. *
  1435. * \param xField First textual coordinate.
  1436. * \param yField Second textual coordinate.
  1437. *
  1438. * \return The resulting RVector or 'undefined' when failed.
  1439. */
  1440. DrawFromCSV.prototype.getCoordinate = function(xField, yField) {
  1441. // # Bug Fixed # Last validated reference may serve as relative position before something valid was cast.
  1442. // Were two different things before and opted for the bad. Reverted back.
  1443. // Requires: this.relativePos Last known relative position
  1444. // this.lastCoordinate Last valid coordinate
  1445. var xValue, yValue; // Absolute X/Y coordinates
  1446. var newRv; // Intermediate new vector
  1447. var ret; // Return value
  1448. // Fail without both textual coordinates
  1449. if (isNull(xField) || isNull(yField)) return undefined;
  1450. // Calculate absolute return vector:
  1451. if (xField.startsWith("@")) { // When relative >
  1452. // Fail when relative position is invalid:
  1453. if (!isValidVector(this.relativePos)) return undefined;
  1454. // Revert to relative position when last valid coordinate is empty:
  1455. if (isNull(this.lastCoordinate)) this.lastCoordinate = this.relativePos
  1456. // Fail when xValue is no number:
  1457. xValue = parseFloat(xField.slice(1));
  1458. if (!isNumber(xValue)) return undefined;
  1459. // Check for polar:
  1460. if (yField.startsWith("<")) { // When relative/polar
  1461. // Fail when xValue doesn't validate as a radius:
  1462. if (!this.validateAsRadius(xValue)) return undefined;
  1463. // Fail when yValue doesn't validate to a number:
  1464. yValue = this.getAngleValue(yField.slice(1), false);
  1465. if (isNull(yValue)) return undefined;
  1466. // Set relative/polar vector
  1467. newRv = new RVector();
  1468. newRv.setPolar(xValue, yValue);
  1469. } // End polar
  1470. else { // When NOT polar >
  1471. // Fail when yValue is no number:
  1472. yValue = parseFloat(yField);
  1473. if (!isNumber(yValue)) return undefined;
  1474. // Set relative vector
  1475. newRv = new RVector(xValue, yValue);
  1476. } // End NOT polar
  1477. // -> Continue with a newRv
  1478. // Fail when new vector is invalid:
  1479. if (!isValidVector(newRv)) return undefined;
  1480. // Use relative vector:
  1481. ret = this.lastCoordinate.operator_add(newRv);
  1482. } // End relative
  1483. else { // When absolute
  1484. // Fail when one of the values is not a number:
  1485. xValue = parseFloat(xField);
  1486. yValue = parseFloat(yField);
  1487. if (!isNumber(xValue) || !isNumber(yValue)) return undefined;
  1488. // Use absolute vector:
  1489. ret = new RVector(xValue, yValue);
  1490. } // End absolute
  1491. // -> Continue with a return vector
  1492. // Return 'undefined' when invalid:
  1493. if (!isValidVector(ret)) return udefined;
  1494. // Remember last valid coordinate and return an RVector:
  1495. this.lastCoordinate = ret;
  1496. return ret;
  1497. }
  1498. /**
  1499. * Validates a value as a radius within limits.
  1500. * Returns 'true' when valid else 'false'.
  1501. */
  1502. DrawFromCSV.prototype.validateAsRadius = function(value) {
  1503. // Fail when not a number:
  1504. if (!isNumber(value)) return false;
  1505. // Restrict between +RS.PointTolerance (zero leads to Null-entities)
  1506. // and +1.0e6 (Conform QCAD Arc methods getCircles2TR/getArc2PR/getCircle2PR):
  1507. return !(value < RS.PointTolerance || value > 1.0e6);
  1508. };
  1509. /**
  1510. * Gets an angle in radians from a textual value in degrees or radians.
  1511. * Validates entry as angle or as sweep.
  1512. * \author CVH © 2021
  1513. *
  1514. * \param angleStr A textual angle notation.
  1515. * \param isSweep True to validate as sweep angle.
  1516. *
  1517. * \return The normalized angle in radians or 'undefined' when failed.
  1518. */
  1519. DrawFromCSV.prototype.getAngleValue = function(angleStr, isSweep) {
  1520. var ret; // Return value
  1521. var isRadians; // Condition
  1522. // Fail without a angle string:
  1523. if (isNull(angleStr) || angleStr.length === 0) return undefined;
  1524. // Fail when not a number:
  1525. ret = parseFloat(angleStr);
  1526. if (!isNumber(ret)) return undefined;
  1527. // Use as radians or as degrees:
  1528. isRadians = angleStr.toUpperCase().endsWith("R") || angleStr.toUpperCase().endsWith("RAD");
  1529. if (!isRadians) {
  1530. ret = RMath.deg2rad(ret);
  1531. }
  1532. // Optionally fail when outside +2Pi and -2Pi excluding the limits:
  1533. if (isSweep) {
  1534. if (ret >= Math.PI*2-RS.AngleTolerance) return undefined;
  1535. if (ret <= -Math.PI*2+RS.AngleTolerance) return undefined;
  1536. }
  1537. // Return the normalized angle:
  1538. return RMath.getNormalizedAngle(ret);
  1539. };
  1540. /**
  1541. * Validates a set of general properties from an attributes set.
  1542. * \author CVH © 2021
  1543. *
  1544. * \param fields Attribute set.
  1545. * \param textLine CSV text line number.
  1546. *
  1547. * \return Validated attributes set.
  1548. */
  1549. DrawFromCSV.prototype.validateAttributes = function(fields, textLine) {
  1550. // fields[0] matches 'ATTRIB', fields count >1, fields are key/value pairs
  1551. // Requires: this.doc Current document
  1552. // this.currentLayerName Script level current layer
  1553. // this.nativeLstSep Native list separator for messages
  1554. // this.layerSep Layer hierarchy separator
  1555. var i, pairs; // Pair iterator and limit
  1556. var keyValue; // Key/value pair a array
  1557. var anyError = ""; // Error message
  1558. var layerName, color, weight, type; // Attributes
  1559. var ret = { // Default set = Current layer, byLayer & scale 1
  1560. layer : this.currentLayerName,
  1561. color : new RColor(RColor.ByLayer),
  1562. lineweight : RLineweight.WeightByLayer,
  1563. linetype : this.doc.getLinetypeId("BYLAYER"),
  1564. LinetypeScale : 1};
  1565. // Define pairs count:
  1566. pairs = fields.length - 1;
  1567. // Report none:
  1568. if (pairs < 1) {
  1569. anyError = qsTr("Attributes empty. Text line: %1").arg(textLine);
  1570. }
  1571. // Limit to 5 pairs, ignoring any extra field silently:
  1572. if (pairs > 5) pairs = 5;
  1573. // Add 1 for counter limit:
  1574. pairs++;
  1575. //debugger;
  1576. for (i=1; i<pairs; i++) { // Cycle through up to 5 attributes pairs
  1577. // Split the key/value pair:
  1578. keyValue = fields[i].split(":");
  1579. // Reset keyValue when not a pair:
  1580. if (keyValue.length !== 2) keyValue = ["Dummy", "void"];
  1581. // Process by key in uppercase:
  1582. switch (keyValue[0].toUpperCase()) {
  1583. case "LAYER":
  1584. // Adapt layer naming to proper layer hierarchy separator:
  1585. keyValue[1] = keyValue[1].replace(/\//g, this.layerSep);
  1586. // Validate layer naming:
  1587. layerName = this.validateLayerString(keyValue[1]);
  1588. if (isNull(layerName)) {
  1589. anyError = qsTr("Not a correct layer name, ignored. Text line: %1%2 Name: %3")
  1590. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1591. }
  1592. // Check if the layer exist:
  1593. if (this.doc.hasLayer(layerName)) {
  1594. ret.layer = layerName;
  1595. }
  1596. else {
  1597. anyError = qsTr("Layer doesn't exist, ignored. Text line: %1%2 Layer: %3")
  1598. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1599. }
  1600. break;
  1601. case "COLOR":
  1602. // Validate Color attribute from string:
  1603. color = this.validateColorString(keyValue[1], true);
  1604. if (isNull(color)) {
  1605. anyError = qsTr("Not a correct color, ignored. Text line: %1%2 Color: %3")
  1606. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1607. }
  1608. else {
  1609. ret.color = color;
  1610. }
  1611. break;
  1612. case "LINEWEIGHT":
  1613. // Validate Lineweight attribute from string:
  1614. weight = this.validateWeightString(keyValue[1], true);
  1615. if (isNull(weight)) {
  1616. anyError = qsTr("Not a correct Lineweight, ignored. Text line: %1%2 Weight: %3")
  1617. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1618. }
  1619. else {
  1620. ret.lineweight = weight;
  1621. }
  1622. break;
  1623. case "LINETYPE":
  1624. // Validate Linetype attribute from string:
  1625. type = this.validateTypeString(keyValue[1], true);
  1626. if (isNull(type)) {
  1627. anyError = qsTr("Not a correct Linetype, ignored. Text line: %1%2 Type: %3")
  1628. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1629. }
  1630. else {
  1631. ret.linetype = type;
  1632. }
  1633. break;
  1634. case "LINETYPESCALE":
  1635. // Validate LinetypeScale attribute from string:
  1636. if (!isNumber(parseFloat(keyValue[1])) || parseFloat(keyValue[1]) < 0) {
  1637. anyError = qsTr("Not a correct Linetype scale, ignored. Text line: %1%2 Scale: %3")
  1638. .arg(textLine).arg(this.nativeLstSep).arg("\'" + keyValue[1] + "\'");
  1639. }
  1640. else {
  1641. ret.LinetypeScale = parseFloat(keyValue[1]);
  1642. }
  1643. break;
  1644. default:
  1645. // None of the above:
  1646. anyError = qsTr("Unsupported attribute, ignored. Text line: %1 > %2").arg(textLine).arg(fields[i]);
  1647. } // End switch on key
  1648. } // Loop attributes pairs
  1649. // Report any error:
  1650. if (anyError.length > 0) { // With an error message >
  1651. EAction.handleUserWarning(anyError); // Push warning to history (Win)
  1652. qDebug(anyError); // Push warning to stdout
  1653. } // End message
  1654. // Return attribute set:
  1655. return ret;
  1656. };
  1657. /**
  1658. * Validates a set of layer attributes from a layer switch.
  1659. * Updates an exiting layer or creates a new layer if such doesn't exist.
  1660. * New sublayers require an existing parent.
  1661. * \author CVH © 2021
  1662. *
  1663. * \param fields Attribute set.
  1664. * \param textLine CSV text line number.
  1665. *
  1666. * \return Nothing. Applies a layer operation, reports errors.
  1667. * Updates script level current layer.
  1668. */
  1669. DrawFromCSV.prototype.newOrUpdateLayer = function(fields, textLine) {
  1670. // fields[0] matches 'NEWLAYER', fields count =7
  1671. // Requires: this.di Document interface
  1672. // this.doc Current document
  1673. // this.currentLayerName Script level current layer
  1674. // this.nativeLstSep Native list separator for messages
  1675. // this.layerSep Layer hierarchy separator
  1676. var anyError = ""; // Error message
  1677. var layerName, color, weight, type, plot, snap; // Layer attributes
  1678. var parentLayer; // Parent layer name
  1679. var layer; // A layer object
  1680. var layerOp; // Layer operation
  1681. var defaultColor, defaultType, defaultWeight; // Default attributes
  1682. var msg = ""; // Intermediate message strings
  1683. //debugger;
  1684. // Adapt layer naming to proper layer hierarchy separator:
  1685. fields[1] = fields[1].replace(/\//g, this.layerSep);
  1686. // Validate layer naming:
  1687. layerName = this.validateLayerString(fields[1]);
  1688. if (isNull(layerName)) { // Without a valid layer name >
  1689. anyError = qsTr("Not a correct layer name. Text line: %1%2 Name: %3")
  1690. .arg(textLine).arg(this.nativeLstSep).arg("\'" + fields[1] + "\'");
  1691. } // End without name
  1692. else { // With a valid layer name >
  1693. if (layerName.indexOf(this.layerSep) !== -1) { // With a parent/sub layer >
  1694. parentLayer = layerName.split(this.layerSep, layerName.count(this.layerSep)).join(this.layerSep);
  1695. if (!this.doc.hasLayer(parentLayer)) { // Without a parent layer >
  1696. anyError = qsTr("Parent layer doesn't exist. Text line: %1%2 Layer: %3")
  1697. .arg(textLine).arg(this.nativeLstSep).arg("\'" + parentLayer + "\'");
  1698. } // End without parent
  1699. } //End with sub
  1700. // -> Continue with a verified name
  1701. // With a name, retrieve the layer or create a new one:
  1702. if (this.doc.hasLayer(layerName)) { // With an existing layer >
  1703. layer = this.doc.queryLayer(layerName);
  1704. } // End with existing
  1705. else { // Without an existing layer >
  1706. // Get the application preferences or use defaults:
  1707. defaultColor = RSettings.getColor("Layer/DefaultColor", new RColor("black"));
  1708. defaultType = this.doc.getLinetypeId("CONTINUOUS");
  1709. defaultWeight = RSettings.getIntValue("Layer/DefaultLineweight", RLineweight.Weight025);
  1710. // Create new layer object:
  1711. layer = new RLayer(this.doc, layerName, false , false, // NOTfrozen, NOTlocked
  1712. defaultColor,
  1713. defaultType,
  1714. defaultWeight,
  1715. false); // NOToff
  1716. } // End with new
  1717. // -> Continue with a layer object
  1718. } // End with a valid layer name
  1719. // -> Continue with/without a layer object
  1720. // Validate attributes:
  1721. if (!isNull(layer)) { // With a layer object >
  1722. // Validate Color from a string:
  1723. color = this.validateColorString(fields[2], false);
  1724. if (isNull(color)) {
  1725. anyError = qsTr("Not a correct color. Text line: %1%2 Color: %3")
  1726. .arg(textLine).arg(this.nativeLstSep).arg("\'" + fields[2] + "\'");
  1727. }
  1728. else {
  1729. layer.setColor(color);
  1730. }
  1731. // Validate Lineweight from a string:
  1732. weight = this.validateWeightString(fields[3], false);
  1733. if (isNull(weight)) {
  1734. anyError = qsTr("Not a correct Lineweight. Text line: %1%2 Weight: %3")
  1735. .arg(textLine).arg(this.nativeLstSep).arg("\'" + fields[3] + "\'");
  1736. }
  1737. else {
  1738. layer.setLineweight(weight);
  1739. }
  1740. // Validate Linetype from a string:
  1741. type = this.validateTypeString(fields[4], false);
  1742. if (isNull(type)) {
  1743. anyError = qsTr("Not a correct Linetype. Text line: %1%2 Type: %3")
  1744. .arg(textLine).arg(this.nativeLstSep).arg("\'" + fields[4] + "\'");
  1745. }
  1746. else {
  1747. layer.setLinetypeId(type);
  1748. }
  1749. // Set Plottable & Snappable:
  1750. plot = !(fields[5].toUpperCase() === "FALSE"); // Ignoring anything else, default to true
  1751. layer.setPlottable(plot);
  1752. snap = !(fields[6].toUpperCase() === "FALSE"); // Ignoring anything else, default to true
  1753. layer.setSnappable(snap);
  1754. } // End with a layer object
  1755. // Report any error:
  1756. if (anyError.length > 0) { // With an error message >
  1757. EAction.handleUserWarning(anyError); // Push warning to history (Win)
  1758. qDebug(anyError); // Push warning to stdout
  1759. msg = qsTr("Add/Update layer ignored.");
  1760. EAction.handleUserWarning(msg); // Push warning to history (Win)
  1761. qDebug(msg); // Push warning to stdout
  1762. return undefined; // Failed, can't add or update
  1763. } // End message
  1764. // Continue without error ->
  1765. // Diversify adding/updating:
  1766. if (this.doc.hasLayer(layerName)) {
  1767. msg = qsTr("Updated layer: %1").arg(layerName);
  1768. }
  1769. else {
  1770. msg = qsTr("Added new layer: %1").arg(layerName);
  1771. }
  1772. // Initiate an operation for adding/updating a layer:
  1773. layerOp = new RAddObjectOperation(layer, false); // NOTuseCurrentAttributes, undoAble=true
  1774. // Use global transaction group:
  1775. layerOp.setTransactionGroup(this.doc.getTransactionGroup());
  1776. // Add/update the layer on the fly:
  1777. this.di.applyOperation(layerOp)
  1778. // Report change and set script level current layer:
  1779. EAction.handleUserInfo(msg); // Push info to history (Win)
  1780. qDebug(msg); // Push info to stdout
  1781. this.currentLayerName = layerName;
  1782. };
  1783. /**
  1784. * Validate a string as a layer name.
  1785. * \param nameString String to validate.
  1786. *
  1787. * \return Validated layer name or undefined.
  1788. */
  1789. DrawFromCSV.prototype.validateLayerString = function(nameString) {
  1790. // library.js fixSymbolTableName(name)...
  1791. // https://qcad.org/rsforum/viewtopic.php?t=8248
  1792. // Test naming with removing unallowed characters:
  1793. var layerName = nameString.replace(/[<>/\\\":;\?\*|,=`]/g, "").trim();
  1794. // Return 'undefined' when failing or return a layerName:
  1795. if (layerName.length < 1 || layerName.length > 255 || layerName !== nameString) {
  1796. return undefined;
  1797. }
  1798. return layerName;
  1799. };
  1800. /**
  1801. * Validate a string as a color. Provides in QCAD orange & gray.
  1802. * \author CVH © 2021
  1803. *
  1804. * \param colorString String to validate.
  1805. * \param asAttribute True to validate as an entity attribute.
  1806. *
  1807. * \return Validated color or undefined.
  1808. */
  1809. DrawFromCSV.prototype.validateColorString = function(colorString, asAttribute) {
  1810. var colorList = new Array("red","lime","blue","cyan","magenta","yellow",
  1811. "maroon","green","navy","teal","purple","olive",
  1812. "black","silver","white");
  1813. var color; // Color from string
  1814. var testString; // Intermediate test string
  1815. // # Issue Fixed # No CAD-Orange or CAD-Gray
  1816. // Additional textual colors:
  1817. colorList.push("Qorange"); // = #ff7f00
  1818. colorList.push("Qgray"); // = #a0a0a4
  1819. // Diversify on colorString and asAttribute flag:
  1820. if (colorString.toUpperCase() === "BYLAYER" ) {
  1821. // For attributes set ColorByLayer:
  1822. color = (asAttribute) ? new RColor(RColor.ByLayer) : undefined;
  1823. }
  1824. else if (colorString.toUpperCase() === "BYBLOCK" ) {
  1825. // For attributes set ColorByBlock:
  1826. color = (asAttribute) ? new RColor(RColor.ByBlock) : undefined;
  1827. }
  1828. else if (colorString.startsWith("#") && colorString.length === 7) {
  1829. // Test colorString with removing unallowed characters:
  1830. testString = colorString.replace(/[^0-9a-fA-F\#]/g,"");
  1831. if (testString !== colorString) {
  1832. return undefined; // Failed, invalid RGBColor
  1833. }
  1834. // Try setting colorString as HTML color code:
  1835. color = new RColor(colorString);
  1836. }
  1837. else if (colorString === "Qorange") {
  1838. // Additional QCAD orange:
  1839. color = new RColor("#ff7f00");
  1840. }
  1841. else if (colorString === "Qgray") {
  1842. // Additional QCAD gray:
  1843. color = new RColor("#a0a0a4");
  1844. }
  1845. else if (colorList.containsIgnoreCase(colorString)) {
  1846. // Try setting colorString as color name:
  1847. color = new RColor(colorString);
  1848. }
  1849. // Return 'undefined' when failing or return a color:
  1850. if (!isValidColor(color)) {
  1851. return undefined;
  1852. // Default = RSettings.getColor("Layer/DefaultColor", new RColor("black"))
  1853. // Default = RSettings.getColor("Layer/DefaultColorLayer0", new RColor("white"))
  1854. }
  1855. return color;
  1856. };
  1857. /**
  1858. * Validate a string as a Lineweight.
  1859. * \param weightString String to validate.
  1860. * \param asAttribute True to validate as an entity attribute.
  1861. *
  1862. * \return Validated Lineweight or undefined.
  1863. */
  1864. DrawFromCSV.prototype.validateWeightString = function(weightString, asAttribute) {
  1865. var lineWeight; // Line weight from string
  1866. // Diversify on weightString and asAttribute flag:
  1867. if (weightString.toUpperCase() === "BYLAYER" ) {
  1868. // For attributes set WeightByLayer:
  1869. lineWeight = (asAttribute) ? RLineweight.WeightByLayer : undefined;
  1870. }
  1871. else if (weightString.toUpperCase() === "BYBLOCK" ) {
  1872. // For attributes set WeightByBlock:
  1873. lineWeight = (asAttribute) ? RLineweight.WeightByBlock : undefined;
  1874. }
  1875. else if (weightString.toUpperCase() === "DEFAULT" ) {
  1876. // Set application default or Weight025:
  1877. lineWeight = RSettings.getIntValue("Layer/DefaultLineweight", RLineweight.Weight025)
  1878. }
  1879. else if (isNumber(parseFloat(weightString))) {
  1880. // Try setting weightString as number:
  1881. lineWeight = parseFloat(weightString);
  1882. }
  1883. // Return 'undefined' when failing or return a lineweight:
  1884. if (isNull(lineWeight) || RLineweight.getName(lineWeight).isEmpty()) {
  1885. return undefined;
  1886. // Default = RSettings.getIntValue("Layer/DefaultLineweight", RLineweight.Weight025)
  1887. // Default = RSettings.getIntValue("GraphicsView/DefaultLineweight", RLineweight.Weight000);
  1888. }
  1889. return lineWeight;
  1890. };
  1891. /**
  1892. * Validate a string as a Linetype.
  1893. * \param typeString String to validate.
  1894. * \param asAttribute True to validate as an entity attribute.
  1895. *
  1896. * \return Validated Linetype or undefined.
  1897. */
  1898. DrawFromCSV.prototype.validateTypeString = function(typeString, asAttribute) {
  1899. // # Oddity # RDocument.getLinetypeNames() are all in uppercase except "Continuous"
  1900. // # Issue # How to include/allow textual RDocument.getLinetype labels?
  1901. // Requires: this.doc Current document
  1902. var linetype; // Line type from string
  1903. // Diversify on typeString and asAttribute flag:
  1904. if (typeString.toUpperCase() === "BYLAYER" ) {
  1905. // For attributes set linetypeByLayer:
  1906. linetype = (asAttribute) ? this.doc.getLinetypeByLayerId() : undefined;
  1907. }
  1908. else if (typeString.toUpperCase() === "BYBLOCK" ) {
  1909. // For attributes set linetypeByBlock:
  1910. linetype = (asAttribute) ? this.doc.getLinetypeByBlockId() : undefined;
  1911. }
  1912. else {
  1913. // Try setting typeString textual:
  1914. linetype = this.doc.getLinetypeId(typeString);
  1915. }
  1916. //if (!isNull(linetype)) EAction.handleUserInfo(typeString + "||" + linetype + "||" + this.doc.getLinetypeLabel(linetype));
  1917. // Return 'undefined' when failing or return a line type:
  1918. if (isNull(linetype) || linetype < 0) {
  1919. return undefined;
  1920. // Default = this.doc.getLinetypeId()("CONTINUOUS")
  1921. }
  1922. return linetype;
  1923. };
  1924. /**
  1925. * Returns the given polyline shape rounded by given radius if applicable.
  1926. * Limited by used resource to non-bulging polyline shapes.
  1927. * Limited by used resource to non-self-intersecting polyline shapes.
  1928. * Limited to cut of 1/3 of the smallest edge adjacent to any enclosed angle.
  1929. * (0° < enclosed angle <= 180°)
  1930. *
  1931. * \author CVH © 2021
  1932. *
  1933. * \param polylineShape Polyline shape to round.
  1934. * \param radius Rounding radius.
  1935. *
  1936. * \return Polyline shape, rounded or original.
  1937. */
  1938. DrawFromCSV.prototype.roundPolyline = function(polylineShape, radius) {
  1939. // Based on: https://qcad.org/rsforum/viewtopic.php?t=8459
  1940. // Rounding is fully unreliably with arc segments, self-intersecting or when R is too large.
  1941. var edges, i, j, iMax;
  1942. var enclosedAngle;
  1943. var maxRadius;
  1944. var isValidated = true;
  1945. //debugger;
  1946. // Do nothing when NOT a polyline shape or without the required proxy:
  1947. if (!isPolylineShape(polylineShape) || !RPolyline.hasProxy()) {
  1948. return polylineShape;
  1949. }
  1950. // Do nothing when the radius is invalid:
  1951. if (!this.validateAsRadius(radius)) {
  1952. return polylineShape;
  1953. }
  1954. // Do nothing when the polyline shape has arc segments:
  1955. if (polylineShape.hasArcSegments()) {
  1956. return polylineShape;
  1957. }
  1958. // Do nothing when the polyline shape is self-intersecting:
  1959. if (polylineShape.getSelfIntersectionPoints().length > 0) {
  1960. return polylineShape;
  1961. }
  1962. // Get line shapes from given polyline shape:
  1963. edges = polylineShape.getExploded();
  1964. // Verify all successive edges pair-wise except last:
  1965. iMax = edges.length;
  1966. for (i=0; i<iMax; i++) { // Cycle all edges
  1967. j = i + 1; // Points to next
  1968. if (j === iMax) {
  1969. if (polylineShape.isGeometricallyClosed()) {
  1970. j = 0; // Points to first edge with last
  1971. }
  1972. else {
  1973. break; // Stop cycling edges
  1974. }
  1975. }
  1976. // Define absolute enclosed angle between edges:
  1977. enclosedAngle = Math.abs(RMath.getAngleDifference180(edges[i].getAngle() + Math.PI, edges[j].getAngle()));
  1978. // Avoid a retrace:
  1979. if (RMath.fuzzyAngleCompare(enclosedAngle, 0.0)) {
  1980. isValidated = false; // Failed, can't round a retrace
  1981. break; // Stop cycling edges
  1982. }
  1983. // Avoid tan(90°) for inline segments:
  1984. // # Informational # Inline segments are merged but the rounding may not land in or passed the point
  1985. else if (RMath.fuzzyAngleCompare(enclosedAngle, Math.PI)) {
  1986. maxRadius = Math.min(edges[i].getLength(), edges[j].getLength()) / 3;
  1987. }
  1988. // Define the local maximum rounding radius:
  1989. else {
  1990. maxRadius = Math.min(edges[i].getLength(), edges[j].getLength()) / 3 * Math.tan(enclosedAngle / 2);
  1991. }
  1992. // Limit cutoff to 1/3 of the smallest edge:
  1993. if (radius > maxRadius) {
  1994. isValidated = false; // Failed, one or both edge(-s) too small
  1995. break; // Stop cycling edges
  1996. }
  1997. } // Loop edges
  1998. // Return rounded polyline if validated:
  1999. return (isValidated) ? polylineShape.roundAllCorners(radius) : polylineShape;
  2000. }
  2001. /* # ToDo # Hatch entities
  2002. Shape.complementOperation()
  2003. var hatchData = new RHatchData();
  2004. hatchData.setDocument(doc);
  2005. hatchData.setAngle(0.0);
  2006. hatchData.setScale(1.0);
  2007. hatchData.setSolid(true);
  2008. hatchData.setPatternName("SOLID");
  2009. hatchData.newLoop();
  2010. for (var k=0; k<shapes.length; ++k) {
  2011. hatchData.addBoundary(shapes[k]);
  2012. }
  2013. op.addObject(new RHatchEntity(doc, hatchData));
  2014. Have a look at HatchDialog.prototype.patternChanged()
  2015. */