Bug Summary

File:librecad/src/lib/filters/rs_filterdxfrw.cpp
Warning:line 1677, column 5
Called C++ object pointer is null

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name rs_filterdxfrw.cpp -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=cplusplus -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=none -fmath-errno -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -tune-cpu generic -debugger-tuning=gdb -fdebug-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -fcoverage-compilation-dir=/home/runner/work/LibreCAD/LibreCAD/librecad/src -resource-dir /usr/lib/llvm-18/lib/clang/18 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D QC_APPDIR="librecad" -D LC_VERSION=2.2.2_alpha1-607-g330d41cec -D LC_PRERELEASE=true; -D QT_NO_DEBUG -D QT_SVG_LIB -D QT_PRINTSUPPORT_LIB -D QT_WIDGETS_LIB -D QT_GUI_LIB -D QT_NETWORK_LIB -D QT_CORE_LIB -I . -I /usr/include -I ../../libraries/lciconengine -I ../../libraries/libdxfrw/src -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I cmd -I lib/actions -I lib/actions/visual_snap -I lib/actions/options -I lib/creation -I lib/debug -I lib/engine -I lib/engine/document -I lib/engine/document/blocks -I lib/engine/document/container -I lib/engine/document/dimstyles -I lib/engine/document/entities -I lib/engine/document/entities/support -I lib/engine/document/fonts -I lib/engine/document/io -I lib/engine/document/layers -I lib/engine/document/patterns -I lib/engine/document/selection -I lib/engine/document/textstyles -I lib/engine/document/ucs -I lib/engine/document/variables -I lib/engine/document/views -I lib/engine/clipboard -I lib/engine/overlays -I lib/engine/overlays/angles_base -I lib/engine/overlays/highlight -I lib/engine/overlays/preview -I lib/engine/overlays/references -I lib/engine/overlays/crosshair -I lib/engine/overlays/info_cursor -I lib/engine/overlays/overlay_box -I lib/engine/overlays/ucs_mark -I lib/engine/undo -I lib/engine/utils -I lib/engine/settings -I lib/fileio -I lib/filters -I lib/generators -I lib/generators/makercamsvg -I lib/generators/layers -I lib/generators/image -I lib/gui -I lib/gui/grid -I lib/gui/render -I lib/gui/render/headless -I lib/gui/render/widget -I lib/information -I lib/math -I lib/modification -I lib/selection -I lib/selection/metaentity -I lib/selection/metaentity/entities -I lib/printing -I lib/properties -I actions -I actions/dock_widgets -I actions/dock_widgets/block -I actions/dock_widgets/entity_info -I actions/dock_widgets/layer -I actions/dock_widgets/library -I actions/dock_widgets/ucs_list -I actions/drawing -I actions/drawing/draw -I actions/drawing/draw/arc -I actions/drawing/draw/circle -I actions/drawing/draw/curve -I actions/drawing/draw/spline -I actions/drawing/draw/dimensions -I actions/drawing/draw/ellipse -I actions/drawing/draw/hatch -I actions/drawing/draw/image -I actions/drawing/draw/line -I actions/drawing/draw/point -I actions/drawing/draw/rect -I actions/drawing/draw/polygon -I actions/drawing/draw/misc -I actions/drawing/draw/line/shapes -I actions/drawing/draw/line/misc -I actions/drawing/draw/line/shapes/rect -I actions/drawing/draw/line/shapes/polygon -I actions/drawing/draw/polyline -I actions/drawing/draw/text -I actions/drawing/edit -I actions/drawing/info -I actions/drawing/pick -I actions/drawing/modify -I actions/drawing/pen -I actions/drawing/rel_zero -I actions/drawing/selection -I actions/drawing/snap -I actions/drawing/zoom -I actions/file -I actions/options -I actions/print_preview -I ui -I ui/action_options -I ui/action_options/circle -I ui/action_options/curve -I ui/action_options/spline -I ui/action_options/dimensions -I ui/action_options/edit -I ui/action_options/image -I ui/action_options/info -I ui/action_options/insert -I ui/action_options/line -I ui/action_options/rect -I ui/action_options/polygon -I ui/action_options/misc -I ui/action_options/modify -I ui/action_options/ellipse -I ui/action_options/other -I ui/action_options/polyline -I ui/action_options/point -I ui/action_options/print_preview -I ui/action_options/selection -I ui/action_options/snap -I ui/action_options/text -I ui/actions -I ui/components -I ui/components/relative_position_assistant -I ui/components/comboboxes -I ui/components/containers -I ui/components/creators -I ui/components/layouts -I ui/components/pen -I ui/components/status_bar -I ui/components/toolbars -I ui/components/utils -I ui/dialogs -I ui/dialogs/actions -I ui/dialogs/actions/modify -I ui/dialogs/actions/quick_selection -I ui/dialogs/modify -I ui/dialogs/entity -I ui/dialogs/creators -I ui/dialogs/file -I ui/dialogs/file/export -I ui/dialogs/file/export/layers -I ui/dialogs/file/export/image -I ui/dialogs/file/export/makercam -I ui/dialogs/main -I ui/dialogs/settings -I ui/dialogs/settings/dimstyles -I ui/dialogs/settings/dimstyles/dimstyle_manager -I ui/dialogs/settings/dimstyles/dimstyle_manager/support -I ui/dialogs/settings/options_device -I ui/dialogs/settings/options_drawing -I ui/dialogs/settings/options_general -I ui/dialogs/settings/options_widget -I ui/dialogs/settings/shortcuts -I ui/dock_widgets -I ui/dock_widgets/block_widget -I ui/dock_widgets/command_line -I ui/dock_widgets/entity_info -I ui/dock_widgets/layer_widget -I ui/dock_widgets/layers_tree -I ui/dock_widgets/library_widget -I ui/dock_widgets/pen_palette -I ui/dock_widgets/pen_wizard -I ui/dock_widgets/property_sheet -I ui/dock_widgets/property_sheet/lib -I ui/dock_widgets/property_sheet/lib/properties -I ui/dock_widgets/property_sheet/lib/view -I ui/dock_widgets/property_sheet/lib/view/edit -I ui/dock_widgets/property_sheet/lib/widgets -I ui/dock_widgets/property_sheet/lib/widgets/sheet -I ui/dock_widgets/property_sheet/metaentity -I ui/dock_widgets/property_sheet/metaentity/entities -I ui/dock_widgets/property_sheet/metaentity/entities/document -I ui/dock_widgets/property_sheet/properties -I ui/dock_widgets/property_sheet/properties/action -I ui/dock_widgets/property_sheet/properties/bool -I ui/dock_widgets/property_sheet/properties/color -I ui/dock_widgets/property_sheet/properties/double -I ui/dock_widgets/property_sheet/properties/enum -I ui/dock_widgets/property_sheet/properties/int -I ui/dock_widgets/property_sheet/properties/layer -I ui/dock_widgets/property_sheet/properties/linetype -I ui/dock_widgets/property_sheet/properties/linewidth -I ui/dock_widgets/property_sheet/properties/rect -I ui/dock_widgets/property_sheet/properties/rsvector -I ui/dock_widgets/property_sheet/properties/string -I ui/dock_widgets/views_list -I ui/dock_widgets/ucs_list -I ui/dock_widgets/workspaces -I ui/dock_widgets/cad -I ui/main -I ui/main/init -I ui/main/persistence -I ui/main/release_check -I ui/main/support -I ui/main/fontviewer -I ui/main/workspaces -I ui/view -I main -I main/console_dxf2pdf -I test -I plugins -I ../res -I ../res/arrows -I ../res/controls -I ../res/dxf -I ../res/gdt -I ../res/icons -I ../res/images -I ../../../Qt/6.9.0/gcc_64/include -I ../../../Qt/6.9.0/gcc_64/include/QtSvg -I ../../../Qt/6.9.0/gcc_64/include/QtPrintSupport -I ../../../Qt/6.9.0/gcc_64/include/QtWidgets -I ../../../Qt/6.9.0/gcc_64/include/QtGui -I ../../../Qt/6.9.0/gcc_64/include/QtNetwork -I ../../../Qt/6.9.0/gcc_64/include/QtCore -I ../../generated/librecad/moc -I ../../generated/librecad/ui -I ../../../Qt/6.9.0/gcc_64/mkspecs/linux-g++ -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/x86_64-linux-gnu/c++/14 -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/backward -internal-isystem /usr/lib/llvm-18/lib/clang/18/include -internal-isystem /usr/local/include -internal-isystem /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -O2 -std=gnu++1z -fdeprecated-macro -ferror-limit 19 -fgnuc-version=4.2.1 -fskip-odr-check-in-gmf -fcxx-exceptions -fexceptions -vectorize-loops -vectorize-slp -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/runner/work/LibreCAD/LibreCAD/out/2026-08-04-154929-5069-1 -x c++ lib/filters/rs_filterdxfrw.cpp
1/****************************************************************************
2**
3** This file is part of the LibreCAD project, a 2D CAD program
4**
5** Copyright (C) 2015 A. Stebich (librecad@mail.lordofbikes.de)
6** Copyright (C) 2011 Rallaz, rallazz@gmail.com
7** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
8** Copyright (C) 2026 LibreCAD (librecad.org)
9**
10**
11** This file may be distributed and/or modified under the terms of the
12** GNU General Public License as published by the Free Software
13** Foundation either version 2 of the License, or (at your option)
14** any later version.
15**
16** This program is distributed in the hope that it will be useful,
17** but WITHOUT ANY WARRANTY; without even the implied warranty of
18** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19** GNU General Public License for more details.
20**
21** You should have received a copy of the GNU General Public License
22** along with this program; if not, write to the Free Software
23** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24**
25**********************************************************************/
26
27#include <algorithm>
28#include <array>
29#include <chrono>
30#include <cstdlib>
31#include <cmath>
32#include <iostream>
33#include <set>
34#include <stack>
35#include <utility>
36#include <vector>
37
38#include <QDir>
39#include <QFile>
40#include <QFileInfo>
41#include <QLocale>
42#include <QRegularExpression>
43#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))
44#include <QStringConverter>
45#endif
46#include <QStringList>
47
48#include "drw_acis.h"
49#include "dxf_format.h"
50#include "lc_containertraverser.h"
51#include "lc_defaults.h"
52#include "lc_dimarc.h"
53#include "lc_dimarrowregistry.h"
54#include "lc_dimordinate.h"
55#include "lc_dwgadvancedmetadata.h"
56#include "lc_dimstyle.h"
57#include "lc_extentitydata.h"
58#include "lc_hyperbola.h"
59#include "lc_hyperbolaspline.h"
60#include "lc_mleader.h"
61#include "lc_parabola.h"
62#include "lc_parabolaspline.h"
63#include "lc_splinepoints.h"
64#include "lc_tolerance.h"
65#include "lc_wipeout.h"
66#include "rs_arc.h"
67#include "rs_block.h"
68#include "rs_blocklist.h"
69#include "rs_circle.h"
70#include "rs_dialogfactory.h"
71#include "rs_dialogfactoryinterface.h"
72#include "rs_dimaligned.h"
73#include "rs_dimangular.h"
74#include "rs_dimdiametric.h"
75#include "rs_dimlinear.h"
76#include "rs_dimradial.h"
77#include "rs_ellipse.h"
78#include "rs_filterdxfrw.h"
79#include "rs_graphicview.h"
80#include "rs_hatch.h"
81#include "rs_image.h"
82#include "rs_insert.h"
83#include "rs_layer.h"
84#include "rs_leader.h"
85#include "rs_line.h"
86#include "rs_math.h"
87#include "rs_mtext.h"
88#include "rs_point.h"
89#include "rs_polyline.h"
90#include "rs_solid.h"
91#include "rs_spline.h"
92#include "rs_system.h"
93#include "rs_text.h"
94
95#ifdef DWGSUPPORT1
96#include "lc_graphicviewport.h"
97#include "lc_ucs.h"
98#include "lc_view.h"
99#include "libdwgr.h"
100#include "rs_debug.h"
101#endif // DWGSUPPORT
102
103namespace {
104
105constexpr int kImportedSplineFallbackSamples = 96;
106
107template <typename T>
108bool hasNormalizedAxialExtrusion(const T& entity) {
109 return entity.extPoint.x == 0.0 && entity.extPoint.y == 0.0
110 && (entity.extPoint.z == 1.0 || entity.extPoint.z == -1.0);
111}
112
113bool layerExtStringEmpty(const DRW_Variant* v) {
114 if (v == nullptr || v->type() != DRW_Variant::STRING)
115 return false;
116 return v->content.s == nullptr || v->content.s->empty();
117}
118
119QString layerExtAppName(const DRW_Variant* v) {
120 if (v == nullptr || v->code() != 1001 || v->type() != DRW_Variant::STRING
121 || v->content.s == nullptr) {
122 return {};
123 }
124 return QString::fromUtf8(v->content.s->c_str()).trimmed();
125}
126
127// AutoCAD AEC layer-standard marker: APPID + two empty UTF-16 strings, no
128// actionable LibreCAD semantics.
129bool isAcAecLayerStandardMarker(const std::vector<DRW_Variant*>& extData,
130 std::size_t appBlockStart) {
131 if (appBlockStart >= extData.size())
132 return false;
133 const QString app = layerExtAppName(extData[appBlockStart]);
134 if (!app.compare(QLatin1String("AcAecLayerStandard"), Qt::CaseInsensitive))
135 ;
136 else if (app.startsWith(QLatin1String("AcAec"), Qt::CaseInsensitive)
137 && app.endsWith(QLatin1String("LayerStandard"), Qt::CaseInsensitive))
138 ;
139 else
140 return false;
141
142 std::size_t idx = appBlockStart + 1;
143 int emptyStrings = 0;
144 while (idx < extData.size() && emptyStrings < 2) {
145 const DRW_Variant* v = extData[idx];
146 if (v == nullptr)
147 return false;
148 if (v->code() == 1000) {
149 if (!layerExtStringEmpty(v))
150 return false;
151 ++emptyStrings;
152 ++idx;
153 continue;
154 }
155 if (v->code() == 1002 && v->type() == DRW_Variant::STRING
156 && v->content.s != nullptr
157 && (*v->content.s == "{" || *v->content.s == "}")) {
158 ++idx;
159 continue;
160 }
161 break;
162 }
163 return emptyStrings == 2;
164}
165
166bool isIgnorableLayerExtData(const std::vector<DRW_Variant*>& extData) {
167 if (extData.empty())
168 return true;
169 for (std::size_t i = 0; i < extData.size(); ) {
170 if (extData[i] == nullptr || extData[i]->code() != 1001)
171 return false;
172 if (layerExtAppName(extData[i]).compare(QLatin1String("LibreCad"),
173 Qt::CaseInsensitive) == 0) {
174 return false;
175 }
176 if (isAcAecLayerStandardMarker(extData, i)) {
177 std::size_t idx = i + 1;
178 int emptyStrings = 0;
179 while (idx < extData.size() && emptyStrings < 2) {
180 const DRW_Variant* v = extData[idx];
181 if (v != nullptr && v->code() == 1000) {
182 ++emptyStrings;
183 ++idx;
184 } else if (v != nullptr && v->code() == 1002) {
185 ++idx;
186 } else {
187 break;
188 }
189 }
190 i = idx;
191 continue;
192 }
193 return false;
194 }
195 return true;
196}
197
198bool differsFromUnitWeight(double weight) {
199 return std::fabs(weight - 1.0) > 1e-12;
200}
201
202bool hasRationalSplineWeights(const DRW_Spline* data) {
203 if (data == nullptr)
204 return false;
205 return (data->flags & 0x4) != 0 ||
206 std::any_of(data->weightlist.begin(), data->weightlist.end(), differsFromUnitWeight);
207}
208
209bool buildSplineDataFromDrw(const DRW_Spline* source, RS_SplineData& target) {
210 if (source == nullptr || source->degree < 1 || source->controllist.empty())
211 return false;
212
213 const size_t degree = static_cast<size_t>(source->degree);
214 const size_t controlCount = source->controllist.size();
215 if (controlCount < degree + 1)
216 return false;
217
218 const size_t requiredKnots = controlCount + degree + 1;
219 if (source->knotslist.size() < requiredKnots)
220 return false;
221
222 const bool closed = (source->flags & 0x3) != 0;
223 target = RS_SplineData(source->degree, closed);
224 target.type = closed ? RS_SplineData::SplineType::WrappedClosed
225 : RS_SplineData::SplineType::ClampedOpen;
226
227 target.knotslist.assign(source->knotslist.begin(),
228 source->knotslist.begin() + requiredKnots);
229
230 target.controlPoints.reserve(controlCount);
231 target.weights.reserve(controlCount);
232 const bool rational = hasRationalSplineWeights(source);
233 for (size_t i = 0; i < controlCount; ++i) {
234 const auto& control = source->controllist[i];
235 if (!control)
236 return false;
237 target.controlPoints.push_back({control->x, control->y});
238 double weight = 1.0;
239 if (rational && i < source->weightlist.size())
240 weight = source->weightlist[i];
241 if (weight <= 0.0 || !std::isfinite(weight))
242 return false;
243 target.weights.push_back(weight);
244 }
245
246 return true;
247}
248
249constexpr double kTableFallbackDimension = 1.0;
250constexpr double kTableFallbackMinTextHeight = 0.1;
251
252enum class TableFallbackCellKind {
253 Empty,
254 PlainText,
255 PlaceholderField,
256 PlaceholderBlock,
257 PlaceholderAttribute,
258 PlaceholderUnknown
259};
260
261struct TableFallbackCellDisplay {
262 QString text;
263 TableFallbackCellKind kind = TableFallbackCellKind::Empty;
264};
265
266TableFallbackCellDisplay tableCellDisplay(const DRW_TableCell& cell,
267 bool tableParseComplete) {
268 auto placeholder = [](TableFallbackCellKind kind, const char *text) {
269 TableFallbackCellDisplay display;
270 display.kind = kind;
271 display.text = QString::fromLatin1(text);
272 return display;
273 };
274
275 if (!tableParseComplete || cell.m_geometryFlags != 0
276 || cell.m_geometryHandle != 0 || cell.m_overrideFlags != 0
277 || cell.m_isMerged) {
278 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
279 }
280
281 for (const DRW_TableCellContent& content : cell.m_contents) {
282 if (content.m_type == 4)
283 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
284 if (content.m_type == 2 || content.m_handle != 0)
285 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
286 if (!content.m_text.empty()) {
287 return {QString::fromUtf8(content.m_text.c_str()),
288 TableFallbackCellKind::PlainText};
289 }
290 if (!content.m_value.m_valueString.empty()) {
291 return {QString::fromUtf8(content.m_value.m_valueString.c_str()),
292 TableFallbackCellKind::PlainText};
293 }
294 if (content.m_type != 0 && content.m_type != 1)
295 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
296 }
297 if (cell.m_blockHandle != 0)
298 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
299 if (cell.m_valueHandle != 0)
300 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
301 for (const DRW_TableCellAttribute& attribute : cell.m_attributes) {
302 if (!attribute.m_text.empty()) {
303 return {QString::fromUtf8(attribute.m_text.c_str()),
304 TableFallbackCellKind::PlaceholderAttribute};
305 }
306 if (attribute.m_attdefHandle != 0)
307 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
308 }
309 if (!cell.m_attributes.empty())
310 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
311 return {};
312}
313
314bool tableFallbackCellIsPlaceholder(TableFallbackCellKind kind) {
315 return kind != TableFallbackCellKind::PlainText
316 && kind != TableFallbackCellKind::Empty;
317}
318
319double tableColumnWidth(const DRW_TableContent& content, size_t index,
320 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
321 if (index < content.m_columns.size()
322 && std::isfinite(content.m_columns[index].m_width)
323 && content.m_columns[index].m_width > 0.0) {
324 return content.m_columns[index].m_width;
325 }
326 if (summary != nullptr)
327 ++summary->clampedDimensionCount;
328 return kTableFallbackDimension;
329}
330
331double tableRowHeight(const DRW_TableContent& content, size_t index,
332 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
333 if (index < content.m_rows.size()
334 && std::isfinite(content.m_rows[index].m_height)
335 && content.m_rows[index].m_height > 0.0) {
336 return content.m_rows[index].m_height;
337 }
338 if (summary != nullptr)
339 ++summary->clampedDimensionCount;
340 return kTableFallbackDimension;
341}
342
343DRW_UnsupportedObject rawObjectFromMetadata(
344 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
345 DRW_UnsupportedObject object;
346 object.m_version = record.version;
347 object.m_objectType = record.objectType;
348 object.m_handle = record.handle;
349 object.m_bodyBitSize = record.bodyBitSize;
350 object.m_objectOffset = record.objectOffset;
351 object.m_objectSize = record.objectSize;
352 object.m_isEntity = record.isEntity;
353 object.m_isCustomClass = record.isCustomClass;
354 object.m_recordName = record.recordName;
355 object.m_className = record.className;
356 object.m_rawBytes = record.rawBytes;
357 return object;
358}
359
360bool isSunRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
361 return record.recordName == "SUN" || record.className == "AcDbSun";
362}
363
364bool isAcDbPlaceholderRawObject(
365 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
366 return record.objectType == 80 || record.recordName == "ACDBPLACEHOLDER"
367 || record.className == "AcDbPlaceHolder";
368}
369
370bool isMLeaderStyleRawObject(
371 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
372 return record.recordName == "MLEADERSTYLE"
373 || record.className == "AcDbMLeaderStyle";
374}
375
376bool isMLineStyleRawObject(
377 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
378 return record.objectType == 73 || record.recordName == "MLINESTYLE"
379 || record.className == "AcDbMlineStyle";
380}
381
382// DICTIONARY raw-object predicate. Fixed type 42 is the universal ODA
383// identifier; recordName / className fallbacks cover custom-class-emitted
384// dictionaries (rare but spec-allowed).
385bool isDictionaryRawObject(
386 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
387 return record.objectType == 42 || record.recordName == "DICTIONARY"
388 || record.className == "AcDbDictionary";
389}
390
391// XRECORD raw-object predicate. Fixed type 79 (0x4f) is the universal ODA
392// identifier; the custom-class branch in dwgreader still emits raw bytes
393// under the AcDbXrecord recName/className for non-fixed XRECORDs.
394bool isXRecordRawObject(
395 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
396 return record.objectType == 79 || record.recordName == "XRECORD"
397 || record.className == "AcDbXrecord";
398}
399
400// LAYOUT raw-object predicate. Fixed type 82 + recordName/className
401// fallback for vendor extensions.
402bool isLayoutRawObject(
403 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
404 return record.objectType == 82 || record.recordName == "LAYOUT"
405 || record.className == "AcDbLayout";
406}
407
408// GROUP raw-object predicate. Fixed type 72 (ODA §20.4.72) + recordName/
409// className fallback for vendor extensions.
410bool isGroupRawObject(
411 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
412 return record.objectType == 72 || record.recordName == "GROUP"
413 || record.className == "AcDbGroup";
414}
415
416// RASTERVARIABLES raw-object predicate. Custom-class object (no fixed
417// ODA type) — keyed on recordName/className per dwgreader's dispatch.
418bool isRasterVariablesRawObject(
419 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
420 return record.recordName == "RASTERVARIABLES"
421 || record.className == "AcDbRasterVariables";
422}
423
424bool isWipeoutVariablesRawObject(
425 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
426 return record.recordName == "WIPEOUTVARIABLES"
427 || record.className == "AcDbWipeoutVariables";
428}
429
430// GEODATA raw-object predicate. Custom-class (no fixed ODA type).
431bool isGeoDataRawObject(
432 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
433 return record.recordName == "GEODATA"
434 || record.className == "AcDbGeoData";
435}
436
437// SPATIAL_FILTER raw-object predicate. Custom-class (no fixed ODA type).
438bool isSpatialFilterRawObject(
439 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
440 return record.recordName == "SPATIAL_FILTER"
441 || record.className == "AcDbSpatialFilter";
442}
443
444// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
445// (no fixed ODA type); recordName / className strings must match the
446// dwgreader.cpp dispatch (case-sensitive).
447bool isScaleRawObject(
448 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
449 return record.recordName == "SCALE"
450 || record.className == "AcDbScale";
451}
452
453bool isIDBufferRawObject(
454 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
455 return record.recordName == "IDBUFFER"
456 || record.className == "AcDbIdBuffer";
457}
458
459bool isLayerIndexRawObject(
460 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
461 return record.recordName == "LAYER_INDEX"
462 || record.className == "AcDbLayerIndex";
463}
464
465bool isSpatialIndexRawObject(
466 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
467 return record.recordName == "SPATIAL_INDEX"
468 || record.className == "AcDbSpatialIndex";
469}
470
471bool isDictionaryVarRawObject(
472 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
473 return record.recordName == "DICTIONARYVAR"
474 || record.className == "AcDbDictionaryVar";
475}
476
477// PR 8d.2b — four larger no-storage OBJECTS families. recordName /
478// className strings must match dwgreader.cpp dispatch (case-sensitive).
479bool isDictionaryWithDefaultRawObject(
480 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
481 return record.recordName == "ACDBDICTIONARYWDFLT"
482 || record.recordName == "DICTIONARYWDFLT"
483 || record.className == "AcDbDictionaryWithDefault";
484}
485
486bool isSortEntsTableRawObject(
487 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
488 return record.recordName == "SORTENTSTABLE"
489 || record.className == "AcDbSortentsTable";
490}
491
492bool isFieldListRawObject(
493 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
494 return record.recordName == "FIELDLIST"
495 || record.className == "AcDbFieldList";
496}
497
498bool isFieldRawObject(
499 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
500 return record.recordName == "FIELD"
501 || record.className == "AcDbField";
502}
503
504bool isUnderlayDefinitionRawObject(
505 const LC_DwgAdvancedMetadata::RawObjectRecord& record) {
506 return record.recordName == "PDFDEFINITION"
507 || record.recordName == "DGNDEFINITION"
508 || record.recordName == "DWFDEFINITION"
509 || record.className == "AcDbPdfDefinition"
510 || record.className == "AcDbDgnDefinition"
511 || record.className == "AcDbDwfDefinition";
512}
513
514bool hasReplayableRawMLeaderStyle(const LC_DwgAdvancedMetadata& metadata,
515 std::uint32_t handle) {
516 if (handle == 0)
517 return false;
518 for (const auto& record : metadata.rawObjects()) {
519 if (record.handle == handle && isMLeaderStyleRawObject(record)
520 && LC_DwgAdvancedMetadata::rawReplayBlocker(record)
521 == LC_DwgAdvancedMetadata::ReplayBlocker::None) {
522 return true;
523 }
524 }
525 return false;
526}
527
528// Handles the DWG writer emits at FIXED values (HandleAllocator::seedReserved):
529// control objects 0x01-0x0B, canonical table records 0x0F-0x18 and the
530// model/paper space BLOCK/ENDBLK entities 0x1B-0x1E. Real source files
531// (notably AC1021+) reuse these very numbers for ordinary preserved OBJECTS
532// (placeholders, dictionaries, ...); emitting both sides duplicates the
533// object-map entry and writeDwgHandles() aborts the whole save (BAD_OPEN,
534// zero-byte file). Typed preserved objects are therefore remapped to fresh
535// handles above the preserved high-water mark (fileExport builds
536// m_dwgWriteHandleRemap, writeObjects applies it); raw verbatim objects
537// cannot be rewritten and their replay is blocked instead. Mirrors the DXF
538// path's kFixedStructural remap in fileExport.
539bool isFixedStructuralDwgHandle(std::uint32_t h) {
540 static const std::set<std::uint32_t> kFixed = {
541 0x01, 0x02, 0x03, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
542 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
543 0x1B, 0x1C, 0x1D, 0x1E};
544 return kFixed.count(h) != 0;
545}
546
547DRW_AcDbPlaceholder placeholderFromMetadata(
548 const LC_DwgAdvancedMetadata::PlaceholderRecord& record) {
549 DRW_AcDbPlaceholder placeholder;
550 placeholder.handle = record.handle;
551 placeholder.parentHandle = static_cast<int>(record.parentHandle);
552 return placeholder;
553}
554
555DRW_Dictionary dictionaryFromMetadata(
556 const LC_DwgAdvancedMetadata::DictionaryRecord& record) {
557 DRW_Dictionary dictionary;
558 dictionary.handle = record.handle;
559 dictionary.parentHandle = static_cast<int>(record.parentHandle);
560 dictionary.cloning = record.cloning;
561 dictionary.hardOwner = record.hardOwner;
562 dictionary.name = record.name;
563 dictionary.m_entries.reserve(record.entries.size());
564 for (const auto& er : record.entries) {
565 DRW_Dictionary::Entry entry;
566 entry.m_name = er.name;
567 entry.m_handle = er.handle;
568 dictionary.m_entries.push_back(std::move(entry));
569 }
570 return dictionary;
571}
572
573DRW_XRecord xrecordFromMetadata(
574 const LC_DwgAdvancedMetadata::XRecordRecord& record) {
575 DRW_XRecord xrecord;
576 xrecord.handle = record.handle;
577 xrecord.parentHandle = static_cast<int>(record.parentHandle);
578 xrecord.m_cloning = record.cloning;
579 xrecord.m_values = record.values;
580 xrecord.m_handleValues = record.handleValues;
581 return xrecord;
582}
583
584DRW_Layout layoutFromMetadata(
585 const LC_DwgAdvancedMetadata::LayoutRecord& record) {
586 DRW_Layout layout;
587 layout.handle = record.handle;
588 layout.parentHandle = static_cast<int>(record.parentHandle);
589 // PlotSettings prefix.
590 layout.pageSetupName = record.pageSetupName;
591 layout.printerConfig = record.printerConfig;
592 layout.plotLayoutFlags = record.plotLayoutFlags;
593 layout.marginLeft = record.marginLeft;
594 layout.marginBottom = record.marginBottom;
595 layout.marginRight = record.marginRight;
596 layout.marginTop = record.marginTop;
597 layout.paperWidth = record.paperWidth;
598 layout.paperHeight = record.paperHeight;
599 layout.paperSize = record.paperSize;
600 layout.plotOriginX = record.plotOriginX;
601 layout.plotOriginY = record.plotOriginY;
602 layout.paperUnits = record.paperUnits;
603 layout.plotRotation = record.plotRotation;
604 layout.plotType = record.plotType;
605 layout.windowMinX = record.windowMinX;
606 layout.windowMinY = record.windowMinY;
607 layout.windowMaxX = record.windowMaxX;
608 layout.windowMaxY = record.windowMaxY;
609 layout.plotViewName = record.plotViewName;
610 layout.realWorldUnits = record.realWorldUnits;
611 layout.drawingUnits = record.drawingUnits;
612 layout.currentStyleSheet = record.currentStyleSheet;
613 layout.scaleType = record.scaleType;
614 layout.scaleFactor = record.scaleFactor;
615 layout.paperImageOriginX = record.paperImageOriginX;
616 layout.paperImageOriginY = record.paperImageOriginY;
617 layout.shadePlotMode = record.shadePlotMode;
618 layout.shadePlotResLevel = record.shadePlotResLevel;
619 layout.shadePlotCustomDPI = record.shadePlotCustomDPI;
620 // Layout-specific.
621 layout.name = record.name;
622 layout.layoutFlags = record.layoutFlags;
623 layout.tabOrder = record.tabOrder;
624 layout.ucsOrigin = record.ucsOrigin;
625 layout.limMinX = record.limMinX;
626 layout.limMinY = record.limMinY;
627 layout.limMaxX = record.limMaxX;
628 layout.limMaxY = record.limMaxY;
629 layout.insPoint = record.insPoint;
630 layout.ucsXAxis = record.ucsXAxis;
631 layout.ucsYAxis = record.ucsYAxis;
632 layout.elevation = record.elevation;
633 layout.orthoViewType = record.orthoViewType;
634 layout.extMin = record.extMin;
635 layout.extMax = record.extMax;
636 layout.viewportCount = record.viewportCount;
637 layout.plotViewHandle.ref = record.plotViewHandle;
638 layout.visualStyleHandle.ref = record.visualStyleHandle;
639 layout.paperSpaceBlockRecordHandle.ref = record.paperSpaceBlockRecordHandle;
640 layout.lastActiveViewportHandle.ref = record.lastActiveViewportHandle;
641 layout.baseUcsHandle.ref = record.baseUcsHandle;
642 layout.namedUcsHandle.ref = record.namedUcsHandle;
643 layout.viewportHandles = record.viewportHandles;
644 return layout;
645}
646
647DRW_Group groupFromMetadata(
648 const LC_DwgAdvancedMetadata::GroupRecord& record) {
649 DRW_Group group;
650 group.handle = record.handle;
651 group.parentHandle = static_cast<int>(record.parentHandle);
652 group.m_description = record.description;
653 group.m_isUnnamed = record.isUnnamed;
654 group.m_selectable = record.selectable;
655 group.m_entityHandles = record.entityHandles;
656 return group;
657}
658
659DRW_UnderlayDefinition underlayDefinitionFromMetadata(
660 const LC_DwgAdvancedMetadata::UnderlayDefinitionRecord& record) {
661 DRW_UnderlayDefinition definition;
662 definition.handle = record.handle;
663 definition.parentHandle = static_cast<int>(record.parentHandle);
664 definition.kind = static_cast<DRW_UnderlayDefinition::Kind>(record.kind);
665 definition.filename = record.path;
666 definition.sheetName = record.sheetName;
667 return definition;
668}
669
670DRW_RasterVariables rasterVariablesFromMetadata(
671 const LC_DwgAdvancedMetadata::RasterVariablesRecord& record) {
672 DRW_RasterVariables rv;
673 rv.handle = record.handle;
674 rv.parentHandle = static_cast<int>(record.parentHandle);
675 rv.m_classVersion = record.classVersion;
676 rv.m_imageFrame = record.imageFrame;
677 rv.m_imageQuality = record.imageQuality;
678 rv.m_units = record.units;
679 return rv;
680}
681
682DRW_GeoData geoDataFromMetadata(
683 const LC_DwgAdvancedMetadata::GeoDataRecord& record) {
684 DRW_GeoData gd;
685 gd.handle = record.handle;
686 gd.parentHandle = static_cast<int>(record.parentHandle);
687 gd.m_hostBlockHandle = record.hostBlockHandle;
688 gd.m_version = record.version;
689 gd.m_coordinatesType = record.coordinatesType;
690 gd.m_horizontalUnits = record.horizontalUnits;
691 gd.m_verticalUnits = record.verticalUnits;
692 gd.m_horizontalUnitScale = record.horizontalUnitScale;
693 gd.m_verticalUnitScale = record.verticalUnitScale;
694 gd.m_coordinateSystemDefinition = record.coordinateSystemDefinition;
695 gd.m_geoRssTag = record.geoRssTag;
696 gd.m_designPoint = record.designPoint;
697 gd.m_referencePoint = record.referencePoint;
698 gd.m_upDirection = record.upDirection;
699 gd.m_northDirection = record.northDirection;
700 gd.m_scaleEstimationMethod = record.scaleEstimationMethod;
701 gd.m_userSpecifiedScaleFactor = record.userSpecifiedScaleFactor;
702 gd.m_enableSeaLevelCorrection = record.enableSeaLevelCorrection;
703 gd.m_seaLevelElevation = record.seaLevelElevation;
704 gd.m_coordinateProjectionRadius = record.coordinateProjectionRadius;
705 gd.m_observationFromTag = record.observationFromTag;
706 gd.m_observationToTag = record.observationToTag;
707 gd.m_observationCoverageTag = record.observationCoverageTag;
708 gd.m_points.reserve(record.meshPoints.size());
709 for (const auto& mp : record.meshPoints) {
710 DRW_GeoMeshPoint point;
711 point.m_source = mp.source;
712 point.m_destination = mp.destination;
713 gd.m_points.push_back(point);
714 }
715 gd.m_faces.reserve(record.meshFaces.size());
716 for (const auto& mf : record.meshFaces) {
717 DRW_GeoMeshFace face;
718 face.m_index1 = mf.index1;
719 face.m_index2 = mf.index2;
720 face.m_index3 = mf.index3;
721 gd.m_faces.push_back(face);
722 }
723 return gd;
724}
725
726DRW_SpatialFilter spatialFilterFromMetadata(
727 const LC_DwgAdvancedMetadata::SpatialFilterRecord& record) {
728 DRW_SpatialFilter sf;
729 sf.handle = record.handle;
730 sf.parentHandle = static_cast<int>(record.parentHandle);
731 sf.m_boundaryPoints = record.boundaryPoints;
732 sf.m_normal = record.normal;
733 sf.m_origin = record.origin;
734 sf.m_displayBoundary = record.displayBoundary;
735 sf.m_clipFrontPlane = record.clipFrontPlane;
736 sf.m_clipBackPlane = record.clipBackPlane;
737 sf.m_frontDistance = record.frontDistance;
738 sf.m_backDistance = record.backDistance;
739 sf.m_inverseInsertTransform = record.inverseInsertTransform;
740 sf.m_insertTransform = record.insertTransform;
741 return sf;
742}
743
744// PR 8d.2a — five small no-storage OBJECTS families. Flat field-copy
745// builders matching the addX captures above.
746DRW_Scale scaleFromMetadata(
747 const LC_DwgAdvancedMetadata::ScaleRecord& record) {
748 DRW_Scale s;
749 s.handle = record.handle;
750 s.parentHandle = static_cast<int>(record.parentHandle);
751 s.name = record.name;
752 s.flag = record.flag;
753 s.paperUnits = record.paperUnits;
754 s.drawingUnits = record.drawingUnits;
755 s.isUnitScale = record.isUnitScale;
756 return s;
757}
758
759DRW_MLineStyle mlineStyleFromMetadata(
760 const LC_DwgAdvancedMetadata::MLineStyleRecord& record) {
761 DRW_MLineStyle s;
762 s.handle = record.handle;
763 s.parentHandle = static_cast<int>(record.parentHandle);
764 s.name = record.name;
765 s.flags = record.flags;
766 s.description = record.description;
767 s.fillColor = record.fillColor;
768 s.startAngle = record.startAngle;
769 s.endAngle = record.endAngle;
770 s.elements.reserve(record.elements.size());
771 for (const auto& er : record.elements) {
772 DRW_MLineElement e;
773 e.offset = er.offset;
774 e.color = er.color;
775 e.linetype = er.linetype;
776 s.elements.push_back(std::move(e));
777 }
778 return s;
779}
780
781DRW_WipeoutVariables wipeoutVariablesFromMetadata(
782 const LC_DwgAdvancedMetadata::WipeoutVariablesRecord& record) {
783 DRW_WipeoutVariables w;
784 w.handle = record.handle;
785 w.parentHandle = static_cast<int>(record.parentHandle);
786 w.m_displayFrame = static_cast<std::uint16_t>(record.displayFrame);
787 return w;
788}
789
790DRW_IDBuffer idBufferFromMetadata(
791 const LC_DwgAdvancedMetadata::IDBufferRecord& record) {
792 DRW_IDBuffer b;
793 b.handle = record.handle;
794 b.parentHandle = static_cast<int>(record.parentHandle);
795 b.classVersion = record.classVersion;
796 b.objIds = record.objIds;
797 return b;
798}
799
800DRW_LayerIndex layerIndexFromMetadata(
801 const LC_DwgAdvancedMetadata::LayerIndexRecord& record) {
802 DRW_LayerIndex li;
803 li.handle = record.handle;
804 li.parentHandle = static_cast<int>(record.parentHandle);
805 li.timestamp1 = record.timestamp1;
806 li.timestamp2 = record.timestamp2;
807 li.entries.reserve(record.entries.size());
808 for (const auto& er : record.entries) {
809 DRW_LayerIndexEntry e;
810 e.indexLong = er.indexLong;
811 e.name = er.name;
812 e.entryHandle = er.entryHandle;
813 li.entries.push_back(std::move(e));
814 }
815 return li;
816}
817
818DRW_SpatialIndex spatialIndexFromMetadata(
819 const LC_DwgAdvancedMetadata::SpatialIndexRecord& record) {
820 DRW_SpatialIndex si;
821 si.handle = record.handle;
822 si.parentHandle = static_cast<int>(record.parentHandle);
823 si.timestamp1 = record.timestamp1;
824 si.timestamp2 = record.timestamp2;
825 return si;
826}
827
828DRW_DictionaryVar dictionaryVarFromMetadata(
829 const LC_DwgAdvancedMetadata::DictionaryVarRecord& record) {
830 DRW_DictionaryVar dv;
831 dv.handle = record.handle;
832 dv.parentHandle = static_cast<int>(record.parentHandle);
833 dv.m_schema = record.schema;
834 dv.m_value = record.value;
835 return dv;
836}
837
838// PR 8d.2b — four larger no-storage OBJECTS families. Flat field-copy
839// builders matching the addX captures above.
840DRW_DictionaryWithDefault dictionaryWithDefaultFromMetadata(
841 const LC_DwgAdvancedMetadata::DictionaryWithDefaultRecord& record) {
842 DRW_DictionaryWithDefault dwd;
843 dwd.handle = record.handle;
844 dwd.parentHandle = static_cast<int>(record.parentHandle);
845 dwd.cloning = record.cloning;
846 dwd.hardOwner = record.hardOwner;
847 dwd.name = record.name;
848 dwd.m_entries.reserve(record.entries.size());
849 for (const auto& er : record.entries) {
850 DRW_Dictionary::Entry entry;
851 entry.m_name = er.name;
852 entry.m_handle = er.handle;
853 dwd.m_entries.push_back(std::move(entry));
854 }
855 dwd.m_defaultEntryHandle = record.defaultEntryHandle;
856 return dwd;
857}
858
859DRW_SortEntsTable sortEntsTableFromMetadata(
860 const LC_DwgAdvancedMetadata::SortEntsTableRecord& record) {
861 DRW_SortEntsTable se;
862 se.handle = record.handle;
863 se.parentHandle = static_cast<int>(record.parentHandle);
864 se.m_sortHandles = record.sortHandles;
865 se.m_blockOwnerHandle = record.blockOwnerHandle;
866 se.m_entityHandles = record.entityHandles;
867 return se;
868}
869
870DRW_FieldList fieldListFromMetadata(
871 const LC_DwgAdvancedMetadata::FieldListRecord& record) {
872 DRW_FieldList fl;
873 fl.handle = record.handle;
874 fl.parentHandle = static_cast<int>(record.parentHandle);
875 fl.m_unknown = record.unknown;
876 fl.m_fieldHandles = record.fieldHandles;
877 return fl;
878}
879
880DRW_CadValue cadValueFromRecord(
881 const LC_DwgAdvancedMetadata::CadValueRecord& cr) {
882 DRW_CadValue cv;
883 cv.m_formatFlags = cr.formatFlags;
884 cv.m_dataType = cr.dataType;
885 cv.m_dataSize = cr.dataSize;
886 cv.m_unitType = cr.unitType;
887 cv.m_value = cr.value;
888 cv.m_formatString = cr.formatString;
889 cv.m_valueString = cr.valueString;
890 cv.m_handle = cr.handle;
891 cv.m_rawData = cr.rawData;
892 return cv;
893}
894
895DRW_Field fieldFromMetadata(
896 const LC_DwgAdvancedMetadata::FieldRecord& record) {
897 DRW_Field f;
898 f.handle = record.handle;
899 f.parentHandle = static_cast<int>(record.parentHandle);
900 f.m_evaluatorId = record.evaluatorId;
901 f.m_fieldCode = record.fieldCode;
902 f.m_formatString = record.formatString;
903 f.m_evaluationOptionFlags = record.evaluationOptionFlags;
904 f.m_filingOptionFlags = record.filingOptionFlags;
905 f.m_fieldStateFlags = record.fieldStateFlags;
906 f.m_evaluationStatusFlags = record.evaluationStatusFlags;
907 f.m_evaluationErrorCode = record.evaluationErrorCode;
908 f.m_evaluationErrorMessage = record.evaluationErrorMessage;
909 f.m_value = cadValueFromRecord(record.value);
910 f.m_valueString = record.valueString;
911 f.m_valueStringLength = record.valueStringLength;
912 f.m_childHandles = record.childHandles;
913 f.m_objectHandles = record.objectHandles;
914 f.m_childValues.reserve(record.childValues.size());
915 for (const auto& cv : record.childValues) {
916 DRW_Field::ChildValue dcv;
917 dcv.m_key = cv.key;
918 dcv.m_value = cadValueFromRecord(cv.value);
919 f.m_childValues.push_back(std::move(dcv));
920 }
921 return f;
922}
923
924DRW_Sun sunFromMetadata(const LC_DwgAdvancedMetadata::SunRecord& record) {
925 DRW_Sun sun;
926 sun.handle = record.handle;
927 sun.parentHandle = static_cast<int>(record.parentHandle);
928 sun.m_classVersion = record.classVersion;
929 sun.m_isOn = record.isOn;
930 sun.m_color = record.color;
931 sun.m_intensity = record.intensity;
932 sun.m_hasShadow = record.hasShadow;
933 sun.m_julianDay = record.julianDay;
934 sun.m_milliseconds = record.milliseconds;
935 sun.m_isDaylightSavings = record.isDaylightSavings;
936 sun.m_shadowType = record.shadowType;
937 sun.m_shadowMapSize = record.shadowMapSize;
938 sun.m_shadowSoftness = record.shadowSoftness;
939 return sun;
940}
941
942DRW_MLeaderStyle mleaderStyleFromMetadata(
943 const LC_DwgAdvancedMetadata::MLeaderStyleRecord& record) {
944 DRW_MLeaderStyle style;
945 style.handle = record.handle;
946 style.parentHandle = static_cast<int>(record.parentHandle);
947 style.name = record.name;
948 style.styleVersion = record.styleVersion != 0 ? record.styleVersion : 2;
949 style.contentType = record.contentType;
950 style.drawMLeaderOrder = record.drawMLeaderOrder;
951 style.drawLeaderOrder = record.drawLeaderOrder;
952 style.maxLeaderPoints = record.maxLeaderPoints;
953 style.firstSegmentAngle = record.firstSegmentAngle;
954 style.secondSegmentAngle = record.secondSegmentAngle;
955 style.leaderType = record.leaderType;
956 style.leaderColor = record.leaderColor;
957 style.leaderLineTypeHandle.ref = record.leaderLineTypeHandle;
958 style.leaderLineWeight = record.leaderLineWeight;
959 style.landingEnabled = record.landingEnabled;
960 style.landingGap = record.landingGap;
961 style.autoIncludeLanding = record.autoIncludeLanding;
962 style.landingDistance = record.landingDistance;
963 style.description = record.description;
964 style.arrowHeadBlockHandle.ref = record.arrowHeadBlockHandle;
965 style.arrowHeadSize = record.arrowHeadSize;
966 style.textDefault = record.textDefault;
967 style.textStyleHandle.ref = record.textStyleHandle;
968 style.leftAttachment = record.leftAttachment;
969 style.rightAttachment = record.rightAttachment;
970 style.textAngleType = record.textAngleType;
971 style.textAlignmentType = record.textAlignmentType;
972 style.textColor = record.textColor;
973 style.textHeight = record.textHeight;
974 style.textFrameEnabled = record.textFrameEnabled;
975 style.alwaysAlignTextLeft = record.alwaysAlignTextLeft;
976 style.alignSpace = record.alignSpace;
977 style.blockHandle.ref = record.blockHandle;
978 style.blockColor = record.blockColor;
979 style.blockScale = record.blockScale;
980 style.blockScaleEnabled = record.blockScaleEnabled;
981 style.blockRotation = record.blockRotation;
982 style.blockRotationEnabled = record.blockRotationEnabled;
983 style.blockConnectionType = record.blockConnectionType;
984 style.scaleFactor = record.scaleFactor;
985 style.propertyChanged = record.propertyChanged;
986 style.isAnnotative = record.isAnnotative;
987 style.breakSize = record.breakSize;
988 style.attachmentDirection = record.attachmentDirection;
989 style.topAttachment = record.topAttachment;
990 style.bottomAttachment = record.bottomAttachment;
991 style.textExtended = record.textExtended;
992 return style;
993}
994
995std::unique_ptr<LC_SplinePoints> approximateDrwSpline(
996 RS_EntityContainer* parent, const DRW_Spline* data) {
997 RS_SplineData splineData;
998 if (!buildSplineDataFromDrw(data, splineData))
999 return nullptr;
1000
1001 const size_t degree = static_cast<size_t>(data->degree);
1002 const size_t controlCount = splineData.controlPoints.size();
1003 const double tmin = splineData.knotslist[degree];
1004 const double tmax = splineData.knotslist[controlCount];
1005 if (!std::isfinite(tmin) || !std::isfinite(tmax) || tmax <= tmin)
1006 return nullptr;
1007
1008 LC_SplinePointsData sampled((data->flags & 0x3) != 0, false);
1009 sampled.useControlPoints = false;
1010 sampled.splinePoints.reserve(kImportedSplineFallbackSamples + 1);
1011 for (int i = 0; i <= kImportedSplineFallbackSamples; ++i) {
1012 const double u = tmin + (tmax - tmin) *
1013 (static_cast<double>(i) / kImportedSplineFallbackSamples);
1014 const RS_Vector point = RS_Spline::evaluateNURBS(splineData, u);
1015 if (!point.valid)
1016 return nullptr;
1017 sampled.splinePoints.push_back(point);
1018 }
1019
1020 if (sampled.closed && sampled.splinePoints.size() > 1 &&
1021 sampled.splinePoints.front().distanceTo(sampled.splinePoints.back()) <= 1e-8) {
1022 sampled.splinePoints.pop_back();
1023 }
1024
1025 if (sampled.splinePoints.size() < 2)
1026 return nullptr;
1027
1028 return std::make_unique<LC_SplinePoints>(parent, std::move(sampled));
1029}
1030// convert DRW_Coord to RS_Vector
1031RS_Vector coordToVector(const std::shared_ptr<DRW_Coord>& c) {
1032 return c ? RS_Vector(c->x, c->y) : RS_Vector(false);
1033}
1034
1035}
1036
1037/**
1038 * Default constructor.
1039 *
1040 */
1041RS_FilterDXFRW::RS_FilterDXFRW()
1042 :RS_FilterInterface(),DRW_Interface() {
1043 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW()");
1044
1045 m_currentContainer = nullptr;
1046 m_graphic = nullptr;
1047 // Init hash to change the QCAD "normal" style to the more correct ISO-3059
1048 // or draftsight symbol (AR*.shx) to sy*.lff
1049 m_fontList["arastro"] = "syastro";
1050 m_fontList["armap"] = "symap";
1051 m_fontList["armeteo"] = "symeteo";
1052 m_fontList["armusic"] = "symusic";
1053 m_fontList["math"] = "symath";
1054 m_fontList["normal"] = "iso";
1055 m_fontList["normallatin1"] = "iso";
1056 m_fontList["normallatin2"] = "iso";
1057
1058 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW(): OK");
1059}
1060
1061/**
1062 * Destructor.
1063 */
1064RS_FilterDXFRW::~RS_FilterDXFRW() {
1065 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::~RS_FilterDXFRW(): OK");
1066}
1067
1068// Human-readable display for a DRW::Version. Year suffixes anchor users
1069// in time so they can decide whether to convert (pre-R13) or update their
1070// install (post-R2018 future versions).
1071static QString dwgVersionDisplay(DRW::Version v) {
1072 switch (v) {
1073 case DRW::MC00:
1074 return QStringLiteral("R1.1 (1982)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.1 (1982)")));
1075 case DRW::AC12:
1076 return QStringLiteral("R1.2 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.2 (1983)")));
1077 case DRW::AC14:
1078 return QStringLiteral("R1.4 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.4 (1983)")));
1079 case DRW::AC150:
1080 return QStringLiteral("R2.0 (1984)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.0 (1984)")));
1081 case DRW::AC210:
1082 return QStringLiteral("R2.10 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.10 (1986)")));
1083 case DRW::AC1002:
1084 return QStringLiteral("R2.5 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.5 (1986)")));
1085 case DRW::AC1003:
1086 return QStringLiteral("R2.6 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.6 (1987)")));
1087 case DRW::AC1004:
1088 return QStringLiteral("R9 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R9 (1987)")));
1089 case DRW::AC1006:
1090 return QStringLiteral("R10 (1988)")(QString(QtPrivate::qMakeStringPrivate(u"" "R10 (1988)")));
1091 case DRW::AC1009:
1092 return QStringLiteral("R11/R12 (1990)")(QString(QtPrivate::qMakeStringPrivate(u"" "R11/R12 (1990)"))
)
;
1093 case DRW::AC1012:
1094 return QStringLiteral("R13 (1994)")(QString(QtPrivate::qMakeStringPrivate(u"" "R13 (1994)")));
1095 case DRW::AC1014:
1096 return QStringLiteral("R14 (1997)")(QString(QtPrivate::qMakeStringPrivate(u"" "R14 (1997)")));
1097 case DRW::AC1015:
1098 return QStringLiteral("AutoCAD 2000")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2000")));
1099 case DRW::AC1018:
1100 return QStringLiteral("AutoCAD 2004")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2004")));
1101 case DRW::AC1021:
1102 return QStringLiteral("AutoCAD 2007")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2007")));
1103 case DRW::AC1024:
1104 return QStringLiteral("AutoCAD 2010")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2010")));
1105 case DRW::AC1027:
1106 return QStringLiteral("AutoCAD 2013")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2013")));
1107 case DRW::AC1032:
1108 return QStringLiteral("AutoCAD 2018")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2018")));
1109 case DRW::UNKNOWNV:
1110 default:
1111 return QStringLiteral("unknown")(QString(QtPrivate::qMakeStringPrivate(u"" "unknown")));
1112 }
1113}
1114
1115// fixme - sand - non informative error message (mixed file format which is known)
1116QString RS_FilterDXFRW::lastError() const{
1117 switch (m_errorCode) {
1118 case DRW::BAD_NONE:
1119 return (QObject::tr( "no DXF/DWG error", "RS_FilterDXFRW"));
1120 case DRW::BAD_OPEN:
1121 return (QObject::tr( "error opening DXF/DWG file", "RS_FilterDXFRW"));
1122 case DRW::BAD_VERSION:
1123 if (m_dwgVersion != DRW::UNKNOWNV) {
1124 return QObject::
1125 tr("Cannot open DWG: file is %1; LibreCAD supports %2 and newer. "
1126 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
1127 "from a recent CAD tool.",
1128 "RS_FilterDXFRW")
1129 .arg(dwgVersionDisplay(m_dwgVersion))
1130 .arg(dwgVersionDisplay(DRW::AC1012));
1131 }
1132 return (QObject::tr( "unsupported DXF/DWG file version", "RS_FilterDXFRW"));
1133 case DRW::BAD_READ_METADATA:
1134 return (QObject::tr( "error reading DXF/DWG meta data", "RS_FilterDXFRW"));
1135 case DRW::BAD_READ_FILE_HEADER:
1136 return (QObject::tr( "error reading DXF/DWG file header", "RS_FilterDXFRW"));
1137 case DRW::BAD_READ_HEADER:
1138 return (QObject::tr( "error reading DXF/DWG header data", "RS_FilterDXFRW"));
1139 case DRW::BAD_READ_HANDLES:
1140 return (QObject::tr( "error reading DXF/DWG object map", "RS_FilterDXFRW"));
1141 case DRW::BAD_READ_CLASSES:
1142 return (QObject::tr( "error reading DXF/DWG classes", "RS_FilterDXFRW"));
1143 case DRW::BAD_READ_TABLES:
1144 return (QObject::tr( "error reading DXF/DWG tables", "RS_FilterDXFRW"));
1145 case DRW::BAD_READ_BLOCKS:
1146 return (QObject::tr( "error reading DXF/DWG blocks", "RS_FilterDXFRW"));
1147 case DRW::BAD_READ_ENTITIES:
1148 return (QObject::tr( "error reading DXF/DWG entities", "RS_FilterDXFRW"));
1149 case DRW::BAD_READ_OBJECTS:
1150 return (QObject::tr( "error reading DXF/DWG objects", "RS_FilterDXFRW"));
1151 case DRW::BAD_READ_SECTION:
1152 return (QObject::tr( "error reading DXF/DWG sections", "RS_FilterDXFRW"));
1153 case DRW::BAD_CODE_PARSED:
1154 return (QObject::tr( "error reading DXF/DWG code", "RS_FilterDXFRW"));
1155 default:
1156 break;
1157 }
1158
1159 return RS_FilterInterface::lastError();
1160}
1161
1162namespace {
1163
1164// Pre-R13 DWG files often omit $ACADVER from the table snapshot delivered to
1165// addHeader(), leaving m_version at the 1021 fallback during entity import.
1166// Sniff the on-disk magic so import-time guards can key off AC1009 et al.
1167int versionIdFromDwgMagic(const QString &filePath) {
1168 QFile file(filePath);
1169 if (!file.open(QIODevice::ReadOnly))
1170 return 0;
1171 const QByteArray magic = file.read(6);
1172 if (magic.size() < 6)
1173 return 0;
1174 if (magic == "AC1009")
1175 return 1009;
1176 if (magic == "AC1012")
1177 return 1012;
1178 if (magic == "AC1014")
1179 return 1014;
1180 if (magic == "AC1015")
1181 return 1015;
1182 if (magic == "AC1018")
1183 return 1018;
1184 if (magic == "AC1021")
1185 return 1021;
1186 if (magic == "AC1024")
1187 return 1024;
1188 if (magic == "AC1027")
1189 return 1027;
1190 if (magic == "AC1032")
1191 return 1032;
1192 return 0;
1193}
1194
1195} // namespace
1196
1197/**
1198 * Implementation of the method used for RS_Import to communicate
1199 * with this filter.
1200 *
1201 * @param g The m_graphic in which the entities from the m_file
1202 * will be created or the graphics from which the entities are
1203 * taken to be stored in a m_file.
1204 */
1205bool RS_FilterDXFRW::fileImport(RS_Graphic& g, const QString& file, [[maybe_unused]] RS2::FormatType type) {
1206 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport");
1207 RS_DEBUGRS_Debug::instance()->print("DXFRW Filter: importing file '%s'...", (const char*)QFile::encodeName(file));
1208
1209 m_graphic = &g;
1210 m_currentContainer = m_graphic;
1211 m_dummyContainer = new RS_EntityContainer(nullptr, true);
1212 // RAII: free the scratch container on every return path (success and the
1213 // BAD_VERSION / parse-failure early returns), not just the success path.
1214 struct DummyContainerGuard {
1215 RS_EntityContainer** p;
1216 ~DummyContainerGuard() { delete *p; *p = nullptr; }
1217 } dummyGuard{&m_dummyContainer};
1218
1219 this->m_file = file;
1220 // Register the file being loaded into the XREF recursion guard so
1221 // A → B → A cycles are detected at depth 2 rather than depth 4.
1222 // Done via QFileInfo::absoluteFilePath() to match resolveXrefPath()
1223 // (which returns absolute paths). RAII-erased on return so failure
1224 // paths (BAD_VERSION, parse failure, etc.) don't leak the entry.
1225 const QString thisFileAbs = QFileInfo(file).absoluteFilePath();
1226 const bool stackInsertedSelf = m_xrefStack.insert(thisFileAbs).second;
1227 struct XrefStackGuard {
1228 std::set<QString> *stack = nullptr;
1229 QString key;
1230 bool owned = false;
1231 ~XrefStackGuard() {
1232 if (stack && owned)
1233 stack->erase(key);
1234 }
1235 } stackGuard{&m_xrefStack, thisFileAbs, stackInsertedSelf};
1236 // add some variables that need to be there for DXF drawings:
1237 m_graphic->addVariable("$DIMSTYLE", "Standard", 2);
1238 m_dimStyle = "Standard";
1239 m_codePage = "ANSI_1252";
1240 m_textStyle = "Standard";
1241 //reset library version
1242 m_isLibDxfRw = false;
1243 m_libDxfRwVersion = 0;
1244 m_unsupportedDwgObjects.clear();
1245 m_graphic->dwgAdvancedMetadata().clear();
1246 // Clear per-import caches so a reused filter instance does not carry stale
1247 // (and, for m_blockHash, potentially dangling) state across imports. XREF
1248 // sub-imports use a separate child filter, so this never wipes parent state.
1249 m_blockHash.clear();
1250 m_importLayerCache.clear();
1251 m_importLayerRawCache.clear();
1252 m_mlineStyleCache.clear();
1253 m_underlayDefMap.clear();
1254 m_xrefBlockNames.clear();
1255
1256#ifdef DWGSUPPORT1
1257 if (type == RS2::FormatDWG) {
1258 const int magicVersion = versionIdFromDwgMagic(file);
1259 if (magicVersion != 0)
1260 m_version = magicVersion;
1261
1262 dwgR dwgr(QFile::encodeName(file));
1263 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file");
1264 if (RS_DEBUGRS_Debug::instance()->getLevel()== RS_Debug::D_DEBUGGING)
1265 dwgr.setDebug(DRW::DebugLevel::Debug);
1266 const auto dwgReadStart = std::chrono::steady_clock::now();
1267 bool success = dwgr.read(this, true);
1268 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
1269 const auto dwgReadMs = std::chrono::duration_cast<std::chrono::milliseconds>(
1270 std::chrono::steady_clock::now() - dwgReadStart).count();
1271 std::cerr << "[import-bench] dwg_read_ms=" << dwgReadMs << "\n";
1272 }
1273 // Capture the recognized version BEFORE acting on the result so
1274 // BAD_VERSION error reporting (printDwgError / lastError) can
1275 // name the format the user supplied. dwgR::version is set by
1276 // openFile() even on the BAD_VERSION fork.
1277 m_dwgVersion = dwgr.getVersion();
1278 // Persist the source version on the document so the export filter can
1279 // gate raw-replay (emit + CLASSES registration) on source==target.
1280 m_graphic->dwgAdvancedMetadata().setSourceDwgVersion(m_dwgVersion);
1281 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file: OK");
1282 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("Opened DWG file version %1.").arg(printDwgVersion(dwgr.getVersion())));
1283 std::cout << "DWG file version: " << printDwgVersion(dwgr.getVersion()).toStdString() << "\n";
1284 const size_t parseFailures = dwgr.getEntityParseFailures();
1285 if (parseFailures > 0) {
1286 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1287 QObject::tr("DWG load: %1 %2 had parse errors and were skipped. "
1288 "Drawing loaded with the rest.")
1289 .arg(parseFailures)
1290 .arg(parseFailures == 1 ? QObject::tr("entity")
1291 : QObject::tr("entities")));
1292 }
1293 // Vendor-extension custom classes (AutoCAD Mechanical AmgStdPart aka
1294 // STDPART2D, AcmBomRow, etc.) whose proprietary geometry libdxfrw
1295 // can't decode. Surface the top breakdown so the user knows what's
1296 // missing rather than silently rendering a partial drawing.
1297 const auto skipped = dwgr.getSkippedCustomClasses();
1298 if (!skipped.empty()) {
1299 size_t totalSkipped = 0;
1300 std::vector<std::pair<QString, size_t>> sorted;
1301 sorted.reserve(skipped.size());
1302 for (const auto &kv : skipped) {
1303 totalSkipped += kv.second;
1304 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
1305 }
1306 std::sort(
1307 sorted.begin(), sorted.end(),
1308 [](const auto &a, const auto &b) { return a.second > b.second; });
1309 QStringList top;
1310 const size_t showN = std::min<size_t>(3, sorted.size());
1311 for (size_t i = 0; i < showN; ++i)
1312 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
1313 QString breakdown = top.join(QLatin1String(", "));
1314 if (sorted.size() > showN)
1315 breakdown += QObject::tr(", and %n more class(es)", "",
1316 static_cast<int>(sorted.size() - showN));
1317 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1318 QObject::tr(
1319 "DWG load: %1 vendor-extension entities not rendered (%2). "
1320 "These are typically AutoCAD Mechanical or other "
1321 "vertical-product "
1322 "custom classes that libdxfrw cannot decode.")
1323 .arg(totalSkipped)
1324 .arg(breakdown));
1325 }
1326 const auto unsupportedObjects = dwgr.getSkippedUnsupportedObjects();
1327 if (!unsupportedObjects.empty()) {
1328 size_t totalSkipped = 0;
1329 std::vector<std::pair<QString, size_t>> sorted;
1330 sorted.reserve(unsupportedObjects.size());
1331 for (const auto &kv : unsupportedObjects) {
1332 totalSkipped += kv.second;
1333 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
1334 }
1335 std::sort(
1336 sorted.begin(), sorted.end(),
1337 [](const auto &a, const auto &b) { return a.second > b.second; });
1338 QStringList top;
1339 const size_t showN = std::min<size_t>(3, sorted.size());
1340 for (size_t i = 0; i < showN; ++i)
1341 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
1342 QString breakdown = top.join(QLatin1String(", "));
1343 if (sorted.size() > showN)
1344 breakdown += QObject::tr(", and %n more object type(s)", "",
1345 static_cast<int>(sorted.size() - showN));
1346 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1347 QObject::tr("DWG load: %1 unsupported metadata object(s) skipped "
1348 "(%2). Drawing geometry may still be complete.")
1349 .arg(totalSkipped)
1350 .arg(breakdown));
1351 }
1352 RS_DEBUGRS_Debug::instance()->print("DWG read summary: %d entities, %d blocks, error=%d",
1353 m_graphic ? m_graphic->count() : -1,
1354 m_graphic ? m_graphic->countBlocks() : -1,
1355 dwgr.getError());
1356 int lastError = dwgr.getError();
1357 if (false == success) {
1358 printDwgError(lastError);
1359 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DWG file '%s'.", (const char*)QFile::encodeName(file));
1360 m_errorCode = dwgr.getError();
1361 return false;
1362 }
1363 }
1364 else {
1365#endif
1366
1367 m_dxfR = new dxfRW(QFile::encodeName(file));
1368
1369 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file");
1370 if (RS_Debug::D_DEBUGGING == RS_DEBUGRS_Debug::instance()->getLevel()) {
1371 m_dxfR->setDebug(DRW::DebugLevel::Debug);
1372 }
1373 bool success{false};
1374 if (file.startsWith(":")) {
1375 // load content from resources. It SHOULD be present in resource!
1376 QFile resourceFile(file);
1377 if (resourceFile.open(QIODevice::ReadOnly)) {
1378 QByteArray contentString = resourceFile.readAll();
1379 resourceFile.close();
1380 std::string content = contentString.toStdString();
1381 success = m_dxfR->readAscii(this, true, content);
1382 }
1383 }
1384 else {
1385 success = m_dxfR->read(this, true);
1386 }
1387 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file: OK");
1388 if (success) {
1389 std::cout << "DXF file version: " << printDwgVersion(m_dxfR->getVersion()).toStdString() << "\n";
1390 }
1391 //graphic->setAutoUpdateBorders(true);
1392
1393 if (false == success) {
1394 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DXF file '%s'.", (const char*)QFile::encodeName(file));
1395 m_errorCode = m_dxfR->getError();
1396 delete m_dxfR;
1397 return false;
1398 }
1399 else {
1400 delete m_dxfR;
1401 }
1402#ifdef DWGSUPPORT1
1403 }
1404#endif
1405
1406 /*set current layer */
1407 auto cl = m_graphic->findLayer(m_graphic->getVariableString("$CLAYER", "0"));
1408 if (cl) {
1409 //require to notify
1410 m_graphic->activateLayer(cl, true);
1411 }
1412 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: updating inserts");
1413 const auto updateInsertsStart = std::chrono::steady_clock::now();
1414
1415 // BLOCK_CONTROL order is not a dependency order. Resolve definition-owned
1416 // INSERTs first, from their referenced blocks outward, so model-space
1417 // INSERT expansion never snapshots an unresolved nested definition.
1418 std::set<RS_Block*> resolvedBlocks;
1419 std::set<RS_Block*> resolvingBlocks;
1420 const auto updateBlockInserts = [&](auto&& self, RS_Block* block) -> void {
1421 if (block == nullptr || resolvedBlocks.find(block) != resolvedBlocks.end())
1422 return;
1423 if (!resolvingBlocks.insert(block).second)
1424 return; // RS_Insert::update() will reject the reference cycle.
1425 for (RS_Entity* entity : *block) {
1426 if (entity == nullptr || entity->rtti() != RS2::EntityInsert)
1427 continue;
1428 self(self, static_cast<RS_Insert*>(entity)->getBlockForInsert());
1429 }
1430 block->updateInserts();
1431 resolvingBlocks.erase(block);
1432 resolvedBlocks.insert(block);
1433 };
1434 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i)
1435 updateBlockInserts(updateBlockInserts, m_graphic->blockAt(i));
1436 m_graphic->updateInserts();
1437 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
1438 const auto updateInsertsMs = std::chrono::duration_cast<std::chrono::milliseconds>(
1439 std::chrono::steady_clock::now() - updateInsertsStart).count();
1440 int modelInserts = 0;
1441 for (RS_Entity *e : *m_graphic) {
1442 if (e != nullptr && e->rtti() == RS2::EntityInsert)
1443 ++modelInserts;
1444 }
1445 std::cerr << "[import-bench] updateInserts_ms=" << updateInsertsMs
1446 << " blocks=" << m_graphic->countBlocks()
1447 << " model_entities=" << m_graphic->count()
1448 << " model_inserts=" << modelInserts << "\n";
1449 }
1450
1451 // Orphan XREF detection. An XREF block whose contents were embedded
1452 // is still invisible in modelspace unless something INSERTs it.
1453 // AutoCAD typically renders these through a paper-space layout
1454 // viewport, which LibreCAD doesn't implement, so the user should
1455 // know why the externally-referenced geometry isn't visible.
1456 // Only run for the outer fileImport (m_xrefStack empty); skip when
1457 // recursively loading an XREF source.
1458 if (m_xrefStack.empty() && !m_xrefBlockNames.empty()) {
1459 std::set<QString> referenced;
1460 for (auto *e : *m_graphic) {
1461 if (e && e->rtti() == RS2::EntityInsert) {
1462 referenced.insert(static_cast<RS_Insert *>(e)->getName());
1463 }
1464 }
1465 QStringList orphans;
1466 for (const QString &xrefName : m_xrefBlockNames) {
1467 if (!referenced.count(xrefName))
1468 orphans << xrefName;
1469 }
1470 if (!orphans.isEmpty()) {
1471 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1472 QObject::tr("DWG/DXF load: %1 XREF block(s) (%2) loaded but not "
1473 "INSERTed into modelspace. Their externally-referenced "
1474 "geometry won't be visible — AutoCAD typically renders "
1475 "these through a paper-space layout viewport, which "
1476 "LibreCAD doesn't render.")
1477 .arg(orphans.size())
1478 .arg(orphans.join(QStringLiteral(", ")(QString(QtPrivate::qMakeStringPrivate(u"" ", "))))));
1479 }
1480 }
1481
1482 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport OK");
1483 return true;
1484}
1485
1486/**
1487 * Implementation of the method which handles layers.
1488 */
1489RS_Layer *RS_FilterDXFRW::importLayerForEntity(const QString &layName,
1490 const std::string &rawLayerName) {
1491 // Fast path: most entities repeat one of a handful of distinct layer
1492 // names, so a raw-string hit here skips the NFC-normalization pass below
1493 // entirely. A raw-key hit can only return a layer that was itself
1494 // resolved (and validated) via the normalized-key path below for this
1495 // exact byte-identical string, so there is no correctness difference
1496 // from a miss -- just a skipped normalization.
1497 auto rawCached = m_importLayerRawCache.constFind(layName);
1498 if (rawCached != m_importLayerRawCache.constEnd())
1499 return rawCached.value();
1500
1501 const QString key = layName.normalized(QString::NormalizationForm_C);
1502 auto cached = m_importLayerCache.constFind(key);
1503 if (cached != m_importLayerCache.constEnd()) {
1504 m_importLayerRawCache.insert(layName, cached.value());
1505 return cached.value();
1506 }
1507
1508 RS_Layer *layer = m_graphic->findLayer(layName);
1509 if (layer == nullptr) {
1510 DRW_Layer lay;
1511 lay.name = rawLayerName;
1512 addLayer(lay);
1513 layer = m_graphic->findLayer(layName);
1514 }
1515 if (layer != nullptr) {
1516 m_importLayerCache.insert(key, layer);
1517 m_importLayerRawCache.insert(layName, layer);
1518 }
1519 return layer;
1520}
1521
1522void RS_FilterDXFRW::addLayer(const DRW_Layer& data) {
1523 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer");
1524 RS_DEBUGRS_Debug::instance()->print(" adding layer: %s", data.name.c_str());
1525
1526 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: creating layer");
1527
1528 QString name = QString::fromUtf8(data.name.c_str());
1529 const QString key = name.normalized(QString::NormalizationForm_C);
1530 if (name != "0") {
1531 auto cached = m_importLayerCache.constFind(key);
1532 if (cached != m_importLayerCache.constEnd())
1533 return;
1534 RS_Layer *existing = m_graphic->findLayer(name);
1535 if (existing != nullptr) {
1536 m_importLayerCache.insert(key, existing);
1537 return;
1538 }
1539 }
1540 auto* layer = new RS_Layer(name);
1541 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: set pen");
1542 layer->setPen(attributesToPen(&data));
1543
1544 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: flags");
1545 if (data.flags & 0x01) {
1546 layer->freeze(true);
1547 }
1548 if (data.flags & 0x04) {
1549 layer->lock(true);
1550 }
1551 layer->setPrint(data.plotF);
1552
1553 //parse extended data to read construction flag
1554 if (!data.extData.empty()) {
1555 if (!isIgnorableLayerExtData(data.extData)) {
1556 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING,
1557 "RS_FilterDXFRW::addLayer: layer %s has extended data",
1558 layer->getName().toStdString().c_str());
1559 }
1560 bool isLCdata = false;
1561 for (std::vector<DRW_Variant*>::const_iterator it=data.extData.begin(); it!=data.extData.end(); ++it){
1562 if ((*it)->code() == 1001) {
1563 if (*(*it)->content.s == std::string("LibreCad")) {
1564 isLCdata = true;
1565 }
1566 else {
1567 isLCdata = false;
1568 }
1569 }
1570 else if (isLCdata && (*it)->code() == 1070) {
1571 if ((*it)->content.i == 1) {
1572 layer->setConstruction(true);
1573 }
1574 }
1575 }
1576 }
1577 //pre dxfrw 0.5.13 plot flag are used to store construction layer
1578 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 13)(((0) << 16) | ((5) << 8) | (13))) {
1579 layer->setConstruction(!data.plotF);
1580 }
1581
1582 if (layer->isConstruction()) {
1583 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "RS_FilterDXF::addLayer: layer %s is construction layer",
1584 layer->getName().toStdString().c_str());
1585 }
1586
1587 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: add layer to graphic");
1588 m_graphic->addLayer(layer);
1589 m_importLayerCache.insert(key, layer);
1590 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: OK");
1591}
1592
1593/**
1594 * Implementation of the method which handles dimension styles.
1595 */
1596void RS_FilterDXFRW::addDimStyle(const DRW_Dimstyle& data) {
1597 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayer");
1598 QString dimStyleName = m_graphic->getVariableString("$DIMSTYLE", "standard");
1599
1600 if (QString::compare(data.name.c_str(), dimStyleName, Qt::CaseInsensitive) == 0) {
1601 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 6, 2)(((0) << 16) | ((6) << 8) | (2))) {
1602 m_graphic->addVariable("$DIMDEC", m_graphic->getVariableInt("$DIMDEC", m_graphic->getVariableInt("$LUPREC", 4)), 70);
1603 m_graphic->addVariable("$DIMADEC", m_graphic->getVariableInt("$DIMADEC", m_graphic->getVariableInt("$AUPREC", 2)), 70);
1604 //do nothing;
1605 }
1606 else {
1607 m_graphic->addVariable("$DIMDEC", data.dimdec, 70);
1608 m_graphic->addVariable("$DIMADEC", data.dimadec, 70);
1609 }
1610 }
1611
1612 LC_DimStyle* dimStyle = createDimStyle(data);
1613 m_graphic->addDimStyle(dimStyle);
1614}
1615
1616// todo - sand - ucs - rework to direct setup of LC_GraphicViewport instead of RS_GraphicView
1617// But this modification requires cleanup of the overall file open flow, so leave it as it is for now
1618/**
1619 * Implementation of the method which handles vports.
1620 */
1621void RS_FilterDXFRW::addVport(const DRW_Vport& data) {
1622 if (m_graphic != nullptr)
1623 m_graphic->dwgAdvancedMetadata().addVport(data);
1624 QString name = QString::fromStdString(data.name);
1625 if (name.toLower() == "*active") {
1626 data.grid == 1 ? m_graphic->setGridOn(true) : m_graphic->setGridOn(false);
1627 m_graphic->setIsometricGrid(data.snapStyle);
1628 m_graphic->setIsoView(static_cast<RS2::IsoGridViewType>(data.snapIsopair));
1629 const RS_GraphicView* gv = m_graphic->getGraphicView(); // fixme - sand - review this dependency
1630 if (gv != nullptr) {
1631 const double width = data.height * data.ratio;
1632 // todo - sand - ucs - investigate support/usage of different x and y factors.
1633 double factorX = gv->getWidth() / width;
1634 const double factorY = gv->getHeight() / data.height;
1635 if (factorX > factorY) {
1636 factorX = factorY;
1637 }
1638 const int ox = gv->getWidth() - data.center.x * 2 * factorX;
1639 const int oy = gv->getHeight() - data.center.y * 2 * factorX;
1640 gv->getViewPort()->justSetOffsetAndFactor(ox, oy, factorX);
1641 }
1642 }
1643}
1644
1645void RS_FilterDXFRW::addUCS(const DRW_UCS& data) {
1646 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS");
1647 RS_DEBUGRS_Debug::instance()->print(" adding ucs: %s", data.name.c_str());
1648 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: creating ucs");
1649
1650 const QString name = QString::fromUtf8(data.name.c_str());
1651 if (m_graphic != nullptr)
1
Assuming pointer value is null
1652 m_graphic->dwgAdvancedMetadata().addUcs(data);
1653 if (!name.isEmpty() && m_graphic->findNamedUCS(name) != nullptr) {
2
Assuming the condition is false
1654 const int existingIndex = m_graphic->getUCSList()->getIndex(name);
1655 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(
1656 data.handle, data.name, existingIndex);
1657 return;
1658 }
1659
1660 auto* ucs = new LC_UCS(name);
1661 const auto origin = RS_Vector(data.origin.x, data.origin.y, data.origin.z);
1662 ucs->setOrigin(origin);
1663
1664 ucs->setElevation(data.elevation);
1665 ucs->setOrthoType(data.orthoType);
1666
1667 const auto orthoOrigin = RS_Vector(data.orthoOrigin.x, data.orthoOrigin.y, data.orthoOrigin.z);
1668 ucs->setOrthoOrigin(orthoOrigin);
1669
1670 const auto xAxis = RS_Vector(data.xAxisDirection.x, data.xAxisDirection.y, data.xAxisDirection.z);
1671 ucs->setXAxis(xAxis);
1672
1673 const auto yAxis = RS_Vector(data.yAxisDirection.x, data.yAxisDirection.y, data.yAxisDirection.z);
1674 ucs->setYAxis(yAxis);
1675
1676 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: add ucs to graphic");
1677 m_graphic->addUCS(ucs);
3
Called C++ object pointer is null
1678 const int documentItemIndex = m_graphic->getUCSList()->getIndex(name);
1679 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(
1680 data.handle, data.name, documentItemIndex);
1681 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: OK");
1682}
1683
1684void RS_FilterDXFRW::addView(const DRW_View& data) {
1685 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView");
1686 RS_DEBUGRS_Debug::instance()->print(" adding view: %s", data.name.c_str());
1687 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: creating view");
1688
1689 if (m_graphic != nullptr) {
1690 m_graphic->dwgAdvancedMetadata().addView(data);
1691 }
1692 QString name = QString::fromUtf8(data.name.c_str());
1693 if (!name.isEmpty() && m_graphic->findNamedView(name) != nullptr) {
1694 const int existingIndex = m_graphic->getViewList()->getIndex(name);
1695 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(
1696 data.handle, data.name, existingIndex);
1697 return;
1698 }
1699 auto* view = new LC_View(name);
1700 const auto center = RS_Vector(data.center.x, data.center.y, data.center.z);
1701 view->setCenter(center);
1702
1703 const auto size = RS_Vector(data.size.x, data.size.y, data.size.z);
1704 view->setSize(size);
1705
1706 const auto targetPoint = RS_Vector(data.targetPoint.x, data.targetPoint.y, data.targetPoint.z);
1707 view->setTargetPoint(targetPoint);
1708
1709 const auto viewDirection = RS_Vector(data.viewDirectionFromTarget.x, data.viewDirectionFromTarget.y, data.viewDirectionFromTarget.z);
1710 view->setViewDirection(viewDirection);
1711
1712 view->setLensLen(data.lensLen);
1713 view->setCameraPlottable(data.cameraPlottable);
1714
1715 view->setRenderMode(data.renderMode);
1716 view->setBackClippingPlaneOffset(data.backClippingPlaneOffset);
1717 view->setFrontClippingPlaneOffset(data.frontClippingPlaneOffset);
1718 view->setTwistAngle(data.twistAngle);
1719 view->setFlags(data.flags); // todo - review, use differ properties?
1720 view->setViewMode(data.viewMode); // todo - probably it might be simpler than long?
1721
1722 if (data.hasUCS) {
1723 const auto ucs = new LC_UCS();
1724
1725 const auto ucsOrigin = RS_Vector(data.ucsOrigin.x, data.ucsOrigin.y, data.ucsOrigin.z);
1726 const auto ucsXAxis = RS_Vector(data.ucsXAxis.x, data.ucsXAxis.y, data.ucsXAxis.z);
1727 const auto ucsYAxis = RS_Vector(data.ucsYAxis.x, data.ucsYAxis.y, data.ucsYAxis.z);
1728 ucs->setOrthoOrigin(RS_Vector(false));
1729 ucs->setOrigin(ucsOrigin);
1730 ucs->setXAxis(ucsXAxis);
1731 ucs->setYAxis(ucsYAxis);
1732 ucs->setElevation(data.ucsElevation);
1733 ucs->setOrthoType(data.ucsOrthoType);
1734 view->setUCS(ucs);
1735 }
1736
1737 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: set pen");
1738
1739 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: add view to graphic");
1740 m_graphic->addNamedView(view);
1741 const int documentItemIndex = m_graphic->getViewList()->getIndex(name);
1742 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(
1743 data.handle, data.name, documentItemIndex);
1744 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: OK");
1745}
1746
1747void RS_FilterDXFRW::addVisualStyle(const DRW_VisualStyle& data) {
1748 if (m_graphic != nullptr)
1749 m_graphic->dwgAdvancedMetadata().addVisualStyle(data);
1750}
1751
1752/**
1753 * Implementation of the method which handles blocks.
1754 *
1755 * @todo Adding blocks to blocks (stack for m_currentContainer)
1756 */
1757void RS_FilterDXFRW::addBlock(const DRW_Block& data) {
1758 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addBlock");
1759 RS_DEBUGRS_Debug::instance()->print(" adding block: %s", data.name.c_str());
1760 /*TODO correct handle of model-space*/
1761
1762 const QString name = QString::fromUtf8(data.name.c_str());
1763 const QString mid = name.mid(1, 11);
1764 // Prevent special blocks (paper_space, model_space) from being added:
1765 if (mid.toLower() != "paper_space" && mid.toLower() != "model_space") {
1766 RS_Vector bp(data.basePoint.x, data.basePoint.y);
1767 auto block = new RS_Block(m_graphic, RS_BlockData(name, bp, false ));
1768 block->setInsertionUnits(data.insUnits);
1769 //block->setFlags(flags);
1770
1771 if (m_graphic->addBlock(block)) {
1772 m_currentContainer = block;
1773 m_blockHash.insert(data.parentHandle, m_currentContainer);
1774
1775 // Bound/unbound XREF: load the external file and embed its
1776 // modelspace contents into this block. Layers are namespaced
1777 // `<blockName>|<layerName>` per AutoCAD BIND convention.
1778 // Bit 0x04 = XREF, bit 0x08 = XREF overlay.
1779 const bool isXref = (data.flags & 0x0c) != 0;
1780 if (isXref) {
1781 m_xrefBlockNames.insert(name);
1782 if (!data.xrefPath.empty()) {
1783 embedXref(block, QString::fromUtf8(data.xrefPath.c_str()),
1784 name);
1785 }
1786 }
1787 } else {
1788 // RS_BlockList::add returns false on name collision and
1789 // deletes the block. Without updating m_currentContainer,
1790 // subsequent addEntity calls would leak into whatever the
1791 // previous endBlock left behind (typically m_graphic). Route
1792 // them to m_dummyContainer so the failure is contained.
1793 m_blockHash.insert(data.parentHandle, m_dummyContainer);
1794 m_currentContainer = m_dummyContainer;
1795 }
1796 } else {
1797 if (mid.toLower() == "model_space") {
1798 m_blockHash.insert(data.parentHandle, m_graphic);
1799 }
1800 else {
1801 m_blockHash.insert(data.parentHandle, m_dummyContainer);
1802 }
1803 }
1804}
1805
1806QString RS_FilterDXFRW::resolveXrefPath(const QString &xrefPath) const {
1807 if (xrefPath.isEmpty())
1808 return {};
1809
1810 // Normalize separators: XREFs authored on Windows store backslashes
1811 // that QFileInfo doesn't recognize on Unix.
1812 QString normalized = xrefPath;
1813 normalized.replace('\\', '/');
1814
1815 // 1. Try the stored path verbatim (could be absolute on this host).
1816 if (QFileInfo::exists(normalized))
1817 return QFileInfo(normalized).absoluteFilePath();
1818
1819 QFileInfo host(m_file);
1820 const QString hostDir = host.absolutePath();
1821
1822 // 2. Treat stored path as relative to host file's directory.
1823 {
1824 const QString f = hostDir + "/" + normalized;
1825 if (QFileInfo::exists(f))
1826 return QFileInfo(f).absoluteFilePath();
1827 }
1828
1829 // 3. host-dir + basename (most common: XREF was authored on a
1830 // different machine but file shipped alongside the host).
1831 const QString basename = QFileInfo(normalized).fileName();
1832 {
1833 const QString f = hostDir + "/" + basename;
1834 if (QFileInfo::exists(f))
1835 return QFileInfo(f).absoluteFilePath();
1836 }
1837
1838 // 4-5. Tolerant match in host-dir: case-insensitive + space-to-underscore
1839 // normalization on the stem; accept .dwg or .dxf extension.
1840 const QString basenameNorm = basename.toLower().replace(' ', '_');
1841 QFileInfo basenameInfo(basenameNorm);
1842 const QString stemNorm = basenameInfo.completeBaseName();
1843 QDir dir(hostDir);
1844 const QStringList entries =
1845 dir.entryList({QStringLiteral("*.dwg")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dwg"))), QStringLiteral("*.DWG")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DWG"))),
1846 QStringLiteral("*.dxf")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dxf"))), QStringLiteral("*.DXF")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DXF")))},
1847 QDir::Files);
1848 for (const QString &entry : entries) {
1849 const QString entryNorm = entry.toLower().replace(' ', '_');
1850 if (entryNorm == basenameNorm) {
1851 return dir.absoluteFilePath(entry);
1852 }
1853 QFileInfo entryInfo(entry);
1854 const QString entryStemNorm =
1855 entryInfo.completeBaseName().toLower().replace(' ', '_');
1856 if (entryStemNorm == stemNorm) {
1857 return dir.absoluteFilePath(entry);
1858 }
1859 }
1860
1861 return {};
1862}
1863
1864bool RS_FilterDXFRW::embedXref(RS_Block *block, const QString &xrefPath,
1865 const QString &blockName) {
1866 if (!block)
1867 return false;
1868 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::embedXref: %s -> %s", qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData(),
1869 qPrintable(xrefPath)QtPrivate::asString(xrefPath).toLocal8Bit().constData());
1870
1871 const QString resolved = resolveXrefPath(xrefPath);
1872 if (resolved.isEmpty()) {
1873 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1874 QObject::tr("XREF not resolved for block \"%1\": %2 (file not found in "
1875 "host directory). The block will render as empty.")
1876 .arg(blockName, xrefPath));
1877 return false;
1878 }
1879
1880 if (m_xrefStack.count(resolved) > 0) {
1881 RS_DEBUGRS_Debug::instance()->print(" XREF cycle detected, skipping: %s",
1882 qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
1883 return false;
1884 }
1885 if (m_xrefStack.size() >= 8) {
1886 RS_DEBUGRS_Debug::instance()->print(" XREF depth limit reached at %s", qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
1887 return false;
1888 }
1889 m_xrefStack.insert(resolved);
1890
1891 RS_Graphic ext;
1892 RS_FilterDXFRW xrefFilter;
1893 xrefFilter.m_xrefStack = m_xrefStack; // propagate cycle guard
1894 const bool isDwg =
1895 resolved.endsWith(QStringLiteral(".dwg")(QString(QtPrivate::qMakeStringPrivate(u"" ".dwg"))), Qt::CaseInsensitive);
1896 const RS2::FormatType fmt = isDwg ? RS2::FormatDWG : RS2::FormatDXFRW;
1897 const bool ok = xrefFilter.fileImport(ext, resolved, fmt);
1898
1899 m_xrefStack.erase(resolved);
1900
1901 if (!ok) {
1902 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
1903 QObject::tr("XREF load failed for block \"%1\": %2")
1904 .arg(blockName, resolved));
1905 return false;
1906 }
1907
1908 // Layer namespacing: copy the XREF's layers into the host with prefix.
1909 RS_LayerList *extLayers = ext.getLayerList();
1910 RS_LayerList *hostLayers = m_graphic ? m_graphic->getLayerList() : nullptr;
1911 if (extLayers && hostLayers) {
1912 for (unsigned i = 0; i < extLayers->count(); ++i) {
1913 RS_Layer *extLyr = extLayers->at(i);
1914 if (!extLyr)
1915 continue;
1916 const QString nsName = blockName + "|" + extLyr->getName();
1917 if (!hostLayers->find(nsName)) {
1918 RS_Layer *clone = extLyr->clone();
1919 clone->setName(nsName);
1920 hostLayers->add(clone);
1921 }
1922 }
1923 }
1924
1925 // Per-entity clone helper: clone @p src, redirect layer pointer to the
1926 // host's namespaced layer, and (if it's an INSERT) rewrite its block
1927 // name reference to the namespaced form. Returns the cloned entity
1928 // owned by no parent yet; caller adds it to the target container.
1929 auto cloneAndRedirect = [&](const RS_Entity *src,
1930 RS_EntityContainer *target) -> RS_Entity * {
1931 if (!src)
1932 return nullptr;
1933 RS_Entity *cloned = src->clone();
1934 if (!cloned)
1935 return nullptr;
1936 cloned->setParent(target);
1937 if (hostLayers && cloned->getLayer()) {
1938 const QString lyrNs = blockName + "|" + cloned->getLayer()->getName();
1939 if (auto *hostLyr = hostLayers->find(lyrNs)) {
1940 cloned->setLayer(hostLyr);
1941 }
1942 }
1943 if (cloned->rtti() == RS2::EntityInsert) {
1944 auto *ins = static_cast<RS_Insert *>(cloned);
1945 const QString ref = ins->getName();
1946 // Don't rewrite if the reference is already namespaced (defensive
1947 // — shouldn't happen for a freshly-loaded XREF source, but harmless).
1948 if (!ref.startsWith(blockName + "|")) {
1949 ins->setName(blockName + "|" + ref);
1950 }
1951 }
1952 return cloned;
1953 };
1954
1955 // Block-def propagation: clone every block definition from the XREF
1956 // source into the host's blockList with namespaced name. Without this,
1957 // any cloned RS_Insert (whether at modelspace top-level or inside a
1958 // cloned block def) would reference a block name that doesn't exist
1959 // in the host, leaving a dangling reference.
1960 RS_BlockList *extBlocks = ext.getBlockList();
1961 RS_BlockList *hostBlocks = m_graphic ? m_graphic->getBlockList() : nullptr;
1962 int blockDefsCopied = 0;
1963 if (extBlocks && hostBlocks) {
1964 for (int i = 0; i < extBlocks->count(); ++i) {
1965 RS_Block *extBk = extBlocks->at(i);
1966 if (!extBk)
1967 continue;
1968 const QString origName = extBk->getName();
1969 // Skip special blocks — *MODEL_SPACE/*PAPER_SPACE aren't in
1970 // blockList anyway, but be defensive.
1971 if (origName.startsWith(QLatin1String("*Model_Space"),
1972 Qt::CaseInsensitive) ||
1973 origName.startsWith(QLatin1String("*Paper_Space"),
1974 Qt::CaseInsensitive)) {
1975 continue;
1976 }
1977 const QString nsName = blockName + "|" + origName;
1978 if (hostBlocks->find(nsName))
1979 continue; // already present
1980
1981 auto nsBlock = std::make_unique<RS_Block>(
1982 m_graphic, RS_BlockData(nsName, extBk->getBasePoint(), false));
1983 for (auto *e : *extBk) {
1984 if (auto *cloned = cloneAndRedirect(e, nsBlock.get())) {
1985 nsBlock->addEntity(cloned);
1986 }
1987 }
1988 // RS_BlockList::add takes ownership on success and deletes on failure,
1989 // so either way the raw pointer is consumed once released here.
1990 if (!hostBlocks->add(nsBlock.release())) {
1991 // Name collision against an existing host block — already deleted.
1992 } else {
1993 ++blockDefsCopied;
1994 }
1995 }
1996 }
1997
1998 // Move modelspace entities (top-level) into the local block, with
1999 // their layer pointers + INSERT block-name references redirected.
2000 int moved = 0;
2001 QList<RS_Entity *> snapshot;
2002 for (auto *e : ext)
2003 snapshot.push_back(e);
2004 for (RS_Entity *e : snapshot) {
2005 if (auto *cloned = cloneAndRedirect(e, block)) {
2006 block->addEntity(cloned);
2007 ++moved;
2008 }
2009 }
2010
2011 RS_DEBUGRS_Debug::instance()->print(" XREF embedded: %d entities + %d block defs into "
2012 "block \"%s\"",
2013 moved, blockDefsCopied, qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData());
2014 return true;
2015}
2016
2017void RS_FilterDXFRW::setBlock(const int handle) {
2018 if (m_blockHash.contains(handle)) {
2019 m_currentContainer = m_blockHash.value(handle);
2020 }
2021 else {
2022 m_currentContainer = m_graphic;
2023 }
2024 // Never leave a null container: subsequent addEntity would SIGSEGV.
2025 // Fall back to the import sink used for paper-space / name collisions.
2026 if (m_currentContainer == nullptr) {
2027 m_currentContainer = m_dummyContainer != nullptr ? m_dummyContainer : m_graphic;
2028 }
2029}
2030
2031/**
2032 * Implementation of the method which closes blocks.
2033 */
2034void RS_FilterDXFRW::endBlock() {
2035 if (m_currentContainer != nullptr && m_currentContainer->rtti() == RS2::EntityBlock) {
2036 auto bk = static_cast<RS_Block*>(m_currentContainer);
2037 //remove unnamed blocks *D only if version != R12
2038 if (m_version != 1009 && m_graphic != nullptr) {
2039 if (bk->getName().startsWith("*D")) {
2040 m_graphic->removeBlock(bk);
2041 }
2042 }
2043 }
2044 m_currentContainer = m_graphic;
2045}
2046
2047namespace {
2048// F2 type-fidelity sidecar markers. POINT and LINE coordinates are WCS, but
2049// LibreCAD's 2D entities cannot retain their Z values, thickness, or arbitrary
2050// extrusion direction. Keep those fields separately for native re-emission.
2051constexpr const char *kPointExtrusionMarker = "LibreCAD_POINT_EXTRUSION";
2052constexpr const char *kLineExtrusionMarker = "LibreCAD_LINE_EXTRUSION";
2053// HATCH boundary coordinates remain raw OCS in libdxfrw. Preserve the
2054// accompanying elevation/normal so the writer does not relabel them as WCS.
2055constexpr const char *kHatchExtrusionMarker = "LibreCAD_HATCH_EXTRUSION";
2056// IMAGE has no extrusion normal. Its insertion and pixel U/V vectors are WCS,
2057// while RS_Image exposes a 2D-editable frame, so retain their source Z values
2058// separately for native output.
2059constexpr const char *kImageFrameMarker = "LibreCAD_IMAGE_FRAME";
2060constexpr const char *kTextOcsMarker = "LibreCAD_TEXT_OCS";
2061constexpr const char *kMTextOcsMarker = "LibreCAD_MTEXT_OCS";
2062constexpr const char *kRayMarker = "LibreCAD_RAY";
2063constexpr const char *kXlineMarker = "LibreCAD_XLINE";
2064constexpr const char *kTraceMarker = "LibreCAD_TRACE";
2065constexpr const char *kSolidMarker = "LibreCAD_SOLID";
2066constexpr const char *k3dFaceMarker = "LibreCAD_3DFACE";
2067
2068bool needsWcsThicknessSidecar(const DRW_Point& entity) {
2069 return entity.thickness != 0.0 || entity.extPoint.x != 0.0
2070 || entity.extPoint.y != 0.0 || entity.extPoint.z != 1.0
2071 || entity.xAxisAngle != 0.0;
2072}
2073
2074void appendTypeSidecar(RS_Entity *entity, const char *marker,
2075 std::vector<std::shared_ptr<DRW_Variant>> payload);
2076} // namespace
2077
2078/**
2079 * Implementation of the method which handles point entities.
2080 */
2081void RS_FilterDXFRW::addPoint(const DRW_Point& data) {
2082 const RS_Vector v(data.basePoint.x, data.basePoint.y);
2083 const auto entity = new RS_Point(m_currentContainer, RS_PointData(v));
2084 setEntityAttributes(entity, &data);
2085 if (needsWcsThicknessSidecar(data)) {
2086 std::vector<std::shared_ptr<DRW_Variant>> payload;
2087 payload.push_back(std::make_shared<DRW_Variant>(
2088 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
2089 payload.push_back(std::make_shared<DRW_Variant>(
2090 1011, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
2091 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
2092 payload.push_back(std::make_shared<DRW_Variant>(1041, data.xAxisAngle));
2093 appendTypeSidecar(entity, kPointExtrusionMarker, std::move(payload));
2094 }
2095 m_currentContainer->addEntity(entity);
2096}
2097
2098/**
2099 * Implementation of the method which handles line entities.
2100 */
2101void RS_FilterDXFRW::addLine(const DRW_Line& data) {
2102 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine");
2103
2104 RS_Vector v1(data.basePoint.x, data.basePoint.y);
2105 RS_Vector v2(data.secPoint.x, data.secPoint.y);
2106
2107 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: create line");
2108
2109 if (!m_currentContainer) {
2110 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: currentContainer is nullptr");
2111 }
2112
2113 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
2114 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: set attributes");
2115 setEntityAttributes(entity, &data);
2116 if (needsWcsThicknessSidecar(data)) {
2117 std::vector<std::shared_ptr<DRW_Variant>> payload;
2118 payload.push_back(std::make_shared<DRW_Variant>(
2119 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
2120 payload.push_back(std::make_shared<DRW_Variant>(
2121 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
2122 payload.push_back(std::make_shared<DRW_Variant>(
2123 1012, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
2124 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
2125 appendTypeSidecar(entity, kLineExtrusionMarker, std::move(payload));
2126 }
2127
2128 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: add entity");
2129
2130 if (m_currentContainer) {
2131 m_currentContainer->addEntity(entity);
2132 }
2133
2134 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: OK");
2135}
2136
2137namespace {
2138// Append a type-fidelity sidecar (marker + payload variants) to whatever
2139// XDATA the entity already carries from setEntityAttributes().
2140void appendTypeSidecar(RS_Entity *entity, const char *marker,
2141 std::vector<std::shared_ptr<DRW_Variant>> payload) {
2142 std::vector<std::shared_ptr<DRW_Variant>> ext;
2143 if (entity->hasDrwExtData())
2144 ext = entity->getDrwExtData();
2145 ext.push_back(std::make_shared<DRW_Variant>(1001, std::string(marker)));
2146 for (auto &p : payload)
2147 ext.push_back(std::move(p));
2148 entity->setDrwExtData(std::move(ext));
2149}
2150} // namespace
2151
2152/**
2153 * Implementation of the method which handles ray entities.
2154 */
2155void RS_FilterDXFRW::addRay(const DRW_Ray& data) {
2156 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay");
2157
2158 RS_Vector v1{data.basePoint.x, data.basePoint.y};
2159 RS_Vector v2{data.basePoint.x + data.secPoint.x, data.basePoint.y + data.secPoint.y};
2160
2161 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: create line");
2162
2163 if (!m_currentContainer) {
2164 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: currentContainer is nullptr");
2165 }
2166
2167 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
2168 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: set attributes");
2169 setEntityAttributes(entity, &data);
2170
2171 // F2 sidecar: base point + direction (preserve Z for full fidelity).
2172 std::vector<std::shared_ptr<DRW_Variant>> payload;
2173 payload.push_back(std::make_shared<DRW_Variant>(
2174 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
2175 payload.push_back(std::make_shared<DRW_Variant>(
2176 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
2177 appendTypeSidecar(entity, kRayMarker, std::move(payload));
2178
2179 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: add entity");
2180
2181 if (m_currentContainer) {
2182 m_currentContainer->addEntity(entity);
2183 }
2184
2185 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: OK");
2186}
2187
2188/**
2189 * Implementation of the method which handles line entities.
2190 */
2191void RS_FilterDXFRW::addXline(const DRW_Xline& data) {
2192 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline");
2193
2194 RS_Vector v1(data.basePoint.x, data.basePoint.y);
2195 RS_Vector v2(data.basePoint.x + data.secPoint.x, data.basePoint.y + data.secPoint.y);
2196
2197 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: create line");
2198
2199 if (!m_currentContainer) {
2200 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: currentContainer is nullptr");
2201 }
2202
2203 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
2204 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: set attributes");
2205 setEntityAttributes(entity, &data);
2206
2207 // F2 sidecar: base point + direction (preserve Z for full fidelity).
2208 std::vector<std::shared_ptr<DRW_Variant>> payload;
2209 payload.push_back(std::make_shared<DRW_Variant>(
2210 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
2211 payload.push_back(std::make_shared<DRW_Variant>(
2212 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
2213 appendTypeSidecar(entity, kXlineMarker, std::move(payload));
2214
2215 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: add entity");
2216
2217 if (m_currentContainer) {
2218 m_currentContainer->addEntity(entity);
2219 }
2220
2221 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: OK");
2222}
2223
2224/**
2225 * Implementation of the method which handles circle entities.
2226 */
2227namespace {
2228template <typename T>
2229bool canCreatePlanarEntity(const T& entity, const char* typeName) {
2230 if (hasNormalizedAxialExtrusion(entity))
2231 return true;
2232 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
2233 "RS_FilterDXFRW::%s: skipped non-axial extrusion", typeName);
2234 return false;
2235}
2236} // namespace
2237
2238void RS_FilterDXFRW::addCircle(const DRW_Circle& data) {
2239 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addCircle");
2240 if (!canCreatePlanarEntity(data, "addCircle"))
2241 return;
2242
2243 RS_Vector v{data.basePoint.x, data.basePoint.y};
2244 const auto entity = new RS_Circle(m_currentContainer, {v, data.radious});
2245 setEntityAttributes(entity, &data);
2246 m_currentContainer->addEntity(entity);
2247}
2248
2249/**
2250 * Implementation of the method which handles arc entities.
2251 *
2252 * @param data
2253 */
2254void RS_FilterDXFRW::addArc(const DRW_Arc& data) {
2255 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addArc");
2256 if (!canCreatePlanarEntity(data, "addArc"))
2257 return;
2258 const RS_Vector v(data.basePoint.x, data.basePoint.y);
2259 const RS_ArcData d(v, data.radious, data.staangle, data.endangle, false);
2260 const auto entity = new RS_Arc(m_currentContainer, d);
2261 setEntityAttributes(entity, &data);
2262 m_currentContainer->addEntity(entity);
2263}
2264
2265/**
2266 * Implementation of the method which handles ellipse entities.
2267 *
2268 * @param data
2269 */
2270void RS_FilterDXFRW::addEllipse(const DRW_Ellipse& data) {
2271 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addEllipse");
2272 if (!canCreatePlanarEntity(data, "addEllipse"))
2273 return;
2274
2275 const RS_Vector v1(data.basePoint.x, data.basePoint.y);
2276 const RS_Vector v2(data.secPoint.x, data.secPoint.y);
2277 double ang2 = data.endparam;
2278 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < RS_TOLERANCE1.0e-10 && fabs(data.staparam) < RS_TOLERANCE1.0e-10) {
2279 ang2 = 0.;
2280 }
2281 const auto entity = new RS_Ellipse{m_currentContainer, {v1, v2, data.ratio, data.staparam, ang2, false}};
2282 setEntityAttributes(entity, &data);
2283 m_currentContainer->addEntity(entity);
2284}
2285
2286/**
2287 * Implementation of the method which handles trace entities.
2288 */
2289void RS_FilterDXFRW::addTrace(const DRW_Trace& data) {
2290 // Pre-R13 (AC1009) typed LINEAR/ALIGNED dimensions rebuild arrows in
2291 // RS_DimLinear/RS_DimAligned. ENTITIES-section SOLID/TRACE records are
2292 // duplicate *D-block graphics (ACEB10 and peers); importing them inflates
2293 // the model-space bbox with misplaced arrow triangles.
2294 if (m_version == 1009 && m_currentContainer == m_graphic) {
2295 return;
2296 }
2297 if (!hasNormalizedAxialExtrusion(data)) {
2298 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
2299 "RS_FilterDXFRW::addTrace: skipped non-axial extrusion");
2300 return;
2301 }
2302
2303 RS_Solid* entity;
2304 const RS_Vector v1{data.basePoint.x, data.basePoint.y};
2305 const RS_Vector v2{data.secPoint.x, data.secPoint.y};
2306 const RS_Vector v3{data.thirdPoint.x, data.thirdPoint.y};
2307 const RS_Vector v4{data.fourPoint.x, data.fourPoint.y};
2308 if (v3 == v4) {
2309 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3));
2310 }
2311 else {
2312 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3, v4));
2313 }
2314
2315 setEntityAttributes(entity, &data);
2316
2317 // F2 sidecar: preserve the exact native TRACE/SOLID type and all four WCS
2318 // corners. RS_Solid is 2D-lossy, so the write pre-pass must rebuild the
2319 // source type with canonicalized axial thickness.
2320 const char *marker = data.eType == DRW::DXF_TRACE ? kTraceMarker
2321 : data.eType == DRW::SOLID ? kSolidMarker : nullptr;
2322 if (marker != nullptr) {
2323 const bool reflectedOcs = data.extPoint.z < 0.0;
2324 std::vector<std::shared_ptr<DRW_Variant>> payload;
2325 payload.push_back(std::make_shared<DRW_Variant>(
2326 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
2327 payload.push_back(std::make_shared<DRW_Variant>(
2328 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
2329 payload.push_back(std::make_shared<DRW_Variant>(
2330 1012, DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
2331 payload.push_back(std::make_shared<DRW_Variant>(
2332 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
2333 payload.push_back(std::make_shared<DRW_Variant>(
2334 1040, reflectedOcs ? -data.thickness : data.thickness));
2335 appendTypeSidecar(entity, marker, std::move(payload));
2336 }
2337
2338 m_currentContainer->addEntity(entity);
2339}
2340
2341void RS_FilterDXFRW::addTolerance(const DRW_Tolerance& data) {
2342 const RS_Vector insertionPoint{data.insertionPoint.x, data.insertionPoint.y};
2343 const RS_Vector axisDirectionVector{data.xAxisDirectionVector.x, data.xAxisDirectionVector.y};
2344
2345 const QString text = toNativeString(QString::fromUtf8(data.text.c_str()));
2346
2347 QString sty = QString::fromUtf8(data.dimStyleName.c_str());
2348 if (sty.isEmpty()) {
2349 sty = m_dimStyle;
2350 }
2351
2352 const auto tolData = LC_ToleranceData(insertionPoint, axisDirectionVector, text, sty);
2353
2354 const auto entity = new LC_Tolerance{m_currentContainer, tolData};
2355 setEntityAttributes(entity, &data);
2356 entity->update();
2357 m_currentContainer->addEntity(entity);
2358}
2359
2360/**
2361 * Implementation of the method which handles solid entities.
2362 */
2363void RS_FilterDXFRW::addSolid(const DRW_Solid& data) {
2364 addTrace(data);
2365}
2366
2367// Convert a decoded ACIS wireframe (drw_acis.h) into 2D RS_* entities, mirroring
2368// addMesh: LibreCAD is 2D, so every point is projected by dropping Z. The result
2369// is added to `container`; the created entities are returned so a caller can
2370// apply setEntityAttributes afterwards. Static + free of m_graphic so it can be
2371// unit-tested directly against a bare RS_EntityContainer.
2372std::vector<RS_Entity*> RS_FilterDXFRW::acisWireframeToEntities(
2373 const DRW_AcisBrep &brep, RS_EntityContainer *container) {
2374 std::vector<RS_Entity*> created;
2375 if (container == nullptr)
2376 return created;
2377
2378 auto xy = [](const DRW_Coord &c) { return RS_Vector(c.x, c.y); };
2379 auto pushEntity = [&](RS_Entity *e) {
2380 container->addEntity(e);
2381 created.push_back(e);
2382 };
2383 auto addLineSeg = [&](const DRW_AcisEdge &edge) {
2384 if (edge.hasStart && edge.hasEnd)
2385 pushEntity(new RS_Line(container, {xy(edge.start), xy(edge.end)}));
2386 };
2387
2388 // Parametric (eccentric) angle of a 2D point on an ellipse defined by its
2389 // center, major-axis vector and minor/major ratio.
2390 auto ellipseParam = [](const RS_Vector &center, const RS_Vector &majorP,
2391 double ratio, const RS_Vector &p) -> double {
2392 double majLen = majorP.magnitude();
2393 if (majLen < RS_TOLERANCE1.0e-10)
2394 return 0.0;
2395 RS_Vector d = p - center;
2396 RS_Vector majHat = majorP / majLen;
2397 RS_Vector minHat(-majHat.y, majHat.x); // major rotated +90 deg
2398 double cosT = d.dotP(majHat) / majLen;
2399 double sinT = d.dotP(minHat);
2400 if (ratio > RS_TOLERANCE1.0e-10)
2401 sinT /= (majLen * ratio);
2402 return RS_Math::correctAngle(std::atan2(sinT, cosT));
2403 };
2404
2405 // Track edge endpoints so isolated vertices can be rendered as points.
2406 std::vector<RS_Vector> endpoints;
2407 auto noteEndpoints = [&](const DRW_AcisEdge &edge) {
2408 if (edge.hasStart) endpoints.push_back(xy(edge.start));
2409 if (edge.hasEnd) endpoints.push_back(xy(edge.end));
2410 };
2411
2412 for (const DRW_AcisEdge &edge : brep.edges) {
2413 noteEndpoints(edge);
2414 switch (edge.curveType) {
2415 case DRW_AcisCurve::Straight:
2416 addLineSeg(edge);
2417 break;
2418 case DRW_AcisCurve::Ellipse: {
2419 RS_Vector center = xy(edge.p0);
2420 RS_Vector majorP = xy(edge.p2);
2421 double ratio = edge.ratio;
2422 if (majorP.magnitude() < RS_TOLERANCE1.0e-10 || ratio <= 0.0) {
2423 addLineSeg(edge); // insufficient/degenerate ellipse params
2424 break;
2425 }
2426 double a1 = 0.0;
2427 double a2 = 2.0 * M_PI3.14159265358979323846;
2428 if (edge.hasStart && edge.hasEnd) {
2429 a1 = ellipseParam(center, majorP, ratio, xy(edge.start));
2430 a2 = ellipseParam(center, majorP, ratio, xy(edge.end));
2431 }
2432 pushEntity(new RS_Ellipse(container, {center, majorP, ratio, a1, a2, false}));
2433 break;
2434 }
2435 case DRW_AcisCurve::Intcurve: {
2436 if (edge.controlPoints.size() >= 2) {
2437 size_t n = edge.controlPoints.size();
2438 int degree = (n >= 4) ? 3 : static_cast<int>(n - 1);
2439 RS_SplineData d(degree, /*closed=*/false);
2440 auto *spline = new RS_Spline(container, d);
2441 for (const DRW_Coord &cp : edge.controlPoints)
2442 spline->addControlPointRaw(RS_Vector(cp.x, cp.y), 1.0);
2443 pushEntity(spline);
2444 } else {
2445 addLineSeg(edge); // no control polygon -> straight fallback
2446 }
2447 break;
2448 }
2449 case DRW_AcisCurve::Unknown:
2450 default:
2451 addLineSeg(edge);
2452 break;
2453 }
2454 }
2455
2456 // Isolated vertices (no incident edge endpoint) -> RS_Point.
2457 for (const DRW_AcisVertex &v : brep.vertices) {
2458 if (!v.valid)
2459 continue;
2460 RS_Vector p = xy(v.point);
2461 bool incident = false;
2462 for (const RS_Vector &e : endpoints) {
2463 if (std::fabs(e.x - p.x) < RS_TOLERANCE1.0e-10
2464 && std::fabs(e.y - p.y) < RS_TOLERANCE1.0e-10) {
2465 incident = true;
2466 break;
2467 }
2468 }
2469 if (!incident)
2470 pushEntity(new RS_Point(container, RS_PointData(p)));
2471 }
2472
2473 return created;
2474}
2475
2476void RS_FilterDXFRW::addModelerGeometry(const DRW_ModelerGeometry &data) {
2477 // Preserve the raw modeler payload as advanced-metadata sidecar so a native
2478 // ACIS body is never silently lost on round-trip.
2479 if (m_graphic != nullptr) {
2480 m_graphic->dwgAdvancedMetadata().addModelerGeometry(data);
2481 }
2482 // Additionally render the decoded SAB wireframe as 2D geometry (mirrors
2483 // addMesh). Guard: only touch the container when the decode yields edges, so
2484 // undecodable bodies keep the previous metadata-only behavior (no regression).
2485 DRW_ModelerGeometry &mut = const_cast<DRW_ModelerGeometry &>(data);
2486 if (mut.decodeWireframe() && !mut.m_wireframe.edges.empty()) {
2487 std::vector<RS_Entity*> ents =
2488 acisWireframeToEntities(mut.m_wireframe, m_currentContainer);
2489 for (RS_Entity *e : ents)
2490 setEntityAttributes(e, &data);
2491 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addModelerGeometry: rendered %zu wireframe entities",
2492 ents.size());
2493 }
2494 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addModelerGeometry: type %d handle %d history %d",
2495 static_cast<int>(data.eType),
2496 static_cast<int>(data.handle),
2497 static_cast<int>(data.m_historyHandle));
2498}
2499
2500void RS_FilterDXFRW::addSurface(const DRW_Surface *data) {
2501 if (data == nullptr)
2502 return;
2503 // Decode the lazily-cached SAB wireframe (idempotent; never throws), then
2504 // render its edges as 2D geometry via the shared helper. Guard on edges to
2505 // avoid emitting anything for bodies we cannot decode (no regression over the
2506 // base no-op).
2507 DRW_Surface *surf = const_cast<DRW_Surface *>(data);
2508 if (surf->decodeWireframe() && !surf->m_wireframe.edges.empty()) {
2509 std::vector<RS_Entity*> ents =
2510 acisWireframeToEntities(surf->m_wireframe, m_currentContainer);
2511 for (RS_Entity *e : ents)
2512 setEntityAttributes(e, data);
2513 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSurface: rendered %zu wireframe entities",
2514 ents.size());
2515 }
2516}
2517
2518void RS_FilterDXFRW::addLight(const DRW_Light &data) {
2519 if (m_graphic != nullptr) {
2520 m_graphic->dwgAdvancedMetadata().addLight(data);
2521 }
2522 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLight: %s handle %d",
2523 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str(),
2524 static_cast<int>(data.handle));
2525}
2526
2527/**
2528 * Implementation of the method which handles lightweight polyline entities.
2529 */
2530void RS_FilterDXFRW::addLWPolyline(const DRW_LWPolyline& data) {
2531 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLWPolyline");
2532 if (data.vertlist.empty()) {
2533 return;
2534 }
2535
2536 // libdxfrw has already applied the OCS frame before this callback. A
2537 // LibreCAD polyline is strictly 2D, so only the two normalized axial
2538 // frames can be represented without retaining a second coordinate space.
2539 if (!hasNormalizedAxialExtrusion(data)) {
2540 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
2541 "RS_FilterDXFRW::addLWPolyline: skipped non-axial extrusion");
2542 return;
2543 }
2544 const bool reflectedOcs = data.extPoint.z < 0.0;
2545 RS_PolylineData d(RS_Vector{},
2546 RS_Vector{},
2547 data.flags&0x1);
2548 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, d);
2549 setEntityAttributes(polyline.get(), &data);
2550
2551 std::vector<std::pair<RS_Vector, double>> verList;
2552 for (const auto& v : data.vertlist) {
2553 verList.emplace_back(std::make_pair(
2554 RS_Vector{v->x, v->y}, reflectedOcs ? -v->bulge : v->bulge));
2555 }
2556
2557 polyline->appendVertexs(verList);
2558 const bool defaultExtrusion = !reflectedOcs;
2559 bool hasVertexMetadata = false;
2560 for (const auto& v : data.vertlist) {
2561 if (v && (v->stawidth != 0.0 || v->endwidth != 0.0
2562 || v->identifier != 0)) {
2563 hasVertexMetadata = true;
2564 break;
2565 }
2566 }
2567 if (data.width != 0.0 || data.elevation != 0.0 || data.thickness != 0.0
2568 || !defaultExtrusion || hasVertexMetadata) {
2569 std::vector<std::shared_ptr<DRW_Variant>> ext;
2570 ext.push_back(std::make_shared<DRW_Variant>(
2571 1001, std::string("LibreCAD_LWPOLYLINE")));
2572 ext.push_back(std::make_shared<DRW_Variant>(1040, data.width));
2573 ext.push_back(std::make_shared<DRW_Variant>(
2574 1040, reflectedOcs ? -data.elevation : data.elevation));
2575 ext.push_back(std::make_shared<DRW_Variant>(
2576 1040, reflectedOcs ? -data.thickness : data.thickness));
2577 ext.push_back(std::make_shared<DRW_Variant>(
2578 1010, DRW_Coord{0.0, 0.0, 1.0}));
2579 ext.push_back(std::make_shared<DRW_Variant>(
2580 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
2581 for (const auto& v : data.vertlist) {
2582 ext.push_back(std::make_shared<DRW_Variant>(
2583 1040, v ? v->stawidth : 0.0));
2584 ext.push_back(std::make_shared<DRW_Variant>(
2585 1040, v ? v->endwidth : 0.0));
2586 ext.push_back(std::make_shared<DRW_Variant>(
2587 1071, std::int32_t{v ? v->identifier : 0}));
2588 }
2589 polyline->setDrwExtData(std::move(ext));
2590 }
2591 m_currentContainer->addEntity(polyline.release());
2592}
2593
2594/**
2595 * Implementation of the method which handles MLINESTYLE table entries.
2596 * Cache by name so a later addMLine can resolve element offsets/colors.
2597 */
2598void RS_FilterDXFRW::addMLineStyle(const DRW_MLineStyle &data) {
2599 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLineStyle: %s (%zu elements)",
2600 data.name.c_str(), data.elements.size());
2601 QString key = QString::fromUtf8(data.name.c_str());
2602 if (!key.isEmpty()) {
2603 m_mlineStyleCache[key] = data;
2604 }
2605 //Durable metadata (in ADDITION to the transient cache addMLine uses to
2606 //decompose). The DWG reader populates ONLY this; the DXF->DXF path keeps
2607 //MLINESTYLE in the raw net, so writeObjects dedups by handle to avoid a
2608 //double-emit.
2609 if (m_graphic != nullptr) {
2610 m_graphic->dwgAdvancedMetadata().addMLineStyle(data);
2611 }
2612}
2613
2614void RS_FilterDXFRW::addWipeoutVariables(const DRW_WipeoutVariables &data) {
2615 //DWG read populates only this typed metadata; DXF read also keeps it in the
2616 //raw net (writeObjects dedups by handle). Enables DWG->DXF typed re-emit.
2617 if (m_graphic != nullptr) {
2618 m_graphic->dwgAdvancedMetadata().addWipeoutVariables(data);
2619 }
2620}
2621
2622/**
2623 * Implementation of the method which handles MLINE entities.
2624 *
2625 * LibreCAD has no native multiline. Decompose each MLINE into N parallel
2626 * RS_Polylines (one per element in the referenced MLINESTYLE), with
2627 * round-trip metadata stored on each polyline's drwExtData so the export
2628 * path can reconstruct an MLINE on save.
2629 */
2630void RS_FilterDXFRW::addMLine(const DRW_MLine *data) {
2631 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLine");
2632 if (!data || data->vertlist.empty() || data->numLines == 0)
2633 return;
2634
2635 const int N = data->numLines;
2636 QString styleName = QString::fromUtf8(data->styleName.c_str());
2637 auto styleIt = m_mlineStyleCache.find(styleName);
2638 const DRW_MLineStyle *style =
2639 (styleIt != m_mlineStyleCache.end()) ? &styleIt->second : nullptr;
2640
2641 // Compute effective per-element offsets after justification + scale.
2642 // Justification: 0=top, 1=zero/middle, 2=bottom. The reference offset
2643 // is removed from each element's offset so the chosen line aligns
2644 // with the baseline polyline path.
2645 std::vector<double> effOffsets(N, 0.0);
2646 if (style && static_cast<int>(style->elements.size()) == N) {
2647 double ref = 0.0;
2648 if (data->justification == 0) { // top
2649 for (auto &e : style->elements)
2650 ref = std::max(ref, e.offset);
2651 } else if (data->justification == 2) { // bottom
2652 for (auto &e : style->elements)
2653 ref = std::min(ref, e.offset);
2654 }
2655 for (int i = 0; i < N; ++i) {
2656 effOffsets[i] = (style->elements[i].offset - ref) * data->scale;
2657 }
2658 } else {
2659 // Fallback: evenly spaced [-(N-1)/2 .. +(N-1)/2] * scale.
2660 for (int i = 0; i < N; ++i) {
2661 effOffsets[i] = (i - 0.5 * (N - 1)) * data->scale;
2662 }
2663 }
2664
2665 const QString mlineId = QStringLiteral("mline_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "mline_%1"))).arg(data->handle);
2666 const bool closed = (data->openClosed & 0x1) != 0;
2667
2668 for (int i = 0; i < N; ++i) {
2669 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, closed);
2670 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
2671 setEntityAttributes(polyline.get(), data);
2672
2673 for (const auto &v : data->vertlist) {
2674 RS_Vector miter(v.miterDir.x, v.miterDir.y);
2675 const double mlen = miter.magnitude();
2676 RS_Vector pos;
2677 if (mlen < RS_TOLERANCE1.0e-10) {
2678 pos = RS_Vector{v.position.x, v.position.y};
2679 } else {
2680 miter /= mlen;
2681 pos = RS_Vector{v.position.x + miter.x * effOffsets[i],
2682 v.position.y + miter.y * effOffsets[i]};
2683 }
2684 polyline->addVertex(pos, 0.0, false);
2685 }
2686
2687 // Round-trip metadata as XDATA. Schema per the implementation plan:
2688 // 1001 "LibreCAD_MLINE", 1000 mlineId, 1000 styleName,
2689 // 1071 styleHandle, 1040 scale, 1070 justification, 1070 elementCount,
2690 // 1070 elementIndex, 1040 offset, 1070 flags.
2691 // Anchor (i==0) additionally stores per-vertex baseline + miter
2692 // so the export side can reconstruct without averaging.
2693 std::vector<std::shared_ptr<DRW_Variant>> ext;
2694 ext.push_back(
2695 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_MLINE")));
2696 ext.push_back(std::make_shared<DRW_Variant>(1000, mlineId.toStdString()));
2697 ext.push_back(std::make_shared<DRW_Variant>(1000, data->styleName));
2698 ext.push_back(std::make_shared<DRW_Variant>(
2699 1071, static_cast<std::int32_t>(data->styleHandle)));
2700 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale));
2701 ext.push_back(
2702 std::make_shared<DRW_Variant>(1070, std::int32_t{data->justification}));
2703 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{N}));
2704 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{i}));
2705 ext.push_back(std::make_shared<DRW_Variant>(1040, effOffsets[i]));
2706 ext.push_back(
2707 std::make_shared<DRW_Variant>(1070, std::int32_t{data->openClosed}));
2708 if (i == 0) {
2709 // Anchor carries baseline + miter for each vertex.
2710 for (const auto &v : data->vertlist) {
2711 ext.push_back(std::make_shared<DRW_Variant>(
2712 1011, DRW_Coord(v.position.x, v.position.y, v.position.z)));
2713 ext.push_back(std::make_shared<DRW_Variant>(
2714 1013, DRW_Coord(v.miterDir.x, v.miterDir.y, v.miterDir.z)));
2715 }
2716 }
2717 polyline->setDrwExtData(std::move(ext));
2718
2719 m_currentContainer->addEntity(polyline.release());
2720 }
2721}
2722
2723/**
2724 * Implementation of the UNDERLAYDEFINITION callback.
2725 * Caches by handle so addUnderlay (entities arrive earlier) and the
2726 * export reconstruction can resolve filename + sheet on demand.
2727 */
2728void RS_FilterDXFRW::linkUnderlay(const DRW_UnderlayDefinition *d) {
2729 if (!d)
2730 return;
2731 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkUnderlay: %s", d->filename.c_str());
2732 m_underlayDefMap[d->handle] = *d;
2733 if (m_graphic != nullptr)
2734 m_graphic->dwgAdvancedMetadata().addUnderlayDefinition(*d);
2735}
2736
2737void RS_FilterDXFRW::addShape(const DRW_Shape &data) {
2738 if (m_graphic != nullptr)
2739 m_graphic->dwgAdvancedMetadata().addShape(data);
2740 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addShape: index %d style %d",
2741 static_cast<int>(data.m_shapeIndex),
2742 static_cast<int>(data.m_shapeFileHandle));
2743}
2744
2745void RS_FilterDXFRW::addOle2Frame(const DRW_Ole2Frame &data) {
2746 if (m_graphic != nullptr)
2747 m_graphic->dwgAdvancedMetadata().addOle2Frame(data);
2748 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addOle2Frame: bytes %d declared %d",
2749 static_cast<int>(data.m_payloadByteCount),
2750 static_cast<int>(data.m_declaredPayloadLength));
2751}
2752
2753/**
2754 * Implementation of the UNDERLAY entity callback (PDFUNDERLAY/DGNUNDERLAY/
2755 * DWFUNDERLAY). Decomposes the underlay into a single closed RS_Polyline
2756 * showing the clip-boundary placeholder. The filename + kind ride along
2757 * in XDATA so the export side can reconstruct the original UNDERLAY entity.
2758 *
2759 * Filename resolution: OBJECTS are parsed AFTER ENTITIES in libdxfrw, so
2760 * m_underlayDefMap may be empty here. We store the definitionHandle in
2761 * XDATA only; on-demand lookups (UI tooltip, export reconstruction) use
2762 * the cache once it's populated by linkUnderlay.
2763 */
2764void RS_FilterDXFRW::addUnderlay(const DRW_Underlay *data) {
2765 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addUnderlay");
2766 if (!data)
2767 return;
2768
2769 // Build clip polygon in WCS. clipBoundary is in OCS-2D; for a typical
2770 // 2D extrusion (extPoint == (0,0,1)), OCS == WCS modulo position +
2771 // scale + rotation. LibreCAD is 2D so we project z-up regardless.
2772 std::vector<RS_Vector> verts;
2773 const bool fallbackPreviewGenerated = true;
2774 if (data->clipBoundary.size() >= 3) {
2775 verts.reserve(data->clipBoundary.size());
2776 for (const auto &v : data->clipBoundary) {
2777 verts.emplace_back(v.x, v.y);
2778 }
2779 } else if (data->clipBoundary.size() == 2) {
2780 // Two corners of an axis-aligned rectangle (in OCS).
2781 const auto &a = data->clipBoundary[0];
2782 const auto &b = data->clipBoundary[1];
2783 verts.emplace_back(a.x, a.y);
2784 verts.emplace_back(b.x, a.y);
2785 verts.emplace_back(b.x, b.y);
2786 verts.emplace_back(a.x, b.y);
2787 } else {
2788 // No clip — synthesize a unit-square placeholder centered on origin.
2789 // Real underlay sizes need PDF page dims we don't have access to.
2790 verts.emplace_back(-0.5, -0.5);
2791 verts.emplace_back(0.5, -0.5);
2792 verts.emplace_back(0.5, 0.5);
2793 verts.emplace_back(-0.5, 0.5);
2794 }
2795
2796 // Apply scale, rotation, translation (rotation is in radians per DWG).
2797 const double cs = std::cos(data->rotation);
2798 const double sn = std::sin(data->rotation);
2799 for (auto &p : verts) {
2800 const double sx = p.x * data->scale.x;
2801 const double sy = p.y * data->scale.y;
2802 const double rx = sx * cs - sy * sn;
2803 const double ry = sx * sn + sy * cs;
2804 p = RS_Vector{rx + data->position.x, ry + data->position.y};
2805 }
2806
2807 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, /*closed=*/true);
2808 auto polyline = new RS_Polyline(m_currentContainer, pd);
2809 setEntityAttributes(polyline, data);
2810 for (const auto &p : verts)
2811 polyline->addVertex(p, 0.0, false);
2812
2813 // Round-trip metadata as XDATA. App marker LibreCAD_UNDERLAY.
2814 const QString underlayId = QStringLiteral("underlay_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "underlay_%1"))).arg(data->handle);
2815 const char *kindStr = (data->kind == DRW_Underlay::DGN) ? "DGN"
2816 : (data->kind == DRW_Underlay::DWF) ? "DWF"
2817 : "PDF";
2818 std::vector<std::shared_ptr<DRW_Variant>> ext;
2819 ext.push_back(
2820 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_UNDERLAY")));
2821 ext.push_back(std::make_shared<DRW_Variant>(1000, underlayId.toStdString()));
2822 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string(kindStr)));
2823 ext.push_back(std::make_shared<DRW_Variant>(
2824 1071, std::int32_t{static_cast<int>(data->definitionHandle)}));
2825 ext.push_back(std::make_shared<DRW_Variant>(
2826 1010, DRW_Coord(data->position.x, data->position.y, data->position.z)));
2827 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.x));
2828 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.y));
2829 ext.push_back(std::make_shared<DRW_Variant>(1040, data->rotation));
2830 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->flags}));
2831 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->contrast}));
2832 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->fade}));
2833 polyline->setDrwExtData(std::move(ext));
2834
2835 if (m_graphic != nullptr)
2836 m_graphic->dwgAdvancedMetadata().addUnderlay(
2837 *data, fallbackPreviewGenerated);
2838 m_currentContainer->addEntity(polyline);
2839}
2840
2841/**
2842 * Implementation of the method which handles polyline entities.
2843 */
2844void RS_FilterDXFRW::addPolyline(const DRW_Polyline& data) {
2845 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPolyline");
2846 if (data.flags & 0x10) {
2847 // the polyline is a polygon mesh
2848 int M = data.vertexcount;
2849 int N = data.facecount;
2850 const bool canRenderFallback =
2851 M > 0 && N > 0
2852 && data.vertlist.size() == static_cast<size_t>(M * N)
2853 && data.curvetype == 0;
2854 if (m_graphic != nullptr)
2855 m_graphic->dwgAdvancedMetadata().addMeshPolyline(
2856 data, canRenderFallback);
2857 if (M <= 0 || N <= 0 || data.vertlist.size() != static_cast<size_t>(M * N)) {
2858 return; // invalid mesh
2859 }
2860 if (data.curvetype != 0) {
2861 return; // smooth surfaces not handled
2862 }
2863 bool closedM = (data.flags & 0x1); // closed in M direction
2864 bool closedN = (data.flags & 0x20); // closed in N direction
2865 const std::string meshId = std::string("polyline_mesh_")
2866 + std::to_string(data.handle);
2867 const int meshElementCount = M + N;
2868 auto makeMeshExtData = [&](int elementIndex, const std::string& role,
2869 int roleIndex, bool anchor) {
2870 std::vector<std::shared_ptr<DRW_Variant>> ext;
2871 ext.push_back(std::make_shared<DRW_Variant>(
2872 1001, std::string("LibreCAD_POLYLINE_MESH")));
2873 ext.push_back(std::make_shared<DRW_Variant>(1000, meshId));
2874 ext.push_back(std::make_shared<DRW_Variant>(1000, role));
2875 ext.push_back(std::make_shared<DRW_Variant>(
2876 1070, std::int32_t{elementIndex}));
2877 ext.push_back(std::make_shared<DRW_Variant>(
2878 1070, std::int32_t{meshElementCount}));
2879 ext.push_back(std::make_shared<DRW_Variant>(
2880 1070, std::int32_t{roleIndex}));
2881 ext.push_back(std::make_shared<DRW_Variant>(
2882 1070, std::int32_t{data.flags}));
2883 ext.push_back(std::make_shared<DRW_Variant>(
2884 1070, std::int32_t{M}));
2885 ext.push_back(std::make_shared<DRW_Variant>(
2886 1070, std::int32_t{N}));
2887 ext.push_back(std::make_shared<DRW_Variant>(
2888 1070, std::int32_t{data.smoothM}));
2889 ext.push_back(std::make_shared<DRW_Variant>(
2890 1070, std::int32_t{data.smoothN}));
2891 ext.push_back(std::make_shared<DRW_Variant>(
2892 1070, std::int32_t{data.curvetype}));
2893 if (anchor) {
2894 for (const auto& vertex : data.vertlist) {
2895 if (!vertex) {
2896 continue;
2897 }
2898 ext.push_back(std::make_shared<DRW_Variant>(
2899 1010,
2900 DRW_Coord{vertex->basePoint.x, vertex->basePoint.y,
2901 vertex->basePoint.z}));
2902 }
2903 }
2904 return ext;
2905 };
2906 auto addMeshSidecarMetadata = [&](const RS_Entity* entity,
2907 int elementIndex,
2908 const std::string& role,
2909 int roleIndex, bool anchor) {
2910 if (m_graphic == nullptr || entity == nullptr)
2911 return;
2912 LC_DwgAdvancedMetadata::MeshSidecarRecord record;
2913 record.sourceHandle = data.handle;
2914 record.fallbackEntityId = entity->getId();
2915 record.meshId = meshId;
2916 record.role = role;
2917 record.elementIndex = elementIndex;
2918 record.elementCount = meshElementCount;
2919 record.roleIndex = roleIndex;
2920 record.flags = data.flags;
2921 record.mCount = M;
2922 record.nCount = N;
2923 record.smoothM = data.smoothM;
2924 record.smoothN = data.smoothN;
2925 record.curveType = data.curvetype;
2926 record.sourceVertexCount = anchor ? data.vertlist.size() : 0u;
2927 record.anchor = anchor;
2928 m_graphic->dwgAdvancedMetadata().addMeshSidecar(std::move(record));
2929 };
2930
2931 // Add row polylines (along N direction)
2932 for (int i = 0; i < M; i++) {
2933 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedN);
2934 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
2935 setEntityAttributes(pl.get(), &data);
2936 for (int j = 0; j < N; j++) {
2937 auto v = data.vertlist.at(i * N + j);
2938 auto basePoint = v->basePoint;
2939 RS_Vector pos(basePoint.x, basePoint.y);
2940 pl->addVertex(pos, 0.0, false);
2941 }
2942 pl->setDrwExtData(makeMeshExtData(i, "row", i, i == 0));
2943 addMeshSidecarMetadata(pl.get(), i, "row", i, i == 0);
2944 m_currentContainer->addEntity(pl.release());
2945 }
2946
2947 // Add column polylines (along M direction)
2948 for (int j = 0; j < N; j++) {
2949 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedM);
2950 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
2951 setEntityAttributes(pl.get(), &data);
2952 for (int i = 0; i < M; i++) {
2953 auto v = data.vertlist.at(i * N + j);
2954 RS_Vector pos(v->basePoint.x, v->basePoint.y);
2955 pl->addVertex(pos, 0.0, false);
2956 }
2957 pl->setDrwExtData(
2958 makeMeshExtData(M + j, "column", j, false));
2959 addMeshSidecarMetadata(pl.get(), M + j, "column", j, false);
2960 m_currentContainer->addEntity(pl.release());
2961 }
2962 return;
2963 }
2964
2965 if (data.flags & 0x40) {
2966 // the polyline is a polyface mesh
2967 std::vector<RS_Vector> vertices;
2968 std::vector<DRW_Coord> sourceVertices;
2969 std::vector<std::shared_ptr<DRW_Vertex>> faceRecords;
2970 for (const std::shared_ptr<DRW_Vertex>& v : data.vertlist) {
2971 if (!v) {
2972 continue;
2973 }
2974 const bool coordinateVertex =
2975 v->dwgSubtype() == DRW_Vertex::DwgSubtype::Polyface
2976 || ((v->flags & 0x40) != 0 && (v->flags & 0x80) != 0);
2977 const bool faceRecord =
2978 v->dwgSubtype() == DRW_Vertex::DwgSubtype::PolyfaceFace
2979 || ((v->flags & 0x80) != 0 && (v->flags & 0x40) == 0);
2980 if (coordinateVertex) {
2981 vertices.emplace_back(v->basePoint.x, v->basePoint.y);
2982 sourceVertices.emplace_back(v->basePoint.x, v->basePoint.y,
2983 v->basePoint.z);
2984 }
2985 else if (faceRecord) {
2986 faceRecords.push_back(v);
2987 }
2988 }
2989 const std::string polyfaceId = std::string("polyline_pface_")
2990 + std::to_string(data.handle);
2991 auto makePolyfaceExtData =
2992 [&](const DRW_Vertex& face, int faceIndex, bool anchor) {
2993 std::vector<std::shared_ptr<DRW_Variant>> ext;
2994 ext.push_back(std::make_shared<DRW_Variant>(
2995 1001, std::string("LibreCAD_POLYLINE_PFACE")));
2996 ext.push_back(std::make_shared<DRW_Variant>(
2997 1000, polyfaceId));
2998 ext.push_back(std::make_shared<DRW_Variant>(
2999 1070, std::int32_t{faceIndex}));
3000 ext.push_back(std::make_shared<DRW_Variant>(
3001 1070, std::int32_t{static_cast<int>(faceRecords.size())}));
3002 ext.push_back(std::make_shared<DRW_Variant>(
3003 1070, std::int32_t{data.flags}));
3004 ext.push_back(std::make_shared<DRW_Variant>(
3005 1070, std::int32_t{data.vertexcount}));
3006 ext.push_back(std::make_shared<DRW_Variant>(
3007 1070, std::int32_t{data.facecount}));
3008 ext.push_back(std::make_shared<DRW_Variant>(
3009 1070, std::int32_t{face.vindex1}));
3010 ext.push_back(std::make_shared<DRW_Variant>(
3011 1070, std::int32_t{face.vindex2}));
3012 ext.push_back(std::make_shared<DRW_Variant>(
3013 1070, std::int32_t{face.vindex3}));
3014 ext.push_back(std::make_shared<DRW_Variant>(
3015 1070, std::int32_t{face.vindex4}));
3016 if (anchor) {
3017 for (const auto& coord : sourceVertices) {
3018 ext.push_back(std::make_shared<DRW_Variant>(
3019 1010, coord));
3020 }
3021 }
3022 return ext;
3023 };
3024 // add faces as closed polylines
3025 for (size_t faceIndex = 0; faceIndex < faceRecords.size();
3026 ++faceIndex) {
3027 const auto& f = faceRecords[faceIndex];
3028 std::vector<int> indices = {{f->vindex1, f->vindex2,
3029 f->vindex3, f->vindex4}};
3030 int num_points = (f->vindex4 == 0) ? 3 : 4;
3031 RS_PolylineData pd(RS_Vector(), RS_Vector(), true); // closed
3032 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
3033 setEntityAttributes(pl.get(), &data);
3034 bool valid = true;
3035 for (int k = 0; k < num_points; ++k) {
3036 int idx = std::abs(indices[k]);
3037 if (idx < 1 || idx > static_cast<int>(vertices.size())) {
3038 valid = false;
3039 break;
3040 }
3041 pl->addVertex(vertices[idx - 1], 0.0);
3042 }
3043 if (valid) {
3044 pl->setDrwExtData(
3045 makePolyfaceExtData(*f, static_cast<int>(faceIndex),
3046 faceIndex == 0));
3047 m_currentContainer->addEntity(pl.release());
3048 }
3049 }
3050 return;
3051 }
3052
3053 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, data.flags & 0x1);
3054 const bool is2dPolyline = (data.flags & (8 | 16 | 64)) == 0;
3055 if (is2dPolyline && !hasNormalizedAxialExtrusion(data)) {
3056 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
3057 "RS_FilterDXFRW::addPolyline: skipped non-axial 2D extrusion");
3058 return;
3059 }
3060 const bool reflectedOcs = is2dPolyline && data.extPoint.z < 0.0;
3061 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
3062 setEntityAttributes(polyline.get(), &data);
3063
3064 if (data.vertlist.empty()) {
3065 m_currentContainer->addEntity(polyline.release());
3066 return;
3067 }
3068
3069 auto vert0 = data.vertlist[0];
3070 RS_Vector first_pos(reflectedOcs ? -vert0->basePoint.x : vert0->basePoint.x,
3071 vert0->basePoint.y);
3072 const double firstBulge = reflectedOcs ? -vert0->bulge : vert0->bulge;
3073 polyline->addVertex(first_pos, firstBulge, false);
3074 RS_Vector prev_pos = first_pos;
3075
3076 bool closed = (data.flags & 0x1) != 0;
3077 size_t num_segments = closed ? data.vertlist.size() : data.vertlist.size() - 1;
3078
3079 for (size_t i = 0; i < num_segments; ++i) {
3080 size_t vert_idx = (i + 1) % data.vertlist.size();
3081 auto vert = data.vertlist[vert_idx];
3082 RS_Vector curr_pos(reflectedOcs ? -vert->basePoint.x : vert->basePoint.x,
3083 vert->basePoint.y);
3084
3085 size_t bulge_idx = i % data.vertlist.size();
3086 double segmentBulge = data.vertlist[bulge_idx]->bulge;
3087 if (reflectedOcs)
3088 segmentBulge = -segmentBulge;
3089 double nextBulge = vert->bulge;
3090 if (reflectedOcs)
3091 nextBulge = -nextBulge;
3092 const auto& extData = data.vertlist[bulge_idx]->extData;
3093
3094 addPolylineSegment(*polyline, prev_pos, curr_pos, segmentBulge,
3095 nextBulge, extData);
3096
3097 prev_pos = curr_pos;
3098 }
3099
3100 if (closed) {
3101 polyline->setFlag(RS2::FlagClosed);
3102 polyline->getData().endpoint = polyline->getData().startpoint;
3103 }
3104 else {
3105 polyline->endPolyline();
3106 }
3107
3108 bool hasVertexMetadata = data.defstawidth != 0.0 || data.defendwidth != 0.0;
3109 for (const auto& vertex : data.vertlist) {
3110 if (vertex && (vertex->stawidth != 0.0 || vertex->endwidth != 0.0
3111 || vertex->identifier != 0)) {
3112 hasVertexMetadata = true;
3113 break;
3114 }
3115 }
3116 if (is2dPolyline && (data.basePoint.z != 0.0 || data.thickness != 0.0
3117 || reflectedOcs || hasVertexMetadata)) {
3118 std::vector<std::shared_ptr<DRW_Variant>> ext;
3119 ext.push_back(std::make_shared<DRW_Variant>(
3120 1001, std::string("LibreCAD_LWPOLYLINE")));
3121 ext.push_back(std::make_shared<DRW_Variant>(1040, 0.0));
3122 ext.push_back(std::make_shared<DRW_Variant>(
3123 1040, reflectedOcs ? -data.basePoint.z : data.basePoint.z));
3124 ext.push_back(std::make_shared<DRW_Variant>(
3125 1040, reflectedOcs ? -data.thickness : data.thickness));
3126 ext.push_back(std::make_shared<DRW_Variant>(
3127 1010, DRW_Coord{0.0, 0.0, 1.0}));
3128 ext.push_back(std::make_shared<DRW_Variant>(
3129 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
3130 for (const auto& vertex : data.vertlist) {
3131 const double startWidth = vertex && vertex->stawidth != 0.0
3132 ? vertex->stawidth : data.defstawidth;
3133 const double endWidth = vertex && vertex->endwidth != 0.0
3134 ? vertex->endwidth : data.defendwidth;
3135 ext.push_back(std::make_shared<DRW_Variant>(1040, startWidth));
3136 ext.push_back(std::make_shared<DRW_Variant>(1040, endWidth));
3137 ext.push_back(std::make_shared<DRW_Variant>(
3138 1071, std::int32_t{vertex ? vertex->identifier : 0}));
3139 }
3140 polyline->setDrwExtData(std::move(ext));
3141 }
3142
3143 m_currentContainer->addEntity(polyline.release());
3144}
3145
3146bool RS_FilterDXFRW::handleQuadraticConicSpline(const DRW_Spline* data) {
3147 if (data->degree != 2 || data->controllist.size() != 3) {
3148 return false;
3149 }
3150
3151 // Three explicit branches by middle weight: hyperbola (w>1), parabola
3152 // (weights absent or w=1), or fall-through for ellipse arc (w<1) and
3153 // anything else the dispatcher can't classify cleanly.
3154 std::unique_ptr<RS_Entity> en =
3155 LC_HyperbolaSpline::splineToHyperbola(*data, m_currentContainer);
3156 if (en == nullptr) {
3157 en = LC_ParabolaSpline::splineToParabola(*data, m_currentContainer);
3158 }
3159 if (en == nullptr) {
3160 // Not a hyperbola or parabola — likely an ellipse arc (w<1), a
3161 // collinear/degenerate spline, or a non-canonical knot vector. Let
3162 // the caller fall through to the generic addSpline path so the
3163 // entity isn't silently mis-classified as a parabola.
3164 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::handleQuadraticConicSpline: "
3165 "degree-2/3-control spline not classified as "
3166 "parabola or hyperbola; falling through to addSpline");
3167 return false;
3168 }
3169 setEntityAttributes(en.get(), data);
3170 en->update();
3171 m_currentContainer->addEntity(en.release());
3172
3173 return true;
3174}
3175
3176/**
3177 * Implementation of the method which handles splines.
3178 */
3179void RS_FilterDXFRW::addSpline(const DRW_Spline* data) {
3180 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpline: degree: %d", data->degree);
3181
3182 // Special case: rational quadratic conic (hyperbola or parabola)
3183 if (handleQuadraticConicSpline(data)) {
3184 return; // Conic handled successfully
3185 }
3186
3187 // Spline points case. Weighted degree-2 splines are exact rational conics
3188 // or NURBS segments; keep them on the RS_Spline path so weights survive.
3189 if (data->degree == 2 && !hasRationalSplineWeights(data)) {
3190 bool closed = (data->flags & 0x1) == 0x1;
3191 bool controlOnly = data->nfit == 0;
3192
3193 const LC_SplinePointsData d(closed, controlOnly);
3194 const auto splinePoints = new LC_SplinePoints(m_currentContainer, d);
3195 setEntityAttributes(splinePoints, data);
3196
3197 for (const auto& vert : data->controllist) {
3198 if (vert) {
3199 splinePoints->addControlPoint({vert->x, vert->y});
3200 }
3201 }
3202 for (const auto& vert : data->fitlist) {
3203 if (vert) {
3204 splinePoints->addPoint({vert->x, vert->y});
3205 }
3206 }
3207
3208 splinePoints->update();
3209 m_currentContainer->addEntity(splinePoints);
3210 return;
3211 }
3212
3213 // General spline (degree 1-3)
3214 if (data->degree < 1) {
3215 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
3216 "RS_FilterDXFRW::addSpline: unsupported spline degree %d", data->degree);
3217 return;
3218 }
3219 if (data->degree > 3) {
3220 std::unique_ptr<LC_SplinePoints> sampled = approximateDrwSpline(m_currentContainer, data);
3221 if (sampled) {
3222 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
3223 "RS_FilterDXFRW::addSpline: approximating unsupported spline degree %d",
3224 data->degree);
3225 setEntityAttributes(sampled.get(), data);
3226 sampled->update();
3227 m_currentContainer->addEntity(sampled.release());
3228 return;
3229 }
3230
3231 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
3232 "RS_FilterDXFRW::addSpline: unsupported spline degree %d", data->degree);
3233 return;
3234 }
3235
3236 const bool isClosed = (data->flags & 0x1) == 0x1;
3237
3238 RS_SplineData d(data->degree, isClosed);
3239 if (!data->knotslist.empty()) {
3240 const double tolknot = (data->tolknot > 0.0) ? data->tolknot : 1e-7;
3241 for (const double k : data->knotslist) {
3242 d.knotslist.push_back(RS_Math::round(k, tolknot));
3243 }
3244 }
3245
3246 d.type = isClosed ? RS_SplineData::SplineType::Standard : RS_SplineData::SplineType::ClampedOpen;
3247
3248 const auto spline = new RS_Spline(m_currentContainer, d);
3249 setEntityAttributes(spline, data);
3250 m_currentContainer->addEntity(spline);
3251
3252 // Control points and weights. Non-rational B-splines have no weight array
3253 // (weight=1.0 implied); only warn for rational splines (flag bit 2).
3254 size_t numCtrl = data->controllist.size();
3255 bool isRational = (data->flags & 0x4) != 0;
3256 if (isRational && numCtrl != data->weightlist.size()) {
3257 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
3258 "RS_FilterDXFRW::addSpline: rational spline control "
3259 "points (%zu) != weights (%zu)",
3260 numCtrl, data->weightlist.size());
3261 }
3262
3263 for (size_t i = 0; i < numCtrl; ++i) {
3264 const auto& vert = data->controllist[i];
3265 const double weight = (i < data->weightlist.size()) ? data->weightlist[i] : 1.0;
3266 if (vert) {
3267 spline->addControlPointRaw({vert->x, vert->y}, weight);
3268 }
3269 }
3270
3271 // Fit points fallback
3272 if (numCtrl == 0 && data->degree != 2) {
3273 std::vector<RS_Vector> fitPoints;
3274 std::transform(data->fitlist.begin(), data->fitlist.end(), std::back_inserter(fitPoints), [](const std::shared_ptr<DRW_Coord>& coord) {
3275 return RS_Vector{coord->x, coord->y};
3276 });
3277 spline->setFitPoints(fitPoints);
3278 }
3279
3280 if (isClosed) {
3281 spline->setClosed(true);
3282 }
3283
3284 spline->update();
3285}
3286
3287/**
3288 * Handles a HELIX entity. LibreCAD has no native helix; the entity is mapped
3289 * to its spline approximation via addSpline. The AcDbHelix axis/turns metadata
3290 * (radius, turns, turnHeight, axisVector, ...) is preserved across a DWG
3291 * round-trip but is not represented in the RS entity model, so it is dropped
3292 * on import.
3293 */
3294void RS_FilterDXFRW::addHelix(const DRW_Helix* data) {
3295 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addHelix: mapping HELIX to spline "
3296 "approximation (axis/turns metadata dropped)");
3297 addSpline(data);
3298}
3299
3300/**
3301 * Implementation of the method which handles inserts.
3302 */
3303void RS_FilterDXFRW::addInsert(const DRW_Insert& data) {
3304 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addInsert");
3305
3306 RS_Vector ip(data.basePoint.x, data.basePoint.y, data.basePoint.z);
3307 RS_Vector sc(data.xscale, data.yscale, data.zscale);
3308 RS_Vector sp(data.colspace, data.rowspace);
3309
3310 //cout << "Insert: " << name << " " << ip << " " << cols << "/" << rows << endl;
3311
3312 RS_InsertData d( QString::fromUtf8(data.name.c_str()),
3313 ip, sc, data.angle,
3314 data.colcount, data.rowcount,
3315 sp, nullptr, RS2::NoUpdate);
3316 d.extrusion = RS_Vector(data.extPoint.x, data.extPoint.y, data.extPoint.z);
3317 RS_Insert* entity = new RS_Insert(m_currentContainer, d);
3318 setEntityAttributes(entity, &data);
3319 RS_DEBUGRS_Debug::instance()->print(" id: %lu", entity->getId());
3320 // entity->update();
3321 m_currentContainer->addEntity(entity);
3322
3323 // Render visible block attributes (ATTRIB) attached to this INSERT.
3324 // ATTRIB coordinates are in world space (per AutoCAD convention), so
3325 // each becomes an independent RS_Text in the current container.
3326 // Bit 1 of attribFlags marks the attribute as invisible.
3327 for (const auto &att : data.attlist) {
3328 if (!att || (att->attribFlags & 0x1) != 0)
3329 continue;
3330
3331 // Multi-line ATTRIB (R2018+, ODA spec §20.4.4 Attribute type 2/4):
3332 // libdxfrw saw the `100 / Embedded Object` DXF subclass marker on the
3333 // ATTRIB and populated att->mtext from the embedded MTEXT body.
3334 // Render it as RS_MText so the multi-line content + formatting codes
3335 // survive; the plain `text` field is the single-line fallback.
3336 if (att->mtext) {
3337 auto *mt = mtextEntityFromDRW(*att->mtext);
3338 mt->setParent(m_currentContainer);
3339 setEntityAttributes(mt, att.get());
3340 mt->update();
3341 m_currentContainer->addEntity(mt);
3342 continue;
3343 }
3344
3345 RS_Vector refPoint(att->basePoint.x, att->basePoint.y);
3346 RS_Vector secPoint(att->secPoint.x, att->secPoint.y);
3347 if (att->alignV != 0 || att->alignH != 0 ||
3348 att->alignH == DRW_Text::HMiddle) {
3349 if (att->alignH != DRW_Text::HAligned &&
3350 att->alignH != DRW_Text::HFit) {
3351 secPoint = RS_Vector(att->basePoint.x, att->basePoint.y);
3352 refPoint = RS_Vector(att->secPoint.x, att->secPoint.y);
3353 }
3354 }
3355
3356 RS_TextData::VAlign valign = (RS_TextData::VAlign)att->alignV;
3357 RS_TextData::HAlign halign = (RS_TextData::HAlign)att->alignH;
3358 RS_TextData::TextGeneration dir;
3359 if (att->textgen == 2)
3360 dir = RS_TextData::Backward;
3361 else if (att->textgen == 4)
3362 dir = RS_TextData::UpsideDown;
3363 else
3364 dir = RS_TextData::None;
3365
3366 QString sty = QString::fromUtf8(att->style.c_str());
3367 prepareTextStyleName(sty);
3368 QString text = toNativeString(QString::fromUtf8(att->text.c_str()));
3369
3370 RS_TextData td(refPoint, secPoint, att->height, att->widthscale, valign,
3371 halign, dir, text, sty, att->angle * M_PI3.14159265358979323846 / 180,
3372 RS2::NoUpdate);
3373 auto textEntity = std::make_unique<RS_Text>(m_currentContainer, td);
3374 setEntityAttributes(textEntity.get(), att.get());
3375 textEntity->update();
3376 m_currentContainer->addEntity(textEntity.release());
3377 }
3378}
3379
3380void RS_FilterDXFRW::addTable(const DRW_Table& data) {
3381 TableFallbackRenderSummary fallbackSummary;
3382 const bool fallbackRendered = addTableFallback(data, &fallbackSummary);
3383 if (m_graphic != nullptr) {
3384 m_graphic->dwgAdvancedMetadata().addTable(data, fallbackRendered);
3385 LC_DwgAdvancedMetadata::TableFallbackRenderSummary metadataSummary;
3386 metadataSummary.tableHandle = data.handle;
3387 metadataSummary.gridEntityCount = fallbackSummary.gridEntityCount;
3388 metadataSummary.textEntityCount = fallbackSummary.textEntityCount;
3389 metadataSummary.placeholderEntityCount =
3390 fallbackSummary.placeholderEntityCount;
3391 metadataSummary.unresolvedTextStyleCount =
3392 fallbackSummary.unresolvedTextStyleCount;
3393 metadataSummary.clampedDimensionCount =
3394 fallbackSummary.clampedDimensionCount;
3395 m_graphic->dwgAdvancedMetadata().updateTableFallbackRenderSummary(
3396 metadataSummary);
3397 }
3398 addInsert(data);
3399}
3400
3401bool RS_FilterDXFRW::addTableFallback(
3402 const DRW_Table& data, TableFallbackRenderSummary *summary) {
3403 if (!data.m_hasSemanticContent || data.m_content.m_rows.empty()
3404 || data.m_content.m_columns.empty() || m_currentContainer == nullptr) {
3405 return false;
3406 }
3407
3408 const DRW_TableContent& content = data.m_content;
3409 const RS_Vector origin(data.basePoint.x, data.basePoint.y, data.basePoint.z);
3410 RS_Vector xAxis(data.m_horizontalDirection.x, data.m_horizontalDirection.y,
3411 data.m_horizontalDirection.z);
3412 if (!xAxis.valid || xAxis.magnitude() <= RS_TOLERANCE1.0e-10) {
3413 xAxis = RS_Vector::polar(1.0, data.angle);
3414 } else {
3415 xAxis.set(xAxis.x / xAxis.magnitude(), xAxis.y / xAxis.magnitude(),
3416 xAxis.z / xAxis.magnitude());
3417 }
3418 const RS_Vector yAxis = RS_Vector(-xAxis.y, xAxis.x, 0.0);
3419
3420 std::vector<double> columnOffsets;
3421 columnOffsets.reserve(content.m_columns.size() + 1);
3422 columnOffsets.push_back(0.0);
3423 for (size_t column = 0; column < content.m_columns.size(); ++column) {
3424 columnOffsets.push_back(
3425 columnOffsets.back() + tableColumnWidth(content, column, summary));
3426 }
3427
3428 std::vector<double> rowOffsets;
3429 rowOffsets.reserve(content.m_rows.size() + 1);
3430 rowOffsets.push_back(0.0);
3431 for (size_t row = 0; row < content.m_rows.size(); ++row) {
3432 rowOffsets.push_back(
3433 rowOffsets.back() + tableRowHeight(content, row, summary));
3434 }
3435
3436 auto tablePoint = [&](double x, double y) {
3437 return origin + xAxis * x - yAxis * y;
3438 };
3439 auto addFallbackRecord = [&](RS_Entity *entity, int row, int column,
3440 LC_DwgAdvancedMetadata::TableFallbackRole role) {
3441 if (m_graphic == nullptr || entity == nullptr)
3442 return;
3443 LC_DwgAdvancedMetadata::TableFallbackEntityRecord record;
3444 record.tableHandle = data.handle;
3445 record.sourceHandle = data.handle;
3446 record.row = row;
3447 record.column = column;
3448 record.role = role;
3449 record.entityId = entity->getId();
3450 m_graphic->dwgAdvancedMetadata().addTableFallbackEntity(record);
3451 };
3452 auto addBorder = [&](const RS_Vector& a, const RS_Vector& b,
3453 int row, int column) {
3454 auto *line = new RS_Line{m_currentContainer, {a, b}};
3455 setEntityAttributes(line, &data);
3456 line->update();
3457 addFallbackRecord(line, row, column,
3458 LC_DwgAdvancedMetadata::TableFallbackRole::GridLine);
3459 if (summary != nullptr)
3460 ++summary->gridEntityCount;
3461 m_currentContainer->addEntity(line);
3462 };
3463
3464 for (size_t column = 0; column < columnOffsets.size(); ++column) {
3465 const double x = columnOffsets[column];
3466 addBorder(tablePoint(x, 0.0), tablePoint(x, rowOffsets.back()),
3467 -1, static_cast<int>(column));
3468 }
3469 for (size_t row = 0; row < rowOffsets.size(); ++row) {
3470 const double y = rowOffsets[row];
3471 addBorder(tablePoint(0.0, y), tablePoint(columnOffsets.back(), y),
3472 static_cast<int>(row), -1);
3473 }
3474
3475 bool renderedText = false;
3476 QString style = m_textStyle;
3477 prepareTextStyleName(style);
3478 const double angle = std::atan2(xAxis.y, xAxis.x);
3479 for (size_t row = 0; row < content.m_rows.size(); ++row) {
3480 const auto& tableRow = content.m_rows[row];
3481 const size_t cellCount = std::min(tableRow.m_cells.size(),
3482 content.m_columns.size());
3483 for (size_t column = 0; column < cellCount; ++column) {
3484 const DRW_TableCell& tableCell = tableRow.m_cells[column];
3485 TableFallbackCellDisplay display =
3486 tableCellDisplay(tableCell, data.m_semanticContentComplete);
3487 if (display.text.isEmpty())
3488 continue;
3489 const bool placeholder =
3490 tableFallbackCellIsPlaceholder(display.kind);
3491 QString text = toNativeString(display.text);
3492 const double left = columnOffsets[column];
3493 const double right = columnOffsets[column + 1];
3494 const double top = rowOffsets[row];
3495 const double bottom = rowOffsets[row + 1];
3496 const double cellHeight = std::max(0.1, bottom - top);
3497 const double cellWidth = std::max(0.1, right - left);
3498 const RS_Vector insertion = tablePoint(left + cellWidth * 0.08,
3499 top + cellHeight * 0.25);
3500 double textHeight = cellHeight * 0.35;
3501 if (tableCell.m_contentHeight > 0.0
3502 && std::isfinite(tableCell.m_contentHeight)) {
3503 textHeight = tableCell.m_contentHeight;
3504 } else if (tableCell.m_height > 0.0
3505 && std::isfinite(tableCell.m_height)) {
3506 textHeight = tableCell.m_height * 0.35;
3507 }
3508 textHeight = std::max(kTableFallbackMinTextHeight, textHeight);
3509 if (summary != nullptr
3510 && (tableCell.m_textStyleHandle != 0
3511 || tableCell.m_textStyleOverrideHandle != 0)) {
3512 ++summary->unresolvedTextStyleCount;
3513 }
3514 RS_MTextData textData(insertion, textHeight, cellWidth * 0.84,
3515 RS_MTextData::VATop, RS_MTextData::HALeft,
3516 RS_MTextData::LeftToRight, RS_MTextData::AtLeast,
3517 1.0, text, style, angle, RS2::NoUpdate);
3518 auto *mtext = new RS_MText(m_currentContainer, textData);
3519 setEntityAttributes(mtext, &data);
3520 mtext->update();
3521 addFallbackRecord(
3522 mtext, static_cast<int>(row), static_cast<int>(column),
3523 placeholder
3524 ? LC_DwgAdvancedMetadata::TableFallbackRole::Placeholder
3525 : LC_DwgAdvancedMetadata::TableFallbackRole::CellText);
3526 if (summary != nullptr) {
3527 ++summary->textEntityCount;
3528 if (placeholder)
3529 ++summary->placeholderEntityCount;
3530 }
3531 m_currentContainer->addEntity(mtext);
3532 renderedText = true;
3533 }
3534 }
3535
3536 return renderedText || columnOffsets.size() > 1 || rowOffsets.size() > 1;
3537}
3538
3539void RS_FilterDXFRW::prepareTextStyleName(QString& sty) const {
3540 // use default style for the drawing:
3541 if (sty.isEmpty()) {
3542 // japanese, cyrillic:
3543 if (m_codePage == "ANSI_932" || m_codePage == "ANSI_1251") {
3544 sty = "Unicode";
3545 }
3546 else {
3547 sty = m_textStyle;
3548 }
3549 }
3550 else {
3551 sty = m_fontList.value(sty, sty);
3552 }
3553}
3554
3555/**
3556 * Builds an RS_MText entity from a DRW_MText payload, applying the same
3557 * alignment/drawing-direction/line-spacing/legacy-correction logic that
3558 * addMText() uses. Shared between addMText() (standalone MTEXT entities) and
3559 * addInsert() (block attributes whose `100 / Embedded Object` DXF subclass
3560 * produced an att->mtext payload). Returns a freshly-constructed entity not
3561 * yet attached to any container; the caller is responsible for
3562 * setEntityAttributes() and appending.
3563 */
3564RS_MText *RS_FilterDXFRW::mtextEntityFromDRW(const DRW_MText &data) {
3565 RS_MTextData::VAlign valign;
3566 RS_MTextData::HAlign halign;
3567 RS_MTextData::MTextDrawingDirection dir;
3568 RS_MTextData::MTextLineSpacingStyle lss;
3569
3570 if (data.textgen <= 3) {
3571 valign = RS_MTextData::VATop;
3572 } else if (data.textgen <= 6) {
3573 valign = RS_MTextData::VAMiddle;
3574 } else {
3575 valign = RS_MTextData::VABottom;
3576 }
3577
3578 if (data.textgen % 3 == 1) {
3579 halign = RS_MTextData::HALeft;
3580 } else if (data.textgen % 3 == 2) {
3581 halign = RS_MTextData::HACenter;
3582 } else {
3583 halign = RS_MTextData::HARight;
3584 }
3585
3586 if (data.alignH == 1) {
3587 dir = RS_MTextData::LeftToRight;
3588 } else if (data.alignH == 3) {
3589 dir = RS_MTextData::TopToBottom;
3590 } else {
3591 dir = RS_MTextData::ByStyle;
3592 }
3593
3594 if (data.alignV == 1) {
3595 lss = RS_MTextData::AtLeast;
3596 } else {
3597 lss = RS_MTextData::Exact;
3598 }
3599 QString mtext = toNativeString(QString::fromUtf8(data.text.c_str()));
3600 QString sty = QString::fromUtf8(data.style.c_str());
3601 prepareTextStyleName(sty);
3602
3603 double interlin = data.interlin;
3604 double angle = data.angle * M_PI3.14159265358979323846 / 180.;
3605 RS_Vector ip = RS_Vector(data.basePoint.x, data.basePoint.y);
3606
3607 if (m_oldMText) {
3608 interlin = data.interlin * 0.96;
3609 if (valign == RS_MTextData::VABottom) {
3610#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
3611 const QStringList tl = mtext.split('\n', Qt::SkipEmptyParts);
3612#else
3613 QStringList tl = mtext.split('\n', QString::SkipEmptyParts);
3614#endif
3615 if (!tl.isEmpty()) {
3616 QString txt = tl.at(tl.size()-1);
3617 RS_TextData d(RS_Vector(0., 0., 0.), RS_Vector(0., 0., 0.),
3618 data.height, 1, RS_TextData::VABaseline,
3619 RS_TextData::HALeft, RS_TextData::None, txt, sty,
3620 0, RS2::Update);
3621 auto entity = new RS_Text(nullptr, d);
3622 double textTail = entity->getMin().y;
3623 delete entity;
3624 const auto ot = RS_Vector(0.0, textTail).rotate(angle);
3625 ip.move(ot);
3626 }
3627 }
3628 }
3629
3630 // drawingDirection was resolved above (1→LeftToRight, 3→TopToBottom,
3631 // else→ByStyle), matching the DXF MTEXT group 72 spec. There is no DXF
3632 // value for RightToLeft; that flag round-trips via XDATA below — when
3633 // an MTEXT carries a "LibreCad" + 1071 marker, restore the RTL setting.
3634 bool wantRTL = false;
3635 for (size_t k = 0; k + 1 < data.extData.size(); ++k) {
3636 const auto &appTag = data.extData[k];
3637 if (!appTag || appTag->code() != 1001 ||
3638 appTag->type() != DRW_Variant::STRING ||
3639 appTag->content.s == nullptr || *appTag->content.s != "LibreCad")
3640 continue;
3641 // Walk subsequent entries until the next 1001 (next app block).
3642 for (size_t j = k + 1; j < data.extData.size(); ++j) {
3643 const auto &v = data.extData[j];
3644 if (!v)
3645 continue;
3646 if (v->code() == 1001)
3647 break;
3648 if (v->code() == 1071 && v->type() == DRW_Variant::INTEGER &&
3649 v->content.i != 0) {
3650 wantRTL = true;
3651 }
3652 }
3653 if (wantRTL)
3654 break;
3655 }
3656 if (wantRTL)
3657 dir = RS_MTextData::RightToLeft;
3658
3659 RS_MTextData d(ip, data.height, data.widthscale, valign, halign, dir, lss,
3660 interlin, mtext, sty, angle, RS2::NoUpdate);
3661 return new RS_MText(nullptr, d);
3662}
3663
3664/**
3665 * Implementation of the method which handles
3666 * multi texts (MTEXT).
3667 */
3668void RS_FilterDXFRW::addMText(const DRW_MText &data) {
3669 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addMText: %s", data.text.c_str());
3670 auto *entity = mtextEntityFromDRW(data);
3671 entity->setParent(m_currentContainer);
3672 setEntityAttributes(entity, &data);
3673 if (data.basePoint.z != 0.0 || data.secPoint.x != 0.0
3674 || data.secPoint.y != 0.0 || data.secPoint.z != 0.0
3675 || data.extPoint.x != 0.0 || data.extPoint.y != 0.0
3676 || data.extPoint.z != 1.0) {
3677 std::vector<std::shared_ptr<DRW_Variant>> payload;
3678 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
3679 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
3680 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
3681 appendTypeSidecar(entity, kMTextOcsMarker, std::move(payload));
3682 }
3683 entity->update();
3684 m_currentContainer->addEntity(entity);
3685}
3686
3687/**
3688 * Implementation of the method which handles
3689 * texts (TEXT).
3690 */
3691void RS_FilterDXFRW::addText(const DRW_Text& data) {
3692 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addText");
3693 auto refPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
3694 auto secPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
3695 const double angle = data.angle;
3696
3697 if (data.alignV != 0 || data.alignH != 0 || data.alignH == DRW_Text::HMiddle) {
3698 // fixme - sand - review this condition
3699 if (data.alignH != DRW_Text::HAligned && data.alignH != DRW_Text::HFit) {
3700 secPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
3701 refPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
3702 }
3703 }
3704
3705 const auto valign = static_cast<RS_TextData::VAlign>(data.alignV);
3706 const auto halign = static_cast<RS_TextData::HAlign>(data.alignH);
3707 RS_TextData::TextGeneration dir;
3708 QString sty = QString::fromUtf8(data.style.c_str());
3709
3710 if (data.textgen == 2) {
3711 dir = RS_TextData::Backward;
3712 }
3713 else if (data.textgen == 4) {
3714 dir = RS_TextData::UpsideDown;
3715 }
3716 else {
3717 dir = RS_TextData::None;
3718 }
3719
3720 const QString text = toNativeString(QString::fromUtf8(data.text.c_str()));
3721
3722 prepareTextStyleName(sty);
3723
3724 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
3725 RS_DEBUGRS_Debug::instance()->printUnicode(text);
3726
3727 const RS_TextData d(refPoint, secPoint, data.height, data.widthscale, valign, halign, dir, text, sty, angle * M_PI3.14159265358979323846 / 180,
3728 RS2::NoUpdate);
3729 auto* entity = new RS_Text(m_currentContainer, d);
3730
3731 setEntityAttributes(entity, &data);
3732 if (data.basePoint.z != 0.0 || data.secPoint.z != 0.0
3733 || data.thickness != 0.0 || data.extPoint.x != 0.0
3734 || data.extPoint.y != 0.0 || data.extPoint.z != 1.0) {
3735 std::vector<std::shared_ptr<DRW_Variant>> payload;
3736 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
3737 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
3738 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
3739 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
3740 appendTypeSidecar(entity, kTextOcsMarker, std::move(payload));
3741 }
3742 entity->update();
3743 m_currentContainer->addEntity(entity);
3744}
3745
3746void RS_FilterDXFRW::addAttDef(const DRW_Attdef& data) {
3747 // LibreCAD has no standalone attribute-definition model. Preserve the
3748 // visible BLOCK definition as text instead of silently dropping it; the
3749 // original DXF/DWG attribute fields remain available to libdxfrw callers.
3750 addText(data);
3751}
3752
3753/**
3754 * Implementation of the method which handles
3755 * dimensions (DIMENSION).
3756 */
3757RS_DimensionData RS_FilterDXFRW::convDimensionData(const DRW_Dimension* data) {
3758 DRW_Coord crd = data->getDefPoint();
3759 const RS_Vector defP(crd.x, crd.y);
3760 crd = data->getTextPoint();
3761 RS_Vector midP(crd.x, crd.y);
3762 RS_MTextData::VAlign valign;
3763 RS_MTextData::HAlign halign;
3764 RS_MTextData::MTextLineSpacingStyle lss;
3765 QString sty = QString::fromUtf8(data->getStyle().c_str());
3766
3767 QString t; //= data.text;
3768
3769 // middlepoint of text can be 0/0 which is considered to be invalid (!):
3770 // 0/0 because older QCad versions save the middle of the text as 0/0
3771 // although they didn't support saving of the middle of the text.
3772 if (fabs(crd.x) < 1.0e-6 && fabs(crd.y) < 1.0e-6) {
3773 midP = RS_Vector(false);
3774 }
3775
3776 if (data->getAlign() <= 3) {
3777 valign = RS_MTextData::VATop;
3778 }
3779 else if (data->getAlign() <= 6) {
3780 valign = RS_MTextData::VAMiddle;
3781 }
3782 else {
3783 valign = RS_MTextData::VABottom;
3784 }
3785
3786 if (data->getAlign() % 3 == 1) {
3787 halign = RS_MTextData::HALeft;
3788 }
3789 else if (data->getAlign() % 3 == 2) {
3790 halign = RS_MTextData::HACenter;
3791 }
3792 else {
3793 halign = RS_MTextData::HARight;
3794 }
3795
3796 if (data->getTextLineStyle() == 1) {
3797 lss = RS_MTextData::AtLeast;
3798 }
3799 else {
3800 lss = RS_MTextData::Exact;
3801 }
3802
3803 t = toNativeString(QString::fromUtf8(data->getText().c_str()));
3804
3805 if (sty.isEmpty()) {
3806 sty = m_dimStyle;
3807 }
3808
3809 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
3810 RS_DEBUGRS_Debug::instance()->printUnicode(t);
3811
3812 const bool customTextLocation = data->type >= 128;
3813
3814 LC_DimStyle* dimStyleOverride = nullptr;
3815
3816 if (!data->extData.empty()) {
3817 LC_ExtEntityData* extData = extractEntityExtData(data->extData);
3818 if (extData != nullptr) {
3819 dimStyleOverride = parseDimStyleOverride(extData);
3820 delete extData;
3821 }
3822 }
3823
3824 // data needed to add the actual dimension entity
3825 return RS_DimensionData(defP, midP, valign, halign, lss, data->getTextLineFactor(), t, sty, data->getDir(), data->getHDir(),
3826 !customTextLocation, dimStyleOverride, data->getFlipArrow1(), data->getFlipArrow2());
3827}
3828
3829void RS_FilterDXFRW::fillEntityExtData(std::vector<std::shared_ptr<DRW_Variant>>& extData, LC_ExtEntityData* entityData) {
3830 const auto appDatas = entityData->getAppData();
3831 for (const auto appData : *appDatas) {
3832 extData.push_back(std::make_shared<DRW_Variant>(1001, appData->getName().toStdString())); // application name
3833 const auto groups = appData->getGroups();
3834 for (const auto group : *groups) {
3835 extData.push_back(std::make_shared<DRW_Variant>(1000, group->getName().toStdString())); // group name
3836 extData.push_back(std::make_shared<DRW_Variant>(1002, "{")); // start
3837
3838 const auto tagsList = group->getTagsList();
3839
3840 for (const auto tag : *tagsList) {
3841 if (tag->isAtomic()) {
3842 // fixme - just plain list of tags within the group, no nesting!
3843 const auto variable = tag->var();
3844 int code = variable->getCode();
3845 extData.push_back(std::make_shared<DRW_Variant>(1070, code)); // code of variable
3846 if (tag->isBinary()) {
3847 // Wrap the QByteArray bytes in a BINARY DRW_Variant
3848 // (DXF group 1004). The DXF writer hex-encodes them
3849 // on emit; the DWG path will use the raw bytes.
3850 const QByteArray &bytes = tag->bytes();
3851 std::vector<std::uint8_t> raw(bytes.size());
3852 for (int i = 0; i < bytes.size(); ++i) {
3853 raw[i] = static_cast<std::uint8_t>(bytes[i]);
3854 }
3855 extData.push_back(
3856 std::make_shared<DRW_Variant>(1004, std::move(raw)));
3857 continue;
3858 }
3859 auto varType = variable->getType();
3860 switch (varType) {
3861 case RS2::VariableInt: {
3862 extData.push_back(std::make_shared<DRW_Variant>(1070, variable->getInt())); // int value
3863 break;
3864 }
3865 case RS2::VariableDouble: {
3866 extData.push_back(std::make_shared<DRW_Variant>(1040, variable->getDouble())); // double value
3867 break;
3868 }
3869 case RS2::VariableString: {
3870 if (tag->isLayerRef()) {
3871 // DXF code 1003 — layer name reference. The
3872 // isLayerRef flag rides on the variant so that
3873 // the DWG path (when written) can resolve the
3874 // name back to a layer-table handle.
3875 extData.push_back(std::make_shared<DRW_Variant>(
3876 1003, variable->getString().toStdString(),
3877 /*isLayerRef=*/true));
3878 } else {
3879 extData.push_back(std::make_shared<DRW_Variant>(
3880 (tag->isRef() ? 1005 : 1003),
3881 variable->getString()
3882 .toStdString())); // string value
3883 }
3884 break;
3885 }
3886 case RS2::VariableVector: {
3887 RS_Vector vec = variable->getVector();
3888 extData.push_back(std::make_shared<DRW_Variant>(1010, vec.x)); // vector value
3889 extData.push_back(std::make_shared<DRW_Variant>(1011, vec.y)); // vector value
3890 extData.push_back(std::make_shared<DRW_Variant>(1012, vec.z)); // vector value
3891 break;
3892 }
3893 case RS2::VariableVoid: {
3894 break;
3895 }
3896 }
3897 }
3898 }
3899 extData.push_back(std::make_shared<DRW_Variant>(1002, "}")); // end
3900 }
3901 }
3902}
3903
3904// this method is quite generic and may be used for parsing any entity's ext data
3905// fixme & todo - sand - nesting of tags is not supported so far in code, yet it's supported by DXF !!!!
3906LC_ExtEntityData* RS_FilterDXFRW::extractEntityExtData(const std::vector<std::shared_ptr<DRW_Variant>>& extData) {
3907 auto* result = new LC_ExtEntityData();
3908 LC_ExtDataAppData* currentAppData = nullptr;
3909 LC_ExtDataGroup* currentGroup = nullptr;
3910
3911 int currentValType = -1;
3912 bool expectType = false;
3913 [[maybe_unused]] int listLevel = 0;
3914 bool inTagsList = false;
3915 for (const auto &v : extData) {
3916 const int code = v->code();
3917 switch (code) {
3918 case 1001: {
3919 // application name
3920 QString applicationName = v->c_str();
3921 currentAppData = result->addAppData(applicationName);
3922 break;
3923 }
3924 case 1000: {
3925 // group name
3926 QString groupName = v->c_str();
3927 if (currentAppData != nullptr) {
3928 currentGroup = currentAppData->addGroup(groupName);
3929 }
3930 break;
3931 }
3932 case 1002: {
3933 // control braces
3934 QString ctrlString = v->c_str();
3935 if (ctrlString == "{") {
3936 // fixme - sand - add support of lists nesting!!!
3937 listLevel++;
3938 inTagsList = true;
3939 expectType = false; // for later "not", as actually we do expect it
3940 }
3941 else {
3942 // end of list
3943 listLevel--;
3944 inTagsList = false;
3945 }
3946 break;
3947 }
3948 case 1003: {
3949 if (currentGroup != nullptr) {
3950 if (v->isLayerRef()) {
3951 currentGroup->addLayerRef(currentValType,
3952 QString{v->c_str()});
3953 } else {
3954 currentGroup->add(currentValType, QString{v->c_str()});
3955 }
3956 }
3957 break;
3958 }
3959 case 1004: {
3960 if (currentGroup != nullptr) {
3961 if (v->type() == DRW_Variant::BINARY) {
3962 const auto *bytes = v->binary();
3963 if (bytes != nullptr) {
3964 currentGroup->add(
3965 currentValType,
3966 QByteArray(
3967 reinterpret_cast<const char *>(bytes->data()),
3968 static_cast<int>(bytes->size())));
3969 } else {
3970 currentGroup->add(currentValType, QByteArray{});
3971 }
3972 } else {
3973 // DXF round-trip path: 1004 arrives as a hex-encoded
3974 // string. Decode back to raw bytes so the in-memory
3975 // representation is consistent regardless of source.
3976 QByteArray hex(v->c_str());
3977 currentGroup->add(currentValType, QByteArray::fromHex(hex));
3978 }
3979 }
3980 break;
3981 }
3982 case 1005: {
3983 QString val = v->c_str();
3984 if (currentGroup != nullptr) {
3985 currentGroup->addRef(currentValType, val);
3986 }
3987 break;
3988 }
3989 case 1010:
3990 case 1011:
3991 case 1012:
3992 case 1013: {
3993 if (currentGroup != nullptr) {
3994 const auto coord = v->coord();
3995 // fixme - sand - review how actually coordinate is parsed, and why it's on several codes??
3996 if (coord != nullptr) {
3997 currentGroup->add(currentValType, RS_Vector(coord->x, coord->y, coord->z));
3998 }
3999 }
4000 break;
4001 }
4002 case 1070: // integer
4003 case 1071: {
4004 // long
4005 const int val = v->i_val();
4006 if (expectType) {
4007 // code of var
4008 currentValType = val;
4009 }
4010 else {
4011 // int field
4012 if (currentGroup != nullptr) {
4013 currentGroup->add(currentValType, val);
4014 }
4015 }
4016 break;
4017 }
4018 case 1040: // real
4019 case 1041: // distance
4020 case 1042: {
4021 // scale factor
4022 const double val = v->d_val();
4023 if (currentGroup != nullptr) {
4024 currentGroup->add(currentValType, val);
4025 }
4026 break;
4027 }
4028 default:
4029 break;
4030 }
4031 if (inTagsList) {
4032 expectType = !expectType;
4033 }
4034 }
4035 return result;
4036}
4037
4038bool RS_FilterDXFRW::shouldGenerateExtEntityData(const RS_Dimension* entity) {
4039 // todo - so far, we support only dimension style override as extension data.
4040 // however, that's logic may be expanded later and store, for example,
4041 // something like entity-specific meta information or so.
4042 return entity->getDimStyleOverride() != nullptr;
4043}
4044
4045QString RS_FilterDXFRW::toHexStr(const int n) {
4046 return QString::number(n, 16).toUpper();
4047}
4048
4049void RS_FilterDXFRW::addDimStyleOverrideToExtendedData(LC_ExtEntityData* extEntityData, LC_DimStyle* styleOverride) {
4050 if (styleOverride == nullptr) {
4051 return;
4052 }
4053 auto appData = extEntityData->addAppData("ACAD");
4054 auto group = appData->addGroup("DSTYLE");
4055
4056 auto arrowhead = styleOverride->arrowhead();
4057 auto linearFormat = styleOverride->linearFormat();
4058 auto extensionLine = styleOverride->extensionLine();
4059 auto dimensionLine = styleOverride->dimensionLine();
4060 auto text = styleOverride->text();
4061 auto tolerance = styleOverride->latteralTolerance();
4062 auto zerosSuppression = styleOverride->zerosSuppression();
4063 auto scaling = styleOverride->scaling();
4064 auto roundoff = styleOverride->roundOff();
4065 auto radial = styleOverride->radial();
4066 auto angularFormat = styleOverride->angularFormat();
4067 auto fractions = styleOverride->fractions();
4068 auto leader = styleOverride->leader();
4069 auto arc = styleOverride->arc();
4070
4071 auto savedModificationMode = linearFormat->getModifyCheckMode();
4072 // here we're interested only in actually modified fields
4073 styleOverride->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
4074
4075 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
4076 // $DIMPOST
4077 group->add(3, linearFormat->prefixOrSuffix());
4078 }
4079 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
4080 // $DIMAPOST
4081 group->add(4, linearFormat->altPrefixOrSuffix());
4082 }
4083 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
4084 // $DIMBLK
4085 // fixme - restore after test!
4086 // group->add(5, arrowhead->sameBlockName());
4087 }
4088 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
4089 // $DIMBLK1
4090 // fixme - restore after test!
4091 // group->add(6, arrowhead->arrowHeadBlockNameFirst());
4092 }
4093 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
4094 // $DIMBLK2
4095 // fixme - restore after test!
4096 // group->add(7, arrowhead->arrowHeadBlockNameSecond());
4097 }
4098 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
4099 // $DIMSCALE
4100 group->add(40, scaling->scale());
4101 }
4102 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
4103 // $DIMASZ
4104 group->add(41, arrowhead->size());
4105 }
4106 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
4107 // $DIMEXO
4108 group->add(42, extensionLine->distanceFromOriginPoint());
4109 }
4110 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
4111 // $DIMDLI
4112 group->add(43, dimensionLine->baseLineDimLinesSpacing());
4113 }
4114 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
4115 // $DIMEXE
4116 group->add(44, extensionLine->distanceBeyondDimLine());
4117 }
4118 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
4119 // $DIMRND
4120 group->add(45, roundoff->roundTo());
4121 }
4122 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
4123 // $DIMDLE
4124 group->add(46, dimensionLine->distanceBeyondExtLinesForObliqueStroke());
4125 }
4126 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTP)) {
4127 // $DIMDTP
4128 group->add(47, tolerance->upperToleranceLimit());
4129 }
4130 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
4131 // $DIMDTM
4132 group->add(48, tolerance->lowerToleranceLimit());
4133 }
4134 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
4135 // $DIMFXL
4136 group->add(49, extensionLine->fixedLength());
4137 }
4138 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
4139 // $DIMTXT
4140 group->add(140, text->height());
4141 }
4142 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
4143 // $DIMCEN
4144 group->add(141, radial->centerCenterMarkOrLineSize());
4145 }
4146 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
4147 // $DIMTSZ
4148 group->add(142, arrowhead->tickSize());
4149 }
4150 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
4151 // $DIMALTF
4152 group->add(143, linearFormat->altUnitsMultiplier());
4153 }
4154 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
4155 // $DIMLFAC
4156 group->add(144, scaling->linearFactor());
4157 }
4158 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
4159 // $DIMTVP
4160 group->add(145, text->verticalDistanceToDimLine());
4161 }
4162 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
4163 // $DIMTFAC
4164 group->add(146, tolerance->heightScaleFactorToDimText());
4165 }
4166 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
4167 // $DIMGAP
4168 group->add(147, dimensionLine->lineGap());
4169 }
4170 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
4171 // $DIMALTRND
4172 group->add(148, roundoff->altRoundTo());
4173 }
4174 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
4175 // $DIMTFILL
4176 group->add(69, text->backgroundFillMode());
4177 }
4178 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
4179 // $DIMTFILLCLR
4180 int colorRgb;
4181 int colorNumber = colorToNumber(text->explicitBackgroundFillColor(), &colorRgb);
4182 group->add(70, colorNumber);
4183 }
4184 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
4185 // $DIMTOL
4186 group->add(71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
4187 }
4188 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
4189 // $DIMLIM
4190 group->add(72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
4191 }
4192 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
4193 // $DIMTIH
4194 group->add(73, text->orientationInside());
4195 }
4196 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
4197 // $DIMTOH
4198 group->add(74, text->orientationOutside());
4199 }
4200 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
4201 // $DIMSE1
4202 group->add(75, extensionLine->suppressFirstLine());
4203 }
4204 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
4205 // $DIMSE2
4206 group->add(76, extensionLine->suppressSecondLine());
4207 }
4208 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
4209 // $DIMTAD
4210 group->add(77, text->verticalPositioning());
4211 }
4212 if (zerosSuppression->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMZIN)) {
4213 // $DIMZIN
4214 group->add(78, zerosSuppression->linearRaw());
4215 }
4216 if (zerosSuppression->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
4217 // $DIMAZIN
4218 group->add(79, zerosSuppression->angularRaw());
4219 }
4220 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
4221 // $DIMARCSYM
4222 group->add(90, arc->arcSymbolPosition());
4223 }
4224 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
4225 // $DIMALT
4226 group->add(170, linearFormat->alternateUnits());
4227 }
4228 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
4229 // $DIMALTD
4230 group->add(171, linearFormat->altDecimalPlaces());
4231 }
4232 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
4233 // $DIMTOFL
4234 group->add(172, dimensionLine->drawPolicyForOutsideText());
4235 }
4236 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
4237 // $DIMSAH
4238 group->add(173, arrowhead->isUseSeparateArrowHeads()); // fixme - check value
4239 }
4240 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
4241 // $DIMTIX
4242 group->add(174, text->extLinesRelativePlacement());
4243 }
4244 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
4245 // $DIMSOXD
4246 group->add(175, arrowhead->suppression()); // fixme - check value
4247 }
4248 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
4249 // $DIMCLRD
4250 int colorRgb;
4251 int colorNumber = colorToNumber(dimensionLine->color(), &colorRgb);
4252 group->add(176, colorNumber);
4253 }
4254 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
4255 // $DIMCLRE
4256 int colorRgb;
4257 int color = colorToNumber(extensionLine->color(), &colorRgb);
4258 group->add(177, color);
4259 }
4260 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
4261 // $DIMCLRT
4262 int colorRgb;
4263 int colorNumber = colorToNumber(text->color(), &colorRgb);
4264 group->add(178, colorNumber);
4265 }
4266 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
4267 // $DIMADEC
4268 group->add(179, angularFormat->decimalPlaces());
4269 }
4270 // case 270: // fixme - sand - obsolete DIMUNIT
4271 // result->linearFormat()->setUDecimalPlaces(var->getInt());
4272 // dimunit = reader->getInt32();
4273 // add("$DIMUNIT", code, dimunit);
4274 // break;
4275 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
4276 // $DIMDEC
4277 group->add(271, linearFormat->decimalPlaces());
4278 }
4279 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
4280 // $DIMTDEC
4281 group->add(272, tolerance->decimalPlaces());
4282 }
4283 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
4284 // $DIMALTU
4285 group->add(273, linearFormat->altFormat());
4286 }
4287 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
4288 // $DIMALTTD
4289 group->add(274, tolerance->decimalPlacesAltDim());
4290 }
4291 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
4292 // $DIMAUNIT
4293 group->add(275, angularFormat->format());
4294 }
4295 if (fractions->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
4296 // $DIMFRAC
4297 group->add(276, fractions->style());
4298 }
4299 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
4300 // $DIMLUNIT
4301 group->add(277, linearFormat->formatRaw());
4302 }
4303 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
4304 // $DIMDSEP
4305 group->add(278, linearFormat->decimalFormatSeparatorChar());
4306 }
4307 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
4308 // $DIMTMOVE
4309 group->add(279, text->positionMovementPolicy());
4310 }
4311 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
4312 // $DIMJUST
4313 group->add(280, text->horizontalPositioning());
4314 }
4315 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
4316 // $DIMSD1
4317 group->add(281, dimensionLine->suppressFirstLine());
4318 }
4319 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
4320 // $DIMSD2
4321 group->add(282, dimensionLine->suppressSecondLine());
4322 }
4323 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
4324 // $DIMTOLJ
4325 group->add(283, tolerance->verticalJustification());
4326 }
4327 if (zerosSuppression->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
4328 // $DIMTZIN
4329 group->add(284, zerosSuppression->toleranceRaw());
4330 }
4331 if (zerosSuppression->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
4332 // $DIMALTZ
4333 group->add(285, zerosSuppression->altLinearRaw());
4334 }
4335 if (zerosSuppression->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
4336 // $DIMALTTZ
4337 group->add(286, zerosSuppression->altToleranceRaw());
4338 }
4339 //case 287: // fixme - DIMFIT
4340 // dimfit = reader->getInt32();
4341 // add("$DIMFIT", code, dimfit);
4342 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
4343 // $DIMUPT
4344 group->add(288, text->cursorControlPolicy());
4345 }
4346 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
4347 // $DIMATFIT
4348 group->add(289, text->unsufficientSpacePolicy());
4349 }
4350 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
4351 // $DIMFXLON
4352 group->add(290, extensionLine->hasFixedLength() ? 1 : 0); // fixme - check
4353 }
4354 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
4355 // $DIMTXTDIRECTION
4356 group->add(292, text->readingDirection());
4357 }
4358 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
4359 // $DIMTXSTY
4360 group->add(340, text->style()); // fixme - ref to style?
4361 }
4362 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
4363 //DIMLDRBLK
4364 auto blockName = leader->arrowBlockName();
4365 if (!blockName.isEmpty()) {
4366 auto blkName = blockName.toStdString();
4367 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
4368 if (blkHandle > 0) {
4369 group->addRef(341, toHexStr(blkHandle));
4370 }
4371 }
4372 }
4373 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
4374 //$DIMBLK
4375 auto blockName = arrowhead->sameBlockName();
4376 if (!blockName.isEmpty()) {
4377 auto blkName = blockName.toStdString();
4378 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
4379 if (blkHandle > 0) {
4380 group->addRef(342, toHexStr(blkHandle));
4381 }
4382 }
4383 }
4384 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
4385 //$DIMBLK1
4386 auto blockName = arrowhead->arrowHeadBlockNameFirst();
4387 if (!blockName.isEmpty()) {
4388 auto blkName = blockName.toStdString();
4389 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
4390 if (blkHandle > 0) {
4391 group->addRef(343, toHexStr(blkHandle));
4392 }
4393 }
4394 }
4395
4396 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
4397 //$DIMBLK2
4398 auto blockName = arrowhead->arrowHeadBlockNameSecond();
4399 if (!blockName.isEmpty()) {
4400 auto blkName = blockName.toStdString();
4401 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
4402 if (blkHandle > 0) {
4403 group->addRef(344, toHexStr(blkHandle));
4404 }
4405 }
4406 }
4407 /*
4408 // case 345: // codes///
4409 // fixme - may this code be used for DIMLDRBLK?
4410 // dimblk2 = reader->getUtf8String();
4411 // add("$DIMBLK2", code, dimblk2);
4412 // break;
4413 */
4414
4415 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
4416 // $DIMLTYPE
4417 int lineTypeHandle = findLineTypeHandleToWrite(dimensionLine->lineTypeName());
4418 if (lineTypeHandle > 0) {
4419 auto handleStr = toHexStr(lineTypeHandle);
4420 group->addRef(345, handleStr);
4421 }
4422 }
4423 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
4424 // $DIMLTEX1
4425 int lineTypeHandle = findLineTypeHandleToWrite(extensionLine->lineTypeFirstRaw());
4426 if (lineTypeHandle > 0) {
4427 auto handleStr = toHexStr(lineTypeHandle);
4428 group->addRef(347, handleStr);
4429 }
4430 }
4431 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
4432 // $DIMLTEX2
4433 int lineTypeHandle = findLineTypeHandleToWrite(extensionLine->lineTypeSecondRaw());
4434 if (lineTypeHandle > 0) {
4435 auto handleStr = toHexStr(lineTypeHandle);
4436 group->addRef(348, handleStr);
4437 }
4438 }
4439 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
4440 // $DIMLWD
4441 auto lineWidth = dimensionLine->lineWidth();
4442 int lw = RS2::lineWidth2dxfInt(lineWidth);
4443 group->add(371, lw);
4444 }
4445 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
4446 // $DIMLWE
4447 auto lineWidth = extensionLine->lineWidth();
4448 int lw = RS2::lineWidth2dxfInt(lineWidth);
4449 group->add(372, lw);
4450 }
4451
4452 styleOverride->setModifyCheckMode(savedModificationMode);
4453}
4454
4455LC_DimStyle* RS_FilterDXFRW::parseDimStyleOverride(LC_ExtEntityData* extEntityData) const {
4456 if (extEntityData != nullptr) {
4457 auto dimStyleGroup = extEntityData->getGroupByName("ACAD", "DSTYLE");
4458 if (dimStyleGroup != nullptr) {
4459 auto dimStyleVariables = dimStyleGroup->getTagsList();
4460 if (dimStyleVariables->empty()) {
4461 return nullptr;
4462 }
4463 auto* result = new LC_DimStyle();
4464 auto arrowhead = result->arrowhead();
4465 auto linearFormat = result->linearFormat();
4466 auto extensionLine = result->extensionLine();
4467 auto dimensionLine = result->dimensionLine();
4468 auto text = result->text();
4469 auto tolerance = result->latteralTolerance();
4470 auto zerosSuppression = result->zerosSuppression();
4471 auto arc = result->arc();
4472
4473 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
4474 auto count = dimStyleVariables->size();
4475 for (size_t i = 0; i < count; i++) {
4476 LC_ExtDataTag* tag = dimStyleVariables->at(i);
4477 RS_Variable* var = tag->var();
4478 int code = var->getCode();
4479
4480 switch (code) {
4481 case 105:
4482 // handle = reader->getHandleString();
4483 break;
4484 case 3: // "$DIMPOST"
4485 linearFormat->setPrefixOrSuffix(var->getString());
4486 break;
4487 case 4: // "$DIMAPOST"
4488 linearFormat->setAltPrefixOrSuffix(var->getString());
4489 break;
4490 case 5: // $DIMBLK
4491 arrowhead->setSameBlockName(var->getString());
4492 break;
4493 case 6: // "$DIMBLK1"
4494 arrowhead->setArrowHeadBlockNameFirst(var->getString());
4495 break;
4496 case 7: // "$DIMBLK2"
4497 arrowhead->setArrowHeadBlockNameSecond(var->getString());
4498 break;
4499 case 40: // "$DIMSCALE"
4500 result->scaling()->setScale(var->getDouble());
4501 break;
4502 case 41: // "$DIMASZ"
4503 arrowhead->setSize(var->getDouble());
4504 break;
4505 case 42: // "$DIMEXO"
4506 extensionLine->setDistanceFromOriginPoint(var->getDouble());
4507 break;
4508 case 43: //"$DIMDLI"
4509 dimensionLine->setBaselineDimLinesSpacing(var->getDouble());
4510 break;
4511 case 44: //"$DIMEXE"
4512 extensionLine->setDistanceBeyondDimLine(var->getDouble());
4513 break;
4514 case 45: //$DIMRND
4515 result->roundOff()->setRoundToValue(var->getDouble());
4516 break;
4517 case 46: //$DIMDLE
4518 dimensionLine->setDistanceBeyondExtLinesForObliqueStroke(var->getDouble());
4519 break;
4520 case 47: //"$DIMTP"
4521 tolerance->setUpperToleranceLimit(var->getDouble());
4522 break;
4523 case 48: //"$DIMTM"
4524 tolerance->setLowerToleranceLimit(var->getDouble());
4525 break;
4526 case 49: //"$DIMFXL"
4527 extensionLine->setFixedLength(var->getDouble());
4528 break;
4529 case 140: // "$DIMTXT"
4530 text->setHeight(var->getDouble());
4531 break;
4532 case 141: // "$DIMCEN"
4533 result->radial()->setCenterMarkOrLineSize(var->getDouble());
4534 break;
4535 case 142: //"$DIMTSZ"
4536 arrowhead->setTickSize(var->getDouble());
4537 break;
4538 case 143: //"$DIMALTF"
4539 linearFormat->setAltUnitsMultiplier(var->getDouble());
4540 break;
4541 case 144: //"$DIMLFAC"
4542 result->scaling()->setLinearFactor(var->getDouble());
4543 break;
4544 case 145: //"$DIMTVP"
4545 text->setVerticalDistanceToDimLine(var->getDouble());
4546 break;
4547 case 146: //"$DIMTFAC"
4548 tolerance->setHeightScaleFactorToDimText(var->getDouble());
4549 break;
4550 case 147: //"$DIMGAP"
4551 dimensionLine->setLineGap(var->getDouble());
4552 break;
4553 case 148: //"$DIMALTRND"
4554 result->roundOff()->setAltRoundToValue(var->getDouble());
4555 break;
4556 case 69: // "$DIMTFILL"
4557 text->setBackgroundFillModeRaw(var->getInt());
4558 break;
4559 case 70: {
4560 //"$DIMTFILLCLR"
4561 RS_Color fillClr = numberToColor(var->getInt());
4562 text->setExplicitBackgroundFillColor(fillClr);
4563 break;
4564 }
4565 case 71: // "$DIMTOL"
4566 tolerance->setAppendTolerancesToDimText(var->getInt() == 1); // fixme - review
4567 break;
4568 case 72: //"$DIMLIM"
4569 tolerance->setLimitsAreGeneratedAsDefaultText(var->getInt() == 1); // fixme - review
4570 break;
4571 case 73: //"$DIMTIH"
4572 text->setOrientationInsideRaw(var->getInt());
4573 break;
4574 case 74: //"$DIMTOH"
4575 text->setOrientationOutsideRaw(var->getInt());
4576 break;
4577 case 75: //"$DIMSE1"
4578 extensionLine->setSuppressFirstRaw(var->getInt());
4579 break;
4580 case 76: //"$DIMSE2"
4581 extensionLine->setSuppressSecondRaw(var->getInt());
4582 break;
4583 case 77: //"$DIMTAD"
4584 text->setVerticalPositioningRaw(var->getInt());
4585 break;
4586 case 78: // "$DIMZIN"
4587 zerosSuppression->setLinearRaw(var->getInt());
4588 break;
4589 case 79: //"$DIMAZIN"
4590 zerosSuppression->setAngularRaw(var->getInt());
4591 break;
4592 case 170: //"$DIMALT"
4593 linearFormat->setAlternateUnitsRaw(var->getInt());
4594 break;
4595 case 171: //"$DIMALTD"
4596 linearFormat->setAltDecimalPlaces(var->getInt());
4597 break;
4598 case 172: //"$DIMTOFL"
4599 dimensionLine->setDrawPolicyForOutsideTextRaw(var->getInt());
4600 break;
4601 case 173: //"$DIMSAH"
4602 arrowhead->setUseSeparateArrowHeads(var->getInt()); // fixme - check value
4603 break;
4604 case 174: // "$DIMTIX"
4605 text->setExtLinesRelativePlacementRaw(var->getInt());
4606 break;
4607 case 175: //"$DIMSOXD"
4608 arrowhead->setSuppressionsRaw(var->getInt()); // fixme - check value
4609 break;
4610 case 176: {
4611 //"$DIMCLRD"
4612 RS_Color color = numberToColor(var->getInt());
4613 dimensionLine->setColor(color);
4614 break;
4615 }
4616 case 177: {
4617 //"$DIMCLRE"
4618 RS_Color color = numberToColor(var->getInt());
4619 extensionLine->setColor(color);
4620 break;
4621 }
4622 case 178: {
4623 //"$DIMCLRT"
4624 RS_Color color = numberToColor(var->getInt());
4625 text->setColor(color);
4626 break;
4627 }
4628 case 179: //"$DIMADEC"
4629 result->angularFormat()->setDecimalPlaces(var->getInt());
4630 break;
4631 case 270: // fixme - sand - obsolete DIMUNIT
4632 // result->linearFormat()->setUDecimalPlaces(var->getInt());
4633 // dimunit = reader->getInt32();
4634 // add("$DIMUNIT", code, dimunit);
4635 break;
4636 case 271: //"$DIMDEC"
4637 linearFormat->setDecimalPlaces(var->getInt());
4638 break;
4639 case 272: //"$DIMTDEC"
4640 tolerance->setDecimalPlaces(var->getInt());
4641 break;
4642 case 273: //"$DIMALTU"
4643 linearFormat->setAltFormatRaw(var->getInt());
4644 break;
4645 case 274: // "$DIMALTTD"
4646 tolerance->setDecimalPlacesAltDim(var->getInt());
4647 break;
4648 case 275: //"$DIMAUNIT"
4649 result->angularFormat()->setFormatRaw(var->getInt());
4650 break;
4651 case 276: // "$DIMFRAC"
4652 result->fractions()->setStyleRaw(var->getInt());
4653 break;
4654 case 277: // "$DIMLUNIT"
4655 linearFormat->setFormatRaw(var->getInt());
4656 break;
4657 case 278: //"$DIMDSEP"
4658 linearFormat->setDecimalFormatSeparatorChar(var->getInt());
4659 break;
4660 case 279: // "$DIMTMOVE"
4661 text->setPositionMovementPolicyRaw(var->getInt());
4662 break;
4663 case 280: // "$DIMJUST"
4664 text->setHorizontalPositioningRaw(var->getInt());
4665 break;
4666 case 281: // "$DIMSD1"
4667 dimensionLine->setSuppressFirstLineRaw(var->getInt());
4668 break;
4669 case 282: // "$DIMSD2"
4670 dimensionLine->setSuppressSecondLineRaw(var->getInt());
4671 break;
4672 case 283: // "$DIMTOLJ"
4673 tolerance->setVerticalJustificationRaw(var->getInt());
4674 break;
4675 case 284: // "$DIMTZIN"
4676 zerosSuppression->setToleranceRaw(var->getInt());
4677 break;
4678 case 285: // "$DIMALTZ"
4679 zerosSuppression->setAltLinearRaw(var->getInt());
4680 break;
4681 case 286: //"$DIMALTTZ"
4682 zerosSuppression->setAltToleranceRaw(var->getInt());
4683 break;
4684 case 287: // fixme - DIMFIT
4685 // dimfit = reader->getInt32();
4686 // add("$DIMFIT", code, dimfit);
4687 break;
4688 case 288: // "$DIMUPT"
4689 text->setCursorControlPolicyRaw(var->getInt());
4690 break;
4691 case 289: //"$DIMATFIT"
4692 text->setUnsufficientSpacePolicyRaw(var->getInt());
4693 break;
4694 case 290: // "$DIMFXLON"
4695 extensionLine->setHasFixedLength(var->getInt()); // fixme - check
4696 break;
4697 case 292: // "$DIMTXTDIRECTION"
4698 text->setReadingDirectionRaw(var->getInt());
4699 break;
4700 case 340: // "$DIMTXSTY"
4701 {
4702 auto dimtxsty = var->getString();
4703 prepareTextStyleName(dimtxsty);
4704 text->setStyle(dimtxsty); // fixme - ref to style?
4705 // fixme - ref to style?
4706 break;
4707 }
4708 case 341: {
4709 // "_$DIMLDRBLK"
4710 QString refHandleStr = var->getString();
4711 bool ok;
4712 int refHandle = refHandleStr.toInt(&ok, 16);
4713 if (ok) {
4714 QString blockName;
4715 ok = resolveBlockNameByHandle(refHandle, blockName);
4716 if (ok) {
4717 result->leader()->setArrowBlockName(blockName);
4718 }
4719 }
4720 else {
4721 // the string is not handle, but a direct name of the block.
4722 // fixme - DIMLDRBLK reading!
4723 // This is workaround for referring leader by name, not by block ref...
4724 // however, it may be not ACad compatible..
4725 // if (LC_DimArrowRegistry::isStandardBlockName(refHandleStr)) {
4726 // result->leader()->setArrowBlockName(refHandleStr);
4727 // }
4728 }
4729 break;
4730 }
4731 case 342: {
4732 // "_$DIMBLK"
4733 QString refHandleStr = var->getString();
4734 bool ok;
4735 int refHandle = refHandleStr.toInt(&ok, 16);
4736 if (ok) {
4737 QString blockName;
4738 ok = resolveBlockNameByHandle(refHandle, blockName);
4739 if (ok) {
4740 arrowhead->setSameBlockName(blockName);
4741 }
4742 }
4743 break;
4744 }
4745 case 343: {
4746 // "_$DIMBLK1"
4747 QString refHandleStr = var->getString();
4748 bool ok;
4749 int refHandle = refHandleStr.toInt(&ok, 16);
4750 if (ok) {
4751 QString blockName;
4752 ok = resolveBlockNameByHandle(refHandle, blockName);
4753 if (ok) {
4754 arrowhead->setArrowHeadBlockNameFirst(blockName);
4755 }
4756 }
4757 break;
4758 }
4759 case 344: {
4760 // "_$DIMBLK2"
4761 QString refHandleStr = var->getString();
4762 bool ok;
4763 int refHandle = refHandleStr.toInt(&ok, 16);
4764 if (ok) {
4765 QString blockName;
4766 ok = resolveBlockNameByHandle(refHandle, blockName);
4767 if (ok) {
4768 arrowhead->setArrowHeadBlockNameSecond(blockName);
4769 }
4770 }
4771 break;
4772 }
4773 // case 345: // codes///
4774 // fixme - may this code be used for DIMLDRBLK?
4775 // dimblk2 = reader->getUtf8String();
4776 // add("$DIMBLK2", code, dimblk2);
4777 // break;
4778 case 345: {
4779 // "$DIMLTYPE"
4780 auto refHandleStr = var->getString();
4781 bool ok;
4782 int refHandle = refHandleStr.toInt(&ok, 16);
4783 auto lineTypeName = m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
4784 if (!lineTypeName.empty()) {
4785 QString name = QString::fromStdString(lineTypeName);
4786 dimensionLine->setLineType(name);
4787 }
4788 break;
4789 }
4790 case 347: {
4791 // "$DIMLTEX1"
4792 auto refHandleStr = var->getString();
4793 bool ok;
4794 int refHandle = refHandleStr.toInt(&ok, 16);
4795 auto lineTypeName = m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
4796 if (!lineTypeName.empty()) {
4797 QString name = QString::fromStdString(lineTypeName);
4798 extensionLine->setLineTypeFirst(name);
4799 }
4800 break;
4801 }
4802 case 348: {
4803 //"$DIMLTEX2"
4804 auto refHandleStr = var->getString();
4805 bool ok;
4806 int refHandle = refHandleStr.toInt(&ok, 16);
4807 auto lineTypeName = m_dxfR->getReadingContext()->resolveLineTypeName(refHandle);
4808 if (!lineTypeName.empty()) {
4809 QString name = QString::fromStdString(lineTypeName);
4810 extensionLine->setLineTypeSecond(name);
4811 }
4812 break;
4813 }
4814 case 371: // "$DIMLWD"
4815 dimensionLine->setLineWidthRaw(var->getInt());
4816 break;
4817 case 372: //"$DIMLWE"
4818 extensionLine->setLineWidthRaw(var->getInt());
4819 break;
4820 case 90: //"$DIMARCSYM"
4821 arc->setArcSymbolPositionRaw(var->getInt());
4822 break;
4823 default:
4824 break;
4825 }
4826 }
4827
4828 // workaround for AutoCAD - it seems that later versions ignores DIMSAH and consider blocks for arrowhead to be
4829 // different if individual arrows are different. So we set the flag manually
4830 // fixme - check whether given condition is enough for properly setting the flag
4831 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
4832 if (!arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
4833 if (arrowhead->sameBlockName().isEmpty() && (arrowhead->arrowHeadBlockNameFirst() != arrowhead->
4834 arrowHeadBlockNameSecond())) {
4835 arrowhead->setUseSeparateArrowHeads(true);
4836 }
4837 }
4838
4839 return result;
4840 }
4841 }
4842 return nullptr;
4843}
4844
4845/**
4846 * Implementation of the method which handles
4847 * aligned dimensions (DIMENSION).
4848 */
4849void RS_FilterDXFRW::addDimAlign(const DRW_DimAligned* data) {
4850 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAligned");
4851 if (data == nullptr || m_currentContainer == nullptr) {
4852 return;
4853 }
4854
4855 const RS_DimensionData dimensionData = convDimensionData(data);
4856
4857 const RS_Vector ext1(data->getDef1Point().x, data->getDef1Point().y);
4858 const RS_Vector ext2(data->getDef2Point().x, data->getDef2Point().y);
4859
4860 const RS_DimAlignedData d(ext1, ext2);
4861 auto* entity = new RS_DimAligned(m_currentContainer, dimensionData, d);
4862 setEntityAttributes(entity, data);
4863 entity->updateDimPoint();
4864 entity->update();
4865 m_currentContainer->addEntity(entity);
4866}
4867
4868/**
4869 * Implementation of the method which handles
4870 * linear dimensions (DIMENSION).
4871 */
4872void RS_FilterDXFRW::addDimLinear(const DRW_DimLinear* data) {
4873 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLinear");
4874 if (data == nullptr || m_currentContainer == nullptr) {
4875 return;
4876 }
4877
4878 const RS_DimensionData dimensionData = convDimensionData(data);
4879
4880 const RS_Vector dxt1(data->getDef1Point().x, data->getDef1Point().y);
4881 const RS_Vector dxt2(data->getDef2Point().x, data->getDef2Point().y);
4882
4883 const RS_DimLinearData d(dxt1, dxt2, RS_Math::deg2rad(data->getAngle()), RS_Math::deg2rad(data->getOblique()));
4884
4885 const auto entity = new RS_DimLinear(m_currentContainer, dimensionData, d);
4886 setEntityAttributes(entity, data);
4887 entity->update();
4888 m_currentContainer->addEntity(entity);
4889}
4890
4891/**
4892 * Implementation of the method which handles
4893 * radial dimensions (DIMENSION).
4894 */
4895void RS_FilterDXFRW::addDimRadial(const DRW_DimRadial* data) {
4896 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimRadial");
4897 if (data == nullptr || m_currentContainer == nullptr) {
4898 return;
4899 }
4900
4901 const RS_DimensionData dimensionData = convDimensionData(data);
4902 const RS_Vector dp(data->getDiameterPoint().x, data->getDiameterPoint().y);
4903
4904 const RS_DimRadialData d(dp, data->getLeaderLength());
4905 const auto entity = new RS_DimRadial(m_currentContainer, dimensionData, d);
4906
4907 setEntityAttributes(entity, data);
4908 entity->update();
4909 m_currentContainer->addEntity(entity);
4910}
4911
4912/**
4913 * Implementation of the method which handles
4914 * diametric dimensions (DIMENSION).
4915 */
4916void RS_FilterDXFRW::addDimDiametric(const DRW_DimDiametric* data) {
4917 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimDiametric");
4918 if (data == nullptr || m_currentContainer == nullptr) {
4919 return;
4920 }
4921
4922 const RS_DimensionData dimensionData = convDimensionData(data);
4923 const RS_Vector dp(data->getDiameter1Point().x, data->getDiameter1Point().y);
4924
4925 const RS_DimDiametricData d(dp, data->getLeaderLength());
4926 const auto entity = new RS_DimDiametric(m_currentContainer, dimensionData, d);
4927
4928 setEntityAttributes(entity, data);
4929 entity->update();
4930 m_currentContainer->addEntity(entity);
4931}
4932
4933/**
4934 * Implementation of the method which handles
4935 * angular dimensions (DIMENSION).
4936 */
4937void RS_FilterDXFRW::addDimAngular(const DRW_DimAngular* data) {
4938 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular");
4939 if (data == nullptr || m_currentContainer == nullptr) {
4940 return;
4941 }
4942
4943 const RS_DimensionData dimensionData = convDimensionData(data);
4944 const RS_Vector dp1(data->getFirstLine1().x, data->getFirstLine1().y);
4945 const RS_Vector dp2(data->getFirstLine2().x, data->getFirstLine2().y);
4946 const RS_Vector dp3(data->getSecondLine1().x, data->getSecondLine1().y);
4947 const RS_Vector dp4(data->getDimPoint().x, data->getDimPoint().y);
4948
4949 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
4950
4951 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
4952
4953 setEntityAttributes(entity, data);
4954 entity->update();
4955 m_currentContainer->addEntity(entity);
4956}
4957
4958/**
4959 * Implementation of the method which handles
4960 * angular dimensions (DIMENSION).
4961 */
4962void RS_FilterDXFRW::addDimAngular3P(const DRW_DimAngular3p* data) {
4963 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular3P");
4964 if (data == nullptr || m_currentContainer == nullptr) {
4965 return;
4966 }
4967
4968 const auto& vertexPoint = data->getVertexPoint();
4969
4970 RS_DimensionData dimensionData = convDimensionData(data);
4971 const RS_Vector dp1(data->getFirstLine().x, data->getFirstLine().y);
4972 const RS_Vector dp2(data->getSecondLine().x, data->getSecondLine().y);
4973 const RS_Vector dp3(vertexPoint.x, vertexPoint.y);
4974 const RS_Vector dp4 = dimensionData.definitionPoint;
4975 dimensionData.definitionPoint = RS_Vector(vertexPoint.x, vertexPoint.y);
4976
4977 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
4978
4979 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
4980
4981 setEntityAttributes(entity, data);
4982 entity->update();
4983 m_currentContainer->addEntity(entity);
4984}
4985
4986void RS_FilterDXFRW::addDimArc(const DRW_DimArc* data) {
4987 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimArc");
4988 if (data == nullptr || m_currentContainer == nullptr) {
4989 return;
4990 }
4991 RS_DimensionData dd = convDimensionData(data);
4992 RS_Vector centre(data->getArcCenter().x, data->getArcCenter().y);
4993 double radius = dd.definitionPoint.distanceTo(centre);
4994 LC_DimArcData arcData(
4995 radius,
4996 radius * std::abs(data->arcEndAngle - data->arcStartAngle),
4997 centre,
4998 RS_Vector::polar(1.0, data->arcEndAngle),
4999 RS_Vector::polar(1.0, data->arcStartAngle)
5000 );
5001 arcData.arcSymbol = data->arcSymbol;
5002 arcData.isPartial = data->isPartial;
5003 arcData.hasLeader = data->hasLeader;
5004 arcData.leaderPt1 = RS_Vector(data->getLeaderPt1().x, data->getLeaderPt1().y);
5005 arcData.leaderPt2 = RS_Vector(data->leaderPt2.x, data->leaderPt2.y);
5006 auto dimEntity = new LC_DimArc(m_currentContainer, dd, arcData);
5007 setEntityAttributes(dimEntity, data);
5008 dimEntity->update();
5009 m_currentContainer->addEntity(dimEntity);
5010}
5011
5012void RS_FilterDXFRW::addDimOrdinate(const DRW_DimOrdinate* data) {
5013 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimOrdinate(const DL_DimensionData&, const DL_DimOrdinateData&) not yet implemented");
5014 const RS_DimensionData dimensionData = convDimensionData(data);
5015
5016 const RS_Vector featurePoint{data->getFirstLine().x, data->getFirstLine().y};
5017 const RS_Vector leaderEndPoint{data->getSecondLine().x, data->getSecondLine().y};
5018
5019 bool ordinateTypeForX = false;
5020 const int type = data->type;
5021 if (type & 64) {
5022 ordinateTypeForX = true;
5023 }
5024 const LC_DimOrdinateData d(featurePoint, leaderEndPoint, ordinateTypeForX);
5025 auto* entity = new LC_DimOrdinate(m_currentContainer, dimensionData, d);
5026 setEntityAttributes(entity, data);
5027 entity->update();
5028 m_currentContainer->addEntity(entity);
5029}
5030
5031/**
5032 * Implementation of the method which handles leader entities.
5033 */
5034void RS_FilterDXFRW::addLeader(const DRW_Leader* data) {
5035 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLeader");
5036 const RS_LeaderData d(data->arrow != 0, QString::fromUtf8(data->style.c_str()));
5037 const auto leader = new RS_Leader(m_currentContainer, d);
5038 setEntityAttributes(leader, data);
5039
5040 for (const auto& vert : data->vertexlist) {
5041 leader->addVertex({vert->x, vert->y});
5042 }
5043
5044 leader->update();
5045 m_currentContainer->addEntity(leader);
5046}
5047
5048namespace {
5049// Sample count for the cubic-NURBS → quadratic spline-points sampling branch
5050// in buildHatchSplineEdge. See plan §C.2.
5051constexpr int kHatchSplineSamples = 64;
5052} // namespace
5053
5054/**
5055 * Build an LC_SplinePoints from a DRW_Spline hatch-boundary edge.
5056 * All degrees converge to a quadratic spline-points representation:
5057 * degree==2 fit-points-only → pass-through as splinePoints
5058 * degree==2 control-net → pass-through as controlPoints
5059 * degree==1 or degree==3 → sample 64 points via a throwaway
5060 * RS_Spline evaluator
5061 * Returns nullptr on degenerate input. See plan §C.1-§C.3.
5062 */
5063LC_SplinePoints *
5064RS_FilterDXFRW::buildHatchSplineEdge(RS_EntityContainer *hatchLoop,
5065 const DRW_Spline *s) {
5066 if (s == nullptr)
5067 return nullptr;
5068 if (s->degree < 1 || s->degree > 3) {
5069 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
5070 "buildHatchSplineEdge: unsupported degree %d", s->degree);
5071 return nullptr;
5072 }
5073 if (s->controllist.empty() && s->fitlist.empty()) {
5074 RS_DEBUGRS_Debug::instance()->print(
5075 RS_Debug::D_WARNING,
5076 "buildHatchSplineEdge: spline edge has neither control nor fit points");
5077 return nullptr;
5078 }
5079
5080 // DXF entity-level splines store "closed" in bit 0; DWG hatch-boundary
5081 // streams store "periodic" in bit 1 and never set bit 0 (see
5082 // libraries/libdxfrw/src/drw_entities.cpp parseDwg at lines 2799-2800).
5083 // Either bit implies a closed curve.
5084 const bool closed = (s->flags & 0x3) != 0;
5085 LC_SplinePointsData sd(closed, /*cut*/ false);
5086
5087 if (s->degree == 2 && s->controllist.empty() && !s->fitlist.empty()) {
5088 // Branch 1: degree==2 with fit points only — pass through.
5089 sd.useControlPoints = false;
5090 for (const auto &fp : s->fitlist) {
5091 if (fp)
5092 sd.splinePoints.push_back(RS_Vector{fp->x, fp->y});
5093 }
5094 } else if (s->degree == 2 && !s->controllist.empty()) {
5095 // Branch 2: degree==2 with control net — pass through.
5096 sd.useControlPoints = true;
5097 for (const auto &cp : s->controllist) {
5098 if (cp)
5099 sd.controlPoints.push_back(RS_Vector{cp->x, cp->y});
5100 }
5101 } else {
5102 // Branch 3: degree==1 or degree==3. Sample the NURBS at 64 points
5103 // via a throwaway RS_Spline; the samples become fit points.
5104 // Fall back to treating control points as a polyline if the knot
5105 // vector is missing or short.
5106 const size_t needKnots = s->controllist.size() + size_t(s->degree) + 1;
5107 if (s->knotslist.size() < needKnots) {
5108 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
5109 "buildHatchSplineEdge: short knot vector (%zu < %zu); "
5110 "treating control points as polyline",
5111 s->knotslist.size(), needKnots);
5112 sd.useControlPoints = false;
5113 for (const auto &cp : s->controllist) {
5114 if (cp)
5115 sd.splinePoints.push_back(RS_Vector{cp->x, cp->y});
5116 }
5117 } else {
5118 RS_SplineData td(s->degree, closed);
5119 td.type = closed ? RS_SplineData::SplineType::Standard
5120 : RS_SplineData::SplineType::ClampedOpen;
5121 const double tolknot = (s->tolknot > 0.0) ? s->tolknot : 1e-7;
5122 for (double k : s->knotslist) {
5123 td.knotslist.push_back(RS_Math::round(k, tolknot));
5124 }
5125
5126 auto tmp = std::make_unique<RS_Spline>(nullptr, td);
5127 const bool isRational = (s->flags & 0x4) != 0;
5128 for (size_t i = 0; i < s->controllist.size(); ++i) {
5129 const auto &cp = s->controllist[i];
5130 if (!cp)
5131 continue;
5132 // DXF stores rational weights in weightlist; DWG hatch-
5133 // boundary stream stores them on controllist[i]->z. Check
5134 // both. See plan §C.3.
5135 double w = 1.0;
5136 if (isRational) {
5137 if (i < s->weightlist.size())
5138 w = s->weightlist[i];
5139 else
5140 w = cp->z;
5141 }
5142 tmp->addControlPointRaw({cp->x, cp->y}, w);
5143 }
5144 if (closed)
5145 tmp->setClosed(true);
5146 tmp->update();
5147
5148 sd.useControlPoints = false;
5149 sd.splinePoints.reserve(kHatchSplineSamples);
5150 tmp->fillStrokePoints(kHatchSplineSamples - 1, sd.splinePoints);
5151 }
5152 }
5153
5154 if (sd.splinePoints.size() < 2 && sd.controlPoints.size() < 2) {
5155 RS_DEBUGRS_Debug::instance()->print(
5156 RS_Debug::D_WARNING,
5157 "buildHatchSplineEdge: degenerate spline edge (too few points)");
5158 return nullptr;
5159 }
5160
5161 // Some DWG hatch boundaries encode a geometrically-closed spline with
5162 // both flag bits unset (and an accompanying 0-length line as a closure
5163 // marker). Detect endpoint coincidence in the populated point list and
5164 // flip the closed flag so LoopExtractor treats it as a single closed
5165 // loop instead of an open edge whose start==end. Closed LC_SplinePoints
5166 // expects a periodic point list without an explicit closing repeat —
5167 // drop the duplicate tail when present.
5168 if (!sd.closed) {
5169 if (!sd.splinePoints.empty() &&
5170 sd.splinePoints.front().distanceTo(sd.splinePoints.back()) <= 1e-8) {
5171 sd.closed = true;
5172 if (sd.splinePoints.size() > 2)
5173 sd.splinePoints.pop_back();
5174 } else if (!sd.controlPoints.empty() &&
5175 sd.controlPoints.front().distanceTo(sd.controlPoints.back()) <=
5176 1e-8) {
5177 sd.closed = true;
5178 if (sd.controlPoints.size() > 2)
5179 sd.controlPoints.pop_back();
5180 }
5181 }
5182
5183 return new LC_SplinePoints(hatchLoop, std::move(sd));
5184}
5185
5186/**
5187 * Snap LC_SplinePoints endpoints inside hatchLoop to neighboring line-like
5188 * edge endpoints when the gap is within 10× ENDPOINT_TOLERANCE (1e-7) but
5189 * non-zero. Boundary-stream float drift can leave ~1e-9 to ~1e-8 gaps that
5190 * break LoopExtractor's endpoint-chaining. The 10× window is small enough
5191 * that geometrically distinct edges never stitch.
5192 */
5193void RS_FilterDXFRW::snapSplineEdgeEndpoints(RS_EntityContainer *hatchLoop) {
5194 if (hatchLoop == nullptr || hatchLoop->count() < 2)
5195 return;
5196 constexpr double kSnapWindow = 1e-7; // 10× ENDPOINT_TOLERANCE
5197 const unsigned int n = hatchLoop->count();
5198
5199 auto setSplineEnd = [](LC_SplinePoints *sp, bool atFront,
5200 const RS_Vector &v) {
5201 auto &d = sp->getData();
5202 if (d.useControlPoints && !d.controlPoints.empty()) {
5203 if (atFront)
5204 d.controlPoints.front() = v;
5205 else
5206 d.controlPoints.back() = v;
5207 } else if (!d.splinePoints.empty()) {
5208 if (atFront)
5209 d.splinePoints.front() = v;
5210 else
5211 d.splinePoints.back() = v;
5212 }
5213 sp->update();
5214 };
5215
5216 for (unsigned int i = 0; i < n; ++i) {
5217 RS_Entity *cur = hatchLoop->entityAt(i);
5218 RS_Entity *nxt = hatchLoop->entityAt((i + 1) % n);
5219 if (cur == nullptr || nxt == nullptr)
5220 continue;
5221
5222 const bool curSpline = cur->rtti() == RS2::EntitySplinePoints;
5223 const bool nxtSpline = nxt->rtti() == RS2::EntitySplinePoints;
5224 if (curSpline == nxtSpline)
5225 continue; // line-line or spline-spline
5226
5227 const RS_Vector curEnd = cur->getEndpoint();
5228 const RS_Vector nxtBeg = nxt->getStartpoint();
5229 const double gap = curEnd.distanceTo(nxtBeg);
5230 if (gap <= 0.0 || gap >= kSnapWindow)
5231 continue;
5232
5233 if (curSpline) {
5234 setSplineEnd(static_cast<LC_SplinePoints *>(cur), /*atFront*/ false,
5235 nxtBeg);
5236 } else {
5237 setSplineEnd(static_cast<LC_SplinePoints *>(nxt), /*atFront*/ true,
5238 curEnd);
5239 }
5240 }
5241}
5242
5243/**
5244 * Implementation of the method which handles hatch entities.
5245 */
5246void RS_FilterDXFRW::addHatch(const DRW_Hatch* data) {
5247 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addHatch()");
5248 const auto hatch = new RS_Hatch(m_currentContainer,
5249 RS_HatchData(data->solid, data->scale, data->angle, QString::fromUtf8(data->name.c_str())));
5250 setEntityAttributes(hatch, data);
5251 if (data->basePoint.z != 0.0 || data->extPoint.x != 0.0
5252 || data->extPoint.y != 0.0 || data->extPoint.z != 1.0) {
5253 std::vector<std::shared_ptr<DRW_Variant>> payload;
5254 payload.push_back(std::make_shared<DRW_Variant>(
5255 1010, DRW_Coord(0.0, 0.0, data->basePoint.z)));
5256 payload.push_back(std::make_shared<DRW_Variant>(
5257 1011, DRW_Coord(data->extPoint.x, data->extPoint.y,
5258 data->extPoint.z)));
5259 appendTypeSidecar(hatch, kHatchExtrusionMarker, std::move(payload));
5260 }
5261 m_currentContainer->appendEntity(hatch);
5262
5263 for (size_t i = 0; i < data->looplist.size(); i++) {
5264 auto& loop = data->looplist.at(i);
5265 if ((loop->type & 32) == 32) {
5266 continue;
5267 }
5268 const auto hatchLoop = new RS_EntityContainer(hatch);
5269 hatchLoop->setLayer(nullptr);
5270 hatch->addEntity(hatchLoop);
5271
5272 RS_Entity* e = nullptr;
5273 if ((loop->type & 2) == 2){ //polyline, convert to lines & arcs
5274 if (loop->objlist.empty())
5275 continue;
5276 DRW_LWPolyline* pline = static_cast<DRW_LWPolyline*>(loop->objlist.at(0).get());
5277 RS_Polyline polyline{nullptr,
5278 RS_PolylineData(RS_Vector(false), RS_Vector(false), pline->flags)};
5279 for (auto const& vert: pline->vertlist) {
5280 polyline.addVertex(RS_Vector{vert->x, vert->y}, vert->bulge);
5281 }
5282
5283 for (const RS_Entity* e : lc::LC_ContainerTraverser{polyline, RS2::ResolveNone}.entities()) {
5284 // for (RS_Entity* e=polyline.firstEntity(); e; e=polyline.nextEntity()) {
5285 RS_Entity* tmp = e->clone();
5286 tmp->reparent(hatchLoop);
5287 tmp->setLayer(nullptr);
5288 hatchLoop->addEntity(tmp);
5289 }
5290 }
5291 else {
5292 RS_Entity* e = nullptr;
5293 for (unsigned int j = 0; j < loop->objlist.size(); j++) {
5294 e = nullptr;
5295 auto& ent = loop->objlist.at(j);
5296 switch (ent->eType) {
5297 case DRW::LINE: {
5298 const auto e2 = static_cast<DRW_Line*>(ent.get());
5299 e = new RS_Line{hatchLoop, {{e2->basePoint.x, e2->basePoint.y}, {e2->secPoint.x, e2->secPoint.y}}};
5300 break;
5301 }
5302 case DRW::ARC: {
5303 const auto e2 = static_cast<DRW_Arc*>(ent.get());
5304 if (e2->isccw && e2->staangle < 1.0e-6 && e2->endangle > RS_Math::deg2rad(360) - 1.0e-6) {
5305 e = new RS_Circle(hatchLoop, {{e2->basePoint.x, e2->basePoint.y}, e2->radious});
5306 }
5307 else {
5308 if (e2->isccw) {
5309 e = new RS_Arc(hatchLoop, RS_ArcData(RS_Vector(e2->basePoint.x, e2->basePoint.y), e2->radious,
5310 RS_Math::correctAngle(e2->staangle),
5311 RS_Math::correctAngle(e2->endangle), false));
5312 }
5313 else {
5314 e = new RS_Arc(hatchLoop, RS_ArcData(RS_Vector(e2->basePoint.x, e2->basePoint.y), e2->radious,
5315 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->staangle),
5316 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->endangle), true));
5317 }
5318 }
5319 break;
5320 }
5321 case DRW::ELLIPSE: {
5322 const auto e2 = static_cast<DRW_Ellipse*>(ent.get());
5323 double ang1 = e2->staparam;
5324 double ang2 = e2->endparam;
5325 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < 1.0e-10 && fabs(ang1) < 1.0e-10) {
5326 ang2 = 0.0;
5327 }
5328 else {
5329 //convert angle to parameter
5330 ang1 = atan(tan(ang1) / e2->ratio);
5331 ang2 = atan(tan(ang2) / e2->ratio);
5332 if (ang1 < 0) {
5333 //quadrant 2 & 4
5334 ang1 += M_PI3.14159265358979323846;
5335 if (e2->staparam > M_PI3.14159265358979323846) {
5336 //quadrant 4
5337 ang1 += M_PI3.14159265358979323846;
5338 }
5339 }
5340 else if (e2->staparam > M_PI3.14159265358979323846) {
5341 //3 quadrant
5342 ang1 += M_PI3.14159265358979323846;
5343 }
5344 if (ang2 < 0) {
5345 //quadrant 2 & 4
5346 ang2 += M_PI3.14159265358979323846;
5347 if (e2->endparam > M_PI3.14159265358979323846) {
5348 //quadrant 4
5349 ang2 += M_PI3.14159265358979323846;
5350 }
5351 }
5352 else if (e2->endparam > M_PI3.14159265358979323846) {
5353 //3 quadrant
5354 ang2 += M_PI3.14159265358979323846;
5355 }
5356 }
5357 e = new RS_Ellipse{
5358 hatchLoop,
5359 {{e2->basePoint.x, e2->basePoint.y}, {e2->secPoint.x, e2->secPoint.y}, e2->ratio, ang1, ang2, !e2->isccw}
5360 };
5361 break;
5362 }
5363 case DRW::SPLINE: {
5364 e = buildHatchSplineEdge(
5365 hatchLoop, static_cast<DRW_Spline *>(ent.get()));
5366 break;
5367 }
5368 default:
5369 break;
5370 }
5371 if (e != nullptr) {
5372 e->setLayer(nullptr);
5373 hatchLoop->addEntity(e);
5374 }
5375 }
5376 // After all explicit-segment edges are attached, snap spline
5377 // endpoints to neighboring line endpoints if a tiny float gap
5378 // is the only thing standing between LoopExtractor and a closed
5379 // chain. See plan §C.1.
5380 snapSplineEdgeEndpoints(hatchLoop);
5381 }
5382 }
5383
5384 RS_DEBUGRS_Debug::instance()->print("hatch->update()");
5385 if (hatch->validate()) {
5386 hatch->update();
5387 }
5388 else {
5389 m_graphic->removeEntity(hatch);
5390 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXFRW::endEntity(): updating hatch failed: invalid hatch area");
5391 }
5392}
5393
5394/**
5395 * Implementation of the method which handles image entities.
5396 */
5397void RS_FilterDXFRW::addImage(const DRW_Image* data) {
5398 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addImage");
5399 if (m_graphic != nullptr && data != nullptr)
5400 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, false);
5401
5402 if (data == nullptr)
5403 return;
5404
5405 const RS_Vector ip(data->basePoint.x, data->basePoint.y);
5406 const RS_Vector uv(data->secPoint.x, data->secPoint.y);
5407 const RS_Vector vv(data->vVector.x, data->vVector.y);
5408 const RS_Vector size(data->sizeu, data->sizev);
5409
5410 const auto image = new RS_Image(m_currentContainer,
5411 RS_ImageData(data->ref, ip, uv, vv, size, QString(), data->brightness, data->contrast, data->fade));
5412
5413 setEntityAttributes(image, data);
5414 if (data->basePoint.z != 0.0 || data->secPoint.z != 0.0
5415 || data->vVector.z != 0.0) {
5416 std::vector<std::shared_ptr<DRW_Variant>> payload;
5417 payload.push_back(std::make_shared<DRW_Variant>(
5418 1010, DRW_Coord(data->basePoint.x, data->basePoint.y,
5419 data->basePoint.z)));
5420 payload.push_back(std::make_shared<DRW_Variant>(
5421 1011, DRW_Coord(data->secPoint.x, data->secPoint.y,
5422 data->secPoint.z)));
5423 payload.push_back(std::make_shared<DRW_Variant>(
5424 1012, DRW_Coord(data->vVector.x, data->vVector.y,
5425 data->vVector.z)));
5426 appendTypeSidecar(image, kImageFrameMarker, std::move(payload));
5427 }
5428 m_currentContainer->appendEntity(image);
5429}
5430
5431/**
5432 * Implementation of the method which handles WIPEOUT entities.
5433 *
5434 * WIPEOUT shares the IMAGE binary layout but only the polygon (clipPath) and
5435 * the IMAGE's frame parameters (basePoint, secPoint=uVector, vVector,
5436 * sizeu/sizev) are meaningful — there's no raster file behind it.
5437 *
5438 * Group 11/12 are single-pixel U/V vectors. With the half-pixel origin
5439 * offset, the WCS transform is:
5440 * P_wcs = basePoint + (px + 0.5) * uVector + (py + 0.5) * vVector.
5441 * The clip coordinates already express the image size; sizeu/sizev must not
5442 * multiply the vectors again.
5443 */
5444void RS_FilterDXFRW::addWipeout(const DRW_Wipeout *data) {
5445 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addWipeout");
5446 if (m_graphic != nullptr && data != nullptr)
5447 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, true);
5448 if (data == nullptr || data->clipPath.empty()) {
5449 return;
5450 }
5451
5452 LC_WipeoutData wipeoutData;
5453 wipeoutData.hasNativeFrame = true;
5454 wipeoutData.insertionPoint =
5455 RS_Vector(data->basePoint.x, data->basePoint.y, data->basePoint.z);
5456 wipeoutData.uPixel =
5457 RS_Vector(data->secPoint.x, data->secPoint.y, data->secPoint.z);
5458 wipeoutData.vPixel =
5459 RS_Vector(data->vVector.x, data->vVector.y, data->vVector.z);
5460 wipeoutData.sizeU = data->sizeu;
5461 wipeoutData.sizeV = data->sizev;
5462 wipeoutData.displayProps = data->m_displayProps;
5463 wipeoutData.imageDefHandle = data->ref;
5464 wipeoutData.imageDefReactorHandle = data->m_imageDefReactorHandle;
5465 wipeoutData.clip = data->clip;
5466 wipeoutData.brightness = data->brightness;
5467 wipeoutData.contrast = data->contrast;
5468 wipeoutData.fade = data->fade;
5469 wipeoutData.clipBoundaryType = data->m_clipBoundaryType;
5470 wipeoutData.clipMode = data->clipMode;
5471 wipeoutData.clipPath.reserve(data->clipPath.size());
5472 for (const DRW_Coord &clipPoint : data->clipPath)
5473 wipeoutData.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
5474
5475 auto w = std::make_unique<LC_Wipeout>(m_currentContainer,
5476 std::move(wipeoutData));
5477 setEntityAttributes(w.get(), data);
5478 m_currentContainer->appendEntity(w.release());
5479}
5480
5481/**
5482 * Build an LC_MLeader from a parsed DRW_MLeader (ODA spec §20.4.48).
5483 *
5484 * MLEADER carries a multi-root callout structure plus either text or block
5485 * content. This conversion captures the geometric structure (roots →
5486 * leader lines → points) and content reference. Style-handle resolution
5487 * (against LC_MLeaderStyleList) is still metadata-only, but we preserve the
5488 * DWG handle references needed for native re-export.
5489 */
5490void RS_FilterDXFRW::addMLeader(const DRW_MLeader *data) {
5491 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeader");
5492 if (data == nullptr)
5493 return;
5494 if (m_graphic != nullptr) {
5495 m_graphic->dwgAdvancedMetadata().addMLeader(*data);
5496 }
5497
5498 LC_MLeaderData md;
5499 md.roots.reserve(data->context.roots.size());
5500 for (const auto &r : data->context.roots) {
5501 LC_MLeaderRoot root;
5502 root.connectionPoint = RS_Vector(r.connectionPoint.x, r.connectionPoint.y);
5503 root.direction = RS_Vector(r.direction.x, r.direction.y);
5504 root.landingDistance = r.landingDistance;
5505 root.attachmentDirection = r.attachmentDirection;
5506 root.leaderLines.reserve(r.leaderLines.size());
5507 for (const auto &ll : r.leaderLines) {
5508 LC_MLeaderLine line;
5509 line.leaderLineIndex = ll.leaderLineIndex;
5510 line.points.reserve(ll.points.size());
5511 for (const auto &p : ll.points) {
5512 line.points.emplace_back(p.x, p.y);
5513 }
5514 root.leaderLines.push_back(std::move(line));
5515 }
5516 md.roots.push_back(std::move(root));
5517 }
5518
5519 md.contentBasePoint = RS_Vector(data->context.contentBasePoint.x,
5520 data->context.contentBasePoint.y);
5521 md.basePoint =
5522 RS_Vector(data->context.basePoint.x, data->context.basePoint.y);
5523
5524 md.hasTextContents = data->context.hasTextContents;
5525 md.hasBlockContents =
5526 !data->context.hasTextContents && data->context.hasContentsBlock;
5527 if (md.hasTextContents) {
5528 md.textLabel =
5529 toNativeString(QString::fromUtf8(data->context.textLabel.c_str()));
5530 md.textLocation =
5531 RS_Vector(data->context.textLocation.x, data->context.textLocation.y);
5532 md.textHeight = data->context.textHeight;
5533 md.textRotation = data->context.textRotation;
5534 md.boundaryWidth = data->context.boundaryWidth;
5535 md.boundaryHeight = data->context.boundaryHeight;
5536 md.textColor = data->context.textColor;
5537 }
5538 if (md.hasBlockContents) {
5539 md.blockLocation =
5540 RS_Vector(data->context.blockLocation.x, data->context.blockLocation.y);
5541 md.blockScale =
5542 RS_Vector(data->context.blockScale.x, data->context.blockScale.y,
5543 data->context.blockScale.z);
5544 md.blockRotation = data->context.blockRotation;
5545 // Resolve the content block handle -> block name so LC_MLeader can render
5546 // the block symbol the leader points at. The context's
5547 // blockTableRecordHandle is authoritative; fall back to the entity-level
5548 // style block handle.
5549 std::uint32_t blockHandle = data->context.blockTableRecordHandle.ref != 0
5550 ? data->context.blockTableRecordHandle.ref
5551 : data->styleBlockHandle.ref;
5552 if (blockHandle != 0)
5553 resolveBlockNameByHandle(blockHandle, md.blockName);
5554 }
5555
5556 md.leaderType = data->leaderType;
5557 md.leaderColor = data->leaderColor;
5558 md.landingDistance = data->landingDistance;
5559 md.arrowSize = data->defaultArrowHeadSize;
5560 md.landingEnabled = data->landingEnabled;
5561 md.doglegEnabled = data->doglegEnabled;
5562 md.contentType = data->styleContentType;
5563 md.scaleFactor = data->scaleFactor;
5564 md.dwgStyleHandle = data->styleHandle.ref;
5565 md.dwgLeaderLineTypeHandle = data->leaderLineTypeHandle.ref;
5566 md.dwgArrowHeadHandle = data->arrowHeadHandle.ref;
5567 md.dwgTextStyleHandle = data->styleTextStyleHandle.ref != 0
5568 ? data->styleTextStyleHandle.ref
5569 : data->context.textStyleHandle.ref;
5570 md.dwgBlockHandle = data->styleBlockHandle.ref != 0
5571 ? data->styleBlockHandle.ref
5572 : data->context.blockTableRecordHandle.ref;
5573
5574 auto m = std::make_unique<LC_MLeader>(m_currentContainer, std::move(md));
5575 setEntityAttributes(m.get(), data);
5576 m_currentContainer->appendEntity(m.release());
5577}
5578
5579/**
5580 * MLEADERSTYLE dictionary entry capture. Styles are kept in the DWG advanced
5581 * metadata store for future style resolution and raw/native round-trip work;
5582 * the current LC_MLeader entity still copies effective scalar values from the
5583 * entity itself.
5584 */
5585void RS_FilterDXFRW::addMLeaderStyle(const DRW_MLeaderStyle *data) {
5586 if (data == nullptr)
5587 return;
5588 if (m_graphic != nullptr) {
5589 m_graphic->dwgAdvancedMetadata().addMLeaderStyle(*data);
5590 }
5591 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeaderStyle: %s",
5592 data->name.empty() ? "(unnamed)" : data->name.c_str());
5593}
5594
5595void RS_FilterDXFRW::addDetailViewStyle(const DRW_DetailViewStyle &data) {
5596 if (m_graphic != nullptr) {
5597 m_graphic->dwgAdvancedMetadata().addDetailViewStyle(data);
5598 }
5599 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDetailViewStyle: %s",
5600 data.m_modelDoc.m_displayName.empty()
5601 ? data.m_modelDoc.m_description.c_str()
5602 : data.m_modelDoc.m_displayName.c_str());
5603}
5604
5605void RS_FilterDXFRW::addSectionViewStyle(const DRW_SectionViewStyle &data) {
5606 if (m_graphic != nullptr) {
5607 m_graphic->dwgAdvancedMetadata().addSectionViewStyle(data);
5608 }
5609 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSectionViewStyle: %s",
5610 data.m_modelDoc.m_displayName.empty()
5611 ? data.m_modelDoc.m_description.c_str()
5612 : data.m_modelDoc.m_displayName.c_str());
5613}
5614
5615void RS_FilterDXFRW::addBreakData(const DRW_BreakData &data) {
5616 if (m_graphic != nullptr) {
5617 m_graphic->dwgAdvancedMetadata().addBreakData(data);
5618 }
5619 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakData: %d refs",
5620 static_cast<int>(data.m_pointRefHandles.size()));
5621}
5622
5623void RS_FilterDXFRW::addBreakPointRef(const DRW_BreakPointRef &data) {
5624 if (m_graphic != nullptr) {
5625 m_graphic->dwgAdvancedMetadata().addBreakPointRef(data);
5626 }
5627 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakPointRef: %d",
5628 static_cast<int>(data.handle));
5629}
5630
5631void RS_FilterDXFRW::addGroup(const DRW_Group &data) {
5632 if (m_graphic != nullptr) {
5633 m_graphic->dwgAdvancedMetadata().addGroup(data);
5634 }
5635 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGroup: %s (%d handles)",
5636 data.m_description.empty() ? "(unnamed)" : data.m_description.c_str(),
5637 static_cast<int>(data.m_entityHandles.size()));
5638}
5639
5640void RS_FilterDXFRW::addImageDefinitionReactor(const DRW_ImageDefinitionReactor &data) {
5641 if (m_graphic != nullptr) {
5642 m_graphic->dwgAdvancedMetadata().addImageDefinitionReactor(data);
5643 }
5644 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addImageDefinitionReactor: class version %d",
5645 static_cast<int>(data.m_classVersion));
5646}
5647
5648void RS_FilterDXFRW::addRasterVariables(const DRW_RasterVariables &data) {
5649 if (m_graphic != nullptr) {
5650 m_graphic->dwgAdvancedMetadata().addRasterVariables(data);
5651 }
5652 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addRasterVariables: frame %d quality %d units %d",
5653 data.m_imageFrame,
5654 data.m_imageQuality,
5655 data.m_units);
5656}
5657
5658void RS_FilterDXFRW::addSpatialFilter(const DRW_SpatialFilter &data) {
5659 if (m_graphic != nullptr) {
5660 m_graphic->dwgAdvancedMetadata().addSpatialFilter(data);
5661 }
5662 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialFilter: %d boundary points",
5663 static_cast<int>(data.m_boundaryPoints.size()));
5664}
5665
5666void RS_FilterDXFRW::addGeoData(const DRW_GeoData &data) {
5667 if (m_graphic != nullptr) {
5668 m_graphic->dwgAdvancedMetadata().addGeoData(data);
5669 }
5670 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoData: version %d",
5671 static_cast<int>(data.m_version));
5672}
5673
5674void RS_FilterDXFRW::addTableGeometry(const DRW_TableGeometry &data) {
5675 if (m_graphic != nullptr) {
5676 m_graphic->dwgAdvancedMetadata().addTableGeometry(data);
5677 }
5678 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableGeometry: %d x %d",
5679 static_cast<int>(data.m_rowCount),
5680 static_cast<int>(data.m_columnCount));
5681}
5682
5683void RS_FilterDXFRW::addTableStyle(const DRW_TableStyle &data) {
5684 if (m_graphic != nullptr) {
5685 m_graphic->dwgAdvancedMetadata().addTableStyle(data);
5686 }
5687 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableStyle: %s",
5688 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str());
5689}
5690
5691void RS_FilterDXFRW::addTableContent(const DRW_TableContentObject &data) {
5692 if (m_graphic != nullptr) {
5693 m_graphic->dwgAdvancedMetadata().addTableContent(data);
5694 }
5695 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableContent: %d x %d",
5696 static_cast<int>(data.m_content.m_rows.size()),
5697 static_cast<int>(data.m_content.m_columns.size()));
5698}
5699
5700void RS_FilterDXFRW::addObjectContextData(const DRW_ObjectContextData &data) {
5701 if (m_graphic != nullptr) {
5702 m_graphic->dwgAdvancedMetadata().addObjectContextData(data);
5703 }
5704 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addObjectContextData: %s handle %d scale %d",
5705 data.m_recordName.empty() ? "(object-context)" : data.m_recordName.c_str(),
5706 static_cast<int>(data.handle),
5707 static_cast<int>(data.m_scaleHandle));
5708}
5709
5710void RS_FilterDXFRW::addCellStyleMap(const DRW_CellStyleMap &data) {
5711 if (m_graphic != nullptr) {
5712 m_graphic->dwgAdvancedMetadata().addCellStyleMap(data);
5713 }
5714 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCellStyleMap: %d styles",
5715 static_cast<int>(data.m_cellStyles.size()));
5716}
5717
5718void RS_FilterDXFRW::addRawDxfObject(const DRW_RawDxfObject &data) {
5719 if (m_graphic != nullptr) {
5720 m_graphic->dwgAdvancedMetadata().addRawDxfObject(data);
5721 }
5722}
5723
5724void RS_FilterDXFRW::addRawDxfEntity(const DRW_RawDxfObject &data) {
5725 if (m_graphic != nullptr) {
5726 m_graphic->dwgAdvancedMetadata().addRawDxfEntity(data);
5727 }
5728}
5729
5730void RS_FilterDXFRW::addDxfClass(const DRW_Class &data) {
5731 if (m_graphic != nullptr) {
5732 m_graphic->dwgAdvancedMetadata().addDxfClass(data);
5733 }
5734}
5735
5736void RS_FilterDXFRW::addUnsupportedObject(const DRW_UnsupportedObject &data) {
5737 if (m_graphic != nullptr) {
5738 m_graphic->dwgAdvancedMetadata().addUnsupportedObject(data);
5739 // Cross-read P1: Navisworks (and related) stay metadata-only until typed
5740 // DRW models land; classify by CLASSES record name so they are not silent.
5741 const std::string &rn = data.m_recordName;
5742 if (rn.find("NAVISWORKS") != std::string::npos) {
5743 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5744 rn.empty() ? "NAVISWORKSMODEL" : rn, data.m_handle, 0,
5745 "metadata-only unsupported shell");
5746 }
5747 }
5748 m_unsupportedDwgObjects.push_back(data);
5749 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addUnsupportedObject: %s handle %d (%d bytes)",
5750 data.m_recordName.empty() ? "(fixed)" : data.m_recordName.c_str(),
5751 static_cast<int>(data.m_handle),
5752 static_cast<int>(data.m_rawBytes.size()));
5753}
5754
5755void RS_FilterDXFRW::addRawDwgSection(const DRW_RawDwgSection &data) {
5756 if (m_graphic != nullptr) {
5757 m_graphic->dwgAdvancedMetadata().addRawDwgSection(data);
5758 }
5759 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addRawDwgSection: %s (%d bytes)",
5760 data.m_name.c_str(), static_cast<int>(data.m_data.size()));
5761}
5762
5763void RS_FilterDXFRW::addDataStorage(const DRW_DataStorageSection &data) {
5764 if (m_graphic != nullptr) {
5765 m_graphic->dwgAdvancedMetadata().addDataStorage(data);
5766 }
5767 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataStorage: %s records=%d",
5768 data.m_name.c_str(), static_cast<int>(data.records.size()));
5769}
5770
5771void RS_FilterDXFRW::addAcDbPlaceholder(const DRW_AcDbPlaceholder &data) {
5772 if (m_graphic != nullptr) {
5773 m_graphic->dwgAdvancedMetadata().addAcDbPlaceholder(data);
5774 }
5775 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcDbPlaceholder: %d",
5776 static_cast<int>(data.handle));
5777}
5778
5779void RS_FilterDXFRW::addSun(const DRW_Sun &data) {
5780 if (m_graphic != nullptr) {
5781 m_graphic->dwgAdvancedMetadata().addSun(data);
5782 }
5783 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSun: handle %d on=%d",
5784 static_cast<int>(data.handle),
5785 data.m_isOn ? 1 : 0);
5786}
5787
5788// ── Cross-read parity: metadata-only family exposure (no RS document geometry) ──
5789void RS_FilterDXFRW::addPointCloud(const DRW_PointCloud *data) {
5790 if (m_graphic != nullptr && data != nullptr) {
5791 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5792 "POINTCLOUD", data->handle, data->parentHandle, data->savedFilename);
5793 }
5794 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloud: metadata-only handle %d",
5795 data ? static_cast<int>(data->handle) : 0);
5796}
5797
5798void RS_FilterDXFRW::addPointCloudEx(const DRW_PointCloudEx *data) {
5799 if (m_graphic != nullptr && data != nullptr) {
5800 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5801 "POINTCLOUDEX", data->handle, data->parentHandle, data->name);
5802 }
5803 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudEx: metadata-only handle %d",
5804 data ? static_cast<int>(data->handle) : 0);
5805}
5806
5807void RS_FilterDXFRW::addPointCloudDef(const DRW_PointCloudDef &data) {
5808 if (m_graphic != nullptr) {
5809 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5810 "POINTCLOUDDEF", data.handle, data.parentHandle, data.m_sourceFilename);
5811 }
5812 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudDef: kind metadata handle %d",
5813 static_cast<int>(data.handle));
5814}
5815
5816void RS_FilterDXFRW::addBackground(const DRW_Background &data) {
5817 if (m_graphic != nullptr) {
5818 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5819 "BACKGROUND", data.handle, data.parentHandle);
5820 }
5821}
5822
5823void RS_FilterDXFRW::addMaterial(const DRW_Material &data) {
5824 if (m_graphic != nullptr) {
5825 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5826 "MATERIAL", data.handle, data.parentHandle, data.m_name);
5827 }
5828}
5829
5830void RS_FilterDXFRW::addRenderSettings(const DRW_RenderSettings &data) {
5831 if (m_graphic != nullptr) {
5832 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5833 "RENDERSETTINGS", data.handle, data.parentHandle);
5834 }
5835}
5836
5837void RS_FilterDXFRW::addSunStudy(const DRW_SunStudy &data) {
5838 if (m_graphic != nullptr) {
5839 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5840 "SUNSTUDY", data.handle, data.parentHandle);
5841 }
5842}
5843
5844void RS_FilterDXFRW::addDbColor(const DRW_DbColor &data) {
5845 if (m_graphic != nullptr) {
5846 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5847 "DBCOLOR", data.handle, data.parentHandle);
5848 }
5849}
5850
5851void RS_FilterDXFRW::addDimensionAssociation(const DRW_DimensionAssociation &data) {
5852 if (m_graphic != nullptr) {
5853 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5854 "DIMASSOC", data.handle, data.parentHandle);
5855 }
5856}
5857
5858void RS_FilterDXFRW::addEvaluationGraph(const DRW_EvaluationGraph &data) {
5859 if (m_graphic != nullptr) {
5860 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5861 "EVALUATION_GRAPH", data.handle, data.parentHandle);
5862 }
5863}
5864
5865void RS_FilterDXFRW::addSection(const DRW_Section &data) {
5866 if (m_graphic != nullptr) {
5867 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5868 "SECTION", data.handle, data.parentHandle);
5869 }
5870}
5871
5872void RS_FilterDXFRW::addSectionObject(const DRW_SectionObject &data) {
5873 if (m_graphic != nullptr) {
5874 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5875 "SECTIONOBJECT", data.handle, data.parentHandle);
5876 }
5877}
5878
5879void RS_FilterDXFRW::addMPolygon(const DRW_MPolygon *data) {
5880 // Prefer hatch rendering path for geometry, but also record metadata exposure.
5881 if (m_graphic != nullptr && data != nullptr) {
5882 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
5883 "MPOLYGON", data->handle, data->parentHandle);
5884 }
5885 addHatch(data);
5886}
5887
5888void RS_FilterDXFRW::addDictionary(const DRW_Dictionary &data) {
5889 if (m_graphic != nullptr) {
5890 m_graphic->dwgAdvancedMetadata().addDictionary(data);
5891 }
5892 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionary: handle %d entries=%d",
5893 static_cast<int>(data.handle),
5894 static_cast<int>(data.m_entries.size()));
5895}
5896
5897void RS_FilterDXFRW::addXRecord(const DRW_XRecord &data) {
5898 if (m_graphic != nullptr) {
5899 m_graphic->dwgAdvancedMetadata().addXRecord(data);
5900 }
5901 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addXRecord: handle %d values=%d",
5902 static_cast<int>(data.handle),
5903 static_cast<int>(data.m_values.size()));
5904}
5905
5906void RS_FilterDXFRW::addLayout(const DRW_Layout &data) {
5907 if (m_graphic != nullptr) {
5908 m_graphic->dwgAdvancedMetadata().addLayout(data);
5909 // PR 11 — populate activeLayoutHandle on first import. AutoCAD uses
5910 // tabOrder=0 for the default-visible paper-space layout; if no layout
5911 // carries tabOrder=0 (rare — most DWGs have one), fall back to the
5912 // first layout added so the UI still has a non-zero handle to display.
5913 // setActiveLayoutHandle bumps the modified flag only on actual change,
5914 // so re-loading the same DWG idempotently leaves the dirty state alone.
5915 if (m_graphic->activeLayoutHandle() == 0u && data.handle != 0u) {
5916 const bool isDefaultTab = data.tabOrder == 0;
5917 const bool isFirstLayout = m_graphic->layouts().size() == 1;
5918 if (isDefaultTab || isFirstLayout) {
5919 m_graphic->setActiveLayoutHandle(data.handle);
5920 }
5921 }
5922 }
5923 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayout: handle %d name=%s viewports=%d",
5924 static_cast<int>(data.handle),
5925 data.name.c_str(),
5926 static_cast<int>(data.viewportHandles.size()));
5927}
5928
5929// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
5930// (≥ 500); the filter routes each call into round-trip-grade metadata
5931// storage so RS_FilterDXFRW::writeObjects can dispatch the native writer.
5932void RS_FilterDXFRW::addScale(const DRW_Scale &data) {
5933 if (m_graphic != nullptr) {
5934 m_graphic->dwgAdvancedMetadata().addScale(data);
5935 }
5936 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addScale: handle %d name=%s factor=%f",
5937 static_cast<int>(data.handle),
5938 data.name.c_str(),
5939 data.scaleFactor());
5940}
5941
5942void RS_FilterDXFRW::addIDBuffer(const DRW_IDBuffer &data) {
5943 if (m_graphic != nullptr) {
5944 m_graphic->dwgAdvancedMetadata().addIDBuffer(data);
5945 }
5946 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addIDBuffer: handle %d ids=%d",
5947 static_cast<int>(data.handle),
5948 static_cast<int>(data.objIds.size()));
5949}
5950
5951void RS_FilterDXFRW::addLayerIndex(const DRW_LayerIndex &data) {
5952 if (m_graphic != nullptr) {
5953 m_graphic->dwgAdvancedMetadata().addLayerIndex(data);
5954 }
5955 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayerIndex: handle %d entries=%d",
5956 static_cast<int>(data.handle),
5957 static_cast<int>(data.entries.size()));
5958}
5959
5960void RS_FilterDXFRW::addSpatialIndex(const DRW_SpatialIndex &data) {
5961 if (m_graphic != nullptr) {
5962 m_graphic->dwgAdvancedMetadata().addSpatialIndex(data);
5963 }
5964 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialIndex: handle %d ts=%u/%u",
5965 static_cast<int>(data.handle),
5966 static_cast<unsigned>(data.timestamp1),
5967 static_cast<unsigned>(data.timestamp2));
5968}
5969
5970void RS_FilterDXFRW::addDictionaryVar(const DRW_DictionaryVar &data) {
5971 if (m_graphic != nullptr) {
5972 m_graphic->dwgAdvancedMetadata().addDictionaryVar(data);
5973 }
5974 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryVar: handle %d schema=%d",
5975 static_cast<int>(data.handle),
5976 data.m_schema);
5977}
5978
5979// PR 8d.2b — four larger no-storage OBJECTS families. All custom-class
5980// (513-516); filter routes each call into round-trip-grade metadata storage
5981// so RS_FilterDXFRW::writeObjects can dispatch the native writer.
5982void RS_FilterDXFRW::addDictionaryWithDefault(const DRW_DictionaryWithDefault &data) {
5983 if (m_graphic != nullptr) {
5984 m_graphic->dwgAdvancedMetadata().addDictionaryWithDefault(data);
5985 }
5986 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryWithDefault: handle %d entries=%d default=%d",
5987 static_cast<int>(data.handle),
5988 static_cast<int>(data.m_entries.size()),
5989 static_cast<int>(data.m_defaultEntryHandle));
5990}
5991
5992void RS_FilterDXFRW::addSortEntsTable(const DRW_SortEntsTable &data) {
5993 if (m_graphic != nullptr) {
5994 m_graphic->dwgAdvancedMetadata().addSortEntsTable(data);
5995 }
5996 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSortEntsTable: handle %d entries=%d block=%d",
5997 static_cast<int>(data.handle),
5998 static_cast<int>(data.m_entityHandles.size()),
5999 static_cast<int>(data.m_blockOwnerHandle));
6000}
6001
6002void RS_FilterDXFRW::addFieldList(const DRW_FieldList &data) {
6003 if (m_graphic != nullptr) {
6004 m_graphic->dwgAdvancedMetadata().addFieldList(data);
6005 }
6006 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addFieldList: handle %d fields=%d",
6007 static_cast<int>(data.handle),
6008 static_cast<int>(data.m_fieldHandles.size()));
6009}
6010
6011void RS_FilterDXFRW::addDataTable(const DRW_DataTable &data) {
6012 if (m_graphic != nullptr) {
6013 m_graphic->dwgAdvancedMetadata().addDataTable(data);
6014 }
6015 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataTable: handle %d name=%s cols=%d rows=%d",
6016 static_cast<int>(data.handle),
6017 data.tableName.c_str(),
6018 static_cast<int>(data.columnCount),
6019 static_cast<int>(data.rowCount));
6020}
6021
6022void RS_FilterDXFRW::addDynamicBlockObject(const DRW_DynamicBlockObject &data) {
6023 if (m_graphic != nullptr) {
6024 m_graphic->dwgAdvancedMetadata().addDynamicBlockObject(data);
6025 }
6026 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDynamicBlockObject: handle %d rec=%s kind=%d",
6027 static_cast<int>(data.handle),
6028 data.m_recordName.c_str(),
6029 static_cast<int>(data.m_kind));
6030}
6031
6032void RS_FilterDXFRW::addField(const DRW_Field &data) {
6033 if (m_graphic != nullptr) {
6034 m_graphic->dwgAdvancedMetadata().addField(data);
6035 }
6036 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addField: handle %d evaluator=%s code=%s",
6037 static_cast<int>(data.handle),
6038 data.m_evaluatorId.c_str(),
6039 data.m_fieldCode.c_str());
6040}
6041
6042void RS_FilterDXFRW::addAssociativeObject(const DRW_AssociativeObject &data) {
6043 // TODO: Reconstruct associative dimension/dynamic-block relationship graphs
6044 // from these shell objects after native consumers exist.
6045 if (m_graphic != nullptr) {
6046 m_graphic->dwgAdvancedMetadata().addAssociativeObject(data);
6047 }
6048 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAssociativeObject: %s handle %d",
6049 data.m_recordName.empty() ? "(assoc)" : data.m_recordName.c_str(),
6050 static_cast<int>(data.handle));
6051}
6052
6053void RS_FilterDXFRW::addAcShHistoryObject(const DRW_AcShHistoryObject &data) {
6054 // TODO: Connect ACSH history nodes to 3DSOLID modeler geometry history.
6055 if (m_graphic != nullptr) {
6056 m_graphic->dwgAdvancedMetadata().addAcShObject(data);
6057 }
6058 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcShHistoryObject: %s handle %d",
6059 data.m_recordName.empty() ? "(acsh)" : data.m_recordName.c_str(),
6060 static_cast<int>(data.handle));
6061}
6062
6063/**
6064 * Implementation of the method which links image entities to image files.
6065 */
6066void RS_FilterDXFRW::linkImage(const DRW_ImageDef* data) {
6067 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkImage");
6068 if (m_graphic != nullptr && data != nullptr)
6069 m_graphic->dwgAdvancedMetadata().addImageDefinition(*data);
6070
6071 if (data == nullptr)
6072 return;
6073
6074 const int handle = data->handle;
6075 QString sfile(QString::fromUtf8(data->name.c_str()));
6076 const QFileInfo fiDxf(m_file);
6077 const QFileInfo fiBitmap(sfile);
6078
6079 // try to find the image file:
6080
6081 // first: absolute path:
6082 if (!fiBitmap.exists()) {
6083 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.", (const char*)QFile::encodeName(sfile));
6084 // try relative path:
6085 const QString f1 = fiDxf.absolutePath() + "/" + sfile;
6086 if (QFileInfo::exists(f1)) {
6087 sfile = f1;
6088 }
6089 else {
6090 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.", (const char*)QFile::encodeName(f1));
6091 // try drawing path:
6092 const QString f2 = fiDxf.absolutePath() + "/" + fiBitmap.fileName();
6093 if (QFileInfo::exists(f2)) {
6094 sfile = f2;
6095 }
6096 else {
6097 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.", (const char*)QFile::encodeName(f2));
6098 }
6099 }
6100 }
6101
6102 // Also link images in subcontainers (e.g. inserts):
6103 for (RS_Entity* e : lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
6104 if (e->rtti() == RS2::EntityImage) {
6105 const auto img = static_cast<RS_Image*>(e);
6106 if (img->getHandle() == handle) {
6107 img->setFile(sfile);
6108 RS_DEBUGRS_Debug::instance()->print("image found: %s", (const char*)QFile::encodeName(img->getFile()));
6109 img->update();
6110 }
6111 }
6112 }
6113
6114 // update images in blocks:
6115 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
6116 const RS_Block* b = m_graphic->blockAt(i);
6117 for (RS_Entity* e : lc::LC_ContainerTraverser{*b, RS2::ResolveNone}.entities()) {
6118 if (e->rtti() == RS2::EntityImage) {
6119 const auto img = static_cast<RS_Image*>(e);
6120 if (img->getHandle() == handle) {
6121 img->setFile(sfile);
6122 RS_DEBUGRS_Debug::instance()->print("image in block found: %s", (const char*)QFile::encodeName(img->getFile()));
6123 img->update();
6124 }
6125 }
6126 }
6127 }
6128 RS_DEBUGRS_Debug::instance()->print("linking image: OK");
6129}
6130
6131using std::map;
6132/**
6133 * Sets the header variables from the DXF file.
6134 */
6135void RS_FilterDXFRW::addHeader(const DRW_Header* data) {
6136 RS_Graphic* container = nullptr;
6137 if (m_currentContainer->rtti() == RS2::EntityGraphic) {
6138 container = static_cast<RS_Graphic*>(m_currentContainer);
6139 }
6140 else {
6141 return;
6142 }
6143
6144 //initialize units vars if not are present in dxf file - they will be overriden if they are present
6145 m_graphic->addVariable("$LUNITS", 2, 70);
6146 m_graphic->addVariable("$LUPREC", 4,70);
6147 m_graphic->addVariable("$AUNITS", 0, 70);
6148 m_graphic->addVariable("$AUPREC", 4, 70);
6149 //initialize points drawing style vars if not present in dxf file
6150 m_graphic->addVariable("$PDMODE", LC_DEFAULTS_PDMode(0|96), DXF_FORMAT_GC_VarName9);
6151 m_graphic->addVariable("$PDSIZE", LC_DEFAULTS_PDSize(- 2.0), DXF_FORMAT_GC_VarName9);
6152 m_graphic->addVariable("$JOINSTYLE", 1, DXF_FORMAT_GC_JoinStyle280);
6153 m_graphic->addVariable("$ENDCAPS", 1, DXF_FORMAT_GC_Endcaps280);
6154
6155 for (auto it = data->vars.begin(); it != data->vars.end(); ++it) {
6156 QString key = QString::fromStdString((*it).first);
6157 // DWG-read header vars arrive under bare keys (e.g. "LUPREC"), while the
6158 // app (and the DXF path) query them $-prefixed ("$LUPREC"). Normalize
6159 // here so DWG-stored vars are reachable. DXF keys already start with '$'
6160 // (drw_header.cpp), so the guard makes this a no-op for that path and
6161 // prevents double-prefixing ("$$ACADVER").
6162 if (!key.startsWith('$')) {
6163 key.prepend('$');
6164 }
6165 DRW_Variant *var = (*it).second;
6166 switch (var->type()) {
6167 case DRW_Variant::COORD:
6168 container->addVariable(key, RS_Vector(var->content.v->x, var->content.v->y, var->content.v->z), var->code());
6169 break;
6170 case DRW_Variant::STRING:
6171 container->addVariable(key, QString::fromUtf8(var->content.s->c_str()), var->code());
6172 break;
6173 case DRW_Variant::INTEGER:
6174 container->addVariable(key, var->content.i, var->code());
6175 break;
6176 case DRW_Variant::DOUBLE:
6177 container->addVariable(key, var->content.d, var->code());
6178 break;
6179 default:
6180 break;
6181 }
6182 }
6183
6184 for (auto it = data->customVars.begin(); it != data->customVars.end(); ++it) {
6185 QString key = QString::fromStdString(it->first);
6186 const DRW_Variant* var = it->second;
6187 container->addCustomProperty(key, var->c_str());
6188 }
6189
6190 m_codePage = m_graphic->getVariableString("$DWGCODEPAGE", "ANSI_1252");
6191 m_textStyle = m_graphic->getVariableString("$TEXTSTYLE", "Standard");
6192 m_dimStyle = m_graphic->getVariableString("$DIMSTYLE", "Standard");
6193
6194 QString acadver = m_graphic->getVariableString("$ACADVER", "");
6195 m_versionStr = acadver;
6196 acadver.replace(QRegularExpression("[a-zA-Z]"), "");
6197 bool ok;
6198 const int parsedVersion = acadver.toInt(&ok);
6199 if (ok) {
6200 m_version = parsedVersion;
6201 } else if (m_version == 0) {
6202 // Pre-R13 DWG may omit $ACADVER; fileImport primes m_version from the
6203 // on-disk magic before entities are read.
6204 m_version = 1021;
6205 }
6206
6207 //detect if dxf lib are a old dxflib or libdxfrw < 0.5.4 (used to correct mtext alignment)
6208 m_oldMText = false;
6209 m_isLibDxfRw = false;
6210 m_libDxfRwVersion = 0;
6211#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
6212 constexpr auto option = Qt::SkipEmptyParts;
6213#else
6214 auto option = QString::SkipEmptyParts;
6215#endif
6216 QStringList commentList = QString::fromStdString(data->getComments()).split('\n', option);
6217 for (const auto &commentLine : std::as_const(commentList)) {
6218 QStringList commentWords = commentLine.split(' ', option);
6219 if (0 < commentWords.size()) {
6220 if ("dxflib" == commentWords.at(0)) {
6221 m_oldMText = true;
6222 break;
6223 }
6224 if ("dxfrw" == commentWords.at(0)) {
6225 const QStringList libVersionList = commentWords.at(1).split('.', option);
6226 if (2 < libVersionList.size()) {
6227 m_isLibDxfRw = true;
6228 m_libDxfRwVersion = LIBDXFRW_VERSION(libVersionList.at(0).toInt(), libVersionList.at(1).toInt(),(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
6229 libVersionList.at(2).toInt())(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
;
6230 if (m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 4)(((0) << 16) | ((5) << 8) | (4))) {
6231 m_oldMText = true;
6232 }
6233 }
6234 break;
6235 }
6236 }
6237 }
6238}
6239
6240/**
6241 * Implementation of the method used for RS_Export to communicate
6242 * with this filter.
6243 *
6244 * @param g
6245 * @param file Full path to the DXF file that will be written.
6246 * @param type
6247 */
6248bool RS_FilterDXFRW::fileExport(RS_Graphic& g, const QString& file, const RS2::FormatType type) {
6249 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: exporting file '%s'...", (const char*)QFile::encodeName(file));
6250 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: file type '%d'", type);
6251
6252 this->m_graphic = &g;
6253 m_writeFailed = false;
6254
6255 // check if we can write to that directory:
6256#ifndef Q_OS_WIN
6257
6258 QString path = QFileInfo(file).absolutePath(); if (QFileInfo(path).isWritable() == false) {
6259 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileExport: can't write file: " "no permission");
6260 return false;
6261 }
6262 //
6263#endif
6264
6265#ifdef DWGSUPPORT1
6266 auto dwgVersionForFormat = [](RS2::FormatType format) {
6267 switch (format) {
6268 case RS2::FormatDWG: return DRW::AC1015;
6269 case RS2::FormatDWG2004: return DRW::AC1018;
6270 case RS2::FormatDWG2010: return DRW::AC1024;
6271 case RS2::FormatDWG2013: return DRW::AC1027;
6272 case RS2::FormatDWG2018: return DRW::AC1032;
6273 default: return DRW::UNKNOWNV;
6274 }
6275 };
6276 DRW::Version dwgVer = dwgVersionForFormat(type);
6277 if (dwgVer != DRW::UNKNOWNV) {
6278 switch (dwgVer) {
6279 case DRW::AC1015: m_version = 1015; break;
6280 case DRW::AC1018: m_version = 1018; break;
6281 case DRW::AC1024: m_version = 1024; break;
6282 case DRW::AC1027: m_version = 1027; break;
6283 case DRW::AC1032: m_version = 1032; break;
6284 default: break;
6285 }
6286 m_exactColor = false;
6287 m_lastDwgWriteSkipCounters = {};
6288 m_dwgW = new dwgRW(QFile::encodeName(file).constData());
6289 // P3 #1: reserve every preserved fixed-type OBJECT + raw-object handle
6290 // BEFORE write() so defineBlock() (which mints user-block handles from
6291 // 0x30) can never mint a handle a later object re-emits -> no duplicate
6292 // object-map entry -> no writeDwgHandles() failure -> no BAD_OPEN whole-
6293 // file abort. Mirrors the DXF reserve pass (this DWG branch previously
6294 // had none); reserving a handle that is ultimately not emitted is
6295 // harmless (next() simply skips it). Validated by the [dwg-write][reserve]
6296 // unit test.
6297 {
6298 const auto &md = g.dwgAdvancedMetadata();
6299 // High-water mark over every preserved handle; remap targets are
6300 // minted above it (and above 0x2F, the writer's minting floor).
6301 std::uint32_t highWater = 0x2F;
6302 auto reserveAll = [&](const auto &records) {
6303 for (const auto &r : records)
6304 if (r.handle != 0) {
6305 m_dwgW->reserveHandle(r.handle);
6306 highWater = std::max<std::uint32_t>(highWater, r.handle);
6307 }
6308 };
6309 reserveAll(md.rawObjects());
6310 reserveAll(md.placeholders());
6311 reserveAll(md.dictionaries());
6312 reserveAll(md.xrecords());
6313 reserveAll(md.groups());
6314 reserveAll(md.layouts());
6315 reserveAll(md.mlineStyles());
6316 reserveAll(md.dictionaryVars());
6317 reserveAll(md.fieldLists());
6318 reserveAll(md.fields());
6319 reserveAll(md.suns());
6320 reserveAll(md.mleaderStyles());
6321 reserveAll(md.rasterVariables());
6322 reserveAll(md.wipeoutVariables());
6323 reserveAll(md.geoData());
6324 reserveAll(md.spatialFilters());
6325 reserveAll(md.scales());
6326 reserveAll(md.idBuffers());
6327 reserveAll(md.layerIndexes());
6328 reserveAll(md.spatialIndexes());
6329 reserveAll(md.dictionariesWithDefault());
6330 reserveAll(md.sortEntsTables());
6331 reserveAll(md.underlayDefinitions());
6332 // P3 #2: structural-collision remap. The writer emits its control
6333 // objects / canonical table records at FIXED low handles (LTYPE
6334 // control 0x05, VIEW control 0x06, UCS control 0x07, ...) that
6335 // AC1021+ sources reuse for ordinary preserved OBJECTS. Reserving
6336 // alone cannot break that tie (both sides want the same handle),
6337 // so colliding typed objects are moved to fresh handles above the
6338 // high-water mark; writeObjects rewrites the object's own handle
6339 // and every typed reference to it. Colliding raw objects are
6340 // blocked in writeObjects instead (verbatim bytes cannot be
6341 // rewritten).
6342 m_dwgWriteHandleRemap.clear();
6343 auto remapIfColliding = [&](const auto &records) {
6344 for (const auto &r : records) {
6345 if (r.handle == 0 || !isFixedStructuralDwgHandle(r.handle))
6346 continue;
6347 if (m_dwgWriteHandleRemap.count(r.handle) != 0)
6348 continue;
6349 const std::uint32_t target = ++highWater;
6350 m_dwgW->reserveHandle(target);
6351 m_dwgWriteHandleRemap.emplace(r.handle, target);
6352 }
6353 };
6354 remapIfColliding(md.placeholders());
6355 remapIfColliding(md.dictionaries());
6356 remapIfColliding(md.xrecords());
6357 remapIfColliding(md.groups());
6358 remapIfColliding(md.layouts());
6359 remapIfColliding(md.mlineStyles());
6360 remapIfColliding(md.dictionaryVars());
6361 remapIfColliding(md.fieldLists());
6362 remapIfColliding(md.fields());
6363 remapIfColliding(md.suns());
6364 remapIfColliding(md.mleaderStyles());
6365 remapIfColliding(md.rasterVariables());
6366 remapIfColliding(md.wipeoutVariables());
6367 remapIfColliding(md.geoData());
6368 remapIfColliding(md.spatialFilters());
6369 remapIfColliding(md.scales());
6370 remapIfColliding(md.idBuffers());
6371 remapIfColliding(md.layerIndexes());
6372 remapIfColliding(md.spatialIndexes());
6373 remapIfColliding(md.dictionariesWithDefault());
6374 remapIfColliding(md.sortEntsTables());
6375 remapIfColliding(md.underlayDefinitions());
6376 }
6377 bool success = m_dwgW->write(this, dwgVer, false) && !m_writeFailed;
6378 m_lastDwgWriteSkipCounters = m_dwgW->getWriteSkipCounters();
6379 // Geometry / typed-object / class-registration skips mean the save is
6380 // incomplete. Raw same-version replay may still skip cross-family
6381 // blobs; those are counted but do not alone fail the save.
6382 const auto& skips = m_lastDwgWriteSkipCounters;
6383 const std::size_t hardSkips = skips.entityWrites + skips.objectWrites
6384 + skips.classRegistrations + skips.blockDefinitions;
6385 if (success && hardSkips > 0) {
6386 success = false;
6387 m_writeFailed = true;
6388 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
6389 "RS_FilterDXFRW: DWG write dropped %zu typed item(s) "
6390 "(entities=%zu objects=%zu classes=%zu blocks=%zu; "
6391 "raw skips=%zu)",
6392 hardSkips,
6393 skips.entityWrites,
6394 skips.objectWrites,
6395 skips.classRegistrations,
6396 skips.blockDefinitions,
6397 skips.rawObjectWrites + skips.rawSectionWrites);
6398 }
6399 delete m_dwgW;
6400 m_dwgW = nullptr;
6401 return success;
6402 }
6403#endif
6404
6405 // set version for DXF filter:
6406 m_exactColor = false;
6407 DRW::Version exportVersion;
6408 if (type == RS2::FormatDXFRW12) {
6409 exportVersion = DRW::AC1009;
6410 m_version = 1009;
6411 }
6412 else if (type == RS2::FormatDXFRW14) {
6413 exportVersion = DRW::AC1014;
6414 m_version = 1014;
6415 }
6416 else if (type == RS2::FormatDXFRW2000) {
6417 exportVersion = DRW::AC1015;
6418 m_version = 1015;
6419 }
6420 else if (type == RS2::FormatDXFRW2004) {
6421 exportVersion = DRW::AC1018;
6422 m_version = 1018;
6423 m_exactColor = true;
6424 }
6425 else if (type == RS2::FormatDXFRW) {
6426 exportVersion = DRW::AC1021;
6427 m_version = 1021;
6428 m_exactColor = true;
6429 }
6430 else {
6431 exportVersion = DRW::AC1032;
6432 m_version = 1032;
6433 m_exactColor = true;
6434 }
6435 /**
6436 * fixme - sand - files - RESTORE!!! Under win, encodeName() prevents using unicode file names!!! Due to that, blocks/files may be saved incorrectly if name is localized
6437 */
6438 m_dxfW = new dxfRW(QFile::encodeName(file));
6439 // fixme - sand - save to binary format enabling/disabling!!
6440 const bool binary = false;
6441
6442 // Before write(): (1) reserve every verbatim code-5 handle preserved in the
6443 // raw-passthrough net (rawDxfObjects/rawDxfEntities re-emit their original
6444 // handle) in the codec's HandleAllocator so a minted handle can never
6445 // collide with a preserved raw handle, AND a preserved raw handle that lands
6446 // in the codec's fixed-low band (0x10-0x25) is skipped by next() too — this
6447 // is the DWG-path reserve-and-preserve mechanism (dwgWriter::reserveHandle),
6448 // and (2) register a DXF CLASS record for each custom-class object present in
6449 // the raw net so AutoCAD/ODA accept those instances (entry + instance co-emit).
6450 {
6451 const auto &metadata = g.dwgAdvancedMetadata();
6452 std::vector<DRW_Class> classes;
6453 std::map<std::string, std::size_t> classIdx;
6454 std::map<std::string, DRW_Class> sourceClasses;
6455 for (const DRW_Class &cls : metadata.dxfClasses()) {
6456 if (!cls.recName.empty())
6457 sourceClasses.emplace(cls.recName, cls);
6458 }
6459 auto registerClassFor = [&](const std::string &recordName) {
6460 DRW_Class cls;
6461 auto sourceIt = sourceClasses.find(recordName);
6462 if (sourceIt != sourceClasses.end()) {
6463 cls = sourceIt->second;
6464 } else if (!dxfRW::dxfClassForRecordName(recordName, cls)) {
6465 return;
6466 }
6467 auto it = classIdx.find(recordName);
6468 if (it == classIdx.end()) {
6469 cls.instanceCount = 1;
6470 classIdx.emplace(recordName, classes.size());
6471 classes.push_back(cls);
6472 } else {
6473 ++classes[it->second].instanceCount;
6474 }
6475 };
6476 std::set<std::uint32_t> rawObjectHandles;
6477 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
6478 if (o.handle != 0) {
6479 m_dxfW->reserveHandle(o.handle);
6480 rawObjectHandles.insert(o.handle);
6481 }
6482 registerClassFor(o.name);
6483 }
6484 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities()) {
6485 if (e.handle != 0)
6486 m_dxfW->reserveHandle(e.handle);
6487 registerClassFor(e.name); //custom ENTITIES need a CLASS too
6488 }
6489
6490 //F4: the routed data-only OBJECTS (SUN/SCALE/DICTIONARYVAR/
6491 //RASTERVARIABLES) are typed-emitted on DWG->DXF (the DWG reader does NOT
6492 //put them in the raw net — see writeObjects). For each such record that
6493 //will be typed-emitted (ReplayAllowed, nonzero handle, NOT already in the
6494 //raw net → not double-counted), reserve its verbatim code-5 handle so a
6495 //minted handle can't collide, and register the same CLASS the raw-net
6496 //objects use (else AutoCAD/ezdxf drop the instance). The dedup predicate
6497 //here MUST match writeObjects' emitTyped.
6498 auto reserveTyped = [&](std::uint32_t handle,
6499 LC_DwgAdvancedMetadata::ReplayState state,
6500 const char *recordName) {
6501 if (handle == 0
6502 || state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
6503 || rawObjectHandles.count(handle) != 0)
6504 return;
6505 m_dxfW->reserveHandle(handle);
6506 registerClassFor(recordName);
6507 };
6508 for (const auto &record : metadata.suns())
6509 reserveTyped(record.handle, record.replayState, "SUN");
6510 for (const auto &record : metadata.scales())
6511 reserveTyped(record.handle, record.replayState, "SCALE");
6512 for (const auto &record : metadata.dictionaryVars())
6513 reserveTyped(record.handle, record.replayState, "DICTIONARYVAR");
6514 for (const auto &record : metadata.rasterVariables())
6515 reserveTyped(record.handle, record.replayState, "RASTERVARIABLES");
6516 for (const auto &record : metadata.mleaderStyles())
6517 reserveTyped(record.handle, record.replayState, "MLEADERSTYLE");
6518 for (const auto &record : metadata.geoData())
6519 reserveTyped(record.handle, record.replayState, "GEODATA");
6520 for (const auto &record : metadata.spatialFilters())
6521 reserveTyped(record.handle, record.replayState, "SPATIAL_FILTER");
6522 for (const auto &record : metadata.sortEntsTables())
6523 reserveTyped(record.handle, record.replayState, "SORTENTSTABLE");
6524 for (const auto &record : metadata.fields())
6525 reserveTyped(record.handle, record.replayState, "FIELD");
6526 for (const auto &record : metadata.fieldLists())
6527 reserveTyped(record.handle, record.replayState, "FIELDLIST");
6528 //SLICE 2: WIPEOUTVARIABLES is a CUSTOM class -> reserve + register CLASS.
6529 for (const auto &record : metadata.wipeoutVariables())
6530 reserveTyped(record.handle, record.replayState, "WIPEOUTVARIABLES");
6531 //SLICE 1: fixed built-ins -> reserve handles but register NO CLASS.
6532 auto reserveFixedTyped = [&](std::uint32_t handle,
6533 LC_DwgAdvancedMetadata::ReplayState state) {
6534 if (handle == 0
6535 || state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
6536 || rawObjectHandles.count(handle) != 0)
6537 return;
6538 m_dxfW->reserveHandle(handle);
6539 };
6540 for (const auto &record : metadata.mlineStyles())
6541 reserveFixedTyped(record.handle, record.replayState);
6542 for (const auto &record : metadata.layouts())
6543 reserveFixedTyped(record.handle, record.replayState);
6544
6545 if (!classes.empty())
6546 m_dxfW->setDxfClasses(classes);
6547
6548 // (3) Re-attach raw-net-routed named dictionaries to the codec's
6549 // regenerated root NamedObjectsDictionary (handle C). The child dict NAME
6550 // is not on the child object — it lives in the SOURCE root dict's entry
6551 // list (the typed DictionaryRecord with parentHandle==0). Build the
6552 // (name, verbatim-handle) entries for the codec, and a suppression set so
6553 // the source ACAD_GROUP dict (codec regenerates it at D) and any C/D
6554 // collisions are not re-emitted. The source root dict itself is never in
6555 // the raw net (processDictionary skips parentHandle==0).
6556 m_dxfSuppressedObjectHandles.clear();
6557 m_dxfEmittedNamedDictHandles.clear();
6558 std::uint32_t sourceRootHandle = 0;
6559 std::uint32_t acadGroupHandle = 0;
6560 std::map<std::uint32_t, std::string> rootEntryName; // child handle -> name
6561 for (const auto &d : metadata.dictionaries()) {
6562 if (d.parentHandle != 0)
6563 continue;
6564 sourceRootHandle = d.handle;
6565 for (const auto &e : d.entries) {
6566 if (e.name == "ACAD_GROUP")
6567 acadGroupHandle = e.handle;
6568 else
6569 rootEntryName.emplace(e.handle, e.name);
6570 }
6571 }
6572 if (acadGroupHandle != 0)
6573 m_dxfSuppressedObjectHandles.insert(acadGroupHandle);
6574
6575 // (3a) Structural-collision remap. The codec emits its own table/block
6576 // records and root dictionaries at FIXED low handles (table heads, LAYER
6577 // 0x10, APPID 0x12, LTYPE 0x14-0x16, BLOCK_RECORD/BLOCK/ENDBLK 0x1C-0x21,
6578 // root C / ACAD_GROUP D). Real source files reuse those very handles for
6579 // unrelated OBJECTS (extension dicts, material dicts, xrecords) preserved
6580 // verbatim in the raw net — an unavoidable raw-vs-fixed-structural
6581 // collision the reserve-and-preserve mechanism alone cannot break (both
6582 // want the same handle). The fixed structural set is non-negotiable (the
6583 // codec's 330/350 cross-refs are literals), so the colliding raw objects
6584 // are remapped to fresh allocator handles; writeRawDxfObject rewrites the
6585 // object's own handle AND every reference to a remapped handle, keeping
6586 // the raw subtree internally consistent. 0xC/0xD raw dicts are suppressed
6587 // (the codec regenerates root/group) and so are NOT remapped.
6588 static const std::set<std::uint32_t> kFixedStructural = {
6589 0x1, 0x2, 0x3, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA,
6590 0x10, 0x12, 0x14, 0x15, 0x16,
6591 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21};
6592 std::map<std::uint32_t, std::uint32_t> handleRemap;
6593 auto remapIfColliding = [&](std::uint32_t h) {
6594 if (h == 0 || kFixedStructural.count(h) == 0)
6595 return;
6596 if (h == 0xCu || h == 0xDu ||
6597 m_dxfSuppressedObjectHandles.count(h) != 0)
6598 return; // suppressed, not emitted -> nothing to remap
6599 if (handleRemap.count(h) != 0)
6600 return;
6601 handleRemap.emplace(h, m_dxfW->allocHandle());
6602 };
6603 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects())
6604 remapIfColliding(o.handle);
6605 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities())
6606 remapIfColliding(e.handle);
6607 // setHandleRemap is pushed AFTER the named-dict block below so any remap
6608 // a colliding named-dict handle adds is included in the codec's table.
6609
6610 std::vector<std::pair<std::string, std::string>> rootEntries;
6611 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
6612 if (o.name != "DICTIONARY" && o.name != "ACDBDICTIONARYWDFLT")
6613 continue;
6614 if (o.handle == 0xCu || o.handle == 0xDu) { // collide with fixed C/D
6615 m_dxfSuppressedObjectHandles.insert(o.handle);
6616 continue;
6617 }
6618 if (o.handle == acadGroupHandle)
6619 continue; // already suppressed
6620 if (o.parentHandle != sourceRootHandle)
6621 continue; // only direct root children get a root entry
6622 auto it = rootEntryName.find(o.handle);
6623 if (it == rootEntryName.end())
6624 continue; // name not recoverable -> cannot re-attach
6625 std::string handleStr; // code-5; matches the (possibly remapped) re-emit
6626 for (const DRW_Variant &g : o.groups)
6627 if (g.code() == 5) {
6628 handleStr = g.c_str();
6629 break;
6630 }
6631 // If this child dict's handle was remapped (structural collision), the
6632 // root entry must point at the NEW handle so the 350 ref resolves.
6633 auto rm = handleRemap.find(o.handle);
6634 if (rm != handleRemap.end())
6635 handleStr = m_dxfW->toHexStrHandle(rm->second);
6636 if (!handleStr.empty())
6637 rootEntries.emplace_back(it->second, handleStr);
6638 }
6639
6640 // (3b) F4-followup: DWG->DXF parent dictionaries for the data-only
6641 // OBJECTS. On the DWG path the only INVALID_OWNER_HANDLE fixes ezdxf
6642 // applies are typed OBJECTS (SUN / SCALE / DICTIONARYVAR /
6643 // RASTERVARIABLES / LAYOUT / ...) whose 330 owner is a named dictionary
6644 // that LibreCAD does not regenerate (e.g. ACAD_DICTIONARYVAR @0x70
6645 // owning the DICTIONARYVARs). Emit EXACTLY those parent dictionaries as real,
6646 // C-owned OBJECTS so the children resolve to a valid owner; keep ONLY the
6647 // entries that target an object we actually emit (the data-only children)
6648 // so the dict introduces no new dangling 350. We deliberately do NOT emit
6649 // the dictionaries whose children are unmodeled objects LibreCAD drops:
6650 // emitting those would replace one missing-owner fix with many
6651 // dangling-entry fixes and corrupt cross-refs.
6652 //
6653 // The set of objects we emit on this path (their code-5 handles):
6654 // the ReplayAllowed, non-raw-net data-only records (matches writeObjects'
6655 // emitTyped), plus the raw-net objects (re-emitted verbatim above).
6656 std::set<std::uint32_t> emittedObjectHandles = rawObjectHandles;
6657 std::set<std::uint32_t> neededParents; // 330 owners we must materialize
6658 auto noteDataOnly = [&](std::uint32_t handle, std::uint32_t parent,
6659 LC_DwgAdvancedMetadata::ReplayState state) {
6660 if (handle == 0
6661 || state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
6662 || rawObjectHandles.count(handle) != 0)
6663 return; // not emitted on this path
6664 emittedObjectHandles.insert(handle);
6665 if (parent != 0)
6666 neededParents.insert(parent);
6667 };
6668 for (const auto &r : metadata.suns())
6669 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6670 for (const auto &r : metadata.scales())
6671 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6672 for (const auto &r : metadata.dictionaryVars())
6673 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6674 for (const auto &r : metadata.rasterVariables())
6675 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6676 for (const auto &r : metadata.mleaderStyles())
6677 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6678 for (const auto &r : metadata.geoData())
6679 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6680 for (const auto &r : metadata.spatialFilters())
6681 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6682 for (const auto &r : metadata.sortEntsTables())
6683 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6684 for (const auto &r : metadata.fields())
6685 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6686 for (const auto &r : metadata.fieldLists())
6687 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6688 for (const auto &r : metadata.mlineStyles())
6689 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6690 for (const auto &r : metadata.layouts())
6691 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6692 for (const auto &r : metadata.wipeoutVariables())
6693 noteDataOnly(r.handle, r.parentHandle, r.replayState);
6694
6695 std::vector<DRW_Dictionary> namedDicts;
6696 std::set<std::uint32_t> emittedDictHandles;
6697 for (const auto &d : metadata.dictionaries()) {
6698 if (d.parentHandle == 0)
6699 continue; // source root: harvested for names only
6700 if (d.handle == 0 || d.handle == 0xCu || d.handle == 0xDu)
6701 continue;
6702 if (d.handle == acadGroupHandle)
6703 continue;
6704 if (m_dxfSuppressedObjectHandles.count(d.handle) != 0)
6705 continue;
6706 if (rawObjectHandles.count(d.handle) != 0)
6707 continue; // DXF-read dict already round-tripped via the raw net
6708 if (neededParents.count(d.handle) == 0)
6709 continue; // not a parent of any object we emit -> skip
6710 if (emittedDictHandles.count(d.handle) != 0)
6711 continue; // de-dup (dictionaries() may list a handle once, guard anyway)
6712
6713 // Reserve + structural-collision remap, mirroring the raw path so a
6714 // dict handle landing on a fixed literal is moved.
6715 m_dxfW->reserveHandle(d.handle);
6716 remapIfColliding(d.handle);
6717 auto remapOf = [&](std::uint32_t h) {
6718 auto rm = handleRemap.find(h);
6719 return rm != handleRemap.end() ? rm->second : h;
6720 };
6721 DRW_Dictionary dict = dictionaryFromMetadata(d);
6722 dict.handle = remapOf(d.handle);
6723 // Owner: re-point a child of the source root at C (handle 0 ->
6724 // writeObjectOwner emits "C"). If the explicit parent is itself an
6725 // emitted dict, keep it (remapped); otherwise fall back to C so the
6726 // dict is never itself a dangling-owner object.
6727 std::uint32_t parent = static_cast<std::uint32_t>(d.parentHandle);
6728 if (parent == sourceRootHandle || neededParents.count(parent) == 0)
6729 dict.parentHandle = 0;
6730 else
6731 dict.parentHandle = static_cast<int>(remapOf(parent));
6732 // Keep ONLY entries whose target is an object we actually emit, so the
6733 // dict adds no dangling 350.
6734 std::vector<DRW_Dictionary::Entry> keptEntries;
6735 keptEntries.reserve(dict.m_entries.size());
6736 for (auto &entry : dict.m_entries) {
6737 if (emittedObjectHandles.count(entry.m_handle) == 0)
6738 continue;
6739 entry.m_handle = remapOf(entry.m_handle);
6740 keptEntries.push_back(entry);
6741 }
6742 dict.m_entries = std::move(keptEntries);
6743 namedDicts.push_back(std::move(dict));
6744 emittedDictHandles.insert(d.handle);
6745 m_dxfEmittedNamedDictHandles.insert(d.handle);
6746
6747 // Re-attach under root C so the dict object is reachable. Use the
6748 // harvested name if known, else the source dict name, else a synthetic.
6749 auto nameIt = rootEntryName.find(d.handle);
6750 std::string entryName = nameIt != rootEntryName.end()
6751 ? nameIt->second
6752 : (!d.name.empty() ? d.name : std::string());
6753 if (!entryName.empty())
6754 rootEntries.emplace_back(
6755 entryName, m_dxfW->toHexStrHandle(remapOf(d.handle)));
6756 }
6757 if (!namedDicts.empty())
6758 m_dxfW->setNamedDictObjects(namedDicts);
6759
6760 // (4) F3: typed GROUP emit (both directions). GROUP is read into typed
6761 // metadata only (processGroup -> addGroup; never routed to the DXF raw
6762 // net — confirmed against the DXF reader's processGroup which makes no
6763 // addRawDxfObject call), so there is no raw-net dedup to do here. The
6764 // codec mints each group's handle, augments the ACAD_GROUP D dict, and
6765 // resolves member entityHandles (SOURCE handles) through the source->minted
6766 // map captured during writeEntity. The source ACAD_GROUP dict is already
6767 // suppressed above (acadGroupHandle); the codec regenerates D.
6768 //
6769 // Group NAMES live in the ACAD_GROUP dict's entries (groupName ->
6770 // groupHandle), not on the GROUP record; harvest them. Unnamed groups
6771 // (isUnnamed) carry a generated "*An" name AutoCAD assigns; synthesize a
6772 // stable "*A<n>" when no name is recoverable so the D-dict entry + GROUP
6773 // are still well-formed.
6774 std::map<std::uint32_t, std::string> groupHandleToName;
6775 for (const auto &d : metadata.dictionaries()) {
6776 if (d.handle != acadGroupHandle || acadGroupHandle == 0)
6777 continue;
6778 for (const auto &e : d.entries)
6779 groupHandleToName.emplace(e.handle, e.name);
6780 }
6781 std::vector<DRW_Group> groupsToWrite;
6782 groupsToWrite.reserve(metadata.groups().size());
6783 int unnamedSeq = 0;
6784 for (const auto &record : metadata.groups()) {
6785 if (record.replayState
6786 != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
6787 continue;
6788 DRW_Group group = groupFromMetadata(record);
6789 auto nameIt = groupHandleToName.find(record.handle);
6790 if (nameIt != groupHandleToName.end() && !nameIt->second.empty())
6791 group.name = nameIt->second;
6792 else
6793 group.name = "*A" + std::to_string(++unnamedSeq);
6794 groupsToWrite.push_back(std::move(group));
6795 }
6796 if (!groupsToWrite.empty())
6797 m_dxfW->setGroups(groupsToWrite);
6798
6799 if (!handleRemap.empty())
6800 m_dxfW->setHandleRemap(handleRemap);
6801
6802 if (!rootEntries.empty())
6803 m_dxfW->setRootDictEntries(rootEntries);
6804 }
6805
6806 // bool success = m_dxfW->write(this, exportVersion, false); //ascii
6807 const bool success = m_dxfW->write(this, exportVersion, binary)
6808 && !m_writeFailed; //binary
6809 delete m_dxfW;
6810
6811 if (!success) {
6812 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: can't write file");
6813 return false;
6814 }
6815 /*RLZ pte*/
6816 /* RS_DEBUG->print("writing tables...");
6817 dw->sectionTables();
6818 // VPORT:
6819 dxf.writeVPort(*dw);
6820 dw->tableEnd();
6821
6822 // VIEW:
6823 RS_DEBUG->print("writing views...");
6824 dxf.writeView(*dw);
6825
6826 // UCS:
6827 RS_DEBUG->print("writing ucs...");
6828 dxf.writeUcs(*dw);
6829
6830 // Appid:
6831 RS_DEBUG->print("writing appid...");
6832 dw->tableAppid(1);
6833 writeAppid(*dw, "ACAD");
6834 dw->tableEnd();
6835 */
6836 return success;
6837}
6838
6839/**
6840 * Prepare unnamed blocks.
6841 */
6842void RS_FilterDXFRW::prepareBlocks() {
6843 int dimNum = 0;
6844 int hatchNum = 0;
6845
6846 //check for existing *D?? or *U??
6847 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
6848 const RS_Block* blk = m_graphic->blockAt(i);
6849 const QString prefix = blk->getName().left(2).toUpper();
6850 QString sufix = blk->getName().mid(2);
6851 if (prefix == "*D") {
6852 if (sufix.toInt() > dimNum) {
6853 dimNum = sufix.toInt();
6854 }
6855 }
6856 else if (prefix == "*U") {
6857 if (sufix.toInt() > hatchNum) {
6858 hatchNum = sufix.toInt();
6859 }
6860 }
6861 }
6862 //Add a name to each dimension, in dxfR12 also for hatches
6863 for (RS_Entity* e : lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
6864 if (e->isAlive()) {
6865 switch (e->rtti()) {
6866 case RS2::EntityDimLinear:
6867 case RS2::EntityDimOrdinate:
6868 case RS2::EntityDimAligned:
6869 case RS2::EntityDimAngular:
6870 case RS2::EntityDimRadial:
6871 case RS2::EntityDimDiametric:
6872 case RS2::EntityDimLeader: {
6873 const QString prefix = "*D" + QString::number(++dimNum);
6874 m_noNameBlock[e] = prefix;
6875 break;
6876 }
6877 case RS2::EntityHatch: {
6878 if (m_version == 1009) {
6879 if (!static_cast<RS_Hatch*>(e)->isSolid()) {
6880 const QString prefix = "*U" + QString::number(++hatchNum);
6881 m_noNameBlock[e] = prefix;
6882 }
6883 }
6884 break;
6885 }
6886 default:
6887 break;
6888 } //end switch
6889 } //end if !RS2::FlagUndone
6890 }
6891}
6892
6893/**
6894 * Writes block records (just the name, not the entities in it).
6895 */
6896void RS_FilterDXFRW::writeBlockRecords() {
6897 //first prepare and send unnamed blocks, the while loop can be omitted for R12
6898 prepareBlocks();
6899 QHash<RS_Entity*, QString>::const_iterator it = m_noNameBlock.constBegin();
6900 while (it != m_noNameBlock.constEnd()) {
6901 m_dxfW->writeBlockRecord(it.value().toStdString());
6902 ++it;
6903 }
6904
6905 //next send "normal" blocks
6906 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
6907 const RS_Block* blk = m_graphic->blockAt(i);
6908 if (!blk->isDeleted()) {
6909 RS_DEBUGRS_Debug::instance()->print("writing block record: %s", (const char*)blk->getName().toLocal8Bit());
6910 m_dxfW->writeBlockRecord(blk->getName().toUtf8().data(),
6911 blk->getInsertionUnits());
6912 }
6913 }
6914}
6915
6916/**
6917 * Writes blocks.
6918 */
6919void RS_FilterDXFRW::writeBlocks() {
6920#ifdef DWGSUPPORT1
6921 if (m_dwgW) {
6922 std::vector<std::pair<RS_Block*, std::uint32_t>> blockHandles;
6923 blockHandles.reserve(m_graphic->countBlocks());
6924
6925 // Register every block before encoding any contents. This makes a
6926 // nested INSERT resolvable even when its referenced definition is
6927 // later in the graphic's block list.
6928 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
6929 RS_Block *blk = m_graphic->blockAt(i);
6930 if (!blk->isDeleted()) {
6931 DRW_Coord bp{blk->getBasePoint().x, blk->getBasePoint().y,
6932 blk->getBasePoint().z};
6933 const std::uint32_t handle = m_dwgW->defineBlock(
6934 blk->getName().toUtf8().constData(), bp,
6935 blk->getInsertionUnits());
6936 if (handle == 0) {
6937 m_writeFailed = true;
6938 continue;
6939 }
6940 blockHandles.emplace_back(blk, handle);
6941 }
6942 }
6943
6944 for (const auto& blockHandle : blockHandles) {
6945 RS_Block* blk = blockHandle.first;
6946 if (!m_dwgW->beginBlockContent(blockHandle.second)) {
6947 m_writeFailed = true;
6948 continue;
6949 }
6950 for (RS_Entity* entity :
6951 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
6952 if (!entity->getFlag(RS2::FlagDeleted))
6953 writeEntity(entity);
6954 }
6955 if (!m_dwgW->endBlockContent())
6956 m_writeFailed = true;
6957 }
6958 return;
6959 }
6960#endif
6961
6962 RS_Block *blk;
6963
6964 //write unnamed blocks
6965 QHash<RS_Entity*, QString>::const_iterator it = m_noNameBlock.constBegin();
6966 while (it != m_noNameBlock.constEnd()) {
6967 DRW_Block block;
6968 block.name = it.value().toStdString();
6969 block.basePoint.x = 0.0;
6970 block.basePoint.y = 0.0;
6971 block.basePoint.z = 0.0;
6972 block.flags = 1; //flag for unnamed block
6973 m_dxfW->writeBlock(&block);
6974 const auto ct = static_cast<RS_EntityContainer*>(it.key());
6975 for (RS_Entity* e : lc::LC_ContainerTraverser{*ct, RS2::ResolveNone}.entities()) {
6976 if (e->isAlive()) {
6977 writeEntity(e);
6978 }
6979 }
6980 ++it;
6981 }
6982
6983 //next write "normal" blocks
6984 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
6985 const RS_Block* blk = m_graphic->blockAt(i);
6986 if (!blk->isDeleted()) {
6987 RS_DEBUGRS_Debug::instance()->print("writing block: %s", (const char*)blk->getName().toLocal8Bit());
6988
6989 DRW_Block block;
6990 block.name = blk->getName().toUtf8().data();
6991 block.basePoint.x = blk->getBasePoint().x;
6992 block.basePoint.y = blk->getBasePoint().y;
6993 block.basePoint.z = blk->getBasePoint().z;
6994 m_dxfW->writeBlock(&block);
6995 for (RS_Entity* e : lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
6996 if ( !(e->getFlag(RS2::FlagDeleted)) ) {
6997 writeEntity(e);
6998 }
6999 }
7000 }
7001 }
7002}
7003
7004void RS_FilterDXFRW::writeHeader(DRW_Header& data) {
7005 RS_Vector v;
7006 /*TODO $ISOMETRICGRID == $SNAPSTYLE and "GRID on/off" not handled because is part of
7007 active vport to save is required read/write VPORT table */
7008 QHash<QString, RS_Variable> vars = m_graphic->getVariableDict();
7009 if (!vars.contains("$DWGCODEPAGE")) {
7010 //RLZ: TODO verify this
7011 m_codePage = RS_SYSTEMRS_System::instance()->localeToISO(QLocale::system().name().toLocal8Bit());
7012 // RS_Variable v( QString(RS_SYSTEM->localeToISO(QLocale::system().name().toLocal8Bit())),0 );
7013 vars.insert(QString("$DWGCODEPAGE"), RS_Variable(m_codePage, 0));
7014 }
7015
7016 QHash<QString, RS_Variable>::iterator it = vars.begin();
7017 while (it != vars.end()) {
7018 auto value = it.value();
7019 int code = value.getCode();
7020 auto key = it.key().toStdString();
7021 switch (value.getType()) {
7022 case RS2::VariableInt:
7023 data.addInt(key, value.getInt(), code);
7024 break;
7025 case RS2::VariableDouble:
7026 data.addDouble(key, value.getDouble(), code);
7027 break;
7028 case RS2::VariableString:
7029 data.addStr(key, value.getString().toUtf8().data(), code);
7030 break;
7031 case RS2::VariableVector:
7032 v = value.getVector();
7033 data.addCoord(key, DRW_Coord(v.x, v.y, v.z), code);
7034 break;
7035 default:
7036 break;
7037 }
7038 ++it;
7039 }
7040 // $EXTMIN must use getMin() and $EXTMAX getMax(); the previous code
7041 // overwrote vmin with getMax() before reading it, so $EXTMIN==$EXTMAX
7042 // (a degenerate point bbox → broken ZOOM-EXTENTS/auto-scale) on every
7043 // DWG+DXF write, and the Z extent was forced to 0. (write-review P3 #3)
7044 const RS_Vector vmin = m_graphic->getMin();
7045 const RS_Vector vmax = m_graphic->getMax();
7046 data.addCoord("$EXTMIN", DRW_Coord(vmin.x, vmin.y, vmin.z), 0);
7047 data.addCoord("$EXTMAX", DRW_Coord(vmax.x, vmax.y, vmax.z), 0);
7048
7049 //when saving a block, there is no active layer. ignore it to avoid crash
7050 if (m_graphic->getActiveLayer() == nullptr) {
7051 return;
7052 }
7053 data.addStr("$CLAYER", (m_graphic->getActiveLayer()->getName()).toUtf8().data(), 8);
7054
7055 const QHash<QString, RS_Variable> customVars = m_graphic->getCustomProperties();
7056
7057 QHashIterator<QString, RS_Variable> customVar(customVars);
7058 while (customVar.hasNext()) {
7059 customVar.next();
7060 auto val = customVar.value().getString();
7061 if (!val.isEmpty()) {
7062 auto key = customVar.key().toStdString();
7063 data.customVars[key] = new DRW_Variant(1, val.toStdString());
7064 }
7065 }
7066}
7067
7068void RS_FilterDXFRW::writeDwgClasses() {
7069#ifdef DWGSUPPORT1
7070 if (m_dwgW == nullptr || m_graphic == nullptr)
7071 return;
7072 const auto& metadata = m_graphic->dwgAdvancedMetadata();
7073 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
7074 // PR 13f — custom-class registration gate for the RasterVariables /
7075 // GeoData / SpatialFilter families. Broadened from AC1021+ to AC1015+
7076 // because their encoders + parsers are version-clean (only the
7077 // standard `version > AC1018` split-buffer routing). The dispatch
7078 // loop in writeObjects uses a matching `canRegisterCustomClassObjects`
7079 // gate so the CLASSES section entry and the OBJECTS instance land
7080 // together (otherwise the reader's class lookup fails and the object
7081 // is silently dropped).
7082 const bool canRegisterCustomClassObjects =
7083 m_dwgW->getVersion() >= DRW::AC1015;
7084 std::set<std::uint32_t> nativeSunHandles;
7085 std::set<std::uint32_t> nativeMLeaderStyleHandles;
7086 std::set<std::uint32_t> nativeRasterVariablesHandles;
7087 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
7088 std::set<std::uint32_t> nativeGeoDataHandles;
7089 std::set<std::uint32_t> nativeSpatialFilterHandles;
7090 std::set<std::uint32_t> nativeScaleHandles;
7091 std::set<std::uint32_t> nativeIDBufferHandles;
7092 std::set<std::uint32_t> nativeLayerIndexHandles;
7093 std::set<std::uint32_t> nativeSpatialIndexHandles;
7094 std::set<std::uint32_t> nativeDictionaryVarHandles;
7095 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
7096 std::set<std::uint32_t> nativeSortEntsTableHandles;
7097 std::set<std::uint32_t> nativeFieldListHandles;
7098 std::set<std::uint32_t> nativeFieldHandles;
7099 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
7100
7101 // Entity classes must be counted before dwgRW seals the CLASSES section.
7102 // Traverse model/paper space and block definitions separately, matching
7103 // the later entity write path without resolving INSERT references.
7104 const auto registerWipeoutClasses = [this](const RS_EntityContainer& container) {
7105 for (RS_Entity* entity : lc::LC_ContainerTraverser{container, RS2::ResolveNone}.entities()) {
7106 if (entity != nullptr && entity->rtti() == RS2::EntityWipeout)
7107 m_dwgW->registerWipeoutEntityClass();
7108 }
7109 };
7110 registerWipeoutClasses(*m_graphic);
7111 for (unsigned int index = 0; index < m_graphic->countBlocks(); ++index) {
7112 const RS_Block* block = m_graphic->blockAt(index);
7113 if (block != nullptr)
7114 registerWipeoutClasses(*block);
7115 }
7116
7117 if (canWriteModernObjects) {
7118 for (const auto& record : metadata.suns()) {
7119 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7120 || record.handle == 0) {
7121 continue;
7122 }
7123 DRW_Sun sun = sunFromMetadata(record);
7124 if (m_dwgW->registerSunObjectClass(&sun))
7125 nativeSunHandles.insert(record.handle);
7126 }
7127 for (const auto& record : metadata.mleaderStyles()) {
7128 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7129 || record.handle == 0
7130 || hasReplayableRawMLeaderStyle(metadata, record.handle)) {
7131 continue;
7132 }
7133 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
7134 if (m_dwgW->registerMLeaderStyleObjectClass(&style))
7135 nativeMLeaderStyleHandles.insert(record.handle);
7136 }
7137 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER registration moved
7138 // below to the `canRegisterCustomClassObjects` block (PR 13f).
7139 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
7140 // DICTIONARYVAR registration moved below to the same block.
7141 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
7142 // registration moved below to the `canRegisterCustomClassObjects`
7143 // block.
7144 }
7145
7146 // PR 13f — custom-class families with version-clean encoders +
7147 // round-trip-grade Records register here on the broadened
7148 // `canRegisterCustomClassObjects` gate (≥AC1015) instead of the
7149 // AC1021+ `canWriteModernObjects` gate. The matching dispatch loop
7150 // in writeObjects uses the same gate so the CLASSES section entry and
7151 // the OBJECTS instance always land together.
7152 if (canRegisterCustomClassObjects) {
7153 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) — must be
7154 // registered here, BEFORE writeDwgClasses() emits the CLASSES section.
7155 // PR 8d.1b + PR 13f.
7156 for (const auto& record : metadata.rasterVariables()) {
7157 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7158 || record.handle == 0) {
7159 continue;
7160 }
7161 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
7162 if (m_dwgW->registerRasterVariablesObjectClass(&rv))
7163 nativeRasterVariablesHandles.insert(record.handle);
7164 }
7165 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
7166 for (const auto& record : metadata.wipeoutVariables()) {
7167 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7168 || record.handle == 0) {
7169 continue;
7170 }
7171 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
7172 if (m_dwgW->registerWipeoutVariablesObjectClass(&wv))
7173 nativeWipeoutVariablesHandles.insert(record.handle);
7174 }
7175 // GEODATA (AcDbGeoData, custom class 506) — PR 8d.1c + PR 13f.
7176 for (const auto& record : metadata.geoData()) {
7177 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7178 || record.handle == 0) {
7179 continue;
7180 }
7181 DRW_GeoData gd = geoDataFromMetadata(record);
7182 if (m_dwgW->registerGeoDataObjectClass(&gd))
7183 nativeGeoDataHandles.insert(record.handle);
7184 }
7185 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) — PR 8d.1d +
7186 // PR 13f.
7187 for (const auto& record : metadata.spatialFilters()) {
7188 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7189 || record.handle == 0) {
7190 continue;
7191 }
7192 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
7193 if (m_dwgW->registerSpatialFilterObjectClass(&sf))
7194 nativeSpatialFilterHandles.insert(record.handle);
7195 }
7196 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
7197 // DICTIONARYVAR (custom classes 508-512). Encoders are version-
7198 // clean; SPATIAL_INDEX additionally gates its common-handle prefix
7199 // on `version > AC1018` (mirrors its parser). All 5 default to
7200 // enabled in `isDwgClassEnabled` (no per-class gate needed in
7201 // dwgwriter.h).
7202 for (const auto& record : metadata.scales()) {
7203 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7204 || record.handle == 0) {
7205 continue;
7206 }
7207 DRW_Scale s = scaleFromMetadata(record);
7208 if (m_dwgW->registerScaleObjectClass(&s))
7209 nativeScaleHandles.insert(record.handle);
7210 }
7211 for (const auto& record : metadata.idBuffers()) {
7212 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7213 || record.handle == 0) {
7214 continue;
7215 }
7216 DRW_IDBuffer b = idBufferFromMetadata(record);
7217 if (m_dwgW->registerIDBufferObjectClass(&b))
7218 nativeIDBufferHandles.insert(record.handle);
7219 }
7220 for (const auto& record : metadata.layerIndexes()) {
7221 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7222 || record.handle == 0) {
7223 continue;
7224 }
7225 DRW_LayerIndex li = layerIndexFromMetadata(record);
7226 if (m_dwgW->registerLayerIndexObjectClass(&li))
7227 nativeLayerIndexHandles.insert(record.handle);
7228 }
7229 for (const auto& record : metadata.spatialIndexes()) {
7230 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7231 || record.handle == 0) {
7232 continue;
7233 }
7234 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
7235 if (m_dwgW->registerSpatialIndexObjectClass(&si))
7236 nativeSpatialIndexHandles.insert(record.handle);
7237 }
7238 for (const auto& record : metadata.dictionaryVars()) {
7239 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7240 || record.handle == 0) {
7241 continue;
7242 }
7243 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
7244 if (m_dwgW->registerDictionaryVarObjectClass(&dv))
7245 nativeDictionaryVarHandles.insert(record.handle);
7246 }
7247 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
7248 // (custom classes 513-516). Encoders are version-clean (FIELD
7249 // has a parser-mirrored `version < AC1021` branch for the
7250 // legacy m_formatString TV). All 4 default to enabled in
7251 // `isDwgClassEnabled`.
7252 for (const auto& record : metadata.dictionariesWithDefault()) {
7253 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7254 || record.handle == 0) {
7255 continue;
7256 }
7257 DRW_DictionaryWithDefault dwd =
7258 dictionaryWithDefaultFromMetadata(record);
7259 if (m_dwgW->registerDictionaryWithDefaultObjectClass(&dwd))
7260 nativeDictionaryWithDefaultHandles.insert(record.handle);
7261 }
7262 for (const auto& record : metadata.sortEntsTables()) {
7263 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7264 || record.handle == 0) {
7265 continue;
7266 }
7267 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
7268 if (m_dwgW->registerSortEntsTableObjectClass(&se))
7269 nativeSortEntsTableHandles.insert(record.handle);
7270 }
7271 for (const auto& record : metadata.fieldLists()) {
7272 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7273 || record.handle == 0) {
7274 continue;
7275 }
7276 DRW_FieldList fl = fieldListFromMetadata(record);
7277 if (m_dwgW->registerFieldListObjectClass(&fl))
7278 nativeFieldListHandles.insert(record.handle);
7279 }
7280 for (const auto& record : metadata.fields()) {
7281 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7282 || record.handle == 0) {
7283 continue;
7284 }
7285 DRW_Field f = fieldFromMetadata(record);
7286 if (m_dwgW->registerFieldObjectClass(&f))
7287 nativeFieldHandles.insert(record.handle);
7288 }
7289 for (const auto& record : metadata.underlayDefinitions()) {
7290 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7291 || record.handle == 0) {
7292 continue;
7293 }
7294 DRW_UnderlayDefinition definition =
7295 underlayDefinitionFromMetadata(record);
7296 if (m_dwgW->registerUnderlayDefinitionObjectClass(&definition))
7297 nativeUnderlayDefinitionHandles.insert(record.handle);
7298 }
7299 }
7300
7301 for (const auto& record : metadata.rawObjects()) {
7302 // Never register a CLASSES entry for raw bytes that cannot be replayed
7303 // into this exact target version. This mirrors the emit-loop guard so
7304 // a rejected carrier cannot leave an orphan CLASSES entry behind.
7305 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
7306 continue;
7307 }
7308 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
7309 || !record.isCustomClass) {
7310 continue;
7311 }
7312 if (nativeSunHandles.count(record.handle) != 0 && isSunRawObject(record))
7313 continue;
7314 if (nativeMLeaderStyleHandles.count(record.handle) != 0
7315 && isMLeaderStyleRawObject(record))
7316 continue;
7317 if (nativeRasterVariablesHandles.count(record.handle) != 0
7318 && isRasterVariablesRawObject(record))
7319 continue;
7320 if (nativeWipeoutVariablesHandles.count(record.handle) != 0
7321 && isWipeoutVariablesRawObject(record))
7322 continue;
7323 if (nativeGeoDataHandles.count(record.handle) != 0
7324 && isGeoDataRawObject(record))
7325 continue;
7326 if (nativeSpatialFilterHandles.count(record.handle) != 0
7327 && isSpatialFilterRawObject(record))
7328 continue;
7329 // PR 8d.2a — five small no-storage OBJECTS families.
7330 if (nativeScaleHandles.count(record.handle) != 0
7331 && isScaleRawObject(record))
7332 continue;
7333 if (nativeIDBufferHandles.count(record.handle) != 0
7334 && isIDBufferRawObject(record))
7335 continue;
7336 if (nativeLayerIndexHandles.count(record.handle) != 0
7337 && isLayerIndexRawObject(record))
7338 continue;
7339 if (nativeSpatialIndexHandles.count(record.handle) != 0
7340 && isSpatialIndexRawObject(record))
7341 continue;
7342 if (nativeDictionaryVarHandles.count(record.handle) != 0
7343 && isDictionaryVarRawObject(record))
7344 continue;
7345 // PR 8d.2b — four larger no-storage OBJECTS families.
7346 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0
7347 && isDictionaryWithDefaultRawObject(record))
7348 continue;
7349 if (nativeSortEntsTableHandles.count(record.handle) != 0
7350 && isSortEntsTableRawObject(record))
7351 continue;
7352 if (nativeFieldListHandles.count(record.handle) != 0
7353 && isFieldListRawObject(record))
7354 continue;
7355 if (nativeFieldHandles.count(record.handle) != 0
7356 && isFieldRawObject(record))
7357 continue;
7358 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0
7359 && isUnderlayDefinitionRawObject(record))
7360 continue;
7361 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
7362 m_dwgW->registerRawDwgObjectClass(&object);
7363 }
7364#endif
7365}
7366
7367void RS_FilterDXFRW::writeLType(const UTF8STRINGstd::string& lTypeName, const UTF8STRINGstd::string& ltDescription, int ltSize,
7368 double ltLength, const std::vector<double>& ltPath) {
7369 DRW_LType ltype;
7370 ltype.updateValues(lTypeName, ltDescription, ltSize, ltLength, ltPath);
7371 if (m_dwgW) {
7372 m_dwgW->addLType(&ltype);
7373 return;
7374 }
7375 m_dxfW->writeLineType(&ltype);
7376}
7377
7378void RS_FilterDXFRW::writeLTypes() {
7379 writeLType("CONTINUOUS", "Solid line", 0, 0, {});
7380 writeLType("ByLayer", "", 0, 0, {});
7381 writeLType("ByBlock", "", 0, 0, {});
7382 writeLType("DOT", "Dot . . . . . . . . . . . . . . . . . . . . . .", 2, 6.35, {0.0, -6.35});
7383 writeLType("DOTTINY", "Dot (.15x) .....................................", 2, 0.9525, {0.0, -0.9525});
7384 writeLType("DOT2", "Dot (.5x) .....................................", 2, 3.175, {0.0, -3.175});
7385 writeLType("DOTX2", "Dot (2x) . . . . . . . . . . . . .", 2, 12.7, {0.0, -12.7});
7386 writeLType("DASHED", "Dashed _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2, 19.05, {12.7, -6.35});
7387 writeLType("DASHEDTINY", "Dashed (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2, 2.8575, {1.905, -0.9525});
7388 writeLType("DASHED2", "Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _", 2, 9.525, {6.35, -3.175});
7389 writeLType("DASHEDX2", "Dashed (2x) ____ ____ ____ ____ ____ ___", 2, 38.1, {25.4, -12.7});
7390 writeLType("DASHDOT", "Dash dot __ . __ . __ . __ . __ . __ . __ . __", 4, 25.4, {12.7, -6.35, 0.0, -6.35});
7391 writeLType("DASHDOTTINY", "Dash dot (.15x) _._._._._._._._._._._._._._._.", 4, 3.81, {1.905, -0.9525, 0.0, -0.9525});
7392 writeLType("DASHDOT2", "Dash dot (.5x) _._._._._._._._._._._._._._._.", 4, 12.7, {6.35, -3.175, 0.0, -3.175});
7393 writeLType("DASHDOTX2", "Dash dot (2x) ____ . ____ . ____ . ___", 4, 50.8, {25.4, -12.7, 0.0, -12.7});
7394 writeLType("DIVIDE", "Divide ____ . . ____ . . ____ . . ____ . . ____", 6, 31.75, {12.7, -6.35, 0.0, -6.35, 0.0, -6.35});
7395 writeLType("DIVIDETINY", "Divide (.15x) __..__..__..__..__..__..__..__.._", 6, 4.7625, {1.905, -0.9525, 0.0, -0.9525, 0.0, -0.9525});
7396 writeLType("DIVIDE2", "Divide (.5x) __..__..__..__..__..__..__..__.._", 6, 15.875, {6.35, -3.175, 0.0, -3.175, 0.0, -3.175});
7397 writeLType("DIVIDEX2", "Divide (2x) ________ . . ________ . . _", 6, 63.5, {25.4, -12.7, 0.0, -12.7, 0.0, -12.7});
7398 writeLType("BORDER", "Border __ __ . __ __ . __ __ . __ __ . __ __ .", 6, 44.45, {12.7, -6.35, 12.7, -6.35, 0.0, -6.35});
7399 writeLType("BORDERTINY", "Border (.15x) __.__.__.__.__.__.__.__.__.__.__.", 6, 6.6675, {1.905, -0.9525, 1.905, -0.9525, 0.0, -0.9525});
7400 writeLType("BORDER2", "Border (.5x) __.__.__.__.__.__.__.__.__.__.__.", 6, 22.225, {6.35, -3.175, 6.35, -3.175, 0.0, -3.175});
7401 writeLType("BORDERX2", "Border (2x) ____ ____ . ____ ____ . ___", 6, 88.9, {25.4, -12.7, 25.4, -12.7, 0.0, -12.7});
7402 writeLType("CENTER", "Center ____ _ ____ _ ____ _ ____ _ ____ _ ____", 4, 50.8, {31.75, -6.35, 6.35, -6.35});
7403 writeLType("CENTERTINY", "Center (.15x) ___ _ ___ _ ___ _ ___ _ ___ _ ___", 4, 7.62, {4.7625, -0.9525, 0.9525, -0.9525});
7404 writeLType("CENTER2", "Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___", 4, 28.575, {19.05, -3.175, 3.175, -3.175});
7405 writeLType("CENTERX2", "Center (2x) ________ __ ________ __ _____", 4, 101.6, {63.5, -12.7, 12.7, -12.7});
7406}
7407
7408void RS_FilterDXFRW::writeLayers() {
7409 DRW_Layer lay;
7410 RS_LayerList* ll = m_graphic->getLayerList();
7411 int exact_rgb;
7412 for (unsigned int i = 0; i < ll->count(); i++) {
7413 lay.reset();
7414 RS_Layer* l = ll->at(i);
7415 RS_Pen pen = l->getPen();
7416 lay.name = l->getName().toUtf8().data();
7417 lay.color = colorToNumber(pen.getColor(), &exact_rgb);
7418 lay.color24 = exact_rgb;
7419 lay.lWeight = widthToNumber(pen.getWidth());
7420 lay.lineType = lineTypeToName(pen.getLineType()).toStdString();
7421 lay.flags = l->isFrozen() ? 0x01 : 0x00;
7422 if (l->isLocked()) {
7423 lay.flags |= 0x04;
7424 }
7425 lay.plotF = l->isPrint();
7426 if (l->isConstruction()) {
7427 lay.extData.push_back(new DRW_Variant(1001, "LibreCad"));
7428 lay.extData.push_back(new DRW_Variant(1070, 1));
7429 // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_FilterDXF::writeLayers: layer %s saved as construction layer", lay.name.c_str());
7430 }
7431 if (m_dwgW) {
7432 m_dwgW->addLayer(&lay);
7433 }
7434 else {
7435 m_dxfW->writeLayer(&lay);
7436 }
7437 }
7438}
7439
7440void RS_FilterDXFRW::writeUCSs() {
7441 LC_UCSList* vl = m_graphic->getUCSList();
7442 DRW_UCS ucs;
7443 for (unsigned int i = 1; i < vl->count(); i++) {
7444 ucs.reset();
7445
7446 const LC_UCS* u = vl->at(i);
7447 if (u->isTemporary()) {
7448 // temporary ucs without name are not persistent
7449 continue;
7450 }
7451 ucs.name = u->getName().toUtf8().data();
7452 ucs.origin.x = u->getOrigin().x;
7453 ucs.origin.y = u->getOrigin().y;
7454 ucs.origin.z = u->getOrigin().z;
7455
7456 ucs.xAxisDirection.x = u->getXAxis().x;
7457 ucs.xAxisDirection.y = u->getXAxis().y;
7458 ucs.xAxisDirection.z = u->getXAxis().z;
7459
7460 ucs.yAxisDirection.x = u->getYAxis().x;
7461 ucs.yAxisDirection.y = u->getYAxis().y;
7462 ucs.yAxisDirection.z = u->getYAxis().z;
7463
7464 ucs.orthoOrigin.x = u->getOrthoOrigin().x;
7465 ucs.orthoOrigin.y = u->getOrthoOrigin().y;
7466 ucs.orthoOrigin.z = u->getOrthoOrigin().z;
7467
7468 ucs.orthoType = u->getOrthoType();
7469 ucs.elevation = u->getElevation();
7470
7471 if (m_dwgW) {
7472 m_dwgW->addUCS(&ucs);
7473 }
7474 else {
7475 m_dxfW->writeUCS(&ucs);
7476 }
7477 }
7478}
7479
7480void RS_FilterDXFRW::writeViews() {
7481 LC_ViewList* vl = m_graphic->getViewList();
7482 DRW_View vie;
7483 for (unsigned int i = 0; i < vl->count(); i++) {
7484 vie.reset();
7485 LC_View* view = vl->at(i);
7486 vie.name = view->getName().toUtf8().data();
7487 vie.center.x = view->getCenter().x;
7488 vie.center.y = view->getCenter().y;
7489 vie.center.z = view->getCenter().z;
7490
7491 vie.targetPoint.x = view->getTargetPoint().x;
7492 vie.targetPoint.y = view->getTargetPoint().y;
7493 vie.targetPoint.z = view->getTargetPoint().z;
7494
7495 vie.size.x = view->getSize().x;
7496 vie.size.y = view->getSize().y;
7497 vie.size.z = view->getSize().z;
7498
7499 vie.frontClippingPlaneOffset = view->getFrontClippingPlaneOffset();
7500 vie.backClippingPlaneOffset = view->getBackClippingPlaneOffset();
7501 vie.lensLen = view->getLensLen();
7502 vie.flags = view->getFlags();
7503 vie.viewMode = view->getViewMode();
7504
7505 vie.viewDirectionFromTarget.x = view->getViewDirection().x;
7506 vie.viewDirectionFromTarget.y = view->getViewDirection().y;
7507 vie.viewDirectionFromTarget.z = view->getViewDirection().z;
7508
7509 vie.cameraPlottable = view->isCameraPlottable();
7510 vie.renderMode = view->getRenderMode();
7511
7512 vie.twistAngle = view->getTwistAngle();
7513
7514 if (view->isHasUCS()){
7515 vie.hasUCS = true;
7516 LC_UCS *ucs = view->getUCS();
7517 vie.ucsOrigin.x = ucs->getOrigin().x;
7518 vie.ucsOrigin.y = ucs->getOrigin().y;
7519 vie.ucsOrigin.z = ucs->getOrigin().z;
7520
7521 vie.ucsOrthoType = ucs->getOrthoType();
7522 vie.ucsElevation = ucs->getElevation();
7523
7524 vie.ucsXAxis.x = ucs->getXAxis().x;
7525 vie.ucsXAxis.y = ucs->getXAxis().y;
7526 vie.ucsXAxis.z = ucs->getXAxis().z;
7527
7528 vie.ucsYAxis.x = ucs->getYAxis().x;
7529 vie.ucsYAxis.y = ucs->getYAxis().y;
7530 vie.ucsYAxis.z = ucs->getYAxis().z;
7531
7532 // fixme - complete - base UCS_ID and Named UCS_ID support. That's might be necessary to support views/UCS
7533 // created outside of LibreCAD.
7534 // Return to this after normal support of UCS.
7535 // vie.namedUCS_ID = ucs.
7536 // vie.baseUCS_ID = ucs.
7537 }
7538 if (const auto* viewMetadata =
7539 m_graphic->dwgAdvancedMetadata().findViewByName(vie.name)) {
7540 vie.namedUCS_ID = viewMetadata->namedUcsHandle;
7541 vie.baseUCS_ID = viewMetadata->baseUcsHandle;
7542 vie.m_useDefaultLights = viewMetadata->useDefaultLights;
7543 vie.m_defaultLightingType = viewMetadata->defaultLightingType;
7544 vie.m_brightness = viewMetadata->brightness;
7545 vie.m_contrast = viewMetadata->contrast;
7546 vie.m_ambientColor = viewMetadata->ambientColor;
7547 vie.m_backgroundHandle = viewMetadata->backgroundHandle;
7548 vie.m_visualStyleHandle = viewMetadata->visualStyleHandle;
7549 vie.m_sunHandle = viewMetadata->sunHandle;
7550 vie.m_liveSectionHandle = viewMetadata->liveSectionHandle;
7551 }
7552 if (m_dwgW != nullptr) {
7553 m_dwgW->addView(&vie);
7554 } else {
7555 m_dxfW->writeView(&vie);
7556 }
7557 }
7558}
7559
7560void RS_FilterDXFRW::writeTextstyles() {
7561 QHash<QString, QString> styles;
7562 QString sty;
7563 //Find fonts used by text entities in drawing
7564 for (RS_Entity* e : lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
7565 if (!e->isDeleted()) {
7566 auto rtti = e->rtti();
7567 switch (rtti) {
7568 case RS2::EntityMText:
7569 sty = static_cast<RS_MText*>(e)->getStyle();
7570 break;
7571 case RS2::EntityText:
7572 sty = static_cast<RS_Text*>(e)->getStyle();
7573 break;
7574 default:
7575 if (RS2::isDimensionalEntity(rtti)) {
7576 auto dim = dynamic_cast<RS_Dimension*>(e);
7577 if (dim != nullptr) {
7578 auto styleOverride = dim->getDimStyleOverride();
7579 if (styleOverride != nullptr) {
7580 auto dimTextStyle = styleOverride->text()->style();
7581 sty = dimTextStyle;
7582 }
7583 }
7584 }
7585 else {
7586 sty.clear();
7587 }
7588 break;
7589 }
7590 if (!sty.isEmpty() && !styles.contains(sty)) {
7591 styles.insert(sty, sty);
7592 }
7593 }
7594 }
7595 //Find fonts used by text entities in blocks
7596 RS_Block* blk;
7597 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
7598 blk = m_graphic->blockAt(i);
7599 for (RS_Entity* e : lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
7600 if (!e->isDeleted()) {
7601 RS2::EntityType rtti = e->rtti();
7602 switch (rtti) {
7603 case RS2::EntityMText:
7604 sty = static_cast<RS_MText*>(e)->getStyle();
7605 break;
7606 case RS2::EntityText:
7607 sty = static_cast<RS_Text*>(e)->getStyle();
7608 break;
7609 default:
7610 if (RS2::isDimensionalEntity(rtti)) {
7611 auto dim = dynamic_cast<RS_Dimension*>(e);
7612 if (dim != nullptr) {
7613 auto styleOverride = dim->getDimStyleOverride();
7614 if (styleOverride != nullptr) {
7615 auto dimTextStyle = styleOverride->text()->style();
7616 sty = dimTextStyle;
7617 }
7618 }
7619 }
7620 else {
7621 sty.clear();
7622 }
7623
7624 break;
7625 }
7626 if (!sty.isEmpty() && !styles.contains(sty)) {
7627 styles.insert(sty, sty);
7628 }
7629 }
7630 }
7631 }
7632
7633 auto dimStyleList = m_graphic->getDimStyleList();
7634
7635 for (const auto ds : *dimStyleList->getStylesList()) {
7636 sty = ds->text()->style();
7637 if (!sty.isEmpty() && !styles.contains(sty)) {
7638 styles.insert(sty, sty);
7639 }
7640 }
7641
7642 DRW_Textstyle ts;
7643 QHash<QString, QString>::const_iterator it = styles.constBegin();
7644 while (it != styles.constEnd()) {
7645 ts.name = (it.key()).toStdString();
7646 ts.font = it.value().toStdString();
7647 // ts.flags;
7648 if (m_dwgW) {
7649 m_dwgW->addTextstyle(&ts);
7650 }
7651 else {
7652 m_dxfW->writeTextstyle(&ts);
7653 }
7654 ++it;
7655 }
7656}
7657
7658void RS_FilterDXFRW::writeVports() {
7659 DRW_Vport vp;
7660 vp.name = "*Active";
7661 m_graphic->isGridOn() ? vp.grid = 1 : vp.grid = 0;
7662 RS_Vector spacing = m_graphic->getVariableVector("$GRIDUNIT",RS_Vector(0.0,0.0));
7663 vp.gridBehavior = 3;
7664 vp.gridSpacing.x = spacing.x;
7665 vp.gridSpacing.y = spacing.y;
7666 vp.snapStyle = m_graphic->isIsometricGrid();
7667 vp.snapIsopair = m_graphic->getIsoView();
7668 if (vp.snapIsopair > 2) {
7669 vp.snapIsopair = 0;
7670 }
7671 if (fabs(spacing.x) < 1.0e-6) {
7672 vp.gridBehavior = 7; //auto
7673 vp.gridSpacing.x = 10;
7674 }
7675 if (fabs(spacing.y) < 1.0e-6) {
7676 vp.gridBehavior = 7; //auto
7677 vp.gridSpacing.y = 10;
7678 }
7679 RS_GraphicView *gv = m_graphic->getGraphicView();
7680 if (gv) {
7681 LC_GraphicViewport *viewport = gv->getViewPort();
7682 RS_Vector fac = viewport->getFactor();
7683 vp.height = gv->getHeight() / fac.y;
7684 vp.ratio = (double) gv->getWidth() / (double) gv->getHeight();
7685 vp.center.x = (gv->getWidth() - viewport->getOffsetX()) / (fac.x * 2.0);
7686 vp.center.y = (gv->getHeight() - viewport->getOffsetY()) / (fac.y * 2.0);
7687 }
7688 if (m_dwgW) {
7689 m_dwgW->addVport(&vp);
7690 return;
7691 }
7692 m_dxfW->writeVport(&vp);
7693}
7694
7695void RS_FilterDXFRW::writeDimstyles() {
7696 LC_DimStylesList* dimStylesList = m_graphic->getDimStyleList();
7697 auto stylesList = dimStylesList->getStylesList();
7698 for (auto ds: *stylesList) {
7699 DRW_Dimstyle dst;
7700 prepareDRWDimStyle(dst, ds);
7701 if (m_dwgW) {
7702 m_dwgW->addDimstyle(&dst);
7703 }
7704 else {
7705 m_dxfW->writeDimstyle(&dst);
7706 }
7707 }
7708}
7709
7710void RS_FilterDXFRW::prepareDRWDimStyleZerosSuppression(DRW_Dimstyle& d, const LC_DimStyle* ds){
7711 auto zeros = ds->zerosSuppression();
7712 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMZIN)) {
7713 d.add("$DIMZIN", 78, zeros->linearRaw());
7714 }
7715 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
7716 d.add("$DIMAZIN", 79, zeros->angularRaw());
7717 }
7718
7719 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
7720 d.add("$DIMTZIN", 284, zeros->toleranceRaw());
7721 }
7722 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
7723 d.add("$DIMALTZ", 285, zeros->altLinearRaw());
7724 }
7725 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
7726 d.add("$DIMALTTZ", 286, zeros->altToleranceRaw());
7727 }
7728}
7729
7730void RS_FilterDXFRW::prepareDRWDimStyleArrows(DRW_Dimstyle& d, const LC_DimStyle* ds){
7731 auto arrow = ds->arrowhead();
7732 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
7733 QString blockName = arrow->sameBlockName();
7734 if (!blockName.isEmpty()) {
7735 auto blkName = blockName.toStdString();
7736 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
7737 if (blkHandle > 0) {
7738 d.add("_$DIMBLK", 342, toHexStr(blkHandle).toStdString());
7739 }
7740 d.add("$DIMBLK", 5, blkName);
7741 }
7742 }
7743 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
7744 QString blockName = arrow->arrowHeadBlockNameFirst();
7745 if (!blockName.isEmpty()) {
7746 auto blkName = blockName.toStdString();
7747 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
7748 if (blkHandle > 0) {
7749 d.add("_$DIMBLK1", 343, toHexStr(blkHandle).toStdString());
7750 }
7751 d.add("$DIMBLK1", 6, blkName);
7752 }
7753 }
7754 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
7755 QString blockName = arrow->arrowHeadBlockNameSecond();
7756 if (!blockName.isEmpty()) {
7757 auto blkName = blockName.toStdString();
7758 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
7759 if (blkHandle > 0) {
7760 d.add("_$DIMBLK2", 344, toHexStr(blkHandle).toStdString());
7761 }
7762 d.add("$DIMBLK2", 7, blkName);
7763 }
7764 }
7765 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
7766 d.add("$DIMASZ", 41, arrow->size());
7767 }
7768 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
7769 d.add("$DIMTSZ", 142, arrow->tickSize());
7770 }
7771 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
7772 d.add("$DIMSAH", 173, arrow->isUseSeparateArrowHeads() ? 1 : 0);
7773 }
7774 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
7775 d.add("$DIMSOXD", 175, arrow->suppression());
7776 }
7777}
7778
7779void RS_FilterDXFRW::prepareDRWDimStyleScaling(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7780 auto scale = ds->scaling();
7781 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
7782 d.add("$DIMSCALE", 40, scale->scale());
7783 }
7784 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
7785 d.add("$DIMLFAC", 144, scale->linearFactor());
7786 }
7787}
7788
7789void RS_FilterDXFRW::prepareDRWDimStyleExtLine(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7790 auto extLine = ds->extensionLine();
7791 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
7792 d.add("$DIMEXO", 42, extLine->distanceFromOriginPoint());
7793 }
7794 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
7795 d.add("$DIMEXE", 44, extLine->distanceBeyondDimLine());
7796 }
7797 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
7798 d.add("$DIMFXL", 49, extLine->fixedLength());
7799 }
7800 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
7801 d.add("$DIMFXLON", 290, extLine->hasFixedLength() ? 1 : 0);
7802 }
7803 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
7804 auto lineWidth = extLine->lineWidth();
7805 int lw = RS2::lineWidth2dxfInt(lineWidth);
7806 d.add("$DIMLWE", 372, lw);
7807 }
7808 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
7809 auto lineColor = extLine->color();
7810 int colRGB;
7811 int colNum = colorToNumber(lineColor, &colRGB);
7812 d.add("$DIMCLRE", 177, colNum);
7813 }
7814 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
7815 d.add("$DIMSE1", 75, extLine->suppressFirstLine());
7816 }
7817 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
7818 d.add("$DIMSE2", 76, extLine->suppressSecondLine());
7819 }
7820 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
7821 int lineTypeHandle = findLineTypeHandleToWrite(extLine->lineTypeFirstRaw());
7822 if (lineTypeHandle > 0) {
7823 auto handleStr = toHexStr(lineTypeHandle);
7824 d.add("$DIMLTEX1", 347, handleStr.toStdString());
7825 }
7826 // auto lineType = extLine->lineTypeFirstRaw().toStdString();
7827 // d.add("$DIMLTEX1", 347, lineType);
7828 }
7829 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
7830 int lineTypeHandle = findLineTypeHandleToWrite(extLine->lineTypeSecondRaw());
7831 if (lineTypeHandle > 0) {
7832 auto handleStr = toHexStr(lineTypeHandle);
7833 d.add("$DIMLTEX2", 348, handleStr.toStdString());
7834 }
7835 // auto lineType = extLine->lineTypeFirstRaw().toStdString();
7836 // d.add("$DIMLTEX2", 348, lineType);
7837 }
7838}
7839
7840void RS_FilterDXFRW::prepareDRWDimStyleDimLine(DRW_Dimstyle& d,const LC_DimStyle* ds) {
7841 auto dimLine = ds->dimensionLine();
7842 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
7843 auto lineWidth = dimLine->lineWidth();
7844 int lw = RS2::lineWidth2dxfInt(lineWidth);
7845 d.add("$DIMLWD", 371, lw);
7846 }
7847 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
7848 d.add("$DIMDLE", 46, dimLine->distanceBeyondExtLinesForObliqueStroke());
7849 }
7850 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
7851 d.add("$DIMDLI", 43, dimLine->baseLineDimLinesSpacing());
7852 }
7853 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
7854 d.add("$DIMGAP", 147, dimLine->lineGap());
7855 }
7856 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
7857 auto lineColor = dimLine->color();
7858 int colRGB;
7859 int colNum = colorToNumber(lineColor, &colRGB);
7860 d.add("$DIMCLRD", 176, colNum);
7861 }
7862 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
7863 d.add("$DIMSD1", 281, dimLine->suppressFirstLine());
7864 }
7865 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
7866 d.add("$DIMSD2", 281, dimLine->suppressSecondLine());
7867 }
7868 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
7869 d.add("$DIMTOFL", 172, dimLine->drawPolicyForOutsideText());
7870 }
7871 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
7872 // auto value = dimLine->lineType();
7873 // auto lineType = dimLine->lineTypeName().toStdString();
7874 int lineTypeHandle = findLineTypeHandleToWrite(dimLine->lineTypeName());
7875 if (lineTypeHandle > 0) {
7876 auto handleStr = toHexStr(lineTypeHandle);
7877 d.add("$DIMLTYPE", 345, handleStr.toStdString());
7878 }
7879 }
7880}
7881
7882int RS_FilterDXFRW::findLineTypeHandleToWrite(const QString& name) const {
7883 if (m_dxfW == nullptr) {
7884 return -1;
7885 }
7886 std::string lineName = name.toUpper().toStdString();
7887 for (auto p: m_dxfW->getWritingContext()->lineTypesMap) {
7888 if (p.first.compare(lineName) == 0) {
7889 return p.second;
7890 }
7891 }
7892 return -1;
7893}
7894
7895void RS_FilterDXFRW::prepareDRWDimStyleText(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7896 auto text = ds->text();
7897 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
7898 d.add("$DIMTXT", 140, text->height());
7899 }
7900
7901 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
7902 auto styleName = text->style().toStdString();
7903 int styleHandle = m_dxfW != nullptr ? m_dxfW->getTextStyleHandle(styleName) : -1;
7904 if (styleHandle > 0) {
7905 d.add("$DIMTXSTY", 340, toHexStr(styleHandle).toStdString());
7906 }
7907 }
7908 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
7909 d.add("$DIMTOH", 74, text->orientationOutside());
7910 }
7911 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
7912 d.add("$DIMTIH", 73, text->orientationInside());
7913 }
7914 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
7915 d.add("$DIMJUST", 280, text->horizontalPositioning());
7916 }
7917 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
7918 auto lineColor = text->color();
7919 int colRGB;
7920 int colNum = colorToNumber(lineColor, &colRGB);
7921 d.add("$DIMCLRT", 178, colNum);
7922 }
7923 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
7924 d.add("$DIMTAD", 77, text->verticalPositioning());
7925 }
7926 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
7927 d.add("$DIMTIX", 174, text->extLinesRelativePlacement());
7928 }
7929 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
7930 d.add("$DIMTFILL", 69, text->backgroundFillMode());
7931 }
7932 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
7933 auto lineColor = text->explicitBackgroundFillColor();
7934 int colRGB;
7935 int colNum = colorToNumber(lineColor, &colRGB);
7936 d.add("$DIMTFILLCLR", 70, colNum);
7937 }
7938 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
7939 d.add("$DIMTXTDIRECTION", 292, text->readingDirection());
7940 }
7941 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
7942 d.add("$DIMTVP", 145, text->verticalDistanceToDimLine());
7943 }
7944 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
7945 d.add("$DIMUPT", 288, text->cursorControlPolicy());
7946 }
7947 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
7948 d.add("$DIMTMOVE", 279, text->positionMovementPolicy());
7949 }
7950 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
7951 d.add("$DIMATFIT", 289, text->unsufficientSpacePolicy());
7952 }
7953}
7954
7955void RS_FilterDXFRW::prepareDRWDimStyleLinearFormat(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7956 auto linear = ds->linearFormat();
7957 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
7958 d.add("$DIMLUNIT", 277, linear->formatRaw());
7959 }
7960 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
7961 d.add("$DIMDSEP", 278, linear->decimalFormatSeparatorChar());
7962 }
7963 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
7964 d.add("$DIMDEC", 271, linear->decimalPlaces());
7965 }
7966 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
7967 d.add("$DIMPOST", 3, linear->prefixOrSuffix().toStdString());
7968 }
7969 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
7970 d.add("$DIMALT", 170, linear->alternateUnits());
7971 }
7972 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
7973 d.add("$DIMALTU", 273, linear->altFormatRaw());
7974 }
7975 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
7976 d.add("$DIMALTD", 171, linear->altDecimalPlaces());
7977 }
7978 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
7979 d.add("$DIMALTF", 143, linear->altUnitsMultiplier());
7980 }
7981 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
7982 d.add("$DIMAPOST", 4, linear->altPrefixOrSuffix().toStdString());
7983 }
7984}
7985
7986void RS_FilterDXFRW::prepareDRWDimStyleFractions(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7987 auto fraction = ds->fractions();
7988 if (fraction->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
7989 d.add("$DIMFRAC", 276, fraction->style());
7990 }
7991}
7992
7993void RS_FilterDXFRW::prepareDRWDimStyleAngularFormat(DRW_Dimstyle& d, const LC_DimStyle* ds) {
7994 auto angular = ds->angularFormat();
7995 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
7996 d.add("$DIMAUNIT", 275, angular->format());
7997 }
7998 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
7999 d.add("$DIMADEC", 179, angular->decimalPlaces());
8000 }
8001
8002 auto round = ds->roundOff();
8003 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
8004 d.add("$DIMRND", 45, round->roundTo());
8005 }
8006 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
8007 d.add("$DIMALTRND", 148, round->altRoundTo());
8008 }
8009}
8010
8011void RS_FilterDXFRW::prepareDRWDimStyleRadial(DRW_Dimstyle& d,const LC_DimStyle* ds) {
8012 auto radial = ds->radial();
8013 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
8014 d.add("$DIMCEN", 141, radial->centerCenterMarkOrLineSize());
8015 }
8016}
8017
8018void RS_FilterDXFRW::prepareDRWDimStyleTolerance(DRW_Dimstyle& d, const LC_DimStyle* ds) {
8019 auto tolerance = ds->latteralTolerance();
8020 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
8021 d.add("$DIMTDEC", 272, tolerance->decimalPlaces());
8022 }
8023 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
8024 d.add("$DIMALTTD", 274, tolerance->decimalPlacesAltDim());
8025 }
8026 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
8027 d.add("$DIMTOL", 71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
8028 }
8029 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
8030 d.add("$DIMTOLJ", 283, tolerance->verticalJustification());
8031 }
8032 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
8033 d.add("$DIMTM", 48, tolerance->lowerToleranceLimit());
8034 }
8035 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
8036 d.add("$DIMTP", 47, tolerance->upperToleranceLimit());
8037 }
8038 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
8039 d.add("$DIMTFAC", 146, tolerance->heightScaleFactorToDimText());
8040 }
8041 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
8042 d.add("$DIMLIM", 72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
8043 }
8044}
8045
8046void RS_FilterDXFRW::prepareDRWDimStyleArc(DRW_Dimstyle& d, const LC_DimStyle* ds) {
8047 auto arc = ds->arc();
8048 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
8049 d.add("$DIMARCSYM", 90, arc->arcSymbolPosition());
8050 }
8051}
8052
8053void RS_FilterDXFRW::prepareDRWDimStyleLeader(DRW_Dimstyle& d,const LC_DimStyle* ds) {
8054 auto leader = ds->leader();
8055 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
8056 QString blockName = leader->arrowBlockName();
8057 if (!blockName.isEmpty()) {
8058 auto blkName = blockName.toStdString();
8059 int blkHandle = m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName) : -1;
8060 if (blkHandle > 0) {
8061 d.add("_$DIMLDRBLK", 341, toHexStr(blkHandle).toStdString());
8062 }
8063 }
8064 }
8065}
8066
8067void RS_FilterDXFRW::prepareDRWDimStyleExtData(DRW_Dimstyle& d,const LC_DimStyle* ds) {
8068 auto tolerance = ds->latteralTolerance();
8069 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTALN)) {
8070 d.extData.push_back(new DRW_Variant(1001, "ACAD_DSTYLE_DIMTALN"));
8071 d.extData.push_back(new DRW_Variant(1070, 392));
8072 d.extData.push_back(new DRW_Variant(1070, tolerance->adjustment()));
8073 }
8074 // todo - add support of ACAD_DIMSTYLE_DIMBREAK
8075 // todo - add support of ACAD_DIMSTYLE_DIMJAG
8076}
8077
8078void RS_FilterDXFRW::prepareDRWDimStyle(DRW_Dimstyle &d,const LC_DimStyle* ds) {
8079 d.name = ds->getName().toStdString();
8080
8081 auto savedMode = ds->getModifyCheckMode();
8082 if (ds->isBaseStyle()) {
8083 // base styles are written completely, regardless of fields modification state!
8084 ds->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
8085 }
8086
8087 // in AutoCAD, dimstyle that is specific for the type of dimension will contain only
8088 // values that are really overriden for the type. Other properties will be
8089 // extracted from the base style.
8090 // therefore, have need to check for properties that are modified and write only
8091 // one that has modified flag set...
8092
8093 prepareDRWDimStyleArrows(d, ds);
8094 prepareDRWDimStyleScaling(d, ds);
8095 prepareDRWDimStyleExtLine(d, ds);
8096 prepareDRWDimStyleDimLine(d, ds);
8097 prepareDRWDimStyleText(d, ds);
8098 prepareDRWDimStyleZerosSuppression(d, ds);
8099 prepareDRWDimStyleLinearFormat(d, ds);
8100 prepareDRWDimStyleAngularFormat(d, ds);
8101 prepareDRWDimStyleFractions(d, ds);
8102 prepareDRWDimStyleRadial(d, ds);
8103 prepareDRWDimStyleTolerance(d, ds);
8104 prepareDRWDimStyleArc(d, ds);
8105 prepareDRWDimStyleLeader(d, ds);
8106 prepareDRWDimStyleExtData(d, ds);
8107 // result->setDimunit(s.dimunit); // $DIMLUNIT
8108
8109 ds->setModifyCheckMode(savedMode); // restore modification flags
8110
8111 // fixme - return to this later, move to MLeaderStyle
8112 // auto mleader = ds->mleader();
8113 // var = s.get("$MLEADERSCALE");
8114 // if (var != nullptr) {
8115 // mleaderStyle->setScale(var->d_val());
8116 // }
8117}
8118
8119void RS_FilterDXFRW::writeObjects() {
8120 if (m_dwgW) {
8121 const auto& metadata = m_graphic->dwgAdvancedMetadata();
8122 // P3 #2: structural-collision remap built by fileExport — rewrite the
8123 // handle (and typed references to it) of preserved objects that
8124 // collide with the writer's fixed structural handles.
8125 auto remapHandle = [this](std::uint32_t h) -> std::uint32_t {
8126 auto it = m_dwgWriteHandleRemap.find(h);
8127 return it == m_dwgWriteHandleRemap.end() ? h : it->second;
8128 };
8129 auto remapEntry = [&remapHandle](DRW_TableEntry& e) {
8130 e.handle = remapHandle(e.handle);
8131 if (e.parentHandle > 0)
8132 e.parentHandle = static_cast<int>(remapHandle(
8133 static_cast<std::uint32_t>(e.parentHandle)));
8134 };
8135 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
8136 // PR 13a/b/c/d — DICTIONARY (ODA fixed type 42), XRECORD (type 79),
8137 // GROUP (type 72), LAYOUT (type 82), and ACDBPLACEHOLDER (type 80)
8138 // are universally available since R2000. Their encoders gate
8139 // AC1018+-only fields (e.g., LAYOUT shadePlot* + viewportCount
8140 // RawLong32 + plotViewHandle) on `version >= DRW::AC1018`, and
8141 // the AC1015-only plotViewName branch on `version < DRW::AC1018`.
8142 // ACDBPLACEHOLDER's encoder has no version-gated body fields —
8143 // only the standard string/handle split-buffer routing on
8144 // `version > AC1018`. Encoder smoke tests cover
8145 // AC1015/AC1018/AC1024/AC1027/AC1032 round-trip (see
8146 // `[dictionary]` / `[xrecord]` / `[group]` / `[layout]` /
8147 // `[placeholder]` cases). Broaden the dispatch gate for these
8148 // families from AC1021+ to AC1015+ ahead of the long-tail
8149 // families which still need their own validation.
8150 const bool canWriteFixedTypeObjects =
8151 m_dwgW->getVersion() >= DRW::AC1015;
8152 // PR 13f — custom-class families with version-clean encoders +
8153 // round-trip-grade Records dispatch on the broadened gate
8154 // (≥AC1015). Matches the same gate used in writeDwgClasses for
8155 // the CLASSES section entry registration; the entry and the
8156 // OBJECTS instance must land together or the reader's class
8157 // lookup fails and the object is silently dropped (see plan's
8158 // "Discovered during PR 13f" notes).
8159 const bool canRegisterCustomClassObjects =
8160 m_dwgW->getVersion() >= DRW::AC1015;
8161 std::set<std::uint32_t> nativeSunHandles;
8162 std::set<std::uint32_t> nativePlaceholderHandles;
8163 std::set<std::uint32_t> nativeMLeaderStyleHandles;
8164 std::set<std::uint32_t> nativeDictionaryHandles;
8165 std::set<std::uint32_t> nativeXRecordHandles;
8166 std::set<std::uint32_t> nativeLayoutHandles;
8167 std::set<std::uint32_t> nativeGroupHandles;
8168 std::set<std::uint32_t> nativeMLineStyleHandles;
8169 std::set<std::uint32_t> nativeRasterVariablesHandles;
8170 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
8171 std::set<std::uint32_t> nativeGeoDataHandles;
8172 std::set<std::uint32_t> nativeSpatialFilterHandles;
8173 // PR 8d.2a — five small no-storage OBJECTS families.
8174 std::set<std::uint32_t> nativeScaleHandles;
8175 std::set<std::uint32_t> nativeIDBufferHandles;
8176 std::set<std::uint32_t> nativeLayerIndexHandles;
8177 std::set<std::uint32_t> nativeSpatialIndexHandles;
8178 std::set<std::uint32_t> nativeDictionaryVarHandles;
8179 // PR 8d.2b — four larger no-storage OBJECTS families.
8180 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
8181 std::set<std::uint32_t> nativeSortEntsTableHandles;
8182 std::set<std::uint32_t> nativeFieldListHandles;
8183 std::set<std::uint32_t> nativeFieldHandles;
8184 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
8185 int nativeSunObjects = 0;
8186 int nativePlaceholderObjects = 0;
8187 int nativeMLeaderStyleObjects = 0;
8188 int nativeDictionaryObjects = 0;
8189 int nativeXRecordObjects = 0;
8190 int nativeLayoutObjects = 0;
8191 int nativeGroupObjects = 0;
8192 int nativeMLineStyleObjects = 0;
8193 int nativeRasterVariablesObjects = 0;
8194 int nativeWipeoutVariablesObjects = 0;
8195 int nativeGeoDataObjects = 0;
8196 int nativeSpatialFilterObjects = 0;
8197 int nativeScaleObjects = 0;
8198 int nativeIDBufferObjects = 0;
8199 int nativeLayerIndexObjects = 0;
8200 int nativeSpatialIndexObjects = 0;
8201 int nativeDictionaryVarObjects = 0;
8202 // PR 8d.2b — four larger no-storage OBJECTS families.
8203 int nativeDictionaryWithDefaultObjects = 0;
8204 int nativeSortEntsTableObjects = 0;
8205 int nativeFieldListObjects = 0;
8206 int nativeFieldObjects = 0;
8207 int nativeUnderlayDefinitionObjects = 0;
8208 if (canWriteModernObjects) {
8209 for (const auto& record : metadata.suns()) {
8210 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8211 && record.handle != 0) {
8212 nativeSunHandles.insert(record.handle);
8213 }
8214 }
8215 // ACDBPLACEHOLDER handle-set construction moved below to the
8216 // `canWriteFixedTypeObjects` block (PR 13d) — fixed type 80
8217 // is available since R2000 and the encoder is version-clean.
8218 for (const auto& record : metadata.mleaderStyles()) {
8219 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8220 && record.handle != 0
8221 && !hasReplayableRawMLeaderStyle(metadata, record.handle)) {
8222 nativeMLeaderStyleHandles.insert(record.handle);
8223 }
8224 }
8225 // DICTIONARY / XRECORD / LAYOUT / GROUP handle-set construction
8226 // moved below to the `canWriteFixedTypeObjects` block
8227 // (PR 13a/b/c) so the broadened gate applies even when
8228 // canWriteModernObjects is false (AC1015/AC1018 export path).
8229 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
8230 // construction moved to the `canRegisterCustomClassObjects`
8231 // block (PR 13f).
8232 // PR 8d.2a — five small no-storage OBJECTS families.
8233 // Handle-set construction moved to the
8234 // `canRegisterCustomClassObjects` block (PR 13g).
8235 // PR 8d.2b — four larger no-storage OBJECTS families.
8236 // Handle-set construction moved to the
8237 // `canRegisterCustomClassObjects` block (PR 13h).
8238 }
8239 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
8240 // construction sits in its own broadened block (≥AC1015) so the
8241 // raw-replay blocker (below) skips raw bytes for these handles at
8242 // AC1015/AC1018 too.
8243 if (canRegisterCustomClassObjects) {
8244 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) —
8245 // round-trip-grade RasterVariablesRecord captures every encoder
8246 // field (classVersion, imageFrame, imageQuality, units). PR 8d.1.
8247 for (const auto& record : metadata.rasterVariables()) {
8248 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8249 && record.handle != 0) {
8250 nativeRasterVariablesHandles.insert(record.handle);
8251 }
8252 }
8253 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
8254 for (const auto& record : metadata.wipeoutVariables()) {
8255 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8256 && record.handle != 0) {
8257 nativeWipeoutVariablesHandles.insert(record.handle);
8258 }
8259 }
8260 // GEODATA (AcDbGeoData, custom class 506) — round-trip-grade
8261 // GeoDataRecord extended in PR 8d.1c to capture all encoder
8262 // fields (designPoint, referencePoint, up/north direction,
8263 // scale estimation, sea-level correction, observation tags,
8264 // mesh points + faces).
8265 for (const auto& record : metadata.geoData()) {
8266 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8267 && record.handle != 0) {
8268 nativeGeoDataHandles.insert(record.handle);
8269 }
8270 }
8271 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) —
8272 // round-trip-grade SpatialFilterRecord extended in PR 8d.1d to
8273 // capture boundary points + 4x3 clip transform matrices.
8274 for (const auto& record : metadata.spatialFilters()) {
8275 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8276 && record.handle != 0) {
8277 nativeSpatialFilterHandles.insert(record.handle);
8278 }
8279 }
8280 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
8281 // DICTIONARYVAR (custom classes 508-512) handle-set
8282 // construction.
8283 for (const auto& record : metadata.scales()) {
8284 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8285 && record.handle != 0) {
8286 nativeScaleHandles.insert(record.handle);
8287 }
8288 }
8289 for (const auto& record : metadata.idBuffers()) {
8290 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8291 && record.handle != 0) {
8292 nativeIDBufferHandles.insert(record.handle);
8293 }
8294 }
8295 for (const auto& record : metadata.layerIndexes()) {
8296 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8297 && record.handle != 0) {
8298 nativeLayerIndexHandles.insert(record.handle);
8299 }
8300 }
8301 for (const auto& record : metadata.spatialIndexes()) {
8302 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8303 && record.handle != 0) {
8304 nativeSpatialIndexHandles.insert(record.handle);
8305 }
8306 }
8307 for (const auto& record : metadata.dictionaryVars()) {
8308 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8309 && record.handle != 0) {
8310 nativeDictionaryVarHandles.insert(record.handle);
8311 }
8312 }
8313 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
8314 // (custom classes 513-516) handle-set construction.
8315 for (const auto& record : metadata.dictionariesWithDefault()) {
8316 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8317 && record.handle != 0) {
8318 nativeDictionaryWithDefaultHandles.insert(record.handle);
8319 }
8320 }
8321 for (const auto& record : metadata.sortEntsTables()) {
8322 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8323 && record.handle != 0) {
8324 nativeSortEntsTableHandles.insert(record.handle);
8325 }
8326 }
8327 for (const auto& record : metadata.fieldLists()) {
8328 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8329 && record.handle != 0) {
8330 nativeFieldListHandles.insert(record.handle);
8331 }
8332 }
8333 for (const auto& record : metadata.fields()) {
8334 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8335 && record.handle != 0) {
8336 nativeFieldHandles.insert(record.handle);
8337 }
8338 }
8339 for (const auto& record : metadata.underlayDefinitions()) {
8340 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8341 && record.handle != 0) {
8342 nativeUnderlayDefinitionHandles.insert(record.handle);
8343 }
8344 }
8345 }
8346 // PR 13a/b/c — DICTIONARY / XRECORD / GROUP / LAYOUT handle-set
8347 // construction sits in its own broadened block (≥ AC1015) so the
8348 // raw-replay blocker (below) skips raw bytes for these handles at
8349 // AC1015/AC1018 too.
8350 if (canWriteFixedTypeObjects) {
8351 for (const auto& record : metadata.placeholders()) {
8352 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8353 && record.handle != 0) {
8354 nativePlaceholderHandles.insert(record.handle);
8355 }
8356 }
8357 for (const auto& record : metadata.dictionaries()) {
8358 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8359 && record.handle != 0) {
8360 nativeDictionaryHandles.insert(record.handle);
8361 }
8362 }
8363 for (const auto& record : metadata.xrecords()) {
8364 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8365 && record.handle != 0) {
8366 nativeXRecordHandles.insert(record.handle);
8367 }
8368 }
8369 for (const auto& record : metadata.groups()) {
8370 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8371 && record.handle != 0) {
8372 nativeGroupHandles.insert(record.handle);
8373 }
8374 }
8375 for (const auto& record : metadata.mlineStyles()) {
8376 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8377 && record.handle != 0) {
8378 nativeMLineStyleHandles.insert(record.handle);
8379 }
8380 }
8381 for (const auto& record : metadata.layouts()) {
8382 if (record.replayState == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8383 && record.handle != 0) {
8384 nativeLayoutHandles.insert(record.handle);
8385 }
8386 }
8387 }
8388
8389 bool hasBlockedReplay = false;
8390 int blockedInvalidated = 0;
8391 int blockedReplaced = 0;
8392 int blockedEntityReplay = 0;
8393 int blockedMissingRawBytes = 0;
8394 int blockedMissingClassMetadata = 0;
8395 int blockedWriterRejected = 0;
8396 int blockedVersionMismatch = 0;
8397 int blockedFixedHandle = 0;
8398 int replayedObjects = 0;
8399 int replayedSections = 0;
8400 const LC_DwgAdvancedMetadata::RawObjectFamilyCounts rawFamilyCounts =
8401 metadata.rawObjectFamilyCounts();
8402 const LC_DwgAdvancedMetadata::TableNativeWriterBlockerCounts tableBlockers =
8403 metadata.tableNativeWriterBlockerCounts(m_dwgW->getVersion());
8404 const LC_DwgAdvancedMetadata::MLeaderWriterBlockerCounts mleaderBlockers =
8405 metadata.mleaderWriterBlockerCounts();
8406 const LC_DwgAdvancedMetadata::AdvancedEntityWriterBlockerCounts
8407 advancedEntityBlockers =
8408 metadata.advancedEntityWriterBlockerCounts(m_dwgW->getVersion());
8409 const LC_DwgAdvancedMetadata::ModelerPayloadCounts modelerPayloads =
8410 metadata.modelerPayloadCounts();
8411 const LC_DwgAdvancedMetadata::MeshWriterBlockerCounts meshBlockers =
8412 metadata.meshWriterBlockerCounts();
8413 const LC_DwgAdvancedMetadata::ExternalReferenceCounts externalRefs =
8414 metadata.externalReferenceCounts();
8415 const LC_DwgAdvancedMetadata::ShapeOleWriterBlockerCounts shapeOleBlockers =
8416 metadata.shapeOleWriterBlockerCounts();
8417 const LC_DwgAdvancedMetadata::VisualMetadataWriterBlockerCounts
8418 visualBlockers =
8419 metadata.visualMetadataWriterBlockerCounts(m_dwgW->getVersion());
8420 const LC_DwgAdvancedMetadata::AssociativeShellCounts associativeShells =
8421 metadata.associativeShellCounts();
8422 const LC_DwgAdvancedMetadata::AssociativePrefixCounts associativePrefixes =
8423 metadata.associativePrefixCounts();
8424 LC_DwgAdvancedMetadata::GraphReplayPolicyCounts graphReplayPolicy =
8425 metadata.graphReplayPolicyCounts();
8426 for (const auto& record : metadata.rawDwgSections()) {
8427 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
8428 hasBlockedReplay = true;
8429 ++blockedInvalidated;
8430 continue;
8431 }
8432 if (metadata.sourceDwgVersion() != DRW::UNKNOWNV
8433 && metadata.sourceDwgVersion() != m_dwgW->getVersion()) {
8434 hasBlockedReplay = true;
8435 ++blockedVersionMismatch;
8436 continue;
8437 }
8438 DRW_RawDwgSection section;
8439 section.m_name = record.name;
8440 section.m_version = record.version;
8441 section.m_data = record.data;
8442 if (m_dwgW->writeRawDwgSection(&section)) {
8443 ++replayedSections;
8444 } else {
8445 hasBlockedReplay = true;
8446 ++blockedWriterRejected;
8447 }
8448 }
8449 for (const auto& record : metadata.rawObjects()) {
8450 if (nativeSunHandles.count(record.handle) != 0 && isSunRawObject(record)) {
8451 hasBlockedReplay = true;
8452 ++blockedReplaced;
8453 continue;
8454 }
8455 if (nativePlaceholderHandles.count(record.handle) != 0
8456 && isAcDbPlaceholderRawObject(record)) {
8457 hasBlockedReplay = true;
8458 ++blockedReplaced;
8459 continue;
8460 }
8461 if (nativeMLeaderStyleHandles.count(record.handle) != 0
8462 && isMLeaderStyleRawObject(record)) {
8463 hasBlockedReplay = true;
8464 ++blockedReplaced;
8465 continue;
8466 }
8467 if (nativeDictionaryHandles.count(record.handle) != 0
8468 && isDictionaryRawObject(record)) {
8469 hasBlockedReplay = true;
8470 ++blockedReplaced;
8471 continue;
8472 }
8473 if (nativeXRecordHandles.count(record.handle) != 0
8474 && isXRecordRawObject(record)) {
8475 hasBlockedReplay = true;
8476 ++blockedReplaced;
8477 continue;
8478 }
8479 if (nativeLayoutHandles.count(record.handle) != 0
8480 && isLayoutRawObject(record)) {
8481 hasBlockedReplay = true;
8482 ++blockedReplaced;
8483 continue;
8484 }
8485 if (nativeGroupHandles.count(record.handle) != 0
8486 && isGroupRawObject(record)) {
8487 hasBlockedReplay = true;
8488 ++blockedReplaced;
8489 continue;
8490 }
8491 if (nativeMLineStyleHandles.count(record.handle) != 0
8492 && isMLineStyleRawObject(record)) {
8493 hasBlockedReplay = true;
8494 ++blockedReplaced;
8495 continue;
8496 }
8497 if (nativeRasterVariablesHandles.count(record.handle) != 0
8498 && isRasterVariablesRawObject(record)) {
8499 hasBlockedReplay = true;
8500 ++blockedReplaced;
8501 continue;
8502 }
8503 if (nativeWipeoutVariablesHandles.count(record.handle) != 0
8504 && isWipeoutVariablesRawObject(record)) {
8505 hasBlockedReplay = true;
8506 ++blockedReplaced;
8507 continue;
8508 }
8509 if (nativeGeoDataHandles.count(record.handle) != 0
8510 && isGeoDataRawObject(record)) {
8511 hasBlockedReplay = true;
8512 ++blockedReplaced;
8513 continue;
8514 }
8515 if (nativeSpatialFilterHandles.count(record.handle) != 0
8516 && isSpatialFilterRawObject(record)) {
8517 hasBlockedReplay = true;
8518 ++blockedReplaced;
8519 continue;
8520 }
8521 // PR 8d.2a — five small no-storage OBJECTS families.
8522 if (nativeScaleHandles.count(record.handle) != 0
8523 && isScaleRawObject(record)) {
8524 hasBlockedReplay = true;
8525 ++blockedReplaced;
8526 continue;
8527 }
8528 if (nativeIDBufferHandles.count(record.handle) != 0
8529 && isIDBufferRawObject(record)) {
8530 hasBlockedReplay = true;
8531 ++blockedReplaced;
8532 continue;
8533 }
8534 if (nativeLayerIndexHandles.count(record.handle) != 0
8535 && isLayerIndexRawObject(record)) {
8536 hasBlockedReplay = true;
8537 ++blockedReplaced;
8538 continue;
8539 }
8540 if (nativeSpatialIndexHandles.count(record.handle) != 0
8541 && isSpatialIndexRawObject(record)) {
8542 hasBlockedReplay = true;
8543 ++blockedReplaced;
8544 continue;
8545 }
8546 if (nativeDictionaryVarHandles.count(record.handle) != 0
8547 && isDictionaryVarRawObject(record)) {
8548 hasBlockedReplay = true;
8549 ++blockedReplaced;
8550 continue;
8551 }
8552 // PR 8d.2b — four larger no-storage OBJECTS families.
8553 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0
8554 && isDictionaryWithDefaultRawObject(record)) {
8555 hasBlockedReplay = true;
8556 ++blockedReplaced;
8557 continue;
8558 }
8559 if (nativeSortEntsTableHandles.count(record.handle) != 0
8560 && isSortEntsTableRawObject(record)) {
8561 hasBlockedReplay = true;
8562 ++blockedReplaced;
8563 continue;
8564 }
8565 if (nativeFieldListHandles.count(record.handle) != 0
8566 && isFieldListRawObject(record)) {
8567 hasBlockedReplay = true;
8568 ++blockedReplaced;
8569 continue;
8570 }
8571 if (nativeFieldHandles.count(record.handle) != 0
8572 && isFieldRawObject(record)) {
8573 hasBlockedReplay = true;
8574 ++blockedReplaced;
8575 continue;
8576 }
8577 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0
8578 && isUnderlayDefinitionRawObject(record)) {
8579 hasBlockedReplay = true;
8580 ++blockedReplaced;
8581 continue;
8582 }
8583 // A raw object whose handle collides with the writer's fixed
8584 // structural handles can never be replayed: its verbatim bytes
8585 // cannot be rewritten to a fresh handle, and double emission
8586 // duplicates the object-map entry, failing the whole save in
8587 // writeDwgHandles(). Typed counterparts of these records are
8588 // remapped and emitted instead (see fileExport / P3 #2).
8589 if (isFixedStructuralDwgHandle(record.handle)) {
8590 hasBlockedReplay = true;
8591 ++blockedFixedHandle;
8592 continue;
8593 }
8594 // Raw object bytes must remain in their exact source DWG version.
8595 // The matching writeDwgClasses guard prevents orphan CLASSES data.
8596 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
8597 hasBlockedReplay = true;
8598 ++blockedVersionMismatch;
8599 continue;
8600 }
8601 const LC_DwgAdvancedMetadata::ReplayBlocker blocker =
8602 LC_DwgAdvancedMetadata::rawReplayBlocker(record);
8603 if (blocker != LC_DwgAdvancedMetadata::ReplayBlocker::None) {
8604 hasBlockedReplay = true;
8605 if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Invalidated)
8606 ++blockedInvalidated;
8607 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Replaced)
8608 ++blockedReplaced;
8609 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::EntityReplayUnsupported)
8610 ++blockedEntityReplay;
8611 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::MissingRawBytes)
8612 ++blockedMissingRawBytes;
8613 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::MissingClassMetadata)
8614 ++blockedMissingClassMetadata;
8615 continue;
8616 }
8617 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
8618 if (m_dwgW->writeRawDwgObject(&object)) {
8619 ++replayedObjects;
8620 } else {
8621 hasBlockedReplay = true;
8622 ++blockedWriterRejected;
8623 }
8624 }
8625 if (canWriteModernObjects) {
8626 // ACDBPLACEHOLDER dispatch moved below to the
8627 // `canWriteFixedTypeObjects` block (PR 13d).
8628 // PR 13d — SUN stays gated AC1021+ — custom class 503 was
8629 // introduced in R2007 and the encoder explicitly rejects
8630 // below AC1021 (see DRW_Sun::encodeDwg at drw_objects.cpp:
8631 // `if (buf == nullptr || version < DRW::AC1021) return
8632 // false`). Do not move into canWriteFixedTypeObjects.
8633 for (const auto& record : metadata.suns()) {
8634 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8635 || record.handle == 0) {
8636 continue;
8637 }
8638 DRW_Sun sun = sunFromMetadata(record);
8639 remapEntry(sun);
8640 if (m_dwgW->writeSun(&sun)) {
8641 ++nativeSunObjects;
8642 } else {
8643 hasBlockedReplay = true;
8644 ++blockedWriterRejected;
8645 }
8646 }
8647 // PR 13e — MLeaderStyle stays gated AC1021+ — the encoder
8648 // requires R2007+ (see DRW_MLeaderStyle::encodeDwg at
8649 // drw_objects.cpp:4275: `if (version < DRW::AC1021) return
8650 // false`). Do not move into canWriteFixedTypeObjects.
8651 for (const auto& record : metadata.mleaderStyles()) {
8652 if (nativeMLeaderStyleHandles.count(record.handle) == 0)
8653 continue;
8654 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
8655 remapEntry(style);
8656 if (m_dwgW->writeMLeaderStyle(&style)) {
8657 ++nativeMLeaderStyleObjects;
8658 } else {
8659 hasBlockedReplay = true;
8660 ++blockedWriterRejected;
8661 }
8662 }
8663 // DICTIONARY / XRECORD / LAYOUT / GROUP dispatch moved below
8664 // to the `canWriteFixedTypeObjects` block (PR 13a/b/c).
8665 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER dispatch moved
8666 // to the `canRegisterCustomClassObjects` block (PR 13f).
8667 // PR 8d.2a — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
8668 // DICTIONARYVAR dispatch moved to the
8669 // `canRegisterCustomClassObjects` block (PR 13g).
8670 // PR 8d.2b — four larger no-storage OBJECTS families.
8671 // Dispatch moved to the `canRegisterCustomClassObjects` block
8672 // (PR 13h).
8673 }
8674 // PR 13a/b/c/d — DICTIONARY / XRECORD / GROUP / LAYOUT /
8675 // ACDBPLACEHOLDER native dispatch. Gate broadened from AC1021+ to
8676 // AC1015+ now that the encoder smoke tests cover the full
8677 // AC1015/AC1018/AC1024/AC1027/AC1032 range (see
8678 // `[dwg-write][dictionary]`, `[dwg-write][xrecord]`,
8679 // `[dwg-write][group]`, `[dwg-write][layout]`,
8680 // `[dwg-write][placeholder]` cases). Sits outside the `if
8681 // (canWriteModernObjects)` block so the broadened gate also
8682 // applies at AC1015/AC1018.
8683 if (canWriteFixedTypeObjects) {
8684 for (const auto& record : metadata.placeholders()) {
8685 if (record.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
8686 || record.handle == 0) {
8687 continue;
8688 }
8689 DRW_AcDbPlaceholder placeholder = placeholderFromMetadata(record);
8690 remapEntry(placeholder);
8691 if (m_dwgW->writeAcDbPlaceholder(&placeholder)) {
8692 ++nativePlaceholderObjects;
8693 } else {
8694 hasBlockedReplay = true;
8695 ++blockedWriterRejected;
8696 }
8697 }
8698 for (const auto& record : metadata.dictionaries()) {
8699 if (nativeDictionaryHandles.count(record.handle) == 0)
8700 continue;
8701 DRW_Dictionary dictionary = dictionaryFromMetadata(record);
8702 remapEntry(dictionary);
8703 for (auto& en : dictionary.m_entries)
8704 en.m_handle = remapHandle(en.m_handle);
8705 if (m_dwgW->writeDictionary(&dictionary)) {
8706 ++nativeDictionaryObjects;
8707 } else {
8708 hasBlockedReplay = true;
8709 ++blockedWriterRejected;
8710 }
8711 }
8712 for (const auto& record : metadata.xrecords()) {
8713 if (nativeXRecordHandles.count(record.handle) == 0)
8714 continue;
8715 DRW_XRecord xrecord = xrecordFromMetadata(record);
8716 remapEntry(xrecord);
8717 for (auto& hv : xrecord.m_handleValues)
8718 hv.second = remapHandle(hv.second);
8719 if (m_dwgW->writeXRecord(&xrecord)) {
8720 ++nativeXRecordObjects;
8721 } else {
8722 hasBlockedReplay = true;
8723 ++blockedWriterRejected;
8724 }
8725 }
8726 for (const auto& record : metadata.groups()) {
8727 if (nativeGroupHandles.count(record.handle) == 0)
8728 continue;
8729 DRW_Group group = groupFromMetadata(record);
8730 remapEntry(group);
8731 if (m_dwgW->writeGroup(&group)) {
8732 ++nativeGroupObjects;
8733 } else {
8734 hasBlockedReplay = true;
8735 ++blockedWriterRejected;
8736 }
8737 }
8738 for (const auto& record : metadata.mlineStyles()) {
8739 if (nativeMLineStyleHandles.count(record.handle) == 0)
8740 continue;
8741 DRW_MLineStyle style = mlineStyleFromMetadata(record);
8742 remapEntry(style);
8743 if (m_dwgW->writeMLineStyle(&style)) {
8744 ++nativeMLineStyleObjects;
8745 } else {
8746 hasBlockedReplay = true;
8747 ++blockedWriterRejected;
8748 }
8749 }
8750 for (const auto& record : metadata.layouts()) {
8751 if (nativeLayoutHandles.count(record.handle) == 0)
8752 continue;
8753 DRW_Layout layout = layoutFromMetadata(record);
8754 remapEntry(layout);
8755 if (m_dwgW->writeLayout(&layout)) {
8756 ++nativeLayoutObjects;
8757 } else {
8758 hasBlockedReplay = true;
8759 ++blockedWriterRejected;
8760 }
8761 }
8762 }
8763 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER native
8764 // dispatch. Gate broadened from AC1021+ to AC1015+ now that the
8765 // smoke tests cover AC1015/AC1018/AC1024/AC1027/AC1032 (see
8766 // `[dwg-write][rastervariables]`, `[dwg-write][geodata]`,
8767 // `[dwg-write][spatial-filter]`). Matches the
8768 // `canRegisterCustomClassObjects` gate used in writeDwgClasses so
8769 // the CLASSES section entry and the OBJECTS instance always land
8770 // together.
8771 if (canRegisterCustomClassObjects) {
8772 for (const auto& record : metadata.rasterVariables()) {
8773 if (nativeRasterVariablesHandles.count(record.handle) == 0)
8774 continue;
8775 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
8776 remapEntry(rv);
8777 if (m_dwgW->writeRasterVariables(&rv)) {
8778 ++nativeRasterVariablesObjects;
8779 } else {
8780 hasBlockedReplay = true;
8781 ++blockedWriterRejected;
8782 }
8783 }
8784 for (const auto& record : metadata.wipeoutVariables()) {
8785 if (nativeWipeoutVariablesHandles.count(record.handle) == 0)
8786 continue;
8787 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
8788 remapEntry(wv);
8789 if (m_dwgW->writeWipeoutVariables(&wv)) {
8790 ++nativeWipeoutVariablesObjects;
8791 } else {
8792 hasBlockedReplay = true;
8793 ++blockedWriterRejected;
8794 }
8795 }
8796 for (const auto& record : metadata.geoData()) {
8797 if (nativeGeoDataHandles.count(record.handle) == 0)
8798 continue;
8799 DRW_GeoData gd = geoDataFromMetadata(record);
8800 remapEntry(gd);
8801 if (m_dwgW->writeGeoData(&gd)) {
8802 ++nativeGeoDataObjects;
8803 } else {
8804 hasBlockedReplay = true;
8805 ++blockedWriterRejected;
8806 }
8807 }
8808 for (const auto& record : metadata.spatialFilters()) {
8809 if (nativeSpatialFilterHandles.count(record.handle) == 0)
8810 continue;
8811 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
8812 remapEntry(sf);
8813 if (m_dwgW->writeSpatialFilter(&sf)) {
8814 ++nativeSpatialFilterObjects;
8815 } else {
8816 hasBlockedReplay = true;
8817 ++blockedWriterRejected;
8818 }
8819 }
8820 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
8821 // DICTIONARYVAR (custom classes 508-512) dispatch. Smoke
8822 // tests at AC1015/AC1018 confirm the encoders + parsers
8823 // round-trip cleanly; SPATIAL_INDEX's pre-R2007 opaque body
8824 // path is exercised at AC1015/AC1018.
8825 for (const auto& record : metadata.scales()) {
8826 if (nativeScaleHandles.count(record.handle) == 0)
8827 continue;
8828 DRW_Scale s = scaleFromMetadata(record);
8829 remapEntry(s);
8830 if (m_dwgW->writeScale(&s)) {
8831 ++nativeScaleObjects;
8832 } else {
8833 hasBlockedReplay = true;
8834 ++blockedWriterRejected;
8835 }
8836 }
8837 for (const auto& record : metadata.idBuffers()) {
8838 if (nativeIDBufferHandles.count(record.handle) == 0)
8839 continue;
8840 DRW_IDBuffer b = idBufferFromMetadata(record);
8841 remapEntry(b);
8842 if (m_dwgW->writeIDBuffer(&b)) {
8843 ++nativeIDBufferObjects;
8844 } else {
8845 hasBlockedReplay = true;
8846 ++blockedWriterRejected;
8847 }
8848 }
8849 for (const auto& record : metadata.layerIndexes()) {
8850 if (nativeLayerIndexHandles.count(record.handle) == 0)
8851 continue;
8852 DRW_LayerIndex li = layerIndexFromMetadata(record);
8853 remapEntry(li);
8854 if (m_dwgW->writeLayerIndex(&li)) {
8855 ++nativeLayerIndexObjects;
8856 } else {
8857 hasBlockedReplay = true;
8858 ++blockedWriterRejected;
8859 }
8860 }
8861 for (const auto& record : metadata.spatialIndexes()) {
8862 if (nativeSpatialIndexHandles.count(record.handle) == 0)
8863 continue;
8864 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
8865 remapEntry(si);
8866 if (m_dwgW->writeSpatialIndex(&si)) {
8867 ++nativeSpatialIndexObjects;
8868 } else {
8869 hasBlockedReplay = true;
8870 ++blockedWriterRejected;
8871 }
8872 }
8873 for (const auto& record : metadata.dictionaryVars()) {
8874 if (nativeDictionaryVarHandles.count(record.handle) == 0)
8875 continue;
8876 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
8877 remapEntry(dv);
8878 if (m_dwgW->writeDictionaryVar(&dv)) {
8879 ++nativeDictionaryVarObjects;
8880 } else {
8881 hasBlockedReplay = true;
8882 ++blockedWriterRejected;
8883 }
8884 }
8885 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
8886 // (custom classes 513-516) dispatch. Smoke tests at AC1015/
8887 // AC1018 confirm the encoders + parsers round-trip cleanly;
8888 // FIELD exercises the parser-mirrored `version < AC1021`
8889 // m_formatString branch.
8890 for (const auto& record : metadata.dictionariesWithDefault()) {
8891 if (nativeDictionaryWithDefaultHandles.count(record.handle) == 0)
8892 continue;
8893 DRW_DictionaryWithDefault dwd =
8894 dictionaryWithDefaultFromMetadata(record);
8895 remapEntry(dwd);
8896 for (auto& en : dwd.m_entries)
8897 en.m_handle = remapHandle(en.m_handle);
8898 dwd.m_defaultEntryHandle = remapHandle(dwd.m_defaultEntryHandle);
8899 if (m_dwgW->writeDictionaryWithDefault(&dwd)) {
8900 ++nativeDictionaryWithDefaultObjects;
8901 } else {
8902 hasBlockedReplay = true;
8903 ++blockedWriterRejected;
8904 }
8905 }
8906 for (const auto& record : metadata.sortEntsTables()) {
8907 if (nativeSortEntsTableHandles.count(record.handle) == 0)
8908 continue;
8909 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
8910 remapEntry(se);
8911 if (m_dwgW->writeSortEntsTable(&se)) {
8912 ++nativeSortEntsTableObjects;
8913 } else {
8914 hasBlockedReplay = true;
8915 ++blockedWriterRejected;
8916 }
8917 }
8918 for (const auto& record : metadata.fieldLists()) {
8919 if (nativeFieldListHandles.count(record.handle) == 0)
8920 continue;
8921 DRW_FieldList fl = fieldListFromMetadata(record);
8922 remapEntry(fl);
8923 for (auto& fh : fl.m_fieldHandles)
8924 fh = remapHandle(fh);
8925 if (m_dwgW->writeFieldList(&fl)) {
8926 ++nativeFieldListObjects;
8927 } else {
8928 hasBlockedReplay = true;
8929 ++blockedWriterRejected;
8930 }
8931 }
8932 for (const auto& record : metadata.fields()) {
8933 if (nativeFieldHandles.count(record.handle) == 0)
8934 continue;
8935 DRW_Field f = fieldFromMetadata(record);
8936 remapEntry(f);
8937 for (auto& ch : f.m_childHandles)
8938 ch = remapHandle(ch);
8939 for (auto& oh : f.m_objectHandles)
8940 oh = remapHandle(oh);
8941 if (m_dwgW->writeField(&f)) {
8942 ++nativeFieldObjects;
8943 } else {
8944 hasBlockedReplay = true;
8945 ++blockedWriterRejected;
8946 }
8947 }
8948 for (const auto& record : metadata.underlayDefinitions()) {
8949 if (nativeUnderlayDefinitionHandles.count(record.handle) == 0)
8950 continue;
8951 DRW_UnderlayDefinition definition =
8952 underlayDefinitionFromMetadata(record);
8953 remapEntry(definition);
8954 if (m_dwgW->writeUnderlayDefinition(&definition)) {
8955 ++nativeUnderlayDefinitionObjects;
8956 } else {
8957 hasBlockedReplay = true;
8958 ++blockedWriterRejected;
8959 }
8960 }
8961 }
8962 if (replayedObjects > 0) {
8963 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: replayed %d raw DWG objects",
8964 replayedObjects);
8965 }
8966 if (replayedSections > 0) {
8967 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: replayed %d raw DWG data sections",
8968 replayedSections);
8969 }
8970 if (rawFamilyCounts.total() > 0) {
8971 RS_DEBUGRS_Debug::instance()->print(
8972 "RS_FilterDXFRW::writeObjects: raw DWG object families "
8973 "assoc=%d eval-graph=%d dynamic-block=%d object-context=%d unknown=%d",
8974 static_cast<int>(rawFamilyCounts.associative),
8975 static_cast<int>(rawFamilyCounts.evaluationGraph),
8976 static_cast<int>(rawFamilyCounts.dynamicBlock),
8977 static_cast<int>(rawFamilyCounts.objectContext),
8978 static_cast<int>(rawFamilyCounts.unknown));
8979 }
8980 if (nativeSunObjects > 0) {
8981 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native SUN objects",
8982 nativeSunObjects);
8983 }
8984 if (nativePlaceholderObjects > 0) {
8985 RS_DEBUGRS_Debug::instance()->print(
8986 "RS_FilterDXFRW::writeObjects: wrote %d native ACDBPLACEHOLDER objects",
8987 nativePlaceholderObjects);
8988 }
8989 if (nativeMLeaderStyleObjects > 0) {
8990 RS_DEBUGRS_Debug::instance()->print(
8991 "RS_FilterDXFRW::writeObjects: wrote %d native MLEADERSTYLE objects",
8992 nativeMLeaderStyleObjects);
8993 }
8994 if (nativeDictionaryObjects > 0) {
8995 RS_DEBUGRS_Debug::instance()->print(
8996 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARY objects",
8997 nativeDictionaryObjects);
8998 }
8999 if (nativeXRecordObjects > 0) {
9000 RS_DEBUGRS_Debug::instance()->print(
9001 "RS_FilterDXFRW::writeObjects: wrote %d native XRECORD objects",
9002 nativeXRecordObjects);
9003 }
9004 if (nativeLayoutObjects > 0) {
9005 RS_DEBUGRS_Debug::instance()->print(
9006 "RS_FilterDXFRW::writeObjects: wrote %d native LAYOUT objects",
9007 nativeLayoutObjects);
9008 }
9009 if (nativeGroupObjects > 0) {
9010 RS_DEBUGRS_Debug::instance()->print(
9011 "RS_FilterDXFRW::writeObjects: wrote %d native GROUP objects",
9012 nativeGroupObjects);
9013 }
9014 if (nativeMLineStyleObjects > 0) {
9015 RS_DEBUGRS_Debug::instance()->print(
9016 "RS_FilterDXFRW::writeObjects: wrote %d native MLINESTYLE objects",
9017 nativeMLineStyleObjects);
9018 }
9019 if (nativeRasterVariablesObjects > 0) {
9020 RS_DEBUGRS_Debug::instance()->print(
9021 "RS_FilterDXFRW::writeObjects: wrote %d native RASTERVARIABLES objects",
9022 nativeRasterVariablesObjects);
9023 }
9024 if (nativeWipeoutVariablesObjects > 0) {
9025 RS_DEBUGRS_Debug::instance()->print(
9026 "RS_FilterDXFRW::writeObjects: wrote %d native WIPEOUTVARIABLES objects",
9027 nativeWipeoutVariablesObjects);
9028 }
9029 if (nativeGeoDataObjects > 0) {
9030 RS_DEBUGRS_Debug::instance()->print(
9031 "RS_FilterDXFRW::writeObjects: wrote %d native GEODATA objects",
9032 nativeGeoDataObjects);
9033 }
9034 if (nativeSpatialFilterObjects > 0) {
9035 RS_DEBUGRS_Debug::instance()->print(
9036 "RS_FilterDXFRW::writeObjects: wrote %d native SPATIAL_FILTER objects",
9037 nativeSpatialFilterObjects);
9038 }
9039 // PR 8d.2a — five small no-storage OBJECTS families.
9040 if (nativeScaleObjects > 0) {
9041 RS_DEBUGRS_Debug::instance()->print(
9042 "RS_FilterDXFRW::writeObjects: wrote %d native SCALE objects",
9043 nativeScaleObjects);
9044 }
9045 if (nativeIDBufferObjects > 0) {
9046 RS_DEBUGRS_Debug::instance()->print(
9047 "RS_FilterDXFRW::writeObjects: wrote %d native IDBUFFER objects",
9048 nativeIDBufferObjects);
9049 }
9050 if (nativeLayerIndexObjects > 0) {
9051 RS_DEBUGRS_Debug::instance()->print(
9052 "RS_FilterDXFRW::writeObjects: wrote %d native LAYER_INDEX objects",
9053 nativeLayerIndexObjects);
9054 }
9055 if (nativeSpatialIndexObjects > 0) {
9056 RS_DEBUGRS_Debug::instance()->print(
9057 "RS_FilterDXFRW::writeObjects: wrote %d native SPATIAL_INDEX objects",
9058 nativeSpatialIndexObjects);
9059 }
9060 if (nativeDictionaryVarObjects > 0) {
9061 RS_DEBUGRS_Debug::instance()->print(
9062 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARYVAR objects",
9063 nativeDictionaryVarObjects);
9064 }
9065 // PR 8d.2b — four larger no-storage OBJECTS families.
9066 if (nativeDictionaryWithDefaultObjects > 0) {
9067 RS_DEBUGRS_Debug::instance()->print(
9068 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARYWDFLT objects",
9069 nativeDictionaryWithDefaultObjects);
9070 }
9071 if (nativeSortEntsTableObjects > 0) {
9072 RS_DEBUGRS_Debug::instance()->print(
9073 "RS_FilterDXFRW::writeObjects: wrote %d native SORTENTSTABLE objects",
9074 nativeSortEntsTableObjects);
9075 }
9076 if (nativeFieldListObjects > 0) {
9077 RS_DEBUGRS_Debug::instance()->print(
9078 "RS_FilterDXFRW::writeObjects: wrote %d native FIELDLIST objects",
9079 nativeFieldListObjects);
9080 }
9081 if (nativeFieldObjects > 0) {
9082 RS_DEBUGRS_Debug::instance()->print(
9083 "RS_FilterDXFRW::writeObjects: wrote %d native FIELD objects",
9084 nativeFieldObjects);
9085 }
9086 if (nativeUnderlayDefinitionObjects > 0) {
9087 RS_DEBUGRS_Debug::instance()->print(
9088 "RS_FilterDXFRW::writeObjects: wrote %d native UNDERLAYDEFINITION objects",
9089 nativeUnderlayDefinitionObjects);
9090 }
9091 if (modelerPayloads.recordCount > 0) {
9092 const RS_Debug::RS_DebugLevel level =
9093 modelerPayloads.inconsistentSplit > 0
9094 ? RS_Debug::D_WARNING
9095 : RS_Debug::D_DEBUGGING;
9096 RS_DEBUGRS_Debug::instance()->print(
9097 level,
9098 "DWG modeler geometry payloads: records=%d SAT=%d SAB=%d "
9099 "unknown=%d split-inconsistent=%d marker-body=%d marker-handle=%d",
9100 static_cast<int>(modelerPayloads.recordCount),
9101 static_cast<int>(modelerPayloads.sat),
9102 static_cast<int>(modelerPayloads.sab),
9103 static_cast<int>(modelerPayloads.unknown),
9104 static_cast<int>(modelerPayloads.inconsistentSplit),
9105 static_cast<int>(modelerPayloads.markerInBody),
9106 static_cast<int>(modelerPayloads.markerInHandleStream));
9107 }
9108 if (associativeShells.recordCount > 0) {
9109 RS_DEBUGRS_Debug::instance()->print(
9110 "DWG associative/action shells: records=%d network=%d action=%d "
9111 "dependency=%d geom-dependency=%d action-param=%d value-param=%d/%d "
9112 "prefix=%d/%d single-dep=%d compound=%d prefix-status=%d "
9113 "complete=%d partial=%d overflow=%d handles=%d values=%d unknown=%d",
9114 static_cast<int>(associativeShells.recordCount),
9115 static_cast<int>(associativeShells.network),
9116 static_cast<int>(associativeShells.action),
9117 static_cast<int>(associativeShells.dependency),
9118 static_cast<int>(associativeShells.geometryDependency),
9119 static_cast<int>(associativeShells.actionParamRecords),
9120 static_cast<int>(associativeShells.parsedValueParamRecords),
9121 static_cast<int>(associativeShells.valueParamRecords),
9122 static_cast<int>(associativeShells.parsedActionParamPrefixes),
9123 static_cast<int>(associativeShells.actionParamRecords),
9124 static_cast<int>(associativeShells.singleDependencyActionParamPrefixes),
9125 static_cast<int>(associativeShells.compoundActionParamPrefixes),
9126 static_cast<int>(associativePrefixes.prefixCount),
9127 static_cast<int>(associativePrefixes.complete),
9128 static_cast<int>(associativePrefixes.partial),
9129 static_cast<int>(associativePrefixes.boundedCountOverflow),
9130 static_cast<int>(associativePrefixes.decodedHandleCount),
9131 static_cast<int>(associativePrefixes.decodedValueCount),
9132 static_cast<int>(associativeShells.unknown));
9133 }
9134 const size_t graphReplayKnownFamilies =
9135 (graphReplayPolicy.preserved.total()
9136 - graphReplayPolicy.preserved.unknown)
9137 + (graphReplayPolicy.suppressed.total()
9138 - graphReplayPolicy.suppressed.unknown);
9139 if (graphReplayKnownFamilies > 0
9140 || graphReplayPolicy.totalSemanticOnly() > 0
9141 || graphReplayPolicy.totalReasons() > 0) {
9142 RS_DEBUGRS_Debug::instance()->print(
9143 graphReplayPolicy.suppressed.total() > 0
9144 || graphReplayPolicy.totalReasons() > 0
9145 ? RS_Debug::D_WARNING
9146 : RS_Debug::D_DEBUGGING,
9147 "DWG graph replay policy: preserved dimassoc=%d eval=%d "
9148 "assoc=%d dynamic-block=%d object-context=%d acsh=%d unknown=%d "
9149 "suppressed dimassoc=%d eval=%d assoc=%d dynamic-block=%d "
9150 "object-context=%d acsh=%d unknown=%d semantic-only assoc=%d "
9151 "acsh=%d reasons edited=%d missing-target=%d evaluator=%d "
9152 "parser-partial=%d fallback-edited=%d native-replaced=%d "
9153 "cycle-path=%d owner-deleted=%d raw-invalidated=%d "
9154 "raw-replaced=%d entity=%d missing-bytes=%d missing-class=%d",
9155 static_cast<int>(graphReplayPolicy.preserved.dimensionAssociation),
9156 static_cast<int>(graphReplayPolicy.preserved.evaluationGraph),
9157 static_cast<int>(graphReplayPolicy.preserved.acDbAssoc),
9158 static_cast<int>(graphReplayPolicy.preserved.dynamicBlock),
9159 static_cast<int>(graphReplayPolicy.preserved.objectContext),
9160 static_cast<int>(graphReplayPolicy.preserved.acShHistory),
9161 static_cast<int>(graphReplayPolicy.preserved.unknown),
9162 static_cast<int>(graphReplayPolicy.suppressed.dimensionAssociation),
9163 static_cast<int>(graphReplayPolicy.suppressed.evaluationGraph),
9164 static_cast<int>(graphReplayPolicy.suppressed.acDbAssoc),
9165 static_cast<int>(graphReplayPolicy.suppressed.dynamicBlock),
9166 static_cast<int>(graphReplayPolicy.suppressed.objectContext),
9167 static_cast<int>(graphReplayPolicy.suppressed.acShHistory),
9168 static_cast<int>(graphReplayPolicy.suppressed.unknown),
9169 static_cast<int>(graphReplayPolicy.semanticOnlyAssociative),
9170 static_cast<int>(graphReplayPolicy.semanticOnlyAcSh),
9171 static_cast<int>(graphReplayPolicy.editedEntity),
9172 static_cast<int>(graphReplayPolicy.missingTarget),
9173 static_cast<int>(graphReplayPolicy.unsupportedEvaluator),
9174 static_cast<int>(graphReplayPolicy.parserPartial),
9175 static_cast<int>(graphReplayPolicy.fallbackGeometryEdited),
9176 static_cast<int>(graphReplayPolicy.nativeReplacement),
9177 static_cast<int>(graphReplayPolicy.cyclePathInvalidated),
9178 static_cast<int>(graphReplayPolicy.ownerDeleted),
9179 static_cast<int>(graphReplayPolicy.invalidated),
9180 static_cast<int>(graphReplayPolicy.replaced),
9181 static_cast<int>(graphReplayPolicy.entityReplayUnsupported),
9182 static_cast<int>(graphReplayPolicy.missingRawBytes),
9183 static_cast<int>(graphReplayPolicy.missingClassMetadata));
9184 }
9185 if (tableBlockers.tableCount > 0 && tableBlockers.totalBlockers() > 0) {
9186 RS_DEBUGRS_Debug::instance()->print(
9187 RS_Debug::D_WARNING,
9188 "Native DWG table writing blocked: tables=%d eligible-text=%d "
9189 "layout-direct=%d layout-separate=%d layout-embedded=%d "
9190 "layout-unsupported=%d semantic=%d version=%d ambiguous-layout=%d "
9191 "owner=%d style=%d cell-style=%d unknown-ranges=%d "
9192 "incomplete-ranges=%d override=%d break=%d geometry=%d "
9193 "merged=%d field=%d block=%d attributes=%d unknown-content=%d "
9194 "value-payload=%d edited-fallback=%d missing-fallback-links=%d "
9195 "anon-block=%d text-style=%d linetype=%d raw-invalidated=%d "
9196 "raw-replaced=%d dimensions=%d",
9197 static_cast<int>(tableBlockers.tableCount),
9198 static_cast<int>(tableBlockers.eligibleTextOnly),
9199 static_cast<int>(tableBlockers.legacyDirectLayout),
9200 static_cast<int>(tableBlockers.separateTableContentLayout),
9201 static_cast<int>(tableBlockers.embeddedTableContentLayout),
9202 static_cast<int>(tableBlockers.unsupportedLayout),
9203 static_cast<int>(tableBlockers.noSemanticTableContent),
9204 static_cast<int>(tableBlockers.unsupportedTableVersion),
9205 static_cast<int>(tableBlockers.ambiguousTableContentStorage),
9206 static_cast<int>(tableBlockers.missingOwnerHandle),
9207 static_cast<int>(tableBlockers.unresolvedTableStyle),
9208 static_cast<int>(tableBlockers.unresolvedCellStyleMap),
9209 static_cast<int>(tableBlockers.unknownSubrecordRange),
9210 static_cast<int>(tableBlockers.incompleteSubrecordRange),
9211 static_cast<int>(tableBlockers.overrideMask),
9212 static_cast<int>(tableBlockers.breakData),
9213 static_cast<int>(tableBlockers.geometryTail),
9214 static_cast<int>(tableBlockers.mergedCell),
9215 static_cast<int>(tableBlockers.fieldContent),
9216 static_cast<int>(tableBlockers.blockContent),
9217 static_cast<int>(tableBlockers.attributeContent),
9218 static_cast<int>(tableBlockers.unknownCellContent),
9219 static_cast<int>(tableBlockers.incompleteValuePayload),
9220 static_cast<int>(tableBlockers.editedFallback),
9221 static_cast<int>(tableBlockers.missingFallbackAttachment),
9222 static_cast<int>(tableBlockers.anonymousBlockPolicyUnresolved),
9223 static_cast<int>(tableBlockers.unresolvedTextStyle),
9224 static_cast<int>(tableBlockers.unresolvedLineType),
9225 static_cast<int>(tableBlockers.rawReplayInvalidated),
9226 static_cast<int>(tableBlockers.rawReplayReplaced),
9227 static_cast<int>(tableBlockers.nonPositiveDimension));
9228 }
9229 if (mleaderBlockers.mleaderCount > 0 && mleaderBlockers.totalBlockers() > 0) {
9230 RS_DEBUGRS_Debug::instance()->print(
9231 RS_Debug::D_WARNING,
9232 "Native DWG MLEADER writing limited: mleaders=%d unresolved-style=%d "
9233 "missing-text=%d block=%d tolerance=%d overrides=%d geometry=%d "
9234 "invalidated=%d replaced=%d",
9235 static_cast<int>(mleaderBlockers.mleaderCount),
9236 static_cast<int>(mleaderBlockers.unresolvedStyle),
9237 static_cast<int>(mleaderBlockers.missingTextContent),
9238 static_cast<int>(mleaderBlockers.blockContent),
9239 static_cast<int>(mleaderBlockers.toleranceContent),
9240 static_cast<int>(mleaderBlockers.overrideFlags),
9241 static_cast<int>(mleaderBlockers.missingLeaderGeometry),
9242 static_cast<int>(mleaderBlockers.invalidated),
9243 static_cast<int>(mleaderBlockers.replaced));
9244 }
9245 if (meshBlockers.meshCount > 0 && meshBlockers.totalBlockers() > 0) {
9246 RS_DEBUGRS_Debug::instance()->print(
9247 RS_Debug::D_WARNING,
9248 "Native DWG mesh writing blocked: meshes=%d sidecars=%d "
9249 "range-complete=%d range-missing=%d range-incomplete=%d "
9250 "crease=%d subdivision=%d fallback-only=%d edited-fallback=%d "
9251 "owner-class=%d malformed-counts=%d invalidated=%d replaced=%d",
9252 static_cast<int>(meshBlockers.meshCount),
9253 static_cast<int>(meshBlockers.sidecarCount),
9254 static_cast<int>(meshBlockers.completeRawRange),
9255 static_cast<int>(meshBlockers.missingRawRange),
9256 static_cast<int>(meshBlockers.incompleteRawRange),
9257 static_cast<int>(meshBlockers.missingCreaseData),
9258 static_cast<int>(meshBlockers.unsupportedSubdivisionData),
9259 static_cast<int>(meshBlockers.fallbackOnlyPreview),
9260 static_cast<int>(meshBlockers.editedFallback),
9261 static_cast<int>(meshBlockers.missingOwnerOrClassHandle),
9262 static_cast<int>(meshBlockers.malformedCountRelationships),
9263 static_cast<int>(meshBlockers.invalidated),
9264 static_cast<int>(meshBlockers.replaced));
9265 }
9266 if (externalRefs.imageEntities > 0 || externalRefs.wipeouts > 0
9267 || externalRefs.underlays > 0
9268 || externalRefs.imageDefinitions > 0
9269 || externalRefs.underlayDefinitions > 0) {
9270 const bool hasExternalIssues =
9271 externalRefs.totalPathIssues() > 0
9272 || externalRefs.missingDefinitionHandles > 0
9273 || externalRefs.malformedClips > 0;
9274 RS_DEBUGRS_Debug::instance()->print(
9275 hasExternalIssues ? RS_Debug::D_WARNING
9276 : RS_Debug::D_DEBUGGING,
9277 "DWG external references: images=%d wipeouts=%d image-defs=%d "
9278 "underlays=%d underlay-defs=%d raster-vars=%d path-empty=%d "
9279 "path-relative=%d path-missing=%d external=%d scheme=%d "
9280 "case-candidate=%d missing-def=%d orphan-def=%d clip-none=%d "
9281 "clip-rect=%d clip-poly=%d clip-bad=%d inverted=%d hidden-frame=%d",
9282 static_cast<int>(externalRefs.imageEntities),
9283 static_cast<int>(externalRefs.wipeouts),
9284 static_cast<int>(externalRefs.imageDefinitions),
9285 static_cast<int>(externalRefs.underlays),
9286 static_cast<int>(externalRefs.underlayDefinitions),
9287 static_cast<int>(externalRefs.rasterVariables),
9288 static_cast<int>(externalRefs.emptyPaths),
9289 static_cast<int>(externalRefs.relativePaths),
9290 static_cast<int>(externalRefs.absoluteMissingPaths),
9291 static_cast<int>(externalRefs.externalPaths),
9292 static_cast<int>(externalRefs.unsupportedSchemes),
9293 static_cast<int>(externalRefs.caseMismatchCandidates),
9294 static_cast<int>(externalRefs.missingDefinitionHandles),
9295 static_cast<int>(externalRefs.definitionsWithoutEntities),
9296 static_cast<int>(externalRefs.noBoundaryClips),
9297 static_cast<int>(externalRefs.rectangularClips),
9298 static_cast<int>(externalRefs.polygonalClips),
9299 static_cast<int>(externalRefs.malformedClips),
9300 static_cast<int>(externalRefs.invertedClips),
9301 static_cast<int>(externalRefs.hiddenFrames));
9302 }
9303 if (shapeOleBlockers.shapeCount > 0
9304 || shapeOleBlockers.ole2FrameCount > 0) {
9305 RS_DEBUGRS_Debug::instance()->print(
9306 shapeOleBlockers.totalBlockers() > 0
9307 ? RS_Debug::D_WARNING
9308 : RS_Debug::D_DEBUGGING,
9309 "DWG shape/OLE writer blockers: shapes=%d ole2=%d "
9310 "missing-style=%d unresolved-style=%d missing-ole=%d "
9311 "truncated-ole=%d oversized-ole=%d edited-preview=%d "
9312 "unsupported-ole=%d raw-missing=%d raw-incomplete=%d "
9313 "invalidated=%d replaced=%d",
9314 static_cast<int>(shapeOleBlockers.shapeCount),
9315 static_cast<int>(shapeOleBlockers.ole2FrameCount),
9316 static_cast<int>(shapeOleBlockers.missingStyleHandle),
9317 static_cast<int>(shapeOleBlockers.unresolvedShapeStyle),
9318 static_cast<int>(shapeOleBlockers.missingOlePayload),
9319 static_cast<int>(shapeOleBlockers.truncatedOlePayload),
9320 static_cast<int>(shapeOleBlockers.oversizedOlePayload),
9321 static_cast<int>(shapeOleBlockers.editedPreviewFrame),
9322 static_cast<int>(shapeOleBlockers.unsupportedOlePayloadRegeneration),
9323 static_cast<int>(shapeOleBlockers.missingRawRange),
9324 static_cast<int>(shapeOleBlockers.incompleteRawRange),
9325 static_cast<int>(shapeOleBlockers.invalidated),
9326 static_cast<int>(shapeOleBlockers.replaced));
9327 }
9328 if (visualBlockers.recordCount > 0 || visualBlockers.rawPayloads > 0) {
9329 RS_DEBUGRS_Debug::instance()->print(
9330 visualBlockers.totalBlockers() > 0
9331 ? RS_Debug::D_WARNING
9332 : RS_Debug::D_DEBUGGING,
9333 "DWG visual metadata export policy: records=%d raw=%d "
9334 "raw-replayable=%d raw-suppressed=%d ucs=%d base-ucs=%d "
9335 "visual-style=%d sun=%d background=%d live-section=%d "
9336 "owner-layout=%d raw-invalidated=%d raw-replaced=%d "
9337 "unsupported-visual-style=%d",
9338 static_cast<int>(visualBlockers.recordCount),
9339 static_cast<int>(visualBlockers.rawPayloads),
9340 static_cast<int>(visualBlockers.replayableRawPayloads),
9341 static_cast<int>(visualBlockers.suppressedRawPayloads),
9342 static_cast<int>(visualBlockers.unresolvedUcs),
9343 static_cast<int>(visualBlockers.unresolvedBaseUcs),
9344 static_cast<int>(visualBlockers.unresolvedVisualStyle),
9345 static_cast<int>(visualBlockers.unresolvedSun),
9346 static_cast<int>(visualBlockers.unresolvedBackground),
9347 static_cast<int>(visualBlockers.unresolvedLiveSection),
9348 static_cast<int>(visualBlockers.missingOwnerOrLayout),
9349 static_cast<int>(visualBlockers.invalidatedRawPayload),
9350 static_cast<int>(visualBlockers.replacedNativeUnavailablePayload),
9351 static_cast<int>(visualBlockers.unsupportedVisualStyleWriter));
9352 }
9353 if (advancedEntityBlockers.recordCount > 0
9354 && advancedEntityBlockers.totalBlockers() > 0) {
9355 RS_DEBUGRS_Debug::instance()->print(
9356 RS_Debug::D_WARNING,
9357 "DWG advanced entity writer readiness: records=%d mesh=%d "
9358 "shape=%d ole2=%d image=%d wipeout=%d underlay=%d "
9359 "mleader=%d arc-dim=%d unknown=%d native=%d raw=%d "
9360 "fallback=%d edited-fallback=%d metadata=%d payload=%d "
9361 "advanced-content=%d oda-complete=%d oda-partial=%d "
9362 "oda-absent=%d",
9363 static_cast<int>(advancedEntityBlockers.recordCount),
9364 static_cast<int>(advancedEntityBlockers.mesh),
9365 static_cast<int>(advancedEntityBlockers.shape),
9366 static_cast<int>(advancedEntityBlockers.ole2Frame),
9367 static_cast<int>(advancedEntityBlockers.rasterImage),
9368 static_cast<int>(advancedEntityBlockers.wipeout),
9369 static_cast<int>(advancedEntityBlockers.underlay),
9370 static_cast<int>(advancedEntityBlockers.mleader),
9371 static_cast<int>(advancedEntityBlockers.arcDimension),
9372 static_cast<int>(advancedEntityBlockers.unknown),
9373 static_cast<int>(advancedEntityBlockers.nativeWriterAvailable),
9374 static_cast<int>(advancedEntityBlockers.rawReplayAvailable),
9375 static_cast<int>(advancedEntityBlockers.fallbackAvailable),
9376 static_cast<int>(advancedEntityBlockers.editedFallbackInvalidated),
9377 static_cast<int>(advancedEntityBlockers.missingRequiredMetadata),
9378 static_cast<int>(advancedEntityBlockers.missingPayloadBytes),
9379 static_cast<int>(advancedEntityBlockers.unsupportedAdvancedContent),
9380 static_cast<int>(advancedEntityBlockers.odaComplete),
9381 static_cast<int>(advancedEntityBlockers.odaPartial),
9382 static_cast<int>(advancedEntityBlockers.odaAbsent));
9383 }
9384 const size_t nativeSemanticRecords =
9385 static_cast<size_t>(nativeSunObjects + nativePlaceholderObjects
9386 + nativeMLeaderStyleObjects
9387 + nativeDictionaryObjects
9388 + nativeXRecordObjects
9389 + nativeLayoutObjects
9390 + nativeGroupObjects
9391 + nativeRasterVariablesObjects
9392 + nativeWipeoutVariablesObjects
9393 + nativeGeoDataObjects
9394 + nativeSpatialFilterObjects);
9395 const size_t semanticOnlyRecords =
9396 metadata.semanticOnlyRecordCount() > nativeSemanticRecords
9397 ? metadata.semanticOnlyRecordCount() - nativeSemanticRecords
9398 : 0;
9399 const bool hasSemanticOnlyReplayable = semanticOnlyRecords > 0;
9400 if (hasBlockedReplay || hasSemanticOnlyReplayable) {
9401 RS_DEBUGRS_Debug::instance()->print(
9402 RS_Debug::D_WARNING,
9403 "Some DWG advanced metadata still cannot be emitted natively; "
9404 "unchanged raw OBJECTS/data sections are replayed where safe, "
9405 "while entities and semantic-only records remain diagnostic-only");
9406 if (hasBlockedReplay) {
9407 RS_DEBUGRS_Debug::instance()->print(
9408 RS_Debug::D_WARNING,
9409 "Blocked raw DWG replay: invalidated=%d replaced=%d entity=%d "
9410 "missing-bytes=%d missing-class=%d writer-rejected=%d "
9411 "version-mismatch=%d fixed-handle=%d (objects=%d sections=%d)",
9412 blockedInvalidated, blockedReplaced, blockedEntityReplay,
9413 blockedMissingRawBytes, blockedMissingClassMetadata,
9414 blockedWriterRejected, blockedVersionMismatch,
9415 blockedFixedHandle,
9416 replayedObjects, replayedSections);
9417 }
9418 if (semanticOnlyRecords > 0) {
9419 RS_DEBUGRS_Debug::instance()->print(
9420 RS_Debug::D_WARNING,
9421 "Semantic-only DWG metadata records not emitted natively: %d",
9422 static_cast<int>(semanticOnlyRecords));
9423 }
9424 }
9425 return; // DWG writer handles object section internally
9426 }
9427 /* PLOTSETTINGS */
9428 DRW_PlotSettings ps;
9429 LC_PlotSettings* gps = m_graphic->getPlotSettings();
9430 const QString horizXvert = QString("%1x%2").arg(gps->getPagesNumHoriz()).arg(gps->getPagesNumVert());
9431 ps.plotViewName = horizXvert.toStdString(); // fixme - use other property?
9432 ps.marginLeft = gps->getMarginLeftMm();
9433 ps.marginTop = gps->getMarginTopMm();
9434 ps.marginRight = gps->getMarginRightMm();
9435 ps.marginBottom = gps->getMarginBottomMm();
9436
9437 ps.currentStyleSheet = gps->getCurrentStyleName().toStdString();
9438 ps.pageSetupName = gps->getPaperSizeName().toStdString();
9439
9440 ps.paperWidth = gps->getPaperWidthMm();
9441 ps.paperHeight = gps->getPaperHeightMm();
9442 ps.plotOriginX = gps->getOriginOffsetXMm();
9443 ps.plotOriginY = gps->getOriginOffsetYMm();
9444 ps.windowMinX = gps->getPlotWindowLowerLeftX();
9445 ps.windowMinY = gps->getPlotWindowLowerLeftY();
9446 ps.windowMaxX = gps->getPlotWindowUpperRightX();
9447 ps.windowMaxY = gps->getPlotWindowUpperRightY();
9448 ps.realWorldUnits = gps->getCustomPrintScalePaperUnitsNumerator();
9449 ps.drawingUnits = gps->getCustomPrintScaleDrawingUnitsDenominator();
9450 ps.plotLayoutFlags = gps->getPlotLayoutFlag();
9451 ps.paperUnits = gps->getPlotPaperUnits();
9452 ps.plotRotation = gps->getPlotRotation();
9453 ps.plotType = gps->getStandardScaleType();
9454 ps.scaleType = gps->getStandardScaleType();
9455 ps.shadePlotMode = gps->getShadePlotMode();
9456 ps.shadePlotMode = gps->getShadePlotResolutionMode();
9457 ps.shadePlotCustomDPI = gps->getShadePlotCustomDpi();
9458 ps.scaleFactor = gps->getStandardScaleFactor();
9459 ps.paperImageOriginX = gps->getPaperImageOriginX();
9460 ps.paperImageOriginY = gps->getPaperImageOriginY();
9461
9462 m_dxfW->writePlotSettings(&ps);
9463
9464 //Slice A2: re-emit OBJECTS captured verbatim on read (A1) so a LibreCAD DXF
9465 //round-trip preserves unmodeled objects rather than dropping them. Records live
9466 //on the graphic, so this (separate) write-filter instance still sees them.
9467 //Skip handles the codec regenerates itself (source ACAD_GROUP dict, C/D
9468 //collisions — see m_dxfSuppressedObjectHandles set up in fileExport) so routed
9469 //named dictionaries don't duplicate the root/group dictionaries.
9470 if (m_graphic != nullptr) {
9471 for (const DRW_RawDxfObject &rawObject :
9472 m_graphic->dwgAdvancedMetadata().rawDxfObjects()) {
9473 if (rawObject.handle != 0
9474 && m_dxfSuppressedObjectHandles.count(rawObject.handle) != 0)
9475 continue;
9476 DRW_RawDxfObject object = rawObject;
9477 m_dxfW->writeRawDxfObject(&object);
9478 }
9479
9480 //F4: typed DXF emit for the routed data-only OBJECTS the DWG reader stores
9481 //ONLY in typed metadata (NOT the raw net) — SUN/SCALE/DICTIONARYVAR/
9482 //RASTERVARIABLES. On DWG->DXF these are absent from the raw net, so the
9483 //loop above never emits them; emit them natively here so the type is
9484 //preserved. DEDUP vs the raw net: a DXF-READ object lives in BOTH the
9485 //typed metadata AND the raw net (processSun calls addSun AND
9486 //addRawDxfObject), so it was already re-emitted above — skip the typed
9487 //emit when its code-5 handle is already in the raw net to avoid a
9488 //double-emit. (Build the raw-handle set once.) Only ReplayAllowed,
9489 //nonzero-handle records are emitted; handles are reserved and a CLASS
9490 //record is registered in fileExport's pre-write pass.
9491 const auto &metadata = m_graphic->dwgAdvancedMetadata();
9492 std::set<std::uint32_t> rawHandles;
9493 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects())
9494 if (o.handle != 0)
9495 rawHandles.insert(o.handle);
9496 auto emitTyped = [&](std::uint32_t handle,
9497 LC_DwgAdvancedMetadata::ReplayState state) {
9498 return handle != 0
9499 && state == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed
9500 && rawHandles.count(handle) == 0;
9501 };
9502 //F4f-3: owner fallback. A data-only OBJECT's 330 parent must resolve to
9503 //an object that actually exists in the output, else ezdxf deletes the
9504 //object (INVALID_OWNER_HANDLE). A parent is reachable iff it is 0 (->
9505 //root C), a named parent dict we emit (F4f-2), or a raw-net object
9506 //re-emitted verbatim. Otherwise (e.g. a SUN owned by a per-viewport ACAD
9507 //dict LibreCAD never materializes) zero it so writeObjectOwner emits C.
9508 auto resolveOwner = [&](std::uint32_t parent) -> int {
9509 if (parent == 0
9510 || m_dxfEmittedNamedDictHandles.count(parent) != 0
9511 || rawHandles.count(parent) != 0)
9512 return static_cast<int>(parent);
9513 return 0; // dangling -> owner C
9514 };
9515 for (const auto &record : metadata.suns()) {
9516 if (!emitTyped(record.handle, record.replayState))
9517 continue;
9518 DRW_Sun sun = sunFromMetadata(record);
9519 sun.parentHandle = resolveOwner(record.parentHandle);
9520 m_dxfW->writeSun(&sun);
9521 }
9522 for (const auto &record : metadata.scales()) {
9523 if (!emitTyped(record.handle, record.replayState))
9524 continue;
9525 DRW_Scale scale = scaleFromMetadata(record);
9526 scale.parentHandle = resolveOwner(record.parentHandle);
9527 m_dxfW->writeScale(&scale);
9528 }
9529 for (const auto &record : metadata.dictionaryVars()) {
9530 if (!emitTyped(record.handle, record.replayState))
9531 continue;
9532 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
9533 dv.parentHandle = resolveOwner(record.parentHandle);
9534 m_dxfW->writeDictionaryVar(&dv);
9535 }
9536 for (const auto &record : metadata.rasterVariables()) {
9537 if (!emitTyped(record.handle, record.replayState))
9538 continue;
9539 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
9540 rv.parentHandle = resolveOwner(record.parentHandle);
9541 m_dxfW->writeRasterVariables(&rv);
9542 }
9543 //MLEADERSTYLE is a custom object with a CLASS record. This is
9544 //load-bearing for later MLEADER DXF completion, since MLEADER entities
9545 //reference these styles by handle.
9546 for (const auto &record : metadata.mleaderStyles()) {
9547 if (!emitTyped(record.handle, record.replayState))
9548 continue;
9549 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
9550 style.parentHandle = resolveOwner(record.parentHandle);
9551 m_dxfW->writeMLeaderStyle(&style);
9552 }
9553 //SLICE 1: MLINESTYLE (FIXED built-in, no CLASS). DWG read populates only
9554 //typed metadata; DXF read keeps it in the raw net (so emitTyped dedups by
9555 //handle). The STANDARD mline style is present in most drawings.
9556 for (const auto &record : metadata.mlineStyles()) {
9557 if (!emitTyped(record.handle, record.replayState))
9558 continue;
9559 DRW_MLineStyle style = mlineStyleFromMetadata(record);
9560 style.parentHandle = resolveOwner(record.parentHandle);
9561 m_dxfW->writeMLineStyle(&style);
9562 }
9563 //LAYOUT is a fixed built-in object. It is typed metadata only on DXF
9564 //read too, so emit it here and let the prepass materialize ACAD_LAYOUT
9565 //when the source had a reachable layout dictionary.
9566 for (const auto &record : metadata.layouts()) {
9567 if (!emitTyped(record.handle, record.replayState))
9568 continue;
9569 DRW_Layout layout = layoutFromMetadata(record);
9570 layout.parentHandle = resolveOwner(record.parentHandle);
9571 m_dxfW->writeLayout(&layout);
9572 }
9573 //GEODATA is a custom object with a CLASS record. DWG read captures all
9574 //typed fields, including mesh points/faces, so emit it on DWG->DXF.
9575 for (const auto &record : metadata.geoData()) {
9576 if (!emitTyped(record.handle, record.replayState))
9577 continue;
9578 DRW_GeoData gd = geoDataFromMetadata(record);
9579 gd.parentHandle = resolveOwner(record.parentHandle);
9580 m_dxfW->writeGeoData(&gd);
9581 }
9582 //SPATIAL_FILTER is a custom object with a CLASS record. The parent is
9583 //usually an extension dictionary; resolve it through the materialized
9584 //dictionary set so exported DXF owners are reachable.
9585 for (const auto &record : metadata.spatialFilters()) {
9586 if (!emitTyped(record.handle, record.replayState))
9587 continue;
9588 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
9589 sf.parentHandle = resolveOwner(record.parentHandle);
9590 m_dxfW->writeSpatialFilter(&sf);
9591 }
9592 //SORTENTSTABLE is a custom object with entity draw-order references.
9593 //dxfRW remaps source entity handles to the minted DXF entity handles.
9594 for (const auto &record : metadata.sortEntsTables()) {
9595 if (!emitTyped(record.handle, record.replayState))
9596 continue;
9597 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
9598 se.parentHandle = resolveOwner(record.parentHandle);
9599 m_dxfW->writeSortEntsTable(&se);
9600 }
9601 //FIELD/FIELDLIST are custom objects. Emit FIELD first so FIELDLIST 330
9602 //references point at an object that already exists in OBJECTS.
9603 for (const auto &record : metadata.fields()) {
9604 if (!emitTyped(record.handle, record.replayState))
9605 continue;
9606 DRW_Field field = fieldFromMetadata(record);
9607 field.parentHandle = resolveOwner(record.parentHandle);
9608 m_dxfW->writeField(&field);
9609 }
9610 for (const auto &record : metadata.fieldLists()) {
9611 if (!emitTyped(record.handle, record.replayState))
9612 continue;
9613 DRW_FieldList fieldList = fieldListFromMetadata(record);
9614 fieldList.parentHandle = resolveOwner(record.parentHandle);
9615 m_dxfW->writeFieldList(&fieldList);
9616 }
9617 //SLICE 2: WIPEOUTVARIABLES (custom, CLASS registered). Same dedup-vs-raw
9618 //-net + owner re-attach as the other data-only OBJECTS.
9619 for (const auto &record : metadata.wipeoutVariables()) {
9620 if (!emitTyped(record.handle, record.replayState))
9621 continue;
9622 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
9623 wv.parentHandle = resolveOwner(record.parentHandle);
9624 m_dxfW->writeWipeoutVariables(&wv);
9625 }
9626 }
9627}
9628
9629void RS_FilterDXFRW::writeAppId() {
9630 DRW_AppId ai;
9631 ai.name = "LibreCad";
9632 if (m_dwgW) {
9633 m_dwgW->addAppId(&ai);
9634 }
9635 else {
9636 m_dxfW->writeAppId(&ai);
9637 }
9638
9639 ai.name = "ACAD_DSTYLE_DIMTALN";
9640 if (m_dwgW) {
9641 m_dwgW->addAppId(&ai);
9642 }
9643 else {
9644 m_dxfW->writeAppId(&ai);
9645 }
9646
9647 ai.name = "ACAD_DSTYLE_DIMJAG_POSITION";
9648 if (m_dwgW) {
9649 m_dwgW->addAppId(&ai);
9650 }
9651 else {
9652 m_dxfW->writeAppId(&ai);
9653 }
9654
9655 ai.name = "ACAD_DSTYLE_DIMJAG";
9656 if (m_dwgW) {
9657 m_dwgW->addAppId(&ai);
9658 }
9659 else {
9660 m_dxfW->writeAppId(&ai);
9661 }
9662
9663 // ACAD_DSTYLE_DIMJAG
9664 // fixme - sand - probably we can add version there, check format
9665}
9666
9667void RS_FilterDXFRW::writeEntities(){
9668 // Pre-pass: reconstruct MLINE entities from decomposed polylines that
9669 // carry LibreCAD_MLINE XDATA. Consumed polylines are emitted as
9670 // MLINE; the rest fall through to the normal write path.
9671 std::set<RS_Entity *> consumed;
9672 reconstructPolylineSidecars(m_graphic, consumed);
9673 reconstructMLines(m_graphic, consumed);
9674 // F2: rebuild RAY/XLINE/TRACE/3DFACE from their type-fidelity sidecars.
9675 // Both the DXF and DWG writers expose writeRay/writeXline/writeTrace/
9676 // write3dface, so this runs for either output.
9677 reconstructTypedConversions(m_graphic, consumed);
9678 // Rebuild underlay sidecar metadata into native writer calls.
9679 reconstructUnderlays(m_graphic, consumed);
9680 for (RS_Entity *e :
9681 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
9682 if (e->getFlag(RS2::FlagDeleted))
9683 continue;
9684 if (consumed.find(e) != consumed.end())
9685 continue;
9686 writeEntity(e);
9687 }
9688
9689 //Slice A2 (entities): re-emit ENTITIES captured verbatim on read (A4) so a
9690 //LibreCAD DXF round-trip preserves unmodeled entities rather than dropping
9691 //them. DXF-only: the DWG writer has its own object/entity
9692 //path. Records live on the graphic, shared across read/write filter instances.
9693 if (!m_dwgW && m_graphic != nullptr) {
9694 for (const DRW_RawDxfObject &rawEntity :
9695 m_graphic->dwgAdvancedMetadata().rawDxfEntities()) {
9696 DRW_RawDxfObject entity = rawEntity;
9697 m_dxfW->writeRawDxfObject(&entity);
9698 }
9699 // 3DSOLID/REGION/BODY modeler shells read from DWG or typed DXF live only
9700 // on the metadata shelf. Re-emit their opaque ACIS/SAB payload without
9701 // attempting B-rep interpretation.
9702 for (const auto &rec : m_graphic->dwgAdvancedMetadata().modelerGeometry()) {
9703 if (rec.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
9704 continue;
9705 DRW_ModelerGeometry geom(rec.type);
9706 geom.handle = rec.handle;
9707 geom.parentHandle = rec.parentHandle;
9708 geom.m_modelerVersion = rec.modelerVersion;
9709 geom.m_bodyBitSize = static_cast<std::uint32_t>(rec.rawBodyByteCount * 8u);
9710 geom.m_objectSize = rec.objectSize;
9711 geom.m_isEmpty = rec.isEmpty;
9712 geom.m_hasModelerData = rec.hasModelerData;
9713 geom.m_modelerDataUnknownBit = rec.modelerDataUnknownBit;
9714 geom.m_hasWireframe = rec.hasWireframe;
9715 geom.m_historyHandle = rec.historyHandle;
9716 geom.m_rawBytes = rec.rawBytes;
9717 geom.extData = rec.extData;
9718 m_dxfW->writeModelerGeometry(&geom);
9719 }
9720 // LIGHT entities read from a DWG live only on the metadata shelf (no RS_Light
9721 // model), so without this loop a DWG->DXF export silently drops them. Re-emit
9722 // them as typed AcDbLight (R2007+; writeLight no-ops on older DXF targets).
9723 if (m_dxfW->getVersion() >= DRW::AC1021) {
9724 for (const auto &rec : m_graphic->dwgAdvancedMetadata().lights()) {
9725 DRW_Light light;
9726 light.handle = rec.handle;
9727 light.parentHandle = rec.parentHandle;
9728 light.m_classVersion = rec.classVersion;
9729 light.m_name = rec.name;
9730 light.m_type = rec.type;
9731 light.m_status = rec.status;
9732 light.m_color = rec.color;
9733 light.m_plotGlyph = rec.plotGlyph;
9734 light.m_intensity = rec.intensity;
9735 light.m_position = rec.position;
9736 light.m_target = rec.target;
9737 light.m_attenuationType = rec.attenuationType;
9738 light.m_useAttenuationLimits = rec.useAttenuationLimits;
9739 light.m_attenuationStartLimit = rec.attenuationStartLimit;
9740 light.m_attenuationEndLimit = rec.attenuationEndLimit;
9741 light.m_hotspotAngle = rec.hotspotAngle;
9742 light.m_falloffAngle = rec.falloffAngle;
9743 light.m_castShadows = rec.castShadows;
9744 light.m_shadowType = rec.shadowType;
9745 light.m_shadowMapSize = rec.shadowMapSize;
9746 light.m_shadowMapSoftness = rec.shadowMapSoftness;
9747 m_dxfW->writeLight(&light);
9748 }
9749 }
9750 // SHAPE entities read from a DWG live only on the metadata shelf (no RS_Shape
9751 // model) -> re-emit as typed AcDbShape so DWG->DXF preserves them. Group 2 is
9752 // the resolved SHAPEFILE/STYLE record name (the glyph index is not round-
9753 // trippable without the .shx; this matches libredwg/ACadSharp output).
9754 for (const auto &rec : m_graphic->dwgAdvancedMetadata().shapes()) {
9755 DRW_Shape shape;
9756 shape.handle = rec.handle;
9757 shape.parentHandle = rec.parentHandle;
9758 shape.m_shapeFileHandle = rec.shapeFileHandle;
9759 shape.m_shapeIndex = rec.shapeIndex;
9760 shape.m_styleName = rec.styleName;
9761 shape.m_insertionPoint = rec.insertionPoint;
9762 shape.m_extrusion = rec.extrusion;
9763 shape.m_scale = rec.scale;
9764 shape.m_rotation = rec.rotation;
9765 shape.m_oblique = rec.oblique;
9766 shape.m_widthFactor = rec.widthFactor;
9767 shape.m_thickness = rec.thickness;
9768 m_dxfW->writeShape(&shape);
9769 }
9770 // OLE2FRAME entities read from a DWG live only on the metadata shelf -> re-emit
9771 // as typed AcDbOle2Frame. pt1/pt2 (frame rectangle) were decoded from the OLE
9772 // payload header on read; the opaque payload is replayed verbatim (group 310).
9773 for (const auto &rec : m_graphic->dwgAdvancedMetadata().ole2Frames()) {
9774 if (rec.payloadBytes.empty())
9775 continue; // nothing to preserve (truncated/absent payload)
9776 DRW_Ole2Frame ole;
9777 ole.handle = rec.handle;
9778 ole.parentHandle = rec.parentHandle;
9779 ole.m_flags = rec.flags;
9780 ole.m_mode = rec.mode;
9781 ole.m_oleVersion = rec.oleVersion;
9782 ole.m_pt1 = rec.pt1;
9783 ole.m_pt2 = rec.pt2;
9784 ole.m_payloadBytes = rec.payloadBytes;
9785 m_dxfW->writeOle2Frame(&ole);
9786 }
9787 }
9788}
9789
9790namespace {
9791// Parsed view of one polyline's LibreCAD_MLINE XDATA.
9792struct MLineEntry {
9793 RS_Entity *entity = nullptr;
9794 QString mlineId;
9795 QString styleName;
9796 std::uint32_t styleHandle = 0;
9797 double scale = 1.0;
9798 int justification = 0;
9799 int elementCount = 0;
9800 int elementIndex = 0;
9801 double offset = 0.0;
9802 int openClosed = 1;
9803 bool isAnchor = false; // i==0 polyline carries baseline + miter
9804 std::vector<DRW_Coord> baselineVerts;
9805 std::vector<DRW_Coord> miterDirs;
9806};
9807
9808struct LWPolylineMeta {
9809 double width = 0.0;
9810 double elevation = 0.0;
9811 double thickness = 0.0;
9812 DRW_Coord extrusion {0.0, 0.0, 1.0};
9813 int vertexCount = 0;
9814 std::vector<double> startWidths;
9815 std::vector<double> endWidths;
9816 std::vector<int> identifiers;
9817};
9818
9819std::optional<LWPolylineMeta> extractLWPolylineMeta(RS_Entity *e) {
9820 if (!e || !e->hasDrwExtData())
9821 return std::nullopt;
9822 const auto &ext = e->getDrwExtData();
9823 bool inGroup = false;
9824 LWPolylineMeta meta;
9825 int seen1040 = 0; // 0=width, 1=elevation, 2=thickness, then vertex widths
9826 bool gotMarker = false;
9827
9828 for (const auto &sp : ext) {
9829 if (!sp)
9830 continue;
9831 const int code = sp->code();
9832 if (code == 1001) {
9833 inGroup = (std::string{sp->c_str()} == "LibreCAD_LWPOLYLINE");
9834 if (inGroup)
9835 gotMarker = true;
9836 continue;
9837 }
9838 if (!inGroup)
9839 continue;
9840
9841 switch (code) {
9842 case 1040: {
9843 const double d = sp->d_val();
9844 if (seen1040 == 0)
9845 meta.width = d;
9846 else if (seen1040 == 1)
9847 meta.elevation = d;
9848 else if (seen1040 == 2)
9849 meta.thickness = d;
9850 else if ((seen1040 - 3) % 2 == 0)
9851 meta.startWidths.push_back(d);
9852 else
9853 meta.endWidths.push_back(d);
9854 ++seen1040;
9855 break;
9856 }
9857 case 1010: {
9858 const auto *c = sp->coord();
9859 if (c)
9860 meta.extrusion = *c;
9861 break;
9862 }
9863 case 1070:
9864 if (meta.vertexCount == 0)
9865 meta.vertexCount = static_cast<int>(sp->i_val());
9866 break;
9867 case 1071:
9868 meta.identifiers.push_back(static_cast<int>(sp->i_val()));
9869 break;
9870 default:
9871 break;
9872 }
9873 }
9874
9875 if (!gotMarker)
9876 return std::nullopt;
9877 return meta;
9878}
9879
9880struct MeshSidecarEntry {
9881 RS_Entity *entity = nullptr;
9882 QString meshId;
9883 QString role;
9884 int elementIndex = -1;
9885 int elementCount = 0;
9886 int roleIndex = -1;
9887 int flags = 0;
9888 int mCount = 0;
9889 int nCount = 0;
9890 int smoothM = 0;
9891 int smoothN = 0;
9892 int curveType = 0;
9893 std::vector<DRW_Coord> sourceVertices;
9894};
9895
9896struct PolyfaceSidecarEntry {
9897 RS_Entity *entity = nullptr;
9898 QString polyfaceId;
9899 int faceIndex = -1;
9900 int faceCount = 0;
9901 int flags = 0;
9902 int vertexCount = 0;
9903 int originalFaceCount = 0;
9904 std::array<int, 4> indices {{0, 0, 0, 0}};
9905 std::vector<DRW_Coord> sourceVertices;
9906};
9907
9908std::vector<RS_Vector> collectPolylineVertices(RS_Polyline *polyline) {
9909 std::vector<RS_Vector> vertices;
9910 if (polyline == nullptr)
9911 return vertices;
9912
9913 const RS_AtomicEntity *lastAtomic = nullptr;
9914 for (RS_Entity *sub :
9915 lc::LC_ContainerTraverser{*polyline, RS2::ResolveNone}.entities()) {
9916 if (sub == nullptr || !sub->isAtomic())
9917 continue;
9918 const auto *atomic = static_cast<const RS_AtomicEntity *>(sub);
9919 vertices.push_back(atomic->getStartpoint());
9920 lastAtomic = atomic;
9921 }
9922 if (lastAtomic != nullptr && !polyline->isClosed())
9923 vertices.push_back(lastAtomic->getEndpoint());
9924
9925 return vertices;
9926}
9927
9928bool pointsMatch2D(const RS_Vector &point, const DRW_Coord &coord) {
9929 return std::abs(point.x - coord.x) <= RS_TOLERANCE1.0e-10
9930 && std::abs(point.y - coord.y) <= RS_TOLERANCE1.0e-10;
9931}
9932
9933std::optional<MeshSidecarEntry> extractMeshSidecar(RS_Entity *e) {
9934 if (!e || !e->hasDrwExtData())
9935 return std::nullopt;
9936
9937 MeshSidecarEntry meta;
9938 meta.entity = e;
9939 bool inGroup = false;
9940 bool gotMarker = false;
9941 int seen1000 = 0;
9942 int seen1070 = 0;
9943
9944 for (const auto &sp : e->getDrwExtData()) {
9945 if (!sp)
9946 continue;
9947 const int code = sp->code();
9948 if (code == 1001) {
9949 inGroup = (std::string{sp->c_str()} == "LibreCAD_POLYLINE_MESH");
9950 if (inGroup)
9951 gotMarker = true;
9952 continue;
9953 }
9954 if (!inGroup)
9955 continue;
9956
9957 switch (code) {
9958 case 1000: {
9959 const QString value = QString::fromStdString(std::string{sp->c_str()});
9960 if (seen1000 == 0)
9961 meta.meshId = value;
9962 else if (seen1000 == 1)
9963 meta.role = value;
9964 ++seen1000;
9965 break;
9966 }
9967 case 1070: {
9968 const int value = static_cast<int>(sp->i_val());
9969 if (seen1070 == 0)
9970 meta.elementIndex = value;
9971 else if (seen1070 == 1)
9972 meta.elementCount = value;
9973 else if (seen1070 == 2)
9974 meta.roleIndex = value;
9975 else if (seen1070 == 3)
9976 meta.flags = value;
9977 else if (seen1070 == 4)
9978 meta.mCount = value;
9979 else if (seen1070 == 5)
9980 meta.nCount = value;
9981 else if (seen1070 == 6)
9982 meta.smoothM = value;
9983 else if (seen1070 == 7)
9984 meta.smoothN = value;
9985 else if (seen1070 == 8)
9986 meta.curveType = value;
9987 ++seen1070;
9988 break;
9989 }
9990 case 1010: {
9991 const auto *coord = sp->coord();
9992 if (coord)
9993 meta.sourceVertices.push_back(*coord);
9994 break;
9995 }
9996 default:
9997 break;
9998 }
9999 }
10000
10001 if (!gotMarker || meta.meshId.isEmpty())
10002 return std::nullopt;
10003 return meta;
10004}
10005
10006std::optional<PolyfaceSidecarEntry> extractPolyfaceSidecar(RS_Entity *e) {
10007 if (!e || !e->hasDrwExtData())
10008 return std::nullopt;
10009
10010 PolyfaceSidecarEntry meta;
10011 meta.entity = e;
10012 bool inGroup = false;
10013 bool gotMarker = false;
10014 int seen1070 = 0;
10015
10016 for (const auto &sp : e->getDrwExtData()) {
10017 if (!sp)
10018 continue;
10019 const int code = sp->code();
10020 if (code == 1001) {
10021 inGroup = (std::string{sp->c_str()} == "LibreCAD_POLYLINE_PFACE");
10022 if (inGroup)
10023 gotMarker = true;
10024 continue;
10025 }
10026 if (!inGroup)
10027 continue;
10028
10029 switch (code) {
10030 case 1000:
10031 if (meta.polyfaceId.isEmpty())
10032 meta.polyfaceId = QString::fromStdString(std::string{sp->c_str()});
10033 break;
10034 case 1070: {
10035 const int value = static_cast<int>(sp->i_val());
10036 if (seen1070 == 0)
10037 meta.faceIndex = value;
10038 else if (seen1070 == 1)
10039 meta.faceCount = value;
10040 else if (seen1070 == 2)
10041 meta.flags = value;
10042 else if (seen1070 == 3)
10043 meta.vertexCount = value;
10044 else if (seen1070 == 4)
10045 meta.originalFaceCount = value;
10046 else if (seen1070 >= 5 && seen1070 < 9)
10047 meta.indices[seen1070 - 5] = value;
10048 ++seen1070;
10049 break;
10050 }
10051 case 1010: {
10052 const auto *coord = sp->coord();
10053 if (coord)
10054 meta.sourceVertices.push_back(*coord);
10055 break;
10056 }
10057 default:
10058 break;
10059 }
10060 }
10061
10062 if (!gotMarker || meta.polyfaceId.isEmpty())
10063 return std::nullopt;
10064 return meta;
10065}
10066
10067// Walk the entity's drwExtData (XDATA stream) and extract LibreCAD_MLINE
10068// metadata. Returns nullopt if the marker isn't present. The XDATA layout
10069// matches RS_FilterDXFRW::addMLine's emission schema.
10070std::optional<MLineEntry> extractMLineMeta(RS_Entity *e) {
10071 if (!e || !e->hasDrwExtData())
10072 return std::nullopt;
10073 const auto &ext = e->getDrwExtData();
10074 bool inGroup = false;
10075 MLineEntry m;
10076 int seen1000 = 0; // 0 = mlineId, 1 = styleName
10077 int seen1040 = 0; // 0 = scale, 1 = offset
10078 int seen1070 = 0; // 0 = just, 1 = N, 2 = i, 3 = flags
10079 for (const auto &sp : ext) {
10080 if (!sp)
10081 continue;
10082 const int code = sp->code();
10083 if (code == 1001) {
10084 inGroup = (std::string{sp->c_str()} == "LibreCAD_MLINE");
10085 continue;
10086 }
10087 if (!inGroup)
10088 continue;
10089 switch (code) {
10090 case 1000: {
10091 QString s = QString::fromStdString(std::string{sp->c_str()});
10092 if (seen1000 == 0)
10093 m.mlineId = s;
10094 else if (seen1000 == 1)
10095 m.styleName = s;
10096 ++seen1000;
10097 break;
10098 }
10099 case 1040: {
10100 const double d = sp->d_val();
10101 if (seen1040 == 0)
10102 m.scale = d;
10103 else if (seen1040 == 1)
10104 m.offset = d;
10105 ++seen1040;
10106 break;
10107 }
10108 case 1070: {
10109 const int v = static_cast<int>(sp->i_val());
10110 if (seen1070 == 0)
10111 m.justification = v;
10112 else if (seen1070 == 1)
10113 m.elementCount = v;
10114 else if (seen1070 == 2)
10115 m.elementIndex = v;
10116 else if (seen1070 == 3)
10117 m.openClosed = v;
10118 ++seen1070;
10119 break;
10120 }
10121 case 1071:
10122 m.styleHandle = static_cast<std::uint32_t>(sp->i_val());
10123 break;
10124 case 1011: {
10125 // Anchor-only baseline vertex
10126 const auto *c = sp->coord();
10127 if (c)
10128 m.baselineVerts.push_back(*c);
10129 m.isAnchor = true;
10130 break;
10131 }
10132 case 1013: {
10133 const auto *c = sp->coord();
10134 if (c)
10135 m.miterDirs.push_back(*c);
10136 break;
10137 }
10138 default:
10139 break;
10140 }
10141 }
10142 if (m.mlineId.isEmpty() || m.elementCount == 0)
10143 return std::nullopt;
10144 m.entity = e;
10145 return m;
10146}
10147} // namespace
10148
10149void RS_FilterDXFRW::reconstructPolylineSidecars(
10150 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
10151 if (container == nullptr)
10152 return;
10153
10154 std::map<QString, std::vector<MeshSidecarEntry>> meshGroups;
10155 std::map<QString, std::vector<PolyfaceSidecarEntry>> polyfaceGroups;
10156
10157 for (RS_Entity *entity :
10158 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
10159 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted)
10160 || consumed.find(entity) != consumed.end()
10161 || entity->rtti() != RS2::EntityPolyline) {
10162 continue;
10163 }
10164 if (auto meshMeta = extractMeshSidecar(entity))
10165 meshGroups[meshMeta->meshId].push_back(std::move(*meshMeta));
10166 else if (auto polyfaceMeta = extractPolyfaceSidecar(entity))
10167 polyfaceGroups[polyfaceMeta->polyfaceId].push_back(
10168 std::move(*polyfaceMeta));
10169 }
10170
10171 for (auto &[meshId, entries] : meshGroups) {
10172 (void)meshId;
10173 if (entries.empty())
10174 continue;
10175
10176 const MeshSidecarEntry *anchor = nullptr;
10177 for (const auto &entry : entries) {
10178 if (!entry.sourceVertices.empty()) {
10179 anchor = &entry;
10180 break;
10181 }
10182 }
10183 if (anchor == nullptr || anchor->mCount <= 0 || anchor->nCount <= 0)
10184 continue;
10185
10186 const int mCount = anchor->mCount;
10187 const int nCount = anchor->nCount;
10188 const int expectedElementCount = mCount + nCount;
10189 if (anchor->elementCount != expectedElementCount
10190 || static_cast<int>(entries.size()) != expectedElementCount
10191 || static_cast<int>(anchor->sourceVertices.size()) != mCount * nCount) {
10192 continue;
10193 }
10194
10195 bool valid = true;
10196 std::vector<bool> seenElements(expectedElementCount, false);
10197 for (const auto &entry : entries) {
10198 if (entry.elementIndex < 0 || entry.elementIndex >= expectedElementCount
10199 || seenElements[entry.elementIndex] || entry.mCount != mCount
10200 || entry.nCount != nCount || entry.elementCount != expectedElementCount) {
10201 valid = false;
10202 break;
10203 }
10204 seenElements[entry.elementIndex] = true;
10205
10206 auto *polyline = static_cast<RS_Polyline *>(entry.entity);
10207 const std::vector<RS_Vector> visible = collectPolylineVertices(polyline);
10208 if (entry.role == "row") {
10209 if (entry.roleIndex < 0 || entry.roleIndex >= mCount
10210 || static_cast<int>(visible.size()) != nCount) {
10211 valid = false;
10212 break;
10213 }
10214 for (int j = 0; j < nCount; ++j) {
10215 const DRW_Coord &coord =
10216 anchor->sourceVertices[entry.roleIndex * nCount + j];
10217 if (!pointsMatch2D(visible[j], coord)) {
10218 valid = false;
10219 break;
10220 }
10221 }
10222 }
10223 else if (entry.role == "column") {
10224 if (entry.roleIndex < 0 || entry.roleIndex >= nCount
10225 || static_cast<int>(visible.size()) != mCount) {
10226 valid = false;
10227 break;
10228 }
10229 for (int i = 0; i < mCount; ++i) {
10230 const DRW_Coord &coord =
10231 anchor->sourceVertices[i * nCount + entry.roleIndex];
10232 if (!pointsMatch2D(visible[i], coord)) {
10233 valid = false;
10234 break;
10235 }
10236 }
10237 }
10238 else {
10239 valid = false;
10240 }
10241 if (!valid)
10242 break;
10243 }
10244 if (!valid)
10245 continue;
10246
10247 DRW_Polyline polyline;
10248 polyline.flags = anchor->flags | 0x10;
10249 polyline.vertexcount = mCount;
10250 polyline.facecount = nCount;
10251 polyline.smoothM = anchor->smoothM;
10252 polyline.smoothN = anchor->smoothN;
10253 polyline.curvetype = anchor->curveType;
10254 getEntityAttributes(&polyline, anchor->entity);
10255 for (const DRW_Coord &coord : anchor->sourceVertices) {
10256 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
10257 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Mesh);
10258 polyline.addVertex(vertex);
10259 }
10260
10261 if (m_dwgW) {
10262 if (!m_dwgW->writePolyline(&polyline))
10263 m_writeFailed = true;
10264 } else if (m_dxfW) {
10265 m_dxfW->writePolyline(&polyline);
10266 }
10267
10268 for (const auto &entry : entries)
10269 consumed.insert(entry.entity);
10270 }
10271
10272 for (auto &[polyfaceId, entries] : polyfaceGroups) {
10273 (void)polyfaceId;
10274 if (entries.empty())
10275 continue;
10276
10277 const PolyfaceSidecarEntry *anchor = nullptr;
10278 for (const auto &entry : entries) {
10279 if (!entry.sourceVertices.empty()) {
10280 anchor = &entry;
10281 break;
10282 }
10283 }
10284 if (anchor == nullptr || anchor->vertexCount <= 0
10285 || anchor->faceCount <= 0) {
10286 continue;
10287 }
10288
10289 if (static_cast<int>(entries.size()) != anchor->faceCount
10290 || static_cast<int>(anchor->sourceVertices.size())
10291 != anchor->vertexCount) {
10292 continue;
10293 }
10294
10295 bool valid = true;
10296 std::vector<bool> seenFaces(anchor->faceCount, false);
10297 for (const auto &entry : entries) {
10298 if (entry.faceIndex < 0 || entry.faceIndex >= anchor->faceCount
10299 || seenFaces[entry.faceIndex] || entry.faceCount != anchor->faceCount
10300 || entry.vertexCount != anchor->vertexCount) {
10301 valid = false;
10302 break;
10303 }
10304 seenFaces[entry.faceIndex] = true;
10305
10306 const int expectedPoints = entry.indices[3] == 0 ? 3 : 4;
10307 auto *polyline = static_cast<RS_Polyline *>(entry.entity);
10308 const std::vector<RS_Vector> visible = collectPolylineVertices(polyline);
10309 if (static_cast<int>(visible.size()) != expectedPoints) {
10310 valid = false;
10311 break;
10312 }
10313 for (int i = 0; i < expectedPoints; ++i) {
10314 const int vertexIndex = std::abs(entry.indices[i]);
10315 if (vertexIndex < 1 || vertexIndex > anchor->vertexCount) {
10316 valid = false;
10317 break;
10318 }
10319 if (!pointsMatch2D(visible[i],
10320 anchor->sourceVertices[vertexIndex - 1])) {
10321 valid = false;
10322 break;
10323 }
10324 }
10325 if (!valid)
10326 break;
10327 }
10328 if (!valid)
10329 continue;
10330
10331 std::sort(entries.begin(), entries.end(),
10332 [](const PolyfaceSidecarEntry &lhs,
10333 const PolyfaceSidecarEntry &rhs) {
10334 return lhs.faceIndex < rhs.faceIndex;
10335 });
10336
10337 DRW_Polyline polyline;
10338 polyline.flags = anchor->flags | 0x40;
10339 polyline.vertexcount = anchor->vertexCount;
10340 polyline.facecount = anchor->faceCount;
10341 getEntityAttributes(&polyline, anchor->entity);
10342
10343 for (const DRW_Coord &coord : anchor->sourceVertices) {
10344 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
10345 vertex.flags = 0x40 | 0x80;
10346 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Polyface);
10347 polyline.addVertex(vertex);
10348 }
10349 for (const auto &entry : entries) {
10350 DRW_Vertex face;
10351 face.flags = 0x80;
10352 face.vindex1 = entry.indices[0];
10353 face.vindex2 = entry.indices[1];
10354 face.vindex3 = entry.indices[2];
10355 face.vindex4 = entry.indices[3];
10356 face.setDwgSubtype(DRW_Vertex::DwgSubtype::PolyfaceFace);
10357 polyline.addVertex(face);
10358 }
10359
10360 if (m_dwgW) {
10361 if (!m_dwgW->writePolyline(&polyline))
10362 m_writeFailed = true;
10363 } else if (m_dxfW) {
10364 m_dxfW->writePolyline(&polyline);
10365 }
10366
10367 for (const auto &entry : entries)
10368 consumed.insert(entry.entity);
10369 }
10370}
10371
10372void RS_FilterDXFRW::reconstructMLines(RS_EntityContainer *container,
10373 std::set<RS_Entity *> &consumed) {
10374 if (!container)
10375 return;
10376
10377 // First pass: collect all polylines carrying the MLINE marker.
10378 std::map<QString, std::vector<MLineEntry>> groups;
10379 for (RS_Entity *e :
10380 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
10381 if (e->getFlag(RS2::FlagDeleted))
10382 continue;
10383 if (e->rtti() != RS2::EntityPolyline)
10384 continue;
10385 auto m = extractMLineMeta(e);
10386 if (!m)
10387 continue;
10388 groups[m->mlineId].push_back(std::move(*m));
10389 }
10390
10391 // Second pass: reconstruct + emit each complete group.
10392 for (auto &[mlineId, entries] : groups) {
10393 if (entries.empty())
10394 continue;
10395 const int N = entries.front().elementCount;
10396 if (static_cast<int>(entries.size()) != N) {
10397 // Incomplete group (user deleted siblings) — leave for plain
10398 // LWPOLYLINE export. Don't mark consumed.
10399 continue;
10400 }
10401 // Sort by elementIndex.
10402 std::sort(entries.begin(), entries.end(),
10403 [](const MLineEntry &a, const MLineEntry &b) {
10404 return a.elementIndex < b.elementIndex;
10405 });
10406 // Find the anchor (elementIndex == 0).
10407 const MLineEntry *anchor = nullptr;
10408 for (const auto &m : entries) {
10409 if (m.elementIndex == 0 && m.isAnchor && !m.baselineVerts.empty()) {
10410 anchor = &m;
10411 break;
10412 }
10413 }
10414 if (!anchor) {
10415 // No anchor found — group is malformed, fall through.
10416 continue;
10417 }
10418
10419 // Build DRW_MLine from anchor metadata + baseline vertices.
10420 DRW_MLine ml;
10421 ml.styleName = anchor->styleName.toStdString();
10422 if (anchor->styleHandle != 0) {
10423 ml.styleHandle = anchor->styleHandle;
10424 } else if (m_dwgW && m_graphic != nullptr) {
10425 const auto &metadata = m_graphic->dwgAdvancedMetadata();
10426 for (const auto &record : metadata.mlineStyles()) {
10427 if (record.handle == 0
10428 || record.replayState
10429 != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
10430 continue;
10431 if (QString::compare(QString::fromUtf8(record.name.c_str()),
10432 anchor->styleName, Qt::CaseInsensitive) == 0) {
10433 ml.styleHandle = record.handle;
10434 break;
10435 }
10436 }
10437 }
10438 ml.scale = anchor->scale;
10439 ml.justification = static_cast<std::uint8_t>(anchor->justification);
10440 ml.openClosed = anchor->openClosed;
10441 ml.numLines = static_cast<std::uint8_t>(N);
10442 ml.numVerts = static_cast<std::uint16_t>(anchor->baselineVerts.size());
10443 if (!anchor->baselineVerts.empty()) {
10444 ml.basePoint = anchor->baselineVerts.front();
10445 }
10446 ml.layer = entries.front().entity->getLayer()
10447 ? entries.front().entity->getLayer()->getName().toStdString()
10448 : std::string{"0"};
10449 const size_t V = anchor->baselineVerts.size();
10450 const size_t M = anchor->miterDirs.size();
10451 ml.vertlist.reserve(V);
10452 for (size_t i = 0; i < V; ++i) {
10453 DRW_MLineVertex v;
10454 v.position = anchor->baselineVerts[i];
10455 if (i < M)
10456 v.miterDir = anchor->miterDirs[i];
10457 // Default segment params: a single zero parameter per element
10458 // (sufficient for a continuous unbroken multiline).
10459 v.segParms.assign(N, std::vector<double>{0.0});
10460 v.areaFillParms.assign(N, std::vector<double>{});
10461 ml.vertlist.push_back(std::move(v));
10462 }
10463
10464 if (m_dwgW) {
10465 if (!m_dwgW->writeMLine(&ml))
10466 m_writeFailed = true;
10467 } else if (m_dxfW) {
10468 m_dxfW->writeMLine(&ml);
10469 }
10470 for (const auto &m : entries)
10471 consumed.insert(m.entity);
10472 }
10473}
10474
10475namespace {
10476// One parsed F2 type-fidelity sidecar (marker + payload coords/flag).
10477struct TypedConversionSidecar {
10478 QString marker;
10479 std::vector<DRW_Coord> coords; // 1010.. payload coordinates
10480 int flag = 0; // 1070 invisible-edge flag (3DFACE only)
10481 double thickness = 0.0; // 1040 POINT/LINE thickness
10482 double pointXAxisAngle = 0.0; // 1041 POINT x-axis angle, in radians
10483};
10484
10485// Find a single F2 type-fidelity sidecar on an RS entity's XDATA, if present.
10486// The marker codes match those written by appendTypeSidecar() at read time.
10487std::optional<TypedConversionSidecar>
10488extractTypedConversionSidecar(RS_Entity *e) {
10489 if (!e || !e->hasDrwExtData())
10490 return std::nullopt;
10491 TypedConversionSidecar meta;
10492 bool inGroup = false;
10493 for (const auto &sp : e->getDrwExtData()) {
10494 if (!sp)
10495 continue;
10496 const int code = sp->code();
10497 if (code == 1001) {
10498 const std::string app{sp->c_str()};
10499 if (app == kPointExtrusionMarker || app == kLineExtrusionMarker ||
10500 app == kRayMarker || app == kXlineMarker ||
10501 app == kTraceMarker || app == kSolidMarker || app == k3dFaceMarker) {
10502 meta.marker = QString::fromStdString(app);
10503 inGroup = true;
10504 } else {
10505 inGroup = false;
10506 }
10507 continue;
10508 }
10509 if (!inGroup)
10510 continue;
10511 if (code >= 1010 && code <= 1013) {
10512 if (const DRW_Coord *crd = sp->coord())
10513 meta.coords.push_back(*crd);
10514 } else if (code == 1070) {
10515 meta.flag = static_cast<int>(sp->i_val());
10516 } else if (code == 1040) {
10517 meta.thickness = sp->d_val();
10518 } else if (code == 1041) {
10519 meta.pointXAxisAngle = sp->d_val();
10520 }
10521 }
10522 if (meta.marker.isEmpty())
10523 return std::nullopt;
10524 return meta;
10525}
10526
10527struct HatchExtrusionSidecar {
10528 double elevation = 0.0;
10529 DRW_Coord extrusion {0.0, 0.0, 1.0};
10530};
10531
10532std::optional<HatchExtrusionSidecar>
10533extractHatchExtrusionSidecar(const RS_Entity *entity) {
10534 if (!entity || !entity->hasDrwExtData())
10535 return std::nullopt;
10536
10537 HatchExtrusionSidecar sidecar;
10538 bool inGroup = false;
10539 bool gotElevation = false;
10540 bool gotExtrusion = false;
10541 for (const auto &value : entity->getDrwExtData()) {
10542 if (!value)
10543 continue;
10544 if (value->code() == 1001) {
10545 inGroup = std::string{value->c_str()} == kHatchExtrusionMarker;
10546 continue;
10547 }
10548 if (!inGroup)
10549 continue;
10550 if (value->code() == 1010) {
10551 if (const DRW_Coord *point = value->coord()) {
10552 sidecar.elevation = point->z;
10553 gotElevation = true;
10554 }
10555 } else if (value->code() == 1011) {
10556 if (const DRW_Coord *normal = value->coord()) {
10557 sidecar.extrusion = *normal;
10558 gotExtrusion = true;
10559 }
10560 }
10561 }
10562 if (!gotElevation || !gotExtrusion)
10563 return std::nullopt;
10564 return sidecar;
10565}
10566
10567void removeHatchExtrusionSidecar(DRW_Entity &entity) {
10568 std::vector<std::shared_ptr<DRW_Variant>> filtered;
10569 bool inGroup = false;
10570 for (const auto &value : entity.extData) {
10571 if (value && value->code() == 1001) {
10572 inGroup = std::string{value->c_str()} == kHatchExtrusionMarker;
10573 if (inGroup)
10574 continue;
10575 }
10576 if (!inGroup)
10577 filtered.push_back(value);
10578 }
10579 entity.extData = std::move(filtered);
10580}
10581
10582struct ImageFrameSidecar {
10583 DRW_Coord insertion;
10584 DRW_Coord uVector;
10585 DRW_Coord vVector;
10586};
10587
10588std::optional<ImageFrameSidecar>
10589extractImageFrameSidecar(const RS_Entity *entity) {
10590 if (!entity || !entity->hasDrwExtData())
10591 return std::nullopt;
10592
10593 ImageFrameSidecar sidecar;
10594 bool gotInsertion = false;
10595 bool gotUVector = false;
10596 bool gotVVector = false;
10597 bool inGroup = false;
10598 for (const auto &value : entity->getDrwExtData()) {
10599 if (!value)
10600 continue;
10601 if (value->code() == 1001) {
10602 inGroup = std::string{value->c_str()} == kImageFrameMarker;
10603 continue;
10604 }
10605 if (!inGroup || value->code() < 1010 || value->code() > 1012)
10606 continue;
10607 const DRW_Coord *point = value->coord();
10608 if (!point)
10609 continue;
10610 if (value->code() == 1010) {
10611 sidecar.insertion = *point;
10612 gotInsertion = true;
10613 } else if (value->code() == 1011) {
10614 sidecar.uVector = *point;
10615 gotUVector = true;
10616 } else {
10617 sidecar.vVector = *point;
10618 gotVVector = true;
10619 }
10620 }
10621 if (!gotInsertion || !gotUVector || !gotVVector)
10622 return std::nullopt;
10623 return sidecar;
10624}
10625
10626void removeImageFrameSidecar(DRW_Entity &entity) {
10627 std::vector<std::shared_ptr<DRW_Variant>> filtered;
10628 bool inGroup = false;
10629 for (const auto &value : entity.extData) {
10630 if (value && value->code() == 1001) {
10631 inGroup = std::string{value->c_str()} == kImageFrameMarker;
10632 if (inGroup)
10633 continue;
10634 }
10635 if (!inGroup)
10636 filtered.push_back(value);
10637 }
10638 entity.extData = std::move(filtered);
10639}
10640
10641struct TextOcsSidecar {
10642 double baseElevation = 0.0;
10643 double alignmentElevation = 0.0;
10644 DRW_Coord extrusion {0.0, 0.0, 1.0};
10645 double thickness = 0.0;
10646};
10647
10648std::optional<TextOcsSidecar>
10649extractTextOcsSidecar(const RS_Entity *entity) {
10650 if (!entity || !entity->hasDrwExtData())
10651 return std::nullopt;
10652 TextOcsSidecar sidecar;
10653 bool gotBase = false;
10654 bool gotAlignment = false;
10655 bool gotExtrusion = false;
10656 bool gotThickness = false;
10657 bool inGroup = false;
10658 for (const auto &value : entity->getDrwExtData()) {
10659 if (!value)
10660 continue;
10661 if (value->code() == 1001) {
10662 inGroup = std::string{value->c_str()} == kTextOcsMarker;
10663 continue;
10664 }
10665 if (!inGroup)
10666 continue;
10667 if (value->code() == 1010) {
10668 if (const DRW_Coord *point = value->coord()) {
10669 sidecar.baseElevation = point->z;
10670 gotBase = true;
10671 }
10672 } else if (value->code() == 1011) {
10673 if (const DRW_Coord *point = value->coord()) {
10674 sidecar.alignmentElevation = point->z;
10675 gotAlignment = true;
10676 }
10677 } else if (value->code() == 1012) {
10678 if (const DRW_Coord *normal = value->coord()) {
10679 sidecar.extrusion = *normal;
10680 gotExtrusion = true;
10681 }
10682 } else if (value->code() == 1040) {
10683 sidecar.thickness = value->d_val();
10684 gotThickness = true;
10685 }
10686 }
10687 if (!gotBase || !gotAlignment || !gotExtrusion || !gotThickness)
10688 return std::nullopt;
10689 return sidecar;
10690}
10691
10692void removeTextOcsSidecar(DRW_Entity &entity) {
10693 std::vector<std::shared_ptr<DRW_Variant>> filtered;
10694 bool inGroup = false;
10695 for (const auto &value : entity.extData) {
10696 if (value && value->code() == 1001) {
10697 inGroup = std::string{value->c_str()} == kTextOcsMarker;
10698 if (inGroup)
10699 continue;
10700 }
10701 if (!inGroup)
10702 filtered.push_back(value);
10703 }
10704 entity.extData = std::move(filtered);
10705}
10706
10707struct MTextOcsSidecar {
10708 double elevation = 0.0;
10709 DRW_Coord xAxis;
10710 DRW_Coord extrusion {0.0, 0.0, 1.0};
10711};
10712
10713std::optional<MTextOcsSidecar>
10714extractMTextOcsSidecar(const RS_Entity *entity) {
10715 if (!entity || !entity->hasDrwExtData())
10716 return std::nullopt;
10717 MTextOcsSidecar sidecar;
10718 bool gotElevation = false;
10719 bool gotXAxis = false;
10720 bool gotExtrusion = false;
10721 bool inGroup = false;
10722 for (const auto &value : entity->getDrwExtData()) {
10723 if (!value)
10724 continue;
10725 if (value->code() == 1001) {
10726 inGroup = std::string{value->c_str()} == kMTextOcsMarker;
10727 continue;
10728 }
10729 if (!inGroup)
10730 continue;
10731 if (value->code() == 1010) {
10732 if (const DRW_Coord *point = value->coord()) {
10733 sidecar.elevation = point->z;
10734 gotElevation = true;
10735 }
10736 } else if (value->code() == 1011) {
10737 if (const DRW_Coord *axis = value->coord()) {
10738 sidecar.xAxis = *axis;
10739 gotXAxis = true;
10740 }
10741 } else if (value->code() == 1012) {
10742 if (const DRW_Coord *normal = value->coord()) {
10743 sidecar.extrusion = *normal;
10744 gotExtrusion = true;
10745 }
10746 }
10747 }
10748 if (!gotElevation || !gotXAxis || !gotExtrusion)
10749 return std::nullopt;
10750 return sidecar;
10751}
10752
10753void removeMTextOcsSidecar(DRW_Entity &entity) {
10754 std::vector<std::shared_ptr<DRW_Variant>> filtered;
10755 bool inGroup = false;
10756 for (const auto &value : entity.extData) {
10757 if (value && value->code() == 1001) {
10758 inGroup = std::string{value->c_str()} == kMTextOcsMarker;
10759 if (inGroup)
10760 continue;
10761 }
10762 if (!inGroup)
10763 filtered.push_back(value);
10764 }
10765 entity.extData = std::move(filtered);
10766}
10767} // namespace
10768
10769void RS_FilterDXFRW::reconstructTypedConversions(
10770 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
10771 if (!container)
10772 return;
10773
10774 for (RS_Entity *e :
10775 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
10776 if (e->getFlag(RS2::FlagDeleted))
10777 continue;
10778 if (consumed.find(e) != consumed.end())
10779 continue;
10780 auto meta = extractTypedConversionSidecar(e);
10781 if (!meta)
10782 continue;
10783
10784 // Build the native DRW entity with full geometry. Attributes (layer,
10785 // color, linetype, ...) come from getEntityAttributes(); afterwards the
10786 // F2 sidecar markers are stripped so the rebuilt native type does not
10787 // carry them back out as XDATA.
10788 auto emitNative = [&](DRW_Entity *drw) {
10789 getEntityAttributes(drw, e);
10790 // Drop the F2 sidecar groups from the re-emitted XDATA so the rebuilt
10791 // native type does not carry the marker back out.
10792 std::vector<std::shared_ptr<DRW_Variant>> filtered;
10793 bool inGroup = false;
10794 for (const auto &v : drw->extData) {
10795 if (v && v->code() == 1001) {
10796 const std::string app{v->c_str()};
10797 inGroup = (app == kPointExtrusionMarker || app == kLineExtrusionMarker
10798 || app == kRayMarker || app == kXlineMarker
10799 || app == kTraceMarker || app == kSolidMarker || app == k3dFaceMarker);
10800 if (inGroup)
10801 continue;
10802 }
10803 if (inGroup)
10804 continue;
10805 filtered.push_back(v);
10806 }
10807 drw->extData = std::move(filtered);
10808 };
10809
10810 if (meta->marker == kPointExtrusionMarker) {
10811 if (meta->coords.size() < 2 || e->rtti() != RS2::EntityPoint)
10812 continue;
10813 auto *source = static_cast<RS_Point *>(e);
10814 DRW_Point point;
10815 point.basePoint = meta->coords[0];
10816 point.basePoint.x = source->getStartpoint().x;
10817 point.basePoint.y = source->getStartpoint().y;
10818 point.extPoint = meta->coords[1];
10819 point.thickness = meta->thickness;
10820 point.xAxisAngle = meta->pointXAxisAngle;
10821 emitNative(&point);
10822 if (m_dwgW) {
10823 if (!m_dwgW->writePoint(&point))
10824 m_writeFailed = true;
10825 } else if (m_dxfW) {
10826 m_dxfW->writePoint(&point);
10827 }
10828 consumed.insert(e);
10829 } else if (meta->marker == kLineExtrusionMarker) {
10830 if (meta->coords.size() < 3 || e->rtti() != RS2::EntityLine)
10831 continue;
10832 auto *source = static_cast<RS_Line *>(e);
10833 DRW_Line line;
10834 line.basePoint = meta->coords[0];
10835 line.secPoint = meta->coords[1];
10836 line.basePoint.x = source->getStartpoint().x;
10837 line.basePoint.y = source->getStartpoint().y;
10838 line.secPoint.x = source->getEndpoint().x;
10839 line.secPoint.y = source->getEndpoint().y;
10840 line.extPoint = meta->coords[2];
10841 line.thickness = meta->thickness;
10842 emitNative(&line);
10843 if (m_dwgW) {
10844 if (!m_dwgW->writeLine(&line))
10845 m_writeFailed = true;
10846 } else if (m_dxfW) {
10847 m_dxfW->writeLine(&line);
10848 }
10849 consumed.insert(e);
10850 } else if (meta->marker == kRayMarker) {
10851 if (meta->coords.size() < 2)
10852 continue;
10853 DRW_Ray ray;
10854 ray.basePoint = meta->coords[0];
10855 ray.secPoint = meta->coords[1];
10856 emitNative(&ray);
10857 if (m_dwgW) {
10858 if (!m_dwgW->writeRay(&ray))
10859 m_writeFailed = true;
10860 } else if (m_dxfW) {
10861 m_dxfW->writeRay(&ray);
10862 }
10863 consumed.insert(e);
10864 } else if (meta->marker == kXlineMarker) {
10865 if (meta->coords.size() < 2)
10866 continue;
10867 DRW_Xline xline;
10868 xline.basePoint = meta->coords[0];
10869 xline.secPoint = meta->coords[1];
10870 emitNative(&xline);
10871 if (m_dwgW) {
10872 if (!m_dwgW->writeXline(&xline))
10873 m_writeFailed = true;
10874 } else if (m_dxfW) {
10875 m_dxfW->writeXline(&xline);
10876 }
10877 consumed.insert(e);
10878 } else if (meta->marker == kTraceMarker) {
10879 if (meta->coords.size() < 4)
10880 continue;
10881 DRW_Trace trace;
10882 trace.basePoint = meta->coords[0];
10883 trace.secPoint = meta->coords[1];
10884 trace.thirdPoint = meta->coords[2];
10885 trace.fourPoint = meta->coords[3];
10886 trace.thickness = meta->thickness;
10887 emitNative(&trace);
10888 if (m_dwgW) {
10889 if (!m_dwgW->writeTrace(&trace))
10890 m_writeFailed = true;
10891 } else if (m_dxfW) {
10892 m_dxfW->writeTrace(&trace);
10893 }
10894 consumed.insert(e);
10895 } else if (meta->marker == kSolidMarker) {
10896 if (meta->coords.size() < 4)
10897 continue;
10898 DRW_Solid solid;
10899 solid.basePoint = meta->coords[0];
10900 solid.secPoint = meta->coords[1];
10901 solid.thirdPoint = meta->coords[2];
10902 solid.fourPoint = meta->coords[3];
10903 solid.thickness = meta->thickness;
10904 emitNative(&solid);
10905 if (m_dwgW) {
10906 if (!m_dwgW->writeSolid(&solid))
10907 m_writeFailed = true;
10908 } else if (m_dxfW) {
10909 m_dxfW->writeSolid(&solid);
10910 }
10911 consumed.insert(e);
10912 } else if (meta->marker == k3dFaceMarker) {
10913 if (meta->coords.size() < 4)
10914 continue;
10915 DRW_3Dface face;
10916 face.basePoint = meta->coords[0];
10917 face.secPoint = meta->coords[1];
10918 face.thirdPoint = meta->coords[2];
10919 face.fourPoint = meta->coords[3];
10920 face.invisibleflag = meta->flag;
10921 emitNative(&face);
10922 if (m_dwgW) {
10923 if (!m_dwgW->write3dface(&face))
10924 m_writeFailed = true;
10925 } else if (m_dxfW) {
10926 m_dxfW->write3dface(&face);
10927 }
10928 consumed.insert(e);
10929 }
10930 }
10931}
10932
10933void RS_FilterDXFRW::writeEntity(RS_Entity* e) {
10934 switch (e->rtti()) {
10935 case RS2::EntityPoint:
10936 writePoint(static_cast<RS_Point*>(e));
10937 break;
10938 case RS2::EntityLine:
10939 writeLine(static_cast<RS_Line*>(e));
10940 break;
10941 case RS2::EntityCircle:
10942 writeCircle(static_cast<RS_Circle*>(e));
10943 break;
10944 case RS2::EntityArc:
10945 writeArc(static_cast<RS_Arc*>(e));
10946 break;
10947 case RS2::EntitySolid:
10948 writeSolid(static_cast<RS_Solid*>(e));
10949 break;
10950 case RS2::EntityEllipse:
10951 writeEllipse(static_cast<RS_Ellipse*>(e));
10952 break;
10953 case RS2::EntityHyperbola:
10954 writeHyperbola(static_cast<LC_Hyperbola*>(e));
10955 break;
10956 case RS2::EntityPolyline:
10957 writeLWPolyline(static_cast<RS_Polyline*>(e));
10958 break;
10959 case RS2::EntitySpline:
10960 writeSpline(static_cast<RS_Spline*>(e));
10961 break;
10962 case RS2::EntitySplinePoints:
10963 writeSplinePoints(static_cast<LC_SplinePoints*>(e));
10964 break;
10965 case RS2::EntityParabola:
10966 writeParabola(static_cast<LC_Parabola*>(e));
10967 break;
10968// case RS2::EntityVertex:
10969// break;
10970 case RS2::EntityInsert:
10971 writeInsert(static_cast<RS_Insert*>(e));
10972 break;
10973 case RS2::EntityMText:
10974 writeMText(static_cast<RS_MText*>(e));
10975 break;
10976 case RS2::EntityText:
10977 writeText(static_cast<RS_Text*>(e));
10978 break;
10979 case RS2::EntityDimLinear:
10980 case RS2::EntityDimOrdinate:
10981 case RS2::EntityDimAligned:
10982 case RS2::EntityDimAngular:
10983 case RS2::EntityDimRadial:
10984 case RS2::EntityDimDiametric:
10985 case RS2::EntityDimArc:
10986 writeDimension(static_cast<RS_Dimension*>(e));
10987 break;
10988 case RS2::EntityDimLeader:
10989 writeLeader(static_cast<RS_Leader*>(e));
10990 break;
10991 case RS2::EntityTolerance:
10992 writeTolerance(static_cast<LC_Tolerance*>(e));
10993 break;
10994 case RS2::EntityHatch:
10995 writeHatch(static_cast<RS_Hatch*>(e));
10996 break;
10997 case RS2::EntityImage:
10998 writeImage(static_cast<RS_Image*>(e));
10999 break;
11000 case RS2::EntityWipeout:
11001 writeWipeout(static_cast<LC_Wipeout *>(e));
11002 break;
11003 case RS2::EntityMLeader:
11004 writeMLeader(static_cast<LC_MLeader *>(e));
11005 break;
11006 default:
11007 break;
11008 }
11009}
11010
11011void RS_FilterDXFRW::reconstructUnderlays(RS_EntityContainer *container,
11012 std::set<RS_Entity *> &consumed) {
11013 if (!container)
11014 return;
11015 // Single-pass: each polyline carrying LibreCAD_UNDERLAY XDATA
11016 // reconstructs ONE DRW_Underlay (no group/sibling matching like MLINE).
11017 for (RS_Entity *e :
11018 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
11019 if (e->getFlag(RS2::FlagDeleted))
11020 continue;
11021 if (e->rtti() != RS2::EntityPolyline)
11022 continue;
11023 if (!e->hasDrwExtData())
11024 continue;
11025
11026 const auto &ext = e->getDrwExtData();
11027 bool inGroup = false;
11028 DRW_Underlay u;
11029 DRW_Coord position;
11030 bool gotPosition = false;
11031 int seen1000 = 0; // 0=id, 1=kind
11032 int seen1040 = 0; // 0=scale.x, 1=scale.y, 2=rotation
11033 int seen1070 = 0; // 0=flags, 1=contrast, 2=fade
11034
11035 for (const auto &sp : ext) {
11036 if (!sp)
11037 continue;
11038 const int code = sp->code();
11039 if (code == 1001) {
11040 inGroup = (std::string{sp->c_str()} == "LibreCAD_UNDERLAY");
11041 continue;
11042 }
11043 if (!inGroup)
11044 continue;
11045 switch (code) {
11046 case 1000: {
11047 const std::string s = sp->c_str();
11048 if (seen1000 == 1) {
11049 // kind
11050 if (s == "DGN")
11051 u.kind = DRW_Underlay::DGN;
11052 else if (s == "DWF")
11053 u.kind = DRW_Underlay::DWF;
11054 }
11055 ++seen1000;
11056 break;
11057 }
11058 case 1071:
11059 u.definitionHandle = static_cast<std::uint32_t>(sp->i_val());
11060 break;
11061 case 1010: {
11062 const auto *c = sp->coord();
11063 if (c) {
11064 position = *c;
11065 gotPosition = true;
11066 }
11067 break;
11068 }
11069 case 1040: {
11070 const double d = sp->d_val();
11071 if (seen1040 == 0)
11072 u.scale.x = d;
11073 else if (seen1040 == 1)
11074 u.scale.y = d;
11075 else if (seen1040 == 2)
11076 u.rotation = d;
11077 ++seen1040;
11078 break;
11079 }
11080 case 1070: {
11081 const int v = static_cast<int>(sp->i_val());
11082 if (seen1070 == 0)
11083 u.flags = static_cast<std::uint8_t>(v);
11084 else if (seen1070 == 1)
11085 u.contrast = static_cast<std::uint8_t>(v);
11086 else if (seen1070 == 2)
11087 u.fade = static_cast<std::uint8_t>(v);
11088 ++seen1070;
11089 break;
11090 }
11091 default:
11092 break;
11093 }
11094 }
11095
11096 if (!gotPosition)
11097 continue; // not a real LibreCAD_UNDERLAY block
11098 u.position = position;
11099
11100 // Reverse-transform polyline vertices back to OCS clip coordinates:
11101 // poly_v = position + R(rotation) * scale * clip_v
11102 // → clip_v = scale^-1 * R^-T * (poly_v - position)
11103 const auto *pl = static_cast<const RS_Polyline *>(e);
11104 const double cs = std::cos(u.rotation);
11105 const double sn = std::sin(u.rotation);
11106 const double sx = (std::abs(u.scale.x) > RS_TOLERANCE1.0e-10) ? u.scale.x : 1.0;
11107 const double sy = (std::abs(u.scale.y) > RS_TOLERANCE1.0e-10) ? u.scale.y : 1.0;
11108 u.clipBoundary.clear();
11109 for (RS_Entity *sub : *pl) {
11110 // Polyline sub-entities are line segments; first endpoint of each
11111 // gives the polygon vertex.
11112 const RS_Vector p = sub->getStartpoint();
11113 const double dx = p.x - u.position.x;
11114 const double dy = p.y - u.position.y;
11115 const double rx = (dx * cs + dy * sn) / sx;
11116 const double ry = (-dx * sn + dy * cs) / sy;
11117 u.clipBoundary.emplace_back(rx, ry, 0.0);
11118 }
11119
11120 if (m_dwgW) {
11121 if (!m_dwgW->writeUnderlay(&u))
11122 m_writeFailed = true;
11123 } else if (m_dxfW) {
11124 m_dxfW->writeUnderlay(&u);
11125 }
11126 consumed.insert(e);
11127 }
11128}
11129
11130/**
11131 * Writes the given Point entity to the file.
11132 */
11133void RS_FilterDXFRW::writePoint(const RS_Point* p) {
11134 DRW_Point point;
11135 getEntityAttributes(&point, p);
11136 point.basePoint.x = p->getStartpoint().x;
11137 point.basePoint.y = p->getStartpoint().y;
11138 if (m_dwgW) {
11139 if (!m_dwgW->writePoint(&point))
11140 m_writeFailed = true;
11141 return;
11142 }
11143 m_dxfW->writePoint(&point);
11144}
11145
11146/**
11147 * Writes the given Line( entity to the file.
11148 */
11149void RS_FilterDXFRW::writeLine(const RS_Line* l) {
11150 DRW_Line line;
11151 getEntityAttributes(&line, l);
11152 line.basePoint.x = l->getStartpoint().x;
11153 line.basePoint.y = l->getStartpoint().y;
11154 line.secPoint.x = l->getEndpoint().x;
11155 line.secPoint.y = l->getEndpoint().y;
11156 if (m_dwgW) {
11157 if (!m_dwgW->writeLine(&line))
11158 m_writeFailed = true;
11159 return;
11160 }
11161 m_dxfW->writeLine(&line);
11162}
11163
11164/**
11165 * Writes the given circle entity to the file.
11166 */
11167void RS_FilterDXFRW::writeCircle(const RS_Circle* c) {
11168 DRW_Circle circle;
11169 getEntityAttributes(&circle, c);
11170 circle.basePoint.x = c->getCenter().x;
11171 circle.basePoint.y = c->getCenter().y;
11172 circle.radious = c->getRadius();
11173 if (m_dwgW) {
11174 if (!m_dwgW->writeCircle(&circle))
11175 m_writeFailed = true;
11176 return;
11177 }
11178 m_dxfW->writeCircle(&circle);
11179}
11180
11181/**
11182 * Writes the given arc entity to the file.
11183 */
11184void RS_FilterDXFRW::writeArc(const RS_Arc* a) {
11185 DRW_Arc arc;
11186 getEntityAttributes(&arc, a);
11187 arc.basePoint.x = a->getCenter().x;
11188 arc.basePoint.y = a->getCenter().y;
11189 arc.radious = a->getRadius();
11190 if (a->isReversed()) {
11191 arc.staangle = a->getAngle2();
11192 arc.endangle = a->getAngle1();
11193 }
11194 else {
11195 arc.staangle = a->getAngle1();
11196 arc.endangle = a->getAngle2();
11197 }
11198 if (m_dwgW) {
11199 if (!m_dwgW->writeArc(&arc))
11200 m_writeFailed = true;
11201 return;
11202 }
11203 m_dxfW->writeArc(&arc);
11204}
11205
11206/**
11207 * Writes the given polyline entity to the file as lwpolyline.
11208 */
11209void RS_FilterDXFRW::writeLWPolyline(RS_Polyline* l) {
11210 //skip if are empty polyline
11211 if (l->isEmpty()) {
11212 return;
11213 }
11214 // version 12 are old style polyline
11215 if (m_version == 1009) {
11216 writePolyline(l);
11217 return;
11218 }
11219 bool has_ellipse = false;
11220 for (RS_Entity* e=l->firstEntity(RS2::ResolveNone); e; e=l->nextEntity(RS2::ResolveNone)) {
11221 if (e->rtti() == RS2::EntityEllipse) {
11222 has_ellipse = true;
11223 break;
11224 }
11225 }
11226 if (has_ellipse) {
11227 writePolyline(l);
11228 return;
11229 }
11230 DRW_LWPolyline pol;
11231 RS_Entity* currEntity = nullptr;
11232
11233 RS_AtomicEntity* ae = nullptr;
11234 double bulge = 0.0;
11235
11236 lc::LC_ContainerTraverser traverser{*l, RS2::ResolveNone};
11237 for (RS_Entity* e = traverser.first(); e != nullptr; e = traverser.next()) {
11238 currEntity = e;
11239 // nextEntity = traverser.next();
11240
11241 if (!e->isAtomic()) {
11242 continue;
11243 }
11244 ae = static_cast<RS_AtomicEntity*>(e);
11245
11246 // Write vertex:
11247 if (e->rtti() == RS2::EntityArc) {
11248 bulge = static_cast<RS_Arc*>(e)->getBulge();
11249 }
11250 else {
11251 bulge = 0.0;
11252 }
11253 pol.addVertex(DRW_Vertex2D(ae->getStartpoint().x, ae->getStartpoint().y, bulge));
11254 }
11255 if (l->isClosed()) {
11256 pol.flags = 1;
11257 }
11258 else {
11259 ae = static_cast<RS_AtomicEntity*>(currEntity);
11260 if (ae->rtti() == RS2::EntityArc) {
11261 bulge = static_cast<RS_Arc*>(ae)->getBulge();
11262 }
11263 pol.addVertex(DRW_Vertex2D(ae->getEndpoint().x, ae->getEndpoint().y, bulge));
11264 }
11265 pol.vertexnum = pol.vertlist.size();
11266 getEntityAttributes(&pol, l);
11267 auto lwMeta = extractLWPolylineMeta(l);
11268 if (lwMeta) {
11269 pol.width = lwMeta->width;
11270 pol.elevation = lwMeta->elevation;
11271 pol.thickness = lwMeta->thickness;
11272 pol.extPoint = lwMeta->extrusion;
11273 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
11274 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
11275 if (i < lwMeta->startWidths.size())
11276 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
11277 if (i < lwMeta->endWidths.size())
11278 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
11279 if (i < lwMeta->identifiers.size())
11280 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
11281 }
11282 }
11283 }
11284 if (m_dwgW) {
11285 if (!m_dwgW->writeLWPolyline(&pol))
11286 m_writeFailed = true;
11287 return;
11288 }
11289 m_dxfW->writeLWPolyline(&pol);
11290}
11291
11292/**
11293 * Writes the given polyline entity to the file (old style).
11294 */
11295void RS_FilterDXFRW::writePolyline(const RS_Polyline* p) {
11296 if (p == nullptr)
11297 return;
11298
11299 DRW_Polyline pol;
11300 if (p->isClosed()) {
11301 pol.flags = 1;
11302 }
11303
11304 RS_Entity* nextEntity = nullptr;
11305 for (RS_Entity* e = p->firstEntity(RS2::ResolveNone); e != nullptr; e = nextEntity) {
11306 nextEntity = p->nextEntity(RS2::ResolveNone);
11307
11308 if (!e->isAtomic()) {
11309 continue;
11310 }
11311 RS_AtomicEntity* ae = static_cast<RS_AtomicEntity*>(e);
11312
11313 // Write vertex:
11314 double bulge = 0.0;
11315 bool isElliptic = false;
11316 double yRadius = 0.0;
11317 switch (e->rtti()) {
11318 case RS2::EntityLine:
11319 break;
11320 case RS2::EntityArc:
11321 bulge = ((RS_Arc*)e)->getBulge();
11322 break;
11323 case RS2::EntityEllipse: {
11324 // Issue #1946: prepare to write elliptic arcs as RS_Arc
11325 RS_Ellipse* ellipse = static_cast<RS_Ellipse*>(e);
11326 auto pair = RS_Polyline::convertToArcPair(ellipse);
11327 std::unique_ptr<RS_Arc> arc{ pair.first };
11328 bulge = arc->getBulge();
11329 yRadius = arc->getRadius() * pair.second;
11330 isElliptic = true;
11331 }
11332 break;
11333 default:
11334 // should not happen: unknown entity type
11335 continue;
11336 }
11337 pol.addVertex(DRW_Vertex(ae->getStartpoint().x, ae->getStartpoint().y, 0.0, bulge));
11338 if (isElliptic) {
11339 // Add flag to indicate the vertex should be elliptic
11340 pol.vertlist.back()->extData.push_back(std::make_shared<DRW_Variant>(1001, "LibreCad"));
11341 // Keep to ellipse minor radius to allow elliptic creation at loading
11342 pol.vertlist.back()->extData.push_back(std::make_shared<DRW_Variant>(1040, yRadius));
11343 }
11344 }
11345 getEntityAttributes(&pol, p);
11346 if (auto lwMeta = extractLWPolylineMeta(const_cast<RS_Polyline *>(p))) {
11347 pol.basePoint.z = lwMeta->elevation;
11348 pol.thickness = lwMeta->thickness;
11349 pol.extPoint = lwMeta->extrusion;
11350 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
11351 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
11352 if (i < lwMeta->startWidths.size())
11353 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
11354 if (i < lwMeta->endWidths.size())
11355 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
11356 if (i < lwMeta->identifiers.size())
11357 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
11358 }
11359 }
11360 }
11361 if (m_dwgW) {
11362 if (!m_dwgW->writePolyline(&pol))
11363 m_writeFailed = true;
11364 return;
11365 }
11366 m_dxfW->writePolyline(&pol);
11367}
11368
11369/**
11370 * Writes the given spline entity to the file.
11371 */
11372void RS_FilterDXFRW::writeSpline(RS_Spline *s) {
11373 if (s == nullptr) {
11374 return;
11375 }
11376
11377 if (s->getNumberOfControlPoints() < size_t(s->getDegree()+1)) {
11378 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXF::writeSpline: " "Discarding spline: not enough control points given.");
11379 return;
11380 }
11381
11382 // version 12 do not support Spline write as polyline
11383 if (m_version == 1009) {
11384 DRW_Polyline pol;
11385 for(RS_Entity* e: lc::LC_ContainerTraverser{*s, RS2::ResolveNone}.entities()) {
11386 pol.addVertex(DRW_Vertex(e->getStartpoint().x, e->getStartpoint().y, 0.0, 0.0));
11387 }
11388 if (s->isClosed()) {
11389 pol.flags = 1;
11390 }
11391 else {
11392 pol.addVertex(DRW_Vertex(s->getEndpoint().x, s->getEndpoint().y, 0.0, 0.0));
11393 }
11394 getEntityAttributes(&pol, s);
11395 m_dxfW->writePolyline(&pol);
11396 return;
11397 }
11398
11399 DRW_Spline sp{};
11400
11401 // dxf spline group code=70
11402 // bit coded: 1: closed; 2: periodic; 4: rational; 8: planar; 16:linear
11403 sp.flags = (s->isClosed()) ? 0b1011 : 0b1000;
11404
11405 // write spline control points:
11406 for (const RS_Vector& v : s->getUnwrappedControlPoints()) {
11407 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
11408 }
11409 sp.weightlist = s->getUnwrappedWeights();
11410 if (std::any_of(sp.weightlist.begin(), sp.weightlist.end(), differsFromUnitWeight)) {
11411 sp.flags |= 0x04;
11412 }
11413
11414 sp.ncontrol = sp.controllist.size();
11415 sp.degree = s->getDegree();
11416
11417 // knot vector from RS_Spline
11418 sp.knotslist = s->getUnwrappedKnotVector();
11419 sp.nknots = sp.knotslist.size();
11420
11421 getEntityAttributes(&sp, s);
11422 if (m_dwgW) {
11423 if (!m_dwgW->writeSpline(&sp))
11424 m_writeFailed = true;
11425 return;
11426 }
11427 m_dxfW->writeSpline(&sp);
11428}
11429
11430/**
11431 * Writes the given spline entity to the file.
11432 */
11433void RS_FilterDXFRW::writeSplinePoints(LC_SplinePoints* s) {
11434 int nCtrls = s->getNumberOfControlPoints();
11435 auto const& cp = s->getControlPoints();
11436
11437 if(nCtrls < 3){
11438 if(nCtrls > 1){
11439 DRW_Line line;
11440 line.basePoint.x = cp.at(0).x;
11441 line.basePoint.y = cp.at(0).y;
11442 line.secPoint.x = cp.at(1).x;
11443 line.secPoint.y = cp.at(1).y;
11444 getEntityAttributes(&line, s);
11445 if (m_dwgW) {
11446 if (!m_dwgW->writeLine(&line))
11447 m_writeFailed = true;
11448 return;
11449 }
11450 m_dxfW->writeLine(&line);
11451 }
11452 return;
11453 }
11454
11455 // version 12 do not support Spline write as polyline
11456 if(m_version == 1009){
11457 DRW_Polyline pol;
11458 auto const& sp = s->getStrokePoints();
11459
11460 for (size_t i = 0; i < sp.size(); i++) {
11461 pol.addVertex(DRW_Vertex(sp.at(i).x, sp.at(i).y, 0.0, 0.0));
11462 }
11463
11464 if (s->isClosed()) {
11465 pol.flags = 1;
11466 }
11467
11468 getEntityAttributes(&pol, s);
11469 m_dxfW->writePolyline(&pol);
11470 return;
11471 }
11472
11473 DRW_Spline sp;
11474 if (s->isClosed()) {
11475 sp.flags = 11;
11476 }
11477 else {
11478 sp.flags = 8;
11479 }
11480
11481 LC_SplinePointsData &data = s->getData();
11482 auto const& fitPoints = data.splinePoints;
11483 const bool writeFitScenario = !data.useControlPoints && fitPoints.size() >= 2;
11484 sp.degree = 2;
11485 sp.nfit = static_cast<std::int32_t>(fitPoints.size());
11486
11487 if (writeFitScenario) {
11488 sp.m_scenario = 2;
11489 sp.m_knotParam = 0;
11490 sp.ncontrol = 0;
11491 sp.nknots = 0;
11492 sp.tolfit = 0.0000001;
11493 const RS_Vector startTangent = fitPoints[1] - fitPoints[0];
11494 const RS_Vector endTangent = fitPoints.back() - fitPoints[fitPoints.size() - 2];
11495 sp.tgStart = DRW_Coord{startTangent.x, startTangent.y, 0.0};
11496 sp.tgEnd = DRW_Coord{endTangent.x, endTangent.y, 0.0};
11497 } else {
11498 sp.ncontrol = nCtrls;
11499 sp.nknots = nCtrls + 3;
11500 }
11501
11502 // write spline knots:
11503 if (!writeFitScenario) {
11504 for(int i = 1; i <= sp.nknots; i++){
11505 if(i <= 3){
11506 sp.knotslist.push_back(0.0);
11507 }
11508 else if(i <= nCtrls){
11509 sp.knotslist.push_back((i - 3.0)/(nCtrls - 2.0));
11510 }
11511 else{
11512 sp.knotslist.push_back(1.0);
11513 }
11514 }
11515 }
11516
11517 // write spline control points:
11518 if (!writeFitScenario) {
11519 for (auto const& v : cp) {
11520 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
11521 }
11522 }
11523
11524 // fit points
11525 for (auto const& v : fitPoints) {
11526 sp.fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
11527 }
11528
11529 getEntityAttributes(&sp, s);
11530 if (m_dwgW) {
11531 if (!writeFitScenario) {
11532 sp.fitlist.clear();
11533 sp.nfit = 0;
11534 }
11535 if (!m_dwgW->writeSpline(&sp))
11536 m_writeFailed = true;
11537 return;
11538 }
11539 m_dxfW->writeSpline(&sp);
11540}
11541
11542/**
11543 * Writes the given Ellipse entity to the file.
11544 */
11545void RS_FilterDXFRW::writeEllipse(const RS_Ellipse* s) {
11546 // version 12 do not support Ellipse but are
11547 // converted in polyline by library
11548 DRW_Ellipse el;
11549 getEntityAttributes(&el, s);
11550 el.basePoint.x = s->getCenter().x;
11551 el.basePoint.y = s->getCenter().y;
11552 el.secPoint.x = s->getMajorP().x;
11553 el.secPoint.y = s->getMajorP().y;
11554 el.ratio = s->getRatio();
11555 if (s->isReversed()) {
11556 el.staparam = s->getAngle2();
11557 el.endparam = s->getAngle1();
11558 }
11559 else {
11560 el.staparam = s->getAngle1();
11561 el.endparam = s->getAngle2();
11562 }
11563 if (m_dwgW) {
11564 if (!m_dwgW->writeEllipse(&el))
11565 m_writeFailed = true;
11566 return;
11567 }
11568 m_dxfW->writeEllipse(&el);
11569}
11570
11571/**
11572 * Write a hyperbola entity as an exact rational quadratic SPLINE.
11573 *
11574 * Uses LC_HyperbolaSpline to create the standard SPLINE representation.
11575 */
11576void RS_FilterDXFRW::writeHyperbola(LC_Hyperbola* h) {
11577 if (h == nullptr || !h->isValid()) return;
11578 DRW_Spline spl;
11579 getEntityAttributes(&spl, h);
11580 if (LC_HyperbolaSpline::hyperbolaToSpline(h->getData(), spl)) {
11581 if (m_dwgW) {
11582 if (!m_dwgW->writeSpline(&spl))
11583 m_writeFailed = true;
11584 return;
11585 }
11586 if (m_dxfW) m_dxfW->writeSpline(&spl);
11587 }
11588}
11589
11590/**
11591 * Write a parabola entity as its canonical non-rational quadratic SPLINE.
11592 */
11593void RS_FilterDXFRW::writeParabola(LC_Parabola* p) {
11594 if (p == nullptr || !p->getData().isValid())
11595 return;
11596
11597 DRW_Spline spl;
11598 getEntityAttributes(&spl, p);
11599 if (LC_ParabolaSpline::parabolaToSpline(p->getData(), spl)) {
11600 if (m_dwgW) {
11601 if (!m_dwgW->writeSpline(&spl))
11602 m_writeFailed = true;
11603 return;
11604 }
11605 if (m_dxfW) m_dxfW->writeSpline(&spl);
11606 }
11607}
11608
11609/**
11610 * Writes the given block insert entity to the file.
11611 */
11612void RS_FilterDXFRW::writeInsert(const RS_Insert* i) {
11613 DRW_Insert in;
11614 getEntityAttributes(&in, i);
11615 in.basePoint.x = i->getInsertionPoint().x;
11616 in.basePoint.y = i->getInsertionPoint().y;
11617 in.basePoint.z = i->getInsertionPoint().z;
11618 in.name = i->getName().toUtf8().data();
11619 in.xscale = i->getScale().x;
11620 in.yscale = i->getScale().y;
11621 in.zscale = i->getScale().z;
11622 in.angle = i->getAngle();
11623 in.colcount = i->getCols();
11624 in.rowcount = i->getRows();
11625 in.colspace = i->getSpacing().x;
11626 in.rowspace =i->getSpacing().y;
11627 const RS_Vector extrusion = i->getData().extrusion;
11628 in.extPoint = DRW_Coord(extrusion.x, extrusion.y, extrusion.z);
11629 if (m_dwgW) {
11630 if (!m_dwgW->writeInsert(&in))
11631 m_writeFailed = true;
11632 return;
11633 }
11634 m_dxfW->writeInsert(&in);
11635}
11636
11637/**
11638 * Writes the given mText entity to the file.
11639 */
11640void RS_FilterDXFRW::writeMText(const RS_MText* t) {
11641 DRW_Text* text;
11642 DRW_Text txt1;
11643 DRW_MText txt2;
11644
11645 if (m_version == 1009) {
11646 text = &txt1;
11647 }
11648 else {
11649 text = &txt2;
11650 }
11651
11652 getEntityAttributes(text, t);
11653 if (m_version != 1009) {
11654 if (const auto ocs = extractMTextOcsSidecar(t)) {
11655 text->basePoint.z = ocs->elevation;
11656 text->extPoint = ocs->extrusion;
11657 static_cast<DRW_MText *>(text)->secPoint = ocs->xAxis;
11658 removeMTextOcsSidecar(*text);
11659 }
11660 }
11661 text->basePoint.x = t->getInsertionPoint().x;
11662 text->basePoint.y = t->getInsertionPoint().y;
11663 text->height = t->getHeight();
11664 text->angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
11665 text->style = t->getStyle().toStdString();
11666
11667 if (m_version == 1009) {
11668 if (t->getHAlign() == RS_MTextData::HALeft) {
11669 text->alignH = DRW_Text::HLeft;
11670 }
11671 else if (t->getHAlign() == RS_MTextData::HACenter) {
11672 text->alignH = DRW_Text::HCenter;
11673 }
11674 else if (t->getHAlign() == RS_MTextData::HARight) {
11675 text->alignH = DRW_Text::HRight;
11676 }
11677 if (t->getVAlign() == RS_MTextData::VATop) {
11678 text->alignV = DRW_Text::VTop;
11679 }
11680 else if (t->getVAlign() == RS_MTextData::VAMiddle) {
11681 text->alignV = DRW_Text::VMiddle;
11682 }
11683 else if (t->getVAlign() == RS_MTextData::VABottom) {
11684 text->alignV = DRW_Text::VBaseLine;
11685 }
11686#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
11687 QStringList txtList = t->getText().split('\n',Qt::KeepEmptyParts);
11688#else
11689 QStringList txtList = t->getText().split('\n', QString::KeepEmptyParts);
11690#endif
11691 double dist = t->getLineSpacingFactor() * 5 * t->getHeight() / 3;
11692 bool setSec = false;
11693 if (text->alignH != DRW_Text::HLeft || text->alignV != DRW_Text::VBaseLine) {
11694 text->secPoint.x = t->getInsertionPoint().x;
11695 text->secPoint.y = t->getInsertionPoint().y;
11696 setSec = true;
11697 }
11698 if (text->alignV == DRW_Text::VTop) {
11699 dist = dist * -1;
11700 }
11701 for (int i = 0; i < txtList.size(); ++i) {
11702 if (!txtList.at(i).isEmpty()) {
11703 text->text = toDxfString(txtList.at(i)).toUtf8().data();
11704 RS_Vector inc = RS_Vector::polar(dist*i, t->getAngle()+M_PI_21.57079632679489661923);
11705 if (setSec) {
11706 text->secPoint.x += inc.x;
11707 text->secPoint.y += inc.y;
11708 }
11709 else {
11710 text->basePoint.x += inc.x;
11711 text->basePoint.y += inc.y;
11712 }
11713 m_dxfW->writeText(text);
11714 }
11715 }
11716 }
11717 else {
11718 if (t->getHAlign() == RS_MTextData::HALeft) {
11719 text->textgen = 1;
11720 }
11721 else if (t->getHAlign() == RS_MTextData::HACenter) {
11722 text->textgen = 2;
11723 }
11724 else if (t->getHAlign() == RS_MTextData::HARight) {
11725 text->textgen = 3;
11726 }
11727 if (t->getVAlign() == RS_MTextData::VAMiddle) {
11728 text->textgen += 3;
11729 }
11730 else if (t->getVAlign() == RS_MTextData::VABottom) {
11731 text->textgen += 6;
11732 }
11733 // DXF MTEXT group 72: 1=LeftToRight, 3=TopToBottom, 5=ByStyle.
11734 // RightToLeft has no DXF encoding — fall through to LeftToRight so
11735 // other readers see a well-defined direction. The RTL preference is
11736 // round-tripped via XDATA (app id "LibreCad", code 1071) below; on
11737 // re-import we restore the flag if the marker is present, otherwise
11738 // the entity reads as LTR.
11739 switch (t->getDrawingDirection()) {
11740 case RS_MTextData::TopToBottom:
11741 text->alignH = static_cast<DRW_Text::HAlign>(3);
11742 break;
11743 case RS_MTextData::ByStyle:
11744 text->alignH = static_cast<DRW_Text::HAlign>(5);
11745 break;
11746 case RS_MTextData::LeftToRight:
11747 case RS_MTextData::RightToLeft:
11748 default:
11749 text->alignH = static_cast<DRW_Text::HAlign>(1);
11750 break;
11751 }
11752 if (t->getDrawingDirection() == RS_MTextData::RightToLeft) {
11753 text->extData.push_back(
11754 std::make_shared<DRW_Variant>(1001, std::string("LibreCad")));
11755 text->extData.push_back(
11756 std::make_shared<DRW_Variant>(1071, std::int32_t{1}));
11757 }
11758 if (t->getLineSpacingStyle() == RS_MTextData::AtLeast) {
11759 text->alignV = static_cast<DRW_Text::VAlign>(1);
11760 }
11761 else {
11762 text->alignV = static_cast<DRW_Text::VAlign>(2);
11763 }
11764
11765 text->text = toDxfString(t->getText()).toUtf8().data();
11766 // text->widthscale =t->getWidth();
11767 text->widthscale = t->getUsedTextWidth(); //getSize().x;
11768 txt2.interlin = t->getLineSpacingFactor();
11769
11770 if (m_dwgW) {
11771 if (!m_dwgW->writeMText(static_cast<DRW_MText*>(text)))
11772 m_writeFailed = true;
11773 return;
11774 }
11775 m_dxfW->writeMText(static_cast<DRW_MText*>(text));
11776 }
11777}
11778
11779/**
11780 * Writes the given Text entity to the file.
11781 */
11782void RS_FilterDXFRW::writeText(RS_Text* t) {
11783 DRW_Text text;
11784
11785 getEntityAttributes(&text, t);
11786 if (const auto ocs = extractTextOcsSidecar(t)) {
11787 text.basePoint.z = ocs->baseElevation;
11788 text.secPoint.z = ocs->alignmentElevation;
11789 text.extPoint = ocs->extrusion;
11790 text.thickness = ocs->thickness;
11791 removeTextOcsSidecar(text);
11792 }
11793 text.basePoint.x = t->getInsertionPoint().x;
11794 text.basePoint.y = t->getInsertionPoint().y;
11795 text.height = t->getHeight();
11796 text.angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
11797 text.style = t->getStyle().toStdString();
11798 text.alignH =(DRW_Text::HAlign)t->getHAlign();
11799 text.alignV =(DRW_Text::VAlign)t->getVAlign();
11800 text.widthscale = t->getWidthRel();
11801
11802 if (text.alignV != DRW_Text::VBaseLine || text.alignH != DRW_Text::HLeft) {
11803 // if (text.alignV != DRW_Text::VBaseLine || text.alignH == DRW_Text::HMiddle) {
11804 // if (text.alignH != DRW_Text::HLeft) {
11805 if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit) {
11806 text.secPoint.x = t->getSecondPoint().x;
11807 text.secPoint.y = t->getSecondPoint().y;
11808 }
11809 else {
11810 text.secPoint.x = t->getInsertionPoint().x;
11811 text.secPoint.y = t->getInsertionPoint().y;
11812 }
11813 }
11814
11815 /* if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit) {
11816 text.secPoint.x = t->getSecondPoint().x;
11817 text.secPoint.y = t->getSecondPoint().y;
11818 }*/
11819
11820 if (!t->getText().isEmpty()) {
11821 text.text = toDxfString(t->getText()).toUtf8().data();
11822 if (m_dwgW) {
11823 if (!m_dwgW->writeText(&text))
11824 m_writeFailed = true;
11825 return;
11826 }
11827 m_dxfW->writeText(&text);
11828 }
11829}
11830
11831/**
11832 * Writes the given dimension entity to the file.
11833 */
11834void RS_FilterDXFRW::writeDimension(RS_Dimension* d) {
11835 QString blkName;
11836 if (m_noNameBlock.contains(d)) {
11837 blkName = m_noNameBlock.take(d);
11838 }
11839
11840 // version 12 are inserts of *D blocks
11841 if (m_version == 1009) {
11842 if (!blkName.isEmpty()) {
11843 DRW_Insert in;
11844 getEntityAttributes(&in, d);
11845 in.basePoint.x = in.basePoint.y = 0.0;
11846 in.basePoint.z = 0.0;
11847 in.name = blkName.toStdString();
11848 in.xscale = in.yscale = 1.0;
11849 in.zscale = 1.0;
11850 in.angle = 0.0;
11851 in.colcount = in.rowcount = 1;
11852 in.colspace = in.rowspace = 0.0;
11853 m_dxfW->writeInsert(&in);
11854 }
11855 return;
11856 }
11857
11858 DRW_Dimension* dim;
11859 int attachmentPoint = 1;
11860 if (d->getHAlign() == RS_MTextData::HALeft) {
11861 attachmentPoint = 1;
11862 }
11863 else if (d->getHAlign() == RS_MTextData::HACenter) {
11864 attachmentPoint = 2;
11865 }
11866 else if (d->getHAlign() == RS_MTextData::HARight) {
11867 attachmentPoint = 3;
11868 }
11869 if (d->getVAlign() == RS_MTextData::VATop) {
11870 attachmentPoint += 0;
11871 }
11872 else if (d->getVAlign() == RS_MTextData::VAMiddle) {
11873 attachmentPoint += 3;
11874 }
11875 else if (d->getVAlign() == RS_MTextData::VABottom) {
11876 attachmentPoint += 6;
11877 }
11878
11879 switch (d->rtti()) {
11880 case RS2::EntityDimAligned: {
11881 auto* da = static_cast<RS_DimAligned*>(d);
11882 auto dd = new DRW_DimAligned();
11883 dim = dd;
11884 dim->type = 1 + 32;
11885 dd->setDef1Point(DRW_Coord(da->getExtensionPoint1().x, da->getExtensionPoint1().y, 0.0));
11886 dd->setDef2Point(DRW_Coord(da->getExtensionPoint2().x, da->getExtensionPoint2().y, 0.0));
11887 break;
11888 }
11889 case RS2::EntityDimDiametric: {
11890 auto* dr = static_cast<RS_DimDiametric*>(d);
11891 auto dd = new DRW_DimDiametric();
11892 dim = dd;
11893 dim->type = 3 + 32;
11894 dd->setDiameter1Point(DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
11895 dd->setLeaderLength(dr->getLeader());
11896 break;
11897 }
11898 case RS2::EntityDimRadial: {
11899 auto* dr = static_cast<RS_DimRadial*>(d);
11900 auto* dd = new DRW_DimRadial();
11901 dim = dd;
11902 dim->type = 4 + 32;
11903 dd->setDiameterPoint(DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
11904 dd->setLeaderLength(dr->getLeader());
11905 break;
11906 }
11907 case RS2::EntityDimAngular: {
11908 auto* da = static_cast<RS_DimAngular*>(d);
11909 if (da->getDefinitionPoint3() == da->getGenericData().definitionPoint) {
11910 auto* dd = new DRW_DimAngular3p();
11911 dim = dd;
11912 dim->type = 5 + 32;
11913 dd->setFirstLine (DRW_Coord(da->getDefinitionPoint1().x, da->getDefinitionPoint1().y, 0.0)); //13
11914 dd->setSecondLine (DRW_Coord(da->getDefinitionPoint2().x, da->getDefinitionPoint2().y, 0.0)); //14
11915 dd->SetVertexPoint(DRW_Coord(da->getDefinitionPoint3().x, da->getDefinitionPoint3().y, 0.0)); //15
11916 dd->setDimPoint (DRW_Coord(da->getDefinitionPoint().x, da->getDefinitionPoint().y, 0.0)); //10
11917 }
11918 else {
11919 auto* dd = new DRW_DimAngular();
11920 dim = dd;
11921 dim->type = 2 + 32;
11922 dd->setFirstLine1(DRW_Coord(da->getDefinitionPoint1().x, da->getDefinitionPoint1().y, 0.0)); //13
11923 dd->setFirstLine2(DRW_Coord(da->getDefinitionPoint2().x, da->getDefinitionPoint2().y, 0.0)); //14
11924 dd->setSecondLine1(DRW_Coord(da->getDefinitionPoint3().x, da->getDefinitionPoint3().y, 0.0)); //15
11925 dd->setDimPoint(DRW_Coord(da->getDefinitionPoint4().x, da->getDefinitionPoint4().y, 0.0)); //16
11926 }
11927 break;
11928 }
11929 case RS2::EntityDimOrdinate: {
11930 auto* da = static_cast<LC_DimOrdinate*>(d);
11931 auto* dd = new DRW_DimOrdinate();
11932 dim = dd;
11933 dd->type = 6 + 32;
11934 auto dimOridinateData = da->getEData();
11935 if (dimOridinateData.ordinateForX) {
11936 dd->type = 6 + 64;
11937 }
11938 dd->setOriginPoint(DRW_Coord(da->getDefinitionPoint().x, da->getDefinitionPoint().y, 0.0));
11939 dd->setSecondLine(DRW_Coord(da->getLeaderEndPoint().x, da->getLeaderEndPoint().y, 0.0));
11940 dd->setFirstLine(DRW_Coord(da->getFeaturePoint().x, da->getFeaturePoint().y, 0.0));
11941 break;
11942 }
11943 case RS2::EntityDimArc: {
11944 auto* da = static_cast<LC_DimArc*>(d);
11945 auto* dd = new DRW_DimArc();
11946 dim = dd;
11947 RS_Vector centre = da->getCenter();
11948 double r = da->getRadius();
11949 double a0 = da->getStartAngle();
11950 double a1 = da->getEndAngle();
11951 double amid = (a0 + a1) / 2.0;
11952 dd->setArcCenter (DRW_Coord(centre.x, centre.y, 0.));
11953 dd->setArcDefPoint(DRW_Coord(centre.x + r * std::cos(amid),
11954 centre.y + r * std::sin(amid), 0.));
11955 dd->setExtLine1 (DRW_Coord(centre.x + r * std::cos(a0),
11956 centre.y + r * std::sin(a0), 0.));
11957 dd->setExtLine2 (DRW_Coord(centre.x + r * std::cos(a1),
11958 centre.y + r * std::sin(a1), 0.));
11959 dd->arcStartAngle = a0;
11960 dd->arcEndAngle = a1;
11961 dd->arcSymbol = da->getArcSymbol();
11962 dd->isPartial = da->getIsPartial();
11963 dd->hasLeader = da->getHasLeader();
11964 if (da->getLeaderPt1().valid)
11965 dd->setLeaderPt1(DRW_Coord(da->getLeaderPt1().x, da->getLeaderPt1().y, 0.));
11966 if (da->getLeaderPt2().valid)
11967 dd->leaderPt2 = DRW_Coord(da->getLeaderPt2().x, da->getLeaderPt2().y, 0.);
11968 break;
11969 }
11970 default: {
11971 //default to DimLinear
11972 auto dl = static_cast<RS_DimLinear*>(d);
11973 auto dd = new DRW_DimLinear();
11974 dim = dd;
11975 dim->type = 0 + 32;
11976 dd->setDef1Point(DRW_Coord(dl->getExtensionPoint1().x, dl->getExtensionPoint1().y, 0.0));
11977 dd->setDef2Point(DRW_Coord(dl->getExtensionPoint2().x, dl->getExtensionPoint2().y, 0.0));
11978 dd->setAngle(RS_Math::rad2deg(dl->getAngle()));
11979 dd->setOblique(dl->getOblique());
11980 break;
11981 }
11982 }
11983 getEntityAttributes(dim, d);
11984 dim->setDefPoint(DRW_Coord(d->getDefinitionPoint().x, d->getDefinitionPoint().y, 0));
11985 dim->setTextPoint(DRW_Coord(d->getMiddleOfText().x, d->getMiddleOfText().y, 0));
11986 dim->setStyle(d->getStyle().toUtf8().data());
11987 dim->setAlign(attachmentPoint);
11988 dim->setTextLineStyle(d->getLineSpacingStyle());
11989 dim->setText (toDxfString(d->getLabel(false)).toUtf8().data());
11990 dim->setTextLineFactor(d->getLineSpacingFactor());
11991 dim->setHDir(d->getHDir());
11992 dim->setFlipArrow1(d->isFlipArrow1());
11993 dim->setFlipArrow2(d->isFlipArrow2());
11994 if (d->hasUserDefinedTextLocation()) {
11995 dim->type = dim->type + 128;
11996 }
11997 if (!blkName.isEmpty()) {
11998 dim->setName(blkName.toStdString());
11999 }
12000 if (m_dwgW) {
12001 if (!m_dwgW->writeDimension(dim))
12002 m_writeFailed = true;
12003 delete dim;
12004 return;
12005 }
12006 LC_DimStyle* override = d->getDimStyleOverride();
12007 if (override != nullptr) {
12008 LC_ExtEntityData extEntityData;
12009 addDimStyleOverrideToExtendedData(&extEntityData, override);
12010 fillEntityExtData(dim->extData, &extEntityData);
12011 }
12012 m_dxfW->writeDimension(dim);
12013 delete dim;
12014}
12015
12016void RS_FilterDXFRW::writeTolerance(LC_Tolerance* t) {
12017 if (t == nullptr)
12018 return;
12019
12020 DRW_Tolerance tol;
12021 getEntityAttributes(&tol, t);
12022 const LC_ToleranceData data = t->getData();
12023 tol.insertionPoint = DRW_Coord(data.insertionPoint.x,
12024 data.insertionPoint.y, 0.0);
12025 tol.xAxisDirectionVector = DRW_Coord(data.directionVector.x,
12026 data.directionVector.y, 0.0);
12027 tol.extPoint = DRW_Coord(0.0, 0.0, 1.0);
12028 tol.text = toDxfString(data.textCode).toUtf8().constData();
12029 const QString style = data.dimStyleName.isEmpty()
12030 ? m_dimStyle
12031 : data.dimStyleName;
12032 tol.dimStyleName = style.toUtf8().constData();
12033
12034 if (m_dwgW) {
12035 if (!m_dwgW->writeTolerance(&tol))
12036 m_writeFailed = true;
12037 return;
12038 }
12039 if (m_dxfW) {
12040 m_dxfW->writeTolerance(&tol);
12041 }
12042}
12043
12044/**
12045 * Writes the given leader entity to the file.
12046 */
12047void RS_FilterDXFRW::writeLeader(const RS_Leader* l) {
12048 if (l->count() <= 0) {
12049 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "dropping leader with no vertices");
12050 return;
12051 }
12052
12053 DRW_Leader leader;
12054 getEntityAttributes(&leader, l);
12055 const QString styleName = l->getData().styleName;
12056 leader.style = (styleName.isEmpty() ? "Standard" : styleName.toUtf8().toStdString());
12057 leader.arrow = l->hasArrowHead();
12058 leader.leadertype = 0;
12059 leader.flag = 3;
12060 leader.hookline = 0;
12061 leader.hookflag = 0;
12062 leader.textheight = 1;
12063 leader.textwidth = 10;
12064
12065 RS_Line* li = nullptr;
12066 for (RS_Entity* v : lc::LC_ContainerTraverser{*l, RS2::ResolveNone}.entities()) {
12067 if (v->rtti() == RS2::EntityLine) {
12068 li = static_cast<RS_Line*>(v);
12069 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
12070 li->getStartpoint().x, li->getStartpoint().y, 0.0));
12071 }
12072 }
12073 if (li) {
12074 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
12075 li->getEndpoint().x, li->getEndpoint().y, 0.0));
12076 }
12077
12078 if (leader.vertexlist.size() < 2) {
12079 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "dropping leader with fewer than 2 vertices");
12080 return;
12081 }
12082 leader.vertnum = static_cast<int>(leader.vertexlist.size());
12083
12084 if (m_dwgW) {
12085 if (!m_dwgW->writeLeader(&leader))
12086 m_writeFailed = true;
12087 return;
12088 }
12089 m_dxfW->writeLeader(&leader);
12090}
12091
12092namespace {
12093
12094// Emit an LC_SplinePoints boundary edge as a degree-2 DRW_Spline. Either
12095// the control net or the fit points are populated depending on the
12096// LC_SplinePointsData mode. Knotslist is left empty; the libdxfrw reader
12097// re-derives a clamped uniform knot vector when consuming this struct
12098// (see drw_entities.cpp parseDwg). See plan §B.1.
12099std::shared_ptr<DRW_Spline>
12100makeDrwSplineFromSplinePoints(const LC_SplinePoints *sp) {
12101 auto drw = std::make_shared<DRW_Spline>();
12102 drw->degree = 2;
12103 // Closed boundary edges set BOTH bit 0 (DXF entity-level "closed")
12104 // AND bit 1 (DWG-boundary / DXF group code 74 "periodic"). The hatch
12105 // boundary read path accepts either bit; this keeps cross-format
12106 // round-trip consistent.
12107 drw->flags = sp->isClosed() ? 0x3 : 0x0;
12108 const auto &d = sp->getData();
12109 if (d.useControlPoints && !d.controlPoints.empty()) {
12110 for (const auto &v : d.controlPoints)
12111 drw->controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
12112 drw->ncontrol = static_cast<std::int32_t>(d.controlPoints.size());
12113 } else {
12114 for (const auto &v : d.splinePoints)
12115 drw->fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
12116 drw->nfit = static_cast<std::int32_t>(d.splinePoints.size());
12117 }
12118 return drw;
12119}
12120
12121// Emit an RS_Spline (general NURBS, degree 1-3, possibly rational) boundary
12122// edge as a DRW_Spline carrying its full data. Mirrors weight into both
12123// drw->weightlist[i] AND drw->controllist[i]->z so both DXF and DWG
12124// consumers can read it correctly. See plan §C.3.
12125std::shared_ptr<DRW_Spline> makeDrwSplineFromRSSpline(const RS_Spline *sp) {
12126 auto drw = std::make_shared<DRW_Spline>();
12127 const auto &sd = sp->getData();
12128 drw->degree = static_cast<std::int32_t>(sd.degree);
12129
12130 const auto cps = sp->getControlPoints();
12131 const auto ws = sp->getWeights();
12132 const bool weightsAlign = (ws.size() == cps.size());
12133 const bool isRational =
12134 weightsAlign && std::any_of(ws.begin(), ws.end(), [](double w) {
12135 return std::abs(w - 1.0) > 1e-9;
12136 });
12137 // Set both DXF entity-level closed (bit 0) and hatch-boundary
12138 // periodic (bit 1) for cross-format consistency. See helper above.
12139 drw->flags = (sp->isClosed() ? 0x3 : 0x0) | (isRational ? 0x4 : 0x0);
12140
12141 for (size_t i = 0; i < cps.size(); ++i) {
12142 const double w = (isRational && i < ws.size()) ? ws[i] : 1.0;
12143 drw->controllist.push_back(
12144 std::make_shared<DRW_Coord>(cps[i].x, cps[i].y, w));
12145 }
12146 drw->ncontrol = static_cast<std::int32_t>(cps.size());
12147 if (isRational) {
12148 drw->weightlist = ws;
12149 }
12150 drw->knotslist = sd.knotslist;
12151 drw->nknots = static_cast<std::int32_t>(sd.knotslist.size());
12152 return drw;
12153}
12154
12155} // namespace
12156
12157/**
12158 * Writes the given hatch entity to the file.
12159 */
12160void RS_FilterDXFRW::writeHatch(RS_Hatch* h) {
12161 // version 12 are inserts of *U blocks
12162 if (m_version == 1009) {
12163 if (m_noNameBlock.contains(h)) {
12164 DRW_Insert in;
12165 getEntityAttributes(&in, h);
12166 in.basePoint.x = in.basePoint.y = 0.0;
12167 in.basePoint.z = 0.0;
12168 in.name = m_noNameBlock.value(h).toUtf8().data();
12169 in.xscale = in.yscale = 1.0;
12170 in.zscale = 1.0;
12171 in.angle = 0.0;
12172 in.colcount = in.rowcount = 1;
12173 in.colspace = in.rowspace = 0.0;
12174 m_dxfW->writeInsert(&in);
12175 }
12176 return;
12177 }
12178
12179 bool writeIt = true;
12180 if (h->countLoops() > 0) {
12181 // check if all of the loops contain entities:
12182 for(RS_Entity* l: lc::LC_ContainerTraverser{*h, RS2::ResolveNone}.entities()){
12183 if (l->isContainer() && !l->getFlag(RS2::FlagTemp)) {
12184 if (l->count() == 0) {
12185 writeIt = false;
12186 }
12187 }
12188 }
12189 }
12190 else {
12191 writeIt = false;
12192 }
12193
12194 if (!writeIt) {
12195 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "RS_FilterDXF::writeHatch: Dropping Hatch");
12196 return;
12197 }
12198
12199 DRW_Hatch ha;
12200 getEntityAttributes(&ha, h);
12201 if (const auto ocs = extractHatchExtrusionSidecar(h)) {
12202 ha.basePoint.z = ocs->elevation;
12203 ha.extPoint = ocs->extrusion;
12204 removeHatchExtrusionSidecar(ha);
12205 }
12206 ha.solid = h->isSolid();
12207 ha.scale = h->getScale();
12208 ha.angle = h->getAngle();
12209 if (ha.solid) {
12210 ha.name = "SOLID";
12211 }
12212 else {
12213 ha.name = h->getPattern().toUtf8().data();
12214 }
12215 ha.loopsnum = h->countLoops();
12216
12217 for (RS_Entity* l = h->firstEntity(RS2::ResolveNone); l; l = h->nextEntity(RS2::ResolveNone)) {
12218 // Write hatch loops:
12219 if (l->isContainer() && !l->getFlag(RS2::FlagTemp)) {
12220 auto loop = static_cast<RS_EntityContainer*>(l);
12221 std::shared_ptr<DRW_HatchLoop> lData = std::make_shared<DRW_HatchLoop>(0);
12222
12223 for (RS_Entity* ed : lc::LC_ContainerTraverser{*loop, RS2::ResolveNone}.entities()) {
12224 // Write hatch loop edges:
12225 if (ed->rtti() == RS2::EntityLine) {
12226 auto* ln = static_cast<RS_Line*>(ed);
12227 std::shared_ptr<DRW_Line> line = std::make_shared<DRW_Line>();
12228 line->basePoint.x = ln->getStartpoint().x;
12229 line->basePoint.y = ln->getStartpoint().y;
12230 line->secPoint.x = ln->getEndpoint().x;
12231 line->secPoint.y = ln->getEndpoint().y;
12232 lData->objlist.push_back(line);
12233 }
12234 else if (ed->rtti() == RS2::EntityArc) {
12235 auto ar = static_cast<RS_Arc*>(ed);
12236 std::shared_ptr<DRW_Arc> arc = std::make_shared<DRW_Arc>();
12237 arc->basePoint.x = ar->getCenter().x;
12238 arc->basePoint.y = ar->getCenter().y;
12239 arc->radious = ar->getRadius();
12240 if (!ar->isReversed()) {
12241 arc->staangle = ar->getAngle1();
12242 arc->endangle = ar->getAngle2();
12243 arc->isccw = true;
12244 }
12245 else {
12246 arc->staangle = 2 * M_PI3.14159265358979323846 - ar->getAngle1();
12247 arc->endangle = 2 * M_PI3.14159265358979323846 - ar->getAngle2();
12248 arc->isccw = false;
12249 }
12250 lData->objlist.push_back(arc);
12251 }
12252 else if (ed->rtti() == RS2::EntityCircle) {
12253 auto ci = static_cast<RS_Circle*>(ed);
12254 std::shared_ptr<DRW_Arc> arc = std::make_shared<DRW_Arc>();
12255 arc->basePoint.x = ci->getCenter().x;
12256 arc->basePoint.y = ci->getCenter().y;
12257 arc->radious = ci->getRadius();
12258 arc->staangle = 0.0;
12259 arc->endangle = 2 * M_PI3.14159265358979323846; //2*M_PI;
12260 arc->isccw = true;
12261 lData->objlist.push_back(arc);
12262 }
12263 else if (ed->rtti() == RS2::EntityEllipse) {
12264 auto el = static_cast<RS_Ellipse*>(ed);
12265 std::shared_ptr<DRW_Ellipse> ell = std::make_shared<DRW_Ellipse>();
12266 ell->basePoint.x = el->getCenter().x;
12267 ell->basePoint.y = el->getCenter().y;
12268 ell->secPoint.x = el->getMajorP().x;
12269 ell->secPoint.y = el->getMajorP().y;
12270 ell->ratio = el->getRatio();
12271 double rot = el->getMajorP().angle();
12272 double startAng = el->getCenter().angleTo(el->getStartpoint()) - rot;
12273 double endAng = el->getCenter().angleTo(el->getEndpoint()) - rot;
12274 if (startAng < 0) {
12275 startAng = M_PI3.14159265358979323846 * 2 + startAng;
12276 }
12277 if (endAng < 0) {
12278 endAng = M_PI3.14159265358979323846 * 2 + endAng;
12279 }
12280 ell->staparam = startAng;
12281 ell->endparam = endAng;
12282 ell->isccw = !el->isReversed();
12283 lData->objlist.push_back(ell);
12284 } else if (ed->rtti() == RS2::EntitySplinePoints) {
12285 lData->objlist.push_back(makeDrwSplineFromSplinePoints(
12286 static_cast<LC_SplinePoints *>(ed)));
12287 } else if (ed->rtti() == RS2::EntitySpline) {
12288 lData->objlist.push_back(
12289 makeDrwSplineFromRSSpline(static_cast<RS_Spline *>(ed)));
12290 }
12291 }
12292 lData->update(); //change to DRW_HatchLoop
12293 ha.appendLoop(lData);
12294 }
12295 }
12296 if (m_dwgW) {
12297 if (!m_dwgW->writeHatch(&ha))
12298 m_writeFailed = true;
12299 return;
12300 }
12301 m_dxfW->writeHatch(&ha);
12302}
12303
12304/**
12305 * Writes the given Solid entity to the file.
12306 */
12307void RS_FilterDXFRW::writeSolid(RS_Solid* s) {
12308 RS_SolidData data;
12309 DRW_Solid solid;
12310 RS_Vector corner;
12311 getEntityAttributes(&solid, s);
12312 corner = s->getCorner(0);
12313 solid.basePoint.x = corner.x;
12314 solid.basePoint.y = corner.y;
12315 corner = s->getCorner(1);
12316 solid.secPoint.x = corner.x;
12317 solid.secPoint.y = corner.y;
12318 corner = s->getCorner(2);
12319 solid.thirdPoint.x = corner.x;
12320 solid.thirdPoint.y = corner.y;
12321 if (s->isTriangle()) {
12322 solid.fourPoint.x = solid.thirdPoint.x;
12323 solid.fourPoint.y = solid.thirdPoint.y;
12324 }
12325 else {
12326 corner = s->getCorner(3);
12327 solid.fourPoint.x = corner.x;
12328 solid.fourPoint.y = corner.y;
12329 }
12330 if (m_dwgW) {
12331 if (!m_dwgW->writeSolid(&solid))
12332 m_writeFailed = true;
12333 return;
12334 }
12335 m_dxfW->writeSolid(&solid);
12336}
12337
12338
12339void RS_FilterDXFRW::writeImage(const RS_Image * i) {
12340 if (i == nullptr)
12341 return;
12342
12343 DRW_Image image;
12344 getEntityAttributes(&image, i);
12345
12346 if (const auto frame = extractImageFrameSidecar(i)) {
12347 image.basePoint.z = frame->insertion.z;
12348 image.secPoint.z = frame->uVector.z;
12349 image.vVector.z = frame->vVector.z;
12350 removeImageFrameSidecar(image);
12351 }
12352
12353 image.basePoint.x = i->getInsertionPoint().x;
12354 image.basePoint.y = i->getInsertionPoint().y;
12355 image.secPoint.x = i->getUVector().x;
12356 image.secPoint.y = i->getUVector().y;
12357 image.vVector.x = i->getVVector().x;
12358 image.vVector.y = i->getVVector().y;
12359 image.sizeu = i->getWidth();
12360 image.sizev = i->getHeight();
12361 image.brightness = i->getBrightness();
12362 image.contrast = i->getContrast();
12363 image.fade = i->getFade();
12364
12365#ifdef DWGSUPPORT1
12366 if (m_dwgW != nullptr) {
12367 const std::string fileName = i->getFile().toUtf8().constData();
12368 if (!m_dwgW->writeImage(&image, &fileName)) {
12369 m_writeFailed = true;
12370 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
12371 "RS_FilterDXFRW::writeImage: DWG writer rejected IMAGE");
12372 }
12373 return;
12374 }
12375#endif
12376 if (m_dxfW == nullptr)
12377 return;
12378
12379 DRW_ImageDef* imgDef = m_dxfW->writeImage(&image, i->getFile().toUtf8().data());
12380 if (imgDef) {
12381 imgDef->loaded = 1;
12382 imgDef->u = i->getData().size.x;
12383 imgDef->v = i->getData().size.y;
12384 imgDef->up = 1;
12385 imgDef->vp = 1;
12386 imgDef->resolution = 0;
12387 }
12388}
12389
12390void RS_FilterDXFRW::writeWipeout(LC_Wipeout *w) {
12391 if (w == nullptr) {
12392 return;
12393 }
12394 DRW_Wipeout img;
12395 getEntityAttributes(&img, w);
12396 const LC_WipeoutData &data = w->getData();
12397 if (data.hasNativeFrame) {
12398 img.basePoint = DRW_Coord(data.insertionPoint.x, data.insertionPoint.y,
12399 data.insertionPoint.z);
12400 img.secPoint = DRW_Coord(data.uPixel.x, data.uPixel.y, data.uPixel.z);
12401 img.vVector = DRW_Coord(data.vPixel.x, data.vPixel.y, data.vPixel.z);
12402 img.sizeu = data.sizeU;
12403 img.sizev = data.sizeV;
12404 img.m_displayProps = data.displayProps;
12405 img.ref = data.imageDefHandle;
12406 img.m_imageDefReactorHandle = data.imageDefReactorHandle;
12407 img.clip = data.clip;
12408 img.brightness = data.brightness;
12409 img.contrast = data.contrast;
12410 img.fade = data.fade;
12411 img.m_clipBoundaryType = data.clipBoundaryType;
12412 img.clipMode = data.clipMode;
12413 img.clipPath.reserve(data.clipPath.size());
12414 for (const RS_Vector &clipPoint : data.clipPath)
12415 img.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
12416 } else {
12417 // User-created WIPEOUTs have no source frame. Emit a documented canonical
12418 // pixel basis whose inverse maps the world polygon without approximation.
12419 img.basePoint = DRW_Coord(0.0, 0.0, 0.0);
12420 img.secPoint = DRW_Coord(1.0, 0.0, 0.0);
12421 img.vVector = DRW_Coord(0.0, 1.0, 0.0);
12422 img.sizeu = 1.0;
12423 img.sizev = 1.0;
12424 img.clip = 1;
12425 img.brightness = 50;
12426 img.contrast = 50;
12427 img.fade = 0;
12428 img.m_clipBoundaryType = 2;
12429 img.clipMode = false;
12430 img.clipPath.reserve(w->getVertices().size());
12431 for (const RS_Vector &vertex : w->getVertices())
12432 img.clipPath.emplace_back(vertex.x - 0.5, vertex.y - 0.5, vertex.z);
12433 }
12434
12435 constexpr std::size_t kMaxDwgWipeoutClipVertices = 100000u;
12436 if (img.clipPath.size() > kMaxDwgWipeoutClipVertices) {
12437 m_writeFailed = true;
12438 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
12439 "RS_FilterDXFRW::writeWipeout: clip path exceeds DWG limit");
12440 return;
12441 }
12442 bool written = false;
12443#ifdef DWGSUPPORT1
12444 if (m_dwgW != nullptr)
12445 written = m_dwgW->writeWipeout(&img);
12446 else
12447#endif
12448 if (m_dxfW != nullptr)
12449 written = m_dxfW->writeWipeout(&img);
12450 if (!written) {
12451 m_writeFailed = true;
12452 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
12453 "RS_FilterDXFRW::writeWipeout: writer rejected entity");
12454 }
12455}
12456
12457/**
12458 * Serialize an LC_MLeader. DWG AC1024+ writes a native text-content
12459 * MULTILEADER subset with context roots, leader lines, and text content.
12460 * DXF keeps the older scalar/context writer. Unsupported complex content
12461 * still falls back to visible LEADER/MTEXT geometry on DWG export.
12462 */
12463void RS_FilterDXFRW::writeMLeader(LC_MLeader *m) {
12464 if (m == nullptr)
12465 return;
12466 DRW_MLeader e;
12467 getEntityAttributes(&e, m);
12468 const auto &d = m->getData();
12469 e.overrideFlags = 0; // Phase 9 doesn't track override bits;
12470 // emit defaults so a re-read picks the
12471 // entity-level values as authoritative.
12472 e.leaderType = d.leaderType;
12473 e.leaderColor = d.leaderColor;
12474 e.landingDistance = d.landingDistance;
12475 e.defaultArrowHeadSize = d.arrowSize;
12476 e.landingEnabled = d.landingEnabled;
12477 e.doglegEnabled = d.doglegEnabled;
12478 e.styleContentType = d.contentType;
12479 e.scaleFactor = d.scaleFactor;
12480 e.classVersion = 2;
12481 e.context.roots.reserve(d.roots.size());
12482 for (const auto &sourceRoot : d.roots) {
12483 DRW_MLeaderRoot root;
12484 root.isContentValid = sourceRoot.connectionPoint.valid;
12485 root.unknown291 = sourceRoot.direction.valid;
12486 root.connectionPoint = DRW_Coord(sourceRoot.connectionPoint.x,
12487 sourceRoot.connectionPoint.y,
12488 sourceRoot.connectionPoint.z);
12489 root.direction = DRW_Coord(sourceRoot.direction.x, sourceRoot.direction.y,
12490 sourceRoot.direction.z);
12491 root.leaderIndex = static_cast<std::int32_t>(e.context.roots.size());
12492 root.landingDistance = sourceRoot.landingDistance;
12493 root.attachmentDirection = static_cast<std::uint16_t>(sourceRoot.attachmentDirection);
12494 root.leaderLines.reserve(sourceRoot.leaderLines.size());
12495 for (const auto &sourceLine : sourceRoot.leaderLines) {
12496 DRW_MLeaderLeaderLine line;
12497 line.leaderLineIndex = sourceLine.leaderLineIndex;
12498 line.leaderType = static_cast<std::uint16_t>(d.leaderType);
12499 line.color = d.leaderColor;
12500 line.lineWeight = e.lWeight;
12501 line.arrowSize = d.arrowSize;
12502 line.overrideFlags = 0;
12503 line.points.reserve(sourceLine.points.size());
12504 for (const RS_Vector &point : sourceLine.points)
12505 line.points.emplace_back(point.x, point.y, point.z);
12506 root.leaderLines.push_back(std::move(line));
12507 }
12508 e.context.roots.push_back(std::move(root));
12509 }
12510 e.context.overallScale = d.scaleFactor;
12511 e.context.contentBasePoint = DRW_Coord(d.contentBasePoint.x,
12512 d.contentBasePoint.y,
12513 d.contentBasePoint.z);
12514 e.context.textHeight = d.textHeight > 0.0 ? d.textHeight : 1.0;
12515 e.context.arrowHeadSize = d.arrowSize;
12516 e.context.landingGap = d.landingDistance;
12517 e.context.hasTextContents = d.hasTextContents;
12518 e.context.textLabel = toDxfString(d.textLabel).toUtf8().data();
12519 e.context.textNormal = DRW_Coord(0.0, 0.0, 1.0);
12520 const RS_Vector textLocation =
12521 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
12522 e.context.textLocation = DRW_Coord(textLocation.x, textLocation.y,
12523 textLocation.z);
12524 e.context.textDirection = DRW_Coord(std::cos(d.textRotation),
12525 std::sin(d.textRotation), 0.0);
12526 e.context.textRotation = d.textRotation;
12527 e.context.boundaryWidth = d.boundaryWidth;
12528 e.context.boundaryHeight = d.boundaryHeight;
12529 e.context.lineSpacingFactor = 1.0;
12530 e.context.lineSpacingStyle = 1;
12531 e.context.textColor = d.textColor;
12532 e.context.alignment = 1;
12533 e.context.flowDirection = 1;
12534 e.context.bgScaleFactor = 1.5;
12535 e.context.basePoint = DRW_Coord(d.basePoint.x, d.basePoint.y, d.basePoint.z);
12536 e.context.baseDirection = DRW_Coord(1.0, 0.0, 0.0);
12537 e.context.baseVertical = DRW_Coord(0.0, 1.0, 0.0);
12538 e.styleHandle.ref = d.dwgStyleHandle;
12539 e.leaderLineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
12540 e.arrowHeadHandle.ref = d.dwgArrowHeadHandle;
12541 e.styleTextStyleHandle.ref = d.dwgTextStyleHandle;
12542 e.styleBlockHandle.ref = d.dwgBlockHandle;
12543 e.context.textStyleHandle.ref = d.dwgTextStyleHandle;
12544 e.context.blockTableRecordHandle.ref = d.dwgBlockHandle;
12545 for (DRW_MLeaderRoot &root : e.context.roots) {
12546 for (DRW_MLeaderLeaderLine &line : root.leaderLines) {
12547 line.lineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
12548 line.arrowHandle.ref = d.dwgArrowHeadHandle;
12549 }
12550 }
12551 if (m_dwgW) {
12552 if (m_version >= 1024 && d.hasTextContents && !d.textLabel.isEmpty()
12553 && m_dwgW->writeMLeader(&e)) {
12554 return;
12555 }
12556 if (d.hasBlockContents) {
12557 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12558 "MLEADER block content is exported as leader fallback geometry; native block-content DWG writing is not implemented");
12559 }
12560 bool wroteGeometry = false;
12561 for (const auto &root : d.roots) {
12562 for (const auto &line : root.leaderLines) {
12563 if (line.points.size() < 2)
12564 continue;
12565 DRW_Leader leader;
12566 getEntityAttributes(&leader, m);
12567 leader.style = d.styleName.isEmpty() ? "Standard" : d.styleName.toStdString();
12568 leader.arrow = true;
12569 leader.leadertype = d.leaderType == 2 ? 1 : 0;
12570 leader.flag = 3;
12571 leader.hookline = 0;
12572 leader.hookflag = 0;
12573 leader.textheight = d.textHeight > 0.0 ? d.textHeight : 1.0;
12574 leader.textwidth = d.boundaryWidth > 0.0 ? d.boundaryWidth : 10.0;
12575 leader.vertnum = static_cast<int>(line.points.size());
12576 leader.vertexlist.reserve(line.points.size());
12577 for (const RS_Vector &point : line.points) {
12578 leader.vertexlist.push_back(
12579 std::make_shared<DRW_Coord>(point.x, point.y, point.z));
12580 }
12581 if (!m_dwgW->writeLeader(&leader))
12582 m_writeFailed = true;
12583 else
12584 wroteGeometry = true;
12585 }
12586 }
12587 if (d.hasTextContents && !d.textLabel.isEmpty()) {
12588 DRW_MText text;
12589 getEntityAttributes(&text, m);
12590 const RS_Vector insertion =
12591 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
12592 text.basePoint.x = insertion.x;
12593 text.basePoint.y = insertion.y;
12594 text.basePoint.z = insertion.z;
12595 text.height = d.textHeight > 0.0 ? d.textHeight : 1.0;
12596 text.angle = d.textRotation * 180.0 / M_PI3.14159265358979323846;
12597 text.style =
12598 d.textStyleName.isEmpty() ? "Standard" : d.textStyleName.toStdString();
12599 text.text = toDxfString(d.textLabel).toUtf8().data();
12600 text.widthscale = d.boundaryWidth;
12601 text.interlin = 1.0;
12602 if (!m_dwgW->writeMText(&text))
12603 m_writeFailed = true;
12604 else
12605 wroteGeometry = true;
12606 }
12607 if (!wroteGeometry) {
12608 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12609 "dropping MLEADER with no writable leader/text geometry");
12610 }
12611 return;
12612 }
12613 m_dxfW->writeMultiLeader(&e);
12614}
12615
12616/*void RS_FilterDXFRW::writeEntityContainer(DL_WriterA& dw, RS_EntityContainer* con,
12617 const DRW_Entity& attrib) {
12618 QString blkName;
12619 blkName = "__CE";
12620
12621 // Creating an unique ID from the element ID
12622 int tmp, c=1; // tmp = temporary var c = counter var
12623 tmp = con->getId();
12624
12625 while (true) {
12626 tmp = tmp/c;
12627 blkName.append((char) tmp %10 + 48);
12628 c *= 10;
12629 if (tmp < 10) {
12630 break;
12631 }
12632 }
12633
12634 //Block definition
12635 dw.sectionTables();
12636 dxf.writeBlockRecord(dw);
12637 dw.dxfString( 0, "BLOCK_RECORD");
12638
12639 dw.handle();
12640 dw.dxfHex(330, 1);
12641 dw.dxfString(100, "AcDbSymbolTableRecord");
12642 dw.dxfString(100, "AcDbBlockTableRecord");
12643 dw.dxfString( 2, blkName.toLatin1().data());
12644 dw.dxfHex(340, 0);
12645 dw.dxfString(0, "ENDTAB");
12646
12647 //Block creation
12648 RS_BlockData blkdata(blkName, RS_Vector(0,0), false);
12649
12650 RS_Block* blk = new RS_Block(graphic, blkdata);
12651
12652 for (RS_Entity* e1 = con->firstEntity(); e1 ;
12653 e1 = con->nextEntity() ) {
12654 blk->addEntity(e1);
12655 }
12656 writeBlock(dw, blk);
12657 //delete e1;
12658}*/
12659
12660/**
12661 * Writes the atomic entities of the given container to the file.
12662 */
12663/*void RS_FilterDXFRW::writeAtomicEntities(DL_WriterA& dw, RS_EntityContainer* c,
12664 const DRW_Entity& attrib,
12665 RS2::ResolveLevel level) {
12666
12667 for (RS_Entity* e=c->firstEntity(level);
12668 e;
12669 e=c->nextEntity(level)) {
12670
12671 writeEntity(dw, e, attrib);
12672 }
12673}*/
12674
12675/**
12676 * Sets the entities attributes according to the attributes
12677 * that come from a DXF file.
12678 */
12679void RS_FilterDXFRW::setEntityAttributes(RS_Entity* entity, const DRW_Entity* attrib) {
12680 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes");
12681
12682 RS_Pen pen;
12683 pen.setColor(Qt::black);
12684 pen.setLineType(RS2::SolidLine);
12685 // A layer name is an identifier, NOT MTEXT content: do NOT run it through
12686 // toNativeString (which caret-decodes ^X, expands \P/%%c, strips font
12687 // tags). Decoding here corrupts names that legitimately contain '^' and,
12688 // worse, desyncs the entity from its layer record — addLayer() below
12689 // stores the RAW name (lay.name = attrib->layer), so a decoded layName
12690 // would never match it. Use the verbatim UTF-8 name on both paths.
12691 QString layName = QString::fromUtf8(attrib->layer.c_str());
12692 entity->setLayer(importLayerForEntity(layName, attrib->layer));
12693
12694 // Color:
12695 RS_Color col;
12696 if (attrib->color24 >= 0) {
12697 col = RS_Color(attrib->color24 >> 16, attrib->color24 >> 8 & 0xFF,
12698 attrib->color24 & 0xFF);
12699 }
12700 else {
12701 col = numberToColor(attrib->color);
12702 }
12703 if (!attrib->colorName.empty()) {
12704 col.setColorName(QString::fromUtf8(attrib->colorName.c_str()));
12705 }
12706 pen.setColor(col);
12707
12708 // Linetype:
12709 pen.setLineType(nameToLineType(QString::fromUtf8(attrib->lineType.c_str())));
12710
12711 // Width:
12712 pen.setWidth(numberToWidth(attrib->lWeight));
12713
12714 // Transparency (DXF code 440): alpha_raw packs alpha_type in high byte
12715 // (0/1 = ByLayer/ByBlock, inherits parent), 3 = explicit alpha (low
12716 // byte 0..255). RS_Pen.alpha is a float 0..1 where 1.0 is opaque.
12717 // libreDWG common_entity_data.spec:432-446 documents the encoding.
12718 // Only override the pen default when alpha_type == 3.
12719 if (attrib->transparency != DRW::Opaque) {
12720 const unsigned int rawAlpha =
12721 static_cast<unsigned int>(attrib->transparency);
12722 const unsigned int alphaType = (rawAlpha >> 24) & 0xFF;
12723 const unsigned int alphaByte = rawAlpha & 0xFF;
12724 if (alphaType == 3) {
12725 pen.setAlpha(static_cast<float>(alphaByte) / 255.0f);
12726 }
12727 }
12728
12729 entity->setPen(pen);
12730
12731 // Passive metadata sidecars — DXF/DWG fields that don't affect
12732 // rendering or equality but must round-trip on save. Skip the
12733 // default sentinels so unset entities stay unchanged.
12734 if (attrib->material != DRW::MaterialByLayer)
12735 entity->setMaterialHandle(attrib->material);
12736 if (attrib->plotStyle != DRW::DefaultPlotStyle)
12737 entity->setPlotStyleHandle(static_cast<quint32>(attrib->plotStyle));
12738 if (attrib->shadow != DRW::CastAndReceieveShadows)
12739 entity->setShadowMode(static_cast<int>(attrib->shadow));
12740 // Visual-style handles (DWG R2010+ only; libdxfrw's DXF reader has no
12741 // codes for these, so they round-trip from DWG only).
12742 entity->setVisualStyleHandles(attrib->fullVisualStyleHandle,
12743 attrib->faceVisualStyleHandle,
12744 attrib->edgeVisualStyleHandle);
12745 // Visibility (DXF code 60 / DWG invisible bit) — import side of the
12746 // two-way fix; getEntityAttributes now exports it. (write-review P3 #11)
12747 entity->setVisible(attrib->visible);
12748
12749 // Source DXF/DWG handle (code 5). Lets the writer build an old->new
12750 // handle map for refs that target model entities (F3a; GROUP code-340).
12751 if (attrib->handle != DRW::NoHandle)
12752 entity->setSourceHandle(attrib->handle);
12753
12754 // Preserve any XDATA / EED that came in with the entity. Stored
12755 // verbatim on RS_Entity so a later getEntityAttributes() can spit
12756 // it back out unchanged. The dimension-style override path reads
12757 // the same source independently and is unaffected.
12758 if (!attrib->extData.empty()) {
12759 entity->setDrwExtData(attrib->extData);
12760 }
12761
12762 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes: OK");
12763}
12764
12765/**
12766 * Gets the entities attributes as a DL_Attributes object.
12767 */
12768void RS_FilterDXFRW::getEntityAttributes(DRW_Entity* ent, const RS_Entity* entity) {
12769 //DRW_Entity RS_FilterDXFRW::getEntityAttributes(RS_Entity* /*entity*/) {
12770
12771 // F3: DXF export seeds ent->handle with the entity's SOURCE handle as a
12772 // source->minted remap key (used to resolve GROUP 340 members). DWG export
12773 // must leave the handle unset so the DWG writer can allocate from its single
12774 // table/entity/object namespace; otherwise imported source handles can
12775 // collide with deferred table records already reserved by this writer.
12776 ent->handle = m_dwgW ? 0 : entity->sourceHandle();
12777
12778 // Layer:
12779 RS_Layer* layer = entity->getLayer();
12780 QString layerName;
12781 if (layer) {
12782 layerName = layer->getName();
12783 }
12784 else {
12785 layerName = "0";
12786 }
12787
12788 RS_Pen pen = entity->getPen(false);
12789
12790 // Color:
12791 int exact_rgb;
12792 int color = colorToNumber(pen.getColor(), &exact_rgb);
12793 //printf("Color is: %s -> %d\n", pen.getColor().name().toLatin1().data(), color);
12794
12795 // Linetype:
12796 QString lineType = lineTypeToName(pen.getLineType());
12797
12798 // Width:
12799 DRW_LW_Conv::lineWidth width = widthToNumber(pen.getWidth());
12800
12801 ent->layer = toDxfString(layerName).toUtf8().data();
12802 ent->color = color;
12803 ent->color24 = exact_rgb;
12804 if (pen.getColor().hasColorName()) {
12805 ent->colorName = pen.getColor().colorName().toUtf8().data();
12806 }
12807 ent->lWeight = width;
12808 ent->lineType = lineType.toUtf8().data();
12809
12810 // Transparency export: encode pen alpha < 1.0 as DXF code 440
12811 // (alpha_raw). High byte 0x03 = "explicit alpha", low byte = 0..255.
12812 // Mirrors the import decoder above.
12813 if (pen.getAlpha() < 1.0f) {
12814 int alphaByte = static_cast<int>(pen.getAlpha() * 255.0f + 0.5f);
12815 ent->transparency = (0x03 << 24) | (alphaByte & 0xFF);
12816 }
12817
12818 // Visibility (DXF code 60 / DWG invisible bit). The writer honors
12819 // ent->visible (writeEntity emits 60 when invisible; encodeDwgCommon sets
12820 // the DWG bit), but this boundary never populated it, so an invisible
12821 // entity round-tripped as visible. (write-review P3 #11)
12822 ent->visible = entity->isVisible();
12823
12824 // Passive metadata sidecars — emit only when overridden.
12825 if (entity->materialHandle() != 0)
12826 ent->material = entity->materialHandle();
12827 if (entity->plotStyleHandle() != 0)
12828 ent->plotStyle = static_cast<int>(entity->plotStyleHandle());
12829 if (entity->shadowMode() != 0)
12830 ent->shadow = static_cast<DRW::ShadowMode>(entity->shadowMode());
12831
12832 // Re-emit any XDATA / EED that was attached on import. Skipped if
12833 // the dimension-export path (or any other caller) already populated
12834 // ent->extData with its own structured override.
12835 if (entity->hasDrwExtData() && ent->extData.empty()) {
12836 ent->extData = entity->getDrwExtData();
12837 }
12838}
12839
12840/**
12841 * @return Pen with the same attributes as 'attrib'.
12842 */
12843RS_Pen RS_FilterDXFRW::attributesToPen(const DRW_Layer* att) const {
12844 RS_Color col;
12845 if (att->color24 >= 0) {
12846 col = RS_Color(att->color24 >> 16, att->color24 >> 8 & 0xFF, att->color24 & 0xFF);
12847 }
12848 else {
12849 col = numberToColor(att->color);
12850 }
12851 if (!att->colorName.empty()) {
12852 col.setColorName(QString::fromUtf8(att->colorName.c_str()));
12853 }
12854
12855 RS_Pen pen(col, numberToWidth(att->lWeight), nameToLineType(QString::fromUtf8(att->lineType.c_str())));
12856 return pen;
12857}
12858
12859/**
12860 * Converts a color index (num) into a RS_Color object.
12861 * Please refer to the dxflib documentation for details.
12862 *
12863 * @param num Color number.
12864 */
12865RS_Color RS_FilterDXFRW::numberToColor(int num) {
12866 if (num == 0) {
12867 return RS_Color(RS2::FlagByBlock);
12868 } else if (num==256) {
12869 return RS_Color(RS2::FlagByLayer);
12870 } else if (num<=255 && num>=0) {
12871 return RS_Color(DRW::dxfColors[num][0],
12872 DRW::dxfColors[num][1],
12873 DRW::dxfColors[num][2]);
12874 } else {
12875 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "RS_FilterDXF::numberToColor: Invalid color number given.");
12876 return RS_Color(RS2::FlagByLayer);
12877 }
12878
12879 return RS_Color();
12880}
12881
12882/**
12883 * Converts a color into a color number in the DXF palette.
12884 * The color that fits best is chosen.
12885 */
12886int RS_FilterDXFRW::colorToNumber(const RS_Color& col, int* rgb) {
12887 //printf("Searching color for %s\n", col.name().toLatin1().data());
12888 *rgb = -1;
12889 // Special color BYBLOCK:
12890 if (col.getFlag(RS2::FlagByBlock)) {
12891 return 0;
12892 }
12893 // Special color BYLAYER
12894 else if (col.getFlag(RS2::FlagByLayer)) {
12895 return 256;
12896 }
12897 // Special color black is not in the table but white represents both
12898 // black and white
12899 else {
12900 int red = col.red();
12901 int green = col.green();
12902 int blue = col.blue();
12903 if (red == 0 && green == 0 && blue == 0) {
12904 return 7;
12905 }
12906 // All other colors
12907 else {
12908 int num = 0;
12909 int diff = 255 * 3; // smallest difference to a color in the table found so far
12910
12911 // Run through the whole table and compare
12912 for (int i = 1; i <= 255; i++) {
12913 int d = abs(red-DRW::dxfColors[i][0])
12914 + abs(green-DRW::dxfColors[i][1])
12915 + abs(blue-DRW::dxfColors[i][2]);
12916
12917 if (d < diff) {
12918 /*
12919 printf("color %f,%f,%f is closer\n",
12920 dxfColors[i][0],
12921 dxfColors[i][1],
12922 dxfColors[i][2]);
12923 */
12924 diff = d;
12925 num = i;
12926 if (d == 0) {
12927 break;
12928 }
12929 }
12930 }
12931 //printf(" Found: %d, diff: %d\n", num, diff);
12932 if (diff != 0) {
12933 *rgb = 0;
12934 *rgb = red << 16 | green << 8 | blue;
12935 }
12936 return num;
12937}
12938 }
12939}
12940
12941void RS_FilterDXFRW::add3dFace(const DRW_3Dface& data) {
12942 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::add3dFace");
12943 RS_PolylineData d(RS_Vector(false),
12944 RS_Vector(false),
12945 !data.invisibleflag);
12946 auto* polyline = new RS_Polyline(m_currentContainer, d);
12947 setEntityAttributes(polyline, &data);
12948 RS_Vector v1(data.basePoint.x, data.basePoint.y);
12949 RS_Vector v2(data.secPoint.x, data.secPoint.y);
12950 RS_Vector v3(data.thirdPoint.x, data.thirdPoint.y);
12951 RS_Vector v4(data.fourPoint.x, data.fourPoint.y);
12952
12953 polyline->addVertex(v1, 0.0);
12954 polyline->addVertex(v2, 0.0);
12955 polyline->addVertex(v3, 0.0);
12956 polyline->addVertex(v4, 0.0);
12957
12958 // F2 sidecar: the RS_Polyline drops Z + the invisible-edge flags, so store
12959 // all 4 corners with Z and the flag (code 70) for native 3DFACE rebuild.
12960 std::vector<std::shared_ptr<DRW_Variant>> payload;
12961 payload.push_back(std::make_shared<DRW_Variant>(
12962 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
12963 payload.push_back(std::make_shared<DRW_Variant>(
12964 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
12965 payload.push_back(std::make_shared<DRW_Variant>(
12966 1012, DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
12967 payload.push_back(std::make_shared<DRW_Variant>(
12968 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
12969 payload.push_back(std::make_shared<DRW_Variant>(
12970 1070, std::int32_t{data.invisibleflag}));
12971 appendTypeSidecar(polyline, k3dFaceMarker, std::move(payload));
12972
12973 m_currentContainer->addEntity(polyline);
12974}
12975
12976void RS_FilterDXFRW::addMesh(const DRW_Mesh& data) {
12977 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMesh: %zu vertices, %zu faces",
12978 data.vertices.size(), data.faces.size());
12979 // Render the base-cage faces as closed polylines (LibreCAD is 2D; Z dropped,
12980 // matching add3dFace). Each face is a list of vertex indices into vertices[].
12981 for (const auto& face : data.faces) {
12982 if (face.size() < 2)
12983 continue;
12984 RS_PolylineData d(RS_Vector(false), RS_Vector(false), /*closed=*/true);
12985 auto *polyline = new RS_Polyline(m_currentContainer, d);
12986 setEntityAttributes(polyline, &data);
12987 bool any = false;
12988 for (std::int32_t idx : face) {
12989 if (idx < 0 || static_cast<size_t>(idx) >= data.vertices.size())
12990 continue;
12991 const DRW_Coord& v = data.vertices[static_cast<size_t>(idx)];
12992 polyline->addVertex(RS_Vector(v.x, v.y), 0.0);
12993 any = true;
12994 }
12995 if (any)
12996 m_currentContainer->addEntity(polyline);
12997 else
12998 delete polyline;
12999 }
13000}
13001
13002void RS_FilterDXFRW::addComment(const char*) {
13003 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addComment(const char*) not yet implemented.");
13004}
13005
13006void RS_FilterDXFRW::addPlotSettings(const DRW_PlotSettings* data) {
13007 LC_PlotSettings* ps = m_graphic->getPlotSettings();
13008 // fixme - review and rework this. It should not be stored there from DXF point of view!
13009 // may be use current style name instead of view name..?
13010 ps->setPagesNum(QString::fromStdString(data->plotViewName));
13011 ps->setMarginsInMm(data->marginLeft, data->marginTop, data->marginRight, data->marginBottom);
13012
13013 ps->setPaperWidthMm(data->paperWidth);
13014 ps->setPaperHeightMm(data->paperHeight);
13015 ps->setCustomPrintScaleDrawingUnitsDenominator(data->drawingUnits);
13016 ps->setCustomPrintScalePaperUnitsNumerator(data->realWorldUnits);
13017 ps->setOriginOffsetXMm(data->paperImageOriginX);
13018 ps->setOriginOffsetYMm(data->paperImageOriginY);
13019 ps->setPlotWindowLowerLeftX(data->windowMinX);
13020 ps->setPlotWindowLowerLeftY(data->windowMinY);
13021 ps->setPlotWindowUpperRightX(data->windowMaxX);
13022 ps->setPlotWindowUpperRightY(data->windowMaxY);
13023 ps->setPlotType(data->plotType);
13024 ps->setPlotLayoutFlag(data->plotLayoutFlags);
13025 ps->setPlotPaperUnits(data->paperUnits);
13026 ps->setPlotRotation(data->plotRotation);
13027 ps->setStandardScaleType(data->scaleType);
13028 ps->setShadePlotMode(data->shadePlotMode);
13029 ps->setShadePlotResolutionMode(data->shadePlotResLevel);
13030 ps->setShadePlotCustomDpi(data->shadePlotCustomDPI);
13031
13032 ps->setStandardScaleFactor(data->scaleFactor);
13033 ps->setPaperImageOriginX(data->paperImageOriginX);
13034 ps->setPaperImageOriginY(data->paperImageOriginY);
13035
13036 ps->setPaperSizeName(QString::fromStdString(data->paperSize));
13037 ps->setCurrentStyleName(QString::fromStdString(data->currentStyleSheet));
13038}
13039
13040/**
13041 * Converts a line type name (e.g. "CONTINUOUS") into a RS2::LineType
13042 * object.
13043 */
13044RS2::LineType RS_FilterDXFRW::nameToLineType(const QString& name) {
13045
13046 QString uName = name.toUpper();
13047
13048 // Standard linetypes for QCad II / AutoCAD
13049 if (uName.isEmpty() || uName == "BYLAYER") {
13050 return RS2::LineByLayer;
13051 }
13052 if (uName == "BYBLOCK") {
13053 return RS2::LineByBlock;
13054 }
13055 if (uName == "CONTINUOUS" || uName == "ACAD_ISO01W100") {
13056 return RS2::SolidLine;
13057 }
13058 if (uName == "ACAD_ISO07W100" || uName == "DOT") {
13059 return RS2::DotLine;
13060 }
13061 if (uName == "DOTTINY") {
13062 return RS2::DotLineTiny;
13063 }
13064 if (uName == "DOT2") {
13065 return RS2::DotLine2;
13066 }
13067 if (uName == "DOTX2") {
13068 return RS2::DotLineX2;
13069 }
13070 if (uName == "ACAD_ISO02W100" || uName == "ACAD_ISO03W100" || uName == "DASHED" || uName == "HIDDEN") {
13071 return RS2::DashLine;
13072 }
13073 if (uName == "DASHEDTINY" || uName == "HIDDEN2") {
13074 return RS2::DashLineTiny;
13075 }
13076 if (uName == "DASHED2" || uName == "HIDDEN2") {
13077 return RS2::DashLine2;
13078 }
13079 if (uName == "DASHEDX2" || uName == "HIDDENX2") {
13080 return RS2::DashLineX2;
13081 }
13082 if (uName == "ACAD_ISO10W100" || uName == "DASHDOT") {
13083 return RS2::DashDotLine;
13084 }
13085 if (uName == "DASHDOTTINY") {
13086 return RS2::DashDotLineTiny;
13087 }
13088 if (uName == "DASHDOT2") {
13089 return RS2::DashDotLine2;
13090 }
13091 if (uName == "ACAD_ISO04W100" || uName == "DASHDOTX2") {
13092 return RS2::DashDotLineX2;
13093 }
13094 if (uName == "ACAD_ISO12W100" || uName == "DIVIDE") {
13095 return RS2::DivideLine;
13096 }
13097 if (uName == "DIVIDETINY") {
13098 return RS2::DivideLineTiny;
13099 }
13100 if (uName == "DIVIDE2") {
13101 return RS2::DivideLine2;
13102 }
13103 if (uName == "ACAD_ISO05W100" || uName == "DIVIDEX2") {
13104 return RS2::DivideLineX2;
13105 }
13106 if (uName == "CENTER") {
13107 return RS2::CenterLine;
13108 }
13109 if (uName == "CENTERTINY") {
13110 return RS2::CenterLineTiny;
13111 }
13112 if (uName == "CENTER2") {
13113 return RS2::CenterLine2;
13114 }
13115 if (uName == "CENTERX2") {
13116 return RS2::CenterLineX2;
13117 }
13118 if (uName == "BORDER") {
13119 return RS2::BorderLine;
13120 }
13121 if (uName == "BORDERTINY") {
13122 return RS2::BorderLineTiny;
13123 }
13124 if (uName == "BORDER2") {
13125 return RS2::BorderLine2;
13126 }
13127 if (uName == "BORDERX2") {
13128 return RS2::BorderLineX2;
13129 }
13130
13131 return RS2::SolidLine;
13132}
13133
13134/**
13135 * Converts a RS_LineType into a name for a line type.
13136 */
13137QString RS_FilterDXFRW::lineTypeToName(RS2::LineType lineType) {
13138 // Standard linetypes for QCad II / AutoCAD
13139 switch (lineType) {
13140 case RS2::SolidLine:
13141 return "CONTINUOUS";
13142 case RS2::DotLine:
13143 return "DOT";
13144 case RS2::DotLineTiny:
13145 return "DOTTINY";
13146 case RS2::DotLine2:
13147 return "DOT2";
13148 case RS2::DotLineX2:
13149 return "DOTX2";
13150 case RS2::DashLine:
13151 return "DASHED";
13152 case RS2::DashLineTiny:
13153 return "DASHEDTINY";
13154 case RS2::DashLine2:
13155 return "DASHED2";
13156 case RS2::DashLineX2:
13157 return "DASHEDX2";
13158 case RS2::DashDotLine:
13159 return "DASHDOT";
13160 case RS2::DashDotLineTiny:
13161 return "DASHDOTTINY";
13162 case RS2::DashDotLine2:
13163 return "DASHDOT2";
13164 case RS2::DashDotLineX2:
13165 return "DASHDOTX2";
13166 case RS2::DivideLine:
13167 return "DIVIDE";
13168 case RS2::DivideLineTiny:
13169 return "DIVIDETINY";
13170 case RS2::DivideLine2:
13171 return "DIVIDE2";
13172 case RS2::DivideLineX2:
13173 return "DIVIDEX2";
13174 case RS2::CenterLine:
13175 return "CENTER";
13176 case RS2::CenterLineTiny:
13177 return "CENTERTINY";
13178 case RS2::CenterLine2:
13179 return "CENTER2";
13180 case RS2::CenterLineX2:
13181 return "CENTERX2";
13182 case RS2::BorderLine:
13183 return "BORDER";
13184 case RS2::BorderLineTiny:
13185 return "BORDERTINY";
13186 case RS2::BorderLine2:
13187 return "BORDER2";
13188 case RS2::BorderLineX2:
13189 return "BORDERX2";
13190 case RS2::LineByLayer:
13191 return "ByLayer";
13192 case RS2::LineByBlock:
13193 return "ByBlock";
13194 default:
13195 break;
13196 }
13197 return "CONTINUOUS";
13198}
13199
13200/**
13201 * Converts a RS_LineType into a name for a line type.
13202 */
13203/*QString RS_FilterDXFRW::lineTypeToDescription(RS2::LineType lineType) {
13204
13205 // Standard linetypes for QCad II / AutoCAD
13206 switch (lineType) {
13207 case RS2::SolidLine:
13208 return "Solid line";
13209 case RS2::DotLine:
13210 return "ISO Dashed __ __ __ __ __ __ __ __ __ __ _";
13211 case RS2::DashLine:
13212 return "ISO Dashed with Distance __ __ __ _";
13213 case RS2::DashDotLine:
13214 return "ISO Long Dashed Dotted ____ . ____ . __";
13215 case RS2::DashDotDotLine:
13216 return "ISO Long Dashed Double Dotted ____ .. __";
13217 case RS2::LineByLayer:
13218 return "";
13219 case RS2::LineByBlock:
13220 return "";
13221 default:
13222 break;
13223 }
13224
13225 return "CONTINUOUS";
13226}*/
13227
13228/**
13229 * Converts a DRW_LW_Conv::lineWidth into a RS2::LineWidth.
13230 */
13231RS2::LineWidth RS_FilterDXFRW::numberToWidth(DRW_LW_Conv::lineWidth lw) {
13232 switch (lw) {
13233 case DRW_LW_Conv::widthByLayer:
13234 return RS2::WidthByLayer;
13235 case DRW_LW_Conv::widthByBlock:
13236 return RS2::WidthByBlock;
13237 case DRW_LW_Conv::widthDefault:
13238 return RS2::WidthDefault;
13239 case DRW_LW_Conv::width00:
13240 return RS2::Width00;
13241 case DRW_LW_Conv::width01:
13242 return RS2::Width01;
13243 case DRW_LW_Conv::width02:
13244 return RS2::Width02;
13245 case DRW_LW_Conv::width03:
13246 return RS2::Width03;
13247 case DRW_LW_Conv::width04:
13248 return RS2::Width04;
13249 case DRW_LW_Conv::width05:
13250 return RS2::Width05;
13251 case DRW_LW_Conv::width06:
13252 return RS2::Width06;
13253 case DRW_LW_Conv::width07:
13254 return RS2::Width07;
13255 case DRW_LW_Conv::width08:
13256 return RS2::Width08;
13257 case DRW_LW_Conv::width09:
13258 return RS2::Width09;
13259 case DRW_LW_Conv::width10:
13260 return RS2::Width10;
13261 case DRW_LW_Conv::width11:
13262 return RS2::Width11;
13263 case DRW_LW_Conv::width12:
13264 return RS2::Width12;
13265 case DRW_LW_Conv::width13:
13266 return RS2::Width13;
13267 case DRW_LW_Conv::width14:
13268 return RS2::Width14;
13269 case DRW_LW_Conv::width15:
13270 return RS2::Width15;
13271 case DRW_LW_Conv::width16:
13272 return RS2::Width16;
13273 case DRW_LW_Conv::width17:
13274 return RS2::Width17;
13275 case DRW_LW_Conv::width18:
13276 return RS2::Width18;
13277 case DRW_LW_Conv::width19:
13278 return RS2::Width19;
13279 case DRW_LW_Conv::width20:
13280 return RS2::Width20;
13281 case DRW_LW_Conv::width21:
13282 return RS2::Width21;
13283 case DRW_LW_Conv::width22:
13284 return RS2::Width22;
13285 case DRW_LW_Conv::width23:
13286 return RS2::Width23;
13287 default:
13288 break;
13289 }
13290 return RS2::WidthDefault;
13291}
13292
13293/**
13294 * Converts a RS2::LineWidth into an DRW_LW_Conv::lineWidth.
13295 */
13296DRW_LW_Conv::lineWidth RS_FilterDXFRW::widthToNumber(RS2::LineWidth width) {
13297 switch (width) {
13298 case RS2::WidthByLayer:
13299 return DRW_LW_Conv::widthByLayer;
13300 case RS2::WidthByBlock:
13301 return DRW_LW_Conv::widthByBlock;
13302 case RS2::WidthDefault:
13303 return DRW_LW_Conv::widthDefault;
13304 case RS2::Width00:
13305 return DRW_LW_Conv::width00;
13306 case RS2::Width01:
13307 return DRW_LW_Conv::width01;
13308 case RS2::Width02:
13309 return DRW_LW_Conv::width02;
13310 case RS2::Width03:
13311 return DRW_LW_Conv::width03;
13312 case RS2::Width04:
13313 return DRW_LW_Conv::width04;
13314 case RS2::Width05:
13315 return DRW_LW_Conv::width05;
13316 case RS2::Width06:
13317 return DRW_LW_Conv::width06;
13318 case RS2::Width07:
13319 return DRW_LW_Conv::width07;
13320 case RS2::Width08:
13321 return DRW_LW_Conv::width08;
13322 case RS2::Width09:
13323 return DRW_LW_Conv::width09;
13324 case RS2::Width10:
13325 return DRW_LW_Conv::width10;
13326 case RS2::Width11:
13327 return DRW_LW_Conv::width11;
13328 case RS2::Width12:
13329 return DRW_LW_Conv::width12;
13330 case RS2::Width13:
13331 return DRW_LW_Conv::width13;
13332 case RS2::Width14:
13333 return DRW_LW_Conv::width14;
13334 case RS2::Width15:
13335 return DRW_LW_Conv::width15;
13336 case RS2::Width16:
13337 return DRW_LW_Conv::width16;
13338 case RS2::Width17:
13339 return DRW_LW_Conv::width17;
13340 case RS2::Width18:
13341 return DRW_LW_Conv::width18;
13342 case RS2::Width19:
13343 return DRW_LW_Conv::width19;
13344 case RS2::Width20:
13345 return DRW_LW_Conv::width20;
13346 case RS2::Width21:
13347 return DRW_LW_Conv::width21;
13348 case RS2::Width22:
13349 return DRW_LW_Conv::width22;
13350 case RS2::Width23:
13351 return DRW_LW_Conv::width23;
13352 default:
13353 break;
13354 }
13355 return DRW_LW_Conv::widthDefault;
13356}
13357
13358/**
13359 * Converts a native unicode string into a DXF encoded string.
13360 *
13361 * DXF endoding includes the following special sequences:
13362 * - %%%c for a diameter sign
13363 * - %%%d for a degree sign
13364 * - %%%p for a plus/minus sign
13365 */
13366QString RS_FilterDXFRW::toDxfString(const QString& str) {
13367 QString res;
13368 res.reserve(str.length() + 16);
13369
13370 for (const QChar& qchar : str) {
13371 switch (qchar.unicode()) {
13372 case 0x0A:
13373 res.append(uR"(\P)");
13374 break;
13375 case 0x2205:
13376 case 0x2300:
13377 res.append(u"%%C");
13378 break;
13379 case 0x00B0:
13380 res.append(u"%%D");
13381 break;
13382 case 0x00B1:
13383 res.append(u"%%P");
13384 break;
13385 default:
13386 res.append(qchar);
13387 break;
13388 }
13389 }
13390 return res;
13391}
13392
13393
13394/**
13395 * Converts a DXF encoded string into a native Unicode string.
13396 */
13397QString RS_FilterDXFRW::toNativeString(const QString& data) {
13398 QString res;
13399 const int n = data.length();
13400
13401 // Ignore font tags:
13402 int j = 0;
13403 for (int i=0; i<n; ++i) {
13404 // Need at least "{\X" available — bounds-check before any at(i+N).
13405 if (i + 2 >= n) break;
13406 if (data.at(i).unicode() != 0x7B) continue; //is '{' ?
13407 if (data.at(i+1).unicode() != 0x5c) continue; //and is "{\" ?
13408 const ushort tagChar = data.at(i+2).unicode();
13409 if (tagChar != 0x66 && tagChar != 0x48 && tagChar != 0x43) continue; // "\f" "\H" "\C"
13410
13411 //found tag, append parsed part
13412 res.append(data.mid(j, i - j));
13413 qsizetype pos = data.indexOf(QChar(0x7D), i+3); //find '}'
13414 if (pos < 0) break; //'}' not found
13415 QString tmp = data.mid(i + 1, pos - i - 1);
13416 do {
13417 qsizetype semi = tmp.indexOf(QChar(0x3B), 0); //find ';'
13418 if (semi < 0) break; // malformed: no ';' — bail to avoid infinite loop
13419 tmp.remove(0, semi + 1);
13420 } while (tmp.startsWith(QLatin1StringView("\\f"))
13421 || tmp.startsWith(QLatin1StringView("\\H"))
13422 || tmp.startsWith(QLatin1StringView("\\C")));
13423 res.append(tmp);
13424 i = pos;
13425 j = pos + 1;
13426 }
13427 res.append(data.mid(j));
13428
13429 // AutoCAD caret convention: ^X → chr((X-64) mod 126); ^Space → '^'
13430 // literal. Mirrors ezdxf tools/text.py:501. Run BEFORE the \P/\~ pass
13431 // so ^J (LF), ^M (CR), ^I (TAB) decode first; also subsumes the prior
13432 // hard-coded "^I → 4 spaces" rule with the general formula (^I → TAB).
13433 QString caretDecoded;
13434 caretDecoded.reserve(res.size());
13435 for (int k = 0; k < res.size(); ++k) {
13436 const QChar ch = res.at(k);
13437 if (ch.unicode() == 0x5E /* '^' */ && k + 1 < res.size()) {
13438 const QChar nx = res.at(k + 1);
13439 if (nx.unicode() == 0x20) {
13440 caretDecoded.append(QChar(0x5E)); // ^space → literal ^
13441 } else {
13442 const int code = (static_cast<int>(nx.unicode()) - 64) % 126;
13443 caretDecoded.append(QChar(static_cast<ushort>(code < 0 ? code + 126 : code)));
13444 }
13445 ++k; // consume the X
13446 } else {
13447 caretDecoded.append(ch);
13448 }
13449 }
13450 res = std::move(caretDecoded);
13451
13452 // Replace literal escape sequences. Each replace() mutates in place;
13453 // the prior `res = res.replace(...)` was an unnecessary self-assignment.
13454 res.replace(QLatin1StringView("\\P"), QLatin1StringView("\n")); // line feed
13455 res.replace(QLatin1StringView("\\~"), QLatin1StringView(" ")); // space
13456 // diameter / degree / plus-minus — case-insensitive match of %%c / %%C etc.
13457 // (RLZ: Empty_set is 0x2205, diameter is 0x2300 — needs to be in all fonts.)
13458 res.replace(QLatin1StringView("%%c"), QStringLiteral("⌀")(QString(QtPrivate::qMakeStringPrivate(u"" "⌀"))), Qt::CaseInsensitive);
13459 res.replace(QLatin1StringView("%%d"), QStringLiteral("°")(QString(QtPrivate::qMakeStringPrivate(u"" "°"))), Qt::CaseInsensitive);
13460 res.replace(QLatin1StringView("%%p"), QStringLiteral("±")(QString(QtPrivate::qMakeStringPrivate(u"" "±"))), Qt::CaseInsensitive);
13461
13462 return res;
13463}
13464
13465/**
13466 * Converts the given number from a DXF file into an AngleFormat enum.
13467 *
13468 * @param num $DIMAUNIT from DXF (0: decimal deg, 1: deg/min/sec, 2: gradians,
13469 * 3: radians, 4: surveyor's units)
13470 *
13471 * @ret Matching AngleFormat enum value.
13472 */
13473RS2::AngleFormat RS_FilterDXFRW::numberToAngleFormat(int num) {
13474 switch (num) {
13475 default: case 0:
13476 return RS2::DegreesDecimal;
13477 case 1:
13478 return RS2::DegreesMinutesSeconds;
13479 case 2:
13480 return RS2::Gradians;
13481 case 3:
13482 return RS2::Radians;
13483 case 4:
13484 return RS2::Surveyors;
13485 }
13486}
13487
13488/**
13489 * Converts AngleFormat enum to DXF number.
13490 */
13491int RS_FilterDXFRW::angleFormatToNumber(RS2::AngleFormat af) {
13492 switch (af) {
13493 default: case RS2::DegreesDecimal:
13494 return 0;
13495 case RS2::DegreesMinutesSeconds:
13496 return 1;
13497 case RS2::Gradians:
13498 return 2;
13499 case RS2::Radians:
13500 return 3;
13501 case RS2::Surveyors:
13502 return 4;
13503 }
13504}
13505
13506/**
13507 * converts a DXF unit setting (e.g. INSUNITS) to a unit enum.
13508 */
13509RS2::Unit RS_FilterDXFRW::numberToUnit(int num) {
13510 switch (num) {
13511 default: case 0:
13512 return RS2::None;
13513 case 1:
13514 return RS2::Inch;
13515 case 2:
13516 return RS2::Foot;
13517 case 3:
13518 return RS2::Mile;
13519 case 4:
13520 return RS2::Millimeter;
13521 case 5:
13522 return RS2::Centimeter;
13523 case 6:
13524 return RS2::Meter;
13525 case 7:
13526 return RS2::Kilometer;
13527 case 8:
13528 return RS2::Microinch;
13529 case 9:
13530 return RS2::Mil;
13531 case 10:
13532 return RS2::Yard;
13533 case 11:
13534 return RS2::Angstrom;
13535 case 12:
13536 return RS2::Nanometer;
13537 case 13:
13538 return RS2::Micron;
13539 case 14:
13540 return RS2::Decimeter;
13541 case 15:
13542 return RS2::Decameter;
13543 case 16:
13544 return RS2::Hectometer;
13545 case 17:
13546 return RS2::Gigameter;
13547 case 18:
13548 return RS2::Astro;
13549 case 19:
13550 return RS2::Lightyear;
13551 case 20:
13552 return RS2::Parsec;
13553 }
13554 return RS2::None;
13555}
13556
13557/**
13558 * Converts a unit enum into a DXF unit number e.g. for INSUNITS.
13559 */
13560int RS_FilterDXFRW::unitToNumber(RS2::Unit unit) {
13561 switch (unit) {
13562 default:
13563 case RS2::None:
13564 return 0;
13565 case RS2::Inch:
13566 return 1;
13567 case RS2::Foot:
13568 return 2;
13569 case RS2::Mile:
13570 return 3;
13571 case RS2::Millimeter:
13572 return 4;
13573 case RS2::Centimeter:
13574 return 5;
13575 case RS2::Meter:
13576 return 6;
13577 case RS2::Kilometer:
13578 return 7;
13579 case RS2::Microinch:
13580 return 8;
13581 case RS2::Mil:
13582 return 9;
13583 case RS2::Yard:
13584 return 10;
13585 case RS2::Angstrom:
13586 return 11;
13587 case RS2::Nanometer:
13588 return 12;
13589 case RS2::Micron:
13590 return 13;
13591 case RS2::Decimeter:
13592 return 14;
13593 case RS2::Decameter:
13594 return 15;
13595 case RS2::Hectometer:
13596 return 16;
13597 case RS2::Gigameter:
13598 return 17;
13599 case RS2::Astro:
13600 return 18;
13601 case RS2::Lightyear:
13602 return 19;
13603 case RS2::Parsec:
13604 return 20;
13605 }
13606 return 0;
13607 }
13608
13609/**
13610 * Checks if the given variable is two-dimensional (e.g. $LIMMIN).
13611 */
13612bool RS_FilterDXFRW::isVariableTwoDimensional(const QString& var) {
13613 if (var == "$LIMMIN" || var == "$LIMMAX" || var == "$PLIMMIN" || var == "$PLIMMAX" || var == "$GRIDUNIT" || var == "$VIEWCTR") {
13614 return true;
13615 } else {
13616 return false;
13617}
13618}
13619
13620void RS_FilterDXFRW::addPolylineSegment(
13621 RS_Polyline& polyline, const RS_Vector& prev_pos, const RS_Vector& curr_pos,
13622 const double segmentBulge, const double nextBulge,
13623 const std::vector<std::shared_ptr<DRW_Variant>>& extData) {
13624 bool isLcData = false;
13625 double yRadius = 0.0;
13626
13627 // Issue #1946: LibreCAD may use elliptic arcs internally, but dxf file saving
13628 // uses arcs only. Minor radius of elliptic is saved as DRW_Variant::content
13629 // The content is flagged as "LibreCAD"
13630 for (const std::shared_ptr<DRW_Variant>& var : extData) {
13631 if (var->code() == 1001) {
13632 isLcData = *(var->content.s) == "LibreCad";
13633 }
13634 else if (isLcData && var->code() == 1040) {
13635 yRadius = var->content.d;
13636 }
13637 }
13638 bool isElliptic = yRadius > RS_TOLERANCE1.0e-10;
13639
13640 if (isElliptic) {
13641 std::unique_ptr<RS_Arc> arc{
13642 RS_Polyline::arcFromBulge(prev_pos, curr_pos, segmentBulge)};
13643 if (arc != nullptr && arc->getRadius() >= RS_TOLERANCE1.0e-10) {
13644 double radius = arc->getRadius();
13645 double scaleRatio = yRadius / radius;
13646 RS_Ellipse* ellipse = RS_Polyline::convertToEllipse(std::make_pair(arc.get(), scaleRatio));
13647 if (ellipse != nullptr) {
13648 ellipse->setParent(&polyline);
13649 // ellipse->setSelected(polyline.isSelected());
13650 ellipse->setPen(RS_Pen(RS2::FlagInvalid));
13651 ellipse->setLayer(nullptr);
13652 polyline.addEntity(ellipse);
13653 polyline.getData().endpoint = curr_pos;
13654 polyline.setNextBulge(nextBulge);
13655 return;
13656 }
13657 }
13658 }
13659
13660 // A malformed LibreCAD elliptic sidecar must not leave a gap in an
13661 // otherwise valid polyline. addVertex consumes the current outgoing bulge
13662 // and stores the following vertex's bulge for the next segment.
13663 polyline.addVertex(curr_pos, nextBulge, false);
13664}
13665
13666#ifdef DWGSUPPORT1
13667QString RS_FilterDXFRW::printDwgVersion(int v){
13668 switch (v) {
13669 case DRW::MC00:
13670 return "MC0.0/R1.1";
13671 case DRW::AC12:
13672 return "AC1.2/R1.2";
13673 case DRW::AC14:
13674 return "AC1.4/R1.4";
13675 case DRW::AC150:
13676 return "AC1.50/R2.0";
13677 case DRW::AC210:
13678 return "AC2.10/R2.10";
13679 case DRW::AC1002:
13680 return "AC1002/R2.5";
13681 case DRW::AC1003:
13682 return "AC1003/R2.6";
13683 case DRW::AC1004:
13684 return "AC1004/R9";
13685 case DRW::AC1006:
13686 return "AC1006/R10";
13687 case DRW::AC1009:
13688 return "AC1009/R11-R12";
13689 case DRW::AC1012:
13690 return "AC1012/R13";
13691 case DRW::AC1014:
13692 return "AC1014/R14";
13693 case DRW::AC1015:
13694 return "AC1015/R2000";
13695 case DRW::AC1018:
13696 return "AC1018/R2004";
13697 case DRW::AC1021:
13698 return "AC1021/R2007";
13699 case DRW::AC1024:
13700 return "AC1024/R2010";
13701 case DRW::AC1027:
13702 return "AC1027/R2013";
13703 case DRW::AC1032:
13704 return "AC1032/R2018";
13705 default:
13706 return "unknown";
13707 }
13708}
13709
13710bool RS_FilterDXFRW::sameRawObjectEncodingFamily(DRW::Version src, DRW::Version tgt) {
13711 // STRICT source==target only. The earlier "encoding family" widening (Alt-A)
13712 // grouped {AC1015,AC1018} and {AC1024,AC1027,AC1032} as cross-version
13713 // replayable, but a deep review proved the raw OBJECT/ENTITY bytes are NOT
13714 // safely replayable across a version boundary: the common object/entity
13715 // header layout differs (AC1015 lacks the R2004 xDictFlag bit and carries
13716 // entity prev/next link handles AC1018 drops; AC1024 lacks the has_ds_data
13717 // bit AC1027/AC1032 add), and even where common headers match the per-version
13718 // object BODY encoding can differ -- the raw bytes were written FOR the source
13719 // version. Replaying them into another version's frame (with a fresh valid
13720 // CRC) yields silently-malformed output. So raw replay is allowed only when
13721 // the target version equals the source version; a cross-version save cleanly
13722 // drops the raw-preserved metadata instead of corrupting it.
13723 return src == tgt;
13724}
13725
13726void RS_FilterDXFRW::printDwgError(int le){
13727 switch (le) {
13728 case DRW::BAD_UNKNOWN: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("unknown error opening dwg file"));
13729 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_UNKNOWN");
13730 break;
13731 case DRW::BAD_OPEN: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("can't open this dwg file"));
13732 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_OPEN");
13733 break;
13734 case DRW::BAD_VERSION:
13735 if (m_dwgVersion != DRW::UNKNOWNV) {
13736 const QString actual = dwgVersionDisplay(m_dwgVersion);
13737 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
13738 QObject::tr(
13739 "Cannot open DWG: file is %1; LibreCAD supports %2 and "
13740 "newer. "
13741 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
13742 "from a recent CAD tool.")
13743 .arg(actual)
13744 .arg(dwgVersionDisplay(DRW::AC1012)));
13745 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: BAD_VERSION (%s)",
13746 actual.toUtf8().constData());
13747 } else {
13748 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
13749 QObject::tr("unsupported dwg version"));
13750 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_VERSION");
13751 }
13752 break;
13753 case DRW::BAD_READ_METADATA: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading file metadata in dwg file"));
13754 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
13755 break;
13756 case DRW::BAD_READ_FILE_HEADER: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading file header in dwg file"));
13757 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
13758 break;
13759 case DRW::BAD_READ_HEADER: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading header vars in dwg file"));
13760 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_HEADER");
13761 break;
13762 case DRW::BAD_READ_CLASSES: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading classes in dwg file"));
13763 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_CLASSES");
13764 break;
13765 case DRW::BAD_READ_HANDLES: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading offsets in dwg file"));
13766 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
13767 break;
13768 case DRW::BAD_READ_TABLES: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading tables in dwg file"));
13769 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_TABLES");
13770 break;
13771 case DRW::BAD_READ_BLOCKS: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading blocks in dwg file"));
13772 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
13773 break;
13774 case DRW::BAD_READ_ENTITIES: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading entities in dwg file"));
13775 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_ENTITIES");
13776 break;
13777 case DRW::BAD_READ_OBJECTS: RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("error reading objects in dwg file"));
13778 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OBJECTS");
13779 break;
13780 default:
13781 break;
13782 }
13783}
13784
13785QString RS_FilterDXFRW::strVal(const DRW_Variant* var) {
13786 return QString::fromUtf8(var->c_str());
13787}
13788
13789LC_DimStyle* RS_FilterDXFRW::createDimStyle(const DRW_Dimstyle& s) {
13790 auto* result = new LC_DimStyle();
13791 QString name = QString::fromUtf8(s.name.c_str());
13792 result->setName(name);
13793
13794 bool styleForEntityType = !result->isBaseStyle();
13795 if (styleForEntityType) {
13796 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
13797 }
13798
13799 auto arrowStyle = result->arrowhead();
13800
13801 DRW_Variant* var = s.get("$DIMBLK");
13802 if (var != nullptr) {
13803 arrowStyle->setSameBlockName(strVal(var));
13804 }
13805 var = s.get("$DIMBLK1");
13806 if (var != nullptr) {
13807 arrowStyle->setArrowHeadBlockNameFirst(strVal(var));
13808 }
13809 var = s.get("$DIMBLK2");
13810 if (var != nullptr) {
13811 arrowStyle->setArrowHeadBlockNameSecond(strVal(var));
13812 }
13813 var = s.get("$DIMASZ");
13814 if (var != nullptr) {
13815 arrowStyle->setSize(var->d_val());
13816 }
13817 var = s.get("$DIMTSZ");
13818 if (var != nullptr) {
13819 arrowStyle->setTickSize(var->d_val());
13820 }
13821 var = s.get("$DIMSAH");
13822 if (var != nullptr) {
13823 arrowStyle->setUseSeparateArrowHeads(var->i_val());
13824 }
13825 var = s.get("$DIMSOXD");
13826 if (var != nullptr) {
13827 arrowStyle->setSuppressionsRaw(var->i_val());
13828 }
13829
13830 auto scaleStyle = result->scaling();
13831 var = s.get("$DIMSCALE");
13832 if (var != nullptr) {
13833 scaleStyle->setScale(var->d_val());
13834 }
13835 var = s.get("$DIMLFAC");
13836 if (var != nullptr) {
13837 scaleStyle->setLinearFactor(var->d_val());
13838 }
13839
13840 auto extLineStyle = result->extensionLine();
13841
13842 var = s.get("$DIMEXO");
13843 if (var != nullptr) {
13844 extLineStyle->setDistanceFromOriginPoint(var->d_val());
13845 }
13846 var = s.get("$DIMEXE");
13847 if (var != nullptr) {
13848 extLineStyle->setDistanceBeyondDimLine(var->d_val());
13849 }
13850 var = s.get("$DIMFXL");
13851 if (var != nullptr) {
13852 extLineStyle->setFixedLength(var->d_val());
13853 }
13854 var = s.get("$DIMFXLON");
13855 if (var != nullptr) {
13856 extLineStyle->setHasFixedLength(var->i_val() == 1);
13857 }
13858 var = s.get("$DIMLWE");
13859 if (var != nullptr) {
13860 extLineStyle->setLineWidthRaw(var->i_val());
13861 }
13862 var = s.get("$DIMCLRE");
13863 if (var != nullptr) {
13864 extLineStyle->setColor(numberToColor(var->i_val()));
13865 }
13866 var = s.get("$DIMSE1");
13867 if (var != nullptr) {
13868 extLineStyle->setSuppressFirstRaw(var->i_val());
13869 }
13870 var = s.get("$DIMSE2");
13871 if (var != nullptr) {
13872 extLineStyle->setSuppressSecondRaw(var->i_val());
13873 }
13874 var = s.get("$DIMLTEX1");
13875 if (var != nullptr) {
13876 auto dimltex1 = strVal(var);
13877 if (!dimltex1.isEmpty()) {
13878 extLineStyle->setLineTypeFirst(dimltex1);
13879 }
13880 }
13881 var = s.get("$DIMLTEX2");
13882 if (var != nullptr) {
13883 auto dimltex2 = strVal(var);
13884 if (!dimltex2.isEmpty()) {
13885 extLineStyle->setLineTypeSecond(dimltex2);
13886 }
13887 }
13888
13889 auto dimLineStyle = result->dimensionLine();
13890 var = s.get("$DIMLWD");
13891 if (var != nullptr) {
13892 dimLineStyle->setLineWidthRaw(var->i_val());
13893 }
13894 var = s.get("$DIMDLE");
13895 if (var != nullptr) {
13896 dimLineStyle->setDistanceBeyondExtLinesForObliqueStroke(var->d_val());
13897 }
13898 var = s.get("$DIMDLI");
13899 if (var != nullptr) {
13900 dimLineStyle->setBaselineDimLinesSpacing(var->d_val());
13901 }
13902 var = s.get("$DIMGAP");
13903 if (var != nullptr) {
13904 dimLineStyle->setLineGap(var->d_val());
13905 }
13906 var = s.get("$DIMCLRD");
13907 if (var != nullptr) {
13908 dimLineStyle->setColor(numberToColor(var->i_val()));
13909 }
13910 var = s.get("$DIMSD1");
13911 if (var != nullptr) {
13912 dimLineStyle->setSuppressFirstLineRaw(var->i_val());
13913 }
13914 var = s.get("$DIMSD2");
13915 if (var != nullptr) {
13916 dimLineStyle->setSuppressSecondLineRaw(var->i_val());
13917 }
13918 var = s.get("$DIMTOFL");
13919 if (var != nullptr) {
13920 dimLineStyle->setDrawPolicyForOutsideTextRaw(var->i_val());
13921 }
13922 var = s.get("$DIMLTYPE");
13923 if (var != nullptr) {
13924 auto dimltype = strVal(var);
13925 if (!dimltype.isEmpty()) {
13926 dimLineStyle->setLineType(dimltype);
13927 }
13928 }
13929
13930 auto textStyle = result->text();
13931 var = s.get("$DIMTXT");
13932 if (var != nullptr) {
13933 textStyle->setHeight(var->d_val());
13934 }
13935 var = s.get("$DIMTXSTY");
13936 if (var != nullptr) {
13937 auto dimtxsty = strVal(var);
13938 prepareTextStyleName(dimtxsty);
13939 textStyle->setStyle(dimtxsty); // fixme - resolve via table?
13940 }
13941 var = s.get("$DIMTOH");
13942 if (var != nullptr) {
13943 textStyle->setOrientationOutsideRaw(var->i_val());
13944 }
13945 var = s.get("$DIMTIH");
13946 if (var != nullptr) {
13947 textStyle->setOrientationInsideRaw(var->i_val());
13948 }
13949 var = s.get("$DIMJUST");
13950 if (var != nullptr) {
13951 textStyle->setHorizontalPositioningRaw(var->i_val());
13952 }
13953 var = s.get("$DIMCLRT");
13954 if (var != nullptr) {
13955 textStyle->setColor(numberToColor(var->i_val()));
13956 }
13957 var = s.get("$DIMTAD");
13958 if (var != nullptr) {
13959 textStyle->setVerticalPositioningRaw(var->i_val());
13960 }
13961 var = s.get("$DIMTIX");
13962 if (var != nullptr) {
13963 textStyle->setExtLinesRelativePlacementRaw(var->i_val());
13964 }
13965 var = s.get("$DIMTFILL");
13966 if (var != nullptr) {
13967 textStyle->setBackgroundFillModeRaw(var->i_val());
13968 }
13969 var = s.get("$DIMTFILLCLR");
13970 if (var != nullptr) {
13971 textStyle->setExplicitBackgroundFillColor(numberToColor(var->i_val()));
13972 }
13973 var = s.get("$DIMTXTDIRECTION");
13974 if (var != nullptr) {
13975 textStyle->setReadingDirectionRaw(var->i_val());
13976 }
13977 var = s.get("$DIMTVP");
13978 if (var != nullptr) {
13979 textStyle->setVerticalDistanceToDimLine(var->d_val());
13980 }
13981 var = s.get("$DIMUPT");
13982 if (var != nullptr) {
13983 textStyle->setCursorControlPolicyRaw(var->i_val());
13984 }
13985 var = s.get("$DIMTMOVE");
13986 if (var != nullptr) {
13987 textStyle->setPositionMovementPolicyRaw(var->i_val());
13988 }
13989 var = s.get("$DIMATFIT");
13990 if (var != nullptr) {
13991 textStyle->setUnsufficientSpacePolicyRaw(var->i_val());
13992 }
13993
13994 auto zerosSuppression = result->zerosSuppression();
13995
13996 var = s.get("$DIMZIN");
13997 if (var != nullptr) {
13998 zerosSuppression->setLinearRaw(var->i_val());
13999 }
14000 var = s.get("$DIMAZIN");
14001 if (var != nullptr) {
14002 zerosSuppression->setAngularRaw(var->i_val());
14003 }
14004 var = s.get("$DIMTZIN");
14005 if (var != nullptr) {
14006 zerosSuppression->setToleranceRaw(var->i_val());
14007 }
14008 var = s.get("$DIMALTZ");
14009 if (var != nullptr) {
14010 zerosSuppression->setAltLinearRaw(var->i_val());
14011 }
14012 var = s.get("$DIMALTTZ");
14013 if (var != nullptr) {
14014 zerosSuppression->setAltToleranceRaw(var->i_val());
14015 }
14016
14017 auto linearFormat = result->linearFormat();
14018
14019 var = s.get("$DIMLUNIT");
14020 if (var != nullptr) {
14021 linearFormat->setFormatRaw(var->i_val());
14022 }
14023 var = s.get("$DIMDSEP");
14024 if (var != nullptr) {
14025 linearFormat->setDecimalFormatSeparatorChar(var->i_val());
14026 }
14027 var = s.get("$DIMDEC");
14028 if (var != nullptr) {
14029 linearFormat->setDecimalPlaces(var->i_val());
14030 }
14031 var = s.get("$DIMPOST");
14032 if (var != nullptr) {
14033 linearFormat->setPrefixOrSuffix(strVal(var));
14034 }
14035 var = s.get("$DIMALT");
14036 if (var != nullptr) {
14037 linearFormat->setAlternateUnitsRaw(var->i_val());
14038 }
14039 var = s.get("$DIMALTU");
14040 if (var != nullptr) {
14041 linearFormat->setAltFormatRaw(var->i_val());
14042 }
14043 var = s.get("$DIMALTD");
14044 if (var != nullptr) {
14045 linearFormat->setAltDecimalPlaces(var->i_val());
14046 }
14047 var = s.get("$DIMALTF");
14048 if (var != nullptr) {
14049 linearFormat->setAltUnitsMultiplier(var->d_val());
14050 }
14051 var = s.get("$DIMAPOST");
14052 if (var != nullptr) {
14053 linearFormat->setAltPrefixOrSuffix(strVal(var));
14054 }
14055
14056 auto angularFormat = result->angularFormat();
14057 var = s.get("$DIMAUNIT");
14058 if (var != nullptr) {
14059 angularFormat->setFormatRaw(var->i_val());
14060 }
14061 var = s.get("$DIMADEC");
14062 if (var != nullptr) {
14063 angularFormat->setDecimalPlaces(var->i_val());
14064 }
14065
14066 auto linearRoundOff = result->roundOff();
14067 var = s.get("$DIMRND");
14068 if (var != nullptr) {
14069 linearRoundOff->setRoundToValue(var->d_val());
14070 }
14071 var = s.get("$DIMALTRND");
14072 if (var != nullptr) {
14073 linearRoundOff->setAltRoundToValue(var->d_val());
14074 }
14075
14076 auto fractionStyle = result->fractions();
14077 var = s.get("$DIMFRAC");
14078 if (var != nullptr) {
14079 fractionStyle->setStyleRaw(var->i_val());
14080 }
14081
14082 auto radialStyle = result->radial();
14083 var = s.get("$DIMCEN");
14084 if (var != nullptr) {
14085 radialStyle->setCenterMarkOrLineSize(var->d_val());
14086 }
14087
14088 auto toleranceStyle = result->latteralTolerance();
14089
14090 var = s.get("$DIMTDEC");
14091 if (var != nullptr) {
14092 toleranceStyle->setDecimalPlaces(var->i_val());
14093 }
14094 var = s.get("$DIMALTTD");
14095 if (var != nullptr) {
14096 toleranceStyle->setDecimalPlacesAltDim(var->i_val());
14097 }
14098 var = s.get("$DIMTOL");
14099 if (var != nullptr) {
14100 toleranceStyle->setAppendTolerancesToDimText(var->i_val()); // fixme - review
14101 }
14102 var = s.get("$DIMTOLJ");
14103 if (var != nullptr) {
14104 toleranceStyle->setVerticalJustificationRaw(var->i_val());
14105 }
14106 var = s.get("$DIMTM");
14107 if (var != nullptr) {
14108 toleranceStyle->setLowerToleranceLimit(var->d_val()); // fixme - review
14109 }
14110 var = s.get("$DIMTP");
14111 if (var != nullptr) {
14112 toleranceStyle->setUpperToleranceLimit(var->d_val());
14113 }
14114 var = s.get("$DIMTFAC");
14115 if (var != nullptr) {
14116 toleranceStyle->setHeightScaleFactorToDimText(var->d_val());
14117 }
14118 var = s.get("$DIMLIM");
14119 if (var != nullptr) {
14120 toleranceStyle->setLimitsAreGeneratedAsDefaultText(var->i_val()); // fixme - review
14121 }
14122
14123 auto leaderStyle = result->leader();
14124 var = s.get("$DIMLDRBLK");
14125 if (var != nullptr) {
14126 leaderStyle->setArrowBlockName(strVal(var));
14127 }
14128
14129 auto arc = result->arc();
14130 var = s.get("$DIMARCSYM");
14131 if (var != nullptr) {
14132 arc->setArcSymbolPositionRaw(var->i_val());
14133 }
14134
14135 // fixme - remove to MLeaderStyle
14136 /*auto mleaderStyle = result->mleader();
14137 var = s.get("$MLEADERSCALE");
14138 if (var != nullptr) {
14139 mleaderStyle->setScale(var->d_val());
14140 }*/
14141 // mleaderStyle->setScale(s.mleaderscale);
14142
14143 parseDimStyleExtData(s, result);
14144
14145 if (styleForEntityType) {
14146 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
14147 }
14148 return result;
14149}
14150
14151bool RS_FilterDXFRW::resolveBlockNameByHandle(std::uint32_t blockHandle, QString& blockName) const {
14152 // The reading context lives on the DXF reader; it is null on the DWG read
14153 // path (and after the reader is torn down). Resolution by handle is a
14154 // DXF-only facility — bail out safely otherwise.
14155 if (m_dxfR == nullptr)
14156 return false;
14157 std::string name = m_dxfR->getReadingContext()->resolveBlockRecordName(blockHandle);
14158 if (name.empty()) {
14159 return false;
14160 }
14161 blockName = name.c_str();
14162 return true;
14163}
14164
14165void RS_FilterDXFRW::parseDimStyleExtData(const DRW_Dimstyle& s, LC_DimStyle* result) {
14166 std::vector<DRW_Variant> tagData;
14167 int currentValType = 0;
14168 QString applicationName = "";
14169 bool expectType = false;
14170 // https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-3F0380A5-1C15-464D-BC66-2C5F094BCFB9
14171 // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-7943.htm
14172 for (auto v: s.extData) {
14173 int code = v->code();
14174 switch (code) {
14175 case 1001: {
14176 // application name
14177 if (!applicationName.isEmpty()) {
14178 applyParsedDimStyleExtData(result, applicationName, tagData);
14179 tagData.clear();
14180 }
14181 applicationName = v->c_str();
14182 expectType = false; // for later "not", as actually we do expect it
14183 break;
14184 }
14185 case 1002: {
14186 // control name
14187 break;
14188 }
14189 case 1070: // integer
14190 case 1071: {
14191 // long
14192 int val = v->i_val();
14193 if (expectType) {
14194 // code of var
14195 currentValType = val;
14196 }
14197 else {
14198 // it fields
14199 auto intVar = DRW_Variant(currentValType, val);
14200 tagData.push_back(intVar);
14201 }
14202 break;
14203 }
14204 case 1040: // real
14205 case 1041: // distance
14206 case 1042: {
14207 // scale factor
14208 double val = v->d_val();
14209 auto doubleVar = DRW_Variant(currentValType, val);
14210 tagData.push_back(doubleVar);
14211 break;
14212 }
14213 default:
14214 break;
14215 }
14216 expectType = !expectType;
14217 }
14218 if (!applicationName.isEmpty()) {
14219 // process last app name setion
14220 applyParsedDimStyleExtData(result, applicationName, tagData);
14221 tagData.clear();
14222 }
14223}
14224
14225void RS_FilterDXFRW::applyParsedDimStyleExtData(const LC_DimStyle* dimStyle, const QString& appName, const std::vector<DRW_Variant>& vector) {
14226 if (vector.empty()) {
14227 return;
14228 }
14229 const DRW_Variant* var = &vector.at(0);
14230 int code = var->code();
14231
14232 if (appName == "ACAD_DSTYLE_DIMJAG") {
14233 if (code == 388) {
14234 // double val = var->d_val();
14235 // fixme - decide where to store it. this is "Jog Height Factor"
14236 // dimStyle->dimensionLine()->set
14237 }
14238
14239 // code 388, float
14240 }
14241 else if (appName == "ACAD_DSTYLE_DIMTALN") {
14242 // code 392, int
14243 if (code == 392) {
14244 int val = var->i_val();
14245 dimStyle->latteralTolerance()->setAdjustmentRaw(val);
14246 }
14247 }
14248 else if (appName == "ACAD_DSTYLE_DIMBREAK") {
14249 // code 391, float
14250 if (code == 391) {
14251 // double val = var->d_val();
14252 // fixme - decide where to store it. This is "Dimension Break" in acad.
14253 }
14254 }
14255}
14256
14257#endif // DWGSUPPORT