Bug Summary

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

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-pc-linux-gnu -O2 -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-21/lib/clang/21 -D _REENTRANT -D DWGSUPPORT -D MUPARSER_STATIC -D LC_WITH_OPENTYPE=0 -D QC_APPDIR=librecad -D LC_VERSION=2.2.2_alpha1-853-ge2a8c2931 -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/libdxfrw/src/intern -I ../../libraries/jwwlib/src -I ../../libraries/shapelib/src -I ../../libraries/qtbidi -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/linetypes -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/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/x86_64-linux-gnu/c++/15 -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../include/c++/15/backward -internal-isystem /usr/lib/llvm-21/lib/clang/21/include -internal-isystem /usr/local/include -internal-isystem /usr/lib/gcc/x86_64-linux-gnu/15/../../../../x86_64-linux-gnu/include -internal-externc-isystem /usr/include/x86_64-linux-gnu -internal-externc-isystem /include -internal-externc-isystem /usr/include -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-11-06-031751-4837-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
24* USA
25**
26**********************************************************************/
27
28#include <algorithm>
29#include <array>
30#include <cctype>
31#include <cerrno>
32#include <chrono>
33#include <cmath>
34#include <cstdint>
35#include <cstdlib>
36#include <cstring>
37#include <functional>
38#include <iomanip>
39#include <iostream>
40#include <limits>
41#include <list>
42#include <map>
43#include <memory>
44#include <optional>
45#include <set>
46#include <sstream>
47#include <stack>
48#include <type_traits>
49#include <utility>
50#include <vector>
51
52#include <QCryptographicHash>
53#include <QDir>
54#include <QFile>
55#include <QFileInfo>
56#include <QLocale>
57#include <QRegularExpression>
58#include <QScopeGuard>
59#include <QTemporaryFile>
60#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 4, 0)((6<<16)|(4<<8)|(0))
61#include <QByteArrayView>
62#endif
63#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 0, 0)((6<<16)|(0<<8)|(0))
64#include <QStringConverter>
65#endif
66#include <QStringList>
67
68#include "drw_acis.h"
69#include "dwg_fixed_handles.h"
70#include "dxf_format.h"
71#include "intern/dwgbufferw.h"
72#include "intern/dwgsafety.h"
73#include "lc_colornumbers.h"
74#include "lc_containertraverser.h"
75#include "lc_defaults.h"
76#include "lc_dimarc.h"
77#include "lc_dimarrowblock.h"
78#include "lc_dimarrowregistry.h"
79#include "lc_dimordinate.h"
80#include "lc_dimstyle.h"
81#include "lc_dwgadvancedmetadata.h"
82#include "lc_extentitydata.h"
83#include "lc_hyperbola.h"
84#include "lc_hyperbolaspline.h"
85#include "lc_linetype.h"
86#include "lc_linetypenames.h"
87#include "lc_mleader.h"
88#include "lc_parabola.h"
89#include "lc_parabolaspline.h"
90#include "lc_splinehelper.h"
91#include "lc_splinepoints.h"
92#include "lc_shxshapenames.h"
93#include "lc_tolerance.h"
94#include "lc_wipeout.h"
95#include "rs_arc.h"
96#include "rs_block.h"
97#include "rs_blocklist.h"
98#include "rs_circle.h"
99#include "lc_hatchdefinitionlines.h"
100#include "lc_hatchdensity.h"
101#include "lc_hatchdensitytext.h"
102#include "rs_dialogfactory.h"
103#include "rs_dialogfactoryinterface.h"
104#include "rs_dimaligned.h"
105#include "rs_dimangular.h"
106#include "rs_dimdiametric.h"
107#include "rs_dimlinear.h"
108#include "rs_dimradial.h"
109#include "rs_ellipse.h"
110#include "rs_filterdxfrw.h"
111#include "rs_graphicview.h"
112#include "rs_hatch.h"
113#include "rs_image.h"
114#include "rs_insert.h"
115#include "rs_layer.h"
116#include "rs_leader.h"
117#include "rs_line.h"
118#include "rs_math.h"
119#include "rs_mtext.h"
120#include "rs_point.h"
121#include "rs_polyline.h"
122#include "rs_solid.h"
123#include "rs_spline.h"
124#include "rs_system.h"
125#include "rs_text.h"
126
127#ifdef DWGSUPPORT1
128#include "lc_graphicviewport.h"
129#include "lc_ucs.h"
130#include "lc_view.h"
131#include "libdwgr.h"
132#include "rs_debug.h"
133#endif // DWGSUPPORT
134
135// Catch distribution builds against a libdxfrw before 0.6.4, where DRW_Hatch
136// stored DXF group 52 raw instead of radians. Without this guard every legacy
137// angle would be multiplied by 57.3 (see issue #1585).
138#if !defined(DRW_HATCH_ANGLE_RADIANS1) || DRW_HATCH_ANGLE_RADIANS1 != 1
139#error "libdxfrw stores DXF hatch angles raw; LibreCAD needs DRW_HATCH_ANGLE_RADIANS 1 (libdxfrw 0.6.4)"
140#endif
141
142namespace {
143constexpr std::int32_t kLegacyRtlLayout = 1;
144constexpr std::int32_t kLogicalRtlLayout = 2;
145
146bool isLibreCadApp(const std::shared_ptr<DRW_Variant> &tag) {
147 return tag && tag->code() == 1001 && tag->type() == DRW_Variant::STRING &&
148 tag->content.s && *tag->content.s == "LibreCad";
149}
150
151std::int32_t mtextRtlLayout(const DRW_Entity &entity) {
152 std::int32_t layout = 0;
153 bool inLibreCad = false;
154 for (const auto &tag : entity.extData) {
155 if (tag && tag->code() == 1001)
156 inLibreCad = isLibreCadApp(tag);
157 if (inLibreCad && tag && tag->code() == 1071 &&
158 tag->type() == DRW_Variant::INTEGER &&
159 (tag->content.i == kLegacyRtlLayout || tag->content.i == kLogicalRtlLayout))
160 layout = std::max(layout, tag->content.i);
161 }
162 return layout;
163}
164
165void setMTextRtlMarker(DRW_Text &text, std::int32_t layout) {
166 decltype(text.extData) filtered;
167 bool inLibreCad = false;
168 for (const auto &tag : text.extData) {
169 if (tag && tag->code() == 1001)
170 inLibreCad = isLibreCadApp(tag);
171 if (inLibreCad && tag && tag->code() == 1071 &&
172 tag->type() == DRW_Variant::INTEGER &&
173 (tag->content.i == kLegacyRtlLayout || tag->content.i == kLogicalRtlLayout))
174 continue;
175 filtered.push_back(tag);
176 }
177 for (size_t i = 0; i < filtered.size();) {
178 if (isLibreCadApp(filtered[i]) &&
179 (i + 1 == filtered.size() ||
180 (filtered[i + 1] && filtered[i + 1]->code() == 1001))) {
181 filtered.erase(filtered.begin() + i);
182 } else {
183 ++i;
184 }
185 }
186 if (layout != 0) {
187 auto app = std::find_if(filtered.begin(), filtered.end(), isLibreCadApp);
188 if (app == filtered.end()) {
189 filtered.push_back(std::make_shared<DRW_Variant>(1001, std::string("LibreCad")));
190 app = std::prev(filtered.end());
191 }
192 auto end = std::find_if(std::next(app), filtered.end(), [](const auto &tag) {
193 return tag && tag->code() == 1001;
194 });
195 filtered.insert(end, std::make_shared<DRW_Variant>(1071, layout));
196 }
197 text.extData = std::move(filtered);
198}
199
200// MTEXT group 73, in DWG the BS after the text: 1 = at least, 2 = exact.
201// A file may hold another value (0 in the embedded MTEXT of older LibreCAD
202// DWG output); it reads as the default and is not written back.
203RS_MTextData::MTextLineSpacingStyle mtextLineSpacingStyle(const DRW_MText &mtext) {
204 return mtext.linespacingStyle == 2 ? RS_MTextData::Exact : RS_MTextData::AtLeast;
205}
206
207std::uint16_t mtextLineSpacingCode(RS_MTextData::MTextLineSpacingStyle style) {
208 return style == RS_MTextData::Exact ? 2 : 1;
209}
210
211// The private right-to-left marker a model text needs, 0 for none. Marker 1
212// preserves old drawings; marker 2 stores logical Unicode RTL.
213std::int32_t rtlLayoutOf(const RS_MTextData &data) {
214 if (data.drawingDirection != RS_MTextData::RightToLeft)
215 return 0;
216 return data.legacyRtlLayout ? kLegacyRtlLayout : kLogicalRtlLayout;
217}
218
219// Attachment, drawing direction and line spacing of a model text as MTEXT
220// stores them (groups 71, 72, 73, 44).
221// DXF MTEXT group 72: 1=LeftToRight, 3=TopToBottom, 5=ByStyle. RightToLeft
222// has no DXF encoding: it is written as LeftToRight so that other readers see
223// a well-defined direction, and setMTextRtlMarker() carries the preference.
224void setMTextLayout(DRW_MText &mtext, const RS_MTextData &data) {
225 mtext.textgen =
226 static_cast<int>(data.valign) * 3 + static_cast<int>(data.halign) + 1;
227 switch (data.drawingDirection) {
228 case RS_MTextData::TopToBottom:
229 mtext.alignH = static_cast<DRW_Text::HAlign>(3);
230 break;
231 case RS_MTextData::ByStyle:
232 mtext.alignH = static_cast<DRW_Text::HAlign>(5);
233 break;
234 case RS_MTextData::LeftToRight:
235 case RS_MTextData::RightToLeft:
236 default:
237 mtext.alignH = static_cast<DRW_Text::HAlign>(1);
238 break;
239 }
240 // Always the model's style: 1 and 2 read back as themselves, and no other
241 // source value may reach the file.
242 mtext.linespacingStyle = mtextLineSpacingCode(data.lineSpacingStyle);
243 mtext.interlin = data.lineSpacingFactor;
244}
245
246// RS_MText::setText() takes a leading \A0; \A1; \A2; out of the text and
247// makes it the vertical alignment.
248bool hasMTextAlignmentPrefix(const QString &text) {
249 return text.startsWith(QLatin1StringView("\\A0;")) ||
250 text.startsWith(QLatin1StringView("\\A1;")) ||
251 text.startsWith(QLatin1StringView("\\A2;"));
252}
253
254// The x axis direction of an MTEXT (group 11, WCS) for a text drawn at
255// `angle`. The stored direction is kept while it says that angle: it is what
256// the file had and may point out of the drawing plane. A record read with a
257// rotation angle alone has none, all three zero; DXF keeps the angle in
258// group 50 and such a record stays as it was, DWG has no other place for it.
259DRW_Coord mtextXAxis(double angle, const DRW_Coord &stored, bool angleEdited,
260 bool required) {
261 const DRW_Coord fromAngle(std::cos(angle), std::sin(angle), 0.0);
262 if (stored.x == 0.0 && stored.y == 0.0 && stored.z == 0.0)
263 return required || angleEdited ? fromAngle : stored;
264 // The angle a reader takes from the axis, as DRW_MText::updateAngle()
265 // does. An axis along Z alone, of a text that runs up a vertical face, is
266 // an axis like any other: it reads as the angle of its two zeros, and the
267 // direction of that angle is no replacement for it.
268 const double read = std::atan2(stored.y, stored.x);
269 const bool sameAngle = std::fabs(std::cos(read) - fromAngle.x) < 1.0e-9 &&
270 std::fabs(std::sin(read) - fromAngle.y) < 1.0e-9;
271 return sameAngle ? stored : fromAngle;
272}
273
274
275constexpr int kImportedSplineFallbackSamples = 96;
276
277template <typename T, typename = void>
278struct hasDwgReplayState : std::false_type {};
279
280template <typename T>
281struct hasDwgReplayState<
282 T, std::void_t<decltype(std::declval<const T &>().replayState)>>
283 : std::true_type {};
284
285std::string normalizeDwgTableName(const std::string &name) {
286 std::string normalized = name;
287 std::transform(
288 normalized.begin(), normalized.end(), normalized.begin(),
289 [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
290 return normalized;
291}
292
293// The handle the DWG being written has for the table record of a name, 0 if
294// it has none: a DWG names a layer or a text style by the handle of its
295// record, and the codec writes the handle of "0" or STANDARD for none.
296std::uint32_t dwgWriteHandleByName(
297 const std::map<std::string, std::uint32_t> &handles,
298 const std::string &name) {
299 const auto found = handles.find(normalizeDwgTableName(name));
300 return found == handles.end() ? 0 : found->second;
301}
302
303std::string normalizeDwgDataStorageHandleKey(std::string value) {
304 value.erase(
305 std::remove_if(value.begin(), value.end(),
306 [](unsigned char c) { return std::isspace(c) != 0; }),
307 value.end());
308 if (value.size() >= 2 && value[0] == '0' &&
309 (value[1] == 'x' || value[1] == 'X')) {
310 value.erase(0, 2);
311 }
312 for (char &c : value)
313 c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
314 const std::size_t firstNonZero = value.find_first_not_of('0');
315 if (firstNonZero == std::string::npos)
316 return "0";
317 value.erase(0, firstNonZero);
318 return value;
319}
320
321// Callback members observe only the writer owned by their current export.
322// Clear the observer before destruction on success, failure or exception.
323template <typename Writer> class ExportWriterScope final {
324public:
325 ExportWriterScope(Writer *&observer, std::unique_ptr<Writer> owner) noexcept
326 : m_observer(observer), m_owner(std::move(owner)) {
327 m_observer = m_owner.get();
328 }
329 ~ExportWriterScope() { reset(); }
330 ExportWriterScope(const ExportWriterScope &) = delete;
331 ExportWriterScope &operator=(const ExportWriterScope &) = delete;
332 void reset() noexcept {
333 m_observer = nullptr;
334 m_owner.reset();
335 }
336
337private:
338 Writer *&m_observer;
339 std::unique_ptr<Writer> m_owner;
340};
341
342enum class XDataReadResult {
343 Absent,
344 Malformed,
345 Valid,
346};
347
348XDataReadResult readXDataString(const DRW_Variant *value, int code,
349 std::string &result) {
350 if (value == nullptr || value->code() != code)
351 return XDataReadResult::Absent;
352 if (value->type() != DRW_Variant::STRING || value->content.s == nullptr)
353 return XDataReadResult::Malformed;
354 result = *value->content.s;
355 return XDataReadResult::Valid;
356}
357
358XDataReadResult readXDataInteger(const DRW_Variant *value, int code,
359 std::int64_t &result,
360 bool allowInteger64 = true) {
361 if (value == nullptr || value->code() != code)
362 return XDataReadResult::Absent;
363 if (value->type() == DRW_Variant::INTEGER) {
364 result = value->i_val();
365 return XDataReadResult::Valid;
366 }
367 if (allowInteger64 && value->type() == DRW_Variant::INTEGER64) {
368 result = value->i64_val();
369 return XDataReadResult::Valid;
370 }
371 return XDataReadResult::Malformed;
372}
373
374XDataReadResult readXDataDouble(const DRW_Variant *value, int code,
375 double &result) {
376 if (value == nullptr || value->code() != code)
377 return XDataReadResult::Absent;
378 if (value->type() != DRW_Variant::DOUBLE || !std::isfinite(value->d_val())) {
379 return XDataReadResult::Malformed;
380 }
381 result = value->d_val();
382 return XDataReadResult::Valid;
383}
384
385XDataReadResult readXDataCoord(const DRW_Variant *value, int code,
386 DRW_Coord &result) {
387 if (value == nullptr || value->code() != code)
388 return XDataReadResult::Absent;
389 if (value->type() != DRW_Variant::COORD || value->coord() == nullptr ||
390 !std::isfinite(value->coord()->x) || !std::isfinite(value->coord()->y) ||
391 !std::isfinite(value->coord()->z)) {
392 return XDataReadResult::Malformed;
393 }
394 result = *value->coord();
395 return XDataReadResult::Valid;
396}
397
398XDataReadResult readXDataBinary(const DRW_Variant *value, int code,
399 const std::vector<std::uint8_t> *&result) {
400 if (value == nullptr || value->code() != code)
401 return XDataReadResult::Absent;
402 if (value->type() != DRW_Variant::BINARY || value->content.b == nullptr)
403 return XDataReadResult::Malformed;
404 result = value->binary();
405 return result == nullptr ? XDataReadResult::Malformed
406 : XDataReadResult::Valid;
407}
408
409XDataReadResult readXDataMarker(const DRW_Variant *value, std::string &result) {
410 return readXDataString(value, 1001, result);
411}
412
413XDataReadResult readXDataHexString(const DRW_Variant *value, int code,
414 QByteArray &result) {
415 std::string text;
416 const XDataReadResult status = readXDataString(value, code, text);
417 if (status != XDataReadResult::Valid)
418 return status;
419 if (text.size() % 2 != 0)
420 return XDataReadResult::Malformed;
421 for (const unsigned char character : text) {
422 if (!std::isxdigit(character))
423 return XDataReadResult::Malformed;
424 }
425 result = QByteArray::fromHex(QByteArray::fromStdString(text));
426 return XDataReadResult::Valid;
427}
428
429bool fitsXDataInt32(std::int64_t value) {
430 return value >= std::numeric_limits<std::int32_t>::min() &&
431 value <= std::numeric_limits<std::int32_t>::max();
432}
433
434DRW_Variant *checkedDimStyleVariable(const DRW_Dimstyle &style,
435 const char *key) {
436 static const std::map<std::string, DRW_Variant::TYPE> expectedTypes = {
437 {"$DIMBLK", DRW_Variant::STRING},
438 {"$DIMBLK1", DRW_Variant::STRING},
439 {"$DIMBLK2", DRW_Variant::STRING},
440 {"$DIMLTEX1", DRW_Variant::STRING},
441 {"$DIMLTEX2", DRW_Variant::STRING},
442 {"$DIMLTYPE", DRW_Variant::STRING},
443 {"$DIMTXSTY", DRW_Variant::STRING},
444 {"$DIMPOST", DRW_Variant::STRING},
445 {"$DIMAPOST", DRW_Variant::STRING},
446 {"$DIMLDRBLK", DRW_Variant::STRING},
447 {"$DIMASZ", DRW_Variant::DOUBLE},
448 {"$DIMTSZ", DRW_Variant::DOUBLE},
449 {"$DIMSCALE", DRW_Variant::DOUBLE},
450 {"$DIMLFAC", DRW_Variant::DOUBLE},
451 {"$DIMEXO", DRW_Variant::DOUBLE},
452 {"$DIMEXE", DRW_Variant::DOUBLE},
453 {"$DIMFXL", DRW_Variant::DOUBLE},
454 {"$DIMDLE", DRW_Variant::DOUBLE},
455 {"$DIMDLI", DRW_Variant::DOUBLE},
456 {"$DIMGAP", DRW_Variant::DOUBLE},
457 {"$DIMTXT", DRW_Variant::DOUBLE},
458 {"$DIMTVP", DRW_Variant::DOUBLE},
459 {"$DIMALTF", DRW_Variant::DOUBLE},
460 {"$DIMRND", DRW_Variant::DOUBLE},
461 {"$DIMALTRND", DRW_Variant::DOUBLE},
462 {"$DIMCEN", DRW_Variant::DOUBLE},
463 {"$DIMTM", DRW_Variant::DOUBLE},
464 {"$DIMTP", DRW_Variant::DOUBLE},
465 {"$DIMTFAC", DRW_Variant::DOUBLE},
466 {"$DIMSAH", DRW_Variant::INTEGER},
467 {"$DIMSOXD", DRW_Variant::INTEGER},
468 {"$DIMFXLON", DRW_Variant::INTEGER},
469 {"$DIMLWE", DRW_Variant::INTEGER},
470 {"$DIMCLRE", DRW_Variant::INTEGER},
471 {"$DIMSE1", DRW_Variant::INTEGER},
472 {"$DIMSE2", DRW_Variant::INTEGER},
473 {"$DIMLWD", DRW_Variant::INTEGER},
474 {"$DIMCLRD", DRW_Variant::INTEGER},
475 {"$DIMSD1", DRW_Variant::INTEGER},
476 {"$DIMSD2", DRW_Variant::INTEGER},
477 {"$DIMTOFL", DRW_Variant::INTEGER},
478 {"$DIMTOH", DRW_Variant::INTEGER},
479 {"$DIMTIH", DRW_Variant::INTEGER},
480 {"$DIMJUST", DRW_Variant::INTEGER},
481 {"$DIMCLRT", DRW_Variant::INTEGER},
482 {"$DIMTAD", DRW_Variant::INTEGER},
483 {"$DIMTIX", DRW_Variant::INTEGER},
484 {"$DIMTFILL", DRW_Variant::INTEGER},
485 {"$DIMTFILLCLR", DRW_Variant::INTEGER},
486 {"$DIMTXTDIRECTION", DRW_Variant::INTEGER},
487 {"$DIMUPT", DRW_Variant::INTEGER},
488 {"$DIMTMOVE", DRW_Variant::INTEGER},
489 {"$DIMATFIT", DRW_Variant::INTEGER},
490 {"$DIMZIN", DRW_Variant::INTEGER},
491 {"$DIMAZIN", DRW_Variant::INTEGER},
492 {"$DIMTZIN", DRW_Variant::INTEGER},
493 {"$DIMALTZ", DRW_Variant::INTEGER},
494 {"$DIMALTTZ", DRW_Variant::INTEGER},
495 {"$DIMLUNIT", DRW_Variant::INTEGER},
496 {"$DIMDSEP", DRW_Variant::INTEGER},
497 {"$DIMDEC", DRW_Variant::INTEGER},
498 {"$DIMALT", DRW_Variant::INTEGER},
499 {"$DIMALTU", DRW_Variant::INTEGER},
500 {"$DIMALTD", DRW_Variant::INTEGER},
501 {"$DIMAUNIT", DRW_Variant::INTEGER},
502 {"$DIMADEC", DRW_Variant::INTEGER},
503 {"$DIMFRAC", DRW_Variant::INTEGER},
504 {"$DIMTDEC", DRW_Variant::INTEGER},
505 {"$DIMALTTD", DRW_Variant::INTEGER},
506 {"$DIMTOL", DRW_Variant::INTEGER},
507 {"$DIMTOLJ", DRW_Variant::INTEGER},
508 {"$DIMLIM", DRW_Variant::INTEGER},
509 {"$DIMARCSYM", DRW_Variant::INTEGER}};
510 if (key == nullptr)
511 return nullptr;
512 const auto expected = expectedTypes.find(key);
513 if (expected == expectedTypes.end())
514 return nullptr;
515 DRW_Variant *value = style.get(key);
516 if (value == nullptr || value->type() != expected->second)
517 return nullptr;
518 if (value->type() == DRW_Variant::STRING)
519 return value->content.s == nullptr ? nullptr : value;
520 if (value->type() == DRW_Variant::DOUBLE)
521 return std::isfinite(value->d_val()) ? value : nullptr;
522 return value;
523}
524
525// The name of the imported LTYPE record with this handle, or empty.
526QString lineTypeNameForHandle(const RS_Graphic *graphic,
527 std::uint32_t handle) {
528 if (graphic == nullptr || handle == 0)
529 return {};
530 return QString::fromStdString(
531 graphic->dwgAdvancedMetadata().lineTypeNameForHandle(handle));
532}
533
534std::string dwgDataStorageNumericHandleKey(std::uint64_t handle) {
535 std::ostringstream stream;
536 stream << std::uppercase << std::hex << handle;
537 return normalizeDwgDataStorageHandleKey(stream.str());
538}
539
540bool hasCustomDxfApplicationGroups(
541 const std::list<std::list<DRW_Variant>> &appData) {
542 for (const auto &group : appData) {
543 if (group.empty())
544 return true;
545 const DRW_Variant &opener = group.front();
546 if (opener.code() != 102 || opener.type() != DRW_Variant::STRING ||
547 opener.content.s == nullptr) {
548 return true;
549 }
550 std::string name = *opener.content.s;
551 if (!name.empty() && name.front() == '{')
552 name.erase(name.begin());
553 if (name != "ACAD_REACTORS" && name != "ACAD_XDICTIONARY")
554 return true;
555 }
556 return false;
557}
558
559bool hasDxfTableAppDataUnsupportedByDwg(const DRW_TableEntry &entry) {
560 return hasCustomDxfApplicationGroups(entry.appData);
561}
562
563bool isDwgSpaceBlockOwner(std::uint32_t handle) {
564 return handle == DRW::DwgModelSpaceBlockRecordHandle ||
565 handle == DRW::DwgPaperSpaceBlockRecordHandle ||
566 handle == DRW::DxfModelSpaceBlockRecordHandle ||
567 handle == DRW::DxfPaperSpaceBlockRecordHandle;
568}
569
570enum class DwgMLeaderWriteRoute : std::uint8_t { Invalid, Native, Fallback };
571
572enum class DwgUnderlaySidecarCoordinateMode : std::uint8_t {
573 Position,
574 Extrusion,
575 Clip,
576 InverseClip
577};
578
579struct DwgUnderlaySidecar {
580 DRW_Underlay value;
581 bool gotPosition = false;
582 bool hasOriginalClip = false;
583 bool noClip = false;
584};
585
586bool parseDwgUnderlaySidecar(const RS_Entity *entity,
587 DwgUnderlaySidecar &result) {
588 if (entity == nullptr || entity->rtti() != RS2::EntityPolyline ||
589 !entity->hasDrwExtData())
590 return false;
591
592 DwgUnderlaySidecar parsed;
593 bool inGroup = false;
594 bool sawApplication = false;
595 int stringOrdinal = 0;
596 bool scale3 = false;
597 std::size_t scalarCount = 0;
598 std::size_t displayValueCount = 0;
599 DwgUnderlaySidecarCoordinateMode coordinateMode =
600 DwgUnderlaySidecarCoordinateMode::Position;
601
602 for (const auto &variant : entity->getDrwExtData()) {
603 if (variant == nullptr) {
604 if (inGroup)
605 return false;
606 continue;
607 }
608 const int code = variant->code();
609 if (code == 1001) {
610 std::string marker;
611 const XDataReadResult markerStatus =
612 readXDataMarker(variant.get(), marker);
613 if (markerStatus != XDataReadResult::Valid) {
614 if (inGroup)
615 return false;
616 continue;
617 }
618 inGroup = marker == "LibreCAD_UNDERLAY";
619 sawApplication = sawApplication || inGroup;
620 continue;
621 }
622 if (!inGroup)
623 continue;
624
625 switch (code) {
626 case 1000: {
627 std::string value;
628 if (readXDataString(variant.get(), 1000, value) != XDataReadResult::Valid)
629 return false;
630 if (stringOrdinal < 2) {
631 if (stringOrdinal == 1) {
632 if (value == "PDF")
633 parsed.value.kind = DRW_Underlay::PDF;
634 else if (value == "DGN")
635 parsed.value.kind = DRW_Underlay::DGN;
636 else if (value == "DWF")
637 parsed.value.kind = DRW_Underlay::DWF;
638 else
639 return false;
640 }
641 ++stringOrdinal;
642 } else if (value == "SCALE3") {
643 scale3 = true;
644 } else if (value == "EXTRUSION") {
645 coordinateMode = DwgUnderlaySidecarCoordinateMode::Extrusion;
646 } else if (value == "CLIP_ORIGINAL") {
647 parsed.hasOriginalClip = true;
648 coordinateMode = DwgUnderlaySidecarCoordinateMode::Clip;
649 } else if (value == "NO_CLIP") {
650 parsed.noClip = true;
651 } else if (value == "INVERSE_CLIP") {
652 coordinateMode = DwgUnderlaySidecarCoordinateMode::InverseClip;
653 }
654 break;
655 }
656 case 1071: {
657 std::int64_t value = 0;
658 if (readXDataInteger(variant.get(), 1071, value) !=
659 XDataReadResult::Valid)
660 return false;
661 if (value < 0 || static_cast<std::uint64_t>(value) >
662 std::numeric_limits<std::uint32_t>::max())
663 return false;
664 parsed.value.definitionHandle = static_cast<std::uint32_t>(value);
665 break;
666 }
667 case 1010: {
668 DRW_Coord point;
669 if (readXDataCoord(variant.get(), 1010, point) != XDataReadResult::Valid)
670 return false;
671 switch (coordinateMode) {
672 case DwgUnderlaySidecarCoordinateMode::Position:
673 if (parsed.gotPosition)
674 return false;
675 parsed.value.position = point;
676 parsed.gotPosition = true;
677 break;
678 case DwgUnderlaySidecarCoordinateMode::Extrusion:
679 parsed.value.extPoint = point;
680 break;
681 case DwgUnderlaySidecarCoordinateMode::Clip:
682 parsed.value.clipBoundary.push_back(point);
683 break;
684 case DwgUnderlaySidecarCoordinateMode::InverseClip:
685 parsed.value.inverseClipBoundary.push_back(point);
686 break;
687 }
688 break;
689 }
690 case 1040: {
691 double value = 0.0;
692 if (readXDataDouble(variant.get(), 1040, value) != XDataReadResult::Valid)
693 return false;
694 if (scale3) {
695 switch (scalarCount) {
696 case 0:
697 parsed.value.scale.x = value;
698 break;
699 case 1:
700 parsed.value.scale.y = value;
701 break;
702 case 2:
703 parsed.value.scale.z = value;
704 break;
705 case 3:
706 parsed.value.rotation = value;
707 break;
708 default:
709 return false;
710 }
711 } else {
712 switch (scalarCount) {
713 case 0:
714 parsed.value.scale.x = value;
715 break;
716 case 1:
717 parsed.value.scale.y = value;
718 break;
719 case 2:
720 parsed.value.rotation = value;
721 break;
722 default:
723 return false;
724 }
725 }
726 ++scalarCount;
727 break;
728 }
729 case 1070: {
730 std::int64_t value = 0;
731 if (readXDataInteger(variant.get(), 1070, value, false) !=
732 XDataReadResult::Valid ||
733 value < 0 || value > std::numeric_limits<std::uint8_t>::max())
734 return false;
735 if (displayValueCount == 0)
736 parsed.value.flags = static_cast<std::uint8_t>(value);
737 else if (displayValueCount == 1)
738 parsed.value.contrast = static_cast<std::uint8_t>(value);
739 else if (displayValueCount == 2)
740 parsed.value.fade = static_cast<std::uint8_t>(value);
741 else
742 return false;
743 ++displayValueCount;
744 break;
745 }
746 default:
747 break;
748 }
749 }
750
751 const std::size_t expectedScalars = scale3 ? 4u : 3u;
752 if (!sawApplication || stringOrdinal != 2 || !parsed.gotPosition ||
753 scalarCount != expectedScalars || displayValueCount != 3 ||
754 parsed.value.definitionHandle == 0 ||
755 (parsed.noClip && parsed.hasOriginalClip) ||
756 !std::isfinite(parsed.value.rotation) ||
757 std::abs(parsed.value.scale.x) <= RS_TOLERANCE1.0e-10 ||
758 std::abs(parsed.value.scale.y) <= RS_TOLERANCE1.0e-10)
759 return false;
760
761 result = std::move(parsed);
762 return true;
763}
764
765bool hasDwgUnderlaySidecar(const RS_Entity *entity) {
766 if (entity == nullptr || !entity->hasDrwExtData())
767 return false;
768 for (const auto &variant : entity->getDrwExtData()) {
769 std::string marker;
770 if (readXDataMarker(variant.get(), marker) == XDataReadResult::Valid &&
771 marker == "LibreCAD_UNDERLAY")
772 return true;
773 }
774 return false;
775}
776
777#ifdef DWGSUPPORT1
778std::uint16_t underlayEntityClassNumber(const DRW_Underlay::Kind kind,
779 const dwgRW *writer) {
780 if (writer == nullptr)
781 return 0;
782 switch (kind) {
783 case DRW_Underlay::DGN:
784 return writer->getDwgTypedClassNumber("AcDbDgnReference", "DGNUNDERLAY",
785 DRW_Underlay::kDwgClassNumDgn);
786 case DRW_Underlay::DWF:
787 return writer->getDwgTypedClassNumber("AcDbDwfReference", "DWFUNDERLAY",
788 DRW_Underlay::kDwgClassNumDwf);
789 case DRW_Underlay::PDF:
790 default:
791 return writer->getDwgTypedClassNumber("AcDbPdfReference", "PDFUNDERLAY",
792 DRW_Underlay::kDwgClassNumPdf);
793 }
794}
795#endif
796
797bool isFiniteDwgVector(const RS_Vector &value) {
798 return std::isfinite(value.x) && std::isfinite(value.y) &&
799 std::isfinite(value.z);
800}
801
802DwgMLeaderWriteRoute selectDwgMLeaderWriteRoute(const LC_MLeaderData &data,
803 DRW::Version version) {
804 const bool hasText = data.hasTextContents && !data.textLabel.isEmpty();
805 const bool hasBlock = data.hasBlockContents;
806
807 if ((data.hasTextContents && data.textLabel.isEmpty()) ||
808 (hasBlock && (data.blockName.isEmpty() || !data.blockLocation.isValid())))
809 return DwgMLeaderWriteRoute::Invalid;
810 if (data.roots.size() > DRW_MLeader::kMaxRoots || data.leaderType < 0 ||
811 data.leaderType > 2 || data.contentType < 0 || data.contentType > 3)
812 return DwgMLeaderWriteRoute::Invalid;
813
814 const auto finite = [](double value) { return std::isfinite(value); };
815 if (!finite(data.landingDistance) || !finite(data.arrowSize) ||
816 !finite(data.scaleFactor) || !finite(data.textHeight) ||
817 !finite(data.textRotation) || !finite(data.boundaryWidth) ||
818 !finite(data.boundaryHeight) || !finite(data.blockRotation) ||
819 !isFiniteDwgVector(data.contentBasePoint) ||
820 !isFiniteDwgVector(data.basePoint) ||
821 !isFiniteDwgVector(data.textLocation) ||
822 !isFiniteDwgVector(data.blockLocation) ||
823 !isFiniteDwgVector(data.blockScale))
824 return DwgMLeaderWriteRoute::Invalid;
825
826 for (const LC_MLeaderRoot &root : data.roots) {
827 if (!isFiniteDwgVector(root.connectionPoint) ||
828 !isFiniteDwgVector(root.direction) || !finite(root.landingDistance) ||
829 root.leaderLines.size() > DRW_MLeader::kMaxLeaderLines)
830 return DwgMLeaderWriteRoute::Invalid;
831 for (const LC_MLeaderLine &line : root.leaderLines) {
832 if (line.points.size() > DRW_MLeader::kMaxLeaderPoints ||
833 std::any_of(
834 line.points.cbegin(), line.points.cend(),
835 [](const RS_Vector &point) { return !isFiniteDwgVector(point); }))
836 return DwgMLeaderWriteRoute::Invalid;
837 }
838 }
839
840 if (version >= DRW::AC1024 && hasText && !hasBlock)
841 return DwgMLeaderWriteRoute::Native;
842 return DwgMLeaderWriteRoute::Fallback;
843}
844
845void overlayActivePlotSettings(DRW_PlotSettings &target,
846 LC_PlotSettings &source) {
847 const QString pages = QString("%1x%2")
848 .arg(source.getPagesNumHoriz())
849 .arg(source.getPagesNumVert());
850 target.plotViewName = pages.toStdString();
851 target.marginLeft = source.getMarginLeftMm();
852 target.marginTop = source.getMarginTopMm();
853 target.marginRight = source.getMarginRightMm();
854 target.marginBottom = source.getMarginBottomMm();
855 target.currentStyleSheet = source.getCurrentStyleName().toStdString();
856 target.pageSetupName = source.getPaperSizeName().toStdString();
857 target.paperWidth = source.getPaperWidthMm();
858 target.paperHeight = source.getPaperHeightMm();
859 target.plotOriginX = source.getOriginOffsetXMm();
860 target.plotOriginY = source.getOriginOffsetYMm();
861 target.windowMinX = source.getPlotWindowLowerLeftX();
862 target.windowMinY = source.getPlotWindowLowerLeftY();
863 target.windowMaxX = source.getPlotWindowUpperRightX();
864 target.windowMaxY = source.getPlotWindowUpperRightY();
865 target.realWorldUnits = source.getCustomPrintScalePaperUnitsNumerator();
866 target.drawingUnits = source.getCustomPrintScaleDrawingUnitsDenominator();
867 target.plotLayoutFlags = source.getPlotLayoutFlag();
868 target.paperUnits = source.getPlotPaperUnits();
869 target.plotRotation = source.getPlotRotation();
870 target.plotType = source.getPlotType();
871 target.scaleType = source.getStandardScaleType();
872 target.shadePlotMode = source.getShadePlotMode();
873 target.shadePlotResLevel = source.getShadePlotResolutionMode();
874 target.shadePlotCustomDPI = source.getShadePlotCustomDpi();
875 target.scaleFactor = source.getStandardScaleFactor();
876 target.paperImageOriginX = source.getPaperImageOriginX();
877 target.paperImageOriginY = source.getPaperImageOriginY();
878}
879
880bool restoreTableEntryExtData(
881 DRW_TableEntry &destination,
882 const std::vector<std::shared_ptr<DRW_Variant>> &source) {
883 for (const auto &value : source) {
884 if (!value ||
885 !destination.addExtData(std::make_unique<DRW_Variant>(*value))) {
886 return false;
887 }
888 }
889 return true;
890}
891
892bool restoreTableEntryExtData(DRW_TableEntry &destination,
893 const std::vector<DRW_Variant *> &source) {
894 for (const DRW_Variant *value : source) {
895 if (value == nullptr ||
896 !destination.addExtData(std::make_unique<DRW_Variant>(*value))) {
897 return false;
898 }
899 }
900 return true;
901}
902
903bool sameDxfClassDefinition(const DRW_Class &lhs, const DRW_Class &rhs) {
904 // instanceCount is recomputed from emitted records, while DWG-only fields
905 // do not participate in the DXF CLASS record. Compare only values that can
906 // change the emitted definition.
907 return lhs.className == rhs.className && lhs.appName == rhs.appName &&
908 lhs.proxyFlag == rhs.proxyFlag &&
909 lhs.wasaProxyFlag == rhs.wasaProxyFlag &&
910 lhs.entityFlag == rhs.entityFlag;
911}
912
913template <typename T> bool hasNormalizedAxialExtrusion(const T &entity) {
914 return entity.extPoint.x == 0.0 && entity.extPoint.y == 0.0 &&
915 (entity.extPoint.z == 1.0 || entity.extPoint.z == -1.0);
916}
917
918bool layerExtStringEmpty(const DRW_Variant *v) {
919 if (v == nullptr || v->type() != DRW_Variant::STRING)
920 return false;
921 return v->content.s == nullptr || v->content.s->empty();
922}
923
924QString layerExtAppName(const DRW_Variant *v) {
925 if (v == nullptr || v->code() != 1001 || v->type() != DRW_Variant::STRING ||
926 v->content.s == nullptr) {
927 return {};
928 }
929 return QString::fromUtf8(v->content.s->c_str()).trimmed();
930}
931
932enum class LayerConstructionMarkerStatus { Absent, Valid, Malformed };
933
934// A layer's construction marker is one complete 1001 application-data
935// group. Classify the complete group before applying its meaning so a bad
936// marker cannot be mistaken for an absent marker and emitted a second time.
937LayerConstructionMarkerStatus
938classifyLayerConstructionMarker(const std::vector<DRW_Variant *> &extData) {
939 bool foundLibreCadGroup = false;
940 bool malformedLibreCadGroup = false;
941 std::size_t validLibreCadGroups = 0;
942
943 for (std::size_t start = 0; start < extData.size();) {
944 const DRW_Variant *appId = extData[start];
945 std::size_t end = start + 1;
946 while (end < extData.size() &&
947 (extData[end] == nullptr || extData[end]->code() != 1001)) {
948 ++end;
949 }
950
951 const bool isLibreCad =
952 layerExtAppName(appId).compare(QLatin1String("LibreCad"),
953 Qt::CaseInsensitive) == 0;
954 if (isLibreCad) {
955 foundLibreCadGroup = true;
956 // The layer marker has exactly one payload value: INTEGER 1.
957 // Any extra, missing, null, or differently typed value makes the
958 // recognized group malformed rather than silently ignorable.
959 const bool valid = end == start + 2 && extData[start + 1] != nullptr &&
960 extData[start + 1]->code() == 1070 &&
961 extData[start + 1]->type() == DRW_Variant::INTEGER &&
962 extData[start + 1]->content.i == 1;
963 if (valid)
964 ++validLibreCadGroups;
965 else
966 malformedLibreCadGroup = true;
967 }
968
969 start = end;
970 }
971
972 if (!foundLibreCadGroup)
973 return LayerConstructionMarkerStatus::Absent;
974 if (malformedLibreCadGroup || validLibreCadGroups != 1)
975 return LayerConstructionMarkerStatus::Malformed;
976 return LayerConstructionMarkerStatus::Valid;
977}
978
979// AutoCAD AEC layer-standard marker: APPID + two empty UTF-16 strings, no
980// actionable LibreCAD semantics.
981bool isAcAecLayerStandardMarker(const std::vector<DRW_Variant *> &extData,
982 std::size_t appBlockStart) {
983 if (appBlockStart >= extData.size())
984 return false;
985 const QString app = layerExtAppName(extData[appBlockStart]);
986 if (!app.compare(QLatin1String("AcAecLayerStandard"), Qt::CaseInsensitive))
987 ;
988 else if (app.startsWith(QLatin1String("AcAec"), Qt::CaseInsensitive) &&
989 app.endsWith(QLatin1String("LayerStandard"), Qt::CaseInsensitive))
990 ;
991 else
992 return false;
993
994 std::size_t idx = appBlockStart + 1;
995 int emptyStrings = 0;
996 while (idx < extData.size() && emptyStrings < 2) {
997 const DRW_Variant *v = extData[idx];
998 if (v == nullptr)
999 return false;
1000 if (v->code() == 1000) {
1001 if (!layerExtStringEmpty(v))
1002 return false;
1003 ++emptyStrings;
1004 ++idx;
1005 continue;
1006 }
1007 if (v->code() == 1002 && v->type() == DRW_Variant::STRING &&
1008 v->content.s != nullptr &&
1009 (*v->content.s == "{" || *v->content.s == "}")) {
1010 ++idx;
1011 continue;
1012 }
1013 break;
1014 }
1015 return emptyStrings == 2;
1016}
1017
1018bool isIgnorableLayerExtData(const std::vector<DRW_Variant *> &extData) {
1019 if (extData.empty())
1020 return true;
1021 for (std::size_t i = 0; i < extData.size();) {
1022 if (extData[i] == nullptr || extData[i]->code() != 1001)
1023 return false;
1024 if (layerExtAppName(extData[i])
1025 .compare(QLatin1String("LibreCad"), Qt::CaseInsensitive) == 0) {
1026 return false;
1027 }
1028 if (isAcAecLayerStandardMarker(extData, i)) {
1029 std::size_t idx = i + 1;
1030 int emptyStrings = 0;
1031 while (idx < extData.size() && emptyStrings < 2) {
1032 const DRW_Variant *v = extData[idx];
1033 if (v != nullptr && v->code() == 1000) {
1034 ++emptyStrings;
1035 ++idx;
1036 } else if (v != nullptr && v->code() == 1002) {
1037 ++idx;
1038 } else {
1039 break;
1040 }
1041 }
1042 i = idx;
1043 continue;
1044 }
1045 return false;
1046 }
1047 return true;
1048}
1049
1050bool differsFromUnitWeight(double weight) {
1051 return std::fabs(weight - 1.0) > 1e-12;
1052}
1053
1054// R12 has no SPLINE entity: an RS_Spline goes out as a POLYLINE within this
1055// fraction of the diagonal of its control points' box, with at most
1056// kMaxR12SplineVertices vertices. Unrelated to the drawn segments, to
1057// $SPLINESEGS and to the offset engine's modeling tolerance. Over 637 splines
1058// of the DXF/DWG test corpora the median needs 63 vertices, the largest 542.
1059constexpr double kR12SplineRelativeTolerance = 1e-4;
1060constexpr size_t kMaxR12SplineVertices = 32767;
1061
1062/**
1063 * The vertices of the POLYLINE an R12 file stores for @p s, which is written
1064 * closed when @p closed; false if none fits.
1065 */
1066bool r12SplineVertices(const RS_Spline &s, bool closed,
1067 std::vector<RS_Vector> &vertices) {
1068 vertices.clear();
1069 RS_Vector low{false};
1070 RS_Vector high{false};
1071 double coordinate = 0.0;
1072 for (const RS_Vector &v : s.getUnwrappedControlPoints()) {
1073 if (!std::isfinite(v.x) || !std::isfinite(v.y)) {
1074 return false;
1075 }
1076 low = RS_Vector::minimum(low, v);
1077 high = RS_Vector::maximum(high, v);
1078 coordinate = std::max({coordinate, std::abs(v.x), std::abs(v.y)});
1079 }
1080 const double size = low.valid ? low.distanceTo(high) : 0.0;
1081 const double floor = 64.0 * std::numeric_limits<double>::epsilon() *
1082 std::max(coordinate, std::numeric_limits<double>::min());
1083 const double tolerance = std::max(kR12SplineRelativeTolerance * size, floor);
1084 if (!std::isfinite(tolerance) ||
1085 !s.tryStroke(tolerance, kMaxR12SplineVertices, vertices)) {
1086 return false;
1087 }
1088 // a closed POLYLINE repeats no vertex
1089 if (closed && vertices.size() > 2 &&
1090 vertices.back().distanceTo(vertices.front()) <= tolerance) {
1091 vertices.pop_back();
1092 }
1093 return true;
1094}
1095
1096bool hasRationalSplineWeights(const DRW_Spline *data) {
1097 if (data == nullptr)
1098 return false;
1099 return (data->flags & 0x4) != 0 ||
1100 std::any_of(data->weightlist.begin(), data->weightlist.end(),
1101 differsFromUnitWeight);
1102}
1103
1104bool buildSplineDataFromDrw(const DRW_Spline *source, RS_SplineData &target) {
1105 if (source == nullptr || source->degree < 1 || source->controllist.empty())
1106 return false;
1107
1108 const size_t degree = static_cast<size_t>(source->degree);
1109 const size_t controlCount = source->controllist.size();
1110 if (controlCount < degree + 1)
1111 return false;
1112
1113 const size_t requiredKnots = controlCount + degree + 1;
1114 if (source->knotslist.size() < requiredKnots)
1115 return false;
1116
1117 const bool closed = (source->flags & 0x3) != 0;
1118 target = RS_SplineData(source->degree, closed);
1119 target.type = closed ? RS_SplineData::SplineType::WrappedClosed
1120 : RS_SplineData::SplineType::ClampedOpen;
1121
1122 target.knotslist.assign(source->knotslist.begin(),
1123 source->knotslist.begin() + requiredKnots);
1124
1125 target.controlPoints.reserve(controlCount);
1126 target.weights.reserve(controlCount);
1127 const bool rational = hasRationalSplineWeights(source);
1128 for (size_t i = 0; i < controlCount; ++i) {
1129 const auto &control = source->controllist[i];
1130 if (!control)
1131 return false;
1132 target.controlPoints.push_back({control->x, control->y});
1133 double weight = 1.0;
1134 if (rational && i < source->weightlist.size())
1135 weight = source->weightlist[i];
1136 if (weight <= 0.0 || !std::isfinite(weight))
1137 return false;
1138 target.weights.push_back(weight);
1139 }
1140
1141 return true;
1142}
1143
1144// Whether the curve a B-spline's control points, weights and knots draw over
1145// [knots[degree], knots[count]] ends where it starts.
1146bool splineCurveCloses(const RS_SplineData &data) {
1147 const size_t degree = data.degree;
1148 const size_t count = data.controlPoints.size();
1149 if (degree < 1 || count < degree + 1 || data.weights.size() != count ||
1150 data.knotslist.size() != count + degree + 1)
1151 return false;
1152 double scale = 1.0;
1153 for (const RS_Vector &p : data.controlPoints)
1154 scale = std::max({scale, std::abs(p.x), std::abs(p.y)});
1155 const RS_Vector start = RS_Spline::evaluateNURBS(data, data.knotslist[degree]);
1156 const RS_Vector end = RS_Spline::evaluateNURBS(data, data.knotslist[count]);
1157 return start.valid && end.valid && start.distanceTo(end) <= 1e-9 * scale;
1158}
1159
1160// Whether the knot spacing repeats with the period of the count - degree
1161// distinct control points of a spline whose first degree control points are
1162// repeated at the end, which makes its curve a periodic one.
1163bool hasPeriodicKnots(const RS_SplineData &data, double tolerance) {
1164 const std::vector<double> &knots = data.knotslist;
1165 const size_t period = data.controlPoints.size() - data.degree;
1166 for (size_t i = 0; i + period + 1 < knots.size(); ++i) {
1167 const double spacing = knots[i + 1] - knots[i];
1168 const double repeated = knots[i + period + 1] - knots[i + period];
1169 if (std::abs(spacing - repeated) > tolerance)
1170 return false;
1171 }
1172 return true;
1173}
1174
1175// The RS_Spline of a SPLINE entity, or of a spline edge of a hatch boundary:
1176// its control points with weights (weights[i] belongs to controllist[i]) and
1177// knots, control points + degree + 1 of them, or none for a clamped uniform
1178// knot vector.
1179//
1180// Whatever its closed (DXF group 70 bit 0) and periodic (bit 1) flags say, the
1181// spline is the curve that data draws over [knots[degree], knots[count]], and
1182// AutoCAD writes closed and periodic splines with a clamped knot vector and the
1183// last control point on the first. So the data is kept exactly, in the spline
1184// type that draws it:
1185// - a closed or periodic spline with its first degree control points repeated
1186// at the end and a periodic knot vector: WrappedClosed, which is that data;
1187// - clamped knots: ClampedOpen, with the file's flags kept for the export while
1188// the ends meet;
1189// - other unclamped knots: Standard;
1190// - anything else, such as knots clamped at one end only: clamped by knot
1191// insertion, which keeps the curve.
1192// The exception is a closed or periodic spline whose curve does not close:
1193// one LibreCAD wrote closed without the repeated control points. Up to 2.2.0
1194// it wrote such a spline with clamped knots (uniform ones for a spline drawn
1195// in LibreCAD), which it ignored when drawing a closed spline: it drew the
1196// uniform periodic spline of the control points, so that is what the spline
1197// is read as. Since 75c48f9ac (not in 2.2.1, which writes the control points
1198// wrapped) it writes the first knots of the periodic knot vector, which are
1199// kept. Either way the spline is wrapped, as before.
1200//
1201// Closing every flagged spline with setClosed() instead wrapped the clamped
1202// control points of AutoCAD's closed splines into a periodic spline: another
1203// curve, and mostly one validate() rejects, so that update() drew nothing.
1204std::unique_ptr<RS_Spline> makeSplineFromDrw(RS_EntityContainer *parent,
1205 const DRW_Spline &source,
1206 const std::vector<double> &weights) {
1207 using SplineType = RS_SplineData::SplineType;
1208 const bool closedFlag = (source.flags & 0x1) != 0;
1209 const bool periodicFlag = (source.flags & 0x2) != 0;
1210
1211 RS_SplineData data(source.degree, false);
1212 const double knotTolerance = (source.tolknot > 0.0) ? source.tolknot : 1e-7;
1213 for (const double k : source.knotslist)
1214 data.knotslist.push_back(RS_Math::round(k, knotTolerance));
1215 for (size_t i = 0; i < source.controllist.size(); ++i) {
1216 if (const auto &control = source.controllist[i]) {
1217 data.controlPoints.emplace_back(control->x, control->y);
1218 data.weights.push_back(i < weights.size() ? weights[i] : 1.0);
1219 }
1220 }
1221 const size_t count = data.controlPoints.size();
1222 if (data.knotslist.empty() && count > data.degree)
1223 data.knotslist = LC_SplineHelper::knot(count, data.degree + 1);
1224 // whether the file's curve closes, from its knots before rounding: rounding
1225 // moves the ends of a periodic curve apart
1226 RS_SplineData unrounded = data;
1227 if (!source.knotslist.empty())
1228 unrounded.knotslist = source.knotslist;
1229 const bool closes = splineCurveCloses(unrounded);
1230
1231 auto spline =
1232 std::make_unique<RS_Spline>(parent, RS_SplineData(source.degree, false));
1233 RS_SplineData &kept = spline->getData();
1234 kept = data;
1235 const bool wrapped = spline->hasWrappedControlPoints();
1236 const auto keepAs = [&](SplineType type) {
1237 kept = data;
1238 kept.type = type;
1239 return spline->validate();
1240 };
1241 const auto finish = [&]() {
1242 if (!spline->isClosed() && closes) {
1243 kept.m_closedFlag = closedFlag;
1244 kept.m_periodicFlag = periodicFlag;
1245 }
1246 spline->calculateBorders();
1247 spline->update();
1248 return std::move(spline);
1249 };
1250
1251 if ((closedFlag || periodicFlag) && closes && wrapped &&
1252 count > 2 * data.degree && hasPeriodicKnots(data, 2 * knotTolerance) &&
1253 keepAs(SplineType::WrappedClosed))
1254 return finish();
1255 if ((closedFlag || periodicFlag) && !closes) {
1256 // a spline LibreCAD wrote closed; clamped knots give way to uniform ones
1257 kept = data;
1258 kept.type = SplineType::Standard;
1259 if (count > data.degree &&
1260 data.knotslist[data.degree] - data.knotslist.front() <= knotTolerance)
1261 kept.knotslist =
1262 LC_SplineHelper::generateOpenUniformKnotVector(count, data.degree + 1);
1263 spline->setClosed(true);
1264 if (spline->isClosed() && spline->validate())
1265 return finish();
1266 }
1267 if (keepAs(SplineType::ClampedOpen))
1268 return finish();
1269 if (keepAs(SplineType::Standard))
1270 return finish();
1271 kept = data;
1272 if (!LC_SplineHelper::clampPreservingShape(kept) || !spline->validate()) {
1273 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
1274 "makeSplineFromDrw: no spline type accepts the data "
1275 "(degree %zu, %zu control points, %zu knots)",
1276 data.degree, count, data.knotslist.size());
1277 kept = data;
1278 }
1279 return finish();
1280}
1281
1282constexpr double kTableFallbackDimension = 1.0;
1283constexpr double kTableFallbackMinTextHeight = 0.1;
1284
1285enum class TableFallbackCellKind {
1286 Empty,
1287 PlainText,
1288 PlaceholderField,
1289 PlaceholderBlock,
1290 PlaceholderAttribute,
1291 PlaceholderUnknown
1292};
1293
1294struct TableFallbackCellDisplay {
1295 QString text;
1296 TableFallbackCellKind kind = TableFallbackCellKind::Empty;
1297};
1298
1299TableFallbackCellDisplay tableCellDisplay(const DRW_TableCell &cell,
1300 bool tableParseComplete) {
1301 auto placeholder = [](TableFallbackCellKind kind, const char *text) {
1302 TableFallbackCellDisplay display;
1303 display.kind = kind;
1304 display.text = QString::fromLatin1(text);
1305 return display;
1306 };
1307
1308 if (!tableParseComplete || cell.m_geometryFlags != 0 ||
1309 cell.m_geometryHandle != 0 || cell.m_overrideFlags != 0 ||
1310 cell.m_isMerged) {
1311 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
1312 }
1313
1314 for (const DRW_TableCellContent &content : cell.m_contents) {
1315 if (content.m_type == 4)
1316 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
1317 if (content.m_type == 2 || content.m_handle != 0)
1318 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
1319 if (!content.m_text.empty()) {
1320 return {QString::fromUtf8(content.m_text.c_str()),
1321 TableFallbackCellKind::PlainText};
1322 }
1323 if (!content.m_value.m_valueString.empty()) {
1324 return {QString::fromUtf8(content.m_value.m_valueString.c_str()),
1325 TableFallbackCellKind::PlainText};
1326 }
1327 if (content.m_type != 0 && content.m_type != 1)
1328 return placeholder(TableFallbackCellKind::PlaceholderUnknown, "[TABLE]");
1329 }
1330 if (cell.m_blockHandle != 0)
1331 return placeholder(TableFallbackCellKind::PlaceholderBlock, "[BLOCK]");
1332 if (cell.m_valueHandle != 0)
1333 return placeholder(TableFallbackCellKind::PlaceholderField, "[FIELD]");
1334 for (const DRW_TableCellAttribute &attribute : cell.m_attributes) {
1335 if (!attribute.m_text.empty()) {
1336 return {QString::fromUtf8(attribute.m_text.c_str()),
1337 TableFallbackCellKind::PlaceholderAttribute};
1338 }
1339 if (attribute.m_attdefHandle != 0)
1340 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
1341 }
1342 if (!cell.m_attributes.empty())
1343 return placeholder(TableFallbackCellKind::PlaceholderAttribute, "[ATTR]");
1344 return {};
1345}
1346
1347bool tableFallbackCellIsPlaceholder(TableFallbackCellKind kind) {
1348 return kind != TableFallbackCellKind::PlainText &&
1349 kind != TableFallbackCellKind::Empty;
1350}
1351
1352double tableColumnWidth(const DRW_TableContent &content, size_t index,
1353 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
1354 if (index < content.m_columns.size() &&
1355 std::isfinite(content.m_columns[index].m_width) &&
1356 content.m_columns[index].m_width > 0.0) {
1357 return content.m_columns[index].m_width;
1358 }
1359 if (summary != nullptr)
1360 ++summary->clampedDimensionCount;
1361 return kTableFallbackDimension;
1362}
1363
1364double tableRowHeight(const DRW_TableContent &content, size_t index,
1365 RS_FilterDXFRW::TableFallbackRenderSummary *summary) {
1366 if (index < content.m_rows.size() &&
1367 std::isfinite(content.m_rows[index].m_height) &&
1368 content.m_rows[index].m_height > 0.0) {
1369 return content.m_rows[index].m_height;
1370 }
1371 if (summary != nullptr)
1372 ++summary->clampedDimensionCount;
1373 return kTableFallbackDimension;
1374}
1375
1376DRW_UnsupportedObject
1377rawObjectFromMetadata(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1378 DRW_UnsupportedObject object;
1379 object.m_version = record.version;
1380 object.m_objectType = record.objectType;
1381 object.m_handle = record.handle;
1382 object.m_parentHandle = record.parentHandle;
1383 object.setCommonLinkEvidence(record.commonLinkEvidence);
1384 object.m_reactorHandles = record.reactorHandles;
1385 object.m_xDictHandle = record.xDictHandle;
1386 object.m_numReactors = record.numReactors;
1387 object.m_xDictFlag = record.xDictFlag;
1388 object.m_blockOwnerHandle = record.blockOwnerHandle;
1389 object.m_bodyBitSize = record.bodyBitSize;
1390 object.m_objectOffset = record.objectOffset;
1391 object.m_objectSize = record.objectSize;
1392 object.m_isEntity = record.isEntity;
1393 object.m_isCustomClass = record.isCustomClass;
1394 object.m_hasDataStorage = record.hasDataStorage;
1395 object.m_hasClassDefinition = record.hasClassDefinition;
1396 object.m_classProxyFlag = record.classProxyFlag;
1397 object.m_classAppName = record.classAppName;
1398 object.m_classWasProxy = record.classWasProxy;
1399 object.m_classEntityFlagRaw = record.classEntityFlagRaw;
1400 object.m_classDwgVersion = record.classDwgVersion;
1401 object.m_classMaintenanceVersion = record.classMaintenanceVersion;
1402 object.m_classUnknown1 = record.classUnknown1;
1403 object.m_classUnknown2 = record.classUnknown2;
1404 object.m_recordName = record.recordName;
1405 object.m_className = record.className;
1406 object.m_rawBytes = record.rawBytes;
1407 object.m_typedPayloadValidated = record.typedPayloadValidated;
1408 return object;
1409}
1410
1411bool rawCustomEntityOwnerFieldsAreReplayable(
1412 const DRW_UnsupportedObject &object, DRW::Version targetVersion) {
1413 if (!object.m_isEntity || !object.m_isCustomClass)
1414 return true;
1415 if (object.m_blockOwnerHandle != DRW::NoHandle &&
1416 object.m_parentHandle == DRW::NoHandle &&
1417 !isDwgSpaceBlockOwner(object.m_blockOwnerHandle)) {
1418 // The reverse BLOCK_RECORD edge has no corresponding owner field in
1419 // a user block's opaque body, so it cannot be repaired without a
1420 // typed encoder. Model/paper-space entries are the documented
1421 // ownerless entmode forms and are safe aliases.
1422 return false;
1423 }
1424 if (object.m_blockOwnerHandle != DRW::NoHandle &&
1425 object.m_parentHandle != DRW::NoHandle &&
1426 object.m_blockOwnerHandle != object.m_parentHandle &&
1427 targetVersion < DRW::AC1021) {
1428 // The legacy layouts do not expose a validated owner bit range.
1429 return false;
1430 }
1431 return true;
1432}
1433
1434bool isSunRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1435 return record.recordName == "SUN" || record.className == "AcDbSun";
1436}
1437
1438bool isLightRawEntity(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1439 return record.isEntity &&
1440 (record.recordName == "LIGHT" || record.className == "AcDbLight");
1441}
1442
1443bool isNavisworksModelRawEntity(
1444 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1445 return record.isEntity && (record.recordName == "NAVISWORKSMODEL" ||
1446 record.className == "AcDbNavisworksModel");
1447}
1448
1449bool isCameraRawEntity(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1450 return record.isEntity &&
1451 (record.recordName == "CAMERA" || record.className == "AcDbCamera");
1452}
1453
1454bool isNativeMetadataEntityRawObject(
1455 const LC_DwgAdvancedMetadata &metadata,
1456 const LC_DwgAdvancedMetadata::RawObjectRecord &raw) {
1457 if (raw.hasDataStorage || raw.handle == 0)
1458 return false;
1459
1460 const auto isRetained = [handle = raw.handle](const auto &record) {
1461 return record.handle == handle &&
1462 record.replayState ==
1463 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
1464 };
1465 if (isLightRawEntity(raw)) {
1466 return std::any_of(metadata.lights().cbegin(), metadata.lights().cend(),
1467 isRetained);
1468 }
1469 if (isNavisworksModelRawEntity(raw)) {
1470 return std::any_of(metadata.navisworksModels().cbegin(),
1471 metadata.navisworksModels().cend(), isRetained);
1472 }
1473 if (isCameraRawEntity(raw)) {
1474 return std::any_of(metadata.cameras().cbegin(), metadata.cameras().cend(),
1475 isRetained);
1476 }
1477 const bool isSectionObject =
1478 raw.isEntity &&
1479 (raw.recordName == "SECTIONOBJECT" ||
1480 raw.recordName == "SECTION_OBJECT" || raw.className == "AcDbSection");
1481 return isSectionObject &&
1482 std::any_of(metadata.sectionObjects().cbegin(),
1483 metadata.sectionObjects().cend(), isRetained);
1484}
1485
1486bool isSunStudyRawObject(
1487 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1488 return record.objectType == DRW_SunStudy::kDwgType ||
1489 record.recordName == "SUNSTUDY" ||
1490 record.recordName == "ACDBSUNSTUDY" ||
1491 record.className == "AcDbSunStudy";
1492}
1493
1494bool isMotionPathRawObject(
1495 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1496 return record.objectType == DRW_MotionPath::kDwgClassNum ||
1497 record.objectType == DRW_MotionPath::kDwgType ||
1498 record.recordName == "MOTIONPATH" ||
1499 record.recordName == "ACDBMOTIONPATH" ||
1500 record.className == "AcDbMotionPath";
1501}
1502
1503bool isCurvePathRawObject(
1504 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1505 return record.objectType == DRW_CurvePath::kDwgClassNum ||
1506 record.objectType == DRW_CurvePath::kDwgType ||
1507 record.recordName == "CURVEPATH" ||
1508 record.recordName == "ACDBCURVEPATH" ||
1509 record.className == "AcDbCurvePath";
1510}
1511
1512bool isPointPathRawObject(
1513 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1514 return record.objectType == DRW_PointPath::kDwgClassNum ||
1515 record.objectType == DRW_PointPath::kDwgType ||
1516 record.recordName == "POINTPATH" ||
1517 record.recordName == "ACDBPOINTPATH" ||
1518 record.className == "AcDbPointPath";
1519}
1520
1521bool isObjectPtrRawObject(
1522 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1523 return record.objectType == DRW_ObjectPtr::kDwgClassNum ||
1524 record.objectType == DRW_ObjectPtr::kDwgType ||
1525 record.recordName == "OBJECT_PTR" ||
1526 record.recordName == "OBJECTPTR" ||
1527 record.recordName == "ACDBOBJECTPTR" ||
1528 record.className == "AcDbObjectPtr";
1529}
1530
1531bool isPartialViewingIndexRawObject(
1532 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1533 return record.objectType == DRW_PartialViewingIndex::kDwgClassNum ||
1534 record.objectType == DRW_PartialViewingIndex::kDwgType ||
1535 record.recordName == "PARTIAL_VIEWING_INDEX" ||
1536 record.recordName == "PARTIALVIEWINGINDEX" ||
1537 record.recordName == "ACDBPARTIALVIEWINGINDEX" ||
1538 record.className == "AcDbPartialViewingIndex";
1539}
1540
1541bool isRenderSettingsRawObject(
1542 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1543 return record.recordName == "RENDERENTRY" ||
1544 record.recordName == "RENDERENVIRONMENT" ||
1545 record.recordName == "RENDERGLOBAL" ||
1546 record.recordName == "ACDBRENDERENTRY" ||
1547 record.recordName == "ACDBRENDERENVIRONMENT" ||
1548 record.recordName == "ACDBRENDERGLOBAL" ||
1549 record.recordName == "RAPIDRTRENDERSETTINGS" ||
1550 record.recordName == "ACDBRAPIDRTRENDERSETTINGS" ||
1551 record.recordName == "MENTALRAYRENDERSETTINGS" ||
1552 record.recordName == "ACDBMENTALRAYRENDERSETTINGS" ||
1553 record.className == "AcDbRenderEntry" ||
1554 record.className == "AcDbRenderEnvironment" ||
1555 record.className == "AcDbRenderGlobal" ||
1556 record.className == "AcDbRapidRTRenderSettings" ||
1557 record.className == "AcDbMentalRayRenderSettings";
1558}
1559
1560bool isVisualStyleRawObject(
1561 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1562 return record.objectType == DRW_VisualStyle::kDwgClassNum ||
1563 record.recordName == "VISUALSTYLE" ||
1564 record.recordName == "ACDB_VISUALSTYLE_CLASS" ||
1565 record.className == "AcDbVisualStyle";
1566}
1567
1568bool isTvDevicePropertiesRawObject(
1569 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1570 return record.objectType == DRW_TvDeviceProperties::kDwgClassNum ||
1571 record.recordName == "TVDEVICEPROPERTIES" ||
1572 record.className == "AcDbTvDeviceProperties";
1573}
1574
1575bool isVxControlRawObject(
1576 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1577 return record.objectType == DRW_VxControl::kDwgClassNum ||
1578 record.recordName == "VXCONTROL" ||
1579 record.recordName == "VX_CONTROL" ||
1580 record.className == "AcDbVxControl";
1581}
1582
1583bool isVxTableRecordRawObject(
1584 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1585 return record.objectType == DRW_VxTableRecord::kDwgClassNum ||
1586 record.recordName == "VXTABLERECORD" ||
1587 record.recordName == "VX_TABLE_RECORD" ||
1588 record.className == "AcDbVxTableRecord";
1589}
1590
1591bool isViewportEntityHeaderRawObject(
1592 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1593 return !record.isEntity &&
1594 record.objectType == DRW_ViewportEntityHeader::kDwgType;
1595}
1596
1597bool isViewportEntityHeaderControlRawObject(
1598 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1599 // Fixed type 70 is regenerated by dwgWriter15 only when typed type-71
1600 // records are available for the target AC1015 file. Otherwise the raw
1601 // carrier is the only representation and must remain replayable.
1602 return !record.isEntity &&
1603 record.objectType == DRW_ViewportEntityHeader::kDwgControlType;
1604}
1605
1606bool hasReplayableRawVxControl(const LC_DwgAdvancedMetadata &metadata,
1607 std::uint32_t handle,
1608 DRW::Version targetVersion) {
1609 for (const auto &record : metadata.rawObjects()) {
1610 if (record.handle == handle && record.version == targetVersion &&
1611 record.replayState ==
1612 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
1613 isVxControlRawObject(record))
1614 return true;
1615 }
1616 return false;
1617}
1618
1619bool hasReplayableRawVxTableRecord(const LC_DwgAdvancedMetadata &metadata,
1620 std::uint32_t handle,
1621 DRW::Version targetVersion) {
1622 for (const auto &record : metadata.rawObjects()) {
1623 if (record.handle == handle && record.version == targetVersion &&
1624 record.replayState ==
1625 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
1626 isVxTableRecordRawObject(record))
1627 return true;
1628 }
1629 return false;
1630}
1631
1632bool isAcDbPlaceholderRawObject(
1633 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1634 return record.objectType == 80 || record.recordName == "ACDBPLACEHOLDER" ||
1635 record.className == "AcDbPlaceHolder";
1636}
1637
1638bool isMLeaderStyleRawObject(
1639 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1640 return record.recordName == "MLEADERSTYLE" ||
1641 record.className == "AcDbMLeaderStyle";
1642}
1643
1644bool isTableStyleRawObject(
1645 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1646 // Unlike a fixed system object type (e.g. ACDBPLACEHOLDER's 80), a custom
1647 // class number such as DRW_TableStyle::kDwgClassNum (509) is assigned
1648 // per-file by that file's own CLASSES section (dwgreader.cpp) -- it is not
1649 // a stable cross-file identifier, and this codebase's own writer reuses it
1650 // as a bare default for unrelated raw objects (dwg_write_smoke_tests.cpp's
1651 // makeRawReplayObject()). Match on name/class only, matching every other
1652 // dynamic-class classifier in this file (isMLeaderStyleRawObject, etc.).
1653 return record.recordName == "TABLESTYLE" || record.className == "AcDbTableStyle";
1654}
1655
1656bool isOpaqueTableRawObject(
1657 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1658 return isTableStyleRawObject(record) || record.recordName == "CELLSTYLEMAP" ||
1659 record.className == "AcDbCellStyleMap" ||
1660 record.recordName == "ACAD_TABLE" ||
1661 record.recordName == "TABLECONTENT" ||
1662 record.className == "AcDbTable" ||
1663 record.className == "AcDbTableContent";
1664}
1665
1666// R2010 introduced the TABLESTYLE layout that the typed writer deliberately
1667// does not encode. Legacy table carriers retain their established raw fallback
1668// even when the partial parser cannot produce a semantic summary.
1669bool requiresDwgOpaqueTableAdmission(
1670 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
1671 return !record.isEntity && record.version >= DRW::AC1024 &&
1672 isOpaqueTableRawObject(record);
1673}
1674
1675using DwgOpaqueTableDigest = std::array<std::uint8_t, 32>;
1676
1677class DwgOpaqueTableHasher final {
1678public:
1679 explicit DwgOpaqueTableHasher(const char *domain)
1680 : m_hash(QCryptographicHash::Sha256) {
1681 appendString(domain);
1682 }
1683
1684 void appendUnsigned(std::uint64_t value) {
1685 std::array<char, sizeof(value)> bytes{};
1686 for (std::size_t index = 0; index < bytes.size(); ++index)
1687 bytes[index] = static_cast<char>((value >> (index * 8u)) & 0xffu);
1688 appendBytes(reinterpret_cast<const std::uint8_t *>(bytes.data()),
1689 bytes.size());
1690 }
1691
1692 void appendSigned(std::int64_t value) {
1693 appendUnsigned(static_cast<std::uint64_t>(value));
1694 }
1695
1696 void appendBoolean(bool value) {
1697 const std::uint8_t byte = value ? 1u : 0u;
1698 appendBytes(&byte, 1);
1699 }
1700
1701 void appendDouble(double value) {
1702 static_assert(sizeof(value) == sizeof(std::uint64_t));
1703 std::uint64_t bits = 0;
1704 std::memcpy(&bits, &value, sizeof(bits));
1705 appendUnsigned(bits);
1706 }
1707
1708 void appendString(const std::string &value) {
1709 appendUnsigned(value.size());
1710 appendBytes(reinterpret_cast<const std::uint8_t *>(value.data()),
1711 value.size());
1712 }
1713
1714 void appendBytes(const std::vector<std::uint8_t> &value) {
1715 appendUnsigned(value.size());
1716 appendBytes(value.data(), value.size());
1717 }
1718
1719 DwgOpaqueTableDigest finish() {
1720 const QByteArray result = m_hash.result();
1721 DwgOpaqueTableDigest digest{};
1722 if (result.size() != static_cast<int>(digest.size()))
1723 return digest;
1724 std::copy_n(reinterpret_cast<const std::uint8_t *>(result.constData()),
1725 digest.size(), digest.begin());
1726 return digest;
1727 }
1728
1729private:
1730 void appendBytes(const std::uint8_t *data, std::size_t size) {
1731 while (size != 0) {
1732 const std::size_t chunk = std::min(
1733 size, static_cast<std::size_t>(std::numeric_limits<int>::max()));
1734#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(6, 4, 0)((6<<16)|(4<<8)|(0))
1735 m_hash.addData(QByteArrayView(reinterpret_cast<const char *>(data),
1736 static_cast<qsizetype>(chunk)));
1737#else
1738 m_hash.addData(reinterpret_cast<const char *>(data),
1739 static_cast<int>(chunk));
1740#endif
1741 data += chunk;
1742 size -= chunk;
1743 }
1744 }
1745
1746 QCryptographicHash m_hash;
1747};
1748
1749void appendDwgOpaqueTableUnsignedList(
1750 DwgOpaqueTableHasher &hash, const std::vector<std::uint32_t> &values) {
1751 hash.appendUnsigned(values.size());
1752 for (const std::uint32_t value : values)
1753 hash.appendUnsigned(value);
1754}
1755
1756DwgOpaqueTableDigest dwgOpaqueTableDictionaryDigest(
1757 const LC_DwgAdvancedMetadata::DictionaryRecord &dictionary) {
1758 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.Dictionary.v1");
1759 hash.appendUnsigned(dictionary.handle);
1760 hash.appendUnsigned(dictionary.parentHandle);
1761 hash.appendSigned(dictionary.cloning);
1762 hash.appendSigned(dictionary.hardOwner);
1763 hash.appendString(dictionary.name);
1764 hash.appendUnsigned(dictionary.entries.size());
1765 for (const auto &entry : dictionary.entries) {
1766 hash.appendString(entry.name);
1767 hash.appendUnsigned(entry.handle);
1768 }
1769 hash.appendBoolean(dictionary.countCap.has_value());
1770 hash.appendUnsigned(dictionary.appData.size());
1771 hash.appendUnsigned(dictionary.extData.size());
1772 appendDwgOpaqueTableUnsignedList(hash, dictionary.reactorHandles);
1773 hash.appendUnsigned(dictionary.xDictHandle);
1774 hash.appendSigned(dictionary.numReactors);
1775 hash.appendUnsigned(dictionary.xDictFlag);
1776 hash.appendBoolean(dictionary.hasDsData);
1777 hash.appendUnsigned(static_cast<std::uint64_t>(dictionary.replayState));
1778 return hash.finish();
1779}
1780
1781bool isCanonicalDwgNamedDictionary(
1782 const LC_DwgAdvancedMetadata::DictionaryRecord &dictionary) {
1783 return dictionary.cloning == 1 && dictionary.hardOwner == 0 &&
1784 dictionary.name.empty() && !dictionary.countCap.has_value() &&
1785 dictionary.appData.empty() && dictionary.extData.empty() &&
1786 dictionary.reactorHandles.empty() && dictionary.xDictHandle == 0 &&
1787 dictionary.numReactors == 0 && dictionary.xDictFlag == 0 &&
1788 !dictionary.hasDsData &&
1789 dictionary.replayState ==
1790 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
1791}
1792
1793void appendDwgOpaqueTableSignedList(DwgOpaqueTableHasher &hash,
1794 const std::vector<int> &values) {
1795 hash.appendUnsigned(values.size());
1796 for (const int value : values)
1797 hash.appendSigned(value);
1798}
1799
1800void appendDwgOpaqueTableDoubleList(DwgOpaqueTableHasher &hash,
1801 const std::vector<double> &values) {
1802 hash.appendUnsigned(values.size());
1803 for (const double value : values)
1804 hash.appendDouble(value);
1805}
1806
1807void appendDwgOpaqueTableStringList(DwgOpaqueTableHasher &hash,
1808 const std::vector<std::string> &values) {
1809 hash.appendUnsigned(values.size());
1810 for (const std::string &value : values)
1811 hash.appendString(value);
1812}
1813
1814void appendDwgOpaqueTableCell(
1815 DwgOpaqueTableHasher &hash,
1816 const LC_DwgAdvancedMetadata::TableCellRecord &cell) {
1817 hash.appendSigned(cell.row);
1818 hash.appendSigned(cell.column);
1819 hash.appendSigned(cell.flags);
1820 hash.appendSigned(cell.type);
1821 hash.appendSigned(cell.styleId);
1822 hash.appendUnsigned(cell.overrideFlags);
1823 hash.appendUnsigned(cell.valueHandle);
1824 hash.appendUnsigned(cell.textStyleHandle);
1825 hash.appendUnsigned(cell.textStyleOverrideHandle);
1826 hash.appendUnsigned(cell.blockHandle);
1827 hash.appendUnsigned(cell.geometryHandle);
1828 hash.appendBoolean(cell.isMerged);
1829 hash.appendBoolean(cell.autoFit);
1830 hash.appendUnsigned(cell.contentCount);
1831 hash.appendUnsigned(cell.textContentCount);
1832 hash.appendUnsigned(cell.fieldContentCount);
1833 hash.appendUnsigned(cell.blockContentCount);
1834 hash.appendUnsigned(cell.attributeCount);
1835 hash.appendUnsigned(cell.attributeHandleCount);
1836 hash.appendUnsigned(cell.unknownContentCount);
1837 appendDwgOpaqueTableStringList(hash, cell.texts);
1838 appendDwgOpaqueTableStringList(hash, cell.attributeTexts);
1839 appendDwgOpaqueTableUnsignedList(hash, cell.contentHandles);
1840 appendDwgOpaqueTableUnsignedList(hash, cell.attributeHandles);
1841}
1842
1843DwgOpaqueTableDigest
1844dwgOpaqueTableRawDigest(const LC_DwgAdvancedMetadata::RawObjectRecord &raw) {
1845 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.Raw.v4");
1846 hash.appendUnsigned(static_cast<std::uint64_t>(raw.version));
1847 hash.appendSigned(raw.objectType);
1848 hash.appendUnsigned(raw.handle);
1849 hash.appendUnsigned(raw.parentHandle);
1850 hash.appendBoolean(drwDwgCommonLinkEvidenceValidated(raw.commonLinkEvidence));
1851 hash.appendUnsigned(static_cast<std::uint64_t>(raw.commonLinkEvidence));
1852 hash.appendUnsigned(raw.reactorHandles.size());
1853 for (const std::uint32_t handle : raw.reactorHandles)
1854 hash.appendUnsigned(handle);
1855 hash.appendUnsigned(raw.xDictHandle);
1856 hash.appendSigned(raw.numReactors);
1857 hash.appendUnsigned(raw.xDictFlag);
1858 hash.appendUnsigned(raw.blockOwnerHandle);
1859 hash.appendUnsigned(raw.bodyBitSize);
1860 hash.appendUnsigned(raw.objectOffset);
1861 hash.appendUnsigned(raw.objectSize);
1862 hash.appendBoolean(raw.isEntity);
1863 hash.appendBoolean(raw.isCustomClass);
1864 hash.appendUnsigned(static_cast<std::uint64_t>(raw.family));
1865 hash.appendString(raw.recordName);
1866 hash.appendString(raw.className);
1867 hash.appendBytes(raw.rawBytes);
1868 hash.appendBoolean(raw.typedPayloadValidated);
1869 hash.appendUnsigned(static_cast<std::uint64_t>(raw.replayState));
1870 hash.appendBoolean(raw.hasDataStorage);
1871 hash.appendBoolean(raw.hasClassDefinition);
1872 hash.appendUnsigned(raw.classProxyFlag);
1873 hash.appendString(raw.classAppName);
1874 hash.appendBoolean(raw.classWasProxy);
1875 hash.appendUnsigned(raw.classEntityFlagRaw);
1876 hash.appendSigned(raw.classDwgVersion);
1877 hash.appendSigned(raw.classMaintenanceVersion);
1878 hash.appendSigned(raw.classUnknown1);
1879 hash.appendSigned(raw.classUnknown2);
1880 return hash.finish();
1881}
1882
1883DwgOpaqueTableDigest
1884dwgOpaqueTableSummaryDigest(const LC_DwgAdvancedMetadata::TableRecord &table) {
1885 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.TableSummary.v1");
1886 hash.appendUnsigned(table.handle);
1887 hash.appendUnsigned(table.parentHandle);
1888 hash.appendString(table.recordName);
1889 hash.appendUnsigned(table.tableStyleHandle);
1890 hash.appendSigned(table.rowCount);
1891 hash.appendSigned(table.columnCount);
1892 hash.appendUnsigned(table.cellCount);
1893 hash.appendUnsigned(table.contentCount);
1894 hash.appendUnsigned(table.textContentCount);
1895 hash.appendUnsigned(table.fieldContentCount);
1896 hash.appendUnsigned(table.blockContentCount);
1897 hash.appendUnsigned(table.attributeCount);
1898 hash.appendUnsigned(table.unknownContentCount);
1899 hash.appendUnsigned(table.valueHandleCount);
1900 hash.appendUnsigned(table.blockHandleCount);
1901 hash.appendUnsigned(table.fieldHandleCount);
1902 hash.appendUnsigned(table.attributeHandleCount);
1903 hash.appendUnsigned(table.textStyleHandleCount);
1904 hash.appendUnsigned(table.lineTypeHandleCount);
1905 hash.appendUnsigned(table.mergedRangeCount);
1906 hash.appendUnsigned(table.overrideCellCount);
1907 hash.appendUnsigned(table.geometryCellCount);
1908 hash.appendUnsigned(table.rowStyleCount);
1909 hash.appendUnsigned(table.cellStyleCount);
1910 hash.appendUnsigned(table.borderCount);
1911 hash.appendUnsigned(table.m_tableContentFormatCount);
1912 hash.appendUnsigned(table.m_tableNamedCellStyleCount);
1913 hash.appendUnsigned(table.m_tableVisibleBorderCount);
1914 hash.appendUnsigned(table.m_tableMarginStyleCount);
1915 hash.appendUnsigned(table.m_unknownRangeCount);
1916 hash.appendUnsigned(table.m_incompleteRangeCount);
1917 hash.appendUnsigned(table.m_overrideMaskCount);
1918 hash.appendUnsigned(table.m_breakRangeCount);
1919 hash.appendUnsigned(table.m_tableGeometryTailRangeCount);
1920 hash.appendUnsigned(table.m_fallbackGridEntityCount);
1921 hash.appendUnsigned(table.m_fallbackTextEntityCount);
1922 hash.appendUnsigned(table.m_fallbackPlaceholderEntityCount);
1923 hash.appendUnsigned(table.m_fallbackUnresolvedTextStyleCount);
1924 hash.appendUnsigned(table.m_fallbackClampedDimensionCount);
1925 hash.appendSigned(table.m_tableFlowDirection);
1926 hash.appendSigned(table.m_tableFlags);
1927 hash.appendDouble(table.m_tableHorizontalCellMargin);
1928 hash.appendDouble(table.m_tableVerticalCellMargin);
1929 hash.appendUnsigned(table.m_tableUnknownHandle);
1930 hash.appendBoolean(table.titleSuppressed);
1931 hash.appendBoolean(table.headerSuppressed);
1932 hash.appendBoolean(table.hasTextContent);
1933 hash.appendBoolean(table.hasBlockContent);
1934 hash.appendBoolean(table.rawReplayAvailable);
1935 hash.appendBoolean(table.semanticParsed);
1936 hash.appendBoolean(table.styleResolved);
1937 hash.appendBoolean(table.fallbackRendered);
1938 hash.appendBoolean(table.fallbackInvalidated);
1939 appendDwgOpaqueTableDoubleList(hash, table.columnWidths);
1940 appendDwgOpaqueTableDoubleList(hash, table.rowHeights);
1941 appendDwgOpaqueTableSignedList(hash, table.cellStyleIds);
1942 appendDwgOpaqueTableStringList(hash, table.cellTexts);
1943 appendDwgOpaqueTableStringList(hash, table.attributeTexts);
1944 appendDwgOpaqueTableUnsignedList(hash, table.valueHandles);
1945 appendDwgOpaqueTableUnsignedList(hash, table.blockHandles);
1946 appendDwgOpaqueTableUnsignedList(hash, table.fieldHandles);
1947 appendDwgOpaqueTableUnsignedList(hash, table.attributeHandles);
1948 appendDwgOpaqueTableUnsignedList(hash, table.textStyleHandles);
1949 appendDwgOpaqueTableUnsignedList(hash, table.lineTypeHandles);
1950 appendDwgOpaqueTableUnsignedList(hash, table.geometryHandles);
1951 appendDwgOpaqueTableSignedList(hash, table.m_tableStyleIds);
1952 appendDwgOpaqueTableStringList(hash, table.m_tableStyleNames);
1953 appendDwgOpaqueTableDoubleList(hash, table.m_tableTextHeights);
1954 appendDwgOpaqueTableSignedList(hash, table.m_tableAlignments);
1955 appendDwgOpaqueTableSignedList(hash, table.m_tableColors);
1956 hash.appendBoolean(table.nativeTableStyle.has_value());
1957 hash.appendUnsigned(table.mergedRanges.size());
1958 for (const auto &range : table.mergedRanges) {
1959 hash.appendSigned(range.topRow);
1960 hash.appendSigned(range.leftColumn);
1961 hash.appendSigned(range.bottomRow);
1962 hash.appendSigned(range.rightColumn);
1963 }
1964 hash.appendUnsigned(table.cells.size());
1965 for (const auto &cell : table.cells)
1966 appendDwgOpaqueTableCell(hash, cell);
1967 hash.appendBoolean(table.hasDsData);
1968 hash.appendUnsigned(static_cast<std::uint64_t>(table.replayState));
1969 return hash.finish();
1970}
1971
1972std::size_t
1973dwgOpaqueTableRelationCount(const LC_DwgAdvancedMetadata::TableRecord &table) {
1974 return 2u + table.valueHandles.size() + table.blockHandles.size() +
1975 table.fieldHandles.size() + table.attributeHandles.size() +
1976 table.geometryHandles.size() + table.textStyleHandles.size() +
1977 table.lineTypeHandles.size();
1978}
1979
1980DwgOpaqueTableDigest dwgOpaqueTableSummaryDigest(
1981 const LC_DwgAdvancedMetadata::CellStyleMapRecord &map) {
1982 DwgOpaqueTableHasher hash("LibreCAD.DwgOpaqueTable.CellStyleMapSummary.v1");
1983 hash.appendUnsigned(map.handle);
1984 hash.appendUnsigned(map.parentHandle);
1985 hash.appendUnsigned(map.m_cellStyleCount);
1986 hash.appendUnsigned(map.m_borderCount);
1987 hash.appendUnsigned(map.m_contentFormatCount);
1988 hash.appendUnsigned(map.m_namedCellStyleCount);
1989 hash.appendUnsigned(map.m_visibleBorderCount);
1990 hash.appendUnsigned(map.m_marginStyleCount);
1991 hash.appendUnsigned(map.m_unknownRangeCount);
1992 hash.appendUnsigned(map.m_incompleteRangeCount);
1993 appendDwgOpaqueTableSignedList(hash, map.m_styleIds);
1994 appendDwgOpaqueTableSignedList(hash, map.m_styleClasses);
1995 appendDwgOpaqueTableStringList(hash, map.m_styleNames);
1996 appendDwgOpaqueTableUnsignedList(hash, map.m_textStyleHandles);
1997 appendDwgOpaqueTableUnsignedList(hash, map.m_lineTypeHandles);
1998 appendDwgOpaqueTableDoubleList(hash, map.m_textHeights);
1999 appendDwgOpaqueTableSignedList(hash, map.m_alignments);
2000 appendDwgOpaqueTableSignedList(hash, map.m_colors);
2001 hash.appendUnsigned(static_cast<std::uint64_t>(map.replayState));
2002 return hash.finish();
2003}
2004
2005std::size_t dwgOpaqueTableRelationCount(
2006 const LC_DwgAdvancedMetadata::CellStyleMapRecord &map) {
2007 return map.m_textStyleHandles.size() + map.m_lineTypeHandles.size();
2008}
2009
2010bool isMaterialRawObject(
2011 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2012 return record.recordName == "MATERIAL" || record.className == "AcDbMaterial";
2013}
2014
2015bool isLightListRawObject(
2016 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2017 return record.recordName == "LIGHTLIST" ||
2018 record.recordName == "ACDBLIGHTLIST" ||
2019 record.className == "AcDbLightList";
2020}
2021
2022bool isVbaProjectRawObject(
2023 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2024 return record.recordName == "VBA_PROJECT" ||
2025 record.className == "AcDbVbaProject";
2026}
2027
2028bool isBackgroundRawObject(
2029 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2030 return record.objectType == DRW_Background::kDwgClassNumSolid ||
2031 record.objectType == DRW_Background::kDwgClassNumGradient ||
2032 record.objectType == DRW_Background::kDwgClassNumGroundPlane ||
2033 record.objectType == DRW_Background::kDwgClassNumIbl ||
2034 record.objectType == DRW_Background::kDwgClassNumImage ||
2035 record.objectType == DRW_Background::kDwgClassNumSkylight ||
2036 record.recordName == "SOLIDBACKGROUND" ||
2037 record.recordName == "SOLID_BACKGROUND" ||
2038 record.recordName == "GRADIENTBACKGROUND" ||
2039 record.recordName == "GRADIENT_BACKGROUND" ||
2040 record.recordName == "GROUNDPLANEBACKGROUND" ||
2041 record.recordName == "GROUNDPLANE_BACKGROUND" ||
2042 record.recordName == "IMAGEBACKGROUND" ||
2043 record.recordName == "IMAGE_BACKGROUND" ||
2044 record.recordName == "IBLBACKGROUND" ||
2045 record.recordName == "IBL_BACKGROUND" ||
2046 record.recordName == "RAPIDRTRENDERENVIRONMENT" ||
2047 record.recordName == "SKYLIGHTBACKGROUND" ||
2048 record.recordName == "SKYLIGHT_BACKGROUND" ||
2049 record.className == "AcDbSolidBackground" ||
2050 record.className == "AcDbGradientBackground" ||
2051 record.className == "AcDbGroundPlaneBackground" ||
2052 record.className == "AcDbImageBackground" ||
2053 record.className == "AcDbIBLBackground" ||
2054 record.className == "AcDbSkyBackground";
2055}
2056
2057bool isMLineStyleRawObject(
2058 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2059 return record.objectType == 73 || record.recordName == "MLINESTYLE" ||
2060 record.className == "AcDbMlineStyle";
2061}
2062
2063bool isDbColorRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2064 return record.objectType == DRW_DbColor::kDwgType ||
2065 record.recordName == "DBCOLOR" || record.className == "AcDbColor";
2066}
2067
2068bool isPlotSettingsRawObject(
2069 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2070 return record.recordName == "PLOTSETTINGS" ||
2071 record.className == "AcDbPlotSettings";
2072}
2073
2074bool isDimensionAssociationRawObject(
2075 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2076 return record.objectType == DRW_DimensionAssociation::kDwgClassNum ||
2077 record.recordName == "DIMASSOC" || record.className == "AcDbDimAssoc";
2078}
2079
2080bool isEvaluationGraphRawObject(
2081 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2082 return record.objectType == DRW_EvaluationGraph::kDwgClassNum ||
2083 record.recordName == "EVALUATION_GRAPH" ||
2084 record.recordName == "ACAD_EVALUATION_GRAPH" ||
2085 record.className == "AcDbEvalGraph";
2086}
2087
2088bool isBlockRepresentationDataRawObject(
2089 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2090 return record.objectType == DRW_BlockRepresentationData::kDwgClassNum ||
2091 record.recordName == "BLOCKREPRESENTATIONDATA" ||
2092 record.recordName == "ACDB_BLOCKREPRESENTATION_DATA" ||
2093 record.className == "AcDbBlockRepresentationData";
2094}
2095
2096bool hasReplayableRawBlockRepresentationData(
2097 const LC_DwgAdvancedMetadata &metadata, std::uint32_t handle,
2098 DRW::Version targetVersion) {
2099 if (handle == 0)
2100 return false;
2101 return std::any_of(
2102 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
2103 [handle, targetVersion](const auto &record) {
2104 return record.handle == handle && record.version == targetVersion &&
2105 isBlockRepresentationDataRawObject(record) &&
2106 !record.rawBytes.empty() &&
2107 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2108 LC_DwgAdvancedMetadata::ReplayBlocker::None;
2109 });
2110}
2111
2112// DICTIONARY raw-object predicate. Fixed type 42 is the universal ODA
2113// identifier; recordName / className fallbacks cover custom-class-emitted
2114// dictionaries (rare but spec-allowed).
2115bool isDictionaryRawObject(
2116 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2117 return record.objectType == 42 || record.recordName == "DICTIONARY" ||
2118 record.className == "AcDbDictionary";
2119}
2120
2121// XRECORD raw-object predicate. Fixed type 79 (0x4f) is the universal ODA
2122// identifier; the custom-class branch in dwgreader still emits raw bytes
2123// under the AcDbXrecord recName/className for non-fixed XRECORDs.
2124bool isXRecordRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2125 return record.objectType == 79 || record.recordName == "XRECORD" ||
2126 record.className == "AcDbXrecord";
2127}
2128
2129// LAYOUT raw-object predicate. Fixed type 82 + recordName/className
2130// fallback for vendor extensions.
2131bool isLayoutRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2132 return record.objectType == 82 || record.recordName == "LAYOUT" ||
2133 record.className == "AcDbLayout";
2134}
2135
2136// GROUP raw-object predicate. Fixed type 72 (ODA §20.4.72) + recordName/
2137// className fallback for vendor extensions.
2138bool isGroupRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2139 return record.objectType == 72 || record.recordName == "GROUP" ||
2140 record.className == "AcDbGroup";
2141}
2142
2143// RASTERVARIABLES raw-object predicate. Custom-class object (no fixed
2144// ODA type) — keyed on recordName/className per dwgreader's dispatch.
2145bool isRasterVariablesRawObject(
2146 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2147 return record.recordName == "RASTERVARIABLES" ||
2148 record.className == "AcDbRasterVariables";
2149}
2150
2151bool isWipeoutVariablesRawObject(
2152 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2153 return record.recordName == "WIPEOUTVARIABLES" ||
2154 record.className == "AcDbWipeoutVariables";
2155}
2156
2157// GEODATA raw-object predicate. Custom-class (no fixed ODA type).
2158bool isGeoDataRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2159 return record.recordName == "GEODATA" || record.className == "AcDbGeoData";
2160}
2161
2162// SPATIAL_FILTER raw-object predicate. Custom-class (no fixed ODA type).
2163bool isSpatialFilterRawObject(
2164 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2165 return record.recordName == "SPATIAL_FILTER" ||
2166 record.className == "AcDbSpatialFilter";
2167}
2168
2169// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
2170// (no fixed ODA type); recordName / className strings must match the
2171// dwgreader.cpp dispatch (case-sensitive).
2172bool isScaleRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2173 return record.recordName == "SCALE" || record.className == "AcDbScale";
2174}
2175
2176bool isIDBufferRawObject(
2177 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2178 return record.recordName == "IDBUFFER" || record.className == "AcDbIdBuffer";
2179}
2180
2181bool isLayerIndexRawObject(
2182 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2183 return record.recordName == "LAYER_INDEX" ||
2184 record.className == "AcDbLayerIndex";
2185}
2186
2187bool isSpatialIndexRawObject(
2188 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2189 return record.recordName == "SPATIAL_INDEX" ||
2190 record.className == "AcDbSpatialIndex";
2191}
2192
2193bool isDictionaryVarRawObject(
2194 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2195 return record.recordName == "DICTIONARYVAR" ||
2196 record.className == "AcDbDictionaryVar";
2197}
2198
2199// PR 8d.2b — four larger no-storage OBJECTS families. recordName /
2200// className strings must match dwgreader.cpp dispatch (case-sensitive).
2201bool isDictionaryWithDefaultRawObject(
2202 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2203 return record.recordName == "ACDBDICTIONARYWDFLT" ||
2204 record.recordName == "DICTIONARYWDFLT" ||
2205 record.className == "AcDbDictionaryWithDefault";
2206}
2207
2208bool isSortEntsTableRawObject(
2209 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2210 return record.recordName == "SORTENTSTABLE" ||
2211 record.className == "AcDbSortentsTable";
2212}
2213
2214bool isFixedStructuralDwgHandle(std::uint32_t handle);
2215
2216bool hasReplayableRawSortEntsTable(
2217 const LC_DwgAdvancedMetadata &metadata, std::uint32_t handle,
2218 DRW::Version targetVersion) {
2219 return std::any_of(
2220 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
2221 [handle, targetVersion](const auto &record) {
2222 return record.handle == handle && !record.isEntity &&
2223 record.isCustomClass && isSortEntsTableRawObject(record) &&
2224 record.version == targetVersion &&
2225 record.replayState ==
2226 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
2227 !record.rawBytes.empty() &&
2228 !isFixedStructuralDwgHandle(record.handle) &&
2229 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2230 LC_DwgAdvancedMetadata::ReplayBlocker::None;
2231 });
2232}
2233
2234bool isFieldListRawObject(
2235 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2236 return record.recordName == "FIELDLIST" &&
2237 record.className == "AcDbFieldList";
2238}
2239
2240bool isFieldRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2241 return record.recordName == "FIELD" && record.className == "AcDbField";
2242}
2243
2244bool isUnderlayDefinitionRawObject(
2245 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2246 return record.recordName == "PDFDEFINITION" ||
2247 record.recordName == "DGNDEFINITION" ||
2248 record.recordName == "DWFDEFINITION" ||
2249 record.className == "AcDbPdfDefinition" ||
2250 record.className == "AcDbDgnDefinition" ||
2251 record.className == "AcDbDwfDefinition";
2252}
2253
2254bool isPointCloudDefinitionRawObject(
2255 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2256 return record.recordName == "POINTCLOUDDEFINITION" ||
2257 record.recordName == "POINTCLOUDDEFINITIONEX" ||
2258 record.recordName == "POINTCLOUDDEFREACTOR" ||
2259 record.recordName == "POINTCLOUDDEFREACTOREX" ||
2260 record.className == "AcDbPointCloudDef" ||
2261 record.className == "AcDbPointCloudDefEx" ||
2262 record.className == "AcDbPointCloudDefReactor" ||
2263 record.className == "AcDbPointCloudDefReactorEx";
2264}
2265
2266bool isNavisworksModelDefinitionRawObject(
2267 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2268 return record.recordName == "NAVISWORKSMODELDEF" ||
2269 record.className == "AcDbNavisworksModelDef";
2270}
2271
2272bool isPointCloudColorMapRawObject(
2273 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2274 return record.recordName == "POINTCLOUDCOLORMAP" ||
2275 record.className == "AcDbPointCloudColorMap";
2276}
2277
2278bool isSectionRawObject(const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2279 return record.objectType == DRW_Section::kDwgClassNumManager ||
2280 record.objectType == DRW_Section::kDwgClassNumSettings ||
2281 record.recordName == "SECTION_MANAGER" ||
2282 record.recordName == "SECTIONMANAGER" ||
2283 record.recordName == "SECTION_SETTINGS" ||
2284 record.recordName == "SECTIONSETTINGS" ||
2285 record.className == "AcDbSectionManager" ||
2286 record.className == "AcDbSectionSettings";
2287}
2288
2289bool isSectionObjectRawEntity(
2290 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2291 return record.isEntity && (record.recordName == "SECTIONOBJECT" ||
2292 record.recordName == "SECTION_OBJECT" ||
2293 record.className == "AcDbSection");
2294}
2295
2296bool hasReplayableSectionObject(const LC_DwgAdvancedMetadata &metadata,
2297 std::uint32_t handle) {
2298 if (handle == 0)
2299 return false;
2300 return std::any_of(
2301 metadata.sectionObjects().cbegin(), metadata.sectionObjects().cend(),
2302 [handle](const auto &record) {
2303 return record.handle == handle &&
2304 record.replayState ==
2305 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
2306 });
2307}
2308
2309const char *pointCloudDefinitionRecordName(int kind) {
2310 switch (static_cast<DRW_PointCloudDef::Kind>(kind)) {
2311 case DRW_PointCloudDef::Definition:
2312 return "POINTCLOUDDEFINITION";
2313 case DRW_PointCloudDef::DefinitionEx:
2314 return "POINTCLOUDDEFINITIONEX";
2315 case DRW_PointCloudDef::Reactor:
2316 return "POINTCLOUDDEFREACTOR";
2317 case DRW_PointCloudDef::ReactorEx:
2318 return "POINTCLOUDDEFREACTOREX";
2319 }
2320 return nullptr;
2321}
2322
2323bool hasReplayableRawMLeaderStyle(const LC_DwgAdvancedMetadata &metadata,
2324 std::uint32_t handle,
2325 DRW::Version targetVersion) {
2326 if (handle == 0)
2327 return false;
2328 for (const auto &record : metadata.rawObjects()) {
2329 if (record.handle == handle && record.version == targetVersion &&
2330 isMLeaderStyleRawObject(record) &&
2331 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2332 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2333 return true;
2334 }
2335 }
2336 return false;
2337}
2338
2339bool hasReplayableRawSunStudy(const LC_DwgAdvancedMetadata &metadata,
2340 std::uint32_t handle,
2341 DRW::Version targetVersion) {
2342 if (handle == 0)
2343 return false;
2344 for (const auto &record : metadata.rawObjects()) {
2345 if (record.handle == handle && record.version == targetVersion &&
2346 isSunStudyRawObject(record) &&
2347 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2348 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2349 return true;
2350 }
2351 }
2352 return false;
2353}
2354
2355bool hasReplayableRawMotionPath(const LC_DwgAdvancedMetadata &metadata,
2356 std::uint32_t handle,
2357 DRW::Version targetVersion) {
2358 if (handle == 0)
2359 return false;
2360 for (const auto &record : metadata.rawObjects()) {
2361 if (record.handle == handle && record.version == targetVersion &&
2362 isMotionPathRawObject(record) &&
2363 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2364 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2365 return true;
2366 }
2367 return false;
2368}
2369
2370bool hasReplayableRawCurvePath(const LC_DwgAdvancedMetadata &metadata,
2371 std::uint32_t handle,
2372 DRW::Version targetVersion) {
2373 if (handle == 0)
2374 return false;
2375 for (const auto &record : metadata.rawObjects()) {
2376 if (record.handle == handle && record.version == targetVersion &&
2377 isCurvePathRawObject(record) &&
2378 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2379 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2380 return true;
2381 }
2382 return false;
2383}
2384
2385bool hasReplayableRawPointPath(const LC_DwgAdvancedMetadata &metadata,
2386 std::uint32_t handle,
2387 DRW::Version targetVersion) {
2388 if (handle == 0)
2389 return false;
2390 for (const auto &record : metadata.rawObjects()) {
2391 if (record.handle == handle && record.version == targetVersion &&
2392 isPointPathRawObject(record) &&
2393 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2394 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2395 return true;
2396 }
2397 return false;
2398}
2399
2400bool hasReplayableRawObjectPtr(const LC_DwgAdvancedMetadata &metadata,
2401 std::uint32_t handle,
2402 DRW::Version targetVersion) {
2403 if (handle == 0)
2404 return false;
2405 for (const auto &record : metadata.rawObjects()) {
2406 if (record.handle == handle && record.version == targetVersion &&
2407 isObjectPtrRawObject(record) &&
2408 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2409 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2410 return true;
2411 }
2412 return false;
2413}
2414
2415bool hasReplayableRawPartialViewingIndex(const LC_DwgAdvancedMetadata &metadata,
2416 std::uint32_t handle,
2417 DRW::Version targetVersion) {
2418 if (handle == 0)
2419 return false;
2420 for (const auto &record : metadata.rawObjects()) {
2421 if (record.handle == handle && record.version == targetVersion &&
2422 isPartialViewingIndexRawObject(record) &&
2423 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2424 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2425 return true;
2426 }
2427 return false;
2428}
2429
2430bool hasReplayableRawRenderSettings(const LC_DwgAdvancedMetadata &metadata,
2431 std::uint32_t handle,
2432 DRW_RenderSettings::Kind kind,
2433 DRW::Version targetVersion) {
2434 if (handle == 0)
2435 return false;
2436 const auto matchesKind =
2437 [kind](const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2438 switch (kind) {
2439 case DRW_RenderSettings::Settings:
2440 return record.recordName == "RENDERSETTINGS" ||
2441 record.recordName == "ACDBRENDERSETTINGS" ||
2442 record.className == "AcDbRenderSettings";
2443 case DRW_RenderSettings::Entry:
2444 return record.recordName == "RENDERENTRY" ||
2445 record.recordName == "ACDBRENDERENTRY" ||
2446 record.className == "AcDbRenderEntry";
2447 case DRW_RenderSettings::Environment:
2448 return record.recordName == "RENDERENVIRONMENT" ||
2449 record.recordName == "ACDBRENDERENVIRONMENT" ||
2450 record.className == "AcDbRenderEnvironment";
2451 case DRW_RenderSettings::Global:
2452 return record.recordName == "RENDERGLOBAL" ||
2453 record.recordName == "ACDBRENDERGLOBAL" ||
2454 record.className == "AcDbRenderGlobal";
2455 case DRW_RenderSettings::RapidRT:
2456 return record.recordName == "RAPIDRTRENDERSETTINGS" ||
2457 record.recordName == "ACDBRAPIDRTRENDERSETTINGS" ||
2458 record.className == "AcDbRapidRTRenderSettings";
2459 case DRW_RenderSettings::MentalRay:
2460 return record.recordName == "MENTALRAYRENDERSETTINGS" ||
2461 record.recordName == "ACDBMENTALRAYRENDERSETTINGS" ||
2462 record.className == "AcDbMentalRayRenderSettings";
2463 default:
2464 return false;
2465 }
2466 };
2467 for (const auto &record : metadata.rawObjects()) {
2468 if (record.handle == handle && record.version == targetVersion &&
2469 matchesKind(record) &&
2470 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2471 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2472 return true;
2473 }
2474 return false;
2475}
2476
2477bool hasReplayableRawVisualStyle(const LC_DwgAdvancedMetadata &metadata,
2478 std::uint32_t handle,
2479 DRW::Version targetVersion) {
2480 if (handle == 0)
2481 return false;
2482 for (const auto &record : metadata.rawObjects()) {
2483 if (record.handle == handle && record.version == targetVersion &&
2484 isVisualStyleRawObject(record) &&
2485 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2486 LC_DwgAdvancedMetadata::ReplayBlocker::None)
2487 return true;
2488 }
2489 return false;
2490}
2491
2492bool hasReplayableRawMaterial(const LC_DwgAdvancedMetadata &metadata,
2493 std::uint32_t handle,
2494 DRW::Version targetVersion) {
2495 if (handle == 0)
2496 return false;
2497 for (const auto &record : metadata.rawObjects()) {
2498 if (record.handle == handle && record.version == targetVersion &&
2499 isMaterialRawObject(record) &&
2500 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2501 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2502 return true;
2503 }
2504 }
2505 return false;
2506}
2507
2508bool hasReplayableRawSpatialIndex(const LC_DwgAdvancedMetadata &metadata,
2509 std::uint32_t handle,
2510 DRW::Version targetVersion) {
2511 if (handle == 0)
2512 return false;
2513 for (const auto &record : metadata.rawObjects()) {
2514 if (record.handle == handle && record.version == targetVersion &&
2515 isSpatialIndexRawObject(record) &&
2516 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2517 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2518 return true;
2519 }
2520 }
2521 return false;
2522}
2523
2524template <typename StableReference>
2525bool hasReplayableRawLightList(const LC_DwgAdvancedMetadata &metadata,
2526 std::uint32_t handle, DRW::Version targetVersion,
2527 StableReference stableReference) {
2528 if (handle == 0)
2529 return false;
2530 const auto lightListCount = std::count_if(
2531 metadata.lightLists().cbegin(), metadata.lightLists().cend(),
2532 [handle](const auto &record) { return record.handle == handle; });
2533 if (lightListCount != 1)
2534 return false;
2535 const auto lightList = std::find_if(
2536 metadata.lightLists().cbegin(), metadata.lightLists().cend(),
2537 [handle](const auto &record) { return record.handle == handle; });
2538 if (lightList == metadata.lightLists().cend())
2539 return false;
2540 const auto rawCarrierCount = std::count_if(
2541 metadata.rawObjects().cbegin(), metadata.rawObjects().cend(),
2542 [handle, targetVersion](const auto &record) {
2543 return !record.isEntity && record.handle == handle &&
2544 record.version == targetVersion &&
2545 isLightListRawObject(record) &&
2546 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2547 LC_DwgAdvancedMetadata::ReplayBlocker::None;
2548 });
2549 if (rawCarrierCount != 1 || !stableReference(lightList->parentHandle) ||
2550 !stableReference(lightList->xDictHandle) ||
2551 !std::all_of(lightList->reactorHandles.cbegin(),
2552 lightList->reactorHandles.cend(), stableReference)) {
2553 return false;
2554 }
2555
2556 for (const auto &light : lightList->lights) {
2557 if (light.handle == 0 || !stableReference(light.handle))
2558 return false;
2559 const auto lightRecord =
2560 std::find_if(metadata.lights().cbegin(), metadata.lights().cend(),
2561 [&light](const auto &candidate) {
2562 return candidate.handle == light.handle;
2563 });
2564 if (lightRecord == metadata.lights().cend() ||
2565 lightRecord->replayState !=
2566 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
2567 return false;
2568 }
2569 return true;
2570}
2571
2572bool hasReplayableRawVbaProject(const LC_DwgAdvancedMetadata &metadata,
2573 std::uint32_t handle,
2574 DRW::Version targetVersion) {
2575 if (handle == 0)
2576 return false;
2577 for (const auto &record : metadata.rawObjects()) {
2578 if (record.handle == handle && record.version == targetVersion &&
2579 isVbaProjectRawObject(record) &&
2580 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2581 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2582 return true;
2583 }
2584 }
2585 return false;
2586}
2587
2588bool hasReplayableRawBackground(const LC_DwgAdvancedMetadata &metadata,
2589 std::uint32_t handle,
2590 DRW::Version targetVersion) {
2591 if (handle == 0)
2592 return false;
2593 for (const auto &record : metadata.rawObjects()) {
2594 if (record.handle == handle && record.version == targetVersion &&
2595 isBackgroundRawObject(record) &&
2596 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
2597 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
2598 return true;
2599 }
2600 }
2601 return false;
2602}
2603
2604// Handles the DWG writer emits at FIXED values (HandleAllocator::seedReserved):
2605// control objects 0x01-0x0B, the canonical root dictionaries 0x0C-0x0D,
2606// canonical table records 0x0F-0x18 and the model/paper space BLOCK/ENDBLK
2607// entities 0x1B-0x1E. Real source files
2608// (notably AC1021+) reuse these very numbers for ordinary preserved OBJECTS
2609// (placeholders, dictionaries, ...); emitting both sides duplicates the
2610// object-map entry and writeDwgHandles() aborts the whole save (BAD_OPEN,
2611// zero-byte file). Typed preserved objects are therefore remapped to fresh
2612// handles above the preserved high-water mark (fileExport builds
2613// m_dwgWriteHandleRemap, writeObjects applies it); raw verbatim objects
2614// cannot be rewritten and their replay is blocked instead. Mirrors the DXF
2615// path's kFixedStructural remap in fileExport.
2616bool isFixedStructuralDwgHandle(std::uint32_t h) {
2617 static const std::set<std::uint32_t> kFixed = {
2618 DRW::DwgBlockControlHandle,
2619 DRW::DwgLayerControlHandle,
2620 DRW::DwgStyleControlHandle,
2621 DRW::DwgLtypeControlHandle,
2622 DRW::DwgViewControlHandle,
2623 DRW::DwgUcsControlHandle,
2624 DRW::DwgVportControlHandle,
2625 DRW::DwgAppIdControlHandle,
2626 DRW::DwgDimstyleControlHandle,
2627 DRW::DwgViewportEntityHeaderControlHandle,
2628 DRW::DwgNamedObjectsDictionaryHandle,
2629 DRW::DwgAcadGroupDictionaryHandle,
2630 DRW::DwgLtypeByBlockHandle,
2631 DRW::DwgLtypeByLayerHandle,
2632 DRW::DwgLtypeContinuousHandle,
2633 DRW::DwgLayer0Handle,
2634 DRW::DwgStandardTextStyleHandle,
2635 DRW::DwgAcadAppIdHandle,
2636 DRW::DwgStandardDimstyleHandle,
2637 DRW::DwgActiveVportHandle,
2638 DRW::DwgModelSpaceBlockRecordHandle,
2639 DRW::DwgPaperSpaceBlockRecordHandle,
2640 DRW::DwgModelSpaceBlockEntityHandle,
2641 DRW::DwgModelSpaceEndBlockEntityHandle,
2642 DRW::DwgPaperSpaceBlockEntityHandle,
2643 DRW::DwgPaperSpaceEndBlockEntityHandle};
2644 return kFixed.count(h) != 0;
2645}
2646
2647bool isRawViewportEntityHeaderControl(
2648 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
2649 return !record.isEntity &&
2650 record.objectType == DRW_ViewportEntityHeader::kDwgControlType &&
2651 record.handle == DRW_ViewportEntityHeader::kDwgControlHandle;
2652}
2653
2654DRW_AcDbPlaceholder placeholderFromMetadata(
2655 const LC_DwgAdvancedMetadata::PlaceholderRecord &record) {
2656 DRW_AcDbPlaceholder placeholder;
2657 placeholder.handle = record.handle;
2658 placeholder.parentHandle = record.parentHandle;
2659 placeholder.appData = record.appData;
2660 restoreTableEntryExtData(placeholder, record.extData);
2661 placeholder.reactorHandles = record.reactorHandles;
2662 placeholder.xDictHandle = record.xDictHandle;
2663 placeholder.setDwgCommonObjectState(
2664 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
2665 record.hasDsData);
2666 return placeholder;
2667}
2668
2669DRW_Dictionary
2670dictionaryFromMetadata(const LC_DwgAdvancedMetadata::DictionaryRecord &record) {
2671 DRW_Dictionary dictionary;
2672 dictionary.handle = record.handle;
2673 dictionary.parentHandle = record.parentHandle;
2674 dictionary.cloning = record.cloning;
2675 dictionary.hardOwner = record.hardOwner;
2676 dictionary.name = record.name;
2677 dictionary.appData = record.appData;
2678 restoreTableEntryExtData(dictionary, record.extData);
2679 dictionary.reactorHandles = record.reactorHandles;
2680 dictionary.xDictHandle = record.xDictHandle;
2681 dictionary.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2682 record.hasDsData);
2683 dictionary.m_entries.reserve(record.entries.size());
2684 for (const auto &er : record.entries) {
2685 DRW_Dictionary::Entry entry;
2686 entry.m_name = er.name;
2687 entry.m_handle = er.handle;
2688 dictionary.m_entries.push_back(std::move(entry));
2689 }
2690 return dictionary;
2691}
2692
2693DRW_XRecord
2694xrecordFromMetadata(const LC_DwgAdvancedMetadata::XRecordRecord &record) {
2695 DRW_XRecord xrecord;
2696 xrecord.handle = record.handle;
2697 xrecord.parentHandle = record.parentHandle;
2698 xrecord.m_cloning = record.cloning;
2699 xrecord.m_values = record.values;
2700 xrecord.m_handleValues = record.handleValues;
2701 xrecord.m_dataEntries = record.dataEntries;
2702 xrecord.m_rawData = record.rawData;
2703 xrecord.m_rawDataValid = record.rawDataValid;
2704 xrecord.appData = record.appData;
2705 restoreTableEntryExtData(xrecord, record.extData);
2706 xrecord.reactorHandles = record.reactorHandles;
2707 xrecord.xDictHandle = record.xDictHandle;
2708 xrecord.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2709 record.hasDsData);
2710 return xrecord;
2711}
2712
2713DRW_Layout
2714layoutFromMetadata(const LC_DwgAdvancedMetadata::LayoutRecord &record) {
2715 DRW_Layout layout;
2716 layout.handle = record.handle;
2717 layout.parentHandle = record.parentHandle;
2718 layout.appData = record.appData;
2719 restoreTableEntryExtData(layout, record.extData);
2720 layout.reactorHandles = record.reactorHandles;
2721 layout.xDictHandle = record.xDictHandle;
2722 layout.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2723 record.hasDsData);
2724 // PlotSettings prefix.
2725 layout.pageSetupName = record.pageSetupName;
2726 layout.printerConfig = record.printerConfig;
2727 layout.plotLayoutFlags = record.plotLayoutFlags;
2728 layout.marginLeft = record.marginLeft;
2729 layout.marginBottom = record.marginBottom;
2730 layout.marginRight = record.marginRight;
2731 layout.marginTop = record.marginTop;
2732 layout.paperWidth = record.paperWidth;
2733 layout.paperHeight = record.paperHeight;
2734 layout.paperSize = record.paperSize;
2735 layout.plotOriginX = record.plotOriginX;
2736 layout.plotOriginY = record.plotOriginY;
2737 layout.paperUnits = record.paperUnits;
2738 layout.plotRotation = record.plotRotation;
2739 layout.plotType = record.plotType;
2740 layout.windowMinX = record.windowMinX;
2741 layout.windowMinY = record.windowMinY;
2742 layout.windowMaxX = record.windowMaxX;
2743 layout.windowMaxY = record.windowMaxY;
2744 layout.plotViewName = record.plotViewName;
2745 layout.realWorldUnits = record.realWorldUnits;
2746 layout.drawingUnits = record.drawingUnits;
2747 layout.currentStyleSheet = record.currentStyleSheet;
2748 layout.scaleType = record.scaleType;
2749 layout.scaleFactor = record.scaleFactor;
2750 layout.paperImageOriginX = record.paperImageOriginX;
2751 layout.paperImageOriginY = record.paperImageOriginY;
2752 layout.shadePlotMode = record.shadePlotMode;
2753 layout.shadePlotResLevel = record.shadePlotResLevel;
2754 layout.shadePlotCustomDPI = record.shadePlotCustomDPI;
2755 // Layout-specific.
2756 layout.name = record.name;
2757 layout.layoutFlags = record.layoutFlags;
2758 layout.tabOrder = record.tabOrder;
2759 layout.ucsOrigin = record.ucsOrigin;
2760 layout.limMinX = record.limMinX;
2761 layout.limMinY = record.limMinY;
2762 layout.limMaxX = record.limMaxX;
2763 layout.limMaxY = record.limMaxY;
2764 layout.insPoint = record.insPoint;
2765 layout.ucsXAxis = record.ucsXAxis;
2766 layout.ucsYAxis = record.ucsYAxis;
2767 layout.elevation = record.elevation;
2768 layout.orthoViewType = record.orthoViewType;
2769 layout.extMin = record.extMin;
2770 layout.extMax = record.extMax;
2771 layout.viewportCount = record.viewportCount;
2772 layout.plotViewHandle.ref = record.plotViewHandle;
2773 layout.shadePlotHandle.ref = record.shadePlotHandle;
2774 layout.paperSpaceBlockRecordHandle.ref = record.paperSpaceBlockRecordHandle;
2775 layout.lastActiveViewportHandle.ref = record.lastActiveViewportHandle;
2776 layout.baseUcsHandle.ref = record.baseUcsHandle;
2777 layout.namedUcsHandle.ref = record.namedUcsHandle;
2778 layout.viewportHandles = record.viewportHandles;
2779 return layout;
2780}
2781
2782DRW_Group groupFromMetadata(const LC_DwgAdvancedMetadata::GroupRecord &record) {
2783 DRW_Group group;
2784 group.handle = record.handle;
2785 group.parentHandle = record.parentHandle;
2786 group.appData = record.appData;
2787 restoreTableEntryExtData(group, record.extData);
2788 group.reactorHandles = record.reactorHandles;
2789 group.xDictHandle = record.xDictHandle;
2790 group.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2791 record.hasDsData);
2792 group.m_description = record.description;
2793 group.m_isUnnamed = record.isUnnamed;
2794 group.m_selectable = record.selectable;
2795 group.m_entityHandles = record.entityHandles;
2796 return group;
2797}
2798
2799DRW_UnderlayDefinition underlayDefinitionFromMetadata(
2800 const LC_DwgAdvancedMetadata::UnderlayDefinitionRecord &record) {
2801 DRW_UnderlayDefinition definition;
2802 definition.handle = record.handle;
2803 definition.parentHandle = record.parentHandle;
2804 definition.appData = record.appData;
2805 restoreTableEntryExtData(definition, record.extData);
2806 definition.kind = static_cast<DRW_UnderlayDefinition::Kind>(record.kind);
2807 definition.filename = record.path;
2808 definition.sheetName = record.sheetName;
2809 definition.reactorHandles = record.reactorHandles;
2810 definition.xDictHandle = record.xDictHandle;
2811 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2812 record.hasDsData);
2813 return definition;
2814}
2815
2816DRW_ImageDef imageDefinitionFromMetadata(
2817 const LC_DwgAdvancedMetadata::ImageDefinitionRecord &record) {
2818 DRW_ImageDef definition;
2819 definition.handle = record.handle;
2820 definition.parentHandle = record.parentHandle;
2821 definition.appData = record.appData;
2822 restoreTableEntryExtData(definition, record.extData);
2823 definition.name = record.path;
2824 definition.imgVersion = record.classVersion;
2825 definition.u = record.imageSizeU;
2826 definition.v = record.imageSizeV;
2827 definition.up = record.pixelSizeU;
2828 definition.vp = record.pixelSizeV;
2829 definition.loaded = record.loaded;
2830 definition.resolution = record.resolution;
2831 definition.reactorHandles = record.commonReactorHandles;
2832 definition.xDictHandle = record.xDictHandle;
2833 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2834 record.hasDsData);
2835 return definition;
2836}
2837
2838DRW_ImageDefinitionReactor imageDefinitionReactorFromMetadata(
2839 const LC_DwgAdvancedMetadata::ImageDefinitionReactorRecord &record) {
2840 DRW_ImageDefinitionReactor reactor;
2841 reactor.handle = record.handle;
2842 reactor.parentHandle = record.parentHandle;
2843 reactor.appData = record.appData;
2844 restoreTableEntryExtData(reactor, record.extData);
2845 reactor.m_classVersion = record.classVersion;
2846 reactor.reactorHandles = record.reactorHandles;
2847 reactor.xDictHandle = record.xDictHandle;
2848 reactor.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2849 record.hasDsData);
2850 return reactor;
2851}
2852
2853DRW_PointCloudDef pointCloudDefinitionFromMetadata(
2854 const LC_DwgAdvancedMetadata::PointCloudDefinitionRecord &record) {
2855 DRW_PointCloudDef definition;
2856 definition.handle = record.handle;
2857 definition.parentHandle = record.parentHandle;
2858 definition.appData = record.appData;
2859 restoreTableEntryExtData(definition, record.extData);
2860 definition.m_kind = static_cast<DRW_PointCloudDef::Kind>(record.kind);
2861 definition.m_classVersion = record.classVersion;
2862 definition.m_sourceFilename = record.sourceFilename;
2863 definition.m_isLoaded = record.isLoaded;
2864 definition.m_pointCount = record.pointCount;
2865 definition.m_extentsMin = record.extentsMin;
2866 definition.m_extentsMax = record.extentsMax;
2867 definition.reactorHandles = record.reactorHandles;
2868 definition.xDictHandle = record.xDictHandle;
2869 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2870 record.hasDsData);
2871 return definition;
2872}
2873
2874DRW_NavisworksModelDef navisworksModelDefinitionFromMetadata(
2875 const LC_DwgAdvancedMetadata::NavisworksModelDefinitionRecord &record) {
2876 DRW_NavisworksModelDef definition;
2877 definition.handle = record.handle;
2878 definition.parentHandle = record.parentHandle;
2879 definition.appData = record.appData;
2880 restoreTableEntryExtData(definition, record.extData);
2881 definition.m_flags = record.flags;
2882 definition.m_path = record.path;
2883 definition.m_status = record.status;
2884 definition.m_minExtent = record.minExtent;
2885 definition.m_maxExtent = record.maxExtent;
2886 definition.m_hostDrawingVisibility = record.hostDrawingVisibility;
2887 definition.reactorHandles = record.reactorHandles;
2888 definition.xDictHandle = record.xDictHandle;
2889 definition.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2890 record.hasDsData);
2891 return definition;
2892}
2893
2894DRW_NavisworksModel navisworksModelFromMetadata(
2895 const LC_DwgAdvancedMetadata::NavisworksModelRecord &record) {
2896 DRW_NavisworksModel model;
2897 model.handle = record.handle;
2898 model.parentHandle = record.parentHandle;
2899 model.flags = record.flags;
2900 model.definitionHandle = record.definitionHandle;
2901 model.transform = record.transform;
2902 model.unitFactor = record.unitFactor;
2903 return model;
2904}
2905
2906DRW_PointCloudColorMap pointCloudColorMapFromMetadata(
2907 const LC_DwgAdvancedMetadata::PointCloudColorMapRecord &record) {
2908 DRW_PointCloudColorMap colorMap;
2909 colorMap.handle = record.handle;
2910 colorMap.parentHandle = record.parentHandle;
2911 colorMap.appData = record.appData;
2912 restoreTableEntryExtData(colorMap, record.extData);
2913 colorMap.m_classVersion = record.classVersion;
2914 colorMap.m_defaultIntensityColorScheme = record.defaultIntensityColorScheme;
2915 colorMap.m_defaultElevationColorScheme = record.defaultElevationColorScheme;
2916 colorMap.m_defaultClassificationColorScheme =
2917 record.defaultClassificationColorScheme;
2918 colorMap.m_colorRampCount = record.declaredColorRampCount;
2919 colorMap.m_classificationColorRampCount =
2920 record.declaredClassificationColorRampCount;
2921 const auto copyRamps = [](const auto &source, auto &destination) {
2922 destination.reserve(source.size());
2923 for (const auto &ramp : source) {
2924 DRW_PointCloudColorMapRamp copied;
2925 copied.m_classVersion = ramp.classVersion;
2926 copied.m_rampCount = ramp.declaredColorCount;
2927 copied.m_colorSchemes = ramp.colorSchemes;
2928 destination.push_back(std::move(copied));
2929 }
2930 };
2931 copyRamps(record.colorRamps, colorMap.m_colorRamps);
2932 copyRamps(record.classificationColorRamps,
2933 colorMap.m_classificationColorRamps);
2934 colorMap.reactorHandles = record.reactorHandles;
2935 colorMap.xDictHandle = record.xDictHandle;
2936 colorMap.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2937 record.hasDsData);
2938 return colorMap;
2939}
2940
2941DRW_RasterVariables rasterVariablesFromMetadata(
2942 const LC_DwgAdvancedMetadata::RasterVariablesRecord &record) {
2943 DRW_RasterVariables rv;
2944 rv.handle = record.handle;
2945 rv.parentHandle = record.parentHandle;
2946 rv.appData = record.appData;
2947 restoreTableEntryExtData(rv, record.extData);
2948 rv.m_classVersion = record.classVersion;
2949 rv.m_imageFrame = record.imageFrame;
2950 rv.m_imageQuality = record.imageQuality;
2951 rv.m_units = record.units;
2952 rv.reactorHandles = record.reactorHandles;
2953 rv.xDictHandle = record.xDictHandle;
2954 rv.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2955 record.hasDsData);
2956 return rv;
2957}
2958
2959DRW_GeoData
2960geoDataFromMetadata(const LC_DwgAdvancedMetadata::GeoDataRecord &record) {
2961 DRW_GeoData gd;
2962 gd.handle = record.handle;
2963 gd.parentHandle = record.parentHandle;
2964 gd.appData = record.appData;
2965 restoreTableEntryExtData(gd, record.extData);
2966 gd.reactorHandles = record.reactorHandles;
2967 gd.xDictHandle = record.xDictHandle;
2968 gd.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
2969 record.hasDsData);
2970 gd.m_hostBlockHandle = record.hostBlockHandle;
2971 gd.m_version = record.version;
2972 gd.m_coordinatesType = record.coordinatesType;
2973 gd.m_horizontalUnits = record.horizontalUnits;
2974 gd.m_verticalUnits = record.verticalUnits;
2975 gd.m_horizontalUnitScale = record.horizontalUnitScale;
2976 gd.m_verticalUnitScale = record.verticalUnitScale;
2977 gd.m_coordinateSystemDefinition = record.coordinateSystemDefinition;
2978 gd.m_geoRssTag = record.geoRssTag;
2979 gd.m_designPoint = record.designPoint;
2980 gd.m_referencePoint = record.referencePoint;
2981 gd.m_upDirection = record.upDirection;
2982 gd.m_northDirection = record.northDirection;
2983 gd.m_scaleEstimationMethod = record.scaleEstimationMethod;
2984 gd.m_userSpecifiedScaleFactor = record.userSpecifiedScaleFactor;
2985 gd.m_enableSeaLevelCorrection = record.enableSeaLevelCorrection;
2986 gd.m_seaLevelElevation = record.seaLevelElevation;
2987 gd.m_coordinateProjectionRadius = record.coordinateProjectionRadius;
2988 gd.m_observationFromTag = record.observationFromTag;
2989 gd.m_observationToTag = record.observationToTag;
2990 gd.m_observationCoverageTag = record.observationCoverageTag;
2991 gd.m_points.reserve(record.meshPoints.size());
2992 for (const auto &mp : record.meshPoints) {
2993 DRW_GeoMeshPoint point;
2994 point.m_source = mp.source;
2995 point.m_destination = mp.destination;
2996 gd.m_points.push_back(point);
2997 }
2998 gd.m_faces.reserve(record.meshFaces.size());
2999 for (const auto &mf : record.meshFaces) {
3000 DRW_GeoMeshFace face;
3001 face.m_index1 = mf.index1;
3002 face.m_index2 = mf.index2;
3003 face.m_index3 = mf.index3;
3004 gd.m_faces.push_back(face);
3005 }
3006 return gd;
3007}
3008
3009DRW_SpatialFilter spatialFilterFromMetadata(
3010 const LC_DwgAdvancedMetadata::SpatialFilterRecord &record) {
3011 DRW_SpatialFilter sf;
3012 sf.handle = record.handle;
3013 sf.parentHandle = record.parentHandle;
3014 sf.appData = record.appData;
3015 restoreTableEntryExtData(sf, record.extData);
3016 sf.reactorHandles = record.reactorHandles;
3017 sf.xDictHandle = record.xDictHandle;
3018 sf.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3019 record.hasDsData);
3020 sf.m_boundaryPoints = record.boundaryPoints;
3021 sf.m_normal = record.normal;
3022 sf.m_origin = record.origin;
3023 sf.m_displayBoundary = record.displayBoundary;
3024 sf.m_clipFrontPlane = record.clipFrontPlane;
3025 sf.m_clipBackPlane = record.clipBackPlane;
3026 sf.m_frontDistance = record.frontDistance;
3027 sf.m_backDistance = record.backDistance;
3028 sf.m_inverseInsertTransform = record.inverseInsertTransform;
3029 sf.m_insertTransform = record.insertTransform;
3030 return sf;
3031}
3032
3033// PR 8d.2a — five small no-storage OBJECTS families. Flat field-copy
3034// builders matching the addX captures above.
3035DRW_Scale scaleFromMetadata(const LC_DwgAdvancedMetadata::ScaleRecord &record) {
3036 DRW_Scale s;
3037 s.handle = record.handle;
3038 s.parentHandle = record.parentHandle;
3039 s.appData = record.appData;
3040 restoreTableEntryExtData(s, record.extData);
3041 s.reactorHandles = record.reactorHandles;
3042 s.xDictHandle = record.xDictHandle;
3043 s.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3044 record.hasDsData);
3045 s.name = record.name;
3046 s.flag = record.flag;
3047 s.paperUnits = record.paperUnits;
3048 s.drawingUnits = record.drawingUnits;
3049 s.isUnitScale = record.isUnitScale;
3050 return s;
3051}
3052
3053DRW_MLineStyle
3054mlineStyleFromMetadata(const LC_DwgAdvancedMetadata::MLineStyleRecord &record) {
3055 DRW_MLineStyle s;
3056 s.handle = record.handle;
3057 s.parentHandle = record.parentHandle;
3058 s.appData = record.appData;
3059 restoreTableEntryExtData(s, record.extData);
3060 s.reactorHandles = record.reactorHandles;
3061 s.xDictHandle = record.xDictHandle;
3062 s.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3063 record.hasDsData);
3064 s.name = record.name;
3065 s.flags = record.flags;
3066 s.description = record.description;
3067 s.fillColor = record.fillColor;
3068 s.startAngle = record.startAngle;
3069 s.endAngle = record.endAngle;
3070 s.elements.reserve(record.elements.size());
3071 for (const auto &er : record.elements) {
3072 DRW_MLineElement e;
3073 e.offset = er.offset;
3074 e.color = er.color;
3075 e.color24 = er.color24;
3076 e.linetypeIndex = er.linetypeIndex;
3077 e.linetypeHandle = er.linetypeHandle;
3078 e.linetype = er.linetype;
3079 s.elements.push_back(std::move(e));
3080 }
3081 return s;
3082}
3083
3084DRW_WipeoutVariables wipeoutVariablesFromMetadata(
3085 const LC_DwgAdvancedMetadata::WipeoutVariablesRecord &record) {
3086 DRW_WipeoutVariables w;
3087 w.handle = record.handle;
3088 w.parentHandle = record.parentHandle;
3089 w.appData = record.appData;
3090 restoreTableEntryExtData(w, record.extData);
3091 w.m_displayFrame = static_cast<std::uint16_t>(record.displayFrame);
3092 w.reactorHandles = record.reactorHandles;
3093 w.xDictHandle = record.xDictHandle;
3094 w.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3095 record.hasDsData);
3096 return w;
3097}
3098
3099DRW_IDBuffer
3100idBufferFromMetadata(const LC_DwgAdvancedMetadata::IDBufferRecord &record) {
3101 DRW_IDBuffer b;
3102 b.handle = record.handle;
3103 b.parentHandle = record.parentHandle;
3104 b.appData = record.appData;
3105 restoreTableEntryExtData(b, record.extData);
3106 b.reactorHandles = record.reactorHandles;
3107 b.xDictHandle = record.xDictHandle;
3108 b.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3109 record.hasDsData);
3110 b.classVersion = record.classVersion;
3111 b.objIds = record.objIds;
3112 return b;
3113}
3114
3115DRW_LayerIndex
3116layerIndexFromMetadata(const LC_DwgAdvancedMetadata::LayerIndexRecord &record) {
3117 DRW_LayerIndex li;
3118 li.handle = record.handle;
3119 li.parentHandle = record.parentHandle;
3120 li.appData = record.appData;
3121 restoreTableEntryExtData(li, record.extData);
3122 li.timestamp1 = record.timestamp1;
3123 li.timestamp2 = record.timestamp2;
3124 li.entries.reserve(record.entries.size());
3125 for (const auto &er : record.entries) {
3126 DRW_LayerIndexEntry e;
3127 e.indexLong = er.indexLong;
3128 e.name = er.name;
3129 e.entryHandle = er.entryHandle;
3130 li.entries.push_back(std::move(e));
3131 }
3132 li.reactorHandles = record.reactorHandles;
3133 li.xDictHandle = record.xDictHandle;
3134 li.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3135 record.hasDsData);
3136 return li;
3137}
3138
3139DRW_SpatialIndex spatialIndexFromMetadata(
3140 const LC_DwgAdvancedMetadata::SpatialIndexRecord &record) {
3141 DRW_SpatialIndex si;
3142 si.handle = record.handle;
3143 si.parentHandle = record.parentHandle;
3144 si.appData = record.appData;
3145 restoreTableEntryExtData(si, record.extData);
3146 si.timestamp1 = record.timestamp1;
3147 si.timestamp2 = record.timestamp2;
3148 si.reactorHandles = record.reactorHandles;
3149 si.xDictHandle = record.xDictHandle;
3150 si.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3151 record.hasDsData);
3152 return si;
3153}
3154
3155DRW_DictionaryVar dictionaryVarFromMetadata(
3156 const LC_DwgAdvancedMetadata::DictionaryVarRecord &record) {
3157 DRW_DictionaryVar dv;
3158 dv.handle = record.handle;
3159 dv.parentHandle = record.parentHandle;
3160 dv.appData = record.appData;
3161 restoreTableEntryExtData(dv, record.extData);
3162 dv.m_schema = record.schema;
3163 dv.m_value = record.value;
3164 dv.reactorHandles = record.reactorHandles;
3165 dv.xDictHandle = record.xDictHandle;
3166 dv.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3167 record.hasDsData);
3168 return dv;
3169}
3170
3171// PR 8d.2b — four larger no-storage OBJECTS families. Flat field-copy
3172// builders matching the addX captures above.
3173DRW_DictionaryWithDefault dictionaryWithDefaultFromMetadata(
3174 const LC_DwgAdvancedMetadata::DictionaryWithDefaultRecord &record) {
3175 DRW_DictionaryWithDefault dwd;
3176 dwd.handle = record.handle;
3177 dwd.parentHandle = record.parentHandle;
3178 dwd.appData = record.appData;
3179 restoreTableEntryExtData(dwd, record.extData);
3180 dwd.cloning = record.cloning;
3181 dwd.hardOwner = record.hardOwner;
3182 dwd.name = record.name;
3183 dwd.m_entries.reserve(record.entries.size());
3184 for (const auto &er : record.entries) {
3185 DRW_Dictionary::Entry entry;
3186 entry.m_name = er.name;
3187 entry.m_handle = er.handle;
3188 dwd.m_entries.push_back(std::move(entry));
3189 }
3190 dwd.m_defaultEntryHandle = record.defaultEntryHandle;
3191 dwd.reactorHandles = record.reactorHandles;
3192 dwd.xDictHandle = record.xDictHandle;
3193 dwd.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3194 record.hasDsData);
3195 return dwd;
3196}
3197
3198bool canWriteDwgDictionaryRecord(
3199 const LC_DwgAdvancedMetadata &metadata,
3200 const LC_DwgAdvancedMetadata::DictionaryRecord &record,
3201 DRW::Version version) {
3202 const DRW_Dictionary dictionary = dictionaryFromMetadata(record);
3203 const auto sourceAuthorization =
3204 metadata.authorizeDwgDictionarySource(record);
3205 return record.typedPayloadComplete &&
3206 !hasDxfTableAppDataUnsupportedByDwg(dictionary) &&
3207 dictionary.isDwgPayloadValid(version) &&
3208 (sourceAuthorization.status ==
3209 LC_DwgAdvancedMetadata::DwgDictionarySourceAuthorizationStatus::
3210 NotDwgSource ||
3211 sourceAuthorization.authorized());
3212}
3213
3214bool canWriteDwgDictionaryWithDefaultRecord(
3215 const LC_DwgAdvancedMetadata &metadata,
3216 const LC_DwgAdvancedMetadata::DictionaryWithDefaultRecord &record,
3217 DRW::Version version) {
3218 const DRW_DictionaryWithDefault dictionary =
3219 dictionaryWithDefaultFromMetadata(record);
3220 const auto sourceAuthorization =
3221 metadata.authorizeDwgDictionaryWithDefaultSource(record);
3222 return record.typedPayloadComplete &&
3223 !hasDxfTableAppDataUnsupportedByDwg(dictionary) &&
3224 dictionary.isDwgPayloadValid(version) &&
3225 (sourceAuthorization.status ==
3226 LC_DwgAdvancedMetadata::
3227 DwgDictionaryWithDefaultSourceAuthorizationStatus::
3228 NotDwgSource ||
3229 sourceAuthorization.authorized());
3230}
3231
3232DRW_SortEntsTable sortEntsTableFromMetadata(
3233 const LC_DwgAdvancedMetadata::SortEntsTableRecord &record) {
3234 DRW_SortEntsTable se;
3235 se.handle = record.handle;
3236 se.parentHandle = record.parentHandle;
3237 se.appData = record.appData;
3238 restoreTableEntryExtData(se, record.extData);
3239 se.m_sortHandles = record.sortHandles;
3240 se.m_blockOwnerHandle = record.blockOwnerHandle;
3241 se.m_entityHandles = record.entityHandles;
3242 se.reactorHandles = record.reactorHandles;
3243 se.xDictHandle = record.xDictHandle;
3244 se.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3245 record.hasDsData);
3246 return se;
3247}
3248
3249DRW_FieldList
3250fieldListFromMetadata(const LC_DwgAdvancedMetadata::FieldListRecord &record) {
3251 DRW_FieldList fl;
3252 fl.handle = record.handle;
3253 fl.parentHandle = record.parentHandle;
3254 fl.m_unknown = record.unknown;
3255 fl.m_fieldHandles = record.fieldHandles;
3256 fl.appData = record.appData;
3257 restoreTableEntryExtData(fl, record.extData);
3258 fl.reactorHandles = record.reactorHandles;
3259 fl.xDictHandle = record.xDictHandle;
3260 fl.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3261 record.hasDsData);
3262 return fl;
3263}
3264
3265bool canWriteDwgFieldListRecord(
3266 const LC_DwgAdvancedMetadata &metadata,
3267 const LC_DwgAdvancedMetadata::FieldListRecord &record,
3268 const DRW_FieldList &fieldList,
3269 DRW::Version version) {
3270 // The export-tolerant proof: a member that names no object frame of the
3271 // source file does not refuse the list (the plan drops it).
3272 const auto sourceAuthorization =
3273 metadata.authorizeDwgFieldListSourceForExport(record);
3274 return !hasDxfTableAppDataUnsupportedByDwg(fieldList) &&
3275 fieldList.isDwgPayloadValid(version) &&
3276 (sourceAuthorization.status ==
3277 LC_DwgAdvancedMetadata::DwgFieldListSourceAuthorizationStatus::
3278 NotDwgSource ||
3279 sourceAuthorization.authorized());
3280}
3281
3282DRW_CadValue
3283cadValueFromRecord(const LC_DwgAdvancedMetadata::CadValueRecord &cr) {
3284 DRW_CadValue cv;
3285 cv.m_formatFlags = cr.formatFlags;
3286 cv.m_dataType = cr.dataType;
3287 cv.m_dataSize = cr.dataSize;
3288 cv.m_unitType = cr.unitType;
3289 cv.m_value = cr.value;
3290 cv.m_formatString = cr.formatString;
3291 cv.m_valueString = cr.valueString;
3292 cv.m_handle = cr.handle;
3293 cv.m_rawData = cr.rawData;
3294 return cv;
3295}
3296
3297DRW_Field fieldFromMetadata(const LC_DwgAdvancedMetadata::FieldRecord &record) {
3298 DRW_Field f;
3299 f.handle = record.handle;
3300 f.parentHandle = record.parentHandle;
3301 f.m_evaluatorId = record.evaluatorId;
3302 f.m_fieldCode = record.fieldCode;
3303 f.m_formatString = record.formatString;
3304 f.m_evaluationOptionFlags = record.evaluationOptionFlags;
3305 f.m_filingOptionFlags = record.filingOptionFlags;
3306 f.m_fieldStateFlags = record.fieldStateFlags;
3307 f.m_evaluationStatusFlags = record.evaluationStatusFlags;
3308 f.m_evaluationErrorCode = record.evaluationErrorCode;
3309 f.m_evaluationErrorMessage = record.evaluationErrorMessage;
3310 f.m_value = cadValueFromRecord(record.value);
3311 f.m_valueString = record.valueString;
3312 f.m_valueStringLength = record.valueStringLength;
3313 f.m_childHandles = record.childHandles;
3314 f.m_objectHandles = record.objectHandles;
3315 f.appData = record.appData;
3316 restoreTableEntryExtData(f, record.extData);
3317 f.reactorHandles = record.reactorHandles;
3318 f.xDictHandle = record.xDictHandle;
3319 f.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3320 record.hasDsData);
3321 f.m_childValues.reserve(record.childValues.size());
3322 for (const auto &cv : record.childValues) {
3323 DRW_Field::ChildValue dcv;
3324 dcv.m_key = cv.key;
3325 dcv.m_value = cadValueFromRecord(cv.value);
3326 f.m_childValues.push_back(std::move(dcv));
3327 }
3328 return f;
3329}
3330
3331bool canWriteDwgFieldRecord(const LC_DwgAdvancedMetadata &metadata,
3332 const LC_DwgAdvancedMetadata::FieldRecord &record,
3333 const DRW_Field &field,
3334 DRW::Version version) {
3335 const auto sourceAuthorization = metadata.authorizeDwgFieldSource(record);
3336 return record.typedPayloadComplete &&
3337 !hasDxfTableAppDataUnsupportedByDwg(field) &&
3338 field.isDwgPayloadValid(version) &&
3339 (sourceAuthorization.status ==
3340 LC_DwgAdvancedMetadata::DwgFieldSourceAuthorizationStatus::
3341 NotDwgSource ||
3342 sourceAuthorization.authorized());
3343}
3344
3345DRW_Sun sunFromMetadata(const LC_DwgAdvancedMetadata::SunRecord &record) {
3346 DRW_Sun sun;
3347 sun.handle = record.handle;
3348 sun.parentHandle = record.parentHandle;
3349 sun.appData = record.appData;
3350 restoreTableEntryExtData(sun, record.extData);
3351 sun.m_classVersion = record.classVersion;
3352 sun.m_isOn = record.isOn;
3353 sun.m_color = record.color;
3354 sun.m_intensity = record.intensity;
3355 sun.m_hasShadow = record.hasShadow;
3356 sun.m_julianDay = record.julianDay;
3357 sun.m_milliseconds = record.milliseconds;
3358 sun.m_isDaylightSavings = record.isDaylightSavings;
3359 sun.m_shadowType = record.shadowType;
3360 sun.m_shadowMapSize = record.shadowMapSize;
3361 sun.m_shadowSoftness = record.shadowSoftness;
3362 sun.reactorHandles = record.reactorHandles;
3363 sun.xDictHandle = record.xDictHandle;
3364 sun.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3365 record.hasDsData);
3366 return sun;
3367}
3368
3369DRW_TvDeviceProperties tvDevicePropertiesFromMetadata(
3370 const LC_DwgAdvancedMetadata::TvDevicePropertiesRecord &record) {
3371 DRW_TvDeviceProperties properties;
3372 properties.handle = record.handle;
3373 properties.parentHandle = record.parentHandle;
3374 properties.flags = record.flags;
3375 properties.maxRegenThreads = record.maxRegenThreads;
3376 properties.useLutPalette = record.useLutPalette;
3377 properties.alternateHighlight = record.alternateHighlight;
3378 properties.alternateHighlightColor = record.alternateHighlightColor;
3379 properties.geometryShaderUsage = record.geometryShaderUsage;
3380 properties.blendingMode = record.blendingMode;
3381 properties.antialiasingLevel = record.antialiasingLevel;
3382 properties.valueBd2 = record.valueBd2;
3383 properties.appData = record.appData;
3384 restoreTableEntryExtData(properties, record.extData);
3385 properties.reactorHandles = record.reactorHandles;
3386 properties.xDictHandle = record.xDictHandle;
3387 properties.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3388 record.hasDsData);
3389 return properties;
3390}
3391
3392DRW_VxControl
3393vxControlFromMetadata(const LC_DwgAdvancedMetadata::VxControlRecord &record) {
3394 DRW_VxControl control;
3395 control.handle = record.handle;
3396 control.parentHandle = record.parentHandle;
3397 control.appData = record.appData;
3398 restoreTableEntryExtData(control, record.extData);
3399 control.classVersion = record.classVersion;
3400 control.flags = record.flags;
3401 control.recordHandles = record.recordHandles;
3402 if (record.rawDataValid)
3403 (void)control.setDwgRawData(record.rawData, record.rawDataBitSize,
3404 record.rawDataVersion);
3405 control.reactorHandles = record.reactorHandles;
3406 control.xDictHandle = record.xDictHandle;
3407 control.setDwgCommonObjectState(
3408 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3409 record.hasDsData);
3410 return control;
3411}
3412
3413DRW_VxTableRecord vxTableRecordFromMetadata(
3414 const LC_DwgAdvancedMetadata::VxTableRecordRecord &record) {
3415 DRW_VxTableRecord tableRecord;
3416 tableRecord.handle = record.handle;
3417 tableRecord.parentHandle = record.parentHandle;
3418 tableRecord.appData = record.appData;
3419 restoreTableEntryExtData(tableRecord, record.extData);
3420 tableRecord.name = record.name;
3421 tableRecord.classVersion = record.classVersion;
3422 tableRecord.flags = record.flags;
3423 if (record.rawDataValid)
3424 (void)tableRecord.setDwgRawData(record.rawData, record.rawDataBitSize,
3425 record.rawDataVersion);
3426 tableRecord.reactorHandles = record.reactorHandles;
3427 tableRecord.xDictHandle = record.xDictHandle;
3428 tableRecord.setDwgCommonObjectState(
3429 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3430 record.hasDsData);
3431 return tableRecord;
3432}
3433
3434const char *backgroundRecordName(DRW_Background::Kind kind) {
3435 switch (kind) {
3436 case DRW_Background::Gradient:
3437 return "GRADIENTBACKGROUND";
3438 case DRW_Background::GroundPlane:
3439 return "GROUNDPLANEBACKGROUND";
3440 case DRW_Background::Image:
3441 return "IMAGEBACKGROUND";
3442 case DRW_Background::Ibl:
3443 return "IBLBACKGROUND";
3444 case DRW_Background::Skylight:
3445 return "SKYLIGHTBACKGROUND";
3446 case DRW_Background::Solid:
3447 default:
3448 return "SOLIDBACKGROUND";
3449 }
3450}
3451
3452DRW_Background
3453backgroundFromMetadata(const LC_DwgAdvancedMetadata::BackgroundRecord &record) {
3454 DRW_Background background;
3455 background.handle = record.handle;
3456 background.parentHandle = record.parentHandle;
3457 background.appData = record.appData;
3458 restoreTableEntryExtData(background, record.extData);
3459 background.m_kind = record.kind;
3460 background.m_classVersion = record.classVersion;
3461 background.m_solidColor = record.solidColor;
3462 background.m_colorTop = record.colorTop;
3463 background.m_colorMiddle = record.colorMiddle;
3464 background.m_colorBottom = record.colorBottom;
3465 background.m_horizon = record.horizon;
3466 background.m_height = record.height;
3467 background.m_rotation = record.rotation;
3468 background.m_colorSkyZenith = record.colorSkyZenith;
3469 background.m_colorSkyHorizon = record.colorSkyHorizon;
3470 background.m_colorUndergroundHorizon = record.colorUndergroundHorizon;
3471 background.m_colorUndergroundAzimuth = record.colorUndergroundAzimuth;
3472 background.m_colorNear = record.colorNear;
3473 background.m_colorFar = record.colorFar;
3474 background.m_fileName = record.fileName;
3475 background.m_fitToScreen = record.fitToScreen;
3476 background.m_maintainAspect = record.maintainAspect;
3477 background.m_useTiling = record.useTiling;
3478 background.m_offset = record.offset;
3479 background.m_scale = record.scale;
3480 background.m_iblName = record.iblName;
3481 background.m_enabled = record.enabled;
3482 background.m_displayImage = record.displayImage;
3483 background.m_secondaryBackgroundHandle = record.secondaryBackgroundHandle;
3484 background.m_sunHandle = record.sunHandle;
3485 background.reactorHandles = record.reactorHandles;
3486 background.xDictHandle = record.xDictHandle;
3487 background.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3488 record.hasDsData);
3489 return background;
3490}
3491
3492DRW_Material
3493materialFromMetadata(const LC_DwgAdvancedMetadata::MaterialRecord &record) {
3494 DRW_Material material;
3495 material.handle = record.handle;
3496 material.parentHandle = record.parentHandle;
3497 material.m_name = record.name;
3498 material.m_description = record.description;
3499 material.appData = record.appData;
3500 restoreTableEntryExtData(material, record.extData);
3501 material.reactorHandles = record.reactorHandles;
3502 material.xDictHandle = record.xDictHandle;
3503 material.setDwgCommonObjectState(
3504 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3505 record.hasDsData);
3506 if (record.rawDataValid)
3507 (void)material.setDwgRawData(record.rawData, record.rawDataBitSize,
3508 record.rawDataVersion);
3509 return material;
3510}
3511
3512DRW_LightList
3513lightListFromMetadata(const LC_DwgAdvancedMetadata::LightListRecord &record) {
3514 DRW_LightList lightList;
3515 lightList.handle = record.handle;
3516 lightList.parentHandle = record.parentHandle;
3517 lightList.m_classVersion = record.classVersion;
3518 lightList.m_lightCount = record.lightCount;
3519 lightList.appData = record.appData;
3520 restoreTableEntryExtData(lightList, record.extData);
3521 lightList.reactorHandles = record.reactorHandles;
3522 lightList.xDictHandle = record.xDictHandle;
3523 lightList.setDwgCommonObjectState(
3524 static_cast<std::int32_t>(record.reactorHandles.size()), record.xDictFlag,
3525 record.hasDsData);
3526 lightList.m_lights.reserve(record.lights.size());
3527 for (const auto &source : record.lights) {
3528 DRW_LightListEntry light;
3529 light.m_handle = source.handle;
3530 light.m_name = source.name;
3531 lightList.m_lights.push_back(std::move(light));
3532 }
3533 return lightList;
3534}
3535
3536DRW_Light lightFromMetadata(const LC_DwgAdvancedMetadata::LightRecord &record) {
3537 DRW_Light light;
3538 light.handle = record.handle;
3539 light.parentHandle = record.parentHandle;
3540 light.m_classVersion = record.classVersion;
3541 light.m_name = record.name;
3542 light.m_type = record.type;
3543 light.m_status = record.status;
3544 light.m_color = record.color;
3545 light.m_plotGlyph = record.plotGlyph;
3546 light.m_intensity = record.intensity;
3547 light.m_position = record.position;
3548 light.m_target = record.target;
3549 light.m_attenuationType = record.attenuationType;
3550 light.m_useAttenuationLimits = record.useAttenuationLimits;
3551 light.m_attenuationStartLimit = record.attenuationStartLimit;
3552 light.m_attenuationEndLimit = record.attenuationEndLimit;
3553 light.m_hotspotAngle = record.hotspotAngle;
3554 light.m_falloffAngle = record.falloffAngle;
3555 light.m_castShadows = record.castShadows;
3556 light.m_shadowType = record.shadowType;
3557 light.m_shadowMapSize = record.shadowMapSize;
3558 light.m_shadowMapSoftness = record.shadowMapSoftness;
3559 light.m_hasPhotometricData = record.hasPhotometricData;
3560 light.m_hasWebFile = record.hasWebFile;
3561 light.m_webFile = record.webFile;
3562 light.m_physicalIntensityMethod = record.physicalIntensityMethod;
3563 light.m_physicalIntensity = record.physicalIntensity;
3564 light.m_illuminanceDistance = record.illuminanceDistance;
3565 light.m_lampColorType = record.lampColorType;
3566 light.m_lampColorTemperature = record.lampColorTemperature;
3567 light.m_lampColorPreset = record.lampColorPreset;
3568 light.m_webRotation = record.webRotation;
3569 light.m_extendedLightShape = record.extendedLightShape;
3570 light.m_extendedLightLength = record.extendedLightLength;
3571 light.m_extendedLightWidth = record.extendedLightWidth;
3572 light.m_extendedLightRadius = record.extendedLightRadius;
3573 return light;
3574}
3575
3576DRW_VisualStyle visualStyleFromMetadata(
3577 const LC_DwgAdvancedMetadata::VisualStyleRecord &record) {
3578 DRW_VisualStyle style;
3579 style.handle = record.handle;
3580 style.parentHandle = record.parentHandle;
3581 style.appData = record.appData;
3582 restoreTableEntryExtData(style, record.extData);
3583 style.name = record.name;
3584 style.desc = record.description;
3585 style.type = record.type;
3586 style.m_body = record.body;
3587 style.m_bodyDecoded = record.bodyDecoded;
3588 style.reactorHandles = record.reactorHandles;
3589 style.xDictHandle = record.xDictHandle;
3590 style.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3591 record.hasDsData);
3592 return style;
3593}
3594
3595const char *renderSettingsRecordName(DRW_RenderSettings::Kind kind) {
3596 switch (kind) {
3597 case DRW_RenderSettings::Global:
3598 return "RENDERGLOBAL";
3599 case DRW_RenderSettings::Environment:
3600 return "RENDERENVIRONMENT";
3601 case DRW_RenderSettings::Entry:
3602 return "RENDERENTRY";
3603 case DRW_RenderSettings::RapidRT:
3604 return "RAPIDRTRENDERSETTINGS";
3605 case DRW_RenderSettings::MentalRay:
3606 return "MENTALRAYRENDERSETTINGS";
3607 case DRW_RenderSettings::Settings:
3608 default:
3609 return "RENDERSETTINGS";
3610 }
3611}
3612
3613DRW_RenderSettings renderSettingsFromMetadata(
3614 const LC_DwgAdvancedMetadata::RenderSettingsRecord &record) {
3615 DRW_RenderSettings settings;
3616 settings.handle = record.handle;
3617 settings.parentHandle = record.parentHandle;
3618 settings.appData = record.appData;
3619 restoreTableEntryExtData(settings, record.extData);
3620 settings.m_kind = record.kind;
3621 settings.m_classVersion = record.classVersion;
3622 settings.m_name = record.name;
3623 settings.m_longs = record.longs;
3624 settings.m_strings = record.strings;
3625 settings.m_bools = record.bools;
3626 settings.m_shorts = record.shorts;
3627 settings.m_bytes = record.bytes;
3628 settings.m_doubles = record.doubles;
3629 settings.m_fogEnabled = record.fogEnabled;
3630 settings.m_fogBackgroundEnabled = record.fogBackgroundEnabled;
3631 settings.m_backfacesEnabled = record.backfacesEnabled;
3632 settings.m_environmentImageEnabled = record.environmentImageEnabled;
3633 settings.m_environmentImageFilename = record.environmentImageFilename;
3634 settings.m_description = record.description;
3635 settings.m_displayIndex = record.displayIndex;
3636 settings.m_hasPredefined = record.hasPredefined;
3637 settings.m_hasPredefinedPresent = record.hasPredefinedPresent;
3638 settings.m_fogColorR = record.fogColorR;
3639 settings.m_fogColorG = record.fogColorG;
3640 settings.m_fogColorB = record.fogColorB;
3641 settings.m_fogDensityNear = record.fogDensityNear;
3642 settings.m_fogDensityFar = record.fogDensityFar;
3643 settings.m_fogDistanceNear = record.fogDistanceNear;
3644 settings.m_fogDistanceFar = record.fogDistanceFar;
3645 settings.m_procedure = record.procedure;
3646 settings.m_destination = record.destination;
3647 settings.reactorHandles = record.reactorHandles;
3648 settings.xDictHandle = record.xDictHandle;
3649 settings.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3650 record.hasDsData);
3651 if (record.rawDataValid)
3652 (void)settings.setDwgRawData(record.rawData, record.rawDataBitSize,
3653 record.rawDataVersion);
3654 return settings;
3655}
3656
3657DRW_SunStudy
3658sunStudyFromMetadata(const LC_DwgAdvancedMetadata::SunStudyRecord &record) {
3659 DRW_SunStudy study;
3660 study.handle = record.handle;
3661 study.parentHandle = record.parentHandle;
3662 study.appData = record.appData;
3663 restoreTableEntryExtData(study, record.extData);
3664 study.m_classVersion = record.classVersion;
3665 study.m_setupName = record.setupName;
3666 study.m_description = record.description;
3667 study.m_sheetSetName = record.sheetSetName;
3668 study.m_sheetSubsetName = record.sheetSubsetName;
3669 study.m_outputType = record.outputType;
3670 study.m_useSubset = record.useSubset;
3671 study.m_selectDatesFromCalendar = record.selectDatesFromCalendar;
3672 study.m_selectRangeOfDates = record.selectRangeOfDates;
3673 study.m_lockViewports = record.lockViewports;
3674 study.m_labelViewports = record.labelViewports;
3675 study.m_startTime = record.startTime;
3676 study.m_endTime = record.endTime;
3677 study.m_interval = record.interval;
3678 study.m_shadePlotType = record.shadePlotType;
3679 study.m_viewportCount = record.viewportCount;
3680 study.m_rowCount = record.rowCount;
3681 study.m_columnCount = record.columnCount;
3682 study.m_spacing = record.spacing;
3683 study.m_dates = record.dates;
3684 study.m_hours = record.hours;
3685 study.m_pageSetupWizardHandle = record.pageSetupWizardHandle;
3686 study.m_viewHandle = record.viewHandle;
3687 study.m_visualStyleHandle = record.visualStyleHandle;
3688 study.m_textStyleHandle = record.textStyleHandle;
3689 study.reactorHandles = record.reactorHandles;
3690 study.xDictHandle = record.xDictHandle;
3691 study.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3692 record.hasDsData);
3693 return study;
3694}
3695
3696DRW_MotionPath
3697motionPathFromMetadata(const LC_DwgAdvancedMetadata::MotionPathRecord &record) {
3698 DRW_MotionPath path;
3699 path.handle = record.handle;
3700 path.parentHandle = record.parentHandle;
3701 path.appData = record.appData;
3702 restoreTableEntryExtData(path, record.extData);
3703 path.m_classVersion = record.classVersion;
3704 path.m_cameraPathHandle = record.cameraPathHandle;
3705 path.m_targetPathHandle = record.targetPathHandle;
3706 path.m_viewTableHandle = record.viewTableHandle;
3707 path.m_frames = record.frames;
3708 path.m_frameRate = record.frameRate;
3709 path.m_cornerDeceleration = record.cornerDeceleration;
3710 path.reactorHandles = record.reactorHandles;
3711 path.xDictHandle = record.xDictHandle;
3712 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3713 record.hasDsData);
3714 return path;
3715}
3716
3717DRW_CurvePath
3718curvePathFromMetadata(const LC_DwgAdvancedMetadata::CurvePathRecord &record) {
3719 DRW_CurvePath path;
3720 path.handle = record.handle;
3721 path.parentHandle = record.parentHandle;
3722 path.appData = record.appData;
3723 restoreTableEntryExtData(path, record.extData);
3724 path.m_classVersion = record.classVersion;
3725 path.m_entityHandle = record.entityHandle;
3726 path.reactorHandles = record.reactorHandles;
3727 path.xDictHandle = record.xDictHandle;
3728 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3729 record.hasDsData);
3730 return path;
3731}
3732
3733DRW_PointPath
3734pointPathFromMetadata(const LC_DwgAdvancedMetadata::PointPathRecord &record) {
3735 DRW_PointPath path;
3736 path.handle = record.handle;
3737 path.parentHandle = record.parentHandle;
3738 path.appData = record.appData;
3739 restoreTableEntryExtData(path, record.extData);
3740 path.m_classVersion = record.classVersion;
3741 path.m_point = record.point;
3742 path.reactorHandles = record.reactorHandles;
3743 path.xDictHandle = record.xDictHandle;
3744 path.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3745 record.hasDsData);
3746 return path;
3747}
3748
3749DRW_ObjectPtr
3750objectPtrFromMetadata(const LC_DwgAdvancedMetadata::ObjectPtrRecord &record) {
3751 DRW_ObjectPtr object;
3752 object.handle = record.handle;
3753 object.parentHandle = record.parentHandle;
3754 object.appData = record.appData;
3755 restoreTableEntryExtData(object, record.extData);
3756 object.reactorHandles = record.reactorHandles;
3757 object.xDictHandle = record.xDictHandle;
3758 object.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3759 record.hasDsData);
3760 return object;
3761}
3762
3763DRW_PartialViewingIndex partialViewingIndexFromMetadata(
3764 const LC_DwgAdvancedMetadata::PartialViewingIndexRecord &record) {
3765 DRW_PartialViewingIndex index;
3766 index.handle = record.handle;
3767 index.parentHandle = record.parentHandle;
3768 index.appData = record.appData;
3769 restoreTableEntryExtData(index, record.extData);
3770 index.m_entryCount = static_cast<std::uint32_t>(record.entries.size());
3771 index.m_hasEntries = record.hasEntries && !record.entries.empty();
3772 index.m_entries = record.entries;
3773 index.reactorHandles = record.reactorHandles;
3774 index.xDictHandle = record.xDictHandle;
3775 index.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3776 record.hasDsData);
3777 return index;
3778}
3779
3780DRW_DbColor
3781dbColorFromMetadata(const LC_DwgAdvancedMetadata::DbColorRecord &record) {
3782 DRW_DbColor color;
3783 color.handle = record.handle;
3784 color.parentHandle = record.parentHandle;
3785 color.appData = record.appData;
3786 restoreTableEntryExtData(color, record.extData);
3787 color.reactorHandles = record.reactorHandles;
3788 color.xDictHandle = record.xDictHandle;
3789 color.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3790 record.hasDsData);
3791 color.name = record.name;
3792 color.rgb = record.rgb;
3793 color.colorIndex = record.colorIndex;
3794 color.colorMethod = record.colorMethod;
3795 color.bookName = record.bookName;
3796 return color;
3797}
3798
3799DRW_DimensionAssociation dimensionAssociationFromMetadata(
3800 const LC_DwgAdvancedMetadata::DimensionAssociationRecord &record) {
3801 DRW_DimensionAssociation association;
3802 association.handle = record.handle;
3803 association.parentHandle = record.parentHandle;
3804 association.appData = record.appData;
3805 restoreTableEntryExtData(association, record.extData);
3806 association.reactorHandles = record.reactorHandles;
3807 association.xDictHandle = record.xDictHandle;
3808 association.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3809 record.hasDsData);
3810 association.m_dimensionHandle = record.dimensionHandle;
3811 association.m_associativityFlags = record.associativityFlags;
3812 association.m_isTransSpace = record.isTransSpace;
3813 association.m_rotatedDimensionType = record.rotatedDimensionType;
3814 association.m_osnapRefs = record.osnapRefs;
3815 association.m_hasUnrepresentableDetail = record.hasUnrepresentableDetail;
3816 return association;
3817}
3818
3819bool canWriteNativeDimensionAssociation(
3820 const DRW_DimensionAssociation &association) {
3821 if ((association.m_associativityFlags & ~0x0Fu) != 0 ||
3822 association.m_osnapRefs.size() > 4 ||
3823 association.m_hasUnrepresentableDetail)
3824 return false;
3825
3826 std::size_t activeSlots = 0;
3827 for (std::uint32_t flag = 1; flag <= 8; flag <<= 1) {
3828 if ((association.m_associativityFlags & flag) != 0)
3829 ++activeSlots;
3830 }
3831 return activeSlots == association.m_osnapRefs.size() &&
3832 std::all_of(association.m_osnapRefs.begin(),
3833 association.m_osnapRefs.end(),
3834 [](const DRW_DimensionAssociationOsnapRef &ref) {
3835 return ref.m_objectOsnapType == 0;
3836 });
3837}
3838
3839DRW_EvaluationGraph evaluationGraphFromMetadata(
3840 const LC_DwgAdvancedMetadata::EvaluationGraphRecord &record) {
3841 DRW_EvaluationGraph graph;
3842 graph.handle = record.handle;
3843 graph.parentHandle = record.parentHandle;
3844 graph.appData = record.appData;
3845 restoreTableEntryExtData(graph, record.extData);
3846 graph.reactorHandles = record.reactorHandles;
3847 graph.xDictHandle = record.xDictHandle;
3848 graph.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3849 record.hasDsData);
3850 graph.m_value96 = record.value96;
3851 graph.m_value97 = record.value97;
3852 graph.m_nodes = record.nodes;
3853 graph.m_edges = record.edges;
3854 return graph;
3855}
3856
3857DRW_BlockRepresentationData blockRepresentationFromMetadata(
3858 const LC_DwgAdvancedMetadata::BlockRepresentationRecord &record) {
3859 DRW_BlockRepresentationData data;
3860 data.handle = record.handle;
3861 data.parentHandle = record.parentHandle;
3862 data.appData = record.appData;
3863 restoreTableEntryExtData(data, record.extData);
3864 data.reactorHandles = record.reactorHandles;
3865 data.xDictHandle = record.xDictHandle;
3866 data.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3867 record.hasDsData);
3868 data.m_flag = record.flag;
3869 data.m_blockHandle = record.blockHandle;
3870 return data;
3871}
3872
3873bool canWriteNativeEvaluationGraph(const DRW_EvaluationGraph &graph) {
3874 return graph.m_nodes.size() <= DRW_EvaluationGraph::kMaxEntries &&
3875 graph.m_edges.size() <= DRW_EvaluationGraph::kMaxEntries;
3876}
3877
3878const char *sectionRecordName(DRW_Section::Kind kind) {
3879 return kind == DRW_Section::Settings ? "SECTIONSETTINGS" : "SECTIONMANAGER";
3880}
3881
3882DRW_Section
3883sectionFromMetadata(const LC_DwgAdvancedMetadata::SectionRecord &record) {
3884 DRW_Section section;
3885 section.handle = record.handle;
3886 section.parentHandle = record.parentHandle;
3887 section.appData = record.appData;
3888 restoreTableEntryExtData(section, record.extData);
3889 section.reactorHandles = record.reactorHandles;
3890 section.xDictHandle = record.xDictHandle;
3891 section.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3892 record.hasDsData);
3893 if (record.rawDataValid)
3894 (void)section.setDwgRawData(record.rawData, record.rawDataBitSize,
3895 record.rawDataVersion);
3896 section.m_kind = record.kind;
3897 section.m_isLive = record.isLive;
3898 section.m_sectionCount = record.sectionCount;
3899 section.m_sectionHandles = record.sectionHandles;
3900 section.m_classVersion = record.classVersion;
3901 section.m_sectionType = record.sectionType;
3902 section.m_generationOptions = record.generationOptions;
3903 section.m_currentType = record.currentType;
3904 section.m_typeCount = record.typeCount;
3905 section.m_types = record.types;
3906 section.m_sourceHandle = record.sourceHandle;
3907 section.m_destinationBlockHandle = record.destinationBlockHandle;
3908 section.m_destinationFileHandle = record.destinationFileHandle;
3909 section.m_destinationFile = record.destinationFile;
3910 return section;
3911}
3912
3913DRW_SectionObject sectionObjectFromMetadata(
3914 const LC_DwgAdvancedMetadata::SectionObjectRecord &record) {
3915 DRW_SectionObject section;
3916 section.handle = record.handle;
3917 section.parentHandle = record.parentHandle;
3918 section.m_state = record.state;
3919 section.m_flags = record.flags;
3920 section.m_name = record.name;
3921 section.m_vertDir = record.vertDir;
3922 section.m_topHeight = record.topHeight;
3923 section.m_bottomHeight = record.bottomHeight;
3924 section.m_indicatorAlpha = record.indicatorAlpha;
3925 section.m_indicatorColor = record.indicatorColor;
3926 section.m_verts = record.verts;
3927 section.m_blVerts = record.blVerts;
3928 section.m_sectionSettingsHandle = record.sectionSettingsHandle;
3929 section.extData = record.extData;
3930 return section;
3931}
3932
3933DRW_MLeaderStyle mleaderStyleFromMetadata(
3934 const LC_DwgAdvancedMetadata::MLeaderStyleRecord &record) {
3935 DRW_MLeaderStyle style;
3936 style.handle = record.handle;
3937 style.parentHandle = record.parentHandle;
3938 style.name = record.name;
3939 style.appData = record.appData;
3940 restoreTableEntryExtData(style, record.extData);
3941 style.reactorHandles = record.reactorHandles;
3942 style.xDictHandle = record.xDictHandle;
3943 style.setDwgCommonObjectState(record.numReactors, record.xDictFlag,
3944 record.hasDsData);
3945 style.styleVersion = record.styleVersion != 0 ? record.styleVersion : 2;
3946 style.contentType = record.contentType;
3947 style.drawMLeaderOrder = record.drawMLeaderOrder;
3948 style.drawLeaderOrder = record.drawLeaderOrder;
3949 style.maxLeaderPoints = record.maxLeaderPoints;
3950 style.firstSegmentAngle = record.firstSegmentAngle;
3951 style.secondSegmentAngle = record.secondSegmentAngle;
3952 style.leaderType = record.leaderType;
3953 style.leaderColor = record.leaderColor;
3954 style.leaderLineTypeHandle.ref = record.leaderLineTypeHandle;
3955 style.leaderLineWeight = record.leaderLineWeight;
3956 style.landingEnabled = record.landingEnabled;
3957 style.landingGap = record.landingGap;
3958 style.autoIncludeLanding = record.autoIncludeLanding;
3959 style.landingDistance = record.landingDistance;
3960 style.description = record.description;
3961 style.arrowHeadBlockHandle.ref = record.arrowHeadBlockHandle;
3962 style.arrowHeadSize = record.arrowHeadSize;
3963 style.textDefault = record.textDefault;
3964 style.textStyleHandle.ref = record.textStyleHandle;
3965 style.leftAttachment = record.leftAttachment;
3966 style.rightAttachment = record.rightAttachment;
3967 style.textAngleType = record.textAngleType;
3968 style.textAlignmentType = record.textAlignmentType;
3969 style.textColor = record.textColor;
3970 style.textHeight = record.textHeight;
3971 style.textFrameEnabled = record.textFrameEnabled;
3972 style.alwaysAlignTextLeft = record.alwaysAlignTextLeft;
3973 style.alignSpace = record.alignSpace;
3974 style.blockHandle.ref = record.blockHandle;
3975 style.blockColor = record.blockColor;
3976 style.blockScale = record.blockScale;
3977 style.blockScaleEnabled = record.blockScaleEnabled;
3978 style.blockRotation = record.blockRotation;
3979 style.blockRotationEnabled = record.blockRotationEnabled;
3980 style.blockConnectionType = record.blockConnectionType;
3981 style.scaleFactor = record.scaleFactor;
3982 style.propertyChanged = record.propertyChanged;
3983 style.isAnnotative = record.isAnnotative;
3984 style.breakSize = record.breakSize;
3985 style.attachmentDirection = record.attachmentDirection;
3986 style.topAttachment = record.topAttachment;
3987 style.bottomAttachment = record.bottomAttachment;
3988 style.textExtended = record.textExtended;
3989 return style;
3990}
3991
3992bool validateDwgMLeaderStylePayload(
3993 const LC_DwgAdvancedMetadata::MLeaderStyleRecord &record,
3994 DRW::Version version, std::uint32_t &payloadBitEstimate) {
3995 payloadBitEstimate = 0;
3996 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
3997 if (style.extData.size() != record.extData.size() ||
3998 hasDxfTableAppDataUnsupportedByDwg(style) ||
3999 !style.validateDwgPayload(version)) {
4000 return false;
4001 }
4002
4003 dwgBufferW body;
4004 dwgBufferW strings;
4005 dwgBufferW handles;
4006 if (!style.encodeDwg(version, &body, &strings, &handles))
4007 return false;
4008
4009 std::uint64_t payloadBytes = 0;
4010 for (const dwgBufferW *buffer : {&body, &strings, &handles}) {
4011 const std::uint64_t size = static_cast<std::uint64_t>(buffer->size());
4012 if (payloadBytes > std::numeric_limits<std::uint64_t>::max() - size)
4013 return false;
4014 payloadBytes += size;
4015 }
4016 if (payloadBytes > std::numeric_limits<std::uint32_t>::max() / 8u)
4017 return false;
4018 payloadBitEstimate = static_cast<std::uint32_t>(payloadBytes * 8u);
4019 return true;
4020}
4021
4022std::unique_ptr<LC_SplinePoints>
4023approximateDrwSpline(RS_EntityContainer *parent, const DRW_Spline *data) {
4024 RS_SplineData splineData;
4025 if (!buildSplineDataFromDrw(data, splineData))
4026 return nullptr;
4027
4028 const size_t degree = static_cast<size_t>(data->degree);
4029 const size_t controlCount = splineData.controlPoints.size();
4030 const double tmin = splineData.knotslist[degree];
4031 const double tmax = splineData.knotslist[controlCount];
4032 if (!std::isfinite(tmin) || !std::isfinite(tmax) || tmax <= tmin)
4033 return nullptr;
4034
4035 LC_SplinePointsData sampled((data->flags & 0x3) != 0, false);
4036 sampled.useControlPoints = false;
4037 sampled.splinePoints.reserve(kImportedSplineFallbackSamples + 1);
4038 for (int i = 0; i <= kImportedSplineFallbackSamples; ++i) {
4039 const double u = tmin + (tmax - tmin) * (static_cast<double>(i) /
4040 kImportedSplineFallbackSamples);
4041 const RS_Vector point = RS_Spline::evaluateNURBS(splineData, u);
4042 if (!point.valid)
4043 return nullptr;
4044 sampled.splinePoints.push_back(point);
4045 }
4046
4047 if (sampled.closed && sampled.splinePoints.size() > 1 &&
4048 sampled.splinePoints.front().distanceTo(sampled.splinePoints.back()) <=
4049 1e-8) {
4050 sampled.splinePoints.pop_back();
4051 }
4052
4053 if (sampled.splinePoints.size() < 2)
4054 return nullptr;
4055
4056 return std::make_unique<LC_SplinePoints>(parent, std::move(sampled));
4057}
4058} // namespace
4059
4060/**
4061 * Default constructor.
4062 *
4063 */
4064RS_FilterDXFRW::RS_FilterDXFRW() : RS_FilterInterface(), DRW_Interface() {
4065 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW()");
4066
4067 m_currentContainer = nullptr;
4068 m_graphic = nullptr;
4069 // Init hash to change the QCAD "normal" style to the more correct ISO-3059
4070 // or draftsight symbol (AR*.shx) to sy*.lff
4071 m_fontList["arastro"] = "syastro";
4072 m_fontList["armap"] = "symap";
4073 m_fontList["armeteo"] = "symeteo";
4074 m_fontList["armusic"] = "symusic";
4075 m_fontList["math"] = "symath";
4076 m_fontList["normal"] = "iso";
4077 m_fontList["normallatin1"] = "iso";
4078 m_fontList["normallatin2"] = "iso";
4079
4080 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::RS_FilterDXFRW(): OK");
4081}
4082
4083/**
4084 * Destructor.
4085 */
4086RS_FilterDXFRW::~RS_FilterDXFRW() {
4087 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::~RS_FilterDXFRW(): OK");
4088}
4089
4090// Human-readable display for a DRW::Version. Year suffixes anchor users
4091// in time so they can decide whether to convert (pre-R13) or update their
4092// install (post-R2018 future versions).
4093static QString dwgVersionDisplay(DRW::Version v) {
4094 switch (v) {
4095 case DRW::MC00:
4096 return QStringLiteral("R1.1 (1982)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.1 (1982)")));
4097 case DRW::AC12:
4098 return QStringLiteral("R1.2 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.2 (1983)")));
4099 case DRW::AC14:
4100 return QStringLiteral("R1.4 (1983)")(QString(QtPrivate::qMakeStringPrivate(u"" "R1.4 (1983)")));
4101 case DRW::AC150:
4102 return QStringLiteral("R2.0 (1984)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.0 (1984)")));
4103 case DRW::AC210:
4104 return QStringLiteral("R2.10 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.10 (1986)")));
4105 case DRW::AC1002:
4106 return QStringLiteral("R2.5 (1986)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.5 (1986)")));
4107 case DRW::AC1003:
4108 return QStringLiteral("R2.6 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R2.6 (1987)")));
4109 case DRW::AC1004:
4110 return QStringLiteral("R9 (1987)")(QString(QtPrivate::qMakeStringPrivate(u"" "R9 (1987)")));
4111 case DRW::AC1006:
4112 return QStringLiteral("R10 (1988)")(QString(QtPrivate::qMakeStringPrivate(u"" "R10 (1988)")));
4113 case DRW::AC1009:
4114 return QStringLiteral("R11/R12 (1990)")(QString(QtPrivate::qMakeStringPrivate(u"" "R11/R12 (1990)"))
)
;
4115 case DRW::AC1012:
4116 return QStringLiteral("R13 (1994)")(QString(QtPrivate::qMakeStringPrivate(u"" "R13 (1994)")));
4117 case DRW::AC1014:
4118 return QStringLiteral("R14 (1997)")(QString(QtPrivate::qMakeStringPrivate(u"" "R14 (1997)")));
4119 case DRW::AC1015:
4120 return QStringLiteral("AutoCAD 2000")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2000")));
4121 case DRW::AC1018:
4122 return QStringLiteral("AutoCAD 2004")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2004")));
4123 case DRW::AC1021:
4124 return QStringLiteral("AutoCAD 2007")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2007")));
4125 case DRW::AC1024:
4126 return QStringLiteral("AutoCAD 2010")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2010")));
4127 case DRW::AC1027:
4128 return QStringLiteral("AutoCAD 2013")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2013")));
4129 case DRW::AC1032:
4130 return QStringLiteral("AutoCAD 2018")(QString(QtPrivate::qMakeStringPrivate(u"" "AutoCAD 2018")));
4131 case DRW::UNKNOWNV:
4132 default:
4133 return QStringLiteral("unknown")(QString(QtPrivate::qMakeStringPrivate(u"" "unknown")));
4134 }
4135}
4136
4137// fixme - sand - non informative error message (mixed file format which is
4138// known)
4139QString RS_FilterDXFRW::lastError() const {
4140 switch (m_errorCode) {
4141 case DRW::BAD_NONE:
4142 return (QObject::tr("no DXF/DWG error", "RS_FilterDXFRW"));
4143 case DRW::BAD_OPEN:
4144 return (QObject::tr("error opening DXF/DWG file", "RS_FilterDXFRW"));
4145 case DRW::BAD_VERSION:
4146 if (m_dwgVersion != DRW::UNKNOWNV) {
4147 return QObject::tr(
4148 "Cannot open DWG: file is %1; LibreCAD supports %2 and newer. "
4149 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
4150 "from a recent CAD tool.",
4151 "RS_FilterDXFRW")
4152 .arg(dwgVersionDisplay(m_dwgVersion))
4153 .arg(dwgVersionDisplay(DRW::AC1012));
4154 }
4155 return (QObject::tr("unsupported DXF/DWG file version", "RS_FilterDXFRW"));
4156 case DRW::BAD_READ_METADATA:
4157 return (QObject::tr("error reading DXF/DWG meta data", "RS_FilterDXFRW"));
4158 case DRW::BAD_READ_FILE_HEADER:
4159 return (QObject::tr("error reading DXF/DWG file header", "RS_FilterDXFRW"));
4160 case DRW::BAD_READ_HEADER:
4161 return (QObject::tr("error reading DXF/DWG header data", "RS_FilterDXFRW"));
4162 case DRW::BAD_READ_HANDLES:
4163 return (QObject::tr("error reading DXF/DWG object map", "RS_FilterDXFRW"));
4164 case DRW::BAD_READ_CLASSES:
4165 return (QObject::tr("error reading DXF/DWG classes", "RS_FilterDXFRW"));
4166 case DRW::BAD_READ_TABLES:
4167 return (QObject::tr("error reading DXF/DWG tables", "RS_FilterDXFRW"));
4168 case DRW::BAD_READ_BLOCKS:
4169 return (QObject::tr("error reading DXF/DWG blocks", "RS_FilterDXFRW"));
4170 case DRW::BAD_READ_ENTITIES:
4171 return (QObject::tr("error reading DXF/DWG entities", "RS_FilterDXFRW"));
4172 case DRW::BAD_READ_OBJECTS:
4173 return (QObject::tr("error reading DXF/DWG objects", "RS_FilterDXFRW"));
4174 case DRW::BAD_READ_SECTION:
4175 return (QObject::tr("error reading DXF/DWG sections", "RS_FilterDXFRW"));
4176 case DRW::BAD_CODE_PARSED:
4177 return (QObject::tr("error reading DXF/DWG code", "RS_FilterDXFRW"));
4178 default:
4179 break;
4180 }
4181
4182 return RS_FilterInterface::lastError();
4183}
4184
4185namespace {
4186
4187// Pre-R13 DWG files often omit $ACADVER from the table snapshot delivered to
4188// addHeader(), leaving m_version at the 1021 fallback during entity import.
4189// Sniff the on-disk magic so import-time guards can key off AC1009 et al.
4190int versionIdFromDwgMagic(const QString &filePath) {
4191 QFile file(filePath);
4192 if (!file.open(QIODevice::ReadOnly))
4193 return 0;
4194 const QByteArray magic = file.read(6);
4195 if (magic.size() < 6)
4196 return 0;
4197 if (magic == "AC1009")
4198 return 1009;
4199 if (magic == "AC1012")
4200 return 1012;
4201 if (magic == "AC1014")
4202 return 1014;
4203 if (magic == "AC1015")
4204 return 1015;
4205 if (magic == "AC1018")
4206 return 1018;
4207 if (magic == "AC1021")
4208 return 1021;
4209 if (magic == "AC1024")
4210 return 1024;
4211 if (magic == "AC1027")
4212 return 1027;
4213 if (magic == "AC1032")
4214 return 1032;
4215 return 0;
4216}
4217
4218// Expand NoUpdate INSERTs even after a failed read, which the caller may keep.
4219void updateImportedInserts(RS_Graphic &graphic) {
4220 // BLOCK_CONTROL order is not a dependency order. Resolve definition-owned
4221 // INSERTs first, from their referenced blocks outward, so model-space
4222 // INSERT expansion never snapshots an unresolved nested definition.
4223 std::set<RS_Block *> resolvedBlocks;
4224 std::set<RS_Block *> resolvingBlocks;
4225 const auto updateBlockInserts = [&](auto &&self, RS_Block *block) -> void {
4226 if (block == nullptr || resolvedBlocks.find(block) != resolvedBlocks.end())
4227 return;
4228 if (!resolvingBlocks.insert(block).second)
4229 return; // RS_Insert::update() will reject the reference cycle.
4230 for (RS_Entity *entity : *block) {
4231 if (entity == nullptr || entity->rtti() != RS2::EntityInsert)
4232 continue;
4233 self(self, static_cast<RS_Insert *>(entity)->getBlockForInsert());
4234 }
4235 block->updateInserts();
4236 resolvingBlocks.erase(block);
4237 resolvedBlocks.insert(block);
4238 };
4239 for (unsigned i = 0; i < graphic.countBlocks(); ++i)
4240 updateBlockInserts(updateBlockInserts, graphic.blockAt(i));
4241 graphic.updateInserts();
4242}
4243
4244} // namespace
4245
4246/**
4247 * Implementation of the method used for RS_Import to communicate
4248 * with this filter.
4249 *
4250 * @param g The m_graphic in which the entities from the m_file
4251 * will be created or the graphics from which the entities are
4252 * taken to be stored in a m_file.
4253 */
4254bool RS_FilterDXFRW::fileImport(RS_Graphic &g, const QString &file,
4255 [[maybe_unused]] RS2::FormatType type) {
4256 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport");
4257 RS_DEBUGRS_Debug::instance()->print("DXFRW Filter: importing file '%s'...",
4258 (const char *)QFile::encodeName(file));
4259
4260 m_graphic = &g;
4261 m_currentContainer = m_graphic;
4262 m_dummyContainer = new RS_EntityContainer(nullptr, true);
4263 // RAII: free the scratch container on every return path (success and the
4264 // BAD_VERSION / parse-failure early returns), not just the success path.
4265 struct DummyContainerGuard {
4266 RS_EntityContainer **p;
4267 ~DummyContainerGuard() {
4268 delete *p;
4269 *p = nullptr;
4270 }
4271 } dummyGuard{&m_dummyContainer};
4272
4273 this->m_file = file;
4274 // Register the file being loaded into the XREF recursion guard so
4275 // A → B → A cycles are detected at depth 2 rather than depth 4.
4276 // Done via QFileInfo::absoluteFilePath() to match resolveXrefPath()
4277 // (which returns absolute paths). RAII-erased on return so failure
4278 // paths (BAD_VERSION, parse failure, etc.) don't leak the entry.
4279 const QString thisFileAbs = QFileInfo(file).absoluteFilePath();
4280 const bool stackInsertedSelf = m_xrefStack.insert(thisFileAbs).second;
4281 struct XrefStackGuard {
4282 std::set<QString> *stack = nullptr;
4283 QString key;
4284 bool owned = false;
4285 ~XrefStackGuard() {
4286 if (stack && owned)
4287 stack->erase(key);
4288 }
4289 } stackGuard{&m_xrefStack, thisFileAbs, stackInsertedSelf};
4290 // add some variables that need to be there for DXF drawings:
4291 m_graphic->addVariable("$DIMSTYLE", "Standard", 2);
4292 m_dimStyle = "Standard";
4293 m_codePage = "ANSI_1252";
4294 m_textStyle = "Standard";
4295 // reset library version
4296 m_isLibDxfRw = false;
4297 m_libDxfRwVersion = 0;
4298 m_dxflibMajor = 0;
4299 m_legacyHatchRadians = false;
4300 m_unsupportedDwgObjects.clear();
4301 m_graphic->dwgAdvancedMetadata().clear();
4302 // Clear per-import caches so a reused filter instance does not carry stale
4303 // (and, for m_blockHash, potentially dangling) state across imports. XREF
4304 // sub-imports use a separate child filter, so this never wipes parent state.
4305 m_blockHash.clear();
4306 m_importLayerCache.clear();
4307 m_importLayerRawCache.clear();
4308 m_mlineStyleCache.clear();
4309 m_underlayDefMap.clear();
4310 m_xrefBlockNames.clear();
4311 m_pendingDimStyleArrowRefs.clear();
4312
4313#ifdef DWGSUPPORT1
4314 if (type == RS2::FormatDWG || type == RS2::FormatDWG2004 ||
4315 type == RS2::FormatDWG2007 || type == RS2::FormatDWG2010 ||
4316 type == RS2::FormatDWG2013 || type == RS2::FormatDWG2018) {
4317 const int magicVersion = versionIdFromDwgMagic(file);
4318 if (magicVersion != 0)
4319 m_version = magicVersion;
4320
4321 dwgR dwgr(QFile::encodeName(file));
4322 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file");
4323 if (RS_DEBUGRS_Debug::instance()->getLevel() == RS_Debug::D_DEBUGGING)
4324 dwgr.setDebug(DRW::DebugLevel::Debug);
4325 const auto dwgReadStart = std::chrono::steady_clock::now();
4326 bool success = dwgr.read(this, true);
4327 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
4328 const auto dwgReadMs =
4329 std::chrono::duration_cast<std::chrono::milliseconds>(
4330 std::chrono::steady_clock::now() - dwgReadStart)
4331 .count();
4332 std::cerr << "[import-bench] dwg_read_ms=" << dwgReadMs << "\n";
4333 }
4334 // Capture the recognized version BEFORE acting on the result so
4335 // BAD_VERSION error reporting (printDwgError / lastError) can
4336 // name the format the user supplied. dwgR::version is set by
4337 // openFile() even on the BAD_VERSION fork.
4338 m_dwgVersion = dwgr.getVersion();
4339 // Persist the source version on the document so the export filter can
4340 // gate raw-replay (emit + CLASSES registration) on source==target.
4341 m_graphic->dwgAdvancedMetadata().setSourceDwgVersion(m_dwgVersion);
4342 // Save writes the drawing back in the version it was read in, if it can
4343 m_graphic->setFormatType(formatForDwgVersion(m_dwgVersion));
4344 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading DWG file: OK");
4345 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4346 QObject::tr("Opened DWG file version %1.")
4347 .arg(printDwgVersion(dwgr.getVersion())));
4348 std::cout << "DWG file version: "
4349 << printDwgVersion(dwgr.getVersion()).toStdString() << "\n";
4350 const size_t parseFailures = dwgr.getEntityParseFailures();
4351 if (parseFailures > 0) {
4352 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4353 QObject::tr("DWG load: %1 %2 had parse errors and were skipped. "
4354 "Drawing loaded with the rest.")
4355 .arg(parseFailures)
4356 .arg(parseFailures == 1 ? QObject::tr("entity")
4357 : QObject::tr("entities")));
4358 }
4359 // A drawing that keeps the entries or the leftovers of erased block
4360 // records is readable, but it is not a clean one: say so, rather than
4361 // leaving the user to wonder why the entity count above is not zero.
4362 const size_t danglingBlockRecords = dwgr.getDanglingBlockRecords();
4363 if (danglingBlockRecords > 0) {
4364 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4365 QObject::tr("DWG load: the block table lists %n block record(s) "
4366 "that the file does not contain (erased). They were "
4367 "skipped.",
4368 "", static_cast<int>(danglingBlockRecords)));
4369 }
4370 const size_t orphanedEntities = dwgr.getOrphanedEntities();
4371 if (orphanedEntities > 0) {
4372 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4373 QObject::tr("DWG load: %n entity(ies) belonged to block records "
4374 "that no longer exist (erased) and were skipped.",
4375 "", static_cast<int>(orphanedEntities)));
4376 }
4377 // Vendor-extension custom classes (AutoCAD Mechanical AmgStdPart aka
4378 // STDPART2D, AcmBomRow, etc.) whose proprietary geometry libdxfrw
4379 // can't decode. Surface the top breakdown so the user knows what's
4380 // missing rather than silently rendering a partial drawing.
4381 const auto skipped = dwgr.getSkippedCustomClasses();
4382 if (!skipped.empty()) {
4383 size_t totalSkipped = 0;
4384 std::vector<std::pair<QString, size_t>> sorted;
4385 sorted.reserve(skipped.size());
4386 for (const auto &kv : skipped) {
4387 totalSkipped += kv.second;
4388 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
4389 }
4390 std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) {
4391 return a.second > b.second;
4392 });
4393 QStringList top;
4394 const size_t showN = std::min<size_t>(3, sorted.size());
4395 for (size_t i = 0; i < showN; ++i)
4396 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
4397 QString breakdown = top.join(QLatin1String(", "));
4398 if (sorted.size() > showN)
4399 breakdown += QObject::tr(", and %n more class(es)", "",
4400 static_cast<int>(sorted.size() - showN));
4401 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4402 QObject::tr(
4403 "DWG load: %1 vendor-extension entities not rendered (%2). "
4404 "These are typically AutoCAD Mechanical or other "
4405 "vertical-product "
4406 "custom classes that libdxfrw cannot decode.")
4407 .arg(totalSkipped)
4408 .arg(breakdown));
4409 }
4410 const auto unsupportedObjects = dwgr.getSkippedUnsupportedObjects();
4411 if (!unsupportedObjects.empty()) {
4412 size_t totalSkipped = 0;
4413 std::vector<std::pair<QString, size_t>> sorted;
4414 sorted.reserve(unsupportedObjects.size());
4415 for (const auto &kv : unsupportedObjects) {
4416 totalSkipped += kv.second;
4417 sorted.emplace_back(QString::fromStdString(kv.first), kv.second);
4418 }
4419 std::sort(sorted.begin(), sorted.end(), [](const auto &a, const auto &b) {
4420 return a.second > b.second;
4421 });
4422 QStringList top;
4423 const size_t showN = std::min<size_t>(3, sorted.size());
4424 for (size_t i = 0; i < showN; ++i)
4425 top << QString("%1×%2").arg(sorted[i].second).arg(sorted[i].first);
4426 QString breakdown = top.join(QLatin1String(", "));
4427 if (sorted.size() > showN)
4428 breakdown += QObject::tr(", and %n more object type(s)", "",
4429 static_cast<int>(sorted.size() - showN));
4430 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4431 QObject::tr("DWG load: %1 unsupported metadata object(s) skipped "
4432 "(%2). Drawing geometry may still be complete.")
4433 .arg(totalSkipped)
4434 .arg(breakdown));
4435 }
4436 RS_DEBUGRS_Debug::instance()->print("DWG read summary: %d entities, %d blocks, error=%d",
4437 m_graphic ? m_graphic->count() : -1,
4438 m_graphic ? m_graphic->countBlocks() : -1, dwgr.getError());
4439 int lastError = dwgr.getError();
4440 if (false == success) {
4441 printDwgError(lastError);
4442 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DWG file '%s'.",
4443 (const char *)QFile::encodeName(file));
4444 m_errorCode = dwgr.getError();
4445 updateImportedInserts(*m_graphic);
4446 return false;
4447 }
4448 m_graphic->dwgAdvancedMetadata().markVportsImported();
4449 } else {
4450#endif
4451
4452 // A caller may explicitly accept a partial import via RS_FileIO's error
4453 // callback. Protect the original before parsing, not only on full success.
4454 if (m_dxfTextEncoding == RS2::DxfTextEncoding::LegacyLibreCadUtf8)
4455 m_graphic->setDxfRecoverySourceFile(QFileInfo(file).absoluteFilePath());
4456 m_dxfR = new dxfRW(QFile::encodeName(file));
4457 m_dxfR->setDxfTextEncoding(
4458 m_dxfTextEncoding == RS2::DxfTextEncoding::LegacyLibreCadUtf8
4459 ? DRW::DxfTextEncoding::LegacyLibreCadUtf8
4460 : DRW::DxfTextEncoding::Declared);
4461
4462 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file");
4463 if (RS_Debug::D_DEBUGGING == RS_DEBUGRS_Debug::instance()->getLevel()) {
4464 m_dxfR->setDebug(DRW::DebugLevel::Debug);
4465 }
4466 bool success{false};
4467 if (file.startsWith(":")) {
4468 // load content from resources. It SHOULD be present in resource!
4469 QFile resourceFile(file);
4470 if (resourceFile.open(QIODevice::ReadOnly)) {
4471 QByteArray contentString = resourceFile.readAll();
4472 resourceFile.close();
4473 std::string content = contentString.toStdString();
4474 success = m_dxfR->readAscii(this, true, content);
4475 }
4476 } else {
4477 success = m_dxfR->read(this, true);
4478 }
4479 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: reading file: OK");
4480 if (success) {
4481 std::cout << "DXF file version: "
4482 << printDwgVersion(m_dxfR->getSourceVersion()).toStdString() << "\n";
4483 // Save writes the drawing back in the version it was read in, if it can
4484 m_graphic->setFormatType(formatForDxfVersion(m_dxfR->getSourceVersion()));
4485 // The whole file, so every TABLES entry including BLOCK_RECORD, has now
4486 // been read while m_dxfR (and its reading context) is still alive.
4487 resolvePendingDimStyleArrowBlocks();
4488 // The VPORT records are the file's own, as those of a DWG are. The DWG
4489 // writer refuses an "*Active" record that no reader delivered.
4490 m_graphic->dwgAdvancedMetadata().markVportsImported();
4491 }
4492 // graphic->setAutoUpdateBorders(true);
4493
4494 if (false == success) {
4495 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "Cannot open DXF file '%s'.",
4496 (const char *)QFile::encodeName(file));
4497 m_errorCode = m_dxfR->getError();
4498 delete m_dxfR;
4499 updateImportedInserts(*m_graphic);
4500 return false;
4501 } else {
4502 delete m_dxfR;
4503 }
4504#ifdef DWGSUPPORT1
4505 }
4506#endif
4507
4508 /*set current layer */
4509 auto cl = m_graphic->findLayer(m_graphic->getVariableString("$CLAYER", "0"));
4510 if (cl) {
4511 // require to notify; as saved, also when it is hidden or locked
4512 m_graphic->restoreActiveLayer(cl);
4513 }
4514 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport: updating inserts");
4515 const auto updateInsertsStart = std::chrono::steady_clock::now();
4516 updateImportedInserts(*m_graphic);
4517 if (std::getenv("LC_IMPORT_BENCH") != nullptr) {
4518 const auto updateInsertsMs =
4519 std::chrono::duration_cast<std::chrono::milliseconds>(
4520 std::chrono::steady_clock::now() - updateInsertsStart)
4521 .count();
4522 int modelInserts = 0;
4523 for (RS_Entity *e : *m_graphic) {
4524 if (e != nullptr && e->rtti() == RS2::EntityInsert)
4525 ++modelInserts;
4526 }
4527 std::cerr << "[import-bench] updateInserts_ms=" << updateInsertsMs
4528 << " blocks=" << m_graphic->countBlocks()
4529 << " model_entities=" << m_graphic->count()
4530 << " model_inserts=" << modelInserts << "\n";
4531 }
4532
4533 // Orphan XREF detection. An XREF block whose contents were embedded
4534 // is still invisible in modelspace unless something INSERTs it.
4535 // AutoCAD typically renders these through a paper-space layout
4536 // viewport, which LibreCAD doesn't implement, so the user should
4537 // know why the externally-referenced geometry isn't visible.
4538 // Only run for the outer fileImport (m_xrefStack empty); skip when
4539 // recursively loading an XREF source.
4540 if (m_xrefStack.empty() && !m_xrefBlockNames.empty()) {
4541 std::set<QString> referenced;
4542 for (auto *e : *m_graphic) {
4543 if (e && e->rtti() == RS2::EntityInsert) {
4544 referenced.insert(static_cast<RS_Insert *>(e)->getName());
4545 }
4546 }
4547 QStringList orphans;
4548 for (const QString &xrefName : m_xrefBlockNames) {
4549 if (!referenced.count(xrefName))
4550 orphans << xrefName;
4551 }
4552 if (!orphans.isEmpty()) {
4553 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4554 QObject::tr("DWG/DXF load: %1 XREF block(s) (%2) loaded but not "
4555 "INSERTed into modelspace. Their externally-referenced "
4556 "geometry won't be visible — AutoCAD typically renders "
4557 "these through a paper-space layout viewport, which "
4558 "LibreCAD doesn't render.")
4559 .arg(orphans.size())
4560 .arg(orphans.join(QStringLiteral(", ")(QString(QtPrivate::qMakeStringPrivate(u"" ", "))))));
4561 }
4562 }
4563
4564 // Count pattern hatches whose density guard tripped and tint-fill them in
4565 // model-space, every block and every top-level insert's direct children.
4566 // One summary message per import, so the user is told that some fills are
4567 // shown as tints rather than pattern lines.
4568 const auto denseFromHatch = [](const RS_Entity* e) -> bool {
4569 if (e == nullptr || e->rtti() != RS2::EntityHatch) return false;
4570 const auto* h = static_cast<const RS_Hatch*>(e);
4571 return h->getUpdateError() == RS_Hatch::HATCH_TOO_DENSE;
4572 };
4573 int tooDenseCount = 0;
4574 if (m_graphic != nullptr) {
4575 for (const RS_Entity* en : std::as_const(*m_graphic)) {
4576 if (denseFromHatch(en)) ++tooDenseCount;
4577 if (en != nullptr && en->rtti() == RS2::EntityInsert) {
4578 const auto* ins = static_cast<const RS_EntityContainer*>(en);
4579 for (const RS_Entity* child : std::as_const(*ins)) {
4580 if (denseFromHatch(child)) ++tooDenseCount;
4581 }
4582 }
4583 }
4584 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
4585 const RS_Block* blk = m_graphic->blockAt(i);
4586 if (blk == nullptr) continue;
4587 for (const RS_Entity* en : std::as_const(*blk)) {
4588 if (denseFromHatch(en)) ++tooDenseCount;
4589 }
4590 }
4591 }
4592 if (tooDenseCount > 0) {
4593 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
4594 LC_HatchDensityText::importSummary(tooDenseCount));
4595 }
4596
4597 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileImport OK");
4598 return true;
4599}
4600
4601/**
4602 * Implementation of the method which handles layers.
4603 */
4604RS_Layer *
4605RS_FilterDXFRW::importLayerForEntity(const QString &layName,
4606 const std::string &rawLayerName) {
4607 // Fast path: most entities repeat one of a handful of distinct layer
4608 // names, so a raw-string hit here skips the NFC-normalization pass below
4609 // entirely. A raw-key hit can only return a layer that was itself
4610 // resolved (and validated) via the normalized-key path below for this
4611 // exact byte-identical string, so there is no correctness difference
4612 // from a miss -- just a skipped normalization.
4613 auto rawCached = m_importLayerRawCache.constFind(layName);
4614 if (rawCached != m_importLayerRawCache.constEnd())
4615 return rawCached.value();
4616
4617 const QString key = layName.normalized(QString::NormalizationForm_C);
4618 auto cached = m_importLayerCache.constFind(key);
4619 if (cached != m_importLayerCache.constEnd()) {
4620 m_importLayerRawCache.insert(layName, cached.value());
4621 return cached.value();
4622 }
4623
4624 RS_Layer *layer = m_graphic->findLayer(layName);
4625 if (layer == nullptr) {
4626 DRW_Layer lay;
4627 lay.name = rawLayerName;
4628 addLayer(lay);
4629 layer = m_graphic->findLayer(layName);
4630 }
4631 if (layer != nullptr) {
4632 m_importLayerCache.insert(key, layer);
4633 m_importLayerRawCache.insert(layName, layer);
4634 }
4635 return layer;
4636}
4637
4638void RS_FilterDXFRW::addLayer(const DRW_Layer &data) {
4639 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer");
4640 RS_DEBUGRS_Debug::instance()->print(" adding layer: %s", data.name.c_str());
4641
4642 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: creating layer");
4643
4644 QString name = QString::fromUtf8(data.name.c_str());
4645 const QString key = name.normalized(QString::NormalizationForm_C);
4646 if (name != "0") {
4647 auto cached = m_importLayerCache.constFind(key);
4648 if (cached != m_importLayerCache.constEnd())
4649 return;
4650 RS_Layer *existing = m_graphic->findLayer(name);
4651 if (existing != nullptr) {
4652 m_importLayerCache.insert(key, existing);
4653 return;
4654 }
4655 }
4656 auto *layer = new RS_Layer(name);
4657 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: set pen");
4658 layer->setPen(attributesToPen(&data));
4659
4660 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: flags");
4661 // AutoCAD turns a layer off with a negative colour, and keeps that apart from
4662 // frozen: either hides the layer, which remembers how it was saved
4663 const bool frozen = (data.flags & 0x01) != 0;
4664 const bool off = data.color < 0;
4665 if (frozen || off) {
4666 layer->freeze(true);
4667 layer->setHiddenAs(!off ? RS_Layer::HiddenAs::Frozen
4668 : frozen ? RS_Layer::HiddenAs::FrozenAndOff
4669 : RS_Layer::HiddenAs::Off);
4670 }
4671 if (data.flags & 0x04) {
4672 layer->lock(true);
4673 }
4674 layer->setPrint(data.plotF);
4675
4676 // parse extended data to read construction flag
4677 if (!data.extData.empty()) {
4678 if (!isIgnorableLayerExtData(data.extData)) {
4679 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_DEBUGGING,
4680 "RS_FilterDXFRW::addLayer: layer %s has extended data",
4681 layer->getName().toStdString().c_str());
4682 }
4683 if (classifyLayerConstructionMarker(data.extData) ==
4684 LayerConstructionMarkerStatus::Valid) {
4685 layer->setConstruction(true);
4686 }
4687 }
4688 // pre dxfrw 0.5.13 plot flag are used to store construction layer
4689 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 13)(((0) << 16) | ((5) << 8) | (13))) {
4690 layer->setConstruction(!data.plotF);
4691 }
4692
4693 if (layer->isConstruction()) {
4694 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
4695 "RS_FilterDXF::addLayer: layer %s is construction layer",
4696 layer->getName().toStdString().c_str());
4697 }
4698
4699 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: add layer to graphic");
4700 m_graphic->addLayer(layer);
4701 // RS_LayerList::add() deletes `layer` and updates the existing layer in
4702 // place instead when one with this name already exists (documented on
4703 // that function) - which happens for "0" on every import, since
4704 // RS_Graphic always bootstraps a default "0" layer before this runs.
4705 // Re-resolve rather than trusting `layer`, which may already be freed.
4706 layer = m_graphic->findLayer(name);
4707 m_graphic->dwgAdvancedMetadata().addLayerTableEntry(data);
4708 m_importLayerCache.insert(key, layer);
4709 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLayer: OK");
4710}
4711
4712void RS_FilterDXFRW::addLType(const DRW_LType &data) {
4713 if (m_graphic == nullptr || data.name.empty())
4714 return;
4715 m_graphic->dwgAdvancedMetadata().addLineTypeName(data);
4716 auto *entry = new LC_LineType(QString::fromUtf8(data.name.c_str()));
4717 entry->description = QString::fromUtf8(data.desc.c_str());
4718 entry->pattern = data.path;
4719 entry->hasImportedRecord = true;
4720 m_graphic->addLineType(entry);
4721}
4722
4723void RS_FilterDXFRW::addTextStyle(const DRW_Textstyle &data) {
4724 if (m_graphic != nullptr)
4725 m_graphic->dwgAdvancedMetadata().addTextStyleName(data);
4726}
4727
4728void RS_FilterDXFRW::addAppId(const DRW_AppId &data) {
4729 if (m_graphic != nullptr)
4730 m_graphic->dwgAdvancedMetadata().addAppIdTableEntry(data);
4731}
4732
4733/**
4734 * Implementation of the method which handles dimension styles.
4735 */
4736void RS_FilterDXFRW::addDimStyle(const DRW_Dimstyle &data) {
4737 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayer");
4738 m_graphic->dwgAdvancedMetadata().addDimStyleTableEntry(data);
4739 QString dimStyleName = m_graphic->getVariableString("$DIMSTYLE", "standard");
4740
4741 if (QString::compare(data.name.c_str(), dimStyleName, Qt::CaseInsensitive) ==
4742 0) {
4743 if (m_isLibDxfRw && m_libDxfRwVersion < LIBDXFRW_VERSION(0, 6, 2)(((0) << 16) | ((6) << 8) | (2))) {
4744 m_graphic->addVariable(
4745 "$DIMDEC",
4746 m_graphic->getVariableInt("$DIMDEC",
4747 m_graphic->getVariableInt("$LUPREC", 4)),
4748 70);
4749 m_graphic->addVariable(
4750 "$DIMADEC",
4751 m_graphic->getVariableInt("$DIMADEC",
4752 m_graphic->getVariableInt("$AUPREC", 2)),
4753 70);
4754 // do nothing;
4755 } else {
4756 m_graphic->addVariable("$DIMDEC", data.dimdec, 70);
4757 m_graphic->addVariable("$DIMADEC", data.dimadec, 70);
4758 }
4759 }
4760
4761 LC_DimStyle *dimStyle = createDimStyle(data);
4762 m_graphic->addDimStyle(dimStyle);
4763}
4764
4765// todo - sand - ucs - rework to direct setup of LC_GraphicViewport instead of
4766// RS_GraphicView But this modification requires cleanup of the overall file
4767// open flow, so leave it as it is for now
4768/**
4769 * Implementation of the method which handles vports.
4770 */
4771void RS_FilterDXFRW::addVport(const DRW_Vport &data) {
4772 if (m_graphic != nullptr)
4773 m_graphic->dwgAdvancedMetadata().addVport(data);
4774 QString name = QString::fromStdString(data.name);
4775 if (name.toLower() == "*active") {
4776 data.grid == 1 ? m_graphic->setGridOn(true) : m_graphic->setGridOn(false);
4777 m_graphic->setIsometricGrid(data.snapStyle);
4778 m_graphic->setIsoView(static_cast<RS2::IsoGridViewType>(data.snapIsopair));
4779 const RS_GraphicView *gv =
4780 m_graphic->getGraphicView(); // fixme - sand - review this dependency
4781 if (gv != nullptr) {
4782 const double width = data.height * data.ratio;
4783 // todo - sand - ucs - investigate support/usage of different x and y
4784 // factors.
4785 double factorX = gv->getWidth() / width;
4786 const double factorY = gv->getHeight() / data.height;
4787 if (factorX > factorY) {
4788 factorX = factorY;
4789 }
4790 const int ox = gv->getWidth() - data.center.x * 2 * factorX;
4791 const int oy = gv->getHeight() - data.center.y * 2 * factorX;
4792 gv->getViewPort()->justSetOffsetAndFactor(ox, oy, factorX);
4793 }
4794 }
4795}
4796
4797void RS_FilterDXFRW::addUCS(const DRW_UCS &data) {
4798 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS");
4799 RS_DEBUGRS_Debug::instance()->print(" adding ucs: %s", data.name.c_str());
4800 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: creating ucs");
4801
4802 const QString name = QString::fromUtf8(data.name.c_str());
4803 if (m_graphic != nullptr)
4804 m_graphic->dwgAdvancedMetadata().addUcs(data);
4805 if (!name.isEmpty() && m_graphic->findNamedUCS(name) != nullptr) {
4806 const int existingIndex = m_graphic->getUCSList()->getIndex(name);
4807 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(
4808 data.handle, data.name, existingIndex);
4809 return;
4810 }
4811
4812 auto *ucs = new LC_UCS(name);
4813 const auto origin = RS_Vector(data.origin.x, data.origin.y, data.origin.z);
4814 ucs->setOrigin(origin);
4815
4816 ucs->setElevation(data.elevation);
4817 ucs->setOrthoType(data.orthoType);
4818
4819 const auto orthoOrigin =
4820 RS_Vector(data.orthoOrigin.x, data.orthoOrigin.y, data.orthoOrigin.z);
4821 ucs->setOrthoOrigin(orthoOrigin);
4822
4823 const auto xAxis = RS_Vector(data.xAxisDirection.x, data.xAxisDirection.y,
4824 data.xAxisDirection.z);
4825 ucs->setXAxis(xAxis);
4826
4827 const auto yAxis = RS_Vector(data.yAxisDirection.x, data.yAxisDirection.y,
4828 data.yAxisDirection.z);
4829 ucs->setYAxis(yAxis);
4830
4831 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: add ucs to graphic");
4832 m_graphic->addUCS(ucs);
4833 const int documentItemIndex = m_graphic->getUCSList()->getIndex(name);
4834 m_graphic->dwgAdvancedMetadata().mapUcsToDocumentItem(data.handle, data.name,
4835 documentItemIndex);
4836 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addUCS: OK");
4837}
4838
4839void RS_FilterDXFRW::addView(const DRW_View &data) {
4840 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView");
4841 RS_DEBUGRS_Debug::instance()->print(" adding view: %s", data.name.c_str());
4842 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: creating view");
4843
4844 if (m_graphic != nullptr) {
4845 m_graphic->dwgAdvancedMetadata().addView(data);
4846 }
4847 QString name = QString::fromUtf8(data.name.c_str());
4848 if (!name.isEmpty() && m_graphic->findNamedView(name) != nullptr) {
4849 const int existingIndex = m_graphic->getViewList()->getIndex(name);
4850 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(
4851 data.handle, data.name, existingIndex);
4852 return;
4853 }
4854 auto *view = new LC_View(name);
4855 const auto center = RS_Vector(data.center.x, data.center.y, data.center.z);
4856 view->setCenter(center);
4857
4858 const auto size = RS_Vector(data.size.x, data.size.y, data.size.z);
4859 view->setSize(size);
4860
4861 const auto targetPoint =
4862 RS_Vector(data.targetPoint.x, data.targetPoint.y, data.targetPoint.z);
4863 view->setTargetPoint(targetPoint);
4864
4865 const auto viewDirection =
4866 RS_Vector(data.viewDirectionFromTarget.x, data.viewDirectionFromTarget.y,
4867 data.viewDirectionFromTarget.z);
4868 view->setViewDirection(viewDirection);
4869
4870 view->setLensLen(data.lensLen);
4871 view->setCameraPlottable(data.cameraPlottable);
4872
4873 view->setRenderMode(data.renderMode);
4874 view->setBackClippingPlaneOffset(data.backClippingPlaneOffset);
4875 view->setFrontClippingPlaneOffset(data.frontClippingPlaneOffset);
4876 view->setTwistAngle(data.twistAngle);
4877 view->setFlags(data.flags); // todo - review, use differ properties?
4878 view->setViewMode(
4879 data.viewMode); // todo - probably it might be simpler than long?
4880
4881 if (data.hasUCS) {
4882 const auto ucs = new LC_UCS();
4883
4884 const auto ucsOrigin =
4885 RS_Vector(data.ucsOrigin.x, data.ucsOrigin.y, data.ucsOrigin.z);
4886 const auto ucsXAxis =
4887 RS_Vector(data.ucsXAxis.x, data.ucsXAxis.y, data.ucsXAxis.z);
4888 const auto ucsYAxis =
4889 RS_Vector(data.ucsYAxis.x, data.ucsYAxis.y, data.ucsYAxis.z);
4890 ucs->setOrthoOrigin(RS_Vector(false));
4891 ucs->setOrigin(ucsOrigin);
4892 ucs->setXAxis(ucsXAxis);
4893 ucs->setYAxis(ucsYAxis);
4894 ucs->setElevation(data.ucsElevation);
4895 ucs->setOrthoType(data.ucsOrthoType);
4896 view->setUCS(ucs);
4897 }
4898
4899 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: set pen");
4900
4901 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: add view to graphic");
4902 m_graphic->addNamedView(view);
4903 const int documentItemIndex = m_graphic->getViewList()->getIndex(name);
4904 m_graphic->dwgAdvancedMetadata().mapViewToDocumentItem(data.handle, data.name,
4905 documentItemIndex);
4906 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addView: OK");
4907}
4908
4909void RS_FilterDXFRW::addVisualStyle(const DRW_VisualStyle &data) {
4910 if (m_graphic != nullptr)
4911 m_graphic->dwgAdvancedMetadata().addVisualStyle(data);
4912}
4913
4914/**
4915 * Implementation of the method which handles blocks.
4916 *
4917 * @todo Adding blocks to blocks (stack for m_currentContainer)
4918 */
4919void RS_FilterDXFRW::addBlock(const DRW_Block &data) {
4920 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addBlock");
4921 RS_DEBUGRS_Debug::instance()->print(" adding block: %s", data.name.c_str());
4922 /*TODO correct handle of model-space*/
4923
4924 const QString name = QString::fromUtf8(data.name.c_str());
4925 const QString mid = name.mid(1, 11);
4926 // Prevent special blocks (paper_space, model_space) from being added:
4927 if (mid.toLower() != "paper_space" && mid.toLower() != "model_space") {
4928 RS_Vector bp(data.basePoint.x, data.basePoint.y);
4929 RS_BlockData blockData(name, bp, false);
4930 blockData.previewData = data.previewData;
4931 auto block = new RS_Block(m_graphic, blockData);
4932 block->setInsertionUnits(data.insUnits);
4933 if (data.parentHandle != DRW::NoHandle)
4934 block->setSourceHandle(static_cast<quint32>(data.parentHandle));
4935 // block->setFlags(flags);
4936
4937 if (m_graphic->addBlock(block)) {
4938 m_currentContainer = block;
4939 m_blockHash.insert(data.parentHandle, m_currentContainer);
4940
4941 // Bound/unbound XREF: load the external file and embed its
4942 // modelspace contents into this block. Layers are namespaced
4943 // `<blockName>|<layerName>` per AutoCAD BIND convention.
4944 // Bit 0x04 = XREF, bit 0x08 = XREF overlay.
4945 const bool isXref = (data.flags & 0x0c) != 0;
4946 if (isXref) {
4947 m_xrefBlockNames.insert(name);
4948 if (!data.xrefPath.empty()) {
4949 embedXref(block, QString::fromUtf8(data.xrefPath.c_str()), name);
4950 }
4951 }
4952 } else {
4953 // RS_BlockList::add returns false on name collision and
4954 // deletes the block. Without updating m_currentContainer,
4955 // subsequent addEntity calls would leak into whatever the
4956 // previous endBlock left behind (typically m_graphic). Route
4957 // them to m_dummyContainer so the failure is contained.
4958 m_blockHash.insert(data.parentHandle, m_dummyContainer);
4959 m_currentContainer = m_dummyContainer;
4960 }
4961 } else {
4962 if (mid.toLower() == "model_space") {
4963 m_blockHash.insert(data.parentHandle, m_graphic);
4964 } else {
4965 m_blockHash.insert(data.parentHandle, m_dummyContainer);
4966 }
4967 // Layouts name these block records; a save must find them by name.
4968 m_graphic->dwgAdvancedMetadata().addSpaceBlockRecord(
4969 static_cast<std::uint32_t>(data.parentHandle), data.name);
4970 }
4971}
4972
4973QString RS_FilterDXFRW::resolveXrefPath(const QString &xrefPath) const {
4974 if (xrefPath.isEmpty())
4975 return {};
4976
4977 // Normalize separators: XREFs authored on Windows store backslashes
4978 // that QFileInfo doesn't recognize on Unix.
4979 QString normalized = xrefPath;
4980 normalized.replace('\\', '/');
4981
4982 // 1. Try the stored path verbatim (could be absolute on this host).
4983 if (QFileInfo::exists(normalized))
4984 return QFileInfo(normalized).absoluteFilePath();
4985
4986 QFileInfo host(m_file);
4987 const QString hostDir = host.absolutePath();
4988
4989 // 2. Treat stored path as relative to host file's directory.
4990 {
4991 const QString f = hostDir + "/" + normalized;
4992 if (QFileInfo::exists(f))
4993 return QFileInfo(f).absoluteFilePath();
4994 }
4995
4996 // 3. host-dir + basename (most common: XREF was authored on a
4997 // different machine but file shipped alongside the host).
4998 const QString basename = QFileInfo(normalized).fileName();
4999 {
5000 const QString f = hostDir + "/" + basename;
5001 if (QFileInfo::exists(f))
5002 return QFileInfo(f).absoluteFilePath();
5003 }
5004
5005 // 4-5. Tolerant match in host-dir: case-insensitive + space-to-underscore
5006 // normalization on the stem; accept .dwg or .dxf extension.
5007 const QString basenameNorm = basename.toLower().replace(' ', '_');
5008 QFileInfo basenameInfo(basenameNorm);
5009 const QString stemNorm = basenameInfo.completeBaseName();
5010 QDir dir(hostDir);
5011 const QStringList entries =
5012 dir.entryList({QStringLiteral("*.dwg")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dwg"))), QStringLiteral("*.DWG")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DWG"))),
5013 QStringLiteral("*.dxf")(QString(QtPrivate::qMakeStringPrivate(u"" "*.dxf"))), QStringLiteral("*.DXF")(QString(QtPrivate::qMakeStringPrivate(u"" "*.DXF")))},
5014 QDir::Files);
5015 for (const QString &entry : entries) {
5016 const QString entryNorm = entry.toLower().replace(' ', '_');
5017 if (entryNorm == basenameNorm) {
5018 return dir.absoluteFilePath(entry);
5019 }
5020 QFileInfo entryInfo(entry);
5021 const QString entryStemNorm =
5022 entryInfo.completeBaseName().toLower().replace(' ', '_');
5023 if (entryStemNorm == stemNorm) {
5024 return dir.absoluteFilePath(entry);
5025 }
5026 }
5027
5028 return {};
5029}
5030
5031bool RS_FilterDXFRW::embedXref(RS_Block *block, const QString &xrefPath,
5032 const QString &blockName) {
5033 if (!block)
5034 return false;
5035 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::embedXref: %s -> %s", qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData(),
5036 qPrintable(xrefPath)QtPrivate::asString(xrefPath).toLocal8Bit().constData());
5037
5038 const QString resolved = resolveXrefPath(xrefPath);
5039 if (resolved.isEmpty()) {
5040 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
5041 QObject::tr("XREF not resolved for block \"%1\": %2 (file not found in "
5042 "host directory). The block will render as empty.")
5043 .arg(blockName, xrefPath));
5044 return false;
5045 }
5046
5047 if (m_xrefStack.count(resolved) > 0) {
5048 RS_DEBUGRS_Debug::instance()->print(" XREF cycle detected, skipping: %s",
5049 qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
5050 return false;
5051 }
5052 if (m_xrefStack.size() >= 8) {
5053 RS_DEBUGRS_Debug::instance()->print(" XREF depth limit reached at %s", qPrintable(resolved)QtPrivate::asString(resolved).toLocal8Bit().constData());
5054 return false;
5055 }
5056 m_xrefStack.insert(resolved);
5057
5058 RS_Graphic ext;
5059 RS_FilterDXFRW xrefFilter;
5060 xrefFilter.m_xrefStack = m_xrefStack; // propagate cycle guard
5061 const bool isDwg =
5062 resolved.endsWith(QStringLiteral(".dwg")(QString(QtPrivate::qMakeStringPrivate(u"" ".dwg"))), Qt::CaseInsensitive);
5063 const RS2::FormatType fmt = isDwg ? RS2::FormatDWG : RS2::FormatDXFRW;
5064 const bool ok = xrefFilter.fileImport(ext, resolved, fmt);
5065
5066 m_xrefStack.erase(resolved);
5067
5068 if (!ok) {
5069 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
5070 QObject::tr("XREF load failed for block \"%1\": %2")
5071 .arg(blockName, resolved));
5072 return false;
5073 }
5074
5075 // Layer namespacing: copy the XREF's layers into the host with prefix.
5076 RS_LayerList *extLayers = ext.getLayerList();
5077 RS_LayerList *hostLayers = m_graphic ? m_graphic->getLayerList() : nullptr;
5078 if (extLayers && hostLayers) {
5079 for (unsigned i = 0; i < extLayers->count(); ++i) {
5080 RS_Layer *extLyr = extLayers->at(i);
5081 if (!extLyr)
5082 continue;
5083 const QString nsName = blockName + "|" + extLyr->getName();
5084 if (!hostLayers->find(nsName)) {
5085 RS_Layer *clone = extLyr->clone();
5086 clone->setName(nsName);
5087 hostLayers->add(clone);
5088 }
5089 }
5090 }
5091
5092 // Points @p entity and its children at the host's namespaced copies of
5093 // the XREF's layers. This reads the raw layer pointer: once an entity is
5094 // parented into the host, getLayer() only answers layers of the host's
5095 // own list, so it would report none for the XREF's layers and leave them
5096 // in place. No pointer to an XREF layer may survive: they are freed with
5097 // `ext` when this function returns.
5098 std::function<void(RS_Entity *)> redirectLayers = [&](RS_Entity *entity) {
5099 RS_Layer *extLyr = entity->getLayer(false);
5100 if (extLyr != nullptr) {
5101 RS_Layer *hostLyr = nullptr;
5102 if (hostLayers && extLayers && extLayers->contains(extLyr)) {
5103 hostLyr = hostLayers->find(blockName + "|" + extLyr->getName());
5104 }
5105 entity->setLayer(hostLyr);
5106 }
5107 if (entity->isContainer() &&
5108 static_cast<RS_EntityContainer *>(entity)->isOwner()) {
5109 for (RS_Entity *child : *static_cast<RS_EntityContainer *>(entity)) {
5110 if (child != nullptr)
5111 redirectLayers(child);
5112 }
5113 }
5114 };
5115
5116 // Per-entity clone helper: clone @p src, redirect its layer pointers to
5117 // the host's namespaced layers, and (if it's an INSERT) rewrite its block
5118 // name reference to the namespaced form. Returns the cloned entity
5119 // owned by no parent yet; caller adds it to the target container.
5120 auto cloneAndRedirect = [&](const RS_Entity *src,
5121 RS_EntityContainer *target) -> RS_Entity * {
5122 if (!src)
5123 return nullptr;
5124 RS_Entity *cloned = src->clone();
5125 if (!cloned)
5126 return nullptr;
5127 cloned->setParent(target);
5128 redirectLayers(cloned);
5129 if (cloned->rtti() == RS2::EntityInsert) {
5130 auto *ins = static_cast<RS_Insert *>(cloned);
5131 const QString ref = ins->getName();
5132 // Don't rewrite if the reference is already namespaced (defensive
5133 // — shouldn't happen for a freshly-loaded XREF source, but harmless).
5134 if (!ref.startsWith(blockName + "|")) {
5135 ins->setName(blockName + "|" + ref);
5136 }
5137 }
5138 return cloned;
5139 };
5140
5141 // Block-def propagation: clone every block definition from the XREF
5142 // source into the host's blockList with namespaced name. Without this,
5143 // any cloned RS_Insert (whether at modelspace top-level or inside a
5144 // cloned block def) would reference a block name that doesn't exist
5145 // in the host, leaving a dangling reference.
5146 RS_BlockList *extBlocks = ext.getBlockList();
5147 RS_BlockList *hostBlocks = m_graphic ? m_graphic->getBlockList() : nullptr;
5148 int blockDefsCopied = 0;
5149 if (extBlocks && hostBlocks) {
5150 for (int i = 0; i < extBlocks->count(); ++i) {
5151 RS_Block *extBk = extBlocks->at(i);
5152 if (!extBk)
5153 continue;
5154 const QString origName = extBk->getName();
5155 // Skip special blocks — *MODEL_SPACE/*PAPER_SPACE aren't in
5156 // blockList anyway, but be defensive.
5157 if (origName.startsWith(QLatin1String("*Model_Space"),
5158 Qt::CaseInsensitive) ||
5159 origName.startsWith(QLatin1String("*Paper_Space"),
5160 Qt::CaseInsensitive)) {
5161 continue;
5162 }
5163 const QString nsName = blockName + "|" + origName;
5164 if (hostBlocks->find(nsName))
5165 continue; // already present
5166
5167 auto nsBlock = std::make_unique<RS_Block>(
5168 m_graphic, RS_BlockData(nsName, extBk->getBasePoint(), false));
5169 for (auto *e : *extBk) {
5170 if (auto *cloned = cloneAndRedirect(e, nsBlock.get())) {
5171 nsBlock->addEntity(cloned);
5172 }
5173 }
5174 // RS_BlockList::add takes ownership on success and deletes on failure,
5175 // so either way the raw pointer is consumed once released here.
5176 if (!hostBlocks->add(nsBlock.release())) {
5177 // Name collision against an existing host block — already deleted.
5178 } else {
5179 ++blockDefsCopied;
5180 }
5181 }
5182 }
5183
5184 // Move modelspace entities (top-level) into the local block, with
5185 // their layer pointers + INSERT block-name references redirected.
5186 int moved = 0;
5187 QList<RS_Entity *> snapshot;
5188 for (auto *e : ext)
5189 snapshot.push_back(e);
5190 for (RS_Entity *e : snapshot) {
5191 if (auto *cloned = cloneAndRedirect(e, block)) {
5192 block->addEntity(cloned);
5193 ++moved;
5194 }
5195 }
5196
5197 RS_DEBUGRS_Debug::instance()->print(" XREF embedded: %d entities + %d block defs into "
5198 "block \"%s\"",
5199 moved, blockDefsCopied, qPrintable(blockName)QtPrivate::asString(blockName).toLocal8Bit().constData());
5200 return true;
5201}
5202
5203void RS_FilterDXFRW::setBlock(const int handle) {
5204 if (m_blockHash.contains(handle)) {
5205 m_currentContainer = m_blockHash.value(handle);
5206 } else {
5207 m_currentContainer = m_graphic;
5208 }
5209 // Never leave a null container: subsequent addEntity would SIGSEGV.
5210 // Fall back to the import sink used for paper-space / name collisions.
5211 if (m_currentContainer == nullptr) {
5212 m_currentContainer =
5213 m_dummyContainer != nullptr ? m_dummyContainer : m_graphic;
5214 }
5215}
5216
5217/**
5218 * Implementation of the method which closes blocks.
5219 */
5220void RS_FilterDXFRW::endBlock() {
5221 if (m_currentContainer != nullptr &&
5222 m_currentContainer->rtti() == RS2::EntityBlock) {
5223 auto bk = static_cast<RS_Block *>(m_currentContainer);
5224 // remove unnamed blocks *D only if version != R12
5225 if (m_version != 1009 && m_graphic != nullptr) {
5226 if (bk->getName().startsWith("*D")) {
5227 m_graphic->removeBlock(bk);
5228 }
5229 }
5230 }
5231 m_currentContainer = m_graphic;
5232}
5233
5234namespace {
5235// F2 type-fidelity sidecar markers. POINT and LINE coordinates are WCS, but
5236// LibreCAD's 2D entities cannot retain their Z values, thickness, or arbitrary
5237// extrusion direction. Keep those fields separately for native re-emission.
5238constexpr const char *kPointExtrusionMarker = "LibreCAD_POINT_EXTRUSION";
5239constexpr const char *kLineExtrusionMarker = "LibreCAD_LINE_EXTRUSION";
5240// CIRCLE centers are OCS data. Preserve plane metadata so editable planar
5241// circles can be transformed back to their source OCS on export.
5242constexpr const char *kCircleOcsMarker = "LibreCAD_CIRCLE_OCS";
5243// ARC center and angles are OCS data. Keep its plane metadata separately so
5244// the editable 2D arc can be transformed back to the source OCS on export.
5245constexpr const char *kArcOcsMarker = "LibreCAD_ARC_OCS";
5246// 3DLINE is distinct from LINE even when its normal and thickness are default.
5247constexpr const char *k3DLineMarker = "LibreCAD_3DLINE";
5248// HATCH boundary coordinates remain raw OCS in libdxfrw. Preserve the
5249// accompanying elevation/normal so the writer does not relabel them as WCS.
5250constexpr const char *kHatchExtrusionMarker = "LibreCAD_HATCH_EXTRUSION";
5251// IMAGE has no extrusion normal. Its insertion and pixel U/V vectors are WCS,
5252// while RS_Image exposes a 2D-editable frame, so retain their source Z values
5253// separately for native output.
5254constexpr const char *kImageFrameMarker = "LibreCAD_IMAGE_FRAME";
5255constexpr const char *kTextOcsMarker = "LibreCAD_TEXT_OCS";
5256constexpr const char *kMTextOcsMarker = "LibreCAD_MTEXT_OCS";
5257constexpr const char *kRayMarker = "LibreCAD_RAY";
5258constexpr const char *kXlineMarker = "LibreCAD_XLINE";
5259constexpr const char *kTraceMarker = "LibreCAD_TRACE";
5260constexpr const char *kSolidMarker = "LibreCAD_SOLID";
5261constexpr const char *k3dFaceMarker = "LibreCAD_3DFACE";
5262// RS_Insert expands its block into derived children and therefore cannot own
5263// imported ATTRIB entities directly. The rendered sibling text names its
5264// INSERT by lineage (RS_Entity::ownerLineage()) and is tagged with what a
5265// text does not model, so the writer can rebuild the native child sequence.
5266constexpr const char *kInsertAttribMarker = "LibreCAD_INSERT_ATTRIB";
5267constexpr const char *kInsertAttribMTextMarker = "LibreCAD_INSERT_ATTRIB_MTEXT";
5268constexpr const char *kInsertAttribMTextXDataMarker =
5269 "LibreCAD_INSERT_ATTRIB_MTEXT_XDATA";
5270constexpr const char *kInsertAttribMTextCommonMarker =
5271 "LibreCAD_INSERT_ATTRIB_MTEXT_COMMON";
5272constexpr int kInsertAttribMTextSchema = 3;
5273constexpr int kInsertAttribMTextXDataSchema = 1;
5274constexpr int kInsertAttribMTextCommonSchema = 2;
5275constexpr int kMaxInsertAttribMTextXDataValues = 4096;
5276constexpr int kMaxInsertAttribMTextReactors = 4096;
5277
5278enum class DwgTypedConversionFixedRoute : std::uint8_t {
5279 PointExtrusion,
5280 LineExtrusion,
5281 Circle,
5282 Arc,
5283 Ray,
5284 Xline,
5285 Trace,
5286 Solid,
5287 Face3d
5288};
5289
5290struct DwgTypedConversionFixedDescriptor {
5291 DwgTypedConversionFixedRoute route;
5292 std::uint16_t wireType;
5293};
5294
5295enum class TypedConversionSidecarKind : std::uint8_t {
5296 PointExtrusion,
5297 LineExtrusion,
5298 Circle,
5299 Arc,
5300 ThreeDLine,
5301 Ray,
5302 Xline,
5303 Trace,
5304 Solid,
5305 Face3d
5306};
5307
5308struct TypedConversionSidecar {
5309 TypedConversionSidecarKind kind;
5310 QString marker;
5311 std::array<DRW_Coord, 4> coords{};
5312 double thickness{0.0};
5313 double pointXAxisAngle{0.0};
5314 int flag{0};
5315};
5316
5317std::optional<TypedConversionSidecarKind>
5318typedConversionSidecarKind(const std::string &marker) {
5319 if (marker == kPointExtrusionMarker)
5320 return TypedConversionSidecarKind::PointExtrusion;
5321 if (marker == kLineExtrusionMarker)
5322 return TypedConversionSidecarKind::LineExtrusion;
5323 if (marker == kCircleOcsMarker)
5324 return TypedConversionSidecarKind::Circle;
5325 if (marker == kArcOcsMarker)
5326 return TypedConversionSidecarKind::Arc;
5327 if (marker == k3DLineMarker)
5328 return TypedConversionSidecarKind::ThreeDLine;
5329 if (marker == kRayMarker)
5330 return TypedConversionSidecarKind::Ray;
5331 if (marker == kXlineMarker)
5332 return TypedConversionSidecarKind::Xline;
5333 if (marker == kTraceMarker)
5334 return TypedConversionSidecarKind::Trace;
5335 if (marker == kSolidMarker)
5336 return TypedConversionSidecarKind::Solid;
5337 if (marker == k3dFaceMarker)
5338 return TypedConversionSidecarKind::Face3d;
5339 return std::nullopt;
5340}
5341
5342std::size_t typedConversionCoordinateCount(TypedConversionSidecarKind kind) {
5343 switch (kind) {
5344 case TypedConversionSidecarKind::PointExtrusion:
5345 case TypedConversionSidecarKind::Circle:
5346 case TypedConversionSidecarKind::Arc:
5347 case TypedConversionSidecarKind::Ray:
5348 case TypedConversionSidecarKind::Xline:
5349 return 2;
5350 case TypedConversionSidecarKind::LineExtrusion:
5351 case TypedConversionSidecarKind::ThreeDLine:
5352 return 3;
5353 case TypedConversionSidecarKind::Trace:
5354 case TypedConversionSidecarKind::Solid:
5355 case TypedConversionSidecarKind::Face3d:
5356 return 4;
5357 }
5358 return 0;
5359}
5360
5361bool typedConversionRequiresThickness(TypedConversionSidecarKind kind) {
5362 return kind == TypedConversionSidecarKind::PointExtrusion ||
5363 kind == TypedConversionSidecarKind::LineExtrusion ||
5364 kind == TypedConversionSidecarKind::ThreeDLine ||
5365 kind == TypedConversionSidecarKind::Circle ||
5366 kind == TypedConversionSidecarKind::Arc ||
5367 kind == TypedConversionSidecarKind::Trace ||
5368 kind == TypedConversionSidecarKind::Solid;
5369}
5370
5371bool typedConversionMatchesEntity(TypedConversionSidecarKind kind,
5372 const RS_Entity *entity) {
5373 if (entity == nullptr)
5374 return false;
5375 switch (kind) {
5376 case TypedConversionSidecarKind::PointExtrusion:
5377 return entity->rtti() == RS2::EntityPoint;
5378 case TypedConversionSidecarKind::LineExtrusion:
5379 case TypedConversionSidecarKind::ThreeDLine:
5380 case TypedConversionSidecarKind::Ray:
5381 case TypedConversionSidecarKind::Xline:
5382 return entity->rtti() == RS2::EntityLine;
5383 case TypedConversionSidecarKind::Circle:
5384 return entity->rtti() == RS2::EntityCircle;
5385 case TypedConversionSidecarKind::Arc:
5386 return entity->rtti() == RS2::EntityArc;
5387 case TypedConversionSidecarKind::Trace:
5388 case TypedConversionSidecarKind::Solid:
5389 return entity->rtti() == RS2::EntitySolid;
5390 case TypedConversionSidecarKind::Face3d:
5391 return entity->rtti() == RS2::EntityPolyline;
5392 }
5393 return false;
5394}
5395
5396// The sidecar is internal, versioned type-fidelity data. Treat it as a
5397// compact record rather than permissive application XDATA: every recognized
5398// marker has one exact schema and a matching editable entity type.
5399std::optional<TypedConversionSidecar>
5400parseTypedConversionSidecar(const RS_Entity *entity) {
5401 if (entity == nullptr || !entity->hasDrwExtData())
5402 return std::nullopt;
5403
5404 TypedConversionSidecar parsed{};
5405 bool found = false;
5406 bool inGroup = false;
5407 std::array<bool, 4> gotCoords{};
5408 bool gotThickness = false;
5409 bool gotPointXAxisAngle = false;
5410 bool gotFlag = false;
5411
5412 for (const auto &value : entity->getDrwExtData()) {
5413 if (value == nullptr) {
5414 if (inGroup)
5415 return std::nullopt;
5416 continue;
5417 }
5418 const int code = value->code();
5419 if (code == 1001) {
5420 std::string marker;
5421 const XDataReadResult markerStatus = readXDataMarker(value.get(), marker);
5422 if (markerStatus != XDataReadResult::Valid) {
5423 if (inGroup)
5424 return std::nullopt;
5425 continue;
5426 }
5427 const auto kind = typedConversionSidecarKind(marker);
5428 if (!kind) {
5429 inGroup = false;
5430 continue;
5431 }
5432 if (found)
5433 return std::nullopt;
5434 found = true;
5435 inGroup = true;
5436 parsed.kind = *kind;
5437 parsed.marker = QString::fromStdString(marker);
5438 continue;
5439 }
5440 if (!inGroup)
5441 continue;
5442
5443 if (code >= 1010 && code <= 1013) {
5444 const std::size_t index = static_cast<std::size_t>(code - 1010);
5445 DRW_Coord coordinate;
5446 const XDataReadResult coordinateStatus =
5447 readXDataCoord(value.get(), code, coordinate);
5448 if (index >= typedConversionCoordinateCount(parsed.kind) ||
5449 gotCoords[index] || coordinateStatus != XDataReadResult::Valid) {
5450 return std::nullopt;
5451 }
5452 parsed.coords[index] = coordinate;
5453 gotCoords[index] = true;
5454 continue;
5455 }
5456
5457 if (code == 1040 && typedConversionRequiresThickness(parsed.kind) &&
5458 !gotThickness) {
5459 double thickness = 0.0;
5460 if (readXDataDouble(value.get(), 1040, thickness) !=
5461 XDataReadResult::Valid)
5462 return std::nullopt;
5463 parsed.thickness = thickness;
5464 gotThickness = true;
5465 continue;
5466 }
5467 if (code == 1041 &&
5468 parsed.kind == TypedConversionSidecarKind::PointExtrusion &&
5469 !gotPointXAxisAngle) {
5470 double xAxisAngle = 0.0;
5471 if (readXDataDouble(value.get(), 1041, xAxisAngle) !=
5472 XDataReadResult::Valid)
5473 return std::nullopt;
5474 parsed.pointXAxisAngle = xAxisAngle;
5475 gotPointXAxisAngle = true;
5476 continue;
5477 }
5478 if (code == 1070 && parsed.kind == TypedConversionSidecarKind::Face3d &&
5479 !gotFlag) {
5480 std::int64_t flag = 0;
5481 if (readXDataInteger(value.get(), 1070, flag, false) !=
5482 XDataReadResult::Valid ||
5483 !fitsXDataInt32(flag))
5484 return std::nullopt;
5485 parsed.flag = static_cast<int>(flag);
5486 gotFlag = true;
5487 continue;
5488 }
5489 return std::nullopt;
5490 }
5491
5492 if (!found || !typedConversionMatchesEntity(parsed.kind, entity))
5493 return std::nullopt;
5494 const std::size_t coordinateCount =
5495 typedConversionCoordinateCount(parsed.kind);
5496 for (std::size_t index = 0; index < coordinateCount; ++index) {
5497 if (!gotCoords[index])
5498 return std::nullopt;
5499 }
5500 if ((typedConversionRequiresThickness(parsed.kind) && !gotThickness) ||
5501 (parsed.kind == TypedConversionSidecarKind::PointExtrusion &&
5502 !gotPointXAxisAngle) ||
5503 (parsed.kind == TypedConversionSidecarKind::Face3d && !gotFlag)) {
5504 return std::nullopt;
5505 }
5506 if ((parsed.kind == TypedConversionSidecarKind::Circle ||
5507 parsed.kind == TypedConversionSidecarKind::Arc) &&
5508 (parsed.coords[1].x != 0.0 || parsed.coords[1].y != 0.0 ||
5509 (parsed.coords[1].z != 1.0 && parsed.coords[1].z != -1.0))) {
5510 return std::nullopt;
5511 }
5512 return parsed;
5513}
5514
5515// Predict only the sidecars reconstructed as one fixed-wire entity. A
5516// malformed or multi-group sidecar must not reserve a receipt for the
5517// ordinary entity fallback; 3DLINE is deliberately handled by its class
5518// descriptor instead.
5519std::optional<DwgTypedConversionFixedDescriptor>
5520findDwgTypedConversionFixedDescriptor(const RS_Entity *entity) {
5521 const auto sidecar = parseTypedConversionSidecar(entity);
5522 if (!sidecar)
5523 return std::nullopt;
5524 switch (sidecar->kind) {
5525 case TypedConversionSidecarKind::PointExtrusion:
5526 return DwgTypedConversionFixedDescriptor{
5527 DwgTypedConversionFixedRoute::PointExtrusion, dwgType::POINT};
5528 case TypedConversionSidecarKind::LineExtrusion:
5529 return DwgTypedConversionFixedDescriptor{
5530 DwgTypedConversionFixedRoute::LineExtrusion, dwgType::LINE};
5531 case TypedConversionSidecarKind::Circle:
5532 return DwgTypedConversionFixedDescriptor{
5533 DwgTypedConversionFixedRoute::Circle, dwgType::CIRCLE};
5534 case TypedConversionSidecarKind::Arc:
5535 return DwgTypedConversionFixedDescriptor{DwgTypedConversionFixedRoute::Arc,
5536 dwgType::ARC};
5537 case TypedConversionSidecarKind::Ray:
5538 return DwgTypedConversionFixedDescriptor{DwgTypedConversionFixedRoute::Ray,
5539 dwgType::RAY};
5540 case TypedConversionSidecarKind::Xline:
5541 return DwgTypedConversionFixedDescriptor{
5542 DwgTypedConversionFixedRoute::Xline, dwgType::XLINE};
5543 case TypedConversionSidecarKind::Trace:
5544 return DwgTypedConversionFixedDescriptor{
5545 DwgTypedConversionFixedRoute::Trace, dwgType::TRACE};
5546 case TypedConversionSidecarKind::Solid:
5547 return DwgTypedConversionFixedDescriptor{
5548 DwgTypedConversionFixedRoute::Solid, dwgType::SOLID};
5549 case TypedConversionSidecarKind::Face3d:
5550 return DwgTypedConversionFixedDescriptor{
5551 DwgTypedConversionFixedRoute::Face3d, dwgType::FACE3D};
5552 case TypedConversionSidecarKind::ThreeDLine:
5553 return std::nullopt;
5554 }
5555 return std::nullopt;
5556}
5557
5558bool hasDwgTypedConversionSidecar(const RS_Entity *entity) {
5559 if (entity == nullptr || !entity->hasDrwExtData())
5560 return false;
5561 for (const auto &value : entity->getDrwExtData()) {
5562 std::string marker;
5563 if (readXDataMarker(value.get(), marker) == XDataReadResult::Valid &&
5564 typedConversionSidecarKind(marker)) {
5565 return true;
5566 }
5567 }
5568 return false;
5569}
5570
5571bool hasValidDwg3DLineSidecar(const RS_Entity *entity) {
5572 const auto sidecar = parseTypedConversionSidecar(entity);
5573 return sidecar && sidecar->kind == TypedConversionSidecarKind::ThreeDLine;
5574}
5575
5576bool needsWcsThicknessSidecar(const DRW_Point &entity) {
5577 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5578 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5579 entity.extPoint.z != 1.0 || entity.xAxisAngle != 0.0;
5580}
5581
5582bool needsWcsThicknessSidecar(const DRW_Line &entity) {
5583 return needsWcsThicknessSidecar(static_cast<const DRW_Point &>(entity)) ||
5584 entity.secPoint.z != 0.0;
5585}
5586
5587bool needsArcOcsSidecar(const DRW_Arc &entity) {
5588 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5589 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5590 entity.extPoint.z != 1.0;
5591}
5592
5593bool needsCircleOcsSidecar(const DRW_Circle &entity) {
5594 return entity.basePoint.z != 0.0 || entity.thickness != 0.0 ||
5595 entity.extPoint.x != 0.0 || entity.extPoint.y != 0.0 ||
5596 entity.extPoint.z != 1.0;
5597}
5598
5599DRW_Coord sourceAxialOcsCenter(const DRW_Circle &entity) {
5600 DRW_Coord center = entity.basePoint;
5601 if (entity.extPoint.z < 0.0) {
5602 center.x = -center.x;
5603 center.z = -center.z;
5604 }
5605 return center;
5606}
5607
5608void appendTypeSidecar(RS_Entity *entity, const char *marker,
5609 std::vector<std::shared_ptr<DRW_Variant>> payload);
5610
5611struct InsertAttribSidecar {
5612 int ordinal = -1;
5613 int flags = 0;
5614 int fieldLength = 0;
5615 int attVersion = 0;
5616 int attributeType = 1;
5617 bool lockPosition = false;
5618 int textgen = 0; // ATTRIB group 71, generation flags
5619 int alignH = 0; // ATTRIB group 72 as read
5620 int alignV = 0; // ATTRIB group 74 as read
5621 bool recordVisible = true; // common group 60 of the ATTRIB as read
5622 std::string tag;
5623 std::string value; // ATTRIB group 1 as read, in the file's own spelling
5624 struct MText {
5625 std::string text;
5626 int textgen = 1;
5627 int alignH = 1;
5628 int linespacingStyle = 1;
5629 double interlin = 1.0;
5630 double originalHeight = 0.0;
5631 double originalWidth = 0.0;
5632 double originalAngle = 0.0;
5633 double originalSpacing = 1.0;
5634 DRW_Coord originalPosition;
5635 std::string originalStyle;
5636 int backgroundFlags = 0;
5637 double backgroundScale = 0.0;
5638 int backgroundColor = 0;
5639 int backgroundTrueColor = -1; // group 421; -1: none
5640 int backgroundTransparency = 0;
5641 bool r2018IsNotAnnotative = false;
5642 int r2018Version = 0;
5643 bool r2018DefaultFlag = false;
5644 std::uint32_t r2018AppIdHandle = 0;
5645 int r2018Attachment = 0;
5646 DRW_Coord r2018XAxisDir;
5647 DRW_Coord r2018InsertionPoint;
5648 double r2018RectWidth = 0.0;
5649 double r2018RectHeight = 0.0;
5650 double r2018ExtentsHeight = 0.0;
5651 double r2018ExtentsWidth = 0.0;
5652 int r2018ColumnType = 0;
5653 int r2018ColumnCount = 0;
5654 double r2018ColumnWidth = 0.0;
5655 double r2018ColumnGutter = 0.0;
5656 bool r2018ColumnAutoHeight = false;
5657 bool r2018ColumnFlowReversed = false;
5658 std::vector<double> r2018ColumnHeights;
5659 std::vector<std::uint8_t> annotativeData;
5660 int annotativeUnknown = 0;
5661 std::uint32_t annotativeAppHandle = 0;
5662 bool reallyLocked = false;
5663 std::uint32_t styleHandle = 0;
5664 bool hasDataStorageBinaryData = false;
5665 std::vector<std::uint32_t> reactorHandles;
5666 std::uint32_t xDictHandle = 0;
5667 int color = DRW::ColorByLayer;
5668 int color24 = -1;
5669 std::string colorName;
5670 int transparency = DRW::Opaque;
5671 bool hasAcDbColorHandle = false;
5672 std::uint32_t acDbColorHandle = 0;
5673 double ltypeScale = 1.0;
5674 std::string lineType = "BYLAYER";
5675 bool visible = true;
5676 int lineWeight = static_cast<int>(DRW_LW_Conv::widthByLayer);
5677 std::uint32_t material = DRW::MaterialByLayer;
5678 int shadow = static_cast<int>(DRW::CastAndReceieveShadows);
5679 std::uint32_t shadowHandle = 0;
5680 std::uint32_t plotStyle = DRW::DefaultPlotStyle;
5681 std::uint32_t fullVisualStyleHandle = 0;
5682 std::uint32_t faceVisualStyleHandle = 0;
5683 std::uint32_t edgeVisualStyleHandle = 0;
5684 std::uint32_t layerHandle = 0;
5685 std::uint32_t linetypeHandle = 0;
5686 std::vector<std::shared_ptr<DRW_Variant>> xdata;
5687 DRW_Coord basePoint;
5688 DRW_Coord secPoint;
5689 DRW_Coord extPoint{0.0, 0.0, 1.0};
5690 } mtext;
5691 bool hasMText = false;
5692 DRW_Coord basePoint; // ATTRIB group 10, OCS
5693 DRW_Coord secPoint; // ATTRIB group 11, OCS
5694 DRW_Coord extPoint{0.0, 0.0, 1.0};
5695 double thickness = 0.0;
5696 double widthFactor = 1.0; // ATTRIB group 41
5697};
5698} // namespace
5699
5700/**
5701 * Implementation of the method which handles point entities.
5702 */
5703void RS_FilterDXFRW::addPoint(const DRW_Point &data) {
5704 const RS_Vector v(data.basePoint.x, data.basePoint.y);
5705 const auto entity = new RS_Point(m_currentContainer, RS_PointData(v));
5706 setEntityAttributes(entity, &data);
5707 if (needsWcsThicknessSidecar(data)) {
5708 std::vector<std::shared_ptr<DRW_Variant>> payload;
5709 payload.push_back(std::make_shared<DRW_Variant>(
5710 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5711 payload.push_back(std::make_shared<DRW_Variant>(
5712 1011, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5713 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5714 payload.push_back(std::make_shared<DRW_Variant>(1041, data.xAxisAngle));
5715 appendTypeSidecar(entity, kPointExtrusionMarker, std::move(payload));
5716 }
5717 m_currentContainer->addEntity(entity);
5718}
5719
5720/**
5721 * Implementation of the method which handles line entities.
5722 */
5723void RS_FilterDXFRW::addLine(const DRW_Line &data) {
5724 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine");
5725
5726 RS_Vector v1(data.basePoint.x, data.basePoint.y);
5727 RS_Vector v2(data.secPoint.x, data.secPoint.y);
5728
5729 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: create line");
5730
5731 if (!m_currentContainer) {
5732 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: currentContainer is nullptr");
5733 }
5734
5735 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
5736 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: set attributes");
5737 setEntityAttributes(entity, &data);
5738 if (needsWcsThicknessSidecar(data)) {
5739 std::vector<std::shared_ptr<DRW_Variant>> payload;
5740 payload.push_back(std::make_shared<DRW_Variant>(
5741 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5742 payload.push_back(std::make_shared<DRW_Variant>(
5743 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
5744 payload.push_back(std::make_shared<DRW_Variant>(
5745 1012, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5746 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5747 appendTypeSidecar(entity, kLineExtrusionMarker, std::move(payload));
5748 }
5749
5750 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: add entity");
5751
5752 if (m_currentContainer) {
5753 m_currentContainer->addEntity(entity);
5754 }
5755
5756 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addLine: OK");
5757}
5758
5759void RS_FilterDXFRW::add3DLine(const DRW_3DLine &data) {
5760 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::add3DLine");
5761
5762 const auto entity =
5763 new RS_Line{m_currentContainer,
5764 {RS_Vector(data.basePoint.x, data.basePoint.y),
5765 RS_Vector(data.secPoint.x, data.secPoint.y)}};
5766 setEntityAttributes(entity, &data);
5767
5768 // Preserve the entity kind and all fields because RS_Line cannot retain
5769 // 3D coordinates or distinguish a modern 3DLINE from an ordinary LINE.
5770 std::vector<std::shared_ptr<DRW_Variant>> payload;
5771 payload.push_back(std::make_shared<DRW_Variant>(
5772 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
5773 payload.push_back(std::make_shared<DRW_Variant>(
5774 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
5775 payload.push_back(std::make_shared<DRW_Variant>(
5776 1012, DRW_Coord(data.extPoint.x, data.extPoint.y, data.extPoint.z)));
5777 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
5778 appendTypeSidecar(entity, k3DLineMarker, std::move(payload));
5779 m_currentContainer->addEntity(entity);
5780}
5781
5782namespace {
5783// Append a type-fidelity sidecar (marker + payload variants) to whatever
5784// XDATA the entity already carries from setEntityAttributes().
5785void appendTypeSidecar(RS_Entity *entity, const char *marker,
5786 std::vector<std::shared_ptr<DRW_Variant>> payload) {
5787 std::vector<std::shared_ptr<DRW_Variant>> ext;
5788 if (entity->hasDrwExtData())
5789 ext = entity->getDrwExtData();
5790 ext.push_back(std::make_shared<DRW_Variant>(1001, std::string(marker)));
5791 for (auto &p : payload)
5792 ext.push_back(std::move(p));
5793 entity->setDrwExtData(std::move(ext));
5794}
5795
5796void appendInsertAttribSidecar(RS_Entity *entity, const DRW_Attrib &attrib,
5797 int ordinal) {
5798 std::vector<std::shared_ptr<DRW_Variant>> payload;
5799 payload.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{ordinal}));
5800 payload.push_back(
5801 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.attribFlags}));
5802 payload.push_back(
5803 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.m_fieldLength}));
5804 payload.push_back(
5805 std::make_shared<DRW_Variant>(1070, std::int32_t{attrib.attVersion}));
5806 payload.push_back(std::make_shared<DRW_Variant>(
5807 1070, std::int32_t{attrib.m_attributeType}));
5808 payload.push_back(std::make_shared<DRW_Variant>(
5809 1070, std::int32_t{attrib.lockPosition ? 1 : 0}));
5810 // The TEXT part of the record that the text entity does not model.
5811 payload.push_back(std::make_shared<DRW_Variant>(
5812 1070, static_cast<std::int32_t>(attrib.textgen)));
5813 payload.push_back(std::make_shared<DRW_Variant>(
5814 1070, static_cast<std::int32_t>(attrib.alignH)));
5815 payload.push_back(std::make_shared<DRW_Variant>(
5816 1070, static_cast<std::int32_t>(attrib.alignV)));
5817 // The record's own visibility: the text is hidden for the attribute's
5818 // invisible flag as well, and the two are not one in a file.
5819 payload.push_back(std::make_shared<DRW_Variant>(
5820 1070, std::int32_t{attrib.visible ? 1 : 0}));
5821 payload.push_back(
5822 std::make_shared<DRW_Variant>(1000, std::string{attrib.tag}));
5823 payload.push_back(
5824 std::make_shared<DRW_Variant>(1000, std::string{attrib.text}));
5825 payload.push_back(std::make_shared<DRW_Variant>(1010, attrib.basePoint));
5826 payload.push_back(std::make_shared<DRW_Variant>(1011, attrib.secPoint));
5827 payload.push_back(std::make_shared<DRW_Variant>(1012, attrib.extPoint));
5828 payload.push_back(std::make_shared<DRW_Variant>(1040, attrib.thickness));
5829 // A value that is no number would make the reader drop the attribute.
5830 payload.push_back(std::make_shared<DRW_Variant>(
5831 1040, std::isfinite(attrib.widthscale) && attrib.widthscale > 0.0
5832 ? attrib.widthscale
5833 : 1.0));
5834 appendTypeSidecar(entity, kInsertAttribMarker, std::move(payload));
5835
5836 if (!attrib.mtext)
5837 return;
5838
5839 const DRW_MText &mtext = *attrib.mtext;
5840 if (mtext.m_r2018ColumnHeights.size() >
5841 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max()) ||
5842 mtext.m_r2018AnnotativeData.size() >
5843 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) {
5844 return;
5845 }
5846 std::vector<std::shared_ptr<DRW_Variant>> mtextPayload;
5847 // The group also retains modeled geometry for detecting edits at export.
5848 mtextPayload.push_back(
5849 std::make_shared<DRW_Variant>(1070, kInsertAttribMTextSchema));
5850 const std::size_t maxChunkBytes = 240;
5851 const std::size_t chunkCount =
5852 mtext.text.empty()
5853 ? 0
5854 : (mtext.text.size() + maxChunkBytes - 1) / maxChunkBytes;
5855 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5856 1070, static_cast<std::int32_t>(chunkCount)));
5857 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5858 1070, static_cast<std::int32_t>(mtext.textgen)));
5859 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5860 1070, static_cast<std::int32_t>(mtext.alignH)));
5861 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5862 1070, static_cast<std::int32_t>(mtext.linespacingStyle)));
5863 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5864 1070, static_cast<std::int32_t>(mtext.m_backgroundFlags)));
5865 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5866 1070, static_cast<std::int32_t>(mtext.m_r2018IsNotAnnotative ? 1 : 0)));
5867 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5868 1070, static_cast<std::int32_t>(mtext.m_r2018Version)));
5869 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5870 1070, static_cast<std::int32_t>(mtext.m_r2018DefaultFlag ? 1 : 0)));
5871 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5872 1070, static_cast<std::int32_t>(mtext.m_r2018Attachment)));
5873 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5874 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnType)));
5875 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5876 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnAutoHeight ? 1 : 0)));
5877 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5878 1070,
5879 static_cast<std::int32_t>(mtext.m_r2018ColumnFlowReversed ? 1 : 0)));
5880 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5881 1070, static_cast<std::int32_t>(mtext.m_r2018ColumnHeights.size())));
5882 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5883 1070, static_cast<std::int32_t>(mtext.m_r2018AnnotativeData.size())));
5884 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5885 1070, static_cast<std::int32_t>(mtext.m_r2018AnnotativeUnknown)));
5886 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5887 1070, static_cast<std::int32_t>(mtext.m_r2018ReallyLocked ? 1 : 0)));
5888 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5889 1071, static_cast<std::int32_t>(mtext.m_backgroundColor)));
5890 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5891 1071, static_cast<std::int32_t>(mtext.m_backgroundTransparency)));
5892 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5893 1071, static_cast<std::int32_t>(mtext.m_r2018ColumnCount)));
5894 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5895 1071, static_cast<std::int32_t>(mtext.m_r2018AppIdHandle)));
5896 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5897 1071, static_cast<std::int32_t>(mtext.styleH.ref)));
5898 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5899 1071, static_cast<std::int32_t>(mtext.m_r2018AnnotativeAppHandle)));
5900 // The true colour of the background, only where there is one: the reader
5901 // of this group takes no negative number.
5902 if (mtext.m_backgroundTrueColor >= 0) {
5903 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5904 1071, static_cast<std::int32_t>(mtext.m_backgroundTrueColor)));
5905 }
5906 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, mtext.interlin));
5907 mtextPayload.push_back(
5908 std::make_shared<DRW_Variant>(1040, mtext.m_backgroundScale));
5909 mtextPayload.push_back(
5910 std::make_shared<DRW_Variant>(1040, mtext.m_r2018RectWidth));
5911 mtextPayload.push_back(
5912 std::make_shared<DRW_Variant>(1040, mtext.m_r2018RectHeight));
5913 mtextPayload.push_back(
5914 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ExtentsHeight));
5915 mtextPayload.push_back(
5916 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ExtentsWidth));
5917 mtextPayload.push_back(
5918 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ColumnWidth));
5919 mtextPayload.push_back(
5920 std::make_shared<DRW_Variant>(1040, mtext.m_r2018ColumnGutter));
5921 const auto modeled = static_cast<const RS_MText *>(entity)->getData();
5922 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, modeled.height));
5923 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, modeled.width));
5924 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, modeled.angle));
5925 mtextPayload.push_back(
5926 std::make_shared<DRW_Variant>(1040, modeled.lineSpacingFactor));
5927 for (double height : mtext.m_r2018ColumnHeights)
5928 mtextPayload.push_back(std::make_shared<DRW_Variant>(1040, height));
5929 for (std::uint8_t byte : mtext.m_r2018AnnotativeData)
5930 mtextPayload.push_back(
5931 std::make_shared<DRW_Variant>(1070, static_cast<std::int32_t>(byte)));
5932 mtextPayload.push_back(std::make_shared<DRW_Variant>(1010, mtext.basePoint));
5933 mtextPayload.push_back(std::make_shared<DRW_Variant>(1011, mtext.secPoint));
5934 mtextPayload.push_back(std::make_shared<DRW_Variant>(1012, mtext.extPoint));
5935 mtextPayload.push_back(
5936 std::make_shared<DRW_Variant>(1010, mtext.m_r2018XAxisDir));
5937 mtextPayload.push_back(
5938 std::make_shared<DRW_Variant>(1011, mtext.m_r2018InsertionPoint));
5939 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5940 1010, DRW_Coord(modeled.insertionPoint.x, modeled.insertionPoint.y, 0.0)));
5941 mtextPayload.push_back(std::make_shared<DRW_Variant>(
5942 1000, modeled.style.toUtf8().toStdString()));
5943 for (std::size_t offset = 0; offset < mtext.text.size();) {
5944 std::size_t length = std::min(maxChunkBytes, mtext.text.size() - offset);
5945 while (length > 0 && offset + length < mtext.text.size() &&
5946 (static_cast<unsigned char>(mtext.text[offset + length]) & 0xC0) ==
5947 0x80) {
5948 --length;
5949 }
5950 if (length == 0)
5951 length = std::min(maxChunkBytes, mtext.text.size() - offset);
5952 mtextPayload.push_back(
5953 std::make_shared<DRW_Variant>(1000, mtext.text.substr(offset, length)));
5954 offset += length;
5955 }
5956 appendTypeSidecar(entity, kInsertAttribMTextMarker, std::move(mtextPayload));
5957
5958 const bool hasExtendedCommonMetadata =
5959 mtext.hasDataStorageBinaryData() || !mtext.reactorHandles.empty() ||
5960 mtext.xDictHandle != 0 || mtext.color != DRW::ColorByLayer ||
5961 mtext.color24 >= 0 || !mtext.colorName.empty() ||
5962 mtext.transparency != DRW::Opaque || mtext.hasDwgAcDbColorHandle() ||
5963 mtext.ltypeScale != 1.0 || mtext.lineType != "BYLAYER" ||
5964 !mtext.visible || mtext.lWeight != DRW_LW_Conv::widthByLayer ||
5965 mtext.material != DRW::MaterialByLayer ||
5966 mtext.shadow != DRW::CastAndReceieveShadows || mtext.shadowHandle != 0 ||
5967 mtext.plotStyle != DRW::DefaultPlotStyle ||
5968 mtext.fullVisualStyleHandle != 0 || mtext.faceVisualStyleHandle != 0 ||
5969 mtext.edgeVisualStyleHandle != 0 || mtext.dwgLayerHandle() != 0 ||
5970 mtext.dwgLinetypeHandle() != 0;
5971 if (hasExtendedCommonMetadata) {
5972 if (mtext.reactorHandles.size() >
5973 static_cast<std::size_t>(kMaxInsertAttribMTextReactors)) {
5974 return;
5975 }
5976 std::vector<std::shared_ptr<DRW_Variant>> commonPayload;
5977 commonPayload.push_back(
5978 std::make_shared<DRW_Variant>(1070, kInsertAttribMTextCommonSchema));
5979 commonPayload.push_back(std::make_shared<DRW_Variant>(
5980 1070, mtext.hasDataStorageBinaryData() ? 1 : 0));
5981 commonPayload.push_back(std::make_shared<DRW_Variant>(
5982 1070, static_cast<std::int32_t>(mtext.reactorHandles.size())));
5983 const auto appendHandle = [&commonPayload](std::uint32_t handle) {
5984 commonPayload.push_back(std::make_shared<DRW_Variant>(
5985 1071, static_cast<std::int32_t>(handle & 0xFFFFFFFFu)));
5986 commonPayload.push_back(
5987 std::make_shared<DRW_Variant>(1071, std::int32_t{0}));
5988 };
5989 for (const std::uint32_t handle : mtext.reactorHandles)
5990 appendHandle(handle);
5991 appendHandle(mtext.xDictHandle);
5992 commonPayload.push_back(std::make_shared<DRW_Variant>(1070, mtext.color));
5993 commonPayload.push_back(std::make_shared<DRW_Variant>(1071, mtext.color24));
5994 commonPayload.push_back(
5995 std::make_shared<DRW_Variant>(1000, mtext.colorName));
5996 commonPayload.push_back(
5997 std::make_shared<DRW_Variant>(1071, mtext.transparency));
5998 commonPayload.push_back(std::make_shared<DRW_Variant>(
5999 1070, mtext.hasDwgAcDbColorHandle() ? 1 : 0));
6000 appendHandle(mtext.dwgAcDbColorHandle());
6001 commonPayload.push_back(
6002 std::make_shared<DRW_Variant>(1040, mtext.ltypeScale));
6003 commonPayload.push_back(
6004 std::make_shared<DRW_Variant>(1000, mtext.lineType));
6005 commonPayload.push_back(
6006 std::make_shared<DRW_Variant>(1070, mtext.visible ? 1 : 0));
6007 commonPayload.push_back(
6008 std::make_shared<DRW_Variant>(1070, static_cast<int>(mtext.lWeight)));
6009 commonPayload.push_back(
6010 std::make_shared<DRW_Variant>(1071, mtext.material));
6011 commonPayload.push_back(
6012 std::make_shared<DRW_Variant>(1070, static_cast<int>(mtext.shadow)));
6013 appendHandle(mtext.shadowHandle);
6014 commonPayload.push_back(
6015 std::make_shared<DRW_Variant>(1071, mtext.plotStyle));
6016 appendHandle(mtext.fullVisualStyleHandle);
6017 appendHandle(mtext.faceVisualStyleHandle);
6018 appendHandle(mtext.edgeVisualStyleHandle);
6019 appendHandle(mtext.dwgLayerHandle());
6020 appendHandle(mtext.dwgLinetypeHandle());
6021 appendTypeSidecar(entity, kInsertAttribMTextCommonMarker,
6022 std::move(commonPayload));
6023 }
6024
6025 if (mtext.extData.empty() ||
6026 mtext.extData.size() > kMaxInsertAttribMTextXDataValues)
6027 return;
6028
6029 std::vector<std::shared_ptr<DRW_Variant>> xdataPayload;
6030 xdataPayload.push_back(
6031 std::make_shared<DRW_Variant>(1070, kInsertAttribMTextXDataSchema));
6032 xdataPayload.push_back(std::make_shared<DRW_Variant>(
6033 1070, static_cast<std::int32_t>(mtext.extData.size())));
6034 for (const auto &value : mtext.extData) {
6035 if (!value || value->type() == DRW_Variant::INVALID)
6036 return;
6037 const auto type = static_cast<std::int32_t>(value->type());
6038 xdataPayload.push_back(std::make_shared<DRW_Variant>(1070, type));
6039 xdataPayload.push_back(std::make_shared<DRW_Variant>(1070, value->code()));
6040 switch (value->type()) {
6041 case DRW_Variant::STRING:
6042 xdataPayload.push_back(
6043 std::make_shared<DRW_Variant>(1070, value->isLayerRef() ? 1 : 0));
6044 xdataPayload.push_back(
6045 std::make_shared<DRW_Variant>(1000, value->c_str()));
6046 break;
6047 case DRW_Variant::INTEGER:
6048 xdataPayload.push_back(
6049 std::make_shared<DRW_Variant>(1071, value->i_val()));
6050 break;
6051 case DRW_Variant::INTEGER64: {
6052 const std::uint64_t bits = static_cast<std::uint64_t>(value->i64_val());
6053 xdataPayload.push_back(std::make_shared<DRW_Variant>(
6054 1071, static_cast<std::int32_t>(static_cast<std::uint32_t>(bits))));
6055 xdataPayload.push_back(std::make_shared<DRW_Variant>(
6056 1071,
6057 static_cast<std::int32_t>(static_cast<std::uint32_t>(bits >> 32u))));
6058 break;
6059 }
6060 case DRW_Variant::DOUBLE:
6061 xdataPayload.push_back(
6062 std::make_shared<DRW_Variant>(1040, value->d_val()));
6063 break;
6064 case DRW_Variant::COORD:
6065 if (value->coord() == nullptr)
6066 return;
6067 xdataPayload.push_back(
6068 std::make_shared<DRW_Variant>(1010, *value->coord()));
6069 break;
6070 case DRW_Variant::BINARY: {
6071 const auto *bytes = value->binary();
6072 if (bytes == nullptr)
6073 return;
6074 static constexpr char hex[] = "0123456789ABCDEF";
6075 std::string encoded;
6076 encoded.reserve(bytes->size() * 2);
6077 for (const std::uint8_t byte : *bytes) {
6078 encoded.push_back(hex[(byte >> 4u) & 0x0Fu]);
6079 encoded.push_back(hex[byte & 0x0Fu]);
6080 }
6081 xdataPayload.push_back(
6082 std::make_shared<DRW_Variant>(1004, std::move(encoded)));
6083 break;
6084 }
6085 case DRW_Variant::INVALID:
6086 return;
6087 }
6088 }
6089 appendTypeSidecar(entity, kInsertAttribMTextXDataMarker,
6090 std::move(xdataPayload));
6091}
6092
6093bool decodeInsertAttribMTextXData(
6094 const std::vector<std::shared_ptr<DRW_Variant>> &encoded,
6095 std::vector<std::shared_ptr<DRW_Variant>> &decoded) {
6096 auto nextInteger = [&encoded](std::size_t &index, int &value) {
6097 if (index >= encoded.size() || !encoded[index] ||
6098 encoded[index]->type() != DRW_Variant::INTEGER)
6099 return false;
6100 value = encoded[index++]->i_val();
6101 return true;
6102 };
6103 auto nextValue = [&encoded](std::size_t &index, int code,
6104 DRW_Variant::TYPE type,
6105 std::shared_ptr<DRW_Variant> &value) {
6106 if (index >= encoded.size() || !encoded[index] ||
6107 encoded[index]->type() != type)
6108 return false;
6109 const auto &source = encoded[index++];
6110 switch (type) {
6111 case DRW_Variant::STRING:
6112 if (source->content.s == nullptr)
6113 return false;
6114 value = std::make_shared<DRW_Variant>(code, *source->content.s);
6115 break;
6116 case DRW_Variant::INTEGER:
6117 value = std::make_shared<DRW_Variant>(code, source->i_val());
6118 break;
6119 case DRW_Variant::DOUBLE:
6120 if (!std::isfinite(source->d_val()))
6121 return false;
6122 value = std::make_shared<DRW_Variant>(code, source->d_val());
6123 break;
6124 case DRW_Variant::COORD:
6125 if (source->coord() == nullptr || !std::isfinite(source->coord()->x) ||
6126 !std::isfinite(source->coord()->y) ||
6127 !std::isfinite(source->coord()->z))
6128 return false;
6129 value = std::make_shared<DRW_Variant>(code, *source->coord());
6130 break;
6131 case DRW_Variant::INTEGER64:
6132 case DRW_Variant::BINARY:
6133 case DRW_Variant::INVALID:
6134 return false;
6135 }
6136 return true;
6137 };
6138
6139 std::size_t index = 0;
6140 int schema = 0;
6141 int count = 0;
6142 if (!nextInteger(index, schema) || schema != kInsertAttribMTextXDataSchema ||
6143 !nextInteger(index, count) || count < 0 ||
6144 count > kMaxInsertAttribMTextXDataValues)
6145 return false;
6146 decoded.clear();
6147 decoded.reserve(static_cast<std::size_t>(count));
6148 for (int item = 0; item < count; ++item) {
6149 int typeValue = 0;
6150 int code = 0;
6151 if (!nextInteger(index, typeValue) || !nextInteger(index, code) ||
6152 typeValue < DRW_Variant::STRING || typeValue > DRW_Variant::BINARY ||
6153 code < 1000 || code > 1071)
6154 return false;
6155 const auto type = static_cast<DRW_Variant::TYPE>(typeValue);
6156 std::shared_ptr<DRW_Variant> value;
6157 if (type == DRW_Variant::STRING) {
6158 int layerRef = 0;
6159 if (!nextInteger(index, layerRef) || (layerRef != 0 && layerRef != 1) ||
6160 index >= encoded.size() || !encoded[index] ||
6161 encoded[index]->type() != DRW_Variant::STRING ||
6162 encoded[index]->content.s == nullptr)
6163 return false;
6164 value = std::make_shared<DRW_Variant>(code, *encoded[index]->content.s,
6165 layerRef != 0);
6166 ++index;
6167 } else if (type == DRW_Variant::INTEGER64) {
6168 if (index + 1 >= encoded.size() || !encoded[index] ||
6169 !encoded[index + 1] ||
6170 encoded[index]->type() != DRW_Variant::INTEGER ||
6171 encoded[index + 1]->type() != DRW_Variant::INTEGER)
6172 return false;
6173 const std::uint64_t low =
6174 static_cast<std::uint32_t>(encoded[index++]->i_val());
6175 const std::uint64_t high =
6176 static_cast<std::uint32_t>(encoded[index++]->i_val());
6177 const std::uint64_t bits = low | (high << 32u);
6178 std::int64_t restored = 0;
6179 std::memcpy(&restored, &bits, sizeof(restored));
6180 value = std::make_shared<DRW_Variant>(code, restored);
6181 } else if (type == DRW_Variant::BINARY) {
6182 if (index >= encoded.size() || !encoded[index] ||
6183 encoded[index]->type() != DRW_Variant::STRING ||
6184 encoded[index]->content.s == nullptr)
6185 return false;
6186 const std::string hex = *encoded[index++]->content.s;
6187 if ((hex.size() & 1u) != 0)
6188 return false;
6189 std::vector<std::uint8_t> bytes;
6190 bytes.reserve(hex.size() / 2);
6191 auto hexValue = [](char c) -> int {
6192 if (c >= '0' && c <= '9')
6193 return c - '0';
6194 if (c >= 'A' && c <= 'F')
6195 return c - 'A' + 10;
6196 if (c >= 'a' && c <= 'f')
6197 return c - 'a' + 10;
6198 return -1;
6199 };
6200 for (std::size_t offset = 0; offset < hex.size(); offset += 2) {
6201 const int high = hexValue(hex[offset]);
6202 const int low = hexValue(hex[offset + 1]);
6203 if (high < 0 || low < 0)
6204 return false;
6205 bytes.push_back(static_cast<std::uint8_t>((high << 4) | low));
6206 }
6207 value = std::make_shared<DRW_Variant>(code, std::move(bytes));
6208 } else {
6209 const DRW_Variant::TYPE storageType = type;
6210 if (!nextValue(index, code, storageType, value))
6211 return false;
6212 }
6213 decoded.push_back(std::move(value));
6214 }
6215 return index == encoded.size();
6216}
6217
6218bool decodeInsertAttribMTextCommon(
6219 const std::vector<std::shared_ptr<DRW_Variant>> &encoded,
6220 InsertAttribSidecar::MText &decoded) {
6221 std::size_t index = 0;
6222 const auto nextInteger = [&encoded](std::size_t &position, int code,
6223 int &value) {
6224 if (position >= encoded.size() || !encoded[position] ||
6225 encoded[position]->code() != code ||
6226 encoded[position]->type() != DRW_Variant::INTEGER)
6227 return false;
6228 value = encoded[position++]->i_val();
6229 return true;
6230 };
6231 const auto nextHandle = [&encoded](std::size_t &position,
6232 std::uint32_t &handle) {
6233 if (position + 1 >= encoded.size() || !encoded[position] ||
6234 !encoded[position + 1] || encoded[position]->code() != 1071 ||
6235 encoded[position + 1]->code() != 1071 ||
6236 encoded[position]->type() != DRW_Variant::INTEGER ||
6237 encoded[position + 1]->type() != DRW_Variant::INTEGER)
6238 return false;
6239 const std::uint32_t low =
6240 static_cast<std::uint32_t>(encoded[position++]->i_val());
6241 const std::uint32_t high =
6242 static_cast<std::uint32_t>(encoded[position++]->i_val());
6243 if (high != 0)
6244 return false;
6245 handle = low;
6246 return true;
6247 };
6248 const auto nextDouble = [&encoded](std::size_t &position, double &value) {
6249 if (position >= encoded.size() || !encoded[position] ||
6250 encoded[position]->code() != 1040 ||
6251 encoded[position]->type() != DRW_Variant::DOUBLE ||
6252 !std::isfinite(encoded[position]->d_val()))
6253 return false;
6254 value = encoded[position++]->d_val();
6255 return true;
6256 };
6257 const auto nextString = [&encoded](std::size_t &position,
6258 std::string &value) {
6259 if (position >= encoded.size() || !encoded[position] ||
6260 encoded[position]->code() != 1000 ||
6261 encoded[position]->type() != DRW_Variant::STRING ||
6262 encoded[position]->content.s == nullptr)
6263 return false;
6264 value = *encoded[position++]->content.s;
6265 return true;
6266 };
6267
6268 int schema = 0;
6269 int hasDataStorage = 0;
6270 int reactorCount = 0;
6271 if (!nextInteger(index, 1070, schema) ||
6272 schema != kInsertAttribMTextCommonSchema ||
6273 !nextInteger(index, 1070, hasDataStorage) ||
6274 (hasDataStorage != 0 && hasDataStorage != 1) ||
6275 !nextInteger(index, 1070, reactorCount) || reactorCount < 0 ||
6276 reactorCount > kMaxInsertAttribMTextReactors)
6277 return false;
6278
6279 decoded.hasDataStorageBinaryData = hasDataStorage != 0;
6280 decoded.reactorHandles.clear();
6281 decoded.reactorHandles.reserve(static_cast<std::size_t>(reactorCount));
6282 for (int i = 0; i < reactorCount; ++i) {
6283 std::uint32_t handle = 0;
6284 if (!nextHandle(index, handle))
6285 return false;
6286 if (handle != 0)
6287 decoded.reactorHandles.push_back(handle);
6288 }
6289 if (!nextHandle(index, decoded.xDictHandle))
6290 return false;
6291
6292 int hasAcDbColor = 0;
6293 int visible = 0;
6294 if (!nextInteger(index, 1070, decoded.color) || index >= encoded.size() ||
6295 !encoded[index] || encoded[index]->code() != 1071 ||
6296 encoded[index]->type() != DRW_Variant::INTEGER)
6297 return false;
6298 decoded.color24 = encoded[index++]->i_val();
6299 if (!nextString(index, decoded.colorName) || index >= encoded.size() ||
6300 !encoded[index] || encoded[index]->code() != 1071 ||
6301 encoded[index]->type() != DRW_Variant::INTEGER)
6302 return false;
6303 decoded.transparency = encoded[index++]->i_val();
6304 if (!nextInteger(index, 1070, hasAcDbColor) ||
6305 (hasAcDbColor != 0 && hasAcDbColor != 1) ||
6306 !nextHandle(index, decoded.acDbColorHandle) ||
6307 !nextDouble(index, decoded.ltypeScale) ||
6308 !nextString(index, decoded.lineType) ||
6309 !nextInteger(index, 1070, visible) || (visible != 0 && visible != 1) ||
6310 !nextInteger(index, 1070, decoded.lineWeight) ||
6311 index >= encoded.size() || !encoded[index] ||
6312 encoded[index]->code() != 1071 ||
6313 encoded[index]->type() != DRW_Variant::INTEGER)
6314 return false;
6315 decoded.hasAcDbColorHandle = hasAcDbColor != 0;
6316 decoded.visible = visible != 0;
6317 decoded.material = static_cast<std::uint32_t>(encoded[index++]->i_val());
6318 if (!nextInteger(index, 1070, decoded.shadow) ||
6319 !nextHandle(index, decoded.shadowHandle) || index >= encoded.size() ||
6320 !encoded[index] || encoded[index]->code() != 1071 ||
6321 encoded[index]->type() != DRW_Variant::INTEGER)
6322 return false;
6323 decoded.plotStyle = static_cast<std::uint32_t>(encoded[index++]->i_val());
6324 if (!nextHandle(index, decoded.fullVisualStyleHandle) ||
6325 !nextHandle(index, decoded.faceVisualStyleHandle) ||
6326 !nextHandle(index, decoded.edgeVisualStyleHandle) ||
6327 !nextHandle(index, decoded.layerHandle) ||
6328 !nextHandle(index, decoded.linetypeHandle))
6329 return false;
6330 return index == encoded.size();
6331}
6332
6333std::optional<InsertAttribSidecar>
6334extractInsertAttribSidecar(const RS_Entity *entity) {
6335 if (entity == nullptr || !entity->hasDrwExtData())
6336 return std::nullopt;
6337
6338 InsertAttribSidecar sidecar;
6339 bool inGroup = false;
6340 bool inMTextGroup = false;
6341 bool inMTextCommonGroup = false;
6342 int seenValues = 0;
6343 int seenStrings = 0;
6344 int seenPoints = 0;
6345 int seenDoubles = 0;
6346 int mtextSchema = 0;
6347 int seenMText1070 = 0;
6348 int seenMText1071 = 0;
6349 int seenMText1040 = 0;
6350 int seenMText1010 = 0;
6351 int seenMTextStrings = 0;
6352 std::vector<std::shared_ptr<DRW_Variant>> encodedMTextXData;
6353 int expectedColumnHeights = -1;
6354 int expectedAnnotativeBytes = -1;
6355 int seenAnnotativeBytes = 0;
6356 bool foundMarker = false;
6357 bool foundMTextMarker = false;
6358 bool foundMTextXDataMarker = false;
6359 bool foundMTextCommonMarker = false;
6360 bool inMTextXDataGroup = false;
6361 std::vector<std::shared_ptr<DRW_Variant>> encodedMTextCommon;
6362 for (const auto &value : entity->getDrwExtData()) {
6363 if (!value)
6364 continue;
6365 if (value->code() == 1001) {
6366 std::string marker;
6367 if (readXDataString(value.get(), 1001, marker) !=
6368 XDataReadResult::Valid) {
6369 inGroup = false;
6370 inMTextGroup = false;
6371 inMTextXDataGroup = false;
6372 inMTextCommonGroup = false;
6373 continue;
6374 }
6375 inGroup = marker == kInsertAttribMarker;
6376 inMTextGroup = marker == kInsertAttribMTextMarker;
6377 inMTextXDataGroup = marker == kInsertAttribMTextXDataMarker;
6378 inMTextCommonGroup = marker == kInsertAttribMTextCommonMarker;
6379 foundMarker = foundMarker || inGroup;
6380 foundMTextMarker = foundMTextMarker || inMTextGroup;
6381 foundMTextXDataMarker = foundMTextXDataMarker || inMTextXDataGroup;
6382 foundMTextCommonMarker = foundMTextCommonMarker || inMTextCommonGroup;
6383 continue;
6384 }
6385 if (inMTextCommonGroup) {
6386 encodedMTextCommon.push_back(std::make_shared<DRW_Variant>(*value));
6387 continue;
6388 }
6389 if (inMTextXDataGroup) {
6390 encodedMTextXData.push_back(std::make_shared<DRW_Variant>(*value));
6391 continue;
6392 }
6393 if (inMTextGroup) {
6394 if (value->code() == 1070) {
6395 std::int64_t integerValue = 0;
6396 if (readXDataInteger(value.get(), 1070, integerValue, false) !=
6397 XDataReadResult::Valid ||
6398 !fitsXDataInt32(integerValue)) {
6399 return std::nullopt;
6400 }
6401 const int number = static_cast<int>(integerValue);
6402 switch (seenMText1070++) {
6403 case 0:
6404 mtextSchema = number;
6405 break;
6406 case 1:
6407 // The number of text chunks: the chunks are the strings after the
6408 // first one.
6409 break;
6410 case 2:
6411 sidecar.mtext.textgen = number;
6412 break;
6413 case 3:
6414 sidecar.mtext.alignH = number;
6415 break;
6416 case 4:
6417 sidecar.mtext.linespacingStyle = number;
6418 break;
6419 case 5:
6420 sidecar.mtext.backgroundFlags = number;
6421 break;
6422 case 6:
6423 sidecar.mtext.r2018IsNotAnnotative = number != 0;
6424 break;
6425 case 7:
6426 sidecar.mtext.r2018Version = number;
6427 break;
6428 case 8:
6429 sidecar.mtext.r2018DefaultFlag = number != 0;
6430 break;
6431 case 9:
6432 sidecar.mtext.r2018Attachment = number;
6433 break;
6434 case 10:
6435 sidecar.mtext.r2018ColumnType = number;
6436 break;
6437 case 11:
6438 sidecar.mtext.r2018ColumnAutoHeight = number != 0;
6439 break;
6440 case 12:
6441 sidecar.mtext.r2018ColumnFlowReversed = number != 0;
6442 break;
6443 case 13:
6444 expectedColumnHeights = number;
6445 if (number >= 0 && number <= 10000) {
6446 sidecar.mtext.r2018ColumnHeights.reserve(
6447 static_cast<std::size_t>(number));
6448 }
6449 break;
6450 case 14:
6451 expectedAnnotativeBytes = number;
6452 break;
6453 case 15:
6454 sidecar.mtext.annotativeUnknown = number;
6455 break;
6456 case 16:
6457 sidecar.mtext.reallyLocked = number != 0;
6458 break;
6459 default:
6460 if (expectedAnnotativeBytes >= 0 &&
6461 seenAnnotativeBytes < expectedAnnotativeBytes && number >= 0 &&
6462 number <= 255) {
6463 sidecar.mtext.annotativeData.push_back(
6464 static_cast<std::uint8_t>(number));
6465 ++seenAnnotativeBytes;
6466 }
6467 break;
6468 }
6469 } else if (value->code() == 1071) {
6470 std::int64_t integerValue = 0;
6471 if (readXDataInteger(value.get(), 1071, integerValue) !=
6472 XDataReadResult::Valid ||
6473 integerValue < 0 ||
6474 static_cast<std::uint64_t>(integerValue) >
6475 std::numeric_limits<std::uint32_t>::max()) {
6476 return std::nullopt;
6477 }
6478 const std::uint32_t number = static_cast<std::uint32_t>(integerValue);
6479 switch (seenMText1071++) {
6480 case 0:
6481 sidecar.mtext.backgroundColor = static_cast<int>(number);
6482 break;
6483 case 1:
6484 sidecar.mtext.backgroundTransparency = static_cast<int>(number);
6485 break;
6486 case 2:
6487 sidecar.mtext.r2018ColumnCount = static_cast<int>(number);
6488 break;
6489 case 3:
6490 sidecar.mtext.r2018AppIdHandle = number;
6491 break;
6492 case 4:
6493 sidecar.mtext.styleHandle = number;
6494 break;
6495 case 5:
6496 sidecar.mtext.annotativeAppHandle = number;
6497 break;
6498 case 6:
6499 sidecar.mtext.backgroundTrueColor = static_cast<int>(number);
6500 break;
6501 default:
6502 break;
6503 }
6504 } else if (value->code() == 1040) {
6505 double number = 0.0;
6506 if (readXDataDouble(value.get(), 1040, number) !=
6507 XDataReadResult::Valid) {
6508 return std::nullopt;
6509 }
6510 switch (seenMText1040++) {
6511 case 0:
6512 sidecar.mtext.interlin = number;
6513 break;
6514 case 1:
6515 sidecar.mtext.backgroundScale = number;
6516 break;
6517 case 2:
6518 sidecar.mtext.r2018RectWidth = number;
6519 break;
6520 case 3:
6521 sidecar.mtext.r2018RectHeight = number;
6522 break;
6523 case 4:
6524 sidecar.mtext.r2018ExtentsHeight = number;
6525 break;
6526 case 5:
6527 sidecar.mtext.r2018ExtentsWidth = number;
6528 break;
6529 case 6:
6530 sidecar.mtext.r2018ColumnWidth = number;
6531 break;
6532 case 7:
6533 sidecar.mtext.r2018ColumnGutter = number;
6534 break;
6535 case 8:
6536 sidecar.mtext.originalHeight = number;
6537 break;
6538 case 9:
6539 sidecar.mtext.originalWidth = number;
6540 break;
6541 case 10:
6542 sidecar.mtext.originalAngle = number;
6543 break;
6544 case 11:
6545 sidecar.mtext.originalSpacing = number;
6546 break;
6547 default:
6548 sidecar.mtext.r2018ColumnHeights.push_back(number);
6549 break;
6550 }
6551 } else if (value->code() == 1010 || value->code() == 1011 ||
6552 value->code() == 1012) {
6553 DRW_Coord point;
6554 if (readXDataCoord(value.get(), value->code(), point) !=
6555 XDataReadResult::Valid) {
6556 return std::nullopt;
6557 }
6558 switch (seenMText1010++) {
6559 case 0:
6560 sidecar.mtext.basePoint = point;
6561 break;
6562 case 1:
6563 sidecar.mtext.secPoint = point;
6564 break;
6565 case 2:
6566 sidecar.mtext.extPoint = point;
6567 break;
6568 case 3:
6569 sidecar.mtext.r2018XAxisDir = point;
6570 break;
6571 case 4:
6572 sidecar.mtext.r2018InsertionPoint = point;
6573 break;
6574 case 5:
6575 sidecar.mtext.originalPosition = point;
6576 break;
6577 default:
6578 break;
6579 }
6580 } else if (value->code() == 1000) {
6581 std::string text;
6582 if (readXDataString(value.get(), 1000, text) !=
6583 XDataReadResult::Valid) {
6584 return std::nullopt;
6585 }
6586 if (seenMTextStrings++ == 0)
6587 sidecar.mtext.originalStyle = text;
6588 else
6589 sidecar.mtext.text += text;
6590 }
6591 continue;
6592 }
6593 if (!inGroup)
6594 continue;
6595 if (value->code() == 1070) {
6596 std::int64_t integerValue = 0;
6597 if (readXDataInteger(value.get(), 1070, integerValue, false) !=
6598 XDataReadResult::Valid ||
6599 !fitsXDataInt32(integerValue)) {
6600 return std::nullopt;
6601 }
6602 const int number = static_cast<int>(integerValue);
6603 switch (seenValues++) {
6604 case 0:
6605 sidecar.ordinal = number;
6606 break;
6607 case 1:
6608 sidecar.flags = number;
6609 break;
6610 case 2:
6611 sidecar.fieldLength = number;
6612 break;
6613 case 3:
6614 sidecar.attVersion = number;
6615 break;
6616 case 4:
6617 sidecar.attributeType = number;
6618 break;
6619 case 5:
6620 sidecar.lockPosition = number != 0;
6621 break;
6622 case 6:
6623 sidecar.textgen = number;
6624 break;
6625 case 7:
6626 sidecar.alignH = number;
6627 break;
6628 case 8:
6629 sidecar.alignV = number;
6630 break;
6631 case 9:
6632 sidecar.recordVisible = number != 0;
6633 break;
6634 default:
6635 break;
6636 }
6637 } else if (value->code() == 1000) {
6638 std::string text;
6639 if (readXDataString(value.get(), 1000, text) != XDataReadResult::Valid) {
6640 return std::nullopt;
6641 }
6642 if (seenStrings++ == 0)
6643 sidecar.tag = text;
6644 else if (seenStrings == 2)
6645 sidecar.value = text;
6646 } else if (value->code() >= 1010 && value->code() <= 1012) {
6647 DRW_Coord point;
6648 if (readXDataCoord(value.get(), value->code(), point) !=
6649 XDataReadResult::Valid) {
6650 return std::nullopt;
6651 }
6652 if (value->code() == 1010) {
6653 sidecar.basePoint = point;
6654 seenPoints |= 1;
6655 } else if (value->code() == 1011) {
6656 sidecar.secPoint = point;
6657 seenPoints |= 2;
6658 } else {
6659 sidecar.extPoint = point;
6660 seenPoints |= 4;
6661 }
6662 } else if (value->code() == 1040) {
6663 double number = 0.0;
6664 if (readXDataDouble(value.get(), 1040, number) !=
6665 XDataReadResult::Valid) {
6666 return std::nullopt;
6667 }
6668 if (seenDoubles++ == 0)
6669 sidecar.thickness = number;
6670 else if (seenDoubles == 2)
6671 sidecar.widthFactor = number;
6672 }
6673 }
6674
6675 sidecar.hasMText =
6676 foundMTextMarker && mtextSchema == kInsertAttribMTextSchema &&
6677 expectedColumnHeights >= 0 &&
6678 static_cast<int>(sidecar.mtext.r2018ColumnHeights.size()) ==
6679 expectedColumnHeights &&
6680 expectedAnnotativeBytes >= 0 &&
6681 static_cast<int>(sidecar.mtext.annotativeData.size()) ==
6682 expectedAnnotativeBytes &&
6683 seenMText1040 >= 12 && seenMText1010 >= 6 && seenMTextStrings > 0;
6684 if (foundMTextMarker && !sidecar.hasMText)
6685 return std::nullopt;
6686 if (foundMTextXDataMarker &&
6687 !decodeInsertAttribMTextXData(encodedMTextXData, sidecar.mtext.xdata))
6688 return std::nullopt;
6689 if (foundMTextCommonMarker &&
6690 !decodeInsertAttribMTextCommon(encodedMTextCommon, sidecar.mtext))
6691 return std::nullopt;
6692 // Every value of the main group is always written: one that is missing
6693 // makes the sidecar unusable.
6694 if (!foundMarker || sidecar.ordinal < 0 || seenValues < 10 ||
6695 seenStrings < 2 || seenPoints != 7 || seenDoubles < 2) {
6696 return std::nullopt;
6697 }
6698 return sidecar;
6699}
6700
6701// The names under which a text keeps what it was read with as a block
6702// attribute.
6703bool isInsertAttribSidecarMarker(const std::string &marker) {
6704 return marker == kInsertAttribMarker || marker == kInsertAttribMTextMarker ||
6705 marker == kInsertAttribMTextXDataMarker ||
6706 marker == kInsertAttribMTextCommonMarker;
6707}
6708
6709void removeInsertAttribSidecar(DRW_Entity &entity) {
6710 std::vector<std::shared_ptr<DRW_Variant>> filtered;
6711 bool inGroup = false;
6712 for (const auto &value : entity.extData) {
6713 if (value && value->code() == 1001 &&
6714 value->type() == DRW_Variant::STRING && value->content.s != nullptr) {
6715 inGroup = isInsertAttribSidecarMarker(value->c_str());
6716 if (inGroup)
6717 continue;
6718 }
6719 if (!inGroup)
6720 filtered.push_back(value);
6721 }
6722 entity.extData = std::move(filtered);
6723}
6724
6725// A line of an ASCII DXF file cannot hold these; a string read from a DWG can.
6726bool fitsDxfLine(const std::string &value) {
6727 return value.find('\r') == std::string::npos &&
6728 value.find('\n') == std::string::npos &&
6729 value.find('\0') == std::string::npos;
6730}
6731
6732// From the alignment point of a single-line text to its baseline left point,
6733// before rotation, when nothing of the text is drawn: the vertical offsets
6734// RS_Text::update() uses for a capital letter, and no width.
6735RS_Vector emptyTextFirstPointOffset(RS_TextData::VAlign valign,
6736 RS_TextData::HAlign halign, double height) {
6737 if (halign == RS_TextData::HAAligned || halign == RS_TextData::HAFit)
6738 return RS_Vector(0.0, 0.0);
6739 if (halign == RS_TextData::HAMiddle)
6740 return RS_Vector(0.0, -height / 2.0);
6741 switch (valign) {
6742 case RS_TextData::VATop:
6743 return RS_Vector(0.0, -height);
6744 case RS_TextData::VAMiddle:
6745 return RS_Vector(0.0, -height / 2.0);
6746 case RS_TextData::VABottom:
6747 return RS_Vector(0.0, height / 3.0);
6748 case RS_TextData::VABaseline:
6749 default:
6750 return RS_Vector(0.0, 0.0);
6751 }
6752}
6753
6754// Points and justification of a single-line ATTRIB: group 10 the baseline
6755// left point, group 11 the point the text is aligned to. RS_Text keeps the
6756// aligned point as its insertion point and, once laid out, the baseline left
6757// point as its second point; an aligned or fitted text has its two ends there.
6758// A text without a glyph has no borders, and its second point is not usable.
6759void setSingleLineAttribPlacement(DRW_Attrib &attrib, const RS_Text &text) {
6760 const RS_TextData::HAlign halign = text.getHAlign();
6761 const RS_TextData::VAlign valign = text.getVAlign();
6762 attrib.alignH = static_cast<DRW_Text::HAlign>(static_cast<int>(halign));
6763 attrib.alignV = static_cast<DRW_Text::VAlign>(static_cast<int>(valign));
6764 const RS_Vector insertion = text.getInsertionPoint();
6765 RS_Vector first = insertion;
6766 RS_Vector alignment = insertion;
6767 if (halign == RS_TextData::HAAligned || halign == RS_TextData::HAFit) {
6768 alignment = text.getSecondPoint();
6769 } else if (halign != RS_TextData::HALeft ||
6770 valign != RS_TextData::VABaseline) {
6771 const RS_Vector second = text.getSecondPoint();
6772 if (text.count() > 0 && second.valid && std::isfinite(second.x) &&
6773 std::isfinite(second.y)) {
6774 first = second;
6775 } else {
6776 RS_Vector offset =
6777 emptyTextFirstPointOffset(valign, halign, text.getHeight());
6778 offset.rotate(text.getAngle());
6779 first = insertion + offset;
6780 }
6781 }
6782 attrib.basePoint.x = first.x;
6783 attrib.basePoint.y = first.y;
6784 attrib.secPoint.x = alignment.x;
6785 attrib.secPoint.y = alignment.y;
6786}
6787
6788// Axes of the object coordinate system of an extrusion direction: the
6789// arbitrary axis algorithm, with the limits of DRW_Entity::calculateAxis().
6790struct OcsAxes {
6791 DRW_Coord x{1.0, 0.0, 0.0};
6792 DRW_Coord y{0.0, 1.0, 0.0};
6793 DRW_Coord z{0.0, 0.0, 1.0};
6794 bool world = true;
6795};
6796
6797OcsAxes ocsAxes(DRW_Coord normal) {
6798 OcsAxes axes;
6799 if (!std::isfinite(normal.x) || !std::isfinite(normal.y) ||
6800 !std::isfinite(normal.z))
6801 return axes;
6802 normal.unitize();
6803 if (normal.x == 0.0 && normal.y == 0.0 && normal.z >= 0.0)
6804 return axes; // +Z, or no direction at all
6805 axes.world = false;
6806 axes.z = normal;
6807 if (std::fabs(normal.x) < 0.015625 && std::fabs(normal.y) < 0.015625)
6808 axes.x = DRW_Coord(normal.z, 0.0, -normal.x);
6809 else
6810 axes.x = DRW_Coord(-normal.y, normal.x, 0.0);
6811 axes.x.unitize();
6812 axes.y = DRW_Coord(normal.y * axes.x.z - normal.z * axes.x.y,
6813 normal.z * axes.x.x - normal.x * axes.x.z,
6814 normal.x * axes.x.y - normal.y * axes.x.x);
6815 axes.y.unitize();
6816 return axes;
6817}
6818
6819// A WCS point in that system; the inverse of DRW_Entity::extrudePoint().
6820DRW_Coord wcsToOcs(const OcsAxes &axes, const DRW_Coord &point) {
6821 if (axes.world)
6822 return point;
6823 return DRW_Coord(
6824 point.x * axes.x.x + point.y * axes.x.y + point.z * axes.x.z,
6825 point.x * axes.y.x + point.y * axes.y.y + point.z * axes.y.z,
6826 point.x * axes.z.x + point.y * axes.z.y + point.z * axes.z.z);
6827}
6828
6829bool sameXY(const DRW_Coord &a, const DRW_Coord &b) {
6830 const double tolerance =
6831 1.0e-9 * std::max({1.0, std::fabs(a.x), std::fabs(a.y)});
6832 return std::fabs(a.x - b.x) <= tolerance && std::fabs(a.y - b.y) <= tolerance;
6833}
6834
6835// From the alignment point of a single-line text to its baseline left point,
6836// before rotation, as RS_Text::update() lays the text out, the one place
6837// that knows it. An empty value, one of spaces only or a missing font leaves
6838// RS_Text without a glyph and without usable borders.
6839RS_Vector singleLineFirstPointOffset(const QString &text, const QString &style,
6840 double height, double widthFactor,
6841 RS_TextData::VAlign valign,
6842 RS_TextData::HAlign halign) {
6843 const RS_Vector origin(0.0, 0.0);
6844 const RS_TextData d(origin, origin, height, widthFactor, valign, halign,
6845 RS_TextData::None, text, style, 0.0, RS2::Update);
6846 const RS_Text probe(nullptr, d);
6847 const RS_Vector first = probe.getSecondPoint();
6848 if (probe.count() == 0 || !first.valid || !std::isfinite(first.x) ||
6849 !std::isfinite(first.y))
6850 return emptyTextFirstPointOffset(valign, halign, height);
6851 return first;
6852}
6853
6854// The TEXT part of a multi-line ATTRIB, kept in step with the MTEXT shown in
6855// its place as AutoCAD does: justification from the attachment point, group
6856// 11 the MTEXT insertion point, group 10 the baseline left point. The MTEXT
6857// is in WCS, the ATTRIB in its OCS. attrib.text, extPoint and widthscale
6858// must be final. shownValue is attrib.text as a reader shows it: a text of
6859// one line, in which "\P", "\~" and a font in braces are characters with a
6860// width.
6861void setMultilineAttribPlacement(DRW_Attrib &attrib, const DRW_MText &mtext,
6862 const RS_MTextData &data,
6863 const InsertAttribSidecar &sidecar,
6864 bool firstPointEdited,
6865 const QString &shownValue) {
6866 const auto halign =
6867 static_cast<RS_TextData::HAlign>(static_cast<int>(data.halign));
6868 const auto valign =
6869 static_cast<RS_TextData::VAlign>(3 - static_cast<int>(data.valign));
6870 attrib.alignH = static_cast<DRW_Text::HAlign>(static_cast<int>(halign));
6871 attrib.alignV = static_cast<DRW_Text::VAlign>(static_cast<int>(valign));
6872 attrib.angle = mtext.angle;
6873 const OcsAxes axes = ocsAxes(attrib.extPoint);
6874 attrib.secPoint = wcsToOcs(axes, mtext.basePoint);
6875 // The first point that was read belongs to the justification, the
6876 // alignment point and the value it was read with.
6877 if (!firstPointEdited &&
6878 sidecar.alignH == static_cast<int>(attrib.alignH) &&
6879 sidecar.alignV == static_cast<int>(attrib.alignV) &&
6880 sameXY(sidecar.secPoint, attrib.secPoint) &&
6881 attrib.text == sidecar.value) {
6882 attrib.basePoint = sidecar.basePoint;
6883 return;
6884 }
6885 attrib.basePoint = attrib.secPoint;
6886 // Group 11 and the justification place the text. In another plane than
6887 // the world's the rotation is not converted, so no offset is derived
6888 // from it either.
6889 if (!axes.world)
6890 return;
6891 RS_Vector offset =
6892 singleLineFirstPointOffset(shownValue, data.style, data.height,
6893 attrib.widthscale, valign, halign);
6894 offset.rotate(data.angle);
6895 attrib.basePoint.x += offset.x;
6896 attrib.basePoint.y += offset.y;
6897}
6898
6899// Group 71 of a single-line text: 2 backward, 4 upside down. RS_Text holds
6900// one of them at most and is read as neither from a record that has both;
6901// such a record keeps both while the text is still neither.
6902int textGenerationFlags(RS_TextData::TextGeneration generation, int stored) {
6903 if (generation == RS_TextData::Backward)
6904 return 2;
6905 if (generation == RS_TextData::UpsideDown)
6906 return 4;
6907 return (stored & 6) == 6 ? 6 : 0;
6908}
6909
6910// The MTEXT embedded in an attribute names no linetype record of its own in
6911// a file LibreCAD writes: only the three keywords survive.
6912std::string embeddedMTextLineType(const std::string &sourceName) {
6913 const std::string name = normalizeDwgTableName(sourceName);
6914 if (name == "BYBLOCK" || name == "CONTINUOUS")
6915 return name;
6916 return "BYLAYER";
6917}
6918
6919// A DWG R2018 MTEXT that is not annotative repeats its placement in a second
6920// block (ODA 20.4.46, "redundant fields"); AutoCAD writes version 4 and the
6921// default flag there. The model holds one placement, so the block is written
6922// from the fields it repeats. keepsScaleData: the attribute was read from a
6923// DWG whose embedded MTEXT was annotative, and its scale payload is written
6924// again; only then is a clear "not annotative" bit kept. A clear bit with no
6925// payload after it is no form that AutoCAD writes.
6926void completeEmbeddedMTextForDwg(DRW_MText &mtext, bool keepsScaleData) {
6927 if (!keepsScaleData)
6928 mtext.m_r2018IsNotAnnotative = true;
6929 if (!mtext.m_r2018IsNotAnnotative)
6930 return;
6931 if (mtext.m_r2018Version == 0) {
6932 mtext.m_r2018Version = 4;
6933 mtext.m_r2018DefaultFlag = true;
6934 }
6935 mtext.m_r2018Attachment = mtext.textgen;
6936 mtext.m_r2018XAxisDir = mtext.secPoint;
6937 mtext.m_r2018InsertionPoint = mtext.basePoint;
6938 mtext.m_r2018RectWidth = mtext.widthscale;
6939}
6940} // namespace
6941
6942/**
6943 * Implementation of the method which handles ray entities.
6944 */
6945void RS_FilterDXFRW::addRay(const DRW_Ray &data) {
6946 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay");
6947
6948 RS_Vector v1{data.basePoint.x, data.basePoint.y};
6949 RS_Vector v2{data.basePoint.x + data.secPoint.x,
6950 data.basePoint.y + data.secPoint.y};
6951
6952 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: create line");
6953
6954 if (!m_currentContainer) {
6955 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: currentContainer is nullptr");
6956 }
6957
6958 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
6959 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: set attributes");
6960 setEntityAttributes(entity, &data);
6961
6962 // F2 sidecar: base point + direction (preserve Z for full fidelity).
6963 std::vector<std::shared_ptr<DRW_Variant>> payload;
6964 payload.push_back(std::make_shared<DRW_Variant>(
6965 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
6966 payload.push_back(std::make_shared<DRW_Variant>(
6967 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
6968 appendTypeSidecar(entity, kRayMarker, std::move(payload));
6969
6970 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: add entity");
6971
6972 if (m_currentContainer) {
6973 m_currentContainer->addEntity(entity);
6974 }
6975
6976 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addRay: OK");
6977}
6978
6979/**
6980 * Implementation of the method which handles line entities.
6981 */
6982void RS_FilterDXFRW::addXline(const DRW_Xline &data) {
6983 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline");
6984
6985 RS_Vector v1(data.basePoint.x, data.basePoint.y);
6986 RS_Vector v2(data.basePoint.x + data.secPoint.x,
6987 data.basePoint.y + data.secPoint.y);
6988
6989 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: create line");
6990
6991 if (!m_currentContainer) {
6992 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: currentContainer is nullptr");
6993 }
6994
6995 const auto entity = new RS_Line{m_currentContainer, {v1, v2}};
6996 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: set attributes");
6997 setEntityAttributes(entity, &data);
6998
6999 // F2 sidecar: base point + direction (preserve Z for full fidelity).
7000 std::vector<std::shared_ptr<DRW_Variant>> payload;
7001 payload.push_back(std::make_shared<DRW_Variant>(
7002 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
7003 payload.push_back(std::make_shared<DRW_Variant>(
7004 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
7005 appendTypeSidecar(entity, kXlineMarker, std::move(payload));
7006
7007 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: add entity");
7008
7009 if (m_currentContainer) {
7010 m_currentContainer->addEntity(entity);
7011 }
7012
7013 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addXline: OK");
7014}
7015
7016/**
7017 * Implementation of the method which handles circle entities.
7018 */
7019namespace {
7020template <typename T>
7021bool canCreatePlanarEntity(const T &entity, const char *typeName) {
7022 if (hasNormalizedAxialExtrusion(entity))
7023 return true;
7024 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7025 "RS_FilterDXFRW::%s: skipped non-axial extrusion", typeName);
7026 return false;
7027}
7028} // namespace
7029
7030void RS_FilterDXFRW::addCircle(const DRW_Circle &data) {
7031 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addCircle");
7032 if (!canCreatePlanarEntity(data, "addCircle"))
7033 return;
7034
7035 RS_Vector v{data.basePoint.x, data.basePoint.y};
7036 const auto entity = new RS_Circle(m_currentContainer, {v, data.radious});
7037 setEntityAttributes(entity, &data);
7038 if (needsCircleOcsSidecar(data)) {
7039 std::vector<std::shared_ptr<DRW_Variant>> payload;
7040 payload.push_back(
7041 std::make_shared<DRW_Variant>(1010, sourceAxialOcsCenter(data)));
7042 payload.push_back(std::make_shared<DRW_Variant>(1011, data.extPoint));
7043 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
7044 appendTypeSidecar(entity, kCircleOcsMarker, std::move(payload));
7045 }
7046 m_currentContainer->addEntity(entity);
7047}
7048
7049/**
7050 * Implementation of the method which handles arc entities.
7051 *
7052 * @param data
7053 */
7054void RS_FilterDXFRW::addArc(const DRW_Arc &data) {
7055 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addArc");
7056 if (!canCreatePlanarEntity(data, "addArc"))
7057 return;
7058 const RS_Vector v(data.basePoint.x, data.basePoint.y);
7059 const RS_ArcData d(v, data.radious, data.staangle, data.endangle, false);
7060 const auto entity = new RS_Arc(m_currentContainer, d);
7061 setEntityAttributes(entity, &data);
7062 if (needsArcOcsSidecar(data)) {
7063 std::vector<std::shared_ptr<DRW_Variant>> payload;
7064 payload.push_back(
7065 std::make_shared<DRW_Variant>(1010, sourceAxialOcsCenter(data)));
7066 payload.push_back(std::make_shared<DRW_Variant>(1011, data.extPoint));
7067 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
7068 appendTypeSidecar(entity, kArcOcsMarker, std::move(payload));
7069 }
7070 m_currentContainer->addEntity(entity);
7071}
7072
7073/**
7074 * Implementation of the method which handles ellipse entities.
7075 *
7076 * @param data
7077 */
7078void RS_FilterDXFRW::addEllipse(const DRW_Ellipse &data) {
7079 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addEllipse");
7080 if (!canCreatePlanarEntity(data, "addEllipse"))
7081 return;
7082
7083 const RS_Vector v1(data.basePoint.x, data.basePoint.y);
7084 const RS_Vector v2(data.secPoint.x, data.secPoint.y);
7085 double ang2 = data.endparam;
7086 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < RS_TOLERANCE1.0e-10 &&
7087 fabs(data.staparam) < RS_TOLERANCE1.0e-10) {
7088 ang2 = 0.;
7089 }
7090 const auto entity = new RS_Ellipse{
7091 m_currentContainer, {v1, v2, data.ratio, data.staparam, ang2, false}};
7092 setEntityAttributes(entity, &data);
7093 m_currentContainer->addEntity(entity);
7094}
7095
7096/**
7097 * Implementation of the method which handles trace entities.
7098 */
7099void RS_FilterDXFRW::addTrace(const DRW_Trace &data) {
7100 // Pre-R13 (AC1009) typed LINEAR/ALIGNED dimensions rebuild arrows in
7101 // RS_DimLinear/RS_DimAligned. ENTITIES-section SOLID/TRACE records are
7102 // duplicate *D-block graphics (ACEB10 and peers); importing them inflates
7103 // the model-space bbox with misplaced arrow triangles.
7104 if (m_version == 1009 && m_currentContainer == m_graphic) {
7105 return;
7106 }
7107 if (!hasNormalizedAxialExtrusion(data)) {
7108 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
7109 "RS_FilterDXFRW::addTrace: skipped non-axial extrusion");
7110 return;
7111 }
7112
7113 RS_Solid *entity;
7114 const RS_Vector v1{data.basePoint.x, data.basePoint.y};
7115 const RS_Vector v2{data.secPoint.x, data.secPoint.y};
7116 const RS_Vector v3{data.thirdPoint.x, data.thirdPoint.y};
7117 const RS_Vector v4{data.fourPoint.x, data.fourPoint.y};
7118 if (v3 == v4) {
7119 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3));
7120 } else {
7121 entity = new RS_Solid(m_currentContainer, RS_SolidData(v1, v2, v3, v4));
7122 }
7123
7124 setEntityAttributes(entity, &data);
7125
7126 // F2 sidecar: preserve the exact native TRACE/SOLID type and all four WCS
7127 // corners. RS_Solid is 2D-lossy, so the write pre-pass must rebuild the
7128 // source type with canonicalized axial thickness.
7129 const char *marker = data.eType == DRW::DXF_TRACE ? kTraceMarker
7130 : data.eType == DRW::SOLID ? kSolidMarker
7131 : nullptr;
7132 if (marker != nullptr) {
7133 const bool reflectedOcs = data.extPoint.z < 0.0;
7134 std::vector<std::shared_ptr<DRW_Variant>> payload;
7135 payload.push_back(std::make_shared<DRW_Variant>(
7136 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
7137 payload.push_back(std::make_shared<DRW_Variant>(
7138 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
7139 payload.push_back(std::make_shared<DRW_Variant>(
7140 1012,
7141 DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
7142 payload.push_back(std::make_shared<DRW_Variant>(
7143 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
7144 payload.push_back(std::make_shared<DRW_Variant>(
7145 1040, reflectedOcs ? -data.thickness : data.thickness));
7146 appendTypeSidecar(entity, marker, std::move(payload));
7147 }
7148
7149 m_currentContainer->addEntity(entity);
7150}
7151
7152void RS_FilterDXFRW::addTolerance(const DRW_Tolerance &data) {
7153 const RS_Vector insertionPoint{data.insertionPoint.x, data.insertionPoint.y};
7154 const RS_Vector axisDirectionVector{data.xAxisDirectionVector.x,
7155 data.xAxisDirectionVector.y};
7156
7157 const QString text = toNativeString(QString::fromUtf8(data.text.c_str()));
7158
7159 QString sty = QString::fromUtf8(data.dimStyleName.c_str());
7160 if (sty.isEmpty()) {
7161 sty = m_dimStyle;
7162 }
7163
7164 const auto tolData =
7165 LC_ToleranceData(insertionPoint, axisDirectionVector, text, sty);
7166
7167 const auto entity = new LC_Tolerance{m_currentContainer, tolData};
7168 setEntityAttributes(entity, &data);
7169 entity->update();
7170 m_currentContainer->addEntity(entity);
7171}
7172
7173/**
7174 * Implementation of the method which handles solid entities.
7175 */
7176void RS_FilterDXFRW::addSolid(const DRW_Solid &data) { addTrace(data); }
7177
7178// Convert a decoded ACIS wireframe (drw_acis.h) into 2D RS_* entities,
7179// mirroring addMesh: LibreCAD is 2D, so every point is projected by dropping Z.
7180// The result is added to `container`; the created entities are returned so a
7181// caller can apply setEntityAttributes afterwards. Static + free of m_graphic
7182// so it can be unit-tested directly against a bare RS_EntityContainer.
7183std::vector<RS_Entity *>
7184RS_FilterDXFRW::acisWireframeToEntities(const DRW_AcisBrep &brep,
7185 RS_EntityContainer *container) {
7186 std::vector<RS_Entity *> created;
7187 if (container == nullptr)
7188 return created;
7189
7190 auto xy = [](const DRW_Coord &c) { return RS_Vector(c.x, c.y); };
7191 auto pushEntity = [&](RS_Entity *e) {
7192 container->addEntity(e);
7193 created.push_back(e);
7194 };
7195 auto addLineSeg = [&](const DRW_AcisEdge &edge) {
7196 if (edge.hasStart && edge.hasEnd)
7197 pushEntity(new RS_Line(container, {xy(edge.start), xy(edge.end)}));
7198 };
7199
7200 // Parametric (eccentric) angle of a 2D point on an ellipse defined by its
7201 // center, major-axis vector and minor/major ratio.
7202 auto ellipseParam = [](const RS_Vector &center, const RS_Vector &majorP,
7203 double ratio, const RS_Vector &p) -> double {
7204 double majLen = majorP.magnitude();
7205 if (majLen < RS_TOLERANCE1.0e-10)
7206 return 0.0;
7207 RS_Vector d = p - center;
7208 RS_Vector majHat = majorP / majLen;
7209 RS_Vector minHat(-majHat.y, majHat.x); // major rotated +90 deg
7210 double cosT = d.dotP(majHat) / majLen;
7211 double sinT = d.dotP(minHat);
7212 if (ratio > RS_TOLERANCE1.0e-10)
7213 sinT /= (majLen * ratio);
7214 return RS_Math::correctAngle(std::atan2(sinT, cosT));
7215 };
7216
7217 // Track edge endpoints so isolated vertices can be rendered as points.
7218 std::vector<RS_Vector> endpoints;
7219 auto noteEndpoints = [&](const DRW_AcisEdge &edge) {
7220 if (edge.hasStart)
7221 endpoints.push_back(xy(edge.start));
7222 if (edge.hasEnd)
7223 endpoints.push_back(xy(edge.end));
7224 };
7225
7226 for (const DRW_AcisEdge &edge : brep.edges) {
7227 noteEndpoints(edge);
7228 switch (edge.curveType) {
7229 case DRW_AcisCurve::Straight:
7230 addLineSeg(edge);
7231 break;
7232 case DRW_AcisCurve::Ellipse: {
7233 RS_Vector center = xy(edge.p0);
7234 RS_Vector majorP = xy(edge.p2);
7235 double ratio = edge.ratio;
7236 if (majorP.magnitude() < RS_TOLERANCE1.0e-10 || ratio <= 0.0) {
7237 addLineSeg(edge); // insufficient/degenerate ellipse params
7238 break;
7239 }
7240 double a1 = 0.0;
7241 double a2 = 2.0 * M_PI3.14159265358979323846;
7242 if (edge.hasStart && edge.hasEnd) {
7243 a1 = ellipseParam(center, majorP, ratio, xy(edge.start));
7244 a2 = ellipseParam(center, majorP, ratio, xy(edge.end));
7245 }
7246 pushEntity(
7247 new RS_Ellipse(container, {center, majorP, ratio, a1, a2, false}));
7248 break;
7249 }
7250 case DRW_AcisCurve::Intcurve: {
7251 if (edge.controlPoints.size() >= 2) {
7252 size_t n = edge.controlPoints.size();
7253 int degree = (n >= 4) ? 3 : static_cast<int>(n - 1);
7254 RS_SplineData d(degree, /*closed=*/false);
7255 auto *spline = new RS_Spline(container, d);
7256 for (const DRW_Coord &cp : edge.controlPoints)
7257 spline->addControlPointRaw(RS_Vector(cp.x, cp.y), 1.0);
7258 pushEntity(spline);
7259 } else {
7260 addLineSeg(edge); // no control polygon -> straight fallback
7261 }
7262 break;
7263 }
7264 case DRW_AcisCurve::Unknown:
7265 default:
7266 addLineSeg(edge);
7267 break;
7268 }
7269 }
7270
7271 // Isolated vertices (no incident edge endpoint) -> RS_Point.
7272 for (const DRW_AcisVertex &v : brep.vertices) {
7273 if (!v.valid)
7274 continue;
7275 RS_Vector p = xy(v.point);
7276 bool incident = false;
7277 for (const RS_Vector &e : endpoints) {
7278 if (std::fabs(e.x - p.x) < RS_TOLERANCE1.0e-10 &&
7279 std::fabs(e.y - p.y) < RS_TOLERANCE1.0e-10) {
7280 incident = true;
7281 break;
7282 }
7283 }
7284 if (!incident)
7285 pushEntity(new RS_Point(container, RS_PointData(p)));
7286 }
7287
7288 return created;
7289}
7290
7291void RS_FilterDXFRW::addModelerGeometry(const DRW_ModelerGeometry &data) {
7292 // Preserve the raw modeler payload as advanced-metadata sidecar so a native
7293 // ACIS body is never silently lost on round-trip.
7294 if (m_graphic != nullptr) {
7295 m_graphic->dwgAdvancedMetadata().addModelerGeometry(data);
7296 }
7297 // Additionally render the decoded SAB wireframe as 2D geometry (mirrors
7298 // addMesh). Guard: only touch the container when the decode yields edges, so
7299 // undecodable bodies keep the previous metadata-only behavior (no
7300 // regression).
7301 DRW_ModelerGeometry &mut = const_cast<DRW_ModelerGeometry &>(data);
7302 if (mut.decodeWireframe() && !mut.m_wireframe.edges.empty()) {
7303 std::vector<RS_Entity *> ents =
7304 acisWireframeToEntities(mut.m_wireframe, m_currentContainer);
7305 for (RS_Entity *e : ents) {
7306 setEntityAttributes(e, &data);
7307 // Wireframe entities are derived display geometry, not additional
7308 // instances of the opaque modeler object.
7309 e->setSourceHandle(0);
7310 }
7311 RS_DEBUGRS_Debug::instance()->print(
7312 "RS_FilterDXFRW::addModelerGeometry: rendered %zu wireframe entities",
7313 ents.size());
7314 }
7315 RS_DEBUGRS_Debug::instance()->print(
7316 "RS_FilterDXFRW::addModelerGeometry: type %d handle %d history %d",
7317 static_cast<int>(data.eType), static_cast<int>(data.handle),
7318 static_cast<int>(data.m_historyHandle));
7319}
7320
7321void RS_FilterDXFRW::addSurface(const DRW_Surface *data) {
7322 if (data == nullptr)
7323 return;
7324 if (m_graphic != nullptr) {
7325 m_graphic->dwgAdvancedMetadata().addSurface(*data);
7326 }
7327 // Decode the lazily-cached SAB wireframe (idempotent; never throws), then
7328 // render its edges as 2D geometry via the shared helper. Guard on edges to
7329 // avoid emitting anything for bodies we cannot decode (no regression over the
7330 // base no-op).
7331 DRW_Surface *surf = const_cast<DRW_Surface *>(data);
7332 if (surf->decodeWireframe() && !surf->m_wireframe.edges.empty()) {
7333 std::vector<RS_Entity *> ents =
7334 acisWireframeToEntities(surf->m_wireframe, m_currentContainer);
7335 for (RS_Entity *e : ents) {
7336 setEntityAttributes(e, data);
7337 // Keep the source handle reserved for the opaque surface body.
7338 e->setSourceHandle(0);
7339 }
7340 RS_DEBUGRS_Debug::instance()->print(
7341 "RS_FilterDXFRW::addSurface: rendered %zu wireframe entities",
7342 ents.size());
7343 }
7344}
7345
7346void RS_FilterDXFRW::addLight(const DRW_Light &data) {
7347 if (m_graphic != nullptr) {
7348 m_graphic->dwgAdvancedMetadata().addLight(data);
7349 }
7350 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLight: %s handle %d",
7351 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str(),
7352 static_cast<int>(data.handle));
7353}
7354
7355void RS_FilterDXFRW::addLightList(const DRW_LightList &data) {
7356 if (m_graphic != nullptr)
7357 m_graphic->dwgAdvancedMetadata().addLightList(data);
7358 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLightList: handle %d lights %d",
7359 static_cast<int>(data.handle),
7360 static_cast<int>(data.m_lights.size()));
7361}
7362
7363void RS_FilterDXFRW::addLayerFilter(const DRW_LayerFilter &data) {
7364 if (m_graphic != nullptr)
7365 m_graphic->dwgAdvancedMetadata().addLayerFilter(data);
7366 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayerFilter: handle %d names %d",
7367 static_cast<int>(data.handle),
7368 static_cast<int>(data.m_names.size()));
7369}
7370
7371void RS_FilterDXFRW::addDataLink(const DRW_DataLink &data) {
7372 if (m_graphic != nullptr)
7373 m_graphic->dwgAdvancedMetadata().addDataLink(data);
7374 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataLink: handle %d custom data %d",
7375 static_cast<int>(data.handle),
7376 static_cast<int>(data.m_customData.size()));
7377}
7378
7379void RS_FilterDXFRW::addGeoMapImage(const DRW_GeoMapImage &data) {
7380 if (m_graphic != nullptr)
7381 m_graphic->dwgAdvancedMetadata().addGeoMapImage(data);
7382 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoMapImage: handle %d",
7383 static_cast<int>(data.handle));
7384}
7385
7386void RS_FilterDXFRW::addCamera(const DRW_Camera &data) {
7387 if (m_graphic != nullptr)
7388 m_graphic->dwgAdvancedMetadata().addCamera(data);
7389 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCamera: handle %d view %d",
7390 static_cast<int>(data.handle),
7391 static_cast<int>(data.m_viewHandle));
7392}
7393
7394void RS_FilterDXFRW::addGeoPositionMarker(const DRW_GeoPositionMarker &data) {
7395 if (m_graphic != nullptr)
7396 m_graphic->dwgAdvancedMetadata().addGeoPositionMarker(data);
7397 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoPositionMarker: handle %d",
7398 static_cast<int>(data.handle));
7399}
7400
7401void RS_FilterDXFRW::addTvDeviceProperties(const DRW_TvDeviceProperties &data) {
7402 if (m_graphic != nullptr)
7403 m_graphic->dwgAdvancedMetadata().addTvDeviceProperties(data);
7404 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTvDeviceProperties: handle %d",
7405 static_cast<int>(data.handle));
7406}
7407
7408void RS_FilterDXFRW::addViewportEntityHeader(
7409 const DRW_ViewportEntityHeader &data) {
7410 if (m_graphic != nullptr)
7411 m_graphic->dwgAdvancedMetadata().addViewportEntityHeader(data);
7412 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addViewportEntityHeader: handle %d",
7413 static_cast<int>(data.handle));
7414}
7415
7416void RS_FilterDXFRW::addVxControl(const DRW_VxControl &data) {
7417 if (m_graphic != nullptr)
7418 m_graphic->dwgAdvancedMetadata().addVxControl(data);
7419 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVxControl: handle %d",
7420 static_cast<int>(data.handle));
7421}
7422
7423void RS_FilterDXFRW::addVxTableRecord(const DRW_VxTableRecord &data) {
7424 if (m_graphic != nullptr)
7425 m_graphic->dwgAdvancedMetadata().addVxTableRecord(data);
7426 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVxTableRecord: handle %d",
7427 static_cast<int>(data.handle));
7428}
7429
7430namespace {
7431// Keep imported XDATA, replacing only the polyline metadata we regenerate.
7432std::vector<std::shared_ptr<DRW_Variant>>
7433polylineXDataWithoutSidecar(const RS_Entity &entity) {
7434 std::vector<std::shared_ptr<DRW_Variant>> ext;
7435 bool inSidecar = false;
7436 for (const auto &value : entity.getDrwExtData()) {
7437 if (value && value->code() == 1001) {
7438 std::string marker;
7439 inSidecar = readXDataMarker(value.get(), marker) == XDataReadResult::Valid &&
7440 normalizeDwgTableName(marker) == "LIBRECAD_LWPOLYLINE";
7441 }
7442 if (!inSidecar)
7443 ext.push_back(value);
7444 }
7445 return ext;
7446}
7447} // namespace
7448
7449/**
7450 * Implementation of the method which handles lightweight polyline entities.
7451 */
7452void RS_FilterDXFRW::addLWPolyline(const DRW_LWPolyline &data) {
7453 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLWPolyline");
7454 if (data.vertlist.empty()) {
7455 return;
7456 }
7457
7458 // libdxfrw has already applied the OCS frame before this callback. A
7459 // LibreCAD polyline is strictly 2D, so only the two normalized axial
7460 // frames can be represented without retaining a second coordinate space.
7461 if (!hasNormalizedAxialExtrusion(data)) {
7462 RS_DEBUGRS_Debug::instance()->print(
7463 RS_Debug::D_WARNING,
7464 "RS_FilterDXFRW::addLWPolyline: skipped non-axial extrusion");
7465 return;
7466 }
7467 const bool reflectedOcs = data.extPoint.z < 0.0;
7468 RS_PolylineData d(RS_Vector{}, RS_Vector{}, data.flags & 0x1);
7469 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, d);
7470 setEntityAttributes(polyline.get(), &data);
7471
7472 std::vector<std::pair<RS_Vector, double>> verList;
7473 for (const auto &v : data.vertlist) {
7474 verList.emplace_back(std::make_pair(RS_Vector{v->x, v->y},
7475 reflectedOcs ? -v->bulge : v->bulge));
7476 }
7477
7478 polyline->appendVertexs(verList);
7479 const bool defaultExtrusion = !reflectedOcs;
7480 bool hasVertexMetadata = false;
7481 for (const auto &v : data.vertlist) {
7482 if (v && (v->stawidth != 0.0 || v->endwidth != 0.0 || v->identifier != 0)) {
7483 hasVertexMetadata = true;
7484 break;
7485 }
7486 }
7487 if (data.width != 0.0 || data.elevation != 0.0 || data.thickness != 0.0 ||
7488 !defaultExtrusion || hasVertexMetadata) {
7489 auto ext = polylineXDataWithoutSidecar(*polyline);
7490 ext.push_back(std::make_shared<DRW_Variant>(
7491 1001, std::string("LibreCAD_LWPOLYLINE")));
7492 ext.push_back(std::make_shared<DRW_Variant>(1040, data.width));
7493 ext.push_back(std::make_shared<DRW_Variant>(
7494 1040, reflectedOcs ? -data.elevation : data.elevation));
7495 ext.push_back(std::make_shared<DRW_Variant>(
7496 1040, reflectedOcs ? -data.thickness : data.thickness));
7497 ext.push_back(
7498 std::make_shared<DRW_Variant>(1010, DRW_Coord{0.0, 0.0, 1.0}));
7499 ext.push_back(std::make_shared<DRW_Variant>(
7500 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
7501 for (const auto &v : data.vertlist) {
7502 ext.push_back(std::make_shared<DRW_Variant>(1040, v ? v->stawidth : 0.0));
7503 ext.push_back(std::make_shared<DRW_Variant>(1040, v ? v->endwidth : 0.0));
7504 ext.push_back(std::make_shared<DRW_Variant>(
7505 1071, std::int32_t{v ? v->identifier : 0}));
7506 }
7507 polyline->setDrwExtData(std::move(ext));
7508 }
7509 m_currentContainer->addEntity(polyline.release());
7510}
7511
7512/**
7513 * Implementation of the method which handles MLINESTYLE table entries.
7514 * Cache by name so a later addMLine can resolve element offsets/colors.
7515 */
7516void RS_FilterDXFRW::addMLineStyle(const DRW_MLineStyle &data) {
7517 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLineStyle: %s (%zu elements)",
7518 data.name.c_str(), data.elements.size());
7519 QString key = QString::fromUtf8(data.name.c_str());
7520 if (!key.isEmpty()) {
7521 m_mlineStyleCache[key] = data;
7522 }
7523 // Durable metadata (in ADDITION to the transient cache addMLine uses to
7524 // decompose). The DWG reader populates ONLY this; the DXF->DXF path keeps
7525 // MLINESTYLE in the raw net, so writeObjects dedups by handle to avoid a
7526 // double-emit.
7527 if (m_graphic != nullptr) {
7528 m_graphic->dwgAdvancedMetadata().addMLineStyle(data);
7529 }
7530}
7531
7532void RS_FilterDXFRW::addWipeoutVariables(const DRW_WipeoutVariables &data) {
7533 // DWG read populates only this typed metadata; DXF read also keeps it in the
7534 // raw net (writeObjects dedups by handle). Enables DWG->DXF typed re-emit.
7535 if (m_graphic != nullptr) {
7536 m_graphic->dwgAdvancedMetadata().addWipeoutVariables(data);
7537 }
7538}
7539
7540/**
7541 * Implementation of the method which handles MLINE entities.
7542 *
7543 * LibreCAD has no native multiline. Decompose each MLINE into N parallel
7544 * RS_Polylines (one per element in the referenced MLINESTYLE), with
7545 * round-trip metadata stored on each polyline's drwExtData so the export
7546 * path can reconstruct an MLINE on save.
7547 */
7548void RS_FilterDXFRW::addMLine(const DRW_MLine *data) {
7549 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLine");
7550 if (!data || data->vertlist.empty() || data->numLines == 0)
7551 return;
7552
7553 const int N = data->numLines;
7554 QString styleName = QString::fromUtf8(data->styleName.c_str());
7555 auto styleIt = m_mlineStyleCache.find(styleName);
7556 const DRW_MLineStyle *style =
7557 (styleIt != m_mlineStyleCache.end()) ? &styleIt->second : nullptr;
7558
7559 // Compute effective per-element offsets after justification + scale.
7560 // Justification: 0=top, 1=zero/middle, 2=bottom. The reference offset
7561 // is removed from each element's offset so the chosen line aligns
7562 // with the baseline polyline path.
7563 std::vector<double> effOffsets(N, 0.0);
7564 if (style && static_cast<int>(style->elements.size()) == N) {
7565 double ref = 0.0;
7566 if (data->justification == 0) { // top
7567 for (auto &e : style->elements)
7568 ref = std::max(ref, e.offset);
7569 } else if (data->justification == 2) { // bottom
7570 for (auto &e : style->elements)
7571 ref = std::min(ref, e.offset);
7572 }
7573 for (int i = 0; i < N; ++i) {
7574 effOffsets[i] = (style->elements[i].offset - ref) * data->scale;
7575 }
7576 } else {
7577 // Fallback: evenly spaced [-(N-1)/2 .. +(N-1)/2] * scale.
7578 for (int i = 0; i < N; ++i) {
7579 effOffsets[i] = (i - 0.5 * (N - 1)) * data->scale;
7580 }
7581 }
7582
7583 const QString mlineId = QStringLiteral("mline_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "mline_%1"))).arg(data->handle);
7584 const bool closed = (data->openClosed & 0x1) != 0;
7585
7586 for (int i = 0; i < N; ++i) {
7587 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, closed);
7588 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7589 setEntityAttributes(polyline.get(), data);
7590
7591 for (const auto &v : data->vertlist) {
7592 RS_Vector miter(v.miterDir.x, v.miterDir.y);
7593 const double mlen = miter.magnitude();
7594 RS_Vector pos;
7595 if (mlen < RS_TOLERANCE1.0e-10) {
7596 pos = RS_Vector{v.position.x, v.position.y};
7597 } else {
7598 miter /= mlen;
7599 pos = RS_Vector{v.position.x + miter.x * effOffsets[i],
7600 v.position.y + miter.y * effOffsets[i]};
7601 }
7602 polyline->addVertex(pos, 0.0, false);
7603 }
7604
7605 // Round-trip metadata as XDATA. Schema per the implementation plan:
7606 // 1001 "LibreCAD_MLINE", 1000 mlineId, 1000 styleName,
7607 // 1071 styleHandle, 1040 scale, 1070 justification, 1070 elementCount,
7608 // 1070 elementIndex, 1040 offset, 1070 flags.
7609 // Anchor (i==0) additionally stores per-vertex baseline + miter
7610 // so the export side can reconstruct without averaging.
7611 std::vector<std::shared_ptr<DRW_Variant>> ext;
7612 ext.push_back(
7613 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_MLINE")));
7614 ext.push_back(std::make_shared<DRW_Variant>(1000, mlineId.toStdString()));
7615 ext.push_back(std::make_shared<DRW_Variant>(1000, data->styleName));
7616 ext.push_back(std::make_shared<DRW_Variant>(
7617 1071, static_cast<std::int32_t>(data->styleHandle)));
7618 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale));
7619 ext.push_back(
7620 std::make_shared<DRW_Variant>(1070, std::int32_t{data->justification}));
7621 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{N}));
7622 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{i}));
7623 ext.push_back(std::make_shared<DRW_Variant>(1040, effOffsets[i]));
7624 ext.push_back(
7625 std::make_shared<DRW_Variant>(1070, std::int32_t{data->openClosed}));
7626 if (i == 0) {
7627 // Anchor carries baseline + miter for each vertex.
7628 for (const auto &v : data->vertlist) {
7629 ext.push_back(std::make_shared<DRW_Variant>(
7630 1011, DRW_Coord(v.position.x, v.position.y, v.position.z)));
7631 ext.push_back(std::make_shared<DRW_Variant>(
7632 1013, DRW_Coord(v.miterDir.x, v.miterDir.y, v.miterDir.z)));
7633 }
7634 }
7635 polyline->setDrwExtData(std::move(ext));
7636
7637 m_currentContainer->addEntity(polyline.release());
7638 }
7639}
7640
7641/**
7642 * Implementation of the UNDERLAYDEFINITION callback.
7643 * Caches by handle so addUnderlay (entities arrive earlier) and the
7644 * export reconstruction can resolve filename + sheet on demand.
7645 */
7646void RS_FilterDXFRW::linkUnderlay(const DRW_UnderlayDefinition *d) {
7647 if (!d)
7648 return;
7649 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkUnderlay: %s", d->filename.c_str());
7650 m_underlayDefMap[d->handle] = *d;
7651 if (m_graphic != nullptr)
7652 m_graphic->dwgAdvancedMetadata().addUnderlayDefinition(*d);
7653}
7654
7655void RS_FilterDXFRW::addShape(const DRW_Shape &data) {
7656 if (m_graphic != nullptr) {
7657 const auto *block = dynamic_cast<const RS_Block *>(m_currentContainer);
7658 m_graphic->dwgAdvancedMetadata().addShape(
7659 data, QFileInfo(m_file).absolutePath().toStdString(),
7660 block != nullptr ? block->getName().toStdString() : std::string{});
7661 }
7662 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addShape: index %d style %d",
7663 static_cast<int>(data.m_shapeIndex),
7664 static_cast<int>(data.m_shapeFileHandle));
7665}
7666
7667void RS_FilterDXFRW::addOle2Frame(const DRW_Ole2Frame &data) {
7668 if (m_graphic != nullptr)
7669 m_graphic->dwgAdvancedMetadata().addOle2Frame(data);
7670 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addOle2Frame: bytes %d declared %d",
7671 static_cast<int>(data.m_payloadByteCount),
7672 static_cast<int>(data.m_declaredPayloadLength));
7673}
7674
7675void RS_FilterDXFRW::addOleFrame(const DRW_OleFrame &data) {
7676 if (m_graphic != nullptr)
7677 m_graphic->dwgAdvancedMetadata().addOleFrame(data);
7678 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addOleFrame: bytes %d declared %d",
7679 static_cast<int>(data.m_payloadByteCount),
7680 static_cast<int>(data.m_declaredPayloadLength));
7681}
7682
7683/**
7684 * Implementation of the UNDERLAY entity callback (PDFUNDERLAY/DGNUNDERLAY/
7685 * DWFUNDERLAY). Decomposes the underlay into a single closed RS_Polyline
7686 * showing the clip-boundary placeholder. The filename + kind ride along
7687 * in XDATA so the export side can reconstruct the original UNDERLAY entity.
7688 *
7689 * Filename resolution: OBJECTS are parsed AFTER ENTITIES in libdxfrw, so
7690 * m_underlayDefMap may be empty here. We store the definitionHandle in
7691 * XDATA only; on-demand lookups (UI tooltip, export reconstruction) use
7692 * the cache once it's populated by linkUnderlay.
7693 */
7694void RS_FilterDXFRW::addUnderlay(const DRW_Underlay *data) {
7695 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addUnderlay");
7696 if (!data)
7697 return;
7698
7699 // Build clip polygon in WCS. clipBoundary is in OCS-2D; for a typical
7700 // 2D extrusion (extPoint == (0,0,1)), OCS == WCS modulo position +
7701 // scale + rotation. LibreCAD is 2D so we project z-up regardless.
7702 std::vector<RS_Vector> verts;
7703 const bool fallbackPreviewGenerated = true;
7704 if (data->clipBoundary.size() >= 3) {
7705 verts.reserve(data->clipBoundary.size());
7706 for (const auto &v : data->clipBoundary) {
7707 verts.emplace_back(v.x, v.y);
7708 }
7709 } else if (data->clipBoundary.size() == 2) {
7710 // Two corners of an axis-aligned rectangle (in OCS).
7711 const auto &a = data->clipBoundary[0];
7712 const auto &b = data->clipBoundary[1];
7713 verts.emplace_back(a.x, a.y);
7714 verts.emplace_back(b.x, a.y);
7715 verts.emplace_back(b.x, b.y);
7716 verts.emplace_back(a.x, b.y);
7717 } else {
7718 // No clip — synthesize a unit-square placeholder centered on origin.
7719 // Real underlay sizes need PDF page dims we don't have access to.
7720 verts.emplace_back(-0.5, -0.5);
7721 verts.emplace_back(0.5, -0.5);
7722 verts.emplace_back(0.5, 0.5);
7723 verts.emplace_back(-0.5, 0.5);
7724 }
7725
7726 // Apply scale, rotation, translation (rotation is in radians per DWG).
7727 const double cs = std::cos(data->rotation);
7728 const double sn = std::sin(data->rotation);
7729 for (auto &p : verts) {
7730 const double sx = p.x * data->scale.x;
7731 const double sy = p.y * data->scale.y;
7732 const double rx = sx * cs - sy * sn;
7733 const double ry = sx * sn + sy * cs;
7734 p = RS_Vector{rx + data->position.x, ry + data->position.y};
7735 }
7736
7737 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, /*closed=*/true);
7738 auto polyline = new RS_Polyline(m_currentContainer, pd);
7739 setEntityAttributes(polyline, data);
7740 for (const auto &p : verts)
7741 polyline->addVertex(p, 0.0, false);
7742
7743 // Round-trip metadata as XDATA. App marker LibreCAD_UNDERLAY.
7744 const QString underlayId = QStringLiteral("underlay_%1")(QString(QtPrivate::qMakeStringPrivate(u"" "underlay_%1"))).arg(data->handle);
7745 const char *kindStr = (data->kind == DRW_Underlay::DGN) ? "DGN"
7746 : (data->kind == DRW_Underlay::DWF) ? "DWF"
7747 : "PDF";
7748 std::vector<std::shared_ptr<DRW_Variant>> ext;
7749 ext.push_back(
7750 std::make_shared<DRW_Variant>(1001, std::string("LibreCAD_UNDERLAY")));
7751 ext.push_back(std::make_shared<DRW_Variant>(1000, underlayId.toStdString()));
7752 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string(kindStr)));
7753 ext.push_back(std::make_shared<DRW_Variant>(
7754 1071, std::int32_t{static_cast<int>(data->definitionHandle)}));
7755 ext.push_back(std::make_shared<DRW_Variant>(
7756 1010, DRW_Coord(data->position.x, data->position.y, data->position.z)));
7757 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("EXTRUSION")));
7758 ext.push_back(std::make_shared<DRW_Variant>(
7759 1010, DRW_Coord(data->extPoint.x, data->extPoint.y, data->extPoint.z)));
7760 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("SCALE3")));
7761 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.x));
7762 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.y));
7763 ext.push_back(std::make_shared<DRW_Variant>(1040, data->scale.z));
7764 ext.push_back(std::make_shared<DRW_Variant>(1040, data->rotation));
7765 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->flags}));
7766 ext.push_back(
7767 std::make_shared<DRW_Variant>(1070, std::int32_t{data->contrast}));
7768 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{data->fade}));
7769 if (data->clipBoundary.empty()) {
7770 ext.push_back(std::make_shared<DRW_Variant>(1000, std::string("NO_CLIP")));
7771 } else {
7772 ext.push_back(
7773 std::make_shared<DRW_Variant>(1000, std::string("CLIP_ORIGINAL")));
7774 for (const DRW_Coord &point : data->clipBoundary) {
7775 ext.push_back(std::make_shared<DRW_Variant>(
7776 1010, DRW_Coord(point.x, point.y, point.z)));
7777 }
7778 }
7779 if (!data->inverseClipBoundary.empty() || (data->flags & 0x10) != 0) {
7780 ext.push_back(
7781 std::make_shared<DRW_Variant>(1000, std::string("INVERSE_CLIP")));
7782 for (const DRW_Coord &point : data->inverseClipBoundary) {
7783 ext.push_back(std::make_shared<DRW_Variant>(
7784 1010, DRW_Coord(point.x, point.y, point.z)));
7785 }
7786 }
7787 polyline->setDrwExtData(std::move(ext));
7788
7789 if (m_graphic != nullptr)
7790 m_graphic->dwgAdvancedMetadata().addUnderlay(*data,
7791 fallbackPreviewGenerated);
7792 m_currentContainer->addEntity(polyline);
7793}
7794
7795/**
7796 * Implementation of the method which handles polyline entities.
7797 */
7798void RS_FilterDXFRW::addPolyline(const DRW_Polyline &data) {
7799 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPolyline");
7800 if (data.flags & 0x10) {
7801 // the polyline is a polygon mesh
7802 int M = data.vertexcount;
7803 int N = data.facecount;
7804 const bool canRenderFallback =
7805 M > 0 && N > 0 && data.vertlist.size() == static_cast<size_t>(M * N) &&
7806 data.curvetype == 0;
7807 if (m_graphic != nullptr)
7808 m_graphic->dwgAdvancedMetadata().addMeshPolyline(data, canRenderFallback);
7809 if (M <= 0 || N <= 0 ||
7810 data.vertlist.size() != static_cast<size_t>(M * N)) {
7811 return; // invalid mesh
7812 }
7813 if (data.curvetype != 0) {
7814 return; // smooth surfaces not handled
7815 }
7816 bool closedM = (data.flags & 0x1); // closed in M direction
7817 bool closedN = (data.flags & 0x20); // closed in N direction
7818 const std::string meshId =
7819 std::string("polyline_mesh_") + std::to_string(data.handle);
7820 const int meshElementCount = M + N;
7821 auto makeMeshExtData = [&](int elementIndex, const std::string &role,
7822 int roleIndex, bool anchor) {
7823 std::vector<std::shared_ptr<DRW_Variant>> ext;
7824 ext.push_back(std::make_shared<DRW_Variant>(
7825 1001, std::string("LibreCAD_POLYLINE_MESH")));
7826 ext.push_back(std::make_shared<DRW_Variant>(1000, meshId));
7827 ext.push_back(std::make_shared<DRW_Variant>(1000, role));
7828 ext.push_back(
7829 std::make_shared<DRW_Variant>(1070, std::int32_t{elementIndex}));
7830 ext.push_back(
7831 std::make_shared<DRW_Variant>(1070, std::int32_t{meshElementCount}));
7832 ext.push_back(
7833 std::make_shared<DRW_Variant>(1070, std::int32_t{roleIndex}));
7834 ext.push_back(
7835 std::make_shared<DRW_Variant>(1070, std::int32_t{data.flags}));
7836 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{M}));
7837 ext.push_back(std::make_shared<DRW_Variant>(1070, std::int32_t{N}));
7838 ext.push_back(
7839 std::make_shared<DRW_Variant>(1070, std::int32_t{data.smoothM}));
7840 ext.push_back(
7841 std::make_shared<DRW_Variant>(1070, std::int32_t{data.smoothN}));
7842 ext.push_back(
7843 std::make_shared<DRW_Variant>(1070, std::int32_t{data.curvetype}));
7844 if (anchor) {
7845 for (const auto &vertex : data.vertlist) {
7846 if (!vertex) {
7847 continue;
7848 }
7849 ext.push_back(std::make_shared<DRW_Variant>(
7850 1010, DRW_Coord{vertex->basePoint.x, vertex->basePoint.y,
7851 vertex->basePoint.z}));
7852 }
7853 }
7854 return ext;
7855 };
7856 auto addMeshSidecarMetadata = [&](const RS_Entity *entity, int elementIndex,
7857 const std::string &role, int roleIndex,
7858 bool anchor) {
7859 if (m_graphic == nullptr || entity == nullptr)
7860 return;
7861 LC_DwgAdvancedMetadata::MeshSidecarRecord record;
7862 record.sourceHandle = data.handle;
7863 record.fallbackEntityId = entity->getId();
7864 record.meshId = meshId;
7865 record.role = role;
7866 record.elementIndex = elementIndex;
7867 record.elementCount = meshElementCount;
7868 record.roleIndex = roleIndex;
7869 record.flags = data.flags;
7870 record.mCount = M;
7871 record.nCount = N;
7872 record.smoothM = data.smoothM;
7873 record.smoothN = data.smoothN;
7874 record.curveType = data.curvetype;
7875 record.sourceVertexCount = anchor ? data.vertlist.size() : 0u;
7876 record.anchor = anchor;
7877 m_graphic->dwgAdvancedMetadata().addMeshSidecar(std::move(record));
7878 };
7879
7880 // Add row polylines (along N direction)
7881 for (int i = 0; i < M; i++) {
7882 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedN);
7883 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7884 setEntityAttributes(pl.get(), &data);
7885 for (int j = 0; j < N; j++) {
7886 auto v = data.vertlist.at(i * N + j);
7887 auto basePoint = v->basePoint;
7888 RS_Vector pos(basePoint.x, basePoint.y);
7889 pl->addVertex(pos, 0.0, false);
7890 }
7891 pl->setDrwExtData(makeMeshExtData(i, "row", i, i == 0));
7892 addMeshSidecarMetadata(pl.get(), i, "row", i, i == 0);
7893 m_currentContainer->addEntity(pl.release());
7894 }
7895
7896 // Add column polylines (along M direction)
7897 for (int j = 0; j < N; j++) {
7898 RS_PolylineData pd(RS_Vector(), RS_Vector(), closedM);
7899 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7900 setEntityAttributes(pl.get(), &data);
7901 for (int i = 0; i < M; i++) {
7902 auto v = data.vertlist.at(i * N + j);
7903 RS_Vector pos(v->basePoint.x, v->basePoint.y);
7904 pl->addVertex(pos, 0.0, false);
7905 }
7906 pl->setDrwExtData(makeMeshExtData(M + j, "column", j, false));
7907 addMeshSidecarMetadata(pl.get(), M + j, "column", j, false);
7908 m_currentContainer->addEntity(pl.release());
7909 }
7910 return;
7911 }
7912
7913 if (data.flags & 0x40) {
7914 // the polyline is a polyface mesh
7915 std::vector<RS_Vector> vertices;
7916 std::vector<DRW_Coord> sourceVertices;
7917 std::vector<std::shared_ptr<DRW_Vertex>> faceRecords;
7918 for (const std::shared_ptr<DRW_Vertex> &v : data.vertlist) {
7919 if (!v) {
7920 continue;
7921 }
7922 const bool coordinateVertex =
7923 v->dwgSubtype() == DRW_Vertex::DwgSubtype::Polyface ||
7924 ((v->flags & 0x40) != 0 && (v->flags & 0x80) != 0);
7925 const bool faceRecord =
7926 v->dwgSubtype() == DRW_Vertex::DwgSubtype::PolyfaceFace ||
7927 ((v->flags & 0x80) != 0 && (v->flags & 0x40) == 0);
7928 if (coordinateVertex) {
7929 vertices.emplace_back(v->basePoint.x, v->basePoint.y);
7930 sourceVertices.emplace_back(v->basePoint.x, v->basePoint.y,
7931 v->basePoint.z);
7932 } else if (faceRecord) {
7933 faceRecords.push_back(v);
7934 }
7935 }
7936 const std::string polyfaceId =
7937 std::string("polyline_pface_") + std::to_string(data.handle);
7938 auto makePolyfaceExtData = [&](const DRW_Vertex &face, int faceIndex,
7939 bool anchor) {
7940 std::vector<std::shared_ptr<DRW_Variant>> ext;
7941 ext.push_back(std::make_shared<DRW_Variant>(
7942 1001, std::string("LibreCAD_POLYLINE_PFACE")));
7943 ext.push_back(std::make_shared<DRW_Variant>(1000, polyfaceId));
7944 ext.push_back(
7945 std::make_shared<DRW_Variant>(1070, std::int32_t{faceIndex}));
7946 ext.push_back(std::make_shared<DRW_Variant>(
7947 1070, std::int32_t{static_cast<int>(faceRecords.size())}));
7948 ext.push_back(
7949 std::make_shared<DRW_Variant>(1070, std::int32_t{data.flags}));
7950 ext.push_back(
7951 std::make_shared<DRW_Variant>(1070, std::int32_t{data.vertexcount}));
7952 ext.push_back(
7953 std::make_shared<DRW_Variant>(1070, std::int32_t{data.facecount}));
7954 ext.push_back(
7955 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex1}));
7956 ext.push_back(
7957 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex2}));
7958 ext.push_back(
7959 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex3}));
7960 ext.push_back(
7961 std::make_shared<DRW_Variant>(1070, std::int32_t{face.vindex4}));
7962 if (anchor) {
7963 for (const auto &coord : sourceVertices) {
7964 ext.push_back(std::make_shared<DRW_Variant>(1010, coord));
7965 }
7966 }
7967 return ext;
7968 };
7969 // add faces as closed polylines
7970 for (size_t faceIndex = 0; faceIndex < faceRecords.size(); ++faceIndex) {
7971 const auto &f = faceRecords[faceIndex];
7972 std::vector<int> indices = {
7973 {f->vindex1, f->vindex2, f->vindex3, f->vindex4}};
7974 int num_points = (f->vindex4 == 0) ? 3 : 4;
7975 RS_PolylineData pd(RS_Vector(), RS_Vector(), true); // closed
7976 auto pl = std::make_unique<RS_Polyline>(m_currentContainer, pd);
7977 setEntityAttributes(pl.get(), &data);
7978 bool valid = true;
7979 for (int k = 0; k < num_points; ++k) {
7980 int idx = std::abs(indices[k]);
7981 if (idx < 1 || idx > static_cast<int>(vertices.size())) {
7982 valid = false;
7983 break;
7984 }
7985 pl->addVertex(vertices[idx - 1], 0.0);
7986 }
7987 if (valid) {
7988 pl->setDrwExtData(makePolyfaceExtData(*f, static_cast<int>(faceIndex),
7989 faceIndex == 0));
7990 m_currentContainer->addEntity(pl.release());
7991 }
7992 }
7993 return;
7994 }
7995
7996 RS_PolylineData pd(RS_Vector{}, RS_Vector{}, data.flags & 0x1);
7997 const bool is2dPolyline = (data.flags & (8 | 16 | 64)) == 0;
7998 if (is2dPolyline && !hasNormalizedAxialExtrusion(data)) {
7999 RS_DEBUGRS_Debug::instance()->print(
8000 RS_Debug::D_WARNING,
8001 "RS_FilterDXFRW::addPolyline: skipped non-axial 2D extrusion");
8002 return;
8003 }
8004 const bool reflectedOcs = is2dPolyline && data.extPoint.z < 0.0;
8005 auto polyline = std::make_unique<RS_Polyline>(m_currentContainer, pd);
8006 setEntityAttributes(polyline.get(), &data);
8007
8008 if (data.vertlist.empty()) {
8009 m_currentContainer->addEntity(polyline.release());
8010 return;
8011 }
8012
8013 auto vert0 = data.vertlist[0];
8014 RS_Vector first_pos(reflectedOcs ? -vert0->basePoint.x : vert0->basePoint.x,
8015 vert0->basePoint.y);
8016 const double firstBulge = reflectedOcs ? -vert0->bulge : vert0->bulge;
8017 polyline->addVertex(first_pos, firstBulge, false);
8018 RS_Vector prev_pos = first_pos;
8019
8020 bool closed = (data.flags & 0x1) != 0;
8021 size_t num_segments =
8022 closed ? data.vertlist.size() : data.vertlist.size() - 1;
8023
8024 for (size_t i = 0; i < num_segments; ++i) {
8025 size_t vert_idx = (i + 1) % data.vertlist.size();
8026 auto vert = data.vertlist[vert_idx];
8027 RS_Vector curr_pos(reflectedOcs ? -vert->basePoint.x : vert->basePoint.x,
8028 vert->basePoint.y);
8029
8030 size_t bulge_idx = i % data.vertlist.size();
8031 double segmentBulge = data.vertlist[bulge_idx]->bulge;
8032 if (reflectedOcs)
8033 segmentBulge = -segmentBulge;
8034 double nextBulge = vert->bulge;
8035 if (reflectedOcs)
8036 nextBulge = -nextBulge;
8037 const auto &extData = data.vertlist[bulge_idx]->extData;
8038
8039 addPolylineSegment(*polyline, prev_pos, curr_pos, segmentBulge, nextBulge,
8040 extData);
8041
8042 prev_pos = curr_pos;
8043 }
8044
8045 if (closed) {
8046 polyline->setFlag(RS2::FlagClosed);
8047 polyline->getData().endpoint = polyline->getData().startpoint;
8048 } else {
8049 polyline->endPolyline();
8050 }
8051
8052 bool hasVertexMetadata = data.defstawidth != 0.0 || data.defendwidth != 0.0;
8053 for (const auto &vertex : data.vertlist) {
8054 if (vertex && (vertex->stawidth != 0.0 || vertex->endwidth != 0.0 ||
8055 vertex->identifier != 0)) {
8056 hasVertexMetadata = true;
8057 break;
8058 }
8059 }
8060 if (is2dPolyline && (data.basePoint.z != 0.0 || data.thickness != 0.0 ||
8061 reflectedOcs || hasVertexMetadata)) {
8062 auto ext = polylineXDataWithoutSidecar(*polyline);
8063 ext.push_back(std::make_shared<DRW_Variant>(
8064 1001, std::string("LibreCAD_LWPOLYLINE")));
8065 ext.push_back(std::make_shared<DRW_Variant>(1040, 0.0));
8066 ext.push_back(std::make_shared<DRW_Variant>(
8067 1040, reflectedOcs ? -data.basePoint.z : data.basePoint.z));
8068 ext.push_back(std::make_shared<DRW_Variant>(
8069 1040, reflectedOcs ? -data.thickness : data.thickness));
8070 ext.push_back(
8071 std::make_shared<DRW_Variant>(1010, DRW_Coord{0.0, 0.0, 1.0}));
8072 ext.push_back(std::make_shared<DRW_Variant>(
8073 1070, std::int32_t{static_cast<int>(data.vertlist.size())}));
8074 for (const auto &vertex : data.vertlist) {
8075 const double startWidth = vertex && vertex->stawidth != 0.0
8076 ? vertex->stawidth
8077 : data.defstawidth;
8078 const double endWidth = vertex && vertex->endwidth != 0.0
8079 ? vertex->endwidth
8080 : data.defendwidth;
8081 ext.push_back(std::make_shared<DRW_Variant>(1040, startWidth));
8082 ext.push_back(std::make_shared<DRW_Variant>(1040, endWidth));
8083 ext.push_back(std::make_shared<DRW_Variant>(
8084 1071, std::int32_t{vertex ? vertex->identifier : 0}));
8085 }
8086 polyline->setDrwExtData(std::move(ext));
8087 }
8088
8089 m_currentContainer->addEntity(polyline.release());
8090}
8091
8092bool RS_FilterDXFRW::handleQuadraticConicSpline(const DRW_Spline *data) {
8093 if (data->degree != 2 || data->controllist.size() != 3) {
8094 return false;
8095 }
8096
8097 // Three explicit branches by middle weight: hyperbola (w>1), parabola
8098 // (weights absent or w=1), or fall-through for ellipse arc (w<1) and
8099 // anything else the dispatcher can't classify cleanly.
8100 std::unique_ptr<RS_Entity> en =
8101 LC_HyperbolaSpline::splineToHyperbola(*data, m_currentContainer);
8102 if (en == nullptr) {
8103 en = LC_ParabolaSpline::splineToParabola(*data, m_currentContainer);
8104 }
8105 if (en == nullptr) {
8106 // Not a hyperbola or parabola — likely an ellipse arc (w<1), a
8107 // collinear/degenerate spline, or a non-canonical knot vector. Let
8108 // the caller fall through to the generic addSpline path so the
8109 // entity isn't silently mis-classified as a parabola.
8110 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::handleQuadraticConicSpline: "
8111 "degree-2/3-control spline not classified as "
8112 "parabola or hyperbola; falling through to addSpline");
8113 return false;
8114 }
8115 setEntityAttributes(en.get(), data);
8116 en->update();
8117 m_currentContainer->addEntity(en.release());
8118
8119 return true;
8120}
8121
8122/**
8123 * Implementation of the method which handles splines.
8124 */
8125void RS_FilterDXFRW::addSpline(const DRW_Spline *data) {
8126 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpline: degree: %d", data->degree);
8127
8128 // Special case: rational quadratic conic (hyperbola or parabola)
8129 if (handleQuadraticConicSpline(data)) {
8130 return; // Conic handled successfully
8131 }
8132
8133 // Spline points case. Weighted degree-2 splines are exact rational conics
8134 // or NURBS segments; keep them on the RS_Spline path so weights survive.
8135 if (data->degree == 2 && !hasRationalSplineWeights(data)) {
8136 bool closed = (data->flags & 0x1) == 0x1;
8137 bool controlOnly = data->nfit == 0;
8138
8139 const LC_SplinePointsData d(closed, controlOnly);
8140 const auto splinePoints = new LC_SplinePoints(m_currentContainer, d);
8141 setEntityAttributes(splinePoints, data);
8142
8143 for (const auto &vert : data->controllist) {
8144 if (vert) {
8145 splinePoints->addControlPoint({vert->x, vert->y});
8146 }
8147 }
8148 for (const auto &vert : data->fitlist) {
8149 if (vert) {
8150 splinePoints->addPoint({vert->x, vert->y});
8151 }
8152 }
8153
8154 splinePoints->update();
8155 m_currentContainer->addEntity(splinePoints);
8156 return;
8157 }
8158
8159 // General spline (degree 1-3)
8160 if (data->degree < 1) {
8161 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
8162 "RS_FilterDXFRW::addSpline: unsupported spline degree %d",
8163 data->degree);
8164 return;
8165 }
8166 if (data->degree > 3) {
8167 std::unique_ptr<LC_SplinePoints> sampled =
8168 approximateDrwSpline(m_currentContainer, data);
8169 if (sampled) {
8170 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
8171 "RS_FilterDXFRW::addSpline: approximating unsupported "
8172 "spline degree %d",
8173 data->degree);
8174 setEntityAttributes(sampled.get(), data);
8175 sampled->update();
8176 m_currentContainer->addEntity(sampled.release());
8177 return;
8178 }
8179
8180 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
8181 "RS_FilterDXFRW::addSpline: unsupported spline degree %d",
8182 data->degree);
8183 return;
8184 }
8185
8186 // Control points and weights. Non-rational B-splines have no weight array
8187 // (weight=1.0 implied); only warn for rational splines (flag bit 2).
8188 size_t numCtrl = data->controllist.size();
8189 bool isRational = (data->flags & 0x4) != 0;
8190 if (isRational && numCtrl != data->weightlist.size()) {
8191 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
8192 "RS_FilterDXFRW::addSpline: rational spline control "
8193 "points (%zu) != weights (%zu)",
8194 numCtrl, data->weightlist.size());
8195 }
8196
8197 if (numCtrl != 0) {
8198 std::unique_ptr<RS_Spline> spline =
8199 makeSplineFromDrw(m_currentContainer, *data, data->weightlist);
8200 setEntityAttributes(spline.get(), data);
8201 m_currentContainer->addEntity(spline.release());
8202 return;
8203 }
8204
8205 // Fit points fallback
8206 const bool isClosed = (data->flags & 0x1) == 0x1;
8207 RS_SplineData d(data->degree, false);
8208 d.type = isClosed ? RS_SplineData::SplineType::Standard
8209 : RS_SplineData::SplineType::ClampedOpen;
8210
8211 const auto spline = new RS_Spline(m_currentContainer, d);
8212 setEntityAttributes(spline, data);
8213 m_currentContainer->addEntity(spline);
8214
8215 if (data->degree != 2) {
8216 std::vector<RS_Vector> fitPoints;
8217 std::transform(data->fitlist.begin(), data->fitlist.end(),
8218 std::back_inserter(fitPoints),
8219 [](const std::shared_ptr<DRW_Coord> &coord) {
8220 return RS_Vector{coord->x, coord->y};
8221 });
8222 spline->setFitPoints(fitPoints);
8223 }
8224
8225 if (isClosed) {
8226 spline->setClosed(true);
8227 }
8228
8229 spline->update();
8230}
8231
8232/**
8233 * Handles a HELIX entity. LibreCAD has no native helix; the entity is mapped
8234 * to its spline approximation via addSpline. The AcDbHelix axis/turns metadata
8235 * (radius, turns, turnHeight, axisVector, ...) is preserved across a DWG
8236 * round-trip but is not represented in the RS entity model, so it is dropped
8237 * on import.
8238 */
8239void RS_FilterDXFRW::addHelix(const DRW_Helix *data) {
8240 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addHelix: mapping HELIX to spline "
8241 "approximation (axis/turns metadata dropped)");
8242 addSpline(data);
8243}
8244
8245/**
8246 * Implementation of the method which handles inserts.
8247 */
8248void RS_FilterDXFRW::addInsert(const DRW_Insert &data) {
8249 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addInsert");
8250
8251 RS_Vector ip(data.basePoint.x, data.basePoint.y, data.basePoint.z);
8252 RS_Vector sc(data.xscale, data.yscale, data.zscale);
8253 RS_Vector sp(data.colspace, data.rowspace);
8254
8255 // cout << "Insert: " << name << " " << ip << " " << cols << "/" << rows <<
8256 // endl;
8257
8258 RS_InsertData d(QString::fromUtf8(data.name.c_str()), ip, sc, data.angle,
8259 data.colcount, data.rowcount, sp, nullptr, RS2::NoUpdate);
8260 d.extrusion = RS_Vector(data.extPoint.x, data.extPoint.y, data.extPoint.z);
8261 RS_Insert *entity = new RS_Insert(m_currentContainer, d);
8262 setEntityAttributes(entity, &data);
8263 RS_DEBUGRS_Debug::instance()->Log() << " id: " << entity->getId();
8264 // entity->update();
8265 m_currentContainer->addEntity(entity);
8266
8267 // Render block attributes (ATTRIB) attached to this INSERT. Invisible
8268 // records are retained as hidden text so their tag/owner/order metadata
8269 // can be reconstructed on the next save.
8270 // ATTRIB coordinates are in world space (per AutoCAD convention), so
8271 // each becomes an independent RS_Text in the current container.
8272 int ordinal = 0;
8273 for (const auto &att : data.attlist) {
8274 if (!att)
8275 continue;
8276 const bool attributeVisible =
8277 att->visible && (att->attribFlags & 0x1) == 0;
8278
8279 // Multi-line ATTRIB (R2018+, ODA spec §20.4.4 Attribute type 2/4):
8280 // libdxfrw saw the `101 / Embedded Object` DXF marker on the
8281 // ATTRIB and populated att->mtext from the embedded MTEXT body.
8282 // Render it as RS_MText so the multi-line content + formatting codes
8283 // survive; the plain `text` field is the single-line fallback.
8284 if (att->mtext) {
8285 DRW_Attrib attribute(*att);
8286 // Embedded DWG MTEXT has no EED frame; its private RTL marker is on ATTRIB.
8287 if (!mtextRtlLayout(*attribute.mtext))
8288 setMTextRtlMarker(*attribute.mtext, mtextRtlLayout(attribute));
8289 auto *mt = mtextEntityFromDRW(*attribute.mtext);
8290 mt->setParent(m_currentContainer);
8291 setEntityAttributes(mt, att.get());
8292 mt->setVisible(attributeVisible && att->mtext->visible);
8293 appendInsertAttribSidecar(mt, attribute, ordinal++);
8294 mt->setOwnerLineage(entity->lineage());
8295 mt->update();
8296 m_currentContainer->addEntity(mt);
8297 continue;
8298 }
8299
8300 RS_Vector refPoint(att->basePoint.x, att->basePoint.y);
8301 RS_Vector secPoint(att->secPoint.x, att->secPoint.y);
8302 if (att->alignV != 0 || att->alignH != 0 ||
8303 att->alignH == DRW_Text::HMiddle) {
8304 if (att->alignH != DRW_Text::HAligned && att->alignH != DRW_Text::HFit) {
8305 secPoint = RS_Vector(att->basePoint.x, att->basePoint.y);
8306 refPoint = RS_Vector(att->secPoint.x, att->secPoint.y);
8307 }
8308 }
8309
8310 RS_TextData::VAlign valign = (RS_TextData::VAlign)att->alignV;
8311 RS_TextData::HAlign halign = (RS_TextData::HAlign)att->alignH;
8312 RS_TextData::TextGeneration dir;
8313 if (att->textgen == 2)
8314 dir = RS_TextData::Backward;
8315 else if (att->textgen == 4)
8316 dir = RS_TextData::UpsideDown;
8317 else
8318 dir = RS_TextData::None;
8319
8320 QString sty = QString::fromUtf8(att->style.c_str());
8321 prepareTextStyleName(sty);
8322 QString text =
8323 toNativeSingleLineString(QString::fromUtf8(att->text.c_str()));
8324
8325 RS_TextData td(refPoint, secPoint, att->height, att->widthscale, valign,
8326 halign, dir, text, sty, att->angle * M_PI3.14159265358979323846 / 180,
8327 RS2::NoUpdate);
8328 auto textEntity = std::make_unique<RS_Text>(m_currentContainer, td);
8329 setEntityAttributes(textEntity.get(), att.get());
8330 textEntity->setVisible(attributeVisible);
8331 appendInsertAttribSidecar(textEntity.get(), *att, ordinal++);
8332 textEntity->setOwnerLineage(entity->lineage());
8333 textEntity->update();
8334 m_currentContainer->addEntity(textEntity.release());
8335 }
8336}
8337
8338void RS_FilterDXFRW::addTable(const DRW_Table &data) {
8339 TableFallbackRenderSummary fallbackSummary;
8340 const bool fallbackRendered = addTableFallback(data, &fallbackSummary);
8341 if (m_graphic != nullptr) {
8342 m_graphic->dwgAdvancedMetadata().addTable(data, fallbackRendered);
8343 LC_DwgAdvancedMetadata::TableFallbackRenderSummary metadataSummary;
8344 metadataSummary.tableHandle = data.handle;
8345 metadataSummary.gridEntityCount = fallbackSummary.gridEntityCount;
8346 metadataSummary.textEntityCount = fallbackSummary.textEntityCount;
8347 metadataSummary.placeholderEntityCount =
8348 fallbackSummary.placeholderEntityCount;
8349 metadataSummary.unresolvedTextStyleCount =
8350 fallbackSummary.unresolvedTextStyleCount;
8351 metadataSummary.clampedDimensionCount =
8352 fallbackSummary.clampedDimensionCount;
8353 m_graphic->dwgAdvancedMetadata().updateTableFallbackRenderSummary(
8354 metadataSummary);
8355 }
8356 addInsert(data);
8357}
8358
8359bool RS_FilterDXFRW::addTableFallback(const DRW_Table &data,
8360 TableFallbackRenderSummary *summary) {
8361 if (!data.m_hasSemanticContent || data.m_content.m_rows.empty() ||
8362 data.m_content.m_columns.empty() || m_currentContainer == nullptr) {
8363 return false;
8364 }
8365
8366 const DRW_TableContent &content = data.m_content;
8367 const RS_Vector origin(data.basePoint.x, data.basePoint.y, data.basePoint.z);
8368 RS_Vector xAxis(data.m_horizontalDirection.x, data.m_horizontalDirection.y,
8369 data.m_horizontalDirection.z);
8370 if (!xAxis.valid || xAxis.magnitude() <= RS_TOLERANCE1.0e-10) {
8371 xAxis = RS_Vector::polar(1.0, data.angle);
8372 } else {
8373 xAxis.set(xAxis.x / xAxis.magnitude(), xAxis.y / xAxis.magnitude(),
8374 xAxis.z / xAxis.magnitude());
8375 }
8376 const RS_Vector yAxis = RS_Vector(-xAxis.y, xAxis.x, 0.0);
8377
8378 std::vector<double> columnOffsets;
8379 columnOffsets.reserve(content.m_columns.size() + 1);
8380 columnOffsets.push_back(0.0);
8381 for (size_t column = 0; column < content.m_columns.size(); ++column) {
8382 columnOffsets.push_back(columnOffsets.back() +
8383 tableColumnWidth(content, column, summary));
8384 }
8385
8386 std::vector<double> rowOffsets;
8387 rowOffsets.reserve(content.m_rows.size() + 1);
8388 rowOffsets.push_back(0.0);
8389 for (size_t row = 0; row < content.m_rows.size(); ++row) {
8390 rowOffsets.push_back(rowOffsets.back() +
8391 tableRowHeight(content, row, summary));
8392 }
8393
8394 auto tablePoint = [&](double x, double y) {
8395 return origin + xAxis * x - yAxis * y;
8396 };
8397 auto addFallbackRecord = [&](RS_Entity *entity, int row, int column,
8398 LC_DwgAdvancedMetadata::TableFallbackRole role) {
8399 if (m_graphic == nullptr || entity == nullptr)
8400 return;
8401 LC_DwgAdvancedMetadata::TableFallbackEntityRecord record;
8402 record.tableHandle = data.handle;
8403 record.sourceHandle = data.handle;
8404 record.row = row;
8405 record.column = column;
8406 record.role = role;
8407 record.entityId = entity->getId();
8408 m_graphic->dwgAdvancedMetadata().addTableFallbackEntity(record);
8409 };
8410 auto addBorder = [&](const RS_Vector &a, const RS_Vector &b, int row,
8411 int column) {
8412 auto *line = new RS_Line{m_currentContainer, {a, b}};
8413 setEntityAttributes(line, &data);
8414 line->update();
8415 addFallbackRecord(line, row, column,
8416 LC_DwgAdvancedMetadata::TableFallbackRole::GridLine);
8417 if (summary != nullptr)
8418 ++summary->gridEntityCount;
8419 m_currentContainer->addEntity(line);
8420 };
8421
8422 for (size_t column = 0; column < columnOffsets.size(); ++column) {
8423 const double x = columnOffsets[column];
8424 addBorder(tablePoint(x, 0.0), tablePoint(x, rowOffsets.back()), -1,
8425 static_cast<int>(column));
8426 }
8427 for (size_t row = 0; row < rowOffsets.size(); ++row) {
8428 const double y = rowOffsets[row];
8429 addBorder(tablePoint(0.0, y), tablePoint(columnOffsets.back(), y),
8430 static_cast<int>(row), -1);
8431 }
8432
8433 bool renderedText = false;
8434 QString style = m_textStyle;
8435 prepareTextStyleName(style);
8436 const double angle = std::atan2(xAxis.y, xAxis.x);
8437 for (size_t row = 0; row < content.m_rows.size(); ++row) {
8438 const auto &tableRow = content.m_rows[row];
8439 const size_t cellCount =
8440 std::min(tableRow.m_cells.size(), content.m_columns.size());
8441 for (size_t column = 0; column < cellCount; ++column) {
8442 const DRW_TableCell &tableCell = tableRow.m_cells[column];
8443 TableFallbackCellDisplay display =
8444 tableCellDisplay(tableCell, data.m_semanticContentComplete);
8445 if (display.text.isEmpty())
8446 continue;
8447 const bool placeholder = tableFallbackCellIsPlaceholder(display.kind);
8448 QString text = toNativeString(display.text);
8449 const double left = columnOffsets[column];
8450 const double right = columnOffsets[column + 1];
8451 const double top = rowOffsets[row];
8452 const double bottom = rowOffsets[row + 1];
8453 const double cellHeight = std::max(0.1, bottom - top);
8454 const double cellWidth = std::max(0.1, right - left);
8455 const RS_Vector insertion =
8456 tablePoint(left + cellWidth * 0.08, top + cellHeight * 0.25);
8457 double textHeight = cellHeight * 0.35;
8458 if (tableCell.m_contentHeight > 0.0 &&
8459 std::isfinite(tableCell.m_contentHeight)) {
8460 textHeight = tableCell.m_contentHeight;
8461 } else if (tableCell.m_height > 0.0 &&
8462 std::isfinite(tableCell.m_height)) {
8463 textHeight = tableCell.m_height * 0.35;
8464 }
8465 textHeight = std::max(kTableFallbackMinTextHeight, textHeight);
8466 if (summary != nullptr && (tableCell.m_textStyleHandle != 0 ||
8467 tableCell.m_textStyleOverrideHandle != 0)) {
8468 ++summary->unresolvedTextStyleCount;
8469 }
8470 RS_MTextData textData(insertion, textHeight, cellWidth * 0.84,
8471 RS_MTextData::VATop, RS_MTextData::HALeft,
8472 RS_MTextData::LeftToRight, RS_MTextData::AtLeast,
8473 1.0, text, style, angle, RS2::NoUpdate);
8474 auto *mtext = new RS_MText(m_currentContainer, textData);
8475 setEntityAttributes(mtext, &data);
8476 mtext->update();
8477 addFallbackRecord(
8478 mtext, static_cast<int>(row), static_cast<int>(column),
8479 placeholder ? LC_DwgAdvancedMetadata::TableFallbackRole::Placeholder
8480 : LC_DwgAdvancedMetadata::TableFallbackRole::CellText);
8481 if (summary != nullptr) {
8482 ++summary->textEntityCount;
8483 if (placeholder)
8484 ++summary->placeholderEntityCount;
8485 }
8486 m_currentContainer->addEntity(mtext);
8487 renderedText = true;
8488 }
8489 }
8490
8491 return renderedText || columnOffsets.size() > 1 || rowOffsets.size() > 1;
8492}
8493
8494void RS_FilterDXFRW::prepareTextStyleName(QString &sty) const {
8495 // use default style for the drawing:
8496 if (sty.isEmpty()) {
8497 // japanese, cyrillic:
8498 if (m_codePage == "ANSI_932" || m_codePage == "ANSI_1251") {
8499 sty = "Unicode";
8500 } else {
8501 sty = m_textStyle;
8502 }
8503 } else {
8504 sty = m_fontList.value(sty, sty);
8505 }
8506}
8507
8508/**
8509 * Builds an RS_MText entity from a DRW_MText payload, applying the same
8510 * alignment/drawing-direction/line-spacing/legacy-correction logic that
8511 * addMText() uses. Shared between addMText() (standalone MTEXT entities) and
8512 * addInsert() (block attributes whose `101 / Embedded Object` DXF marker
8513 * produced an att->mtext payload). Returns a freshly-constructed entity not
8514 * yet attached to any container; the caller is responsible for
8515 * setEntityAttributes() and appending.
8516 */
8517RS_MText *RS_FilterDXFRW::mtextEntityFromDRW(const DRW_MText &data) {
8518 RS_MTextData::VAlign valign;
8519 RS_MTextData::HAlign halign;
8520 RS_MTextData::MTextDrawingDirection dir;
8521 RS_MTextData::MTextLineSpacingStyle lss;
8522
8523 if (data.textgen <= 3) {
8524 valign = RS_MTextData::VATop;
8525 } else if (data.textgen <= 6) {
8526 valign = RS_MTextData::VAMiddle;
8527 } else {
8528 valign = RS_MTextData::VABottom;
8529 }
8530
8531 if (data.textgen % 3 == 1) {
8532 halign = RS_MTextData::HALeft;
8533 } else if (data.textgen % 3 == 2) {
8534 halign = RS_MTextData::HACenter;
8535 } else {
8536 halign = RS_MTextData::HARight;
8537 }
8538
8539 if (data.alignH == 1) {
8540 dir = RS_MTextData::LeftToRight;
8541 } else if (data.alignH == 3) {
8542 dir = RS_MTextData::TopToBottom;
8543 } else {
8544 dir = RS_MTextData::ByStyle;
8545 }
8546
8547 lss = mtextLineSpacingStyle(data);
8548 QString mtext = toNativeString(QString::fromUtf8(data.text.c_str()));
8549 QString sty = QString::fromUtf8(data.style.c_str());
8550 prepareTextStyleName(sty);
8551
8552 double interlin = data.interlin;
8553 double angle = data.angle * M_PI3.14159265358979323846 / 180.;
8554 RS_Vector ip = RS_Vector(data.basePoint.x, data.basePoint.y);
8555
8556 if (m_oldMText) {
8557 interlin = data.interlin * 0.96;
8558 if (valign == RS_MTextData::VABottom) {
8559#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
8560 const QStringList tl = mtext.split('\n', Qt::SkipEmptyParts);
8561#else
8562 QStringList tl = mtext.split('\n', QString::SkipEmptyParts);
8563#endif
8564 if (!tl.isEmpty()) {
8565 QString txt = tl.at(tl.size() - 1);
8566 RS_TextData d(RS_Vector(0., 0., 0.), RS_Vector(0., 0., 0.), data.height,
8567 1, RS_TextData::VABaseline, RS_TextData::HALeft,
8568 RS_TextData::None, txt, sty, 0, RS2::Update);
8569 auto entity = new RS_Text(nullptr, d);
8570 double textTail = entity->getMin().y;
8571 delete entity;
8572 const auto ot = RS_Vector(0.0, textTail).rotate(angle);
8573 ip.move(ot);
8574 }
8575 }
8576 }
8577
8578 // drawingDirection was resolved above (1→LeftToRight, 3→TopToBottom,
8579 // else→ByStyle), matching the DXF MTEXT group 72 spec. There is no DXF
8580 // value for RightToLeft; that flag round-trips via XDATA below — when
8581 // an MTEXT carries a "LibreCad" + 1071 marker, restore the RTL setting.
8582 const auto rtlLayout = mtextRtlLayout(data);
8583 if (rtlLayout != 0)
8584 dir = RS_MTextData::RightToLeft;
8585
8586 RS_MTextData d(ip, data.height, data.widthscale, valign, halign, dir, lss,
8587 interlin, mtext, sty, angle, RS2::NoUpdate);
8588 d.legacyRtlLayout = rtlLayout == kLegacyRtlLayout;
8589 return new RS_MText(nullptr, d);
8590}
8591
8592/**
8593 * Implementation of the method which handles
8594 * multi texts (MTEXT).
8595 */
8596void RS_FilterDXFRW::addMText(const DRW_MText &data) {
8597 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addMText: %s", data.text.c_str());
8598 auto *entity = mtextEntityFromDRW(data);
8599 entity->setParent(m_currentContainer);
8600 setEntityAttributes(entity, &data);
8601 if (data.basePoint.z != 0.0 || data.secPoint.x != 0.0 ||
8602 data.secPoint.y != 0.0 || data.secPoint.z != 0.0 ||
8603 data.extPoint.x != 0.0 || data.extPoint.y != 0.0 ||
8604 data.extPoint.z != 1.0) {
8605 std::vector<std::shared_ptr<DRW_Variant>> payload;
8606 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
8607 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
8608 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
8609 appendTypeSidecar(entity, kMTextOcsMarker, std::move(payload));
8610 }
8611 entity->update();
8612 m_currentContainer->addEntity(entity);
8613}
8614
8615/**
8616 * Implementation of the method which handles
8617 * texts (TEXT).
8618 */
8619void RS_FilterDXFRW::addText(const DRW_Text &data) {
8620 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addText");
8621 auto refPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
8622 auto secPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
8623 const double angle = data.angle;
8624
8625 if (data.alignV != 0 || data.alignH != 0 ||
8626 data.alignH == DRW_Text::HMiddle) {
8627 // fixme - sand - review this condition
8628 if (data.alignH != DRW_Text::HAligned && data.alignH != DRW_Text::HFit) {
8629 secPoint = RS_Vector(data.basePoint.x, data.basePoint.y);
8630 refPoint = RS_Vector(data.secPoint.x, data.secPoint.y);
8631 }
8632 }
8633
8634 const auto valign = static_cast<RS_TextData::VAlign>(data.alignV);
8635 const auto halign = static_cast<RS_TextData::HAlign>(data.alignH);
8636 RS_TextData::TextGeneration dir;
8637 QString sty = QString::fromUtf8(data.style.c_str());
8638
8639 if (data.textgen == 2) {
8640 dir = RS_TextData::Backward;
8641 } else if (data.textgen == 4) {
8642 dir = RS_TextData::UpsideDown;
8643 } else {
8644 dir = RS_TextData::None;
8645 }
8646
8647 const QString text =
8648 toNativeSingleLineString(QString::fromUtf8(data.text.c_str()));
8649
8650 prepareTextStyleName(sty);
8651
8652 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
8653 RS_DEBUGRS_Debug::instance()->printUnicode(text);
8654
8655 const RS_TextData d(refPoint, secPoint, data.height, data.widthscale, valign,
8656 halign, dir, text, sty, angle * M_PI3.14159265358979323846 / 180,
8657 RS2::NoUpdate);
8658 auto *entity = new RS_Text(m_currentContainer, d);
8659
8660 setEntityAttributes(entity, &data);
8661 if (data.basePoint.z != 0.0 || data.secPoint.z != 0.0 ||
8662 data.thickness != 0.0 || data.extPoint.x != 0.0 ||
8663 data.extPoint.y != 0.0 || data.extPoint.z != 1.0) {
8664 std::vector<std::shared_ptr<DRW_Variant>> payload;
8665 payload.push_back(std::make_shared<DRW_Variant>(1010, data.basePoint));
8666 payload.push_back(std::make_shared<DRW_Variant>(1011, data.secPoint));
8667 payload.push_back(std::make_shared<DRW_Variant>(1012, data.extPoint));
8668 payload.push_back(std::make_shared<DRW_Variant>(1040, data.thickness));
8669 appendTypeSidecar(entity, kTextOcsMarker, std::move(payload));
8670 }
8671 entity->update();
8672 m_currentContainer->addEntity(entity);
8673}
8674
8675void RS_FilterDXFRW::addAttDef(const DRW_Attdef &data) {
8676 // LibreCAD has no standalone attribute-definition model. Preserve the
8677 // visible BLOCK definition as text instead of silently dropping it; the
8678 // original DXF/DWG attribute fields remain available to libdxfrw callers.
8679 addText(data);
8680}
8681
8682/**
8683 * Implementation of the method which handles
8684 * dimensions (DIMENSION).
8685 */
8686RS_DimensionData RS_FilterDXFRW::convDimensionData(const DRW_Dimension *data) {
8687 DRW_Coord crd = data->getDefPoint();
8688 const RS_Vector defP(crd.x, crd.y);
8689 crd = data->getTextPoint();
8690 RS_Vector midP(crd.x, crd.y);
8691 RS_MTextData::VAlign valign;
8692 RS_MTextData::HAlign halign;
8693 RS_MTextData::MTextLineSpacingStyle lss;
8694 QString sty = QString::fromUtf8(data->getStyle().c_str());
8695
8696 QString t; //= data.text;
8697
8698 // middlepoint of text can be 0/0 which is considered to be invalid (!):
8699 // 0/0 because older QCad versions save the middle of the text as 0/0
8700 // although they didn't support saving of the middle of the text.
8701 if (fabs(crd.x) < 1.0e-6 && fabs(crd.y) < 1.0e-6) {
8702 midP = RS_Vector(false);
8703 }
8704
8705 if (data->getAlign() <= 3) {
8706 valign = RS_MTextData::VATop;
8707 } else if (data->getAlign() <= 6) {
8708 valign = RS_MTextData::VAMiddle;
8709 } else {
8710 valign = RS_MTextData::VABottom;
8711 }
8712
8713 if (data->getAlign() % 3 == 1) {
8714 halign = RS_MTextData::HALeft;
8715 } else if (data->getAlign() % 3 == 2) {
8716 halign = RS_MTextData::HACenter;
8717 } else {
8718 halign = RS_MTextData::HARight;
8719 }
8720
8721 if (data->getTextLineStyle() == 1) {
8722 lss = RS_MTextData::AtLeast;
8723 } else {
8724 lss = RS_MTextData::Exact;
8725 }
8726
8727 t = toNativeString(QString::fromUtf8(data->getText().c_str()));
8728
8729 if (sty.isEmpty()) {
8730 sty = m_dimStyle;
8731 }
8732
8733 RS_DEBUGRS_Debug::instance()->print("Text as unicode:");
8734 RS_DEBUGRS_Debug::instance()->printUnicode(t);
8735
8736 const bool customTextLocation = data->type >= 128;
8737
8738 std::unique_ptr<LC_DimStyle> parsedDimStyleOverride;
8739
8740 if (!data->extData.empty()) {
8741 std::unique_ptr<LC_ExtEntityData> extData(
8742 extractEntityExtData(data->extData));
8743 parseDimStyleOverride(extData.get(), parsedDimStyleOverride);
8744 }
8745
8746 // data needed to add the actual dimension entity
8747 return RS_DimensionData(defP, midP, valign, halign, lss,
8748 data->getTextLineFactor(), t, sty, data->getDir(),
8749 data->getHDir(), !customTextLocation,
8750 parsedDimStyleOverride.release(),
8751 data->getFlipArrow1(), data->getFlipArrow2());
8752}
8753
8754void RS_FilterDXFRW::fillEntityExtData(
8755 std::vector<std::shared_ptr<DRW_Variant>> &extData,
8756 LC_ExtEntityData *entityData) {
8757 const auto appDatas = entityData->getAppData();
8758 const auto appendAtomic = [&extData](LC_ExtDataTag *tag) {
8759 if (tag == nullptr || !tag->isAtomic())
8760 return;
8761 const auto *variable = tag->var();
8762 if (variable == nullptr)
8763 return;
8764
8765 const int code = variable->getCode();
8766 extData.push_back(std::make_shared<DRW_Variant>(1070, code));
8767 if (tag->isBinary()) {
8768 const QByteArray &bytes = tag->bytes();
8769 std::vector<std::uint8_t> raw(bytes.size());
8770 for (int i = 0; i < bytes.size(); ++i)
8771 raw[i] = static_cast<std::uint8_t>(bytes[i]);
8772 extData.push_back(std::make_shared<DRW_Variant>(1004, std::move(raw)));
8773 return;
8774 }
8775
8776 switch (variable->getType()) {
8777 case RS2::VariableInt:
8778 extData.push_back(
8779 std::make_shared<DRW_Variant>(1070, variable->getInt()));
8780 break;
8781 case RS2::VariableDouble:
8782 extData.push_back(
8783 std::make_shared<DRW_Variant>(1040, variable->getDouble()));
8784 break;
8785 case RS2::VariableString:
8786 if (tag->isLayerRef()) {
8787 extData.push_back(std::make_shared<DRW_Variant>(
8788 1003, variable->getString().toStdString(), true));
8789 } else {
8790 extData.push_back(std::make_shared<DRW_Variant>(
8791 tag->isRef() ? 1005 : 1003, variable->getString().toStdString()));
8792 }
8793 break;
8794 case RS2::VariableVector: {
8795 const RS_Vector vec = variable->getVector();
8796 extData.push_back(std::make_shared<DRW_Variant>(1010, vec.x));
8797 extData.push_back(std::make_shared<DRW_Variant>(1011, vec.y));
8798 extData.push_back(std::make_shared<DRW_Variant>(1012, vec.z));
8799 break;
8800 }
8801 case RS2::VariableVoid:
8802 break;
8803 }
8804 };
8805 const auto appendTags =
8806 [&appendAtomic, &extData](
8807 auto &&self, const std::vector<LC_ExtDataTag *> &tags) -> void {
8808 for (auto *tag : tags) {
8809 if (tag == nullptr)
8810 continue;
8811 if (tag->isAtomic()) {
8812 appendAtomic(tag);
8813 } else {
8814 extData.push_back(std::make_shared<DRW_Variant>(1002, "{"));
8815 self(self, *tag->list());
8816 extData.push_back(std::make_shared<DRW_Variant>(1002, "}"));
8817 }
8818 }
8819 };
8820
8821 for (const auto appData : *appDatas) {
8822 extData.push_back(std::make_shared<DRW_Variant>(
8823 1001, appData->getName().toStdString())); // application name
8824 const auto groups = appData->getGroups();
8825 for (const auto group : *groups) {
8826 extData.push_back(std::make_shared<DRW_Variant>(
8827 1000, group->getName().toStdString())); // group name
8828 extData.push_back(std::make_shared<DRW_Variant>(1002, "{")); // start
8829 appendTags(appendTags, *group->getTagsList());
8830 extData.push_back(std::make_shared<DRW_Variant>(1002, "}")); // end
8831 }
8832 }
8833}
8834
8835// This method is quite generic and may be used for parsing any entity's EED.
8836// 1002 braces are represented as nested LC_ExtDataTag lists so the structure
8837// survives a LibreCAD-side clone and a later DXF/DWG write.
8838LC_ExtEntityData *RS_FilterDXFRW::extractEntityExtData(
8839 const std::vector<std::shared_ptr<DRW_Variant>> &extData) {
8840 auto *result = new LC_ExtEntityData();
8841 LC_ExtDataAppData *currentAppData = nullptr;
8842 LC_ExtDataGroup *currentGroup = nullptr;
8843
8844 int currentValType = -1;
8845 bool expectType = false;
8846 LC_ExtDataTag *currentList = nullptr;
8847 std::vector<LC_ExtDataTag *> listParents;
8848 int controlDepth = 0;
8849 bool malformedGroup = false;
8850 const auto discardCurrentGroup = [&]() {
8851 if (currentAppData == nullptr || currentGroup == nullptr)
8852 return;
8853 auto *groups = currentAppData->getGroups();
8854 const auto it = std::find(groups->begin(), groups->end(), currentGroup);
8855 if (it != groups->end()) {
8856 delete *it;
8857 groups->erase(it);
8858 }
8859 };
8860 const auto resetGroup = [&]() {
8861 if (malformedGroup || controlDepth != 0)
8862 discardCurrentGroup();
8863 currentGroup = nullptr;
8864 currentList = nullptr;
8865 listParents.clear();
8866 currentValType = -1;
8867 expectType = false;
8868 controlDepth = 0;
8869 malformedGroup = false;
8870 };
8871 const auto appendValue = [&currentList, &currentGroup, &expectType,
8872 &malformedGroup](LC_ExtDataTag *value) {
8873 if (value == nullptr)
8874 return;
8875 if (currentList != nullptr) {
8876 currentList->add(value);
8877 expectType = true;
8878 } else {
8879 if (currentGroup != nullptr)
8880 malformedGroup = true;
8881 delete value;
8882 }
8883 };
8884
8885 for (const auto &v : extData) {
8886 if (v == nullptr) {
8887 resetGroup();
8888 currentAppData = nullptr;
8889 continue;
8890 }
8891 const int code = v->code();
8892 switch (code) {
8893 case 1001: {
8894 std::string applicationName;
8895 if (readXDataString(v.get(), 1001, applicationName) !=
8896 XDataReadResult::Valid) {
8897 resetGroup();
8898 currentAppData = nullptr;
8899 break;
8900 }
8901 resetGroup();
8902 currentAppData =
8903 result->addAppData(QString::fromStdString(applicationName));
8904 break;
8905 }
8906 case 1000: {
8907 std::string groupName;
8908 if (readXDataString(v.get(), 1000, groupName) != XDataReadResult::Valid) {
8909 resetGroup();
8910 break;
8911 }
8912 if (currentAppData != nullptr) {
8913 resetGroup();
8914 currentGroup =
8915 currentAppData->addGroup(QString::fromStdString(groupName));
8916 currentList = currentGroup->getData();
8917 listParents.clear();
8918 currentValType = -1;
8919 expectType = false;
8920 controlDepth = 0;
8921 malformedGroup = false;
8922 }
8923 break;
8924 }
8925 case 1002: {
8926 std::string control;
8927 if (readXDataString(v.get(), 1002, control) != XDataReadResult::Valid ||
8928 (control != "{" && control != "}")) {
8929 if (currentGroup != nullptr)
8930 malformedGroup = true;
8931 break;
8932 }
8933 if (control == "{") {
8934 if (currentGroup == nullptr || currentList == nullptr) {
8935 if (currentGroup != nullptr)
8936 malformedGroup = true;
8937 break;
8938 }
8939 const bool isGroupContainer = controlDepth == 0 &&
8940 listParents.empty() &&
8941 currentList == currentGroup->getData();
8942 if (isGroupContainer) {
8943 controlDepth = 1;
8944 } else if (controlDepth > 0) {
8945 auto *nested = new LC_ExtDataTag();
8946 nested->makeList();
8947 currentList->add(nested);
8948 listParents.push_back(currentList);
8949 currentList = nested;
8950 ++controlDepth;
8951 } else {
8952 malformedGroup = true;
8953 break;
8954 }
8955 expectType = true;
8956 } else {
8957 if (currentGroup == nullptr || controlDepth <= 0) {
8958 if (currentGroup != nullptr)
8959 malformedGroup = true;
8960 break;
8961 }
8962 if (!listParents.empty()) {
8963 currentList = listParents.back();
8964 listParents.pop_back();
8965 --controlDepth;
8966 } else {
8967 currentList = nullptr;
8968 controlDepth = 0;
8969 }
8970 expectType = false;
8971 }
8972 break;
8973 }
8974 case 1003: {
8975 std::string stringValue;
8976 if (readXDataString(v.get(), 1003, stringValue) !=
8977 XDataReadResult::Valid) {
8978 if (currentGroup != nullptr)
8979 malformedGroup = true;
8980 break;
8981 }
8982 const QString value = QString::fromStdString(stringValue);
8983 if (v->isLayerRef()) {
8984 appendValue(new LC_ExtDataTag(currentValType, value, false, true));
8985 } else {
8986 appendValue(new LC_ExtDataTag(currentValType, value));
8987 }
8988 break;
8989 }
8990 case 1004: {
8991 const std::vector<std::uint8_t> *bytes = nullptr;
8992 if (v->type() == DRW_Variant::BINARY &&
8993 readXDataBinary(v.get(), 1004, bytes) == XDataReadResult::Valid) {
8994 const QByteArray value(reinterpret_cast<const char *>(bytes->data()),
8995 static_cast<int>(bytes->size()));
8996 appendValue(new LC_ExtDataTag(currentValType, value));
8997 } else if (v->type() == DRW_Variant::STRING) {
8998 QByteArray value;
8999 if (readXDataHexString(v.get(), 1004, value) == XDataReadResult::Valid)
9000 appendValue(new LC_ExtDataTag(currentValType, value));
9001 else if (currentGroup != nullptr)
9002 malformedGroup = true;
9003 } else if (currentGroup != nullptr) {
9004 malformedGroup = true;
9005 }
9006 break;
9007 }
9008 case 1005: {
9009 std::string stringValue;
9010 if (readXDataString(v.get(), 1005, stringValue) !=
9011 XDataReadResult::Valid) {
9012 if (currentGroup != nullptr)
9013 malformedGroup = true;
9014 break;
9015 }
9016 appendValue(new LC_ExtDataTag(currentValType,
9017 QString::fromStdString(stringValue), true));
9018 break;
9019 }
9020 case 1010:
9021 case 1011:
9022 case 1012:
9023 case 1013: {
9024 DRW_Coord coord;
9025 if (readXDataCoord(v.get(), code, coord) == XDataReadResult::Valid) {
9026 appendValue(new LC_ExtDataTag(currentValType,
9027 RS_Vector(coord.x, coord.y, coord.z)));
9028 } else if (currentGroup != nullptr)
9029 malformedGroup = true;
9030 break;
9031 }
9032 case 1070:
9033 case 1071: {
9034 std::int64_t integerValue = 0;
9035 if (readXDataInteger(v.get(), code, integerValue) !=
9036 XDataReadResult::Valid ||
9037 !fitsXDataInt32(integerValue)) {
9038 if (currentGroup != nullptr)
9039 malformedGroup = true;
9040 break;
9041 }
9042 const int value = static_cast<int>(integerValue);
9043 if (expectType) {
9044 currentValType = value;
9045 expectType = false;
9046 } else {
9047 appendValue(new LC_ExtDataTag(currentValType, value));
9048 }
9049 break;
9050 }
9051 case 1040:
9052 case 1041:
9053 case 1042: {
9054 double value = 0.0;
9055 if (readXDataDouble(v.get(), code, value) == XDataReadResult::Valid) {
9056 appendValue(new LC_ExtDataTag(currentValType, value));
9057 } else if (currentGroup != nullptr)
9058 malformedGroup = true;
9059 break;
9060 }
9061 default:
9062 break;
9063 }
9064 }
9065 resetGroup();
9066 return result;
9067}
9068
9069bool RS_FilterDXFRW::shouldGenerateExtEntityData(const RS_Dimension *entity) {
9070 // todo - so far, we support only dimension style override as extension data.
9071 // however, that's logic may be expanded later and store, for example,
9072 // something like entity-specific meta information or so.
9073 return entity->getDimStyleOverride() != nullptr;
9074}
9075
9076QString RS_FilterDXFRW::toHexStr(const int n) {
9077 return QString::number(n, 16).toUpper();
9078}
9079
9080void RS_FilterDXFRW::addDimStyleOverrideToExtendedData(
9081 LC_ExtEntityData *extEntityData, LC_DimStyle *styleOverride) {
9082 if (styleOverride == nullptr) {
9083 return;
9084 }
9085 auto appData = extEntityData->addAppData("ACAD");
9086 auto group = appData->addGroup("DSTYLE");
9087
9088 auto arrowhead = styleOverride->arrowhead();
9089 auto linearFormat = styleOverride->linearFormat();
9090 auto extensionLine = styleOverride->extensionLine();
9091 auto dimensionLine = styleOverride->dimensionLine();
9092 auto text = styleOverride->text();
9093 auto tolerance = styleOverride->latteralTolerance();
9094 auto zerosSuppression = styleOverride->zerosSuppression();
9095 auto scaling = styleOverride->scaling();
9096 auto roundoff = styleOverride->roundOff();
9097 auto radial = styleOverride->radial();
9098 auto angularFormat = styleOverride->angularFormat();
9099 auto fractions = styleOverride->fractions();
9100 auto leader = styleOverride->leader();
9101 auto arc = styleOverride->arc();
9102
9103 auto savedModificationMode = linearFormat->getModifyCheckMode();
9104 // here we're interested only in actually modified fields
9105 styleOverride->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
9106
9107 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
9108 // $DIMPOST
9109 group->add(3, linearFormat->prefixOrSuffix());
9110 }
9111 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
9112 // $DIMAPOST
9113 group->add(4, linearFormat->altPrefixOrSuffix());
9114 }
9115 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
9116 // $DIMBLK
9117 // fixme - restore after test!
9118 // group->add(5, arrowhead->sameBlockName());
9119 }
9120 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
9121 // $DIMBLK1
9122 // fixme - restore after test!
9123 // group->add(6, arrowhead->arrowHeadBlockNameFirst());
9124 }
9125 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
9126 // $DIMBLK2
9127 // fixme - restore after test!
9128 // group->add(7, arrowhead->arrowHeadBlockNameSecond());
9129 }
9130 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
9131 // $DIMSCALE
9132 group->add(40, scaling->scale());
9133 }
9134 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
9135 // $DIMASZ
9136 group->add(41, arrowhead->size());
9137 }
9138 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
9139 // $DIMEXO
9140 group->add(42, extensionLine->distanceFromOriginPoint());
9141 }
9142 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
9143 // $DIMDLI
9144 group->add(43, dimensionLine->baseLineDimLinesSpacing());
9145 }
9146 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
9147 // $DIMEXE
9148 group->add(44, extensionLine->distanceBeyondDimLine());
9149 }
9150 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
9151 // $DIMRND
9152 group->add(45, roundoff->roundTo());
9153 }
9154 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
9155 // $DIMDLE
9156 group->add(46, dimensionLine->distanceBeyondExtLinesForObliqueStroke());
9157 }
9158 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTP)) {
9159 // $DIMDTP
9160 group->add(47, tolerance->upperToleranceLimit());
9161 }
9162 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
9163 // $DIMDTM
9164 group->add(48, tolerance->lowerToleranceLimit());
9165 }
9166 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
9167 // $DIMFXL
9168 group->add(49, extensionLine->fixedLength());
9169 }
9170 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
9171 // $DIMTXT
9172 group->add(140, text->height());
9173 }
9174 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
9175 // $DIMCEN
9176 group->add(141, radial->centerCenterMarkOrLineSize());
9177 }
9178 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
9179 // $DIMTSZ
9180 group->add(142, arrowhead->tickSize());
9181 }
9182 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
9183 // $DIMALTF
9184 group->add(143, linearFormat->altUnitsMultiplier());
9185 }
9186 if (scaling->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
9187 // $DIMLFAC
9188 group->add(144, scaling->linearFactor());
9189 }
9190 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
9191 // $DIMTVP
9192 group->add(145, text->verticalDistanceToDimLine());
9193 }
9194 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
9195 // $DIMTFAC
9196 group->add(146, tolerance->heightScaleFactorToDimText());
9197 }
9198 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
9199 // $DIMGAP
9200 group->add(147, dimensionLine->lineGap());
9201 }
9202 if (roundoff->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
9203 // $DIMALTRND
9204 group->add(148, roundoff->altRoundTo());
9205 }
9206 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
9207 // $DIMTFILL
9208 group->add(69, text->backgroundFillMode());
9209 }
9210 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
9211 // $DIMTFILLCLR
9212 int colorRgb;
9213 int colorNumber =
9214 LC_ColorNumbers::colorToNumber(text->explicitBackgroundFillColor(), &colorRgb);
9215 group->add(70, colorNumber);
9216 }
9217 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
9218 // $DIMTOL
9219 group->add(71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
9220 }
9221 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
9222 // $DIMLIM
9223 group->add(72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
9224 }
9225 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
9226 // $DIMTIH
9227 group->add(73, text->orientationInside());
9228 }
9229 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
9230 // $DIMTOH
9231 group->add(74, text->orientationOutside());
9232 }
9233 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
9234 // $DIMSE1
9235 group->add(75, extensionLine->suppressFirstLine());
9236 }
9237 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
9238 // $DIMSE2
9239 group->add(76, extensionLine->suppressSecondLine());
9240 }
9241 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
9242 // $DIMTAD
9243 group->add(77, text->verticalPositioning());
9244 }
9245 if (zerosSuppression->checkModifyState(
9246 LC_DimStyle::ZerosSuppression::$DIMZIN)) {
9247 // $DIMZIN
9248 group->add(78, zerosSuppression->linearRaw());
9249 }
9250 if (zerosSuppression->checkModifyState(
9251 LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
9252 // $DIMAZIN
9253 group->add(79, zerosSuppression->angularRaw());
9254 }
9255 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
9256 // $DIMARCSYM
9257 group->add(90, arc->arcSymbolPosition());
9258 }
9259 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
9260 // $DIMALT
9261 group->add(170, linearFormat->alternateUnits());
9262 }
9263 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
9264 // $DIMALTD
9265 group->add(171, linearFormat->altDecimalPlaces());
9266 }
9267 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
9268 // $DIMTOFL
9269 group->add(172, dimensionLine->drawPolicyForOutsideText());
9270 }
9271 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
9272 // $DIMSAH
9273 group->add(173,
9274 arrowhead->isUseSeparateArrowHeads()); // fixme - check value
9275 }
9276 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
9277 // $DIMTIX
9278 group->add(174, text->extLinesRelativePlacement());
9279 }
9280 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
9281 // $DIMSOXD
9282 group->add(175, arrowhead->suppression()); // fixme - check value
9283 }
9284 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
9285 // $DIMCLRD
9286 int colorRgb;
9287 int colorNumber = LC_ColorNumbers::colorToNumber(dimensionLine->color(), &colorRgb);
9288 group->add(176, colorNumber);
9289 }
9290 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
9291 // $DIMCLRE
9292 int colorRgb;
9293 int color = LC_ColorNumbers::colorToNumber(extensionLine->color(), &colorRgb);
9294 group->add(177, color);
9295 }
9296 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
9297 // $DIMCLRT
9298 int colorRgb;
9299 int colorNumber = LC_ColorNumbers::colorToNumber(text->color(), &colorRgb);
9300 group->add(178, colorNumber);
9301 }
9302 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
9303 // $DIMADEC
9304 group->add(179, angularFormat->decimalPlaces());
9305 }
9306 // case 270: // fixme - sand - obsolete DIMUNIT
9307 // result->linearFormat()->setUDecimalPlaces(var->getInt());
9308 // dimunit = reader->getInt32();
9309 // add("$DIMUNIT", code, dimunit);
9310 // break;
9311 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
9312 // $DIMDEC
9313 group->add(271, linearFormat->decimalPlaces());
9314 }
9315 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
9316 // $DIMTDEC
9317 group->add(272, tolerance->decimalPlaces());
9318 }
9319 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
9320 // $DIMALTU
9321 group->add(273, linearFormat->altFormat());
9322 }
9323 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
9324 // $DIMALTTD
9325 group->add(274, tolerance->decimalPlacesAltDim());
9326 }
9327 if (angularFormat->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
9328 // $DIMAUNIT
9329 group->add(275, angularFormat->format());
9330 }
9331 if (fractions->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
9332 // $DIMFRAC
9333 group->add(276, fractions->style());
9334 }
9335 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
9336 // $DIMLUNIT
9337 group->add(277, linearFormat->formatRaw());
9338 }
9339 if (linearFormat->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
9340 // $DIMDSEP
9341 group->add(278, linearFormat->decimalFormatSeparatorChar());
9342 }
9343 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
9344 // $DIMTMOVE
9345 group->add(279, text->positionMovementPolicy());
9346 }
9347 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
9348 // $DIMJUST
9349 group->add(280, text->horizontalPositioning());
9350 }
9351 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
9352 // $DIMSD1
9353 group->add(281, dimensionLine->suppressFirstLine());
9354 }
9355 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
9356 // $DIMSD2
9357 group->add(282, dimensionLine->suppressSecondLine());
9358 }
9359 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
9360 // $DIMTOLJ
9361 group->add(283, tolerance->verticalJustification());
9362 }
9363 if (zerosSuppression->checkModifyState(
9364 LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
9365 // $DIMTZIN
9366 group->add(284, zerosSuppression->toleranceRaw());
9367 }
9368 if (zerosSuppression->checkModifyState(
9369 LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
9370 // $DIMALTZ
9371 group->add(285, zerosSuppression->altLinearRaw());
9372 }
9373 if (zerosSuppression->checkModifyState(
9374 LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
9375 // $DIMALTTZ
9376 group->add(286, zerosSuppression->altToleranceRaw());
9377 }
9378 // case 287: // fixme - DIMFIT
9379 // dimfit = reader->getInt32();
9380 // add("$DIMFIT", code, dimfit);
9381 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
9382 // $DIMUPT
9383 group->add(288, text->cursorControlPolicy());
9384 }
9385 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
9386 // $DIMATFIT
9387 group->add(289, text->unsufficientSpacePolicy());
9388 }
9389 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
9390 // $DIMFXLON
9391 group->add(290, extensionLine->hasFixedLength() ? 1 : 0); // fixme - check
9392 }
9393 if (dimStyleTargetVersion() >= DRW::AC1024 &&
9394 text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
9395 // $DIMTXTDIRECTION
9396 group->add(295, text->readingDirection());
9397 }
9398 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
9399 // $DIMTXSTY
9400 group->add(340, text->style()); // fixme - ref to style?
9401 }
9402 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
9403 // DIMLDRBLK
9404 auto blockName = leader->arrowBlockName();
9405 if (!blockName.isEmpty()) {
9406 auto blkName = blockName.toStdString();
9407 std::uint32_t blkHandle =
9408 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
9409 : DRW::NoHandle;
9410 if (blkHandle != DRW::NoHandle) {
9411 group->addRef(341, toHexStr(blkHandle));
9412 }
9413 }
9414 }
9415 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
9416 //$DIMBLK
9417 auto blockName = arrowhead->sameBlockName();
9418 if (!blockName.isEmpty()) {
9419 auto blkName = blockName.toStdString();
9420 std::uint32_t blkHandle =
9421 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
9422 : DRW::NoHandle;
9423 if (blkHandle != DRW::NoHandle) {
9424 group->addRef(342, toHexStr(blkHandle));
9425 }
9426 }
9427 }
9428 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
9429 //$DIMBLK1
9430 auto blockName = arrowhead->arrowHeadBlockNameFirst();
9431 if (!blockName.isEmpty()) {
9432 auto blkName = blockName.toStdString();
9433 std::uint32_t blkHandle =
9434 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
9435 : DRW::NoHandle;
9436 if (blkHandle != DRW::NoHandle) {
9437 group->addRef(343, toHexStr(blkHandle));
9438 }
9439 }
9440 }
9441
9442 if (arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
9443 //$DIMBLK2
9444 auto blockName = arrowhead->arrowHeadBlockNameSecond();
9445 if (!blockName.isEmpty()) {
9446 auto blkName = blockName.toStdString();
9447 std::uint32_t blkHandle =
9448 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
9449 : DRW::NoHandle;
9450 if (blkHandle != DRW::NoHandle) {
9451 group->addRef(344, toHexStr(blkHandle));
9452 }
9453 }
9454 }
9455 /*
9456 // case 345: // codes///
9457 // fixme - may this code be used for DIMLDRBLK?
9458 // dimblk2 = reader->getUtf8String();
9459 // add("$DIMBLK2", code, dimblk2);
9460 // break;
9461 */
9462
9463 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
9464 // $DIMLTYPE
9465 std::uint32_t lineTypeHandle =
9466 findLineTypeHandleToWrite(dimensionLine->lineTypeName());
9467 if (lineTypeHandle != DRW::NoHandle) {
9468 auto handleStr = toHexStr(lineTypeHandle);
9469 group->addRef(345, handleStr);
9470 }
9471 }
9472 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
9473 // $DIMLTEX1
9474 std::uint32_t lineTypeHandle =
9475 findLineTypeHandleToWrite(extensionLine->lineTypeFirstRaw());
9476 if (lineTypeHandle != DRW::NoHandle) {
9477 auto handleStr = toHexStr(lineTypeHandle);
9478 group->addRef(347, handleStr);
9479 }
9480 }
9481 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
9482 // $DIMLTEX2
9483 std::uint32_t lineTypeHandle =
9484 findLineTypeHandleToWrite(extensionLine->lineTypeSecondRaw());
9485 if (lineTypeHandle != DRW::NoHandle) {
9486 auto handleStr = toHexStr(lineTypeHandle);
9487 group->addRef(348, handleStr);
9488 }
9489 }
9490 if (dimensionLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
9491 // $DIMLWD
9492 auto lineWidth = dimensionLine->lineWidth();
9493 int lw = RS2::lineWidth2dxfInt(lineWidth);
9494 group->add(371, lw);
9495 }
9496 if (extensionLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
9497 // $DIMLWE
9498 auto lineWidth = extensionLine->lineWidth();
9499 int lw = RS2::lineWidth2dxfInt(lineWidth);
9500 group->add(372, lw);
9501 }
9502
9503 styleOverride->setModifyCheckMode(savedModificationMode);
9504}
9505
9506RS_FilterDXFRW::DimStyleOverrideParseResult
9507RS_FilterDXFRW::parseDimStyleOverride(
9508 const LC_ExtEntityData *extEntityData,
9509 std::unique_ptr<LC_DimStyle> &parsedOverride) const {
9510 parsedOverride.reset();
9511 if (extEntityData == nullptr)
9512 return DimStyleOverrideParseResult::Absent;
9513
9514 auto *dimStyleGroup = extEntityData->getGroupByName("ACAD", "DSTYLE");
9515 if (dimStyleGroup == nullptr)
9516 return DimStyleOverrideParseResult::Absent;
9517
9518 auto *dimStyleVariables = dimStyleGroup->getTagsList();
9519 if (dimStyleVariables == nullptr || dimStyleVariables->empty())
9520 return DimStyleOverrideParseResult::Absent;
9521
9522 auto result = std::make_unique<LC_DimStyle>();
9523 auto arrowhead = result->arrowhead();
9524 auto linearFormat = result->linearFormat();
9525 auto extensionLine = result->extensionLine();
9526 auto dimensionLine = result->dimensionLine();
9527 auto text = result->text();
9528 auto tolerance = result->latteralTolerance();
9529 auto zerosSuppression = result->zerosSuppression();
9530 auto arc = result->arc();
9531
9532 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
9533 // DSTYLE names a linetype by the hex handle of its LTYPE record.
9534 const auto lineTypeNameForHex = [this](const QString &hex) {
9535 bool ok = false;
9536 const std::uint32_t handle = hex.toUInt(&ok, 16);
9537 return ok ? lineTypeNameForHandle(m_graphic, handle) : QString{};
9538 };
9539 bool directionSeen = false;
9540 int directionValue = 0;
9541 const auto count = dimStyleVariables->size();
9542 for (size_t i = 0; i < count; i++) {
9543 LC_ExtDataTag *tag = dimStyleVariables->at(i);
9544 if (tag == nullptr || !tag->isAtomic() || tag->var() == nullptr)
9545 return DimStyleOverrideParseResult::Malformed;
9546 RS_Variable *var = tag->var();
9547 int code = var->getCode();
9548
9549 switch (code) {
9550 case 105:
9551 // handle = reader->getHandleString();
9552 break;
9553 case 3: // "$DIMPOST"
9554 linearFormat->setPrefixOrSuffix(var->getString());
9555 break;
9556 case 4: // "$DIMAPOST"
9557 linearFormat->setAltPrefixOrSuffix(var->getString());
9558 break;
9559 case 5: // $DIMBLK
9560 arrowhead->setSameBlockName(var->getString());
9561 break;
9562 case 6: // "$DIMBLK1"
9563 arrowhead->setArrowHeadBlockNameFirst(var->getString());
9564 break;
9565 case 7: // "$DIMBLK2"
9566 arrowhead->setArrowHeadBlockNameSecond(var->getString());
9567 break;
9568 case 40: // "$DIMSCALE"
9569 result->scaling()->setScale(var->getDouble());
9570 break;
9571 case 41: // "$DIMASZ"
9572 arrowhead->setSize(var->getDouble());
9573 break;
9574 case 42: // "$DIMEXO"
9575 extensionLine->setDistanceFromOriginPoint(var->getDouble());
9576 break;
9577 case 43: //"$DIMDLI"
9578 dimensionLine->setBaselineDimLinesSpacing(var->getDouble());
9579 break;
9580 case 44: //"$DIMEXE"
9581 extensionLine->setDistanceBeyondDimLine(var->getDouble());
9582 break;
9583 case 45: //$DIMRND
9584 result->roundOff()->setRoundToValue(var->getDouble());
9585 break;
9586 case 46: //$DIMDLE
9587 dimensionLine->setDistanceBeyondExtLinesForObliqueStroke(
9588 var->getDouble());
9589 break;
9590 case 47: //"$DIMTP"
9591 tolerance->setUpperToleranceLimit(var->getDouble());
9592 break;
9593 case 48: //"$DIMTM"
9594 tolerance->setLowerToleranceLimit(var->getDouble());
9595 break;
9596 case 49: //"$DIMFXL"
9597 extensionLine->setFixedLength(var->getDouble());
9598 break;
9599 case 140: // "$DIMTXT"
9600 text->setHeight(var->getDouble());
9601 break;
9602 case 141: // "$DIMCEN"
9603 result->radial()->setCenterMarkOrLineSize(var->getDouble());
9604 break;
9605 case 142: //"$DIMTSZ"
9606 arrowhead->setTickSize(var->getDouble());
9607 break;
9608 case 143: //"$DIMALTF"
9609 linearFormat->setAltUnitsMultiplier(var->getDouble());
9610 break;
9611 case 144: //"$DIMLFAC"
9612 result->scaling()->setLinearFactor(var->getDouble());
9613 break;
9614 case 145: //"$DIMTVP"
9615 text->setVerticalDistanceToDimLine(var->getDouble());
9616 break;
9617 case 146: //"$DIMTFAC"
9618 tolerance->setHeightScaleFactorToDimText(var->getDouble());
9619 break;
9620 case 147: //"$DIMGAP"
9621 dimensionLine->setLineGap(var->getDouble());
9622 break;
9623 case 148: //"$DIMALTRND"
9624 result->roundOff()->setAltRoundToValue(var->getDouble());
9625 break;
9626 case 69: // "$DIMTFILL"
9627 text->setBackgroundFillModeRaw(var->getInt());
9628 break;
9629 case 70: {
9630 //"$DIMTFILLCLR"
9631 RS_Color fillClr = LC_ColorNumbers::numberToColor(var->getInt());
9632 text->setExplicitBackgroundFillColor(fillClr);
9633 break;
9634 }
9635 case 71: // "$DIMTOL"
9636 tolerance->setAppendTolerancesToDimText(var->getInt() ==
9637 1); // fixme - review
9638 break;
9639 case 72: //"$DIMLIM"
9640 tolerance->setLimitsAreGeneratedAsDefaultText(var->getInt() ==
9641 1); // fixme - review
9642 break;
9643 case 73: //"$DIMTIH"
9644 text->setOrientationInsideRaw(var->getInt());
9645 break;
9646 case 74: //"$DIMTOH"
9647 text->setOrientationOutsideRaw(var->getInt());
9648 break;
9649 case 75: //"$DIMSE1"
9650 extensionLine->setSuppressFirstRaw(var->getInt());
9651 break;
9652 case 76: //"$DIMSE2"
9653 extensionLine->setSuppressSecondRaw(var->getInt());
9654 break;
9655 case 77: //"$DIMTAD"
9656 text->setVerticalPositioningRaw(var->getInt());
9657 break;
9658 case 78: // "$DIMZIN"
9659 zerosSuppression->setLinearRaw(var->getInt());
9660 break;
9661 case 79: //"$DIMAZIN"
9662 zerosSuppression->setAngularRaw(var->getInt());
9663 break;
9664 case 170: //"$DIMALT"
9665 linearFormat->setAlternateUnitsRaw(var->getInt());
9666 break;
9667 case 171: //"$DIMALTD"
9668 linearFormat->setAltDecimalPlaces(var->getInt());
9669 break;
9670 case 172: //"$DIMTOFL"
9671 dimensionLine->setDrawPolicyForOutsideTextRaw(var->getInt());
9672 break;
9673 case 173: //"$DIMSAH"
9674 arrowhead->setUseSeparateArrowHeads(var->getInt()); // fixme - check value
9675 break;
9676 case 174: // "$DIMTIX"
9677 text->setExtLinesRelativePlacementRaw(var->getInt());
9678 break;
9679 case 175: //"$DIMSOXD"
9680 arrowhead->setSuppressionsRaw(var->getInt()); // fixme - check value
9681 break;
9682 case 176: {
9683 //"$DIMCLRD"
9684 RS_Color color = LC_ColorNumbers::numberToColor(var->getInt());
9685 dimensionLine->setColor(color);
9686 break;
9687 }
9688 case 177: {
9689 //"$DIMCLRE"
9690 RS_Color color = LC_ColorNumbers::numberToColor(var->getInt());
9691 extensionLine->setColor(color);
9692 break;
9693 }
9694 case 178: {
9695 //"$DIMCLRT"
9696 RS_Color color = LC_ColorNumbers::numberToColor(var->getInt());
9697 text->setColor(color);
9698 break;
9699 }
9700 case 179: //"$DIMADEC"
9701 result->angularFormat()->setDecimalPlaces(var->getInt());
9702 break;
9703 case 270: // fixme - sand - obsolete DIMUNIT
9704 // result->linearFormat()->setUDecimalPlaces(var->getInt());
9705 // dimunit = reader->getInt32();
9706 // add("$DIMUNIT", code, dimunit);
9707 break;
9708 case 271: //"$DIMDEC"
9709 linearFormat->setDecimalPlaces(var->getInt());
9710 break;
9711 case 272: //"$DIMTDEC"
9712 tolerance->setDecimalPlaces(var->getInt());
9713 break;
9714 case 273: //"$DIMALTU"
9715 linearFormat->setAltFormatRaw(var->getInt());
9716 break;
9717 case 274: // "$DIMALTTD"
9718 tolerance->setDecimalPlacesAltDim(var->getInt());
9719 break;
9720 case 275: //"$DIMAUNIT"
9721 result->angularFormat()->setFormatRaw(var->getInt());
9722 break;
9723 case 276: // "$DIMFRAC"
9724 result->fractions()->setStyleRaw(var->getInt());
9725 break;
9726 case 277: // "$DIMLUNIT"
9727 linearFormat->setFormatRaw(var->getInt());
9728 break;
9729 case 278: //"$DIMDSEP"
9730 linearFormat->setDecimalFormatSeparatorChar(var->getInt());
9731 break;
9732 case 279: // "$DIMTMOVE"
9733 text->setPositionMovementPolicyRaw(var->getInt());
9734 break;
9735 case 280: // "$DIMJUST"
9736 text->setHorizontalPositioningRaw(var->getInt());
9737 break;
9738 case 281: // "$DIMSD1"
9739 dimensionLine->setSuppressFirstLineRaw(var->getInt());
9740 break;
9741 case 282: // "$DIMSD2"
9742 dimensionLine->setSuppressSecondLineRaw(var->getInt());
9743 break;
9744 case 283: // "$DIMTOLJ"
9745 tolerance->setVerticalJustificationRaw(var->getInt());
9746 break;
9747 case 284: // "$DIMTZIN"
9748 zerosSuppression->setToleranceRaw(var->getInt());
9749 break;
9750 case 285: // "$DIMALTZ"
9751 zerosSuppression->setAltLinearRaw(var->getInt());
9752 break;
9753 case 286: //"$DIMALTTZ"
9754 zerosSuppression->setAltToleranceRaw(var->getInt());
9755 break;
9756 case 287: // fixme - DIMFIT
9757 // dimfit = reader->getInt32();
9758 // add("$DIMFIT", code, dimfit);
9759 break;
9760 case 288: // "$DIMUPT"
9761 text->setCursorControlPolicyRaw(var->getInt());
9762 break;
9763 case 289: //"$DIMATFIT"
9764 text->setUnsufficientSpacePolicyRaw(var->getInt());
9765 break;
9766 case 290: // "$DIMFXLON"
9767 extensionLine->setHasFixedLength(var->getInt()); // fixme - check
9768 break;
9769 case 292: // Legacy LibreCAD compatibility spelling.
9770 case 294: // LibreDWG compatibility spelling.
9771 case 295: { // "$DIMTXTDIRECTION"
9772 if (var->getType() != RS2::VariableInt)
9773 return DimStyleOverrideParseResult::Malformed;
9774 const int value = var->getInt();
9775 if (value < 0 || value > 1 || (directionSeen && directionValue != value))
9776 return DimStyleOverrideParseResult::Malformed;
9777 directionSeen = true;
9778 directionValue = value;
9779 text->setReadingDirection(
9780 static_cast<LC_DimStyle::Text::TextDirection>(value));
9781 break;
9782 }
9783 case 340: // "$DIMTXSTY"
9784 {
9785 auto dimtxsty = var->getString();
9786 prepareTextStyleName(dimtxsty);
9787 text->setStyle(dimtxsty); // fixme - ref to style?
9788 // fixme - ref to style?
9789 break;
9790 }
9791 case 341: {
9792 // "_$DIMLDRBLK"
9793 QString refHandleStr = var->getString();
9794 bool ok;
9795 int refHandle = refHandleStr.toInt(&ok, 16);
9796 if (ok) {
9797 QString blockName;
9798 ok = resolveBlockNameByHandle(refHandle, blockName);
9799 if (ok) {
9800 result->leader()->setArrowBlockName(blockName);
9801 }
9802 } else {
9803 // the string is not handle, but a direct name of the block.
9804 // fixme - DIMLDRBLK reading!
9805 // This is workaround for referring leader by name, not by block ref...
9806 // however, it may be not ACad compatible..
9807 // if (LC_DimArrowRegistry::isStandardBlockName(refHandleStr)) {
9808 // result->leader()->setArrowBlockName(refHandleStr);
9809 // }
9810 }
9811 break;
9812 }
9813 case 342: {
9814 // "_$DIMBLK"
9815 QString refHandleStr = var->getString();
9816 bool ok;
9817 int refHandle = refHandleStr.toInt(&ok, 16);
9818 if (ok) {
9819 QString blockName;
9820 ok = resolveBlockNameByHandle(refHandle, blockName);
9821 if (ok) {
9822 arrowhead->setSameBlockName(blockName);
9823 }
9824 }
9825 break;
9826 }
9827 case 343: {
9828 // "_$DIMBLK1"
9829 QString refHandleStr = var->getString();
9830 bool ok;
9831 int refHandle = refHandleStr.toInt(&ok, 16);
9832 if (ok) {
9833 QString blockName;
9834 ok = resolveBlockNameByHandle(refHandle, blockName);
9835 if (ok) {
9836 arrowhead->setArrowHeadBlockNameFirst(blockName);
9837 }
9838 }
9839 break;
9840 }
9841 case 344: {
9842 // "_$DIMBLK2"
9843 QString refHandleStr = var->getString();
9844 bool ok;
9845 int refHandle = refHandleStr.toInt(&ok, 16);
9846 if (ok) {
9847 QString blockName;
9848 ok = resolveBlockNameByHandle(refHandle, blockName);
9849 if (ok) {
9850 arrowhead->setArrowHeadBlockNameSecond(blockName);
9851 }
9852 }
9853 break;
9854 }
9855 // case 345: // codes///
9856 // fixme - may this code be used for DIMLDRBLK?
9857 // dimblk2 = reader->getUtf8String();
9858 // add("$DIMBLK2", code, dimblk2);
9859 // break;
9860 case 345: {
9861 // "$DIMLTYPE"
9862 const QString name = lineTypeNameForHex(var->getString());
9863 if (!name.isEmpty()) {
9864 dimensionLine->setLineType(name);
9865 }
9866 break;
9867 }
9868 case 347: {
9869 // "$DIMLTEX1"
9870 const QString name = lineTypeNameForHex(var->getString());
9871 if (!name.isEmpty()) {
9872 extensionLine->setLineTypeFirst(name);
9873 }
9874 break;
9875 }
9876 case 348: {
9877 // "$DIMLTEX2"
9878 const QString name = lineTypeNameForHex(var->getString());
9879 if (!name.isEmpty()) {
9880 extensionLine->setLineTypeSecond(name);
9881 }
9882 break;
9883 }
9884 case 371: // "$DIMLWD"
9885 dimensionLine->setLineWidthRaw(var->getInt());
9886 break;
9887 case 372: //"$DIMLWE"
9888 extensionLine->setLineWidthRaw(var->getInt());
9889 break;
9890 case 90: //"$DIMARCSYM"
9891 arc->setArcSymbolPositionRaw(var->getInt());
9892 break;
9893 default:
9894 break;
9895 }
9896 }
9897
9898 // workaround for AutoCAD - it seems that later versions ignores DIMSAH and
9899 // consider blocks for arrowhead to be different if individual arrows are
9900 // different. So we set the flag manually fixme - check whether given
9901 // condition is enough for properly setting the flag
9902 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
9903 if (!arrowhead->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
9904 if (arrowhead->sameBlockName().isEmpty() &&
9905 (arrowhead->arrowHeadBlockNameFirst() !=
9906 arrowhead->arrowHeadBlockNameSecond())) {
9907 arrowhead->setUseSeparateArrowHeads(true);
9908 }
9909 }
9910
9911 parsedOverride = std::move(result);
9912 return DimStyleOverrideParseResult::Valid;
9913}
9914
9915/**
9916 * Implementation of the method which handles
9917 * aligned dimensions (DIMENSION).
9918 */
9919void RS_FilterDXFRW::addDimAlign(const DRW_DimAligned *data) {
9920 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAligned");
9921 if (data == nullptr || m_currentContainer == nullptr) {
9922 return;
9923 }
9924
9925 const RS_DimensionData dimensionData = convDimensionData(data);
9926
9927 const RS_Vector ext1(data->getDef1Point().x, data->getDef1Point().y);
9928 const RS_Vector ext2(data->getDef2Point().x, data->getDef2Point().y);
9929
9930 const RS_DimAlignedData d(ext1, ext2);
9931 auto *entity = new RS_DimAligned(m_currentContainer, dimensionData, d);
9932 setEntityAttributes(entity, data);
9933 entity->updateDimPoint();
9934 entity->update();
9935 m_currentContainer->addEntity(entity);
9936}
9937
9938/**
9939 * Implementation of the method which handles
9940 * linear dimensions (DIMENSION).
9941 */
9942void RS_FilterDXFRW::addDimLinear(const DRW_DimLinear *data) {
9943 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLinear");
9944 if (data == nullptr || m_currentContainer == nullptr) {
9945 return;
9946 }
9947
9948 const RS_DimensionData dimensionData = convDimensionData(data);
9949
9950 const RS_Vector dxt1(data->getDef1Point().x, data->getDef1Point().y);
9951 const RS_Vector dxt2(data->getDef2Point().x, data->getDef2Point().y);
9952
9953 const RS_DimLinearData d(dxt1, dxt2, RS_Math::deg2rad(data->getAngle()),
9954 RS_Math::deg2rad(data->getOblique()));
9955
9956 const auto entity = new RS_DimLinear(m_currentContainer, dimensionData, d);
9957 setEntityAttributes(entity, data);
9958 entity->update();
9959 m_currentContainer->addEntity(entity);
9960}
9961
9962/**
9963 * Implementation of the method which handles
9964 * radial dimensions (DIMENSION).
9965 */
9966void RS_FilterDXFRW::addDimRadial(const DRW_DimRadial *data) {
9967 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimRadial");
9968 if (data == nullptr || m_currentContainer == nullptr) {
9969 return;
9970 }
9971
9972 const RS_DimensionData dimensionData = convDimensionData(data);
9973 const RS_Vector dp(data->getDiameterPoint().x, data->getDiameterPoint().y);
9974
9975 const RS_DimRadialData d(dp, data->getLeaderLength());
9976 const auto entity = new RS_DimRadial(m_currentContainer, dimensionData, d);
9977
9978 setEntityAttributes(entity, data);
9979 entity->update();
9980 m_currentContainer->addEntity(entity);
9981}
9982
9983/**
9984 * Implementation of the method which handles
9985 * diametric dimensions (DIMENSION).
9986 */
9987void RS_FilterDXFRW::addDimDiametric(const DRW_DimDiametric *data) {
9988 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimDiametric");
9989 if (data == nullptr || m_currentContainer == nullptr) {
9990 return;
9991 }
9992
9993 const RS_DimensionData dimensionData = convDimensionData(data);
9994 const RS_Vector dp(data->getDiameter1Point().x, data->getDiameter1Point().y);
9995
9996 const RS_DimDiametricData d(dp, data->getLeaderLength());
9997 const auto entity = new RS_DimDiametric(m_currentContainer, dimensionData, d);
9998
9999 setEntityAttributes(entity, data);
10000 entity->update();
10001 m_currentContainer->addEntity(entity);
10002}
10003
10004/**
10005 * Implementation of the method which handles
10006 * angular dimensions (DIMENSION).
10007 */
10008void RS_FilterDXFRW::addDimAngular(const DRW_DimAngular *data) {
10009 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular");
10010 if (data == nullptr || m_currentContainer == nullptr) {
10011 return;
10012 }
10013
10014 const RS_DimensionData dimensionData = convDimensionData(data);
10015 const RS_Vector dp1(data->getFirstLine1().x, data->getFirstLine1().y);
10016 const RS_Vector dp2(data->getFirstLine2().x, data->getFirstLine2().y);
10017 const RS_Vector dp3(data->getSecondLine1().x, data->getSecondLine1().y);
10018 const RS_Vector dp4(data->getDimPoint().x, data->getDimPoint().y);
10019
10020 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
10021
10022 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
10023
10024 setEntityAttributes(entity, data);
10025 entity->update();
10026 m_currentContainer->addEntity(entity);
10027}
10028
10029/**
10030 * Implementation of the method which handles
10031 * angular dimensions (DIMENSION).
10032 */
10033void RS_FilterDXFRW::addDimAngular3P(const DRW_DimAngular3p *data) {
10034 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimAngular3P");
10035 if (data == nullptr || m_currentContainer == nullptr) {
10036 return;
10037 }
10038
10039 const auto &vertexPoint = data->getVertexPoint();
10040
10041 RS_DimensionData dimensionData = convDimensionData(data);
10042 const RS_Vector dp1(data->getFirstLine().x, data->getFirstLine().y);
10043 const RS_Vector dp2(data->getSecondLine().x, data->getSecondLine().y);
10044 const RS_Vector dp3(vertexPoint.x, vertexPoint.y);
10045 const RS_Vector dp4 = dimensionData.definitionPoint;
10046 dimensionData.definitionPoint = RS_Vector(vertexPoint.x, vertexPoint.y);
10047
10048 const RS_DimAngularData d(dp1, dp2, dp3, dp4);
10049
10050 const auto entity = new RS_DimAngular(m_currentContainer, dimensionData, d);
10051
10052 setEntityAttributes(entity, data);
10053 entity->update();
10054 m_currentContainer->addEntity(entity);
10055}
10056
10057void RS_FilterDXFRW::addDimArc(const DRW_DimArc *data) {
10058 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimArc");
10059 if (data == nullptr || m_currentContainer == nullptr) {
10060 return;
10061 }
10062 RS_DimensionData dd = convDimensionData(data);
10063 RS_Vector centre(data->getArcCenter().x, data->getArcCenter().y);
10064 double radius = dd.definitionPoint.distanceTo(centre);
10065 LC_DimArcData arcData(
10066 radius, radius * std::abs(data->arcEndAngle - data->arcStartAngle),
10067 centre, RS_Vector::polar(1.0, data->arcEndAngle),
10068 RS_Vector::polar(1.0, data->arcStartAngle));
10069 arcData.arcSymbol = data->arcSymbol;
10070 arcData.isPartial = data->isPartial;
10071 arcData.hasLeader = data->hasLeader;
10072 arcData.leaderPt1 = RS_Vector(data->getLeaderPt1().x, data->getLeaderPt1().y);
10073 arcData.leaderPt2 = RS_Vector(data->leaderPt2.x, data->leaderPt2.y);
10074 auto dimEntity = new LC_DimArc(m_currentContainer, dd, arcData);
10075 setEntityAttributes(dimEntity, data);
10076 dimEntity->update();
10077 m_currentContainer->addEntity(dimEntity);
10078}
10079
10080void RS_FilterDXFRW::addDimOrdinate(const DRW_DimOrdinate *data) {
10081 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimOrdinate(const DL_DimensionData&, "
10082 "const DL_DimOrdinateData&) not yet implemented");
10083 const RS_DimensionData dimensionData = convDimensionData(data);
10084
10085 const RS_Vector featurePoint{data->getFirstLine().x, data->getFirstLine().y};
10086 const RS_Vector leaderEndPoint{data->getSecondLine().x,
10087 data->getSecondLine().y};
10088
10089 bool ordinateTypeForX = false;
10090 const int type = data->type;
10091 if (type & 64) {
10092 ordinateTypeForX = true;
10093 }
10094 const LC_DimOrdinateData d(featurePoint, leaderEndPoint, ordinateTypeForX);
10095 auto *entity = new LC_DimOrdinate(m_currentContainer, dimensionData, d);
10096 setEntityAttributes(entity, data);
10097 entity->update();
10098 m_currentContainer->addEntity(entity);
10099}
10100
10101/**
10102 * Implementation of the method which handles leader entities.
10103 */
10104void RS_FilterDXFRW::addLeader(const DRW_Leader *data) {
10105 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDimLeader");
10106 const RS_LeaderData d(data->arrow != 0,
10107 QString::fromUtf8(data->style.c_str()));
10108 const auto leader = new RS_Leader(m_currentContainer, d);
10109 setEntityAttributes(leader, data);
10110
10111 for (const auto &vert : data->vertexlist) {
10112 leader->addVertex({vert->x, vert->y});
10113 }
10114
10115 leader->update();
10116 m_currentContainer->addEntity(leader);
10117}
10118
10119namespace {
10120// Sample count for the cubic-NURBS → quadratic spline-points sampling branch
10121// in buildHatchSplineEdge. See plan §C.2.
10122constexpr int kHatchSplineSamples = 64;
10123} // namespace
10124
10125/**
10126 * Build an LC_SplinePoints from a DRW_Spline hatch-boundary edge.
10127 * All degrees converge to a quadratic spline-points representation:
10128 * degree==2 fit-points-only → pass-through as splinePoints
10129 * degree==2 control-net → pass-through as controlPoints
10130 * degree==1 or degree==3 → sample 64 points via a throwaway
10131 * RS_Spline evaluator
10132 * Returns nullptr on degenerate input. See plan §C.1-§C.3.
10133 */
10134LC_SplinePoints *
10135RS_FilterDXFRW::buildHatchSplineEdge(RS_EntityContainer *hatchLoop,
10136 const DRW_Spline *s) {
10137 if (s == nullptr)
10138 return nullptr;
10139 if (s->degree < 1 || s->degree > 3) {
10140 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10141 "buildHatchSplineEdge: unsupported degree %d", s->degree);
10142 return nullptr;
10143 }
10144 if (s->controllist.empty() && s->fitlist.empty()) {
10145 RS_DEBUGRS_Debug::instance()->print(
10146 RS_Debug::D_WARNING,
10147 "buildHatchSplineEdge: spline edge has neither control nor fit points");
10148 return nullptr;
10149 }
10150
10151 // DXF entity-level splines store "closed" in bit 0; DWG hatch-boundary
10152 // streams store "periodic" in bit 1 and never set bit 0 (see
10153 // libraries/libdxfrw/src/drw_entities.cpp parseDwg at lines 2799-2800).
10154 // Either bit implies a closed curve.
10155 const bool closed = (s->flags & 0x3) != 0;
10156 LC_SplinePointsData sd(closed, /*cut*/ false);
10157
10158 if (s->degree == 2 && s->controllist.empty() && !s->fitlist.empty()) {
10159 // Branch 1: degree==2 with fit points only — pass through.
10160 sd.useControlPoints = false;
10161 for (const auto &fp : s->fitlist) {
10162 if (fp)
10163 sd.splinePoints.push_back(RS_Vector{fp->x, fp->y});
10164 }
10165 } else if (s->degree == 2 && !s->controllist.empty()) {
10166 // Branch 2: degree==2 with control net — pass through.
10167 sd.useControlPoints = true;
10168 for (const auto &cp : s->controllist) {
10169 if (cp)
10170 sd.controlPoints.push_back(RS_Vector{cp->x, cp->y});
10171 }
10172 } else {
10173 // Branch 3: degree==1 or degree==3. Sample the NURBS at 64 points
10174 // via a throwaway RS_Spline; the samples become fit points.
10175 // Fall back to treating control points as a polyline if the knot
10176 // vector is missing or short.
10177 const size_t needKnots = s->controllist.size() + size_t(s->degree) + 1;
10178 if (s->knotslist.size() < needKnots) {
10179 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10180 "buildHatchSplineEdge: short knot vector (%zu < %zu); "
10181 "treating control points as polyline",
10182 s->knotslist.size(), needKnots);
10183 sd.useControlPoints = false;
10184 for (const auto &cp : s->controllist) {
10185 if (cp)
10186 sd.splinePoints.push_back(RS_Vector{cp->x, cp->y});
10187 }
10188 } else {
10189 const bool isRational = (s->flags & 0x4) != 0;
10190 std::vector<double> weights;
10191 for (size_t i = 0; i < s->controllist.size(); ++i) {
10192 const auto &cp = s->controllist[i];
10193 // DXF stores rational weights in weightlist; DWG hatch-
10194 // boundary stream stores them on controllist[i]->z. Check
10195 // both. See plan §C.3.
10196 double w = 1.0;
10197 if (isRational && cp) {
10198 if (i < s->weightlist.size())
10199 w = s->weightlist[i];
10200 else
10201 w = cp->z;
10202 }
10203 weights.push_back(w);
10204 }
10205 const std::unique_ptr<RS_Spline> tmp =
10206 makeSplineFromDrw(nullptr, *s, weights);
10207
10208 sd.useControlPoints = false;
10209 if (s->controllist.size() >= size_t(s->degree) + 1) {
10210 // sampled even when no spline type accepts the data, such as a knot
10211 // vector longer than needed: the evaluation stays within its arrays
10212 sd.splinePoints.reserve(kHatchSplineSamples);
10213 tmp->fillStrokePoints(kHatchSplineSamples - 1, sd.splinePoints);
10214 } else {
10215 // with fewer than degree + 1 control points the evaluation would read
10216 // outside its arrays: the edge passes through its control points
10217 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
10218 "buildHatchSplineEdge: too few control points for "
10219 "the degree; passing through the control points");
10220 for (const auto &cp : s->controllist) {
10221 if (cp)
10222 sd.splinePoints.push_back(RS_Vector{cp->x, cp->y});
10223 }
10224 }
10225 }
10226 }
10227
10228 if (sd.splinePoints.size() < 2 && sd.controlPoints.size() < 2) {
10229 RS_DEBUGRS_Debug::instance()->print(
10230 RS_Debug::D_WARNING,
10231 "buildHatchSplineEdge: degenerate spline edge (too few points)");
10232 return nullptr;
10233 }
10234
10235 // Some DWG hatch boundaries encode a geometrically-closed spline with
10236 // both flag bits unset (and an accompanying 0-length line as a closure
10237 // marker). Detect endpoint coincidence in the populated point list and
10238 // flip the closed flag so LoopExtractor treats it as a single closed
10239 // loop instead of an open edge whose start==end. Closed LC_SplinePoints
10240 // expects a periodic point list without an explicit closing repeat —
10241 // drop the duplicate tail when present, also from the points sampled from
10242 // a flagged closed spline, whose curve AutoCAD ends where it starts.
10243 if (!sd.splinePoints.empty() &&
10244 sd.splinePoints.front().distanceTo(sd.splinePoints.back()) <= 1e-8) {
10245 sd.closed = true;
10246 if (sd.splinePoints.size() > 2)
10247 sd.splinePoints.pop_back();
10248 } else if (!sd.closed && !sd.controlPoints.empty() &&
10249 sd.controlPoints.front().distanceTo(sd.controlPoints.back()) <=
10250 1e-8) {
10251 sd.closed = true;
10252 if (sd.controlPoints.size() > 2)
10253 sd.controlPoints.pop_back();
10254 }
10255
10256 return new LC_SplinePoints(hatchLoop, std::move(sd));
10257}
10258
10259/**
10260 * Snap LC_SplinePoints endpoints inside hatchLoop to neighboring line-like
10261 * edge endpoints when the gap is within 10× ENDPOINT_TOLERANCE (1e-7) but
10262 * non-zero. Boundary-stream float drift can leave ~1e-9 to ~1e-8 gaps that
10263 * break LoopExtractor's endpoint-chaining. The 10× window is small enough
10264 * that geometrically distinct edges never stitch.
10265 */
10266void RS_FilterDXFRW::snapSplineEdgeEndpoints(RS_EntityContainer *hatchLoop) {
10267 if (hatchLoop == nullptr || hatchLoop->count() < 2)
10268 return;
10269 constexpr double kSnapWindow = 1e-7; // 10× ENDPOINT_TOLERANCE
10270 const unsigned int n = hatchLoop->count();
10271
10272 auto setSplineEnd = [](LC_SplinePoints *sp, bool atFront,
10273 const RS_Vector &v) {
10274 auto &d = sp->getData();
10275 if (d.useControlPoints && !d.controlPoints.empty()) {
10276 if (atFront)
10277 d.controlPoints.front() = v;
10278 else
10279 d.controlPoints.back() = v;
10280 } else if (!d.splinePoints.empty()) {
10281 if (atFront)
10282 d.splinePoints.front() = v;
10283 else
10284 d.splinePoints.back() = v;
10285 }
10286 sp->update();
10287 };
10288
10289 for (unsigned int i = 0; i < n; ++i) {
10290 RS_Entity *cur = hatchLoop->entityAt(i);
10291 RS_Entity *nxt = hatchLoop->entityAt((i + 1) % n);
10292 if (cur == nullptr || nxt == nullptr)
10293 continue;
10294
10295 const bool curSpline = cur->rtti() == RS2::EntitySplinePoints;
10296 const bool nxtSpline = nxt->rtti() == RS2::EntitySplinePoints;
10297 if (curSpline == nxtSpline)
10298 continue; // line-line or spline-spline
10299
10300 const RS_Vector curEnd = cur->getEndpoint();
10301 const RS_Vector nxtBeg = nxt->getStartpoint();
10302 const double gap = curEnd.distanceTo(nxtBeg);
10303 if (gap <= 0.0 || gap >= kSnapWindow)
10304 continue;
10305
10306 if (curSpline) {
10307 setSplineEnd(static_cast<LC_SplinePoints *>(cur), /*atFront*/ false,
10308 nxtBeg);
10309 } else {
10310 setSplineEnd(static_cast<LC_SplinePoints *>(nxt), /*atFront*/ true,
10311 curEnd);
10312 }
10313 }
10314}
10315
10316/**
10317 * Implementation of the method which handles hatch entities.
10318 */
10319void RS_FilterDXFRW::addHatch(const DRW_Hatch *data) {
10320 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addHatch()");
10321 const double hatchAngle =
10322 importedHatchAngle(data->angle, m_legacyHatchRadians, data->solid != 0);
10323 const auto hatch = new RS_Hatch(
10324 m_currentContainer, RS_HatchData(data->solid, data->scale, hatchAngle,
10325 QString::fromUtf8(data->name.c_str())));
10326 setEntityAttributes(hatch, data);
10327 // Strip the approximate definition-line tag the writer may have added so
10328 // a round-trip through LibreCAD never duplicates it: the re-export rebuilds
10329 // the tag from forExport()'s own approximate flag. The strip is case-
10330 // insensitive on the 1001 group name. A caller may read whether the import
10331 // carried the tag through the returned bool (unused today; a future step
10332 // reads it to tell "this hatch was reconstructed through our placeholder"
10333 // from "an exact LibreCAD export").
10334 if (hatch->hasDrwExtData()) {
10335 auto extData = hatch->getDrwExtData();
10336 const bool importedApproximate =
10337 LC_HatchDefinitionLines::stripApproximateTag(extData);
10338 if (importedApproximate)
10339 hatch->setDrwExtData(std::move(extData));
10340 }
10341 if (data->basePoint.z != 0.0 || data->extPoint.x != 0.0 ||
10342 data->extPoint.y != 0.0 || data->extPoint.z != 1.0) {
10343 std::vector<std::shared_ptr<DRW_Variant>> payload;
10344 payload.push_back(std::make_shared<DRW_Variant>(
10345 1010, DRW_Coord(0.0, 0.0, data->basePoint.z)));
10346 payload.push_back(std::make_shared<DRW_Variant>(
10347 1011, DRW_Coord(data->extPoint.x, data->extPoint.y, data->extPoint.z)));
10348 appendTypeSidecar(hatch, kHatchExtrusionMarker, std::move(payload));
10349 }
10350 m_currentContainer->appendEntity(hatch);
10351
10352 for (size_t i = 0; i < data->looplist.size(); i++) {
10353 auto &loop = data->looplist.at(i);
10354 if ((loop->type & 32) == 32) {
10355 continue;
10356 }
10357 const auto hatchLoop = new RS_EntityContainer(hatch);
10358 hatchLoop->setLayer(nullptr);
10359 hatch->addEntity(hatchLoop);
10360
10361 if ((loop->type & 2) == 2) { // polyline, convert to lines & arcs
10362 if (loop->objlist.empty())
10363 continue;
10364 DRW_LWPolyline *pline =
10365 static_cast<DRW_LWPolyline *>(loop->objlist.at(0).get());
10366 RS_Polyline polyline{
10367 nullptr,
10368 RS_PolylineData(RS_Vector(false), RS_Vector(false), pline->flags)};
10369 for (auto const &vert : pline->vertlist) {
10370 polyline.addVertex(RS_Vector{vert->x, vert->y}, vert->bulge);
10371 }
10372
10373 for (const RS_Entity *e :
10374 lc::LC_ContainerTraverser{polyline, RS2::ResolveNone}.entities()) {
10375 // for (RS_Entity* e=polyline.firstEntity(); e;
10376 // e=polyline.nextEntity()) {
10377 RS_Entity *tmp = e->clone();
10378 tmp->reparent(hatchLoop);
10379 tmp->setLayer(nullptr);
10380 hatchLoop->addEntity(tmp);
10381 }
10382 } else {
10383 RS_Entity *e = nullptr;
10384 for (unsigned int j = 0; j < loop->objlist.size(); j++) {
10385 e = nullptr;
10386 auto &ent = loop->objlist.at(j);
10387 switch (ent->eType) {
10388 case DRW::LINE: {
10389 const auto e2 = static_cast<DRW_Line *>(ent.get());
10390 e = new RS_Line{hatchLoop,
10391 {{e2->basePoint.x, e2->basePoint.y},
10392 {e2->secPoint.x, e2->secPoint.y}}};
10393 break;
10394 }
10395 case DRW::ARC: {
10396 const auto e2 = static_cast<DRW_Arc *>(ent.get());
10397 if (e2->isccw && e2->staangle < 1.0e-6 &&
10398 e2->endangle > RS_Math::deg2rad(360) - 1.0e-6) {
10399 e = new RS_Circle(
10400 hatchLoop, {{e2->basePoint.x, e2->basePoint.y}, e2->radious});
10401 } else {
10402 if (e2->isccw) {
10403 e = new RS_Arc(
10404 hatchLoop,
10405 RS_ArcData(RS_Vector(e2->basePoint.x, e2->basePoint.y),
10406 e2->radious, RS_Math::correctAngle(e2->staangle),
10407 RS_Math::correctAngle(e2->endangle), false));
10408 } else {
10409 e = new RS_Arc(
10410 hatchLoop,
10411 RS_ArcData(
10412 RS_Vector(e2->basePoint.x, e2->basePoint.y), e2->radious,
10413 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->staangle),
10414 RS_Math::correctAngle(2 * M_PI3.14159265358979323846 - e2->endangle), true));
10415 }
10416 }
10417 break;
10418 }
10419 case DRW::ELLIPSE: {
10420 const auto e2 = static_cast<DRW_Ellipse *>(ent.get());
10421 double ang1 = e2->staparam;
10422 double ang2 = e2->endparam;
10423 if (fabs(ang2 - 2. * M_PI3.14159265358979323846) < 1.0e-10 && fabs(ang1) < 1.0e-10) {
10424 ang2 = 0.0;
10425 } else {
10426 // convert angle to parameter
10427 ang1 = atan(tan(ang1) / e2->ratio);
10428 ang2 = atan(tan(ang2) / e2->ratio);
10429 if (ang1 < 0) {
10430 // quadrant 2 & 4
10431 ang1 += M_PI3.14159265358979323846;
10432 if (e2->staparam > M_PI3.14159265358979323846) {
10433 // quadrant 4
10434 ang1 += M_PI3.14159265358979323846;
10435 }
10436 } else if (e2->staparam > M_PI3.14159265358979323846) {
10437 // 3 quadrant
10438 ang1 += M_PI3.14159265358979323846;
10439 }
10440 if (ang2 < 0) {
10441 // quadrant 2 & 4
10442 ang2 += M_PI3.14159265358979323846;
10443 if (e2->endparam > M_PI3.14159265358979323846) {
10444 // quadrant 4
10445 ang2 += M_PI3.14159265358979323846;
10446 }
10447 } else if (e2->endparam > M_PI3.14159265358979323846) {
10448 // 3 quadrant
10449 ang2 += M_PI3.14159265358979323846;
10450 }
10451 }
10452 e = new RS_Ellipse{hatchLoop,
10453 {{e2->basePoint.x, e2->basePoint.y},
10454 {e2->secPoint.x, e2->secPoint.y},
10455 e2->ratio,
10456 ang1,
10457 ang2,
10458 !e2->isccw}};
10459 break;
10460 }
10461 case DRW::SPLINE: {
10462 e = buildHatchSplineEdge(hatchLoop,
10463 static_cast<DRW_Spline *>(ent.get()));
10464 break;
10465 }
10466 default:
10467 break;
10468 }
10469 if (e != nullptr) {
10470 e->setLayer(nullptr);
10471 hatchLoop->addEntity(e);
10472 }
10473 }
10474 // After all explicit-segment edges are attached, snap spline
10475 // endpoints to neighboring line endpoints if a tiny float gap
10476 // is the only thing standing between LoopExtractor and a closed
10477 // chain. See plan §C.1.
10478 snapSplineEdgeEndpoints(hatchLoop);
10479 }
10480 }
10481
10482 RS_DEBUGRS_Debug::instance()->print("hatch->update()");
10483 if (hatch->validate()) {
10484 hatch->update();
10485 } else {
10486 m_graphic->removeEntity(hatch);
10487 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXFRW::endEntity(): updating "
10488 "hatch failed: invalid hatch area");
10489 }
10490}
10491
10492/**
10493 * Implementation of the method which handles image entities.
10494 */
10495void RS_FilterDXFRW::addImage(const DRW_Image *data) {
10496 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addImage");
10497 if (m_graphic != nullptr && data != nullptr)
10498 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, false);
10499
10500 if (data == nullptr)
10501 return;
10502
10503 const RS_Vector ip(data->basePoint.x, data->basePoint.y);
10504 const RS_Vector uv(data->secPoint.x, data->secPoint.y);
10505 const RS_Vector vv(data->vVector.x, data->vVector.y);
10506 const RS_Vector size(data->sizeu, data->sizev);
10507
10508 const auto image =
10509 new RS_Image(m_currentContainer,
10510 RS_ImageData(data->ref, ip, uv, vv, size, QString(),
10511 data->brightness, data->contrast, data->fade));
10512
10513 setEntityAttributes(image, data);
10514 if (data->basePoint.z != 0.0 || data->secPoint.z != 0.0 ||
10515 data->vVector.z != 0.0) {
10516 std::vector<std::shared_ptr<DRW_Variant>> payload;
10517 payload.push_back(std::make_shared<DRW_Variant>(
10518 1010,
10519 DRW_Coord(data->basePoint.x, data->basePoint.y, data->basePoint.z)));
10520 payload.push_back(std::make_shared<DRW_Variant>(
10521 1011, DRW_Coord(data->secPoint.x, data->secPoint.y, data->secPoint.z)));
10522 payload.push_back(std::make_shared<DRW_Variant>(
10523 1012, DRW_Coord(data->vVector.x, data->vVector.y, data->vVector.z)));
10524 appendTypeSidecar(image, kImageFrameMarker, std::move(payload));
10525 }
10526 m_currentContainer->appendEntity(image);
10527}
10528
10529/**
10530 * Implementation of the method which handles WIPEOUT entities.
10531 *
10532 * WIPEOUT shares the IMAGE binary layout but only the polygon (clipPath) and
10533 * the IMAGE's frame parameters (basePoint, secPoint=uVector, vVector,
10534 * sizeu/sizev) are meaningful — there's no raster file behind it.
10535 *
10536 * Group 11/12 are single-pixel U/V vectors. With the half-pixel origin
10537 * offset, the WCS transform is:
10538 * P_wcs = basePoint + (px + 0.5) * uVector + (py + 0.5) * vVector.
10539 * The clip coordinates already express the image size; sizeu/sizev must not
10540 * multiply the vectors again.
10541 */
10542void RS_FilterDXFRW::addWipeout(const DRW_Wipeout *data) {
10543 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addWipeout");
10544 if (m_graphic != nullptr && data != nullptr)
10545 m_graphic->dwgAdvancedMetadata().addRasterImage(*data, true);
10546 if (data == nullptr || data->clipPath.empty()) {
10547 return;
10548 }
10549
10550 LC_WipeoutData wipeoutData;
10551 wipeoutData.hasNativeFrame = true;
10552 wipeoutData.insertionPoint =
10553 RS_Vector(data->basePoint.x, data->basePoint.y, data->basePoint.z);
10554 wipeoutData.uPixel =
10555 RS_Vector(data->secPoint.x, data->secPoint.y, data->secPoint.z);
10556 wipeoutData.vPixel =
10557 RS_Vector(data->vVector.x, data->vVector.y, data->vVector.z);
10558 wipeoutData.sizeU = data->sizeu;
10559 wipeoutData.sizeV = data->sizev;
10560 wipeoutData.displayProps = data->m_displayProps;
10561 wipeoutData.imageDefHandle = data->ref;
10562 wipeoutData.imageDefReactorHandle = data->m_imageDefReactorHandle;
10563 wipeoutData.clip = data->clip;
10564 wipeoutData.brightness = data->brightness;
10565 wipeoutData.contrast = data->contrast;
10566 wipeoutData.fade = data->fade;
10567 wipeoutData.clipBoundaryType = data->m_clipBoundaryType;
10568 wipeoutData.clipMode = data->clipMode;
10569 wipeoutData.clipPath.reserve(data->clipPath.size());
10570 for (const DRW_Coord &clipPoint : data->clipPath)
10571 wipeoutData.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
10572
10573 auto w =
10574 std::make_unique<LC_Wipeout>(m_currentContainer, std::move(wipeoutData));
10575 setEntityAttributes(w.get(), data);
10576 m_currentContainer->appendEntity(w.release());
10577}
10578
10579/**
10580 * Build an LC_MLeader from a parsed DRW_MLeader (ODA spec §20.4.48).
10581 *
10582 * MLEADER carries a multi-root callout structure plus either text or block
10583 * content. This conversion captures the geometric structure (roots →
10584 * leader lines → points) and content reference. Style-handle resolution
10585 * (against LC_MLeaderStyleList) is still metadata-only, but we preserve the
10586 * DWG handle references needed for native re-export.
10587 */
10588void RS_FilterDXFRW::addMLeader(const DRW_MLeader *data) {
10589 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeader");
10590 if (data == nullptr)
10591 return;
10592 if (m_graphic != nullptr) {
10593 m_graphic->dwgAdvancedMetadata().addMLeader(*data);
10594 }
10595
10596 LC_MLeaderData md;
10597 md.roots.reserve(data->context.roots.size());
10598 for (const auto &r : data->context.roots) {
10599 LC_MLeaderRoot root;
10600 root.connectionPoint = RS_Vector(r.connectionPoint.x, r.connectionPoint.y);
10601 root.direction = RS_Vector(r.direction.x, r.direction.y);
10602 root.landingDistance = r.landingDistance;
10603 root.attachmentDirection = r.attachmentDirection;
10604 root.leaderLines.reserve(r.leaderLines.size());
10605 for (const auto &ll : r.leaderLines) {
10606 LC_MLeaderLine line;
10607 line.leaderLineIndex = ll.leaderLineIndex;
10608 line.points.reserve(ll.points.size());
10609 for (const auto &p : ll.points) {
10610 line.points.emplace_back(p.x, p.y);
10611 }
10612 root.leaderLines.push_back(std::move(line));
10613 }
10614 md.roots.push_back(std::move(root));
10615 }
10616
10617 md.contentBasePoint = RS_Vector(data->context.contentBasePoint.x,
10618 data->context.contentBasePoint.y);
10619 md.basePoint =
10620 RS_Vector(data->context.basePoint.x, data->context.basePoint.y);
10621
10622 md.hasTextContents = data->context.hasTextContents;
10623 md.hasBlockContents =
10624 !data->context.hasTextContents && data->context.hasContentsBlock;
10625 if (md.hasTextContents) {
10626 md.textLabel =
10627 toNativeString(QString::fromUtf8(data->context.textLabel.c_str()));
10628 md.textLocation =
10629 RS_Vector(data->context.textLocation.x, data->context.textLocation.y);
10630 md.textHeight = data->context.textHeight;
10631 md.textRotation = data->context.textRotation;
10632 md.boundaryWidth = data->context.boundaryWidth;
10633 md.boundaryHeight = data->context.boundaryHeight;
10634 md.textColor = data->context.textColor;
10635 }
10636 if (md.hasBlockContents) {
10637 md.blockLocation =
10638 RS_Vector(data->context.blockLocation.x, data->context.blockLocation.y);
10639 md.blockScale =
10640 RS_Vector(data->context.blockScale.x, data->context.blockScale.y,
10641 data->context.blockScale.z);
10642 md.blockRotation = data->context.blockRotation;
10643 // Resolve the content block handle -> block name so LC_MLeader can render
10644 // the block symbol the leader points at. The context's
10645 // blockTableRecordHandle is authoritative; fall back to the entity-level
10646 // style block handle.
10647 std::uint32_t blockHandle = data->context.blockTableRecordHandle.ref != 0
10648 ? data->context.blockTableRecordHandle.ref
10649 : data->styleBlockHandle.ref;
10650 if (blockHandle != 0)
10651 resolveBlockNameByHandle(blockHandle, md.blockName);
10652 }
10653
10654 md.leaderType = data->leaderType;
10655 md.leaderColor = data->leaderColor;
10656 md.landingDistance = data->landingDistance;
10657 md.arrowSize = data->defaultArrowHeadSize;
10658 md.landingEnabled = data->landingEnabled;
10659 md.doglegEnabled = data->doglegEnabled;
10660 md.contentType = data->styleContentType;
10661 md.scaleFactor = data->scaleFactor;
10662 md.dwgStyleHandle = data->styleHandle.ref;
10663 md.dwgLeaderLineTypeHandle = data->leaderLineTypeHandle.ref;
10664 md.dwgArrowHeadHandle = data->arrowHeadHandle.ref;
10665 md.dwgTextStyleHandle = data->styleTextStyleHandle.ref != 0
10666 ? data->styleTextStyleHandle.ref
10667 : data->context.textStyleHandle.ref;
10668 md.dwgBlockHandle = data->styleBlockHandle.ref != 0
10669 ? data->styleBlockHandle.ref
10670 : data->context.blockTableRecordHandle.ref;
10671
10672 auto m = std::make_unique<LC_MLeader>(m_currentContainer, std::move(md));
10673 setEntityAttributes(m.get(), data);
10674 m_currentContainer->appendEntity(m.release());
10675}
10676
10677/**
10678 * MLEADERSTYLE dictionary entry capture. Styles are kept in the DWG advanced
10679 * metadata store for future style resolution and raw/native round-trip work;
10680 * the current LC_MLeader entity still copies effective scalar values from the
10681 * entity itself.
10682 */
10683void RS_FilterDXFRW::addMLeaderStyle(const DRW_MLeaderStyle *data) {
10684 if (data == nullptr)
10685 return;
10686 if (m_graphic != nullptr) {
10687 m_graphic->dwgAdvancedMetadata().addMLeaderStyle(*data);
10688 }
10689 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMLeaderStyle: %s",
10690 data->name.empty() ? "(unnamed)" : data->name.c_str());
10691}
10692
10693void RS_FilterDXFRW::addDetailViewStyle(const DRW_DetailViewStyle &data) {
10694 if (m_graphic != nullptr) {
10695 m_graphic->dwgAdvancedMetadata().addDetailViewStyle(data);
10696 }
10697 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDetailViewStyle: %s",
10698 data.m_modelDoc.m_displayName.empty()
10699 ? data.m_modelDoc.m_description.c_str()
10700 : data.m_modelDoc.m_displayName.c_str());
10701}
10702
10703void RS_FilterDXFRW::addSectionViewStyle(const DRW_SectionViewStyle &data) {
10704 if (m_graphic != nullptr) {
10705 m_graphic->dwgAdvancedMetadata().addSectionViewStyle(data);
10706 }
10707 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSectionViewStyle: %s",
10708 data.m_modelDoc.m_displayName.empty()
10709 ? data.m_modelDoc.m_description.c_str()
10710 : data.m_modelDoc.m_displayName.c_str());
10711}
10712
10713void RS_FilterDXFRW::addBreakData(const DRW_BreakData &data) {
10714 if (m_graphic != nullptr) {
10715 m_graphic->dwgAdvancedMetadata().addBreakData(data);
10716 }
10717 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakData: %d refs",
10718 static_cast<int>(data.m_pointRefHandles.size()));
10719}
10720
10721void RS_FilterDXFRW::addBreakPointRef(const DRW_BreakPointRef &data) {
10722 if (m_graphic != nullptr) {
10723 m_graphic->dwgAdvancedMetadata().addBreakPointRef(data);
10724 }
10725 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addBreakPointRef: %d",
10726 static_cast<int>(data.handle));
10727}
10728
10729void RS_FilterDXFRW::addGroup(const DRW_Group &data) {
10730 if (m_graphic != nullptr) {
10731 m_graphic->dwgAdvancedMetadata().addGroup(data);
10732 }
10733 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGroup: %s (%d handles)",
10734 data.m_description.empty() ? "(unnamed)"
10735 : data.m_description.c_str(),
10736 static_cast<int>(data.m_entityHandles.size()));
10737}
10738
10739void RS_FilterDXFRW::addImageDefinitionReactor(
10740 const DRW_ImageDefinitionReactor &data) {
10741 if (m_graphic != nullptr) {
10742 m_graphic->dwgAdvancedMetadata().addImageDefinitionReactor(data);
10743 }
10744 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addImageDefinitionReactor: class version %d",
10745 static_cast<int>(data.m_classVersion));
10746}
10747
10748void RS_FilterDXFRW::addRasterVariables(const DRW_RasterVariables &data) {
10749 if (m_graphic != nullptr) {
10750 m_graphic->dwgAdvancedMetadata().addRasterVariables(data);
10751 }
10752 RS_DEBUGRS_Debug::instance()->print(
10753 "RS_FilterDXFRW::addRasterVariables: frame %d quality %d units %d",
10754 data.m_imageFrame, data.m_imageQuality, data.m_units);
10755}
10756
10757void RS_FilterDXFRW::addSpatialFilter(const DRW_SpatialFilter &data) {
10758 if (m_graphic != nullptr) {
10759 m_graphic->dwgAdvancedMetadata().addSpatialFilter(data);
10760 }
10761 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialFilter: %d boundary points",
10762 static_cast<int>(data.m_boundaryPoints.size()));
10763}
10764
10765void RS_FilterDXFRW::addGeoData(const DRW_GeoData &data) {
10766 if (m_graphic != nullptr) {
10767 m_graphic->dwgAdvancedMetadata().addGeoData(data);
10768 }
10769 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addGeoData: version %d",
10770 static_cast<int>(data.m_version));
10771}
10772
10773void RS_FilterDXFRW::addTableGeometry(const DRW_TableGeometry &data) {
10774 if (m_graphic != nullptr) {
10775 m_graphic->dwgAdvancedMetadata().addTableGeometry(data);
10776 }
10777 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableGeometry: %d x %d",
10778 static_cast<int>(data.m_rowCount),
10779 static_cast<int>(data.m_columnCount));
10780}
10781
10782void RS_FilterDXFRW::addTableStyle(const DRW_TableStyle &data) {
10783 if (m_graphic != nullptr) {
10784 m_graphic->dwgAdvancedMetadata().addTableStyle(data);
10785 }
10786 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableStyle: %s",
10787 data.m_name.empty() ? "(unnamed)" : data.m_name.c_str());
10788}
10789
10790void RS_FilterDXFRW::addTableContent(const DRW_TableContentObject &data) {
10791 if (m_graphic != nullptr) {
10792 m_graphic->dwgAdvancedMetadata().addTableContent(data);
10793 }
10794 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addTableContent: %d x %d",
10795 static_cast<int>(data.m_content.m_rows.size()),
10796 static_cast<int>(data.m_content.m_columns.size()));
10797}
10798
10799void RS_FilterDXFRW::addObjectContextData(const DRW_ObjectContextData &data) {
10800 if (m_graphic != nullptr) {
10801 m_graphic->dwgAdvancedMetadata().addObjectContextData(data);
10802 }
10803 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addObjectContextData: %s handle %d scale %d",
10804 data.m_recordName.empty() ? "(object-context)"
10805 : data.m_recordName.c_str(),
10806 static_cast<int>(data.handle),
10807 static_cast<int>(data.m_scaleHandle));
10808}
10809
10810void RS_FilterDXFRW::addCellStyleMap(const DRW_CellStyleMap &data) {
10811 if (m_graphic != nullptr) {
10812 m_graphic->dwgAdvancedMetadata().addCellStyleMap(data);
10813 }
10814 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCellStyleMap: %d styles",
10815 static_cast<int>(data.m_cellStyles.size()));
10816}
10817
10818void RS_FilterDXFRW::addRawDxfObject(const DRW_RawDxfObject &data) {
10819 if (m_graphic != nullptr) {
10820 m_graphic->dwgAdvancedMetadata().addRawDxfObject(data);
10821 }
10822}
10823
10824void RS_FilterDXFRW::addRawDxfEntity(const DRW_RawDxfObject &data) {
10825 if (m_graphic != nullptr) {
10826 m_graphic->dwgAdvancedMetadata().addRawDxfEntity(data);
10827 }
10828}
10829
10830void RS_FilterDXFRW::addRawDxfSection(const DRW_RawDxfSection &data) {
10831 if (m_graphic != nullptr) {
10832 m_graphic->dwgAdvancedMetadata().addRawDxfSection(data);
10833 }
10834}
10835
10836void RS_FilterDXFRW::addDxfClass(const DRW_Class &data) {
10837 if (m_graphic != nullptr) {
10838 m_graphic->dwgAdvancedMetadata().addDxfClass(data);
10839 }
10840}
10841
10842void RS_FilterDXFRW::addUnsupportedObject(const DRW_UnsupportedObject &data) {
10843 if (m_graphic != nullptr) {
10844 m_graphic->dwgAdvancedMetadata().addUnsupportedObject(data);
10845 // Cross-read P1: Navisworks (and related) stay metadata-only until typed
10846 // DRW models land; classify by CLASSES record name so they are not silent.
10847 const std::string &rn = data.m_recordName;
10848 if (rn.find("NAVISWORKS") != std::string::npos) {
10849 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
10850 rn.empty() ? "NAVISWORKSMODEL" : rn, data.m_handle, 0,
10851 "metadata-only unsupported shell");
10852 }
10853 }
10854 m_unsupportedDwgObjects.push_back(data);
10855 RS_DEBUGRS_Debug::instance()->print(
10856 "RS_FilterDXFRW::addUnsupportedObject: %s handle %d (%d bytes)",
10857 data.m_recordName.empty() ? "(fixed)" : data.m_recordName.c_str(),
10858 static_cast<int>(data.m_handle),
10859 static_cast<int>(data.m_rawBytes.size()));
10860}
10861
10862void RS_FilterDXFRW::addDwgFramePublication(
10863 const DRW_DwgFramePublication &data) {
10864 if (m_graphic != nullptr) {
10865 m_graphic->dwgAdvancedMetadata().addDwgFramePublication(data);
10866 }
10867}
10868
10869void RS_FilterDXFRW::addDwgDictionaryMembership(
10870 const DRW_DwgDictionaryMembership &data) {
10871 if (m_graphic != nullptr) {
10872 m_graphic->dwgAdvancedMetadata().addDwgDictionaryMembership(data);
10873 }
10874}
10875
10876void RS_FilterDXFRW::addDwgDictionaryWithDefaultMembership(
10877 const DRW_DwgDictionaryWithDefaultMembership &data) {
10878 if (m_graphic != nullptr) {
10879 m_graphic->dwgAdvancedMetadata().addDwgDictionaryWithDefaultMembership(
10880 data);
10881 }
10882}
10883
10884void RS_FilterDXFRW::addDwgGroupMembership(const DRW_DwgGroupMembership &data) {
10885 if (m_graphic != nullptr) {
10886 m_graphic->dwgAdvancedMetadata().addDwgGroupMembership(data);
10887 }
10888}
10889
10890void RS_FilterDXFRW::addDwgSortEntsMembership(
10891 const DRW_DwgSortEntsMembership &data) {
10892 if (m_graphic != nullptr) {
10893 m_graphic->dwgAdvancedMetadata().addDwgSortEntsMembership(data);
10894 }
10895}
10896
10897void RS_FilterDXFRW::addDwgFieldListMembership(
10898 const DRW_DwgFieldListMembership &data) {
10899 if (m_graphic != nullptr) {
10900 m_graphic->dwgAdvancedMetadata().addDwgFieldListMembership(data);
10901 }
10902}
10903
10904void RS_FilterDXFRW::addDwgFieldPayloadReceipt(
10905 const DRW_DwgFieldPayloadReceipt &data) {
10906 if (m_graphic != nullptr) {
10907 m_graphic->dwgAdvancedMetadata().addDwgFieldPayloadReceipt(data);
10908 }
10909}
10910
10911void RS_FilterDXFRW::addDwgTypedReference(const DRW_DwgTypedReference &data) {
10912 if (m_graphic != nullptr) {
10913 m_graphic->dwgAdvancedMetadata().addDwgTypedReference(data);
10914 }
10915}
10916
10917void RS_FilterDXFRW::addDwgBlockReachability(
10918 const DRW_DwgBlockReachability &data) {
10919 if (m_graphic != nullptr) {
10920 m_graphic->dwgAdvancedMetadata().addDwgBlockReachability(data);
10921 }
10922}
10923
10924void RS_FilterDXFRW::addDwgFrameCoverageReport(
10925 const DRW_DwgFrameCoverageReport &data) {
10926 if (m_graphic != nullptr) {
10927 m_graphic->dwgAdvancedMetadata().addDwgFrameCoverageReport(data);
10928 }
10929}
10930
10931void RS_FilterDXFRW::addDwgClassCoverageReport(
10932 const DRW_DwgClassCoverageReport &data) {
10933 if (m_graphic != nullptr) {
10934 m_graphic->dwgAdvancedMetadata().addDwgClassCoverageReport(data);
10935 }
10936}
10937
10938void RS_FilterDXFRW::addRawDwgSection(const DRW_RawDwgSection &data) {
10939 if (m_graphic != nullptr) {
10940 m_graphic->dwgAdvancedMetadata().addRawDwgSection(data);
10941 }
10942 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addRawDwgSection: %s (%d bytes)",
10943 data.m_name.c_str(), static_cast<int>(data.m_data.size()));
10944}
10945
10946void RS_FilterDXFRW::addDataStorage(const DRW_DataStorageSection &data) {
10947 if (m_graphic != nullptr) {
10948 m_graphic->dwgAdvancedMetadata().addDataStorage(data);
10949 }
10950 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDataStorage: %s records=%d",
10951 data.m_name.c_str(), static_cast<int>(data.records.size()));
10952}
10953
10954void RS_FilterDXFRW::addAcDbPlaceholder(const DRW_AcDbPlaceholder &data) {
10955 if (m_graphic != nullptr) {
10956 m_graphic->dwgAdvancedMetadata().addAcDbPlaceholder(data);
10957 }
10958 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcDbPlaceholder: %d",
10959 static_cast<int>(data.handle));
10960}
10961
10962void RS_FilterDXFRW::addCsacDocumentOptions(
10963 const DRW_CsacDocumentOptions &data) {
10964 if (m_graphic != nullptr) {
10965 m_graphic->dwgAdvancedMetadata().addCsacDocumentOptions(data);
10966 }
10967 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addCsacDocumentOptions: handle %d",
10968 static_cast<int>(data.handle));
10969}
10970
10971void RS_FilterDXFRW::addContextDataManager(const DRW_ContextDataManager &data) {
10972 if (m_graphic != nullptr) {
10973 m_graphic->dwgAdvancedMetadata().addContextDataManager(data);
10974 }
10975 RS_DEBUGRS_Debug::instance()->print(
10976 "RS_FilterDXFRW::addContextDataManager: handle %d sub-managers %d",
10977 static_cast<int>(data.handle), static_cast<int>(data.subManagers.size()));
10978}
10979
10980void RS_FilterDXFRW::addProxyEntity(const DRW_ProxyEntity &data) {
10981 if (m_graphic != nullptr) {
10982 m_graphic->dwgAdvancedMetadata().addProxyEntity(data);
10983 }
10984 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addProxyEntity: handle %d",
10985 static_cast<int>(data.handle));
10986}
10987
10988void RS_FilterDXFRW::addVbaProject(const DRW_VbaProject &data) {
10989 if (m_graphic != nullptr) {
10990 m_graphic->dwgAdvancedMetadata().addVbaProject(data);
10991 }
10992 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addVbaProject: handle %d bytes %d",
10993 static_cast<int>(data.handle),
10994 static_cast<int>(data.m_data.size()));
10995}
10996
10997void RS_FilterDXFRW::addProxyObject(const DRW_ProxyObject &data) {
10998 if (m_graphic != nullptr) {
10999 m_graphic->dwgAdvancedMetadata().addProxyObject(data);
11000 }
11001 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addProxyObject: handle %d",
11002 static_cast<int>(data.handle));
11003}
11004
11005void RS_FilterDXFRW::addSun(const DRW_Sun &data) {
11006 if (m_graphic != nullptr) {
11007 m_graphic->dwgAdvancedMetadata().addSun(data);
11008 }
11009 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSun: handle %d on=%d",
11010 static_cast<int>(data.handle), data.m_isOn ? 1 : 0);
11011}
11012
11013// ── Cross-read parity: metadata-only family exposure (no RS document geometry)
11014// ──
11015void RS_FilterDXFRW::addPointCloud(const DRW_PointCloud *data) {
11016 if (m_graphic != nullptr && data != nullptr) {
11017 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11018 "POINTCLOUD", data->handle, data->parentHandle, data->savedFilename);
11019 }
11020 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloud: metadata-only handle %d",
11021 data ? static_cast<int>(data->handle) : 0);
11022}
11023
11024void RS_FilterDXFRW::addPointCloudEx(const DRW_PointCloudEx *data) {
11025 if (m_graphic != nullptr && data != nullptr) {
11026 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11027 "POINTCLOUDEX", data->handle, data->parentHandle, data->name);
11028 }
11029 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudEx: metadata-only handle %d",
11030 data ? static_cast<int>(data->handle) : 0);
11031}
11032
11033void RS_FilterDXFRW::addNavisworksModel(const DRW_NavisworksModel *data) {
11034 if (m_graphic != nullptr && data != nullptr) {
11035 m_graphic->dwgAdvancedMetadata().addNavisworksModel(*data);
11036 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11037 "NAVISWORKSMODEL", data->handle, data->parentHandle);
11038 }
11039 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addNavisworksModel: metadata-only handle %d",
11040 data ? static_cast<int>(data->handle) : 0);
11041}
11042
11043void RS_FilterDXFRW::addPointCloudDef(const DRW_PointCloudDef &data) {
11044 if (m_graphic != nullptr) {
11045 m_graphic->dwgAdvancedMetadata().addPointCloudDef(data);
11046 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11047 "POINTCLOUDDEF", data.handle, data.parentHandle, data.m_sourceFilename);
11048 }
11049 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addPointCloudDef: kind %d handle %d",
11050 static_cast<int>(data.m_kind), static_cast<int>(data.handle));
11051}
11052
11053void RS_FilterDXFRW::addNavisworksModelDef(const DRW_NavisworksModelDef &data) {
11054 if (m_graphic != nullptr) {
11055 m_graphic->dwgAdvancedMetadata().addNavisworksModelDef(data);
11056 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11057 "NAVISWORKSMODELDEF", data.handle, data.parentHandle, data.m_path);
11058 }
11059 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addNavisworksModelDef: handle %d path %s",
11060 static_cast<int>(data.handle), data.m_path.c_str());
11061}
11062
11063void RS_FilterDXFRW::addPointCloudColorMap(const DRW_PointCloudColorMap &data) {
11064 if (m_graphic != nullptr) {
11065 m_graphic->dwgAdvancedMetadata().addPointCloudColorMap(data);
11066 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11067 "POINTCLOUDCOLORMAP", data.handle, data.parentHandle);
11068 }
11069 RS_DEBUGRS_Debug::instance()->print(
11070 "RS_FilterDXFRW::addPointCloudColorMap: handle %d ramps %d/%d",
11071 static_cast<int>(data.handle), static_cast<int>(data.m_colorRampCount),
11072 static_cast<int>(data.m_classificationColorRampCount));
11073}
11074
11075void RS_FilterDXFRW::addBackground(const DRW_Background &data) {
11076 if (m_graphic != nullptr) {
11077 m_graphic->dwgAdvancedMetadata().addBackground(data);
11078 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11079 "BACKGROUND", data.handle, data.parentHandle);
11080 }
11081}
11082
11083void RS_FilterDXFRW::addMaterial(const DRW_Material &data) {
11084 if (m_graphic != nullptr) {
11085 m_graphic->dwgAdvancedMetadata().addMaterial(data);
11086 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11087 "MATERIAL", data.handle, data.parentHandle, data.m_name);
11088 }
11089}
11090
11091void RS_FilterDXFRW::addRenderSettings(const DRW_RenderSettings &data) {
11092 if (m_graphic != nullptr) {
11093 m_graphic->dwgAdvancedMetadata().addRenderSettings(data);
11094 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11095 "RENDERSETTINGS", data.handle, data.parentHandle);
11096 }
11097}
11098
11099void RS_FilterDXFRW::addSunStudy(const DRW_SunStudy &data) {
11100 if (m_graphic != nullptr) {
11101 m_graphic->dwgAdvancedMetadata().addSunStudy(data);
11102 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("SUNSTUDY", data.handle,
11103 data.parentHandle);
11104 }
11105}
11106
11107void RS_FilterDXFRW::addMotionPath(const DRW_MotionPath &data) {
11108 if (m_graphic != nullptr) {
11109 m_graphic->dwgAdvancedMetadata().addMotionPath(data);
11110 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11111 "MOTIONPATH", data.handle, data.parentHandle);
11112 }
11113}
11114
11115void RS_FilterDXFRW::addCurvePath(const DRW_CurvePath &data) {
11116 if (m_graphic != nullptr) {
11117 m_graphic->dwgAdvancedMetadata().addCurvePath(data);
11118 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11119 "CURVEPATH", data.handle, data.parentHandle);
11120 }
11121}
11122
11123void RS_FilterDXFRW::addPointPath(const DRW_PointPath &data) {
11124 if (m_graphic != nullptr) {
11125 m_graphic->dwgAdvancedMetadata().addPointPath(data);
11126 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11127 "POINTPATH", data.handle, data.parentHandle);
11128 }
11129}
11130
11131void RS_FilterDXFRW::addObjectPtr(const DRW_ObjectPtr &data) {
11132 if (m_graphic != nullptr) {
11133 m_graphic->dwgAdvancedMetadata().addObjectPtr(data);
11134 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11135 "OBJECT_PTR", data.handle, data.parentHandle);
11136 }
11137}
11138
11139void RS_FilterDXFRW::addPartialViewingIndex(
11140 const DRW_PartialViewingIndex &data) {
11141 if (m_graphic != nullptr) {
11142 m_graphic->dwgAdvancedMetadata().addPartialViewingIndex(data);
11143 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11144 "PARTIAL_VIEWING_INDEX", data.handle, data.parentHandle);
11145 }
11146}
11147
11148void RS_FilterDXFRW::addDbColor(const DRW_DbColor &data) {
11149 if (m_graphic != nullptr) {
11150 m_graphic->dwgAdvancedMetadata().addDbColor(data);
11151 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("DBCOLOR", data.handle,
11152 data.parentHandle);
11153 }
11154}
11155
11156void RS_FilterDXFRW::addDimensionAssociation(
11157 const DRW_DimensionAssociation &data) {
11158 if (m_graphic != nullptr) {
11159 m_graphic->dwgAdvancedMetadata().addDimensionAssociation(data);
11160 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("DIMASSOC", data.handle,
11161 data.parentHandle);
11162 }
11163}
11164
11165void RS_FilterDXFRW::addEvaluationGraph(const DRW_EvaluationGraph &data) {
11166 if (m_graphic != nullptr) {
11167 m_graphic->dwgAdvancedMetadata().addEvaluationGraph(data);
11168 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11169 "EVALUATION_GRAPH", data.handle, data.parentHandle);
11170 }
11171}
11172
11173void RS_FilterDXFRW::addBlockRepresentationData(
11174 const DRW_BlockRepresentationData &data) {
11175 if (m_graphic != nullptr) {
11176 m_graphic->dwgAdvancedMetadata().addBlockRepresentationData(data);
11177 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11178 "BLOCKREPRESENTATIONDATA", data.handle, data.parentHandle);
11179 }
11180}
11181
11182void RS_FilterDXFRW::addSection(const DRW_Section &data) {
11183 if (m_graphic != nullptr) {
11184 m_graphic->dwgAdvancedMetadata().addSection(data);
11185 m_graphic->dwgAdvancedMetadata().noteFamilyExposure("SECTION", data.handle,
11186 data.parentHandle);
11187 }
11188}
11189
11190void RS_FilterDXFRW::addSectionObject(const DRW_SectionObject &data) {
11191 if (m_graphic != nullptr) {
11192 m_graphic->dwgAdvancedMetadata().addSectionObject(data);
11193 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11194 "SECTIONOBJECT", data.handle, data.parentHandle);
11195 }
11196}
11197
11198void RS_FilterDXFRW::addMPolygon(const DRW_MPolygon *data) {
11199 // Prefer hatch rendering path for geometry, but also record metadata
11200 // exposure.
11201 if (m_graphic != nullptr && data != nullptr) {
11202 m_graphic->dwgAdvancedMetadata().noteFamilyExposure(
11203 "MPOLYGON", data->handle, data->parentHandle);
11204 }
11205 addHatch(data);
11206}
11207
11208void RS_FilterDXFRW::addDictionary(const DRW_Dictionary &data) {
11209 if (m_graphic != nullptr) {
11210 m_graphic->dwgAdvancedMetadata().addDictionary(data);
11211 }
11212 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionary: handle %d entries=%d",
11213 static_cast<int>(data.handle),
11214 static_cast<int>(data.m_entries.size()));
11215}
11216
11217void RS_FilterDXFRW::addXRecord(const DRW_XRecord &data) {
11218 if (m_graphic != nullptr) {
11219 m_graphic->dwgAdvancedMetadata().addXRecord(data);
11220 }
11221 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addXRecord: handle %d values=%d",
11222 static_cast<int>(data.handle),
11223 static_cast<int>(data.m_values.size()));
11224}
11225
11226void RS_FilterDXFRW::addLayout(const DRW_Layout &data) {
11227 if (m_graphic != nullptr) {
11228 m_graphic->dwgAdvancedMetadata().addLayout(data);
11229 // PR 11 — populate activeLayoutHandle on first import. AutoCAD uses
11230 // tabOrder=0 for the default-visible paper-space layout; if no layout
11231 // carries tabOrder=0 (rare — most DWGs have one), fall back to the
11232 // first layout added so the UI still has a non-zero handle to display.
11233 // setActiveLayoutHandle bumps the modified flag only on actual change,
11234 // so re-loading the same DWG idempotently leaves the dirty state alone.
11235 if (m_graphic->activeLayoutHandle() == 0u && data.handle != 0u) {
11236 const bool isDefaultTab = data.tabOrder == 0;
11237 const bool isFirstLayout = m_graphic->layouts().size() == 1;
11238 if (isDefaultTab || isFirstLayout) {
11239 m_graphic->setActiveLayoutHandle(data.handle);
11240 }
11241 }
11242 }
11243 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayout: handle %d name=%s viewports=%d",
11244 static_cast<int>(data.handle), data.name.c_str(),
11245 static_cast<int>(data.viewportHandles.size()));
11246}
11247
11248// PR 8d.2a — five small no-storage OBJECTS families. All custom-class
11249// (≥ 500); the filter routes each call into round-trip-grade metadata
11250// storage so RS_FilterDXFRW::writeObjects can dispatch the native writer.
11251void RS_FilterDXFRW::addScale(const DRW_Scale &data) {
11252 if (m_graphic != nullptr) {
11253 m_graphic->dwgAdvancedMetadata().addScale(data);
11254 }
11255 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addScale: handle %d name=%s factor=%f",
11256 static_cast<int>(data.handle), data.name.c_str(),
11257 data.scaleFactor());
11258}
11259
11260void RS_FilterDXFRW::addIDBuffer(const DRW_IDBuffer &data) {
11261 if (m_graphic != nullptr) {
11262 m_graphic->dwgAdvancedMetadata().addIDBuffer(data);
11263 }
11264 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addIDBuffer: handle %d ids=%d",
11265 static_cast<int>(data.handle),
11266 static_cast<int>(data.objIds.size()));
11267}
11268
11269void RS_FilterDXFRW::addIndex(const DRW_Index &data) {
11270 if (m_graphic != nullptr) {
11271 m_graphic->dwgAdvancedMetadata().addIndex(data);
11272 }
11273 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addIndex: handle %d ts=%u/%u",
11274 static_cast<int>(data.handle),
11275 static_cast<unsigned>(data.timestamp1),
11276 static_cast<unsigned>(data.timestamp2));
11277}
11278
11279void RS_FilterDXFRW::addLayerIndex(const DRW_LayerIndex &data) {
11280 if (m_graphic != nullptr) {
11281 m_graphic->dwgAdvancedMetadata().addLayerIndex(data);
11282 }
11283 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addLayerIndex: handle %d entries=%d",
11284 static_cast<int>(data.handle),
11285 static_cast<int>(data.entries.size()));
11286}
11287
11288void RS_FilterDXFRW::addSpatialIndex(const DRW_SpatialIndex &data) {
11289 if (m_graphic != nullptr) {
11290 m_graphic->dwgAdvancedMetadata().addSpatialIndex(data);
11291 }
11292 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addSpatialIndex: handle %d ts=%u/%u",
11293 static_cast<int>(data.handle),
11294 static_cast<unsigned>(data.timestamp1),
11295 static_cast<unsigned>(data.timestamp2));
11296}
11297
11298void RS_FilterDXFRW::addDictionaryVar(const DRW_DictionaryVar &data) {
11299 if (m_graphic != nullptr) {
11300 m_graphic->dwgAdvancedMetadata().addDictionaryVar(data);
11301 }
11302 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryVar: handle %d schema=%d",
11303 static_cast<int>(data.handle), data.m_schema);
11304}
11305
11306// PR 8d.2b — four larger no-storage OBJECTS families. All custom-class
11307// (513-516); filter routes each call into round-trip-grade metadata storage
11308// so RS_FilterDXFRW::writeObjects can dispatch the native writer.
11309void RS_FilterDXFRW::addDictionaryWithDefault(
11310 const DRW_DictionaryWithDefault &data) {
11311 if (m_graphic != nullptr) {
11312 m_graphic->dwgAdvancedMetadata().addDictionaryWithDefault(data);
11313 }
11314 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addDictionaryWithDefault: handle %d "
11315 "entries=%d default=%d",
11316 static_cast<int>(data.handle),
11317 static_cast<int>(data.m_entries.size()),
11318 static_cast<int>(data.m_defaultEntryHandle));
11319}
11320
11321void RS_FilterDXFRW::addSortEntsTable(const DRW_SortEntsTable &data) {
11322 if (m_graphic != nullptr) {
11323 m_graphic->dwgAdvancedMetadata().addSortEntsTable(data);
11324 }
11325 RS_DEBUGRS_Debug::instance()->print(
11326 "RS_FilterDXFRW::addSortEntsTable: handle %d entries=%d block=%d",
11327 static_cast<int>(data.handle),
11328 static_cast<int>(data.m_entityHandles.size()),
11329 static_cast<int>(data.m_blockOwnerHandle));
11330}
11331
11332void RS_FilterDXFRW::addFieldList(const DRW_FieldList &data) {
11333 if (m_graphic != nullptr) {
11334 m_graphic->dwgAdvancedMetadata().addFieldList(data);
11335 }
11336 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addFieldList: handle %d fields=%d",
11337 static_cast<int>(data.handle),
11338 static_cast<int>(data.m_fieldHandles.size()));
11339}
11340
11341void RS_FilterDXFRW::addDataTable(const DRW_DataTable &data) {
11342 if (m_graphic != nullptr) {
11343 m_graphic->dwgAdvancedMetadata().addDataTable(data);
11344 }
11345 RS_DEBUGRS_Debug::instance()->print(
11346 "RS_FilterDXFRW::addDataTable: handle %d name=%s cols=%d rows=%d",
11347 static_cast<int>(data.handle), data.tableName.c_str(),
11348 static_cast<int>(data.columnCount), static_cast<int>(data.rowCount));
11349}
11350
11351void RS_FilterDXFRW::addDynamicBlockObject(const DRW_DynamicBlockObject &data) {
11352 if (m_graphic != nullptr) {
11353 m_graphic->dwgAdvancedMetadata().addDynamicBlockObject(data);
11354 }
11355 RS_DEBUGRS_Debug::instance()->print(
11356 "RS_FilterDXFRW::addDynamicBlockObject: handle %d rec=%s kind=%d",
11357 static_cast<int>(data.handle), data.m_recordName.c_str(),
11358 static_cast<int>(data.m_kind));
11359}
11360
11361void RS_FilterDXFRW::addField(const DRW_Field &data) {
11362 if (m_graphic != nullptr) {
11363 m_graphic->dwgAdvancedMetadata().addField(data);
11364 }
11365 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addField: handle %d evaluator=%s code=%s",
11366 static_cast<int>(data.handle), data.m_evaluatorId.c_str(),
11367 data.m_fieldCode.c_str());
11368}
11369
11370void RS_FilterDXFRW::addAssociativeObject(const DRW_AssociativeObject &data) {
11371 // TODO: Reconstruct associative dimension/dynamic-block relationship graphs
11372 // from these shell objects after native consumers exist.
11373 if (m_graphic != nullptr) {
11374 m_graphic->dwgAdvancedMetadata().addAssociativeObject(data);
11375 }
11376 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAssociativeObject: %s handle %d",
11377 data.m_recordName.empty() ? "(assoc)"
11378 : data.m_recordName.c_str(),
11379 static_cast<int>(data.handle));
11380}
11381
11382void RS_FilterDXFRW::addAcShHistoryObject(const DRW_AcShHistoryObject &data) {
11383 // TODO: Connect ACSH history nodes to 3DSOLID modeler geometry history.
11384 if (m_graphic != nullptr) {
11385 m_graphic->dwgAdvancedMetadata().addAcShObject(data);
11386 }
11387 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addAcShHistoryObject: %s handle %d",
11388 data.m_recordName.empty() ? "(acsh)"
11389 : data.m_recordName.c_str(),
11390 static_cast<int>(data.handle));
11391}
11392
11393/**
11394 * Implementation of the method which links image entities to image files.
11395 */
11396void RS_FilterDXFRW::linkImage(const DRW_ImageDef *data) {
11397 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::linkImage");
11398 if (m_graphic != nullptr && data != nullptr)
11399 m_graphic->dwgAdvancedMetadata().addImageDefinition(*data);
11400
11401 if (data == nullptr)
11402 return;
11403
11404 const int handle = data->handle;
11405 QString sfile(QString::fromUtf8(data->name.c_str()));
11406 const QFileInfo fiDxf(m_file);
11407 const QFileInfo fiBitmap(sfile);
11408
11409 // try to find the image file:
11410
11411 // first: absolute path:
11412 if (!fiBitmap.exists()) {
11413 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
11414 (const char *)QFile::encodeName(sfile));
11415 // try relative path:
11416 const QString f1 = fiDxf.absolutePath() + "/" + sfile;
11417 if (QFileInfo::exists(f1)) {
11418 sfile = f1;
11419 } else {
11420 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
11421 (const char *)QFile::encodeName(f1));
11422 // try drawing path:
11423 const QString f2 = fiDxf.absolutePath() + "/" + fiBitmap.fileName();
11424 if (QFileInfo::exists(f2)) {
11425 sfile = f2;
11426 } else {
11427 RS_DEBUGRS_Debug::instance()->print("File %s doesn't exist.",
11428 (const char *)QFile::encodeName(f2));
11429 }
11430 }
11431 }
11432
11433 // Also link images in subcontainers (e.g. inserts):
11434 for (RS_Entity *e :
11435 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
11436 if (e->rtti() == RS2::EntityImage) {
11437 const auto img = static_cast<RS_Image *>(e);
11438 if (img->getHandle() == handle) {
11439 img->setFile(sfile);
11440 RS_DEBUGRS_Debug::instance()->print("image found: %s",
11441 (const char *)QFile::encodeName(img->getFile()));
11442 img->update();
11443 }
11444 }
11445 }
11446
11447 // update images in blocks:
11448 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
11449 const RS_Block *b = m_graphic->blockAt(i);
11450 for (RS_Entity *e :
11451 lc::LC_ContainerTraverser{*b, RS2::ResolveNone}.entities()) {
11452 if (e->rtti() == RS2::EntityImage) {
11453 const auto img = static_cast<RS_Image *>(e);
11454 if (img->getHandle() == handle) {
11455 img->setFile(sfile);
11456 RS_DEBUGRS_Debug::instance()->print("image in block found: %s",
11457 (const char *)QFile::encodeName(img->getFile()));
11458 img->update();
11459 }
11460 }
11461 }
11462 }
11463 RS_DEBUGRS_Debug::instance()->print("linking image: OK");
11464}
11465
11466using std::map;
11467/**
11468 * Sets the header variables from the DXF file.
11469 */
11470void RS_FilterDXFRW::addHeader(const DRW_Header *data) {
11471 if (data == nullptr || m_currentContainer == nullptr ||
11472 m_currentContainer->rtti() != RS2::EntityGraphic) {
11473 return;
11474 }
11475 RS_Graphic *container = static_cast<RS_Graphic *>(m_currentContainer);
11476
11477 // initialize units vars if not are present in dxf file - they will be
11478 // overriden if they are present
11479 m_graphic->addVariable("$LUNITS", 2, 70);
11480 m_graphic->addVariable("$LUPREC", 4, 70);
11481 m_graphic->addVariable("$AUNITS", 0, 70);
11482 m_graphic->addVariable("$AUPREC", 4, 70);
11483 // initialize points drawing style vars if not present in dxf file
11484 m_graphic->addVariable("$PDMODE", LC_DEFAULTS_PDMode(0|96), DXF_FORMAT_GC_VarName9);
11485 m_graphic->addVariable("$PDSIZE", LC_DEFAULTS_PDSize(- 2.0), DXF_FORMAT_GC_VarName9);
11486 m_graphic->addVariable("$JOINSTYLE", 1, DXF_FORMAT_GC_JoinStyle280);
11487 m_graphic->addVariable("$ENDCAPS", 1, DXF_FORMAT_GC_Endcaps280);
11488
11489 for (auto it = data->vars.begin(); it != data->vars.end(); ++it) {
11490 QString key = QString::fromStdString((*it).first);
11491 // DWG-read header vars arrive under bare keys (e.g. "LUPREC"), while the
11492 // app (and the DXF path) query them $-prefixed ("$LUPREC"). Normalize
11493 // here so DWG-stored vars are reachable. DXF keys already start with '$'
11494 // (drw_header.cpp), so the guard makes this a no-op for that path and
11495 // prevents double-prefixing ("$$ACADVER").
11496 if (!key.startsWith('$')) {
11497 key.prepend('$');
11498 }
11499 DRW_Variant *var = (*it).second;
11500 if (var == nullptr)
11501 continue;
11502 switch (var->type()) {
11503 case DRW_Variant::COORD:
11504 if (var->content.v != nullptr && std::isfinite(var->content.v->x) &&
11505 std::isfinite(var->content.v->y) &&
11506 std::isfinite(var->content.v->z)) {
11507 container->addVariable(
11508 key,
11509 RS_Vector(var->content.v->x, var->content.v->y, var->content.v->z),
11510 var->code());
11511 }
11512 break;
11513 case DRW_Variant::STRING:
11514 if (var->content.s != nullptr)
11515 container->addVariable(key, QString::fromUtf8(var->content.s->c_str()),
11516 var->code());
11517 break;
11518 case DRW_Variant::INTEGER:
11519 container->addVariable(key, var->content.i, var->code());
11520 break;
11521 case DRW_Variant::DOUBLE:
11522 if (std::isfinite(var->content.d))
11523 container->addVariable(key, var->content.d, var->code());
11524 break;
11525 default:
11526 break;
11527 }
11528 }
11529
11530 for (auto it = data->customVars.begin(); it != data->customVars.end(); ++it) {
11531 QString key = QString::fromStdString(it->first);
11532 const DRW_Variant *var = it->second;
11533 if (var != nullptr && var->type() == DRW_Variant::STRING &&
11534 var->content.s != nullptr) {
11535 container->addCustomProperty(key,
11536 QString::fromUtf8(var->content.s->c_str()));
11537 }
11538 }
11539
11540 m_codePage = m_graphic->getVariableString("$DWGCODEPAGE", "ANSI_1252");
11541 m_textStyle = m_graphic->getVariableString("$TEXTSTYLE", "Standard");
11542 m_dimStyle = m_graphic->getVariableString("$DIMSTYLE", "Standard");
11543
11544 QString acadver = m_graphic->getVariableString("$ACADVER", "");
11545 m_versionStr = acadver;
11546 acadver.replace(QRegularExpression("[a-zA-Z]"), "");
11547 bool ok;
11548 const int parsedVersion = acadver.toInt(&ok);
11549 if (ok) {
11550 m_version = parsedVersion;
11551 } else if (m_version == 0) {
11552 // Pre-R13 DWG may omit $ACADVER; fileImport primes m_version from the
11553 // on-disk magic before entities are read.
11554 m_version = 1021;
11555 }
11556
11557 // detect if dxf lib are a old dxflib or libdxfrw < 0.5.4 (used to correct
11558 // mtext alignment)
11559 m_oldMText = false;
11560 m_isLibDxfRw = false;
11561 m_libDxfRwVersion = 0;
11562 m_dxflibMajor = 0;
11563 m_legacyHatchRadians = false;
11564#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
11565 constexpr auto option = Qt::SkipEmptyParts;
11566#else
11567 auto option = QString::SkipEmptyParts;
11568#endif
11569 QStringList commentList =
11570 QString::fromStdString(data->getComments()).split('\n', option);
11571 for (const auto &commentLine : std::as_const(commentList)) {
11572 QStringList commentWords = commentLine.split(' ', option);
11573 if (0 < commentWords.size()) {
11574 if ("dxflib" == commentWords.at(0)) {
11575 m_oldMText = true;
11576 // Record the dxflib major version so the hatch-angle reader can
11577 // tell LibreCAD 1.x / QCAD 2 (raw radians) from QCAD 3 (degrees).
11578 if (1 < commentWords.size()) {
11579 m_dxflibMajor = commentWords.at(1).section('.', 0, 0).toInt();
11580 }
11581 break;
11582 }
11583 if ("dxfrw" == commentWords.at(0)) {
11584 if (1 < commentWords.size()) {
11585 const QStringList libVersionList =
11586 commentWords.at(1).split('.', option);
11587 if (2 < libVersionList.size()) {
11588 m_isLibDxfRw = true;
11589 m_libDxfRwVersion = LIBDXFRW_VERSION(libVersionList.at(0).toInt(),(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
11590 libVersionList.at(1).toInt(),(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
11591 libVersionList.at(2).toInt())(((libVersionList.at(0).toInt()) << 16) | ((libVersionList
.at(1).toInt()) << 8) | (libVersionList.at(2).toInt()))
;
11592 if (m_libDxfRwVersion < LIBDXFRW_VERSION(0, 5, 4)(((0) << 16) | ((5) << 8) | (4))) {
11593 m_oldMText = true;
11594 }
11595 }
11596 }
11597 break;
11598 }
11599 }
11600 }
11601 // Legacy writers stored DXF hatch angle 52 raw. Every LibreCAD 2.x file
11602 // (dxfrw < 0.6.4) and every dxflib 1.x / 2.x file; the standalone fork's
11603 // 2.0.0 keeps raw too. Everything else, including bare "dxfrw" and
11604 // dxflib 3+ (QCAD 3), is a degree writer.
11605 m_legacyHatchRadians =
11606 (1 <= m_dxflibMajor && m_dxflibMajor <= 2)
11607 || (m_isLibDxfRw
11608 && (m_libDxfRwVersion < kFirstDegreeHatchWriter
11609 || m_libDxfRwVersion == kRawHatchForkVersion));
11610}
11611
11612double RS_FilterDXFRW::importedHatchAngle(double drwAngle, bool legacyRadians,
11613 bool solid) {
11614 // libdxfrw now hands every DXF 52 value in radians (= raw / ARAD). Solids
11615 // have no geometric angle, so leave them alone except for the normalisation
11616 // that setAngle() applies.
11617 double a = drwAngle;
11618 if (legacyRadians && !solid) {
11619 // The number that stood in group 52 before libdxfrw's divide: use it for
11620 // the tie-break of the three cases below.
11621 const double x = drwAngle * ARAD57.29577951308232;
11622 if (std::fabs(x) <= 2.0 * M_PI3.14159265358979323846 + 1.0e-9) {
11623 // Within [-2pi, 2pi]: legacy LibreCAD/QCAD2 radians stored raw.
11624 a = x;
11625 } else {
11626 // "Nice degrees" (a multiple of 0.5 deg) that is not already a nice
11627 // radian value marks a DWG-origin radian value above 2pi (e.g. 7.854
11628 // = 450 deg). Keep that interpretation.
11629 const auto isNiceDegrees = [](double v) {
11630 return std::fabs(v - 0.5 * std::round(2.0 * v)) <= 1.0e-6;
11631 };
11632 if (isNiceDegrees(x * ARAD57.29577951308232) && !isNiceDegrees(x)) {
11633 a = x;
11634 }
11635 // else: a degree value an older LibreCAD read raw and saved again.
11636 // libdxfrw already divided by ARAD, so keep a = drwAngle.
11637 }
11638 }
11639 return RS_Math::correctAngle(a);
11640}
11641
11642/**
11643 * Implementation of the method used for RS_Export to communicate
11644 * with this filter.
11645 *
11646 * @param g
11647 * @param file Full path to the DXF file that will be written.
11648 * @param type
11649 */
11650bool RS_FilterDXFRW::fileExport(RS_Graphic &g, const QString &file,
11651 const RS2::FormatType type) {
11652 // Also protect callers using the filter directly rather than RS_FileIO.
11653 if (g.isDxfRecoverySourceFile(file)) {
11654 m_errorCode = DRW::BAD_OPEN;
11655 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr(
11656 "The original legacy DXF is protected. Save the recovered drawing to a different file."));
11657 return false;
11658 }
11659 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: exporting file '%s'...",
11660 (const char *)QFile::encodeName(file));
11661 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: file type '%d'", type);
11662
11663 // A callback may not replace the active writer or the outer operation's
11664 // graphic/failure state. In particular, do not clear its observer here.
11665 if (m_dxfW != nullptr
11666#ifdef DWGSUPPORT1
11667 || m_dwgW != nullptr
11668#endif
11669 ) {
11670 LC_ERRRS_Debug::Log(RS_Debug::D_ERROR) << "Cannot start a reentrant export on an active DXF/DWG filter";
11671 return false;
11672 }
11673
11674 this->m_graphic = &g;
11675 m_writeFailed = false;
11676 m_dxfLeftOutRawRecords = 0;
11677 m_writeLeftOut.clear();
11678 m_shapeLeftOut.clear();
11679 m_shapeExportDirectory = QFileInfo(file).absoluteDir().canonicalPath();
11680 m_exportAttribScaleDataLeftOut = 0;
11681 // What becomes of each block attribute's text is the same for every
11682 // callback of this export, the DWG handle pre-pass included.
11683 buildInsertAttribPlan();
11684 struct InsertAttribPlanGuard {
11685 InsertAttribPlan &plan;
11686 ~InsertAttribPlanGuard() { plan = {}; }
11687 } insertAttribPlanGuard{m_insertAttribPlan};
11688
11689 // check if we can write to that directory:
11690#ifndef Q_OS_WIN
11691
11692 QString path = QFileInfo(file).absolutePath();
11693 if (QFileInfo(path).isWritable() == false) {
11694 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::fileExport: can't write file: "
11695 "no permission");
11696 return false;
11697 }
11698 //
11699#endif
11700
11701#ifdef DWGSUPPORT1
11702 auto dwgVersionForFormat = [](RS2::FormatType format) {
11703 switch (format) {
11704 case RS2::FormatDWG:
11705 return DRW::AC1015;
11706 case RS2::FormatDWG2004:
11707 return DRW::AC1018;
11708 case RS2::FormatDWG2007:
11709 return DRW::AC1021;
11710 case RS2::FormatDWG2010:
11711 return DRW::AC1024;
11712 case RS2::FormatDWG2013:
11713 return DRW::AC1027;
11714 case RS2::FormatDWG2018:
11715 return DRW::AC1032;
11716 default:
11717 return DRW::UNKNOWNV;
11718 }
11719 };
11720 DRW::Version dwgVer = dwgVersionForFormat(type);
11721 if (dwgVer != DRW::UNKNOWNV) {
11722 switch (dwgVer) {
11723 case DRW::AC1015:
11724 m_version = 1015;
11725 break;
11726 case DRW::AC1018:
11727 m_version = 1018;
11728 break;
11729 case DRW::AC1021:
11730 m_version = 1021;
11731 break;
11732 case DRW::AC1024:
11733 m_version = 1024;
11734 break;
11735 case DRW::AC1027:
11736 m_version = 1027;
11737 break;
11738 case DRW::AC1032:
11739 m_version = 1032;
11740 break;
11741 default:
11742 break;
11743 }
11744 m_exactColor = false;
11745 m_lastDwgWriteSkipCounters = {};
11746 m_lastDwgOptionalReferenceDropCount = 0;
11747 // Keep failed filter-side validation from exposing a partial target.
11748 // The low-level writer already removes its own incomplete output, but
11749 // the filter can discover dropped metadata after that writer succeeds.
11750 const QFileInfo outputInfo(file);
11751 QTemporaryFile stagedOutput(outputInfo.absolutePath() + "/." +
11752 outputInfo.fileName() + ".librecad-XXXXXX");
11753 stagedOutput.setAutoRemove(true);
11754 if (!stagedOutput.open()) {
11755 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11756 "RS_FilterDXFRW: cannot create staged DWG output");
11757 return false;
11758 }
11759 const QString stagedOutputPath = stagedOutput.fileName();
11760 stagedOutput.close();
11761 // QTemporaryFile creates the name eagerly. Remove its empty placeholder
11762 // before dwgRW opens its own transaction: Windows cannot reliably replace
11763 // that still-associated temporary path with MoveFileExW. The unique name
11764 // remains reserved for this process, and the outer staged commit still
11765 // protects the caller's destination from partial output.
11766 if (!stagedOutput.remove()) {
11767 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11768 "RS_FilterDXFRW: cannot release staged DWG path");
11769 return false;
11770 }
11771 // Having given the name up, the QTemporaryFile no longer removes it. An
11772 // export that fails after the low-level writer published the staged file
11773 // has to remove it here, or it stays beside the caller's destination. A
11774 // committed export has moved it away and leaves nothing to remove.
11775 const auto removeStagedOutput =
11776 qScopeGuard([&stagedOutputPath] { QFile::remove(stagedOutputPath); });
11777 ExportWriterScope<dwgRW> activeWriter(
11778 m_dwgW, std::make_unique<dwgRW>(QFile::encodeName(stagedOutputPath).constData()));
11779 prepareShapesForExport();
11780 if (m_writeFailed)
11781 return false;
11782 clearDwgWriteIdentity();
11783 m_dwgExportAdmissionPlan = {};
11784 m_dwgFieldWritePlan = {};
11785 m_dwgOpaqueTableRuntimeFailures.clear();
11786 m_dwgDataStorageCommittedKinds.clear();
11787 m_dwgDataStorageCommitReceipts.clear();
11788 m_dwgWriteDataStorageKnownHandles.clear();
11789 if (!registerDwgWriteFixedStructuralIdentity(
11790 DRW::DwgNamedObjectsDictionaryHandle) ||
11791 !registerDwgWriteFixedStructuralIdentity(
11792 DRW::DwgAcadGroupDictionaryHandle)) {
11793 return false;
11794 }
11795 if (!prepareDwgDataStorageReplayPlan(dwgVer)) {
11796 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11797 "RS_FilterDXFRW: invalid DWG DataStorage replay plan");
11798 return false;
11799 }
11800 bool duplicateDwgMetadataHandle = false;
11801 // P3 #1: reserve every preserved fixed-type OBJECT + raw-object handle
11802 // BEFORE write() so defineBlock() (which mints user-block handles from
11803 // 0x30) can never mint a handle a later object re-emits -> no duplicate
11804 // object-map entry -> no writeDwgHandles() failure -> no BAD_OPEN whole-
11805 // file abort. Mirrors the DXF reserve pass (this DWG branch previously
11806 // had none); reserving a handle that is ultimately not emitted is
11807 // harmless (next() simply skips it). Validated by the [dwg-write][reserve]
11808 // unit test.
11809 {
11810 const auto &md = g.dwgAdvancedMetadata();
11811 // High-water mark over every preserved handle; remap targets are
11812 // minted above it (and above 0x2F, the writer's minting floor).
11813 std::uint32_t highWater = 0x2F;
11814 for (std::uint32_t handle = 1;
11815 handle <= DRW::DwgPaperSpaceEndBlockEntityHandle; ++handle) {
11816 if (isFixedStructuralDwgHandle(handle))
11817 m_dwgWriteKnownHandles.insert(handle);
11818 }
11819 m_dwgWriteKnownHandles.insert(DRW::DwgNamedObjectsDictionaryHandle);
11820 m_dwgWriteKnownHandles.insert(DRW::DwgAcadGroupDictionaryHandle);
11821 const auto isVersionEffectiveTarget = [dwgVer](const char *kind) {
11822 // These object families are not emitted before their native
11823 // writer gate. They must not validate references in older
11824 // output just because their source handles were reserved.
11825 if (std::strcmp(kind, "SUN") == 0 ||
11826 std::strcmp(kind, "DIMENSIONASSOCIATION") == 0 ||
11827 std::strcmp(kind, "EVALUATIONGRAPH") == 0 ||
11828 std::strcmp(kind, "SECTION") == 0)
11829 return dwgVer >= DRW::AC1021;
11830 if (std::strcmp(kind, "DBCOLOR") == 0)
11831 return dwgVer >= DRW::AC1018;
11832 return true;
11833 };
11834 std::map<std::uint32_t, const char *> metadataHandleOwners;
11835 auto reserveAll = [&](const auto &records, const char *kind,
11836 bool includeInKnownHandles = true,
11837 RS_FilterDXFRW_DwgWriteIdentityRegistry::
11838 DwgWriteSourceKind sourceKind =
11839 RS_FilterDXFRW_DwgWriteIdentityRegistry::
11840 DwgWriteSourceKind::Object) {
11841 for (const auto &r : records)
11842 if (r.handle != 0) {
11843 noteDwgWriteSourceKind(r.handle, sourceKind);
11844 const auto [it, inserted] =
11845 metadataHandleOwners.emplace(r.handle, kind);
11846 if (!inserted) {
11847 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
11848 "RS_FilterDXFRW: duplicate DWG metadata "
11849 "handle 0x%X (%s and %s)",
11850 r.handle, it->second, kind);
11851 duplicateDwgMetadataHandle = true;
11852 }
11853 m_dwgW->reserveHandle(r.handle);
11854 highWater = std::max<std::uint32_t>(highWater, r.handle);
11855 // Reservation protects the allocator even when a
11856 // record is later suppressed. Reference validation
11857 // must use only identities that the current replay
11858 // state permits us to emit.
11859 if constexpr (hasDwgReplayState<std::decay_t<decltype(r)>>::value) {
11860 if (includeInKnownHandles && isVersionEffectiveTarget(kind) &&
11861 r.replayState ==
11862 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
11863 m_dwgWriteKnownHandles.insert(r.handle);
11864 }
11865 }
11866 };
11867 // Reservation protects a source handle from allocator collisions,
11868 // but dictionary targets must only include objects that this
11869 // export can actually emit. A cross-version, fixed-handle, or
11870 // malformed raw carrier is reserved below but is not a valid
11871 // dictionary target.
11872 for (const auto &r : md.rawObjects()) {
11873 if (r.handle == 0)
11874 continue;
11875 noteDwgWriteSourceKind(
11876 r.handle, r.isEntity ? RS_FilterDXFRW_DwgWriteIdentityRegistry::
11877 DwgWriteSourceKind::Entity
11878 : RS_FilterDXFRW_DwgWriteIdentityRegistry::
11879 DwgWriteSourceKind::Object);
11880 m_dwgW->reserveHandle(r.handle);
11881 highWater = std::max<std::uint32_t>(highWater, r.handle);
11882 if (!r.isEntity && sameRawObjectEncodingFamily(r.version, dwgVer) &&
11883 !isFixedStructuralDwgHandle(r.handle) &&
11884 LC_DwgAdvancedMetadata::rawReplayBlocker(r) ==
11885 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
11886 !r.hasDataStorage) {
11887 // DataStorage-backed raw objects are admitted only by the
11888 // frozen owner decision in writeDwgClasses()/OBJECTS.
11889 // This early pass only publishes ordinary raw targets;
11890 // every raw handle is reserved above regardless.
11891 m_dwgWriteKnownHandles.insert(r.handle);
11892 }
11893 }
11894 // These table records are emitted by deferred writers or by the
11895 // later OBJECTS pass. Reserve their source identities before
11896 // structural remap targets are minted. They are published as
11897 // reference targets only after their matching writer accepts
11898 // them.
11899 reserveAll(md.views(), "VIEW", false);
11900 reserveAll(md.ucsRecords(), "UCS", false);
11901 reserveAll(md.vports(), "VPORT", false);
11902 reserveAll(md.tables(), "TABLE", false);
11903 reserveAll(md.shapes(), "SHAPE", false, DwgWriteSourceKind::Entity);
11904 reserveAll(md.placeholders(), "PLACEHOLDER");
11905 reserveAll(md.dictionaries(), "DICTIONARY");
11906 reserveAll(md.xrecords(), "XRECORD");
11907 reserveAll(md.groups(), "GROUP");
11908 reserveAll(md.layouts(), "LAYOUT");
11909 reserveAll(md.mlineStyles(), "MLINESTYLE");
11910 reserveAll(md.dictionaryVars(), "DICTIONARYVAR");
11911 reserveAll(md.fieldLists(), "FIELDLIST");
11912 reserveAll(md.fields(), "FIELD");
11913 reserveAll(md.suns(), "SUN");
11914 reserveAll(md.mleaderStyles(), "MLEADERSTYLE");
11915 reserveAll(md.materials(), "MATERIAL");
11916 reserveAll(md.lights(), "LIGHT");
11917 reserveAll(md.lightLists(), "LIGHTLIST");
11918 if (dwgVer >= DRW::AC1021) {
11919 for (const auto &light : md.lights()) {
11920 if (light.handle != 0 &&
11921 light.replayState ==
11922 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
11923 m_dwgWriteLightHandlesPlanned.insert(light.handle);
11924 }
11925 }
11926 }
11927 // DRW_VbaProject is a low-level carrier without ReplayState. Its
11928 // handle must remain reserved, but it is not a typed reference
11929 // target in the current writer contract.
11930 reserveAll(md.vbaProjects(), "VBA_PROJECT", false);
11931 // PLOTSETTINGS is likewise emitted only as an owned custom
11932 // object, never as a typed reference target in this writer.
11933 reserveAll(md.plotSettings(), "PLOTSETTINGS", false);
11934 reserveAll(md.rasterVariables(), "RASTERVARIABLES");
11935 reserveAll(md.wipeoutVariables(), "WIPEOUTVARIABLES");
11936 reserveAll(md.geoData(), "GEODATA");
11937 reserveAll(md.spatialFilters(), "SPATIAL_FILTER");
11938 reserveAll(md.scales(), "SCALE");
11939 reserveAll(md.idBuffers(), "IDBUFFER");
11940 reserveAll(md.layerIndexes(), "LAYER_INDEX");
11941 reserveAll(md.spatialIndexes(), "SPATIAL_INDEX");
11942 reserveAll(md.dictionariesWithDefault(), "DICTIONARYWDFLT");
11943 reserveAll(md.sortEntsTables(), "SORTENTSTABLE");
11944 // Sort keys need not name objects; reserve them before allocating handles.
11945 for (const auto &record : md.sortEntsTables()) {
11946 if (record.replayState !=
11947 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
11948 continue;
11949 for (const std::uint32_t sortHandle : record.sortHandles) {
11950 if (sortHandle == DRW::NoHandle)
11951 continue;
11952 m_dwgW->reserveHandle(sortHandle);
11953 highWater = std::max<std::uint32_t>(highWater, sortHandle);
11954 }
11955 }
11956 reserveAll(md.dbColors(), "DBCOLOR");
11957 reserveAll(md.underlayDefinitions(), "UNDERLAYDEFINITION");
11958 reserveAll(md.pointCloudDefinitions(), "POINTCLOUDDEF");
11959 reserveAll(md.navisworksModelDefinitions(), "NAVISWORKSMODELDEF");
11960 reserveAll(md.navisworksModels(), "NAVISWORKSMODEL");
11961 reserveAll(md.pointCloudColorMaps(), "POINTCLOUDCOLORMAP");
11962 reserveAll(md.cameras(), "CAMERA");
11963 reserveAll(md.geoPositionMarkers(), "GEOPOSITIONMARKER");
11964 reserveAll(md.sectionObjects(), "SECTIONOBJECT");
11965 reserveAll(md.tvDeviceProperties(), "TVDEVICEPROPERTIES");
11966 reserveAll(md.backgrounds(), "BACKGROUND", false);
11967 reserveAll(md.renderSettings(), "RENDERSETTINGS", false);
11968 reserveAll(md.visualStyles(), "VISUALSTYLE", false);
11969 reserveAll(md.sunStudies(), "SUNSTUDY", false);
11970 reserveAll(md.dimensionAssociations(), "DIMENSIONASSOCIATION", false);
11971 reserveAll(md.evaluationGraphs(), "EVALUATIONGRAPH", false);
11972 reserveAll(md.sections(), "SECTION", false);
11973 reserveAll(md.viewportEntityHeaders(), "VPEHEADER", true,
11974 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
11975 TableRecord);
11976 reserveAll(md.vxControls(), "VXCONTROL");
11977 reserveAll(md.vxTableRecords(), "VXTABLERECORD");
11978 reserveAll(md.motionPaths(), "MOTIONPATH");
11979 reserveAll(md.curvePaths(), "CURVEPATH");
11980 reserveAll(md.pointPaths(), "POINTPATH");
11981 reserveAll(md.objectPtrs(), "OBJECT_PTR");
11982 reserveAll(md.partialViewingIndexes(), "PARTIALVIEWINGINDEX");
11983 reserveAll(md.blockRepresentationData(), "BLOCKREPRESENTATIONDATA");
11984 // P3 #2: structural-collision remap. The writer emits its control
11985 // objects / canonical table records at FIXED low handles (LTYPE
11986 // control 0x05, VIEW control 0x06, UCS control 0x07, ...) that
11987 // AC1021+ sources reuse for ordinary preserved OBJECTS. Reserving
11988 // alone cannot break that tie (both sides want the same handle),
11989 // so colliding typed objects are moved to fresh handles above the
11990 // high-water mark; writeObjects rewrites the object's own handle
11991 // and every typed reference to it. Colliding raw objects are
11992 // blocked in writeObjects instead (verbatim bytes cannot be
11993 // rewritten).
11994 auto remapIfColliding = [&](const auto &records,
11995 bool skipGeneratedDictionaries = false) {
11996 for (const auto &r : records) {
11997 if (r.handle == 0 || !isFixedStructuralDwgHandle(r.handle))
11998 continue;
11999 if (skipGeneratedDictionaries &&
12000 (r.handle == DRW::DwgNamedObjectsDictionaryHandle ||
12001 r.handle == DRW::DwgAcadGroupDictionaryHandle))
12002 continue;
12003 if (m_dwgWriteHandleRemap.count(r.handle) != 0)
12004 continue;
12005 const std::uint32_t target = ++highWater;
12006 m_dwgW->reserveHandle(target);
12007 m_dwgWriteHandleRemap.emplace(r.handle, target);
12008 }
12009 };
12010 remapIfColliding(md.placeholders());
12011 remapIfColliding(md.dictionaries(), true);
12012 remapIfColliding(md.xrecords());
12013 remapIfColliding(md.groups());
12014 remapIfColliding(md.layouts());
12015 remapIfColliding(md.mlineStyles());
12016 remapIfColliding(md.dictionaryVars());
12017 remapIfColliding(md.fieldLists());
12018 remapIfColliding(md.fields());
12019 remapIfColliding(md.suns());
12020 remapIfColliding(md.mleaderStyles());
12021 remapIfColliding(md.materials());
12022 remapIfColliding(md.lights());
12023 remapIfColliding(md.lightLists());
12024 remapIfColliding(md.vbaProjects());
12025 remapIfColliding(md.plotSettings());
12026 remapIfColliding(md.rasterVariables());
12027 remapIfColliding(md.wipeoutVariables());
12028 remapIfColliding(md.geoData());
12029 remapIfColliding(md.spatialFilters());
12030 remapIfColliding(md.scales());
12031 remapIfColliding(md.idBuffers());
12032 remapIfColliding(md.layerIndexes());
12033 remapIfColliding(md.spatialIndexes());
12034 remapIfColliding(md.dictionariesWithDefault());
12035 remapIfColliding(md.sortEntsTables());
12036 remapIfColliding(md.dbColors());
12037 remapIfColliding(md.underlayDefinitions());
12038 remapIfColliding(md.pointCloudDefinitions());
12039 remapIfColliding(md.navisworksModelDefinitions());
12040 remapIfColliding(md.navisworksModels());
12041 remapIfColliding(md.pointCloudColorMaps());
12042 remapIfColliding(md.cameras());
12043 remapIfColliding(md.shapes());
12044 remapIfColliding(md.geoPositionMarkers());
12045 remapIfColliding(md.sectionObjects());
12046 remapIfColliding(md.tvDeviceProperties());
12047 remapIfColliding(md.backgrounds());
12048 remapIfColliding(md.renderSettings());
12049 remapIfColliding(md.visualStyles());
12050 remapIfColliding(md.sunStudies());
12051 remapIfColliding(md.dimensionAssociations());
12052 remapIfColliding(md.evaluationGraphs());
12053 remapIfColliding(md.sections());
12054 remapIfColliding(md.tables());
12055 remapIfColliding(md.viewportEntityHeaders());
12056 remapIfColliding(md.vxControls());
12057 remapIfColliding(md.vxTableRecords());
12058 remapIfColliding(md.motionPaths());
12059 remapIfColliding(md.curvePaths());
12060 remapIfColliding(md.pointPaths());
12061 remapIfColliding(md.objectPtrs());
12062 remapIfColliding(md.partialViewingIndexes());
12063 remapIfColliding(md.blockRepresentationData());
12064 }
12065 if (duplicateDwgMetadataHandle) {
12066 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12067 "RS_FilterDXFRW: refusing DWG export with duplicate "
12068 "typed metadata identities");
12069 return false;
12070 }
12071 bool success = m_dwgW->write(this, dwgVer, false) && !m_writeFailed;
12072 m_lastDwgWriteSkipCounters = m_dwgW->getWriteSkipCounters();
12073 m_writeLeftOut = m_dwgW->leftOut();
12074 m_lastDwgOptionalReferenceDropCount = dwgWriteOptionalReferenceDropCount();
12075 if (!success) {
12076 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12077 "RS_FilterDXFRW: DWG low-level write failed (error=%d, "
12078 "filterFailed=%d, skips=%zu)",
12079 static_cast<int>(m_dwgW->getError()),
12080 m_writeFailed ? 1 : 0,
12081 m_lastDwgWriteSkipCounters.total());
12082 }
12083 // Geometry / typed-object / class-registration skips mean the save is
12084 // incomplete. Raw same-version replay may still skip cross-family
12085 // blobs; those are counted but do not alone fail the save.
12086 const auto &skips = m_lastDwgWriteSkipCounters;
12087 const std::size_t hardSkips = skips.entityWrites + skips.objectWrites +
12088 skips.classRegistrations +
12089 skips.blockDefinitions;
12090 if (success && hardSkips > 0) {
12091 success = false;
12092 m_writeFailed = true;
12093 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12094 "RS_FilterDXFRW: DWG write dropped %zu typed item(s) "
12095 "(entities=%zu objects=%zu classes=%zu blocks=%zu; "
12096 "raw skips=%zu)",
12097 hardSkips, skips.entityWrites, skips.objectWrites,
12098 skips.classRegistrations, skips.blockDefinitions,
12099 skips.rawObjectWrites + skips.rawSectionWrites);
12100 }
12101 activeWriter.reset();
12102 if (!success)
12103 return false;
12104
12105 // QFile::rename() does not replace an existing destination on every
12106 // supported platform. Preserve the old file while committing the
12107 // completed staged output, and restore it if the commit fails.
12108 const auto commitStagedOutput = [&]() {
12109 if (!QFileInfo::exists(file))
12110 return QFile::rename(stagedOutputPath, file);
12111 // Only a file is moved aside. A directory of that name would go to the
12112 // backup name as well, and stay there: the backup is removed as a file.
12113 if (!QFileInfo(file).isFile())
12114 return false;
12115
12116 QTemporaryFile backup(outputInfo.absolutePath() + "/." +
12117 outputInfo.fileName() + ".librecad-backup-XXXXXX");
12118 backup.setAutoRemove(false);
12119 if (!backup.open())
12120 return false;
12121 const QString backupPath = backup.fileName();
12122 // Release the reserved name through the object, as for the staged path
12123 // above: QTemporaryFile::close() keeps the native handle, and Windows
12124 // deletes no file that is open without FILE_SHARE_DELETE. A static
12125 // QFile::remove() of this path failed there, so no DWG could be saved
12126 // over an existing file.
12127 if (!backup.remove() || !QFile::rename(file, backupPath)) {
12128 QFile::remove(backupPath);
12129 return false;
12130 }
12131 if (QFile::rename(stagedOutputPath, file)) {
12132 QFile::remove(backupPath);
12133 return true;
12134 }
12135 if (!QFile::rename(backupPath, file)) {
12136 // Never remove the backup here: it is the only copy of the old file.
12137 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12138 "RS_FilterDXFRW: cannot restore '%s', its previous "
12139 "content is kept as '%s'",
12140 qPrintable(file)QtPrivate::asString(file).toLocal8Bit().constData(), qPrintable(backupPath)QtPrivate::asString(backupPath).toLocal8Bit().constData());
12141 }
12142 return false;
12143 };
12144 if (!commitStagedOutput()) {
12145 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12146 "RS_FilterDXFRW: cannot commit staged DWG output");
12147 m_writeFailed = true;
12148 return false;
12149 }
12150 return true;
12151 }
12152#endif
12153
12154 // set version for DXF filter:
12155 m_exactColor = false;
12156 DRW::Version exportVersion;
12157 if (type == RS2::FormatDXFRW12) {
12158 exportVersion = DRW::AC1009;
12159 m_version = 1009;
12160 } else if (type == RS2::FormatDXFRW14) {
12161 exportVersion = DRW::AC1014;
12162 m_version = 1014;
12163 } else if (type == RS2::FormatDXFRW2000) {
12164 exportVersion = DRW::AC1015;
12165 m_version = 1015;
12166 } else if (type == RS2::FormatDXFRW2004) {
12167 exportVersion = DRW::AC1018;
12168 m_version = 1018;
12169 m_exactColor = true;
12170 } else if (type == RS2::FormatDXFRW) {
12171 exportVersion = DRW::AC1021;
12172 m_version = 1021;
12173 m_exactColor = true;
12174 } else if (type == RS2::FormatDXFRW2010) {
12175 exportVersion = DRW::AC1024;
12176 m_version = 1024;
12177 m_exactColor = true;
12178 } else if (type == RS2::FormatDXFRW2013) {
12179 exportVersion = DRW::AC1027;
12180 m_version = 1027;
12181 m_exactColor = true;
12182 } else {
12183 exportVersion = DRW::AC1032;
12184 m_version = 1032;
12185 m_exactColor = true;
12186 }
12187 m_dxfExportVersion = exportVersion;
12188 /**
12189 * fixme - sand - files - RESTORE!!! Under win, encodeName() prevents using
12190 * unicode file names!!! Due to that, blocks/files may be saved incorrectly if
12191 * name is localized
12192 */
12193 ExportWriterScope<dxfRW> activeWriter(
12194 m_dxfW, std::make_unique<dxfRW>(QFile::encodeName(file).constData()));
12195 prepareShapesForExport();
12196 if (m_writeFailed)
12197 return false;
12198 m_dxfBlockInsertHandles.clear();
12199 m_dxfExtraPaperSpaces.clear();
12200 m_dxfEmittedObjectHandles.clear();
12201 // fixme - sand - save to binary format enabling/disabling!!
12202 const bool binary = false;
12203
12204 // Before write(): (1) reserve every verbatim code-5 handle preserved in the
12205 // raw-passthrough net (rawDxfObjects/rawDxfEntities re-emit their original
12206 // handle) in the codec's HandleAllocator so a minted handle can never
12207 // collide with a preserved raw handle, AND a preserved raw handle that lands
12208 // in the codec's fixed-low band (0x10-0x25) is skipped by next() too — this
12209 // is the DWG-path reserve-and-preserve mechanism (dwgWriter::reserveHandle),
12210 // and (2) register a DXF CLASS record for each custom-class object present in
12211 // the raw net so AutoCAD/ODA accept those instances (entry + instance
12212 // co-emit).
12213 {
12214 const auto &metadata = g.dwgAdvancedMetadata();
12215 std::vector<DRW_Class> classes;
12216 std::map<std::string, std::size_t> classIdx;
12217 std::map<std::string, DRW_Class> sourceClasses;
12218 std::vector<DRW_Class> emptyRecordNameClasses;
12219 std::set<std::string> conflictingSourceClasses;
12220 for (const DRW_Class &cls : metadata.dxfClasses()) {
12221 if (cls.recName.empty()) {
12222 DRW_Class preserved = cls;
12223 preserved.instanceCount = 0;
12224 emptyRecordNameClasses.push_back(std::move(preserved));
12225 continue;
12226 }
12227 const std::string normalizedName = normalizeDwgTableName(cls.recName);
12228 const auto [it, inserted] = sourceClasses.emplace(normalizedName, cls);
12229 if (!inserted && !sameDxfClassDefinition(it->second, cls))
12230 conflictingSourceClasses.insert(normalizedName);
12231 }
12232 bool classPlanningFailed = !conflictingSourceClasses.empty();
12233 for (const auto &entry : sourceClasses) {
12234 DRW_Class preserved = entry.second;
12235 preserved.instanceCount = 0;
12236 classIdx.emplace(entry.first, classes.size());
12237 classes.push_back(std::move(preserved));
12238 }
12239 for (DRW_Class &cls : emptyRecordNameClasses)
12240 classes.push_back(std::move(cls));
12241 auto registerClassFor = [&](const std::string &recordName) {
12242 DRW_Class cls;
12243 const std::string normalizedName = normalizeDwgTableName(recordName);
12244 if (conflictingSourceClasses.count(normalizedName) != 0) {
12245 classPlanningFailed = true;
12246 return;
12247 }
12248 auto sourceIt = sourceClasses.find(normalizedName);
12249 if (sourceIt != sourceClasses.end()) {
12250 cls = sourceIt->second;
12251 } else if (!dxfRW::dxfClassForRecordName(recordName, cls) &&
12252 !dxfRW::dxfClassForRecordName(normalizedName, cls)) {
12253 return;
12254 }
12255 auto it = classIdx.find(normalizedName);
12256 if (it == classIdx.end()) {
12257 cls.instanceCount = 1;
12258 classIdx.emplace(normalizedName, classes.size());
12259 classes.push_back(cls);
12260 } else {
12261 ++classes[it->second].instanceCount;
12262 }
12263 };
12264 // A raw record is replayed only into the version it was read from; one
12265 // read from another is left out of this file, with every reference to it.
12266 std::set<std::uint32_t> rawObjectHandles;
12267 std::set<std::uint32_t> leftOutRawHandles;
12268 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
12269 if (!replaysInDxfExport(o.m_version)) {
12270 ++m_dxfLeftOutRawRecords;
12271 if (o.handle != 0)
12272 leftOutRawHandles.insert(o.handle);
12273 continue;
12274 }
12275 if (o.handle != 0) {
12276 m_dxfW->reserveHandle(o.handle);
12277 rawObjectHandles.insert(o.handle);
12278 }
12279 registerClassFor(o.name);
12280 }
12281 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities()) {
12282 if (!replaysInDxfExport(e.m_version)) {
12283 ++m_dxfLeftOutRawRecords;
12284 if (e.handle != 0)
12285 leftOutRawHandles.insert(e.handle);
12286 continue;
12287 }
12288 if (e.handle != 0)
12289 m_dxfW->reserveHandle(e.handle);
12290 registerClassFor(e.name); // custom ENTITIES need a CLASS too
12291 }
12292 // These polylines are written as native underlays by writeEntities().
12293 for (RS_Entity *entity :
12294 lc::LC_ContainerTraverser{g, RS2::ResolveNone}.entities()) {
12295 DwgUnderlaySidecar sidecar;
12296 if (!entity->getFlag(RS2::FlagDeleted) &&
12297 parseDwgUnderlaySidecar(entity, sidecar)) {
12298 const auto kind = sidecar.value.kind;
12299 registerClassFor(kind == DRW_Underlay::DGN ? "DGNUNDERLAY"
12300 : kind == DRW_Underlay::DWF ? "DWFUNDERLAY"
12301 : "PDFUNDERLAY");
12302 }
12303 }
12304 std::vector<DRW_RawDxfSection> rawSections;
12305 for (const DRW_RawDxfSection &section : metadata.rawDxfSections()) {
12306 if (replaysInDxfExport(section.m_version))
12307 rawSections.push_back(section);
12308 else
12309 ++m_dxfLeftOutRawRecords;
12310 }
12311
12312 std::set<std::uint32_t> plotSettingsHandles;
12313 bool plotSettingsPlanningFailed = false;
12314 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
12315 if (settings.handle == 0)
12316 continue;
12317 if (!plotSettingsHandles.insert(settings.handle).second ||
12318 !m_dxfW->reserveHandle(settings.handle)) {
12319 plotSettingsPlanningFailed = true;
12320 break;
12321 }
12322 }
12323
12324 if (classPlanningFailed || plotSettingsPlanningFailed) {
12325 RS_DEBUGRS_Debug::instance()->print(
12326 RS_Debug::D_WARNING,
12327 classPlanningFailed
12328 ? "RS_FilterDXFRW: refusing DXF export with conflicting "
12329 "case-insensitive CLASS metadata"
12330 : "RS_FilterDXFRW: refusing DXF export with duplicate "
12331 "typed PLOTSETTINGS handles");
12332 return false;
12333 }
12334
12335 auto rawSectionHandle = [](const DRW_Variant &group,
12336 std::uint32_t &handle) {
12337 if ((group.code() != 5 && group.code() != 105) ||
12338 group.type() != DRW_Variant::STRING || group.c_str() == nullptr)
12339 return false;
12340 const char *text = group.c_str();
12341 char *end = nullptr;
12342 errno(*__errno_location ()) = 0;
12343 const unsigned long value = std::strtoul(text, &end, 16);
12344 if (errno(*__errno_location ()) == ERANGE34 || end == text || *end != '\0' ||
12345 value > std::numeric_limits<std::uint32_t>::max())
12346 return false;
12347 handle = static_cast<std::uint32_t>(value);
12348 return true;
12349 };
12350 for (const DRW_RawDxfSection &section : rawSections) {
12351 for (const DRW_Variant &group : section.m_groups) {
12352 std::uint32_t handle = 0;
12353 if (rawSectionHandle(group, handle) && handle != 0)
12354 m_dxfW->reserveHandle(handle);
12355 }
12356 }
12357
12358 // F4: the routed data-only OBJECTS (SUN/SCALE/DICTIONARYVAR/
12359 // RASTERVARIABLES) are typed-emitted on DWG->DXF (the DWG reader does NOT
12360 // put them in the raw net — see writeObjects). For each such record that
12361 // will be typed-emitted (ReplayAllowed, nonzero handle, NOT already in the
12362 // raw net → not double-counted), reserve its verbatim code-5 handle so a
12363 // minted handle can't collide, and register the same CLASS the raw-net
12364 // objects use (else AutoCAD/ezdxf drop the instance). Dedup matches
12365 // writeObjects' emitTyped; minVersion matches the codec writer's gate.
12366 // A typed record written in place of a raw one left out keeps its handle,
12367 // and references to it stay: it is not left out.
12368 const auto standsInForLeftOut = [&](std::uint32_t handle) {
12369 if (leftOutRawHandles.erase(handle) != 0 && m_dxfLeftOutRawRecords > 0)
12370 --m_dxfLeftOutRawRecords;
12371 };
12372 // Handles of the typed objects writeObjects emits, for the
12373 // structural-collision remap; typed entities are written elsewhere.
12374 std::vector<std::uint32_t> typedHandles;
12375 auto reserveTyped = [&](std::uint32_t handle,
12376 LC_DwgAdvancedMetadata::ReplayState state,
12377 const char *recordName,
12378 DRW::Version minVersion = DRW::UNKNOWNV,
12379 bool isObject = true) {
12380 if (exportVersion < minVersion || handle == 0 ||
12381 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
12382 rawObjectHandles.count(handle) != 0)
12383 return;
12384 m_dxfW->reserveHandle(handle);
12385 if (isObject)
12386 typedHandles.push_back(handle);
12387 registerClassFor(recordName);
12388 standsInForLeftOut(handle);
12389 };
12390 for (const auto &record : metadata.suns())
12391 reserveTyped(record.handle, record.replayState, "SUN");
12392 for (const auto &record : metadata.scales())
12393 reserveTyped(record.handle, record.replayState, "SCALE");
12394 for (const auto &record : metadata.dictionaryVars())
12395 reserveTyped(record.handle, record.replayState, "DICTIONARYVAR");
12396 for (const auto &record : metadata.rasterVariables())
12397 reserveTyped(record.handle, record.replayState, "RASTERVARIABLES");
12398 for (const auto &record : metadata.mleaderStyles())
12399 reserveTyped(record.handle, record.replayState, "MLEADERSTYLE",
12400 DRW::AC1021);
12401 for (const auto &record : metadata.geoData())
12402 reserveTyped(record.handle, record.replayState, "GEODATA");
12403 for (const auto &record : metadata.spatialFilters())
12404 reserveTyped(record.handle, record.replayState, "SPATIAL_FILTER");
12405 for (const auto &record : metadata.sortEntsTables())
12406 reserveTyped(record.handle, record.replayState, "SORTENTSTABLE");
12407 for (const auto &record : metadata.fields())
12408 reserveTyped(record.handle, record.replayState, "FIELD", DRW::AC1015);
12409 for (const auto &record : metadata.partialViewingIndexes())
12410 reserveTyped(record.handle, record.replayState, "PARTIALVIEWINGINDEX");
12411 for (const auto &record : metadata.fieldLists())
12412 reserveTyped(record.handle, record.replayState, "FIELDLIST", DRW::AC1015);
12413 // SLICE 2: WIPEOUTVARIABLES is a CUSTOM class -> reserve + register CLASS.
12414 for (const auto &record : metadata.wipeoutVariables())
12415 reserveTyped(record.handle, record.replayState, "WIPEOUTVARIABLES");
12416 for (const auto &record : metadata.pointCloudDefinitions())
12417 reserveTyped(record.handle, record.replayState,
12418 pointCloudDefinitionRecordName(record.kind));
12419 for (const auto &record : metadata.navisworksModelDefinitions())
12420 reserveTyped(record.handle, record.replayState, "NAVISWORKSMODELDEF");
12421 if (exportVersion >= DRW::AC1015) {
12422 for (const auto &record : metadata.navisworksModels())
12423 reserveTyped(record.handle, record.replayState, "NAVISWORKSMODEL",
12424 DRW::UNKNOWNV, false);
12425 }
12426 for (const auto &record : metadata.pointCloudColorMaps())
12427 reserveTyped(record.handle, record.replayState, "POINTCLOUDCOLORMAP");
12428 for (const auto &record : metadata.backgrounds())
12429 reserveTyped(record.handle, record.replayState,
12430 backgroundRecordName(record.kind));
12431 for (const auto &record : metadata.materials())
12432 reserveTyped(record.handle, record.replayState, "MATERIAL");
12433 for (const auto &record : metadata.renderSettings())
12434 reserveTyped(record.handle, record.replayState,
12435 renderSettingsRecordName(record.kind));
12436 for (const auto &record : metadata.sunStudies())
12437 reserveTyped(record.handle, record.replayState, "SUNSTUDY");
12438 for (const auto &record : metadata.motionPaths())
12439 reserveTyped(record.handle, record.replayState, "MOTIONPATH");
12440 for (const auto &record : metadata.curvePaths())
12441 reserveTyped(record.handle, record.replayState, "ACDBCURVEPATH");
12442 for (const auto &record : metadata.pointPaths())
12443 reserveTyped(record.handle, record.replayState, "ACDBPOINTPATH");
12444 for (const auto &record : metadata.objectPtrs())
12445 reserveTyped(record.handle, record.replayState, "OBJECTPTR");
12446 for (const auto &record : metadata.dbColors())
12447 reserveTyped(record.handle, record.replayState, "DBCOLOR");
12448 for (const auto &record : metadata.dimensionAssociations())
12449 reserveTyped(record.handle, record.replayState, "DIMASSOC");
12450 for (const auto &record : metadata.evaluationGraphs()) {
12451 if (exportVersion >= DRW::AC1021 &&
12452 canWriteNativeEvaluationGraph(evaluationGraphFromMetadata(record)))
12453 reserveTyped(record.handle, record.replayState, "EVALUATION_GRAPH");
12454 }
12455 for (const auto &record : metadata.sections())
12456 reserveTyped(record.handle, record.replayState,
12457 sectionRecordName(record.kind));
12458 for (const auto &record : metadata.cameras()) {
12459 if (record.handle != 0 &&
12460 record.replayState ==
12461 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
12462 m_dxfW->reserveHandle(record.handle);
12463 }
12464 // SLICE 1: fixed built-ins -> reserve handles but register NO CLASS.
12465 auto reserveFixedTyped = [&](std::uint32_t handle,
12466 LC_DwgAdvancedMetadata::ReplayState state,
12467 bool isObject = true) {
12468 if (handle == 0 ||
12469 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
12470 rawObjectHandles.count(handle) != 0)
12471 return;
12472 m_dxfW->reserveHandle(handle);
12473 if (isObject)
12474 typedHandles.push_back(handle);
12475 standsInForLeftOut(handle);
12476 };
12477 for (const auto &record : metadata.mlineStyles())
12478 reserveFixedTyped(record.handle, record.replayState);
12479 for (const auto &record : metadata.layouts())
12480 reserveFixedTyped(record.handle, record.replayState);
12481 for (const auto &record : metadata.geoPositionMarkers())
12482 reserveFixedTyped(record.handle, record.replayState, false);
12483 for (const auto &record : metadata.sectionObjects())
12484 reserveFixedTyped(record.handle, record.replayState, false);
12485
12486 // R12 has no CLASSES section, and none of the objects a CLASS describes.
12487 if (exportVersion <= DRW::AC1009)
12488 classes.clear();
12489 // A source CLASS no written record uses gives way to a used one of the
12490 // same class name under another record name, e.g. a typed object written
12491 // in place of a raw one left out: two CLASS records may not share a name.
12492 {
12493 std::set<std::string> usedClassNames;
12494 for (const DRW_Class &cls : classes) {
12495 if (cls.instanceCount > 0)
12496 usedClassNames.insert(normalizeDwgTableName(cls.className));
12497 }
12498 classes.erase(std::remove_if(classes.begin(), classes.end(),
12499 [&usedClassNames](const DRW_Class &cls) {
12500 return cls.instanceCount == 0 &&
12501 usedClassNames.count(normalizeDwgTableName(
12502 cls.className)) != 0;
12503 }),
12504 classes.end());
12505 }
12506 if (!classes.empty())
12507 m_dxfW->setDxfClasses(classes);
12508
12509 // (3) Re-attach raw-net-routed named dictionaries to the codec's
12510 // regenerated root NamedObjectsDictionary (handle C). The child dict NAME
12511 // is not on the child object — it lives in the SOURCE root dict's entry
12512 // list (the typed DictionaryRecord with parentHandle==0). Build the
12513 // (name, verbatim-handle) entries for the codec, and a suppression set so
12514 // the source ACAD_GROUP dict (codec regenerates it at D) and any C/D
12515 // collisions are not re-emitted. The source root dict itself is never in
12516 // the raw net (processDictionary skips parentHandle==0).
12517 m_dxfSuppressedObjectHandles.clear();
12518 m_dxfEmittedNamedDictHandles.clear();
12519 std::uint32_t sourceRootHandle = 0;
12520 std::uint32_t acadGroupHandle = 0;
12521 std::map<std::uint32_t, std::string> rootEntryName; // child handle -> name
12522 for (const auto &d : metadata.dictionaries()) {
12523 if (d.parentHandle != 0)
12524 continue;
12525 sourceRootHandle = d.handle;
12526 for (const auto &e : d.entries) {
12527 if (e.name == "ACAD_GROUP")
12528 acadGroupHandle = e.handle;
12529 else
12530 rootEntryName.emplace(e.handle, e.name);
12531 }
12532 }
12533 if (acadGroupHandle != 0)
12534 m_dxfSuppressedObjectHandles.insert(acadGroupHandle);
12535
12536 // (3a) Structural-collision remap. The codec emits its own table/block
12537 // records and root dictionaries at FIXED low handles (table heads, LAYER
12538 // 0x10, APPID 0x12, LTYPE 0x14-0x16, BLOCK_RECORD/BLOCK/ENDBLK 0x1C-0x21,
12539 // root C / ACAD_GROUP D). Real source files reuse those very handles for
12540 // unrelated OBJECTS (extension dicts, material dicts, xrecords) preserved
12541 // verbatim in the raw net — an unavoidable raw-vs-fixed-structural
12542 // collision the reserve-and-preserve mechanism alone cannot break (both
12543 // want the same handle). The fixed structural set is non-negotiable (the
12544 // codec's 330/350 cross-refs are literals), so the colliding raw objects
12545 // are remapped to fresh allocator handles; writeRawDxfObject rewrites the
12546 // object's own handle AND every reference to a remapped handle, keeping
12547 // the raw subtree internally consistent. 0xC/0xD raw dicts are suppressed
12548 // (the codec regenerates root/group) and so are NOT remapped.
12549 static const std::set<std::uint32_t> kFixedStructural = {
12550 0x1, 0x2, 0x3, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0x10,
12551 0x12, 0x14, 0x15, 0x16, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21};
12552
12553 // A GROUP keeps its source handle, like the typed objects reserved above:
12554 // its members' reactors are written with the entities, before OBJECTS,
12555 // and must still name it. The reactors drop a GROUP that is not written or
12556 // whose handle is structural (the codec gives it a fresh one); a GROUP
12557 // whose handle another object holds gets a fresh one too, and references
12558 // to the handle stay with that object. Reserved before the remap below
12559 // allocates any handle.
12560 m_dxfDroppedReferenceHandles.clear();
12561 std::vector<bool> groupKeepsHandle;
12562 groupKeepsHandle.reserve(metadata.groups().size());
12563 std::set<std::uint32_t> keptGroupHandles;
12564 for (const auto &record : metadata.groups()) {
12565 const std::uint32_t h = record.handle;
12566 const bool written = record.replayState ==
12567 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
12568 const bool structural =
12569 kFixedStructural.count(h) != 0 || h == 0xCu || h == 0xDu;
12570 const bool keep = written && h != 0 && !structural &&
12571 !m_dxfW->isReservedHandle(h) &&
12572 m_dxfW->reserveHandle(h);
12573 groupKeepsHandle.push_back(keep);
12574 if (keep)
12575 keptGroupHandles.insert(h);
12576 else if (h != 0 && (!written || structural))
12577 m_dxfDroppedReferenceHandles.insert(h);
12578 }
12579 m_dxfDroppedReferenceHandles.insert(leftOutRawHandles.cbegin(),
12580 leftOutRawHandles.cend());
12581 for (const std::uint32_t h : keptGroupHandles)
12582 m_dxfDroppedReferenceHandles.erase(h);
12583 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
12584 if (settings.handle != 0 &&
12585 kFixedStructural.count(settings.handle) != 0) {
12586 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
12587 "RS_FilterDXFRW: refusing DXF export with PLOTSETTINGS "
12588 "handle colliding with a structural record");
12589 return false;
12590 }
12591 }
12592 std::map<std::uint32_t, std::uint32_t> handleRemap;
12593 bool remapFailed = false;
12594 auto remapIfColliding = [&](std::uint32_t h) {
12595 if (h == 0 || kFixedStructural.count(h) == 0)
12596 return;
12597 if (h == 0xCu || h == 0xDu || m_dxfSuppressedObjectHandles.count(h) != 0)
12598 return; // suppressed, not emitted -> nothing to remap
12599 if (remapFailed || handleRemap.count(h) != 0)
12600 return;
12601 const std::uint32_t replacement = m_dxfW->allocHandle();
12602 // allocHandle() runs before dxfRW::write(), whose new-session reset
12603 // clears generated handles. Keep the remap explicit so that reset
12604 // cannot reissue this handle to a structural record.
12605 if (replacement == 0 || !m_dxfW->reserveHandle(replacement)) {
12606 remapFailed = true;
12607 return;
12608 }
12609 handleRemap.emplace(h, replacement);
12610 };
12611 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
12612 if (replaysInDxfExport(o.m_version))
12613 remapIfColliding(o.handle);
12614 }
12615 for (const DRW_RawDxfObject &e : metadata.rawDxfEntities()) {
12616 if (replaysInDxfExport(e.m_version))
12617 remapIfColliding(e.handle);
12618 }
12619 // Typed objects keep their source handle too, so the same collisions
12620 // happen there (a DWG's LAYOUT or MATERIAL at 1E or 1F): writeObjects
12621 // writes them, and references to them, under the replacement.
12622 for (const std::uint32_t h : typedHandles)
12623 remapIfColliding(h);
12624 // The codec writes the root dictionary at C; references to the source's
12625 // own root, such as its children's owners and reactors, follow it there.
12626 if (sourceRootHandle != 0 && sourceRootHandle != 0xCu)
12627 handleRemap.emplace(sourceRootHandle, 0xCu);
12628 for (const DRW_RawDxfSection &section : rawSections) {
12629 for (const DRW_Variant &group : section.m_groups) {
12630 std::uint32_t handle = 0;
12631 if (rawSectionHandle(group, handle))
12632 remapIfColliding(handle);
12633 }
12634 }
12635 if (remapFailed) {
12636 RS_DEBUGRS_Debug::instance()->print(
12637 RS_Debug::D_WARNING,
12638 "RS_FilterDXFRW: DXF handle space exhausted while remapping");
12639 return false;
12640 }
12641 if (!rawSections.empty())
12642 m_dxfW->setRawDxfSections(rawSections);
12643 // setHandleRemap is pushed AFTER the named-dict block below so any remap
12644 // a colliding named-dict handle adds is included in the codec's table.
12645
12646 std::vector<std::pair<std::string, std::string>> rootEntries;
12647 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects()) {
12648 if (o.name != "DICTIONARY" && o.name != "ACDBDICTIONARYWDFLT")
12649 continue;
12650 if (o.handle == 0xCu || o.handle == 0xDu) { // collide with fixed C/D
12651 m_dxfSuppressedObjectHandles.insert(o.handle);
12652 continue;
12653 }
12654 if (o.handle == acadGroupHandle || !replaysInDxfExport(o.m_version))
12655 continue; // already suppressed, or left out
12656 if (o.parentHandle != sourceRootHandle)
12657 continue; // only direct root children get a root entry
12658 auto it = rootEntryName.find(o.handle);
12659 if (it == rootEntryName.end())
12660 continue; // name not recoverable -> cannot re-attach
12661 std::string handleStr; // code-5; matches the (possibly remapped) re-emit
12662 for (const DRW_Variant &g : o.groups)
12663 if (g.code() == 5) {
12664 handleStr = g.c_str();
12665 break;
12666 }
12667 // If this child dict's handle was remapped (structural collision), the
12668 // root entry must point at the NEW handle so the 350 ref resolves.
12669 auto rm = handleRemap.find(o.handle);
12670 if (rm != handleRemap.end())
12671 handleStr = m_dxfW->toHexStrHandle(rm->second);
12672 if (!handleStr.empty())
12673 rootEntries.emplace_back(it->second, handleStr);
12674 }
12675
12676 // (3b) F4-followup: DWG->DXF parent dictionaries for the data-only
12677 // OBJECTS. On the DWG path the only INVALID_OWNER_HANDLE fixes ezdxf
12678 // applies are typed OBJECTS (SUN / SCALE / DICTIONARYVAR /
12679 // RASTERVARIABLES / LAYOUT / ...) whose 330 owner is a named dictionary
12680 // that LibreCAD does not regenerate (e.g. ACAD_DICTIONARYVAR @0x70
12681 // owning the DICTIONARYVARs). Emit EXACTLY those parent dictionaries as
12682 // real, C-owned OBJECTS so the children resolve to a valid owner; keep ONLY
12683 // the entries that target an object we actually emit (the data-only
12684 // children) so the dict introduces no new dangling 350. We deliberately do
12685 // NOT emit the dictionaries whose children are unmodeled objects LibreCAD
12686 // drops: emitting those would replace one missing-owner fix with many
12687 // dangling-entry fixes and corrupt cross-refs.
12688 //
12689 // The set of objects we emit on this path (their code-5 handles):
12690 // the version-supported, ReplayAllowed, non-raw-net data-only records,
12691 // plus the raw-net objects (re-emitted verbatim above).
12692 std::set<std::uint32_t> emittedObjectHandles = rawObjectHandles;
12693 std::set<std::uint32_t> neededParents; // 330 owners we must materialize
12694 auto noteDataOnly = [&](std::uint32_t handle, std::uint32_t parent,
12695 LC_DwgAdvancedMetadata::ReplayState state,
12696 DRW::Version minVersion = DRW::UNKNOWNV) {
12697 if (exportVersion < minVersion || handle == 0 ||
12698 state != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
12699 rawObjectHandles.count(handle) != 0)
12700 return; // not emitted on this path
12701 emittedObjectHandles.insert(handle);
12702 if (parent != 0)
12703 neededParents.insert(parent);
12704 };
12705 for (const auto &r : metadata.suns())
12706 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12707 for (const auto &r : metadata.scales())
12708 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12709 for (const auto &r : metadata.dictionaryVars())
12710 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12711 for (const auto &r : metadata.rasterVariables())
12712 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12713 for (const auto &r : metadata.mleaderStyles())
12714 noteDataOnly(r.handle, r.parentHandle, r.replayState, DRW::AC1021);
12715 for (const auto &r : metadata.geoData())
12716 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12717 for (const auto &r : metadata.spatialFilters())
12718 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12719 for (const auto &r : metadata.sortEntsTables())
12720 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12721 for (const auto &r : metadata.fields())
12722 noteDataOnly(r.handle, r.parentHandle, r.replayState, DRW::AC1015);
12723 for (const auto &r : metadata.fieldLists())
12724 noteDataOnly(r.handle, r.parentHandle, r.replayState, DRW::AC1015);
12725 for (const auto &r : metadata.mlineStyles())
12726 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12727 for (const auto &r : metadata.layouts())
12728 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12729 for (const auto &r : metadata.wipeoutVariables())
12730 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12731 for (const auto &r : metadata.pointCloudDefinitions())
12732 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12733 for (const auto &r : metadata.navisworksModelDefinitions())
12734 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12735 for (const auto &r : metadata.pointCloudColorMaps())
12736 noteDataOnly(r.handle, r.parentHandle, r.replayState);
12737 for (const DRW_PlotSettings &settings : metadata.plotSettings()) {
12738 if (settings.handle != 0) {
12739 emittedObjectHandles.insert(settings.handle);
12740 if (settings.parentHandle != 0)
12741 neededParents.insert(
12742 static_cast<std::uint32_t>(settings.parentHandle));
12743 }
12744 }
12745 m_dxfEmittedObjectHandles = emittedObjectHandles;
12746 for (const std::uint32_t h : m_dxfSuppressedObjectHandles)
12747 m_dxfEmittedObjectHandles.erase(h);
12748
12749 std::vector<DRW_Dictionary> namedDicts;
12750 std::set<std::uint32_t> emittedDictHandles;
12751 for (const auto &d : metadata.dictionaries()) {
12752 if (d.parentHandle == 0)
12753 continue; // source root: harvested for names only
12754 if (d.handle == 0 || d.handle == 0xCu || d.handle == 0xDu)
12755 continue;
12756 if (d.handle == acadGroupHandle)
12757 continue;
12758 if (m_dxfSuppressedObjectHandles.count(d.handle) != 0)
12759 continue;
12760 if (rawObjectHandles.count(d.handle) != 0)
12761 continue; // DXF-read dict already round-tripped via the raw net
12762 if (neededParents.count(d.handle) == 0)
12763 continue; // not a parent of any object we emit -> skip
12764 if (emittedDictHandles.count(d.handle) != 0)
12765 continue; // de-dup (dictionaries() may list a handle once, guard
12766 // anyway)
12767
12768 // Reserve + structural-collision remap, mirroring the raw path so a
12769 // dict handle landing on a fixed literal is moved.
12770 m_dxfW->reserveHandle(d.handle);
12771 remapIfColliding(d.handle);
12772 auto remapOf = [&](std::uint32_t h) {
12773 auto rm = handleRemap.find(h);
12774 return rm != handleRemap.end() ? rm->second : h;
12775 };
12776 DRW_Dictionary dict = dictionaryFromMetadata(d);
12777 dict.handle = remapOf(d.handle);
12778 // Owner: re-point a child of the source root at C (handle 0 ->
12779 // writeObjectOwner emits "C"). If the explicit parent is itself an
12780 // emitted dict, keep it (remapped); otherwise fall back to C so the
12781 // dict is never itself a dangling-owner object.
12782 std::uint32_t parent = static_cast<std::uint32_t>(d.parentHandle);
12783 if (parent == sourceRootHandle || neededParents.count(parent) == 0)
12784 dict.parentHandle = 0;
12785 else
12786 dict.parentHandle = static_cast<int>(remapOf(parent));
12787 // Keep ONLY entries whose target is an object we actually emit, so the
12788 // dict adds no dangling 350.
12789 std::vector<DRW_Dictionary::Entry> keptEntries;
12790 keptEntries.reserve(dict.m_entries.size());
12791 for (auto &entry : dict.m_entries) {
12792 if (emittedObjectHandles.count(entry.m_handle) == 0)
12793 continue;
12794 entry.m_handle = remapOf(entry.m_handle);
12795 keptEntries.push_back(entry);
12796 }
12797 dict.m_entries = std::move(keptEntries);
12798 namedDicts.push_back(std::move(dict));
12799 emittedDictHandles.insert(d.handle);
12800 m_dxfEmittedNamedDictHandles.insert(d.handle);
12801
12802 // Re-attach under root C so the dict object is reachable. Use the
12803 // harvested name if known, else the source dict name, else a synthetic.
12804 auto nameIt = rootEntryName.find(d.handle);
12805 std::string entryName = nameIt != rootEntryName.end()
12806 ? nameIt->second
12807 : (!d.name.empty() ? d.name : std::string());
12808 if (!entryName.empty())
12809 rootEntries.emplace_back(entryName,
12810 m_dxfW->toHexStrHandle(remapOf(d.handle)));
12811 }
12812 if (!namedDicts.empty())
12813 m_dxfW->setNamedDictObjects(namedDicts);
12814 for (const std::uint32_t h : m_dxfEmittedNamedDictHandles)
12815 m_dxfDroppedReferenceHandles.erase(h);
12816
12817 // (4) F3: typed GROUP emit (both directions). GROUP is read into typed
12818 // metadata only (processGroup -> addGroup; never routed to the DXF raw
12819 // net — confirmed against the DXF reader's processGroup which makes no
12820 // addRawDxfObject call), so there is no raw-net dedup to do here. The
12821 // codec mints each group's handle, augments the ACAD_GROUP D dict, and
12822 // resolves member entityHandles (SOURCE handles) through the source->minted
12823 // map captured during writeEntity. The source ACAD_GROUP dict is already
12824 // suppressed above (acadGroupHandle); the codec regenerates D.
12825 //
12826 // Group NAMES live in the ACAD_GROUP dict's entries (groupName ->
12827 // groupHandle), not on the GROUP record; harvest them. Unnamed groups
12828 // (isUnnamed) carry a generated "*An" name AutoCAD assigns; synthesize a
12829 // stable "*A<n>" when no name is recoverable so the D-dict entry + GROUP
12830 // are still well-formed.
12831 std::map<std::uint32_t, std::string> groupHandleToName;
12832 for (const auto &d : metadata.dictionaries()) {
12833 if (d.handle != acadGroupHandle || acadGroupHandle == 0)
12834 continue;
12835 for (const auto &e : d.entries)
12836 groupHandleToName.emplace(e.handle, e.name);
12837 }
12838 std::vector<DRW_Group> groupsToWrite;
12839 groupsToWrite.reserve(metadata.groups().size());
12840 int unnamedSeq = 0;
12841 for (std::size_t i = 0; i < metadata.groups().size(); ++i) {
12842 const auto &record = metadata.groups()[i];
12843 if (record.replayState !=
12844 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
12845 continue;
12846 DRW_Group group = groupFromMetadata(record);
12847 if (!groupKeepsHandle[i])
12848 group.handle = 0; // the codec mints one
12849 auto nameIt = groupHandleToName.find(record.handle);
12850 if (nameIt != groupHandleToName.end() && !nameIt->second.empty())
12851 group.name = nameIt->second;
12852 else
12853 group.name = "*A" + std::to_string(++unnamedSeq);
12854 groupsToWrite.push_back(std::move(group));
12855 }
12856 if (!groupsToWrite.empty())
12857 m_dxfW->setGroups(groupsToWrite);
12858 m_dxfW->setReferenceResolver(
12859 [this](std::uint32_t source) { return dxfReference(source); });
12860
12861 if (!handleRemap.empty())
12862 m_dxfW->setHandleRemap(handleRemap);
12863
12864 if (!rootEntries.empty())
12865 m_dxfW->setRootDictEntries(rootEntries);
12866 }
12867
12868 // bool success = m_dxfW->write(this, exportVersion, false); //ascii
12869 const bool success =
12870 m_dxfW->write(this, exportVersion, binary) && !m_writeFailed; // binary
12871 m_writeLeftOut = m_dxfW->leftOut();
12872 activeWriter.reset();
12873
12874 if (!success) {
12875 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFDW::fileExport: can't write file");
12876 return false;
12877 }
12878 /*RLZ pte*/
12879 /* RS_DEBUG->print("writing tables...");
12880 dw->sectionTables();
12881 // VPORT:
12882 dxf.writeVPort(*dw);
12883 dw->tableEnd();
12884
12885 // VIEW:
12886 RS_DEBUG->print("writing views...");
12887 dxf.writeView(*dw);
12888
12889 // UCS:
12890 RS_DEBUG->print("writing ucs...");
12891 dxf.writeUcs(*dw);
12892
12893 // Appid:
12894 RS_DEBUG->print("writing appid...");
12895 dw->tableAppid(1);
12896 writeAppid(*dw, "ACAD");
12897 dw->tableEnd();
12898 */
12899 return success;
12900}
12901
12902/**
12903 * Prepare unnamed blocks.
12904 */
12905void RS_FilterDXFRW::prepareBlocks() {
12906 int dimNum = 0;
12907 int hatchNum = 0;
12908
12909 // check for existing *D?? or *U??
12910 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
12911 const RS_Block *blk = m_graphic->blockAt(i);
12912 const QString prefix = blk->getName().left(2).toUpper();
12913 QString sufix = blk->getName().mid(2);
12914 if (prefix == "*D") {
12915 if (sufix.toInt() > dimNum) {
12916 dimNum = sufix.toInt();
12917 }
12918 } else if (prefix == "*U") {
12919 if (sufix.toInt() > hatchNum) {
12920 hatchNum = sufix.toInt();
12921 }
12922 }
12923 }
12924 // Add a name to each dimension, in dxfR12 also for hatches
12925 for (RS_Entity *e :
12926 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
12927 if (e->isAlive()) {
12928 switch (e->rtti()) {
12929 case RS2::EntityDimLinear:
12930 case RS2::EntityDimOrdinate:
12931 case RS2::EntityDimAligned:
12932 case RS2::EntityDimAngular:
12933 case RS2::EntityDimRadial:
12934 case RS2::EntityDimDiametric:
12935 case RS2::EntityDimArc:
12936 case RS2::EntityDimLeader: {
12937 const QString prefix = "*D" + QString::number(++dimNum);
12938 m_noNameBlock[e] = prefix;
12939 break;
12940 }
12941 case RS2::EntityHatch: {
12942 if (m_version == 1009) {
12943 if (!static_cast<RS_Hatch *>(e)->isSolid()) {
12944 const QString prefix = "*U" + QString::number(++hatchNum);
12945 m_noNameBlock[e] = prefix;
12946 }
12947 }
12948 break;
12949 }
12950 default:
12951 break;
12952 } // end switch
12953 } // end if !RS2::FlagUndone
12954 }
12955 // R12 writes every pattern hatch into a *U block of its own. When the
12956 // density guard tripped the hatch has no pattern pieces, so the *U block
12957 // and its INSERT end up empty. Warn the user once per export.
12958 if (m_version == 1009) {
12959 int r12DenseCount = 0;
12960 for (RS_Entity *e :
12961 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
12962 if (e != nullptr && e->isAlive() && e->rtti() == RS2::EntityHatch) {
12963 const auto *h = static_cast<const RS_Hatch *>(e);
12964 if (!h->isSolid()
12965 && h->getUpdateError() == RS_Hatch::HATCH_TOO_DENSE) {
12966 ++r12DenseCount;
12967 }
12968 }
12969 }
12970 if (r12DenseCount > 0) {
12971 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
12972 LC_HatchDensityText::r12Warning(r12DenseCount));
12973 }
12974 }
12975}
12976
12977/**
12978 * Decides, for the export in progress, what becomes of each text read as a
12979 * block attribute. A text names its INSERT by lineage (RS_Entity::lineage()):
12980 * the pair survives the clones that replace either on an edit, and a copy of
12981 * both is a pair of its own. One pass per container; deleted entities take no
12982 * part.
12983 */
12984void RS_FilterDXFRW::buildInsertAttribPlan() {
12985 m_insertAttribPlan = {};
12986 if (m_graphic == nullptr)
12987 return;
12988 const auto showsNothing = [](const RS_Entity *text) {
12989 if (!text->getFlag(RS2::FlagVisible))
12990 return true;
12991 return text->rtti() == RS2::EntityText
12992 ? static_cast<const RS_Text *>(text)->getText().isEmpty()
12993 : static_cast<const RS_MText *>(text)->getText().isEmpty();
12994 };
12995 const auto scan = [this, &showsNothing](RS_EntityContainer *container) {
12996 if (container == nullptr)
12997 return;
12998 std::vector<RS_Entity *> texts;
12999 for (RS_Entity *entity : *container) {
13000 if (entity != nullptr && !entity->getFlag(RS2::FlagDeleted) &&
13001 entity->ownerLineage() != 0 &&
13002 (entity->rtti() == RS2::EntityText ||
13003 entity->rtti() == RS2::EntityMText)) {
13004 texts.push_back(entity);
13005 }
13006 }
13007 // Most containers hold no text of an attribute, whatever the number of
13008 // their INSERTs: those are looked up only for a text that names one.
13009 if (texts.empty())
13010 return;
13011 std::map<unsigned long long, const RS_Entity *> inserts;
13012 for (const RS_Entity *entity : *container) {
13013 if (entity != nullptr && !entity->getFlag(RS2::FlagDeleted) &&
13014 entity->rtti() == RS2::EntityInsert) {
13015 // The first keeps a lineage two INSERTs share (clones put into a
13016 // container without an undo section).
13017 inserts.emplace(entity->lineage(), entity);
13018 }
13019 }
13020 std::map<const RS_Entity *, std::vector<std::pair<int, RS_Entity *>>> found;
13021 for (RS_Entity *text : texts) {
13022 const auto owner = inserts.find(text->ownerLineage());
13023 if (owner != inserts.end()) {
13024 if (const auto sidecar = extractInsertAttribSidecar(text)) {
13025 found[owner->second].emplace_back(sidecar->ordinal, text);
13026 continue;
13027 }
13028 }
13029 // No INSERT writes this text. One that shows is written as a text of its
13030 // own (writeText(), writeMText()); a hidden or empty one was data of its
13031 // INSERT only and no selection can reach it.
13032 if (showsNothing(text))
13033 m_insertAttribPlan.leftOut.insert(text);
13034 }
13035 for (auto &[insert, attributes] : found) {
13036 // Container order decides between texts of one ordinal.
13037 std::stable_sort(attributes.begin(), attributes.end(),
13038 [](const auto &lhs, const auto &rhs) {
13039 return lhs.first < rhs.first;
13040 });
13041 auto &children = m_insertAttribPlan.children[insert];
13042 children.reserve(attributes.size());
13043 for (const auto &attribute : attributes) {
13044 children.push_back(attribute.second);
13045 m_insertAttribPlan.claimed.insert(attribute.second);
13046 }
13047 }
13048 };
13049 scan(m_graphic);
13050 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
13051 RS_Block *block = m_graphic->blockAt(i);
13052 if (block != nullptr && !block->isDeleted())
13053 scan(block);
13054 }
13055}
13056
13057/**
13058 * Writes block records (just the name, not the entities in it).
13059 */
13060void RS_FilterDXFRW::prepareDxfEntityHandleMap() {
13061 if (m_dxfW == nullptr || m_graphic == nullptr)
13062 return;
13063
13064 m_dxfBlockInsertHandles.clear();
13065 std::map<std::uint32_t, std::size_t> sourceHandleCounts;
13066 std::set<std::uint32_t> attribChildHandles;
13067 std::set<std::uint32_t> rawEntityHandles;
13068 std::set<std::uint32_t> rawObjectHandles;
13069 auto scanEntities = [&](RS_EntityContainer *container) {
13070 if (container == nullptr)
13071 return;
13072 for (RS_Entity *entity :
13073 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
13074 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
13075 m_insertAttribPlan.leftOut.count(entity) != 0) {
13076 continue;
13077 }
13078 const std::uint32_t sourceHandle = entity->sourceHandle();
13079 if (sourceHandle == 0)
13080 continue;
13081 ++sourceHandleCounts[sourceHandle];
13082 // An ATTRIB is counted, so that a handle it shares names no entity, but
13083 // it takes its handle when its INSERT is written.
13084 if (isInsertAttribChild(entity))
13085 attribChildHandles.insert(sourceHandle);
13086 }
13087 };
13088
13089 scanEntities(m_graphic);
13090 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
13091 RS_Block *block = m_graphic->blockAt(i);
13092 if (block != nullptr && !block->isDeleted())
13093 scanEntities(block);
13094 }
13095
13096 // Raw records are emitted later than BLOCK_RECORDS but their identities are
13097 // needed now: typed entities and GROUP/raw references must know the final
13098 // handle before writeEntities starts. A zero convenience handle represents
13099 // either a legacy/null value or a wider-than-uint32 lexeme and is therefore
13100 // intentionally excluded from the map.
13101 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13102 std::map<std::string, std::size_t> rawSelfHandleCounts;
13103 auto rawSelfHandleKey = [](const DRW_RawDxfObject &raw) {
13104 for (const DRW_Variant &group : raw.groups) {
13105 if (group.code() != 5 || group.type() != DRW_Variant::STRING ||
13106 group.c_str() == nullptr || group.c_str()[0] == '\0') {
13107 continue;
13108 }
13109 std::string key = group.c_str();
13110 std::transform(key.begin(), key.end(), key.begin(), [](unsigned char ch) {
13111 return static_cast<char>(std::toupper(ch));
13112 });
13113 return key;
13114 }
13115 return std::string{};
13116 };
13117 auto noteRawSelfHandle = [&](const DRW_RawDxfObject &raw, bool suppressed) {
13118 if (suppressed)
13119 return;
13120 const std::string key = rawSelfHandleKey(raw);
13121 if (key.empty())
13122 return;
13123 const std::size_t count = ++rawSelfHandleCounts[key];
13124 if (count > 1) {
13125 m_writeFailed = true;
13126 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
13127 "RS_FilterDXFRW: duplicate raw DXF self-handle %s; "
13128 "export rejected",
13129 key.c_str());
13130 }
13131 };
13132 for (const DRW_RawDxfObject &raw : metadata.rawDxfEntities())
13133 noteRawSelfHandle(raw, !replaysInDxfExport(raw.m_version));
13134 for (const DRW_RawDxfObject &raw : metadata.rawDxfObjects())
13135 noteRawSelfHandle(raw, m_dxfSuppressedObjectHandles.count(raw.handle) != 0 ||
13136 !replaysInDxfExport(raw.m_version));
13137
13138 for (const DRW_RawDxfObject &raw : metadata.rawDxfEntities()) {
13139 if (raw.handle != 0 && replaysInDxfExport(raw.m_version)) {
13140 ++sourceHandleCounts[raw.handle];
13141 rawEntityHandles.insert(raw.handle);
13142 }
13143 }
13144 for (const DRW_RawDxfObject &raw : metadata.rawDxfObjects()) {
13145 if (raw.handle == 0 || m_dxfSuppressedObjectHandles.count(raw.handle) != 0 ||
13146 !replaysInDxfExport(raw.m_version))
13147 continue;
13148 ++sourceHandleCounts[raw.handle];
13149 rawObjectHandles.insert(raw.handle);
13150 }
13151
13152 // Only a unique source handle can safely be used as a lookup key. A
13153 // duplicate source handle is still written with the legacy minted-handle
13154 // path, but must not become a false BLOCK_RECORD reverse reference or a
13155 // guessed GROUP member target.
13156 for (const auto &[sourceHandle, count] : sourceHandleCounts) {
13157 if (count > 1) {
13158 m_dxfW->markSourceHandleAmbiguous(sourceHandle);
13159 RS_DEBUGRS_Debug::instance()->print(
13160 RS_Debug::D_WARNING,
13161 "RS_FilterDXFRW: source handle 0x%X is shared by %zu entities; "
13162 "deferred references will be omitted",
13163 sourceHandle, count);
13164 continue;
13165 }
13166 if (count != 1)
13167 continue;
13168 if (attribChildHandles.count(sourceHandle) != 0)
13169 continue;
13170 if (rawEntityHandles.count(sourceHandle) != 0 ||
13171 rawObjectHandles.count(sourceHandle) != 0) {
13172 // Raw records retain their source handle unless a fixed structural
13173 // handle collision requires the already-configured remap. Bind it
13174 // before entity emission so typed and raw deferred references agree.
13175 if (!m_dxfW->bindSourceEntityHandle(sourceHandle,
13176 m_dxfW->remapHandle(sourceHandle)))
13177 m_writeFailed = true;
13178 } else if (m_dxfW->preallocateEntityHandle(sourceHandle) == 0) {
13179 m_writeFailed = true;
13180 }
13181 }
13182
13183 auto collectInsertRefs = [&](RS_EntityContainer *container) {
13184 if (container == nullptr)
13185 return;
13186 for (RS_Entity *entity :
13187 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
13188 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
13189 entity->rtti() != RS2::EntityInsert) {
13190 continue;
13191 }
13192 const std::uint32_t sourceHandle = entity->sourceHandle();
13193 const auto count = sourceHandleCounts.find(sourceHandle);
13194 if (sourceHandle == 0 || count == sourceHandleCounts.end() ||
13195 count->second != 1) {
13196 continue;
13197 }
13198 const std::uint32_t emittedHandle =
13199 m_dxfW->preallocateEntityHandle(sourceHandle);
13200 const auto *insert = static_cast<const RS_Insert *>(entity);
13201 if (emittedHandle == 0 || insert->getName().isEmpty()) {
13202 if (emittedHandle == 0)
13203 m_writeFailed = true;
13204 continue;
13205 }
13206 auto &handles = m_dxfBlockInsertHandles[insert->getName().toUpper()];
13207 if (std::find(handles.cbegin(), handles.cend(), emittedHandle) ==
13208 handles.cend()) {
13209 handles.push_back(emittedHandle);
13210 }
13211 }
13212 };
13213
13214 collectInsertRefs(m_graphic);
13215 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
13216 RS_Block *block = m_graphic->blockAt(i);
13217 if (block != nullptr && !block->isDeleted())
13218 collectInsertRefs(block);
13219 }
13220}
13221
13222void RS_FilterDXFRW::writeBlockRecords() {
13223 // first prepare and send unnamed blocks, the while loop can be omitted for
13224 // R12
13225 prepareBlocks();
13226 prepareDxfEntityHandleMap();
13227 const auto insertRefsFor =
13228 [this](const QString &name) -> const std::vector<std::uint32_t> & {
13229 static const std::vector<std::uint32_t> empty;
13230 const auto it = m_dxfBlockInsertHandles.find(name.toUpper());
13231 return it == m_dxfBlockInsertHandles.end() ? empty : it->second;
13232 };
13233 QHash<RS_Entity *, QString>::const_iterator it = m_noNameBlock.constBegin();
13234 while (it != m_noNameBlock.constEnd()) {
13235 noteDxfWrite(m_dxfW->writeBlockRecord(it.value().toStdString(), 0, {},
13236 insertRefsFor(it.value())));
13237 ++it;
13238 }
13239
13240 // next send "normal" blocks
13241 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
13242 const RS_Block *blk = m_graphic->blockAt(i);
13243 if (!blk->isDeleted()) {
13244 RS_DEBUGRS_Debug::instance()->print("writing block record: %s",
13245 (const char *)blk->getName().toLocal8Bit());
13246 noteDxfWrite(m_dxfW->writeBlockRecord(
13247 blk->getName().toUtf8().data(), blk->getInsertionUnits(),
13248 blk->getPreviewData(), insertRefsFor(blk->getName())));
13249 }
13250 }
13251
13252 // A layout's paper space other than the first (*Paper_Space0, ...) has no
13253 // LibreCAD block, but the layout needs its block record: write it, empty.
13254 m_dxfExtraPaperSpaces = dxfExtraPaperSpaceNames();
13255 for (const std::string &name : m_dxfExtraPaperSpaces)
13256 noteDxfWrite(m_dxfW->writeBlockRecord(name, 0, {}, {}));
13257}
13258
13259std::vector<std::string> RS_FilterDXFRW::dxfExtraPaperSpaceNames() const {
13260 std::vector<std::string> names;
13261 if (m_version < 1015) // LAYOUT objects are R2000 and later
13262 return names;
13263 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13264 const auto &spaces = metadata.spaceBlockRecords();
13265 std::set<std::string> seen;
13266 for (const auto &layout : metadata.layouts()) {
13267 if (layout.replayState !=
13268 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
13269 continue;
13270 const auto space = spaces.find(layout.paperSpaceBlockRecordHandle);
13271 if (space == spaces.end())
13272 continue;
13273 const QString name = QString::fromUtf8(space->second.c_str());
13274 static const QRegularExpression numbered(
13275 QStringLiteral("^\\*paper_space\\d+$")(QString(QtPrivate::qMakeStringPrivate(u"" "^\\*paper_space\\d+$"
)))
,
13276 QRegularExpression::CaseInsensitiveOption);
13277 if (numbered.match(name).hasMatch() &&
13278 seen.insert(name.toLower().toStdString()).second)
13279 names.push_back(space->second);
13280 }
13281 return names;
13282}
13283
13284void RS_FilterDXFRW::installDxfBlockRecordRemap() {
13285 // Block records are written under new handles; a reference to one follows
13286 // it by name. The model and paper space records have fixed handles.
13287 const auto written = [this](const std::string &name) {
13288 return m_dxfW->getBlockRecordHandleToWrite(name);
13289 };
13290 for (const auto &[source, name] :
13291 m_graphic->dwgAdvancedMetadata().spaceBlockRecords()) {
13292 const QString lower = QString::fromUtf8(name.c_str()).toLower();
13293 const std::uint32_t target = lower == QLatin1String("*model_space") ? 0x1Fu
13294 : lower == QLatin1String("*paper_space")
13295 ? 0x1Eu
13296 : written(name);
13297 if (target != DRW::NoHandle)
13298 m_dxfW->addDxfWriteHandleRemap(source, target);
13299 }
13300 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
13301 const RS_Block *blk = m_graphic->blockAt(i);
13302 if (blk->isDeleted() || blk->sourceHandle() == 0)
13303 continue;
13304 const std::uint32_t target = written(blk->getName().toUtf8().toStdString());
13305 if (target != DRW::NoHandle)
13306 m_dxfW->addDxfWriteHandleRemap(blk->sourceHandle(), target);
13307 }
13308}
13309
13310/**
13311 * Writes blocks.
13312 */
13313void RS_FilterDXFRW::writeBlocks() {
13314#ifdef DWGSUPPORT1
13315 if (m_dwgW) {
13316 const auto identityCheckpoint = checkpointDwgWriteIdentity();
13317 const std::uint64_t blockTransaction =
13318 m_dwgW->beginDwgWriteTransaction(false, false, false);
13319 if (blockTransaction == 0) {
13320 m_writeFailed = true;
13321 return;
13322 }
13323 const auto rollbackBlockPhase = [&]() {
13324 (void)m_dwgW->rollbackDwgWriteTransaction(blockTransaction);
13325 restoreDwgWriteIdentity(identityCheckpoint);
13326 m_writeFailed = true;
13327 };
13328 if (!validateDwgExportAdmissionPlan(
13329 DwgOpaqueTableFailurePhase::PostClasses)) {
13330 rollbackBlockPhase();
13331 return;
13332 }
13333 const auto &blockPlan = m_dwgExportAdmissionPlan.blockDefinitions;
13334 std::vector<std::pair<RS_Block *, std::uint32_t>> blockHandles;
13335 blockHandles.reserve(blockPlan.size());
13336
13337 // Register every block before encoding any contents. This makes a
13338 // nested INSERT resolvable even when its referenced definition is
13339 // later in the graphic's block list. Names were normalized and
13340 // admitted before CLASSES; do not rediscover them in this phase.
13341 for (const DwgBlockDefinitionPlanRecord &definition : blockPlan) {
13342 RS_Block *blk = definition.block;
13343 DRW_Coord bp{blk->getBasePoint().x, blk->getBasePoint().y,
13344 blk->getBasePoint().z};
13345 const std::uint32_t handle = m_dwgW->defineBlock(
13346 blk->getName().toUtf8().constData(), bp, blk->getInsertionUnits());
13347 if (handle == 0) {
13348 m_writeFailed = true;
13349 continue;
13350 }
13351 if (!bindDwgWriteBlockName(definition.normalizedName, handle)) {
13352 m_writeFailed = true;
13353 continue;
13354 }
13355 const std::uint32_t sourceHandle = definition.sourceHandle;
13356 if (sourceHandle != 0) {
13357 noteDwgWriteSourceKind(
13358 sourceHandle,
13359 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Block);
13360 if (!selectDwgWriteIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
13361 DwgWriteSourceKind::Block,
13362 sourceHandle, handle)) {
13363 m_writeFailed = true;
13364 continue;
13365 }
13366 const bool inserted =
13367 m_dwgWriteBlockHandleRemap.emplace(sourceHandle, handle).second;
13368 if (!inserted)
13369 m_dwgWriteDuplicateBlockHandles.insert(sourceHandle);
13370 }
13371 blockHandles.emplace_back(blk, handle);
13372 }
13373
13374 if (m_writeFailed) {
13375 rollbackBlockPhase();
13376 return;
13377 }
13378
13379 prepareDwgEntityHandleMap();
13380
13381 for (const auto &blockHandle : blockHandles) {
13382 RS_Block *blk = blockHandle.first;
13383 const bool failedBefore = m_writeFailed;
13384 if (!m_dwgW->beginBlockContent(blockHandle.second)) {
13385 rollbackBlockPhase();
13386 return;
13387 }
13388 std::set<RS_Entity *> consumed;
13389 reconstructPolylineSidecars(blk, consumed);
13390 if (m_writeFailed) {
13391 rollbackBlockPhase();
13392 return;
13393 }
13394 if (!reconstructSingleFrameSidecars(blk, consumed)) {
13395 rollbackBlockPhase();
13396 return;
13397 }
13398 for (RS_Entity *entity :
13399 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
13400 if (!entity->getFlag(RS2::FlagDeleted) && consumed.count(entity) == 0 &&
13401 !writeDimArcSymbolAsArc(entity)) {
13402 writeEntity(entity);
13403 }
13404 }
13405 if (failedBefore || m_writeFailed) {
13406 rollbackBlockPhase();
13407 return;
13408 }
13409 const bool ended = m_dwgW->endBlockContent();
13410 if (!ended || m_writeFailed) {
13411 rollbackBlockPhase();
13412 return;
13413 }
13414 }
13415
13416 if (!m_dwgW->commitDwgWriteTransaction(blockTransaction)) {
13417 rollbackBlockPhase();
13418 return;
13419 }
13420 return;
13421 }
13422#endif
13423
13424 if (m_version > 1009)
13425 installDxfBlockRecordRemap();
13426
13427 // write unnamed blocks
13428 QHash<RS_Entity *, QString>::const_iterator it = m_noNameBlock.constBegin();
13429 while (it != m_noNameBlock.constEnd()) {
13430 DRW_Block block;
13431 block.name = it.value().toStdString();
13432 block.basePoint.x = 0.0;
13433 block.basePoint.y = 0.0;
13434 block.basePoint.z = 0.0;
13435 block.flags = 1; // flag for unnamed block
13436 noteDxfWrite(m_dxfW->writeBlock(&block));
13437 const auto ct = static_cast<RS_EntityContainer *>(it.key());
13438 for (RS_Entity *e :
13439 lc::LC_ContainerTraverser{*ct, RS2::ResolveNone}.entities()) {
13440 if (e->isAlive() && !writeDimArcSymbolAsArc(e)) {
13441 writeEntity(e);
13442 }
13443 }
13444 ++it;
13445 }
13446
13447 // next write "normal" blocks
13448 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
13449 const RS_Block *blk = m_graphic->blockAt(i);
13450 if (!blk->isDeleted()) {
13451 RS_DEBUGRS_Debug::instance()->print("writing block: %s",
13452 (const char *)blk->getName().toLocal8Bit());
13453
13454 DRW_Block block;
13455 block.name = blk->getName().toUtf8().data();
13456 block.basePoint.x = blk->getBasePoint().x;
13457 block.basePoint.y = blk->getBasePoint().y;
13458 block.basePoint.z = blk->getBasePoint().z;
13459 noteDxfWrite(m_dxfW->writeBlock(&block));
13460 for (RS_Entity *e :
13461 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
13462 if (!(e->getFlag(RS2::FlagDeleted))) {
13463 writeEntity(e);
13464 }
13465 }
13466 writeDxfShapes(blk->getName().toUtf8().toStdString());
13467 }
13468 }
13469
13470 for (const std::string &name : m_dxfExtraPaperSpaces) {
13471 DRW_Block block;
13472 block.name = name;
13473 noteDxfWrite(m_dxfW->writeBlock(&block));
13474 }
13475}
13476
13477#ifdef DWGSUPPORT1
13478RS_FilterDXFRW::DwgDataStorageSourceRowKey
13479RS_FilterDXFRW::makeDwgDataStorageSourceRowKey(const std::string &sectionName,
13480 DRW::Version version,
13481 std::size_t rawSectionIndex,
13482 std::size_t recordIndex) {
13483 DwgDataStorageSourceRowKey key;
13484 key.sectionName = normalizeDwgTableName(sectionName);
13485 key.version = version;
13486 key.rawSectionIndex = rawSectionIndex;
13487 key.recordIndex = recordIndex;
13488 return key;
13489}
13490
13491RS_FilterDXFRW::DwgDataStorageIdentityResult
13492RS_FilterDXFRW::resolveDwgDataStorageIdentity(
13493 std::uint32_t ownerHandle, std::uint64_t storageHandle,
13494 bool hasStorageHandle, const std::string &storageHandleKey,
13495 bool hasStorageHandleKey, std::size_t rawSectionIndex,
13496 std::size_t recordIndex) {
13497 DwgDataStorageIdentityResult result;
13498 result.rawSectionIndex = rawSectionIndex;
13499 result.recordIndex = recordIndex;
13500 result.numericHandle = storageHandle;
13501 result.hasNumericHandle = hasStorageHandle && storageHandle != 0;
13502 result.handleKey = hasStorageHandleKey
13503 ? normalizeDwgDataStorageHandleKey(storageHandleKey)
13504 : std::string{};
13505 result.hasHandleKey = hasStorageHandleKey && !result.handleKey.empty();
13506
13507 if (!result.hasNumericHandle && !result.hasHandleKey) {
13508 result.status = DwgDataStorageIdentityStatus::Missing;
13509 result.matchesOwnerBySourceValue = true;
13510 return result;
13511 }
13512
13513 const std::string ownerKey = dwgDataStorageNumericHandleKey(ownerHandle);
13514 const std::string numericKey =
13515 result.hasNumericHandle
13516 ? dwgDataStorageNumericHandleKey(result.numericHandle)
13517 : std::string{};
13518 if (result.hasNumericHandle && result.hasHandleKey &&
13519 numericKey != result.handleKey) {
13520 result.status = DwgDataStorageIdentityStatus::Conflict;
13521 return result;
13522 }
13523
13524 const bool numericMatches = result.hasNumericHandle && ownerHandle != 0 &&
13525 result.numericHandle == ownerHandle;
13526 const bool keyMatches =
13527 result.hasHandleKey && ownerHandle != 0 && result.handleKey == ownerKey;
13528 if ((result.hasNumericHandle && !numericMatches) ||
13529 (result.hasHandleKey && !keyMatches)) {
13530 result.status = DwgDataStorageIdentityStatus::OwnerMismatch;
13531 return result;
13532 }
13533
13534 if (result.hasNumericHandle && result.hasHandleKey)
13535 result.status = DwgDataStorageIdentityStatus::Consistent;
13536 else if (result.hasNumericHandle)
13537 result.status = DwgDataStorageIdentityStatus::NumericOnly;
13538 else
13539 result.status = DwgDataStorageIdentityStatus::LexicalOnly;
13540 result.matchesOwnerBySourceValue = true;
13541 return result;
13542}
13543
13544std::vector<RS_FilterDXFRW::DwgDataStorageCarrier>
13545RS_FilterDXFRW::collectDwgDataStorageCarriers() const {
13546 std::vector<DwgDataStorageCarrier> result;
13547 if (m_graphic == nullptr)
13548 return result;
13549
13550 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13551 const std::string sectionName =
13552 normalizeDwgTableName(DRW_DataStorageSection{}.m_name);
13553 using Descriptor = DwgDataStorageWriterDescriptor;
13554 const auto descriptor =
13555 [](const char *family, const char *className, const char *recordName,
13556 DRW::Version minVersion, DRW::Version maxVersion = DRW::UNKNOWNV,
13557 std::uint16_t classNumber = 0, bool supportsDataStorage = true) {
13558 Descriptor result;
13559 result.family = family;
13560 result.className = className;
13561 result.recordName = recordName;
13562 result.classNumber = classNumber;
13563 result.minVersion = minVersion;
13564 result.maxVersion = maxVersion;
13565 result.supportsDataStorage = supportsDataStorage;
13566 result.binding =
13567 findDwgDataStorageWriterBinding(family, className, recordName);
13568 // The strings above identify an intentionally unbound policy entry or
13569 // select an executable low-level capability. Once bound, copy all
13570 // wire identity and version fields from the authoritative table so a
13571 // carrier cannot advertise metadata that its writer does not implement.
13572 DwgDataStorageWriterCapability capability;
13573 if (result.binding != DwgDataStorageWriterBinding::None &&
13574 getDwgDataStorageWriterCapability(result.binding, capability)) {
13575 result.family = capability.family;
13576 result.className = capability.className;
13577 result.recordName = capability.recordName;
13578 result.classNumber = capability.classNumber;
13579 result.minVersion = capability.minVersion;
13580 result.maxVersion = capability.maxVersion;
13581 result.supportsDataStorage =
13582 supportsDataStorage && capability.acceptsPresenceBit;
13583 result.phase = capability.phase == DwgDataStorageWriterPhase::Section
13584 ? DwgDataStorageCarrier::EmissionPhase::Section
13585 : DwgDataStorageCarrier::EmissionPhase::Objects;
13586 }
13587 return result;
13588 };
13589 const auto descriptorForBinding = [](DwgDataStorageWriterBinding binding) {
13590 Descriptor result;
13591 DwgDataStorageWriterCapability capability;
13592 if (binding == DwgDataStorageWriterBinding::None ||
13593 !getDwgDataStorageWriterCapability(binding, capability))
13594 return result;
13595 result.family = capability.family;
13596 result.className = capability.className;
13597 result.recordName = capability.recordName;
13598 result.classNumber = capability.classNumber;
13599 result.minVersion = capability.minVersion;
13600 result.maxVersion = capability.maxVersion;
13601 result.supportsDataStorage = capability.acceptsPresenceBit;
13602 result.binding = binding;
13603 result.phase = capability.phase == DwgDataStorageWriterPhase::Section
13604 ? DwgDataStorageCarrier::EmissionPhase::Section
13605 : DwgDataStorageCarrier::EmissionPhase::Objects;
13606 return result;
13607 };
13608 const auto appendTyped = [&result, &sectionName](const auto &records,
13609 const Descriptor &writer) {
13610 for (const auto &record : records) {
13611 if (!record.hasDsData)
13612 continue;
13613 DwgDataStorageCarrier carrier;
13614 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
13615 carrier.binding = writer.binding;
13616 carrier.phase = writer.phase;
13617 carrier.capability =
13618 writer.supportsDataStorage &&
13619 writer.binding != DwgDataStorageWriterBinding::None
13620 ? DwgDataStorageCarrier::WriterCapability::Typed
13621 : DwgDataStorageCarrier::WriterCapability::None;
13622 carrier.capabilityMinVersion = writer.minVersion;
13623 carrier.capabilityMaxVersion = writer.maxVersion;
13624 carrier.sectionName = sectionName;
13625 carrier.writerFamily = writer.family;
13626 carrier.className = writer.className;
13627 carrier.recordName = writer.recordName;
13628 carrier.classNumber = writer.classNumber;
13629 carrier.ownerHandle = record.handle;
13630 carrier.storageHandle = record.handle;
13631 carrier.hasStorageHandle = true;
13632 carrier.replayAllowed =
13633 record.replayState ==
13634 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
13635 result.push_back(std::move(carrier));
13636 }
13637 };
13638 const auto appendTypedByDescriptor = [&result, &sectionName](
13639 const auto &record,
13640 const Descriptor &writer) {
13641 DwgDataStorageCarrier carrier;
13642 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
13643 carrier.binding = writer.binding;
13644 carrier.phase = writer.phase;
13645 carrier.capability =
13646 writer.supportsDataStorage &&
13647 writer.binding != DwgDataStorageWriterBinding::None
13648 ? DwgDataStorageCarrier::WriterCapability::Typed
13649 : DwgDataStorageCarrier::WriterCapability::None;
13650 carrier.capabilityMinVersion = writer.minVersion;
13651 carrier.capabilityMaxVersion = writer.maxVersion;
13652 carrier.sectionName = sectionName;
13653 carrier.writerFamily = writer.family == nullptr ? "" : writer.family;
13654 carrier.className = writer.className == nullptr ? "" : writer.className;
13655 carrier.recordName = writer.recordName == nullptr ? "" : writer.recordName;
13656 carrier.classNumber = writer.classNumber;
13657 carrier.ownerHandle = record.handle;
13658 carrier.storageHandle = record.handle;
13659 carrier.hasStorageHandle = true;
13660 carrier.replayAllowed = record.replayState ==
13661 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
13662 result.push_back(std::move(carrier));
13663 };
13664 const auto appendTypedWith =
13665 [&appendTypedByDescriptor](const auto &records,
13666 const auto &descriptorFor) {
13667 for (const auto &record : records) {
13668 if (!record.hasDsData)
13669 continue;
13670 appendTypedByDescriptor(record, descriptorFor(record));
13671 }
13672 };
13673
13674 const auto underlayDescriptor = [&descriptor,
13675 &descriptorForBinding](const auto &record) {
13676 switch (static_cast<DRW_UnderlayDefinition::Kind>(record.kind)) {
13677 case DRW_UnderlayDefinition::PDF:
13678 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayPdf);
13679 case DRW_UnderlayDefinition::DGN:
13680 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayDgn);
13681 case DRW_UnderlayDefinition::DWF:
13682 return descriptorForBinding(DwgDataStorageWriterBinding::UnderlayDwf);
13683 default:
13684 return descriptor("", "", "", DRW::UNKNOWNV);
13685 }
13686 };
13687 const auto pointCloudDescriptor =
13688 [&descriptor, &descriptorForBinding](const auto &record) {
13689 switch (static_cast<DRW_PointCloudDef::Kind>(record.kind)) {
13690 case DRW_PointCloudDef::Definition:
13691 return descriptorForBinding(
13692 DwgDataStorageWriterBinding::PointCloudDefinition);
13693 case DRW_PointCloudDef::DefinitionEx:
13694 return descriptorForBinding(
13695 DwgDataStorageWriterBinding::PointCloudDefinitionEx);
13696 case DRW_PointCloudDef::Reactor:
13697 return descriptorForBinding(
13698 DwgDataStorageWriterBinding::PointCloudDefinitionReactor);
13699 case DRW_PointCloudDef::ReactorEx:
13700 return descriptorForBinding(
13701 DwgDataStorageWriterBinding::PointCloudDefinitionReactorEx);
13702 default:
13703 return descriptor("", "", "", DRW::UNKNOWNV);
13704 }
13705 };
13706 const auto backgroundDescriptor = [&descriptor](const auto &record) {
13707 switch (static_cast<DRW_Background::Kind>(record.kind)) {
13708 case DRW_Background::Skylight:
13709 return descriptor("BACKGROUND", "AcDbSkyBackground", "SKYLIGHTBACKGROUND",
13710 DRW::AC1015, DRW::UNKNOWNV,
13711 DRW_Background::kDwgClassNumSkylight, false);
13712 case DRW_Background::Solid:
13713 return descriptor("BACKGROUND", "AcDbSolidBackground", "SOLIDBACKGROUND",
13714 DRW::AC1015, DRW::UNKNOWNV,
13715 DRW_Background::kDwgClassNumSolid, false);
13716 case DRW_Background::Gradient:
13717 return descriptor("BACKGROUND", "AcDbGradientBackground",
13718 "GRADIENTBACKGROUND", DRW::AC1015, DRW::UNKNOWNV,
13719 DRW_Background::kDwgClassNumGradient, false);
13720 case DRW_Background::GroundPlane:
13721 return descriptor("BACKGROUND", "AcDbGroundPlaneBackground",
13722 "GROUNDPLANEBACKGROUND", DRW::AC1015, DRW::UNKNOWNV,
13723 DRW_Background::kDwgClassNumGroundPlane, false);
13724 case DRW_Background::Image:
13725 return descriptor("BACKGROUND", "AcDbImageBackground", "IMAGEBACKGROUND",
13726 DRW::AC1015, DRW::UNKNOWNV,
13727 DRW_Background::kDwgClassNumImage, false);
13728 case DRW_Background::Ibl:
13729 return descriptor("BACKGROUND", "AcDbIBLBackground", "IBLBACKGROUND",
13730 DRW::AC1015, DRW::UNKNOWNV,
13731 DRW_Background::kDwgClassNumIbl, false);
13732 default:
13733 return descriptor("", "", "", DRW::UNKNOWNV);
13734 }
13735 };
13736 const auto renderSettingsDescriptor = [&descriptor](const auto &record) {
13737 switch (static_cast<DRW_RenderSettings::Kind>(record.kind)) {
13738 case DRW_RenderSettings::Settings:
13739 return descriptor("RENDERSETTINGS", "AcDbRenderSettings",
13740 "RENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
13741 DRW_RenderSettings::kDwgClassNumSettings, false);
13742 case DRW_RenderSettings::Global:
13743 return descriptor("RENDERSETTINGS", "AcDbRenderGlobal", "RENDERGLOBAL",
13744 DRW::AC1015, DRW::UNKNOWNV,
13745 DRW_RenderSettings::kDwgClassNumGlobal, false);
13746 case DRW_RenderSettings::Environment:
13747 return descriptor("RENDERSETTINGS", "AcDbRenderEnvironment",
13748 "RENDERENVIRONMENT", DRW::AC1015, DRW::UNKNOWNV,
13749 DRW_RenderSettings::kDwgClassNumEnvironment, false);
13750 case DRW_RenderSettings::Entry:
13751 return descriptor("RENDERSETTINGS", "AcDbRenderEntry", "RENDERENTRY",
13752 DRW::AC1015, DRW::UNKNOWNV,
13753 DRW_RenderSettings::kDwgClassNumEntry, false);
13754 case DRW_RenderSettings::RapidRT:
13755 return descriptor("RENDERSETTINGS", "AcDbRapidRTRenderSettings",
13756 "RAPIDRTRENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
13757 DRW_RenderSettings::kDwgClassNumRapidRT, false);
13758 case DRW_RenderSettings::MentalRay:
13759 return descriptor("RENDERSETTINGS", "AcDbMentalRayRenderSettings",
13760 "MENTALRAYRENDERSETTINGS", DRW::AC1015, DRW::UNKNOWNV,
13761 DRW_RenderSettings::kDwgClassNumMentalRay, false);
13762 default:
13763 return descriptor("", "", "", DRW::UNKNOWNV);
13764 }
13765 };
13766 const auto sectionDescriptor = [&descriptor](const auto &record) {
13767 switch (static_cast<DRW_Section::Kind>(record.kind)) {
13768 case DRW_Section::Manager:
13769 return descriptor("SECTION", "AcDbSectionManager", "SECTION_MANAGER",
13770 DRW::AC1021, DRW::UNKNOWNV,
13771 DRW_Section::kDwgClassNumManager, false);
13772 case DRW_Section::Settings:
13773 return descriptor("SECTION", "AcDbSectionSettings", "SECTION_SETTINGS",
13774 DRW::AC1021, DRW::UNKNOWNV,
13775 DRW_Section::kDwgClassNumSettings, false);
13776 default:
13777 return descriptor("", "", "", DRW::UNKNOWNV);
13778 }
13779 };
13780
13781 // Keep this list in lockstep with the typed registrations and OBJECTS
13782 // dispatch below. Image definitions, image reactors, and viewport entity
13783 // headers are emitted through other writer phases and have no independent
13784 // DataStorage object receipt here.
13785 appendTypedWith(metadata.underlayDefinitions(), underlayDescriptor);
13786 appendTypedWith(metadata.pointCloudDefinitions(), pointCloudDescriptor);
13787 appendTyped(metadata.navisworksModelDefinitions(),
13788 descriptorForBinding(
13789 DwgDataStorageWriterBinding::NavisworksModelDefinition));
13790 appendTyped(
13791 metadata.pointCloudColorMaps(),
13792 descriptorForBinding(DwgDataStorageWriterBinding::PointCloudColorMap));
13793 appendTyped(
13794 metadata.rasterVariables(),
13795 descriptorForBinding(DwgDataStorageWriterBinding::RasterVariables));
13796 appendTyped(metadata.vxControls(),
13797 descriptorForBinding(DwgDataStorageWriterBinding::VxControl));
13798 appendTyped(metadata.vxTableRecords(),
13799 descriptorForBinding(DwgDataStorageWriterBinding::VxTableRecord));
13800 appendTyped(metadata.visualStyles(),
13801 descriptor("VISUALSTYLE", "AcDbVisualStyle", "VISUALSTYLE",
13802 DRW::AC1015, DRW::UNKNOWNV, 0, false));
13803 appendTyped(metadata.lightLists(),
13804 descriptorForBinding(DwgDataStorageWriterBinding::LightList));
13805 appendTyped(
13806 metadata.tvDeviceProperties(),
13807 descriptorForBinding(DwgDataStorageWriterBinding::TvDeviceProperties));
13808 appendTyped(metadata.suns(), descriptor("SUN", "AcDbSun", "SUN", DRW::AC1021,
13809 DRW::UNKNOWNV, 0, false));
13810 appendTypedWith(metadata.backgrounds(), backgroundDescriptor);
13811 appendTyped(metadata.materials(),
13812 descriptorForBinding(DwgDataStorageWriterBinding::Material));
13813 appendTypedWith(metadata.renderSettings(), renderSettingsDescriptor);
13814 appendTyped(metadata.sunStudies(),
13815 descriptor("SUNSTUDY", "AcDbSunStudy", "SUNSTUDY", DRW::AC1015,
13816 DRW::UNKNOWNV, 0, false));
13817 appendTyped(metadata.motionPaths(),
13818 descriptor("MOTIONPATH", "AcDbMotionPath", "MOTIONPATH",
13819 DRW::AC1015, DRW::UNKNOWNV, 0, false));
13820 appendTyped(metadata.curvePaths(),
13821 descriptor("CURVEPATH", "AcDbCurvePath", "CURVEPATH", DRW::AC1015,
13822 DRW::UNKNOWNV, 0, false));
13823 appendTyped(metadata.pointPaths(),
13824 descriptor("POINTPATH", "AcDbPointPath", "POINTPATH", DRW::AC1015,
13825 DRW::UNKNOWNV, 0, false));
13826 appendTyped(metadata.objectPtrs(),
13827 descriptor("OBJECT_PTR", "AcDbObjectPtr", "OBJECT_PTR",
13828 DRW::AC1015, DRW::UNKNOWNV, 0, false));
13829 appendTyped(metadata.partialViewingIndexes(),
13830 descriptor("PARTIAL_VIEWING_INDEX", "AcDbPartialViewingIndex",
13831 "PARTIAL_VIEWING_INDEX", DRW::AC1015, DRW::UNKNOWNV, 0,
13832 false));
13833 appendTyped(metadata.dbColors(),
13834 descriptorForBinding(DwgDataStorageWriterBinding::DbColor));
13835 appendTyped(
13836 metadata.dimensionAssociations(),
13837 descriptorForBinding(DwgDataStorageWriterBinding::DimensionAssociation));
13838 appendTyped(
13839 metadata.evaluationGraphs(),
13840 descriptorForBinding(DwgDataStorageWriterBinding::EvaluationGraph));
13841 appendTyped(metadata.blockRepresentationData(),
13842 descriptor("BLOCKREPRESENTATIONDATA", "",
13843 "BLOCKREPRESENTATIONDATA", DRW::AC1015, DRW::UNKNOWNV,
13844 0, false));
13845 appendTypedWith(metadata.sections(), sectionDescriptor);
13846 appendTyped(metadata.mleaderStyles(),
13847 descriptorForBinding(DwgDataStorageWriterBinding::MLeaderStyle));
13848 appendTyped(metadata.groups(),
13849 descriptorForBinding(DwgDataStorageWriterBinding::Group));
13850 appendTyped(metadata.spatialFilters(),
13851 descriptorForBinding(DwgDataStorageWriterBinding::SpatialFilter));
13852 appendTyped(metadata.geoData(),
13853 descriptorForBinding(DwgDataStorageWriterBinding::GeoData));
13854 appendTyped(
13855 metadata.placeholders(),
13856 descriptorForBinding(DwgDataStorageWriterBinding::AcDbPlaceholder));
13857 appendTyped(metadata.dictionaries(),
13858 descriptorForBinding(DwgDataStorageWriterBinding::Dictionary));
13859 appendTyped(metadata.xrecords(),
13860 descriptorForBinding(DwgDataStorageWriterBinding::XRecord));
13861 appendTyped(metadata.layouts(),
13862 descriptorForBinding(DwgDataStorageWriterBinding::Layout));
13863 appendTyped(metadata.scales(),
13864 descriptorForBinding(DwgDataStorageWriterBinding::Scale));
13865 appendTyped(metadata.mlineStyles(),
13866 descriptorForBinding(DwgDataStorageWriterBinding::MLineStyle));
13867 appendTyped(
13868 metadata.wipeoutVariables(),
13869 descriptorForBinding(DwgDataStorageWriterBinding::WipeoutVariables));
13870 appendTyped(metadata.idBuffers(),
13871 descriptorForBinding(DwgDataStorageWriterBinding::IDBuffer));
13872 appendTyped(metadata.layerIndexes(),
13873 descriptorForBinding(DwgDataStorageWriterBinding::LayerIndex));
13874 appendTyped(metadata.spatialIndexes(),
13875 descriptorForBinding(DwgDataStorageWriterBinding::SpatialIndex));
13876 appendTyped(metadata.dictionaryVars(),
13877 descriptorForBinding(DwgDataStorageWriterBinding::DictionaryVar));
13878 appendTyped(
13879 metadata.dictionariesWithDefault(),
13880 descriptorForBinding(DwgDataStorageWriterBinding::DictionaryWithDefault));
13881 appendTyped(metadata.sortEntsTables(),
13882 descriptorForBinding(DwgDataStorageWriterBinding::SortEntsTable));
13883 appendTyped(metadata.fieldLists(),
13884 descriptorForBinding(DwgDataStorageWriterBinding::FieldList));
13885 appendTyped(metadata.fields(),
13886 descriptorForBinding(DwgDataStorageWriterBinding::Field));
13887 // Only TABLESTYLE has a typed writer in this metadata collection. Other
13888 // table records use their own native/raw paths and must not become a
13889 // false DataStorage carrier here.
13890 for (const auto &record : metadata.tables()) {
13891 if (!record.hasDsData || !record.nativeTableStyle)
13892 continue;
13893 DwgDataStorageCarrier carrier;
13894 const Descriptor writer =
13895 descriptorForBinding(DwgDataStorageWriterBinding::TableStyle);
13896 carrier.kind = DwgDataStorageCarrier::Kind::Typed;
13897 carrier.binding = writer.binding;
13898 carrier.phase = writer.phase;
13899 carrier.capability = writer.supportsDataStorage
13900 ? DwgDataStorageCarrier::WriterCapability::Typed
13901 : DwgDataStorageCarrier::WriterCapability::None;
13902 carrier.capabilityMinVersion = writer.minVersion;
13903 carrier.capabilityMaxVersion = writer.maxVersion;
13904 carrier.sectionName = sectionName;
13905 carrier.writerFamily = writer.family;
13906 carrier.className = writer.className;
13907 carrier.recordName = writer.recordName;
13908 carrier.classNumber = writer.classNumber;
13909 carrier.ownerHandle = record.handle;
13910 carrier.storageHandle = record.handle;
13911 carrier.hasStorageHandle = true;
13912 carrier.replayAllowed = record.replayState ==
13913 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
13914 result.push_back(std::move(carrier));
13915 }
13916
13917 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
13918 const auto &record = metadata.rawObjects()[index];
13919 if (!record.hasDataStorage)
13920 continue;
13921 DwgDataStorageCarrier carrier;
13922 carrier.kind = DwgDataStorageCarrier::Kind::Raw;
13923 carrier.binding = DwgDataStorageWriterBinding::None;
13924 carrier.phase = DwgDataStorageCarrier::EmissionPhase::Objects;
13925 carrier.capability = DwgDataStorageCarrier::WriterCapability::Raw;
13926 carrier.sectionName = sectionName;
13927 carrier.writerFamily =
13928 record.recordName.empty() ? "RAW_OBJECT" : record.recordName;
13929 carrier.className = record.className;
13930 carrier.recordName = record.recordName;
13931 const bool validCustomClassOrdinal =
13932 !record.isCustomClass ||
13933 (record.objectType >= 500 &&
13934 static_cast<std::uint64_t>(record.objectType) <=
13935 std::numeric_limits<std::uint16_t>::max());
13936 carrier.classNumber = validCustomClassOrdinal && record.isCustomClass
13937 ? static_cast<std::uint16_t>(record.objectType)
13938 : 0;
13939 carrier.ownerHandle = record.handle;
13940 carrier.storageHandle = record.handle;
13941 carrier.hasStorageHandle = true;
13942 carrier.replayAllowed = record.replayState ==
13943 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
13944 carrier.rawObjectIndex = index;
13945 result.push_back(std::move(carrier));
13946 }
13947 return result;
13948}
13949
13950bool RS_FilterDXFRW::prepareDwgDataStorageReplayPlan(DRW::Version version) {
13951 m_dwgDataStorageReplayPlan = {};
13952 m_dwgDataStorageAdmissionSummary = {};
13953 m_dwgDataStorageSectionReceipts.clear();
13954 m_dwgDataStorageCommittedKinds.clear();
13955 m_dwgDataStorageCommitReceipts.clear();
13956 if (m_nextDwgDataStorageReplayGeneration == 0 ||
13957 m_nextDwgDataStorageReplayGeneration ==
13958 std::numeric_limits<std::uint64_t>::max()) {
13959 return false;
13960 }
13961 m_dwgDataStorageReplayPlan.generation =
13962 m_nextDwgDataStorageReplayGeneration++;
13963 m_dwgDataStorageReplayPlan.version = version;
13964 if (m_graphic == nullptr)
13965 return false;
13966
13967 const auto &metadata = m_graphic->dwgAdvancedMetadata();
13968 const std::string canonicalName =
13969 normalizeDwgTableName(DRW_DataStorageSection{}.m_name);
13970 const auto writerCapabilityMatches =
13971 [](const DwgDataStorageCarrier &carrier) {
13972 if (carrier.kind != DwgDataStorageCarrier::Kind::Typed)
13973 return true;
13974 DwgDataStorageWriterCapability capability;
13975 if (!getDwgDataStorageWriterCapability(carrier.binding, capability) ||
13976 !capability.acceptsPresenceBit ||
13977 capability.requiresClass != !carrier.className.empty() ||
13978 carrier.writerFamily != capability.family ||
13979 carrier.className != capability.className ||
13980 carrier.recordName != capability.recordName ||
13981 (carrier.classNumber != 0 && capability.classNumber != 0 &&
13982 carrier.classNumber != capability.classNumber)) {
13983 return false;
13984 }
13985 const bool phaseMatches =
13986 (carrier.phase == DwgDataStorageCarrier::EmissionPhase::Objects &&
13987 capability.phase == DwgDataStorageWriterPhase::Objects) ||
13988 (carrier.phase == DwgDataStorageCarrier::EmissionPhase::Section &&
13989 capability.phase == DwgDataStorageWriterPhase::Section);
13990 return phaseMatches &&
13991 carrier.capabilityMinVersion == capability.minVersion &&
13992 carrier.capabilityMaxVersion == capability.maxVersion;
13993 };
13994 const auto carrierCapabilityAvailable =
13995 [version,
13996 &writerCapabilityMatches](const DwgDataStorageCarrier &carrier) {
13997 if (carrier.capability ==
13998 DwgDataStorageCarrier::WriterCapability::None ||
13999 carrier.phase != DwgDataStorageCarrier::EmissionPhase::Objects)
14000 return false;
14001 if (!writerCapabilityMatches(carrier))
14002 return false;
14003 // dwgRW receives its target version only when write() begins. At this
14004 // stage, use the explicit target version and immutable capability
14005 // table; validate the live writer capability during write preflight.
14006 if (carrier.capabilityMinVersion != DRW::UNKNOWNV &&
14007 version < carrier.capabilityMinVersion)
14008 return false;
14009 if (carrier.capabilityMaxVersion != DRW::UNKNOWNV &&
14010 version > carrier.capabilityMaxVersion)
14011 return false;
14012 return true;
14013 };
14014 const auto resolveCarrierIdentity =
14015 [&](const DwgDataStorageCarrier &carrier, std::size_t rawSectionIndex,
14016 std::size_t recordIndex, std::uint64_t numericHandle,
14017 const std::string &handleKey) {
14018 const std::uint64_t identityHandle =
14019 carrier.hasStorageHandle ? carrier.storageHandle : numericHandle;
14020 const std::string &identityKey =
14021 carrier.hasStorageHandleKey ? carrier.storageHandleKey : handleKey;
14022 return resolveDwgDataStorageIdentity(
14023 carrier.ownerHandle, identityHandle,
14024 carrier.hasStorageHandle || numericHandle != 0, identityKey,
14025 carrier.hasStorageHandleKey || !handleKey.empty(), rawSectionIndex,
14026 recordIndex);
14027 };
14028
14029 m_dwgDataStorageReplayPlan.carriers = collectDwgDataStorageCarriers();
14030 const std::size_t invalidIndex = std::numeric_limits<std::size_t>::max();
14031 for (const auto &carrier : m_dwgDataStorageReplayPlan.carriers) {
14032 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
14033 if (carrier.rawObjectIndex >= metadata.rawObjects().size()) {
14034 m_dwgDataStorageReplayPlan.valid = false;
14035 continue;
14036 }
14037 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
14038 if (raw.isCustomClass &&
14039 (raw.objectType < 500 ||
14040 static_cast<std::uint64_t>(raw.objectType) >
14041 std::numeric_limits<std::uint16_t>::max())) {
14042 m_dwgDataStorageReplayPlan.valid = false;
14043 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14044 "RS_FilterDXFRW: DataStorage raw class ordinal %d for "
14045 "handle 0x%X is outside the DWG 16-bit domain",
14046 raw.objectType, raw.handle);
14047 }
14048 }
14049 const bool unavailableTyped =
14050 carrier.kind == DwgDataStorageCarrier::Kind::Typed &&
14051 carrier.capability == DwgDataStorageCarrier::WriterCapability::None;
14052 const bool kindMatchesCapability =
14053 unavailableTyped ||
14054 (carrier.capability == DwgDataStorageCarrier::WriterCapability::Typed &&
14055 carrier.kind == DwgDataStorageCarrier::Kind::Typed) ||
14056 (carrier.capability == DwgDataStorageCarrier::WriterCapability::Raw &&
14057 carrier.kind == DwgDataStorageCarrier::Kind::Raw);
14058 if (!kindMatchesCapability) {
14059 m_dwgDataStorageReplayPlan.valid = false;
14060 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14061 "RS_FilterDXFRW: DataStorage carrier kind/capability "
14062 "disagree for owner 0x%X",
14063 carrier.ownerHandle);
14064 }
14065 }
14066 if (!m_dwgDataStorageReplayPlan.valid)
14067 return false;
14068
14069 const auto findMatchingRawSection =
14070 [&](const std::string &name, DRW::Version rawVersion) {
14071 std::size_t match = invalidIndex;
14072 for (std::size_t index = 0; index < metadata.rawDwgSections().size();
14073 ++index) {
14074 const auto &raw = metadata.rawDwgSections()[index];
14075 if (normalizeDwgTableName(raw.name) != normalizeDwgTableName(name) ||
14076 raw.version != rawVersion ||
14077 raw.replayState !=
14078 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
14079 continue;
14080 }
14081 if (match != invalidIndex)
14082 return invalidIndex;
14083 match = index;
14084 }
14085 return match;
14086 };
14087
14088 m_dwgDataStorageReplayPlan.decisionByCarrier.assign(
14089 m_dwgDataStorageReplayPlan.carriers.size(), invalidIndex);
14090 const auto storageIdentityKey = [](const std::string &sectionName,
14091 const std::string &recordKey) {
14092 std::string key;
14093 key.reserve(sectionName.size() + recordKey.size() + 1u);
14094 key.append(sectionName);
14095 key.push_back('\n');
14096 key.append(recordKey);
14097 return key;
14098 };
14099 if (version <= DRW::AC1024) {
14100 // Older DWG layouts may expose the common-object marker bit without
14101 // having a DataStorage section. It is advisory there; only an actual
14102 // section or parsed storage record creates a replay obligation.
14103 m_dwgDataStorageReplayPlan.valid = metadata.dataStorages().empty();
14104 for (const auto &raw : metadata.rawDwgSections()) {
14105 if (normalizeDwgTableName(raw.name) == canonicalName)
14106 m_dwgDataStorageReplayPlan.valid = false;
14107 }
14108 if (m_dwgDataStorageReplayPlan.valid) {
14109 for (std::size_t index = 0;
14110 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
14111 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
14112 if (!carrier.replayAllowed || carrier.ownerHandle == 0)
14113 continue;
14114 DwgDataStorageDecisionRecord decision;
14115 decision.decision = DwgDataStorageDecision::LegacyAdvisory;
14116 decision.sectionName = carrier.sectionName;
14117 decision.writerFamily = carrier.writerFamily;
14118 decision.className = carrier.className;
14119 decision.recordName = carrier.recordName;
14120 decision.classNumber = carrier.classNumber;
14121 decision.binding = carrier.binding;
14122 decision.ownerHandle = carrier.ownerHandle;
14123 decision.storageHandle = carrier.storageHandle;
14124 decision.storageHandleKey = carrier.storageHandleKey;
14125 decision.identityStatus =
14126 resolveDwgDataStorageIdentity(
14127 carrier.ownerHandle, carrier.storageHandle,
14128 carrier.hasStorageHandle, carrier.storageHandleKey,
14129 carrier.hasStorageHandleKey)
14130 .status;
14131 decision.typedCarrierIndex =
14132 carrier.capability == DwgDataStorageCarrier::WriterCapability::Typed
14133 ? index
14134 : invalidIndex;
14135 decision.rawCarrierIndex =
14136 carrier.capability == DwgDataStorageCarrier::WriterCapability::Raw
14137 ? index
14138 : invalidIndex;
14139 decision.selectedPhase = carrier.phase;
14140 decision.selectedCapability = carrier.capability;
14141 const std::size_t decisionIndex =
14142 m_dwgDataStorageReplayPlan.decisions.size();
14143 m_dwgDataStorageReplayPlan.decisions.push_back(decision);
14144 m_dwgDataStorageReplayPlan.decisionByCarrier[index] = decisionIndex;
14145 m_dwgDataStorageReplayPlan.decisionByOwner.emplace(carrier.ownerHandle,
14146 decisionIndex);
14147 }
14148 }
14149 return m_dwgDataStorageReplayPlan.valid;
14150 }
14151
14152 // The current DWG writer emits the common-object presence bit and raw
14153 // modeler/surface bodies, but has no independent wire field to patch an
14154 // explicit DataStorage identity. Such a link is safe only when it names
14155 // the entity's own handle; reject a non-owner identity before CLASSES and
14156 // OBJECTS can observe different decisions. This validation is independent
14157 // of carrier collection because these families are currently raw-only.
14158 const auto validateGeometryStorageIdentities = [&]() {
14159 for (const auto &geometry : metadata.modelerGeometry()) {
14160 const DwgDataStorageIdentityResult identity =
14161 resolveDwgDataStorageIdentity(
14162 geometry.handle, geometry.dataStorageHandle,
14163 geometry.dataStorageHandle != 0, geometry.dataStorageHandleKey,
14164 !geometry.dataStorageHandleKey.empty());
14165 if (!geometry.hasDsData || identity.matchesOwnerBySourceValue) {
14166 continue;
14167 }
14168 m_dwgDataStorageReplayPlan.valid = false;
14169 RS_DEBUGRS_Debug::instance()->print(
14170 RS_Debug::D_WARNING,
14171 "RS_FilterDXFRW: explicit DataStorage identity for modeler "
14172 "owner 0x%X is not independently patchable",
14173 geometry.handle);
14174 }
14175 for (const auto &surface : metadata.surfaceGeometry()) {
14176 const DwgDataStorageIdentityResult identity =
14177 resolveDwgDataStorageIdentity(
14178 surface.handle, surface.dataStorageHandle,
14179 surface.dataStorageHandle != 0, surface.dataStorageHandleKey,
14180 !surface.dataStorageHandleKey.empty());
14181 if (!surface.hasDsData || identity.matchesOwnerBySourceValue) {
14182 continue;
14183 }
14184 m_dwgDataStorageReplayPlan.valid = false;
14185 RS_DEBUGRS_Debug::instance()->print(
14186 RS_Debug::D_WARNING,
14187 "RS_FilterDXFRW: explicit DataStorage identity for surface "
14188 "owner 0x%X is not independently patchable",
14189 surface.handle);
14190 }
14191 };
14192 validateGeometryStorageIdentities();
14193 for (const auto &carrier : m_dwgDataStorageReplayPlan.carriers) {
14194 if (carrier.capability != DwgDataStorageCarrier::WriterCapability::Typed ||
14195 carrier.ownerHandle == 0 || !carrierCapabilityAvailable(carrier))
14196 continue;
14197 const DwgDataStorageIdentityResult identity = resolveCarrierIdentity(
14198 carrier, invalidIndex, invalidIndex, carrier.storageHandle,
14199 carrier.hasStorageHandleKey
14200 ? normalizeDwgDataStorageHandleKey(carrier.storageHandleKey)
14201 : dwgDataStorageNumericHandleKey(carrier.storageHandle));
14202 if (!identity.matchesOwnerBySourceValue) {
14203 m_dwgDataStorageReplayPlan.valid = false;
14204 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14205 "RS_FilterDXFRW: explicit DataStorage identity for owner "
14206 "0x%X is not independently patchable",
14207 carrier.ownerHandle);
14208 }
14209 }
14210 if (!m_dwgDataStorageReplayPlan.valid)
14211 return false;
14212
14213 const auto carrierMatches = [&](const DwgDataStorageCarrier &carrier,
14214 std::uint64_t handle,
14215 const std::string &recordKey) {
14216 if (!carrier.hasStorageHandle && !carrier.hasStorageHandleKey)
14217 return false;
14218 const DwgDataStorageIdentityResult identity = resolveDwgDataStorageIdentity(
14219 carrier.ownerHandle, carrier.storageHandle, carrier.hasStorageHandle,
14220 carrier.storageHandleKey, carrier.hasStorageHandleKey);
14221 if (!identity.matchesOwnerBySourceValue)
14222 return false;
14223 const bool numericMatch =
14224 carrier.hasStorageHandle && carrier.storageHandle == handle;
14225 const bool keyMatch =
14226 carrier.hasStorageHandleKey &&
14227 normalizeDwgDataStorageHandleKey(carrier.storageHandleKey) == recordKey;
14228 return carrier.hasStorageHandle && carrier.hasStorageHandleKey
14229 ? numericMatch && keyMatch
14230 : numericMatch || keyMatch;
14231 };
14232
14233 for (std::size_t storageIndex = 0;
14234 storageIndex < metadata.dataStorages().size(); ++storageIndex) {
14235 const auto &storage = metadata.dataStorages()[storageIndex];
14236 const std::size_t rawSectionIndex =
14237 findMatchingRawSection(storage.name, storage.version);
14238 if (storage.version != version || storage.parseFailed ||
14239 !storage.structurallyValid || !storage.replayAllowed ||
14240 rawSectionIndex == invalidIndex ||
14241 !m_dwgDataStorageReplayPlan.sectionNames
14242 .insert(normalizeDwgTableName(storage.name))
14243 .second) {
14244 m_dwgDataStorageReplayPlan.valid = false;
14245 continue;
14246 }
14247 m_dwgDataStorageReplayPlan.rawSectionIndexes.insert(rawSectionIndex);
14248
14249 for (const auto &record : storage.full.records) {
14250 const std::string key =
14251 record.handleKey.empty()
14252 ? dwgDataStorageNumericHandleKey(record.handle)
14253 : normalizeDwgDataStorageHandleKey(record.handleKey);
14254 if (record.handle == 0 ||
14255 (!record.handleKey.empty() &&
14256 key != dwgDataStorageNumericHandleKey(record.handle))) {
14257 m_dwgDataStorageReplayPlan.valid = false;
14258 continue;
14259 }
14260 }
14261
14262 // Duplicate records are revision history, not a structural error.
14263 // Bind a carrier to the parser's preferred occurrence, while the raw
14264 // section replay below retains every source-order occurrence.
14265 std::map<std::string, std::size_t> preferredRecordIndexes;
14266 for (std::size_t recordIndex = 0; recordIndex < storage.full.records.size();
14267 ++recordIndex) {
14268 const auto &record = storage.full.records[recordIndex];
14269 const std::string key =
14270 record.handleKey.empty()
14271 ? dwgDataStorageNumericHandleKey(record.handle)
14272 : normalizeDwgDataStorageHandleKey(record.handleKey);
14273 if (record.handle == 0 ||
14274 (!record.handleKey.empty() &&
14275 key != dwgDataStorageNumericHandleKey(record.handle)))
14276 continue;
14277 preferredRecordIndexes.emplace(key, recordIndex);
14278 }
14279 for (auto &entry : preferredRecordIndexes) {
14280 const auto byKey = storage.full.recordIndexByHandleKey.find(entry.first);
14281 if (byKey != storage.full.recordIndexByHandleKey.end() &&
14282 byKey->second < storage.full.records.size()) {
14283 entry.second = byKey->second;
14284 continue;
14285 }
14286 std::uint64_t numeric = 0;
14287 for (const auto &record : storage.full.records) {
14288 const std::string recordKey =
14289 record.handleKey.empty()
14290 ? dwgDataStorageNumericHandleKey(record.handle)
14291 : normalizeDwgDataStorageHandleKey(record.handleKey);
14292 if (recordKey == entry.first) {
14293 numeric = record.handle;
14294 break;
14295 }
14296 }
14297 const auto byNumber = storage.full.recordIndexByHandle.find(numeric);
14298 if (byNumber != storage.full.recordIndexByHandle.end() &&
14299 byNumber->second < storage.full.records.size())
14300 entry.second = byNumber->second;
14301 }
14302
14303 for (const auto &preferred : preferredRecordIndexes) {
14304 const std::string &key = preferred.first;
14305 const std::size_t recordIndex = preferred.second;
14306 if (recordIndex >= storage.full.records.size()) {
14307 m_dwgDataStorageReplayPlan.valid = false;
14308 continue;
14309 }
14310 const auto &record = storage.full.records[recordIndex];
14311 if (record.handle > std::numeric_limits<std::uint32_t>::max()) {
14312 // The low-level parser retains the complete UInt64 identity,
14313 // but LibreCAD object ownership and handle remapping are
14314 // UInt32. Preserve this record only through an unchanged,
14315 // same-version section replay; never narrow it for linking.
14316 RS_DEBUGRS_Debug::instance()->Log(RS_Debug::D_WARNING)
14317 << "RS_FilterDXFRW: DataStorage record 0x"
14318 << QString::number(record.handle, 16).toUpper()
14319 << " is retained as an opaque wide-identity record";
14320 m_dwgDataStorageReplayPlan.opaqueRecordIndexes[rawSectionIndex].insert(
14321 recordIndex);
14322 continue;
14323 }
14324 std::vector<std::size_t> matchingCarriers;
14325 for (std::size_t index = 0;
14326 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
14327 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
14328 if (carrier.replayAllowed &&
14329 carrierMatches(carrier, record.handle, key)) {
14330 matchingCarriers.push_back(index);
14331 }
14332 }
14333 // A replayed section must not retain a record whose owner will
14334 // not be emitted. Otherwise the section contains a dangling
14335 // identity and the output is structurally inconsistent.
14336 if (matchingCarriers.empty()) {
14337 m_dwgDataStorageReplayPlan.valid = false;
14338 RS_DEBUGRS_Debug::instance()->Log(RS_Debug::D_WARNING)
14339 << "RS_FilterDXFRW: DataStorage record 0x"
14340 << QString::number(record.handle, 16).toUpper()
14341 << " has no emitted owner";
14342 continue;
14343 }
14344 std::size_t typedCarrier = std::numeric_limits<std::size_t>::max();
14345 std::size_t rawCarrier = std::numeric_limits<std::size_t>::max();
14346 for (const std::size_t index : matchingCarriers) {
14347 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
14348 if (carrier.capability ==
14349 DwgDataStorageCarrier::WriterCapability::Typed &&
14350 carrierCapabilityAvailable(carrier)) {
14351 if (typedCarrier != std::numeric_limits<std::size_t>::max())
14352 m_dwgDataStorageReplayPlan.valid = false;
14353 typedCarrier = index;
14354 } else if (carrier.capability ==
14355 DwgDataStorageCarrier::WriterCapability::Raw &&
14356 carrierCapabilityAvailable(carrier)) {
14357 if (rawCarrier != std::numeric_limits<std::size_t>::max())
14358 m_dwgDataStorageReplayPlan.valid = false;
14359 rawCarrier = index;
14360 } else if (carrier.capability ==
14361 DwgDataStorageCarrier::WriterCapability::None) {
14362 // An unsupported typed writer is only a claim. It must
14363 // not block a matching raw carrier, but it still makes
14364 // the plan fail when no usable carrier exists.
14365 }
14366 }
14367 DwgDataStorageDecisionRecord decision;
14368 decision.storageIndex = storageIndex;
14369 decision.rawSectionIndex = rawSectionIndex;
14370 decision.recordIndex = recordIndex;
14371 decision.sourceRow = makeDwgDataStorageSourceRowKey(
14372 storage.name, storage.version, rawSectionIndex, recordIndex);
14373 decision.sectionName = normalizeDwgTableName(storage.name);
14374 decision.ownerHandle =
14375 typedCarrier != std::numeric_limits<std::size_t>::max()
14376 ? m_dwgDataStorageReplayPlan.carriers[typedCarrier].ownerHandle
14377 : rawCarrier != std::numeric_limits<std::size_t>::max()
14378 ? m_dwgDataStorageReplayPlan.carriers[rawCarrier].ownerHandle
14379 : 0;
14380 decision.storageHandle = record.handle;
14381 decision.storageHandleKey = key;
14382 const std::size_t selectedCarrier =
14383 typedCarrier != std::numeric_limits<std::size_t>::max() ? typedCarrier
14384 : rawCarrier;
14385 const DwgDataStorageIdentityResult identity =
14386 selectedCarrier == invalidIndex
14387 ? DwgDataStorageIdentityResult{}
14388 : resolveCarrierIdentity(
14389 m_dwgDataStorageReplayPlan.carriers[selectedCarrier],
14390 rawSectionIndex, recordIndex, record.handle, key);
14391 decision.identityMatchesOwnerBySourceValue =
14392 identity.matchesOwnerBySourceValue;
14393 decision.identityStatus = identity.status;
14394 decision.typedCarrierIndex = typedCarrier;
14395 decision.rawCarrierIndex = rawCarrier;
14396 decision.selectedCarrierIndex = selectedCarrier;
14397 if (selectedCarrier != std::numeric_limits<std::size_t>::max()) {
14398 const auto &carrier =
14399 m_dwgDataStorageReplayPlan.carriers[selectedCarrier];
14400 decision.writerFamily = carrier.writerFamily;
14401 decision.className = carrier.className;
14402 decision.recordName = carrier.recordName;
14403 decision.classNumber = carrier.classNumber;
14404 decision.binding = carrier.binding;
14405 decision.selectedPhase = carrier.phase;
14406 decision.selectedCapability = carrier.capability;
14407 }
14408 if (typedCarrier != std::numeric_limits<std::size_t>::max()) {
14409 decision.decision = DwgDataStorageDecision::EmitTyped;
14410 } else if (rawCarrier != std::numeric_limits<std::size_t>::max() &&
14411 metadata.rawObjects().size() >
14412 m_dwgDataStorageReplayPlan.carriers[rawCarrier]
14413 .rawObjectIndex) {
14414 const auto &raw =
14415 metadata
14416 .rawObjects()[m_dwgDataStorageReplayPlan.carriers[rawCarrier]
14417 .rawObjectIndex];
14418 if (!raw.rawBytes.empty() &&
14419 sameRawObjectEncodingFamily(raw.version, version) &&
14420 !isFixedStructuralDwgHandle(raw.handle) &&
14421 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) ==
14422 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
14423 decision.decision = DwgDataStorageDecision::EmitRaw;
14424 }
14425 }
14426 if (decision.decision == DwgDataStorageDecision::Fail ||
14427 decision.ownerHandle == 0 ||
14428 !m_dwgDataStorageReplayPlan.decisionByOwner
14429 .emplace(decision.ownerHandle,
14430 m_dwgDataStorageReplayPlan.decisions.size())
14431 .second) {
14432 m_dwgDataStorageReplayPlan.valid = false;
14433 if (decision.decision == DwgDataStorageDecision::Fail) {
14434 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14435 "RS_FilterDXFRW: DataStorage record 0x%llX has no "
14436 "compatible writer",
14437 static_cast<unsigned long long>(record.handle));
14438 }
14439 }
14440 const std::size_t decisionIndex =
14441 m_dwgDataStorageReplayPlan.decisions.size();
14442 if (!decision.sourceRow.isValid() ||
14443 !m_dwgDataStorageReplayPlan.decisionBySourceRow
14444 .emplace(decision.sourceRow, decisionIndex)
14445 .second) {
14446 m_dwgDataStorageReplayPlan.valid = false;
14447 }
14448 if (!m_dwgDataStorageReplayPlan.decisionByStorageIdentity
14449 .emplace(storageIdentityKey(decision.sectionName, key),
14450 decisionIndex)
14451 .second) {
14452 m_dwgDataStorageReplayPlan.valid = false;
14453 }
14454 m_dwgDataStorageReplayPlan.decisions.push_back(std::move(decision));
14455 for (const std::size_t index : matchingCarriers)
14456 m_dwgDataStorageReplayPlan.decisionByCarrier[index] = decisionIndex;
14457 }
14458 }
14459
14460 for (std::size_t index = 0;
14461 index < m_dwgDataStorageReplayPlan.carriers.size(); ++index) {
14462 const auto &carrier = m_dwgDataStorageReplayPlan.carriers[index];
14463 if (m_dwgDataStorageReplayPlan.decisionByCarrier[index] == invalidIndex &&
14464 carrier.capability != DwgDataStorageCarrier::WriterCapability::None) {
14465 m_dwgDataStorageReplayPlan.valid = false;
14466 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
14467 "RS_FilterDXFRW: DataStorage carrier for owner 0x%X has "
14468 "no selected record",
14469 carrier.ownerHandle);
14470 }
14471 }
14472
14473 for (const auto &raw : metadata.rawDwgSections()) {
14474 if (normalizeDwgTableName(raw.name) != canonicalName)
14475 continue;
14476 const bool matched = std::any_of(
14477 metadata.dataStorages().cbegin(), metadata.dataStorages().cend(),
14478 [&](const LC_DwgAdvancedMetadata::DataStorageRecord &storage) {
14479 return storage.version == raw.version &&
14480 normalizeDwgTableName(storage.name) ==
14481 normalizeDwgTableName(raw.name);
14482 });
14483 if (!matched)
14484 m_dwgDataStorageReplayPlan.valid = false;
14485 }
14486 return m_dwgDataStorageReplayPlan.valid;
14487}
14488
14489bool RS_FilterDXFRW::validateDwgDataStoragePreflight(
14490 DRW::Version targetVersion) const {
14491 if (m_dwgW == nullptr || m_graphic == nullptr ||
14492 targetVersion <= DRW::AC1024) {
14493 return true;
14494 }
14495
14496 const auto &plan = m_dwgDataStorageReplayPlan;
14497 if (!plan.valid || plan.frozen || plan.version != targetVersion ||
14498 plan.generation == 0) {
14499 return false;
14500 }
14501
14502 const auto &metadata = m_graphic->dwgAdvancedMetadata();
14503 const std::size_t invalidIndex = std::numeric_limits<std::size_t>::max();
14504 std::set<DwgDataStorageSourceRowKey> seenSourceRows;
14505 for (const auto &decision : plan.decisions) {
14506 if (decision.storageIndex >= metadata.dataStorages().size() ||
14507 decision.rawSectionIndex >= metadata.rawDwgSections().size() ||
14508 decision.recordIndex == invalidIndex ||
14509 decision.selectedCarrierIndex == invalidIndex ||
14510 decision.selectedCarrierIndex >= plan.carriers.size()) {
14511 return false;
14512 }
14513 const auto &section = metadata.rawDwgSections()[decision.rawSectionIndex];
14514 const DwgDataStorageSourceRowKey expectedSourceRow =
14515 makeDwgDataStorageSourceRowKey(section.name, section.version,
14516 decision.rawSectionIndex,
14517 decision.recordIndex);
14518 if (!decision.sourceRow.isValid() ||
14519 decision.sourceRow != expectedSourceRow ||
14520 !seenSourceRows.insert(decision.sourceRow).second) {
14521 return false;
14522 }
14523
14524 const auto &storage = metadata.dataStorages()[decision.storageIndex];
14525 if (storage.version != targetVersion || storage.parseFailed ||
14526 !storage.structurallyValid || !storage.replayAllowed ||
14527 normalizeDwgTableName(storage.name) !=
14528 normalizeDwgTableName(section.name) ||
14529 section.version != targetVersion ||
14530 section.replayState !=
14531 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
14532 section.data.empty() ||
14533 decision.recordIndex >= storage.full.records.size()) {
14534 return false;
14535 }
14536
14537 const auto &record = storage.full.records[decision.recordIndex];
14538 const std::string recordKey =
14539 record.handleKey.empty()
14540 ? dwgDataStorageNumericHandleKey(record.handle)
14541 : normalizeDwgDataStorageHandleKey(record.handleKey);
14542 if (record.handle == 0 ||
14543 record.handle > std::numeric_limits<std::uint32_t>::max() ||
14544 decision.ownerHandle != record.handle ||
14545 decision.storageHandle != record.handle ||
14546 decision.storageHandleKey.empty() ||
14547 normalizeDwgDataStorageHandleKey(decision.storageHandleKey) !=
14548 recordKey) {
14549 return false;
14550 }
14551 if (decision.identityStatus == DwgDataStorageIdentityStatus::Conflict ||
14552 decision.identityStatus ==
14553 DwgDataStorageIdentityStatus::OwnerMismatch) {
14554 return false;
14555 }
14556 if (hasDwgWriteHandleRemap(decision.ownerHandle) ||
14557 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ||
14558 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0) {
14559 return false;
14560 }
14561
14562 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
14563 const bool emitRaw = decision.decision == DwgDataStorageDecision::EmitRaw;
14564 const DwgDataStorageCarrier::Kind expectedKind =
14565 emitRaw ? DwgDataStorageCarrier::Kind::Raw
14566 : DwgDataStorageCarrier::Kind::Typed;
14567 const DwgDataStorageCarrier::WriterCapability expectedCapability =
14568 emitRaw ? DwgDataStorageCarrier::WriterCapability::Raw
14569 : DwgDataStorageCarrier::WriterCapability::Typed;
14570 if (decision.decision != DwgDataStorageDecision::EmitRaw &&
14571 decision.decision != DwgDataStorageDecision::EmitTyped) {
14572 return false;
14573 }
14574 if (carrier.kind != expectedKind ||
14575 carrier.capability != expectedCapability ||
14576 carrier.phase != DwgDataStorageCarrier::EmissionPhase::Objects ||
14577 carrier.ownerHandle != decision.ownerHandle ||
14578 carrier.binding != decision.binding || !carrier.replayAllowed) {
14579 return false;
14580 }
14581
14582 if (!emitRaw) {
14583 DwgDataStorageWriterCapability capability;
14584 if (!getDwgDataStorageWriterCapability(carrier.binding, capability) ||
14585 !m_dwgW->canWriteDwgDataStorageBinding(carrier.binding) ||
14586 carrier.writerFamily != capability.family ||
14587 carrier.className != capability.className ||
14588 carrier.recordName != capability.recordName ||
14589 (carrier.classNumber != 0 && capability.classNumber != 0 &&
14590 carrier.classNumber != capability.classNumber) ||
14591 carrier.capabilityMinVersion != capability.minVersion ||
14592 carrier.capabilityMaxVersion != capability.maxVersion) {
14593 return false;
14594 }
14595 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
14596 const auto styleIt = std::find_if(
14597 metadata.mleaderStyles().cbegin(), metadata.mleaderStyles().cend(),
14598 [&decision](const auto &style) {
14599 return style.handle == decision.ownerHandle;
14600 });
14601 std::uint32_t payloadBitEstimate = 0;
14602 if (styleIt == metadata.mleaderStyles().cend() ||
14603 !validateDwgMLeaderStylePayload(*styleIt, targetVersion,
14604 payloadBitEstimate)) {
14605 return false;
14606 }
14607 }
14608 } else {
14609 if (decision.rawCarrierIndex != decision.selectedCarrierIndex ||
14610 carrier.rawObjectIndex >= metadata.rawObjects().size()) {
14611 return false;
14612 }
14613 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
14614 if (!raw.hasDataStorage || raw.handle != decision.ownerHandle ||
14615 raw.version != targetVersion || raw.rawBytes.empty() ||
14616 isFixedStructuralDwgHandle(raw.handle) ||
14617 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) !=
14618 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
14619 !sameRawObjectEncodingFamily(raw.version, targetVersion)) {
14620 return false;
14621 }
14622 }
14623 }
14624
14625 return seenSourceRows.size() == plan.decisions.size() &&
14626 plan.decisionBySourceRow.size() == plan.decisions.size() &&
14627 plan.decisionByStorageIdentity.size() == plan.decisions.size();
14628}
14629
14630bool RS_FilterDXFRW::freezeDwgDataStorageReplayPlan() {
14631 if (m_dwgW == nullptr || m_graphic == nullptr ||
14632 !m_dwgDataStorageReplayPlan.valid) {
14633 return false;
14634 }
14635
14636 for (auto &decision : m_dwgDataStorageReplayPlan.decisions) {
14637 if (decision.ownerHandle == 0 ||
14638 hasDwgWriteHandleRemap(decision.ownerHandle) ||
14639 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ||
14640 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0) {
14641 RS_DEBUGRS_Debug::instance()->print(
14642 RS_Debug::D_WARNING,
14643 "RS_FilterDXFRW: DataStorage owner 0x%X cannot be frozen "
14644 "(handleRemap=%d entityRemap=%d duplicate=%d)",
14645 decision.ownerHandle,
14646 hasDwgWriteHandleRemap(decision.ownerHandle) ? 1 : 0,
14647 m_dwgWriteEntityHandleRemap.count(decision.ownerHandle) != 0 ? 1 : 0,
14648 m_dwgWriteDuplicateEntityHandles.count(decision.ownerHandle) != 0
14649 ? 1
14650 : 0);
14651 decision.decision = DwgDataStorageDecision::Fail;
14652 m_dwgDataStorageReplayPlan.valid = false;
14653 }
14654 }
14655 if (!m_dwgDataStorageReplayPlan.valid)
14656 return false;
14657
14658 if (m_dwgW->getVersion() > DRW::AC1024) {
14659 for (auto &decision : m_dwgDataStorageReplayPlan.decisions) {
14660 if (m_nextDwgDataStorageAdmissionToken == 0 ||
14661 m_nextDwgDataStorageAdmissionToken ==
14662 std::numeric_limits<std::uint64_t>::max()) {
14663 m_dwgDataStorageReplayPlan.valid = false;
14664 break;
14665 }
14666 decision.admissionToken = m_nextDwgDataStorageAdmissionToken++;
14667 }
14668 if (!m_dwgDataStorageReplayPlan.valid)
14669 return false;
14670
14671 const auto invalidIndex = std::numeric_limits<std::size_t>::max();
14672 const auto &metadata = m_graphic->dwgAdvancedMetadata();
14673 std::map<std::uint32_t, DwgDataStorageCommitReceipt> plannedReceipts;
14674 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
14675 if (decision.ownerHandle == 0 ||
14676 decision.selectedCarrierIndex == invalidIndex ||
14677 decision.selectedCarrierIndex >=
14678 m_dwgDataStorageReplayPlan.carriers.size()) {
14679 m_dwgDataStorageReplayPlan.valid = false;
14680 break;
14681 }
14682 const auto &carrier =
14683 m_dwgDataStorageReplayPlan.carriers[decision.selectedCarrierIndex];
14684 DwgDataStorageCommitReceipt receipt;
14685 receipt.generation = m_dwgDataStorageReplayPlan.generation;
14686 receipt.carrierIndex = decision.selectedCarrierIndex;
14687 receipt.kind = decision.decision == DwgDataStorageDecision::EmitRaw
14688 ? DwgDataStorageCarrier::Kind::Raw
14689 : DwgDataStorageCarrier::Kind::Typed;
14690 receipt.binding = carrier.binding;
14691 receipt.phase = carrier.phase;
14692 receipt.capability = carrier.capability;
14693 receipt.state = DwgDataStorageLedgerState::Planned;
14694 receipt.sourceKind = DwgWriteSourceKind::Object;
14695 receipt.classNumber = carrier.classNumber;
14696 receipt.admissionToken = decision.admissionToken;
14697 receipt.sourceRow = decision.sourceRow;
14698 receipt.writerFamily = carrier.writerFamily;
14699 receipt.className = carrier.className;
14700 receipt.recordName = carrier.recordName;
14701 receipt.sourceHandle = decision.ownerHandle;
14702 receipt.outputHandle = resolveDwgWriteHandle(decision.ownerHandle);
14703 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
14704 carrier.rawObjectIndex < metadata.rawObjects().size()) {
14705 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
14706 receipt.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
14707 : DwgWriteSourceKind::Object;
14708 receipt.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
14709 ? raw.blockOwnerHandle
14710 : raw.parentHandle;
14711 }
14712 if (!plannedReceipts.emplace(receipt.sourceHandle, std::move(receipt))
14713 .second) {
14714 m_dwgDataStorageReplayPlan.valid = false;
14715 break;
14716 }
14717 }
14718 if (!m_dwgDataStorageReplayPlan.valid)
14719 return false;
14720 m_dwgDataStorageCommitReceipts = std::move(plannedReceipts);
14721 }
14722 if (!m_dwgDataStorageReplayPlan.valid)
14723 return false;
14724 if (m_dwgW->getVersion() > DRW::AC1024) {
14725 DwgDataStorageAdmissionSummary summary;
14726 summary.version = m_dwgDataStorageReplayPlan.version;
14727 summary.generation = m_dwgDataStorageReplayPlan.generation;
14728 summary.valid = true;
14729 summary.entries.reserve(m_dwgDataStorageReplayPlan.decisions.size());
14730 const auto invalidIndex = std::numeric_limits<std::size_t>::max();
14731 const auto &metadata = m_graphic->dwgAdvancedMetadata();
14732 for (std::size_t decisionIndex = 0;
14733 decisionIndex < m_dwgDataStorageReplayPlan.decisions.size();
14734 ++decisionIndex) {
14735 const auto &decision =
14736 m_dwgDataStorageReplayPlan.decisions[decisionIndex];
14737 if ((decision.decision != DwgDataStorageDecision::EmitTyped &&
14738 decision.decision != DwgDataStorageDecision::EmitRaw) ||
14739 decision.selectedCarrierIndex == invalidIndex ||
14740 decision.selectedCarrierIndex >=
14741 m_dwgDataStorageReplayPlan.carriers.size()) {
14742 summary.valid = false;
14743 break;
14744 }
14745 const auto &carrier =
14746 m_dwgDataStorageReplayPlan.carriers[decision.selectedCarrierIndex];
14747 DwgDataStorageAdmissionEntry entry;
14748 entry.decisionIndex = decisionIndex;
14749 entry.carrierIndex = decision.selectedCarrierIndex;
14750 entry.sourceHandle = decision.ownerHandle;
14751 entry.outputHandle = resolveDwgWriteHandle(decision.ownerHandle);
14752 entry.classNumber = carrier.classNumber;
14753 entry.admissionToken = decision.admissionToken;
14754 entry.sourceRow = decision.sourceRow;
14755 entry.kind = carrier.kind;
14756 entry.phase = carrier.phase;
14757 entry.capability = carrier.capability;
14758 entry.binding = carrier.binding;
14759 entry.sourceKind = DwgWriteSourceKind::Object;
14760 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
14761 if (carrier.rawObjectIndex >= metadata.rawObjects().size()) {
14762 summary.valid = false;
14763 break;
14764 }
14765 const auto &raw = metadata.rawObjects()[carrier.rawObjectIndex];
14766 entry.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
14767 : DwgWriteSourceKind::Object;
14768 entry.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
14769 ? raw.blockOwnerHandle
14770 : raw.parentHandle;
14771 entry.classRequired = raw.isCustomClass;
14772 } else {
14773 DwgDataStorageWriterCapability capability;
14774 if (!getDwgDataStorageWriterCapability(carrier.binding, capability)) {
14775 summary.valid = false;
14776 break;
14777 }
14778 entry.classRequired = capability.requiresClass;
14779 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
14780 const auto styleIt = std::find_if(
14781 metadata.mleaderStyles().cbegin(),
14782 metadata.mleaderStyles().cend(), [&decision](const auto &style) {
14783 return style.handle == decision.ownerHandle;
14784 });
14785 if (styleIt == metadata.mleaderStyles().cend() ||
14786 !validateDwgMLeaderStylePayload(*styleIt, m_dwgW->getVersion(),
14787 entry.payloadBitEstimate)) {
14788 summary.valid = false;
14789 break;
14790 }
14791 entry.payloadValidated = true;
14792 }
14793 }
14794 const auto receiptIt =
14795 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
14796 if (entry.sourceHandle == 0 || entry.outputHandle == 0 ||
14797 receiptIt == m_dwgDataStorageCommitReceipts.end() ||
14798 receiptIt->second.generation !=
14799 m_dwgDataStorageReplayPlan.generation ||
14800 receiptIt->second.carrierIndex != decision.selectedCarrierIndex ||
14801 receiptIt->second.kind != carrier.kind ||
14802 receiptIt->second.admissionToken != entry.admissionToken ||
14803 receiptIt->second.outputHandle != entry.outputHandle) {
14804 summary.valid = false;
14805 break;
14806 }
14807 summary.entries.push_back(std::move(entry));
14808 }
14809 if (summary.entries.size() != m_dwgDataStorageReplayPlan.decisions.size()) {
14810 summary.valid = false;
14811 }
14812 if (!summary.valid) {
14813 m_dwgDataStorageReplayPlan.valid = false;
14814 return false;
14815 }
14816 m_dwgDataStorageAdmissionSummary = std::move(summary);
14817 }
14818 m_dwgDataStorageReplayPlan.frozen = true;
14819
14820 for (const std::uint32_t handle : m_dwgWriteDataStorageKnownHandles)
14821 m_dwgWriteKnownHandles.erase(handle);
14822 m_dwgWriteDataStorageKnownHandles.clear();
14823 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
14824 if (decision.decision != DwgDataStorageDecision::EmitRaw)
14825 continue;
14826 const auto &carrier =
14827 m_dwgDataStorageReplayPlan.carriers[decision.rawCarrierIndex];
14828 m_dwgWriteKnownHandles.insert(carrier.ownerHandle);
14829 m_dwgWriteDataStorageKnownHandles.insert(carrier.ownerHandle);
14830 }
14831 return true;
14832}
14833
14834bool RS_FilterDXFRW::validateDwgDataStorageAdmissionSummary(
14835 bool requireClassAdmission) const {
14836 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
14837 return true;
14838
14839 const auto &plan = m_dwgDataStorageReplayPlan;
14840 const auto &summary = m_dwgDataStorageAdmissionSummary;
14841 if (!plan.frozen || !plan.valid || plan.version != m_dwgW->getVersion() ||
14842 plan.generation == 0 || !summary.valid ||
14843 summary.version != plan.version ||
14844 summary.generation != plan.generation ||
14845 summary.entries.size() != plan.decisions.size() ||
14846 m_dwgDataStorageCommitReceipts.size() != summary.entries.size()) {
14847 return false;
14848 }
14849
14850 std::set<std::uint32_t> seenHandles;
14851 for (const auto &entry : summary.entries) {
14852 if (entry.decisionIndex >= plan.decisions.size() ||
14853 entry.carrierIndex >= plan.carriers.size() || entry.sourceHandle == 0 ||
14854 entry.outputHandle == 0 ||
14855 !seenHandles.insert(entry.sourceHandle).second) {
14856 return false;
14857 }
14858 const auto &decision = plan.decisions[entry.decisionIndex];
14859 const auto &carrier = plan.carriers[entry.carrierIndex];
14860 if (m_graphic == nullptr ||
14861 entry.sourceRow.rawSectionIndex >=
14862 m_graphic->dwgAdvancedMetadata().rawDwgSections().size() ||
14863 entry.sourceRow.recordIndex ==
14864 std::numeric_limits<std::size_t>::max()) {
14865 return false;
14866 }
14867 const auto &sourceSection =
14868 m_graphic->dwgAdvancedMetadata()
14869 .rawDwgSections()[entry.sourceRow.rawSectionIndex];
14870 const auto expectedSourceRow = makeDwgDataStorageSourceRowKey(
14871 sourceSection.name, sourceSection.version,
14872 entry.sourceRow.rawSectionIndex, entry.sourceRow.recordIndex);
14873 if (decision.storageIndex >=
14874 m_graphic->dwgAdvancedMetadata().dataStorages().size()) {
14875 return false;
14876 }
14877 const auto &sourceStorage =
14878 m_graphic->dwgAdvancedMetadata().dataStorages()[decision.storageIndex];
14879 if (decision.recordIndex >= sourceStorage.full.records.size() ||
14880 sourceStorage.version != sourceSection.version ||
14881 normalizeDwgTableName(sourceStorage.name) !=
14882 normalizeDwgTableName(sourceSection.name)) {
14883 return false;
14884 }
14885 const auto &sourceRecord = sourceStorage.full.records[decision.recordIndex];
14886 const std::string sourceRecordKey =
14887 sourceRecord.handleKey.empty()
14888 ? dwgDataStorageNumericHandleKey(sourceRecord.handle)
14889 : normalizeDwgDataStorageHandleKey(sourceRecord.handleKey);
14890 if (sourceRecord.handle == 0 ||
14891 sourceRecord.handle != decision.ownerHandle ||
14892 decision.storageHandle != sourceRecord.handle ||
14893 decision.storageHandleKey.empty() ||
14894 normalizeDwgDataStorageHandleKey(decision.storageHandleKey) !=
14895 sourceRecordKey) {
14896 return false;
14897 }
14898 const auto ownerIt = plan.decisionByOwner.find(entry.sourceHandle);
14899 if (ownerIt == plan.decisionByOwner.end() ||
14900 ownerIt->second != entry.decisionIndex ||
14901 decision.ownerHandle != entry.sourceHandle ||
14902 decision.selectedCarrierIndex != entry.carrierIndex ||
14903 decision.selectedPhase != entry.phase ||
14904 decision.selectedCapability != entry.capability ||
14905 decision.admissionToken == 0 ||
14906 decision.admissionToken != entry.admissionToken ||
14907 !decision.sourceRow.isValid() ||
14908 decision.rawSectionIndex != entry.sourceRow.rawSectionIndex ||
14909 decision.recordIndex != entry.sourceRow.recordIndex ||
14910 decision.sourceRow != expectedSourceRow ||
14911 entry.sourceRow != expectedSourceRow ||
14912 decision.binding != entry.binding ||
14913 carrier.ownerHandle != entry.sourceHandle ||
14914 carrier.kind != entry.kind || carrier.phase != entry.phase ||
14915 carrier.capability != entry.capability ||
14916 carrier.binding != entry.binding ||
14917 carrier.classNumber != entry.classNumber ||
14918 resolveDwgWriteHandle(entry.sourceHandle) != entry.outputHandle) {
14919 return false;
14920 }
14921
14922 DwgWriteSourceKind currentSourceKind = DwgWriteSourceKind::Object;
14923 std::uint32_t currentBlockOwner = DRW::NoHandle;
14924 bool currentClassRequired = false;
14925 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw) {
14926 if (m_graphic == nullptr ||
14927 carrier.rawObjectIndex >=
14928 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
14929 return false;
14930 }
14931 const auto &raw =
14932 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
14933 currentSourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
14934 : DwgWriteSourceKind::Object;
14935 currentBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
14936 ? raw.blockOwnerHandle
14937 : raw.parentHandle;
14938 currentClassRequired = raw.isCustomClass;
14939 } else {
14940 DwgDataStorageWriterCapability capability;
14941 if (!getDwgDataStorageWriterCapability(carrier.binding, capability)) {
14942 return false;
14943 }
14944 currentClassRequired = capability.requiresClass;
14945 }
14946 if (entry.sourceKind != currentSourceKind ||
14947 entry.expectedBlockOwner != currentBlockOwner ||
14948 entry.classRequired != currentClassRequired) {
14949 return false;
14950 }
14951
14952 if (carrier.binding == DwgDataStorageWriterBinding::MLeaderStyle) {
14953 if (m_graphic == nullptr)
14954 return false;
14955 const auto styleIt = std::find_if(
14956 m_graphic->dwgAdvancedMetadata().mleaderStyles().cbegin(),
14957 m_graphic->dwgAdvancedMetadata().mleaderStyles().cend(),
14958 [&entry](const auto &style) {
14959 return style.handle == entry.sourceHandle;
14960 });
14961 std::uint32_t payloadBitEstimate = 0;
14962 if (styleIt == m_graphic->dwgAdvancedMetadata().mleaderStyles().cend() ||
14963 !entry.payloadValidated ||
14964 !validateDwgMLeaderStylePayload(*styleIt, m_dwgW->getVersion(),
14965 payloadBitEstimate) ||
14966 entry.payloadBitEstimate != payloadBitEstimate) {
14967 return false;
14968 }
14969 } else if (entry.payloadValidated || entry.payloadBitEstimate != 0) {
14970 return false;
14971 }
14972
14973 const auto receiptIt =
14974 m_dwgDataStorageCommitReceipts.find(entry.sourceHandle);
14975 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
14976 return false;
14977 const auto &receipt = receiptIt->second;
14978 if (receipt.generation != summary.generation ||
14979 receipt.carrierIndex != entry.carrierIndex ||
14980 receipt.kind != entry.kind || receipt.binding != entry.binding ||
14981 receipt.phase != entry.phase ||
14982 receipt.capability != entry.capability ||
14983 receipt.sourceHandle != entry.sourceHandle ||
14984 receipt.outputHandle != entry.outputHandle ||
14985 receipt.sourceKind != entry.sourceKind ||
14986 receipt.expectedBlockOwner != entry.expectedBlockOwner ||
14987 receipt.classNumber != entry.classNumber ||
14988 receipt.admissionToken != entry.admissionToken ||
14989 receipt.sourceRow != entry.sourceRow ||
14990 receipt.state == DwgDataStorageLedgerState::Failed ||
14991 receipt.state == DwgDataStorageLedgerState::Suppressed ||
14992 (entry.classRequired && requireClassAdmission &&
14993 !receipt.classAdmitted)) {
14994 return false;
14995 }
14996 if (receipt.objectCommitted &&
14997 (!receipt.frameValid || !receipt.frame.valid ||
14998 receipt.writerClassNumber != receipt.frame.classNumber ||
14999 receipt.frame.admissionToken != receipt.admissionToken)) {
15000 return false;
15001 }
15002 }
15003 return seenHandles.size() == plan.decisions.size();
15004}
15005
15006bool RS_FilterDXFRW::recordDwgDataStorageClassAdmission(
15007 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind) {
15008 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
15009 return true;
15010 const auto &plan = m_dwgDataStorageReplayPlan;
15011 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
15012 plan.version != m_dwgW->getVersion() || plan.generation == 0)
15013 return false;
15014 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
15015 if (decisionIt == plan.decisionByOwner.end() ||
15016 decisionIt->second >= plan.decisions.size())
15017 return false;
15018 const auto &decision = plan.decisions[decisionIt->second];
15019 const DwgDataStorageCarrier::Kind expectedKind =
15020 decision.decision == DwgDataStorageDecision::EmitRaw
15021 ? DwgDataStorageCarrier::Kind::Raw
15022 : DwgDataStorageCarrier::Kind::Typed;
15023 if (kind != expectedKind || decision.selectedCarrierIndex >=
15024 m_dwgDataStorageReplayPlan.carriers.size()) {
15025 return false;
15026 }
15027
15028 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
15029 auto [it, inserted] = m_dwgDataStorageCommitReceipts.emplace(
15030 sourceHandle, DwgDataStorageCommitReceipt{});
15031 auto &receipt = it->second;
15032 if (inserted) {
15033 receipt.generation = plan.generation;
15034 receipt.carrierIndex = decision.selectedCarrierIndex;
15035 receipt.kind = expectedKind;
15036 receipt.binding = carrier.binding;
15037 receipt.phase = carrier.phase;
15038 receipt.capability = carrier.capability;
15039 receipt.sourceHandle = sourceHandle;
15040 receipt.writerFamily = carrier.writerFamily;
15041 receipt.className = carrier.className;
15042 receipt.recordName = carrier.recordName;
15043 receipt.classNumber = carrier.classNumber;
15044 receipt.admissionToken = decision.admissionToken;
15045 receipt.sourceRow = decision.sourceRow;
15046 receipt.sourceKind = DwgWriteSourceKind::Object;
15047 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
15048 carrier.rawObjectIndex <
15049 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
15050 const auto &raw =
15051 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
15052 receipt.sourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
15053 : DwgWriteSourceKind::Object;
15054 receipt.expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
15055 ? raw.blockOwnerHandle
15056 : raw.parentHandle;
15057 }
15058 }
15059 if (receipt.generation != plan.generation ||
15060 receipt.carrierIndex != decision.selectedCarrierIndex ||
15061 receipt.kind != expectedKind || receipt.binding != carrier.binding ||
15062 receipt.writerFamily != carrier.writerFamily ||
15063 receipt.className != carrier.className ||
15064 receipt.recordName != carrier.recordName ||
15065 receipt.classNumber != carrier.classNumber ||
15066 receipt.admissionToken != decision.admissionToken ||
15067 receipt.sourceRow != decision.sourceRow ||
15068 receipt.state != DwgDataStorageLedgerState::Planned ||
15069 receipt.classAdmitted || receipt.objectCommitted ||
15070 receipt.sectionCommitted) {
15071 return false;
15072 }
15073 receipt.classAdmitted = true;
15074 receipt.state = DwgDataStorageLedgerState::ClassAdmitted;
15075 return true;
15076}
15077
15078bool RS_FilterDXFRW::bindDwgDataStorageFrame(std::uint32_t sourceHandle,
15079 DwgDataStorageCarrier::Kind kind) {
15080 if (m_dwgW == nullptr)
15081 return false;
15082 m_dwgW->clearDwgObjectFrameProvenance();
15083 if (m_dwgW->getVersion() <= DRW::AC1024)
15084 return true;
15085
15086 const auto *decision = dataStorageDecisionForOwner(sourceHandle);
15087 if (decision == nullptr)
15088 return true;
15089 const DwgDataStorageCarrier::Kind expectedKind =
15090 decision->decision == DwgDataStorageDecision::EmitRaw
15091 ? DwgDataStorageCarrier::Kind::Raw
15092 : DwgDataStorageCarrier::Kind::Typed;
15093 if (kind != expectedKind ||
15094 decision->selectedPhase !=
15095 DwgDataStorageCarrier::EmissionPhase::Objects ||
15096 decision->selectedCarrierIndex >=
15097 m_dwgDataStorageReplayPlan.carriers.size()) {
15098 return false;
15099 }
15100
15101 const auto &carrier =
15102 m_dwgDataStorageReplayPlan.carriers[decision->selectedCarrierIndex];
15103 std::uint16_t writerClassNumber = 0;
15104 std::uint16_t writerOperation = 0;
15105 if (kind == DwgDataStorageCarrier::Kind::Typed) {
15106 DwgDataStorageWriterCapability capability;
15107 if (!getDwgDataStorageWriterCapability(carrier.binding, capability))
15108 return false;
15109 writerOperation = static_cast<std::uint16_t>(capability.operation);
15110 if (!carrier.className.empty()) {
15111 writerClassNumber = m_dwgW->getDwgTypedClassNumber(
15112 carrier.className.c_str(), carrier.recordName.c_str(),
15113 carrier.classNumber);
15114 if (writerClassNumber == 0)
15115 return false;
15116 }
15117 }
15118 // Both typed and raw classes can be remapped during admission. Query the
15119 // writer's owner ledger so the frame receipt carries the actual ordinal.
15120 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
15121 std::uint16_t admittedClassNumber = 0;
15122 if (m_dwgW->getDwgClassInstanceClass(outputHandle, admittedClassNumber)) {
15123 if (writerClassNumber != 0 && writerClassNumber != admittedClassNumber)
15124 return false;
15125 writerClassNumber = admittedClassNumber;
15126 }
15127 return m_dwgW->setDwgObjectFrameProvenance(writerClassNumber, writerOperation,
15128 decision->admissionToken);
15129}
15130
15131bool RS_FilterDXFRW::recordDwgDataStorageObjectCommit(
15132 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind,
15133 DwgWriteSourceKind sourceKind, std::uint32_t outputHandle,
15134 const DRW::DwgObjectFrameReceipt &frame) {
15135 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
15136 return true;
15137 const auto &plan = m_dwgDataStorageReplayPlan;
15138 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
15139 plan.version != m_dwgW->getVersion() || plan.generation == 0)
15140 return false;
15141 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
15142 if (decisionIt == plan.decisionByOwner.end() ||
15143 decisionIt->second >= plan.decisions.size())
15144 return false;
15145 const auto &decision = plan.decisions[decisionIt->second];
15146 const DwgDataStorageCarrier::Kind expectedKind =
15147 decision.decision == DwgDataStorageDecision::EmitRaw
15148 ? DwgDataStorageCarrier::Kind::Raw
15149 : DwgDataStorageCarrier::Kind::Typed;
15150 if (kind != expectedKind ||
15151 decision.selectedCarrierIndex >=
15152 m_dwgDataStorageReplayPlan.carriers.size() ||
15153 !frame.valid || outputHandle == 0 || frame.objectHandle != outputHandle ||
15154 frame.version != m_dwgW->getVersion()) {
15155 return false;
15156 }
15157 const auto &carrier = plan.carriers[decision.selectedCarrierIndex];
15158 DwgDataStorageWriterCapability writerCapability;
15159 if (expectedKind == DwgDataStorageCarrier::Kind::Typed &&
15160 !getDwgDataStorageWriterCapability(carrier.binding, writerCapability)) {
15161 return false;
15162 }
15163 const std::uint16_t expectedWriterOperation =
15164 expectedKind == DwgDataStorageCarrier::Kind::Typed
15165 ? static_cast<std::uint16_t>(writerCapability.operation)
15166 : 0;
15167 const std::uint16_t expectedWriterClass =
15168 expectedKind == DwgDataStorageCarrier::Kind::Typed &&
15169 !carrier.className.empty()
15170 ? m_dwgW->getDwgTypedClassNumber(carrier.className.c_str(),
15171 carrier.recordName.c_str(),
15172 carrier.classNumber)
15173 : 0;
15174 const bool classRequired =
15175 expectedKind == DwgDataStorageCarrier::Kind::Raw
15176 ? (carrier.rawObjectIndex <
15177 m_graphic->dwgAdvancedMetadata().rawObjects().size() &&
15178 m_graphic->dwgAdvancedMetadata()
15179 .rawObjects()[carrier.rawObjectIndex]
15180 .isCustomClass)
15181 : !carrier.className.empty();
15182 if (decision.admissionToken == 0 ||
15183 frame.admissionToken != decision.admissionToken ||
15184 frame.writerOperation != expectedWriterOperation ||
15185 (classRequired && frame.classNumber == 0) ||
15186 (expectedWriterClass != 0 && frame.classNumber != expectedWriterClass)) {
15187 return false;
15188 }
15189 auto [it, inserted] = m_dwgDataStorageCommitReceipts.emplace(
15190 sourceHandle, DwgDataStorageCommitReceipt{});
15191 auto &receipt = it->second;
15192 if (inserted) {
15193 receipt.generation = plan.generation;
15194 receipt.carrierIndex = decision.selectedCarrierIndex;
15195 receipt.kind = expectedKind;
15196 receipt.binding = carrier.binding;
15197 receipt.phase = carrier.phase;
15198 receipt.capability = carrier.capability;
15199 receipt.sourceHandle = sourceHandle;
15200 receipt.writerFamily = carrier.writerFamily;
15201 receipt.className = carrier.className;
15202 receipt.recordName = carrier.recordName;
15203 receipt.classNumber = carrier.classNumber;
15204 receipt.admissionToken = decision.admissionToken;
15205 }
15206 if (receipt.generation != plan.generation ||
15207 receipt.carrierIndex != decision.selectedCarrierIndex ||
15208 receipt.kind != expectedKind || receipt.binding != carrier.binding ||
15209 receipt.phase != carrier.phase ||
15210 receipt.capability != carrier.capability ||
15211 receipt.writerFamily != carrier.writerFamily ||
15212 receipt.className != carrier.className ||
15213 receipt.recordName != carrier.recordName ||
15214 receipt.classNumber != carrier.classNumber ||
15215 receipt.admissionToken != decision.admissionToken ||
15216 receipt.sourceRow != decision.sourceRow ||
15217 receipt.sourceKind != sourceKind ||
15218 (classRequired && !receipt.classAdmitted) ||
15219 (receipt.outputHandle != 0 && receipt.outputHandle != outputHandle) ||
15220 (receipt.state != DwgDataStorageLedgerState::Planned &&
15221 receipt.state != DwgDataStorageLedgerState::ClassAdmitted) ||
15222 receipt.objectCommitted) {
15223 return false;
15224 }
15225 receipt.sourceKind = sourceKind;
15226 if (carrier.kind == DwgDataStorageCarrier::Kind::Raw &&
15227 carrier.rawObjectIndex <
15228 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
15229 const auto &raw =
15230 m_graphic->dwgAdvancedMetadata().rawObjects()[carrier.rawObjectIndex];
15231 const std::uint32_t expectedOwner = raw.blockOwnerHandle != DRW::NoHandle
15232 ? raw.blockOwnerHandle
15233 : raw.parentHandle;
15234 if (receipt.expectedBlockOwner != DRW::NoHandle &&
15235 receipt.expectedBlockOwner != expectedOwner) {
15236 return false;
15237 }
15238 receipt.expectedBlockOwner = expectedOwner;
15239 }
15240 receipt.outputHandle = outputHandle;
15241 receipt.objectCommitted = true;
15242 receipt.frameValid = true;
15243 receipt.frame = frame;
15244 receipt.writerClassNumber = frame.classNumber;
15245 const auto sectionIt =
15246 m_dwgDataStorageSectionReceipts.find(decision.rawSectionIndex);
15247 receipt.sectionCommitted =
15248 sectionIt != m_dwgDataStorageSectionReceipts.end() &&
15249 sectionIt->second.recordIndexes.count(decision.recordIndex) != 0;
15250 receipt.state = receipt.sectionCommitted
15251 ? DwgDataStorageLedgerState::SectionCommitted
15252 : DwgDataStorageLedgerState::ObjectCommitted;
15253 m_dwgDataStorageCommittedKinds[sourceHandle] = kind;
15254 return true;
15255}
15256
15257bool RS_FilterDXFRW::recordDwgDataStorageSectionCommit(
15258 std::size_t rawSectionIndex, const std::set<std::size_t> &recordIndexes) {
15259 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024)
15260 return true;
15261 const auto &plan = m_dwgDataStorageReplayPlan;
15262 if (m_graphic == nullptr || !plan.frozen || !plan.valid ||
15263 plan.version != m_dwgW->getVersion() || plan.generation == 0)
15264 return false;
15265 const auto planSection = plan.rawSectionIndexes.find(rawSectionIndex);
15266 if (planSection == plan.rawSectionIndexes.end())
15267 return false;
15268 if (rawSectionIndex >=
15269 m_graphic->dwgAdvancedMetadata().rawDwgSections().size())
15270 return false;
15271 const auto &rawSection =
15272 m_graphic->dwgAdvancedMetadata().rawDwgSections()[rawSectionIndex];
15273 const LC_DwgAdvancedMetadata::DataStorageRecord *storage = nullptr;
15274 for (const auto &candidate :
15275 m_graphic->dwgAdvancedMetadata().dataStorages()) {
15276 if (candidate.version == rawSection.version &&
15277 normalizeDwgTableName(candidate.name) ==
15278 normalizeDwgTableName(rawSection.name)) {
15279 if (storage != nullptr)
15280 return false;
15281 storage = &candidate;
15282 }
15283 }
15284 if (storage == nullptr)
15285 return false;
15286 std::set<std::size_t> expected;
15287 for (std::size_t index = 0; index < storage->full.records.size(); ++index)
15288 expected.insert(index);
15289 if (recordIndexes != expected)
15290 return false;
15291 if (m_dwgDataStorageSectionReceipts.find(rawSectionIndex) !=
15292 m_dwgDataStorageSectionReceipts.end())
15293 return false;
15294 for (const auto &decision : plan.decisions) {
15295 if (decision.rawSectionIndex != rawSectionIndex)
15296 continue;
15297 const auto ledger =
15298 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
15299 if (ledger == m_dwgDataStorageCommitReceipts.end() ||
15300 ledger->second.generation != plan.generation ||
15301 ledger->second.state == DwgDataStorageLedgerState::Failed ||
15302 ledger->second.state == DwgDataStorageLedgerState::Suppressed)
15303 return false;
15304 }
15305 DwgDataStorageSectionReceipt receipt;
15306 receipt.generation = plan.generation;
15307 receipt.rawSectionIndex = rawSectionIndex;
15308 receipt.recordIndexes = recordIndexes;
15309 for (const std::size_t recordIndex : recordIndexes) {
15310 receipt.sourceRows.insert(makeDwgDataStorageSourceRowKey(
15311 rawSection.name, rawSection.version, rawSectionIndex, recordIndex));
15312 const bool linked =
15313 std::any_of(plan.decisions.cbegin(), plan.decisions.cend(),
15314 [rawSectionIndex, recordIndex](const auto &decision) {
15315 return decision.rawSectionIndex == rawSectionIndex &&
15316 decision.recordIndex == recordIndex;
15317 });
15318 if (!linked)
15319 receipt.opaqueRecordIndexes.insert(recordIndex);
15320 }
15321 m_dwgDataStorageSectionReceipts[rawSectionIndex] = receipt;
15322 for (const auto &decision : plan.decisions) {
15323 if (decision.rawSectionIndex != rawSectionIndex)
15324 continue;
15325 const auto ledger =
15326 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
15327 if (ledger == m_dwgDataStorageCommitReceipts.end())
15328 continue;
15329 auto &entry = ledger->second;
15330 entry.sectionCommitted = true;
15331 if (entry.objectCommitted)
15332 entry.state = DwgDataStorageLedgerState::SectionCommitted;
15333 }
15334 return true;
15335}
15336
15337void RS_FilterDXFRW::failDwgDataStorageCommit(std::uint32_t sourceHandle) {
15338 const auto receiptIt = m_dwgDataStorageCommitReceipts.find(sourceHandle);
15339 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
15340 return;
15341 auto &receipt = receiptIt->second;
15342 if (receipt.generation != m_dwgDataStorageReplayPlan.generation)
15343 return;
15344 receipt.state = DwgDataStorageLedgerState::Failed;
15345 receipt.objectCommitted = false;
15346 receipt.sectionCommitted = false;
15347 receipt.frameValid = false;
15348 receipt.frame = {};
15349}
15350
15351bool RS_FilterDXFRW::hasDwgDataStorageClassAdmission(
15352 std::uint32_t sourceHandle, DwgDataStorageCarrier::Kind kind) const {
15353 if (m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024 ||
15354 sourceHandle == 0)
15355 return false;
15356 const auto &plan = m_dwgDataStorageReplayPlan;
15357 if (!plan.frozen || !plan.valid || plan.version != m_dwgW->getVersion() ||
15358 plan.generation == 0)
15359 return false;
15360 const auto decisionIt = plan.decisionByOwner.find(sourceHandle);
15361 if (decisionIt == plan.decisionByOwner.end() ||
15362 decisionIt->second >= plan.decisions.size())
15363 return false;
15364 const auto receiptIt = m_dwgDataStorageCommitReceipts.find(sourceHandle);
15365 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
15366 return false;
15367 const auto &decision = plan.decisions[decisionIt->second];
15368 const auto &receipt = receiptIt->second;
15369 return receipt.generation == plan.generation &&
15370 receipt.carrierIndex == decision.selectedCarrierIndex &&
15371 receipt.kind == kind && receipt.binding == decision.binding &&
15372 receipt.classAdmitted;
15373}
15374
15375bool RS_FilterDXFRW::validateDwgDataStorageReplayReceipts(
15376 DRW::Version targetVersion) const {
15377 if (targetVersion <= DRW::AC1024)
15378 return true;
15379
15380 const auto &plan = m_dwgDataStorageReplayPlan;
15381 if (!plan.frozen || !plan.valid || plan.version != targetVersion ||
15382 plan.generation == 0)
15383 return false;
15384
15385 std::map<std::size_t, std::set<std::size_t>> expected;
15386 if (m_graphic == nullptr)
15387 return false;
15388 const auto &metadata = m_graphic->dwgAdvancedMetadata();
15389 for (const std::size_t rawSectionIndex : plan.rawSectionIndexes) {
15390 if (rawSectionIndex >= metadata.rawDwgSections().size())
15391 return false;
15392 const auto &rawSection = metadata.rawDwgSections()[rawSectionIndex];
15393 const LC_DwgAdvancedMetadata::DataStorageRecord *storage = nullptr;
15394 for (const auto &candidate : metadata.dataStorages()) {
15395 if (candidate.version == rawSection.version &&
15396 normalizeDwgTableName(candidate.name) ==
15397 normalizeDwgTableName(rawSection.name)) {
15398 if (storage != nullptr)
15399 return false;
15400 storage = &candidate;
15401 }
15402 }
15403 if (storage == nullptr)
15404 return false;
15405 auto &indexes = expected[rawSectionIndex];
15406 for (std::size_t index = 0; index < storage->full.records.size(); ++index)
15407 indexes.insert(index);
15408 }
15409 for (const auto &decision : plan.decisions) {
15410 if (decision.rawSectionIndex == std::numeric_limits<std::size_t>::max() ||
15411 decision.recordIndex == std::numeric_limits<std::size_t>::max())
15412 return false;
15413 const auto it = expected.find(decision.rawSectionIndex);
15414 if (it == expected.end() || it->second.count(decision.recordIndex) == 0)
15415 return false;
15416 }
15417
15418 if (m_dwgDataStorageSectionReceipts.size() != expected.size())
15419 return false;
15420 for (const auto &entry : expected) {
15421 const auto receiptIt = m_dwgDataStorageSectionReceipts.find(entry.first);
15422 if (receiptIt == m_dwgDataStorageSectionReceipts.end())
15423 return false;
15424 const auto &rawSection = metadata.rawDwgSections()[entry.first];
15425 const auto &receipt = receiptIt->second;
15426 if (receipt.generation != plan.generation ||
15427 receipt.rawSectionIndex != entry.first ||
15428 receipt.recordIndexes != entry.second)
15429 return false;
15430 std::set<DwgDataStorageSourceRowKey> expectedSourceRows;
15431 for (const std::size_t recordIndex : entry.second) {
15432 expectedSourceRows.insert(makeDwgDataStorageSourceRowKey(
15433 rawSection.name, rawSection.version, entry.first, recordIndex));
15434 }
15435 if (receipt.sourceRows != expectedSourceRows)
15436 return false;
15437 std::set<std::size_t> linked;
15438 for (const auto &decision : plan.decisions) {
15439 if (decision.rawSectionIndex == entry.first)
15440 linked.insert(decision.recordIndex);
15441 }
15442 std::set<std::size_t> opaque = entry.second;
15443 for (const std::size_t recordIndex : linked)
15444 opaque.erase(recordIndex);
15445 if (receipt.opaqueRecordIndexes != opaque)
15446 return false;
15447 }
15448
15449 // A section receipt proves only that the opaque section was copied. The
15450 // OBJECTS phase must also commit exactly the carrier selected by the
15451 // frozen decision, otherwise CLASSES and OBJECTS can describe different
15452 // instances under the same source handle. The receipt is authoritative;
15453 // the legacy committed-kind map is only a compatibility index.
15454 if (m_dwgDataStorageCommitReceipts.size() != plan.decisions.size())
15455 return false;
15456 for (const auto &decision : plan.decisions) {
15457 if (decision.selectedCarrierIndex >= plan.carriers.size())
15458 return false;
15459 if (decision.admissionToken == 0)
15460 return false;
15461 if (!decision.sourceRow.isValid() ||
15462 plan.decisionBySourceRow.find(decision.sourceRow) ==
15463 plan.decisionBySourceRow.end() ||
15464 plan.decisionBySourceRow.at(decision.sourceRow) >=
15465 plan.decisions.size() ||
15466 &plan.decisions[plan.decisionBySourceRow.at(decision.sourceRow)] !=
15467 &decision)
15468 return false;
15469 const std::size_t expectedCarrierIndex =
15470 decision.decision == DwgDataStorageDecision::EmitRaw
15471 ? decision.rawCarrierIndex
15472 : decision.typedCarrierIndex;
15473 if (decision.selectedCarrierIndex != expectedCarrierIndex)
15474 return false;
15475 const auto &selectedCarrier = plan.carriers[decision.selectedCarrierIndex];
15476 const DwgDataStorageCarrier::Kind expectedKind =
15477 decision.decision == DwgDataStorageDecision::EmitRaw
15478 ? DwgDataStorageCarrier::Kind::Raw
15479 : DwgDataStorageCarrier::Kind::Typed;
15480 const DwgDataStorageCarrier::WriterCapability expectedCapability =
15481 decision.decision == DwgDataStorageDecision::EmitRaw
15482 ? DwgDataStorageCarrier::WriterCapability::Raw
15483 : DwgDataStorageCarrier::WriterCapability::Typed;
15484 if (selectedCarrier.ownerHandle != decision.ownerHandle ||
15485 selectedCarrier.phase !=
15486 DwgDataStorageCarrier::EmissionPhase::Objects ||
15487 selectedCarrier.kind != expectedKind ||
15488 selectedCarrier.capability != expectedCapability ||
15489 decision.binding != selectedCarrier.binding ||
15490 decision.identityStatus == DwgDataStorageIdentityStatus::Conflict ||
15491 decision.identityStatus == DwgDataStorageIdentityStatus::OwnerMismatch)
15492 return false;
15493 if (decision.selectedPhase !=
15494 DwgDataStorageCarrier::EmissionPhase::Objects ||
15495 decision.selectedCapability != expectedCapability)
15496 return false;
15497 const auto receiptIt =
15498 m_dwgDataStorageCommitReceipts.find(decision.ownerHandle);
15499 if (receiptIt == m_dwgDataStorageCommitReceipts.end())
15500 return false;
15501 const auto &receipt = receiptIt->second;
15502 DwgWriteSourceKind expectedSourceKind = DwgWriteSourceKind::Object;
15503 std::uint32_t expectedBlockOwner = DRW::NoHandle;
15504 if (selectedCarrier.kind == DwgDataStorageCarrier::Kind::Raw &&
15505 m_graphic != nullptr &&
15506 selectedCarrier.rawObjectIndex <
15507 m_graphic->dwgAdvancedMetadata().rawObjects().size()) {
15508 const auto &raw = m_graphic->dwgAdvancedMetadata()
15509 .rawObjects()[selectedCarrier.rawObjectIndex];
15510 expectedSourceKind = raw.isEntity ? DwgWriteSourceKind::Entity
15511 : DwgWriteSourceKind::Object;
15512 expectedBlockOwner = raw.blockOwnerHandle != DRW::NoHandle
15513 ? raw.blockOwnerHandle
15514 : raw.parentHandle;
15515 }
15516 const bool classRequired =
15517 expectedKind == DwgDataStorageCarrier::Kind::Raw
15518 ? (selectedCarrier.rawObjectIndex <
15519 (m_graphic == nullptr ? 0u
15520 : m_graphic->dwgAdvancedMetadata()
15521 .rawObjects()
15522 .size()) &&
15523 m_graphic->dwgAdvancedMetadata()
15524 .rawObjects()[selectedCarrier.rawObjectIndex]
15525 .isCustomClass)
15526 : !selectedCarrier.className.empty();
15527 std::uint16_t expectedWriterOperation = 0;
15528 std::uint16_t expectedWriterClass = 0;
15529 if (expectedKind == DwgDataStorageCarrier::Kind::Typed) {
15530 DwgDataStorageWriterCapability capability;
15531 if (!getDwgDataStorageWriterCapability(selectedCarrier.binding,
15532 capability)) {
15533 return false;
15534 }
15535 expectedWriterOperation =
15536 static_cast<std::uint16_t>(capability.operation);
15537 if (!selectedCarrier.className.empty()) {
15538 expectedWriterClass = m_dwgW->getDwgTypedClassNumber(
15539 selectedCarrier.className.c_str(),
15540 selectedCarrier.recordName.c_str(), selectedCarrier.classNumber);
15541 }
15542 }
15543 if (receipt.generation != plan.generation ||
15544 receipt.carrierIndex != decision.selectedCarrierIndex ||
15545 receipt.kind != expectedKind || receipt.binding != decision.binding ||
15546 receipt.phase != decision.selectedPhase ||
15547 receipt.capability != expectedCapability ||
15548 receipt.sourceHandle != decision.ownerHandle ||
15549 receipt.outputHandle == 0 ||
15550 receipt.outputHandle != resolveDwgWriteHandle(decision.ownerHandle) ||
15551 receipt.sourceKind != expectedSourceKind ||
15552 receipt.expectedBlockOwner != expectedBlockOwner ||
15553 receipt.writerFamily != selectedCarrier.writerFamily ||
15554 receipt.className != selectedCarrier.className ||
15555 receipt.recordName != selectedCarrier.recordName ||
15556 receipt.classNumber != selectedCarrier.classNumber ||
15557 receipt.admissionToken != decision.admissionToken ||
15558 receipt.sourceRow != decision.sourceRow ||
15559 (classRequired && !receipt.classAdmitted) || !receipt.objectCommitted ||
15560 !receipt.sectionCommitted ||
15561 receipt.state != DwgDataStorageLedgerState::SectionCommitted ||
15562 !receipt.frameValid || !receipt.frame.valid ||
15563 receipt.frame.objectHandle != receipt.outputHandle ||
15564 receipt.frame.version != targetVersion ||
15565 (classRequired && receipt.frame.classNumber == 0) ||
15566 receipt.writerClassNumber != receipt.frame.classNumber ||
15567 receipt.frame.writerOperation != expectedWriterOperation ||
15568 receipt.frame.admissionToken != decision.admissionToken ||
15569 (expectedWriterClass != 0 &&
15570 receipt.frame.classNumber != expectedWriterClass))
15571 return false;
15572 }
15573 return true;
15574}
15575
15576const RS_FilterDXFRW::DwgDataStorageDecisionRecord *
15577RS_FilterDXFRW::dataStorageDecisionForCarrier(std::size_t carrierIndex) const {
15578 const auto &plan = m_dwgDataStorageReplayPlan;
15579 if (!plan.frozen || !plan.valid ||
15580 carrierIndex >= plan.decisionByCarrier.size())
15581 return nullptr;
15582 const std::size_t decisionIndex = plan.decisionByCarrier[carrierIndex];
15583 if (decisionIndex >= plan.decisions.size())
15584 return nullptr;
15585 return &plan.decisions[decisionIndex];
15586}
15587
15588const RS_FilterDXFRW::DwgDataStorageDecisionRecord *
15589RS_FilterDXFRW::dataStorageDecisionForOwner(std::uint32_t ownerHandle) const {
15590 const auto &plan = m_dwgDataStorageReplayPlan;
15591 if (!plan.frozen || !plan.valid || ownerHandle == 0)
15592 return nullptr;
15593 const auto it = plan.decisionByOwner.find(ownerHandle);
15594 if (it == plan.decisionByOwner.end() || it->second >= plan.decisions.size())
15595 return nullptr;
15596 return &plan.decisions[it->second];
15597}
15598
15599bool RS_FilterDXFRW::isDwgDataStorageTypedSelected(
15600 std::uint32_t ownerHandle) const {
15601 const auto *decision = dataStorageDecisionForOwner(ownerHandle);
15602 return decision == nullptr ||
15603 decision->decision == DwgDataStorageDecision::EmitTyped;
15604}
15605
15606bool RS_FilterDXFRW::isDwgDataStorageRawSelected(
15607 std::uint32_t ownerHandle) const {
15608 const auto *decision = dataStorageDecisionForOwner(ownerHandle);
15609 return decision != nullptr &&
15610 decision->decision == DwgDataStorageDecision::EmitRaw;
15611}
15612
15613bool RS_FilterDXFRW::allowsDwgDataStoragePresenceBit(
15614 std::uint32_t sourceHandle) const {
15615 if (m_dwgW == nullptr || sourceHandle == 0)
15616 return false;
15617 // AC1027 introduced the common-object DataStorage presence bit. Earlier
15618 // versions may carry an advisory source marker, but cannot encode it.
15619 return m_dwgW->getVersion() <= DRW::AC1024 ||
15620 canReplayDwgDataStorage(sourceHandle);
15621}
15622
15623bool RS_FilterDXFRW::encodedDwgDataStoragePresenceBit(
15624 std::uint32_t sourceHandle, bool requested) const {
15625 if (!requested || m_dwgW == nullptr || m_dwgW->getVersion() <= DRW::AC1024) {
15626 return false;
15627 }
15628 return canReplayDwgDataStorage(sourceHandle);
15629}
15630
15631bool RS_FilterDXFRW::canReplayDwgDataStorage(std::uint32_t sourceHandle) const {
15632 if (m_dwgW == nullptr || m_graphic == nullptr || sourceHandle == 0)
15633 return false;
15634 if (!m_dwgDataStorageReplayPlan.frozen || !m_dwgDataStorageReplayPlan.valid ||
15635 m_dwgDataStorageReplayPlan.version != m_dwgW->getVersion()) {
15636 return false;
15637 }
15638 const auto it = m_dwgDataStorageReplayPlan.decisionByOwner.find(sourceHandle);
15639 if (it == m_dwgDataStorageReplayPlan.decisionByOwner.end() ||
15640 it->second >= m_dwgDataStorageReplayPlan.decisions.size()) {
15641 return false;
15642 }
15643 const DwgDataStorageDecision decision =
15644 m_dwgDataStorageReplayPlan.decisions[it->second].decision;
15645 return decision == DwgDataStorageDecision::EmitTyped ||
15646 decision == DwgDataStorageDecision::EmitRaw;
15647}
15648
15649bool RS_FilterDXFRW::stageDwgTableRecordIdentity(DwgWriteSourceKind kind,
15650 std::uint32_t sourceHandle,
15651 std::uint32_t outputHandle) {
15652 if (m_dwgW == nullptr || sourceHandle == 0 || outputHandle == 0)
15653 return sourceHandle == 0;
15654 noteDwgWriteSourceKind(sourceHandle, kind);
15655 const bool ambiguous = isDwgWriteSourceAmbiguous(sourceHandle, kind);
15656 const bool selected =
15657 !ambiguous && selectDwgWriteIdentity(kind, sourceHandle, outputHandle);
15658 if (!selected) {
15659 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
15660 "RS_FilterDXFRW: cannot stage DWG table identity kind=%d "
15661 "source=0x%X output=0x%X ambiguous=%d",
15662 static_cast<int>(kind), sourceHandle, outputHandle,
15663 ambiguous ? 1 : 0);
15664 m_writeFailed = true;
15665 return false;
15666 }
15667 return true;
15668}
15669
15670bool RS_FilterDXFRW::resolveDwgWriteAppIdHandle(
15671 std::uint32_t sourceHandle, std::uint32_t *outputHandle) const {
15672 if (outputHandle != nullptr)
15673 *outputHandle = 0;
15674 if (sourceHandle == DRW::NoHandle)
15675 return true;
15676 if (m_dwgW == nullptr || m_graphic == nullptr)
15677 return false;
15678 // Only an APPID record qualifies: the ledger keeps layers, styles and the
15679 // other table records under the same kind.
15680 for (const auto &entry :
15681 m_graphic->dwgAdvancedMetadata().appIdTableEntries()) {
15682 if (entry.second.handle != sourceHandle)
15683 continue;
15684 std::uint32_t resolved = 0;
15685 if (normalizeDwgTableName(entry.first) == "ACAD") {
15686 // writeAppId() leaves this record, and its handle, to the codec.
15687 resolved = DRW::DwgAcadAppIdHandle;
15688 } else {
15689 const DwgWriteIdentityLookup state = lookupDwgWriteIdentity(
15690 DwgWriteSourceKind::TableRecord, sourceHandle, &resolved);
15691 if (state != DwgWriteIdentityLookup::Selected &&
15692 state != DwgWriteIdentityLookup::Committed)
15693 return false;
15694 }
15695 if (outputHandle != nullptr)
15696 *outputHandle = resolved;
15697 return resolved != 0;
15698 }
15699 return false;
15700}
15701
15702bool RS_FilterDXFRW::commitDwgTableRecordIdentities() {
15703 if (m_dwgW == nullptr)
15704 return true;
15705
15706 const auto checkpoint = checkpointDwgWriteIdentity();
15707 const auto fail = [this, &checkpoint]() {
15708 restoreDwgWriteIdentity(checkpoint);
15709 m_writeFailed = true;
15710 return false;
15711 };
15712
15713 std::vector<std::uint32_t> emittedHandles;
15714 if (!m_dwgW->getEmittedDwgTableRecordHandles(emittedHandles))
15715 return fail();
15716 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
15717 emittedHandles.cend());
15718
15719 // Validate the completion result before changing any identity state.
15720 // A set alone would hide duplicate handles and could turn a malformed
15721 // low-level result into a falsely complete table transaction.
15722 if (emittedSet.size() != emittedHandles.size() ||
15723 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
15724 [](std::uint32_t handle) { return handle == 0; })) {
15725 return fail();
15726 }
15727 for (const auto &entry : m_dwgWriteIdentityByOutput) {
15728 const auto &identity = entry.second;
15729 if (identity.kind == DwgWriteSourceKind::TableRecord &&
15730 identity.state == DwgWriteIdentityState::Selected &&
15731 emittedSet.count(identity.outputHandle) == 0) {
15732 return fail();
15733 }
15734 }
15735 for (const std::uint32_t outputHandle : emittedHandles) {
15736 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
15737 if (ownerIt == m_dwgWriteIdentityOwnerByOutput.end())
15738 continue;
15739 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
15740 if (ownerIt->second.kind != DwgWriteSourceKind::TableRecord ||
15741 identityIt == m_dwgWriteIdentityByOutput.end() ||
15742 (identityIt->second.state != DwgWriteIdentityState::Selected &&
15743 identityIt->second.state != DwgWriteIdentityState::Committed)) {
15744 return fail();
15745 }
15746 }
15747
15748 // A table record is only committed after the writer has completed every
15749 // deferred table/control record. This prevents a failed or skipped
15750 // encoder from publishing a handle that later references would trust.
15751 for (auto &entry : m_dwgWriteIdentityByOutput) {
15752 auto &identity = entry.second;
15753 if (identity.kind != DwgWriteSourceKind::TableRecord ||
15754 identity.state != DwgWriteIdentityState::Selected)
15755 continue;
15756 if (emittedSet.count(identity.outputHandle) == 0 ||
15757 !commitDwgWriteIdentity(identity.kind, identity.outputHandle)) {
15758 return fail();
15759 }
15760 }
15761
15762 // Register writer-created records (standard fixed records and APPIDs
15763 // discovered from EED) so the ledger accounts for every table output. A
15764 // source-backed record above owns its output identity already.
15765 for (const std::uint32_t outputHandle : emittedSet) {
15766 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
15767 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
15768 if (ownerIt->second.kind != DwgWriteSourceKind::TableRecord ||
15769 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
15770 return fail();
15771 }
15772 continue;
15773 }
15774 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::TableRecord, 0,
15775 outputHandle) ||
15776 !selectDwgWriteIdentity(DwgWriteSourceKind::TableRecord, 0,
15777 outputHandle) ||
15778 !commitDwgWriteIdentity(DwgWriteSourceKind::TableRecord,
15779 outputHandle)) {
15780 return fail();
15781 }
15782 }
15783 return true;
15784}
15785
15786bool RS_FilterDXFRW::commitDwgTableControlIdentities() {
15787 if (m_dwgW == nullptr)
15788 return true;
15789
15790 const auto checkpoint = checkpointDwgWriteIdentity();
15791 const auto fail = [this, &checkpoint]() {
15792 restoreDwgWriteIdentity(checkpoint);
15793 m_writeFailed = true;
15794 return false;
15795 };
15796
15797 std::vector<std::uint32_t> emittedHandles;
15798 if (!m_dwgW->getEmittedDwgTableControlHandles(emittedHandles))
15799 return fail();
15800
15801 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
15802 emittedHandles.cend());
15803 if (emittedSet.size() != emittedHandles.size() ||
15804 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
15805 [](std::uint32_t handle) { return handle == 0; })) {
15806 return fail();
15807 }
15808 for (const std::uint32_t outputHandle : emittedHandles) {
15809 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
15810 if (ownerIt == m_dwgWriteIdentityOwnerByOutput.end())
15811 continue;
15812 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
15813 if (ownerIt->second.kind != DwgWriteSourceKind::TableControl ||
15814 identityIt == m_dwgWriteIdentityByOutput.end() ||
15815 (identityIt->second.state != DwgWriteIdentityState::Selected &&
15816 identityIt->second.state != DwgWriteIdentityState::Committed)) {
15817 return fail();
15818 }
15819 }
15820
15821 // Controls have no source-side table record. Register them only after
15822 // the writer confirms that every control frame was emitted successfully.
15823 for (const std::uint32_t outputHandle : emittedHandles) {
15824 if (outputHandle == 0) {
15825 return fail();
15826 }
15827 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
15828 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
15829 if (ownerIt->second.kind != DwgWriteSourceKind::TableControl ||
15830 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
15831 return fail();
15832 }
15833 continue;
15834 }
15835 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::TableControl, 0,
15836 outputHandle) ||
15837 !selectDwgWriteIdentity(DwgWriteSourceKind::TableControl, 0,
15838 outputHandle) ||
15839 !commitDwgWriteIdentity(DwgWriteSourceKind::TableControl,
15840 outputHandle)) {
15841 return fail();
15842 }
15843 }
15844 return true;
15845}
15846
15847bool RS_FilterDXFRW::commitDwgGeneratedEntityWrite(std::uint32_t outputHandle) {
15848 if (m_dwgW == nullptr || outputHandle == 0)
15849 return false;
15850
15851 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
15852 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
15853 if (ownerIt->second.kind != DwgWriteSourceKind::Entity ||
15854 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
15855 !commitDwgWriteIdentity(DwgWriteSourceKind::Entity, outputHandle)) {
15856 m_writeFailed = true;
15857 return false;
15858 }
15859 return true;
15860 }
15861
15862 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
15863 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, 0, outputHandle) ||
15864 !commitDwgWriteIdentity(DwgWriteSourceKind::Entity, outputHandle)) {
15865 m_writeFailed = true;
15866 return false;
15867 }
15868 return true;
15869}
15870
15871void RS_FilterDXFRW::prepareDwgEntityHandleMap() {
15872 if (m_dwgW == nullptr || m_graphic == nullptr ||
15873 m_dwgWriteEntityHandleMapPrepared)
15874 return;
15875 m_dwgWriteEntityHandleMapPrepared = true;
15876 m_dwgWriteGeneratedEntityHandleByEntity.clear();
15877 m_dwgWriteInsertChildHandlesByEntity.clear();
15878
15879 // R2000 entities are written with nolinks=1: an owner's entity chain is
15880 // implied by consecutive handles, so the writer refuses an owner whose
15881 // entity handles leave a gap (dwgWriter15::emitDeferredBlockControl).
15882 // Source-backed entities get their handles here, before the table and
15883 // control objects are written; everything else would get its handle only
15884 // when it is encoded, after those objects. In an R2000 owner that holds a
15885 // source-backed entity or an eligible metadata SHAPE, take every frame's
15886 // handle here, including generated entities and ATTRIB..SEQEND children.
15887 // SHAPEs follow the RS_Entity frames in each owner's allocation range;
15888 // their identities are selected only by the native metadata writer.
15889 const bool contiguousOwnerChains = m_dwgW->getVersion() <= DRW::AC1015;
15890 std::map<std::uint32_t, std::vector<const DRW_Shape *>> shapesByOwner;
15891 std::map<std::string, std::vector<const DRW_Shape *>> shapesByBlockName;
15892 std::set<std::uint32_t> shapeHandles;
15893 if (contiguousOwnerChains) {
15894 for (const auto &prepared : m_shapesToExport) {
15895 const DRW_Shape &shape = prepared.shape;
15896 if (shape.handle == 0 || !shapeHandles.insert(shape.handle).second) {
15897 m_writeFailed = true;
15898 return;
15899 }
15900 if (!prepared.ownerBlockName.empty()) {
15901 shapesByBlockName[normalizeDwgTableName(prepared.ownerBlockName)]
15902 .push_back(&shape);
15903 } else {
15904 shapesByOwner[shape.parentHandle].push_back(&shape);
15905 }
15906 }
15907 }
15908
15909 auto reserveContainerEntities = [&](RS_EntityContainer *container,
15910 std::uint32_t ownerHandle,
15911 const std::string &blockName) {
15912 std::vector<const DRW_Shape *> ownerShapes;
15913 if (const auto found = shapesByOwner.find(ownerHandle);
15914 ownerHandle != 0 && found != shapesByOwner.end()) {
15915 ownerShapes = std::move(found->second);
15916 shapesByOwner.erase(found);
15917 }
15918 if (const auto found = shapesByBlockName.find(blockName);
15919 !blockName.empty() && found != shapesByBlockName.end()) {
15920 ownerShapes.insert(ownerShapes.end(), found->second.begin(),
15921 found->second.end());
15922 shapesByBlockName.erase(found);
15923 }
15924 // An INSERT's attribute texts are encoded inside its frame, and a text
15925 // the plan leaves out is not written: neither owns an output handle.
15926 const auto hasOwnFrame = [this](const RS_Entity *entity) {
15927 return entity != nullptr && !entity->getFlag(RS2::FlagDeleted) &&
15928 !hasNoOwnRecord(entity);
15929 };
15930 const std::vector<RS_Entity *> entities =
15931 container == nullptr
15932 ? std::vector<RS_Entity *>{}
15933 : lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities();
15934 const bool reserveAll =
15935 contiguousOwnerChains &&
15936 (!ownerShapes.empty() ||
15937 std::any_of(entities.cbegin(), entities.cend(),
15938 [&hasOwnFrame](const RS_Entity *entity) {
15939 return hasOwnFrame(entity) && entity->sourceHandle() != 0;
15940 }));
15941
15942 const auto reserve = [&](RS_Entity *entity) {
15943 const std::uint32_t sourceHandle = entity->sourceHandle();
15944 if (shapeHandles.count(sourceHandle) != 0) {
15945 m_writeFailed = true;
15946 return;
15947 }
15948 if (sourceHandle != 0)
15949 noteDwgWriteSourceKind(sourceHandle,
15950 RS_FilterDXFRW_DwgWriteIdentityRegistry::
15951 DwgWriteSourceKind::Entity);
15952
15953 const std::uint32_t emittedHandle = m_dwgW->allocNextHandle();
15954 if (emittedHandle == 0) {
15955 m_writeFailed = true;
15956 return;
15957 }
15958 if (sourceHandle == 0) {
15959 // No identity is selected here: the entity is still committed as a
15960 // generated entity when it is written. Only its handle is taken.
15961 m_dwgWriteGeneratedEntityHandleByEntity.emplace(entity, emittedHandle);
15962 } else {
15963 if (!selectDwgWriteIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
15964 DwgWriteSourceKind::Entity,
15965 sourceHandle, emittedHandle)) {
15966 m_writeFailed = true;
15967 return;
15968 }
15969 m_dwgWriteEntityHandleByEntity.emplace(entity, emittedHandle);
15970 const bool inserted =
15971 m_dwgWriteEntityHandleRemap.emplace(sourceHandle, emittedHandle)
15972 .second;
15973 if (!inserted) {
15974 m_dwgWriteDuplicateEntityHandles.insert(sourceHandle);
15975 }
15976 }
15977
15978 if (!reserveAll || entity->rtti() != RS2::EntityInsert)
15979 return;
15980 const auto planned = m_insertAttribPlan.children.find(entity);
15981 if (planned == m_insertAttribPlan.children.end() || planned->second.empty())
15982 return;
15983 // One handle per ATTRIB, then the SEQEND.
15984 std::vector<std::uint32_t> childHandles;
15985 childHandles.reserve(planned->second.size() + 1);
15986 for (std::size_t index = 0; index <= planned->second.size(); ++index) {
15987 const std::uint32_t childHandle = m_dwgW->allocNextHandle();
15988 if (childHandle == 0) {
15989 m_writeFailed = true;
15990 break;
15991 }
15992 childHandles.push_back(childHandle);
15993 }
15994 m_dwgWriteInsertChildHandlesByEntity.emplace(entity,
15995 std::move(childHandles));
15996 };
15997
15998 for (RS_Entity *entity : entities) {
15999 if (hasOwnFrame(entity) && entity->sourceHandle() != 0)
16000 reserve(entity);
16001 if (m_writeFailed)
16002 return;
16003 }
16004 if (!reserveAll)
16005 return;
16006 for (RS_Entity *entity : entities) {
16007 if (hasOwnFrame(entity) && entity->sourceHandle() == 0)
16008 reserve(entity);
16009 if (m_writeFailed)
16010 return;
16011 }
16012 for (const DRW_Shape *shape : ownerShapes) {
16013 const std::uint32_t emittedHandle = m_dwgW->allocNextHandle();
16014 if (emittedHandle == 0) {
16015 m_writeFailed = true;
16016 return;
16017 }
16018 noteDwgWriteSourceKind(shape->handle, DwgWriteSourceKind::Entity);
16019 m_dwgWriteHandleRemap[shape->handle] = emittedHandle;
16020 }
16021 };
16022
16023 reserveContainerEntities(m_graphic, DRW::DwgModelSpaceBlockRecordHandle, {});
16024 if (m_writeFailed)
16025 return;
16026 reserveContainerEntities(nullptr, DRW::DwgPaperSpaceBlockRecordHandle, {});
16027 if (m_writeFailed)
16028 return;
16029 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
16030 RS_Block *block = m_graphic->blockAt(i);
16031 if (block != nullptr && !block->isDeleted())
16032 reserveContainerEntities(
16033 block, block->sourceHandle(),
16034 normalizeDwgTableName(block->getName().toUtf8().toStdString()));
16035 if (m_writeFailed)
16036 return;
16037 }
16038 if (!shapesByOwner.empty() || !shapesByBlockName.empty())
16039 m_writeFailed = true;
16040}
16041
16042bool RS_FilterDXFRW::prepareDwgExportAdmissionPlan() {
16043 if (m_dwgW == nullptr || m_graphic == nullptr)
16044 return false;
16045
16046 DwgExportAdmissionPlan plan;
16047 plan.version = m_dwgW->getVersion();
16048 plan.blockListCount = m_graphic->countBlocks();
16049 const auto addRecord = [&plan](DwgExportAdmissionRecord record) {
16050 plan.records.push_back(std::move(record));
16051 return plan.records.size() - 1u;
16052 };
16053 const auto refuse = [&plan, &addRecord](DwgExportAdmissionFamily family,
16054 DwgExportAdmissionReason reason,
16055 std::uint32_t sourceHandle,
16056 std::size_t sourceOrdinal,
16057 std::string normalizedName,
16058 std::uint32_t relatedHandle = 0) {
16059 plan.valid = false;
16060 return addRecord({family, DwgExportAdmissionPolicy::Refuse, reason,
16061 sourceHandle, relatedHandle, sourceOrdinal,
16062 std::move(normalizedName)});
16063 };
16064
16065 std::set<std::string> blockNames;
16066 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
16067 RS_Block *block = m_graphic->blockAt(index);
16068 if (block == nullptr) {
16069 refuse(DwgExportAdmissionFamily::BlockDefinition,
16070 DwgExportAdmissionReason::InvalidBlockDefinition, 0, index, {});
16071 continue;
16072 }
16073 if (block->isDeleted())
16074 continue;
16075 const std::string name =
16076 normalizeDwgTableName(block->getName().toUtf8().toStdString());
16077 if (name.empty()) {
16078 refuse(DwgExportAdmissionFamily::BlockDefinition,
16079 DwgExportAdmissionReason::InvalidBlockDefinition,
16080 block->sourceHandle(), index, {});
16081 continue;
16082 }
16083 if (!blockNames.insert(name).second) {
16084 refuse(DwgExportAdmissionFamily::BlockDefinition,
16085 DwgExportAdmissionReason::DuplicateBlockDefinition,
16086 block->sourceHandle(), index, name);
16087 continue;
16088 }
16089 plan.blockDefinitions.push_back({block, block->sourceHandle(), name});
16090 }
16091
16092 const auto hasBlockTarget = [this, &blockNames](const std::string &name) {
16093 std::uint32_t ignoredHandle = 0;
16094 return resolveDwgWriteBlockName(name, &ignoredHandle) ==
16095 DwgWriteReferenceStatus::LegacySpaceAlias ||
16096 blockNames.count(name) != 0;
16097 };
16098 std::size_t insertOrdinal = 0;
16099 // Build through a local plan, then attach the INSERT lookup below once the
16100 // plan is stable. This avoids publishing a partially populated plan.
16101 std::map<const RS_Insert *, std::size_t> insertRecordIndexes;
16102 const auto collectInserts = [&insertRecordIndexes, &addRecord, &refuse,
16103 &hasBlockTarget, &insertOrdinal](
16104 const RS_EntityContainer *container) {
16105 if (container == nullptr)
16106 return;
16107 for (RS_Entity *entity :
16108 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
16109 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
16110 entity->rtti() != RS2::EntityInsert) {
16111 continue;
16112 }
16113 const auto *insert = static_cast<const RS_Insert *>(entity);
16114 const std::string name =
16115 normalizeDwgTableName(insert->getName().toUtf8().toStdString());
16116 const std::size_t ordinal = insertOrdinal++;
16117 const std::size_t recordIndex =
16118 !name.empty() && hasBlockTarget(name)
16119 ? addRecord({DwgExportAdmissionFamily::Insert,
16120 DwgExportAdmissionPolicy::Native,
16121 DwgExportAdmissionReason::None,
16122 insert->sourceHandle(), 0, ordinal, name})
16123 : refuse(DwgExportAdmissionFamily::Insert,
16124 DwgExportAdmissionReason::MissingBlockDefinition,
16125 insert->sourceHandle(), ordinal, name);
16126 insertRecordIndexes.emplace(insert, recordIndex);
16127 }
16128 };
16129 collectInserts(m_graphic);
16130 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
16131 const RS_Block *block = m_graphic->blockAt(index);
16132 if (block != nullptr && !block->isDeleted())
16133 collectInserts(block);
16134 }
16135 plan.insertRecordIndexes = std::move(insertRecordIndexes);
16136
16137 std::size_t wipeoutOrdinal = 0;
16138 std::map<const LC_Wipeout *, std::size_t> wipeoutRecordIndexes;
16139 const auto collectWipeouts = [&wipeoutRecordIndexes, &addRecord, &refuse,
16140 &wipeoutOrdinal](
16141 const RS_EntityContainer *container) {
16142 if (container == nullptr)
16143 return;
16144 for (RS_Entity *entity :
16145 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
16146 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
16147 entity->rtti() != RS2::EntityWipeout) {
16148 continue;
16149 }
16150 const auto *wipeout = static_cast<const LC_Wipeout *>(entity);
16151 const LC_WipeoutData &data = wipeout->getData();
16152 const std::size_t ordinal = wipeoutOrdinal++;
16153 const bool hasExternalReference =
16154 data.hasNativeFrame &&
16155 (data.imageDefHandle != 0 || data.imageDefReactorHandle != 0);
16156 const std::size_t recordIndex =
16157 hasExternalReference
16158 ? refuse(DwgExportAdmissionFamily::Wipeout,
16159 DwgExportAdmissionReason::UnsupportedWipeoutReference,
16160 wipeout->sourceHandle(), ordinal, {},
16161 data.imageDefHandle)
16162 : addRecord({DwgExportAdmissionFamily::Wipeout,
16163 DwgExportAdmissionPolicy::Native,
16164 DwgExportAdmissionReason::None,
16165 wipeout->sourceHandle(),
16166 0,
16167 ordinal,
16168 {}});
16169 wipeoutRecordIndexes.emplace(wipeout, recordIndex);
16170 }
16171 };
16172 collectWipeouts(m_graphic);
16173 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index) {
16174 const RS_Block *block = m_graphic->blockAt(index);
16175 if (block != nullptr && !block->isDeleted())
16176 collectWipeouts(block);
16177 }
16178 plan.wipeoutRecordIndexes = std::move(wipeoutRecordIndexes);
16179
16180 struct RawPlotSettingsCandidate {
16181 std::size_t index = std::numeric_limits<std::size_t>::max();
16182 bool hasDataStorage = false;
16183 };
16184 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16185 plan.rawObjectCount = metadata.rawObjects().size();
16186 plan.dictionaryCount = metadata.dictionaries().size();
16187 plan.tableCount = metadata.tables().size();
16188 plan.cellStyleMapCount = metadata.cellStyleMaps().size();
16189 std::set<std::uint32_t> opaqueTableHandles;
16190 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
16191 const auto &raw = metadata.rawObjects()[index];
16192 if (!requiresDwgOpaqueTableAdmission(raw))
16193 continue;
16194 const DwgOpaqueTableInspection inspection =
16195 inspectDwgOpaqueTableRawObject(index);
16196 DwgExportAdmissionReason reason =
16197 DwgExportAdmissionReason::OpaqueTableReference;
16198 if (inspection.referenceKind == DwgOpaqueTableReferenceKind::RawFrame) {
16199 reason = DwgExportAdmissionReason::InvalidOpaqueTableRaw;
16200 } else if (inspection.referenceKind ==
16201 DwgOpaqueTableReferenceKind::MissingSummary) {
16202 reason = DwgExportAdmissionReason::MissingOpaqueTableSummary;
16203 } else if (inspection.referenceKind ==
16204 DwgOpaqueTableReferenceKind::AmbiguousSummary) {
16205 reason = DwgExportAdmissionReason::AmbiguousOpaqueTableSummary;
16206 } else if (inspection.referenceKind ==
16207 DwgOpaqueTableReferenceKind::InvalidSummary) {
16208 reason = DwgExportAdmissionReason::InvalidOpaqueTableSummary;
16209 }
16210 const bool duplicate =
16211 inspection.sourceHandle != 0 &&
16212 !opaqueTableHandles.insert(inspection.sourceHandle).second;
16213 const std::size_t recordIndex =
16214 inspection.eligible && !duplicate
16215 ? addRecord({DwgExportAdmissionFamily::OpaqueTable,
16216 DwgExportAdmissionPolicy::SameVersionRaw,
16217 DwgExportAdmissionReason::None,
16218 inspection.sourceHandle, 0, index,
16219 normalizeDwgTableName(raw.recordName)})
16220 : refuse(DwgExportAdmissionFamily::OpaqueTable,
16221 duplicate ? DwgExportAdmissionReason::InvalidOpaqueTableRaw
16222 : reason,
16223 inspection.sourceHandle, index,
16224 normalizeDwgTableName(raw.recordName),
16225 duplicate ? inspection.sourceHandle
16226 : inspection.referenceHandle);
16227 plan.opaqueTableRecordIndexes.emplace(
16228 index, DwgOpaqueTablePlanRecord{
16229 index, recordIndex, inspection.rawFingerprint,
16230 inspection.summaryFingerprint, inspection.referenceKind,
16231 inspection.referenceHandle, inspection.referenceOrdinal,
16232 inspection.referencePlan, inspection.ownerGraph,
16233 inspection.preflightFailure});
16234 }
16235 std::map<std::uint32_t, RawPlotSettingsCandidate> rawPlotCandidates;
16236 std::set<std::uint32_t> invalidRawPlotHandles;
16237 std::set<std::uint32_t> duplicateRawPlotHandles;
16238 for (std::size_t index = 0; index < metadata.rawObjects().size(); ++index) {
16239 const auto &record = metadata.rawObjects()[index];
16240 if (!isPlotSettingsRawObject(record))
16241 continue;
16242 const bool replayable =
16243 !record.isEntity && record.version == plan.version &&
16244 record.handle != 0 && !isFixedStructuralDwgHandle(record.handle) &&
16245 record.replayState ==
16246 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
16247 LC_DwgAdvancedMetadata::rawReplayBlocker(record) ==
16248 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
16249 (!record.hasDataStorage || canReplayDwgDataStorage(record.handle));
16250 if (!replayable) {
16251 invalidRawPlotHandles.insert(record.handle);
16252 continue;
16253 }
16254 const auto [it, inserted] = rawPlotCandidates.emplace(
16255 record.handle, RawPlotSettingsCandidate{index, record.hasDataStorage});
16256 if (!inserted)
16257 duplicateRawPlotHandles.insert(record.handle);
16258 }
16259
16260 plan.plotSettingsCount = metadata.plotSettings().size();
16261 std::set<std::uint32_t> typedPlotSettingsHandles;
16262 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index) {
16263 const DRW_PlotSettings &settings = metadata.plotSettings()[index];
16264 if (settings.handle != 0)
16265 typedPlotSettingsHandles.insert(settings.handle);
16266 DwgExportAdmissionRecord record;
16267 record.family = DwgExportAdmissionFamily::PlotSettings;
16268 record.sourceHandle = settings.handle;
16269 record.sourceOrdinal = index;
16270 const auto rawIt = rawPlotCandidates.find(settings.handle);
16271 if (settings.handle != 0 && rawIt != rawPlotCandidates.end() &&
16272 duplicateRawPlotHandles.count(settings.handle) == 0 &&
16273 (!settings.hasDataStorageBinaryData() ||
16274 rawIt->second.hasDataStorage)) {
16275 record.policy = DwgExportAdmissionPolicy::SameVersionRaw;
16276 record.relatedSourceHandle = settings.handle;
16277 plan.rawPlotSettingsRecordIndexes.emplace(settings.handle,
16278 rawIt->second.index);
16279 plan.rawPlotSettingsHandles.insert(settings.handle);
16280 } else if (settings.hasDataStorageBinaryData()) {
16281 record.policy = DwgExportAdmissionPolicy::Refuse;
16282 record.reason =
16283 DwgExportAdmissionReason::UnsupportedPlotSettingsDataStorage;
16284 plan.valid = false;
16285 } else if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
16286 record.policy = DwgExportAdmissionPolicy::Refuse;
16287 record.reason =
16288 DwgExportAdmissionReason::UnsupportedPlotSettingsApplicationData;
16289 plan.valid = false;
16290 } else if (settings.handle != 0 &&
16291 duplicateRawPlotHandles.count(settings.handle) != 0) {
16292 record.policy = DwgExportAdmissionPolicy::Refuse;
16293 record.reason = DwgExportAdmissionReason::DuplicateRawPlotSettings;
16294 plan.valid = false;
16295 } else {
16296 record.policy = DwgExportAdmissionPolicy::Typed;
16297 record.reason = DwgExportAdmissionReason::None;
16298 }
16299 const std::size_t recordIndex = addRecord(std::move(record));
16300 plan.plotSettingsRecordIndexes.emplace(index, recordIndex);
16301 }
16302 if (metadata.plotSettings().empty()) {
16303 addRecord({DwgExportAdmissionFamily::PlotSettings,
16304 DwgExportAdmissionPolicy::Native,
16305 DwgExportAdmissionReason::None,
16306 0,
16307 0,
16308 0,
16309 {}});
16310 }
16311 for (const auto &entry : rawPlotCandidates) {
16312 const std::uint32_t handle = entry.first;
16313 if (typedPlotSettingsHandles.count(handle) != 0 ||
16314 duplicateRawPlotHandles.count(handle) != 0) {
16315 continue;
16316 }
16317 plan.rawPlotSettingsHandles.insert(handle);
16318 plan.rawPlotSettingsRecordIndexes.emplace(handle, entry.second.index);
16319 addRecord({DwgExportAdmissionFamily::PlotSettings,
16320 DwgExportAdmissionPolicy::SameVersionRaw,
16321 DwgExportAdmissionReason::None,
16322 handle,
16323 handle,
16324 entry.second.index,
16325 {}});
16326 }
16327 for (std::uint32_t handle : invalidRawPlotHandles) {
16328 if (handle != 0 && typedPlotSettingsHandles.count(handle) != 0)
16329 continue;
16330 refuse(DwgExportAdmissionFamily::PlotSettings,
16331 DwgExportAdmissionReason::RawPlotSettingsUnavailable, handle,
16332 std::numeric_limits<std::size_t>::max(), {});
16333 }
16334 for (std::uint32_t handle : duplicateRawPlotHandles) {
16335 if (typedPlotSettingsHandles.count(handle) != 0)
16336 continue;
16337 refuse(DwgExportAdmissionFamily::PlotSettings,
16338 DwgExportAdmissionReason::DuplicateRawPlotSettings, handle,
16339 std::numeric_limits<std::size_t>::max(), {});
16340 }
16341
16342 plan.frozen = true;
16343 m_dwgOpaqueTableRuntimeFailures.clear();
16344 m_dwgExportAdmissionPlan = std::move(plan);
16345 if (!m_dwgExportAdmissionPlan.valid) {
16346 const auto firstFailure =
16347 std::find_if(m_dwgExportAdmissionPlan.records.cbegin(),
16348 m_dwgExportAdmissionPlan.records.cend(),
16349 [](const DwgExportAdmissionRecord &record) {
16350 return record.policy == DwgExportAdmissionPolicy::Refuse;
16351 });
16352 if (firstFailure != m_dwgExportAdmissionPlan.records.cend()) {
16353 const std::size_t firstFailureIndex =
16354 static_cast<std::size_t>(std::distance(
16355 m_dwgExportAdmissionPlan.records.cbegin(), firstFailure));
16356 const auto opaque = std::find_if(
16357 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cbegin(),
16358 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cend(),
16359 [firstFailureIndex](const auto &entry) {
16360 return entry.second.admissionRecordIndex == firstFailureIndex;
16361 });
16362 if (opaque != m_dwgExportAdmissionPlan.opaqueTableRecordIndexes.cend() &&
16363 opaque->second.preflightFailure.has_value()) {
16364 const DwgOpaqueTableFailure &failure = *opaque->second.preflightFailure;
16365 const long long ordinal =
16366 failure.ordinal == std::numeric_limits<std::size_t>::max()
16367 ? -1LL
16368 : static_cast<long long>(failure.ordinal);
16369 RS_DEBUGRS_Debug::instance()->print(
16370 RS_Debug::D_WARNING,
16371 "RS_FilterDXFRW: DWG export admission refused family=%d "
16372 "reason=%d handle=0x%X name=%s opaque-kind=%s "
16373 "related=0x%X ordinal=%lld",
16374 static_cast<int>(firstFailure->family),
16375 static_cast<int>(firstFailure->reason), firstFailure->sourceHandle,
16376 firstFailure->normalizedName.c_str(),
16377 dwgOpaqueTableFailureKindName(failure.kind), failure.relatedHandle,
16378 ordinal);
16379 } else {
16380 RS_DEBUGRS_Debug::instance()->print(
16381 RS_Debug::D_WARNING,
16382 "RS_FilterDXFRW: DWG export admission refused family=%d "
16383 "reason=%d handle=0x%X name=%s",
16384 static_cast<int>(firstFailure->family),
16385 static_cast<int>(firstFailure->reason), firstFailure->sourceHandle,
16386 firstFailure->normalizedName.c_str());
16387 }
16388 }
16389 }
16390 return m_dwgExportAdmissionPlan.valid;
16391}
16392
16393bool RS_FilterDXFRW::prepareDwgFieldWritePlan() {
16394 if (m_dwgW == nullptr || m_graphic == nullptr)
16395 return false;
16396
16397 DwgFieldWritePlan plan;
16398 plan.version = m_dwgW->getVersion();
16399 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16400 plan.fieldRecordCount = metadata.fields().size();
16401 plan.fieldListRecordCount = metadata.fieldLists().size();
16402
16403 for (std::size_t index = 0; index < metadata.fields().size(); ++index) {
16404 const auto &record = metadata.fields()[index];
16405 if (record.replayState !=
16406 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
16407 record.handle == 0 || !isDwgDataStorageTypedSelected(record.handle)) {
16408 continue;
16409 }
16410 DwgFieldWritePlan::FieldCandidate candidate;
16411 candidate.metadataIndex = index;
16412 candidate.sourceHandle = record.handle;
16413 candidate.replayState = record.replayState;
16414 candidate.sourceAuthorization =
16415 metadata.authorizeDwgFieldSource(record).status;
16416 candidate.payload = fieldFromMetadata(record);
16417 if (!canWriteDwgFieldRecord(metadata, record, candidate.payload,
16418 plan.version) ||
16419 !plan.fieldCandidateIndexes
16420 .emplace(record.handle, plan.fieldCandidates.size())
16421 .second) {
16422 plan.valid = false;
16423 break;
16424 }
16425 plan.fieldCandidates.push_back(std::move(candidate));
16426 }
16427 if (plan.valid) {
16428 for (auto &candidate : plan.fieldCandidates) {
16429 candidate.childFieldCandidateIndexes.reserve(
16430 candidate.payload.m_childHandles.size());
16431 for (const std::uint32_t childHandle : candidate.payload.m_childHandles) {
16432 if (childHandle == DRW::NoHandle) {
16433 plan.valid = false;
16434 break;
16435 }
16436 const auto child = plan.fieldCandidateIndexes.find(childHandle);
16437 if (child == plan.fieldCandidateIndexes.cend()) {
16438 plan.valid = false;
16439 break;
16440 }
16441 candidate.childFieldCandidateIndexes.push_back(child->second);
16442 }
16443 if (!plan.valid)
16444 break;
16445 }
16446 }
16447 if (plan.valid) {
16448 std::vector<std::size_t> incoming(plan.fieldCandidates.size(), 0u);
16449 for (const auto &candidate : plan.fieldCandidates) {
16450 for (const std::size_t childIndex : candidate.childFieldCandidateIndexes) {
16451 if (childIndex >= incoming.size()) {
16452 plan.valid = false;
16453 break;
16454 }
16455 ++incoming[childIndex];
16456 }
16457 if (!plan.valid)
16458 break;
16459 }
16460 std::vector<std::size_t> ready;
16461 ready.reserve(incoming.size());
16462 for (std::size_t index = 0; index < incoming.size(); ++index) {
16463 if (incoming[index] == 0u)
16464 ready.push_back(index);
16465 }
16466 for (std::size_t index = 0; plan.valid && index < ready.size(); ++index) {
16467 for (const std::size_t childIndex :
16468 plan.fieldCandidates[ready[index]].childFieldCandidateIndexes) {
16469 if (incoming[childIndex] == 0u) {
16470 plan.valid = false;
16471 break;
16472 }
16473 if (--incoming[childIndex] == 0u)
16474 ready.push_back(childIndex);
16475 }
16476 }
16477 if (plan.valid && ready.size() != plan.fieldCandidates.size())
16478 plan.valid = false;
16479 }
16480 if (plan.valid) {
16481 for (std::size_t index = 0; index < metadata.fieldLists().size();
16482 ++index) {
16483 const auto &record = metadata.fieldLists()[index];
16484 if (record.replayState !=
16485 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
16486 record.handle == 0 || !isDwgDataStorageTypedSelected(record.handle)) {
16487 continue;
16488 }
16489 DwgFieldWritePlan::FieldListCandidate candidate;
16490 candidate.metadataIndex = index;
16491 candidate.sourceHandle = record.handle;
16492 candidate.replayState = record.replayState;
16493 {
16494 const auto authorization =
16495 metadata.authorizeDwgFieldListSourceForExport(record);
16496 candidate.sourceAuthorization = authorization.status;
16497 candidate.framelessMembers = authorization.framelessMembers;
16498 }
16499 candidate.payload = fieldListFromMetadata(record);
16500 if (!canWriteDwgFieldListRecord(metadata, record, candidate.payload,
16501 plan.version)) {
16502 plan.valid = false;
16503 break;
16504 }
16505 // Each member is a soft pointer to a FIELD. A member that resolves to
16506 // an emitted FIELD candidate is written as such. A member that names
16507 // nothing at all is dropped with a warning, exactly as every other
16508 // dangling soft pointer of this writer is (dictionary entries, XRECORD
16509 // handle values, evaluation-graph expressions, view references): the
16510 // emitted list never carries a pointer that cannot be resolved, and a
16511 // pointer to an object that is not there cannot be honoured anyway. A
16512 // member that names something that does exist -- a FIELD record this
16513 // export cannot write, another kind of object, an ambiguous or
16514 // suppressed identity -- is different: leaving it out would silently
16515 // lose part of the document, and pointing it at a non-FIELD would
16516 // write a misleading list, so the export is refused as before.
16517 //
16518 // A list read from a DWG adds a second condition. The receipts record
16519 // what the source file held, so a member of such a list is dropped only
16520 // if the receipt proves that it names no object frame of that file (no
16521 // FIELD frame published for it, no frame of any kind in the coverage
16522 // report; see authorizeDwgFieldListSourceForExport). The registry alone
16523 // does not decide it there: a member whose frame the file does have,
16524 // but that the export cannot resolve, is a real problem to refuse, not
16525 // a dangling pointer to forget.
16526 const bool sourceBacked =
16527 candidate.sourceAuthorization !=
16528 LC_DwgAdvancedMetadata::DwgFieldListSourceAuthorizationStatus::
16529 NotDwgSource;
16530 const auto isDanglingMember = [this, &candidate,
16531 sourceBacked](std::uint32_t handle,
16532 std::size_t ordinal) {
16533 if (resolveDwgWriteObjectHandle(handle) !=
16534 DwgWriteReferenceStatus::Missing ||
16535 hasDwgWriteSourceKind(handle)) {
16536 return false;
16537 }
16538 return !sourceBacked ||
16539 std::find(candidate.framelessMembers.cbegin(),
16540 candidate.framelessMembers.cend(),
16541 static_cast<std::uint32_t>(ordinal)) !=
16542 candidate.framelessMembers.cend();
16543 };
16544 candidate.memberDropped.assign(candidate.payload.m_fieldHandles.size(),
16545 false);
16546 candidate.fieldCandidateIndexes.reserve(candidate.payload.m_fieldHandles.size());
16547 for (std::size_t ordinal = 0;
16548 ordinal < candidate.payload.m_fieldHandles.size(); ++ordinal) {
16549 const std::uint32_t fieldHandle =
16550 candidate.payload.m_fieldHandles[ordinal];
16551 if (fieldHandle == DRW::NoHandle)
16552 continue;
16553 const auto field = plan.fieldCandidateIndexes.find(fieldHandle);
16554 if (field == plan.fieldCandidateIndexes.cend()) {
16555 if (!isDanglingMember(fieldHandle, ordinal)) {
16556 plan.valid = false;
16557 break;
16558 }
16559 RS_DEBUGRS_Debug::instance()->print(
16560 RS_Debug::D_WARNING,
16561 "RS_FilterDXFRW: dropping dangling DWG FIELDLIST field target "
16562 "0x%X",
16563 fieldHandle);
16564 candidate.memberDropped[ordinal] = true;
16565 continue;
16566 }
16567 candidate.fieldCandidateIndexes.push_back(field->second);
16568 }
16569 if (!plan.valid ||
16570 !plan.fieldListCandidateIndexes
16571 .emplace(record.handle, plan.fieldListCandidates.size())
16572 .second) {
16573 plan.valid = false;
16574 break;
16575 }
16576 plan.fieldListCandidates.push_back(std::move(candidate));
16577 }
16578 }
16579
16580 plan.frozen = true;
16581 m_dwgFieldWritePlan = std::move(plan);
16582 return m_dwgFieldWritePlan.valid;
16583}
16584
16585bool RS_FilterDXFRW::validateDwgFieldWritePlan(
16586 bool requireClassAdmission) const {
16587 const DwgFieldWritePlan &plan = m_dwgFieldWritePlan;
16588 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
16589 !plan.valid || plan.version != m_dwgW->getVersion() ||
16590 plan.fieldRecordCount !=
16591 m_graphic->dwgAdvancedMetadata().fields().size() ||
16592 plan.fieldListRecordCount !=
16593 m_graphic->dwgAdvancedMetadata().fieldLists().size() ||
16594 plan.fieldCandidateIndexes.size() != plan.fieldCandidates.size() ||
16595 plan.fieldListCandidateIndexes.size() != plan.fieldListCandidates.size()) {
16596 return false;
16597 }
16598 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16599 for (std::size_t candidateIndex = 0;
16600 candidateIndex < plan.fieldCandidates.size(); ++candidateIndex) {
16601 const DwgFieldWritePlan::FieldCandidate &candidate =
16602 plan.fieldCandidates[candidateIndex];
16603 if (candidate.metadataIndex >= metadata.fields().size() ||
16604 metadata.fields()[candidate.metadataIndex].handle !=
16605 candidate.sourceHandle ||
16606 metadata.fields()[candidate.metadataIndex].replayState !=
16607 candidate.replayState ||
16608 metadata.authorizeDwgFieldSource(
16609 metadata.fields()[candidate.metadataIndex])
16610 .status != candidate.sourceAuthorization ||
16611 !LC_DwgAdvancedMetadata::matchesDwgFieldRecord(
16612 metadata.fields()[candidate.metadataIndex], candidate.payload)) {
16613 return false;
16614 }
16615 const auto indexIt = plan.fieldCandidateIndexes.find(candidate.sourceHandle);
16616 if (indexIt == plan.fieldCandidateIndexes.cend() ||
16617 indexIt->second != candidateIndex ||
16618 candidate.childFieldCandidateIndexes.size() !=
16619 candidate.payload.m_childHandles.size()) {
16620 return false;
16621 }
16622 for (std::size_t childIndex = 0;
16623 childIndex < candidate.payload.m_childHandles.size(); ++childIndex) {
16624 const std::size_t targetIndex =
16625 candidate.childFieldCandidateIndexes[childIndex];
16626 if (candidate.payload.m_childHandles[childIndex] == DRW::NoHandle ||
16627 targetIndex >= plan.fieldCandidates.size() ||
16628 plan.fieldCandidates[targetIndex].sourceHandle !=
16629 candidate.payload.m_childHandles[childIndex]) {
16630 return false;
16631 }
16632 }
16633 }
16634 for (std::size_t candidateIndex = 0;
16635 candidateIndex < plan.fieldListCandidates.size(); ++candidateIndex) {
16636 const DwgFieldWritePlan::FieldListCandidate &candidate =
16637 plan.fieldListCandidates[candidateIndex];
16638 if (candidate.metadataIndex >= metadata.fieldLists().size() ||
16639 metadata.fieldLists()[candidate.metadataIndex].handle !=
16640 candidate.sourceHandle ||
16641 metadata.fieldLists()[candidate.metadataIndex].replayState !=
16642 candidate.replayState ||
16643 metadata
16644 .authorizeDwgFieldListSourceForExport(
16645 metadata.fieldLists()[candidate.metadataIndex])
16646 .status != candidate.sourceAuthorization ||
16647 metadata
16648 .authorizeDwgFieldListSourceForExport(
16649 metadata.fieldLists()[candidate.metadataIndex])
16650 .framelessMembers != candidate.framelessMembers ||
16651 !LC_DwgAdvancedMetadata::matchesDwgFieldListRecord(
16652 metadata.fieldLists()[candidate.metadataIndex], candidate.payload)) {
16653 return false;
16654 }
16655 const auto indexIt =
16656 plan.fieldListCandidateIndexes.find(candidate.sourceHandle);
16657 if (indexIt == plan.fieldListCandidateIndexes.cend() ||
16658 indexIt->second != candidateIndex) {
16659 return false;
16660 }
16661 if (candidate.memberDropped.size() !=
16662 candidate.payload.m_fieldHandles.size()) {
16663 return false;
16664 }
16665 std::size_t referenceIndex = 0;
16666 for (std::size_t ordinal = 0;
16667 ordinal < candidate.payload.m_fieldHandles.size(); ++ordinal) {
16668 const std::uint32_t fieldHandle = candidate.payload.m_fieldHandles[ordinal];
16669 if (fieldHandle == DRW::NoHandle || candidate.isMemberDropped(ordinal))
16670 continue;
16671 if (referenceIndex >= candidate.fieldCandidateIndexes.size() ||
16672 candidate.fieldCandidateIndexes[referenceIndex] >=
16673 plan.fieldCandidates.size() ||
16674 plan.fieldCandidates[candidate.fieldCandidateIndexes[referenceIndex]]
16675 .sourceHandle != fieldHandle) {
16676 return false;
16677 }
16678 ++referenceIndex;
16679 }
16680 if (referenceIndex != candidate.fieldCandidateIndexes.size())
16681 return false;
16682 }
16683 return !requireClassAdmission || plan.classesAdmitted;
16684}
16685
16686bool RS_FilterDXFRW::validateDwgFieldWriteReceipts() const {
16687 if (!validateDwgFieldWritePlan(/*requireClassAdmission=*/true))
16688 return false;
16689
16690 const DwgFieldWritePlan &plan = m_dwgFieldWritePlan;
16691 const auto framesMatch = [](const DRW::DwgObjectFrameReceipt &left,
16692 const DRW::DwgObjectFrameReceipt &right) {
16693 if (left.valid != right.valid || left.generation != right.generation ||
16694 left.objectHandle != right.objectHandle ||
16695 left.version != right.version || left.dataBitSize != right.dataBitSize ||
16696 left.handleBitSize != right.handleBitSize ||
16697 left.classNumber != right.classNumber ||
16698 left.writerOperation != right.writerOperation ||
16699 left.admissionToken != right.admissionToken ||
16700 left.occurrences.size() != right.occurrences.size()) {
16701 return false;
16702 }
16703 for (std::size_t index = 0; index < left.occurrences.size(); ++index) {
16704 const auto &leftOccurrence = left.occurrences[index];
16705 const auto &rightOccurrence = right.occurrences[index];
16706 if (leftOccurrence.objectHandle != rightOccurrence.objectHandle ||
16707 leftOccurrence.stream != rightOccurrence.stream ||
16708 leftOccurrence.serializedSegment != rightOccurrence.serializedSegment ||
16709 leftOccurrence.segmentOrdinal != rightOccurrence.segmentOrdinal ||
16710 leftOccurrence.token.code != rightOccurrence.token.code ||
16711 leftOccurrence.token.reference != rightOccurrence.token.reference ||
16712 leftOccurrence.token.startBit != rightOccurrence.token.startBit ||
16713 leftOccurrence.token.endBit != rightOccurrence.token.endBit) {
16714 return false;
16715 }
16716 }
16717 return true;
16718 };
16719 const auto hasCommittedFrame = [this, &framesMatch, &plan](
16720 std::uint32_t sourceHandle,
16721 std::uint32_t outputHandle,
16722 const DRW::DwgObjectFrameReceipt &frame) {
16723 std::uint32_t resolvedOutputHandle = 0;
16724 std::uint16_t classNumber = 0;
16725 bool classCommitted = false;
16726 const DRW::DwgObjectFrameReceipt *currentFrame =
16727 findDwgWriteObjectOccurrences(outputHandle);
16728 return outputHandle != 0 && frame.valid && frame.generation != 0 &&
16729 frame.objectHandle == outputHandle && frame.version == plan.version &&
16730 currentFrame != nullptr && framesMatch(frame, *currentFrame) &&
16731 lookupDwgWriteIdentity(DwgWriteSourceKind::Object, sourceHandle,
16732 &resolvedOutputHandle) ==
16733 DwgWriteIdentityLookup::Committed &&
16734 resolvedOutputHandle == outputHandle &&
16735 isDwgWriteIdentityCommitted(DwgWriteSourceKind::Object,
16736 outputHandle) &&
16737 findDwgClassFrameAdmission(DwgWriteSourceKind::Object,
16738 sourceHandle, outputHandle, classNumber,
16739 classCommitted) &&
16740 classCommitted && classNumber == frame.classNumber;
16741 };
16742 const auto hasReference = [this](std::uint32_t producerSourceHandle,
16743 std::uint32_t producerOutputHandle,
16744 const char *name, std::uint32_t ordinal,
16745 std::uint8_t wireHandleCode,
16746 std::uint32_t targetSourceHandle,
16747 std::uint32_t targetOutputHandle,
16748 bool allowFixedStructuralTarget) {
16749 const auto matchingReceipts = std::count_if(
16750 m_dwgWriteReferenceReceipts.cbegin(),
16751 m_dwgWriteReferenceReceipts.cend(),
16752 [=](const DwgWriteReferenceReceipt &receipt) {
16753 if (receipt.producerKind != DwgWriteSourceKind::Object ||
16754 receipt.producerSourceHandle != producerSourceHandle ||
16755 receipt.producerOutputHandle != producerOutputHandle ||
16756 receipt.referenceName != name || receipt.referenceOrdinal != ordinal ||
16757 !receipt.required || receipt.wireHandleCode != wireHandleCode ||
16758 receipt.targetOutputHandle == 0) {
16759 return false;
16760 }
16761 if (allowFixedStructuralTarget && receipt.targetSourceHandle == 0) {
16762 return receipt.targetKind == DwgWriteSourceKind::FixedStructural;
16763 }
16764 return receipt.targetSourceHandle == targetSourceHandle &&
16765 (targetOutputHandle == 0 ||
16766 receipt.targetOutputHandle == targetOutputHandle);
16767 });
16768 return matchingReceipts == 1;
16769 };
16770
16771 const DRW_Field::DwgReferencePolicy childFieldPolicy =
16772 DRW_Field::dwgReferencePolicy(plan.version,
16773 DRW_Field::DwgReferenceKind::ChildField);
16774 const DRW_Field::DwgReferencePolicy objectPolicy =
16775 DRW_Field::dwgReferencePolicy(plan.version,
16776 DRW_Field::DwgReferenceKind::Object);
16777 const DRW_Field::DwgReferencePolicy fieldListPolicy =
16778 DRW_Field::dwgReferencePolicy(plan.version,
16779 DRW_Field::DwgReferenceKind::FieldListMember);
16780 if (!childFieldPolicy.hasHandleStreamSlot || childFieldPolicy.allowsNull ||
16781 !objectPolicy.hasHandleStreamSlot || objectPolicy.allowsNull ||
16782 !fieldListPolicy.hasHandleStreamSlot || !fieldListPolicy.allowsNull) {
16783 return false;
16784 }
16785
16786 for (const DwgFieldWritePlan::FieldCandidate &candidate :
16787 plan.fieldCandidates) {
16788 if (!candidate.classAdmitted || !candidate.objectEmitted ||
16789 !hasCommittedFrame(candidate.sourceHandle, candidate.outputHandle,
16790 candidate.frame)) {
16791 return false;
16792 }
16793 if (candidate.childFieldCandidateIndexes.size() !=
16794 candidate.payload.m_childHandles.size()) {
16795 return false;
16796 }
16797 for (std::size_t ordinal = 0;
16798 ordinal < candidate.payload.m_childHandles.size(); ++ordinal) {
16799 const std::size_t targetIndex = candidate.childFieldCandidateIndexes[ordinal];
16800 if (targetIndex >= plan.fieldCandidates.size())
16801 return false;
16802 const DwgFieldWritePlan::FieldCandidate &target =
16803 plan.fieldCandidates[targetIndex];
16804 if (!target.objectEmitted ||
16805 !hasReference(candidate.sourceHandle, candidate.outputHandle,
16806 "FIELD child", static_cast<std::uint32_t>(ordinal),
16807 childFieldPolicy.wireHandleCode, target.sourceHandle,
16808 target.outputHandle,
16809 /*allowFixedStructuralTarget=*/false)) {
16810 return false;
16811 }
16812 }
16813 for (std::size_t ordinal = 0;
16814 ordinal < candidate.payload.m_objectHandles.size(); ++ordinal) {
16815 const std::uint32_t sourceHandle = candidate.payload.m_objectHandles[ordinal];
16816 if (sourceHandle == DRW::NoHandle ||
16817 !hasReference(candidate.sourceHandle, candidate.outputHandle,
16818 "FIELD object", static_cast<std::uint32_t>(ordinal),
16819 objectPolicy.wireHandleCode, sourceHandle,
16820 /*targetOutputHandle=*/0,
16821 /*allowFixedStructuralTarget=*/true)) {
16822 return false;
16823 }
16824 }
16825 const auto hasCadValueReference =
16826 [&hasReference, &candidate, &plan](const DRW_CadValue &value,
16827 const char *name,
16828 std::uint32_t ordinal) {
16829 const DRW_Field::DwgReferencePolicy policy =
16830 DRW_Field::dwgReferencePolicy(
16831 plan.version, DRW_Field::DwgReferenceKind::CadValueObjectId,
16832 &value);
16833 return !policy.hasHandleStreamSlot ||
16834 (value.m_handle != DRW::NoHandle &&
16835 hasReference(candidate.sourceHandle, candidate.outputHandle,
16836 name, ordinal, policy.wireHandleCode,
16837 value.m_handle, /*targetOutputHandle=*/0,
16838 /*allowFixedStructuralTarget=*/true));
16839 };
16840 if (!hasCadValueReference(candidate.payload.m_value, "FIELD value", 0))
16841 return false;
16842 for (std::size_t ordinal = 0;
16843 ordinal < candidate.payload.m_childValues.size(); ++ordinal) {
16844 if (!hasCadValueReference(candidate.payload.m_childValues[ordinal].m_value,
16845 "FIELD child value",
16846 static_cast<std::uint32_t>(ordinal))) {
16847 return false;
16848 }
16849 }
16850 }
16851
16852 for (const DwgFieldWritePlan::FieldListCandidate &candidate :
16853 plan.fieldListCandidates) {
16854 if (!candidate.classAdmitted || !candidate.objectEmitted ||
16855 !hasCommittedFrame(candidate.sourceHandle, candidate.outputHandle,
16856 candidate.frame)) {
16857 return false;
16858 }
16859 std::size_t referenceIndex = 0;
16860 for (std::size_t ordinal = 0;
16861 ordinal < candidate.payload.m_fieldHandles.size(); ++ordinal) {
16862 const std::uint32_t sourceHandle = candidate.payload.m_fieldHandles[ordinal];
16863 if (sourceHandle == DRW::NoHandle || candidate.isMemberDropped(ordinal))
16864 continue;
16865 if (referenceIndex >= candidate.fieldCandidateIndexes.size())
16866 return false;
16867 const std::size_t targetIndex =
16868 candidate.fieldCandidateIndexes[referenceIndex++];
16869 if (targetIndex >= plan.fieldCandidates.size())
16870 return false;
16871 const DwgFieldWritePlan::FieldCandidate &target =
16872 plan.fieldCandidates[targetIndex];
16873 if (target.sourceHandle != sourceHandle || !target.objectEmitted ||
16874 !hasReference(candidate.sourceHandle, candidate.outputHandle,
16875 "FIELDLIST field", static_cast<std::uint32_t>(ordinal),
16876 fieldListPolicy.wireHandleCode, target.sourceHandle,
16877 target.outputHandle,
16878 /*allowFixedStructuralTarget=*/false)) {
16879 return false;
16880 }
16881 }
16882 if (referenceIndex != candidate.fieldCandidateIndexes.size())
16883 return false;
16884 }
16885 return true;
16886}
16887
16888bool RS_FilterDXFRW::validateDwgExportAdmissionPlan(
16889 DwgOpaqueTableFailurePhase phase) const {
16890 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
16891 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
16892 !plan.valid || plan.version != m_dwgW->getVersion()) {
16893 return false;
16894 }
16895 const auto &metadata = m_graphic->dwgAdvancedMetadata();
16896 const bool metadataCountChanged =
16897 plan.blockListCount != m_graphic->countBlocks() ||
16898 plan.plotSettingsCount != metadata.plotSettings().size() ||
16899 plan.rawObjectCount != metadata.rawObjects().size() ||
16900 plan.dictionaryCount != metadata.dictionaries().size() ||
16901 plan.tableCount != metadata.tables().size() ||
16902 plan.cellStyleMapCount != metadata.cellStyleMaps().size();
16903 if (metadataCountChanged) {
16904 for (const auto &entry : plan.opaqueTableRecordIndexes) {
16905 const DwgOpaqueTablePlanRecord &planned = entry.second;
16906 DwgOpaqueTableFailure failure{phase,
16907 DwgOpaqueTableFailureKind::SourceChanged,
16908 planned.rawFingerprint.handle, 0,
16909 std::numeric_limits<std::size_t>::max()};
16910 if (entry.first < metadata.rawObjects().size() &&
16911 requiresDwgOpaqueTableAdmission(metadata.rawObjects()[entry.first])) {
16912 const DwgOpaqueTableInspection inspection =
16913 inspectDwgOpaqueTableRawObject(entry.first);
16914 if (inspection.preflightFailure.has_value()) {
16915 failure = *inspection.preflightFailure;
16916 failure.phase = phase;
16917 }
16918 }
16919 recordDwgOpaqueTableRuntimeFailure(entry.first, failure);
16920 }
16921 return false;
16922 }
16923 for (const DwgBlockDefinitionPlanRecord &definition : plan.blockDefinitions) {
16924 if (definition.block == nullptr || definition.block->isDeleted() ||
16925 definition.sourceHandle != definition.block->sourceHandle() ||
16926 definition.normalizedName !=
16927 normalizeDwgTableName(
16928 definition.block->getName().toUtf8().toStdString())) {
16929 return false;
16930 }
16931 }
16932 for (const auto &entry : plan.insertRecordIndexes) {
16933 const RS_Insert *insert = entry.first;
16934 if (insert == nullptr || insert->getFlag(RS2::FlagDeleted) ||
16935 entry.second >= plan.records.size()) {
16936 return false;
16937 }
16938 const DwgExportAdmissionRecord &record = plan.records[entry.second];
16939 if (record.family != DwgExportAdmissionFamily::Insert ||
16940 record.policy == DwgExportAdmissionPolicy::Refuse ||
16941 record.sourceHandle != insert->sourceHandle() ||
16942 record.normalizedName !=
16943 normalizeDwgTableName(insert->getName().toUtf8().toStdString())) {
16944 return false;
16945 }
16946 }
16947 for (const auto &entry : plan.wipeoutRecordIndexes) {
16948 const LC_Wipeout *wipeout = entry.first;
16949 if (wipeout == nullptr || wipeout->getFlag(RS2::FlagDeleted) ||
16950 entry.second >= plan.records.size()) {
16951 return false;
16952 }
16953 const DwgExportAdmissionRecord &record = plan.records[entry.second];
16954 const LC_WipeoutData &data = wipeout->getData();
16955 if (record.family != DwgExportAdmissionFamily::Wipeout ||
16956 record.policy != DwgExportAdmissionPolicy::Native ||
16957 record.sourceHandle != wipeout->sourceHandle() ||
16958 (data.hasNativeFrame &&
16959 (data.imageDefHandle != 0 || data.imageDefReactorHandle != 0))) {
16960 return false;
16961 }
16962 }
16963 for (const auto &entry : plan.opaqueTableRecordIndexes) {
16964 const DwgOpaqueTablePlanRecord &planned = entry.second;
16965 if (planned.rawObjectIndex != entry.first ||
16966 planned.admissionRecordIndex >= plan.records.size()) {
16967 recordDwgOpaqueTableRuntimeFailure(
16968 entry.first, {phase, DwgOpaqueTableFailureKind::SourceChanged,
16969 planned.rawFingerprint.handle, 0,
16970 std::numeric_limits<std::size_t>::max()});
16971 return false;
16972 }
16973 const DwgOpaqueTableInspection inspection =
16974 inspectDwgOpaqueTableRawObject(entry.first);
16975 const DwgExportAdmissionRecord &record =
16976 plan.records[planned.admissionRecordIndex];
16977 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
16978 if (entry.first >= rawObjects.size() ||
16979 !requiresDwgOpaqueTableAdmission(rawObjects[entry.first]) ||
16980 !inspection.isOpaqueTable || !inspection.eligible ||
16981 !(inspection.rawFingerprint == planned.rawFingerprint) ||
16982 !(inspection.summaryFingerprint == planned.summaryFingerprint) ||
16983 inspection.referenceKind != planned.referenceKind ||
16984 inspection.referenceHandle != planned.referenceHandle ||
16985 inspection.referenceOrdinal != planned.referenceOrdinal ||
16986 inspection.referencePlan != planned.referencePlan ||
16987 inspection.ownerGraph != planned.ownerGraph ||
16988 inspection.preflightFailure != planned.preflightFailure ||
16989 record.family != DwgExportAdmissionFamily::OpaqueTable ||
16990 record.policy != DwgExportAdmissionPolicy::SameVersionRaw ||
16991 record.sourceHandle != inspection.sourceHandle ||
16992 record.sourceOrdinal != entry.first ||
16993 record.normalizedName !=
16994 normalizeDwgTableName(rawObjects[entry.first].recordName)) {
16995 DwgOpaqueTableFailure failure{phase,
16996 DwgOpaqueTableFailureKind::SourceChanged,
16997 planned.rawFingerprint.handle, 0,
16998 std::numeric_limits<std::size_t>::max()};
16999 if (inspection.preflightFailure.has_value()) {
17000 failure = *inspection.preflightFailure;
17001 failure.phase = phase;
17002 }
17003 recordDwgOpaqueTableRuntimeFailure(entry.first, failure);
17004 return false;
17005 }
17006 }
17007 if (!validateDwgOpaqueTableReferenceReceipts(
17008 DwgOpaqueTableReceiptStage::TableRecordsEmitted, phase)) {
17009 return false;
17010 }
17011 const auto &plotSettings = m_graphic->dwgAdvancedMetadata().plotSettings();
17012 for (const auto &entry : plan.plotSettingsRecordIndexes) {
17013 if (entry.first >= plotSettings.size() ||
17014 entry.second >= plan.records.size())
17015 return false;
17016 const DwgExportAdmissionRecord &record = plan.records[entry.second];
17017 if (record.family != DwgExportAdmissionFamily::PlotSettings ||
17018 record.policy == DwgExportAdmissionPolicy::Refuse ||
17019 record.sourceHandle != plotSettings[entry.first].handle) {
17020 return false;
17021 }
17022 }
17023 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
17024 for (const auto &entry : plan.rawPlotSettingsRecordIndexes) {
17025 if (entry.second >= rawObjects.size())
17026 return false;
17027 const auto &record = rawObjects[entry.second];
17028 if (!isPlotSettingsRawObject(record) || record.handle != entry.first ||
17029 record.isEntity || record.version != plan.version ||
17030 isFixedStructuralDwgHandle(record.handle) ||
17031 record.replayState !=
17032 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17033 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
17034 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
17035 (record.hasDataStorage && !canReplayDwgDataStorage(record.handle))) {
17036 return false;
17037 }
17038 }
17039 return true;
17040}
17041
17042const char *RS_FilterDXFRW::dwgOpaqueTableFailurePhaseName(
17043 DwgOpaqueTableFailurePhase phase) {
17044 switch (phase) {
17045 case DwgOpaqueTableFailurePhase::Preflight:
17046 return "preflight";
17047 case DwgOpaqueTableFailurePhase::PostClasses:
17048 return "post-classes";
17049 case DwgOpaqueTableFailurePhase::Objects:
17050 return "objects";
17051 case DwgOpaqueTableFailurePhase::Finalized:
17052 return "finalized";
17053 }
17054 return "unknown";
17055}
17056
17057const char *
17058RS_FilterDXFRW::dwgOpaqueTableFailureKindName(DwgOpaqueTableFailureKind kind) {
17059 switch (kind) {
17060 case DwgOpaqueTableFailureKind::RawFrame:
17061 return "raw-frame";
17062 case DwgOpaqueTableFailureKind::MissingSummary:
17063 return "missing-summary";
17064 case DwgOpaqueTableFailureKind::AmbiguousSummary:
17065 return "ambiguous-summary";
17066 case DwgOpaqueTableFailureKind::InvalidSummary:
17067 return "invalid-summary";
17068 case DwgOpaqueTableFailureKind::OwnerGraph:
17069 return "owner-graph";
17070 case DwgOpaqueTableFailureKind::SourceGroup:
17071 return "source-group";
17072 case DwgOpaqueTableFailureKind::RootEntry:
17073 return "root-entry";
17074 case DwgOpaqueTableFailureKind::ChildEntry:
17075 return "child-entry";
17076 case DwgOpaqueTableFailureKind::Reference:
17077 return "reference";
17078 case DwgOpaqueTableFailureKind::SourceChanged:
17079 return "source-changed";
17080 case DwgOpaqueTableFailureKind::TargetNodReceipt:
17081 return "target-nod-receipt";
17082 case DwgOpaqueTableFailureKind::ChildFrameReceipt:
17083 return "child-frame-receipt";
17084 case DwgOpaqueTableFailureKind::RawFrameReceipt:
17085 return "raw-frame-receipt";
17086 case DwgOpaqueTableFailureKind::TableRecordReceipt:
17087 return "table-record-receipt";
17088 }
17089 return "unknown";
17090}
17091
17092RS2::FormatType RS_FilterDXFRW::formatForDwgVersion(const DRW::Version version) {
17093 switch (version) {
17094 case DRW::AC1015:
17095 return RS2::FormatDWG;
17096 case DRW::AC1018:
17097 return RS2::FormatDWG2004;
17098 case DRW::AC1021:
17099 return RS2::FormatDWG2007;
17100 case DRW::AC1024:
17101 return RS2::FormatDWG2010;
17102 case DRW::AC1027:
17103 return RS2::FormatDWG2013;
17104 case DRW::AC1032:
17105 return RS2::FormatDWG2018;
17106 default: // R14 and older
17107 return RS2::FormatUnknown;
17108 }
17109}
17110
17111QString RS_FilterDXFRW::exportReport() const {
17112 QStringList lines;
17113 if (m_dxfLeftOutRawRecords > 0) {
17114 lines << QObject::tr("%1 object(s) read from another DXF version, which "
17115 "this version cannot hold", "RS_FilterDXFRW")
17116 .arg(m_dxfLeftOutRawRecords);
17117 }
17118 for (const auto &[what, count] : m_writeLeftOut)
17119 lines << QStringLiteral("%1 × %2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 × %2"))).arg(count).arg(QString::fromStdString(what));
17120 for (const auto &[what, count] : m_shapeLeftOut)
17121 lines << QStringLiteral("%1 × %2")(QString(QtPrivate::qMakeStringPrivate(u"" "%1 × %2"))).arg(count).arg(QString::fromStdString(what));
17122 if (m_exportAttribScaleDataLeftOut > 0) {
17123 lines << QObject::tr(
17124 "annotation scale or column data of %1 multi-line attribute(s)",
17125 "RS_FilterDXFRW")
17126 .arg(m_exportAttribScaleDataLeftOut);
17127 }
17128#ifdef DWGSUPPORT1
17129 if (m_lastDwgWriteSkipCounters.total() > 0) {
17130 lines << QObject::tr("%1 object(s) DWG cannot hold", "RS_FilterDXFRW")
17131 .arg(m_lastDwgWriteSkipCounters.total());
17132 }
17133#endif
17134 return lines.join(QLatin1Char('\n'));
17135}
17136
17137RS2::FormatType RS_FilterDXFRW::formatForDxfVersion(const DRW::Version version) {
17138 switch (version) {
17139 case DRW::AC1012:
17140 case DRW::AC1014:
17141 return RS2::FormatDXFRW14;
17142 case DRW::AC1015:
17143 return RS2::FormatDXFRW2000;
17144 case DRW::AC1018:
17145 return RS2::FormatDXFRW2004;
17146 case DRW::AC1021:
17147 case DRW::UNKNOWNV:
17148 return RS2::FormatDXFRW;
17149 case DRW::AC1024:
17150 return RS2::FormatDXFRW2010;
17151 case DRW::AC1027:
17152 return RS2::FormatDXFRW2013;
17153 case DRW::AC1032:
17154 return RS2::FormatDXFRW2018;
17155 default: // R12 and older
17156 return RS2::FormatDXFRW12;
17157 }
17158}
17159
17160QString RS_FilterDXFRW::dwgExportAdmissionReport() const {
17161 const auto familyName = [](DwgExportAdmissionFamily family) {
17162 switch (family) {
17163 case DwgExportAdmissionFamily::BlockDefinition:
17164 return "block";
17165 case DwgExportAdmissionFamily::Insert:
17166 return "insert";
17167 case DwgExportAdmissionFamily::Wipeout:
17168 return "wipeout";
17169 case DwgExportAdmissionFamily::OpaqueTable:
17170 return "opaque-table";
17171 case DwgExportAdmissionFamily::PlotSettings:
17172 return "plot-settings";
17173 }
17174 return "unknown";
17175 };
17176 const auto policyName = [](DwgExportAdmissionPolicy policy) {
17177 switch (policy) {
17178 case DwgExportAdmissionPolicy::Native:
17179 return "native";
17180 case DwgExportAdmissionPolicy::Typed:
17181 return "typed";
17182 case DwgExportAdmissionPolicy::SameVersionRaw:
17183 return "same-version-raw";
17184 case DwgExportAdmissionPolicy::Refuse:
17185 return "refuse";
17186 }
17187 return "unknown";
17188 };
17189 const auto reasonName = [](DwgExportAdmissionReason reason) {
17190 switch (reason) {
17191 case DwgExportAdmissionReason::None:
17192 return "none";
17193 case DwgExportAdmissionReason::InvalidBlockDefinition:
17194 return "invalid-block";
17195 case DwgExportAdmissionReason::DuplicateBlockDefinition:
17196 return "duplicate-block";
17197 case DwgExportAdmissionReason::MissingBlockDefinition:
17198 return "missing-block";
17199 case DwgExportAdmissionReason::UnsupportedWipeoutReference:
17200 return "wipeout-reference";
17201 case DwgExportAdmissionReason::InvalidOpaqueTableRaw:
17202 return "invalid-opaque-table-raw";
17203 case DwgExportAdmissionReason::MissingOpaqueTableSummary:
17204 return "missing-opaque-table-summary";
17205 case DwgExportAdmissionReason::AmbiguousOpaqueTableSummary:
17206 return "ambiguous-opaque-table-summary";
17207 case DwgExportAdmissionReason::InvalidOpaqueTableSummary:
17208 return "invalid-opaque-table-summary";
17209 case DwgExportAdmissionReason::OpaqueTableReference:
17210 return "opaque-table-reference";
17211 case DwgExportAdmissionReason::UnsupportedPlotSettingsDataStorage:
17212 return "plot-settings-data-storage";
17213 case DwgExportAdmissionReason::UnsupportedPlotSettingsApplicationData:
17214 return "plot-settings-application-data";
17215 case DwgExportAdmissionReason::RawPlotSettingsUnavailable:
17216 return "raw-plot-settings-unavailable";
17217 case DwgExportAdmissionReason::DuplicateRawPlotSettings:
17218 return "duplicate-raw-plot-settings";
17219 case DwgExportAdmissionReason::SourceChanged:
17220 return "source-changed";
17221 }
17222 return "unknown";
17223 };
17224 const auto handle = [](std::uint32_t value) {
17225 return QStringLiteral("0x%1")(QString(QtPrivate::qMakeStringPrivate(u"" "0x%1"))).arg(QString::number(value, 16).toUpper());
17226 };
17227 const auto ordinal = [](std::size_t value) {
17228 return value == std::numeric_limits<std::size_t>::max()
17229 ? QStringLiteral("-")(QString(QtPrivate::qMakeStringPrivate(u"" "-")))
17230 : QString::number(static_cast<qulonglong>(value));
17231 };
17232 const auto field = [](const std::string &value) {
17233 QString result = QString::fromStdString(value);
17234 result.replace(QLatin1Char('\\'), QStringLiteral("\\\\")(QString(QtPrivate::qMakeStringPrivate(u"" "\\\\"))));
17235 result.replace(QLatin1Char('\n'), QStringLiteral("\\n")(QString(QtPrivate::qMakeStringPrivate(u"" "\\n"))));
17236 result.replace(QLatin1Char('\r'), QStringLiteral("\\r")(QString(QtPrivate::qMakeStringPrivate(u"" "\\r"))));
17237 result.replace(QLatin1Char('\t'), QStringLiteral("\\t")(QString(QtPrivate::qMakeStringPrivate(u"" "\\t"))));
17238 return result;
17239 };
17240 const auto failureText = [&handle,
17241 &ordinal](const DwgOpaqueTableFailure &failure) {
17242 return QStringLiteral("phase=%1 kind=%2 source=%3 related=%4 ordinal=%5")(QString(QtPrivate::qMakeStringPrivate(u"" "phase=%1 kind=%2 source=%3 related=%4 ordinal=%5"
)))
17243 .arg(QString::fromLatin1(
17244 RS_FilterDXFRW::dwgOpaqueTableFailurePhaseName(failure.phase)),
17245 QString::fromLatin1(
17246 RS_FilterDXFRW::dwgOpaqueTableFailureKindName(failure.kind)),
17247 handle(failure.sourceHandle), handle(failure.relatedHandle),
17248 ordinal(failure.ordinal));
17249 };
17250
17251 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17252 QStringList lines;
17253 lines << QStringLiteral("dwg-admission-report")(QString(QtPrivate::qMakeStringPrivate(u"" "dwg-admission-report"
)))
17254 << QStringLiteral("frozen=%1 valid=%2 version=%3")(QString(QtPrivate::qMakeStringPrivate(u"" "frozen=%1 valid=%2 version=%3"
)))
17255 .arg(plan.frozen ? QStringLiteral("true")(QString(QtPrivate::qMakeStringPrivate(u"" "true")))
17256 : QStringLiteral("false")(QString(QtPrivate::qMakeStringPrivate(u"" "false"))),
17257 plan.valid ? QStringLiteral("true")(QString(QtPrivate::qMakeStringPrivate(u"" "true")))
17258 : QStringLiteral("false")(QString(QtPrivate::qMakeStringPrivate(u"" "false"))),
17259 dwgVersionDisplay(plan.version));
17260 for (std::size_t index = 0; index < plan.records.size(); ++index) {
17261 const DwgExportAdmissionRecord &record = plan.records[index];
17262 lines << QStringLiteral("row=%1 family=%2 policy=%3 reason=%4 source=%5 "(QString(QtPrivate::qMakeStringPrivate(u"" "row=%1 family=%2 policy=%3 reason=%4 source=%5 "
"related=%6 raw-index=%7 name=%8")))
17263 "related=%6 raw-index=%7 name=%8")(QString(QtPrivate::qMakeStringPrivate(u"" "row=%1 family=%2 policy=%3 reason=%4 source=%5 "
"related=%6 raw-index=%7 name=%8")))
17264 .arg(QString::number(static_cast<qulonglong>(index)),
17265 QString::fromLatin1(familyName(record.family)),
17266 QString::fromLatin1(policyName(record.policy)),
17267 QString::fromLatin1(reasonName(record.reason)),
17268 handle(record.sourceHandle),
17269 handle(record.relatedSourceHandle),
17270 record.family == DwgExportAdmissionFamily::OpaqueTable
17271 ? ordinal(record.sourceOrdinal)
17272 : QStringLiteral("-")(QString(QtPrivate::qMakeStringPrivate(u"" "-"))),
17273 field(record.normalizedName));
17274 }
17275 for (const auto &entry : plan.opaqueTableRecordIndexes) {
17276 const DwgOpaqueTablePlanRecord &record = entry.second;
17277 const auto runtime = m_dwgOpaqueTableRuntimeFailures.find(entry.first);
17278 lines << QStringLiteral("opaque-raw-index=%1 preflight=%2 runtime=%3")(QString(QtPrivate::qMakeStringPrivate(u"" "opaque-raw-index=%1 preflight=%2 runtime=%3"
)))
17279 .arg(QString::number(static_cast<qulonglong>(entry.first)),
17280 record.preflightFailure.has_value()
17281 ? failureText(*record.preflightFailure)
17282 : QStringLiteral("none")(QString(QtPrivate::qMakeStringPrivate(u"" "none"))),
17283 runtime != m_dwgOpaqueTableRuntimeFailures.cend()
17284 ? failureText(runtime->second)
17285 : QStringLiteral("none")(QString(QtPrivate::qMakeStringPrivate(u"" "none"))));
17286 }
17287 return lines.join(QLatin1Char('\n'));
17288}
17289
17290void RS_FilterDXFRW::recordDwgOpaqueTableRuntimeFailure(
17291 std::size_t rawObjectIndex, DwgOpaqueTableFailure failure) const {
17292 if (failure.phase == DwgOpaqueTableFailurePhase::Preflight)
17293 return;
17294 const auto [it, inserted] = m_dwgOpaqueTableRuntimeFailures.emplace(
17295 rawObjectIndex, std::move(failure));
17296 if (!inserted)
17297 return;
17298 const long long ordinal =
17299 it->second.ordinal == std::numeric_limits<std::size_t>::max()
17300 ? -1LL
17301 : static_cast<long long>(it->second.ordinal);
17302 RS_DEBUGRS_Debug::instance()->print(
17303 RS_Debug::D_WARNING,
17304 "RS_FilterDXFRW: DWG opaque table runtime refusal phase=%s kind=%s "
17305 "handle=0x%X related=0x%X ordinal=%lld",
17306 dwgOpaqueTableFailurePhaseName(it->second.phase),
17307 dwgOpaqueTableFailureKindName(it->second.kind), it->second.sourceHandle,
17308 it->second.relatedHandle, ordinal);
17309}
17310
17311bool RS_FilterDXFRW::validateDwgOpaqueTableReferenceReceipts(
17312 DwgOpaqueTableReceiptStage stage, DwgOpaqueTableFailurePhase phase) const {
17313 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17314 if (m_dwgW == nullptr || m_graphic == nullptr || !plan.frozen ||
17315 !plan.valid) {
17316 return false;
17317 }
17318 const auto recordAll = [this, &plan, phase](DwgOpaqueTableFailureKind kind) {
17319 for (const auto &entry : plan.opaqueTableRecordIndexes) {
17320 const DwgOpaqueTablePlanRecord &planned = entry.second;
17321 recordDwgOpaqueTableRuntimeFailure(
17322 entry.first, {phase, kind, planned.rawFingerprint.handle, 0,
17323 std::numeric_limits<std::size_t>::max()});
17324 }
17325 };
17326
17327 std::vector<std::uint32_t> emittedHandles;
17328 if (!m_dwgW->getEmittedDwgTableRecordHandles(emittedHandles)) {
17329 recordAll(DwgOpaqueTableFailureKind::TableRecordReceipt);
17330 return false;
17331 }
17332 const std::set<std::uint32_t> emittedSet(emittedHandles.cbegin(),
17333 emittedHandles.cend());
17334 if (emittedSet.size() != emittedHandles.size() ||
17335 std::any_of(emittedHandles.cbegin(), emittedHandles.cend(),
17336 [](std::uint32_t handle) { return handle == 0; })) {
17337 recordAll(DwgOpaqueTableFailureKind::TableRecordReceipt);
17338 return false;
17339 }
17340
17341 std::vector<DRW::DwgNamedObjectDictionaryEntry> namedEntries;
17342 if (!m_dwgW->getEmittedDwgNamedObjectDictionaryEntries(namedEntries)) {
17343 recordAll(DwgOpaqueTableFailureKind::TargetNodReceipt);
17344 return false;
17345 }
17346 const auto &metadata = m_graphic->dwgAdvancedMetadata();
17347 const auto validateOwnerGraph = [this, &metadata, &namedEntries, stage](
17348 const DwgOpaqueTableOwnerGraphPlanRecord
17349 &graph) {
17350 if (graph.rawObjectIndex >= metadata.rawObjects().size() ||
17351 graph.rootDictionaryIndex >= metadata.dictionaries().size() ||
17352 graph.childDictionaryIndex >= metadata.dictionaries().size() ||
17353 graph.rootDictionaryIndex == graph.childDictionaryIndex ||
17354 graph.rootSourceHandle == 0 ||
17355 graph.rootOutputHandle != DRW::DwgNamedObjectsDictionaryHandle ||
17356 graph.childSourceHandle == 0 ||
17357 graph.childSourceHandle != graph.childOutputHandle ||
17358 graph.childSourceHandle != graph.rawOwnerHandle ||
17359 graph.rawSourceHandle == 0 ||
17360 graph.rawSourceHandle != graph.rawOutputHandle ||
17361 graph.rootSourceHandle == graph.childSourceHandle ||
17362 graph.rootSourceHandle == graph.rawSourceHandle ||
17363 graph.childSourceHandle == graph.rawSourceHandle) {
17364 return false;
17365 }
17366 const auto &raw = metadata.rawObjects()[graph.rawObjectIndex];
17367 const auto &root = metadata.dictionaries()[graph.rootDictionaryIndex];
17368 const auto &child = metadata.dictionaries()[graph.childDictionaryIndex];
17369 if (raw.handle != graph.rawSourceHandle ||
17370 raw.parentHandle != graph.rawOwnerHandle ||
17371 root.handle != graph.rootSourceHandle ||
17372 root.parentHandle != DRW::NoHandle ||
17373 graph.rootEntryOrdinal >= root.entries.size() ||
17374 root.entries[graph.rootEntryOrdinal].name != graph.rootEntryName ||
17375 root.entries[graph.rootEntryOrdinal].handle !=
17376 graph.childOutputHandle ||
17377 child.handle != graph.childSourceHandle ||
17378 child.parentHandle != graph.rootSourceHandle ||
17379 graph.childEntryOrdinal >= child.entries.size() ||
17380 child.entries[graph.childEntryOrdinal].name != graph.childEntryName ||
17381 child.entries[graph.childEntryOrdinal].handle !=
17382 graph.rawOutputHandle ||
17383 dwgOpaqueTableDictionaryDigest(root) != graph.rootIdentityDigest ||
17384 dwgOpaqueTableDictionaryDigest(child) != graph.childIdentityDigest) {
17385 return false;
17386 }
17387 const auto entryCount = static_cast<std::size_t>(
17388 std::count_if(namedEntries.cbegin(), namedEntries.cend(),
17389 [&graph](const auto &entry) {
17390 return entry.name == graph.rootEntryName &&
17391 entry.childHandle == graph.childOutputHandle;
17392 }));
17393 if (entryCount != 1)
17394 return false;
17395 if (stage == DwgOpaqueTableReceiptStage::TableRecordsEmitted)
17396 return true;
17397
17398 const DRW::DwgObjectFrameReceipt *childFrame =
17399 findDwgWriteObjectOccurrences(graph.childOutputHandle);
17400 const DRW::DwgObjectFrameReceipt *rawFrame =
17401 findDwgWriteObjectOccurrences(graph.rawOutputHandle);
17402 return childFrame != nullptr && childFrame->valid &&
17403 childFrame->objectHandle == graph.childOutputHandle &&
17404 childFrame->version == m_dwgW->getVersion() && rawFrame != nullptr &&
17405 rawFrame->valid && rawFrame->objectHandle == graph.rawOutputHandle &&
17406 rawFrame->version == m_dwgW->getVersion() &&
17407 isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
17408 DwgWriteSourceKind::Object,
17409 graph.childOutputHandle) &&
17410 isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
17411 DwgWriteSourceKind::RawCarrier,
17412 graph.rawOutputHandle);
17413 };
17414
17415 const auto matchesNamedTableRecord =
17416 [this](const DwgOpaqueTableReferencePlanRecord &reference) {
17417 const auto hasOutput =
17418 [&reference](const std::map<std::string, std::uint32_t> &handles,
17419 const char *name) {
17420 const auto it = handles.find(name);
17421 return it != handles.end() &&
17422 it->second == reference.targetOutputHandle;
17423 };
17424 if (reference.kind == DwgOpaqueTableReferenceKind::LineType) {
17425 if (reference.targetSourceHandle == DRW::DwgLtypeByBlockHandle)
17426 return hasOutput(m_dwgWriteLTypeHandleByName, "BYBLOCK");
17427 if (reference.targetSourceHandle == DRW::DwgLtypeByLayerHandle)
17428 return hasOutput(m_dwgWriteLTypeHandleByName, "BYLAYER");
17429 if (reference.targetSourceHandle == DRW::DwgLtypeContinuousHandle) {
17430 return hasOutput(m_dwgWriteLTypeHandleByName, "CONTINUOUS");
17431 }
17432 }
17433 if (reference.kind == DwgOpaqueTableReferenceKind::TextStyle &&
17434 reference.targetSourceHandle == DRW::DwgStandardTextStyleHandle) {
17435 return hasOutput(m_dwgWriteTextStyleHandleByName, "STANDARD");
17436 }
17437 return false;
17438 };
17439
17440 const auto &rawObjects = m_graphic->dwgAdvancedMetadata().rawObjects();
17441 for (const auto &entry : plan.opaqueTableRecordIndexes) {
17442 const DwgOpaqueTablePlanRecord &planned = entry.second;
17443 if (entry.first >= rawObjects.size() ||
17444 planned.rawObjectIndex != entry.first) {
17445 return false;
17446 }
17447 const auto &raw = rawObjects[entry.first];
17448 if (planned.ownerGraph.has_value() &&
17449 !validateOwnerGraph(*planned.ownerGraph)) {
17450 recordDwgOpaqueTableRuntimeFailure(
17451 entry.first, {phase, DwgOpaqueTableFailureKind::SourceChanged,
17452 planned.rawFingerprint.handle, 0,
17453 std::numeric_limits<std::size_t>::max()});
17454 return false;
17455 }
17456 for (const DwgOpaqueTableReferencePlanRecord &reference :
17457 planned.referencePlan) {
17458 if (reference.producerRawObjectIndex != entry.first ||
17459 reference.producerSourceHandle != raw.handle ||
17460 reference.producerOutputHandle != raw.handle ||
17461 reference.targetOrdinal == std::numeric_limits<std::size_t>::max() ||
17462 reference.targetDomain !=
17463 DwgOpaqueTableReferenceTargetDomain::TableRecord ||
17464 reference.proofPhase !=
17465 DwgOpaqueTableReferenceProofPhase::TableRecords ||
17466 reference.targetSourceHandle == 0 ||
17467 reference.targetOutputHandle != reference.targetSourceHandle ||
17468 emittedSet.count(reference.targetOutputHandle) == 0 ||
17469 !matchesNamedTableRecord(reference)) {
17470 recordDwgOpaqueTableRuntimeFailure(
17471 entry.first,
17472 {phase, DwgOpaqueTableFailureKind::TableRecordReceipt,
17473 planned.rawFingerprint.handle, reference.targetOutputHandle,
17474 reference.targetOrdinal});
17475 return false;
17476 }
17477
17478 if (stage == DwgOpaqueTableReceiptStage::TableRecordsEmitted)
17479 continue;
17480
17481 const DRW::DwgObjectFrameReceipt *frame =
17482 findDwgWriteObjectOccurrences(reference.producerOutputHandle);
17483 if (frame == nullptr || !frame->valid ||
17484 frame->objectHandle != reference.producerOutputHandle ||
17485 frame->version != plan.version ||
17486 !isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
17487 DwgWriteSourceKind::RawCarrier,
17488 reference.producerOutputHandle)) {
17489 recordDwgOpaqueTableRuntimeFailure(
17490 entry.first,
17491 {phase, DwgOpaqueTableFailureKind::RawFrameReceipt,
17492 planned.rawFingerprint.handle, reference.producerOutputHandle,
17493 reference.targetOrdinal});
17494 return false;
17495 }
17496 if (stage == DwgOpaqueTableReceiptStage::Finalized &&
17497 !isDwgWriteIdentityCommitted(RS_FilterDXFRW_DwgWriteIdentityRegistry::
17498 DwgWriteSourceKind::TableRecord,
17499 reference.targetOutputHandle)) {
17500 recordDwgOpaqueTableRuntimeFailure(
17501 entry.first,
17502 {phase, DwgOpaqueTableFailureKind::TableRecordReceipt,
17503 planned.rawFingerprint.handle, reference.targetOutputHandle,
17504 reference.targetOrdinal});
17505 return false;
17506 }
17507 }
17508 }
17509 return true;
17510}
17511
17512bool RS_FilterDXFRW::isDwgInsertAdmitted(const RS_Insert *insert) const {
17513 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17514 if (insert == nullptr || !plan.frozen || !plan.valid)
17515 return false;
17516 const auto it = plan.insertRecordIndexes.find(insert);
17517 if (it == plan.insertRecordIndexes.end() || it->second >= plan.records.size())
17518 return false;
17519 const DwgExportAdmissionRecord &record = plan.records[it->second];
17520 return record.family == DwgExportAdmissionFamily::Insert &&
17521 record.policy != DwgExportAdmissionPolicy::Refuse &&
17522 record.sourceHandle == insert->sourceHandle() &&
17523 record.normalizedName ==
17524 normalizeDwgTableName(insert->getName().toUtf8().toStdString());
17525}
17526
17527bool RS_FilterDXFRW::isDwgWipeoutAdmitted(const LC_Wipeout *wipeout) const {
17528 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17529 if (wipeout == nullptr || !plan.frozen || !plan.valid)
17530 return false;
17531 const auto it = plan.wipeoutRecordIndexes.find(wipeout);
17532 if (it == plan.wipeoutRecordIndexes.end() ||
17533 it->second >= plan.records.size()) {
17534 return false;
17535 }
17536 const DwgExportAdmissionRecord &record = plan.records[it->second];
17537 const LC_WipeoutData &data = wipeout->getData();
17538 return record.family == DwgExportAdmissionFamily::Wipeout &&
17539 record.policy == DwgExportAdmissionPolicy::Native &&
17540 record.sourceHandle == wipeout->sourceHandle() &&
17541 (!data.hasNativeFrame ||
17542 (data.imageDefHandle == 0 && data.imageDefReactorHandle == 0));
17543}
17544
17545RS_FilterDXFRW::DwgOpaqueTableInspection
17546RS_FilterDXFRW::inspectDwgOpaqueTableRawObject(
17547 std::size_t rawObjectIndex) const {
17548 DwgOpaqueTableInspection result;
17549 if (m_dwgW == nullptr || m_graphic == nullptr)
17550 return result;
17551 const auto &metadata = m_graphic->dwgAdvancedMetadata();
17552 if (rawObjectIndex >= metadata.rawObjects().size())
17553 return result;
17554
17555 const auto &raw = metadata.rawObjects()[rawObjectIndex];
17556 if (!isOpaqueTableRawObject(raw))
17557 return result;
17558 result.isOpaqueTable = true;
17559 result.sourceHandle = raw.handle;
17560 DwgOpaqueTableRawFingerprint &rawFingerprint = result.rawFingerprint;
17561 rawFingerprint.version = raw.version;
17562 rawFingerprint.objectType = raw.objectType;
17563 rawFingerprint.handle = raw.handle;
17564 rawFingerprint.parentHandle = raw.parentHandle;
17565 rawFingerprint.commonHandleDataValidated =
17566 drwDwgCommonLinkEvidenceValidated(raw.commonLinkEvidence);
17567 rawFingerprint.commonLinkEvidence = raw.commonLinkEvidence;
17568 rawFingerprint.reactorHandles = raw.reactorHandles;
17569 rawFingerprint.xDictHandle = raw.xDictHandle;
17570 rawFingerprint.numReactors = raw.numReactors;
17571 rawFingerprint.xDictFlag = raw.xDictFlag;
17572 rawFingerprint.blockOwnerHandle = raw.blockOwnerHandle;
17573 rawFingerprint.bodyBitSize = raw.bodyBitSize;
17574 rawFingerprint.objectOffset = raw.objectOffset;
17575 rawFingerprint.objectSize = raw.objectSize;
17576 rawFingerprint.isEntity = raw.isEntity;
17577 rawFingerprint.isCustomClass = raw.isCustomClass;
17578 rawFingerprint.hasDataStorage = raw.hasDataStorage;
17579 rawFingerprint.replayState = raw.replayState;
17580 rawFingerprint.hasClassDefinition = raw.hasClassDefinition;
17581 rawFingerprint.classProxyFlag = raw.classProxyFlag;
17582 rawFingerprint.recordName = raw.recordName;
17583 rawFingerprint.className = raw.className;
17584 rawFingerprint.classAppName = raw.classAppName;
17585 rawFingerprint.classWasProxy = raw.classWasProxy;
17586 rawFingerprint.classEntityFlagRaw = raw.classEntityFlagRaw;
17587 rawFingerprint.classDwgVersion = raw.classDwgVersion;
17588 rawFingerprint.classMaintenanceVersion = raw.classMaintenanceVersion;
17589 rawFingerprint.classUnknown1 = raw.classUnknown1;
17590 rawFingerprint.classUnknown2 = raw.classUnknown2;
17591 rawFingerprint.rawByteCount = raw.rawBytes.size();
17592 rawFingerprint.rawIdentityDigest = dwgOpaqueTableRawDigest(raw);
17593 const auto reject = [&result,
17594 &raw](DwgOpaqueTableReferenceKind referenceKind,
17595 DwgOpaqueTableFailureKind failureKind,
17596 std::uint32_t relatedHandle = 0,
17597 std::size_t ordinal =
17598 std::numeric_limits<std::size_t>::max()) {
17599 result.referenceKind = referenceKind;
17600 result.referenceHandle = relatedHandle;
17601 result.referenceOrdinal = ordinal;
17602 result.preflightFailure =
17603 DwgOpaqueTableFailure{DwgOpaqueTableFailurePhase::Preflight,
17604 failureKind, raw.handle, relatedHandle, ordinal};
17605 return result;
17606 };
17607 if (raw.isEntity || !raw.isCustomClass || raw.handle == 0 ||
17608 raw.objectType < 500 ||
17609 static_cast<std::uint64_t>(raw.objectType) >
17610 std::numeric_limits<std::uint16_t>::max() ||
17611 raw.rawBytes.empty() || isFixedStructuralDwgHandle(raw.handle) ||
17612 !sameRawObjectEncodingFamily(raw.version, m_dwgW->getVersion()) ||
17613 raw.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17614 LC_DwgAdvancedMetadata::rawReplayBlocker(raw) !=
17615 LC_DwgAdvancedMetadata::ReplayBlocker::None ||
17616 (raw.hasDataStorage && !canReplayDwgDataStorage(raw.handle))) {
17617 return reject(DwgOpaqueTableReferenceKind::RawFrame,
17618 DwgOpaqueTableFailureKind::RawFrame);
17619 }
17620 const bool isTableStyle = isTableStyleRawObject(raw);
17621 if (raw.parentHandle != DRW::NoHandle &&
17622 !isFixedStructuralDwgHandle(raw.parentHandle)) {
17623 if (isTableStyle && !raw.typedPayloadValidated)
17624 return reject(DwgOpaqueTableReferenceKind::RawFrame,
17625 DwgOpaqueTableFailureKind::RawFrame);
17626 if (!isTableStyle) {
17627 return reject(DwgOpaqueTableReferenceKind::Owner,
17628 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
17629 }
17630 const auto &dictionaries = metadata.dictionaries();
17631 const auto childCount = std::count_if(
17632 dictionaries.cbegin(), dictionaries.cend(), [&raw](const auto &record) {
17633 return record.handle == raw.parentHandle;
17634 });
17635 if (childCount != 1 || isFixedStructuralDwgHandle(raw.parentHandle) ||
17636 m_dwgWriteHandleRemap.count(raw.parentHandle) != 0) {
17637 return reject(DwgOpaqueTableReferenceKind::Owner,
17638 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
17639 }
17640 const auto child = std::find_if(dictionaries.cbegin(), dictionaries.cend(),
17641 [&raw](const auto &record) {
17642 return record.handle == raw.parentHandle;
17643 });
17644 const auto childIndex =
17645 static_cast<std::size_t>(std::distance(dictionaries.cbegin(), child));
17646 if (child == dictionaries.cend() || child->parentHandle == DRW::NoHandle ||
17647 child->handle == raw.handle || !isCanonicalDwgNamedDictionary(*child)) {
17648 return reject(DwgOpaqueTableReferenceKind::Owner,
17649 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
17650 }
17651 if (child->entries.size() != 1u || child->entries[0].name.empty() ||
17652 child->entries[0].handle != raw.handle) {
17653 return reject(DwgOpaqueTableReferenceKind::Owner,
17654 DwgOpaqueTableFailureKind::ChildEntry, raw.parentHandle);
17655 }
17656 const std::uint32_t rootSourceHandle = child->parentHandle;
17657 const auto rootCount =
17658 std::count_if(dictionaries.cbegin(), dictionaries.cend(),
17659 [rootSourceHandle](const auto &record) {
17660 return record.handle == rootSourceHandle;
17661 });
17662 if (rootCount != 1)
17663 return reject(DwgOpaqueTableReferenceKind::Owner,
17664 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
17665 const auto root = std::find_if(dictionaries.cbegin(), dictionaries.cend(),
17666 [rootSourceHandle](const auto &record) {
17667 return record.handle == rootSourceHandle;
17668 });
17669 const auto rootIndex =
17670 static_cast<std::size_t>(std::distance(dictionaries.cbegin(), root));
17671 if (root == dictionaries.cend() || root->parentHandle != DRW::NoHandle ||
17672 root->handle == child->handle || root->handle == raw.handle ||
17673 !isCanonicalDwgNamedDictionary(*root) || root->entries.size() != 2u) {
17674 return reject(DwgOpaqueTableReferenceKind::Owner,
17675 DwgOpaqueTableFailureKind::OwnerGraph, raw.parentHandle);
17676 }
17677 if (root->entries[0].name != "ACAD_GROUP" ||
17678 root->entries[0].handle != DRW::DwgAcadGroupDictionaryHandle) {
17679 return reject(DwgOpaqueTableReferenceKind::Owner,
17680 DwgOpaqueTableFailureKind::SourceGroup,
17681 root->entries[0].handle, 0);
17682 }
17683 if (root->entries[1].name.empty() ||
17684 root->entries[1].name == "ACAD_GROUP" ||
17685 root->entries[1].handle != child->handle) {
17686 return reject(DwgOpaqueTableReferenceKind::Owner,
17687 DwgOpaqueTableFailureKind::RootEntry, raw.parentHandle);
17688 }
17689
17690 DwgOpaqueTableOwnerGraphPlanRecord graph;
17691 graph.rawObjectIndex = rawObjectIndex;
17692 graph.rawSourceHandle = raw.handle;
17693 graph.rawOutputHandle = raw.handle;
17694 graph.rawOwnerHandle = raw.parentHandle;
17695 graph.rootDictionaryIndex = rootIndex;
17696 graph.childDictionaryIndex = childIndex;
17697 graph.rootSourceHandle = rootSourceHandle;
17698 graph.rootOutputHandle = DRW::DwgNamedObjectsDictionaryHandle;
17699 graph.childSourceHandle = child->handle;
17700 graph.childOutputHandle = child->handle;
17701 graph.rootEntryOrdinal = 1;
17702 graph.childEntryOrdinal = 0;
17703 graph.rootEntryName = root->entries[1].name;
17704 graph.childEntryName = child->entries[0].name;
17705 graph.rootIdentityDigest = dwgOpaqueTableDictionaryDigest(*root);
17706 graph.childIdentityDigest = dwgOpaqueTableDictionaryDigest(*child);
17707 result.ownerGraph = std::move(graph);
17708 }
17709
17710 const auto fixedReferenceOutputHandle =
17711 [](DwgOpaqueTableReferenceKind kind,
17712 std::uint32_t handle) -> std::uint32_t {
17713 if (kind == DwgOpaqueTableReferenceKind::LineType &&
17714 (handle == DRW::DwgLtypeByBlockHandle ||
17715 handle == DRW::DwgLtypeByLayerHandle ||
17716 handle == DRW::DwgLtypeContinuousHandle)) {
17717 return handle;
17718 }
17719 if (kind == DwgOpaqueTableReferenceKind::TextStyle &&
17720 handle == DRW::DwgStandardTextStyleHandle) {
17721 return handle;
17722 }
17723 return 0;
17724 };
17725 const auto firstUnplannedReference =
17726 [&raw, rawObjectIndex, &result, &reject,
17727 &fixedReferenceOutputHandle](const std::vector<std::uint32_t> &handles,
17728 DwgOpaqueTableReferenceKind kind)
17729 -> std::optional<DwgOpaqueTableInspection> {
17730 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
17731 const std::uint32_t handle = handles[ordinal];
17732 if (handle == DRW::NoHandle)
17733 continue;
17734 const std::uint32_t outputHandle =
17735 fixedReferenceOutputHandle(kind, handle);
17736 if (outputHandle == 0)
17737 return reject(kind, DwgOpaqueTableFailureKind::Reference, handle,
17738 ordinal);
17739 DwgOpaqueTableReferencePlanRecord reference;
17740 reference.kind = kind;
17741 reference.producerRawObjectIndex = rawObjectIndex;
17742 reference.producerSourceHandle = raw.handle;
17743 reference.producerOutputHandle = raw.handle;
17744 reference.targetSourceHandle = handle;
17745 reference.targetOrdinal = ordinal;
17746 reference.targetDomain = DwgOpaqueTableReferenceTargetDomain::TableRecord;
17747 reference.targetOutputHandle = outputHandle;
17748 reference.proofPhase = DwgOpaqueTableReferenceProofPhase::TableRecords;
17749 result.referencePlan.push_back(reference);
17750 }
17751 return std::nullopt;
17752 };
17753 const bool isCellStyleMap =
17754 raw.recordName == "CELLSTYLEMAP" || raw.className == "AcDbCellStyleMap";
17755 const bool isTableContent =
17756 raw.recordName == "TABLECONTENT" || raw.className == "AcDbTableContent";
17757 if (isCellStyleMap) {
17758 std::size_t mapIndex = std::numeric_limits<std::size_t>::max();
17759 for (std::size_t index = 0; index < metadata.cellStyleMaps().size();
17760 ++index) {
17761 if (metadata.cellStyleMaps()[index].handle != raw.handle)
17762 continue;
17763 if (mapIndex != std::numeric_limits<std::size_t>::max()) {
17764 return reject(DwgOpaqueTableReferenceKind::AmbiguousSummary,
17765 DwgOpaqueTableFailureKind::AmbiguousSummary, raw.handle,
17766 index);
17767 }
17768 mapIndex = index;
17769 }
17770 if (mapIndex == std::numeric_limits<std::size_t>::max()) {
17771 return reject(DwgOpaqueTableReferenceKind::MissingSummary,
17772 DwgOpaqueTableFailureKind::MissingSummary, raw.handle);
17773 }
17774 const auto &map = metadata.cellStyleMaps()[mapIndex];
17775 DwgOpaqueTableSummaryFingerprint &summary = result.summaryFingerprint;
17776 summary.kind = DwgOpaqueTableSummaryKind::CellStyleMap;
17777 summary.summaryIndex = mapIndex;
17778 summary.handle = map.handle;
17779 summary.parentHandle = map.parentHandle;
17780 summary.replayState = map.replayState;
17781 summary.relationCount = dwgOpaqueTableRelationCount(map);
17782 summary.identityDigest = dwgOpaqueTableSummaryDigest(map);
17783 if (map.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17784 map.parentHandle != raw.parentHandle) {
17785 return reject(DwgOpaqueTableReferenceKind::InvalidSummary,
17786 DwgOpaqueTableFailureKind::InvalidSummary,
17787 map.parentHandle);
17788 }
17789 if (const auto reference = firstUnplannedReference(
17790 map.m_textStyleHandles, DwgOpaqueTableReferenceKind::TextStyle)) {
17791 return *reference;
17792 }
17793 if (const auto reference = firstUnplannedReference(
17794 map.m_lineTypeHandles, DwgOpaqueTableReferenceKind::LineType)) {
17795 return *reference;
17796 }
17797 result.eligible = true;
17798 return result;
17799 }
17800
17801 if (!isTableStyle && !isTableContent)
17802 return reject(DwgOpaqueTableReferenceKind::RawFrame,
17803 DwgOpaqueTableFailureKind::RawFrame);
17804 std::size_t tableIndex = std::numeric_limits<std::size_t>::max();
17805 for (std::size_t index = 0; index < metadata.tables().size(); ++index) {
17806 const auto &candidate = metadata.tables()[index];
17807 const bool matchingKind = isTableStyle
17808 ? candidate.nativeTableStyle.has_value()
17809 : candidate.recordName == "TABLECONTENT";
17810 if (candidate.handle != raw.handle || !matchingKind)
17811 continue;
17812 if (tableIndex != std::numeric_limits<std::size_t>::max()) {
17813 return reject(DwgOpaqueTableReferenceKind::AmbiguousSummary,
17814 DwgOpaqueTableFailureKind::AmbiguousSummary, raw.handle,
17815 index);
17816 }
17817 tableIndex = index;
17818 }
17819 if (tableIndex == std::numeric_limits<std::size_t>::max()) {
17820 return reject(DwgOpaqueTableReferenceKind::MissingSummary,
17821 DwgOpaqueTableFailureKind::MissingSummary, raw.handle);
17822 }
17823 const auto &table = metadata.tables()[tableIndex];
17824 DwgOpaqueTableSummaryFingerprint &summary = result.summaryFingerprint;
17825 summary.kind = isTableStyle ? DwgOpaqueTableSummaryKind::TableStyle
17826 : DwgOpaqueTableSummaryKind::TableContent;
17827 summary.summaryIndex = tableIndex;
17828 summary.handle = table.handle;
17829 summary.parentHandle = table.parentHandle;
17830 summary.replayState = table.replayState;
17831 summary.fallbackRendered = table.fallbackRendered;
17832 summary.fallbackInvalidated = table.fallbackInvalidated;
17833 summary.relationCount = dwgOpaqueTableRelationCount(table);
17834 summary.identityDigest = dwgOpaqueTableSummaryDigest(table);
17835 if (table.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
17836 table.fallbackRendered || table.fallbackInvalidated ||
17837 table.parentHandle != raw.parentHandle) {
17838 return reject(DwgOpaqueTableReferenceKind::InvalidSummary,
17839 DwgOpaqueTableFailureKind::InvalidSummary,
17840 table.parentHandle);
17841 }
17842 if (table.tableStyleHandle != DRW::NoHandle) {
17843 return reject(DwgOpaqueTableReferenceKind::TableStyle,
17844 DwgOpaqueTableFailureKind::Reference, table.tableStyleHandle);
17845 }
17846 if (table.m_tableUnknownHandle != DRW::NoHandle) {
17847 return reject(DwgOpaqueTableReferenceKind::Unknown,
17848 DwgOpaqueTableFailureKind::Reference,
17849 table.m_tableUnknownHandle);
17850 }
17851 if (const auto reference = firstUnplannedReference(
17852 table.valueHandles, DwgOpaqueTableReferenceKind::Value)) {
17853 return *reference;
17854 }
17855 if (const auto reference = firstUnplannedReference(
17856 table.blockHandles, DwgOpaqueTableReferenceKind::Block)) {
17857 return *reference;
17858 }
17859 if (const auto reference = firstUnplannedReference(
17860 table.fieldHandles, DwgOpaqueTableReferenceKind::Field)) {
17861 return *reference;
17862 }
17863 if (const auto reference = firstUnplannedReference(
17864 table.attributeHandles, DwgOpaqueTableReferenceKind::Attribute)) {
17865 return *reference;
17866 }
17867 if (const auto reference = firstUnplannedReference(
17868 table.geometryHandles, DwgOpaqueTableReferenceKind::Geometry)) {
17869 return *reference;
17870 }
17871 if (const auto reference = firstUnplannedReference(
17872 table.textStyleHandles, DwgOpaqueTableReferenceKind::TextStyle)) {
17873 return *reference;
17874 }
17875 if (const auto reference = firstUnplannedReference(
17876 table.lineTypeHandles, DwgOpaqueTableReferenceKind::LineType)) {
17877 return *reference;
17878 }
17879 result.eligible = true;
17880 return result;
17881}
17882
17883bool RS_FilterDXFRW::isDwgOpaqueTableRawSelected(
17884 std::size_t rawObjectIndex) const {
17885 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17886 if (!plan.frozen || !plan.valid)
17887 return false;
17888 const auto it = plan.opaqueTableRecordIndexes.find(rawObjectIndex);
17889 if (it == plan.opaqueTableRecordIndexes.end() ||
17890 it->second.admissionRecordIndex >= plan.records.size()) {
17891 return false;
17892 }
17893 const DwgExportAdmissionRecord &record =
17894 plan.records[it->second.admissionRecordIndex];
17895 return record.family == DwgExportAdmissionFamily::OpaqueTable &&
17896 record.policy == DwgExportAdmissionPolicy::SameVersionRaw &&
17897 record.sourceOrdinal == rawObjectIndex;
17898}
17899
17900bool RS_FilterDXFRW::isDwgTypedPlotSettingsSelected(std::size_t index) const {
17901 const DwgExportAdmissionPlan &plan = m_dwgExportAdmissionPlan;
17902 if (!plan.frozen || !plan.valid)
17903 return false;
17904 const auto it = plan.plotSettingsRecordIndexes.find(index);
17905 if (it == plan.plotSettingsRecordIndexes.end() ||
17906 it->second >= plan.records.size()) {
17907 return false;
17908 }
17909 const DwgExportAdmissionPolicy policy = plan.records[it->second].policy;
17910 return policy == DwgExportAdmissionPolicy::Native ||
17911 policy == DwgExportAdmissionPolicy::Typed;
17912}
17913
17914bool RS_FilterDXFRW::isDwgRawPlotSettingsSelected(
17915 std::uint32_t sourceHandle) const {
17916 return m_dwgExportAdmissionPlan.frozen && m_dwgExportAdmissionPlan.valid &&
17917 sourceHandle != 0 &&
17918 m_dwgExportAdmissionPlan.rawPlotSettingsHandles.count(sourceHandle) !=
17919 0;
17920}
17921
17922bool RS_FilterDXFRW::prepareDwgEntityWritePlans() {
17923 if (m_dwgW == nullptr || m_graphic == nullptr)
17924 return true;
17925
17926 const DRW::Version version = m_dwgW->getVersion();
17927 const std::uint16_t mleaderClassNumber =
17928 version >= DRW::AC1024
17929 ? m_dwgW->getDwgTypedClassNumber("AcDbMLeader", "MULTILEADER",
17930 DRW_MLeader::kDwgClassNum)
17931 : 0;
17932 const auto fixedRoute = [](DwgTypedConversionFixedRoute route)
17933 -> std::optional<DwgFixedEntityRoute> {
17934 switch (route) {
17935 case DwgTypedConversionFixedRoute::PointExtrusion:
17936 return DwgFixedEntityRoute::PointExtrusion;
17937 case DwgTypedConversionFixedRoute::LineExtrusion:
17938 return DwgFixedEntityRoute::LineExtrusion;
17939 case DwgTypedConversionFixedRoute::Circle:
17940 return DwgFixedEntityRoute::Circle;
17941 case DwgTypedConversionFixedRoute::Arc:
17942 return DwgFixedEntityRoute::Arc;
17943 case DwgTypedConversionFixedRoute::Ray:
17944 return DwgFixedEntityRoute::Ray;
17945 case DwgTypedConversionFixedRoute::Xline:
17946 return DwgFixedEntityRoute::Xline;
17947 case DwgTypedConversionFixedRoute::Trace:
17948 return DwgFixedEntityRoute::Trace;
17949 case DwgTypedConversionFixedRoute::Solid:
17950 return DwgFixedEntityRoute::Solid;
17951 case DwgTypedConversionFixedRoute::Face3d:
17952 return DwgFixedEntityRoute::Face3d;
17953 }
17954 return std::nullopt;
17955 };
17956 const auto stage = [this, version](
17957 const RS_Entity *entity, DwgEntityWritePlanRoute route,
17958 DwgFixedEntityRoute fixed, std::uint16_t frameType) {
17959 if (entity == nullptr || entity->sourceHandle() == 0 || frameType == 0 ||
17960 m_dwgWriteDuplicateEntityHandles.count(entity->sourceHandle()) != 0) {
17961 return false;
17962 }
17963 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
17964 return entityIt != m_dwgWriteEntityHandleByEntity.end() &&
17965 stageDwgEntityWritePlan(entity, entity->sourceHandle(),
17966 entityIt->second, route, fixed, frameType,
17967 version);
17968 };
17969 const auto inspect = [&](RS_EntityContainer *container,
17970 bool reconstructsSidecars) {
17971 if (container == nullptr)
17972 return true;
17973 for (RS_Entity *entity :
17974 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
17975 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
17976 hasNoOwnRecord(entity) || entity->sourceHandle() == 0) {
17977 continue;
17978 }
17979
17980 if (entity->rtti() == RS2::EntityMLeader) {
17981 const auto *mleader = static_cast<const LC_MLeader *>(entity);
17982 const DwgMLeaderWriteRoute route =
17983 selectDwgMLeaderWriteRoute(mleader->getData(), version);
17984 if (route == DwgMLeaderWriteRoute::Invalid)
17985 return false;
17986 if (route == DwgMLeaderWriteRoute::Native &&
17987 (mleaderClassNumber < 500 ||
17988 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
17989 DwgFixedEntityRoute::Line, mleaderClassNumber))) {
17990 return false;
17991 }
17992 continue;
17993 }
17994
17995 if (hasValidDwg3DLineSidecar(entity)) {
17996 if (!reconstructsSidecars ||
17997 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
17998 DwgFixedEntityRoute::Line, DRW_3DLine::kDwgClassNum)) {
17999 return false;
18000 }
18001 continue;
18002 }
18003
18004 if (hasDwgUnderlaySidecar(entity)) {
18005 DwgUnderlaySidecar sidecar;
18006 if (!reconstructsSidecars || version < DRW::AC1015 ||
18007 !parseDwgUnderlaySidecar(entity, sidecar)) {
18008 return false;
18009 }
18010 const auto &definitions =
18011 m_graphic->dwgAdvancedMetadata().underlayDefinitions();
18012 const bool hasDefinition = std::any_of(
18013 definitions.cbegin(), definitions.cend(),
18014 [&sidecar](const auto &definition) {
18015 return definition.replayState ==
18016 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
18017 definition.handle == sidecar.value.definitionHandle &&
18018 static_cast<int>(definition.kind) ==
18019 static_cast<int>(sidecar.value.kind);
18020 });
18021 if (!hasDefinition)
18022 return false;
18023 const std::uint16_t classNumber =
18024 underlayEntityClassNumber(sidecar.value.kind, m_dwgW);
18025 if (classNumber < 500 ||
18026 !stage(entity, DwgEntityWritePlanRoute::CustomSingleFrame,
18027 DwgFixedEntityRoute::Line, classNumber)) {
18028 return false;
18029 }
18030 continue;
18031 }
18032
18033 if (hasDwgTypedConversionSidecar(entity)) {
18034 const auto descriptor = findDwgTypedConversionFixedDescriptor(entity);
18035 if (!descriptor)
18036 return false;
18037 const auto route = fixedRoute(descriptor->route);
18038 if (!reconstructsSidecars || !route ||
18039 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame, *route,
18040 descriptor->wireType)) {
18041 return false;
18042 }
18043 continue;
18044 }
18045
18046 if (entity->rtti() == RS2::EntityPoint &&
18047 !hasDwgTypedConversionSidecar(entity) &&
18048 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
18049 DwgFixedEntityRoute::Point, dwgType::POINT)) {
18050 return false;
18051 }
18052
18053 if (entity->rtti() == RS2::EntityCircle &&
18054 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
18055 DwgFixedEntityRoute::Circle, dwgType::CIRCLE)) {
18056 return false;
18057 }
18058
18059 if (entity->rtti() == RS2::EntityArc &&
18060 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
18061 DwgFixedEntityRoute::Arc, dwgType::ARC)) {
18062 return false;
18063 }
18064
18065 if (entity->rtti() == RS2::EntityLine &&
18066 !hasDwgTypedConversionSidecar(entity) &&
18067 !stage(entity, DwgEntityWritePlanRoute::FixedSingleFrame,
18068 DwgFixedEntityRoute::Line, dwgType::LINE)) {
18069 return false;
18070 }
18071 }
18072 return true;
18073 };
18074
18075 if (!inspect(m_graphic, /*reconstructsSidecars=*/true))
18076 return false;
18077 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
18078 RS_Block *block = m_graphic->blockAt(i);
18079 if (block != nullptr && !block->isDeleted() &&
18080 !inspect(block, /*reconstructsSidecars=*/true)) {
18081 return false;
18082 }
18083 }
18084 return true;
18085}
18086
18087bool RS_FilterDXFRW::admitDwgEntityWritePlans() {
18088 if (m_dwgW == nullptr)
18089 return true;
18090
18091 for (const auto &entry : m_dwgWriteEntityPlansByEntity) {
18092 const DwgEntityWritePlan &plan = entry.second;
18093 if (plan.admitted) {
18094 if (plan.committed)
18095 return false;
18096 continue;
18097 }
18098
18099 bool admitted = false;
18100 if (plan.route == DwgEntityWritePlanRoute::FixedSingleFrame) {
18101 admitted = admitDwgFixedEntityFrame(plan.fixedRoute, plan.sourceHandle,
18102 plan.outputHandle,
18103 plan.expectedFrameType, plan.version);
18104 } else {
18105 admitted =
18106 plan.expectedFrameType >= 500 &&
18107 admitDwgClassFrameDescriptor(
18108 DwgWriteSourceKind::Entity, plan.sourceHandle, plan.outputHandle,
18109 plan.expectedFrameType, 0, plan.version) &&
18110 m_dwgW->registerDwgEntityClassInstance(plan.expectedFrameType,
18111 plan.outputHandle);
18112 }
18113 if (!admitted || !admitDwgEntityWritePlan(plan.entity))
18114 return false;
18115 }
18116 return true;
18117}
18118
18119bool RS_FilterDXFRW::commitDwgEntityWrite(const RS_Entity *entity) {
18120 if (m_dwgW == nullptr || entity == nullptr)
18121 return false;
18122
18123 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
18124 if (entityIt == m_dwgWriteEntityHandleByEntity.end())
18125 return entity->sourceHandle() == 0;
18126
18127 const std::uint32_t outputHandle = entityIt->second;
18128 const auto *frame = findDwgWriteObjectOccurrences(outputHandle);
18129 if (frame == nullptr) {
18130 m_writeFailed = true;
18131 return false;
18132 }
18133 if (DwgEntityWritePlan *plan = findDwgEntityWritePlan(entity)) {
18134 if (!plan->admitted || plan->outputHandle != outputHandle ||
18135 plan->sourceHandle != entity->sourceHandle()) {
18136 m_writeFailed = true;
18137 return false;
18138 }
18139 if (plan->route == DwgEntityWritePlanRoute::FixedSingleFrame) {
18140 if (!commitDwgFixedEntityFrame(plan->fixedRoute, plan->sourceHandle,
18141 outputHandle, *frame)) {
18142 m_writeFailed = true;
18143 return false;
18144 }
18145 } else if (!commitDwgClassFrame(DwgWriteSourceKind::Entity,
18146 plan->sourceHandle, outputHandle,
18147 plan->expectedFrameType)) {
18148 m_writeFailed = true;
18149 return false;
18150 }
18151 if (!commitDwgEntityWritePlan(entity, *frame)) {
18152 m_writeFailed = true;
18153 return false;
18154 }
18155 } else {
18156 // Unmigrated routes retain their established receipt behavior until
18157 // their multi-frame/metadata contracts join the shared plan.
18158 const DwgWriteSourceKey fixedSourceKey{DwgWriteSourceKind::Entity,
18159 entity->sourceHandle(), 0};
18160 if (entity->sourceHandle() != 0) {
18161 const auto fixedIt =
18162 m_dwgWriteFixedEntityFrameReceiptsBySource.find(fixedSourceKey);
18163 if (fixedIt != m_dwgWriteFixedEntityFrameReceiptsBySource.end() &&
18164 !commitDwgFixedEntityFrame(fixedIt->second.route,
18165 entity->sourceHandle(), outputHandle,
18166 *frame)) {
18167 m_writeFailed = true;
18168 return false;
18169 }
18170 }
18171 if (entity->sourceHandle() != 0 &&
18172 m_dwgW->hasDwgClassDefinition(frame->classNumber) &&
18173 (!admitDwgClassFrameDescriptor(
18174 DwgWriteSourceKind::Entity, entity->sourceHandle(), outputHandle,
18175 frame->classNumber, 0, m_dwgW->getVersion()) ||
18176 !commitDwgClassFrame(DwgWriteSourceKind::Entity,
18177 entity->sourceHandle(), outputHandle,
18178 frame->classNumber))) {
18179 m_writeFailed = true;
18180 return false;
18181 }
18182 }
18183
18184 if (!commitDwgWriteIdentity(
18185 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Entity,
18186 outputHandle)) {
18187 m_writeFailed = true;
18188 return false;
18189 }
18190
18191 const std::uint32_t sourceHandle = entity->sourceHandle();
18192 if (sourceHandle != 0 &&
18193 m_dwgWriteDuplicateEntityHandles.count(sourceHandle) == 0) {
18194 m_dwgWriteEntityHandlesEmitted.insert(sourceHandle);
18195 }
18196 return true;
18197}
18198
18199bool RS_FilterDXFRW::recordDwgWriteObjectOccurrences(
18200 std::uint32_t outputHandle, const DRW::DwgObjectFrameReceipt &frame) {
18201 if (m_dwgW == nullptr)
18202 return false;
18203
18204 // Bind the provenance token to the writer's current committed frame. A
18205 // copied receipt from an earlier object must not be attached to this one.
18206 DRW::DwgObjectFrameReceipt currentFrame;
18207 if (!m_dwgW->getLastDwgObjectFrame(currentFrame) || !currentFrame.valid ||
18208 currentFrame.generation != frame.generation ||
18209 currentFrame.objectHandle != frame.objectHandle ||
18210 currentFrame.version != frame.version)
18211 return false;
18212
18213 return RS_FilterDXFRW_DwgWriteIdentityRegistry::
18214 recordDwgWriteObjectOccurrences(outputHandle, frame);
18215}
18216
18217bool RS_FilterDXFRW::recordDwgEntityFrame(std::uint32_t outputHandle) {
18218 if (m_dwgW == nullptr || outputHandle == 0)
18219 return false;
18220
18221 DRW::DwgObjectFrameReceipt frame;
18222 if (!m_dwgW->getLastDwgObjectFrame(frame) ||
18223 !recordDwgWriteObjectOccurrences(outputHandle, frame)) {
18224 m_writeFailed = true;
18225 return false;
18226 }
18227 return true;
18228}
18229
18230bool RS_FilterDXFRW::commitDwgPolylineCompound(const RS_Entity *anchor) {
18231 DRW::DwgCompoundEntityWriteReceipt receipt;
18232 if (m_dwgW == nullptr || anchor == nullptr ||
18233 !m_dwgW->getLastDwgCompoundEntityWriteReceipt(receipt) ||
18234 !receipt.valid || receipt.parentHandle == 0 ||
18235 receipt.frames.size() < 3 ||
18236 receipt.frames.front().role != DRW::DwgCompoundEntityFrameRole::Parent ||
18237 receipt.frames.back().role != DRW::DwgCompoundEntityFrameRole::SeqEnd) {
18238 return false;
18239 }
18240
18241 std::set<std::uint32_t> handles;
18242 std::set<std::uint64_t> generations;
18243 for (std::size_t index = 0; index < receipt.frames.size(); ++index) {
18244 const auto &expected = receipt.frames[index];
18245 const bool isParent = index == 0;
18246 const bool isSeqEnd = index + 1 == receipt.frames.size();
18247 if (!expected.frame.valid || expected.frame.generation == 0 ||
18248 expected.frame.objectHandle == 0 ||
18249 expected.frame.version != m_dwgW->getVersion() ||
18250 !handles.insert(expected.frame.objectHandle).second ||
18251 !generations.insert(expected.frame.generation).second ||
18252 (isParent &&
18253 expected.role != DRW::DwgCompoundEntityFrameRole::Parent) ||
18254 (!isParent && !isSeqEnd &&
18255 expected.role != DRW::DwgCompoundEntityFrameRole::Vertex) ||
18256 (isSeqEnd &&
18257 expected.role != DRW::DwgCompoundEntityFrameRole::SeqEnd)) {
18258 return false;
18259 }
18260 }
18261 if (receipt.frames.front().frame.objectHandle != receipt.parentHandle)
18262 return false;
18263
18264 const auto checkpoint = checkpointDwgWriteIdentity();
18265 const auto fail = [this, &checkpoint]() {
18266 restoreDwgWriteIdentity(checkpoint);
18267 return false;
18268 };
18269 for (const auto &entry : receipt.frames) {
18270 if (!RS_FilterDXFRW_DwgWriteIdentityRegistry::
18271 recordDwgWriteObjectOccurrences(entry.frame.objectHandle,
18272 entry.frame)) {
18273 return fail();
18274 }
18275 }
18276
18277 const std::uint32_t parentHandle = receipt.parentHandle;
18278 const bool parentCommitted = anchor->sourceHandle() == 0
18279 ? commitDwgGeneratedEntityWrite(parentHandle)
18280 : commitDwgEntityWrite(anchor);
18281 if (!parentCommitted)
18282 return fail();
18283
18284 for (std::size_t index = 1; index < receipt.frames.size(); ++index) {
18285 const std::uint32_t childHandle = receipt.frames[index].frame.objectHandle;
18286 if (!reserveDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
18287 childHandle) ||
18288 !selectDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
18289 childHandle) ||
18290 !commitDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild,
18291 childHandle)) {
18292 return fail();
18293 }
18294 }
18295 return true;
18296}
18297
18298void RS_FilterDXFRW::rollbackDwgEntityWrite(const RS_Entity *entity) {
18299 if (m_dwgW == nullptr || entity == nullptr)
18300 return;
18301
18302 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
18303 if (entityIt == m_dwgWriteEntityHandleByEntity.end())
18304 return;
18305
18306 (void)rollbackDwgWriteIdentity(
18307 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Entity,
18308 entityIt->second);
18309}
18310#endif
18311
18312void RS_FilterDXFRW::writeHeader(DRW_Header &data) {
18313 RS_Vector v;
18314 /*TODO $ISOMETRICGRID == $SNAPSTYLE and "GRID on/off" not handled because is
18315 part of active vport to save is required read/write VPORT table */
18316 QHash<QString, RS_Variable> vars = m_graphic->getVariableDict();
18317 if (!vars.contains("$DWGCODEPAGE")) {
18318 // RLZ: TODO verify this
18319 m_codePage = RS_SYSTEMRS_System::instance()->localeToISO(QLocale::system().name().toLocal8Bit());
18320 // RS_Variable v(
18321 // QString(RS_SYSTEM->localeToISO(QLocale::system().name().toLocal8Bit())),0
18322 // );
18323 vars.insert(QString("$DWGCODEPAGE"), RS_Variable(m_codePage, 0));
18324 }
18325
18326 QHash<QString, RS_Variable>::iterator it = vars.begin();
18327 while (it != vars.end()) {
18328 auto value = it.value();
18329 int code = value.getCode();
18330 auto key = it.key().toStdString();
18331 switch (value.getType()) {
18332 case RS2::VariableInt:
18333 data.addInt(key, value.getInt(), code);
18334 break;
18335 case RS2::VariableDouble:
18336 data.addDouble(key, value.getDouble(), code);
18337 break;
18338 case RS2::VariableString:
18339 data.addStr(key, value.getString().toUtf8().data(), code);
18340 break;
18341 case RS2::VariableVector:
18342 v = value.getVector();
18343 data.addCoord(key, DRW_Coord(v.x, v.y, v.z), code);
18344 break;
18345 default:
18346 break;
18347 }
18348 ++it;
18349 }
18350 // $EXTMIN must use getMin() and $EXTMAX getMax(); the previous code
18351 // overwrote vmin with getMax() before reading it, so $EXTMIN==$EXTMAX
18352 // (a degenerate point bbox → broken ZOOM-EXTENTS/auto-scale) on every
18353 // DWG+DXF write, and the Z extent was forced to 0. (write-review P3 #3)
18354 const RS_Vector vmin = m_graphic->getMin();
18355 const RS_Vector vmax = m_graphic->getMax();
18356 data.addCoord("$EXTMIN", DRW_Coord(vmin.x, vmin.y, vmin.z), 0);
18357 data.addCoord("$EXTMAX", DRW_Coord(vmax.x, vmax.y, vmax.z), 0);
18358
18359 // when saving a block, there is no active layer. ignore it to avoid crash
18360 if (m_graphic->getActiveLayer() == nullptr) {
18361 return;
18362 }
18363 data.addStr("$CLAYER",
18364 (m_graphic->getActiveLayer()->getName()).toUtf8().data(), 8);
18365
18366 const QHash<QString, RS_Variable> customVars =
18367 m_graphic->getCustomProperties();
18368
18369 QHashIterator<QString, RS_Variable> customVar(customVars);
18370 while (customVar.hasNext()) {
18371 customVar.next();
18372 auto val = customVar.value().getString();
18373 if (!val.isEmpty()) {
18374 auto key = customVar.key().toStdString();
18375 data.addCustomVar(std::move(key), val.toStdString());
18376 }
18377 }
18378}
18379
18380void RS_FilterDXFRW::writeDwgClasses() {
18381#ifdef DWGSUPPORT1
18382 if (m_dwgW == nullptr || m_graphic == nullptr)
18383 return;
18384 prepareDwgEntityHandleMap();
18385 // The concrete DWG writer and its allocator are created immediately
18386 // before this callback. Freeze DataStorage decisions here, before
18387 // CLASSES is emitted, so entity remaps are part of the same decision.
18388 if (!m_dwgDataStorageReplayPlan.frozen) {
18389 prepareDwgEntityHandleMap();
18390 if (m_writeFailed ||
18391 !validateDwgDataStoragePreflight(m_dwgW->getVersion()) ||
18392 !freezeDwgDataStorageReplayPlan()) {
18393 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18394 "RS_FilterDXFRW: DWG DataStorage replay plan changed "
18395 "during handle planning");
18396 m_writeFailed = true;
18397 m_dwgW->markDwgWriteFailure();
18398 return;
18399 }
18400 }
18401 if (!prepareDwgExportAdmissionPlan()) {
18402 m_writeFailed = true;
18403 m_dwgW->markDwgWriteFailure();
18404 return;
18405 }
18406 if (!prepareDwgFieldWritePlan()) {
18407 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18408 "RS_FilterDXFRW: DWG FIELD admission plan is invalid");
18409 m_writeFailed = true;
18410 m_dwgW->markDwgWriteFailure();
18411 return;
18412 }
18413 for (const auto &entry : m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
18414 const auto &graph = entry.second.ownerGraph;
18415 if (!graph.has_value())
18416 continue;
18417 if (graph->rootSourceHandle == 0 ||
18418 graph->rootOutputHandle != DRW::DwgNamedObjectsDictionaryHandle ||
18419 graph->childSourceHandle == 0 ||
18420 graph->childSourceHandle != graph->childOutputHandle ||
18421 graph->childSourceHandle != graph->rawOwnerHandle ||
18422 graph->rootSourceHandle == graph->childSourceHandle ||
18423 graph->rootSourceHandle == graph->rawSourceHandle ||
18424 graph->childSourceHandle == graph->rawSourceHandle ||
18425 graph->rootEntryName.empty() ||
18426 !m_dwgW->registerDwgNamedObjectDictionaryEntry(
18427 graph->rootEntryName, graph->childOutputHandle)) {
18428 m_writeFailed = true;
18429 m_dwgW->markDwgWriteFailure();
18430 return;
18431 }
18432 }
18433 const auto &metadata = m_graphic->dwgAdvancedMetadata();
18434 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
18435 const bool canWriteAssociativeObjects = canWriteModernObjects;
18436 // PR 13f — custom-class registration gate for the RasterVariables /
18437 // GeoData / SpatialFilter families. Broadened from AC1021+ to AC1015+
18438 // because their encoders + parsers are version-clean (only the
18439 // standard `version > AC1018` split-buffer routing). The dispatch
18440 // loop in writeObjects uses a matching `canRegisterCustomClassObjects`
18441 // gate so the CLASSES section entry and the OBJECTS instance land
18442 // together (otherwise the reader's class lookup fails and the object
18443 // is silently dropped).
18444 const bool canRegisterCustomClassObjects =
18445 m_dwgW->getVersion() >= DRW::AC1015;
18446
18447 // DIMARC is the first entity route with a deterministic custom class and
18448 // exactly one frame. Admit only source-backed records here; generated
18449 // and compound routes still use their later, route-specific contracts.
18450 const auto admitSourceBackedDimArc = [this]() {
18451 const std::uint16_t classNumber = m_dwgW->getDwgTypedClassNumber(
18452 "AcDbArcDimension", "ARC_DIMENSION", DRW_DimArc::kDwgClassNum);
18453 if (classNumber < 500)
18454 return false;
18455
18456 const auto inspect = [this, classNumber](RS_EntityContainer *container) {
18457 if (container == nullptr)
18458 return true;
18459 for (RS_Entity *entity :
18460 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
18461 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
18462 entity->rtti() != RS2::EntityDimArc || entity->sourceHandle() == 0)
18463 continue;
18464
18465 const std::uint32_t sourceHandle = entity->sourceHandle();
18466 if (m_dwgWriteDuplicateEntityHandles.count(sourceHandle) != 0)
18467 return false;
18468 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
18469 if (entityIt == m_dwgWriteEntityHandleByEntity.end() ||
18470 entityIt->second == 0 ||
18471 !admitDwgClassFrameDescriptor(
18472 DwgWriteSourceKind::Entity, sourceHandle, entityIt->second,
18473 classNumber, 0, m_dwgW->getVersion()) ||
18474 !m_dwgW->registerDwgEntityClassInstance(classNumber,
18475 entityIt->second))
18476 return false;
18477 }
18478 return true;
18479 };
18480
18481 if (!inspect(m_graphic))
18482 return false;
18483 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
18484 RS_Block *block = m_graphic->blockAt(i);
18485 if (block != nullptr && !block->isDeleted() && !inspect(block))
18486 return false;
18487 }
18488 return true;
18489 };
18490 if (canRegisterCustomClassObjects && !admitSourceBackedDimArc()) {
18491 m_writeFailed = true;
18492 m_dwgW->markDwgWriteFailure();
18493 return;
18494 }
18495 if (!prepareDwgEntityWritePlans() || !admitDwgEntityWritePlans()) {
18496 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18497 "RS_FilterDXFRW: DWG entity-frame plan admission failed");
18498 m_writeFailed = true;
18499 m_dwgW->markDwgWriteFailure();
18500 return;
18501 }
18502 const auto admitSourceBackedMetadataEntity =
18503 [this](const auto &records, std::uint16_t classNumber,
18504 DRW::Version minimumVersion) {
18505 if (m_dwgW->getVersion() < minimumVersion)
18506 return true;
18507 for (const auto &record : records) {
18508 if (record.replayState !=
18509 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18510 record.handle == 0)
18511 continue;
18512 if (isDwgWriteSourceAmbiguous(
18513 record.handle, RS_FilterDXFRW_DwgWriteIdentityRegistry::
18514 DwgWriteSourceKind::Entity))
18515 return false;
18516 const std::uint32_t outputHandle =
18517 resolveDwgWriteHandle(record.handle);
18518 if (outputHandle == 0 ||
18519 !admitDwgClassFrameDescriptor(
18520 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
18521 Entity,
18522 record.handle, outputHandle, classNumber, 0,
18523 m_dwgW->getVersion()) ||
18524 !m_dwgW->registerDwgEntityClassInstance(classNumber,
18525 outputHandle))
18526 return false;
18527 }
18528 return true;
18529 };
18530 if (!admitSourceBackedMetadataEntity(metadata.lights(),
18531 DRW_Light::kDwgClassNum, DRW::AC1021) ||
18532 !admitSourceBackedMetadataEntity(metadata.navisworksModels(),
18533 DRW_NavisworksModel::kDwgClassNum,
18534 DRW::AC1015) ||
18535 !admitSourceBackedMetadataEntity(metadata.cameras(),
18536 DRW_Camera::kDwgClassNum, DRW::AC1015) ||
18537 !admitSourceBackedMetadataEntity(metadata.sectionObjects(),
18538 DRW_SectionObject::kDwgClassNum,
18539 DRW::AC1021)) {
18540 m_writeFailed = true;
18541 m_dwgW->markDwgWriteFailure();
18542 return;
18543 }
18544 const auto remapClassInstanceHandle = [this](auto &object) {
18545 object.handle = resolveDwgWriteHandle(object.handle);
18546 };
18547 const auto noteObjectClassRegistration =
18548 [this](bool registered, std::set<std::uint32_t> &handles,
18549 std::uint32_t sourceHandle) {
18550 const auto *decision = dataStorageDecisionForOwner(sourceHandle);
18551 if (!registered) {
18552 if (decision != nullptr &&
18553 decision->decision == DwgDataStorageDecision::EmitRaw)
18554 return;
18555 m_writeFailed = true;
18556 return;
18557 }
18558 handles.insert(sourceHandle);
18559 if (decision != nullptr &&
18560 !recordDwgDataStorageClassAdmission(
18561 sourceHandle, DwgDataStorageCarrier::Kind::Typed)) {
18562 m_writeFailed = true;
18563 return;
18564 }
18565 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
18566 std::uint16_t classNumber = 0;
18567 if (!m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber) ||
18568 !admitDwgClassFrameDescriptor(
18569 DwgWriteSourceKind::Object, sourceHandle, outputHandle,
18570 classNumber, 0, m_dwgW->getVersion())) {
18571 m_writeFailed = true;
18572 }
18573 };
18574 const auto hasUnresolvedSortEntsReference = [this](
18575 const DRW_SortEntsTable &table) {
18576 const auto isLive = [](DwgWriteReferenceStatus status) {
18577 return status == DwgWriteReferenceStatus::Resolved ||
18578 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
18579 status == DwgWriteReferenceStatus::Reserved ||
18580 status == DwgWriteReferenceStatus::Selected ||
18581 status == DwgWriteReferenceStatus::Committed;
18582 };
18583 return std::any_of(
18584 table.m_sortHandles.cbegin(), table.m_sortHandles.cend(),
18585 [this, &isLive](std::uint32_t handle) {
18586 if (handle == DRW::NoHandle)
18587 return false;
18588 std::uint32_t outputHandle = 0;
18589 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
18590 const DwgWriteReferenceStatus status =
18591 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
18592 return !isLive(status) || outputHandle == 0;
18593 });
18594 };
18595 // The frozen admission plan is the sole carrier decision for this custom
18596 // object. Raw records stay authoritative only when the plan selected their
18597 // exact same-version frame; all other sidecars use the typed path.
18598 std::size_t nativePlotSettingsCount =
18599 metadata.plotSettings().empty() ? 1u : 0u;
18600 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index) {
18601 if (isDwgTypedPlotSettingsSelected(index))
18602 ++nativePlotSettingsCount;
18603 }
18604 const bool emitNativePlotSettings =
18605 canRegisterCustomClassObjects && nativePlotSettingsCount != 0;
18606 if (emitNativePlotSettings) {
18607 if (metadata.plotSettings().empty()) {
18608 if (!m_dwgW->registerPlotSettingsObjectClass())
18609 m_writeFailed = true;
18610 } else {
18611 for (std::size_t index = 0; index < metadata.plotSettings().size();
18612 ++index) {
18613 const auto &settings = metadata.plotSettings()[index];
18614 if (!isDwgTypedPlotSettingsSelected(index))
18615 continue;
18616 if (settings.handle == 0) {
18617 if (!m_dwgW->registerPlotSettingsObjectClass()) {
18618 m_writeFailed = true;
18619 break;
18620 }
18621 continue;
18622 }
18623 const std::uint32_t outputHandle =
18624 resolveDwgWriteHandle(settings.handle);
18625 if (!m_dwgW->registerPlotSettingsObjectClass(outputHandle)) {
18626 m_writeFailed = true;
18627 break;
18628 }
18629 std::uint16_t classNumber = 0;
18630 if (!m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber) ||
18631 !admitDwgClassFrameDescriptor(
18632 DwgWriteSourceKind::Object, settings.handle, outputHandle,
18633 classNumber, 0, m_dwgW->getVersion())) {
18634 m_writeFailed = true;
18635 break;
18636 }
18637 }
18638 }
18639 }
18640 if (emitNativePlotSettings && !m_writeFailed) {
18641 const std::uint16_t classNumber =
18642 m_dwgW->getDwgTypedClassNumber("AcDbPlotSettings", "PLOTSETTINGS", 0);
18643 const auto admitGeneratedPlotSettings = [this,
18644 classNumber](std::size_t index) {
18645 if (index >= std::numeric_limits<std::uint32_t>::max())
18646 return false;
18647 return admitDwgClassFrameDescriptor(
18648 DwgWriteSourceKind::GeneratedChild, 0, 0, classNumber,
18649 static_cast<std::uint32_t>(index + 1), m_dwgW->getVersion());
18650 };
18651 if (classNumber < 500) {
18652 m_writeFailed = true;
18653 } else if (metadata.plotSettings().empty()) {
18654 if (!admitGeneratedPlotSettings(0))
18655 m_writeFailed = true;
18656 } else {
18657 for (std::size_t index = 0; index < metadata.plotSettings().size();
18658 ++index) {
18659 const auto &settings = metadata.plotSettings()[index];
18660 if (settings.handle == 0 && isDwgTypedPlotSettingsSelected(index) &&
18661 !admitGeneratedPlotSettings(index)) {
18662 m_writeFailed = true;
18663 break;
18664 }
18665 }
18666 }
18667 }
18668 const auto registerRawClass =
18669 [this](const DRW_UnsupportedObject &object,
18670 const LC_DwgAdvancedMetadata::RawObjectRecord &record) {
18671 const bool registered = m_dwgW->registerRawDwgObjectClass(&object);
18672 if (!registered) {
18673 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
18674 "RS_FilterDXFRW: cannot register raw DWG class %s "
18675 "for handle 0x%X",
18676 record.recordName.empty() ? "(unnamed)"
18677 : record.recordName.c_str(),
18678 record.handle);
18679 return false;
18680 }
18681 const std::uint32_t outputHandle = resolveDwgWriteHandle(record.handle);
18682 std::uint16_t classNumber = 0;
18683 const bool hasClassInstance =
18684 m_dwgW->getDwgClassInstanceClass(outputHandle, classNumber);
18685 if (object.m_isCustomClass &&
18686 (!hasClassInstance ||
18687 !admitDwgClassFrameDescriptor(
18688 DwgWriteSourceKind::RawCarrier, record.handle, outputHandle,
18689 classNumber, 0, m_dwgW->getVersion())))
18690 return false;
18691 return !record.hasDataStorage ||
18692 recordDwgDataStorageClassAdmission(
18693 record.handle, DwgDataStorageCarrier::Kind::Raw);
18694 };
18695 std::set<std::uint32_t> nativeSunHandles;
18696 std::set<std::uint32_t> nativeSunStudyHandles;
18697 std::set<std::uint32_t> nativeMotionPathHandles;
18698 std::set<std::uint32_t> nativeCurvePathHandles;
18699 std::set<std::uint32_t> nativePointPathHandles;
18700 std::set<std::uint32_t> nativeObjectPtrHandles;
18701 std::set<std::uint32_t> nativePartialViewingIndexHandles;
18702 std::set<std::uint32_t> nativeRenderSettingsHandles;
18703 std::set<std::uint32_t> nativeVisualStyleHandles;
18704 std::set<std::uint32_t> nativeTvDevicePropertiesHandles;
18705 std::set<std::uint32_t> nativeVxControlHandles;
18706 std::set<std::uint32_t> nativeVxTableRecordHandles;
18707 std::set<std::uint32_t> nativeMLeaderStyleHandles;
18708 std::set<std::uint32_t> nativeTableStyleHandles;
18709 std::set<std::uint32_t> nativeMaterialHandles;
18710 std::set<std::uint32_t> nativeLightListHandles;
18711 std::set<std::uint32_t> nativeBackgroundHandles;
18712 std::set<std::uint32_t> nativeDbColorHandles;
18713 std::set<std::uint32_t> nativeRasterVariablesHandles;
18714 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
18715 std::set<std::uint32_t> nativeGeoDataHandles;
18716 std::set<std::uint32_t> nativeSpatialFilterHandles;
18717 std::set<std::uint32_t> nativeScaleHandles;
18718 std::set<std::uint32_t> nativeIDBufferHandles;
18719 std::set<std::uint32_t> nativeLayerIndexHandles;
18720 std::set<std::uint32_t> nativeSpatialIndexHandles;
18721 std::set<std::uint32_t> nativeDictionaryVarHandles;
18722 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
18723 std::set<std::uint32_t> nativeSortEntsTableHandles;
18724 std::set<std::uint32_t> nativeFieldListHandles;
18725 std::set<std::uint32_t> nativeFieldHandles;
18726 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
18727 std::set<std::uint32_t> nativePointCloudDefinitionHandles;
18728 std::set<std::uint32_t> nativeNavisworksModelDefinitionHandles;
18729 std::set<std::uint32_t> nativePointCloudColorMapHandles;
18730 std::set<std::uint32_t> nativeDimensionAssociationHandles;
18731 std::set<std::uint32_t> nativeEvaluationGraphHandles;
18732 std::set<std::uint32_t> nativeBlockRepresentationHandles;
18733 std::set<std::uint32_t> nativeSectionHandles;
18734
18735 // BLOCKREPRESENTATIONDATA is a fixed DWG object type, so it needs no
18736 // CLASSES entry. Still publish its handle before other metadata objects
18737 // resolve references to it.
18738 if (canRegisterCustomClassObjects) {
18739 for (const auto &record : metadata.blockRepresentationData()) {
18740 if (record.replayState ==
18741 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
18742 record.handle != 0 &&
18743 (m_dwgW->getVersion() <= DRW::AC1024 || !record.hasDsData)) {
18744 DRW_BlockRepresentationData data =
18745 blockRepresentationFromMetadata(record);
18746 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
18747 m_writeFailed = true;
18748 continue;
18749 }
18750 nativeBlockRepresentationHandles.insert(record.handle);
18751 }
18752 }
18753 }
18754
18755 if (canWriteModernObjects) {
18756 for (const auto &record : metadata.suns()) {
18757 if (record.replayState !=
18758 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18759 record.handle == 0) {
18760 continue;
18761 }
18762 DRW_Sun sun = sunFromMetadata(record);
18763 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
18764 m_writeFailed = true;
18765 continue;
18766 }
18767 remapClassInstanceHandle(sun);
18768 noteObjectClassRegistration(
18769 isDwgDataStorageTypedSelected(record.handle) &&
18770 m_dwgW->registerSunObjectClass(&sun),
18771 nativeSunHandles, record.handle);
18772 }
18773 for (const auto &record : metadata.dimensionAssociations()) {
18774 if (record.replayState !=
18775 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18776 record.handle == 0 || !canWriteAssociativeObjects) {
18777 continue;
18778 }
18779 DRW_DimensionAssociation association =
18780 dimensionAssociationFromMetadata(record);
18781 if (hasDxfTableAppDataUnsupportedByDwg(association)) {
18782 m_writeFailed = true;
18783 continue;
18784 }
18785 remapClassInstanceHandle(association);
18786 if (canWriteNativeDimensionAssociation(association)) {
18787 noteObjectClassRegistration(
18788 isDwgDataStorageTypedSelected(record.handle) &&
18789 m_dwgW->registerDimensionAssociationObjectClass(&association),
18790 nativeDimensionAssociationHandles, record.handle);
18791 }
18792 }
18793 for (const auto &record : metadata.evaluationGraphs()) {
18794 if (record.replayState !=
18795 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18796 record.handle == 0 || !canWriteAssociativeObjects) {
18797 continue;
18798 }
18799 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
18800 if (hasDxfTableAppDataUnsupportedByDwg(graph)) {
18801 m_writeFailed = true;
18802 continue;
18803 }
18804 remapClassInstanceHandle(graph);
18805 if (canWriteNativeEvaluationGraph(graph)) {
18806 noteObjectClassRegistration(
18807 isDwgDataStorageTypedSelected(record.handle) &&
18808 m_dwgW->registerEvaluationGraphObjectClass(&graph),
18809 nativeEvaluationGraphHandles, record.handle);
18810 }
18811 }
18812 for (const auto &record : metadata.sections()) {
18813 if (record.replayState !=
18814 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18815 record.handle == 0)
18816 continue;
18817 DRW_Section section = sectionFromMetadata(record);
18818 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
18819 m_writeFailed = true;
18820 continue;
18821 }
18822 remapClassInstanceHandle(section);
18823 noteObjectClassRegistration(
18824 isDwgDataStorageTypedSelected(record.handle) &&
18825 m_dwgW->registerSectionObjectClass(&section),
18826 nativeSectionHandles, record.handle);
18827 }
18828 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER registration moved
18829 // below to the `canRegisterCustomClassObjects` block (PR 13f).
18830 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
18831 // DICTIONARYVAR registration moved below to the same block.
18832 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
18833 // registration moved below to the `canRegisterCustomClassObjects`
18834 // block.
18835 }
18836
18837 // PR 13f — custom-class families with version-clean encoders +
18838 // round-trip-grade Records register here on the broadened
18839 // `canRegisterCustomClassObjects` gate (≥AC1015) instead of the
18840 // AC1021+ `canWriteModernObjects` gate. The matching dispatch loop
18841 // in writeObjects uses the same gate so the CLASSES section entry and
18842 // the OBJECTS instance always land together.
18843 if (canRegisterCustomClassObjects) {
18844 for (const auto &record : metadata.mleaderStyles()) {
18845 if (record.replayState !=
18846 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18847 record.handle == 0 ||
18848 hasReplayableRawMLeaderStyle(metadata, record.handle,
18849 m_dwgW->getVersion())) {
18850 continue;
18851 }
18852 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
18853 remapClassInstanceHandle(style);
18854 noteObjectClassRegistration(
18855 isDwgDataStorageTypedSelected(record.handle) &&
18856 m_dwgW->registerMLeaderStyleObjectClass(&style),
18857 nativeMLeaderStyleHandles, record.handle);
18858 }
18859 // MATERIAL has only the modelled name/description fields. Keep a
18860 // replayable source carrier authoritative so its modern opaque tail
18861 // is not discarded by the typed writer.
18862 for (const auto &record : metadata.materials()) {
18863 if (record.replayState !=
18864 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18865 record.handle == 0 ||
18866 hasReplayableRawMaterial(metadata, record.handle,
18867 m_dwgW->getVersion())) {
18868 continue;
18869 }
18870 DRW_Material material = materialFromMetadata(record);
18871 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
18872 m_writeFailed = true;
18873 continue;
18874 }
18875 remapClassInstanceHandle(material);
18876 noteObjectClassRegistration(
18877 isDwgDataStorageTypedSelected(record.handle) &&
18878 m_dwgW->registerMaterialObjectClass(&material),
18879 nativeMaterialHandles, record.handle);
18880 }
18881 if (m_dwgW->getVersion() >= DRW::AC1018) {
18882 for (const auto &record : metadata.dbColors()) {
18883 if (record.replayState !=
18884 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18885 record.handle == 0) {
18886 continue;
18887 }
18888 DRW_DbColor color = dbColorFromMetadata(record);
18889 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
18890 m_writeFailed = true;
18891 continue;
18892 }
18893 remapClassInstanceHandle(color);
18894 noteObjectClassRegistration(
18895 isDwgDataStorageTypedSelected(record.handle) &&
18896 m_dwgW->registerDbColorObjectClass(&color),
18897 nativeDbColorHandles, record.handle);
18898 }
18899 }
18900 for (const auto &record : metadata.lightLists()) {
18901 if (record.replayState !=
18902 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18903 record.handle == 0 ||
18904 hasReplayableRawLightList(
18905 metadata, record.handle, m_dwgW->getVersion(),
18906 [this](std::uint32_t sourceHandle) {
18907 return isDwgWriteOpaqueReferenceStable(sourceHandle);
18908 })) {
18909 continue;
18910 }
18911 DRW_LightList lightList = lightListFromMetadata(record);
18912 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
18913 m_writeFailed = true;
18914 continue;
18915 }
18916 remapClassInstanceHandle(lightList);
18917 noteObjectClassRegistration(
18918 isDwgDataStorageTypedSelected(record.handle) &&
18919 m_dwgW->registerLightListObjectClass(&lightList),
18920 nativeLightListHandles, record.handle);
18921 }
18922 for (const auto &record : metadata.backgrounds()) {
18923 if (record.replayState !=
18924 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18925 record.handle == 0 ||
18926 hasReplayableRawBackground(metadata, record.handle,
18927 m_dwgW->getVersion())) {
18928 continue;
18929 }
18930 DRW_Background background = backgroundFromMetadata(record);
18931 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
18932 m_writeFailed = true;
18933 continue;
18934 }
18935 remapClassInstanceHandle(background);
18936 noteObjectClassRegistration(
18937 isDwgDataStorageTypedSelected(record.handle) &&
18938 m_dwgW->registerBackgroundObjectClass(&background),
18939 nativeBackgroundHandles, record.handle);
18940 }
18941 for (const auto &record : metadata.sunStudies()) {
18942 if (record.replayState !=
18943 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18944 record.handle == 0 ||
18945 hasReplayableRawSunStudy(metadata, record.handle,
18946 m_dwgW->getVersion())) {
18947 continue;
18948 }
18949 DRW_SunStudy study = sunStudyFromMetadata(record);
18950 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
18951 m_writeFailed = true;
18952 continue;
18953 }
18954 remapClassInstanceHandle(study);
18955 noteObjectClassRegistration(
18956 isDwgDataStorageTypedSelected(record.handle) &&
18957 m_dwgW->registerSunStudyObjectClass(&study),
18958 nativeSunStudyHandles, record.handle);
18959 }
18960 for (const auto &record : metadata.motionPaths()) {
18961 if (record.replayState !=
18962 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18963 record.handle == 0 ||
18964 hasReplayableRawMotionPath(metadata, record.handle,
18965 m_dwgW->getVersion())) {
18966 continue;
18967 }
18968 DRW_MotionPath path = motionPathFromMetadata(record);
18969 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
18970 m_writeFailed = true;
18971 continue;
18972 }
18973 remapClassInstanceHandle(path);
18974 noteObjectClassRegistration(
18975 isDwgDataStorageTypedSelected(record.handle) &&
18976 m_dwgW->registerMotionPathObjectClass(&path),
18977 nativeMotionPathHandles, record.handle);
18978 }
18979 for (const auto &record : metadata.curvePaths()) {
18980 if (record.replayState !=
18981 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
18982 record.handle == 0 ||
18983 hasReplayableRawCurvePath(metadata, record.handle,
18984 m_dwgW->getVersion())) {
18985 continue;
18986 }
18987 DRW_CurvePath path = curvePathFromMetadata(record);
18988 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
18989 m_writeFailed = true;
18990 continue;
18991 }
18992 remapClassInstanceHandle(path);
18993 noteObjectClassRegistration(
18994 isDwgDataStorageTypedSelected(record.handle) &&
18995 m_dwgW->registerCurvePathObjectClass(&path),
18996 nativeCurvePathHandles, record.handle);
18997 }
18998 for (const auto &record : metadata.pointPaths()) {
18999 if (record.replayState !=
19000 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19001 record.handle == 0 ||
19002 hasReplayableRawPointPath(metadata, record.handle,
19003 m_dwgW->getVersion())) {
19004 continue;
19005 }
19006 DRW_PointPath path = pointPathFromMetadata(record);
19007 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
19008 m_writeFailed = true;
19009 continue;
19010 }
19011 remapClassInstanceHandle(path);
19012 noteObjectClassRegistration(
19013 isDwgDataStorageTypedSelected(record.handle) &&
19014 m_dwgW->registerPointPathObjectClass(&path),
19015 nativePointPathHandles, record.handle);
19016 }
19017 for (const auto &record : metadata.objectPtrs()) {
19018 if (record.replayState !=
19019 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19020 record.handle == 0 ||
19021 hasReplayableRawObjectPtr(metadata, record.handle,
19022 m_dwgW->getVersion())) {
19023 continue;
19024 }
19025 DRW_ObjectPtr object = objectPtrFromMetadata(record);
19026 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
19027 m_writeFailed = true;
19028 continue;
19029 }
19030 remapClassInstanceHandle(object);
19031 noteObjectClassRegistration(
19032 isDwgDataStorageTypedSelected(record.handle) &&
19033 m_dwgW->registerObjectPtrObjectClass(&object),
19034 nativeObjectPtrHandles, record.handle);
19035 }
19036 for (const auto &record : metadata.partialViewingIndexes()) {
19037 if (record.replayState !=
19038 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19039 record.handle == 0 ||
19040 hasReplayableRawPartialViewingIndex(metadata, record.handle,
19041 m_dwgW->getVersion())) {
19042 continue;
19043 }
19044 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
19045 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
19046 m_writeFailed = true;
19047 continue;
19048 }
19049 remapClassInstanceHandle(index);
19050 noteObjectClassRegistration(
19051 isDwgDataStorageTypedSelected(record.handle) &&
19052 m_dwgW->registerPartialViewingIndexObjectClass(&index),
19053 nativePartialViewingIndexHandles, record.handle);
19054 }
19055 for (const auto &record : metadata.renderSettings()) {
19056 if (record.replayState !=
19057 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19058 record.handle == 0 ||
19059 hasReplayableRawRenderSettings(metadata, record.handle, record.kind,
19060 m_dwgW->getVersion())) {
19061 continue;
19062 }
19063 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
19064 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
19065 m_writeFailed = true;
19066 continue;
19067 }
19068 remapClassInstanceHandle(settings);
19069 noteObjectClassRegistration(
19070 isDwgDataStorageTypedSelected(record.handle) &&
19071 m_dwgW->registerRenderSettingsObjectClass(&settings),
19072 nativeRenderSettingsHandles, record.handle);
19073 }
19074 for (const auto &record : metadata.visualStyles()) {
19075 if (record.replayState !=
19076 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19077 record.handle == 0 ||
19078 hasReplayableRawVisualStyle(metadata, record.handle,
19079 m_dwgW->getVersion())) {
19080 continue;
19081 }
19082 DRW_VisualStyle style = visualStyleFromMetadata(record);
19083 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
19084 m_writeFailed = true;
19085 continue;
19086 }
19087 remapClassInstanceHandle(style);
19088 noteObjectClassRegistration(
19089 isDwgDataStorageTypedSelected(record.handle) &&
19090 m_dwgW->registerVisualStyleObjectClass(&style),
19091 nativeVisualStyleHandles, record.handle);
19092 }
19093 for (const auto &record : metadata.tvDeviceProperties()) {
19094 if (record.replayState !=
19095 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19096 record.handle == 0)
19097 continue;
19098 DRW_TvDeviceProperties properties =
19099 tvDevicePropertiesFromMetadata(record);
19100 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
19101 m_writeFailed = true;
19102 continue;
19103 }
19104 remapClassInstanceHandle(properties);
19105 noteObjectClassRegistration(
19106 isDwgDataStorageTypedSelected(record.handle) &&
19107 m_dwgW->registerTvDevicePropertiesObjectClass(&properties),
19108 nativeTvDevicePropertiesHandles, record.handle);
19109 }
19110 for (const auto &record : metadata.vxControls()) {
19111 if (record.replayState !=
19112 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19113 record.handle == 0 ||
19114 hasReplayableRawVxControl(metadata, record.handle,
19115 m_dwgW->getVersion()))
19116 continue;
19117 DRW_VxControl control = vxControlFromMetadata(record);
19118 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
19119 m_writeFailed = true;
19120 continue;
19121 }
19122 remapClassInstanceHandle(control);
19123 noteObjectClassRegistration(
19124 isDwgDataStorageTypedSelected(record.handle) &&
19125 m_dwgW->registerVxControlObjectClass(&control),
19126 nativeVxControlHandles, record.handle);
19127 }
19128 for (const auto &record : metadata.vxTableRecords()) {
19129 if (record.replayState !=
19130 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19131 record.handle == 0 ||
19132 hasReplayableRawVxTableRecord(metadata, record.handle,
19133 m_dwgW->getVersion()))
19134 continue;
19135 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
19136 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
19137 m_writeFailed = true;
19138 continue;
19139 }
19140 remapClassInstanceHandle(tableRecord);
19141 noteObjectClassRegistration(
19142 isDwgDataStorageTypedSelected(record.handle) &&
19143 m_dwgW->registerVxTableRecordObjectClass(&tableRecord),
19144 nativeVxTableRecordHandles, record.handle);
19145 }
19146 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) — must be
19147 // registered here, BEFORE writeDwgClasses() emits the CLASSES section.
19148 // PR 8d.1b + PR 13f.
19149 for (const auto &record : metadata.rasterVariables()) {
19150 if (record.replayState !=
19151 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19152 record.handle == 0) {
19153 continue;
19154 }
19155 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
19156 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
19157 m_writeFailed = true;
19158 continue;
19159 }
19160 remapClassInstanceHandle(rv);
19161 noteObjectClassRegistration(
19162 isDwgDataStorageTypedSelected(record.handle) &&
19163 m_dwgW->registerRasterVariablesObjectClass(&rv),
19164 nativeRasterVariablesHandles, record.handle);
19165 }
19166 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
19167 for (const auto &record : metadata.wipeoutVariables()) {
19168 if (record.replayState !=
19169 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19170 record.handle == 0) {
19171 continue;
19172 }
19173 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
19174 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
19175 m_writeFailed = true;
19176 continue;
19177 }
19178 remapClassInstanceHandle(wv);
19179 noteObjectClassRegistration(
19180 isDwgDataStorageTypedSelected(record.handle) &&
19181 m_dwgW->registerWipeoutVariablesObjectClass(&wv),
19182 nativeWipeoutVariablesHandles, record.handle);
19183 }
19184 // GEODATA (AcDbGeoData, custom class 506) — PR 8d.1c + PR 13f.
19185 for (const auto &record : metadata.geoData()) {
19186 if (record.replayState !=
19187 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19188 record.handle == 0) {
19189 continue;
19190 }
19191 DRW_GeoData gd = geoDataFromMetadata(record);
19192 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
19193 m_writeFailed = true;
19194 continue;
19195 }
19196 remapClassInstanceHandle(gd);
19197 noteObjectClassRegistration(
19198 isDwgDataStorageTypedSelected(record.handle) &&
19199 m_dwgW->registerGeoDataObjectClass(&gd),
19200 nativeGeoDataHandles, record.handle);
19201 }
19202 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) — PR 8d.1d +
19203 // PR 13f.
19204 for (const auto &record : metadata.spatialFilters()) {
19205 if (record.replayState !=
19206 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19207 record.handle == 0) {
19208 continue;
19209 }
19210 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
19211 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
19212 m_writeFailed = true;
19213 continue;
19214 }
19215 remapClassInstanceHandle(sf);
19216 noteObjectClassRegistration(
19217 isDwgDataStorageTypedSelected(record.handle) &&
19218 m_dwgW->registerSpatialFilterObjectClass(&sf),
19219 nativeSpatialFilterHandles, record.handle);
19220 }
19221 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
19222 // DICTIONARYVAR (custom classes 508-512). Encoders are version-
19223 // clean; SPATIAL_INDEX additionally gates its common-handle prefix
19224 // on `version > AC1018` (mirrors its parser). All 5 default to
19225 // enabled in `isDwgClassEnabled` (no per-class gate needed in
19226 // dwgwriter.h).
19227 for (const auto &record : metadata.scales()) {
19228 if (record.replayState !=
19229 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19230 record.handle == 0) {
19231 continue;
19232 }
19233 DRW_Scale s = scaleFromMetadata(record);
19234 if (hasDxfTableAppDataUnsupportedByDwg(s)) {
19235 m_writeFailed = true;
19236 continue;
19237 }
19238 remapClassInstanceHandle(s);
19239 noteObjectClassRegistration(
19240 isDwgDataStorageTypedSelected(record.handle) &&
19241 m_dwgW->registerScaleObjectClass(&s),
19242 nativeScaleHandles, record.handle);
19243 }
19244 for (const auto &record : metadata.idBuffers()) {
19245 if (record.replayState !=
19246 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19247 record.handle == 0) {
19248 continue;
19249 }
19250 DRW_IDBuffer b = idBufferFromMetadata(record);
19251 if (hasDxfTableAppDataUnsupportedByDwg(b)) {
19252 m_writeFailed = true;
19253 continue;
19254 }
19255 remapClassInstanceHandle(b);
19256 noteObjectClassRegistration(
19257 isDwgDataStorageTypedSelected(record.handle) &&
19258 m_dwgW->registerIDBufferObjectClass(&b),
19259 nativeIDBufferHandles, record.handle);
19260 }
19261 for (const auto &record : metadata.layerIndexes()) {
19262 if (record.replayState !=
19263 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19264 record.handle == 0) {
19265 continue;
19266 }
19267 DRW_LayerIndex li = layerIndexFromMetadata(record);
19268 if (hasDxfTableAppDataUnsupportedByDwg(li)) {
19269 m_writeFailed = true;
19270 continue;
19271 }
19272 remapClassInstanceHandle(li);
19273 noteObjectClassRegistration(
19274 isDwgDataStorageTypedSelected(record.handle) &&
19275 m_dwgW->registerLayerIndexObjectClass(&li),
19276 nativeLayerIndexHandles, record.handle);
19277 }
19278 for (const auto &record : metadata.spatialIndexes()) {
19279 if (record.replayState !=
19280 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19281 record.handle == 0 ||
19282 hasReplayableRawSpatialIndex(metadata, record.handle,
19283 m_dwgW->getVersion())) {
19284 continue;
19285 }
19286 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
19287 if (hasDxfTableAppDataUnsupportedByDwg(si)) {
19288 m_writeFailed = true;
19289 continue;
19290 }
19291 remapClassInstanceHandle(si);
19292 noteObjectClassRegistration(
19293 isDwgDataStorageTypedSelected(record.handle) &&
19294 m_dwgW->registerSpatialIndexObjectClass(&si),
19295 nativeSpatialIndexHandles, record.handle);
19296 }
19297 for (const auto &record : metadata.dictionaryVars()) {
19298 if (record.replayState !=
19299 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19300 record.handle == 0) {
19301 continue;
19302 }
19303 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
19304 if (hasDxfTableAppDataUnsupportedByDwg(dv)) {
19305 m_writeFailed = true;
19306 continue;
19307 }
19308 remapClassInstanceHandle(dv);
19309 noteObjectClassRegistration(
19310 isDwgDataStorageTypedSelected(record.handle) &&
19311 m_dwgW->registerDictionaryVarObjectClass(&dv),
19312 nativeDictionaryVarHandles, record.handle);
19313 }
19314 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
19315 // (custom classes 513-516). Encoders are version-clean (FIELD
19316 // has a parser-mirrored `version < AC1021` branch for the
19317 // legacy m_formatString TV). All 4 default to enabled in
19318 // `isDwgClassEnabled`.
19319 for (const auto &record : metadata.dictionariesWithDefault()) {
19320 if (record.replayState !=
19321 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19322 record.handle == 0) {
19323 continue;
19324 }
19325 if (!canWriteDwgDictionaryWithDefaultRecord(metadata, record,
19326 m_dwgW->getVersion())) {
19327 if (!metadata.hasRawObjectForHandle(record.handle))
19328 m_writeFailed = true;
19329 continue;
19330 }
19331 DRW_DictionaryWithDefault dwd = dictionaryWithDefaultFromMetadata(record);
19332 remapClassInstanceHandle(dwd);
19333 noteObjectClassRegistration(
19334 isDwgDataStorageTypedSelected(record.handle) &&
19335 m_dwgW->registerDictionaryWithDefaultObjectClass(&dwd),
19336 nativeDictionaryWithDefaultHandles, record.handle);
19337 }
19338 for (const auto &record : metadata.sortEntsTables()) {
19339 if (record.replayState !=
19340 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19341 record.handle == 0) {
19342 continue;
19343 }
19344 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
19345 if (hasDxfTableAppDataUnsupportedByDwg(se)) {
19346 m_writeFailed = true;
19347 continue;
19348 }
19349 if (hasReplayableRawSortEntsTable(metadata, record.handle,
19350 m_dwgW->getVersion()) &&
19351 hasUnresolvedSortEntsReference(se)) {
19352 // Preserve a valid source frame when the typed view contains a
19353 // dangling sort key that cannot be remapped safely.
19354 continue;
19355 }
19356 remapClassInstanceHandle(se);
19357 noteObjectClassRegistration(
19358 isDwgDataStorageTypedSelected(record.handle) &&
19359 m_dwgW->registerSortEntsTableObjectClass(&se),
19360 nativeSortEntsTableHandles, record.handle);
19361 }
19362 for (auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
19363 DRW_Field f = candidate.payload;
19364 remapClassInstanceHandle(f);
19365 const bool registered = m_dwgW->registerFieldObjectClass(&f);
19366 noteObjectClassRegistration(registered, nativeFieldHandles,
19367 candidate.sourceHandle);
19368 candidate.classAdmitted =
19369 registered && nativeFieldHandles.count(candidate.sourceHandle) != 0;
19370 }
19371 for (auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
19372 const bool fieldsAdmitted = std::all_of(
19373 candidate.fieldCandidateIndexes.cbegin(),
19374 candidate.fieldCandidateIndexes.cend(),
19375 [this](std::size_t fieldIndex) {
19376 return fieldIndex < m_dwgFieldWritePlan.fieldCandidates.size() &&
19377 m_dwgFieldWritePlan.fieldCandidates[fieldIndex]
19378 .classAdmitted;
19379 });
19380 if (!fieldsAdmitted) {
19381 m_writeFailed = true;
19382 continue;
19383 }
19384 DRW_FieldList fl = candidate.payload;
19385 remapClassInstanceHandle(fl);
19386 const bool registered = m_dwgW->registerFieldListObjectClass(&fl);
19387 noteObjectClassRegistration(registered, nativeFieldListHandles,
19388 candidate.sourceHandle);
19389 candidate.classAdmitted =
19390 registered && nativeFieldListHandles.count(candidate.sourceHandle) !=
19391 0;
19392 }
19393 for (const auto &record : metadata.underlayDefinitions()) {
19394 if (record.replayState !=
19395 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19396 record.handle == 0) {
19397 continue;
19398 }
19399 DRW_UnderlayDefinition definition =
19400 underlayDefinitionFromMetadata(record);
19401 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
19402 m_writeFailed = true;
19403 continue;
19404 }
19405 remapClassInstanceHandle(definition);
19406 noteObjectClassRegistration(
19407 isDwgDataStorageTypedSelected(record.handle) &&
19408 m_dwgW->registerUnderlayDefinitionObjectClass(&definition),
19409 nativeUnderlayDefinitionHandles, record.handle);
19410 }
19411 for (const auto &record : metadata.pointCloudDefinitions()) {
19412 if (record.replayState !=
19413 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19414 record.handle == 0)
19415 continue;
19416 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
19417 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
19418 m_writeFailed = true;
19419 continue;
19420 }
19421 remapClassInstanceHandle(definition);
19422 noteObjectClassRegistration(
19423 isDwgDataStorageTypedSelected(record.handle) &&
19424 m_dwgW->registerPointCloudDefObjectClass(&definition),
19425 nativePointCloudDefinitionHandles, record.handle);
19426 }
19427 for (const auto &record : metadata.navisworksModelDefinitions()) {
19428 if (record.replayState !=
19429 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19430 record.handle == 0)
19431 continue;
19432 DRW_NavisworksModelDef definition =
19433 navisworksModelDefinitionFromMetadata(record);
19434 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
19435 m_writeFailed = true;
19436 continue;
19437 }
19438 remapClassInstanceHandle(definition);
19439 noteObjectClassRegistration(
19440 isDwgDataStorageTypedSelected(record.handle) &&
19441 m_dwgW->registerNavisworksModelDefObjectClass(&definition),
19442 nativeNavisworksModelDefinitionHandles, record.handle);
19443 }
19444 for (const auto &record : metadata.pointCloudColorMaps()) {
19445 if (record.replayState !=
19446 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19447 record.handle == 0)
19448 continue;
19449 DRW_PointCloudColorMap colorMap = pointCloudColorMapFromMetadata(record);
19450 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
19451 m_writeFailed = true;
19452 continue;
19453 }
19454 remapClassInstanceHandle(colorMap);
19455 noteObjectClassRegistration(
19456 isDwgDataStorageTypedSelected(record.handle) &&
19457 m_dwgW->registerPointCloudColorMapObjectClass(&colorMap),
19458 nativePointCloudColorMapHandles, record.handle);
19459 }
19460 }
19461
19462 // TABLESTYLE has a complete positional writer through AC1021. Newer
19463 // versions retain the opaque carrier until their extended layout is
19464 // implemented.
19465 if (canRegisterCustomClassObjects && m_dwgW->getVersion() <= DRW::AC1021) {
19466 for (const auto &record : metadata.tables()) {
19467 if (record.replayState !=
19468 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
19469 record.handle == 0 || !record.nativeTableStyle ||
19470 !isDwgDataStorageTypedSelected(record.handle))
19471 continue;
19472 DRW_TableStyle style = *record.nativeTableStyle;
19473 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
19474 !style.validateDwgPayload(m_dwgW->getVersion())) {
19475 m_writeFailed = true;
19476 continue;
19477 }
19478 remapClassInstanceHandle(style);
19479 noteObjectClassRegistration(
19480 isDwgDataStorageTypedSelected(record.handle) &&
19481 m_dwgW->registerTableStyleObjectClass(&style),
19482 nativeTableStyleHandles, record.handle);
19483 }
19484 }
19485
19486 // Publish the same native-object set used by the later OBJECTS
19487 // dispatch before views and entities emit references to it. The
19488 // writeObjects() copy remains a defensive refresh for records selected
19489 // by that callback's version gates.
19490 auto markNativeHandles = [this](const auto &handles) {
19491 m_dwgWriteKnownHandles.insert(handles.begin(), handles.end());
19492 };
19493 markNativeHandles(nativeSunHandles);
19494 markNativeHandles(nativeSunStudyHandles);
19495 markNativeHandles(nativeMotionPathHandles);
19496 markNativeHandles(nativeCurvePathHandles);
19497 markNativeHandles(nativePointPathHandles);
19498 markNativeHandles(nativeObjectPtrHandles);
19499 markNativeHandles(nativePartialViewingIndexHandles);
19500 markNativeHandles(nativeRenderSettingsHandles);
19501 markNativeHandles(nativeVisualStyleHandles);
19502 markNativeHandles(nativeTvDevicePropertiesHandles);
19503 markNativeHandles(nativeVxControlHandles);
19504 markNativeHandles(nativeVxTableRecordHandles);
19505 markNativeHandles(nativeMLeaderStyleHandles);
19506 markNativeHandles(nativeTableStyleHandles);
19507 markNativeHandles(nativeMaterialHandles);
19508 markNativeHandles(nativeLightListHandles);
19509 markNativeHandles(nativeBackgroundHandles);
19510 markNativeHandles(nativeRasterVariablesHandles);
19511 markNativeHandles(nativeWipeoutVariablesHandles);
19512 markNativeHandles(nativeGeoDataHandles);
19513 markNativeHandles(nativeSpatialFilterHandles);
19514 markNativeHandles(nativeScaleHandles);
19515 markNativeHandles(nativeIDBufferHandles);
19516 markNativeHandles(nativeLayerIndexHandles);
19517 markNativeHandles(nativeSpatialIndexHandles);
19518 markNativeHandles(nativeDictionaryVarHandles);
19519 markNativeHandles(nativeDictionaryWithDefaultHandles);
19520 markNativeHandles(nativeSortEntsTableHandles);
19521 markNativeHandles(nativeFieldListHandles);
19522 markNativeHandles(nativeFieldHandles);
19523 markNativeHandles(nativeUnderlayDefinitionHandles);
19524 markNativeHandles(nativePointCloudDefinitionHandles);
19525 markNativeHandles(nativeNavisworksModelDefinitionHandles);
19526 markNativeHandles(nativePointCloudColorMapHandles);
19527 markNativeHandles(nativeDbColorHandles);
19528 markNativeHandles(nativeDimensionAssociationHandles);
19529 markNativeHandles(nativeEvaluationGraphHandles);
19530 markNativeHandles(nativeBlockRepresentationHandles);
19531 markNativeHandles(nativeSectionHandles);
19532
19533 // CLASSES is serialized immediately after this callback. If a raw
19534 // DataStorage decision has nevertheless admitted a typed class, there is
19535 // no safe way to retract that class later; fail the staged export rather
19536 // than letting CLASSES and OBJECTS choose different carriers.
19537 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
19538 if (decision.decision == DwgDataStorageDecision::EmitRaw &&
19539 hasDwgDataStorageClassAdmission(decision.ownerHandle,
19540 DwgDataStorageCarrier::Kind::Typed)) {
19541 m_writeFailed = true;
19542 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19543 "RS_FilterDXFRW: raw DataStorage decision 0x%X has a "
19544 "typed CLASSES admission",
19545 decision.ownerHandle);
19546 }
19547 }
19548
19549 for (std::size_t rawIndex = 0; rawIndex < metadata.rawObjects().size();
19550 ++rawIndex) {
19551 const auto &record = metadata.rawObjects()[rawIndex];
19552 // Never register a CLASSES entry for raw bytes that cannot be replayed
19553 // into this exact target version. This mirrors the emit-loop guard so
19554 // a rejected carrier cannot leave an orphan CLASSES entry behind.
19555 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
19556 continue;
19557 }
19558 if (isPlotSettingsRawObject(record) &&
19559 !isDwgRawPlotSettingsSelected(record.handle)) {
19560 continue;
19561 }
19562 if (requiresDwgOpaqueTableAdmission(record) &&
19563 !isDwgOpaqueTableRawSelected(rawIndex)) {
19564 continue;
19565 }
19566 if (record.replayState !=
19567 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
19568 continue;
19569 }
19570 if (record.hasDataStorage && m_dwgW->getVersion() > DRW::AC1024 &&
19571 !isDwgDataStorageRawSelected(record.handle)) {
19572 // A typed DataStorage decision owns this source handle. Do not
19573 // admit a second raw class before CLASSES is serialized.
19574 continue;
19575 }
19576 if (isSectionObjectRawEntity(record) &&
19577 m_dwgW->getVersion() >= DRW::AC1021 &&
19578 hasReplayableSectionObject(metadata, record.handle)) {
19579 continue;
19580 }
19581 // LIGHT, CAMERA, NAVISWORKSMODEL, and SECTIONOBJECT retain a raw
19582 // source frame for fallback, but their typed entity routes already
19583 // own the class instance and final frame. Registering both would
19584 // compete for one handle under different source class ordinals.
19585 if (isNativeMetadataEntityRawObject(metadata, record))
19586 continue;
19587 if (isRawViewportEntityHeaderControl(record)) {
19588 if (record.rawBytes.empty() ||
19589 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
19590 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
19591 continue;
19592 }
19593 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
19594 if (!registerRawClass(object, record))
19595 m_writeFailed = true;
19596 continue;
19597 }
19598 if (!record.isCustomClass) {
19599 continue;
19600 }
19601 if (isFixedStructuralDwgHandle(record.handle) ||
19602 LC_DwgAdvancedMetadata::rawReplayBlocker(record) !=
19603 LC_DwgAdvancedMetadata::ReplayBlocker::None) {
19604 continue;
19605 }
19606 const DRW_UnsupportedObject ownerProbe = rawObjectFromMetadata(record);
19607 if (!rawCustomEntityOwnerFieldsAreReplayable(ownerProbe,
19608 m_dwgW->getVersion())) {
19609 continue;
19610 }
19611 const std::uint32_t sourceBlockOwner =
19612 record.blockOwnerHandle != DRW::NoHandle ? record.blockOwnerHandle
19613 : record.parentHandle;
19614 if (record.isEntity && sourceBlockOwner != DRW::NoHandle) {
19615 const auto blockIt = m_dwgWriteBlockHandleRemap.find(sourceBlockOwner);
19616 if (blockIt != m_dwgWriteBlockHandleRemap.end() &&
19617 blockIt->second != sourceBlockOwner &&
19618 (record.version <= DRW::AC1018 ||
19619 record.parentHandle == DRW::NoHandle)) {
19620 // R2000/R2004 opaque bodies have no validated owner boundary.
19621 // A block-owner-only carrier has no body owner to probe in any
19622 // version. AC1021+ entities with a common parent are patched
19623 // by dwgWriter15 after that owner is validated.
19624 continue;
19625 }
19626 }
19627 if (record.hasDataStorage &&
19628 !allowsDwgDataStoragePresenceBit(record.handle)) {
19629 continue;
19630 }
19631 if (nativeSunHandles.count(record.handle) != 0 && isSunRawObject(record))
19632 continue;
19633 if (nativeMLeaderStyleHandles.count(record.handle) != 0 &&
19634 isMLeaderStyleRawObject(record))
19635 continue;
19636 if (nativeTableStyleHandles.count(record.handle) != 0 &&
19637 isTableStyleRawObject(record))
19638 continue;
19639 if (nativeMaterialHandles.count(record.handle) != 0 &&
19640 isMaterialRawObject(record))
19641 continue;
19642 if (nativeLightListHandles.count(record.handle) != 0 &&
19643 isLightListRawObject(record))
19644 continue;
19645 if (nativeBackgroundHandles.count(record.handle) != 0 &&
19646 isBackgroundRawObject(record))
19647 continue;
19648 if (nativeSunStudyHandles.count(record.handle) != 0 &&
19649 isSunStudyRawObject(record))
19650 continue;
19651 if (nativeMotionPathHandles.count(record.handle) != 0 &&
19652 isMotionPathRawObject(record))
19653 continue;
19654 if (nativeCurvePathHandles.count(record.handle) != 0 &&
19655 isCurvePathRawObject(record))
19656 continue;
19657 if (nativePointPathHandles.count(record.handle) != 0 &&
19658 isPointPathRawObject(record))
19659 continue;
19660 if (nativeObjectPtrHandles.count(record.handle) != 0 &&
19661 isObjectPtrRawObject(record))
19662 continue;
19663 if (nativePartialViewingIndexHandles.count(record.handle) != 0 &&
19664 isPartialViewingIndexRawObject(record))
19665 continue;
19666 if (nativeRenderSettingsHandles.count(record.handle) != 0 &&
19667 isRenderSettingsRawObject(record))
19668 continue;
19669 if (nativeVisualStyleHandles.count(record.handle) != 0 &&
19670 isVisualStyleRawObject(record))
19671 continue;
19672 if (nativeTvDevicePropertiesHandles.count(record.handle) != 0 &&
19673 isTvDevicePropertiesRawObject(record))
19674 continue;
19675 if (nativeVxControlHandles.count(record.handle) != 0 &&
19676 isVxControlRawObject(record))
19677 continue;
19678 if (nativeVxTableRecordHandles.count(record.handle) != 0 &&
19679 isVxTableRecordRawObject(record))
19680 continue;
19681 if (nativeRasterVariablesHandles.count(record.handle) != 0 &&
19682 isRasterVariablesRawObject(record))
19683 continue;
19684 if (nativeWipeoutVariablesHandles.count(record.handle) != 0 &&
19685 isWipeoutVariablesRawObject(record))
19686 continue;
19687 if (nativeGeoDataHandles.count(record.handle) != 0 &&
19688 isGeoDataRawObject(record))
19689 continue;
19690 if (nativeSpatialFilterHandles.count(record.handle) != 0 &&
19691 isSpatialFilterRawObject(record))
19692 continue;
19693 // PR 8d.2a — five small no-storage OBJECTS families.
19694 if (nativeScaleHandles.count(record.handle) != 0 &&
19695 isScaleRawObject(record))
19696 continue;
19697 if (nativeIDBufferHandles.count(record.handle) != 0 &&
19698 isIDBufferRawObject(record))
19699 continue;
19700 if (nativeLayerIndexHandles.count(record.handle) != 0 &&
19701 isLayerIndexRawObject(record))
19702 continue;
19703 if (nativeSpatialIndexHandles.count(record.handle) != 0 &&
19704 isSpatialIndexRawObject(record))
19705 continue;
19706 if (nativeDictionaryVarHandles.count(record.handle) != 0 &&
19707 isDictionaryVarRawObject(record))
19708 continue;
19709 // PR 8d.2b — four larger no-storage OBJECTS families.
19710 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0 &&
19711 isDictionaryWithDefaultRawObject(record))
19712 continue;
19713 if (nativeSortEntsTableHandles.count(record.handle) != 0 &&
19714 isSortEntsTableRawObject(record))
19715 continue;
19716 if (nativeFieldListHandles.count(record.handle) != 0 &&
19717 isFieldListRawObject(record))
19718 continue;
19719 if (nativeFieldHandles.count(record.handle) != 0 &&
19720 isFieldRawObject(record))
19721 continue;
19722 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0 &&
19723 isUnderlayDefinitionRawObject(record))
19724 continue;
19725 if (nativePointCloudDefinitionHandles.count(record.handle) != 0 &&
19726 isPointCloudDefinitionRawObject(record))
19727 continue;
19728 if (nativeNavisworksModelDefinitionHandles.count(record.handle) != 0 &&
19729 isNavisworksModelDefinitionRawObject(record))
19730 continue;
19731 if (nativePointCloudColorMapHandles.count(record.handle) != 0 &&
19732 isPointCloudColorMapRawObject(record))
19733 continue;
19734 if (nativeDimensionAssociationHandles.count(record.handle) != 0 &&
19735 isDimensionAssociationRawObject(record))
19736 continue;
19737 if (nativeEvaluationGraphHandles.count(record.handle) != 0 &&
19738 isEvaluationGraphRawObject(record))
19739 continue;
19740 if (nativeBlockRepresentationHandles.count(record.handle) != 0 &&
19741 isBlockRepresentationDataRawObject(record))
19742 continue;
19743 if (nativeSectionHandles.count(record.handle) != 0 &&
19744 isSectionRawObject(record))
19745 continue;
19746 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
19747 if (!registerRawClass(object, record))
19748 m_writeFailed = true;
19749 }
19750 m_dwgFieldWritePlan.classesAdmitted = !m_writeFailed;
19751 if (!validateDwgDataStorageAdmissionSummary(
19752 /*requireClassAdmission=*/true)) {
19753 m_writeFailed = true;
19754 RS_DEBUGRS_Debug::instance()->print(
19755 RS_Debug::D_WARNING,
19756 "RS_FilterDXFRW: DataStorage admission diverged before CLASSES");
19757 }
19758 if (m_dwgPostClassesTestHook)
19759 m_dwgPostClassesTestHook();
19760 // CLASSES is the next low-level section. A preflight/class admission
19761 // failure must stop the writer before it serializes any file section.
19762 if (m_writeFailed)
19763 m_dwgW->markDwgWriteFailure();
19764#endif
19765}
19766
19767namespace {
19768// DXF cannot hold these in a name.
19769bool isWritableLineTypeName(const QString &name) {
19770 return !name.contains(QLatin1Char('\r')) &&
19771 !name.contains(QLatin1Char('\n')) && !name.contains(QChar(u'\0'));
19772}
19773
19774// A pen whose name means its own built-in, or cannot be written, uses the
19775// built-in's name.
19776QString lineTypeNameToWrite(const RS_Pen &pen) {
19777 if (pen.getLineTypeFoldId() != 0) {
19778 QString name = pen.getLineTypeName();
19779 if (isWritableLineTypeName(name))
19780 return name;
19781 }
19782 return LC_LineTypeNames::lineTypeToName(pen.getLineType());
19783}
19784
19785// The record a list entry is written as: groups 73 and 40 follow the dashes.
19786DRW_LType lineTypeToDrw(const std::string &name, const std::string &description,
19787 const std::vector<double> &pattern) {
19788 double length = 0.0;
19789 for (const double dash : pattern)
19790 length += std::fabs(dash);
19791 DRW_LType record;
19792 record.updateValues(name, description, static_cast<int>(pattern.size()),
19793 length, pattern);
19794 return record;
19795}
19796
19797} // namespace
19798
19799void RS_FilterDXFRW::writeLType(const UTF8STRINGstd::string &lTypeName,
19800 const UTF8STRINGstd::string &ltDescription, int ltSize,
19801 double ltLength,
19802 const std::vector<double> &ltPath) {
19803 m_builtinLTypePaths[normalizeDwgTableName(lTypeName)] = ltPath;
19804 DRW_LType ltype;
19805 ltype.updateValues(lTypeName, ltDescription, ltSize, ltLength, ltPath);
19806 if (const auto *source =
19807 m_graphic->dwgAdvancedMetadata().findLineTypeTableEntryByName(
19808 lTypeName)) {
19809 if (source->path.empty() && source->segments.empty() && !ltPath.empty()) {
19810 // A record that only names a built-in keeps its identity, not an empty
19811 // pattern: copy the table entry part and leave the dashes to the literal.
19812 static_cast<DRW_TableEntry &>(ltype) = *source;
19813 if (!source->desc.empty())
19814 ltype.desc = source->desc;
19815 } else {
19816 ltype = *source;
19817 }
19818 }
19819 m_builtinLTypeNames.insert(normalizeDwgTableName(lTypeName));
19820 (void)writeLTypeRecord(ltype);
19821}
19822
19823bool RS_FilterDXFRW::writeLTypeRecord(DRW_LType &ltype) {
19824 if (m_dwgW) {
19825 if (hasDxfTableAppDataUnsupportedByDwg(ltype)) {
19826 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19827 "RS_FilterDXFRW: cannot convert DXF LTYPE "
19828 "application data to DWG");
19829 m_writeFailed = true;
19830 return false;
19831 }
19832 const std::uint32_t sourceHandle = ltype.handle;
19833 if (m_dwgW->addLType(&ltype) && ltype.handle != 0 &&
19834 stageDwgTableRecordIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
19835 DwgWriteSourceKind::TableRecord,
19836 sourceHandle, ltype.handle)) {
19837 m_dwgWriteLTypeHandleByName[normalizeDwgTableName(ltype.name)] =
19838 ltype.handle;
19839 return true;
19840 }
19841 m_writeFailed = true;
19842 return false;
19843 }
19844 return noteDxfWrite(m_dxfW->writeLineType(&ltype));
19845}
19846
19847void RS_FilterDXFRW::writeLTypes() {
19848 m_builtinLTypeNames.clear();
19849 m_builtinLTypePaths.clear();
19850 for (const auto &row : LC_LineTypeNames::builtinMetrics()) {
19851 writeLType(LC_LineTypeNames::lineTypeToName(row.type).toStdString(),
19852 row.description, static_cast<int>(row.pattern.size()),
19853 row.length, row.pattern);
19854 }
19855 // Imported LTYPE records that are not part of the built-in table above are
19856 // re-emitted as they came in; writeLType() records the built-in names so a
19857 // new built-in type cannot end up written twice.
19858 std::set<std::string> emittedNames = m_builtinLTypeNames;
19859 const auto &records =
19860 m_graphic->dwgAdvancedMetadata().lineTypeTableEntries();
19861 // What the list calls each record, as addLType() read it: bytes that are
19862 // not UTF-8 make it another spelling than the record's.
19863 std::set<std::string> listedNames;
19864 std::set<std::string> dashedNames;
19865 for (const auto &entry : records) {
19866 const std::string listed =
19867 QString::fromUtf8(entry.first.c_str()).toStdString();
19868 listedNames.insert(normalizeDwgTableName(listed));
19869 if (!entry.second.path.empty())
19870 dashedNames.insert(listed);
19871 }
19872 for (const auto &entry : records) {
19873 if (!emittedNames.insert(normalizeDwgTableName(entry.first)).second)
19874 continue;
19875 DRW_LType ltype = entry.second;
19876 if (ltype.path.empty() && ltype.segments.empty()) {
19877 // A record that only names a line type takes the dashes an insert or
19878 // a paste gave its entry: the entry is spelt as the record and no
19879 // record gave it those dashes. It is rebuilt around its own table
19880 // entry part, because a parsed group 73 pins the size.
19881 const LC_LineType *lineType =
19882 m_graphic->findLineType(QString::fromUtf8(entry.first.c_str()));
19883 if (lineType != nullptr && !lineType->pattern.empty() &&
19884 normalizeDwgTableName(lineType->name.toStdString()) ==
19885 normalizeDwgTableName(entry.first) &&
19886 dashedNames.count(lineType->name.toStdString()) == 0) {
19887 DRW_LType dashed =
19888 lineTypeToDrw(ltype.name, ltype.desc, lineType->pattern);
19889 static_cast<DRW_TableEntry &>(dashed) = ltype;
19890 dashed.alignment = ltype.alignment;
19891 dashed.xrefBlockHandle = ltype.xrefBlockHandle;
19892 ltype = dashed;
19893 }
19894 }
19895 (void)writeLTypeRecord(ltype);
19896 }
19897 // An entry no record backs, brought in by an insert or a paste, gets a
19898 // record of its own, spelt and described as the entry is.
19899 for (unsigned i = 0; i < m_graphic->countLineTypes(); ++i) {
19900 const LC_LineType *lineType = m_graphic->lineTypeAt(i);
19901 if (!isWritableLineTypeName(lineType->name))
19902 continue;
19903 const std::string key =
19904 normalizeDwgTableName(lineType->name.toStdString());
19905 if (listedNames.count(key) != 0 || !emittedNames.insert(key).second)
19906 continue;
19907 DRW_LType record = lineTypeToDrw(lineType->name.toStdString(),
19908 lineType->description.toStdString(),
19909 lineType->pattern);
19910 (void)writeLTypeRecord(record);
19911 }
19912 // A name that only a pen, a layer or a style gives still needs a record.
19913 // It gets the dashes of the list entry its name folds to, else those of
19914 // the built-in it is drawn as, which are none for a name LibreCAD does not
19915 // know. These records only add to the ones above.
19916 // Blanks around a string do not make another name: pens have none, so
19917 // names without them go first, and a padded one adds a record only for a
19918 // name that has none yet.
19919 std::vector<QString> names = m_graphic->referencedLineTypeNames();
19920 std::stable_partition(names.begin(), names.end(), [](const QString &name) {
19921 return name.trimmed() == name;
19922 });
19923 for (const QString &name : names) {
19924 if (!isWritableLineTypeName(name))
19925 continue;
19926 const std::string utf8 = name.toStdString();
19927 const std::string trimmed =
19928 normalizeDwgTableName(name.trimmed().toStdString());
19929 if (emittedNames.count(trimmed) != 0 ||
19930 !emittedNames.insert(normalizeDwgTableName(utf8)).second)
19931 continue;
19932 emittedNames.insert(trimmed);
19933 const LC_LineType *entry = m_graphic->findLineType(name);
19934 std::vector<double> path;
19935 if (entry != nullptr && !entry->pattern.empty()) {
19936 path = entry->pattern;
19937 } else {
19938 const std::string family = normalizeDwgTableName(
19939 LC_LineTypeNames::lineTypeToName(
19940 LC_LineTypeNames::nameToLineType(name))
19941 .toStdString());
19942 const auto literal = m_builtinLTypePaths.find(family);
19943 if (literal != m_builtinLTypePaths.end())
19944 path = literal->second;
19945 }
19946 DRW_LType marker = lineTypeToDrw(utf8, "", path);
19947 (void)writeLTypeRecord(marker);
19948 }
19949}
19950
19951void RS_FilterDXFRW::writeLayers() {
19952 DRW_Layer lay;
19953 RS_LayerList *ll = m_graphic->getLayerList();
19954 int exact_rgb;
19955 for (unsigned int i = 0; i < ll->count(); i++) {
19956 lay.reset();
19957 RS_Layer *l = ll->at(i);
19958 RS_Pen pen = l->getPen();
19959 lay.name = l->getName().toUtf8().data();
19960 lay.color = LC_ColorNumbers::colorToNumber(pen.getColor(), &exact_rgb);
19961 lay.color24 = exact_rgb;
19962 lay.lWeight = widthToNumber(pen.getWidth());
19963 lay.lineType = lineTypeNameToWrite(pen).toStdString();
19964 lay.flags = 0x00;
19965 if (l->isFrozen()) {
19966 // hidden: frozen, turned off (a negative colour) or both, as the layer
19967 // was read; AutoCAD does not freeze the current layer, so that one is
19968 // turned off
19969 const RS_Layer::HiddenAs hiddenAs = l == m_graphic->getActiveLayer()
19970 ? RS_Layer::HiddenAs::Off
19971 : l->getHiddenAs();
19972 if (hiddenAs != RS_Layer::HiddenAs::Off) {
19973 lay.flags |= 0x01;
19974 }
19975 if (hiddenAs != RS_Layer::HiddenAs::Frozen) {
19976 lay.color = (lay.color > 0 && lay.color < 256) ? -lay.color : -7;
19977 }
19978 }
19979 if (l->isLocked()) {
19980 lay.flags |= 0x04;
19981 }
19982 lay.plotF = l->isPrint();
19983 if (const auto *sourceLayer =
19984 m_graphic->dwgAdvancedMetadata().findLayerTableEntryByName(
19985 lay.name)) {
19986 if (m_dwgW != nullptr &&
19987 hasDxfTableAppDataUnsupportedByDwg(*sourceLayer)) {
19988 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19989 "RS_FilterDXFRW: cannot convert DXF LAYER "
19990 "application data to DWG");
19991 m_writeFailed = true;
19992 continue;
19993 }
19994 if (!restoreTableEntryExtData(lay, sourceLayer->extData)) {
19995 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
19996 "RS_FilterDXFRW: cannot restore DXF LAYER "
19997 "XDATA");
19998 m_writeFailed = true;
19999 continue;
20000 }
20001 lay.appData = sourceLayer->appData;
20002 lay.reactorHandles = sourceLayer->reactorHandles;
20003 lay.xDictHandle = sourceLayer->xDictHandle;
20004 lay.handle = sourceLayer->handle;
20005 }
20006 if (l->isConstruction() && classifyLayerConstructionMarker(lay.extData) ==
20007 LayerConstructionMarkerStatus::Absent) {
20008 try {
20009 if (!lay.addExtData(std::make_unique<DRW_Variant>(1001, "LibreCad")) ||
20010 !lay.addExtData(std::make_unique<DRW_Variant>(1070, 1))) {
20011 m_writeFailed = true;
20012 }
20013 } catch (...) {
20014 m_writeFailed = true;
20015 }
20016 // RS_DEBUG->print(RS_Debug::D_WARNING, "RS_FilterDXF::writeLayers: layer
20017 // %s saved as construction layer", lay.name.c_str());
20018 }
20019 if (m_dwgW) {
20020 const std::uint32_t sourceHandle = lay.handle;
20021 if (!m_dwgW->addLayer(&lay) ||
20022 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
20023 sourceHandle, lay.handle))
20024 m_writeFailed = true;
20025 else
20026 m_dwgWriteLayerHandleByName[normalizeDwgTableName(lay.name)] =
20027 lay.handle;
20028 } else {
20029 // The plot style (390) is named only if its placeholder is written.
20030 lay.handlePlotS.clear();
20031 if (const auto *sourceLayer =
20032 m_graphic->dwgAdvancedMetadata().findLayerTableEntryByName(
20033 lay.name)) {
20034 std::uint32_t plotStyle = sourceLayer->plotStyleHandle.ref;
20035 if (plotStyle == 0 && !sourceLayer->handlePlotS.empty())
20036 plotStyle = static_cast<std::uint32_t>(
20037 std::strtoul(sourceLayer->handlePlotS.c_str(), nullptr, 16));
20038 if (plotStyle != 0 && m_dxfEmittedObjectHandles.count(plotStyle) != 0)
20039 lay.handlePlotS =
20040 m_dxfW->toHexStrHandle(m_dxfW->remapHandle(plotStyle));
20041 }
20042 noteDxfWrite(m_dxfW->writeLayer(&lay));
20043 }
20044 }
20045}
20046
20047void RS_FilterDXFRW::writeUCSs() {
20048 LC_UCSList *vl = m_graphic->getUCSList();
20049 DRW_UCS ucs;
20050 for (unsigned int i = 1; i < vl->count(); i++) {
20051 ucs.reset();
20052
20053 const LC_UCS *u = vl->at(i);
20054 if (u->isTemporary()) {
20055 // temporary ucs without name are not persistent
20056 continue;
20057 }
20058 ucs.name = u->getName().toUtf8().data();
20059 ucs.origin.x = u->getOrigin().x;
20060 ucs.origin.y = u->getOrigin().y;
20061 ucs.origin.z = u->getOrigin().z;
20062
20063 ucs.xAxisDirection.x = u->getXAxis().x;
20064 ucs.xAxisDirection.y = u->getXAxis().y;
20065 ucs.xAxisDirection.z = u->getXAxis().z;
20066
20067 ucs.yAxisDirection.x = u->getYAxis().x;
20068 ucs.yAxisDirection.y = u->getYAxis().y;
20069 ucs.yAxisDirection.z = u->getYAxis().z;
20070
20071 ucs.orthoOrigin.x = u->getOrthoOrigin().x;
20072 ucs.orthoOrigin.y = u->getOrthoOrigin().y;
20073 ucs.orthoOrigin.z = u->getOrthoOrigin().z;
20074
20075 ucs.orthoType = u->getOrthoType();
20076 ucs.elevation = u->getElevation();
20077
20078 const auto *ucsMetadata =
20079 m_graphic->dwgAdvancedMetadata().findUcsByName(ucs.name);
20080 if (ucsMetadata != nullptr) {
20081 if (m_dwgW != nullptr &&
20082 (hasCustomDxfApplicationGroups(ucsMetadata->appData) ||
20083 !ucsMetadata->extData.empty())) {
20084 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20085 "RS_FilterDXFRW: cannot convert DXF UCS "
20086 "application data or XDATA to DWG");
20087 m_writeFailed = true;
20088 continue;
20089 }
20090 if (!restoreTableEntryExtData(ucs, ucsMetadata->extData)) {
20091 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20092 "RS_FilterDXFRW: cannot restore DXF UCS "
20093 "XDATA");
20094 m_writeFailed = true;
20095 continue;
20096 }
20097 ucs.appData = ucsMetadata->appData;
20098 ucs.reactorHandles = ucsMetadata->reactorHandles;
20099 ucs.xDictHandle = ucsMetadata->xDictHandle;
20100 }
20101
20102 if (m_dwgW) {
20103 if (ucsMetadata != nullptr)
20104 ucs.handle = ucsMetadata->handle;
20105 const std::uint32_t sourceHandle = ucs.handle;
20106 if (!m_dwgW->addUCS(&ucs)) {
20107 m_writeFailed = true;
20108 continue;
20109 }
20110 if (sourceHandle != 0 && ucs.handle != 0) {
20111 const auto [it, inserted] =
20112 m_dwgWriteUcsHandleRemap.emplace(sourceHandle, ucs.handle);
20113 if (!inserted && it->second != ucs.handle) {
20114 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20115 "RS_FilterDXFRW: duplicate DWG UCS handle 0x%X",
20116 sourceHandle);
20117 m_writeFailed = true;
20118 }
20119 }
20120 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
20121 sourceHandle, ucs.handle))
20122 m_writeFailed = true;
20123 } else {
20124 noteDxfWrite(m_dxfW->writeUCS(&ucs));
20125 }
20126 }
20127}
20128
20129void RS_FilterDXFRW::writeViews() {
20130 LC_ViewList *vl = m_graphic->getViewList();
20131 DRW_View vie;
20132#ifdef DWGSUPPORT1
20133 struct PendingOptionalDrop {
20134 std::uint32_t targetSourceHandle = 0;
20135 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
20136 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
20137 const char *referenceName = nullptr;
20138 std::uint32_t referenceOrdinal = 0;
20139 const char *reason = nullptr;
20140 };
20141 const auto isDropped = [](DwgWriteReferenceStatus status) {
20142 return status == DwgWriteReferenceStatus::Missing ||
20143 status == DwgWriteReferenceStatus::Ambiguous ||
20144 status == DwgWriteReferenceStatus::Suppressed;
20145 };
20146 auto remapDwgObjectReference = [this, &isDropped](
20147 std::uint32_t sourceHandle,
20148 const char *referenceKind,
20149 std::vector<PendingOptionalDrop> &drops) {
20150 std::uint32_t outputHandle = 0;
20151 const DwgWriteReferenceStatus status =
20152 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
20153 if (isDropped(status)) {
20154 RS_DEBUGRS_Debug::instance()->print(
20155 RS_Debug::D_WARNING,
20156 status == DwgWriteReferenceStatus::Ambiguous
20157 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
20158 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
20159 referenceKind, sourceHandle);
20160 drops.push_back(
20161 {sourceHandle, status, DwgWriteSourceKind::Object, referenceKind, 0,
20162 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
20163 : "missing target"});
20164 return std::uint32_t{0};
20165 }
20166 return outputHandle;
20167 };
20168 auto remapDwgUcsReference =
20169 [this, &isDropped](std::uint32_t sourceHandle, const char *referenceKind,
20170 std::vector<PendingOptionalDrop> &drops) {
20171 std::uint32_t outputHandle = 0;
20172 const DwgWriteReferenceStatus status = resolveDwgWriteTableRecordHandle(
20173 sourceHandle, m_dwgWriteUcsHandleRemap, &outputHandle);
20174 if (status == DwgWriteReferenceStatus::Reserved) {
20175 return outputHandle;
20176 }
20177 if (isDropped(status)) {
20178 RS_DEBUGRS_Debug::instance()->print(
20179 RS_Debug::D_WARNING,
20180 status == DwgWriteReferenceStatus::Ambiguous
20181 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
20182 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
20183 referenceKind, sourceHandle);
20184 drops.push_back({sourceHandle, status,
20185 DwgWriteSourceKind::TableRecord, referenceKind, 0,
20186 status == DwgWriteReferenceStatus::Ambiguous
20187 ? "ambiguous target"
20188 : "missing target"});
20189 return std::uint32_t{0};
20190 }
20191 return outputHandle;
20192 };
20193#endif
20194 for (unsigned int i = 0; i < vl->count(); i++) {
20195 vie.reset();
20196#ifdef DWGSUPPORT1
20197 std::vector<PendingOptionalDrop> optionalDrops;
20198#endif
20199 LC_View *view = vl->at(i);
20200 vie.name = view->getName().toUtf8().data();
20201 vie.center.x = view->getCenter().x;
20202 vie.center.y = view->getCenter().y;
20203 vie.center.z = view->getCenter().z;
20204
20205 vie.targetPoint.x = view->getTargetPoint().x;
20206 vie.targetPoint.y = view->getTargetPoint().y;
20207 vie.targetPoint.z = view->getTargetPoint().z;
20208
20209 vie.size.x = view->getSize().x;
20210 vie.size.y = view->getSize().y;
20211 vie.size.z = view->getSize().z;
20212
20213 vie.frontClippingPlaneOffset = view->getFrontClippingPlaneOffset();
20214 vie.backClippingPlaneOffset = view->getBackClippingPlaneOffset();
20215 vie.lensLen = view->getLensLen();
20216 vie.flags = view->getFlags();
20217 vie.viewMode = view->getViewMode();
20218
20219 vie.viewDirectionFromTarget.x = view->getViewDirection().x;
20220 vie.viewDirectionFromTarget.y = view->getViewDirection().y;
20221 vie.viewDirectionFromTarget.z = view->getViewDirection().z;
20222
20223 vie.cameraPlottable = view->isCameraPlottable();
20224 vie.renderMode = view->getRenderMode();
20225
20226 vie.twistAngle = view->getTwistAngle();
20227
20228 if (view->isHasUCS()) {
20229 vie.hasUCS = true;
20230 LC_UCS *ucs = view->getUCS();
20231 vie.ucsOrigin.x = ucs->getOrigin().x;
20232 vie.ucsOrigin.y = ucs->getOrigin().y;
20233 vie.ucsOrigin.z = ucs->getOrigin().z;
20234
20235 vie.ucsOrthoType = ucs->getOrthoType();
20236 vie.ucsElevation = ucs->getElevation();
20237
20238 vie.ucsXAxis.x = ucs->getXAxis().x;
20239 vie.ucsXAxis.y = ucs->getXAxis().y;
20240 vie.ucsXAxis.z = ucs->getXAxis().z;
20241
20242 vie.ucsYAxis.x = ucs->getYAxis().x;
20243 vie.ucsYAxis.y = ucs->getYAxis().y;
20244 vie.ucsYAxis.z = ucs->getYAxis().z;
20245
20246 // fixme - complete - base UCS_ID and Named UCS_ID support. That's might
20247 // be necessary to support views/UCS created outside of LibreCAD. Return
20248 // to this after normal support of UCS.
20249 // vie.namedUCS_ID = ucs.
20250 // vie.baseUCS_ID = ucs.
20251 }
20252 const auto *viewMetadata =
20253 m_graphic->dwgAdvancedMetadata().findViewByName(vie.name);
20254 if (viewMetadata) {
20255 if (m_dwgW != nullptr &&
20256 (hasCustomDxfApplicationGroups(viewMetadata->appData) ||
20257 !viewMetadata->extData.empty())) {
20258 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20259 "RS_FilterDXFRW: cannot convert DXF VIEW "
20260 "application data or XDATA to DWG");
20261 m_writeFailed = true;
20262 continue;
20263 }
20264 if (!restoreTableEntryExtData(vie, viewMetadata->extData)) {
20265 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20266 "RS_FilterDXFRW: cannot restore DXF VIEW "
20267 "XDATA");
20268 m_writeFailed = true;
20269 continue;
20270 }
20271 vie.handle = viewMetadata->handle;
20272 vie.appData = viewMetadata->appData;
20273 vie.reactorHandles = viewMetadata->reactorHandles;
20274 vie.xDictHandle = viewMetadata->xDictHandle;
20275 vie.namedUCS_ID = viewMetadata->namedUcsHandle;
20276 vie.baseUCS_ID = viewMetadata->baseUcsHandle;
20277 vie.m_useDefaultLights = viewMetadata->useDefaultLights;
20278 vie.m_defaultLightingType = viewMetadata->defaultLightingType;
20279 vie.m_brightness = viewMetadata->brightness;
20280 vie.m_contrast = viewMetadata->contrast;
20281 vie.m_ambientColor = viewMetadata->ambientColor;
20282 vie.m_backgroundHandle = viewMetadata->backgroundHandle;
20283 vie.m_visualStyleHandle = viewMetadata->visualStyleHandle;
20284 vie.m_sunHandle = viewMetadata->sunHandle;
20285 vie.m_liveSectionHandle = viewMetadata->liveSectionHandle;
20286#ifdef DWGSUPPORT1
20287 if (m_dwgW != nullptr) {
20288 vie.namedUCS_ID = remapDwgUcsReference(vie.namedUCS_ID,
20289 "VIEW named UCS", optionalDrops);
20290 vie.baseUCS_ID = remapDwgUcsReference(vie.baseUCS_ID, "VIEW base UCS",
20291 optionalDrops);
20292 vie.m_backgroundHandle = remapDwgObjectReference(
20293 vie.m_backgroundHandle, "VIEW background", optionalDrops);
20294 vie.m_visualStyleHandle = remapDwgObjectReference(
20295 vie.m_visualStyleHandle, "VIEW visual style", optionalDrops);
20296 vie.m_sunHandle =
20297 remapDwgObjectReference(vie.m_sunHandle, "VIEW sun", optionalDrops);
20298 vie.m_liveSectionHandle = remapDwgObjectReference(
20299 vie.m_liveSectionHandle, "VIEW live section", optionalDrops);
20300 }
20301#endif
20302 }
20303 if (m_dwgW != nullptr) {
20304 const std::uint32_t sourceHandle = vie.handle;
20305 if (!m_dwgW->addView(&vie)) {
20306 m_writeFailed = true;
20307 continue;
20308 }
20309 if (sourceHandle != 0 && vie.handle != 0) {
20310 const auto [it, inserted] =
20311 m_dwgWriteViewHandleRemap.emplace(sourceHandle, vie.handle);
20312 if (!inserted && it->second != vie.handle) {
20313 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20314 "RS_FilterDXFRW: duplicate DWG VIEW handle 0x%X",
20315 sourceHandle);
20316 m_writeFailed = true;
20317 }
20318 }
20319 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
20320 sourceHandle, vie.handle)) {
20321 m_writeFailed = true;
20322 } else {
20323 for (const PendingOptionalDrop &drop : optionalDrops) {
20324 if (!recordDwgWriteOptionalDrop(
20325 DwgWriteSourceKind::TableRecord, sourceHandle, vie.handle,
20326 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
20327 drop.targetSourceHandle, drop.targetStatus,
20328 m_dwgW->getVersion(), "DWG VIEWS", drop.reason)) {
20329 m_writeFailed = true;
20330 break;
20331 }
20332 }
20333 }
20334 } else {
20335 noteDxfWrite(m_dxfW->writeView(&vie));
20336 }
20337 }
20338}
20339
20340void RS_FilterDXFRW::writeTextstyles() {
20341 QHash<QString, QString> styles;
20342 QString sty;
20343 // Find fonts used by text entities in drawing
20344 for (RS_Entity *e :
20345 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
20346 if (!e->isDeleted()) {
20347 auto rtti = e->rtti();
20348 switch (rtti) {
20349 case RS2::EntityMText:
20350 sty = static_cast<RS_MText *>(e)->getStyle();
20351 break;
20352 case RS2::EntityText:
20353 sty = static_cast<RS_Text *>(e)->getStyle();
20354 break;
20355 default:
20356 if (RS2::isDimensionalEntity(rtti)) {
20357 auto dim = dynamic_cast<RS_Dimension *>(e);
20358 if (dim != nullptr) {
20359 auto styleOverride = dim->getDimStyleOverride();
20360 if (styleOverride != nullptr) {
20361 auto dimTextStyle = styleOverride->text()->style();
20362 sty = dimTextStyle;
20363 }
20364 }
20365 } else {
20366 sty.clear();
20367 }
20368 break;
20369 }
20370 if (!sty.isEmpty() && !styles.contains(sty)) {
20371 styles.insert(sty, sty);
20372 }
20373 }
20374 }
20375 // Find fonts used by text entities in blocks
20376 RS_Block *blk;
20377 for (unsigned i = 0; i < m_graphic->countBlocks(); i++) {
20378 blk = m_graphic->blockAt(i);
20379 for (RS_Entity *e :
20380 lc::LC_ContainerTraverser{*blk, RS2::ResolveNone}.entities()) {
20381 if (!e->isDeleted()) {
20382 RS2::EntityType rtti = e->rtti();
20383 switch (rtti) {
20384 case RS2::EntityMText:
20385 sty = static_cast<RS_MText *>(e)->getStyle();
20386 break;
20387 case RS2::EntityText:
20388 sty = static_cast<RS_Text *>(e)->getStyle();
20389 break;
20390 default:
20391 if (RS2::isDimensionalEntity(rtti)) {
20392 auto dim = dynamic_cast<RS_Dimension *>(e);
20393 if (dim != nullptr) {
20394 auto styleOverride = dim->getDimStyleOverride();
20395 if (styleOverride != nullptr) {
20396 auto dimTextStyle = styleOverride->text()->style();
20397 sty = dimTextStyle;
20398 }
20399 }
20400 } else {
20401 sty.clear();
20402 }
20403
20404 break;
20405 }
20406 if (!sty.isEmpty() && !styles.contains(sty)) {
20407 styles.insert(sty, sty);
20408 }
20409 }
20410 }
20411 }
20412
20413 auto dimStyleList = m_graphic->getDimStyleList();
20414
20415 for (const auto ds : *dimStyleList->getStylesList()) {
20416 sty = ds->text()->style();
20417 if (!sty.isEmpty() && !styles.contains(sty)) {
20418 styles.insert(sty, sty);
20419 }
20420 }
20421
20422 const auto &metadata = m_graphic->dwgAdvancedMetadata();
20423 std::set<std::string> emittedNames;
20424 DRW_Textstyle ts;
20425 if (const auto *source = metadata.findTextStyleTableEntryByName("STANDARD")) {
20426 ts = *source;
20427 } else {
20428 ts.name = "STANDARD";
20429 ts.font = "txt";
20430 }
20431 emittedNames.insert(normalizeDwgTableName(ts.name));
20432 (void)writeTextStyleRecord(ts);
20433
20434 for (const auto &entry : metadata.textStyleTableEntries()) {
20435 if (!emittedNames.insert(normalizeDwgTableName(entry.first)).second)
20436 continue;
20437 ts = entry.second;
20438 (void)writeTextStyleRecord(ts);
20439 }
20440 for (const auto &[handle, source] : metadata.textStyles()) {
20441 if (source.name.empty() && (source.flags & 1) != 0) {
20442 ts = source;
20443 (void)writeTextStyleRecord(ts);
20444 }
20445 }
20446 QHash<QString, QString>::const_iterator it = styles.constBegin();
20447 while (it != styles.constEnd()) {
20448 if (!emittedNames.insert(normalizeDwgTableName(it.key().toStdString()))
20449 .second) {
20450 ++it;
20451 continue;
20452 }
20453 ts.reset();
20454 ts.name = (it.key()).toStdString();
20455 ts.font = it.value().toStdString();
20456 (void)writeTextStyleRecord(ts);
20457 ++it;
20458 }
20459}
20460
20461bool RS_FilterDXFRW::writeTextStyleRecord(DRW_Textstyle &textStyle) {
20462 if (const auto path = m_shapeFileResolvedPaths.find(textStyle.handle);
20463 path != m_shapeFileResolvedPaths.end()) {
20464 textStyle.font = path->second.toStdString();
20465 }
20466 if (m_dwgW != nullptr) {
20467 if (hasDxfTableAppDataUnsupportedByDwg(textStyle)) {
20468 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20469 "RS_FilterDXFRW: cannot convert DXF STYLE "
20470 "application data to DWG");
20471 m_writeFailed = true;
20472 return false;
20473 }
20474 const std::uint32_t sourceHandle = textStyle.handle;
20475 if (m_dwgW->addTextstyle(&textStyle) && textStyle.handle != 0 &&
20476 stageDwgTableRecordIdentity(RS_FilterDXFRW_DwgWriteIdentityRegistry::
20477 DwgWriteSourceKind::TableRecord,
20478 sourceHandle, textStyle.handle)) {
20479 if (!textStyle.name.empty())
20480 m_dwgWriteTextStyleHandleByName[normalizeDwgTableName(textStyle.name)] =
20481 textStyle.handle;
20482 return true;
20483 }
20484 m_writeFailed = true;
20485 return false;
20486 }
20487 return noteDxfWrite(m_dxfW->writeTextstyle(&textStyle));
20488}
20489
20490void RS_FilterDXFRW::writeVports() {
20491 DRW_Vport vp;
20492 vp.name = "*Active";
20493 m_graphic->isGridOn() ? vp.grid = 1 : vp.grid = 0;
20494 RS_Vector spacing =
20495 m_graphic->getVariableVector("$GRIDUNIT", RS_Vector(0.0, 0.0));
20496 vp.gridBehavior = 3;
20497 vp.gridSpacing.x = spacing.x;
20498 vp.gridSpacing.y = spacing.y;
20499 vp.snapStyle = m_graphic->isIsometricGrid();
20500 vp.snapIsopair = m_graphic->getIsoView();
20501 if (vp.snapIsopair > 2) {
20502 vp.snapIsopair = 0;
20503 }
20504 if (fabs(spacing.x) < 1.0e-6) {
20505 vp.gridBehavior = 7; // auto
20506 vp.gridSpacing.x = 10;
20507 }
20508 if (fabs(spacing.y) < 1.0e-6) {
20509 vp.gridBehavior = 7; // auto
20510 vp.gridSpacing.y = 10;
20511 }
20512 RS_GraphicView *gv = m_graphic->getGraphicView();
20513 if (gv) {
20514 LC_GraphicViewport *viewport = gv->getViewPort();
20515 RS_Vector fac = viewport->getFactor();
20516 vp.height = gv->getHeight() / fac.y;
20517 vp.ratio = (double)gv->getWidth() / (double)gv->getHeight();
20518 vp.center.x = (gv->getWidth() - viewport->getOffsetX()) / (fac.x * 2.0);
20519 vp.center.y = (gv->getHeight() - viewport->getOffsetY()) / (fac.y * 2.0);
20520 }
20521 const auto &metadata = m_graphic->dwgAdvancedMetadata();
20522 if (const auto *activeVport = metadata.findVportTableEntryByName(vp.name)) {
20523 const bool hasUnsupportedAppData =
20524 hasCustomDxfApplicationGroups(activeVport->appData);
20525 if (m_dwgW != nullptr && hasUnsupportedAppData) {
20526 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20527 "RS_FilterDXFRW: dropping unsupported DXF VPORT "
20528 "application data while writing DWG");
20529 }
20530 if (!restoreTableEntryExtData(vp, activeVport->extData)) {
20531 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20532 "RS_FilterDXFRW: cannot restore DXF VPORT "
20533 "XDATA");
20534 m_writeFailed = true;
20535 return;
20536 }
20537 if (!hasUnsupportedAppData)
20538 vp.appData = activeVport->appData;
20539 vp.reactorHandles = activeVport->reactorHandles;
20540 vp.xDictHandle = activeVport->xDictHandle;
20541 vp.handle = activeVport->handle;
20542 }
20543 if (m_dwgW) {
20544 struct PendingOptionalDrop {
20545 std::uint32_t targetSourceHandle = 0;
20546 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
20547 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
20548 const char *referenceName = nullptr;
20549 std::uint32_t referenceOrdinal = 0;
20550 const char *reason = nullptr;
20551 };
20552 const auto isDropped = [](DwgWriteReferenceStatus status) {
20553 return status == DwgWriteReferenceStatus::Missing ||
20554 status == DwgWriteReferenceStatus::Ambiguous ||
20555 status == DwgWriteReferenceStatus::Suppressed;
20556 };
20557 const auto dropReason = [](DwgWriteReferenceStatus status) {
20558 return status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
20559 : status == DwgWriteReferenceStatus::Suppressed
20560 ? "suppressed target"
20561 : "missing target";
20562 };
20563 const bool hasViewportEntityReferences = std::any_of(
20564 metadata.viewportEntityHeaders().cbegin(),
20565 metadata.viewportEntityHeaders().cend(), [](const auto &record) {
20566 return record.replayState ==
20567 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
20568 record.viewportEntityHandle != 0;
20569 });
20570 // Entity handles are allocated while blocks are written, but VPH
20571 // records are queued from this earlier table callback. Precompute the
20572 // entity remap only when a VPH actually refers to an entity, so the
20573 // reference is stable without changing ordinary save allocation.
20574 if (m_dwgW->getVersion() >= DRW::AC1015 && hasViewportEntityReferences) {
20575 prepareDwgEntityHandleMap();
20576 }
20577 const std::uint32_t sourceHandle = vp.handle;
20578 if (!m_dwgW->addVport(&vp)) {
20579 m_writeFailed = true;
20580 return;
20581 }
20582 m_dwgWriteVportHandleRemap[DRW_Vport::kDwgActiveHandle] = vp.handle;
20583 if (sourceHandle != 0) {
20584 const auto [it, inserted] =
20585 m_dwgWriteVportHandleRemap.emplace(sourceHandle, vp.handle);
20586 if (!inserted && it->second != vp.handle) {
20587 RS_DEBUGRS_Debug::instance()->print(
20588 RS_Debug::D_WARNING,
20589 "RS_FilterDXFRW: duplicate DWG active VPORT handle 0x%X",
20590 sourceHandle);
20591 m_writeFailed = true;
20592 }
20593 }
20594 if (!stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
20595 sourceHandle, vp.handle))
20596 m_writeFailed = true;
20597 auto remapHandle = [this](std::uint32_t handle) {
20598 const auto objectIt = m_dwgWriteHandleRemap.find(handle);
20599 if (objectIt != m_dwgWriteHandleRemap.end())
20600 return objectIt->second;
20601 const auto entityIt = m_dwgWriteEntityHandleRemap.find(handle);
20602 if (entityIt != m_dwgWriteEntityHandleRemap.end())
20603 return entityIt->second;
20604 return handle;
20605 };
20606 auto remapObjectReference = [this, &isDropped, &dropReason](
20607 std::uint32_t sourceHandle,
20608 const char *referenceKind,
20609 std::vector<PendingOptionalDrop> &drops,
20610 std::uint32_t referenceOrdinal = 0) {
20611 std::uint32_t outputHandle = 0;
20612 const DwgWriteReferenceStatus status =
20613 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
20614 if (isDropped(status)) {
20615 RS_DEBUGRS_Debug::instance()->print(
20616 RS_Debug::D_WARNING,
20617 status == DwgWriteReferenceStatus::Ambiguous
20618 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
20619 "0x%X"
20620 : "RS_FilterDXFRW: dropping dangling DWG %s target "
20621 "0x%X",
20622 referenceKind, sourceHandle);
20623 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Object,
20624 referenceKind, referenceOrdinal, dropReason(status)});
20625 return std::uint32_t{0};
20626 }
20627 return outputHandle;
20628 };
20629 auto remapUcsReference = [this, &isDropped, &dropReason](
20630 std::uint32_t sourceHandle,
20631 const char *referenceKind,
20632 std::vector<PendingOptionalDrop> &drops) {
20633 std::uint32_t outputHandle = 0;
20634 const DwgWriteReferenceStatus status = resolveDwgWriteTableRecordHandle(
20635 sourceHandle, m_dwgWriteUcsHandleRemap, &outputHandle);
20636 if (status == DwgWriteReferenceStatus::Reserved)
20637 return outputHandle;
20638 if (isDropped(status)) {
20639 RS_DEBUGRS_Debug::instance()->print(
20640 RS_Debug::D_WARNING,
20641 status == DwgWriteReferenceStatus::Ambiguous
20642 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
20643 "0x%X"
20644 : "RS_FilterDXFRW: dropping dangling DWG %s target "
20645 "0x%X",
20646 referenceKind, sourceHandle);
20647 drops.push_back({sourceHandle, status, DwgWriteSourceKind::TableRecord,
20648 referenceKind, 0, dropReason(status)});
20649 return std::uint32_t{0};
20650 }
20651 return outputHandle;
20652 };
20653 std::set<std::string> emittedVportNames;
20654 emittedVportNames.insert("*ACTIVE");
20655 for (const auto &record : metadata.vports()) {
20656 if (record.replayState !=
20657 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
20658 record.handle == 0)
20659 continue;
20660 std::string normalizedName = record.name;
20661 std::transform(
20662 normalizedName.begin(), normalizedName.end(), normalizedName.begin(),
20663 [](unsigned char c) { return static_cast<char>(std::toupper(c)); });
20664 if (normalizedName == "*ACTIVE") {
20665 // The writer always emits the canonical active record. It stands
20666 // for every active record a reader delivered: a drawing with tiled
20667 // viewports has one for each of them. An active-name record from
20668 // anywhere else is an ambiguous second table identity.
20669 if (record.handle == DRW_Vport::kDwgActiveHandle || record.imported)
20670 continue;
20671 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20672 "RS_FilterDXFRW: duplicate generated DWG VPORT "
20673 "'*ACTIVE' record");
20674 m_writeFailed = true;
20675 continue;
20676 }
20677 if (record.handle == DRW_Vport::kDwgActiveHandle) {
20678 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20679 "RS_FilterDXFRW: DWG VPORT handle 0x%X is reserved "
20680 "for '*ACTIVE'",
20681 record.handle);
20682 m_writeFailed = true;
20683 continue;
20684 }
20685 if (!emittedVportNames.insert(normalizedName).second) {
20686 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20687 "RS_FilterDXFRW: duplicate DWG VPORT name '%s'",
20688 record.name.c_str());
20689 m_writeFailed = true;
20690 continue;
20691 }
20692 if (hasCustomDxfApplicationGroups(record.appData)) {
20693 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20694 "RS_FilterDXFRW: dropping unsupported DXF "
20695 "VPORT application data while writing DWG");
20696 }
20697 DRW_Vport named;
20698 named.handle = remapHandle(record.handle);
20699 // VPORT entries are owned by the fixed VPORT control object;
20700 // the source parent is not a remappable object reference.
20701 named.parentHandle = DRW_Vport::kDwgControlHandle;
20702 named.name = record.name;
20703 named.center = record.center;
20704 named.viewTarget = record.viewTarget;
20705 named.viewDir = record.viewDirection;
20706 named.height = record.height;
20707 named.ratio = record.ratio;
20708 named.viewMode = record.viewMode;
20709 named.grid = record.grid;
20710 named.snap = record.snap;
20711 named.gridBehavior = record.gridBehavior;
20712 if (!restoreTableEntryExtData(named, record.extData)) {
20713 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20714 "RS_FilterDXFRW: cannot restore DXF VPORT XDATA");
20715 m_writeFailed = true;
20716 continue;
20717 }
20718 named.reactorHandles = record.reactorHandles;
20719 named.xDictHandle = record.xDictHandle;
20720 std::vector<PendingOptionalDrop> optionalDrops;
20721 named.backgroundHandle = remapObjectReference(
20722 record.backgroundHandle, "VPORT background", optionalDrops);
20723 named.visualStyleHandle = remapObjectReference(
20724 record.visualStyleHandle, "VPORT visual style", optionalDrops);
20725 named.m_sunHandle =
20726 remapObjectReference(record.sunHandle, "VPORT sun", optionalDrops);
20727 named.namedUcsHandle = remapUcsReference(
20728 record.namedUcsHandle, "VPORT named UCS", optionalDrops);
20729 named.baseUcsHandle = remapUcsReference(record.baseUcsHandle,
20730 "VPORT base UCS", optionalDrops);
20731 const std::uint32_t sourceHandle = record.handle;
20732 if (!m_dwgW->addVport(&named)) {
20733 m_writeFailed = true;
20734 continue;
20735 }
20736 if (sourceHandle != 0 && named.handle != 0) {
20737 const auto [it, inserted] =
20738 m_dwgWriteVportHandleRemap.emplace(sourceHandle, named.handle);
20739 if (!inserted && it->second != named.handle) {
20740 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20741 "RS_FilterDXFRW: duplicate DWG VPORT handle 0x%X",
20742 sourceHandle);
20743 m_writeFailed = true;
20744 }
20745 }
20746 const bool identityCommitted = stageDwgTableRecordIdentity(
20747 DwgWriteSourceKind::TableRecord, sourceHandle, named.handle);
20748 if (!identityCommitted) {
20749 m_writeFailed = true;
20750 } else {
20751 for (const PendingOptionalDrop &drop : optionalDrops) {
20752 if (!recordDwgWriteOptionalDrop(
20753 DwgWriteSourceKind::TableRecord, sourceHandle, named.handle,
20754 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
20755 drop.targetSourceHandle, drop.targetStatus,
20756 m_dwgW->getVersion(), "DWG VPORTS", drop.reason)) {
20757 m_writeFailed = true;
20758 break;
20759 }
20760 }
20761 }
20762 }
20763 std::set<std::uint32_t> viewportHeaderHandles;
20764 for (const auto &record : metadata.viewportEntityHeaders()) {
20765 if (record.replayState ==
20766 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
20767 record.handle != 0) {
20768 if (!viewportHeaderHandles.insert(record.handle).second) {
20769 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20770 "RS_FilterDXFRW: duplicate DWG viewport-header "
20771 "handle 0x%X",
20772 record.handle);
20773 m_writeFailed = true;
20774 }
20775 }
20776 }
20777 auto remapBlockReference = [this, &isDropped, &dropReason](
20778 std::uint32_t sourceHandle,
20779 const char *referenceKind,
20780 std::vector<PendingOptionalDrop> &drops) {
20781 if (sourceHandle == DRW::NoHandle)
20782 return std::uint32_t{0};
20783 std::uint32_t outputHandle = 0;
20784 const DwgWriteReferenceStatus status =
20785 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
20786 if (isDropped(status)) {
20787 RS_DEBUGRS_Debug::instance()->print(
20788 RS_Debug::D_WARNING,
20789 status == DwgWriteReferenceStatus::Ambiguous
20790 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
20791 "0x%X"
20792 : "RS_FilterDXFRW: dropping dangling DWG %s target "
20793 "0x%X",
20794 referenceKind, sourceHandle);
20795 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Block,
20796 referenceKind, 0, dropReason(status)});
20797 return std::uint32_t{0};
20798 }
20799 return outputHandle;
20800 };
20801 auto remapEntityReference = [this, &isDropped, &dropReason](
20802 std::uint32_t sourceHandle,
20803 const char *referenceKind,
20804 std::vector<PendingOptionalDrop> &drops) {
20805 std::uint32_t outputHandle = 0;
20806 const DwgWriteReferenceStatus status =
20807 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
20808 if (isDropped(status)) {
20809 RS_DEBUGRS_Debug::instance()->print(
20810 RS_Debug::D_WARNING,
20811 status == DwgWriteReferenceStatus::Ambiguous
20812 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
20813 "0x%X"
20814 : "RS_FilterDXFRW: dropping dangling DWG %s target "
20815 "0x%X",
20816 referenceKind, sourceHandle);
20817 drops.push_back({sourceHandle, status, DwgWriteSourceKind::Entity,
20818 referenceKind, 0, dropReason(status)});
20819 return std::uint32_t{0};
20820 }
20821 return outputHandle;
20822 };
20823 auto remapViewportHeaderReference =
20824 [&remapHandle, &viewportHeaderHandles,
20825 &dropReason](std::uint32_t sourceHandle, const char *referenceKind,
20826 std::vector<PendingOptionalDrop> &drops) {
20827 if (sourceHandle == DRW::NoHandle)
20828 return std::uint32_t{0};
20829 if (viewportHeaderHandles.count(sourceHandle) == 0) {
20830 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20831 "RS_FilterDXFRW: dropping dangling DWG %s target "
20832 "0x%X",
20833 referenceKind, sourceHandle);
20834 drops.push_back({sourceHandle, DwgWriteReferenceStatus::Missing,
20835 DwgWriteSourceKind::TableRecord, referenceKind, 0,
20836 dropReason(DwgWriteReferenceStatus::Missing)});
20837 return std::uint32_t{0};
20838 }
20839 return remapHandle(sourceHandle);
20840 };
20841 for (const auto &record : metadata.viewportEntityHeaders()) {
20842 if (record.replayState !=
20843 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
20844 record.handle == 0) {
20845 continue;
20846 }
20847 DRW_ViewportEntityHeader header;
20848 header.handle = remapHandle(record.handle);
20849 header.parentHandle = static_cast<int>(remapHandle(record.parentHandle));
20850 header.name = record.name;
20851 header.flags = record.flags;
20852 header.isOn = record.isOn;
20853 std::vector<PendingOptionalDrop> optionalDrops;
20854 header.xrefBlockHandle = remapBlockReference(
20855 record.xrefBlockHandle, "VPEHEADER block", optionalDrops);
20856 header.viewportEntityHandle = remapEntityReference(
20857 record.viewportEntityHandle, "VPEHEADER entity", optionalDrops);
20858 header.previousViewportEntityHeaderHandle = remapViewportHeaderReference(
20859 record.previousViewportEntityHeaderHandle, "VPEHEADER previous",
20860 optionalDrops);
20861 for (std::size_t ordinal = 0; ordinal < record.reactorHandles.size();
20862 ++ordinal) {
20863 const std::uint32_t handle = record.reactorHandles[ordinal];
20864 const std::uint32_t remapped =
20865 remapObjectReference(handle, "VPEHEADER reactor", optionalDrops,
20866 static_cast<std::uint32_t>(ordinal));
20867 if (remapped != 0)
20868 header.reactorHandles.push_back(remapped);
20869 }
20870 header.xDictHandle = remapObjectReference(
20871 record.xDictHandle, "VPEHEADER extension dictionary", optionalDrops);
20872 header.setDwgCommonObjectState(
20873 static_cast<std::int32_t>(header.reactorHandles.size()),
20874 header.xDictHandle != 0 ? record.xDictFlag : 0,
20875 encodedDwgDataStoragePresenceBit(record.handle, record.hasDsData));
20876 if (!m_dwgW->addViewportEntityHeader(&header)) {
20877 m_writeFailed = true;
20878 continue;
20879 }
20880 const bool identityCommitted = stageDwgTableRecordIdentity(
20881 DwgWriteSourceKind::TableRecord, record.handle, header.handle);
20882 if (!identityCommitted) {
20883 m_writeFailed = true;
20884 continue;
20885 }
20886 for (const PendingOptionalDrop &drop : optionalDrops) {
20887 if (!recordDwgWriteOptionalDrop(
20888 DwgWriteSourceKind::TableRecord, record.handle, header.handle,
20889 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
20890 drop.targetSourceHandle, drop.targetStatus,
20891 m_dwgW->getVersion(), "DWG VPORT_ENTITY_HEADERS",
20892 drop.reason)) {
20893 m_writeFailed = true;
20894 break;
20895 }
20896 }
20897 }
20898 return;
20899 }
20900 noteDxfWrite(m_dxfW->writeVport(&vp));
20901 if (m_writeFailed)
20902 return;
20903 for (const auto &entry : metadata.vportTableEntries()) {
20904 if (normalizeDwgTableName(entry.first) == "*ACTIVE")
20905 continue;
20906 DRW_Vport named = entry.second;
20907 noteDxfWrite(m_dxfW->writeVport(&named));
20908 if (m_writeFailed)
20909 return;
20910 }
20911}
20912
20913void RS_FilterDXFRW::writeDimstyles() {
20914 LC_DimStylesList *dimStylesList = m_graphic->getDimStyleList();
20915 auto stylesList = dimStylesList->getStylesList();
20916 for (auto ds : *stylesList) {
20917 DRW_Dimstyle dst;
20918 prepareDRWDimStyle(dst, ds);
20919 if (const auto *sourceStyle =
20920 m_graphic->dwgAdvancedMetadata().findDimStyleTableEntryByName(
20921 dst.name)) {
20922 if (m_dwgW != nullptr &&
20923 hasDxfTableAppDataUnsupportedByDwg(*sourceStyle)) {
20924 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20925 "RS_FilterDXFRW: cannot convert DXF DIMSTYLE "
20926 "application data to DWG");
20927 m_writeFailed = true;
20928 continue;
20929 }
20930 if (!restoreTableEntryExtData(dst, sourceStyle->extData)) {
20931 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
20932 "RS_FilterDXFRW: cannot restore DXF "
20933 "DIMSTYLE XDATA");
20934 m_writeFailed = true;
20935 continue;
20936 }
20937 dst.appData = sourceStyle->appData;
20938 dst.reactorHandles = sourceStyle->reactorHandles;
20939 dst.xDictHandle = sourceStyle->xDictHandle;
20940 dst.handle = sourceStyle->handle;
20941 }
20942 if (m_dwgW) {
20943 const std::uint32_t sourceHandle = dst.handle;
20944 if (!m_dwgW->addDimstyle(&dst) ||
20945 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
20946 sourceHandle, dst.handle))
20947 m_writeFailed = true;
20948 } else {
20949 noteDxfWrite(m_dxfW->writeDimstyle(&dst));
20950 }
20951 }
20952}
20953
20954void RS_FilterDXFRW::prepareDRWDimStyleZerosSuppression(DRW_Dimstyle &d,
20955 const LC_DimStyle *ds) {
20956 auto zeros = ds->zerosSuppression();
20957 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMZIN)) {
20958 d.add("$DIMZIN", 78, zeros->linearRaw());
20959 }
20960 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMAZIN)) {
20961 d.add("$DIMAZIN", 79, zeros->angularRaw());
20962 }
20963
20964 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMTZIN)) {
20965 d.add("$DIMTZIN", 284, zeros->toleranceRaw());
20966 }
20967 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTZ)) {
20968 d.add("$DIMALTZ", 285, zeros->altLinearRaw());
20969 }
20970 if (zeros->checkModifyState(LC_DimStyle::ZerosSuppression::$DIMALTTZ)) {
20971 d.add("$DIMALTTZ", 286, zeros->altToleranceRaw());
20972 }
20973}
20974
20975void RS_FilterDXFRW::prepareDRWDimStyleArrows(DRW_Dimstyle &d,
20976 const LC_DimStyle *ds) {
20977 auto arrow = ds->arrowhead();
20978 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK)) {
20979 QString blockName = arrow->sameBlockName();
20980 if (!blockName.isEmpty()) {
20981 auto blkName = blockName.toStdString();
20982 std::uint32_t blkHandle =
20983 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
20984 : DRW::NoHandle;
20985 if (blkHandle != DRW::NoHandle) {
20986 d.add("_$DIMBLK", 342, toHexStr(blkHandle).toStdString());
20987 }
20988 d.add("$DIMBLK", 5, blkName);
20989 }
20990 }
20991 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK1)) {
20992 QString blockName = arrow->arrowHeadBlockNameFirst();
20993 if (!blockName.isEmpty()) {
20994 auto blkName = blockName.toStdString();
20995 std::uint32_t blkHandle =
20996 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
20997 : DRW::NoHandle;
20998 if (blkHandle != DRW::NoHandle) {
20999 d.add("_$DIMBLK1", 343, toHexStr(blkHandle).toStdString());
21000 }
21001 d.add("$DIMBLK1", 6, blkName);
21002 }
21003 }
21004 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMBLK2)) {
21005 QString blockName = arrow->arrowHeadBlockNameSecond();
21006 if (!blockName.isEmpty()) {
21007 auto blkName = blockName.toStdString();
21008 std::uint32_t blkHandle =
21009 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
21010 : DRW::NoHandle;
21011 if (blkHandle != DRW::NoHandle) {
21012 d.add("_$DIMBLK2", 344, toHexStr(blkHandle).toStdString());
21013 }
21014 d.add("$DIMBLK2", 7, blkName);
21015 }
21016 }
21017 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMASZ)) {
21018 d.add("$DIMASZ", 41, arrow->size());
21019 }
21020 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMTSZ)) {
21021 d.add("$DIMTSZ", 142, arrow->tickSize());
21022 }
21023 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSAH)) {
21024 d.add("$DIMSAH", 173, arrow->isUseSeparateArrowHeads() ? 1 : 0);
21025 }
21026 if (arrow->checkModifyState(LC_DimStyle::Arrowhead::$DIMSOXD)) {
21027 d.add("$DIMSOXD", 175, arrow->suppression());
21028 }
21029}
21030
21031void RS_FilterDXFRW::prepareDRWDimStyleScaling(DRW_Dimstyle &d,
21032 const LC_DimStyle *ds) {
21033 auto scale = ds->scaling();
21034 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMSCALE)) {
21035 d.add("$DIMSCALE", 40, scale->scale());
21036 }
21037 if (scale->checkModifyState(LC_DimStyle::Scaling::$DIMLFAC)) {
21038 d.add("$DIMLFAC", 144, scale->linearFactor());
21039 }
21040}
21041
21042void RS_FilterDXFRW::prepareDRWDimStyleExtLine(DRW_Dimstyle &d,
21043 const LC_DimStyle *ds) {
21044 auto extLine = ds->extensionLine();
21045 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXO)) {
21046 d.add("$DIMEXO", 42, extLine->distanceFromOriginPoint());
21047 }
21048 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMEXE)) {
21049 d.add("$DIMEXE", 44, extLine->distanceBeyondDimLine());
21050 }
21051 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXL)) {
21052 d.add("$DIMFXL", 49, extLine->fixedLength());
21053 }
21054 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMFXLON)) {
21055 d.add("$DIMFXLON", 290, extLine->hasFixedLength() ? 1 : 0);
21056 }
21057 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLWE)) {
21058 auto lineWidth = extLine->lineWidth();
21059 int lw = RS2::lineWidth2dxfInt(lineWidth);
21060 d.add("$DIMLWE", 372, lw);
21061 }
21062 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMCLRE)) {
21063 auto lineColor = extLine->color();
21064 int colRGB;
21065 int colNum = LC_ColorNumbers::colorToNumber(lineColor, &colRGB);
21066 d.add("$DIMCLRE", 177, colNum);
21067 }
21068 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE1)) {
21069 d.add("$DIMSE1", 75, extLine->suppressFirstLine());
21070 }
21071 if (extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMSE2)) {
21072 d.add("$DIMSE2", 76, extLine->suppressSecondLine());
21073 }
21074 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
21075 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1)) {
21076 std::uint32_t lineTypeHandle =
21077 findLineTypeHandleToWrite(extLine->lineTypeFirstRaw());
21078 if (lineTypeHandle != DRW::NoHandle) {
21079 auto handleStr = toHexStr(lineTypeHandle);
21080 d.add("$DIMLTEX1", 346, handleStr.toStdString());
21081 }
21082 }
21083 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
21084 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2)) {
21085 std::uint32_t lineTypeHandle =
21086 findLineTypeHandleToWrite(extLine->lineTypeSecondRaw());
21087 if (lineTypeHandle != DRW::NoHandle) {
21088 auto handleStr = toHexStr(lineTypeHandle);
21089 d.add("$DIMLTEX2", 347, handleStr.toStdString());
21090 }
21091 }
21092}
21093
21094void RS_FilterDXFRW::prepareDRWDimStyleDimLine(DRW_Dimstyle &d,
21095 const LC_DimStyle *ds) {
21096 auto dimLine = ds->dimensionLine();
21097 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLWD)) {
21098 auto lineWidth = dimLine->lineWidth();
21099 int lw = RS2::lineWidth2dxfInt(lineWidth);
21100 d.add("$DIMLWD", 371, lw);
21101 }
21102 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLE)) {
21103 d.add("$DIMDLE", 46, dimLine->distanceBeyondExtLinesForObliqueStroke());
21104 }
21105 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMDLI)) {
21106 d.add("$DIMDLI", 43, dimLine->baseLineDimLinesSpacing());
21107 }
21108 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMGAP)) {
21109 d.add("$DIMGAP", 147, dimLine->lineGap());
21110 }
21111 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMCLRD)) {
21112 auto lineColor = dimLine->color();
21113 int colRGB;
21114 int colNum = LC_ColorNumbers::colorToNumber(lineColor, &colRGB);
21115 d.add("$DIMCLRD", 176, colNum);
21116 }
21117 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD1)) {
21118 d.add("$DIMSD1", 281, dimLine->suppressFirstLine());
21119 }
21120 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMSD2)) {
21121 d.add("$DIMSD2", 281, dimLine->suppressSecondLine());
21122 }
21123 if (dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMTOFL)) {
21124 d.add("$DIMTOFL", 172, dimLine->drawPolicyForOutsideText());
21125 }
21126 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1018 &&
21127 dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE)) {
21128 std::uint32_t lineTypeHandle =
21129 findLineTypeHandleToWrite(dimLine->lineTypeName());
21130 if (lineTypeHandle != DRW::NoHandle) {
21131 auto handleStr = toHexStr(lineTypeHandle);
21132 d.add("$DIMLTYPE", 345, handleStr.toStdString());
21133 }
21134 }
21135}
21136
21137std::uint32_t
21138RS_FilterDXFRW::findLineTypeHandleToWrite(const QString &name) const {
21139 if (m_dxfW == nullptr) {
21140 return DRW::NoHandle;
21141 }
21142 // lineTypesMap is keyed by a byte-wise fold of the UTF-8 name, which a Qt
21143 // case mapping does not reproduce for a name holding lower-case non-ASCII.
21144 std::string lineName = normalizeDwgTableName(name.toStdString());
21145 // ByLayer and Continuous are written at fixed handles, outside the map.
21146 // ByBlock gets no reference: a missing one already means ByBlock.
21147 if (m_dxfW->getVersion() > DRW::AC1009) {
21148 if (lineName == "BYLAYER")
21149 return 0x15;
21150 if (lineName == "CONTINUOUS")
21151 return 0x16;
21152 }
21153 for (auto p : m_dxfW->getWritingContext()->lineTypesMap) {
21154 if (p.first.compare(lineName) == 0) {
21155 return p.second;
21156 }
21157 }
21158 return DRW::NoHandle;
21159}
21160
21161DRW::Version RS_FilterDXFRW::dimStyleTargetVersion() const {
21162 if (m_dxfW != nullptr)
21163 return m_dxfW->getVersion();
21164 if (m_dwgW != nullptr)
21165 return m_dwgW->getVersion();
21166 return DRW::UNKNOWNV;
21167}
21168
21169void RS_FilterDXFRW::prepareDRWDimStyleText(DRW_Dimstyle &d,
21170 const LC_DimStyle *ds) {
21171 auto text = ds->text();
21172 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXT)) {
21173 d.add("$DIMTXT", 140, text->height());
21174 }
21175
21176 if (text->checkModifyState(LC_DimStyle::Text::$DIMTXSTY)) {
21177 auto styleName = text->style().toStdString();
21178 std::uint32_t styleHandle = m_dxfW != nullptr
21179 ? m_dxfW->getTextStyleHandle(styleName)
21180 : DRW::NoHandle;
21181 if (styleHandle != DRW::NoHandle) {
21182 d.add("$DIMTXSTY", 340, toHexStr(styleHandle).toStdString());
21183 }
21184 }
21185 if (text->checkModifyState(LC_DimStyle::Text::$DIMTOH)) {
21186 d.add("$DIMTOH", 74, text->orientationOutside());
21187 }
21188 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIH)) {
21189 d.add("$DIMTIH", 73, text->orientationInside());
21190 }
21191 if (text->checkModifyState(LC_DimStyle::Text::$DIMJUST)) {
21192 d.add("$DIMJUST", 280, text->horizontalPositioning());
21193 }
21194 if (text->checkModifyState(LC_DimStyle::Text::$DIMCLRT)) {
21195 auto lineColor = text->color();
21196 int colRGB;
21197 int colNum = LC_ColorNumbers::colorToNumber(lineColor, &colRGB);
21198 d.add("$DIMCLRT", 178, colNum);
21199 }
21200 if (text->checkModifyState(LC_DimStyle::Text::$DIMTAD)) {
21201 d.add("$DIMTAD", 77, text->verticalPositioning());
21202 }
21203 if (text->checkModifyState(LC_DimStyle::Text::$DIMTIX)) {
21204 d.add("$DIMTIX", 174, text->extLinesRelativePlacement());
21205 }
21206 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILL)) {
21207 d.add("$DIMTFILL", 69, text->backgroundFillMode());
21208 }
21209 if (text->checkModifyState(LC_DimStyle::Text::$DIMTFILLCLR)) {
21210 auto lineColor = text->explicitBackgroundFillColor();
21211 int colRGB;
21212 int colNum = LC_ColorNumbers::colorToNumber(lineColor, &colRGB);
21213 d.add("$DIMTFILLCLR", 70, colNum);
21214 }
21215 if (dimStyleTargetVersion() >= DRW::AC1024 &&
21216 text->checkModifyState(LC_DimStyle::Text::$DIMTXTDIRECTION)) {
21217 d.dimtxtdirection = text->readingDirection();
21218 d.add("$DIMTXTDIRECTION", 295, d.dimtxtdirection);
21219 }
21220 if (text->checkModifyState(LC_DimStyle::Text::$DIMTVP)) {
21221 d.add("$DIMTVP", 145, text->verticalDistanceToDimLine());
21222 }
21223 if (text->checkModifyState(LC_DimStyle::Text::$DIMUPT)) {
21224 d.add("$DIMUPT", 288, text->cursorControlPolicy());
21225 }
21226 if (text->checkModifyState(LC_DimStyle::Text::$DIMTMOVE)) {
21227 d.add("$DIMTMOVE", 279, text->positionMovementPolicy());
21228 }
21229 if (text->checkModifyState(LC_DimStyle::Text::$DIMATFIT)) {
21230 d.add("$DIMATFIT", 289, text->unsufficientSpacePolicy());
21231 }
21232}
21233
21234void RS_FilterDXFRW::prepareDRWDimStyleLinearFormat(DRW_Dimstyle &d,
21235 const LC_DimStyle *ds) {
21236 auto linear = ds->linearFormat();
21237 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMLUNIT)) {
21238 d.add("$DIMLUNIT", 277, linear->formatRaw());
21239 }
21240 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMSEP)) {
21241 d.add("$DIMDSEP", 278, linear->decimalFormatSeparatorChar());
21242 }
21243 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMDEC)) {
21244 d.add("$DIMDEC", 271, linear->decimalPlaces());
21245 }
21246 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMPOST)) {
21247 d.add("$DIMPOST", 3, linear->prefixOrSuffix().toStdString());
21248 }
21249 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALT)) {
21250 d.add("$DIMALT", 170, linear->alternateUnits());
21251 }
21252 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTU)) {
21253 d.add("$DIMALTU", 273, linear->altFormatRaw());
21254 }
21255 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTD)) {
21256 d.add("$DIMALTD", 171, linear->altDecimalPlaces());
21257 }
21258 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMALTF)) {
21259 d.add("$DIMALTF", 143, linear->altUnitsMultiplier());
21260 }
21261 if (linear->checkModifyState(LC_DimStyle::LinearFormat::$DIMAPOST)) {
21262 d.add("$DIMAPOST", 4, linear->altPrefixOrSuffix().toStdString());
21263 }
21264}
21265
21266void RS_FilterDXFRW::prepareDRWDimStyleFractions(DRW_Dimstyle &d,
21267 const LC_DimStyle *ds) {
21268 auto fraction = ds->fractions();
21269 if (fraction->checkModifyState(LC_DimStyle::Fractions::$DIMFRAC)) {
21270 d.add("$DIMFRAC", 276, fraction->style());
21271 }
21272}
21273
21274void RS_FilterDXFRW::prepareDRWDimStyleAngularFormat(DRW_Dimstyle &d,
21275 const LC_DimStyle *ds) {
21276 auto angular = ds->angularFormat();
21277 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMAUNIT)) {
21278 d.add("$DIMAUNIT", 275, angular->format());
21279 }
21280 if (angular->checkModifyState(LC_DimStyle::AngularFormat::$DIMADEC)) {
21281 d.add("$DIMADEC", 179, angular->decimalPlaces());
21282 }
21283
21284 auto round = ds->roundOff();
21285 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMRND)) {
21286 d.add("$DIMRND", 45, round->roundTo());
21287 }
21288 if (round->checkModifyState(LC_DimStyle::LinearRoundOff::$DIMALTRND)) {
21289 d.add("$DIMALTRND", 148, round->altRoundTo());
21290 }
21291}
21292
21293void RS_FilterDXFRW::prepareDRWDimStyleRadial(DRW_Dimstyle &d,
21294 const LC_DimStyle *ds) {
21295 auto radial = ds->radial();
21296 if (radial->checkModifyState(LC_DimStyle::Radial::$DIMCEN)) {
21297 d.add("$DIMCEN", 141, radial->centerCenterMarkOrLineSize());
21298 }
21299}
21300
21301void RS_FilterDXFRW::prepareDRWDimStyleTolerance(DRW_Dimstyle &d,
21302 const LC_DimStyle *ds) {
21303 auto tolerance = ds->latteralTolerance();
21304 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTDEC)) {
21305 d.add("$DIMTDEC", 272, tolerance->decimalPlaces());
21306 }
21307 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMALTTD)) {
21308 d.add("$DIMALTTD", 274, tolerance->decimalPlacesAltDim());
21309 }
21310 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOL)) {
21311 d.add("$DIMTOL", 71, tolerance->isAppendTolerancesToDimText() ? 1 : 0);
21312 }
21313 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTOLJ)) {
21314 d.add("$DIMTOLJ", 283, tolerance->verticalJustification());
21315 }
21316 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
21317 d.add("$DIMTM", 48, tolerance->lowerToleranceLimit());
21318 }
21319 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTM)) {
21320 d.add("$DIMTP", 47, tolerance->upperToleranceLimit());
21321 }
21322 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTFAC)) {
21323 d.add("$DIMTFAC", 146, tolerance->heightScaleFactorToDimText());
21324 }
21325 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMLIM)) {
21326 d.add("$DIMLIM", 72, tolerance->isLimitsGeneratedAsDefaultText() ? 1 : 0);
21327 }
21328}
21329
21330void RS_FilterDXFRW::prepareDRWDimStyleArc(DRW_Dimstyle &d,
21331 const LC_DimStyle *ds) {
21332 auto arc = ds->arc();
21333 if (arc->checkModifyState(LC_DimStyle::Arc::$DIMARCSYM)) {
21334 d.add("$DIMARCSYM", 90, arc->arcSymbolPosition());
21335 }
21336}
21337
21338void RS_FilterDXFRW::prepareDRWDimStyleLeader(DRW_Dimstyle &d,
21339 const LC_DimStyle *ds) {
21340 auto leader = ds->leader();
21341 if (leader->checkModifyState(LC_DimStyle::Leader::$DIMLDRBLK)) {
21342 QString blockName = leader->arrowBlockName();
21343 if (!blockName.isEmpty()) {
21344 auto blkName = blockName.toStdString();
21345 std::uint32_t blkHandle =
21346 m_dxfW != nullptr ? m_dxfW->getBlockRecordHandleToWrite(blkName)
21347 : DRW::NoHandle;
21348 if (blkHandle != DRW::NoHandle) {
21349 d.add("_$DIMLDRBLK", 341, toHexStr(blkHandle).toStdString());
21350 }
21351 }
21352 }
21353}
21354
21355void RS_FilterDXFRW::prepareDRWDimStyleExtData(DRW_Dimstyle &d,
21356 const LC_DimStyle *ds) {
21357 auto tolerance = ds->latteralTolerance();
21358 if (tolerance->checkModifyState(LC_DimStyle::LatteralTolerance::$DIMTALN)) {
21359 try {
21360 if (!d.addExtData(
21361 std::make_unique<DRW_Variant>(1001, "ACAD_DSTYLE_DIMTALN")) ||
21362 !d.addExtData(std::make_unique<DRW_Variant>(1070, 392)) ||
21363 !d.addExtData(
21364 std::make_unique<DRW_Variant>(1070, tolerance->adjustment()))) {
21365 m_writeFailed = true;
21366 }
21367 } catch (...) {
21368 m_writeFailed = true;
21369 }
21370 }
21371 // R2000/R2004 keep a style's own DIMLTYPE/DIMLTEX1/DIMLTEX2 in this XDATA
21372 // instead of the native groups 345-347 that prepareDRWDimStyleDimLine/
21373 // ExtLine write for > AC1018. writeDimstyles() replays a source table
21374 // entry's own raw extData verbatim right after this returns, byte for
21375 // byte, whenever this style came from one; only add these three ourselves
21376 // when there is no such source, so a style with a name a file already
21377 // carried never ends up with the pair written twice.
21378 if (m_dxfW != nullptr && m_dxfW->getVersion() > DRW::AC1014 &&
21379 m_dxfW->getVersion() <= DRW::AC1018 &&
21380 m_graphic->dwgAdvancedMetadata().findDimStyleTableEntryByName(
21381 d.name) == nullptr) {
21382 auto dimLine = ds->dimensionLine();
21383 auto extLine = ds->extensionLine();
21384 const auto addLineTypeXData = [&](const char *appName, int typeCode,
21385 const QString &name, bool modified) {
21386 if (!modified)
21387 return;
21388 const std::uint32_t handle = findLineTypeHandleToWrite(name);
21389 if (handle == DRW::NoHandle)
21390 return;
21391 try {
21392 if (!d.addExtData(std::make_unique<DRW_Variant>(1001, appName)) ||
21393 !d.addExtData(std::make_unique<DRW_Variant>(1070, typeCode)) ||
21394 !d.addExtData(std::make_unique<DRW_Variant>(
21395 1005, toHexStr(handle).toStdString()))) {
21396 m_writeFailed = true;
21397 }
21398 } catch (...) {
21399 m_writeFailed = true;
21400 }
21401 };
21402 addLineTypeXData(
21403 "ACAD_DSTYLE_DIM_LINETYPE", 380, dimLine->lineTypeName(),
21404 dimLine->checkModifyState(LC_DimStyle::DimensionLine::$DIMLTYPE));
21405 addLineTypeXData(
21406 "ACAD_DSTYLE_DIM_EXT1_LINETYPE", 381, extLine->lineTypeFirstRaw(),
21407 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX1));
21408 addLineTypeXData(
21409 "ACAD_DSTYLE_DIM_EXT2_LINETYPE", 382, extLine->lineTypeSecondRaw(),
21410 extLine->checkModifyState(LC_DimStyle::ExtensionLine::$DIMLTEX2));
21411 }
21412 // todo - add support of ACAD_DIMSTYLE_DIMBREAK
21413 // todo - add support of ACAD_DIMSTYLE_DIMJAG
21414}
21415
21416void RS_FilterDXFRW::prepareDRWDimStyle(DRW_Dimstyle &d,
21417 const LC_DimStyle *ds) {
21418 d.name = ds->getName().toStdString();
21419
21420 auto savedMode = ds->getModifyCheckMode();
21421 if (ds->isBaseStyle()) {
21422 // base styles are written completely, regardless of fields modification
21423 // state!
21424 ds->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
21425 }
21426
21427 // in AutoCAD, dimstyle that is specific for the type of dimension will
21428 // contain only values that are really overriden for the type. Other
21429 // properties will be extracted from the base style. therefore, have need to
21430 // check for properties that are modified and write only one that has modified
21431 // flag set...
21432
21433 prepareDRWDimStyleArrows(d, ds);
21434 prepareDRWDimStyleScaling(d, ds);
21435 prepareDRWDimStyleExtLine(d, ds);
21436 prepareDRWDimStyleDimLine(d, ds);
21437 prepareDRWDimStyleText(d, ds);
21438 prepareDRWDimStyleZerosSuppression(d, ds);
21439 prepareDRWDimStyleLinearFormat(d, ds);
21440 prepareDRWDimStyleAngularFormat(d, ds);
21441 prepareDRWDimStyleFractions(d, ds);
21442 prepareDRWDimStyleRadial(d, ds);
21443 prepareDRWDimStyleTolerance(d, ds);
21444 prepareDRWDimStyleArc(d, ds);
21445 prepareDRWDimStyleLeader(d, ds);
21446 prepareDRWDimStyleExtData(d, ds);
21447 // result->setDimunit(s.dimunit); // $DIMLUNIT
21448
21449 ds->setModifyCheckMode(savedMode); // restore modification flags
21450
21451 // fixme - return to this later, move to MLeaderStyle
21452 // auto mleader = ds->mleader();
21453 // var = s.get("$MLEADERSCALE");
21454 // if (var != nullptr) {
21455 // mleaderStyle->setScale(var->d_val());
21456 // }
21457}
21458
21459void RS_FilterDXFRW::writeObjects() {
21460 if (m_dwgW) {
21461 if (!validateDwgDataStorageAdmissionSummary(
21462 /*requireClassAdmission=*/true)) {
21463 m_writeFailed = true;
21464 m_dwgW->markDwgWriteFailure();
21465 RS_DEBUGRS_Debug::instance()->print(
21466 RS_Debug::D_WARNING,
21467 "RS_FilterDXFRW: DataStorage admission diverged before OBJECTS");
21468 return;
21469 }
21470 if (!validateDwgExportAdmissionPlan(DwgOpaqueTableFailurePhase::Objects)) {
21471 m_writeFailed = true;
21472 m_dwgW->markDwgWriteFailure();
21473 RS_DEBUGRS_Debug::instance()->print(
21474 RS_Debug::D_WARNING,
21475 "RS_FilterDXFRW: DWG export admission changed before OBJECTS");
21476 return;
21477 }
21478 if (!validateDwgFieldWritePlan(/*requireClassAdmission=*/true)) {
21479 m_writeFailed = true;
21480 m_dwgW->markDwgWriteFailure();
21481 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21482 "RS_FilterDXFRW: DWG FIELD admission changed before "
21483 "OBJECTS");
21484 return;
21485 }
21486 const auto &metadata = m_graphic->dwgAdvancedMetadata();
21487 const auto &rawObjects = metadata.rawObjects();
21488 // P3 #2: structural-collision remap built by fileExport — rewrite the
21489 // handle (and typed references to it) of preserved objects that
21490 // collide with the writer's fixed structural handles.
21491 auto remapHandle = [this](std::uint32_t h) -> std::uint32_t {
21492 return resolveDwgWriteHandle(h);
21493 };
21494 struct DwgWriteReferenceContext {
21495 DwgWriteSourceKind producerKind = DwgWriteSourceKind::Object;
21496 std::uint32_t producerSourceHandle = 0;
21497 std::uint32_t producerOutputHandle = 0;
21498 } referenceContext;
21499 const auto recordOptionalDrop =
21500 [this, &referenceContext](
21501 std::uint32_t targetSourceHandle,
21502 DwgWriteReferenceStatus targetStatus, const char *referenceName,
21503 std::uint32_t referenceOrdinal, const char *reason,
21504 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object) {
21505 if (referenceContext.producerOutputHandle == 0 ||
21506 targetSourceHandle == 0 || m_dwgW == nullptr ||
21507 referenceName == nullptr || reason == nullptr) {
21508 m_writeFailed = true;
21509 return false;
21510 }
21511 if (!recordDwgWriteOptionalDrop(
21512 referenceContext.producerKind,
21513 referenceContext.producerSourceHandle,
21514 referenceContext.producerOutputHandle, referenceName,
21515 referenceOrdinal, targetKind, targetSourceHandle,
21516 targetStatus, m_dwgW->getVersion(), "DWG OBJECTS", reason)) {
21517 m_writeFailed = true;
21518 return false;
21519 }
21520 return true;
21521 };
21522 const auto recordRetainedReference =
21523 [this, &referenceContext](
21524 std::uint32_t targetSourceHandle, std::uint32_t targetOutputHandle,
21525 DwgWriteSourceKind targetKind, const char *referenceName,
21526 std::uint32_t referenceOrdinal, std::uint8_t wireHandleCode,
21527 bool required) {
21528 if (referenceContext.producerOutputHandle == 0 ||
21529 (targetSourceHandle == 0 &&
21530 targetKind != DwgWriteSourceKind::FixedStructural) ||
21531 targetOutputHandle == 0 || m_dwgW == nullptr ||
21532 referenceName == nullptr) {
21533 m_writeFailed = true;
21534 return false;
21535 }
21536 const DwgWriteReferenceReceipt receipt{
21537 referenceContext.producerKind,
21538 referenceContext.producerSourceHandle,
21539 referenceContext.producerOutputHandle,
21540 referenceName,
21541 referenceOrdinal,
21542 targetKind,
21543 targetKind == DwgWriteSourceKind::FixedStructural
21544 ? 0
21545 : targetSourceHandle,
21546 targetOutputHandle,
21547 required,
21548 wireHandleCode};
21549 if (!recordDwgWriteReferenceReceipt(receipt)) {
21550 RS_DEBUGRS_Debug::instance()->print(
21551 RS_Debug::D_WARNING,
21552 "RS_FilterDXFRW: rejected DWG reference receipt "
21553 "producer=0x%X target=0x%X kind=%d name=%s ordinal=%u",
21554 referenceContext.producerSourceHandle, targetSourceHandle,
21555 static_cast<int>(targetKind), referenceName, referenceOrdinal);
21556 m_writeFailed = true;
21557 return false;
21558 }
21559 return true;
21560 };
21561 const auto isLiveDwgWriteReference = [](DwgWriteReferenceStatus status) {
21562 return status == DwgWriteReferenceStatus::Resolved ||
21563 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
21564 status == DwgWriteReferenceStatus::Reserved ||
21565 status == DwgWriteReferenceStatus::Selected ||
21566 status == DwgWriteReferenceStatus::Committed;
21567 };
21568 auto sanitizeOptionalCommonHandle =
21569 [this, &recordOptionalDrop, &recordRetainedReference,
21570 &isLiveDwgWriteReference](std::uint32_t &handle, const char *kind,
21571 std::uint32_t referenceOrdinal = 0,
21572 std::uint8_t wireHandleCode =
21573 DRW::DwgSoftPointer) {
21574 if (handle == 0)
21575 return;
21576 const std::uint32_t sourceHandle = handle;
21577 std::uint32_t outputHandle = 0;
21578 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
21579 const DwgWriteReferenceStatus status =
21580 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
21581 if (isLiveDwgWriteReference(status)) {
21582 if (!recordRetainedReference(sourceHandle, outputHandle, targetKind,
21583 kind, referenceOrdinal, wireHandleCode,
21584 false)) {
21585 handle = 0;
21586 return;
21587 }
21588 handle = outputHandle;
21589 return;
21590 }
21591 const char *reason = status == DwgWriteReferenceStatus::Ambiguous
21592 ? "ambiguous target"
21593 : status == DwgWriteReferenceStatus::Suppressed
21594 ? "suppressed target"
21595 : "missing target";
21596 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
21597 reason, targetKind)) {
21598 handle = 0;
21599 return;
21600 }
21601 RS_DEBUGRS_Debug::instance()->print(
21602 RS_Debug::D_WARNING,
21603 "RS_FilterDXFRW: dropping unresolved DWG %s target 0x%X", kind,
21604 handle);
21605 handle = 0;
21606 };
21607 auto sanitizeOptionalEntityHandle = [this, &recordOptionalDrop,
21608 &recordRetainedReference,
21609 &isLiveDwgWriteReference](
21610 std::uint32_t &handle,
21611 const char *kind,
21612 std::uint32_t referenceOrdinal = 0,
21613 std::uint8_t wireHandleCode =
21614 DRW::DwgSoftPointer) {
21615 if (handle == 0)
21616 return;
21617 const std::uint32_t sourceHandle = handle;
21618 std::uint32_t outputHandle = 0;
21619 const DwgWriteReferenceStatus status =
21620 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
21621 if (isLiveDwgWriteReference(status)) {
21622 if (!recordRetainedReference(sourceHandle, outputHandle,
21623 DwgWriteSourceKind::Entity, kind,
21624 referenceOrdinal, wireHandleCode, false)) {
21625 handle = 0;
21626 return;
21627 }
21628 handle = outputHandle;
21629 return;
21630 }
21631 const char *reason =
21632 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
21633 : status == DwgWriteReferenceStatus::Suppressed ? "suppressed target"
21634 : "missing target";
21635 if (!recordOptionalDrop(sourceHandle, status, kind, referenceOrdinal,
21636 reason, DwgWriteSourceKind::Entity)) {
21637 handle = 0;
21638 return;
21639 }
21640 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21641 "RS_FilterDXFRW: dropping unresolved DWG %s target 0x%X",
21642 kind, sourceHandle);
21643 handle = 0;
21644 };
21645 const auto resolveRequiredEntityHandle = [this](std::uint32_t sourceHandle,
21646 const char *referenceKind) {
21647 if (sourceHandle == 0) {
21648 m_writeFailed = true;
21649 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21650 "RS_FilterDXFRW: zero required DWG %s target",
21651 referenceKind == nullptr ? "entity" : referenceKind);
21652 return std::uint32_t{0};
21653 }
21654 std::uint32_t outputHandle = 0;
21655 const DwgWriteReferenceStatus status =
21656 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
21657 if (status != DwgWriteReferenceStatus::Committed || outputHandle == 0) {
21658 m_writeFailed = true;
21659 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21660 "RS_FilterDXFRW: unresolved required DWG %s target "
21661 "0x%X",
21662 referenceKind, sourceHandle);
21663 return std::uint32_t{0};
21664 }
21665 return outputHandle;
21666 };
21667 // Raw custom entities are written through the opaque-carrier identity
21668 // path, but they remain valid SORTENTSTABLE entity targets. Resolve that
21669 // identity only after the raw frame has committed; a missing or
21670 // uncommitted entity still fails the required-reference contract.
21671 const auto resolveRequiredEntityReference = [this, &rawObjects](
21672 std::uint32_t sourceHandle, const char *referenceKind)
21673 -> std::optional<std::pair<std::uint32_t, DwgWriteSourceKind>> {
21674 if (sourceHandle == 0) {
21675 m_writeFailed = true;
21676 RS_DEBUGRS_Debug::instance()->print(
21677 RS_Debug::D_WARNING, "RS_FilterDXFRW: zero required DWG %s target",
21678 referenceKind == nullptr ? "entity" : referenceKind);
21679 return std::nullopt;
21680 }
21681
21682 std::uint32_t outputHandle = 0;
21683 if (resolveDwgWriteEntityHandle(sourceHandle, &outputHandle) ==
21684 DwgWriteReferenceStatus::Committed &&
21685 outputHandle != 0) {
21686 return std::make_pair(outputHandle, DwgWriteSourceKind::Entity);
21687 }
21688
21689 const auto rawIt = std::find_if(
21690 rawObjects.cbegin(), rawObjects.cend(),
21691 [sourceHandle](const auto &raw) {
21692 return raw.isEntity && raw.handle == sourceHandle;
21693 });
21694 const DwgWriteIdentityLookup rawIdentityStatus =
21695 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier, sourceHandle,
21696 &outputHandle);
21697 if (rawIt != rawObjects.cend() &&
21698 rawIdentityStatus == DwgWriteIdentityLookup::Committed &&
21699 outputHandle != 0) {
21700 return std::make_pair(outputHandle, DwgWriteSourceKind::RawCarrier);
21701 }
21702
21703 m_writeFailed = true;
21704 RS_DEBUGRS_Debug::instance()->print(
21705 RS_Debug::D_WARNING,
21706 "RS_FilterDXFRW: unresolved required DWG %s target 0x%X",
21707 referenceKind, sourceHandle);
21708 return std::nullopt;
21709 };
21710 const auto remapRequiredEntityHandle =
21711 [&resolveRequiredEntityHandle, &recordRetainedReference](
21712 std::uint32_t sourceHandle, const char *referenceKind,
21713 std::uint32_t referenceOrdinal = 0,
21714 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
21715 const std::uint32_t outputHandle =
21716 resolveRequiredEntityHandle(sourceHandle, referenceKind);
21717 if (outputHandle == 0 ||
21718 !recordRetainedReference(
21719 sourceHandle, outputHandle, DwgWriteSourceKind::Entity,
21720 referenceKind, referenceOrdinal, wireHandleCode, true))
21721 return std::uint32_t{0};
21722 return outputHandle;
21723 };
21724 const auto remapRequiredObjectHandle =
21725 [this, &recordRetainedReference](
21726 std::uint32_t sourceHandle, const char *referenceKind,
21727 std::uint32_t referenceOrdinal = 0,
21728 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
21729 if (sourceHandle == 0)
21730 return std::uint32_t{0};
21731 std::uint32_t outputHandle = 0;
21732 const DwgWriteReferenceStatus status =
21733 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
21734 if ((status != DwgWriteReferenceStatus::Resolved &&
21735 status != DwgWriteReferenceStatus::Selected &&
21736 status != DwgWriteReferenceStatus::Committed) ||
21737 outputHandle == 0) {
21738 m_writeFailed = true;
21739 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21740 "RS_FilterDXFRW: unresolved required DWG %s target "
21741 "0x%X",
21742 referenceKind, sourceHandle);
21743 return std::uint32_t{0};
21744 }
21745 if (!recordRetainedReference(
21746 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
21747 referenceKind, referenceOrdinal, wireHandleCode, true))
21748 return std::uint32_t{0};
21749 return outputHandle;
21750 };
21751 const auto remapRequiredEntityList =
21752 [&remapRequiredEntityHandle](std::vector<std::uint32_t> &handles,
21753 const char *referenceKind) {
21754 std::vector<std::uint32_t> remapped;
21755 remapped.reserve(handles.size());
21756 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
21757 const std::uint32_t sourceHandle = handles[ordinal];
21758 if (sourceHandle == 0)
21759 return false;
21760 const std::uint32_t outputHandle = remapRequiredEntityHandle(
21761 sourceHandle, referenceKind,
21762 static_cast<std::uint32_t>(ordinal), DRW::DwgHardPointer);
21763 if (outputHandle == 0)
21764 return false;
21765 remapped.push_back(outputHandle);
21766 }
21767 handles = std::move(remapped);
21768 return true;
21769 };
21770 auto remapEntityHandle = [this](std::uint32_t sourceHandle,
21771 const char *referenceKind) {
21772 std::uint32_t outputHandle = 0;
21773 const DwgWriteReferenceStatus status =
21774 resolveDwgWriteEntityHandle(sourceHandle, &outputHandle);
21775 if (status != DwgWriteReferenceStatus::Committed) {
21776 const bool ambiguous = status == DwgWriteReferenceStatus::Ambiguous;
21777 RS_DEBUGRS_Debug::instance()->print(
21778 RS_Debug::D_WARNING,
21779 ambiguous ? "RS_FilterDXFRW: dropping ambiguous DWG %s target "
21780 "0x%X"
21781 : "RS_FilterDXFRW: dropping dangling DWG %s target "
21782 "0x%X",
21783 referenceKind, sourceHandle);
21784 return std::uint32_t{0};
21785 }
21786 return outputHandle;
21787 };
21788 auto remapSectionObjectHandle =
21789 [this, &remapHandle, &remapEntityHandle](std::uint32_t sourceHandle,
21790 const char *referenceKind) {
21791 if (sourceHandle == 0)
21792 return std::uint32_t{0};
21793
21794 const auto &sectionObjects =
21795 m_graphic->dwgAdvancedMetadata().sectionObjects();
21796 const auto sectionIt = std::find_if(
21797 sectionObjects.cbegin(), sectionObjects.cend(),
21798 [sourceHandle](const auto &record) {
21799 return record.handle == sourceHandle &&
21800 record.replayState ==
21801 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed;
21802 });
21803 if (sectionIt != sectionObjects.cend())
21804 return remapHandle(sourceHandle);
21805
21806 return remapEntityHandle(sourceHandle, referenceKind);
21807 };
21808 auto remapBlockOwnerHandle = [this, &referenceContext, &recordOptionalDrop,
21809 &recordRetainedReference](
21810 std::uint32_t sourceHandle,
21811 const char *referenceKind =
21812 "DWG block owner",
21813 std::uint32_t referenceOrdinal = 0,
21814 std::uint8_t wireHandleCode =
21815 DRW::DwgSoftPointer,
21816 bool recordReceipt = true) {
21817 if (sourceHandle == DRW::NoHandle)
21818 return std::uint32_t{0};
21819 std::uint32_t outputHandle = 0;
21820 const DwgWriteReferenceStatus status =
21821 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
21822 if (status == DwgWriteReferenceStatus::LegacySpaceAlias ||
21823 status == DwgWriteReferenceStatus::Selected ||
21824 status == DwgWriteReferenceStatus::Committed) {
21825 const DwgWriteSourceKind targetKind =
21826 status == DwgWriteReferenceStatus::LegacySpaceAlias
21827 ? DwgWriteSourceKind::FixedStructural
21828 : DwgWriteSourceKind::Block;
21829 const std::uint32_t targetSourceHandle =
21830 status == DwgWriteReferenceStatus::LegacySpaceAlias ? 0
21831 : sourceHandle;
21832 if (recordReceipt && referenceContext.producerOutputHandle != 0 &&
21833 !recordRetainedReference(targetSourceHandle, outputHandle,
21834 targetKind, referenceKind,
21835 referenceOrdinal, wireHandleCode, false))
21836 return std::uint32_t{0};
21837 return outputHandle;
21838 }
21839 const char *reason = nullptr;
21840 if (status == DwgWriteReferenceStatus::Ambiguous)
21841 reason = "ambiguous target";
21842 else if (status == DwgWriteReferenceStatus::Suppressed)
21843 reason = "suppressed target";
21844 else if (status == DwgWriteReferenceStatus::Missing)
21845 reason = "missing target";
21846 if (reason != nullptr && referenceContext.producerOutputHandle != 0 &&
21847 !recordOptionalDrop(sourceHandle, status, referenceKind,
21848 referenceOrdinal, reason,
21849 DwgWriteSourceKind::Block))
21850 return std::uint32_t{0};
21851 if (status == DwgWriteReferenceStatus::Ambiguous) {
21852 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21853 "RS_FilterDXFRW: dropping ambiguous DWG %s 0x%X",
21854 referenceKind, sourceHandle);
21855 return std::uint32_t{0};
21856 }
21857 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21858 "RS_FilterDXFRW: dropping dangling DWG %s 0x%X",
21859 referenceKind, sourceHandle);
21860 return std::uint32_t{0};
21861 };
21862 const auto remapRequiredSortBlockOwnerHandle =
21863 [this, &recordRetainedReference](std::uint32_t sourceHandle) {
21864 if (sourceHandle == 0) {
21865 m_writeFailed = true;
21866 return std::uint32_t{0};
21867 }
21868 std::uint32_t outputHandle = 0;
21869 const DwgWriteReferenceStatus status =
21870 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
21871 if ((status != DwgWriteReferenceStatus::LegacySpaceAlias &&
21872 status != DwgWriteReferenceStatus::Selected &&
21873 status != DwgWriteReferenceStatus::Committed) ||
21874 outputHandle == 0) {
21875 m_writeFailed = true;
21876 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21877 "RS_FilterDXFRW: unresolved required DWG "
21878 "SORTENTSTABLE block owner 0x%X",
21879 sourceHandle);
21880 return std::uint32_t{0};
21881 }
21882 const DwgWriteSourceKind targetKind =
21883 status == DwgWriteReferenceStatus::LegacySpaceAlias
21884 ? DwgWriteSourceKind::FixedStructural
21885 : DwgWriteSourceKind::Block;
21886 const std::uint32_t targetSourceHandle =
21887 status == DwgWriteReferenceStatus::LegacySpaceAlias
21888 ? 0
21889 : sourceHandle;
21890 if (!recordRetainedReference(targetSourceHandle, outputHandle,
21891 targetKind, "SORTENTSTABLE block owner",
21892 0, DRW::DwgSoftPointer, true))
21893 return std::uint32_t{0};
21894 return outputHandle;
21895 };
21896 auto remapObjectOwnerHandle =
21897 [this, &referenceContext, &recordOptionalDrop,
21898 &recordRetainedReference, &remapHandle](
21899 std::uint32_t sourceHandle,
21900 const char *referenceKind = "DWG object owner",
21901 std::uint32_t referenceOrdinal = 0,
21902 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
21903 if (sourceHandle == DRW::NoHandle)
21904 return std::uint32_t{0};
21905 // A source handle can collide with a fixed block record while also
21906 // identifying a preserved dictionary. For OBJECTS owners, the
21907 // dictionary identity is authoritative; block-owned entities use
21908 // remapBlockOwnerHandle separately.
21909 const auto &dictionaries =
21910 m_graphic->dwgAdvancedMetadata().dictionaries();
21911 const bool isRemappedDictionary =
21912 hasDwgWriteHandleRemap(sourceHandle) &&
21913 std::any_of(dictionaries.cbegin(), dictionaries.cend(),
21914 [sourceHandle](const auto &record) {
21915 return record.handle == sourceHandle &&
21916 record.handle !=
21917 DRW::DwgNamedObjectsDictionaryHandle &&
21918 record.handle !=
21919 DRW::DwgAcadGroupDictionaryHandle;
21920 });
21921 if (isRemappedDictionary) {
21922 const std::uint32_t outputHandle = remapHandle(sourceHandle);
21923 if (referenceContext.producerOutputHandle != 0 &&
21924 !recordRetainedReference(
21925 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
21926 referenceKind, referenceOrdinal, wireHandleCode, false))
21927 return std::uint32_t{0};
21928 return outputHandle;
21929 }
21930 std::uint32_t outputHandle = 0;
21931 const DwgWriteReferenceStatus status =
21932 resolveDwgWriteOwnerHandle(sourceHandle, &outputHandle);
21933 if (status == DwgWriteReferenceStatus::Resolved ||
21934 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
21935 status == DwgWriteReferenceStatus::Selected ||
21936 status == DwgWriteReferenceStatus::Committed) {
21937 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
21938 std::uint32_t blockOutputHandle = 0;
21939 const DwgWriteReferenceStatus blockStatus =
21940 resolveDwgWriteBlockOwner(sourceHandle, &blockOutputHandle);
21941 const DwgWriteIdentityLookup rawIdentityStatus =
21942 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier,
21943 sourceHandle);
21944 if (status == DwgWriteReferenceStatus::LegacySpaceAlias) {
21945 targetKind = DwgWriteSourceKind::FixedStructural;
21946 sourceHandle = 0;
21947 } else if (blockStatus == DwgWriteReferenceStatus::Reserved ||
21948 blockStatus == DwgWriteReferenceStatus::Selected ||
21949 blockStatus == DwgWriteReferenceStatus::Committed) {
21950 targetKind = DwgWriteSourceKind::Block;
21951 } else if (rawIdentityStatus == DwgWriteIdentityLookup::Reserved ||
21952 rawIdentityStatus == DwgWriteIdentityLookup::Selected ||
21953 rawIdentityStatus == DwgWriteIdentityLookup::Committed) {
21954 targetKind = DwgWriteSourceKind::RawCarrier;
21955 }
21956 if (referenceContext.producerOutputHandle != 0 &&
21957 !recordRetainedReference(sourceHandle, outputHandle, targetKind,
21958 referenceKind, referenceOrdinal,
21959 wireHandleCode, false))
21960 return std::uint32_t{0};
21961 return outputHandle;
21962 }
21963 const char *reason = nullptr;
21964 if (status == DwgWriteReferenceStatus::Ambiguous)
21965 reason = "ambiguous target";
21966 else if (status == DwgWriteReferenceStatus::Suppressed)
21967 reason = "suppressed target";
21968 else if (status == DwgWriteReferenceStatus::Missing)
21969 reason = "missing target";
21970 if (reason != nullptr && referenceContext.producerOutputHandle != 0 &&
21971 !recordOptionalDrop(sourceHandle, status, referenceKind,
21972 referenceOrdinal, reason,
21973 DwgWriteSourceKind::Object))
21974 return std::uint32_t{0};
21975 if (status == DwgWriteReferenceStatus::Ambiguous) {
21976 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21977 "RS_FilterDXFRW: dropping ambiguous DWG %s 0x%X",
21978 referenceKind, sourceHandle);
21979 return std::uint32_t{0};
21980 }
21981 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
21982 "RS_FilterDXFRW: dropping dangling DWG %s 0x%X",
21983 referenceKind, sourceHandle);
21984 return std::uint32_t{0};
21985 };
21986 const auto remapNamedTableHandle =
21987 [this, &referenceContext, &recordOptionalDrop, &recordRetainedReference,
21988 &isLiveDwgWriteReference](
21989 std::uint32_t sourceHandle,
21990 const std::map<std::uint32_t, std::uint32_t> &remap,
21991 const char *referenceKind, std::uint32_t referenceOrdinal = 0,
21992 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
21993 if (sourceHandle == DRW::NoHandle)
21994 return std::uint32_t{0};
21995 std::uint32_t outputHandle = 0;
21996 const DwgWriteReferenceStatus status =
21997 resolveDwgWriteTableRecordHandle(sourceHandle, remap,
21998 &outputHandle);
21999 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
22000 if (status == DwgWriteReferenceStatus::Missing ||
22001 status == DwgWriteReferenceStatus::Ambiguous ||
22002 status == DwgWriteReferenceStatus::Suppressed) {
22003 if (!recordOptionalDrop(
22004 sourceHandle, status, referenceKind, referenceOrdinal,
22005 status == DwgWriteReferenceStatus::Suppressed
22006 ? "suppressed target"
22007 : status == DwgWriteReferenceStatus::Ambiguous
22008 ? "ambiguous target"
22009 : "missing target",
22010 DwgWriteSourceKind::TableRecord))
22011 return std::uint32_t{0};
22012 }
22013 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22014 "RS_FilterDXFRW: dropping dangling DWG %s target "
22015 "0x%X",
22016 referenceKind, sourceHandle);
22017 return std::uint32_t{0};
22018 }
22019 // Plot settings may be emitted without a source-backed OBJECT
22020 // identity. In that case there is no producer receipt to
22021 // attach; source-backed metadata objects always have context.
22022 if (referenceContext.producerOutputHandle != 0 &&
22023 !recordRetainedReference(
22024 sourceHandle, outputHandle, DwgWriteSourceKind::TableRecord,
22025 referenceKind, referenceOrdinal, wireHandleCode, false))
22026 return std::uint32_t{0};
22027 return outputHandle;
22028 };
22029 auto remapViewHandle =
22030 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
22031 const char *referenceKind,
22032 std::uint32_t referenceOrdinal = 0) {
22033 return remapNamedTableHandle(sourceHandle, m_dwgWriteViewHandleRemap,
22034 referenceKind, referenceOrdinal,
22035 DRW::DwgHardPointer);
22036 };
22037 auto remapUcsHandle =
22038 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
22039 const char *referenceKind,
22040 std::uint32_t referenceOrdinal = 0) {
22041 return remapNamedTableHandle(sourceHandle, m_dwgWriteUcsHandleRemap,
22042 referenceKind, referenceOrdinal,
22043 DRW::DwgHardPointer);
22044 };
22045 auto remapVportHandle =
22046 [this, &remapNamedTableHandle](std::uint32_t sourceHandle,
22047 const char *referenceKind,
22048 std::uint32_t referenceOrdinal = 0) {
22049 return remapNamedTableHandle(sourceHandle, m_dwgWriteVportHandleRemap,
22050 referenceKind, referenceOrdinal,
22051 DRW::DwgSoftPointer);
22052 };
22053 auto remapTableHandle =
22054 [&referenceContext, &recordOptionalDrop,
22055 &recordRetainedReference](
22056 std::uint32_t sourceHandle, const std::string &sourceName,
22057 const std::map<std::string, std::uint32_t> &outputHandles,
22058 const char *referenceKind, std::uint32_t referenceOrdinal = 0,
22059 std::uint8_t wireHandleCode = DRW::DwgHardPointer) {
22060 if (sourceHandle == DRW::NoHandle)
22061 return std::uint32_t{0};
22062 if (sourceName.empty()) {
22063 (void)recordOptionalDrop(
22064 sourceHandle, DwgWriteReferenceStatus::Missing, referenceKind,
22065 referenceOrdinal, "missing source name",
22066 DwgWriteSourceKind::TableRecord);
22067 RS_DEBUGRS_Debug::instance()->print(
22068 RS_Debug::D_WARNING,
22069 "RS_FilterDXFRW: dropping DWG %s 0x%X without a source name",
22070 referenceKind, sourceHandle);
22071 return std::uint32_t{0};
22072 }
22073 const auto it = outputHandles.find(normalizeDwgTableName(sourceName));
22074 if (it == outputHandles.end()) {
22075 (void)recordOptionalDrop(
22076 sourceHandle, DwgWriteReferenceStatus::Missing, referenceKind,
22077 referenceOrdinal, "missing target",
22078 DwgWriteSourceKind::TableRecord);
22079 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22080 "RS_FilterDXFRW: dropping unresolved DWG %s '%s'",
22081 referenceKind, sourceName.c_str());
22082 return std::uint32_t{0};
22083 }
22084 if (referenceContext.producerOutputHandle != 0 &&
22085 !recordRetainedReference(
22086 sourceHandle, it->second, DwgWriteSourceKind::TableRecord,
22087 referenceKind, referenceOrdinal, wireHandleCode, false))
22088 return std::uint32_t{0};
22089 return it->second;
22090 };
22091 auto remapEntryHandle = [&remapHandle,
22092 &referenceContext](DRW_TableEntry &e) {
22093 const std::uint32_t sourceHandle = e.handle;
22094 e.handle = remapHandle(e.handle);
22095 referenceContext.producerKind = DwgWriteSourceKind::Object;
22096 referenceContext.producerSourceHandle = sourceHandle;
22097 referenceContext.producerOutputHandle = e.handle;
22098 };
22099 auto remapEntry = [&remapEntryHandle,
22100 &remapObjectOwnerHandle](DRW_TableEntry &e) {
22101 remapEntryHandle(e);
22102 if (e.parentHandle != 0)
22103 e.parentHandle = remapObjectOwnerHandle(e.parentHandle);
22104 };
22105 auto sanitizeDictionaryOwner = [this,
22106 &remapHandle](std::uint32_t &parentHandle,
22107 const char *kind) {
22108 if (parentHandle == 0)
22109 return; // The root named-object dictionary intentionally has no owner.
22110 const std::uint32_t sourceHandle = parentHandle;
22111 if (!hasDwgWriteKnownHandle(sourceHandle)) {
22112 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22113 "RS_FilterDXFRW: reparenting dangling DWG %s owner "
22114 "0x%X to root 0x%X",
22115 kind, sourceHandle,
22116 DRW::DwgNamedObjectsDictionaryHandle);
22117 parentHandle = DRW::DwgNamedObjectsDictionaryHandle;
22118 return;
22119 }
22120 parentHandle = remapHandle(sourceHandle);
22121 };
22122 const bool canWriteModernObjects = m_dwgW->getVersion() >= DRW::AC1021;
22123 // PR 13a/b/c/d — DICTIONARY (ODA fixed type 42), XRECORD (type 79),
22124 // GROUP (type 72), LAYOUT (type 82), and ACDBPLACEHOLDER (type 80)
22125 // are universally available since R2000. Their encoders gate
22126 // AC1018+-only fields (e.g., LAYOUT shadePlot* + viewportCount
22127 // RawLong32 + plotViewHandle) on `version >= DRW::AC1018`, and
22128 // the AC1015-only plotViewName branch on `version < DRW::AC1018`.
22129 // ACDBPLACEHOLDER's encoder has no version-gated body fields —
22130 // only the standard string/handle split-buffer routing on
22131 // `version > AC1018`. Encoder smoke tests cover
22132 // AC1015/AC1018/AC1024/AC1027/AC1032 round-trip (see
22133 // `[dictionary]` / `[xrecord]` / `[group]` / `[layout]` /
22134 // `[placeholder]` cases). Broaden the dispatch gate for these
22135 // families from AC1021+ to AC1015+ ahead of the long-tail
22136 // families which still need their own validation.
22137 const bool canWriteFixedTypeObjects = m_dwgW->getVersion() >= DRW::AC1015;
22138 const bool canWriteDbColor = m_dwgW->getVersion() >= DRW::AC1018;
22139 const bool canWriteAssociativeObjects = m_dwgW->getVersion() >= DRW::AC1021;
22140 auto sanitizeDictionaryEntries =
22141 [this, &metadata, &recordOptionalDrop, &recordRetainedReference,
22142 &isLiveDwgWriteReference](DRW_Dictionary &dictionary) {
22143 std::vector<DRW_Dictionary::Entry> remapped;
22144 remapped.reserve(dictionary.m_entries.size());
22145 for (std::size_t ordinal = 0; ordinal < dictionary.m_entries.size();
22146 ++ordinal) {
22147 const DRW_Dictionary::Entry &entry = dictionary.m_entries[ordinal];
22148 if (entry.m_name.empty() || entry.m_handle == 0) {
22149 m_writeFailed = true;
22150 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22151 "RS_FilterDXFRW: invalid required DWG dictionary "
22152 "entry at index %d",
22153 static_cast<int>(ordinal));
22154 return false;
22155 }
22156 std::uint32_t outputHandle = 0;
22157 const DwgWriteReferenceStatus status =
22158 resolveDwgWriteObjectHandle(entry.m_handle, &outputHandle);
22159 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
22160 // A declared raw source object that is unavailable in the target
22161 // version is different from a genuinely dangling member. Do
22162 // not silently sever that ownership edge.
22163 if (metadata.hasRawObjectForHandle(entry.m_handle)) {
22164 m_writeFailed = true;
22165 RS_DEBUGRS_Debug::instance()->print(
22166 RS_Debug::D_WARNING,
22167 "RS_FilterDXFRW: unresolved DWG DICTIONARY "
22168 "entry target 0x%X",
22169 entry.m_handle);
22170 return false;
22171 }
22172 if (status != DwgWriteReferenceStatus::Missing &&
22173 status != DwgWriteReferenceStatus::Suppressed &&
22174 status != DwgWriteReferenceStatus::Ambiguous) {
22175 m_writeFailed = true;
22176 RS_DEBUGRS_Debug::instance()->print(
22177 RS_Debug::D_WARNING,
22178 "RS_FilterDXFRW: unresolved DWG DICTIONARY "
22179 "entry target 0x%X",
22180 entry.m_handle);
22181 return false;
22182 }
22183 if (!recordOptionalDrop(
22184 entry.m_handle, status, "DICTIONARY entry",
22185 static_cast<std::uint32_t>(ordinal),
22186 status == DwgWriteReferenceStatus::Ambiguous
22187 ? "ambiguous target"
22188 : status == DwgWriteReferenceStatus::Suppressed
22189 ? "suppressed target"
22190 : "missing target"))
22191 return false;
22192 RS_DEBUGRS_Debug::instance()->print(
22193 RS_Debug::D_WARNING,
22194 "RS_FilterDXFRW: dropping dangling DWG DICTIONARY entry "
22195 "target 0x%X",
22196 entry.m_handle);
22197 continue;
22198 }
22199 if (!recordRetainedReference(
22200 entry.m_handle, outputHandle,
22201 DwgWriteSourceKind::Object, "DICTIONARY entry",
22202 static_cast<std::uint32_t>(ordinal),
22203 dictionary.dwgItemHandleCode(), true))
22204 return false;
22205 remapped.push_back({entry.m_name, outputHandle});
22206 }
22207 dictionary.m_entries = std::move(remapped);
22208 return true;
22209 };
22210 auto sanitizeObjectHandle = [this, &remapHandle, &recordOptionalDrop,
22211 &recordRetainedReference](
22212 std::uint32_t &handle, const char *kind,
22213 std::uint32_t referenceOrdinal = 0,
22214 std::uint8_t wireHandleCode =
22215 DRW::DwgSoftPointer) {
22216 if (handle == 0)
22217 return;
22218 const std::uint32_t sourceHandle = handle;
22219 std::uint32_t outputHandle = 0;
22220 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
22221 const DwgWriteReferenceStatus status =
22222 resolveDwgWriteCommonHandle(handle, &outputHandle, &targetKind);
22223 if (status == DwgWriteReferenceStatus::Ambiguous) {
22224 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
22225 "ambiguous target", targetKind)) {
22226 handle = 0;
22227 return;
22228 }
22229 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22230 "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X",
22231 kind, handle);
22232 handle = 0;
22233 return;
22234 }
22235 if (status == DwgWriteReferenceStatus::Missing ||
22236 status == DwgWriteReferenceStatus::Suppressed) {
22237 if (!recordOptionalDrop(handle, status, kind, referenceOrdinal,
22238 status == DwgWriteReferenceStatus::Suppressed
22239 ? "suppressed target"
22240 : "missing target",
22241 targetKind)) {
22242 handle = 0;
22243 return;
22244 }
22245 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22246 "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
22247 kind, handle);
22248 handle = 0;
22249 return;
22250 }
22251 if (!recordRetainedReference(sourceHandle, outputHandle, targetKind, kind,
22252 referenceOrdinal, wireHandleCode, false)) {
22253 handle = 0;
22254 return;
22255 }
22256 handle = outputHandle != 0 ? outputHandle : remapHandle(handle);
22257 };
22258 auto sanitizeCommonObjectReferences = [&sanitizeObjectHandle](
22259 DRW_TableEntry &entry,
22260 const char *kind) {
22261 for (std::size_t index = 0; index < entry.reactorHandles.size();
22262 ++index) {
22263 sanitizeObjectHandle(entry.reactorHandles[index], kind,
22264 static_cast<std::uint32_t>(index),
22265 DRW::DwgSoftPointer);
22266 }
22267 entry.reactorHandles.erase(std::remove(entry.reactorHandles.begin(),
22268 entry.reactorHandles.end(), 0),
22269 entry.reactorHandles.end());
22270 sanitizeObjectHandle(entry.xDictHandle, kind,
22271 std::numeric_limits<std::uint32_t>::max(),
22272 DRW::DwgHardOwnership);
22273 const std::uint8_t xDictFlag =
22274 entry.xDictHandle != 0 ? entry.extensionDictionaryFlag() : 0;
22275 if (entry.reactorHandles.size() >
22276 static_cast<std::size_t>(std::numeric_limits<std::int32_t>::max())) {
22277 return false;
22278 }
22279 entry.setDwgCommonObjectState(
22280 static_cast<std::int32_t>(entry.reactorHandles.size()), xDictFlag,
22281 entry.hasDataStorageBinaryData());
22282 return true;
22283 };
22284 // Select an OBJECT carrier only for the duration of its actual writer
22285 // call. Dependent receipts must be admitted before this transaction
22286 // commits, otherwise a late validation failure leaves no retry point.
22287 const auto writeDwgObjectWithCompletion =
22288 [this, &remapHandle](
22289 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind kind,
22290 std::uint32_t sourceHandle, const auto &write, const auto &complete,
22291 std::uint32_t sourceOrdinal = 0) {
22292 const bool isGenerated = sourceHandle == 0;
22293 if (m_writeFailed ||
22294 (isGenerated ? sourceOrdinal == 0 : sourceOrdinal != 0))
22295 return false;
22296 if (!isGenerated && isDwgWriteSourceAmbiguous(sourceHandle, kind)) {
22297 m_writeFailed = true;
22298 return false;
22299 }
22300 const auto identityCheckpoint = checkpointDwgWriteIdentity();
22301 const std::uint32_t outputHandle =
22302 isGenerated ? 0 : remapHandle(sourceHandle);
22303 if (!isGenerated &&
22304 !selectDwgWriteIdentity(kind, sourceHandle, outputHandle)) {
22305 m_writeFailed = true;
22306 return false;
22307 }
22308 std::uint16_t admittedClassNumber = 0;
22309 bool classAdmissionCommitted = false;
22310 const bool hasClassAdmission = findDwgClassFrameAdmission(
22311 kind, sourceHandle, outputHandle, admittedClassNumber,
22312 classAdmissionCommitted, sourceOrdinal);
22313 std::uint16_t writerClassNumber = 0;
22314 const bool hasWriterClass =
22315 !isGenerated &&
22316 m_dwgW->getDwgClassInstanceClass(outputHandle, writerClassNumber);
22317 const bool classAdmissionMismatch =
22318 hasClassAdmission &&
22319 (classAdmissionCommitted ||
22320 (!isGenerated && admittedClassNumber != writerClassNumber));
22321 if ((!isGenerated && hasClassAdmission != hasWriterClass) ||
22322 (isGenerated && !hasClassAdmission) || classAdmissionMismatch) {
22323 restoreDwgWriteIdentity(identityCheckpoint);
22324 m_writeFailed = true;
22325 return false;
22326 }
22327 const std::uint64_t transaction =
22328 m_dwgW->beginDwgWriteTransaction(false, false, false);
22329 if (transaction == 0) {
22330 restoreDwgWriteIdentity(identityCheckpoint);
22331 m_writeFailed = true;
22332 return false;
22333 }
22334 std::uint32_t dataStorageCommitHandle = 0;
22335 std::uint32_t recordedOutputHandle = 0;
22336 bool objectOccurrencesRecorded = false;
22337 const auto rollback = [&]() {
22338 m_dwgW->clearDwgObjectFrameProvenance();
22339 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
22340 if (objectOccurrencesRecorded)
22341 (void)rollbackDwgWriteObjectOccurrences(recordedOutputHandle);
22342 if (dataStorageCommitHandle != 0) {
22343 m_dwgDataStorageCommittedKinds.erase(dataStorageCommitHandle);
22344 failDwgDataStorageCommit(dataStorageCommitHandle);
22345 }
22346 restoreDwgWriteIdentity(identityCheckpoint);
22347 m_writeFailed = true;
22348 };
22349 const DwgDataStorageCarrier::Kind actualKind =
22350 kind == DwgWriteSourceKind::RawCarrier
22351 ? DwgDataStorageCarrier::Kind::Raw
22352 : DwgDataStorageCarrier::Kind::Typed;
22353 if (!bindDwgDataStorageFrame(sourceHandle, actualKind)) {
22354 rollback();
22355 return false;
22356 }
22357 // Class-backed ordinary OBJECTS registrations are admitted by the
22358 // low-level writer before CLASSES is serialized. Bind that class
22359 // to this frame too; DataStorage bindings above already include
22360 // their token and must not be replaced.
22361 if (hasClassAdmission &&
22362 dataStorageDecisionForOwner(sourceHandle) == nullptr) {
22363 if (!m_dwgW->setDwgObjectFrameProvenance(admittedClassNumber, 0,
22364 0)) {
22365 rollback();
22366 return false;
22367 }
22368 }
22369 if (!write()) {
22370 rollback();
22371 return false;
22372 }
22373 DRW::DwgObjectFrameReceipt frame;
22374 if (!m_dwgW->getLastDwgObjectFrame(frame) || !frame.valid ||
22375 frame.objectHandle == 0) {
22376 rollback();
22377 return false;
22378 }
22379 recordedOutputHandle =
22380 isGenerated ? frame.objectHandle : outputHandle;
22381 if (isGenerated &&
22382 !bindDwgClassFrameOutput(kind, sourceHandle, sourceOrdinal,
22383 recordedOutputHandle)) {
22384 rollback();
22385 return false;
22386 }
22387 if (!recordDwgWriteObjectOccurrences(recordedOutputHandle, frame)) {
22388 rollback();
22389 return false;
22390 }
22391 if (hasClassAdmission && frame.classNumber != admittedClassNumber) {
22392 rollback();
22393 return false;
22394 }
22395 objectOccurrencesRecorded = true;
22396 if (!complete() || m_writeFailed) {
22397 rollback();
22398 return false;
22399 }
22400 if (m_dwgW->getVersion() > DRW::AC1024) {
22401 const auto decisionIt =
22402 m_dwgDataStorageReplayPlan.decisionByOwner.find(sourceHandle);
22403 if (decisionIt !=
22404 m_dwgDataStorageReplayPlan.decisionByOwner.end() &&
22405 decisionIt->second <
22406 m_dwgDataStorageReplayPlan.decisions.size()) {
22407 const auto &decision =
22408 m_dwgDataStorageReplayPlan.decisions[decisionIt->second];
22409 const DwgDataStorageCarrier::WriterCapability actualCapability =
22410 kind == DwgWriteSourceKind::RawCarrier
22411 ? DwgDataStorageCarrier::WriterCapability::Raw
22412 : DwgDataStorageCarrier::WriterCapability::Typed;
22413 const std::size_t actualCarrierIndex =
22414 kind == DwgWriteSourceKind::RawCarrier
22415 ? decision.rawCarrierIndex
22416 : decision.typedCarrierIndex;
22417 const DwgDataStorageCarrier::Kind expectedKind =
22418 decision.decision == DwgDataStorageDecision::EmitRaw
22419 ? DwgDataStorageCarrier::Kind::Raw
22420 : DwgDataStorageCarrier::Kind::Typed;
22421 dataStorageCommitHandle = sourceHandle;
22422 DwgWriteSourceKind ledgerSourceKind = DwgWriteSourceKind::Object;
22423 if (kind == DwgWriteSourceKind::RawCarrier &&
22424 decision.rawCarrierIndex <
22425 m_dwgDataStorageReplayPlan.carriers.size()) {
22426 const auto &rawCarrier =
22427 m_dwgDataStorageReplayPlan
22428 .carriers[decision.rawCarrierIndex];
22429 const auto &metadata = m_graphic->dwgAdvancedMetadata();
22430 if (rawCarrier.rawObjectIndex < metadata.rawObjects().size()) {
22431 ledgerSourceKind =
22432 metadata.rawObjects()[rawCarrier.rawObjectIndex].isEntity
22433 ? DwgWriteSourceKind::Entity
22434 : DwgWriteSourceKind::Object;
22435 }
22436 }
22437 if (actualKind != expectedKind ||
22438 actualCarrierIndex != decision.selectedCarrierIndex ||
22439 decision.selectedPhase !=
22440 DwgDataStorageCarrier::EmissionPhase::Objects ||
22441 decision.selectedCapability != actualCapability ||
22442 !recordDwgDataStorageObjectCommit(
22443 sourceHandle, actualKind, ledgerSourceKind,
22444 recordedOutputHandle, frame)) {
22445 rollback();
22446 return false;
22447 }
22448 }
22449 }
22450 if (hasClassAdmission &&
22451 !commitDwgClassFrame(kind, sourceHandle, recordedOutputHandle,
22452 admittedClassNumber, sourceOrdinal)) {
22453 rollback();
22454 return false;
22455 }
22456 if (!isGenerated &&
22457 !commitDwgWriteIdentity(kind, recordedOutputHandle)) {
22458 rollback();
22459 return false;
22460 }
22461 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
22462 rollback();
22463 return false;
22464 }
22465 return true;
22466 };
22467 const auto writeDwgObject =
22468 [&writeDwgObjectWithCompletion](DwgWriteSourceKind kind,
22469 std::uint32_t sourceHandle,
22470 const auto &write) {
22471 return writeDwgObjectWithCompletion(kind, sourceHandle, write,
22472 [] { return true; });
22473 };
22474 const auto captureDwgFieldFrame = [this](auto &candidate) {
22475 std::uint32_t outputHandle = 0;
22476 if (lookupDwgWriteIdentity(DwgWriteSourceKind::Object,
22477 candidate.sourceHandle, &outputHandle) !=
22478 DwgWriteIdentityLookup::Committed ||
22479 outputHandle == 0) {
22480 return false;
22481 }
22482 const DRW::DwgObjectFrameReceipt *frame =
22483 findDwgWriteObjectOccurrences(outputHandle);
22484 if (frame == nullptr || !frame->valid || frame->generation == 0 ||
22485 frame->objectHandle != outputHandle ||
22486 frame->version != m_dwgW->getVersion()) {
22487 return false;
22488 }
22489 candidate.outputHandle = outputHandle;
22490 candidate.frame = *frame;
22491 candidate.objectEmitted = true;
22492 return true;
22493 };
22494 auto sanitizeIndexedObjectHandle =
22495 [this, &sanitizeObjectHandle, &sanitizeOptionalEntityHandle](
22496 std::uint32_t &handle, const char *kind,
22497 std::uint32_t referenceOrdinal = 0,
22498 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
22499 if (handle == 0)
22500 return;
22501 if (hasDwgWriteKnownHandle(handle)) {
22502 sanitizeObjectHandle(handle, kind, referenceOrdinal,
22503 wireHandleCode);
22504 return;
22505 }
22506 sanitizeOptionalEntityHandle(handle, kind, referenceOrdinal,
22507 wireHandleCode);
22508 };
22509 auto sanitizeIndexedObjectList =
22510 [&sanitizeIndexedObjectHandle](
22511 std::vector<std::uint32_t> &handles, const char *kind,
22512 std::uint8_t wireHandleCode = DRW::DwgSoftPointer) {
22513 std::vector<std::uint32_t> kept;
22514 kept.reserve(handles.size());
22515 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
22516 std::uint32_t handle = handles[ordinal];
22517 sanitizeIndexedObjectHandle(handle, kind,
22518 static_cast<std::uint32_t>(ordinal),
22519 wireHandleCode);
22520 if (handle != 0)
22521 kept.push_back(handle);
22522 }
22523 handles = std::move(kept);
22524 };
22525 // PR 13f — custom-class families with version-clean encoders +
22526 // round-trip-grade Records dispatch on the broadened gate
22527 // (≥AC1015). Matches the same gate used in writeDwgClasses for
22528 // the CLASSES section entry registration; the entry and the
22529 // OBJECTS instance must land together or the reader's class
22530 // lookup fails and the object is silently dropped (see plan's
22531 // "Discovered during PR 13f" notes).
22532 const bool canRegisterCustomClassObjects =
22533 m_dwgW->getVersion() >= DRW::AC1015;
22534 std::size_t nativePlotSettingsCount =
22535 metadata.plotSettings().empty() ? 1u : 0u;
22536 for (std::size_t index = 0; index < metadata.plotSettings().size();
22537 ++index) {
22538 if (isDwgTypedPlotSettingsSelected(index))
22539 ++nativePlotSettingsCount;
22540 }
22541 const bool emitNativePlotSettings =
22542 canRegisterCustomClassObjects && nativePlotSettingsCount != 0;
22543 std::set<std::uint32_t> nativeSunHandles;
22544 std::set<std::uint32_t> nativeSunStudyHandles;
22545 std::set<std::uint32_t> nativeMotionPathHandles;
22546 std::set<std::uint32_t> nativeCurvePathHandles;
22547 std::set<std::uint32_t> nativePointPathHandles;
22548 std::set<std::uint32_t> nativeObjectPtrHandles;
22549 std::set<std::uint32_t> nativePartialViewingIndexHandles;
22550 std::set<std::uint32_t> nativeRenderSettingsHandles;
22551 std::set<std::uint32_t> nativeVisualStyleHandles;
22552 std::set<std::uint32_t> nativeTvDevicePropertiesHandles;
22553 std::set<std::uint32_t> nativeViewportEntityHeaderHandles;
22554 std::set<std::uint32_t> nativeVxControlHandles;
22555 std::set<std::uint32_t> nativeVxTableRecordHandles;
22556 std::set<std::uint32_t> nativePlaceholderHandles;
22557 std::set<std::uint32_t> nativeVbaProjectHandles;
22558 std::set<std::uint32_t> nativeMLeaderStyleHandles;
22559 std::set<std::uint32_t> nativeMaterialHandles;
22560 std::set<std::uint32_t> nativeLightListHandles;
22561 std::set<std::uint32_t> nativeBackgroundHandles;
22562 std::set<std::uint32_t> nativeDictionaryHandles;
22563 std::set<std::size_t> ownerGraphDictionaryIndexes;
22564 std::set<std::uint32_t> nativeXRecordHandles;
22565 std::set<std::uint32_t> nativeLayoutHandles;
22566 std::set<std::uint32_t> nativeGroupHandles;
22567 std::set<std::uint32_t> nativeMLineStyleHandles;
22568 std::set<std::uint32_t> nativeTableStyleHandles;
22569 std::set<std::uint32_t> nativeDbColorHandles;
22570 std::set<std::uint32_t> nativeDimensionAssociationHandles;
22571 std::set<std::uint32_t> nativeEvaluationGraphHandles;
22572 std::set<std::uint32_t> nativeBlockRepresentationHandles;
22573 std::set<std::uint32_t> nativeRasterVariablesHandles;
22574 std::set<std::uint32_t> nativeWipeoutVariablesHandles;
22575 std::set<std::uint32_t> nativeGeoDataHandles;
22576 std::set<std::uint32_t> nativeSpatialFilterHandles;
22577 // PR 8d.2a — five small no-storage OBJECTS families.
22578 std::set<std::uint32_t> nativeScaleHandles;
22579 std::set<std::uint32_t> nativeIDBufferHandles;
22580 std::set<std::uint32_t> nativeLayerIndexHandles;
22581 std::set<std::uint32_t> nativeSpatialIndexHandles;
22582 std::set<std::uint32_t> nativeDictionaryVarHandles;
22583 // PR 8d.2b — four larger no-storage OBJECTS families.
22584 std::set<std::uint32_t> nativeDictionaryWithDefaultHandles;
22585 std::set<std::uint32_t> nativeSortEntsTableHandles;
22586 std::set<std::uint32_t> nativeFieldListHandles;
22587 std::set<std::uint32_t> nativeFieldHandles;
22588 const auto remapRequiredFieldChildren =
22589 [this, &isLiveDwgWriteReference, &nativeFieldHandles,
22590 &recordRetainedReference](std::vector<std::uint32_t> &handles) {
22591 const DRW_Field::DwgReferencePolicy policy =
22592 DRW_Field::dwgReferencePolicy(
22593 m_dwgW->getVersion(),
22594 DRW_Field::DwgReferenceKind::ChildField);
22595 if (!policy.hasHandleStreamSlot) {
22596 m_writeFailed = true;
22597 return false;
22598 }
22599 std::vector<std::uint32_t> remapped;
22600 remapped.reserve(handles.size());
22601 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
22602 const std::uint32_t sourceHandle = handles[ordinal];
22603 std::uint32_t outputHandle = 0;
22604 if (sourceHandle == DRW::NoHandle ||
22605 nativeFieldHandles.count(sourceHandle) == 0 ||
22606 !isLiveDwgWriteReference(
22607 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle)) ||
22608 outputHandle == 0 ||
22609 !recordRetainedReference(
22610 sourceHandle, outputHandle, DwgWriteSourceKind::Object,
22611 "FIELD child", static_cast<std::uint32_t>(ordinal),
22612 policy.wireHandleCode, true)) {
22613 m_writeFailed = true;
22614 return false;
22615 }
22616 remapped.push_back(outputHandle);
22617 }
22618 handles = std::move(remapped);
22619 return true;
22620 };
22621 const auto remapRequiredFieldObjects =
22622 [this, &isLiveDwgWriteReference,
22623 &recordRetainedReference](std::vector<std::uint32_t> &handles) {
22624 const DRW_Field::DwgReferencePolicy policy =
22625 DRW_Field::dwgReferencePolicy(m_dwgW->getVersion(),
22626 DRW_Field::DwgReferenceKind::Object);
22627 if (!policy.hasHandleStreamSlot) {
22628 m_writeFailed = true;
22629 return false;
22630 }
22631 std::vector<std::uint32_t> remapped;
22632 remapped.reserve(handles.size());
22633 for (std::size_t ordinal = 0; ordinal < handles.size(); ++ordinal) {
22634 const std::uint32_t sourceHandle = handles[ordinal];
22635 std::uint32_t outputHandle = 0;
22636 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
22637 if (sourceHandle == DRW::NoHandle ||
22638 !isLiveDwgWriteReference(resolveDwgWriteCommonHandle(
22639 sourceHandle, &outputHandle, &targetKind)) ||
22640 outputHandle == 0 ||
22641 !recordRetainedReference(sourceHandle, outputHandle, targetKind,
22642 "FIELD object",
22643 static_cast<std::uint32_t>(ordinal),
22644 policy.wireHandleCode, true)) {
22645 m_writeFailed = true;
22646 return false;
22647 }
22648 remapped.push_back(outputHandle);
22649 }
22650 handles = std::move(remapped);
22651 return true;
22652 };
22653 const auto remapRequiredFieldCadValue = [this, &isLiveDwgWriteReference,
22654 &recordRetainedReference](
22655 DRW_CadValue &value,
22656 const char *kind,
22657 std::uint32_t ordinal) {
22658 const DRW_Field::DwgReferencePolicy policy =
22659 DRW_Field::dwgReferencePolicy(
22660 m_dwgW->getVersion(),
22661 DRW_Field::DwgReferenceKind::CadValueObjectId, &value);
22662 if (!policy.hasHandleStreamSlot)
22663 return true;
22664 const std::uint32_t sourceHandle = value.m_handle;
22665 std::uint32_t outputHandle = 0;
22666 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
22667 if (sourceHandle == DRW::NoHandle ||
22668 !isLiveDwgWriteReference(resolveDwgWriteCommonHandle(
22669 sourceHandle, &outputHandle, &targetKind)) ||
22670 outputHandle == 0 ||
22671 !recordRetainedReference(sourceHandle, outputHandle, targetKind, kind,
22672 ordinal, policy.wireHandleCode, true)) {
22673 m_writeFailed = true;
22674 return false;
22675 }
22676 value.m_handle = outputHandle;
22677 return true;
22678 };
22679 std::set<std::uint32_t> nativeUnderlayDefinitionHandles;
22680 std::set<std::uint32_t> nativePointCloudDefinitionHandles;
22681 std::set<std::uint32_t> nativeNavisworksModelDefinitionHandles;
22682 std::set<std::uint32_t> nativePointCloudColorMapHandles;
22683 std::set<std::uint32_t> nativeSectionHandles;
22684 std::set<std::uint32_t> nativeObjectHandles;
22685 int nativeSunObjects = 0;
22686 int nativeSunStudyObjects = 0;
22687 int nativeMotionPathObjects = 0;
22688 int nativeCurvePathObjects = 0;
22689 int nativePointPathObjects = 0;
22690 int nativeObjectPtrObjects = 0;
22691 int nativePartialViewingIndexObjects = 0;
22692 int nativeRenderSettingsObjects = 0;
22693 int nativeVisualStyleObjects = 0;
22694 int nativeTvDevicePropertiesObjects = 0;
22695 int nativeVxControlObjects = 0;
22696 int nativeVxTableRecordObjects = 0;
22697 int nativePlaceholderObjects = 0;
22698 int nativeVbaProjectObjects = 0;
22699 int nativeMLeaderStyleObjects = 0;
22700 int nativeMaterialObjects = 0;
22701 int nativeLightListObjects = 0;
22702 int nativeBackgroundObjects = 0;
22703 int nativeDictionaryObjects = 0;
22704 int nativeXRecordObjects = 0;
22705 int nativeLayoutObjects = 0;
22706 int nativeGroupObjects = 0;
22707 int nativeMLineStyleObjects = 0;
22708 int nativeTableStyleObjects = 0;
22709 int nativeDbColorObjects = 0;
22710 int nativeDimensionAssociationObjects = 0;
22711 int nativeEvaluationGraphObjects = 0;
22712 int nativeRasterVariablesObjects = 0;
22713 int nativeWipeoutVariablesObjects = 0;
22714 int nativeGeoDataObjects = 0;
22715 int nativeSpatialFilterObjects = 0;
22716 int nativeScaleObjects = 0;
22717 int nativeIDBufferObjects = 0;
22718 int nativeLayerIndexObjects = 0;
22719 int nativeSpatialIndexObjects = 0;
22720 int nativeDictionaryVarObjects = 0;
22721 // PR 8d.2b — four larger no-storage OBJECTS families.
22722 int nativeDictionaryWithDefaultObjects = 0;
22723 int nativeSortEntsTableObjects = 0;
22724 int nativeFieldListObjects = 0;
22725 int nativeFieldObjects = 0;
22726 int nativeUnderlayDefinitionObjects = 0;
22727 int nativePointCloudDefinitionObjects = 0;
22728 int nativeNavisworksModelDefinitionObjects = 0;
22729 int nativePointCloudColorMapObjects = 0;
22730 int nativeSectionObjects = 0;
22731 if (emitNativePlotSettings) {
22732 LC_PlotSettings *graphicPlotSettings = m_graphic->getPlotSettings();
22733 const auto writePlotSettings = [&](DRW_PlotSettings plotSettings,
22734 bool overlayActiveSettings,
22735 std::uint32_t generatedOrdinal) {
22736 const std::uint32_t sourceHandle = plotSettings.handle;
22737 remapEntry(plotSettings);
22738 const bool plotSettingsValid = sanitizeCommonObjectReferences(
22739 plotSettings, "PLOTSETTINGS common reference");
22740 if (!plotSettingsValid)
22741 m_writeFailed = true;
22742 if (m_dwgW->getVersion() >= DRW::AC1018) {
22743 plotSettings.plotViewHandle.ref = remapViewHandle(
22744 plotSettings.plotViewHandle.ref, "PLOTSETTINGS plot VIEW");
22745 } else {
22746 plotSettings.plotViewHandle.ref = 0;
22747 }
22748 if (m_dwgW->getVersion() > DRW::AC1018) {
22749 sanitizeObjectHandle(plotSettings.shadePlotHandle.ref,
22750 "PLOTSETTINGS shade plot");
22751 } else {
22752 plotSettings.shadePlotHandle.ref = 0;
22753 }
22754 if (plotSettingsValid && overlayActiveSettings &&
22755 graphicPlotSettings != nullptr) {
22756 overlayActivePlotSettings(plotSettings, *graphicPlotSettings);
22757 }
22758 const bool wrote =
22759 plotSettingsValid &&
22760 (sourceHandle == 0
22761 ? writeDwgObjectWithCompletion(
22762 RS_FilterDXFRW_DwgWriteIdentityRegistry::
22763 DwgWriteSourceKind::GeneratedChild,
22764 0,
22765 [&plotSettings, this] {
22766 return m_dwgW->writePlotSettings(&plotSettings);
22767 },
22768 [] { return true; }, generatedOrdinal)
22769 : writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
22770 DwgWriteSourceKind::Object,
22771 sourceHandle, [&plotSettings, this] {
22772 return m_dwgW->writePlotSettings(
22773 &plotSettings);
22774 }));
22775 if (plotSettingsValid && !wrote) {
22776 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22777 "RS_FilterDXFRW: failed to write DWG "
22778 "PLOTSETTINGS");
22779 m_writeFailed = true;
22780 }
22781 };
22782 if (metadata.plotSettings().empty()) {
22783 writePlotSettings(DRW_PlotSettings{}, true, 1);
22784 } else {
22785 for (std::size_t index = 0; index < metadata.plotSettings().size();
22786 ++index) {
22787 const DRW_PlotSettings &source = metadata.plotSettings()[index];
22788 if (!isDwgTypedPlotSettingsSelected(index))
22789 continue;
22790 const std::uint32_t generatedOrdinal =
22791 source.handle == 0 &&
22792 index < std::numeric_limits<std::uint32_t>::max()
22793 ? static_cast<std::uint32_t>(index + 1)
22794 : 0;
22795 writePlotSettings(source, index == 0, generatedOrdinal);
22796 }
22797 }
22798 }
22799 if (canWriteModernObjects) {
22800 for (const auto &record : metadata.suns()) {
22801 if (record.replayState ==
22802 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22803 record.handle != 0) {
22804 DRW_Sun sun = sunFromMetadata(record);
22805 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
22806 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
22807 "RS_FilterDXFRW: cannot convert DXF SUN "
22808 "application data to DWG");
22809 m_writeFailed = true;
22810 continue;
22811 }
22812 nativeSunHandles.insert(record.handle);
22813 }
22814 }
22815 for (const auto &record : metadata.sections()) {
22816 if (record.replayState ==
22817 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22818 record.handle != 0) {
22819 DRW_Section section = sectionFromMetadata(record);
22820 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
22821 m_writeFailed = true;
22822 continue;
22823 }
22824 nativeSectionHandles.insert(record.handle);
22825 }
22826 }
22827 // ACDBPLACEHOLDER handle-set construction moved below to the
22828 // `canWriteFixedTypeObjects` block (PR 13d) — fixed type 80
22829 // is available since R2000 and the encoder is version-clean.
22830 // DICTIONARY / XRECORD / LAYOUT / GROUP handle-set construction
22831 // moved below to the `canWriteFixedTypeObjects` block
22832 // (PR 13a/b/c) so the broadened gate applies even when
22833 // canWriteModernObjects is false (AC1015/AC1018 export path).
22834 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
22835 // construction moved to the `canRegisterCustomClassObjects`
22836 // block (PR 13f).
22837 // PR 8d.2a — five small no-storage OBJECTS families.
22838 // Handle-set construction moved to the
22839 // `canRegisterCustomClassObjects` block (PR 13g).
22840 // PR 8d.2b — four larger no-storage OBJECTS families.
22841 // Handle-set construction moved to the
22842 // `canRegisterCustomClassObjects` block (PR 13h).
22843 }
22844 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER handle-set
22845 // construction sits in its own broadened block (≥AC1015) so the
22846 // raw-replay blocker (below) skips raw bytes for these handles at
22847 // AC1015/AC1018 too.
22848 if (canRegisterCustomClassObjects) {
22849 for (const auto &record : metadata.materials()) {
22850 if (record.replayState ==
22851 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22852 record.handle != 0 &&
22853 !hasReplayableRawMaterial(metadata, record.handle,
22854 m_dwgW->getVersion())) {
22855 DRW_Material material = materialFromMetadata(record);
22856 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
22857 m_writeFailed = true;
22858 continue;
22859 }
22860 nativeMaterialHandles.insert(record.handle);
22861 }
22862 }
22863 for (const auto &record : metadata.lightLists()) {
22864 if (record.replayState ==
22865 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22866 record.handle != 0 &&
22867 !hasReplayableRawLightList(
22868 metadata, record.handle, m_dwgW->getVersion(),
22869 [this](std::uint32_t handle) {
22870 return isDwgWriteOpaqueReferenceStable(handle);
22871 })) {
22872 DRW_LightList lightList = lightListFromMetadata(record);
22873 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
22874 m_writeFailed = true;
22875 continue;
22876 }
22877 nativeLightListHandles.insert(record.handle);
22878 }
22879 }
22880 for (const auto &record : metadata.backgrounds()) {
22881 if (record.replayState ==
22882 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22883 record.handle != 0 &&
22884 !hasReplayableRawBackground(metadata, record.handle,
22885 m_dwgW->getVersion())) {
22886 DRW_Background background = backgroundFromMetadata(record);
22887 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
22888 m_writeFailed = true;
22889 continue;
22890 }
22891 nativeBackgroundHandles.insert(record.handle);
22892 }
22893 }
22894 for (const auto &record : metadata.sunStudies()) {
22895 if (record.replayState ==
22896 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22897 record.handle != 0 &&
22898 !hasReplayableRawSunStudy(metadata, record.handle,
22899 m_dwgW->getVersion())) {
22900 DRW_SunStudy study = sunStudyFromMetadata(record);
22901 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
22902 m_writeFailed = true;
22903 continue;
22904 }
22905 nativeSunStudyHandles.insert(record.handle);
22906 }
22907 }
22908 for (const auto &record : metadata.motionPaths()) {
22909 if (record.replayState ==
22910 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22911 record.handle != 0 &&
22912 !hasReplayableRawMotionPath(metadata, record.handle,
22913 m_dwgW->getVersion())) {
22914 DRW_MotionPath path = motionPathFromMetadata(record);
22915 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
22916 m_writeFailed = true;
22917 continue;
22918 }
22919 nativeMotionPathHandles.insert(record.handle);
22920 }
22921 }
22922 for (const auto &record : metadata.curvePaths()) {
22923 if (record.replayState ==
22924 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22925 record.handle != 0 &&
22926 !hasReplayableRawCurvePath(metadata, record.handle,
22927 m_dwgW->getVersion())) {
22928 DRW_CurvePath path = curvePathFromMetadata(record);
22929 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
22930 m_writeFailed = true;
22931 continue;
22932 }
22933 nativeCurvePathHandles.insert(record.handle);
22934 }
22935 }
22936 for (const auto &record : metadata.pointPaths()) {
22937 if (record.replayState ==
22938 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22939 record.handle != 0 &&
22940 !hasReplayableRawPointPath(metadata, record.handle,
22941 m_dwgW->getVersion())) {
22942 DRW_PointPath path = pointPathFromMetadata(record);
22943 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
22944 m_writeFailed = true;
22945 continue;
22946 }
22947 nativePointPathHandles.insert(record.handle);
22948 }
22949 }
22950 for (const auto &record : metadata.objectPtrs()) {
22951 if (record.replayState ==
22952 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22953 record.handle != 0 &&
22954 !hasReplayableRawObjectPtr(metadata, record.handle,
22955 m_dwgW->getVersion())) {
22956 DRW_ObjectPtr object = objectPtrFromMetadata(record);
22957 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
22958 m_writeFailed = true;
22959 continue;
22960 }
22961 nativeObjectPtrHandles.insert(record.handle);
22962 }
22963 }
22964 for (const auto &record : metadata.partialViewingIndexes()) {
22965 if (record.replayState ==
22966 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22967 record.handle != 0 &&
22968 !hasReplayableRawPartialViewingIndex(metadata, record.handle,
22969 m_dwgW->getVersion())) {
22970 DRW_PartialViewingIndex index =
22971 partialViewingIndexFromMetadata(record);
22972 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
22973 m_writeFailed = true;
22974 continue;
22975 }
22976 nativePartialViewingIndexHandles.insert(record.handle);
22977 }
22978 }
22979 for (const auto &record : metadata.renderSettings()) {
22980 if (record.replayState ==
22981 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22982 record.handle != 0 &&
22983 !hasReplayableRawRenderSettings(
22984 metadata, record.handle, record.kind, m_dwgW->getVersion())) {
22985 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
22986 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
22987 m_writeFailed = true;
22988 continue;
22989 }
22990 nativeRenderSettingsHandles.insert(record.handle);
22991 }
22992 }
22993 for (const auto &record : metadata.visualStyles()) {
22994 if (record.replayState ==
22995 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
22996 record.handle != 0 &&
22997 !hasReplayableRawVisualStyle(metadata, record.handle,
22998 m_dwgW->getVersion())) {
22999 DRW_VisualStyle style = visualStyleFromMetadata(record);
23000 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
23001 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
23002 "RS_FilterDXFRW: cannot convert DXF VISUALSTYLE "
23003 "application data to DWG");
23004 m_writeFailed = true;
23005 continue;
23006 }
23007 nativeVisualStyleHandles.insert(record.handle);
23008 }
23009 }
23010 for (const auto &record : metadata.tvDeviceProperties()) {
23011 if (record.replayState ==
23012 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23013 record.handle != 0) {
23014 DRW_TvDeviceProperties properties =
23015 tvDevicePropertiesFromMetadata(record);
23016 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
23017 m_writeFailed = true;
23018 continue;
23019 }
23020 nativeTvDevicePropertiesHandles.insert(record.handle);
23021 }
23022 }
23023 for (const auto &record : metadata.vxControls()) {
23024 if (record.replayState ==
23025 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23026 record.handle != 0 &&
23027 !hasReplayableRawVxControl(metadata, record.handle,
23028 m_dwgW->getVersion())) {
23029 DRW_VxControl control = vxControlFromMetadata(record);
23030 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
23031 m_writeFailed = true;
23032 continue;
23033 }
23034 nativeVxControlHandles.insert(record.handle);
23035 }
23036 }
23037 for (const auto &record : metadata.vxTableRecords()) {
23038 if (record.replayState ==
23039 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23040 record.handle != 0 &&
23041 !hasReplayableRawVxTableRecord(metadata, record.handle,
23042 m_dwgW->getVersion())) {
23043 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
23044 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
23045 m_writeFailed = true;
23046 continue;
23047 }
23048 nativeVxTableRecordHandles.insert(record.handle);
23049 }
23050 }
23051 // RASTERVARIABLES (AcDbRasterVariables, custom class 505) —
23052 // round-trip-grade RasterVariablesRecord captures every encoder
23053 // field (classVersion, imageFrame, imageQuality, units). PR 8d.1.
23054 for (const auto &record : metadata.rasterVariables()) {
23055 if (record.replayState ==
23056 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23057 record.handle != 0) {
23058 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
23059 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
23060 m_writeFailed = true;
23061 continue;
23062 }
23063 nativeRasterVariablesHandles.insert(record.handle);
23064 }
23065 }
23066 // WIPEOUTVARIABLES (AcDbWipeoutVariables, custom class 529).
23067 for (const auto &record : metadata.wipeoutVariables()) {
23068 if (record.replayState ==
23069 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23070 record.handle != 0) {
23071 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
23072 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
23073 m_writeFailed = true;
23074 continue;
23075 }
23076 nativeWipeoutVariablesHandles.insert(record.handle);
23077 }
23078 }
23079 // GEODATA (AcDbGeoData, custom class 506) — round-trip-grade
23080 // GeoDataRecord extended in PR 8d.1c to capture all encoder
23081 // fields (designPoint, referencePoint, up/north direction,
23082 // scale estimation, sea-level correction, observation tags,
23083 // mesh points + faces).
23084 for (const auto &record : metadata.geoData()) {
23085 if (record.replayState ==
23086 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23087 record.handle != 0) {
23088 DRW_GeoData gd = geoDataFromMetadata(record);
23089 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
23090 m_writeFailed = true;
23091 continue;
23092 }
23093 nativeGeoDataHandles.insert(record.handle);
23094 }
23095 }
23096 // SPATIAL_FILTER (AcDbSpatialFilter, custom class 507) —
23097 // round-trip-grade SpatialFilterRecord extended in PR 8d.1d to
23098 // capture boundary points + 4x3 clip transform matrices.
23099 for (const auto &record : metadata.spatialFilters()) {
23100 if (record.replayState ==
23101 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23102 record.handle != 0) {
23103 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
23104 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
23105 m_writeFailed = true;
23106 continue;
23107 }
23108 nativeSpatialFilterHandles.insert(record.handle);
23109 }
23110 }
23111 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
23112 // DICTIONARYVAR (custom classes 508-512) handle-set
23113 // construction.
23114 for (const auto &record : metadata.scales()) {
23115 if (record.replayState ==
23116 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23117 record.handle != 0) {
23118 DRW_Scale scale = scaleFromMetadata(record);
23119 if (hasDxfTableAppDataUnsupportedByDwg(scale)) {
23120 m_writeFailed = true;
23121 continue;
23122 }
23123 nativeScaleHandles.insert(record.handle);
23124 }
23125 }
23126 for (const auto &record : metadata.idBuffers()) {
23127 if (record.replayState ==
23128 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23129 record.handle != 0) {
23130 DRW_IDBuffer idBuffer = idBufferFromMetadata(record);
23131 if (hasDxfTableAppDataUnsupportedByDwg(idBuffer)) {
23132 m_writeFailed = true;
23133 continue;
23134 }
23135 nativeIDBufferHandles.insert(record.handle);
23136 }
23137 }
23138 for (const auto &record : metadata.layerIndexes()) {
23139 if (record.replayState ==
23140 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23141 record.handle != 0) {
23142 DRW_LayerIndex layerIndex = layerIndexFromMetadata(record);
23143 if (hasDxfTableAppDataUnsupportedByDwg(layerIndex)) {
23144 m_writeFailed = true;
23145 continue;
23146 }
23147 nativeLayerIndexHandles.insert(record.handle);
23148 }
23149 }
23150 for (const auto &record : metadata.spatialIndexes()) {
23151 if (record.replayState ==
23152 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23153 record.handle != 0 &&
23154 !hasReplayableRawSpatialIndex(metadata, record.handle,
23155 m_dwgW->getVersion())) {
23156 DRW_SpatialIndex spatialIndex = spatialIndexFromMetadata(record);
23157 if (hasDxfTableAppDataUnsupportedByDwg(spatialIndex)) {
23158 m_writeFailed = true;
23159 continue;
23160 }
23161 nativeSpatialIndexHandles.insert(record.handle);
23162 }
23163 }
23164 for (const auto &record : metadata.dictionaryVars()) {
23165 if (record.replayState ==
23166 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23167 record.handle != 0) {
23168 DRW_DictionaryVar dictionaryVar = dictionaryVarFromMetadata(record);
23169 if (hasDxfTableAppDataUnsupportedByDwg(dictionaryVar)) {
23170 m_writeFailed = true;
23171 continue;
23172 }
23173 nativeDictionaryVarHandles.insert(record.handle);
23174 }
23175 }
23176 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
23177 // (custom classes 513-516) handle-set construction.
23178 for (const auto &record : metadata.dictionariesWithDefault()) {
23179 if (record.replayState ==
23180 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23181 record.handle != 0) {
23182 if (!canWriteDwgDictionaryWithDefaultRecord(metadata, record,
23183 m_dwgW->getVersion())) {
23184 if (!metadata.hasRawObjectForHandle(record.handle))
23185 m_writeFailed = true;
23186 continue;
23187 }
23188 nativeDictionaryWithDefaultHandles.insert(record.handle);
23189 }
23190 }
23191 for (const auto &record : metadata.sortEntsTables()) {
23192 if (record.replayState ==
23193 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23194 record.handle != 0) {
23195 DRW_SortEntsTable sortEntsTable = sortEntsTableFromMetadata(record);
23196 if (hasDxfTableAppDataUnsupportedByDwg(sortEntsTable)) {
23197 m_writeFailed = true;
23198 continue;
23199 }
23200 const bool hasUnresolvedSortKey = std::any_of(
23201 sortEntsTable.m_sortHandles.cbegin(),
23202 sortEntsTable.m_sortHandles.cend(),
23203 [this, &isLiveDwgWriteReference](std::uint32_t handle) {
23204 if (handle == DRW::NoHandle)
23205 return false;
23206 std::uint32_t outputHandle = 0;
23207 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
23208 const DwgWriteReferenceStatus status =
23209 resolveDwgWriteCommonHandle(handle, &outputHandle,
23210 &targetKind);
23211 return !isLiveDwgWriteReference(status) || outputHandle == 0;
23212 });
23213 if (hasReplayableRawSortEntsTable(metadata, record.handle,
23214 m_dwgW->getVersion()) &&
23215 hasUnresolvedSortKey) {
23216 // Keep the validated source frame authoritative when the typed
23217 // view contains a dangling sort key.
23218 continue;
23219 }
23220 nativeSortEntsTableHandles.insert(record.handle);
23221 }
23222 }
23223 for (const auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
23224 if (candidate.classAdmitted)
23225 nativeFieldHandles.insert(candidate.sourceHandle);
23226 }
23227 for (const auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
23228 if (candidate.classAdmitted)
23229 nativeFieldListHandles.insert(candidate.sourceHandle);
23230 }
23231 for (const auto &record : metadata.underlayDefinitions()) {
23232 if (record.replayState ==
23233 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23234 record.handle != 0) {
23235 DRW_UnderlayDefinition definition =
23236 underlayDefinitionFromMetadata(record);
23237 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
23238 RS_DEBUGRS_Debug::instance()->print(
23239 RS_Debug::D_WARNING,
23240 "RS_FilterDXFRW: cannot convert DXF UNDERLAYDEFINITION "
23241 "application data to DWG");
23242 m_writeFailed = true;
23243 continue;
23244 }
23245 nativeUnderlayDefinitionHandles.insert(record.handle);
23246 }
23247 }
23248 for (const auto &record : metadata.pointCloudDefinitions()) {
23249 if (record.replayState ==
23250 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23251 record.handle != 0) {
23252 DRW_PointCloudDef definition =
23253 pointCloudDefinitionFromMetadata(record);
23254 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
23255 RS_DEBUGRS_Debug::instance()->print(
23256 RS_Debug::D_WARNING,
23257 "RS_FilterDXFRW: cannot convert DXF POINTCLOUDDEFINITION "
23258 "application data to DWG");
23259 m_writeFailed = true;
23260 continue;
23261 }
23262 nativePointCloudDefinitionHandles.insert(record.handle);
23263 }
23264 }
23265 for (const auto &record : metadata.navisworksModelDefinitions()) {
23266 if (record.replayState ==
23267 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23268 record.handle != 0) {
23269 DRW_NavisworksModelDef definition =
23270 navisworksModelDefinitionFromMetadata(record);
23271 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
23272 RS_DEBUGRS_Debug::instance()->print(
23273 RS_Debug::D_WARNING,
23274 "RS_FilterDXFRW: cannot convert DXF NAVISWORKSMODELDEF "
23275 "application data to DWG");
23276 m_writeFailed = true;
23277 continue;
23278 }
23279 nativeNavisworksModelDefinitionHandles.insert(record.handle);
23280 }
23281 }
23282 for (const auto &record : metadata.pointCloudColorMaps()) {
23283 if (record.replayState ==
23284 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23285 record.handle != 0) {
23286 DRW_PointCloudColorMap colorMap =
23287 pointCloudColorMapFromMetadata(record);
23288 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
23289 RS_DEBUGRS_Debug::instance()->print(
23290 RS_Debug::D_WARNING,
23291 "RS_FilterDXFRW: cannot convert DXF POINTCLOUDCOLORMAP "
23292 "application data to DWG");
23293 m_writeFailed = true;
23294 continue;
23295 }
23296 nativePointCloudColorMapHandles.insert(record.handle);
23297 }
23298 }
23299 }
23300 // PR 13a/b/c — DICTIONARY / XRECORD / GROUP / LAYOUT handle-set
23301 // construction sits in its own broadened block (≥ AC1015) so the
23302 // raw-replay blocker (below) skips raw bytes for these handles at
23303 // AC1015/AC1018 too.
23304 if (canWriteFixedTypeObjects) {
23305 for (const auto &record : metadata.viewportEntityHeaders()) {
23306 if (record.replayState ==
23307 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23308 record.handle != 0) {
23309 nativeViewportEntityHeaderHandles.insert(record.handle);
23310 }
23311 }
23312 for (const auto &record : metadata.placeholders()) {
23313 if (record.replayState ==
23314 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23315 record.handle != 0) {
23316 DRW_AcDbPlaceholder placeholder = placeholderFromMetadata(record);
23317 if (hasDxfTableAppDataUnsupportedByDwg(placeholder)) {
23318 m_writeFailed = true;
23319 continue;
23320 }
23321 nativePlaceholderHandles.insert(record.handle);
23322 }
23323 }
23324 for (const auto &project : metadata.vbaProjects()) {
23325 if (project.handle != 0 &&
23326 !hasReplayableRawVbaProject(metadata, project.handle,
23327 m_dwgW->getVersion())) {
23328 if (project.hasDataStorageBinaryData() ||
23329 hasDxfTableAppDataUnsupportedByDwg(project)) {
23330 m_writeFailed = true;
23331 continue;
23332 }
23333 nativeVbaProjectHandles.insert(project.handle);
23334 }
23335 }
23336 for (const auto &record : metadata.dictionaries()) {
23337 if (record.replayState ==
23338 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23339 record.handle != 0 &&
23340 record.handle != DRW::DwgNamedObjectsDictionaryHandle &&
23341 record.handle != DRW::DwgAcadGroupDictionaryHandle) {
23342 if (!canWriteDwgDictionaryRecord(metadata, record,
23343 m_dwgW->getVersion())) {
23344 if (!metadata.hasRawObjectForHandle(record.handle))
23345 m_writeFailed = true;
23346 continue;
23347 }
23348 nativeDictionaryHandles.insert(record.handle);
23349 }
23350 }
23351 for (const auto &record : metadata.xrecords()) {
23352 if (record.replayState ==
23353 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23354 record.handle != 0) {
23355 nativeXRecordHandles.insert(record.handle);
23356 }
23357 }
23358 for (const auto &record : metadata.groups()) {
23359 if (record.replayState ==
23360 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23361 record.handle != 0) {
23362 nativeGroupHandles.insert(record.handle);
23363 }
23364 }
23365 for (const auto &record : metadata.mlineStyles()) {
23366 if (record.replayState ==
23367 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23368 record.handle != 0) {
23369 nativeMLineStyleHandles.insert(record.handle);
23370 }
23371 }
23372 for (const auto &record : metadata.layouts()) {
23373 if (record.replayState ==
23374 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23375 record.handle != 0) {
23376 nativeLayoutHandles.insert(record.handle);
23377 }
23378 }
23379 }
23380 if (canRegisterCustomClassObjects && m_dwgW->getVersion() <= DRW::AC1021) {
23381 for (const auto &record : metadata.tables()) {
23382 if (record.replayState !=
23383 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
23384 record.handle == 0 || !record.nativeTableStyle ||
23385 !isDwgDataStorageTypedSelected(record.handle))
23386 continue;
23387 DRW_TableStyle style = *record.nativeTableStyle;
23388 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
23389 !style.validateDwgPayload(m_dwgW->getVersion())) {
23390 m_writeFailed = true;
23391 continue;
23392 }
23393 // The class was admitted before CLASSES was emitted. The
23394 // OBJECTS pass only selects the matching typed carrier.
23395 nativeTableStyleHandles.insert(record.handle);
23396 }
23397 }
23398 if (canRegisterCustomClassObjects) {
23399 for (const auto &record : metadata.mleaderStyles()) {
23400 if (record.replayState ==
23401 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23402 record.handle != 0 &&
23403 !hasReplayableRawMLeaderStyle(metadata, record.handle,
23404 m_dwgW->getVersion())) {
23405 nativeMLeaderStyleHandles.insert(record.handle);
23406 }
23407 }
23408 }
23409 if (canWriteDbColor) {
23410 for (const auto &record : metadata.dbColors()) {
23411 if (record.replayState ==
23412 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23413 record.handle != 0) {
23414 DRW_DbColor color = dbColorFromMetadata(record);
23415 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
23416 m_writeFailed = true;
23417 continue;
23418 }
23419 nativeDbColorHandles.insert(record.handle);
23420 }
23421 }
23422 }
23423 if (canWriteAssociativeObjects) {
23424 for (const auto &record : metadata.dimensionAssociations()) {
23425 if (record.replayState ==
23426 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23427 record.handle != 0) {
23428 const DRW_DimensionAssociation association =
23429 dimensionAssociationFromMetadata(record);
23430 if (hasDxfTableAppDataUnsupportedByDwg(association) ||
23431 !canWriteNativeDimensionAssociation(association)) {
23432 if (hasDxfTableAppDataUnsupportedByDwg(association))
23433 m_writeFailed = true;
23434 continue;
23435 }
23436 nativeDimensionAssociationHandles.insert(record.handle);
23437 }
23438 }
23439 for (const auto &record : metadata.evaluationGraphs()) {
23440 if (record.replayState ==
23441 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23442 record.handle != 0) {
23443 const DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
23444 if (hasDxfTableAppDataUnsupportedByDwg(graph) ||
23445 !canWriteNativeEvaluationGraph(graph)) {
23446 if (hasDxfTableAppDataUnsupportedByDwg(graph))
23447 m_writeFailed = true;
23448 continue;
23449 }
23450 nativeEvaluationGraphHandles.insert(record.handle);
23451 }
23452 }
23453 }
23454
23455 if (canWriteFixedTypeObjects) {
23456 for (const auto &record : metadata.blockRepresentationData()) {
23457 if (record.replayState ==
23458 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
23459 record.handle != 0 &&
23460 (m_dwgW->getVersion() <= DRW::AC1024 || !record.hasDsData)) {
23461 DRW_BlockRepresentationData data =
23462 blockRepresentationFromMetadata(record);
23463 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
23464 m_writeFailed = true;
23465 continue;
23466 }
23467 nativeBlockRepresentationHandles.insert(record.handle);
23468 }
23469 }
23470 }
23471
23472 // Handles reserved above are not necessarily emitted: version gates,
23473 // replay state, and native/raw replacement policy can skip a record.
23474 // Only records selected by the matching writer paths are valid future
23475 // OBJECTS targets; entity references are outside this object-only set.
23476 auto markNativeHandles = [this, &nativeObjectHandles](auto &handles) {
23477 // A raw DataStorage decision replaces its typed metadata carrier.
23478 // Remove it before publishing the native set, otherwise the raw
23479 // replay loop sees an artificial same-handle collision and the
23480 // typed OBJECTS loop can emit a second instance.
23481 for (auto it = handles.begin(); it != handles.end();) {
23482 if (isDwgDataStorageRawSelected(*it))
23483 it = handles.erase(it);
23484 else
23485 ++it;
23486 }
23487 m_dwgWriteKnownHandles.insert(handles.begin(), handles.end());
23488 nativeObjectHandles.insert(handles.begin(), handles.end());
23489 };
23490 markNativeHandles(nativeSunHandles);
23491 markNativeHandles(nativeSunStudyHandles);
23492 markNativeHandles(nativeMotionPathHandles);
23493 markNativeHandles(nativeCurvePathHandles);
23494 markNativeHandles(nativePointPathHandles);
23495 markNativeHandles(nativeObjectPtrHandles);
23496 markNativeHandles(nativePartialViewingIndexHandles);
23497 markNativeHandles(nativeRenderSettingsHandles);
23498 markNativeHandles(nativeVisualStyleHandles);
23499 markNativeHandles(nativeTvDevicePropertiesHandles);
23500 markNativeHandles(nativeViewportEntityHeaderHandles);
23501 markNativeHandles(nativeVxControlHandles);
23502 markNativeHandles(nativeVxTableRecordHandles);
23503 markNativeHandles(nativePlaceholderHandles);
23504 markNativeHandles(nativeVbaProjectHandles);
23505 markNativeHandles(nativeMLeaderStyleHandles);
23506 markNativeHandles(nativeMaterialHandles);
23507 markNativeHandles(nativeLightListHandles);
23508 markNativeHandles(nativeDictionaryHandles);
23509 markNativeHandles(nativeXRecordHandles);
23510 markNativeHandles(nativeLayoutHandles);
23511 markNativeHandles(nativeGroupHandles);
23512 markNativeHandles(nativeMLineStyleHandles);
23513 markNativeHandles(nativeTableStyleHandles);
23514 markNativeHandles(nativeDbColorHandles);
23515 markNativeHandles(nativeDimensionAssociationHandles);
23516 markNativeHandles(nativeEvaluationGraphHandles);
23517 markNativeHandles(nativeBlockRepresentationHandles);
23518 markNativeHandles(nativeRasterVariablesHandles);
23519 markNativeHandles(nativeWipeoutVariablesHandles);
23520 markNativeHandles(nativeGeoDataHandles);
23521 markNativeHandles(nativeSpatialFilterHandles);
23522 markNativeHandles(nativeScaleHandles);
23523 markNativeHandles(nativeIDBufferHandles);
23524 markNativeHandles(nativeLayerIndexHandles);
23525 markNativeHandles(nativeSpatialIndexHandles);
23526 markNativeHandles(nativeDictionaryVarHandles);
23527 markNativeHandles(nativeDictionaryWithDefaultHandles);
23528 markNativeHandles(nativeSortEntsTableHandles);
23529 markNativeHandles(nativeFieldListHandles);
23530 markNativeHandles(nativeFieldHandles);
23531 markNativeHandles(nativeUnderlayDefinitionHandles);
23532 markNativeHandles(nativePointCloudDefinitionHandles);
23533 markNativeHandles(nativeNavisworksModelDefinitionHandles);
23534 markNativeHandles(nativePointCloudColorMapHandles);
23535 markNativeHandles(nativeSectionHandles);
23536
23537 for (const auto &entry :
23538 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
23539 const auto &graph = entry.second.ownerGraph;
23540 if (!graph.has_value())
23541 continue;
23542 if (graph->rootDictionaryIndex >= metadata.dictionaries().size() ||
23543 graph->childDictionaryIndex >= metadata.dictionaries().size() ||
23544 graph->rootDictionaryIndex == graph->childDictionaryIndex ||
23545 nativeDictionaryHandles.count(graph->childOutputHandle) == 0 ||
23546 !ownerGraphDictionaryIndexes.insert(graph->rootDictionaryIndex)
23547 .second ||
23548 !ownerGraphDictionaryIndexes.insert(graph->childDictionaryIndex)
23549 .second) {
23550 m_writeFailed = true;
23551 m_dwgW->markDwgWriteFailure();
23552 return;
23553 }
23554 }
23555
23556 bool hasBlockedReplay = false;
23557 int blockedInvalidated = 0;
23558 int blockedReplaced = 0;
23559 int blockedEntityReplay = 0;
23560 int blockedMissingRawBytes = 0;
23561 int blockedMissingClassMetadata = 0;
23562 int blockedWriterRejected = 0;
23563 int blockedVersionMismatch = 0;
23564 int blockedFixedHandle = 0;
23565 int blockedDataStorage = 0;
23566 int replayedObjects = 0;
23567 int replayedSections = 0;
23568 std::set<std::uint32_t> replayedRawObjectHandles;
23569 const LC_DwgAdvancedMetadata::RawObjectFamilyCounts rawFamilyCounts =
23570 metadata.rawObjectFamilyCounts();
23571 const LC_DwgAdvancedMetadata::TableNativeWriterBlockerCounts tableBlockers =
23572 metadata.tableNativeWriterBlockerCounts(m_dwgW->getVersion());
23573 const LC_DwgAdvancedMetadata::MLeaderWriterBlockerCounts mleaderBlockers =
23574 metadata.mleaderWriterBlockerCounts();
23575 const LC_DwgAdvancedMetadata::AdvancedEntityWriterBlockerCounts
23576 advancedEntityBlockers =
23577 metadata.advancedEntityWriterBlockerCounts(m_dwgW->getVersion());
23578 const LC_DwgAdvancedMetadata::ModelerPayloadCounts modelerPayloads =
23579 metadata.modelerPayloadCounts();
23580 const LC_DwgAdvancedMetadata::MeshWriterBlockerCounts meshBlockers =
23581 metadata.meshWriterBlockerCounts();
23582 const LC_DwgAdvancedMetadata::ExternalReferenceCounts externalRefs =
23583 metadata.externalReferenceCounts();
23584 const LC_DwgAdvancedMetadata::ShapeOleWriterBlockerCounts shapeOleBlockers =
23585 metadata.shapeOleWriterBlockerCounts();
23586 const LC_DwgAdvancedMetadata::VisualMetadataWriterBlockerCounts
23587 visualBlockers =
23588 metadata.visualMetadataWriterBlockerCounts(m_dwgW->getVersion());
23589 const LC_DwgAdvancedMetadata::AssociativeShellCounts associativeShells =
23590 metadata.associativeShellCounts();
23591 const LC_DwgAdvancedMetadata::AssociativePrefixCounts associativePrefixes =
23592 metadata.associativePrefixCounts();
23593 LC_DwgAdvancedMetadata::GraphReplayPolicyCounts graphReplayPolicy =
23594 metadata.graphReplayPolicyCounts();
23595 const bool replaceViewportEntityHeaderControl =
23596 m_dwgW->getVersion() < DRW::AC1018 &&
23597 !nativeViewportEntityHeaderHandles.empty();
23598 using DataStorageCarrier = DwgDataStorageCarrier;
23599 const auto &dataStorageCarriers = m_dwgDataStorageReplayPlan.carriers;
23600 const auto rawCarrier = [&metadata](const DataStorageCarrier &carrier)
23601 -> const LC_DwgAdvancedMetadata::RawObjectRecord * {
23602 if (carrier.capability != DataStorageCarrier::WriterCapability::Raw ||
23603 carrier.rawObjectIndex >= metadata.rawObjects().size())
23604 return nullptr;
23605 return &metadata.rawObjects()[carrier.rawObjectIndex];
23606 };
23607 auto isParsedDataStorageSection = [&metadata](const auto &section) {
23608 return std::any_of(metadata.dataStorages().cbegin(),
23609 metadata.dataStorages().cend(),
23610 [&section](const auto &storage) {
23611 return normalizeDwgTableName(storage.name) ==
23612 normalizeDwgTableName(section.name) &&
23613 storage.version == section.version;
23614 });
23615 };
23616
23617 const auto &replayableDataStorageSectionIndexes =
23618 m_dwgDataStorageReplayPlan.rawSectionIndexes;
23619 std::set<std::uint32_t> replayableRawDataStorageHandles;
23620 bool dataStorageLedgerValid = m_dwgDataStorageReplayPlan.valid;
23621 const auto targetDwgVersion = m_dwgW->getVersion();
23622 const bool targetSupportsDataStorage = targetDwgVersion > DRW::AC1024;
23623 const auto hasPlannedTypedClass =
23624 [this](const DwgDataStorageCarrier &carrier) {
23625 return carrier.className.empty() ||
23626 hasDwgDataStorageClassAdmission(
23627 carrier.ownerHandle, DwgDataStorageCarrier::Kind::Typed);
23628 };
23629 const auto hasPlannedRawClass =
23630 [this](const DwgDataStorageCarrier &carrier,
23631 const LC_DwgAdvancedMetadata::RawObjectRecord *raw) {
23632 return raw == nullptr || !raw->isCustomClass ||
23633 hasDwgDataStorageClassAdmission(
23634 carrier.ownerHandle, DwgDataStorageCarrier::Kind::Raw);
23635 };
23636 if (targetSupportsDataStorage) {
23637 // The frozen plan owns identity and preferred-record selection.
23638 // OBJECTS only chooses between the already-approved typed and raw
23639 // carriers after native class admission is known.
23640 for (const auto &decision : m_dwgDataStorageReplayPlan.decisions) {
23641 bool nativeTyped = false;
23642 bool replayableRaw = false;
23643 if (decision.typedCarrierIndex !=
23644 std::numeric_limits<std::size_t>::max()) {
23645 const auto *carrierDecision =
23646 dataStorageDecisionForCarrier(decision.typedCarrierIndex);
23647 if (carrierDecision == &decision &&
23648 decision.selectedCarrierIndex == decision.typedCarrierIndex) {
23649 const auto &carrier =
23650 dataStorageCarriers[decision.typedCarrierIndex];
23651 nativeTyped =
23652 carrier.ownerHandle != 0 &&
23653 m_dwgWriteHandleRemap.count(carrier.ownerHandle) == 0 &&
23654 m_dwgWriteEntityHandleRemap.count(carrier.ownerHandle) == 0 &&
23655 m_dwgWriteDuplicateEntityHandles.count(carrier.ownerHandle) ==
23656 0 &&
23657 nativeObjectHandles.count(carrier.ownerHandle) != 0
23658 // Custom classes are recorded explicitly by the
23659 // CLASSES phase. Fixed object types have no class
23660 // token, so they do not require class admission.
23661 && hasPlannedTypedClass(carrier);
23662 }
23663 }
23664 if (decision.rawCarrierIndex !=
23665 std::numeric_limits<std::size_t>::max()) {
23666 const auto *carrierDecision =
23667 dataStorageDecisionForCarrier(decision.rawCarrierIndex);
23668 if (carrierDecision == &decision &&
23669 decision.selectedCarrierIndex == decision.rawCarrierIndex) {
23670 const auto &carrier = dataStorageCarriers[decision.rawCarrierIndex];
23671 const auto *raw = rawCarrier(carrier);
23672 replayableRaw =
23673 carrier.ownerHandle != 0 &&
23674 m_dwgWriteHandleRemap.count(carrier.ownerHandle) == 0 &&
23675 m_dwgWriteEntityHandleRemap.count(carrier.ownerHandle) == 0 &&
23676 m_dwgWriteDuplicateEntityHandles.count(carrier.ownerHandle) ==
23677 0 &&
23678 raw != nullptr && raw->handle != 0 && !raw->rawBytes.empty() &&
23679 sameRawObjectEncodingFamily(raw->version, targetDwgVersion) &&
23680 !isFixedStructuralDwgHandle(raw->handle) &&
23681 LC_DwgAdvancedMetadata::rawReplayBlocker(*raw) ==
23682 LC_DwgAdvancedMetadata::ReplayBlocker::None &&
23683 hasPlannedRawClass(carrier, raw);
23684 if (replayableRaw && !nativeTyped)
23685 replayableRawDataStorageHandles.insert(raw->handle);
23686 }
23687 }
23688 if (!nativeTyped && !replayableRaw)
23689 dataStorageLedgerValid = false;
23690 }
23691 }
23692 if (targetSupportsDataStorage &&
23693 ((!dataStorageCarriers.empty() && metadata.dataStorages().empty()) ||
23694 (!dataStorageLedgerValid && !metadata.dataStorages().empty()))) {
23695 hasBlockedReplay = true;
23696 ++blockedDataStorage;
23697 }
23698
23699 // BLOCKREPRESENTATIONDATA has a typed prefix writer, but its AC1027+
23700 // DataStorage payload is still opaque. Keep the complete raw object
23701 // for same-version replay; reject an export that has no such carrier.
23702 for (const auto &record : metadata.blockRepresentationData()) {
23703 if (record.replayState !=
23704 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
23705 !record.hasDsData)
23706 continue;
23707 if (!hasReplayableRawBlockRepresentationData(metadata, record.handle,
23708 m_dwgW->getVersion())) {
23709 hasBlockedReplay = true;
23710 ++blockedDataStorage;
23711 }
23712 }
23713
23714 for (std::size_t rawSectionIndex = 0;
23715 rawSectionIndex < metadata.rawDwgSections().size();
23716 ++rawSectionIndex) {
23717 const auto &record = metadata.rawDwgSections()[rawSectionIndex];
23718 const bool isDataStorage = isParsedDataStorageSection(record);
23719 if (record.replayState !=
23720 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
23721 hasBlockedReplay = true;
23722 ++blockedInvalidated;
23723 if (isDataStorage)
23724 ++blockedDataStorage;
23725 continue;
23726 }
23727 if (metadata.sourceDwgVersion() != DRW::UNKNOWNV &&
23728 metadata.sourceDwgVersion() != m_dwgW->getVersion()) {
23729 hasBlockedReplay = true;
23730 ++blockedVersionMismatch;
23731 if (isDataStorage)
23732 ++blockedDataStorage;
23733 continue;
23734 }
23735 if (isDataStorage &&
23736 (!dataStorageLedgerValid ||
23737 replayableDataStorageSectionIndexes.count(rawSectionIndex) == 0)) {
23738 hasBlockedReplay = true;
23739 ++blockedInvalidated;
23740 ++blockedDataStorage;
23741 continue;
23742 }
23743 DRW_RawDwgSection section;
23744 section.m_name = record.name;
23745 section.m_version = record.version;
23746 section.m_data = record.data;
23747 section.m_encoding = record.encoding;
23748 section.m_encrypted = record.encrypted;
23749 section.m_maxSize = record.maxSize;
23750 if (m_dwgW->writeRawDwgSection(&section)) {
23751 if (isDataStorage &&
23752 replayableDataStorageSectionIndexes.count(rawSectionIndex) != 0) {
23753 std::set<std::size_t> recordIndexes;
23754 for (const auto &storage : metadata.dataStorages()) {
23755 if (storage.version != record.version ||
23756 normalizeDwgTableName(storage.name) !=
23757 normalizeDwgTableName(record.name))
23758 continue;
23759 for (std::size_t index = 0; index < storage.full.records.size();
23760 ++index)
23761 recordIndexes.insert(index);
23762 break;
23763 }
23764 if (!recordDwgDataStorageSectionCommit(rawSectionIndex,
23765 recordIndexes)) {
23766 dataStorageLedgerValid = false;
23767 hasBlockedReplay = true;
23768 ++blockedWriterRejected;
23769 ++blockedDataStorage;
23770 }
23771 }
23772 ++replayedSections;
23773 } else {
23774 hasBlockedReplay = true;
23775 ++blockedWriterRejected;
23776 if (isDataStorage)
23777 ++blockedDataStorage;
23778 }
23779 }
23780 for (const auto &entry :
23781 m_dwgExportAdmissionPlan.opaqueTableRecordIndexes) {
23782 const auto &graph = entry.second.ownerGraph;
23783 if (!graph.has_value())
23784 continue;
23785 if (graph->rawObjectIndex >= metadata.rawObjects().size() ||
23786 graph->rootDictionaryIndex >= metadata.dictionaries().size() ||
23787 graph->childDictionaryIndex >= metadata.dictionaries().size()) {
23788 m_writeFailed = true;
23789 m_dwgW->markDwgWriteFailure();
23790 return;
23791 }
23792 const auto &raw = metadata.rawObjects()[graph->rawObjectIndex];
23793 const auto &root = metadata.dictionaries()[graph->rootDictionaryIndex];
23794 const auto &source = metadata.dictionaries()[graph->childDictionaryIndex];
23795 if (raw.handle != graph->rawSourceHandle ||
23796 raw.handle != graph->rawOutputHandle ||
23797 raw.parentHandle != graph->rawOwnerHandle ||
23798 root.handle != graph->rootSourceHandle ||
23799 root.parentHandle != DRW::NoHandle || root.handle == source.handle ||
23800 root.handle == raw.handle ||
23801 graph->rootEntryOrdinal >= root.entries.size() ||
23802 root.entries[graph->rootEntryOrdinal].name != graph->rootEntryName ||
23803 root.entries[graph->rootEntryOrdinal].handle !=
23804 graph->childOutputHandle ||
23805 source.handle != graph->childSourceHandle ||
23806 source.handle != graph->childOutputHandle ||
23807 source.parentHandle != graph->rootSourceHandle ||
23808 graph->childEntryOrdinal >= source.entries.size() ||
23809 source.entries[graph->childEntryOrdinal].name !=
23810 graph->childEntryName ||
23811 source.entries[graph->childEntryOrdinal].handle !=
23812 graph->rawOutputHandle ||
23813 dwgOpaqueTableDictionaryDigest(root) != graph->rootIdentityDigest ||
23814 dwgOpaqueTableDictionaryDigest(source) !=
23815 graph->childIdentityDigest ||
23816 !canWriteDwgDictionaryRecord(metadata, source,
23817 m_dwgW->getVersion()) ||
23818 hasDxfTableAppDataUnsupportedByDwg(dictionaryFromMetadata(source))) {
23819 m_writeFailed = true;
23820 m_dwgW->markDwgWriteFailure();
23821 return;
23822 }
23823
23824 DRW_Dictionary dictionary = dictionaryFromMetadata(source);
23825 dictionary.parentHandle = graph->rootOutputHandle;
23826 referenceContext = {DwgWriteSourceKind::Object, graph->childSourceHandle,
23827 graph->childOutputHandle};
23828 if (!sanitizeDictionaryEntries(dictionary) ||
23829 !sanitizeCommonObjectReferences(dictionary,
23830 "DICTIONARY common reference") ||
23831 !writeDwgObject(DwgWriteSourceKind::Object, graph->childSourceHandle,
23832 [&dictionary, this] {
23833 return m_dwgW->writeDictionary(&dictionary);
23834 })) {
23835 m_writeFailed = true;
23836 m_dwgW->markDwgWriteFailure();
23837 return;
23838 }
23839 ++nativeDictionaryObjects;
23840 }
23841 for (std::size_t rawIndex = 0; rawIndex < metadata.rawObjects().size();
23842 ++rawIndex) {
23843 const auto &record = metadata.rawObjects()[rawIndex];
23844 // Typed metadata entities are emitted during ENTITIES. Their raw
23845 // frame is a read-side fallback, not a second object instance.
23846 if (isNativeMetadataEntityRawObject(metadata, record))
23847 continue;
23848 if (isPlotSettingsRawObject(record) &&
23849 !isDwgRawPlotSettingsSelected(record.handle)) {
23850 continue;
23851 }
23852 if (requiresDwgOpaqueTableAdmission(record) &&
23853 !isDwgOpaqueTableRawSelected(rawIndex)) {
23854 continue;
23855 }
23856 if (replaceViewportEntityHeaderControl &&
23857 isViewportEntityHeaderControlRawObject(record)) {
23858 hasBlockedReplay = true;
23859 ++blockedReplaced;
23860 continue;
23861 }
23862 if (nativeViewportEntityHeaderHandles.count(record.handle) != 0 &&
23863 isViewportEntityHeaderRawObject(record)) {
23864 hasBlockedReplay = true;
23865 ++blockedReplaced;
23866 continue;
23867 }
23868 if (nativeSunHandles.count(record.handle) != 0 &&
23869 isSunRawObject(record)) {
23870 hasBlockedReplay = true;
23871 ++blockedReplaced;
23872 continue;
23873 }
23874 if (nativeTvDevicePropertiesHandles.count(record.handle) != 0 &&
23875 isTvDevicePropertiesRawObject(record)) {
23876 hasBlockedReplay = true;
23877 ++blockedReplaced;
23878 continue;
23879 }
23880 if (nativeVxControlHandles.count(record.handle) != 0 &&
23881 isVxControlRawObject(record)) {
23882 hasBlockedReplay = true;
23883 ++blockedReplaced;
23884 continue;
23885 }
23886 if (nativeVxTableRecordHandles.count(record.handle) != 0 &&
23887 isVxTableRecordRawObject(record)) {
23888 hasBlockedReplay = true;
23889 ++blockedReplaced;
23890 continue;
23891 }
23892 if (nativePlaceholderHandles.count(record.handle) != 0 &&
23893 isAcDbPlaceholderRawObject(record)) {
23894 hasBlockedReplay = true;
23895 ++blockedReplaced;
23896 continue;
23897 }
23898 if (nativeVbaProjectHandles.count(record.handle) != 0 &&
23899 isVbaProjectRawObject(record)) {
23900 hasBlockedReplay = true;
23901 ++blockedReplaced;
23902 continue;
23903 }
23904 if (nativeMLeaderStyleHandles.count(record.handle) != 0 &&
23905 isMLeaderStyleRawObject(record)) {
23906 hasBlockedReplay = true;
23907 ++blockedReplaced;
23908 continue;
23909 }
23910 if (nativeMaterialHandles.count(record.handle) != 0 &&
23911 isMaterialRawObject(record)) {
23912 hasBlockedReplay = true;
23913 ++blockedReplaced;
23914 continue;
23915 }
23916 if (nativeLightListHandles.count(record.handle) != 0 &&
23917 isLightListRawObject(record)) {
23918 hasBlockedReplay = true;
23919 ++blockedReplaced;
23920 continue;
23921 }
23922 if (nativeBackgroundHandles.count(record.handle) != 0 &&
23923 isBackgroundRawObject(record)) {
23924 hasBlockedReplay = true;
23925 ++blockedReplaced;
23926 continue;
23927 }
23928 if (nativeSunStudyHandles.count(record.handle) != 0 &&
23929 isSunStudyRawObject(record)) {
23930 hasBlockedReplay = true;
23931 ++blockedReplaced;
23932 continue;
23933 }
23934 if (nativeMotionPathHandles.count(record.handle) != 0 &&
23935 isMotionPathRawObject(record)) {
23936 hasBlockedReplay = true;
23937 ++blockedReplaced;
23938 continue;
23939 }
23940 if (nativeCurvePathHandles.count(record.handle) != 0 &&
23941 isCurvePathRawObject(record)) {
23942 hasBlockedReplay = true;
23943 ++blockedReplaced;
23944 continue;
23945 }
23946 if (nativePointPathHandles.count(record.handle) != 0 &&
23947 isPointPathRawObject(record)) {
23948 hasBlockedReplay = true;
23949 ++blockedReplaced;
23950 continue;
23951 }
23952 if (nativeObjectPtrHandles.count(record.handle) != 0 &&
23953 isObjectPtrRawObject(record)) {
23954 hasBlockedReplay = true;
23955 ++blockedReplaced;
23956 continue;
23957 }
23958 if (nativePartialViewingIndexHandles.count(record.handle) != 0 &&
23959 isPartialViewingIndexRawObject(record)) {
23960 hasBlockedReplay = true;
23961 ++blockedReplaced;
23962 continue;
23963 }
23964 if (nativeRenderSettingsHandles.count(record.handle) != 0 &&
23965 isRenderSettingsRawObject(record)) {
23966 hasBlockedReplay = true;
23967 ++blockedReplaced;
23968 continue;
23969 }
23970 if (nativeVisualStyleHandles.count(record.handle) != 0 &&
23971 isVisualStyleRawObject(record)) {
23972 hasBlockedReplay = true;
23973 ++blockedReplaced;
23974 continue;
23975 }
23976 if (nativeDictionaryHandles.count(record.handle) != 0 &&
23977 isDictionaryRawObject(record)) {
23978 hasBlockedReplay = true;
23979 ++blockedReplaced;
23980 continue;
23981 }
23982 if (nativeXRecordHandles.count(record.handle) != 0 &&
23983 isXRecordRawObject(record)) {
23984 hasBlockedReplay = true;
23985 ++blockedReplaced;
23986 continue;
23987 }
23988 if (nativeLayoutHandles.count(record.handle) != 0 &&
23989 isLayoutRawObject(record)) {
23990 hasBlockedReplay = true;
23991 ++blockedReplaced;
23992 continue;
23993 }
23994 if (nativeGroupHandles.count(record.handle) != 0 &&
23995 isGroupRawObject(record)) {
23996 hasBlockedReplay = true;
23997 ++blockedReplaced;
23998 continue;
23999 }
24000 if (nativeMLineStyleHandles.count(record.handle) != 0 &&
24001 isMLineStyleRawObject(record)) {
24002 hasBlockedReplay = true;
24003 ++blockedReplaced;
24004 continue;
24005 }
24006 if (nativeTableStyleHandles.count(record.handle) != 0 &&
24007 isTableStyleRawObject(record)) {
24008 hasBlockedReplay = true;
24009 ++blockedReplaced;
24010 continue;
24011 }
24012 if (nativeDbColorHandles.count(record.handle) != 0 &&
24013 isDbColorRawObject(record)) {
24014 hasBlockedReplay = true;
24015 ++blockedReplaced;
24016 continue;
24017 }
24018 if (nativeDimensionAssociationHandles.count(record.handle) != 0 &&
24019 isDimensionAssociationRawObject(record)) {
24020 hasBlockedReplay = true;
24021 ++blockedReplaced;
24022 continue;
24023 }
24024 if (nativeEvaluationGraphHandles.count(record.handle) != 0 &&
24025 isEvaluationGraphRawObject(record)) {
24026 hasBlockedReplay = true;
24027 ++blockedReplaced;
24028 continue;
24029 }
24030 if (nativeSectionHandles.count(record.handle) != 0 &&
24031 isSectionRawObject(record)) {
24032 hasBlockedReplay = true;
24033 ++blockedReplaced;
24034 continue;
24035 }
24036 if (isSectionObjectRawEntity(record) &&
24037 m_dwgW->getVersion() >= DRW::AC1021 &&
24038 hasReplayableSectionObject(metadata, record.handle)) {
24039 hasBlockedReplay = true;
24040 ++blockedReplaced;
24041 continue;
24042 }
24043 if (nativeRasterVariablesHandles.count(record.handle) != 0 &&
24044 isRasterVariablesRawObject(record)) {
24045 hasBlockedReplay = true;
24046 ++blockedReplaced;
24047 continue;
24048 }
24049 if (nativeWipeoutVariablesHandles.count(record.handle) != 0 &&
24050 isWipeoutVariablesRawObject(record)) {
24051 hasBlockedReplay = true;
24052 ++blockedReplaced;
24053 continue;
24054 }
24055 if (nativeGeoDataHandles.count(record.handle) != 0 &&
24056 isGeoDataRawObject(record)) {
24057 hasBlockedReplay = true;
24058 ++blockedReplaced;
24059 continue;
24060 }
24061 if (nativeSpatialFilterHandles.count(record.handle) != 0 &&
24062 isSpatialFilterRawObject(record)) {
24063 hasBlockedReplay = true;
24064 ++blockedReplaced;
24065 continue;
24066 }
24067 // PR 8d.2a — five small no-storage OBJECTS families.
24068 if (nativeScaleHandles.count(record.handle) != 0 &&
24069 isScaleRawObject(record)) {
24070 hasBlockedReplay = true;
24071 ++blockedReplaced;
24072 continue;
24073 }
24074 if (nativeIDBufferHandles.count(record.handle) != 0 &&
24075 isIDBufferRawObject(record)) {
24076 hasBlockedReplay = true;
24077 ++blockedReplaced;
24078 continue;
24079 }
24080 if (nativeLayerIndexHandles.count(record.handle) != 0 &&
24081 isLayerIndexRawObject(record)) {
24082 hasBlockedReplay = true;
24083 ++blockedReplaced;
24084 continue;
24085 }
24086 if (nativeSpatialIndexHandles.count(record.handle) != 0 &&
24087 isSpatialIndexRawObject(record)) {
24088 hasBlockedReplay = true;
24089 ++blockedReplaced;
24090 continue;
24091 }
24092 if (nativeDictionaryVarHandles.count(record.handle) != 0 &&
24093 isDictionaryVarRawObject(record)) {
24094 hasBlockedReplay = true;
24095 ++blockedReplaced;
24096 continue;
24097 }
24098 // PR 8d.2b — four larger no-storage OBJECTS families.
24099 if (nativeDictionaryWithDefaultHandles.count(record.handle) != 0 &&
24100 isDictionaryWithDefaultRawObject(record)) {
24101 hasBlockedReplay = true;
24102 ++blockedReplaced;
24103 continue;
24104 }
24105 if (nativeSortEntsTableHandles.count(record.handle) != 0 &&
24106 isSortEntsTableRawObject(record)) {
24107 hasBlockedReplay = true;
24108 ++blockedReplaced;
24109 continue;
24110 }
24111 if (nativeFieldListHandles.count(record.handle) != 0 &&
24112 isFieldListRawObject(record)) {
24113 hasBlockedReplay = true;
24114 ++blockedReplaced;
24115 continue;
24116 }
24117 if (nativeFieldHandles.count(record.handle) != 0 &&
24118 isFieldRawObject(record)) {
24119 hasBlockedReplay = true;
24120 ++blockedReplaced;
24121 continue;
24122 }
24123 if (nativeUnderlayDefinitionHandles.count(record.handle) != 0 &&
24124 isUnderlayDefinitionRawObject(record)) {
24125 hasBlockedReplay = true;
24126 ++blockedReplaced;
24127 continue;
24128 }
24129 if (nativePointCloudDefinitionHandles.count(record.handle) != 0 &&
24130 isPointCloudDefinitionRawObject(record)) {
24131 hasBlockedReplay = true;
24132 ++blockedReplaced;
24133 continue;
24134 }
24135 if (nativeNavisworksModelDefinitionHandles.count(record.handle) != 0 &&
24136 isNavisworksModelDefinitionRawObject(record)) {
24137 hasBlockedReplay = true;
24138 ++blockedReplaced;
24139 continue;
24140 }
24141 if (nativePointCloudColorMapHandles.count(record.handle) != 0 &&
24142 isPointCloudColorMapRawObject(record)) {
24143 hasBlockedReplay = true;
24144 ++blockedReplaced;
24145 continue;
24146 }
24147 // A raw record with the same handle as a typed object from a
24148 // different family would create two OBJECTS identities. The
24149 // family-specific checks above intentionally allow a raw carrier
24150 // to be replaced by its typed counterpart; every other collision
24151 // is structurally ambiguous and must fail the export.
24152 if (nativeObjectHandles.count(record.handle) != 0) {
24153 hasBlockedReplay = true;
24154 ++blockedWriterRejected;
24155 m_writeFailed = true;
24156 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24157 "RS_FilterDXFRW: raw DWG object %s collides with a "
24158 "typed OBJECT handle 0x%X; export rejected",
24159 record.recordName.empty() ? "(unknown)"
24160 : record.recordName.c_str(),
24161 record.handle);
24162 continue;
24163 }
24164 // A raw object whose handle collides with the writer's fixed
24165 // structural handles can never be replayed: its verbatim bytes
24166 // cannot be rewritten to a fresh handle, and double emission
24167 // duplicates the object-map entry, failing the whole save in
24168 // writeDwgHandles(). Typed counterparts of these records are
24169 // remapped and emitted instead (see fileExport / P3 #2).
24170 if (isFixedStructuralDwgHandle(record.handle) &&
24171 !isRawViewportEntityHeaderControl(record)) {
24172 hasBlockedReplay = true;
24173 ++blockedFixedHandle;
24174 continue;
24175 }
24176 // Raw object bytes must remain in their exact source DWG version.
24177 // The matching writeDwgClasses guard prevents orphan CLASSES data.
24178 if (!sameRawObjectEncodingFamily(record.version, m_dwgW->getVersion())) {
24179 hasBlockedReplay = true;
24180 ++blockedVersionMismatch;
24181 continue;
24182 }
24183 if (targetSupportsDataStorage && record.hasDataStorage &&
24184 (!dataStorageLedgerValid ||
24185 replayableRawDataStorageHandles.count(record.handle) == 0)) {
24186 hasBlockedReplay = true;
24187 ++blockedInvalidated;
24188 ++blockedDataStorage;
24189 continue;
24190 }
24191 if (!record.isEntity && record.parentHandle != DRW::NoHandle &&
24192 !isDwgWriteOpaqueReferenceStable(record.parentHandle)) {
24193 hasBlockedReplay = true;
24194 ++blockedWriterRejected;
24195 m_writeFailed = true;
24196 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24197 "RS_FilterDXFRW: opaque DWG OBJECT %s has a remapped "
24198 "owner 0x%X; export rejected",
24199 record.recordName.empty() ? "(unknown)"
24200 : record.recordName.c_str(),
24201 record.parentHandle);
24202 continue;
24203 }
24204 const LC_DwgAdvancedMetadata::ReplayBlocker blocker =
24205 LC_DwgAdvancedMetadata::rawReplayBlocker(record);
24206 if (blocker != LC_DwgAdvancedMetadata::ReplayBlocker::None) {
24207 hasBlockedReplay = true;
24208 if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Invalidated)
24209 ++blockedInvalidated;
24210 else if (blocker == LC_DwgAdvancedMetadata::ReplayBlocker::Replaced)
24211 ++blockedReplaced;
24212 else if (blocker ==
24213 LC_DwgAdvancedMetadata::ReplayBlocker::EntityReplayUnsupported)
24214 ++blockedEntityReplay;
24215 else if (blocker ==
24216 LC_DwgAdvancedMetadata::ReplayBlocker::MissingRawBytes)
24217 ++blockedMissingRawBytes;
24218 else if (blocker ==
24219 LC_DwgAdvancedMetadata::ReplayBlocker::MissingClassMetadata)
24220 ++blockedMissingClassMetadata;
24221 else if (blocker ==
24222 LC_DwgAdvancedMetadata::ReplayBlocker::WriterRejected) {
24223 // Keep writer-side skip accounting authoritative for a
24224 // carrier rejected by the same validation used by replay.
24225 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
24226 (void)m_dwgW->writeRawDwgObject(&object);
24227 ++blockedWriterRejected;
24228 }
24229 continue;
24230 }
24231 if (record.handle == 0) {
24232 hasBlockedReplay = true;
24233 ++blockedWriterRejected;
24234 m_writeFailed = true;
24235 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24236 "RS_FilterDXFRW: raw DWG object %s has no self-handle; "
24237 "export rejected",
24238 record.recordName.empty() ? "(unknown)"
24239 : record.recordName.c_str());
24240 continue;
24241 }
24242 if (!replayedRawObjectHandles.insert(record.handle).second) {
24243 hasBlockedReplay = true;
24244 ++blockedWriterRejected;
24245 m_writeFailed = true;
24246 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24247 "RS_FilterDXFRW: duplicate raw DWG self-handle 0x%X; "
24248 "export rejected",
24249 record.handle);
24250 continue;
24251 }
24252 DRW_UnsupportedObject object = rawObjectFromMetadata(record);
24253 if (!rawCustomEntityOwnerFieldsAreReplayable(object,
24254 m_dwgW->getVersion())) {
24255 hasBlockedReplay = true;
24256 ++blockedWriterRejected;
24257 m_writeFailed = true;
24258 continue;
24259 }
24260 const std::uint32_t sourceOwner =
24261 object.m_blockOwnerHandle != DRW::NoHandle ? object.m_blockOwnerHandle
24262 : object.m_parentHandle;
24263 if (object.m_isEntity && object.m_isCustomClass &&
24264 sourceOwner != DRW::NoHandle) {
24265 const std::uint32_t targetOwner =
24266 remapBlockOwnerHandle(sourceOwner, "raw custom entity owner", 0,
24267 DRW::DwgSoftPointer, false);
24268 if (targetOwner == 0) {
24269 hasBlockedReplay = true;
24270 ++blockedWriterRejected;
24271 m_writeFailed = true;
24272 continue;
24273 }
24274 // R2000/R2004 owner bytes are not safely locatable in an
24275 // opaque body. Modern entities are patched in the writer.
24276 if (targetOwner != sourceOwner && !isDwgSpaceBlockOwner(sourceOwner) &&
24277 (object.m_version <= DRW::AC1018 ||
24278 object.m_parentHandle == DRW::NoHandle)) {
24279 hasBlockedReplay = true;
24280 ++blockedWriterRejected;
24281 m_writeFailed = true;
24282 continue;
24283 }
24284 object.m_blockOwnerHandle = targetOwner;
24285 }
24286 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24287 DwgWriteSourceKind::RawCarrier,
24288 record.handle, [&object, this] {
24289 return m_dwgW->writeRawDwgObject(&object);
24290 })) {
24291 ++replayedObjects;
24292 } else {
24293 hasBlockedReplay = true;
24294 ++blockedWriterRejected;
24295 if (object.m_isEntity && object.m_isCustomClass)
24296 m_writeFailed = true;
24297 }
24298 }
24299 if (!validateDwgOpaqueTableReferenceReceipts(
24300 DwgOpaqueTableReceiptStage::RawObjectsEmitted,
24301 DwgOpaqueTableFailurePhase::Objects)) {
24302 m_writeFailed = true;
24303 m_dwgW->markDwgWriteFailure();
24304 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24305 "RS_FilterDXFRW: opaque DWG table relation receipts are "
24306 "missing after OBJECTS replay");
24307 return;
24308 }
24309 if (canWriteModernObjects) {
24310 // ACDBPLACEHOLDER dispatch moved below to the
24311 // `canWriteFixedTypeObjects` block (PR 13d).
24312 // PR 13d — SUN stays gated AC1021+ — custom class 503 was
24313 // introduced in R2007 and the encoder explicitly rejects
24314 // below AC1021 (see DRW_Sun::encodeDwg at drw_objects.cpp:
24315 // `if (buf == nullptr || version < DRW::AC1021) return
24316 // false`). Do not move into canWriteFixedTypeObjects.
24317 for (const auto &record : metadata.suns()) {
24318 if (record.replayState !=
24319 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
24320 record.handle == 0) {
24321 continue;
24322 }
24323 DRW_Sun sun = sunFromMetadata(record);
24324 if (hasDxfTableAppDataUnsupportedByDwg(sun)) {
24325 hasBlockedReplay = true;
24326 ++blockedWriterRejected;
24327 continue;
24328 }
24329 remapEntry(sun);
24330 if (!sanitizeCommonObjectReferences(sun, "SUN common reference")) {
24331 hasBlockedReplay = true;
24332 ++blockedWriterRejected;
24333 continue;
24334 }
24335 sun.setDwgCommonObjectState(
24336 static_cast<std::int32_t>(sun.reactorHandles.size()),
24337 sun.extensionDictionaryFlag(),
24338 encodedDwgDataStoragePresenceBit(record.handle,
24339 sun.hasDataStorageBinaryData()));
24340 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24341 DwgWriteSourceKind::Object,
24342 record.handle,
24343 [&sun, this] { return m_dwgW->writeSun(&sun); })) {
24344 ++nativeSunObjects;
24345 } else {
24346 hasBlockedReplay = true;
24347 ++blockedWriterRejected;
24348 }
24349 }
24350 // DICTIONARY / XRECORD / LAYOUT / GROUP dispatch moved below
24351 // to the `canWriteFixedTypeObjects` block (PR 13a/b/c).
24352 // RASTERVARIABLES / GEODATA / SPATIAL_FILTER dispatch moved
24353 // to the `canRegisterCustomClassObjects` block (PR 13f).
24354 // PR 8d.2a — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
24355 // DICTIONARYVAR dispatch moved to the
24356 // `canRegisterCustomClassObjects` block (PR 13g).
24357 // PR 8d.2b — four larger no-storage OBJECTS families.
24358 // Dispatch moved to the `canRegisterCustomClassObjects` block
24359 // (PR 13h).
24360 }
24361 // PR 13a/b/c/d — DICTIONARY / XRECORD / GROUP / LAYOUT /
24362 // ACDBPLACEHOLDER native dispatch. Gate broadened from AC1021+ to
24363 // AC1015+ now that the encoder smoke tests cover the full
24364 // AC1015/AC1018/AC1024/AC1027/AC1032 range (see
24365 // `[dwg-write][dictionary]`, `[dwg-write][xrecord]`,
24366 // `[dwg-write][group]`, `[dwg-write][layout]`,
24367 // `[dwg-write][placeholder]` cases). Sits outside the `if
24368 // (canWriteModernObjects)` block so the broadened gate also
24369 // applies at AC1015/AC1018.
24370 if (canWriteFixedTypeObjects) {
24371 for (const auto &project : metadata.vbaProjects()) {
24372 if (nativeVbaProjectHandles.count(project.handle) == 0)
24373 continue;
24374 DRW_VbaProject object = project;
24375 remapEntry(object);
24376 if (object.hasDataStorageBinaryData() ||
24377 hasDxfTableAppDataUnsupportedByDwg(object)) {
24378 hasBlockedReplay = true;
24379 ++blockedWriterRejected;
24380 continue;
24381 }
24382 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24383 DwgWriteSourceKind::Object,
24384 project.handle, [&object, this] {
24385 return m_dwgW->writeVbaProject(&object);
24386 })) {
24387 ++nativeVbaProjectObjects;
24388 } else {
24389 hasBlockedReplay = true;
24390 ++blockedWriterRejected;
24391 }
24392 }
24393 for (const auto &record : metadata.placeholders()) {
24394 if (record.replayState !=
24395 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
24396 record.handle == 0) {
24397 continue;
24398 }
24399 DRW_AcDbPlaceholder placeholder = placeholderFromMetadata(record);
24400 if (hasDxfTableAppDataUnsupportedByDwg(placeholder)) {
24401 hasBlockedReplay = true;
24402 ++blockedWriterRejected;
24403 continue;
24404 }
24405 remapEntry(placeholder);
24406 if (!sanitizeCommonObjectReferences(
24407 placeholder, "ACDBPLACEHOLDER common reference")) {
24408 hasBlockedReplay = true;
24409 ++blockedWriterRejected;
24410 continue;
24411 }
24412 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24413 DwgWriteSourceKind::Object,
24414 record.handle, [&placeholder, this] {
24415 return m_dwgW->writeAcDbPlaceholder(&placeholder);
24416 })) {
24417 ++nativePlaceholderObjects;
24418 } else {
24419 hasBlockedReplay = true;
24420 ++blockedWriterRejected;
24421 }
24422 }
24423 for (const auto &record : metadata.blockRepresentationData()) {
24424 if (nativeBlockRepresentationHandles.count(record.handle) == 0)
24425 continue;
24426 DRW_BlockRepresentationData data =
24427 blockRepresentationFromMetadata(record);
24428 if (hasDxfTableAppDataUnsupportedByDwg(data)) {
24429 hasBlockedReplay = true;
24430 ++blockedWriterRejected;
24431 continue;
24432 }
24433 remapEntry(data);
24434 if (!sanitizeCommonObjectReferences(
24435 data, "BLOCKREPRESENTATIONDATA common reference")) {
24436 hasBlockedReplay = true;
24437 ++blockedWriterRejected;
24438 continue;
24439 }
24440 if (m_dwgW->getVersion() <= DRW::AC1024 &&
24441 data.hasDataStorageBinaryData()) {
24442 data.setDwgCommonObjectState(data.reactorCount(),
24443 data.extensionDictionaryFlag(), false);
24444 }
24445 const std::uint32_t sourceBlock = data.m_blockHandle;
24446 data.m_blockHandle =
24447 remapBlockOwnerHandle(sourceBlock, "BLOCKREPRESENTATIONDATA block",
24448 0, DRW::DwgHardOwnership);
24449 if (sourceBlock != 0 && data.m_blockHandle == 0) {
24450 hasBlockedReplay = true;
24451 ++blockedWriterRejected;
24452 continue;
24453 }
24454 if (!writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24455 DwgWriteSourceKind::Object,
24456 record.handle, [&data, this] {
24457 return m_dwgW->writeBlockRepresentationData(&data);
24458 })) {
24459 hasBlockedReplay = true;
24460 ++blockedWriterRejected;
24461 }
24462 }
24463 for (std::size_t index = 0; index < metadata.dictionaries().size();
24464 ++index) {
24465 if (ownerGraphDictionaryIndexes.count(index) != 0)
24466 continue;
24467 const auto &record = metadata.dictionaries()[index];
24468 if (nativeDictionaryHandles.count(record.handle) == 0)
24469 continue;
24470 DRW_Dictionary dictionary = dictionaryFromMetadata(record);
24471 if (hasDxfTableAppDataUnsupportedByDwg(dictionary)) {
24472 hasBlockedReplay = true;
24473 ++blockedWriterRejected;
24474 continue;
24475 }
24476 sanitizeDictionaryOwner(dictionary.parentHandle, "dictionary");
24477 remapEntryHandle(dictionary);
24478 if (!sanitizeDictionaryEntries(dictionary) ||
24479 !sanitizeCommonObjectReferences(dictionary,
24480 "DICTIONARY common reference")) {
24481 hasBlockedReplay = true;
24482 ++blockedWriterRejected;
24483 continue;
24484 }
24485 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24486 DwgWriteSourceKind::Object,
24487 record.handle, [&dictionary, this] {
24488 return m_dwgW->writeDictionary(&dictionary);
24489 })) {
24490 ++nativeDictionaryObjects;
24491 } else {
24492 hasBlockedReplay = true;
24493 ++blockedWriterRejected;
24494 }
24495 }
24496 for (const auto &record : metadata.xrecords()) {
24497 if (nativeXRecordHandles.count(record.handle) == 0)
24498 continue;
24499 DRW_XRecord xrecord = xrecordFromMetadata(record);
24500 if (hasDxfTableAppDataUnsupportedByDwg(xrecord)) {
24501 hasBlockedReplay = true;
24502 ++blockedWriterRejected;
24503 continue;
24504 }
24505 remapEntry(xrecord);
24506 for (std::size_t ordinal = 0; ordinal < xrecord.m_handleValues.size();
24507 ++ordinal)
24508 sanitizeObjectHandle(xrecord.m_handleValues[ordinal].second,
24509 "XRECORD", static_cast<std::uint32_t>(ordinal));
24510 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24511 DwgWriteSourceKind::Object,
24512 record.handle, [&xrecord, this] {
24513 return m_dwgW->writeXRecord(&xrecord);
24514 })) {
24515 ++nativeXRecordObjects;
24516 } else {
24517 hasBlockedReplay = true;
24518 ++blockedWriterRejected;
24519 }
24520 }
24521 for (const auto &record : metadata.groups()) {
24522 if (nativeGroupHandles.count(record.handle) == 0)
24523 continue;
24524 DRW_Group group = groupFromMetadata(record);
24525 if (hasDxfTableAppDataUnsupportedByDwg(group)) {
24526 hasBlockedReplay = true;
24527 ++blockedWriterRejected;
24528 continue;
24529 }
24530 remapEntry(group);
24531 if (!remapRequiredEntityList(group.m_entityHandles, "GROUP member")) {
24532 hasBlockedReplay = true;
24533 ++blockedWriterRejected;
24534 continue;
24535 }
24536 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24537 DwgWriteSourceKind::Object,
24538 record.handle, [&group, this] {
24539 return m_dwgW->writeGroup(&group);
24540 })) {
24541 ++nativeGroupObjects;
24542 } else {
24543 hasBlockedReplay = true;
24544 ++blockedWriterRejected;
24545 }
24546 }
24547 for (const auto &record : metadata.mlineStyles()) {
24548 if (nativeMLineStyleHandles.count(record.handle) == 0)
24549 continue;
24550 DRW_MLineStyle style = mlineStyleFromMetadata(record);
24551 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
24552 hasBlockedReplay = true;
24553 ++blockedWriterRejected;
24554 continue;
24555 }
24556 remapEntry(style);
24557 if (m_dwgW->getVersion() >= DRW::AC1032) {
24558 for (std::size_t i = 0; i < style.elements.size(); ++i) {
24559 const auto &element = style.elements[i];
24560 const std::string sourceName =
24561 !element.linetype.empty()
24562 ? element.linetype
24563 : metadata.lineTypeNameForHandle(element.linetypeHandle);
24564 const auto outputIt = m_dwgWriteLTypeHandleByName.find(
24565 normalizeDwgTableName(sourceName));
24566 if (outputIt != m_dwgWriteLTypeHandleByName.end()) {
24567 style.elements[i].linetypeHandle = outputIt->second;
24568 continue;
24569 }
24570 if (element.linetypeHandle != 0) {
24571 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24572 "RS_FilterDXFRW: dropping unresolved DWG "
24573 "MLINESTYLE element LTYPE 0x%X",
24574 element.linetypeHandle);
24575 style.elements[i].linetypeHandle = 0;
24576 }
24577 }
24578 }
24579 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24580 DwgWriteSourceKind::Object,
24581 record.handle, [&style, this] {
24582 return m_dwgW->writeMLineStyle(&style);
24583 })) {
24584 ++nativeMLineStyleObjects;
24585 } else {
24586 hasBlockedReplay = true;
24587 ++blockedWriterRejected;
24588 }
24589 }
24590 for (const auto &record : metadata.layouts()) {
24591 if (nativeLayoutHandles.count(record.handle) == 0)
24592 continue;
24593 DRW_Layout layout = layoutFromMetadata(record);
24594 if (hasDxfTableAppDataUnsupportedByDwg(layout)) {
24595 hasBlockedReplay = true;
24596 ++blockedWriterRejected;
24597 continue;
24598 }
24599 remapEntry(layout);
24600 if (m_dwgW->getVersion() < DRW::AC1018) {
24601 // R2000 stores the plot view by name; later versions use
24602 // a handle in the object tail. Resolve the source handle
24603 // before dropping the unavailable legacy tail field.
24604 if (layout.plotViewName.empty() &&
24605 record.plotViewHandle != DRW::NoHandle) {
24606 const auto *view = metadata.findViewByHandle(record.plotViewHandle);
24607 if (view != nullptr)
24608 layout.plotViewName = view->name;
24609 }
24610 layout.plotViewHandle.ref = 0;
24611 } else {
24612 layout.plotViewHandle.ref =
24613 remapViewHandle(record.plotViewHandle, "LAYOUT plot VIEW");
24614 }
24615 layout.shadePlotHandle.ref = 0;
24616 sanitizeObjectHandle(layout.shadePlotHandle.ref, "LAYOUT shade plot");
24617 layout.paperSpaceBlockRecordHandle.ref =
24618 remapBlockOwnerHandle(record.paperSpaceBlockRecordHandle);
24619 layout.lastActiveViewportHandle.ref = remapVportHandle(
24620 record.lastActiveViewportHandle, "LAYOUT active VPORT");
24621 layout.namedUcsHandle.ref =
24622 remapUcsHandle(record.namedUcsHandle, "LAYOUT named UCS");
24623 layout.baseUcsHandle.ref =
24624 remapUcsHandle(record.baseUcsHandle, "LAYOUT base UCS");
24625 layout.viewportHandles.clear();
24626 layout.viewportHandles.reserve(record.viewportHandles.size());
24627 for (const std::uint32_t sourceHandle : record.viewportHandles) {
24628 const std::uint32_t remapped =
24629 remapVportHandle(sourceHandle, "LAYOUT VPORT");
24630 if (remapped != 0)
24631 layout.viewportHandles.push_back(remapped);
24632 }
24633 layout.viewportCount =
24634 static_cast<std::int32_t>(layout.viewportHandles.size());
24635 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24636 DwgWriteSourceKind::Object,
24637 record.handle, [&layout, this] {
24638 return m_dwgW->writeLayout(&layout);
24639 })) {
24640 ++nativeLayoutObjects;
24641 } else {
24642 hasBlockedReplay = true;
24643 ++blockedWriterRejected;
24644 }
24645 }
24646 }
24647 if (canWriteDbColor) {
24648 for (const auto &record : metadata.dbColors()) {
24649 if (nativeDbColorHandles.count(record.handle) == 0)
24650 continue;
24651 DRW_DbColor color = dbColorFromMetadata(record);
24652 if (hasDxfTableAppDataUnsupportedByDwg(color)) {
24653 hasBlockedReplay = true;
24654 ++blockedWriterRejected;
24655 continue;
24656 }
24657 remapEntry(color);
24658 if (!sanitizeCommonObjectReferences(color,
24659 "DBCOLOR common reference")) {
24660 hasBlockedReplay = true;
24661 ++blockedWriterRejected;
24662 continue;
24663 }
24664 color.setDwgCommonObjectState(
24665 static_cast<std::int32_t>(color.reactorHandles.size()),
24666 color.extensionDictionaryFlag(),
24667 encodedDwgDataStoragePresenceBit(record.handle,
24668 color.hasDataStorageBinaryData()));
24669 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24670 DwgWriteSourceKind::Object,
24671 record.handle, [&color, this] {
24672 return m_dwgW->writeDbColor(&color);
24673 })) {
24674 ++nativeDbColorObjects;
24675 } else {
24676 hasBlockedReplay = true;
24677 ++blockedWriterRejected;
24678 }
24679 }
24680 }
24681 if (canWriteAssociativeObjects) {
24682 for (const auto &record : metadata.dimensionAssociations()) {
24683 if (nativeDimensionAssociationHandles.count(record.handle) == 0)
24684 continue;
24685 DRW_DimensionAssociation association =
24686 dimensionAssociationFromMetadata(record);
24687 if (hasDxfTableAppDataUnsupportedByDwg(association)) {
24688 hasBlockedReplay = true;
24689 ++blockedWriterRejected;
24690 continue;
24691 }
24692 remapEntry(association);
24693 sanitizeOptionalEntityHandle(association.m_dimensionHandle,
24694 "DIMASSOC dimension");
24695 if (m_writeFailed) {
24696 hasBlockedReplay = true;
24697 ++blockedWriterRejected;
24698 continue;
24699 }
24700 for (std::size_t ordinal = 0; ordinal < association.m_osnapRefs.size();
24701 ++ordinal)
24702 sanitizeOptionalCommonHandle(
24703 association.m_osnapRefs[ordinal].m_objectHandle, "DIMASSOC osnap",
24704 static_cast<std::uint32_t>(ordinal));
24705 if (m_writeFailed) {
24706 hasBlockedReplay = true;
24707 ++blockedWriterRejected;
24708 continue;
24709 }
24710 if (!sanitizeCommonObjectReferences(association,
24711 "DIMASSOC common reference")) {
24712 hasBlockedReplay = true;
24713 ++blockedWriterRejected;
24714 continue;
24715 }
24716 association.setDwgCommonObjectState(
24717 static_cast<std::int32_t>(association.reactorHandles.size()),
24718 association.extensionDictionaryFlag(),
24719 encodedDwgDataStoragePresenceBit(
24720 record.handle, association.hasDataStorageBinaryData()));
24721 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24722 DwgWriteSourceKind::Object,
24723 record.handle, [&association, this] {
24724 return m_dwgW->writeDimensionAssociation(
24725 &association);
24726 })) {
24727 ++nativeDimensionAssociationObjects;
24728 } else {
24729 hasBlockedReplay = true;
24730 ++blockedWriterRejected;
24731 }
24732 }
24733 for (const auto &record : metadata.evaluationGraphs()) {
24734 if (nativeEvaluationGraphHandles.count(record.handle) == 0)
24735 continue;
24736 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
24737 if (hasDxfTableAppDataUnsupportedByDwg(graph)) {
24738 hasBlockedReplay = true;
24739 ++blockedWriterRejected;
24740 continue;
24741 }
24742 remapEntry(graph);
24743 for (std::size_t ordinal = 0; ordinal < graph.m_nodes.size(); ++ordinal)
24744 sanitizeObjectHandle(graph.m_nodes[ordinal].m_expressionHandle,
24745 "evaluation-graph",
24746 static_cast<std::uint32_t>(ordinal));
24747 if (!sanitizeCommonObjectReferences(
24748 graph, "EVALUATION_GRAPH common reference")) {
24749 hasBlockedReplay = true;
24750 ++blockedWriterRejected;
24751 continue;
24752 }
24753 graph.setDwgCommonObjectState(
24754 static_cast<std::int32_t>(graph.reactorHandles.size()),
24755 graph.extensionDictionaryFlag(),
24756 encodedDwgDataStoragePresenceBit(record.handle,
24757 graph.hasDataStorageBinaryData()));
24758 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24759 DwgWriteSourceKind::Object,
24760 record.handle, [&graph, this] {
24761 return m_dwgW->writeEvaluationGraph(&graph);
24762 })) {
24763 ++nativeEvaluationGraphObjects;
24764 } else {
24765 hasBlockedReplay = true;
24766 ++blockedWriterRejected;
24767 }
24768 }
24769 }
24770 for (const auto &record : metadata.sections()) {
24771 if (nativeSectionHandles.count(record.handle) == 0)
24772 continue;
24773 DRW_Section section = sectionFromMetadata(record);
24774 if (hasDxfTableAppDataUnsupportedByDwg(section)) {
24775 hasBlockedReplay = true;
24776 ++blockedWriterRejected;
24777 continue;
24778 }
24779 remapEntry(section);
24780 if (!sanitizeCommonObjectReferences(section,
24781 "SECTION common reference")) {
24782 hasBlockedReplay = true;
24783 ++blockedWriterRejected;
24784 continue;
24785 }
24786 section.setDwgCommonObjectState(
24787 static_cast<std::int32_t>(section.reactorHandles.size()),
24788 section.extensionDictionaryFlag(),
24789 encodedDwgDataStoragePresenceBit(record.handle,
24790 section.hasDataStorageBinaryData()));
24791 if (section.m_kind == DRW_Section::Manager) {
24792 section.m_sectionHandles.erase(
24793 std::remove_if(section.m_sectionHandles.begin(),
24794 section.m_sectionHandles.end(),
24795 [&remapSectionObjectHandle](std::uint32_t &handle) {
24796 handle = remapSectionObjectHandle(
24797 handle, "SECTION manager");
24798 return handle == 0;
24799 }),
24800 section.m_sectionHandles.end());
24801 section.m_sectionCount =
24802 static_cast<std::int32_t>(section.m_sectionHandles.size());
24803 } else {
24804 for (auto &type : section.m_types) {
24805 type.m_sourceHandles.erase(
24806 std::remove_if(
24807 type.m_sourceHandles.begin(), type.m_sourceHandles.end(),
24808 [&remapSectionObjectHandle](std::uint32_t &handle) {
24809 handle = remapSectionObjectHandle(handle, "SECTION source");
24810 return handle == 0;
24811 }),
24812 type.m_sourceHandles.end());
24813 type.m_numSources =
24814 static_cast<std::int32_t>(type.m_sourceHandles.size());
24815 type.m_destinationBlockHandle =
24816 remapBlockOwnerHandle(type.m_destinationBlockHandle);
24817 }
24818 section.m_typeCount = static_cast<std::int32_t>(section.m_types.size());
24819 }
24820 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24821 DwgWriteSourceKind::Object,
24822 record.handle, [&section, this] {
24823 return m_dwgW->writeSection(&section);
24824 })) {
24825 ++nativeSectionObjects;
24826 } else {
24827 hasBlockedReplay = true;
24828 ++blockedWriterRejected;
24829 }
24830 }
24831 // PR 13f — RASTERVARIABLES / GEODATA / SPATIAL_FILTER / BACKGROUND native
24832 // dispatch. Gate broadened from AC1021+ to AC1015+ now that the
24833 // smoke tests cover AC1015/AC1018/AC1024/AC1027/AC1032 (see
24834 // `[dwg-write][rastervariables]`, `[dwg-write][geodata]`,
24835 // `[dwg-write][spatial-filter]`). Matches the
24836 // `canRegisterCustomClassObjects` gate used in writeDwgClasses so
24837 // the CLASSES section entry and the OBJECTS instance always land
24838 // together.
24839 if (canRegisterCustomClassObjects) {
24840 for (const auto &record : metadata.mleaderStyles()) {
24841 if (nativeMLeaderStyleHandles.count(record.handle) == 0)
24842 continue;
24843 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
24844 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
24845 hasBlockedReplay = true;
24846 ++blockedWriterRejected;
24847 continue;
24848 }
24849 remapEntry(style);
24850 style.leaderLineTypeHandle.ref = remapTableHandle(
24851 record.leaderLineTypeHandle,
24852 metadata.lineTypeNameForHandle(record.leaderLineTypeHandle),
24853 m_dwgWriteLTypeHandleByName, "MLEADERSTYLE LTYPE");
24854 style.textStyleHandle.ref = remapTableHandle(
24855 record.textStyleHandle,
24856 metadata.textStyleNameForHandle(record.textStyleHandle),
24857 m_dwgWriteTextStyleHandleByName, "MLEADERSTYLE STYLE");
24858 style.arrowHeadBlockHandle.ref = remapBlockOwnerHandle(
24859 record.arrowHeadBlockHandle, "MLEADERSTYLE arrow block", 0,
24860 DRW::DwgHardPointer);
24861 style.blockHandle.ref = remapBlockOwnerHandle(
24862 record.blockHandle, "MLEADERSTYLE block", 0, DRW::DwgHardPointer);
24863 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24864 DwgWriteSourceKind::Object,
24865 record.handle, [&style, this] {
24866 return m_dwgW->writeMLeaderStyle(&style);
24867 })) {
24868 ++nativeMLeaderStyleObjects;
24869 } else {
24870 hasBlockedReplay = true;
24871 ++blockedWriterRejected;
24872 }
24873 }
24874 if (m_dwgW->getVersion() <= DRW::AC1021) {
24875 for (const auto &record : metadata.tables()) {
24876 if (nativeTableStyleHandles.count(record.handle) == 0 ||
24877 !record.nativeTableStyle)
24878 continue;
24879 DRW_TableStyle style = *record.nativeTableStyle;
24880 if (hasDxfTableAppDataUnsupportedByDwg(style) ||
24881 !style.validateDwgPayload(m_dwgW->getVersion())) {
24882 hasBlockedReplay = true;
24883 ++blockedWriterRejected;
24884 continue;
24885 }
24886 remapEntry(style);
24887 if (!sanitizeCommonObjectReferences(style,
24888 "TABLESTYLE common reference")) {
24889 hasBlockedReplay = true;
24890 ++blockedWriterRejected;
24891 continue;
24892 }
24893 for (std::size_t rowIndex = 0; rowIndex < style.m_rowStyles.size();
24894 ++rowIndex) {
24895 DRW_TableStyleRowStyle &row = style.m_rowStyles[rowIndex];
24896 row.m_textStyleHandle = remapTableHandle(
24897 row.m_textStyleHandle,
24898 metadata.textStyleNameForHandle(row.m_textStyleHandle),
24899 m_dwgWriteTextStyleHandleByName, "TABLESTYLE STYLE",
24900 static_cast<std::uint32_t>(rowIndex));
24901 }
24902 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24903 DwgWriteSourceKind::Object,
24904 record.handle, [&style, this] {
24905 return m_dwgW->writeTableStyle(&style);
24906 })) {
24907 ++nativeTableStyleObjects;
24908 } else {
24909 hasBlockedReplay = true;
24910 ++blockedWriterRejected;
24911 }
24912 }
24913 }
24914 for (const auto &record : metadata.materials()) {
24915 if (nativeMaterialHandles.count(record.handle) == 0)
24916 continue;
24917 DRW_Material material = materialFromMetadata(record);
24918 if (hasDxfTableAppDataUnsupportedByDwg(material)) {
24919 hasBlockedReplay = true;
24920 ++blockedWriterRejected;
24921 continue;
24922 }
24923 remapEntry(material);
24924 if (!sanitizeCommonObjectReferences(material,
24925 "MATERIAL common reference")) {
24926 hasBlockedReplay = true;
24927 ++blockedWriterRejected;
24928 continue;
24929 }
24930 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
24931 DwgWriteSourceKind::Object,
24932 record.handle, [&material, this] {
24933 return m_dwgW->writeMaterial(&material);
24934 })) {
24935 ++nativeMaterialObjects;
24936 } else {
24937 hasBlockedReplay = true;
24938 ++blockedWriterRejected;
24939 }
24940 }
24941 for (const auto &record : metadata.lightLists()) {
24942 if (nativeLightListHandles.count(record.handle) == 0)
24943 continue;
24944 DRW_LightList lightList = lightListFromMetadata(record);
24945 if (hasDxfTableAppDataUnsupportedByDwg(lightList)) {
24946 hasBlockedReplay = true;
24947 ++blockedWriterRejected;
24948 continue;
24949 }
24950 remapEntry(lightList);
24951 if (!sanitizeCommonObjectReferences(lightList,
24952 "LIGHTLIST common reference")) {
24953 hasBlockedReplay = true;
24954 ++blockedWriterRejected;
24955 continue;
24956 }
24957 struct PendingLightReference {
24958 std::uint32_t sourceHandle = 0;
24959 std::uint32_t outputHandle = 0;
24960 std::uint32_t ordinal = 0;
24961 DwgWriteReferenceStatus targetStatus =
24962 DwgWriteReferenceStatus::Missing;
24963 bool required = false;
24964 };
24965 std::vector<PendingLightReference> pendingReferences;
24966 std::vector<DRW_LightListEntry> retainedLights;
24967 pendingReferences.reserve(lightList.m_lights.size());
24968 retainedLights.reserve(lightList.m_lights.size());
24969 for (std::uint32_t ordinal = 0; ordinal < lightList.m_lights.size();
24970 ++ordinal) {
24971 DRW_LightListEntry light = lightList.m_lights[ordinal];
24972 const std::uint32_t sourceLightHandle = light.m_handle;
24973 if (sourceLightHandle == 0)
24974 continue;
24975 std::uint32_t outputLightHandle = 0;
24976 const DwgWriteReferenceStatus lightStatus =
24977 resolveDwgWriteEntityHandle(sourceLightHandle,
24978 &outputLightHandle);
24979 if (lightStatus != DwgWriteReferenceStatus::Committed ||
24980 outputLightHandle == 0) {
24981 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
24982 "RS_FilterDXFRW: dropping dangling DWG "
24983 "LIGHTLIST light target 0x%X",
24984 sourceLightHandle);
24985 const DwgWriteReferenceStatus dropStatus =
24986 lightStatus == DwgWriteReferenceStatus::Ambiguous
24987 ? DwgWriteReferenceStatus::Ambiguous
24988 : lightStatus == DwgWriteReferenceStatus::Suppressed
24989 ? DwgWriteReferenceStatus::Suppressed
24990 : DwgWriteReferenceStatus::Missing;
24991 pendingReferences.push_back(
24992 {sourceLightHandle, 0, ordinal, dropStatus, false});
24993 continue;
24994 }
24995 light.m_handle = outputLightHandle;
24996 retainedLights.push_back(light);
24997 pendingReferences.push_back(
24998 {sourceLightHandle, light.m_handle, ordinal,
24999 DwgWriteReferenceStatus::Committed, true});
25000 }
25001 lightList.m_lights = std::move(retainedLights);
25002 lightList.m_lightCount =
25003 static_cast<std::uint32_t>(lightList.m_lights.size());
25004 const bool wroteLightList = writeDwgObjectWithCompletion(
25005 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Object,
25006 record.handle,
25007 [&lightList, this] { return m_dwgW->writeLightList(&lightList); },
25008 [&pendingReferences, &lightList, &recordOptionalDrop, this,
25009 &record]() {
25010 const std::uint32_t outputLightListHandle = lightList.handle;
25011 for (const PendingLightReference &reference : pendingReferences) {
25012 const DwgWriteReferenceReceipt receipt{
25013 DwgWriteSourceKind::Object, record.handle,
25014 outputLightListHandle, "LIGHTLIST member",
25015 reference.ordinal, DwgWriteSourceKind::Entity,
25016 reference.sourceHandle, reference.outputHandle,
25017 reference.required, 5};
25018 const bool recorded =
25019 reference.required
25020 ? recordDwgWriteReferenceReceipt(receipt)
25021 : recordOptionalDrop(
25022 reference.sourceHandle, reference.targetStatus,
25023 "LIGHTLIST member", reference.ordinal,
25024 reference.targetStatus ==
25025 DwgWriteReferenceStatus::Ambiguous
25026 ? "ambiguous target"
25027 : reference.targetStatus ==
25028 DwgWriteReferenceStatus::Suppressed
25029 ? "suppressed target"
25030 : "missing target",
25031 DwgWriteSourceKind::Entity);
25032 if (!recorded) {
25033 return false;
25034 }
25035 }
25036 return true;
25037 });
25038 if (!wroteLightList) {
25039 hasBlockedReplay = true;
25040 ++blockedWriterRejected;
25041 } else {
25042 ++nativeLightListObjects;
25043 }
25044 }
25045 for (const auto &record : metadata.backgrounds()) {
25046 if (nativeBackgroundHandles.count(record.handle) == 0)
25047 continue;
25048 DRW_Background background = backgroundFromMetadata(record);
25049 if (hasDxfTableAppDataUnsupportedByDwg(background)) {
25050 hasBlockedReplay = true;
25051 ++blockedWriterRejected;
25052 continue;
25053 }
25054 remapEntry(background);
25055 if (!sanitizeCommonObjectReferences(background,
25056 "BACKGROUND common reference")) {
25057 hasBlockedReplay = true;
25058 ++blockedWriterRejected;
25059 continue;
25060 }
25061 if (background.m_kind == DRW_Background::Ibl)
25062 sanitizeObjectHandle(background.m_secondaryBackgroundHandle,
25063 "BACKGROUND secondary", 0, DRW::DwgHardPointer);
25064 else if (background.m_kind == DRW_Background::Skylight)
25065 sanitizeObjectHandle(background.m_sunHandle, "BACKGROUND sun", 0,
25066 DRW::DwgHardPointer);
25067 background.setDwgCommonObjectState(
25068 static_cast<std::int32_t>(background.reactorHandles.size()),
25069 background.extensionDictionaryFlag(),
25070 encodedDwgDataStoragePresenceBit(
25071 record.handle, background.hasDataStorageBinaryData()));
25072 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25073 DwgWriteSourceKind::Object,
25074 record.handle, [&background, this] {
25075 return m_dwgW->writeBackground(&background);
25076 })) {
25077 ++nativeBackgroundObjects;
25078 } else {
25079 hasBlockedReplay = true;
25080 ++blockedWriterRejected;
25081 }
25082 }
25083 for (const auto &record : metadata.sunStudies()) {
25084 if (nativeSunStudyHandles.count(record.handle) == 0)
25085 continue;
25086 DRW_SunStudy study = sunStudyFromMetadata(record);
25087 if (hasDxfTableAppDataUnsupportedByDwg(study)) {
25088 hasBlockedReplay = true;
25089 ++blockedWriterRejected;
25090 continue;
25091 }
25092 remapEntry(study);
25093 if (!sanitizeCommonObjectReferences(study,
25094 "SUNSTUDY common reference")) {
25095 hasBlockedReplay = true;
25096 ++blockedWriterRejected;
25097 continue;
25098 }
25099 sanitizeObjectHandle(study.m_pageSetupWizardHandle,
25100 "SUNSTUDY page setup", 0, DRW::DwgHardPointer);
25101 study.m_viewHandle =
25102 remapViewHandle(record.viewHandle, "SUNSTUDY view");
25103 sanitizeObjectHandle(study.m_visualStyleHandle, "SUNSTUDY visual style",
25104 0, DRW::DwgHardPointer);
25105 study.m_textStyleHandle = remapTableHandle(
25106 record.textStyleHandle,
25107 metadata.textStyleNameForHandle(record.textStyleHandle),
25108 m_dwgWriteTextStyleHandleByName, "SUNSTUDY STYLE", 0,
25109 DRW::DwgHardPointer);
25110 study.setDwgCommonObjectState(
25111 static_cast<std::int32_t>(study.reactorHandles.size()),
25112 study.extensionDictionaryFlag(),
25113 encodedDwgDataStoragePresenceBit(record.handle,
25114 study.hasDataStorageBinaryData()));
25115 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25116 DwgWriteSourceKind::Object,
25117 record.handle, [&study, this] {
25118 return m_dwgW->writeSunStudy(&study);
25119 })) {
25120 ++nativeSunStudyObjects;
25121 } else {
25122 hasBlockedReplay = true;
25123 ++blockedWriterRejected;
25124 }
25125 }
25126 for (const auto &record : metadata.motionPaths()) {
25127 if (nativeMotionPathHandles.count(record.handle) == 0)
25128 continue;
25129 DRW_MotionPath path = motionPathFromMetadata(record);
25130 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
25131 hasBlockedReplay = true;
25132 ++blockedWriterRejected;
25133 continue;
25134 }
25135 remapEntry(path);
25136 if (!sanitizeCommonObjectReferences(path,
25137 "MOTIONPATH common reference")) {
25138 hasBlockedReplay = true;
25139 ++blockedWriterRejected;
25140 continue;
25141 }
25142 sanitizeObjectHandle(path.m_cameraPathHandle, "MOTIONPATH camera path",
25143 0, DRW::DwgHardPointer);
25144 sanitizeObjectHandle(path.m_targetPathHandle, "MOTIONPATH target path",
25145 0, DRW::DwgHardPointer);
25146 path.m_viewTableHandle =
25147 remapViewHandle(record.viewTableHandle, "MOTIONPATH view table");
25148 path.setDwgCommonObjectState(
25149 static_cast<std::int32_t>(path.reactorHandles.size()),
25150 path.extensionDictionaryFlag(),
25151 encodedDwgDataStoragePresenceBit(record.handle,
25152 path.hasDataStorageBinaryData()));
25153 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25154 DwgWriteSourceKind::Object,
25155 record.handle, [&path, this] {
25156 return m_dwgW->writeMotionPath(&path);
25157 })) {
25158 ++nativeMotionPathObjects;
25159 } else {
25160 hasBlockedReplay = true;
25161 ++blockedWriterRejected;
25162 }
25163 }
25164 for (const auto &record : metadata.curvePaths()) {
25165 if (nativeCurvePathHandles.count(record.handle) == 0)
25166 continue;
25167 DRW_CurvePath path = curvePathFromMetadata(record);
25168 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
25169 hasBlockedReplay = true;
25170 ++blockedWriterRejected;
25171 continue;
25172 }
25173 remapEntry(path);
25174 path.m_entityHandle =
25175 remapRequiredEntityHandle(record.entityHandle, "CURVEPATH entity");
25176 if (m_writeFailed) {
25177 hasBlockedReplay = true;
25178 ++blockedWriterRejected;
25179 continue;
25180 }
25181 if (!sanitizeCommonObjectReferences(path,
25182 "CURVEPATH common reference")) {
25183 hasBlockedReplay = true;
25184 ++blockedWriterRejected;
25185 continue;
25186 }
25187 path.setDwgCommonObjectState(
25188 static_cast<std::int32_t>(path.reactorHandles.size()),
25189 path.extensionDictionaryFlag(),
25190 encodedDwgDataStoragePresenceBit(record.handle,
25191 path.hasDataStorageBinaryData()));
25192 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25193 DwgWriteSourceKind::Object,
25194 record.handle, [&path, this] {
25195 return m_dwgW->writeCurvePath(&path);
25196 })) {
25197 ++nativeCurvePathObjects;
25198 } else {
25199 hasBlockedReplay = true;
25200 ++blockedWriterRejected;
25201 }
25202 }
25203 for (const auto &record : metadata.pointPaths()) {
25204 if (nativePointPathHandles.count(record.handle) == 0)
25205 continue;
25206 DRW_PointPath path = pointPathFromMetadata(record);
25207 if (hasDxfTableAppDataUnsupportedByDwg(path)) {
25208 hasBlockedReplay = true;
25209 ++blockedWriterRejected;
25210 continue;
25211 }
25212 remapEntry(path);
25213 if (!sanitizeCommonObjectReferences(path,
25214 "POINTPATH common reference")) {
25215 hasBlockedReplay = true;
25216 ++blockedWriterRejected;
25217 continue;
25218 }
25219 path.setDwgCommonObjectState(
25220 static_cast<std::int32_t>(path.reactorHandles.size()),
25221 path.extensionDictionaryFlag(),
25222 encodedDwgDataStoragePresenceBit(record.handle,
25223 path.hasDataStorageBinaryData()));
25224 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25225 DwgWriteSourceKind::Object,
25226 record.handle, [&path, this] {
25227 return m_dwgW->writePointPath(&path);
25228 })) {
25229 ++nativePointPathObjects;
25230 } else {
25231 hasBlockedReplay = true;
25232 ++blockedWriterRejected;
25233 }
25234 }
25235 for (const auto &record : metadata.objectPtrs()) {
25236 if (nativeObjectPtrHandles.count(record.handle) == 0)
25237 continue;
25238 DRW_ObjectPtr object = objectPtrFromMetadata(record);
25239 if (hasDxfTableAppDataUnsupportedByDwg(object)) {
25240 hasBlockedReplay = true;
25241 ++blockedWriterRejected;
25242 continue;
25243 }
25244 remapEntry(object);
25245 if (!sanitizeCommonObjectReferences(object,
25246 "OBJECT_PTR common reference")) {
25247 hasBlockedReplay = true;
25248 ++blockedWriterRejected;
25249 continue;
25250 }
25251 object.setDwgCommonObjectState(
25252 static_cast<std::int32_t>(object.reactorHandles.size()),
25253 object.extensionDictionaryFlag(),
25254 encodedDwgDataStoragePresenceBit(
25255 record.handle, object.hasDataStorageBinaryData()));
25256 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25257 DwgWriteSourceKind::Object,
25258 record.handle, [&object, this] {
25259 return m_dwgW->writeObjectPtr(&object);
25260 })) {
25261 ++nativeObjectPtrObjects;
25262 } else {
25263 hasBlockedReplay = true;
25264 ++blockedWriterRejected;
25265 }
25266 }
25267 for (const auto &record : metadata.partialViewingIndexes()) {
25268 if (nativePartialViewingIndexHandles.count(record.handle) == 0)
25269 continue;
25270 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
25271 if (hasDxfTableAppDataUnsupportedByDwg(index)) {
25272 hasBlockedReplay = true;
25273 ++blockedWriterRejected;
25274 continue;
25275 }
25276 remapEntry(index);
25277 if (!sanitizeCommonObjectReferences(
25278 index, "PARTIAL_VIEWING_INDEX common reference")) {
25279 hasBlockedReplay = true;
25280 ++blockedWriterRejected;
25281 continue;
25282 }
25283 for (std::size_t ordinal = 0; ordinal < index.m_entries.size();
25284 ++ordinal) {
25285 auto &entry = index.m_entries[ordinal];
25286 sanitizeIndexedObjectHandle(
25287 entry.objectHandle, "PARTIAL_VIEWING_INDEX entry",
25288 static_cast<std::uint32_t>(ordinal), DRW::DwgHardPointer);
25289 }
25290 index.m_entryCount = static_cast<std::uint32_t>(index.m_entries.size());
25291 index.setDwgCommonObjectState(
25292 static_cast<std::int32_t>(index.reactorHandles.size()),
25293 index.extensionDictionaryFlag(),
25294 encodedDwgDataStoragePresenceBit(record.handle,
25295 index.hasDataStorageBinaryData()));
25296 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25297 DwgWriteSourceKind::Object,
25298 record.handle, [&index, this] {
25299 return m_dwgW->writePartialViewingIndex(&index);
25300 })) {
25301 ++nativePartialViewingIndexObjects;
25302 } else {
25303 hasBlockedReplay = true;
25304 ++blockedWriterRejected;
25305 }
25306 }
25307 for (const auto &record : metadata.renderSettings()) {
25308 if (nativeRenderSettingsHandles.count(record.handle) == 0)
25309 continue;
25310 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
25311 if (hasDxfTableAppDataUnsupportedByDwg(settings)) {
25312 hasBlockedReplay = true;
25313 ++blockedWriterRejected;
25314 continue;
25315 }
25316 remapEntry(settings);
25317 if (!sanitizeCommonObjectReferences(
25318 settings, "RENDERSETTINGS common reference")) {
25319 hasBlockedReplay = true;
25320 ++blockedWriterRejected;
25321 continue;
25322 }
25323 settings.setDwgCommonObjectState(
25324 static_cast<std::int32_t>(settings.reactorHandles.size()),
25325 settings.extensionDictionaryFlag(),
25326 encodedDwgDataStoragePresenceBit(
25327 record.handle, settings.hasDataStorageBinaryData()));
25328 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25329 DwgWriteSourceKind::Object,
25330 record.handle, [&settings, this] {
25331 return m_dwgW->writeRenderSettings(&settings);
25332 })) {
25333 ++nativeRenderSettingsObjects;
25334 } else {
25335 hasBlockedReplay = true;
25336 ++blockedWriterRejected;
25337 }
25338 }
25339 for (const auto &record : metadata.visualStyles()) {
25340 if (nativeVisualStyleHandles.count(record.handle) == 0)
25341 continue;
25342 DRW_VisualStyle style = visualStyleFromMetadata(record);
25343 if (hasDxfTableAppDataUnsupportedByDwg(style)) {
25344 hasBlockedReplay = true;
25345 ++blockedWriterRejected;
25346 continue;
25347 }
25348 remapEntry(style);
25349 if (!sanitizeCommonObjectReferences(style,
25350 "VISUALSTYLE common reference")) {
25351 hasBlockedReplay = true;
25352 ++blockedWriterRejected;
25353 continue;
25354 }
25355 style.setDwgCommonObjectState(
25356 static_cast<std::int32_t>(style.reactorHandles.size()),
25357 style.extensionDictionaryFlag(),
25358 encodedDwgDataStoragePresenceBit(record.handle,
25359 style.hasDataStorageBinaryData()));
25360 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25361 DwgWriteSourceKind::Object,
25362 record.handle, [&style, this] {
25363 return m_dwgW->writeVisualStyle(&style);
25364 })) {
25365 ++nativeVisualStyleObjects;
25366 } else {
25367 hasBlockedReplay = true;
25368 ++blockedWriterRejected;
25369 }
25370 }
25371 for (const auto &record : metadata.tvDeviceProperties()) {
25372 if (nativeTvDevicePropertiesHandles.count(record.handle) == 0)
25373 continue;
25374 DRW_TvDeviceProperties properties =
25375 tvDevicePropertiesFromMetadata(record);
25376 if (hasDxfTableAppDataUnsupportedByDwg(properties)) {
25377 hasBlockedReplay = true;
25378 ++blockedWriterRejected;
25379 continue;
25380 }
25381 remapEntry(properties);
25382 properties.parentHandle = remapObjectOwnerHandle(record.parentHandle);
25383 if (!sanitizeCommonObjectReferences(
25384 properties, "TVDEVICEPROPERTIES common reference")) {
25385 hasBlockedReplay = true;
25386 ++blockedWriterRejected;
25387 continue;
25388 }
25389 properties.setDwgCommonObjectState(
25390 static_cast<std::int32_t>(properties.reactorHandles.size()),
25391 properties.extensionDictionaryFlag(),
25392 encodedDwgDataStoragePresenceBit(
25393 record.handle, properties.hasDataStorageBinaryData()));
25394 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25395 DwgWriteSourceKind::Object,
25396 record.handle, [&properties, this] {
25397 return m_dwgW->writeTvDeviceProperties(
25398 &properties);
25399 })) {
25400 ++nativeTvDevicePropertiesObjects;
25401 } else {
25402 hasBlockedReplay = true;
25403 ++blockedWriterRejected;
25404 }
25405 }
25406 for (const auto &record : metadata.vxControls()) {
25407 if (nativeVxControlHandles.count(record.handle) == 0)
25408 continue;
25409 DRW_VxControl control = vxControlFromMetadata(record);
25410 if (hasDxfTableAppDataUnsupportedByDwg(control)) {
25411 hasBlockedReplay = true;
25412 ++blockedWriterRejected;
25413 continue;
25414 }
25415 remapEntry(control);
25416 control.parentHandle = remapObjectOwnerHandle(record.parentHandle);
25417 if (!sanitizeCommonObjectReferences(control,
25418 "VXCONTROL common reference")) {
25419 hasBlockedReplay = true;
25420 ++blockedWriterRejected;
25421 continue;
25422 }
25423 sanitizeIndexedObjectList(control.recordHandles, "VXCONTROL record",
25424 DRW::DwgSoftOwnership);
25425 sanitizeObjectHandle(control.xDictHandle,
25426 "VXCONTROL extension dictionary");
25427 control.setDwgCommonObjectState(
25428 static_cast<std::int32_t>(control.reactorHandles.size()),
25429 control.extensionDictionaryFlag(),
25430 encodedDwgDataStoragePresenceBit(
25431 record.handle, control.hasDataStorageBinaryData()));
25432 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25433 DwgWriteSourceKind::Object,
25434 record.handle, [&control, this] {
25435 return m_dwgW->writeVxControl(&control);
25436 })) {
25437 ++nativeVxControlObjects;
25438 } else {
25439 hasBlockedReplay = true;
25440 ++blockedWriterRejected;
25441 }
25442 }
25443 for (const auto &record : metadata.vxTableRecords()) {
25444 if (nativeVxTableRecordHandles.count(record.handle) == 0)
25445 continue;
25446 DRW_VxTableRecord tableRecord = vxTableRecordFromMetadata(record);
25447 if (hasDxfTableAppDataUnsupportedByDwg(tableRecord)) {
25448 hasBlockedReplay = true;
25449 ++blockedWriterRejected;
25450 continue;
25451 }
25452 remapEntry(tableRecord);
25453 tableRecord.parentHandle = remapObjectOwnerHandle(record.parentHandle);
25454 if (!sanitizeCommonObjectReferences(tableRecord,
25455 "VXTABLERECORD common reference")) {
25456 hasBlockedReplay = true;
25457 ++blockedWriterRejected;
25458 continue;
25459 }
25460 tableRecord.setDwgCommonObjectState(
25461 static_cast<std::int32_t>(tableRecord.reactorHandles.size()),
25462 tableRecord.extensionDictionaryFlag(),
25463 encodedDwgDataStoragePresenceBit(
25464 record.handle, tableRecord.hasDataStorageBinaryData()));
25465 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25466 DwgWriteSourceKind::Object,
25467 record.handle, [&tableRecord, this] {
25468 return m_dwgW->writeVxTableRecord(&tableRecord);
25469 })) {
25470 ++nativeVxTableRecordObjects;
25471 } else {
25472 hasBlockedReplay = true;
25473 ++blockedWriterRejected;
25474 }
25475 }
25476 for (const auto &record : metadata.rasterVariables()) {
25477 if (nativeRasterVariablesHandles.count(record.handle) == 0)
25478 continue;
25479 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
25480 if (hasDxfTableAppDataUnsupportedByDwg(rv)) {
25481 hasBlockedReplay = true;
25482 ++blockedWriterRejected;
25483 continue;
25484 }
25485 remapEntry(rv);
25486 if (!sanitizeCommonObjectReferences(
25487 rv, "RASTERVARIABLES common reference")) {
25488 hasBlockedReplay = true;
25489 ++blockedWriterRejected;
25490 continue;
25491 }
25492 rv.setDwgCommonObjectState(
25493 static_cast<std::int32_t>(rv.reactorHandles.size()),
25494 rv.extensionDictionaryFlag(),
25495 encodedDwgDataStoragePresenceBit(record.handle,
25496 rv.hasDataStorageBinaryData()));
25497 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25498 DwgWriteSourceKind::Object,
25499 record.handle, [&rv, this] {
25500 return m_dwgW->writeRasterVariables(&rv);
25501 })) {
25502 ++nativeRasterVariablesObjects;
25503 } else {
25504 hasBlockedReplay = true;
25505 ++blockedWriterRejected;
25506 }
25507 }
25508 for (const auto &record : metadata.wipeoutVariables()) {
25509 if (nativeWipeoutVariablesHandles.count(record.handle) == 0)
25510 continue;
25511 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
25512 if (hasDxfTableAppDataUnsupportedByDwg(wv)) {
25513 hasBlockedReplay = true;
25514 ++blockedWriterRejected;
25515 continue;
25516 }
25517 remapEntry(wv);
25518 if (!sanitizeCommonObjectReferences(
25519 wv, "WIPEOUTVARIABLES common reference")) {
25520 hasBlockedReplay = true;
25521 ++blockedWriterRejected;
25522 continue;
25523 }
25524 wv.setDwgCommonObjectState(
25525 static_cast<std::int32_t>(wv.reactorHandles.size()),
25526 wv.extensionDictionaryFlag(),
25527 encodedDwgDataStoragePresenceBit(record.handle,
25528 wv.hasDataStorageBinaryData()));
25529 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25530 DwgWriteSourceKind::Object,
25531 record.handle, [&wv, this] {
25532 return m_dwgW->writeWipeoutVariables(&wv);
25533 })) {
25534 ++nativeWipeoutVariablesObjects;
25535 } else {
25536 hasBlockedReplay = true;
25537 ++blockedWriterRejected;
25538 }
25539 }
25540 for (const auto &record : metadata.geoData()) {
25541 if (nativeGeoDataHandles.count(record.handle) == 0)
25542 continue;
25543 DRW_GeoData gd = geoDataFromMetadata(record);
25544 if (hasDxfTableAppDataUnsupportedByDwg(gd)) {
25545 hasBlockedReplay = true;
25546 ++blockedWriterRejected;
25547 continue;
25548 }
25549 remapEntry(gd);
25550 gd.m_hostBlockHandle = remapBlockOwnerHandle(record.hostBlockHandle);
25551 if (!sanitizeCommonObjectReferences(gd, "GEODATA common reference")) {
25552 hasBlockedReplay = true;
25553 ++blockedWriterRejected;
25554 continue;
25555 }
25556 gd.setDwgCommonObjectState(
25557 static_cast<std::int32_t>(gd.reactorHandles.size()),
25558 gd.extensionDictionaryFlag(),
25559 encodedDwgDataStoragePresenceBit(record.handle,
25560 gd.hasDataStorageBinaryData()));
25561 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25562 DwgWriteSourceKind::Object,
25563 record.handle,
25564 [&gd, this] { return m_dwgW->writeGeoData(&gd); })) {
25565 ++nativeGeoDataObjects;
25566 } else {
25567 hasBlockedReplay = true;
25568 ++blockedWriterRejected;
25569 }
25570 }
25571 for (const auto &record : metadata.spatialFilters()) {
25572 if (nativeSpatialFilterHandles.count(record.handle) == 0)
25573 continue;
25574 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
25575 if (hasDxfTableAppDataUnsupportedByDwg(sf)) {
25576 hasBlockedReplay = true;
25577 ++blockedWriterRejected;
25578 continue;
25579 }
25580 remapEntry(sf);
25581 if (!sanitizeCommonObjectReferences(
25582 sf, "SPATIAL_FILTER common reference")) {
25583 hasBlockedReplay = true;
25584 ++blockedWriterRejected;
25585 continue;
25586 }
25587 sf.setDwgCommonObjectState(
25588 static_cast<std::int32_t>(sf.reactorHandles.size()),
25589 sf.extensionDictionaryFlag(),
25590 encodedDwgDataStoragePresenceBit(record.handle,
25591 sf.hasDataStorageBinaryData()));
25592 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25593 DwgWriteSourceKind::Object,
25594 record.handle, [&sf, this] {
25595 return m_dwgW->writeSpatialFilter(&sf);
25596 })) {
25597 ++nativeSpatialFilterObjects;
25598 } else {
25599 hasBlockedReplay = true;
25600 ++blockedWriterRejected;
25601 }
25602 }
25603 // PR 13g — SCALE / IDBUFFER / LAYER_INDEX / SPATIAL_INDEX /
25604 // DICTIONARYVAR (custom classes 508-512) dispatch. Smoke
25605 // tests at AC1015/AC1018 confirm the encoders + parsers
25606 // round-trip cleanly; SPATIAL_INDEX's pre-R2007 opaque body
25607 // path is exercised at AC1015/AC1018.
25608 for (const auto &record : metadata.scales()) {
25609 if (nativeScaleHandles.count(record.handle) == 0)
25610 continue;
25611 DRW_Scale s = scaleFromMetadata(record);
25612 if (hasDxfTableAppDataUnsupportedByDwg(s)) {
25613 hasBlockedReplay = true;
25614 ++blockedWriterRejected;
25615 continue;
25616 }
25617 remapEntry(s);
25618 if (!sanitizeCommonObjectReferences(s, "SCALE common reference")) {
25619 hasBlockedReplay = true;
25620 ++blockedWriterRejected;
25621 continue;
25622 }
25623 s.setDwgCommonObjectState(
25624 static_cast<std::int32_t>(s.reactorHandles.size()),
25625 s.extensionDictionaryFlag(),
25626 encodedDwgDataStoragePresenceBit(record.handle,
25627 s.hasDataStorageBinaryData()));
25628 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25629 DwgWriteSourceKind::Object,
25630 record.handle,
25631 [&s, this] { return m_dwgW->writeScale(&s); })) {
25632 ++nativeScaleObjects;
25633 } else {
25634 hasBlockedReplay = true;
25635 ++blockedWriterRejected;
25636 }
25637 }
25638 for (const auto &record : metadata.idBuffers()) {
25639 if (nativeIDBufferHandles.count(record.handle) == 0)
25640 continue;
25641 DRW_IDBuffer b = idBufferFromMetadata(record);
25642 if (hasDxfTableAppDataUnsupportedByDwg(b)) {
25643 hasBlockedReplay = true;
25644 ++blockedWriterRejected;
25645 continue;
25646 }
25647 remapEntry(b);
25648 sanitizeIndexedObjectList(b.objIds, "IDBUFFER");
25649 if (!sanitizeCommonObjectReferences(b, "IDBUFFER common reference")) {
25650 hasBlockedReplay = true;
25651 ++blockedWriterRejected;
25652 continue;
25653 }
25654 b.setDwgCommonObjectState(
25655 static_cast<std::int32_t>(b.reactorHandles.size()),
25656 b.extensionDictionaryFlag(),
25657 encodedDwgDataStoragePresenceBit(record.handle,
25658 b.hasDataStorageBinaryData()));
25659 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25660 DwgWriteSourceKind::Object,
25661 record.handle,
25662 [&b, this] { return m_dwgW->writeIDBuffer(&b); })) {
25663 ++nativeIDBufferObjects;
25664 } else {
25665 hasBlockedReplay = true;
25666 ++blockedWriterRejected;
25667 }
25668 }
25669 for (const auto &record : metadata.layerIndexes()) {
25670 if (nativeLayerIndexHandles.count(record.handle) == 0)
25671 continue;
25672 DRW_LayerIndex li = layerIndexFromMetadata(record);
25673 if (hasDxfTableAppDataUnsupportedByDwg(li)) {
25674 hasBlockedReplay = true;
25675 ++blockedWriterRejected;
25676 continue;
25677 }
25678 remapEntry(li);
25679 std::vector<DRW_LayerIndexEntry> keptEntries;
25680 keptEntries.reserve(li.entries.size());
25681 for (std::size_t ordinal = 0; ordinal < li.entries.size(); ++ordinal) {
25682 DRW_LayerIndexEntry entry = li.entries[ordinal];
25683 sanitizeObjectHandle(entry.entryHandle, "LAYER_INDEX",
25684 static_cast<std::uint32_t>(ordinal),
25685 DRW::DwgHardPointer);
25686 if (entry.entryHandle != 0)
25687 keptEntries.push_back(std::move(entry));
25688 }
25689 li.entries = std::move(keptEntries);
25690 if (!sanitizeCommonObjectReferences(li,
25691 "LAYER_INDEX common reference")) {
25692 hasBlockedReplay = true;
25693 ++blockedWriterRejected;
25694 continue;
25695 }
25696 li.setDwgCommonObjectState(
25697 static_cast<std::int32_t>(li.reactorHandles.size()),
25698 li.extensionDictionaryFlag(),
25699 encodedDwgDataStoragePresenceBit(record.handle,
25700 li.hasDataStorageBinaryData()));
25701 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25702 DwgWriteSourceKind::Object,
25703 record.handle, [&li, this] {
25704 return m_dwgW->writeLayerIndex(&li);
25705 })) {
25706 ++nativeLayerIndexObjects;
25707 } else {
25708 hasBlockedReplay = true;
25709 ++blockedWriterRejected;
25710 }
25711 }
25712 for (const auto &record : metadata.spatialIndexes()) {
25713 if (nativeSpatialIndexHandles.count(record.handle) == 0)
25714 continue;
25715 DRW_SpatialIndex si = spatialIndexFromMetadata(record);
25716 if (hasDxfTableAppDataUnsupportedByDwg(si)) {
25717 hasBlockedReplay = true;
25718 ++blockedWriterRejected;
25719 continue;
25720 }
25721 remapEntry(si);
25722 if (!sanitizeCommonObjectReferences(si,
25723 "SPATIAL_INDEX common reference")) {
25724 hasBlockedReplay = true;
25725 ++blockedWriterRejected;
25726 continue;
25727 }
25728 si.setDwgCommonObjectState(
25729 static_cast<std::int32_t>(si.reactorHandles.size()),
25730 si.extensionDictionaryFlag(),
25731 encodedDwgDataStoragePresenceBit(record.handle,
25732 si.hasDataStorageBinaryData()));
25733 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25734 DwgWriteSourceKind::Object,
25735 record.handle, [&si, this] {
25736 return m_dwgW->writeSpatialIndex(&si);
25737 })) {
25738 ++nativeSpatialIndexObjects;
25739 } else {
25740 hasBlockedReplay = true;
25741 ++blockedWriterRejected;
25742 }
25743 }
25744 for (const auto &record : metadata.dictionaryVars()) {
25745 if (nativeDictionaryVarHandles.count(record.handle) == 0)
25746 continue;
25747 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
25748 if (hasDxfTableAppDataUnsupportedByDwg(dv)) {
25749 hasBlockedReplay = true;
25750 ++blockedWriterRejected;
25751 continue;
25752 }
25753 remapEntry(dv);
25754 if (!sanitizeCommonObjectReferences(dv,
25755 "DICTIONARYVAR common reference")) {
25756 hasBlockedReplay = true;
25757 ++blockedWriterRejected;
25758 continue;
25759 }
25760 dv.setDwgCommonObjectState(
25761 static_cast<std::int32_t>(dv.reactorHandles.size()),
25762 dv.extensionDictionaryFlag(),
25763 encodedDwgDataStoragePresenceBit(record.handle,
25764 dv.hasDataStorageBinaryData()));
25765 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25766 DwgWriteSourceKind::Object,
25767 record.handle, [&dv, this] {
25768 return m_dwgW->writeDictionaryVar(&dv);
25769 })) {
25770 ++nativeDictionaryVarObjects;
25771 } else {
25772 hasBlockedReplay = true;
25773 ++blockedWriterRejected;
25774 }
25775 }
25776 // PR 13h — DICTIONARYWDFLT / SORTENTSTABLE / FIELDLIST / FIELD
25777 // (custom classes 513-516) dispatch. Smoke tests at AC1015/
25778 // AC1018 confirm the encoders + parsers round-trip cleanly;
25779 // FIELD exercises the parser-mirrored `version < AC1021`
25780 // m_formatString branch.
25781 for (const auto &record : metadata.dictionariesWithDefault()) {
25782 if (nativeDictionaryWithDefaultHandles.count(record.handle) == 0)
25783 continue;
25784 DRW_DictionaryWithDefault dwd =
25785 dictionaryWithDefaultFromMetadata(record);
25786 if (hasDxfTableAppDataUnsupportedByDwg(dwd)) {
25787 hasBlockedReplay = true;
25788 ++blockedWriterRejected;
25789 continue;
25790 }
25791 sanitizeDictionaryOwner(dwd.parentHandle, "dictionary-with-default");
25792 remapEntryHandle(dwd);
25793 if (!sanitizeDictionaryEntries(dwd)) {
25794 hasBlockedReplay = true;
25795 ++blockedWriterRejected;
25796 continue;
25797 }
25798 dwd.m_defaultEntryHandle = remapRequiredObjectHandle(
25799 dwd.m_defaultEntryHandle, "DICTIONARYWDFLT default", 0,
25800 DRW::DwgHardPointer);
25801 if (m_writeFailed) {
25802 hasBlockedReplay = true;
25803 ++blockedWriterRejected;
25804 continue;
25805 }
25806 if (!sanitizeCommonObjectReferences(
25807 dwd, "DICTIONARYWDFLT common reference")) {
25808 hasBlockedReplay = true;
25809 ++blockedWriterRejected;
25810 continue;
25811 }
25812 dwd.setDwgCommonObjectState(
25813 static_cast<std::int32_t>(dwd.reactorHandles.size()),
25814 dwd.extensionDictionaryFlag(),
25815 encodedDwgDataStoragePresenceBit(record.handle,
25816 dwd.hasDataStorageBinaryData()));
25817 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
25818 DwgWriteSourceKind::Object,
25819 record.handle, [&dwd, this] {
25820 return m_dwgW->writeDictionaryWithDefault(&dwd);
25821 })) {
25822 ++nativeDictionaryWithDefaultObjects;
25823 } else {
25824 hasBlockedReplay = true;
25825 ++blockedWriterRejected;
25826 }
25827 }
25828 for (const auto &record : metadata.sortEntsTables()) {
25829 if (nativeSortEntsTableHandles.count(record.handle) == 0)
25830 continue;
25831 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
25832 if (hasDxfTableAppDataUnsupportedByDwg(se)) {
25833 hasBlockedReplay = true;
25834 ++blockedWriterRejected;
25835 continue;
25836 }
25837 remapEntry(se);
25838 se.m_blockOwnerHandle =
25839 remapRequiredSortBlockOwnerHandle(se.m_blockOwnerHandle);
25840 if (se.m_entityHandles.size() != se.m_sortHandles.size() ||
25841 se.m_entityHandles.size() > DRW_SortEntsTable::kMaxEntries) {
25842 m_writeFailed = true;
25843 hasBlockedReplay = true;
25844 ++blockedWriterRejected;
25845 continue;
25846 }
25847 std::vector<std::uint32_t> entityHandles;
25848 std::vector<std::uint32_t> sortHandles;
25849 struct PendingSortReference {
25850 bool retained = false;
25851 std::uint32_t sourceHandle = 0;
25852 std::uint32_t outputHandle = 0;
25853 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
25854 DwgWriteReferenceStatus targetStatus =
25855 DwgWriteReferenceStatus::Missing;
25856 const char *referenceName = nullptr;
25857 std::uint32_t ordinal = 0;
25858 std::uint8_t wireHandleCode = DRW::DwgNullReference;
25859 bool required = false;
25860 };
25861 std::vector<PendingSortReference> pendingReferences;
25862 entityHandles.reserve(se.m_entityHandles.size());
25863 sortHandles.reserve(se.m_entityHandles.size());
25864 pendingReferences.reserve(se.m_entityHandles.size() * 2u);
25865 for (std::size_t i = 0; i < se.m_entityHandles.size(); ++i) {
25866 const std::uint32_t sourceEntityHandle = se.m_entityHandles[i];
25867 const auto entityReference = resolveRequiredEntityReference(
25868 sourceEntityHandle, "SORTENTSTABLE entity");
25869 if (!entityReference.has_value())
25870 break;
25871 const std::uint32_t entityHandle = entityReference->first;
25872 const DwgWriteSourceKind entityTargetKind =
25873 entityReference->second;
25874 const std::uint32_t sourceSortHandle = se.m_sortHandles[i];
25875 if (sourceSortHandle == DRW::NoHandle) {
25876 entityHandles.push_back(entityHandle);
25877 sortHandles.push_back(DRW::NoHandle);
25878 pendingReferences.push_back(
25879 {true, sourceEntityHandle, entityHandle,
25880 entityTargetKind, DwgWriteReferenceStatus::Committed,
25881 "SORTENTSTABLE entity", static_cast<std::uint32_t>(i),
25882 DRW::DwgSoftPointer, true});
25883 continue;
25884 }
25885 std::uint32_t sortHandle = 0;
25886 DwgWriteSourceKind sortTargetKind = DwgWriteSourceKind::Object;
25887 const DwgWriteReferenceStatus sortStatus =
25888 resolveDwgWriteCommonHandle(sourceSortHandle, &sortHandle,
25889 &sortTargetKind);
25890 DwgWriteReferenceStatus effectiveSortStatus = sortStatus;
25891 if (!isLiveDwgWriteReference(sortStatus) || sortHandle == 0) {
25892 // A SORTENTSTABLE sort handle may repeat its entity handle. If
25893 // that entity is retained only as an opaque carrier, the common
25894 // resolver cannot select it because the rendered entity map is
25895 // intentionally ambiguous. Use the committed raw identity in
25896 // that case; the receipt still verifies the exact carrier.
25897 const auto rawSortIt = std::find_if(
25898 rawObjects.cbegin(), rawObjects.cend(),
25899 [sourceSortHandle](const auto &raw) {
25900 return raw.isEntity && raw.handle == sourceSortHandle;
25901 });
25902 std::uint32_t rawSortOutputHandle = 0;
25903 const DwgWriteIdentityLookup rawSortIdentityStatus =
25904 lookupDwgWriteIdentity(DwgWriteSourceKind::RawCarrier,
25905 sourceSortHandle,
25906 &rawSortOutputHandle);
25907 if (rawSortIt != rawObjects.cend() &&
25908 rawSortIdentityStatus == DwgWriteIdentityLookup::Committed &&
25909 rawSortOutputHandle != 0) {
25910 sortHandle = rawSortOutputHandle;
25911 sortTargetKind = DwgWriteSourceKind::RawCarrier;
25912 effectiveSortStatus = DwgWriteReferenceStatus::Committed;
25913 }
25914 }
25915 const bool freeSortHandle =
25916 effectiveSortStatus == DwgWriteReferenceStatus::Missing ||
25917 effectiveSortStatus == DwgWriteReferenceStatus::Suppressed;
25918 // A free sort key is an ordering value, not an object reference.
25919 if (freeSortHandle)
25920 sortHandle = sourceSortHandle;
25921 if ((!freeSortHandle && !isLiveDwgWriteReference(effectiveSortStatus)) ||
25922 sortHandle == 0) {
25923 const DwgWriteReferenceStatus dropStatus =
25924 effectiveSortStatus == DwgWriteReferenceStatus::Ambiguous
25925 ? DwgWriteReferenceStatus::Ambiguous
25926 : effectiveSortStatus == DwgWriteReferenceStatus::Suppressed
25927 ? DwgWriteReferenceStatus::Suppressed
25928 : DwgWriteReferenceStatus::Missing;
25929 pendingReferences.push_back({false, sourceSortHandle, 0,
25930 sortTargetKind, dropStatus,
25931 "SORTENTSTABLE sort",
25932 static_cast<std::uint32_t>(i), 0,
25933 false});
25934 m_writeFailed = true;
25935 break;
25936 }
25937 entityHandles.push_back(entityHandle);
25938 sortHandles.push_back(sortHandle);
25939 pendingReferences.push_back(
25940 {true, sourceEntityHandle, entityHandle,
25941 entityTargetKind, DwgWriteReferenceStatus::Committed,
25942 "SORTENTSTABLE entity", static_cast<std::uint32_t>(i),
25943 DRW::DwgSoftPointer, true});
25944 if (!freeSortHandle)
25945 pendingReferences.push_back(
25946 {true, sourceSortHandle, sortHandle, sortTargetKind,
25947 DwgWriteReferenceStatus::Committed, "SORTENTSTABLE sort",
25948 static_cast<std::uint32_t>(i), DRW::DwgNullReference, false});
25949 }
25950 if (m_writeFailed) {
25951 hasBlockedReplay = true;
25952 ++blockedWriterRejected;
25953 continue;
25954 }
25955 se.m_entityHandles = std::move(entityHandles);
25956 se.m_sortHandles = std::move(sortHandles);
25957 if (!sanitizeCommonObjectReferences(se,
25958 "SORTENTSTABLE common reference")) {
25959 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
25960 "RS_FilterDXFRW: SORTENTSTABLE 0x%X common "
25961 "reference sanitization failed",
25962 record.handle);
25963 hasBlockedReplay = true;
25964 ++blockedWriterRejected;
25965 continue;
25966 }
25967 se.setDwgCommonObjectState(
25968 static_cast<std::int32_t>(se.reactorHandles.size()),
25969 se.extensionDictionaryFlag(),
25970 encodedDwgDataStoragePresenceBit(record.handle,
25971 se.hasDataStorageBinaryData()));
25972 const bool wroteSortEntsTable = writeDwgObjectWithCompletion(
25973 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::Object,
25974 record.handle,
25975 [&se, this] { return m_dwgW->writeSortEntsTable(&se); },
25976 [&pendingReferences, &recordRetainedReference,
25977 &recordOptionalDrop]() {
25978 for (const PendingSortReference &reference : pendingReferences) {
25979 if (reference.retained) {
25980 if (!recordRetainedReference(
25981 reference.sourceHandle, reference.outputHandle,
25982 reference.targetKind, reference.referenceName,
25983 reference.ordinal, reference.wireHandleCode,
25984 reference.required)) {
25985 return false;
25986 }
25987 } else if (!recordOptionalDrop(
25988 reference.sourceHandle, reference.targetStatus,
25989 reference.referenceName, reference.ordinal,
25990 reference.targetStatus ==
25991 DwgWriteReferenceStatus::Ambiguous
25992 ? "ambiguous target"
25993 : reference.targetStatus ==
25994 DwgWriteReferenceStatus::Suppressed
25995 ? "suppressed target"
25996 : "missing target",
25997 reference.targetKind)) {
25998 return false;
25999 }
26000 }
26001 return true;
26002 });
26003 if (!wroteSortEntsTable) {
26004 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
26005 "RS_FilterDXFRW: SORTENTSTABLE 0x%X writer call "
26006 "failed",
26007 record.handle);
26008 hasBlockedReplay = true;
26009 ++blockedWriterRejected;
26010 } else {
26011 ++nativeSortEntsTableObjects;
26012 }
26013 }
26014 for (auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
26015 if (!candidate.classAdmitted ||
26016 nativeFieldListHandles.count(candidate.sourceHandle) == 0) {
26017 m_writeFailed = true;
26018 continue;
26019 }
26020 DRW_FieldList fl = candidate.payload;
26021 remapEntry(fl);
26022 struct PendingFieldListReference {
26023 std::uint32_t sourceHandle = 0;
26024 std::uint32_t outputHandle = 0;
26025 std::uint32_t ordinal = 0;
26026 };
26027 std::vector<std::uint32_t> fieldHandles;
26028 std::vector<PendingFieldListReference> pendingReferences;
26029 fieldHandles.reserve(fl.m_fieldHandles.size());
26030 pendingReferences.reserve(fl.m_fieldHandles.size());
26031 const DRW_Field::DwgReferencePolicy fieldPolicy =
26032 DRW_Field::dwgReferencePolicy(
26033 m_dwgW->getVersion(),
26034 DRW_Field::DwgReferenceKind::FieldListMember);
26035 bool fieldsValid = true;
26036 if (!fieldPolicy.hasHandleStreamSlot)
26037 fieldsValid = false;
26038 std::size_t fieldReferenceIndex = 0;
26039 for (std::size_t ordinal = 0;
26040 fieldsValid && ordinal < fl.m_fieldHandles.size(); ++ordinal) {
26041 const std::uint32_t sourceHandle = fl.m_fieldHandles[ordinal];
26042 if (sourceHandle == DRW::NoHandle) {
26043 if (!fieldPolicy.allowsNull) {
26044 fieldsValid = false;
26045 break;
26046 }
26047 fieldHandles.push_back(DRW::NoHandle);
26048 continue;
26049 }
26050 // Pruned when the plan was frozen: the member names no object.
26051 if (candidate.isMemberDropped(ordinal))
26052 continue;
26053 if (fieldReferenceIndex >= candidate.fieldCandidateIndexes.size()) {
26054 fieldsValid = false;
26055 break;
26056 }
26057 const std::size_t fieldIndex =
26058 candidate.fieldCandidateIndexes[fieldReferenceIndex++];
26059 if (fieldIndex >= m_dwgFieldWritePlan.fieldCandidates.size() ||
26060 !m_dwgFieldWritePlan.fieldCandidates[fieldIndex].classAdmitted ||
26061 m_dwgFieldWritePlan.fieldCandidates[fieldIndex].sourceHandle !=
26062 sourceHandle ||
26063 nativeFieldHandles.count(sourceHandle) == 0) {
26064 fieldsValid = false;
26065 break;
26066 }
26067 std::uint32_t outputHandle = 0;
26068 const DwgWriteReferenceStatus status =
26069 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
26070 if (!isLiveDwgWriteReference(status) || outputHandle == 0) {
26071 fieldsValid = false;
26072 break;
26073 }
26074 fieldHandles.push_back(outputHandle);
26075 pendingReferences.push_back({sourceHandle, outputHandle,
26076 static_cast<std::uint32_t>(ordinal)});
26077 }
26078 if (!fieldsValid ||
26079 fieldReferenceIndex != candidate.fieldCandidateIndexes.size()) {
26080 m_writeFailed = true;
26081 hasBlockedReplay = true;
26082 ++blockedWriterRejected;
26083 continue;
26084 }
26085 fl.m_fieldHandles = std::move(fieldHandles);
26086 if (!sanitizeCommonObjectReferences(fl, "FIELDLIST common reference")) {
26087 hasBlockedReplay = true;
26088 ++blockedWriterRejected;
26089 continue;
26090 }
26091 fl.setDwgCommonObjectState(
26092 static_cast<std::int32_t>(fl.reactorHandles.size()),
26093 fl.extensionDictionaryFlag(),
26094 encodedDwgDataStoragePresenceBit(candidate.sourceHandle,
26095 fl.hasDataStorageBinaryData()));
26096 if (writeDwgObjectWithCompletion(
26097 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind::
26098 Object,
26099 candidate.sourceHandle,
26100 [&fl, this] { return m_dwgW->writeFieldList(&fl); },
26101 [&pendingReferences, &recordRetainedReference, fieldPolicy]() {
26102 for (const PendingFieldListReference &reference :
26103 pendingReferences) {
26104 if (!recordRetainedReference(
26105 reference.sourceHandle, reference.outputHandle,
26106 DwgWriteSourceKind::Object, "FIELDLIST field",
26107 reference.ordinal, fieldPolicy.wireHandleCode,
26108 true)) {
26109 return false;
26110 }
26111 }
26112 return true;
26113 }) &&
26114 captureDwgFieldFrame(candidate)) {
26115 ++nativeFieldListObjects;
26116 } else {
26117 hasBlockedReplay = true;
26118 ++blockedWriterRejected;
26119 }
26120 }
26121 for (auto &candidate : m_dwgFieldWritePlan.fieldCandidates) {
26122 if (!candidate.classAdmitted ||
26123 nativeFieldHandles.count(candidate.sourceHandle) == 0) {
26124 m_writeFailed = true;
26125 continue;
26126 }
26127 DRW_Field f = candidate.payload;
26128 remapEntry(f);
26129 if (!remapRequiredFieldCadValue(f.m_value, "FIELD value", 0)) {
26130 hasBlockedReplay = true;
26131 ++blockedWriterRejected;
26132 continue;
26133 }
26134 bool childValuesValid = true;
26135 for (std::size_t ordinal = 0; ordinal < f.m_childValues.size();
26136 ++ordinal) {
26137 auto &child = f.m_childValues[ordinal];
26138 if (!remapRequiredFieldCadValue(
26139 child.m_value, "FIELD child value",
26140 static_cast<std::uint32_t>(ordinal))) {
26141 childValuesValid = false;
26142 break;
26143 }
26144 }
26145 if (!childValuesValid) {
26146 hasBlockedReplay = true;
26147 ++blockedWriterRejected;
26148 continue;
26149 }
26150 if (!remapRequiredFieldChildren(f.m_childHandles) ||
26151 !remapRequiredFieldObjects(f.m_objectHandles)) {
26152 hasBlockedReplay = true;
26153 ++blockedWriterRejected;
26154 continue;
26155 }
26156 if (!sanitizeCommonObjectReferences(f, "FIELD common reference")) {
26157 hasBlockedReplay = true;
26158 ++blockedWriterRejected;
26159 continue;
26160 }
26161 f.setDwgCommonObjectState(
26162 static_cast<std::int32_t>(f.reactorHandles.size()),
26163 f.extensionDictionaryFlag(),
26164 encodedDwgDataStoragePresenceBit(candidate.sourceHandle,
26165 f.hasDataStorageBinaryData()));
26166 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
26167 DwgWriteSourceKind::Object,
26168 candidate.sourceHandle,
26169 [&f, this] { return m_dwgW->writeField(&f); }) &&
26170 captureDwgFieldFrame(candidate)) {
26171 ++nativeFieldObjects;
26172 } else {
26173 hasBlockedReplay = true;
26174 ++blockedWriterRejected;
26175 }
26176 }
26177 for (const auto &candidate : m_dwgFieldWritePlan.fieldListCandidates) {
26178 if (!candidate.objectEmitted)
26179 continue;
26180 const bool targetsCommitted = std::all_of(
26181 candidate.fieldCandidateIndexes.cbegin(),
26182 candidate.fieldCandidateIndexes.cend(),
26183 [this](std::size_t fieldIndex) {
26184 return fieldIndex < m_dwgFieldWritePlan.fieldCandidates.size() &&
26185 m_dwgFieldWritePlan.fieldCandidates[fieldIndex]
26186 .objectEmitted;
26187 });
26188 if (!targetsCommitted) {
26189 m_writeFailed = true;
26190 hasBlockedReplay = true;
26191 ++blockedWriterRejected;
26192 }
26193 }
26194 for (const auto &record : metadata.underlayDefinitions()) {
26195 if (nativeUnderlayDefinitionHandles.count(record.handle) == 0)
26196 continue;
26197 DRW_UnderlayDefinition definition =
26198 underlayDefinitionFromMetadata(record);
26199 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
26200 hasBlockedReplay = true;
26201 ++blockedWriterRejected;
26202 continue;
26203 }
26204 remapEntry(definition);
26205 if (!sanitizeCommonObjectReferences(
26206 definition, "UNDERLAYDEFINITION common reference")) {
26207 hasBlockedReplay = true;
26208 ++blockedWriterRejected;
26209 continue;
26210 }
26211 definition.setDwgCommonObjectState(
26212 static_cast<std::int32_t>(definition.reactorHandles.size()),
26213 definition.extensionDictionaryFlag(),
26214 encodedDwgDataStoragePresenceBit(
26215 record.handle, definition.hasDataStorageBinaryData()));
26216 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
26217 DwgWriteSourceKind::Object,
26218 record.handle, [&definition, this] {
26219 return m_dwgW->writeUnderlayDefinition(
26220 &definition);
26221 })) {
26222 ++nativeUnderlayDefinitionObjects;
26223 } else {
26224 hasBlockedReplay = true;
26225 ++blockedWriterRejected;
26226 }
26227 }
26228 for (const auto &record : metadata.pointCloudDefinitions()) {
26229 if (nativePointCloudDefinitionHandles.count(record.handle) == 0)
26230 continue;
26231 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
26232 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
26233 hasBlockedReplay = true;
26234 ++blockedWriterRejected;
26235 continue;
26236 }
26237 remapEntry(definition);
26238 if (!sanitizeCommonObjectReferences(
26239 definition, "POINTCLOUDDEFINITION common reference")) {
26240 hasBlockedReplay = true;
26241 ++blockedWriterRejected;
26242 continue;
26243 }
26244 definition.setDwgCommonObjectState(
26245 static_cast<std::int32_t>(definition.reactorHandles.size()),
26246 definition.extensionDictionaryFlag(),
26247 encodedDwgDataStoragePresenceBit(
26248 record.handle, definition.hasDataStorageBinaryData()));
26249 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
26250 DwgWriteSourceKind::Object,
26251 record.handle, [&definition, this] {
26252 return m_dwgW->writePointCloudDef(&definition);
26253 })) {
26254 ++nativePointCloudDefinitionObjects;
26255 } else {
26256 hasBlockedReplay = true;
26257 ++blockedWriterRejected;
26258 }
26259 }
26260 for (const auto &record : metadata.navisworksModelDefinitions()) {
26261 if (nativeNavisworksModelDefinitionHandles.count(record.handle) == 0)
26262 continue;
26263 DRW_NavisworksModelDef definition =
26264 navisworksModelDefinitionFromMetadata(record);
26265 if (hasDxfTableAppDataUnsupportedByDwg(definition)) {
26266 hasBlockedReplay = true;
26267 ++blockedWriterRejected;
26268 continue;
26269 }
26270 remapEntry(definition);
26271 if (!sanitizeCommonObjectReferences(
26272 definition, "NAVISWORKSMODELDEF common reference")) {
26273 hasBlockedReplay = true;
26274 ++blockedWriterRejected;
26275 continue;
26276 }
26277 definition.setDwgCommonObjectState(
26278 static_cast<std::int32_t>(definition.reactorHandles.size()),
26279 definition.extensionDictionaryFlag(),
26280 encodedDwgDataStoragePresenceBit(
26281 record.handle, definition.hasDataStorageBinaryData()));
26282 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
26283 DwgWriteSourceKind::Object,
26284 record.handle, [&definition, this] {
26285 return m_dwgW->writeNavisworksModelDef(
26286 &definition);
26287 })) {
26288 ++nativeNavisworksModelDefinitionObjects;
26289 } else {
26290 hasBlockedReplay = true;
26291 ++blockedWriterRejected;
26292 }
26293 }
26294 for (const auto &record : metadata.pointCloudColorMaps()) {
26295 if (nativePointCloudColorMapHandles.count(record.handle) == 0)
26296 continue;
26297 DRW_PointCloudColorMap colorMap =
26298 pointCloudColorMapFromMetadata(record);
26299 if (hasDxfTableAppDataUnsupportedByDwg(colorMap)) {
26300 hasBlockedReplay = true;
26301 ++blockedWriterRejected;
26302 continue;
26303 }
26304 remapEntry(colorMap);
26305 if (!sanitizeCommonObjectReferences(
26306 colorMap, "POINTCLOUDCOLORMAP common reference")) {
26307 hasBlockedReplay = true;
26308 ++blockedWriterRejected;
26309 continue;
26310 }
26311 colorMap.setDwgCommonObjectState(
26312 static_cast<std::int32_t>(colorMap.reactorHandles.size()),
26313 colorMap.extensionDictionaryFlag(),
26314 encodedDwgDataStoragePresenceBit(
26315 record.handle, colorMap.hasDataStorageBinaryData()));
26316 if (writeDwgObject(RS_FilterDXFRW_DwgWriteIdentityRegistry::
26317 DwgWriteSourceKind::Object,
26318 record.handle, [&colorMap, this] {
26319 return m_dwgW->writePointCloudColorMap(&colorMap);
26320 })) {
26321 ++nativePointCloudColorMapObjects;
26322 } else {
26323 hasBlockedReplay = true;
26324 ++blockedWriterRejected;
26325 }
26326 }
26327 }
26328 if (replayedObjects > 0) {
26329 RS_DEBUGRS_Debug::instance()->print(
26330 "RS_FilterDXFRW::writeObjects: replayed %d raw DWG objects",
26331 replayedObjects);
26332 }
26333 if (replayedSections > 0) {
26334 RS_DEBUGRS_Debug::instance()->print(
26335 "RS_FilterDXFRW::writeObjects: replayed %d raw DWG data sections",
26336 replayedSections);
26337 }
26338 if (rawFamilyCounts.total() > 0) {
26339 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: raw DWG object families "
26340 "assoc=%d eval-graph=%d dynamic-block=%d "
26341 "object-context=%d unknown=%d",
26342 static_cast<int>(rawFamilyCounts.associative),
26343 static_cast<int>(rawFamilyCounts.evaluationGraph),
26344 static_cast<int>(rawFamilyCounts.dynamicBlock),
26345 static_cast<int>(rawFamilyCounts.objectContext),
26346 static_cast<int>(rawFamilyCounts.unknown));
26347 }
26348 if (nativeSunObjects > 0) {
26349 RS_DEBUGRS_Debug::instance()->print(
26350 "RS_FilterDXFRW::writeObjects: wrote %d native SUN objects",
26351 nativeSunObjects);
26352 }
26353 if (nativeSunStudyObjects > 0) {
26354 RS_DEBUGRS_Debug::instance()->print(
26355 "RS_FilterDXFRW::writeObjects: wrote %d native SUNSTUDY objects",
26356 nativeSunStudyObjects);
26357 }
26358 if (nativeMotionPathObjects > 0) {
26359 RS_DEBUGRS_Debug::instance()->print(
26360 "RS_FilterDXFRW::writeObjects: wrote %d native MOTIONPATH objects",
26361 nativeMotionPathObjects);
26362 }
26363 if (nativeCurvePathObjects > 0) {
26364 RS_DEBUGRS_Debug::instance()->print(
26365 "RS_FilterDXFRW::writeObjects: wrote %d native CURVEPATH objects",
26366 nativeCurvePathObjects);
26367 }
26368 if (nativePointPathObjects > 0) {
26369 RS_DEBUGRS_Debug::instance()->print(
26370 "RS_FilterDXFRW::writeObjects: wrote %d native POINTPATH objects",
26371 nativePointPathObjects);
26372 }
26373 if (nativeObjectPtrObjects > 0) {
26374 RS_DEBUGRS_Debug::instance()->print(
26375 "RS_FilterDXFRW::writeObjects: wrote %d native OBJECT_PTR objects",
26376 nativeObjectPtrObjects);
26377 }
26378 if (nativePartialViewingIndexObjects > 0) {
26379 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26380 "PARTIAL_VIEWING_INDEX objects",
26381 nativePartialViewingIndexObjects);
26382 }
26383 if (nativeRenderSettingsObjects > 0) {
26384 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native render "
26385 "settings objects",
26386 nativeRenderSettingsObjects);
26387 }
26388 if (nativeVisualStyleObjects > 0) {
26389 RS_DEBUGRS_Debug::instance()->print(
26390 "RS_FilterDXFRW::writeObjects: wrote %d native VISUALSTYLE objects",
26391 nativeVisualStyleObjects);
26392 }
26393 if (nativeTvDevicePropertiesObjects > 0) {
26394 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26395 "TVDEVICEPROPERTIES objects",
26396 nativeTvDevicePropertiesObjects);
26397 }
26398 if (nativeVxControlObjects > 0) {
26399 RS_DEBUGRS_Debug::instance()->print(
26400 "RS_FilterDXFRW::writeObjects: wrote %d native VXCONTROL objects",
26401 nativeVxControlObjects);
26402 }
26403 if (nativeVxTableRecordObjects > 0) {
26404 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26405 "VXTABLERECORD objects",
26406 nativeVxTableRecordObjects);
26407 }
26408 if (nativePlaceholderObjects > 0) {
26409 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26410 "ACDBPLACEHOLDER objects",
26411 nativePlaceholderObjects);
26412 }
26413 if (nativeVbaProjectObjects > 0) {
26414 RS_DEBUGRS_Debug::instance()->print(
26415 "RS_FilterDXFRW::writeObjects: wrote %d native VBA_PROJECT objects",
26416 nativeVbaProjectObjects);
26417 }
26418 if (nativeMLeaderStyleObjects > 0) {
26419 RS_DEBUGRS_Debug::instance()->print(
26420 "RS_FilterDXFRW::writeObjects: wrote %d native MLEADERSTYLE objects",
26421 nativeMLeaderStyleObjects);
26422 }
26423 if (nativeMaterialObjects > 0) {
26424 RS_DEBUGRS_Debug::instance()->print(
26425 "RS_FilterDXFRW::writeObjects: wrote %d native MATERIAL objects",
26426 nativeMaterialObjects);
26427 }
26428 if (nativeLightListObjects > 0) {
26429 RS_DEBUGRS_Debug::instance()->print(
26430 "RS_FilterDXFRW::writeObjects: wrote %d native LIGHTLIST objects",
26431 nativeLightListObjects);
26432 }
26433 if (nativeBackgroundObjects > 0) {
26434 RS_DEBUGRS_Debug::instance()->print(
26435 "RS_FilterDXFRW::writeObjects: wrote %d native BACKGROUND objects",
26436 nativeBackgroundObjects);
26437 }
26438 if (nativeDictionaryObjects > 0) {
26439 RS_DEBUGRS_Debug::instance()->print(
26440 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARY objects",
26441 nativeDictionaryObjects);
26442 }
26443 if (nativeXRecordObjects > 0) {
26444 RS_DEBUGRS_Debug::instance()->print(
26445 "RS_FilterDXFRW::writeObjects: wrote %d native XRECORD objects",
26446 nativeXRecordObjects);
26447 }
26448 if (nativeLayoutObjects > 0) {
26449 RS_DEBUGRS_Debug::instance()->print(
26450 "RS_FilterDXFRW::writeObjects: wrote %d native LAYOUT objects",
26451 nativeLayoutObjects);
26452 }
26453 if (nativeGroupObjects > 0) {
26454 RS_DEBUGRS_Debug::instance()->print(
26455 "RS_FilterDXFRW::writeObjects: wrote %d native GROUP objects",
26456 nativeGroupObjects);
26457 }
26458 if (nativeMLineStyleObjects > 0) {
26459 RS_DEBUGRS_Debug::instance()->print(
26460 "RS_FilterDXFRW::writeObjects: wrote %d native MLINESTYLE objects",
26461 nativeMLineStyleObjects);
26462 }
26463 if (nativeTableStyleObjects > 0) {
26464 RS_DEBUGRS_Debug::instance()->print(
26465 "RS_FilterDXFRW::writeObjects: wrote %d native TABLESTYLE objects",
26466 nativeTableStyleObjects);
26467 }
26468 if (nativeDbColorObjects > 0) {
26469 RS_DEBUGRS_Debug::instance()->print(
26470 "RS_FilterDXFRW::writeObjects: wrote %d native DBCOLOR objects",
26471 nativeDbColorObjects);
26472 }
26473 if (nativeDimensionAssociationObjects > 0) {
26474 RS_DEBUGRS_Debug::instance()->print(
26475 "RS_FilterDXFRW::writeObjects: wrote %d native DIMASSOC objects",
26476 nativeDimensionAssociationObjects);
26477 }
26478 if (nativeEvaluationGraphObjects > 0) {
26479 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26480 "EVALUATION_GRAPH objects",
26481 nativeEvaluationGraphObjects);
26482 }
26483 if (nativeRasterVariablesObjects > 0) {
26484 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26485 "RASTERVARIABLES objects",
26486 nativeRasterVariablesObjects);
26487 }
26488 if (nativeWipeoutVariablesObjects > 0) {
26489 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26490 "WIPEOUTVARIABLES objects",
26491 nativeWipeoutVariablesObjects);
26492 }
26493 if (nativeGeoDataObjects > 0) {
26494 RS_DEBUGRS_Debug::instance()->print(
26495 "RS_FilterDXFRW::writeObjects: wrote %d native GEODATA objects",
26496 nativeGeoDataObjects);
26497 }
26498 if (nativeSpatialFilterObjects > 0) {
26499 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26500 "SPATIAL_FILTER objects",
26501 nativeSpatialFilterObjects);
26502 }
26503 // PR 8d.2a — five small no-storage OBJECTS families.
26504 if (nativeScaleObjects > 0) {
26505 RS_DEBUGRS_Debug::instance()->print(
26506 "RS_FilterDXFRW::writeObjects: wrote %d native SCALE objects",
26507 nativeScaleObjects);
26508 }
26509 if (nativeIDBufferObjects > 0) {
26510 RS_DEBUGRS_Debug::instance()->print(
26511 "RS_FilterDXFRW::writeObjects: wrote %d native IDBUFFER objects",
26512 nativeIDBufferObjects);
26513 }
26514 if (nativeLayerIndexObjects > 0) {
26515 RS_DEBUGRS_Debug::instance()->print(
26516 "RS_FilterDXFRW::writeObjects: wrote %d native LAYER_INDEX objects",
26517 nativeLayerIndexObjects);
26518 }
26519 if (nativeSpatialIndexObjects > 0) {
26520 RS_DEBUGRS_Debug::instance()->print(
26521 "RS_FilterDXFRW::writeObjects: wrote %d native SPATIAL_INDEX objects",
26522 nativeSpatialIndexObjects);
26523 }
26524 if (nativeDictionaryVarObjects > 0) {
26525 RS_DEBUGRS_Debug::instance()->print(
26526 "RS_FilterDXFRW::writeObjects: wrote %d native DICTIONARYVAR objects",
26527 nativeDictionaryVarObjects);
26528 }
26529 // PR 8d.2b — four larger no-storage OBJECTS families.
26530 if (nativeDictionaryWithDefaultObjects > 0) {
26531 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26532 "DICTIONARYWDFLT objects",
26533 nativeDictionaryWithDefaultObjects);
26534 }
26535 if (nativeSortEntsTableObjects > 0) {
26536 RS_DEBUGRS_Debug::instance()->print(
26537 "RS_FilterDXFRW::writeObjects: wrote %d native SORTENTSTABLE objects",
26538 nativeSortEntsTableObjects);
26539 }
26540 if (nativeFieldListObjects > 0) {
26541 RS_DEBUGRS_Debug::instance()->print(
26542 "RS_FilterDXFRW::writeObjects: wrote %d native FIELDLIST objects",
26543 nativeFieldListObjects);
26544 }
26545 if (nativeFieldObjects > 0) {
26546 RS_DEBUGRS_Debug::instance()->print(
26547 "RS_FilterDXFRW::writeObjects: wrote %d native FIELD objects",
26548 nativeFieldObjects);
26549 }
26550 if (nativeUnderlayDefinitionObjects > 0) {
26551 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26552 "UNDERLAYDEFINITION objects",
26553 nativeUnderlayDefinitionObjects);
26554 }
26555 if (nativePointCloudDefinitionObjects > 0) {
26556 RS_DEBUGRS_Debug::instance()->print(
26557 "RS_FilterDXFRW::writeObjects: wrote %d native POINTCLOUDDEF objects",
26558 nativePointCloudDefinitionObjects);
26559 }
26560 if (nativeNavisworksModelDefinitionObjects > 0) {
26561 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26562 "NAVISWORKSMODELDEF objects",
26563 nativeNavisworksModelDefinitionObjects);
26564 }
26565 if (nativePointCloudColorMapObjects > 0) {
26566 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::writeObjects: wrote %d native "
26567 "POINTCLOUDCOLORMAP objects",
26568 nativePointCloudColorMapObjects);
26569 }
26570 if (nativeSectionObjects > 0) {
26571 RS_DEBUGRS_Debug::instance()->print(
26572 "RS_FilterDXFRW::writeObjects: wrote %d native SECTION objects",
26573 nativeSectionObjects);
26574 }
26575 if (modelerPayloads.recordCount > 0) {
26576 const RS_Debug::RS_DebugLevel level =
26577 modelerPayloads.inconsistentSplit > 0 ? RS_Debug::D_WARNING
26578 : RS_Debug::D_DEBUGGING;
26579 RS_DEBUGRS_Debug::instance()->print(
26580 level,
26581 "DWG modeler geometry payloads: records=%d SAT=%d SAB=%d "
26582 "unknown=%d split-inconsistent=%d marker-body=%d marker-handle=%d",
26583 static_cast<int>(modelerPayloads.recordCount),
26584 static_cast<int>(modelerPayloads.sat),
26585 static_cast<int>(modelerPayloads.sab),
26586 static_cast<int>(modelerPayloads.unknown),
26587 static_cast<int>(modelerPayloads.inconsistentSplit),
26588 static_cast<int>(modelerPayloads.markerInBody),
26589 static_cast<int>(modelerPayloads.markerInHandleStream));
26590 }
26591 if (associativeShells.recordCount > 0) {
26592 RS_DEBUGRS_Debug::instance()->print(
26593 "DWG associative/action shells: records=%d network=%d action=%d "
26594 "dependency=%d geom-dependency=%d action-param=%d value-param=%d/%d "
26595 "prefix=%d/%d single-dep=%d compound=%d prefix-status=%d "
26596 "complete=%d partial=%d overflow=%d handles=%d values=%d unknown=%d",
26597 static_cast<int>(associativeShells.recordCount),
26598 static_cast<int>(associativeShells.network),
26599 static_cast<int>(associativeShells.action),
26600 static_cast<int>(associativeShells.dependency),
26601 static_cast<int>(associativeShells.geometryDependency),
26602 static_cast<int>(associativeShells.actionParamRecords),
26603 static_cast<int>(associativeShells.parsedValueParamRecords),
26604 static_cast<int>(associativeShells.valueParamRecords),
26605 static_cast<int>(associativeShells.parsedActionParamPrefixes),
26606 static_cast<int>(associativeShells.actionParamRecords),
26607 static_cast<int>(
26608 associativeShells.singleDependencyActionParamPrefixes),
26609 static_cast<int>(associativeShells.compoundActionParamPrefixes),
26610 static_cast<int>(associativePrefixes.prefixCount),
26611 static_cast<int>(associativePrefixes.complete),
26612 static_cast<int>(associativePrefixes.partial),
26613 static_cast<int>(associativePrefixes.boundedCountOverflow),
26614 static_cast<int>(associativePrefixes.decodedHandleCount),
26615 static_cast<int>(associativePrefixes.decodedValueCount),
26616 static_cast<int>(associativeShells.unknown));
26617 }
26618 const size_t graphReplayKnownFamilies =
26619 (graphReplayPolicy.preserved.total() -
26620 graphReplayPolicy.preserved.unknown) +
26621 (graphReplayPolicy.suppressed.total() -
26622 graphReplayPolicy.suppressed.unknown);
26623 if (graphReplayKnownFamilies > 0 ||
26624 graphReplayPolicy.totalSemanticOnly() > 0 ||
26625 graphReplayPolicy.totalReasons() > 0) {
26626 RS_DEBUGRS_Debug::instance()->print(
26627 graphReplayPolicy.suppressed.total() > 0 ||
26628 graphReplayPolicy.totalReasons() > 0
26629 ? RS_Debug::D_WARNING
26630 : RS_Debug::D_DEBUGGING,
26631 "DWG graph replay policy: preserved dimassoc=%d eval=%d "
26632 "assoc=%d dynamic-block=%d object-context=%d acsh=%d unknown=%d "
26633 "suppressed dimassoc=%d eval=%d assoc=%d dynamic-block=%d "
26634 "object-context=%d acsh=%d unknown=%d semantic-only assoc=%d "
26635 "acsh=%d reasons edited=%d missing-target=%d evaluator=%d "
26636 "parser-partial=%d fallback-edited=%d native-replaced=%d "
26637 "cycle-path=%d owner-deleted=%d raw-invalidated=%d "
26638 "raw-replaced=%d entity=%d missing-bytes=%d missing-class=%d",
26639 static_cast<int>(graphReplayPolicy.preserved.dimensionAssociation),
26640 static_cast<int>(graphReplayPolicy.preserved.evaluationGraph),
26641 static_cast<int>(graphReplayPolicy.preserved.acDbAssoc),
26642 static_cast<int>(graphReplayPolicy.preserved.dynamicBlock),
26643 static_cast<int>(graphReplayPolicy.preserved.objectContext),
26644 static_cast<int>(graphReplayPolicy.preserved.acShHistory),
26645 static_cast<int>(graphReplayPolicy.preserved.unknown),
26646 static_cast<int>(graphReplayPolicy.suppressed.dimensionAssociation),
26647 static_cast<int>(graphReplayPolicy.suppressed.evaluationGraph),
26648 static_cast<int>(graphReplayPolicy.suppressed.acDbAssoc),
26649 static_cast<int>(graphReplayPolicy.suppressed.dynamicBlock),
26650 static_cast<int>(graphReplayPolicy.suppressed.objectContext),
26651 static_cast<int>(graphReplayPolicy.suppressed.acShHistory),
26652 static_cast<int>(graphReplayPolicy.suppressed.unknown),
26653 static_cast<int>(graphReplayPolicy.semanticOnlyAssociative),
26654 static_cast<int>(graphReplayPolicy.semanticOnlyAcSh),
26655 static_cast<int>(graphReplayPolicy.editedEntity),
26656 static_cast<int>(graphReplayPolicy.missingTarget),
26657 static_cast<int>(graphReplayPolicy.unsupportedEvaluator),
26658 static_cast<int>(graphReplayPolicy.parserPartial),
26659 static_cast<int>(graphReplayPolicy.fallbackGeometryEdited),
26660 static_cast<int>(graphReplayPolicy.nativeReplacement),
26661 static_cast<int>(graphReplayPolicy.cyclePathInvalidated),
26662 static_cast<int>(graphReplayPolicy.ownerDeleted),
26663 static_cast<int>(graphReplayPolicy.invalidated),
26664 static_cast<int>(graphReplayPolicy.replaced),
26665 static_cast<int>(graphReplayPolicy.entityReplayUnsupported),
26666 static_cast<int>(graphReplayPolicy.missingRawBytes),
26667 static_cast<int>(graphReplayPolicy.missingClassMetadata));
26668 }
26669 if (tableBlockers.tableCount > 0 && tableBlockers.totalBlockers() > 0) {
26670 RS_DEBUGRS_Debug::instance()->print(
26671 RS_Debug::D_WARNING,
26672 "Native DWG table writing blocked: tables=%d eligible-text=%d "
26673 "layout-direct=%d layout-separate=%d layout-embedded=%d "
26674 "layout-unsupported=%d semantic=%d version=%d ambiguous-layout=%d "
26675 "owner=%d style=%d cell-style=%d unknown-ranges=%d "
26676 "incomplete-ranges=%d override=%d break=%d geometry=%d "
26677 "merged=%d field=%d block=%d attributes=%d unknown-content=%d "
26678 "value-payload=%d edited-fallback=%d missing-fallback-links=%d "
26679 "anon-block=%d text-style=%d linetype=%d raw-invalidated=%d "
26680 "raw-replaced=%d dimensions=%d",
26681 static_cast<int>(tableBlockers.tableCount),
26682 static_cast<int>(tableBlockers.eligibleTextOnly),
26683 static_cast<int>(tableBlockers.legacyDirectLayout),
26684 static_cast<int>(tableBlockers.separateTableContentLayout),
26685 static_cast<int>(tableBlockers.embeddedTableContentLayout),
26686 static_cast<int>(tableBlockers.unsupportedLayout),
26687 static_cast<int>(tableBlockers.noSemanticTableContent),
26688 static_cast<int>(tableBlockers.unsupportedTableVersion),
26689 static_cast<int>(tableBlockers.ambiguousTableContentStorage),
26690 static_cast<int>(tableBlockers.missingOwnerHandle),
26691 static_cast<int>(tableBlockers.unresolvedTableStyle),
26692 static_cast<int>(tableBlockers.unresolvedCellStyleMap),
26693 static_cast<int>(tableBlockers.unknownSubrecordRange),
26694 static_cast<int>(tableBlockers.incompleteSubrecordRange),
26695 static_cast<int>(tableBlockers.overrideMask),
26696 static_cast<int>(tableBlockers.breakData),
26697 static_cast<int>(tableBlockers.geometryTail),
26698 static_cast<int>(tableBlockers.mergedCell),
26699 static_cast<int>(tableBlockers.fieldContent),
26700 static_cast<int>(tableBlockers.blockContent),
26701 static_cast<int>(tableBlockers.attributeContent),
26702 static_cast<int>(tableBlockers.unknownCellContent),
26703 static_cast<int>(tableBlockers.incompleteValuePayload),
26704 static_cast<int>(tableBlockers.editedFallback),
26705 static_cast<int>(tableBlockers.missingFallbackAttachment),
26706 static_cast<int>(tableBlockers.anonymousBlockPolicyUnresolved),
26707 static_cast<int>(tableBlockers.unresolvedTextStyle),
26708 static_cast<int>(tableBlockers.unresolvedLineType),
26709 static_cast<int>(tableBlockers.rawReplayInvalidated),
26710 static_cast<int>(tableBlockers.rawReplayReplaced),
26711 static_cast<int>(tableBlockers.nonPositiveDimension));
26712 }
26713 if (mleaderBlockers.mleaderCount > 0 &&
26714 mleaderBlockers.totalBlockers() > 0) {
26715 RS_DEBUGRS_Debug::instance()->print(
26716 RS_Debug::D_WARNING,
26717 "Native DWG MLEADER writing limited: mleaders=%d unresolved-style=%d "
26718 "missing-text=%d block=%d tolerance=%d overrides=%d geometry=%d "
26719 "invalidated=%d replaced=%d",
26720 static_cast<int>(mleaderBlockers.mleaderCount),
26721 static_cast<int>(mleaderBlockers.unresolvedStyle),
26722 static_cast<int>(mleaderBlockers.missingTextContent),
26723 static_cast<int>(mleaderBlockers.blockContent),
26724 static_cast<int>(mleaderBlockers.toleranceContent),
26725 static_cast<int>(mleaderBlockers.overrideFlags),
26726 static_cast<int>(mleaderBlockers.missingLeaderGeometry),
26727 static_cast<int>(mleaderBlockers.invalidated),
26728 static_cast<int>(mleaderBlockers.replaced));
26729 }
26730 if (meshBlockers.meshCount > 0 && meshBlockers.totalBlockers() > 0) {
26731 RS_DEBUGRS_Debug::instance()->print(
26732 RS_Debug::D_WARNING,
26733 "Native DWG mesh writing blocked: meshes=%d sidecars=%d "
26734 "range-complete=%d range-missing=%d range-incomplete=%d "
26735 "crease=%d subdivision=%d fallback-only=%d edited-fallback=%d "
26736 "owner-class=%d malformed-counts=%d invalidated=%d replaced=%d",
26737 static_cast<int>(meshBlockers.meshCount),
26738 static_cast<int>(meshBlockers.sidecarCount),
26739 static_cast<int>(meshBlockers.completeRawRange),
26740 static_cast<int>(meshBlockers.missingRawRange),
26741 static_cast<int>(meshBlockers.incompleteRawRange),
26742 static_cast<int>(meshBlockers.missingCreaseData),
26743 static_cast<int>(meshBlockers.unsupportedSubdivisionData),
26744 static_cast<int>(meshBlockers.fallbackOnlyPreview),
26745 static_cast<int>(meshBlockers.editedFallback),
26746 static_cast<int>(meshBlockers.missingOwnerOrClassHandle),
26747 static_cast<int>(meshBlockers.malformedCountRelationships),
26748 static_cast<int>(meshBlockers.invalidated),
26749 static_cast<int>(meshBlockers.replaced));
26750 }
26751 if (externalRefs.imageEntities > 0 || externalRefs.wipeouts > 0 ||
26752 externalRefs.underlays > 0 || externalRefs.imageDefinitions > 0 ||
26753 externalRefs.underlayDefinitions > 0) {
26754 const bool hasExternalIssues =
26755 externalRefs.totalPathIssues() > 0 ||
26756 externalRefs.missingDefinitionHandles > 0 ||
26757 externalRefs.malformedClips > 0;
26758 RS_DEBUGRS_Debug::instance()->print(
26759 hasExternalIssues ? RS_Debug::D_WARNING : RS_Debug::D_DEBUGGING,
26760 "DWG external references: images=%d wipeouts=%d image-defs=%d "
26761 "underlays=%d underlay-defs=%d raster-vars=%d path-empty=%d "
26762 "path-relative=%d path-missing=%d external=%d scheme=%d "
26763 "case-candidate=%d missing-def=%d orphan-def=%d clip-none=%d "
26764 "clip-rect=%d clip-poly=%d clip-bad=%d inverted=%d hidden-frame=%d",
26765 static_cast<int>(externalRefs.imageEntities),
26766 static_cast<int>(externalRefs.wipeouts),
26767 static_cast<int>(externalRefs.imageDefinitions),
26768 static_cast<int>(externalRefs.underlays),
26769 static_cast<int>(externalRefs.underlayDefinitions),
26770 static_cast<int>(externalRefs.rasterVariables),
26771 static_cast<int>(externalRefs.emptyPaths),
26772 static_cast<int>(externalRefs.relativePaths),
26773 static_cast<int>(externalRefs.absoluteMissingPaths),
26774 static_cast<int>(externalRefs.externalPaths),
26775 static_cast<int>(externalRefs.unsupportedSchemes),
26776 static_cast<int>(externalRefs.caseMismatchCandidates),
26777 static_cast<int>(externalRefs.missingDefinitionHandles),
26778 static_cast<int>(externalRefs.definitionsWithoutEntities),
26779 static_cast<int>(externalRefs.noBoundaryClips),
26780 static_cast<int>(externalRefs.rectangularClips),
26781 static_cast<int>(externalRefs.polygonalClips),
26782 static_cast<int>(externalRefs.malformedClips),
26783 static_cast<int>(externalRefs.invertedClips),
26784 static_cast<int>(externalRefs.hiddenFrames));
26785 }
26786 if (shapeOleBlockers.shapeCount > 0 ||
26787 shapeOleBlockers.ole2FrameCount > 0) {
26788 RS_DEBUGRS_Debug::instance()->print(
26789 shapeOleBlockers.totalBlockers() > 0 ? RS_Debug::D_WARNING
26790 : RS_Debug::D_DEBUGGING,
26791 "DWG shape/OLE writer blockers: shapes=%d ole2=%d "
26792 "missing-style=%d unresolved-style=%d missing-ole=%d "
26793 "truncated-ole=%d oversized-ole=%d edited-preview=%d "
26794 "unsupported-ole=%d raw-missing=%d raw-incomplete=%d "
26795 "invalidated=%d replaced=%d",
26796 static_cast<int>(shapeOleBlockers.shapeCount),
26797 static_cast<int>(shapeOleBlockers.ole2FrameCount),
26798 static_cast<int>(shapeOleBlockers.missingStyleHandle),
26799 static_cast<int>(shapeOleBlockers.unresolvedShapeStyle),
26800 static_cast<int>(shapeOleBlockers.missingOlePayload),
26801 static_cast<int>(shapeOleBlockers.truncatedOlePayload),
26802 static_cast<int>(shapeOleBlockers.oversizedOlePayload),
26803 static_cast<int>(shapeOleBlockers.editedPreviewFrame),
26804 static_cast<int>(shapeOleBlockers.unsupportedOlePayloadRegeneration),
26805 static_cast<int>(shapeOleBlockers.missingRawRange),
26806 static_cast<int>(shapeOleBlockers.incompleteRawRange),
26807 static_cast<int>(shapeOleBlockers.invalidated),
26808 static_cast<int>(shapeOleBlockers.replaced));
26809 }
26810 if (visualBlockers.recordCount > 0 || visualBlockers.rawPayloads > 0) {
26811 RS_DEBUGRS_Debug::instance()->print(
26812 visualBlockers.totalBlockers() > 0 ? RS_Debug::D_WARNING
26813 : RS_Debug::D_DEBUGGING,
26814 "DWG visual metadata export policy: records=%d raw=%d "
26815 "raw-replayable=%d raw-suppressed=%d ucs=%d base-ucs=%d "
26816 "visual-style=%d sun=%d background=%d live-section=%d "
26817 "owner-layout=%d raw-invalidated=%d raw-replaced=%d "
26818 "unsupported-visual-style=%d",
26819 static_cast<int>(visualBlockers.recordCount),
26820 static_cast<int>(visualBlockers.rawPayloads),
26821 static_cast<int>(visualBlockers.replayableRawPayloads),
26822 static_cast<int>(visualBlockers.suppressedRawPayloads),
26823 static_cast<int>(visualBlockers.unresolvedUcs),
26824 static_cast<int>(visualBlockers.unresolvedBaseUcs),
26825 static_cast<int>(visualBlockers.unresolvedVisualStyle),
26826 static_cast<int>(visualBlockers.unresolvedSun),
26827 static_cast<int>(visualBlockers.unresolvedBackground),
26828 static_cast<int>(visualBlockers.unresolvedLiveSection),
26829 static_cast<int>(visualBlockers.missingOwnerOrLayout),
26830 static_cast<int>(visualBlockers.invalidatedRawPayload),
26831 static_cast<int>(visualBlockers.replacedNativeUnavailablePayload),
26832 static_cast<int>(visualBlockers.unsupportedVisualStyleWriter));
26833 }
26834 if (advancedEntityBlockers.recordCount > 0 &&
26835 advancedEntityBlockers.totalBlockers() > 0) {
26836 RS_DEBUGRS_Debug::instance()->print(
26837 RS_Debug::D_WARNING,
26838 "DWG advanced entity writer readiness: records=%d mesh=%d "
26839 "shape=%d ole2=%d image=%d wipeout=%d underlay=%d "
26840 "mleader=%d arc-dim=%d unknown=%d native=%d raw=%d "
26841 "fallback=%d edited-fallback=%d metadata=%d payload=%d "
26842 "advanced-content=%d oda-complete=%d oda-partial=%d "
26843 "oda-absent=%d",
26844 static_cast<int>(advancedEntityBlockers.recordCount),
26845 static_cast<int>(advancedEntityBlockers.mesh),
26846 static_cast<int>(advancedEntityBlockers.shape),
26847 static_cast<int>(advancedEntityBlockers.ole2Frame),
26848 static_cast<int>(advancedEntityBlockers.rasterImage),
26849 static_cast<int>(advancedEntityBlockers.wipeout),
26850 static_cast<int>(advancedEntityBlockers.underlay),
26851 static_cast<int>(advancedEntityBlockers.mleader),
26852 static_cast<int>(advancedEntityBlockers.arcDimension),
26853 static_cast<int>(advancedEntityBlockers.unknown),
26854 static_cast<int>(advancedEntityBlockers.nativeWriterAvailable),
26855 static_cast<int>(advancedEntityBlockers.rawReplayAvailable),
26856 static_cast<int>(advancedEntityBlockers.fallbackAvailable),
26857 static_cast<int>(advancedEntityBlockers.editedFallbackInvalidated),
26858 static_cast<int>(advancedEntityBlockers.missingRequiredMetadata),
26859 static_cast<int>(advancedEntityBlockers.missingPayloadBytes),
26860 static_cast<int>(advancedEntityBlockers.unsupportedAdvancedContent),
26861 static_cast<int>(advancedEntityBlockers.odaComplete),
26862 static_cast<int>(advancedEntityBlockers.odaPartial),
26863 static_cast<int>(advancedEntityBlockers.odaAbsent));
26864 }
26865 const size_t nativeShapeRecords = static_cast<size_t>(std::count_if(
26866 m_shapesToExport.begin(), m_shapesToExport.end(), [this](const auto &record) {
26867 return lookupDwgWriteIdentity(DwgWriteSourceKind::Entity,
26868 record.shape.handle) ==
26869 DwgWriteIdentityLookup::Committed;
26870 }));
26871 const size_t nativeSemanticRecords = nativeShapeRecords + static_cast<size_t>(
26872 nativeSunObjects + nativeVisualStyleObjects +
26873 nativeTvDevicePropertiesObjects + nativePlaceholderObjects +
26874 nativeMLeaderStyleObjects + nativeDictionaryObjects +
26875 nativeXRecordObjects + nativeLayoutObjects + nativeGroupObjects +
26876 nativeTableStyleObjects + nativeRasterVariablesObjects +
26877 nativeWipeoutVariablesObjects + nativeGeoDataObjects +
26878 nativeSpatialFilterObjects + nativePointCloudDefinitionObjects +
26879 nativeNavisworksModelDefinitionObjects +
26880 nativePointCloudColorMapObjects + nativeSectionObjects);
26881 const size_t semanticOnlyRecords =
26882 metadata.semanticOnlyRecordCount() > nativeSemanticRecords
26883 ? metadata.semanticOnlyRecordCount() - nativeSemanticRecords
26884 : 0;
26885 const bool hasSemanticOnlyReplayable = semanticOnlyRecords > 0;
26886 if (hasBlockedReplay || hasSemanticOnlyReplayable) {
26887 RS_DEBUGRS_Debug::instance()->print(
26888 RS_Debug::D_WARNING,
26889 "Some DWG advanced metadata still cannot be emitted natively; "
26890 "unchanged raw OBJECTS/data sections are replayed where safe, "
26891 "while entities and semantic-only records remain diagnostic-only");
26892 if (hasBlockedReplay) {
26893 RS_DEBUGRS_Debug::instance()->print(
26894 RS_Debug::D_WARNING,
26895 "Blocked raw DWG replay: invalidated=%d replaced=%d entity=%d "
26896 "missing-bytes=%d missing-class=%d writer-rejected=%d "
26897 "version-mismatch=%d fixed-handle=%d (objects=%d sections=%d)",
26898 blockedInvalidated, blockedReplaced, blockedEntityReplay,
26899 blockedMissingRawBytes, blockedMissingClassMetadata,
26900 blockedWriterRejected, blockedVersionMismatch, blockedFixedHandle,
26901 replayedObjects, replayedSections);
26902 }
26903 if (semanticOnlyRecords > 0) {
26904 RS_DEBUGRS_Debug::instance()->print(
26905 RS_Debug::D_WARNING,
26906 "Semantic-only DWG metadata records not emitted natively: %d",
26907 static_cast<int>(semanticOnlyRecords));
26908 }
26909 }
26910 if (blockedDataStorage > 0) {
26911 m_writeFailed = true;
26912 RS_DEBUGRS_Debug::instance()->print(
26913 RS_Debug::D_WARNING,
26914 "DWG export rejected: %d parsed DataStorage section/carrier "
26915 "item(s) could not be preserved for the target version",
26916 blockedDataStorage);
26917 }
26918 return; // DWG writer handles object section internally
26919 }
26920 /* PLOTSETTINGS */
26921 const auto &metadata = m_graphic->dwgAdvancedMetadata();
26922 LC_PlotSettings *gps = m_graphic->getPlotSettings();
26923 if (gps == nullptr) {
26924 m_writeFailed = true;
26925 return;
26926 }
26927 const auto writePlotSettings = [this, gps](DRW_PlotSettings ps,
26928 bool overlayActiveSettings) {
26929 if (overlayActiveSettings)
26930 overlayActivePlotSettings(ps, *gps);
26931 noteDxfWrite(m_dxfW->writePlotSettings(&ps));
26932 };
26933 if (metadata.plotSettings().empty()) {
26934 writePlotSettings(DRW_PlotSettings{}, true);
26935 } else {
26936 for (std::size_t index = 0; index < metadata.plotSettings().size(); ++index)
26937 writePlotSettings(metadata.plotSettings()[index], index == 0);
26938 }
26939
26940 // Slice A2: re-emit OBJECTS captured verbatim on read (A1) so a LibreCAD DXF
26941 // round-trip preserves unmodeled objects rather than dropping them. Records
26942 // live on the graphic, so this (separate) write-filter instance still sees
26943 // them. Skip handles the codec regenerates itself (source ACAD_GROUP dict,
26944 // C/D collisions — see m_dxfSuppressedObjectHandles set up in fileExport) so
26945 // routed named dictionaries don't duplicate the root/group dictionaries.
26946 if (m_graphic != nullptr) {
26947 for (const DRW_RawDxfObject &rawObject :
26948 m_graphic->dwgAdvancedMetadata().rawDxfObjects()) {
26949 if ((rawObject.handle != 0 &&
26950 m_dxfSuppressedObjectHandles.count(rawObject.handle) != 0) ||
26951 !replaysInDxfExport(rawObject.m_version))
26952 continue;
26953 DRW_RawDxfObject object = rawObject;
26954 noteDxfWrite(m_dxfW->writeRawDxfObject(&object));
26955 }
26956
26957 // F4: typed DXF emit for the routed data-only OBJECTS the DWG reader stores
26958 // ONLY in typed metadata (NOT the raw net) — SUN/SCALE/DICTIONARYVAR/
26959 // RASTERVARIABLES. On DWG->DXF these are absent from the raw net, so the
26960 // loop above never emits them; emit them natively here so the type is
26961 // preserved. DEDUP vs the raw net: a DXF-READ object lives in BOTH the
26962 // typed metadata AND the raw net (processSun calls addSun AND
26963 // addRawDxfObject), so it was already re-emitted above — skip the typed
26964 // emit when its code-5 handle is already in the raw net to avoid a
26965 // double-emit. (Build the raw-handle set once.) Only ReplayAllowed,
26966 // nonzero-handle records are emitted; handles are reserved and a CLASS
26967 // record is registered in fileExport's pre-write pass.
26968 const auto &metadata = m_graphic->dwgAdvancedMetadata();
26969 std::set<std::uint32_t> rawHandles;
26970 for (const DRW_RawDxfObject &o : metadata.rawDxfObjects())
26971 if (o.handle != 0 && replaysInDxfExport(o.m_version))
26972 rawHandles.insert(o.handle);
26973 auto emitTyped = [&](std::uint32_t handle,
26974 LC_DwgAdvancedMetadata::ReplayState state) {
26975 return handle != 0 &&
26976 state == LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
26977 rawHandles.count(handle) == 0;
26978 };
26979 // F4f-3: owner fallback. A data-only OBJECT's 330 parent must resolve to
26980 // an object that actually exists in the output, else ezdxf deletes the
26981 // object (INVALID_OWNER_HANDLE). A parent is reachable iff it is 0 (->
26982 // root C), a named parent dict we emit (F4f-2), or a raw-net object
26983 // re-emitted verbatim. Otherwise (e.g. a SUN owned by a per-viewport ACAD
26984 // dict LibreCAD never materializes) zero it so writeObjectOwner emits C.
26985 auto resolveOwner = [&](std::uint32_t parent) -> int {
26986 if (parent == 0 || m_dxfEmittedNamedDictHandles.count(parent) != 0 ||
26987 rawHandles.count(parent) != 0)
26988 return static_cast<int>(m_dxfW->remapHandle(parent));
26989 return 0; // dangling -> owner C
26990 };
26991 for (const auto &record : metadata.suns()) {
26992 if (!emitTyped(record.handle, record.replayState))
26993 continue;
26994 DRW_Sun sun = sunFromMetadata(record);
26995 sun.handle = m_dxfW->remapHandle(sun.handle);
26996 sun.parentHandle = resolveOwner(record.parentHandle);
26997 noteDxfWrite(m_dxfW->writeSun(&sun));
26998 }
26999 for (const auto &record : metadata.scales()) {
27000 if (!emitTyped(record.handle, record.replayState))
27001 continue;
27002 DRW_Scale scale = scaleFromMetadata(record);
27003 scale.handle = m_dxfW->remapHandle(scale.handle);
27004 scale.parentHandle = resolveOwner(record.parentHandle);
27005 noteDxfWrite(m_dxfW->writeScale(&scale));
27006 }
27007 for (const auto &record : metadata.dictionaryVars()) {
27008 if (!emitTyped(record.handle, record.replayState))
27009 continue;
27010 DRW_DictionaryVar dv = dictionaryVarFromMetadata(record);
27011 dv.handle = m_dxfW->remapHandle(dv.handle);
27012 dv.parentHandle = resolveOwner(record.parentHandle);
27013 noteDxfWrite(m_dxfW->writeDictionaryVar(&dv));
27014 }
27015 for (const auto &record : metadata.rasterVariables()) {
27016 if (!emitTyped(record.handle, record.replayState))
27017 continue;
27018 DRW_RasterVariables rv = rasterVariablesFromMetadata(record);
27019 rv.handle = m_dxfW->remapHandle(rv.handle);
27020 rv.parentHandle = resolveOwner(record.parentHandle);
27021 noteDxfWrite(m_dxfW->writeRasterVariables(&rv));
27022 }
27023 // MLEADERSTYLE is a custom object with a CLASS record. This is
27024 // load-bearing for later MLEADER DXF completion, since MLEADER entities
27025 // reference these styles by handle.
27026 for (const auto &record : metadata.mleaderStyles()) {
27027 if (!emitTyped(record.handle, record.replayState))
27028 continue;
27029 DRW_MLeaderStyle style = mleaderStyleFromMetadata(record);
27030 style.handle = m_dxfW->remapHandle(style.handle);
27031 style.parentHandle = resolveOwner(record.parentHandle);
27032 noteDxfWrite(m_dxfW->writeMLeaderStyle(&style));
27033 }
27034 // SLICE 1: MLINESTYLE (FIXED built-in, no CLASS). DWG read populates only
27035 // typed metadata; DXF read keeps it in the raw net (so emitTyped dedups by
27036 // handle). The STANDARD mline style is present in most drawings.
27037 for (const auto &record : metadata.mlineStyles()) {
27038 if (!emitTyped(record.handle, record.replayState))
27039 continue;
27040 DRW_MLineStyle style = mlineStyleFromMetadata(record);
27041 style.handle = m_dxfW->remapHandle(style.handle);
27042 style.parentHandle = resolveOwner(record.parentHandle);
27043 noteDxfWrite(m_dxfW->writeMLineStyle(&style));
27044 }
27045 // LAYOUT is a fixed built-in object. It is typed metadata only on DXF
27046 // read too, so emit it here and let the prepass materialize ACAD_LAYOUT
27047 // when the source had a reachable layout dictionary.
27048 for (const auto &record : metadata.layouts()) {
27049 if (!emitTyped(record.handle, record.replayState))
27050 continue;
27051 DRW_Layout layout = layoutFromMetadata(record);
27052 layout.handle = m_dxfW->remapHandle(layout.handle);
27053 layout.parentHandle = resolveOwner(record.parentHandle);
27054 noteDxfWrite(m_dxfW->writeLayout(&layout));
27055 }
27056 // GEODATA is a custom object with a CLASS record. DWG read captures all
27057 // typed fields, including mesh points/faces, so emit it on DWG->DXF.
27058 for (const auto &record : metadata.geoData()) {
27059 if (!emitTyped(record.handle, record.replayState))
27060 continue;
27061 DRW_GeoData gd = geoDataFromMetadata(record);
27062 gd.handle = m_dxfW->remapHandle(gd.handle);
27063 gd.parentHandle = resolveOwner(record.parentHandle);
27064 noteDxfWrite(m_dxfW->writeGeoData(&gd));
27065 }
27066 // SPATIAL_FILTER is a custom object with a CLASS record. The parent is
27067 // usually an extension dictionary; resolve it through the materialized
27068 // dictionary set so exported DXF owners are reachable.
27069 for (const auto &record : metadata.spatialFilters()) {
27070 if (!emitTyped(record.handle, record.replayState))
27071 continue;
27072 DRW_SpatialFilter sf = spatialFilterFromMetadata(record);
27073 sf.handle = m_dxfW->remapHandle(sf.handle);
27074 sf.parentHandle = resolveOwner(record.parentHandle);
27075 noteDxfWrite(m_dxfW->writeSpatialFilter(&sf));
27076 }
27077 // SORTENTSTABLE is a custom object with entity draw-order references.
27078 // dxfRW remaps source entity handles to the minted DXF entity handles.
27079 for (const auto &record : metadata.sortEntsTables()) {
27080 if (!emitTyped(record.handle, record.replayState))
27081 continue;
27082 DRW_SortEntsTable se = sortEntsTableFromMetadata(record);
27083 se.handle = m_dxfW->remapHandle(se.handle);
27084 se.parentHandle = resolveOwner(record.parentHandle);
27085 // The block record whose entities it orders, under its written handle.
27086 se.m_blockOwnerHandle = m_dxfW->remapHandle(se.m_blockOwnerHandle);
27087 noteDxfWrite(m_dxfW->writeSortEntsTable(&se));
27088 }
27089 // FIELD/FIELDLIST are custom objects. Emit FIELD first so FIELDLIST 330
27090 // references point at an object that already exists in OBJECTS.
27091 for (const auto &record : metadata.fields()) {
27092 if (!emitTyped(record.handle, record.replayState))
27093 continue;
27094 DRW_Field field = fieldFromMetadata(record);
27095 field.handle = m_dxfW->remapHandle(field.handle);
27096 field.parentHandle = resolveOwner(record.parentHandle);
27097 noteDxfWrite(m_dxfW->writeField(&field));
27098 }
27099 for (const auto &record : metadata.fieldLists()) {
27100 if (!emitTyped(record.handle, record.replayState))
27101 continue;
27102 DRW_FieldList fieldList = fieldListFromMetadata(record);
27103 fieldList.handle = m_dxfW->remapHandle(fieldList.handle);
27104 fieldList.parentHandle = resolveOwner(record.parentHandle);
27105 noteDxfWrite(m_dxfW->writeFieldList(&fieldList));
27106 }
27107 // SLICE 2: WIPEOUTVARIABLES (custom, CLASS registered). Same dedup-vs-raw
27108 //-net + owner re-attach as the other data-only OBJECTS.
27109 for (const auto &record : metadata.wipeoutVariables()) {
27110 if (!emitTyped(record.handle, record.replayState))
27111 continue;
27112 DRW_WipeoutVariables wv = wipeoutVariablesFromMetadata(record);
27113 wv.handle = m_dxfW->remapHandle(wv.handle);
27114 wv.parentHandle = resolveOwner(record.parentHandle);
27115 noteDxfWrite(m_dxfW->writeWipeoutVariables(&wv));
27116 }
27117 for (const auto &record : metadata.pointCloudDefinitions()) {
27118 if (!emitTyped(record.handle, record.replayState))
27119 continue;
27120 DRW_PointCloudDef definition = pointCloudDefinitionFromMetadata(record);
27121 definition.handle = m_dxfW->remapHandle(definition.handle);
27122 definition.parentHandle = resolveOwner(record.parentHandle);
27123 noteDxfWrite(m_dxfW->writePointCloudDef(&definition));
27124 }
27125 for (const auto &record : metadata.navisworksModelDefinitions()) {
27126 if (!emitTyped(record.handle, record.replayState))
27127 continue;
27128 DRW_NavisworksModelDef definition =
27129 navisworksModelDefinitionFromMetadata(record);
27130 definition.handle = m_dxfW->remapHandle(definition.handle);
27131 definition.parentHandle = resolveOwner(record.parentHandle);
27132 noteDxfWrite(m_dxfW->writeNavisworksModelDef(&definition));
27133 }
27134 for (const auto &record : metadata.pointCloudColorMaps()) {
27135 if (!emitTyped(record.handle, record.replayState))
27136 continue;
27137 DRW_PointCloudColorMap colorMap = pointCloudColorMapFromMetadata(record);
27138 colorMap.handle = m_dxfW->remapHandle(colorMap.handle);
27139 colorMap.parentHandle = resolveOwner(record.parentHandle);
27140 noteDxfWrite(m_dxfW->writePointCloudColorMap(&colorMap));
27141 }
27142 for (const auto &record : metadata.backgrounds()) {
27143 if (!emitTyped(record.handle, record.replayState))
27144 continue;
27145 DRW_Background background = backgroundFromMetadata(record);
27146 background.handle = m_dxfW->remapHandle(background.handle);
27147 background.parentHandle = resolveOwner(record.parentHandle);
27148 noteDxfWrite(m_dxfW->writeBackground(&background,
27149 backgroundRecordName(record.kind)));
27150 }
27151 for (const auto &record : metadata.materials()) {
27152 if (!emitTyped(record.handle, record.replayState))
27153 continue;
27154 DRW_Material material = materialFromMetadata(record);
27155 material.handle = m_dxfW->remapHandle(material.handle);
27156 material.parentHandle = resolveOwner(record.parentHandle);
27157 noteDxfWrite(m_dxfW->writeMaterial(&material));
27158 }
27159 for (const auto &record : metadata.renderSettings()) {
27160 if (!emitTyped(record.handle, record.replayState))
27161 continue;
27162 DRW_RenderSettings settings = renderSettingsFromMetadata(record);
27163 settings.handle = m_dxfW->remapHandle(settings.handle);
27164 settings.parentHandle = resolveOwner(record.parentHandle);
27165 noteDxfWrite(m_dxfW->writeRenderSettings(
27166 &settings, renderSettingsRecordName(record.kind)));
27167 }
27168 for (const auto &record : metadata.sunStudies()) {
27169 if (!emitTyped(record.handle, record.replayState))
27170 continue;
27171 DRW_SunStudy study = sunStudyFromMetadata(record);
27172 study.handle = m_dxfW->remapHandle(study.handle);
27173 study.parentHandle = resolveOwner(record.parentHandle);
27174 noteDxfWrite(m_dxfW->writeSunStudy(&study));
27175 }
27176 for (const auto &record : metadata.motionPaths()) {
27177 if (!emitTyped(record.handle, record.replayState))
27178 continue;
27179 DRW_MotionPath path = motionPathFromMetadata(record);
27180 path.handle = m_dxfW->remapHandle(path.handle);
27181 path.parentHandle = resolveOwner(record.parentHandle);
27182 noteDxfWrite(m_dxfW->writeMotionPath(&path));
27183 }
27184 for (const auto &record : metadata.curvePaths()) {
27185 if (!emitTyped(record.handle, record.replayState))
27186 continue;
27187 DRW_CurvePath path = curvePathFromMetadata(record);
27188 path.handle = m_dxfW->remapHandle(path.handle);
27189 path.parentHandle = resolveOwner(record.parentHandle);
27190 noteDxfWrite(m_dxfW->writeCurvePath(&path));
27191 }
27192 for (const auto &record : metadata.pointPaths()) {
27193 if (!emitTyped(record.handle, record.replayState))
27194 continue;
27195 DRW_PointPath path = pointPathFromMetadata(record);
27196 path.handle = m_dxfW->remapHandle(path.handle);
27197 path.parentHandle = resolveOwner(record.parentHandle);
27198 noteDxfWrite(m_dxfW->writePointPath(&path));
27199 }
27200 for (const auto &record : metadata.objectPtrs()) {
27201 if (!emitTyped(record.handle, record.replayState))
27202 continue;
27203 DRW_ObjectPtr object = objectPtrFromMetadata(record);
27204 object.handle = m_dxfW->remapHandle(object.handle);
27205 object.parentHandle = resolveOwner(record.parentHandle);
27206 noteDxfWrite(m_dxfW->writeObjectPtr(&object));
27207 }
27208 for (const auto &record : metadata.partialViewingIndexes()) {
27209 if (!emitTyped(record.handle, record.replayState))
27210 continue;
27211 DRW_PartialViewingIndex index = partialViewingIndexFromMetadata(record);
27212 index.handle = m_dxfW->remapHandle(index.handle);
27213 index.parentHandle = resolveOwner(record.parentHandle);
27214 noteDxfWrite(m_dxfW->writePartialViewingIndex(&index));
27215 }
27216 for (const auto &record : metadata.dbColors()) {
27217 if (!emitTyped(record.handle, record.replayState))
27218 continue;
27219 DRW_DbColor color = dbColorFromMetadata(record);
27220 color.handle = m_dxfW->remapHandle(color.handle);
27221 color.parentHandle = resolveOwner(record.parentHandle);
27222 noteDxfWrite(m_dxfW->writeDbColor(&color));
27223 }
27224 for (const auto &record : metadata.dimensionAssociations()) {
27225 DRW_DimensionAssociation association =
27226 dimensionAssociationFromMetadata(record);
27227 association.handle = m_dxfW->remapHandle(association.handle);
27228 if (!canWriteNativeDimensionAssociation(association) ||
27229 !emitTyped(record.handle, record.replayState))
27230 continue;
27231 association.parentHandle = resolveOwner(record.parentHandle);
27232 noteDxfWrite(m_dxfW->writeDimensionAssociation(&association));
27233 }
27234 for (const auto &record : metadata.evaluationGraphs()) {
27235 DRW_EvaluationGraph graph = evaluationGraphFromMetadata(record);
27236 graph.handle = m_dxfW->remapHandle(graph.handle);
27237 if (!canWriteNativeEvaluationGraph(graph) ||
27238 !emitTyped(record.handle, record.replayState))
27239 continue;
27240 graph.parentHandle = resolveOwner(record.parentHandle);
27241 noteDxfWrite(m_dxfW->writeEvaluationGraph(&graph));
27242 }
27243 for (const auto &record : metadata.sections()) {
27244 if (!emitTyped(record.handle, record.replayState))
27245 continue;
27246 DRW_Section section = sectionFromMetadata(record);
27247 section.handle = m_dxfW->remapHandle(section.handle);
27248 section.parentHandle = resolveOwner(record.parentHandle);
27249 if (section.m_kind == DRW_Section::Manager) {
27250 for (auto &handle : section.m_sectionHandles) {
27251 handle = m_dxfW->remapHandle(m_dxfW->remapEntityHandle(handle));
27252 }
27253 section.m_sectionCount =
27254 static_cast<std::int32_t>(section.m_sectionHandles.size());
27255 } else {
27256 for (auto &type : section.m_types) {
27257 for (auto &handle : type.m_sourceHandles) {
27258 handle = m_dxfW->remapHandle(m_dxfW->remapEntityHandle(handle));
27259 }
27260 }
27261 }
27262 noteDxfWrite(
27263 m_dxfW->writeSection(&section, sectionRecordName(record.kind)));
27264 }
27265 }
27266}
27267
27268void RS_FilterDXFRW::collectDwgAppIds() {
27269#ifdef DWGSUPPORT1
27270 if (m_dwgW == nullptr || m_graphic == nullptr)
27271 return;
27272
27273 std::set<std::string> emittedNames;
27274 const auto collectExtData = [this, &emittedNames](const auto &extData,
27275 bool ofText = false) {
27276 for (const auto &value : extData) {
27277 if (value == nullptr || value->code() != 1001 ||
27278 value->type() != DRW_Variant::STRING || value->content.s == nullptr ||
27279 value->content.s->empty()) {
27280 continue;
27281 }
27282 const std::string name = *value->content.s;
27283 // What a text keeps of its block attribute is never written with it
27284 // (removeInsertAttribSidecar()): no data of the file is under these
27285 // names, and a drawing opened from the file would hand them on.
27286 if (ofText && isInsertAttribSidecarMarker(name))
27287 continue;
27288 if (!emittedNames.insert(normalizeDwgTableName(name)).second)
27289 continue;
27290 DRW_AppId appId;
27291 appId.name = name;
27292 if (!m_dwgW->addAppId(&appId))
27293 m_writeFailed = true;
27294 }
27295 };
27296 const auto collectFromEntity = [&collectExtData](const RS_Entity *entity) {
27297 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
27298 !entity->hasDrwExtData()) {
27299 return;
27300 }
27301 collectExtData(entity->getDrwExtData(),
27302 entity->rtti() == RS2::EntityText ||
27303 entity->rtti() == RS2::EntityMText);
27304 };
27305 const auto collectFromContainer = [&collectFromEntity](
27306 const RS_EntityContainer *container) {
27307 if (container == nullptr)
27308 return;
27309 for (const RS_Entity *entity :
27310 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
27311 collectFromEntity(entity);
27312 }
27313 };
27314
27315 collectFromContainer(m_graphic);
27316 for (unsigned index = 0; index < m_graphic->countBlocks(); ++index)
27317 collectFromContainer(m_graphic->blockAt(index));
27318
27319 const auto &metadata = m_graphic->dwgAdvancedMetadata();
27320 for (const auto &record : metadata.visualStyles()) {
27321 if (record.replayState ==
27322 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27323 collectExtData(record.extData);
27324 }
27325 }
27326 for (const auto &record : metadata.suns()) {
27327 if (record.replayState ==
27328 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27329 collectExtData(record.extData);
27330 }
27331 }
27332 for (const auto &record : metadata.backgrounds()) {
27333 if (record.replayState ==
27334 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27335 collectExtData(record.extData);
27336 }
27337 }
27338 for (const auto &record : metadata.renderSettings()) {
27339 if (record.replayState ==
27340 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27341 collectExtData(record.extData);
27342 }
27343 }
27344 for (const auto &record : metadata.sunStudies()) {
27345 if (record.replayState ==
27346 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27347 collectExtData(record.extData);
27348 }
27349 }
27350 for (const auto &record : metadata.motionPaths()) {
27351 if (record.replayState ==
27352 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27353 collectExtData(record.extData);
27354 }
27355 }
27356 for (const auto &record : metadata.curvePaths()) {
27357 if (record.replayState ==
27358 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27359 collectExtData(record.extData);
27360 }
27361 }
27362 for (const auto &record : metadata.pointPaths()) {
27363 if (record.replayState ==
27364 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27365 collectExtData(record.extData);
27366 }
27367 }
27368 for (const auto &record : metadata.objectPtrs()) {
27369 if (record.replayState ==
27370 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27371 collectExtData(record.extData);
27372 }
27373 }
27374 for (const auto &record : metadata.partialViewingIndexes()) {
27375 if (record.replayState ==
27376 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27377 collectExtData(record.extData);
27378 }
27379 }
27380 for (const auto &record : metadata.cameras()) {
27381 if (record.replayState ==
27382 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27383 collectExtData(record.extData);
27384 }
27385 }
27386 for (const auto &record : metadata.geoPositionMarkers()) {
27387 if (record.replayState ==
27388 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27389 collectExtData(record.extData);
27390 }
27391 }
27392 for (const auto &record : metadata.sectionObjects()) {
27393 if (record.replayState ==
27394 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27395 collectExtData(record.extData);
27396 }
27397 }
27398 for (const auto &record : metadata.sections()) {
27399 if (record.replayState ==
27400 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27401 collectExtData(record.extData);
27402 }
27403 }
27404 for (const auto &record : metadata.modelerGeometry()) {
27405 if (record.replayState ==
27406 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27407 collectExtData(record.extData);
27408 }
27409 }
27410 for (const auto &record : metadata.fields()) {
27411 if (record.replayState ==
27412 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27413 collectExtData(record.extData);
27414 }
27415 }
27416 for (const auto &record : metadata.fieldLists()) {
27417 if (record.replayState ==
27418 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27419 collectExtData(record.extData);
27420 }
27421 }
27422 for (const auto &record : metadata.dictionaries()) {
27423 if (record.replayState ==
27424 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27425 collectExtData(record.extData);
27426 }
27427 }
27428 for (const auto &record : metadata.xrecords()) {
27429 if (record.replayState ==
27430 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27431 collectExtData(record.extData);
27432 }
27433 }
27434 for (const auto &record : metadata.groups()) {
27435 if (record.replayState ==
27436 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27437 collectExtData(record.extData);
27438 }
27439 }
27440 for (const auto &record : metadata.mlineStyles()) {
27441 if (record.replayState ==
27442 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27443 collectExtData(record.extData);
27444 }
27445 }
27446 for (const auto &record : metadata.layouts()) {
27447 if (record.replayState ==
27448 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27449 collectExtData(record.extData);
27450 }
27451 }
27452 for (const auto &record : metadata.mleaderStyles()) {
27453 if (record.replayState ==
27454 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27455 collectExtData(record.extData);
27456 }
27457 }
27458 for (const auto &record : metadata.materials()) {
27459 if (record.replayState ==
27460 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27461 collectExtData(record.extData);
27462 }
27463 }
27464 for (const auto &record : metadata.lightLists()) {
27465 if (record.replayState ==
27466 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27467 collectExtData(record.extData);
27468 }
27469 }
27470 for (const auto &record : metadata.tvDeviceProperties()) {
27471 if (record.replayState ==
27472 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27473 collectExtData(record.extData);
27474 }
27475 }
27476 for (const auto &record : metadata.blockRepresentationData()) {
27477 if (record.replayState ==
27478 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27479 collectExtData(record.extData);
27480 }
27481 }
27482 for (const auto &record : metadata.placeholders()) {
27483 if (record.replayState ==
27484 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27485 collectExtData(record.extData);
27486 }
27487 }
27488 for (const auto &project : metadata.vbaProjects())
27489 collectExtData(project.extData);
27490 for (const auto &settings : metadata.plotSettings())
27491 collectExtData(settings.extData);
27492 for (const auto &record : metadata.vxControls()) {
27493 if (record.replayState ==
27494 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27495 collectExtData(record.extData);
27496 }
27497 }
27498 for (const auto &record : metadata.vxTableRecords()) {
27499 if (record.replayState ==
27500 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27501 collectExtData(record.extData);
27502 }
27503 }
27504 for (const auto &record : metadata.rasterVariables()) {
27505 if (record.replayState ==
27506 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27507 collectExtData(record.extData);
27508 }
27509 }
27510 for (const auto &record : metadata.wipeoutVariables()) {
27511 if (record.replayState ==
27512 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27513 collectExtData(record.extData);
27514 }
27515 }
27516 for (const auto &record : metadata.dbColors()) {
27517 if (record.replayState ==
27518 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27519 collectExtData(record.extData);
27520 }
27521 }
27522 for (const auto &record : metadata.dimensionAssociations()) {
27523 if (record.replayState ==
27524 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27525 collectExtData(record.extData);
27526 }
27527 }
27528 for (const auto &record : metadata.evaluationGraphs()) {
27529 if (record.replayState ==
27530 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27531 collectExtData(record.extData);
27532 }
27533 }
27534 for (const auto &record : metadata.scales()) {
27535 if (record.replayState ==
27536 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27537 collectExtData(record.extData);
27538 }
27539 }
27540 for (const auto &record : metadata.idBuffers()) {
27541 if (record.replayState ==
27542 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27543 collectExtData(record.extData);
27544 }
27545 }
27546 for (const auto &record : metadata.spatialIndexes()) {
27547 if (record.replayState ==
27548 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27549 collectExtData(record.extData);
27550 }
27551 }
27552 for (const auto &record : metadata.layerIndexes()) {
27553 if (record.replayState ==
27554 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27555 collectExtData(record.extData);
27556 }
27557 }
27558 for (const auto &record : metadata.dictionaryVars()) {
27559 if (record.replayState ==
27560 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27561 collectExtData(record.extData);
27562 }
27563 }
27564 for (const auto &record : metadata.dictionariesWithDefault()) {
27565 if (record.replayState ==
27566 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27567 collectExtData(record.extData);
27568 }
27569 }
27570 for (const auto &record : metadata.sortEntsTables()) {
27571 if (record.replayState ==
27572 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27573 collectExtData(record.extData);
27574 }
27575 }
27576 for (const auto &record : metadata.underlayDefinitions()) {
27577 if (record.replayState ==
27578 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27579 collectExtData(record.extData);
27580 }
27581 }
27582 for (const auto &record : metadata.imageDefinitions()) {
27583 if (record.replayState ==
27584 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27585 collectExtData(record.extData);
27586 }
27587 }
27588 for (const auto &record : metadata.imageDefinitionReactors()) {
27589 if (record.replayState ==
27590 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27591 collectExtData(record.extData);
27592 }
27593 }
27594 for (const auto &record : metadata.pointCloudDefinitions()) {
27595 if (record.replayState ==
27596 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27597 collectExtData(record.extData);
27598 }
27599 }
27600 for (const auto &record : metadata.navisworksModelDefinitions()) {
27601 if (record.replayState ==
27602 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27603 collectExtData(record.extData);
27604 }
27605 }
27606 for (const auto &record : metadata.pointCloudColorMaps()) {
27607 if (record.replayState ==
27608 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27609 collectExtData(record.extData);
27610 }
27611 }
27612 for (const auto &record : metadata.geoData()) {
27613 if (record.replayState ==
27614 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27615 collectExtData(record.extData);
27616 }
27617 }
27618 for (const auto &record : metadata.spatialFilters()) {
27619 if (record.replayState ==
27620 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
27621 collectExtData(record.extData);
27622 }
27623 }
27624 if (m_dwgW->getVersion() <= DRW::AC1021) {
27625 for (const auto &record : metadata.tables()) {
27626 if (record.replayState ==
27627 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
27628 record.nativeTableStyle) {
27629 collectExtData(record.nativeTableStyle->extData);
27630 }
27631 }
27632 }
27633#endif
27634}
27635
27636namespace {
27637// Calls visit() with the XDATA of each record of the lists that an export
27638// replays.
27639template <typename Visit, typename... Lists>
27640void forEachReplayedRecordExtData(Visit &&visit, const Lists &...lists) {
27641 const auto visitList = [&visit](const auto &list) {
27642 for (const auto &record : list) {
27643 if (record.replayState ==
27644 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
27645 visit(record.extData);
27646 }
27647 };
27648 (visitList(lists), ...);
27649}
27650} // namespace
27651
27652std::vector<std::string> RS_FilterDXFRW::dxfXDataApplications() {
27653 std::vector<std::string> names;
27654 std::set<std::string> seen;
27655 const auto addName = [&names, &seen](const std::string &name) {
27656 if (!name.empty() && seen.insert(normalizeDwgTableName(name)).second)
27657 names.push_back(name);
27658 };
27659 const auto addFrom = [&addName](const auto &extData, bool ofText = false) {
27660 for (const auto &value : extData) {
27661 const DRW_Variant *tag = nullptr;
27662 if constexpr (std::is_pointer_v<std::decay_t<decltype(value)>>)
27663 tag = value;
27664 else if constexpr (std::is_same_v<std::decay_t<decltype(value)>,
27665 DRW_Variant>)
27666 tag = &value;
27667 else
27668 tag = value.get();
27669 std::string name;
27670 if (readXDataMarker(tag, name) != XDataReadResult::Valid)
27671 continue;
27672 // What a text keeps of its block attribute is never written with it
27673 // (removeInsertAttribSidecar()).
27674 if (ofText && isInsertAttribSidecarMarker(name))
27675 continue;
27676 addName(name);
27677 }
27678 };
27679 const auto &metadata = m_graphic->dwgAdvancedMetadata();
27680
27681 // Table records keep the XDATA of the records they were read from; a
27682 // dimension style also has XDATA of its own (prepareDRWDimStyleExtData()).
27683 for (const auto &entry : metadata.lineTypeTableEntries())
27684 addFrom(entry.second.extData);
27685 const RS_LayerList *layers = m_graphic->getLayerList();
27686 for (unsigned i = 0; i < layers->count(); ++i) {
27687 if (const auto *source = metadata.findLayerTableEntryByName(
27688 layers->at(i)->getName().toUtf8().toStdString()))
27689 addFrom(source->extData);
27690 }
27691 for (const auto &entry : metadata.textStyleTableEntries())
27692 addFrom(entry.second.extData);
27693 for (const auto &entry : metadata.textStyles()) {
27694 if (entry.second.name.empty() && (entry.second.flags & 1) != 0)
27695 addFrom(entry.second.extData);
27696 }
27697 for (const auto &entry : metadata.vportTableEntries())
27698 addFrom(entry.second.extData);
27699 forEachReplayedRecordExtData(addFrom, metadata.views(),
27700 metadata.ucsRecords(), metadata.vports());
27701 for (const auto &entry : metadata.appIdTableEntries())
27702 addFrom(entry.second.extData);
27703 for (const LC_DimStyle *style :
27704 *m_graphic->getDimStyleList()->getStylesList()) {
27705 DRW_Dimstyle written;
27706 prepareDRWDimStyle(written, style);
27707 addFrom(written.extData);
27708 if (const auto *source =
27709 metadata.findDimStyleTableEntryByName(written.name))
27710 addFrom(source->extData);
27711 }
27712
27713 // The entities of the model space and of the blocks. What a dimension
27714 // draws, and in R12 a hatch, is written as an anonymous block
27715 // (prepareBlocks()).
27716 const bool hatchBlocks = m_version == 1009;
27717 const auto addFromEntities = [&addFrom, hatchBlocks](
27718 const auto &self,
27719 const RS_EntityContainer &container,
27720 bool nested) -> void {
27721 for (const RS_Entity *entity :
27722 lc::LC_ContainerTraverser{container, RS2::ResolveNone}.entities()) {
27723 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted))
27724 continue;
27725 if (entity->hasDrwExtData())
27726 addFrom(entity->getDrwExtData(), RS2::isTextEntity(entity->rtti()));
27727 if (RS2::isTextEntity(entity->rtti())) {
27728 if (const auto attribute = extractInsertAttribSidecar(entity))
27729 addFrom(attribute->mtext.xdata);
27730 }
27731 if (!nested && (RS2::isDimensionalEntity(entity->rtti()) ||
27732 (hatchBlocks && entity->rtti() == RS2::EntityHatch)))
27733 self(self, *static_cast<const RS_EntityContainer *>(entity), true);
27734 }
27735 };
27736 addFromEntities(addFromEntities, *m_graphic, false);
27737 for (unsigned i = 0; i < m_graphic->countBlocks(); ++i) {
27738 const RS_Block *block = m_graphic->blockAt(i);
27739 if (block != nullptr && !block->isDeleted())
27740 addFromEntities(addFromEntities, *block, false);
27741 }
27742
27743 // The objects, and the entities only the metadata holds.
27744 forEachReplayedRecordExtData(
27745 addFrom, metadata.visualStyles(), metadata.shapes(), metadata.suns(),
27746 metadata.backgrounds(), metadata.renderSettings(),
27747 metadata.sunStudies(), metadata.motionPaths(), metadata.curvePaths(),
27748 metadata.pointPaths(), metadata.objectPtrs(),
27749 metadata.partialViewingIndexes(), metadata.cameras(),
27750 metadata.geoPositionMarkers(), metadata.sectionObjects(),
27751 metadata.sections(), metadata.modelerGeometry(), metadata.fields(),
27752 metadata.fieldLists(), metadata.xrecords(),
27753 metadata.groups(), metadata.mlineStyles(), metadata.layouts(),
27754 metadata.mleaderStyles(), metadata.materials(), metadata.lightLists(),
27755 metadata.tvDeviceProperties(), metadata.blockRepresentationData(),
27756 metadata.placeholders(), metadata.vxControls(),
27757 metadata.vxTableRecords(), metadata.rasterVariables(),
27758 metadata.wipeoutVariables(), metadata.dbColors(),
27759 metadata.dimensionAssociations(), metadata.evaluationGraphs(),
27760 metadata.scales(), metadata.idBuffers(), metadata.spatialIndexes(),
27761 metadata.layerIndexes(), metadata.dictionaryVars(),
27762 metadata.sortEntsTables(),
27763 metadata.underlayDefinitions(), metadata.imageDefinitions(),
27764 metadata.imageDefinitionReactors(), metadata.pointCloudDefinitions(),
27765 metadata.navisworksModelDefinitions(), metadata.pointCloudColorMaps(),
27766 metadata.geoData(), metadata.spatialFilters());
27767 const auto addFromDictionaries = [this, &addFrom](const auto &records) {
27768 for (const auto &record : records) {
27769 if (record.replayState ==
27770 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed &&
27771 m_dxfSuppressedObjectHandles.count(record.handle) == 0)
27772 addFrom(record.extData);
27773 }
27774 };
27775 addFromDictionaries(metadata.dictionaries());
27776 addFromDictionaries(metadata.dictionariesWithDefault());
27777 for (const auto &record : metadata.vbaProjects())
27778 addFrom(record.extData);
27779 for (const auto &settings : metadata.plotSettings())
27780 addFrom(settings.extData);
27781 for (const auto &record : metadata.tables()) {
27782 if (record.nativeTableStyle)
27783 addFrom(record.nativeTableStyle->extData);
27784 }
27785 // Records read from a DXF go out as they came in, XDATA and all.
27786 for (const auto &record : metadata.rawDxfObjects()) {
27787 if (replaysInDxfExport(record.m_version) &&
27788 m_dxfSuppressedObjectHandles.count(record.handle) == 0)
27789 addFrom(record.groups);
27790 }
27791 for (const auto &record : metadata.rawDxfEntities()) {
27792 if (replaysInDxfExport(record.m_version))
27793 addFrom(record.groups);
27794 }
27795 for (const auto &section : metadata.rawDxfSections()) {
27796 if (replaysInDxfExport(section.m_version))
27797 addFrom(section.m_groups);
27798 }
27799 return names;
27800}
27801
27802void RS_FilterDXFRW::writeAppId() {
27803 const auto &metadata = m_graphic->dwgAdvancedMetadata();
27804 std::set<std::string> emittedNames;
27805 const auto emitAppId = [this, &emittedNames](DRW_AppId appId) {
27806 const std::string normalizedName = normalizeDwgTableName(appId.name);
27807 if (!emittedNames.insert(normalizedName).second)
27808 return;
27809 if (m_dwgW != nullptr && hasDxfTableAppDataUnsupportedByDwg(appId)) {
27810 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
27811 "RS_FilterDXFRW: cannot convert DXF APPID "
27812 "application data to DWG");
27813 m_writeFailed = true;
27814 return;
27815 }
27816 if (m_dwgW != nullptr) {
27817 const std::uint32_t sourceHandle = appId.handle;
27818 if (!m_dwgW->addAppId(&appId) ||
27819 !stageDwgTableRecordIdentity(DwgWriteSourceKind::TableRecord,
27820 sourceHandle, appId.handle))
27821 m_writeFailed = true;
27822 } else {
27823 noteDxfWrite(m_dxfW->writeAppId(&appId));
27824 }
27825 };
27826 const auto emitRequired = [&metadata, &emitAppId](const std::string &name) {
27827 if (const auto *source = metadata.findAppIdTableEntryByName(name)) {
27828 emitAppId(*source);
27829 return;
27830 }
27831 DRW_AppId appId;
27832 appId.name = name;
27833 emitAppId(std::move(appId));
27834 };
27835
27836 if (const auto *acad = metadata.findAppIdTableEntryByName("ACAD")) {
27837 if (!acad->appData.empty() || !acad->reactorHandles.empty() ||
27838 acad->xDictHandle != 0 || !acad->extData.empty()) {
27839 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
27840 "RS_FilterDXFRW: cannot preserve application "
27841 "data on the codec-owned ACAD APPID");
27842 m_writeFailed = true;
27843 }
27844 }
27845
27846 emitRequired("LibreCad");
27847 emitRequired("ACAD_DSTYLE_DIMTALN");
27848 emitRequired("ACAD_DSTYLE_DIMJAG_POSITION");
27849 emitRequired("ACAD_DSTYLE_DIMJAG");
27850 for (const auto &entry : metadata.appIdTableEntries()) {
27851 if (normalizeDwgTableName(entry.first) != "ACAD")
27852 emitAppId(entry.second);
27853 }
27854
27855 if (m_dxfW == nullptr) {
27856 for (const auto &record : metadata.shapes()) {
27857 for (const auto &tag : record.extData) {
27858 std::string name;
27859 if (readXDataMarker(tag.get(), name) == XDataReadResult::Valid &&
27860 !name.empty() && normalizeDwgTableName(name) != "ACAD")
27861 emitRequired(name);
27862 }
27863 }
27864 return;
27865 }
27866
27867 // TABLES precedes BLOCKS/ENTITIES/OBJECTS. Reuse imported APPID records
27868 // so flags, application data and source-handle remapping are preserved.
27869 emitRequired("ACAD"); // The codec owns this record and maps it to handle 12.
27870 for (const std::string &name : dxfXDataApplications())
27871 emitRequired(name);
27872}
27873
27874bool RS_FilterDXFRW::finalizeDxfWrite() {
27875 // A failure latched in any DXF write callback reaches dxfRW here, before
27876 // it replaces the destination.
27877 return !m_writeFailed;
27878}
27879
27880bool RS_FilterDXFRW::finalizeDwgWrite() {
27881#ifdef DWGSUPPORT1
27882 if (m_dwgW != nullptr) {
27883 // A completed deferred table phase cannot rescue a failed content
27884 // phase. Keep the filter ledger unpublished for the whole failed
27885 // output transaction.
27886 if (m_writeFailed)
27887 return false;
27888 if (!validateDwgDataStorageReplayReceipts(m_dwgW->getVersion())) {
27889 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
27890 "RS_FilterDXFRW: DWG DataStorage section receipts are "
27891 "missing, stale, or bound to the wrong source record");
27892 m_writeFailed = true;
27893 return false;
27894 }
27895 const auto checkpoint = checkpointDwgWriteIdentity();
27896 const auto fail = [this, &checkpoint]() {
27897 restoreDwgWriteIdentity(checkpoint);
27898 m_writeFailed = true;
27899 return false;
27900 };
27901 const bool tableRecordsOk = commitDwgTableRecordIdentities();
27902 const bool tableControlsOk = commitDwgTableControlIdentities();
27903 const bool opaqueTableReferencesOk =
27904 validateDwgOpaqueTableReferenceReceipts(
27905 DwgOpaqueTableReceiptStage::Finalized,
27906 DwgOpaqueTableFailurePhase::Finalized);
27907 const bool ledgerOk = validateDwgWriteIdentityLedger(
27908 /*allowSelectedBlocks=*/true);
27909 const bool classFrameAdmissionsOk = validateDwgClassFrameAdmissions();
27910 const bool fixedEntityFrameReceiptsOk =
27911 validateDwgFixedEntityFrameReceipts();
27912 const bool entityWritePlansOk = validateDwgEntityWritePlans();
27913 const bool classInstanceCountsOk = m_dwgW->validateDwgClassInstanceCounts();
27914 if (!tableRecordsOk || !tableControlsOk || !opaqueTableReferencesOk ||
27915 !ledgerOk || !classFrameAdmissionsOk || !fixedEntityFrameReceiptsOk ||
27916 !entityWritePlansOk || !classInstanceCountsOk) {
27917 RS_DEBUGRS_Debug::instance()->print(
27918 RS_Debug::D_WARNING,
27919 "RS_FilterDXFRW: DWG final identity checks failed "
27920 "(tableRecords=%d tableControls=%d opaqueTables=%d ledger=%d "
27921 "classFrames=%d fixedFrames=%d entityPlans=%d classes=%d "
27922 "prior=%d)",
27923 tableRecordsOk ? 1 : 0, tableControlsOk ? 1 : 0,
27924 opaqueTableReferencesOk ? 1 : 0, ledgerOk ? 1 : 0,
27925 classFrameAdmissionsOk ? 1 : 0, fixedEntityFrameReceiptsOk ? 1 : 0,
27926 entityWritePlansOk ? 1 : 0, classInstanceCountsOk ? 1 : 0,
27927 m_writeFailed ? 1 : 0);
27928 return fail();
27929 }
27930 }
27931#endif
27932 return !m_writeFailed;
27933}
27934
27935bool RS_FilterDXFRW::finalizeDwgWriteStructure() {
27936#ifdef DWGSUPPORT1
27937 if (m_dwgW == nullptr)
27938 return true;
27939 if (m_writeFailed)
27940 return false;
27941
27942 DRW::DwgBlockWriteResult result;
27943 if (!m_dwgW->getEmittedDwgBlockWriteResult(result) ||
27944 result.blockControlHandle == 0) {
27945 m_writeFailed = true;
27946 return false;
27947 }
27948
27949 const auto checkpoint = checkpointDwgWriteIdentity();
27950 const auto fail = [this, &checkpoint]() {
27951 restoreDwgWriteIdentity(checkpoint);
27952 m_writeFailed = true;
27953 return false;
27954 };
27955
27956 const auto commitOrRegister = [this](DwgWriteSourceKind kind,
27957 std::uint32_t outputHandle) {
27958 if (outputHandle == 0)
27959 return false;
27960 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
27961 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
27962 const auto identityIt = m_dwgWriteIdentityByOutput.find(ownerIt->second);
27963 if (ownerIt->second.kind != kind ||
27964 identityIt == m_dwgWriteIdentityByOutput.end() ||
27965 !selectDwgWriteIdentity(kind, identityIt->second.sourceHandle,
27966 outputHandle) ||
27967 !commitDwgWriteIdentity(kind, outputHandle))
27968 return false;
27969 return true;
27970 }
27971 return reserveDwgWriteIdentity(kind, 0, outputHandle) &&
27972 selectDwgWriteIdentity(kind, 0, outputHandle) &&
27973 commitDwgWriteIdentity(kind, outputHandle);
27974 };
27975
27976 if (!commitOrRegister(DwgWriteSourceKind::TableControl,
27977 result.blockControlHandle)) {
27978 return fail();
27979 }
27980
27981 std::set<std::uint32_t> emittedBlockRecordHandles;
27982 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
27983 if (record.blockRecordHandle == 0 ||
27984 !emittedBlockRecordHandles.insert(record.blockRecordHandle).second) {
27985 return fail();
27986 }
27987 const DwgWriteSourceKind kind =
27988 record.blockRecordHandle == DRW::DwgModelSpaceBlockRecordHandle ||
27989 record.blockRecordHandle == DRW::DwgPaperSpaceBlockRecordHandle
27990 ? DwgWriteSourceKind::FixedStructural
27991 : DwgWriteSourceKind::Block;
27992 if (!commitOrRegister(kind, record.blockRecordHandle)) {
27993 return fail();
27994 }
27995 }
27996
27997 std::set<std::uint32_t> emittedBlockEntityHandles;
27998 for (const std::uint32_t handle : result.blockEntityHandles) {
27999 if (handle == 0 || !emittedBlockEntityHandles.insert(handle).second) {
28000 return fail();
28001 }
28002 const bool fixed = handle == DRW::DwgModelSpaceBlockEntityHandle ||
28003 handle == DRW::DwgModelSpaceEndBlockEntityHandle ||
28004 handle == DRW::DwgPaperSpaceBlockEntityHandle ||
28005 handle == DRW::DwgPaperSpaceEndBlockEntityHandle;
28006 if (!commitOrRegister(fixed ? DwgWriteSourceKind::FixedStructural
28007 : DwgWriteSourceKind::GeneratedChild,
28008 handle)) {
28009 return fail();
28010 }
28011 }
28012
28013 // BLOCK_RECORD lists contain every entity physically owned by the block,
28014 // including compound children such as ATTRIB and SEQEND. Those children
28015 // have no source-side RS_Entity identity, so publish them as generated
28016 // identities only after their parent frame has completed.
28017 const auto commitOwnedEntityOrRegister = [this](std::uint32_t outputHandle) {
28018 if (outputHandle == 0)
28019 return false;
28020 const auto ownerIt = m_dwgWriteIdentityOwnerByOutput.find(outputHandle);
28021 if (ownerIt != m_dwgWriteIdentityOwnerByOutput.end()) {
28022 if ((ownerIt->second.kind != DwgWriteSourceKind::Entity &&
28023 ownerIt->second.kind != DwgWriteSourceKind::GeneratedChild &&
28024 ownerIt->second.kind != DwgWriteSourceKind::RawCarrier) ||
28025 !isDwgWriteIdentityCommitted(ownerIt->second.kind, outputHandle)) {
28026 return false;
28027 }
28028 return true;
28029 }
28030 return reserveDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
28031 outputHandle) &&
28032 selectDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild, 0,
28033 outputHandle) &&
28034 commitDwgWriteIdentity(DwgWriteSourceKind::GeneratedChild,
28035 outputHandle);
28036 };
28037 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
28038 for (const std::uint32_t handle : record.entityHandles) {
28039 if (!commitOwnedEntityOrRegister(handle)) {
28040 return fail();
28041 }
28042 }
28043 for (const std::uint32_t handle : record.insertHandles) {
28044 if (!commitOwnedEntityOrRegister(handle)) {
28045 return fail();
28046 }
28047 }
28048 }
28049
28050 const std::array<std::uint32_t, 4> fixedBlockEntities = {
28051 DRW::DwgModelSpaceBlockEntityHandle,
28052 DRW::DwgModelSpaceEndBlockEntityHandle,
28053 DRW::DwgPaperSpaceBlockEntityHandle,
28054 DRW::DwgPaperSpaceEndBlockEntityHandle};
28055 for (const std::uint32_t handle : fixedBlockEntities) {
28056 if (emittedBlockEntityHandles.count(handle) == 0) {
28057 return fail();
28058 }
28059 }
28060
28061 std::set<std::uint32_t> ownedEntityHandles;
28062 std::set<std::uint32_t> referencedBlockEntities;
28063 const auto validateEdgeList =
28064 [this](const std::vector<std::uint32_t> &handles) {
28065 std::set<std::uint32_t> unique;
28066 for (const std::uint32_t handle : handles) {
28067 if (handle == 0 || !unique.insert(handle).second ||
28068 !isDwgWriteOutputCommitted(handle))
28069 return false;
28070 }
28071 return true;
28072 };
28073 for (const DRW::DwgBlockWriteRecord &record : result.blockRecords) {
28074 for (const std::uint32_t handle : record.entityHandles) {
28075 if (!ownedEntityHandles.insert(handle).second) {
28076 return fail();
28077 }
28078 }
28079 if (!isDwgWriteOutputCommitted(record.blockRecordHandle) ||
28080 record.blockHandle == 0 || record.endBlockHandle == 0 ||
28081 emittedBlockEntityHandles.count(record.blockHandle) == 0 ||
28082 emittedBlockEntityHandles.count(record.endBlockHandle) == 0 ||
28083 !isDwgWriteOutputCommitted(record.blockHandle) ||
28084 !isDwgWriteOutputCommitted(record.endBlockHandle) ||
28085 !validateEdgeList(record.entityHandles) ||
28086 !validateEdgeList(record.insertHandles)) {
28087 return fail();
28088 }
28089 referencedBlockEntities.insert(record.blockHandle);
28090 referencedBlockEntities.insert(record.endBlockHandle);
28091 }
28092 for (const std::uint32_t sourceHandle : m_dwgWriteEntityHandlesEmitted) {
28093 std::uint32_t outputHandle = 0;
28094 if (lookupDwgWriteIdentity(DwgWriteSourceKind::Entity, sourceHandle,
28095 &outputHandle) !=
28096 DwgWriteIdentityLookup::Committed ||
28097 ownedEntityHandles.count(outputHandle) == 0) {
28098 return fail();
28099 }
28100 }
28101 if (referencedBlockEntities != emittedBlockEntityHandles) {
28102 return fail();
28103 }
28104
28105 if (m_dwgPostObjectsTestHook)
28106 m_dwgPostObjectsTestHook();
28107 const bool fieldReceiptsValid = validateDwgFieldWriteReceipts();
28108 const bool ownerReceiptsValid = validateDwgWriteOwnerReceipts(&result);
28109 const bool referenceReceiptsValid = validateDwgWriteReferenceReceipts();
28110 const bool optionalDropReceiptsValid = validateDwgWriteOptionalDropReceipts();
28111 const bool identityLedgerValid = validateDwgWriteIdentityLedger();
28112 if (!fieldReceiptsValid || !ownerReceiptsValid || !referenceReceiptsValid ||
28113 !optionalDropReceiptsValid || !identityLedgerValid) {
28114 if (!referenceReceiptsValid) {
28115 for (const DwgWriteReferenceReceipt &receipt :
28116 m_dwgWriteReferenceReceipts) {
28117 std::uint32_t resolvedTarget = 0;
28118 const bool producerCommitted =
28119 isDwgWriteOutputCommitted(receipt.producerOutputHandle);
28120 const bool targetCommitted =
28121 receipt.targetSourceHandle == 0
28122 ? isDwgWriteIdentityCommitted(receipt.targetKind,
28123 receipt.targetOutputHandle)
28124 : lookupDwgWriteIdentity(
28125 receipt.targetKind, receipt.targetSourceHandle,
28126 &resolvedTarget) == DwgWriteIdentityLookup::Committed &&
28127 resolvedTarget == receipt.targetOutputHandle;
28128 if (!producerCommitted ||
28129 (!receipt.required && receipt.targetOutputHandle == 0) ||
28130 receipt.targetOutputHandle == 0 || !targetCommitted) {
28131 RS_DEBUGRS_Debug::instance()->print(
28132 RS_Debug::D_WARNING,
28133 "RS_FilterDXFRW: invalid DWG reference receipt "
28134 "producer=0x%X target=0x%X target-kind=%d "
28135 "resolved=0x%X producer-committed=%d "
28136 "target-committed=%d name=%s ordinal=%u",
28137 receipt.producerSourceHandle, receipt.targetSourceHandle,
28138 static_cast<int>(receipt.targetKind), resolvedTarget,
28139 producerCommitted ? 1 : 0, targetCommitted ? 1 : 0,
28140 receipt.referenceName.c_str(), receipt.referenceOrdinal);
28141 }
28142 }
28143 }
28144 if (!optionalDropReceiptsValid) {
28145 for (const DwgWriteOptionalDropReceipt &receipt :
28146 m_dwgWriteOptionalDropReceipts) {
28147 const bool valid =
28148 isDwgWriteOutputCommitted(receipt.producerOutputHandle) &&
28149 receipt.targetSourceHandle != 0 &&
28150 receipt.version != DRW::UNKNOWNV && !receipt.family.empty() &&
28151 !receipt.reason.empty() &&
28152 (receipt.targetStatus == DwgWriteReferenceStatus::Missing ||
28153 receipt.targetStatus == DwgWriteReferenceStatus::Ambiguous ||
28154 receipt.targetStatus == DwgWriteReferenceStatus::Suppressed ||
28155 receipt.targetStatus == DwgWriteReferenceStatus::Unpaired);
28156 if (!valid)
28157 RS_DEBUGRS_Debug::instance()->print(
28158 RS_Debug::D_WARNING,
28159 "RS_FilterDXFRW: invalid DWG optional-drop receipt "
28160 "producer=0x%X target=0x%X status=%d committed=%d "
28161 "name=%s ordinal=%u",
28162 receipt.producerSourceHandle, receipt.targetSourceHandle,
28163 static_cast<int>(receipt.targetStatus),
28164 isDwgWriteOutputCommitted(receipt.producerOutputHandle) ? 1 : 0,
28165 receipt.referenceName.c_str(), receipt.referenceOrdinal);
28166 }
28167 }
28168 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
28169 "RS_FilterDXFRW: DWG structure receipts invalid "
28170 "fields=%d owners=%d references=%d optional-drops=%d "
28171 "identities=%d",
28172 fieldReceiptsValid ? 1 : 0,
28173 ownerReceiptsValid ? 1 : 0, referenceReceiptsValid ? 1 : 0,
28174 optionalDropReceiptsValid ? 1 : 0,
28175 identityLedgerValid ? 1 : 0);
28176 return fail();
28177 }
28178#endif
28179 return !m_writeFailed;
28180}
28181
28182void RS_FilterDXFRW::prepareShapesForExport() {
28183 m_shapesToExport.clear();
28184 m_shapeFileResolvedPaths.clear();
28185 if (m_graphic != nullptr) {
28186 const auto &metadata = m_graphic->dwgAdvancedMetadata();
28187 std::map<QString, std::optional<LC_ShxShapeNames::Names>> shapeFiles;
28188 const auto namesFor = [&](const DRW_Textstyle &style,
28189 const std::string &sourceDirectory)
28190 -> const LC_ShxShapeNames::Names * {
28191 QString font = QString::fromStdString(style.font);
28192 font.replace(QLatin1Char('\\'), QLatin1Char('/'));
28193 if (font.isEmpty())
28194 return nullptr;
28195 if (QFileInfo(font).suffix().isEmpty())
28196 font += QStringLiteral(".shx")(QString(QtPrivate::qMakeStringPrivate(u"" ".shx")));
28197 QStringList candidates;
28198 if (QFileInfo(font).isAbsolute()) {
28199 candidates << font;
28200 } else {
28201 if (!sourceDirectory.empty())
28202 candidates << QDir(QString::fromStdString(sourceDirectory)).filePath(font);
28203 if (!m_graphic->getFilename().isEmpty())
28204 candidates << QFileInfo(m_graphic->getFilename()).dir().filePath(font);
28205 candidates << QDir(m_shapeExportDirectory).filePath(font);
28206 }
28207 // Drawings commonly retain an absolute path from another machine.
28208 // Search its basename alongside the drawing and destination too.
28209 const QString basename = QFileInfo(font).fileName();
28210 if (!sourceDirectory.empty())
28211 candidates << QDir(QString::fromStdString(sourceDirectory)).filePath(basename);
28212 if (!m_graphic->getFilename().isEmpty())
28213 candidates << QFileInfo(m_graphic->getFilename()).dir().filePath(basename);
28214 candidates << QDir(m_shapeExportDirectory).filePath(basename);
28215 for (const QString &path : candidates) {
28216 if (!QFileInfo(path).isFile())
28217 continue;
28218 auto [it, inserted] = shapeFiles.emplace(path, std::nullopt);
28219 if (inserted)
28220 it->second = LC_ShxShapeNames::read(path);
28221 if (it->second)
28222 m_shapeFileResolvedPaths[style.handle] = QFileInfo(path).canonicalFilePath();
28223 return it->second ? &*it->second : nullptr;
28224 }
28225 return nullptr;
28226 };
28227 for (const auto &rec : metadata.shapes()) {
28228 if (rec.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed) {
28229 ++m_shapeLeftOut["SHAPE with invalidated metadata"];
28230 continue;
28231 }
28232 DRW_Shape shape;
28233 shape.handle = rec.handle;
28234 shape.parentHandle = rec.parentHandle;
28235 shape.layer = rec.layer;
28236 shape.lineType = rec.lineType;
28237 shape.color = rec.color;
28238 shape.color24 = rec.color24;
28239 shape.lWeight = rec.lineWeight;
28240 shape.space = rec.space;
28241 shape.visible = rec.visible;
28242 shape.ltypeScale = rec.lineTypeScale;
28243 shape.extData = rec.extData;
28244 shape.m_shapeIndex = rec.shapeIndex;
28245 shape.m_insertionPoint = rec.insertionPoint;
28246 shape.m_extrusion = rec.extrusion;
28247 shape.m_scale = rec.scale;
28248 shape.m_rotation = rec.rotation;
28249 shape.m_oblique = rec.oblique;
28250 shape.m_widthFactor = rec.widthFactor;
28251 shape.m_thickness = rec.thickness;
28252 const auto finite = [](const DRW_Coord &p) {
28253 return std::isfinite(p.x) && std::isfinite(p.y) && std::isfinite(p.z);
28254 };
28255 if (!finite(shape.m_insertionPoint) || !finite(shape.m_extrusion) ||
28256 !std::isfinite(shape.m_scale) || !std::isfinite(shape.m_rotation) ||
28257 !std::isfinite(shape.m_widthFactor) || !std::isfinite(shape.m_oblique) ||
28258 !std::isfinite(shape.m_thickness)) {
28259 m_writeFailed = true;
28260 continue;
28261 }
28262 std::string ownerBlockName = rec.ownerBlockName;
28263 bool blockFound = ownerBlockName.empty();
28264 const auto space = metadata.spaceBlockRecords().find(shape.parentHandle);
28265 if (space != metadata.spaceBlockRecords().end()) {
28266 const std::string name = normalizeDwgTableName(space->second);
28267 if (name == "*MODEL_SPACE")
28268 shape.parentHandle = DRW::DwgModelSpaceBlockRecordHandle;
28269 else if (name == "*PAPER_SPACE")
28270 shape.parentHandle = DRW::DwgPaperSpaceBlockRecordHandle;
28271 }
28272 // A source space handle may normalize to an unrelated block's source
28273 // handle. Match ordinary blocks only while still in source identity.
28274 for (unsigned i = 0; space == metadata.spaceBlockRecords().end() &&
28275 i < m_graphic->countBlocks(); ++i) {
28276 const RS_Block *block = m_graphic->blockAt(i);
28277 if (block == nullptr || block->isDeleted())
28278 continue;
28279 if ((!ownerBlockName.empty() && block->getName().toStdString() == ownerBlockName) ||
28280 (shape.parentHandle != 0 && block->sourceHandle() == shape.parentHandle)) {
28281 ownerBlockName = block->getName().toStdString();
28282 shape.parentHandle = block->sourceHandle();
28283 blockFound = true;
28284 break;
28285 }
28286 }
28287 if (!blockFound) {
28288 ++m_shapeLeftOut["SHAPE with an unresolved block owner"];
28289 continue;
28290 }
28291 if (ownerBlockName.empty()) {
28292 if (shape.parentHandle == 0)
28293 shape.parentHandle = shape.space == DRW::PaperSpace
28294 ? DRW::DwgPaperSpaceBlockRecordHandle
28295 : DRW::DwgModelSpaceBlockRecordHandle;
28296 else if (shape.parentHandle == 0x1F)
28297 shape.parentHandle = DRW::DwgModelSpaceBlockRecordHandle;
28298 else if (shape.parentHandle == 0x1E)
28299 shape.parentHandle = DRW::DwgPaperSpaceBlockRecordHandle;
28300 if (shape.parentHandle != DRW::DwgModelSpaceBlockRecordHandle &&
28301 shape.parentHandle != DRW::DwgPaperSpaceBlockRecordHandle) {
28302 ++m_shapeLeftOut["SHAPE with an unresolved block owner"];
28303 continue;
28304 }
28305 shape.space = shape.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle
28306 ? DRW::PaperSpace : DRW::ModelSpace;
28307 }
28308 const DRW_Textstyle *style = nullptr;
28309 if (const auto it = metadata.textStyles().find(rec.shapeFileHandle);
28310 it != metadata.textStyles().end() && (it->second.flags & 1) != 0)
28311 style = &it->second;
28312 // A DXF has no numeric index or STYLE reference. Resolve its glyph
28313 // against the available shape-file directories, rejecting ambiguity.
28314 // This also locates its SHX for DXF Save As into another directory.
28315 if (rec.shapeFileHandle == 0 && !rec.styleName.empty()) {
28316 bool ambiguous = false;
28317 for (const auto &[handle, candidate] : metadata.textStyles()) {
28318 if ((candidate.flags & 1) == 0)
28319 continue;
28320 const auto *names = namesFor(candidate, rec.sourceDirectory);
28321 if (names == nullptr)
28322 continue;
28323 for (const auto &[index, name] : *names) {
28324 if (index == 0 || name.empty() ||
28325 normalizeDwgTableName(name) != normalizeDwgTableName(rec.styleName))
28326 continue;
28327 if (style != nullptr)
28328 ambiguous = true;
28329 style = &candidate;
28330 shape.m_shapeIndex = index;
28331 }
28332 }
28333 if (ambiguous)
28334 style = nullptr;
28335 }
28336 const auto *names = style != nullptr
28337 ? namesFor(*style, rec.sourceDirectory) : nullptr;
28338 if (m_dxfW != nullptr) {
28339 if (rec.shapeFileHandle == 0) {
28340 shape.m_styleName = rec.styleName; // Already a DXF glyph name.
28341 } else if (names != nullptr) {
28342 if (const auto it = names->find(rec.shapeIndex); it != names->end())
28343 shape.m_styleName = it->second;
28344 }
28345 if (shape.m_styleName.empty())
28346 ++m_shapeLeftOut["SHAPE without a name"];
28347 else
28348 m_shapesToExport.push_back({std::move(shape), ownerBlockName});
28349 continue;
28350 }
28351#ifdef DWGSUPPORT1
28352 if (m_dwgW == nullptr)
28353 continue;
28354 if (style == nullptr || shape.m_shapeIndex == 0) {
28355 ++m_shapeLeftOut["SHAPE without a resolved shape-file STYLE or index"];
28356 continue;
28357 }
28358 if (rec.handle == 0) {
28359 ++m_shapeLeftOut["SHAPE without an entity handle"];
28360 continue;
28361 }
28362 if (!ownerBlockName.empty() && shape.parentHandle == 0) {
28363 ++m_shapeLeftOut["SHAPE without a block owner handle"];
28364 continue;
28365 }
28366 shape.m_shapeFileHandle = style->handle;
28367 m_shapesToExport.push_back({std::move(shape), ownerBlockName});
28368#endif
28369 }
28370 }
28371}
28372
28373void RS_FilterDXFRW::writeDxfShapes(const std::string &blockName) {
28374 for (const auto &prepared : m_shapesToExport) {
28375 if (prepared.ownerBlockName != blockName)
28376 continue;
28377 DRW_Shape shape = prepared.shape;
28378 // The codec supplies the current BLOCK owner, or model/paper space in
28379 // ENTITIES. Never carry a source BLOCK_RECORD handle into the new DXF.
28380 if (blockName.empty() && shape.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle)
28381 shape.space = DRW::PaperSpace;
28382 shape.parentHandle = DRW::NoHandle;
28383 noteDxfWrite(m_dxfW->writeShape(&shape));
28384 }
28385}
28386
28387void RS_FilterDXFRW::writeEntities() {
28388#ifdef DWGSUPPORT1
28389 if (m_dwgW != nullptr && !validateDwgExportAdmissionPlan(
28390 DwgOpaqueTableFailurePhase::PostClasses)) {
28391 m_writeFailed = true;
28392 m_dwgW->markDwgWriteFailure();
28393 return;
28394 }
28395#endif
28396 // Pre-pass: reconstruct MLINE entities from decomposed polylines that
28397 // carry LibreCAD_MLINE XDATA. Consumed polylines are emitted as
28398 // MLINE; the rest fall through to the normal write path.
28399 std::set<RS_Entity *> consumed;
28400 reconstructPolylineSidecars(m_graphic, consumed);
28401 if (m_writeFailed)
28402 return;
28403 reconstructMLines(m_graphic, consumed);
28404 if (m_writeFailed || !reconstructSingleFrameSidecars(m_graphic, consumed))
28405 return;
28406#ifdef DWGSUPPORT1
28407 auto resolveDwgEntityOwner = [this](std::uint32_t sourceHandle,
28408 const char *recordName,
28409 std::uint32_t *outputHandle) {
28410 const DwgWriteReferenceStatus status =
28411 resolveDwgWriteBlockOwner(sourceHandle, outputHandle);
28412 if (status == DwgWriteReferenceStatus::LegacySpaceAlias ||
28413 status == DwgWriteReferenceStatus::Selected ||
28414 status == DwgWriteReferenceStatus::Committed) {
28415 return true;
28416 }
28417 RS_DEBUGRS_Debug::instance()->print(
28418 RS_Debug::D_WARNING,
28419 "RS_FilterDXFRW: rejecting DWG %s with unresolved owner 0x%X "
28420 "(status=%d)",
28421 recordName, sourceHandle, static_cast<int>(status));
28422 m_writeFailed = true;
28423 return false;
28424 };
28425 const auto writeDwgMetadataEntityWithCompletion =
28426 [this](std::uint32_t sourceHandle, std::uint32_t sourceOwnerHandle,
28427 const char *recordName, DRW_Entity &entity, const auto &write,
28428 const auto &complete) {
28429 using SourceKind =
28430 RS_FilterDXFRW_DwgWriteIdentityRegistry::DwgWriteSourceKind;
28431 if (m_dwgW == nullptr || sourceHandle == 0 ||
28432 isDwgWriteSourceAmbiguous(sourceHandle, SourceKind::Entity)) {
28433 m_writeFailed = true;
28434 return false;
28435 }
28436
28437 std::uint32_t outputOwnerHandle = 0;
28438 const DwgWriteReferenceStatus ownerStatus =
28439 resolveDwgWriteBlockOwner(sourceOwnerHandle, &outputOwnerHandle);
28440 if (ownerStatus != DwgWriteReferenceStatus::LegacySpaceAlias &&
28441 ownerStatus != DwgWriteReferenceStatus::Selected &&
28442 ownerStatus != DwgWriteReferenceStatus::Committed) {
28443 m_writeFailed = true;
28444 RS_DEBUGRS_Debug::instance()->print(
28445 RS_Debug::D_WARNING,
28446 "RS_FilterDXFRW: rejecting DWG %s with unresolved owner 0x%X",
28447 recordName, sourceOwnerHandle);
28448 return false;
28449 }
28450
28451 std::uint32_t existingOutputHandle = 0;
28452 if (lookupDwgWriteIdentity(SourceKind::Entity, sourceHandle,
28453 &existingOutputHandle) !=
28454 DwgWriteIdentityLookup::Missing) {
28455 m_writeFailed = true;
28456 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
28457 "RS_FilterDXFRW: duplicate DWG %s source handle 0x%X",
28458 recordName, sourceHandle);
28459 return false;
28460 }
28461
28462 const std::uint32_t outputHandle = resolveDwgWriteHandle(sourceHandle);
28463 entity.handle = outputHandle;
28464 entity.parentHandle = outputOwnerHandle;
28465 const auto identityCheckpoint = checkpointDwgWriteIdentity();
28466 if (!selectDwgWriteIdentity(SourceKind::Entity, sourceHandle,
28467 outputHandle)) {
28468 m_writeFailed = true;
28469 return false;
28470 }
28471 const std::uint64_t transaction =
28472 m_dwgW->beginDwgWriteTransaction(false, false, false);
28473 if (transaction == 0) {
28474 restoreDwgWriteIdentity(identityCheckpoint);
28475 m_writeFailed = true;
28476 return false;
28477 }
28478 const auto rollback = [&]() {
28479 m_dwgW->clearDwgObjectFrameProvenance();
28480 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
28481 restoreDwgWriteIdentity(identityCheckpoint);
28482 m_writeFailed = true;
28483 };
28484 if (!bindDwgDataStorageFrame(sourceHandle,
28485 DwgDataStorageCarrier::Kind::Typed)) {
28486 rollback();
28487 return false;
28488 }
28489 const bool failedBefore = m_writeFailed;
28490 if (!write() || failedBefore || m_writeFailed) {
28491 rollback();
28492 return false;
28493 }
28494
28495 DRW::DwgObjectFrameReceipt frame;
28496 if (!m_dwgW->getLastDwgObjectFrame(frame) ||
28497 !recordDwgWriteObjectOccurrences(outputHandle, frame)) {
28498 rollback();
28499 return false;
28500 }
28501
28502 const DwgWriteOwnerReceipt receipt{
28503 SourceKind::Entity, sourceHandle, outputHandle,
28504 sourceOwnerHandle, outputOwnerHandle, true};
28505 if (!recordDwgWriteOwnerReceipt(receipt)) {
28506 rollback();
28507 return false;
28508 }
28509 if (!complete() || m_writeFailed) {
28510 rollback();
28511 return false;
28512 }
28513 if (m_dwgW->getVersion() > DRW::AC1024) {
28514 const auto *dataStorageDecision =
28515 dataStorageDecisionForOwner(sourceHandle);
28516 if (dataStorageDecision != nullptr) {
28517 if (dataStorageDecision->decision !=
28518 DwgDataStorageDecision::EmitTyped ||
28519 dataStorageDecision->selectedPhase !=
28520 DwgDataStorageCarrier::EmissionPhase::Objects ||
28521 !recordDwgDataStorageObjectCommit(
28522 sourceHandle, DwgDataStorageCarrier::Kind::Typed,
28523 DwgWriteSourceKind::Entity, outputHandle, frame)) {
28524 rollback();
28525 return false;
28526 }
28527 }
28528 }
28529 if (sourceHandle != 0 &&
28530 m_dwgW->hasDwgClassDefinition(frame.classNumber) &&
28531 (!admitDwgClassFrameDescriptor(
28532 DwgWriteSourceKind::Entity, sourceHandle, outputHandle,
28533 frame.classNumber, 0, m_dwgW->getVersion()) ||
28534 !commitDwgClassFrame(DwgWriteSourceKind::Entity, sourceHandle,
28535 outputHandle, frame.classNumber))) {
28536 rollback();
28537 return false;
28538 }
28539 if (!commitDwgWriteIdentity(SourceKind::Entity, outputHandle)) {
28540 rollback();
28541 return false;
28542 }
28543 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
28544 rollback();
28545 return false;
28546 }
28547 return true;
28548 };
28549 const auto writeDwgMetadataEntity =
28550 [&writeDwgMetadataEntityWithCompletion](
28551 std::uint32_t sourceHandle, std::uint32_t sourceOwnerHandle,
28552 const char *recordName, DRW_Entity &entity, const auto &write) {
28553 return writeDwgMetadataEntityWithCompletion(
28554 sourceHandle, sourceOwnerHandle, recordName, entity, write,
28555 [] { return true; });
28556 };
28557 struct PendingOptionalDrop {
28558 std::uint32_t targetSourceHandle = 0;
28559 DwgWriteReferenceStatus targetStatus = DwgWriteReferenceStatus::Missing;
28560 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
28561 const char *referenceName = nullptr;
28562 std::uint32_t referenceOrdinal = 0;
28563 const char *reason = nullptr;
28564 };
28565 auto remapDwgObjectReference = [this](
28566 std::uint32_t sourceHandle,
28567 const char *referenceKind,
28568 std::vector<PendingOptionalDrop> &drops) {
28569 std::uint32_t outputHandle = 0;
28570 const DwgWriteReferenceStatus status =
28571 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
28572 if (status == DwgWriteReferenceStatus::Ambiguous ||
28573 status == DwgWriteReferenceStatus::Missing ||
28574 status == DwgWriteReferenceStatus::Suppressed) {
28575 RS_DEBUGRS_Debug::instance()->print(
28576 RS_Debug::D_WARNING,
28577 status == DwgWriteReferenceStatus::Ambiguous
28578 ? "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X"
28579 : "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
28580 referenceKind, sourceHandle);
28581 drops.push_back(
28582 {sourceHandle, status, DwgWriteSourceKind::Object, referenceKind, 0,
28583 status == DwgWriteReferenceStatus::Ambiguous ? "ambiguous target"
28584 : "missing target"});
28585 return std::uint32_t{0};
28586 }
28587 return outputHandle;
28588 };
28589 const auto recordOptionalDrops =
28590 [this](DwgWriteSourceKind producerKind,
28591 std::uint32_t producerSourceHandle,
28592 std::uint32_t producerOutputHandle,
28593 const std::vector<PendingOptionalDrop> &drops,
28594 DRW::Version version, const char *family) {
28595 for (const PendingOptionalDrop &drop : drops) {
28596 if (!recordDwgWriteOptionalDrop(
28597 producerKind, producerSourceHandle, producerOutputHandle,
28598 drop.referenceName, drop.referenceOrdinal, drop.targetKind,
28599 drop.targetSourceHandle, drop.targetStatus, version, family,
28600 drop.reason)) {
28601 return false;
28602 }
28603 }
28604 return true;
28605 };
28606#endif
28607 std::set<std::uint32_t> rawGeoPositionMarkerHandles;
28608 std::set<std::uint32_t> rawSectionObjectHandles;
28609 if (m_dwgW == nullptr && m_graphic != nullptr) {
28610 for (const auto &raw : m_graphic->dwgAdvancedMetadata().rawDxfEntities()) {
28611 if (!replaysInDxfExport(raw.m_version))
28612 continue;
28613 if (raw.name == "GEOPOSITIONMARKER" || raw.name == "POSITIONMARKER")
28614 rawGeoPositionMarkerHandles.insert(raw.handle);
28615 if (raw.name == "SECTIONOBJECT" || raw.name == "SECTION_OBJECT")
28616 rawSectionObjectHandles.insert(raw.handle);
28617 }
28618 }
28619 for (RS_Entity *e :
28620 lc::LC_ContainerTraverser{*m_graphic, RS2::ResolveNone}.entities()) {
28621 if (e->getFlag(RS2::FlagDeleted))
28622 continue;
28623 if (consumed.find(e) != consumed.end())
28624 continue;
28625 writeEntity(e);
28626 }
28627
28628 // LIGHT entities have no RS_Entity representation. Re-emit their typed
28629 // metadata before LIGHTLIST OBJECTS so imported list references remain
28630 // resolvable in DWG output.
28631 if (m_dwgW != nullptr && m_dwgW->getVersion() >= DRW::AC1021 &&
28632 m_graphic != nullptr) {
28633 for (const auto &record : m_graphic->dwgAdvancedMetadata().lights()) {
28634 if (record.replayState !=
28635 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
28636 record.handle == 0) {
28637 continue;
28638 }
28639 DRW_Light light = lightFromMetadata(record);
28640 (void)writeDwgMetadataEntity(
28641 record.handle, record.parentHandle, "LIGHT", light,
28642 [&light, this] { return m_dwgW->writeLight(&light); });
28643 }
28644 }
28645
28646 // NAVISWORKSMODEL has no LibreCAD geometry counterpart. Keep it on the
28647 // metadata shelf and re-emit the typed entity with its source references.
28648 if (m_graphic != nullptr) {
28649 for (const auto &record :
28650 m_graphic->dwgAdvancedMetadata().navisworksModels()) {
28651 if (record.replayState !=
28652 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
28653 record.handle == 0)
28654 continue;
28655 DRW_NavisworksModel model = navisworksModelFromMetadata(record);
28656 if (m_dwgW != nullptr) {
28657 std::vector<PendingOptionalDrop> optionalDrops;
28658 model.definitionHandle = remapDwgObjectReference(
28659 record.definitionHandle, "NAVISWORKSMODEL definition",
28660 optionalDrops);
28661 (void)writeDwgMetadataEntityWithCompletion(
28662 record.handle, record.parentHandle, "NAVISWORKSMODEL", model,
28663 [&model, this] { return m_dwgW->writeNavisworksModel(&model); },
28664 [&optionalDrops, &record, &model, &recordOptionalDrops, this]() {
28665 return recordOptionalDrops(
28666 DwgWriteSourceKind::Entity, record.handle, model.handle,
28667 optionalDrops, m_dwgW->getVersion(), "DWG ENTITIES");
28668 });
28669 } else if (m_dxfW != nullptr && m_dxfW->getVersion() >= DRW::AC1015) {
28670 if (model.parentHandle == DRW::DwgModelSpaceBlockRecordHandle)
28671 model.parentHandle = DRW::NoHandle;
28672 model.definitionHandle = m_dxfW->remapHandle(model.definitionHandle);
28673 noteDxfWrite(m_dxfW->writeNavisworksModel(&model));
28674 }
28675 }
28676 }
28677
28678 // Slice A2 (entities): re-emit ENTITIES captured verbatim on read (A4) so a
28679 // LibreCAD DXF round-trip preserves unmodeled entities rather than dropping
28680 // them. DXF-only: the DWG writer has its own object/entity
28681 // path. Records live on the graphic, shared across read/write filter
28682 // instances.
28683 if (!m_dwgW && m_graphic != nullptr) {
28684 for (const DRW_RawDxfObject &rawEntity :
28685 m_graphic->dwgAdvancedMetadata().rawDxfEntities()) {
28686 if (!replaysInDxfExport(rawEntity.m_version))
28687 continue; // left out, counted in fileExport
28688 DRW_RawDxfObject entity = rawEntity;
28689 noteDxfWrite(m_dxfW->writeRawDxfObject(&entity));
28690 }
28691 // 3DSOLID/REGION/BODY modeler shells read from DWG or typed DXF live only
28692 // on the metadata shelf. Re-emit their opaque ACIS/SAB payload without
28693 // attempting B-rep interpretation.
28694 for (const auto &rec : m_graphic->dwgAdvancedMetadata().modelerGeometry()) {
28695 if (rec.replayState != LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
28696 continue;
28697 DRW_ModelerGeometry geom(rec.type);
28698 geom.handle = rec.handle;
28699 geom.parentHandle = rec.parentHandle;
28700 geom.m_modelerVersion = rec.modelerVersion;
28701 geom.m_bodyBitSize =
28702 static_cast<std::uint32_t>(rec.rawBodyByteCount * 8u);
28703 geom.m_objectSize = rec.objectSize;
28704 geom.m_isEmpty = rec.isEmpty;
28705 geom.m_hasModelerData = rec.hasModelerData;
28706 geom.m_modelerDataUnknownBit = rec.modelerDataUnknownBit;
28707 geom.m_hasWireframe = rec.hasWireframe;
28708 geom.hasDataStorageRecord = rec.hasDataStorageRecord;
28709 geom.dataStorageHandle = rec.dataStorageHandle;
28710 geom.dataStorageHandleKey = rec.dataStorageHandleKey;
28711 geom.dataStorageData = rec.dataStorageData;
28712 geom.dataStorageSegmentIndex = rec.dataStorageSegmentIndex;
28713 geom.dataStorageSchemaIndex = rec.dataStorageSchemaIndex;
28714 geom.m_historyHandle = rec.historyHandle;
28715 geom.m_rawBytes = rec.rawBytes;
28716 geom.extData = rec.extData;
28717 noteDxfWrite(m_dxfW->writeModelerGeometry(&geom));
28718 }
28719 // LIGHT entities read from a DWG live only on the metadata shelf (no
28720 // RS_Light model), so without this loop a DWG->DXF export silently drops
28721 // them. Re-emit them as typed AcDbLight (R2007+; writeLight no-ops on older
28722 // DXF targets).
28723 if (m_dxfW->getVersion() >= DRW::AC1021) {
28724 for (const auto &rec : m_graphic->dwgAdvancedMetadata().lights()) {
28725 DRW_Light light = lightFromMetadata(rec);
28726 noteDxfWrite(m_dxfW->writeLight(&light));
28727 }
28728 }
28729 // OLE2FRAME entities read from a DWG live only on the metadata shelf ->
28730 // re-emit as typed AcDbOle2Frame. pt1/pt2 (frame rectangle) were decoded
28731 // from the OLE payload header on read; the opaque payload is replayed
28732 // verbatim (group 310).
28733 for (const auto &rec : m_graphic->dwgAdvancedMetadata().ole2Frames()) {
28734 if (rec.payloadBytes.empty())
28735 continue; // nothing to preserve (truncated/absent payload)
28736 DRW_Ole2Frame ole;
28737 ole.handle = rec.handle;
28738 ole.parentHandle = rec.parentHandle;
28739 ole.m_flags = rec.flags;
28740 ole.m_mode = rec.mode;
28741 ole.m_lockAspect = rec.lockAspect;
28742 ole.m_oleVersion = rec.oleVersion;
28743 ole.m_pt1 = rec.pt1;
28744 ole.m_pt2 = rec.pt2;
28745 ole.m_payloadBytes = rec.payloadBytes;
28746 noteDxfWrite(m_dxfW->writeOle2Frame(&ole));
28747 }
28748 // Legacy OLEFRAME entities carry no frame rectangle; preserve their
28749 // bounded status and opaque payload as the original DXF entity.
28750 for (const auto &rec : m_graphic->dwgAdvancedMetadata().oleFrames()) {
28751 if (rec.payloadTruncated || rec.payloadTooLarge)
28752 continue;
28753 DRW_OleFrame ole;
28754 ole.handle = rec.handle;
28755 ole.parentHandle = rec.parentHandle;
28756 ole.m_flags = rec.flags;
28757 ole.m_mode = rec.mode;
28758 ole.m_payloadBytes = rec.payloadBytes;
28759 noteDxfWrite(m_dxfW->writeOleFrame(&ole));
28760 }
28761 }
28762
28763 if (m_dxfW != nullptr)
28764 writeDxfShapes({});
28765#ifdef DWGSUPPORT1
28766 if (m_dwgW != nullptr) {
28767 for (const auto &prepared : m_shapesToExport) {
28768 DRW_Shape shape = prepared.shape;
28769 const std::uint32_t sourceHandle = shape.handle;
28770 const std::uint32_t styleSource = shape.m_shapeFileHandle;
28771 std::uint32_t styleOutput = 0;
28772 const auto styleState = lookupDwgWriteIdentity(
28773 DwgWriteSourceKind::TableRecord, styleSource, &styleOutput);
28774 if ((styleState != DwgWriteIdentityLookup::Selected &&
28775 styleState != DwgWriteIdentityLookup::Committed) || styleOutput == 0) {
28776 m_writeFailed = true;
28777 continue;
28778 }
28779 shape.m_shapeFileHandle = styleOutput;
28780 (void)writeDwgMetadataEntityWithCompletion(
28781 sourceHandle, shape.parentHandle, "SHAPE", shape,
28782 [&shape, this] { return m_dwgW->writeShape(&shape); },
28783 [&shape, sourceHandle, styleSource, this] {
28784 return recordDwgWriteReferenceReceipt(
28785 {DwgWriteSourceKind::Entity, sourceHandle, shape.handle,
28786 "SHAPE STYLE", 0, DwgWriteSourceKind::TableRecord,
28787 styleSource, shape.m_shapeFileHandle, true, 5});
28788 });
28789 }
28790 }
28791#endif
28792
28793 // CAMERA has no LibreCAD geometry counterpart. Keep the typed view-table
28794 // reference on the metadata shelf and emit it through the native writer.
28795 if (m_graphic != nullptr) {
28796 for (const auto &record : m_graphic->dwgAdvancedMetadata().cameras()) {
28797 if (record.replayState !=
28798 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
28799 continue;
28800 DRW_Camera camera;
28801 if (m_dwgW != nullptr) {
28802 const DwgWriteReferenceStatus viewStatus =
28803 resolveDwgWriteTableRecordHandle(record.viewHandle,
28804 m_dwgWriteViewHandleRemap,
28805 &camera.m_viewHandle);
28806 if (viewStatus != DwgWriteReferenceStatus::Resolved &&
28807 viewStatus != DwgWriteReferenceStatus::Selected &&
28808 viewStatus != DwgWriteReferenceStatus::Committed) {
28809 RS_DEBUGRS_Debug::instance()->print(
28810 RS_Debug::D_WARNING,
28811 "RS_FilterDXFRW: dropping dangling DWG CAMERA view target "
28812 "0x%X",
28813 record.viewHandle);
28814 m_writeFailed = true;
28815 continue;
28816 }
28817 camera.extData = record.extData;
28818 (void)writeDwgMetadataEntityWithCompletion(
28819 record.handle, record.parentHandle, "CAMERA", camera,
28820 [&camera, this] { return m_dwgW->writeCamera(&camera); },
28821 [&camera, &record, this]() {
28822 if (record.viewHandle == DRW::NoHandle)
28823 return true;
28824 const DwgWriteReferenceReceipt receipt{
28825 DwgWriteSourceKind::Entity,
28826 record.handle,
28827 camera.handle,
28828 "CAMERA VIEW",
28829 0,
28830 DwgWriteSourceKind::TableRecord,
28831 record.viewHandle,
28832 camera.m_viewHandle,
28833 true,
28834 5};
28835 return recordDwgWriteReferenceReceipt(receipt);
28836 });
28837 } else if (m_dxfW != nullptr) {
28838 // dxfRW::writeCamera() leaves CAMERA out below AC1015 and counts
28839 // it, the same as every other version-gated writer; it does not
28840 // need this filter's own gate ahead of it.
28841 camera.handle = record.handle;
28842 camera.parentHandle =
28843 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle
28844 ? DRW::NoHandle
28845 : record.parentHandle;
28846 camera.m_viewHandle = m_dxfW->remapHandle(record.viewHandle);
28847 camera.extData = record.extData;
28848 noteDxfWrite(m_dxfW->writeCamera(&camera));
28849 }
28850 }
28851 }
28852
28853 // GEOPOSITIONMARKER has no LibreCAD geometry counterpart. DWG stores it as
28854 // fixed type 1164; DXF input already has a lossless raw carrier, so use that
28855 // carrier when present and type-emit only metadata originating from DWG.
28856 if (m_graphic != nullptr) {
28857 for (const auto &record :
28858 m_graphic->dwgAdvancedMetadata().geoPositionMarkers()) {
28859 if (record.replayState !=
28860 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
28861 record.handle == 0 ||
28862 (m_dxfW != nullptr &&
28863 rawGeoPositionMarkerHandles.count(record.handle) != 0))
28864 continue;
28865
28866 DRW_GeoPositionMarker marker;
28867 marker.m_classVersion = record.classVersion;
28868 marker.m_position = record.position;
28869 marker.m_radius = record.radius;
28870 marker.m_notes = record.notes;
28871 marker.m_landingGap = record.landingGap;
28872 marker.m_mtextVisible = record.mtextVisible;
28873 marker.m_textAlignment = record.textAlignment;
28874 marker.m_enableFrameText = record.enableFrameText;
28875 if (record.embeddedMText != nullptr)
28876 marker.mtext = std::make_unique<DRW_MText>(*record.embeddedMText);
28877 marker.extData = record.extData;
28878
28879 if (m_dwgW != nullptr) {
28880 if (m_dwgW->getVersion() < DRW::AC1027) {
28881 m_writeFailed = true;
28882 continue;
28883 }
28884 auto remap = [this](std::uint32_t handle) {
28885 const auto it = m_dwgWriteHandleRemap.find(handle);
28886 return it == m_dwgWriteHandleRemap.end() ? handle : it->second;
28887 };
28888 marker.handle = remap(record.handle);
28889 if (!resolveDwgEntityOwner(record.parentHandle, "GEOPOSITIONMARKER",
28890 &marker.parentHandle)) {
28891 continue;
28892 }
28893 (void)writeDwgMetadataEntity(
28894 record.handle, record.parentHandle, "GEOPOSITIONMARKER", marker,
28895 [&marker, this] {
28896 return m_dwgW->writeGeoPositionMarker(&marker);
28897 });
28898 } else if (m_dxfW != nullptr) {
28899 // dxfRW::writeGeoPositionMarker() leaves the record out below
28900 // AC1027 and counts it; no need to fail the save ahead of it.
28901 marker.handle = record.handle;
28902 const bool paperSpace =
28903 record.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle ||
28904 record.parentHandle == DRW::DxfPaperSpaceBlockRecordHandle;
28905 const bool modelSpace =
28906 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle ||
28907 record.parentHandle == DRW::DxfModelSpaceBlockRecordHandle ||
28908 record.parentHandle == DRW::NoHandle;
28909 if (paperSpace || modelSpace) {
28910 marker.parentHandle = DRW::NoHandle;
28911 marker.space = paperSpace ? DRW::PaperSpace : DRW::ModelSpace;
28912 } else {
28913 marker.parentHandle = record.parentHandle;
28914 }
28915 noteDxfWrite(m_dxfW->writeGeoPositionMarker(&marker));
28916 }
28917 }
28918 }
28919
28920 // SECTIONOBJECT has no LibreCAD geometry counterpart. DXF-originated
28921 // records remain raw-authoritative; DWG-originated metadata is typed-emitted.
28922 if (m_graphic != nullptr) {
28923 for (const auto &record :
28924 m_graphic->dwgAdvancedMetadata().sectionObjects()) {
28925 if (record.replayState !=
28926 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed ||
28927 record.handle == 0 ||
28928 (m_dxfW != nullptr &&
28929 rawSectionObjectHandles.count(record.handle) != 0)) {
28930 continue;
28931 }
28932
28933 DRW_SectionObject section = sectionObjectFromMetadata(record);
28934 if (m_dwgW != nullptr) {
28935 if (m_dwgW->getVersion() < DRW::AC1021) {
28936 m_writeFailed = true;
28937 continue;
28938 }
28939 std::vector<PendingOptionalDrop> optionalDrops;
28940 section.m_sectionSettingsHandle =
28941 remapDwgObjectReference(record.sectionSettingsHandle,
28942 "SECTIONOBJECT settings", optionalDrops);
28943 (void)writeDwgMetadataEntityWithCompletion(
28944 record.handle, record.parentHandle, "SECTIONOBJECT", section,
28945 [&section, this] { return m_dwgW->writeSectionObject(&section); },
28946 [&optionalDrops, &record, &section, &recordOptionalDrops, this]() {
28947 return recordOptionalDrops(
28948 DwgWriteSourceKind::Entity, record.handle, section.handle,
28949 optionalDrops, m_dwgW->getVersion(), "DWG ENTITIES");
28950 });
28951 } else if (m_dxfW != nullptr) {
28952 // dxfRW::writeSectionObject() leaves the record out below AC1021
28953 // and counts it; no need to fail the save ahead of it.
28954 section.handle = record.handle;
28955 section.m_sectionSettingsHandle =
28956 m_dxfW->remapHandle(record.sectionSettingsHandle);
28957 const bool paperSpace =
28958 record.parentHandle == DRW::DwgPaperSpaceBlockRecordHandle ||
28959 record.parentHandle == DRW::DxfPaperSpaceBlockRecordHandle;
28960 const bool modelSpace =
28961 record.parentHandle == DRW::DwgModelSpaceBlockRecordHandle ||
28962 record.parentHandle == DRW::DxfModelSpaceBlockRecordHandle ||
28963 record.parentHandle == DRW::NoHandle;
28964 if (paperSpace || modelSpace) {
28965 section.parentHandle = DRW::NoHandle;
28966 section.space = paperSpace ? DRW::PaperSpace : DRW::ModelSpace;
28967 } else {
28968 section.parentHandle = m_dxfW->remapHandle(record.parentHandle);
28969 }
28970 noteDxfWrite(m_dxfW->writeSectionObject(&section));
28971 }
28972 }
28973 }
28974}
28975
28976namespace {
28977// Parsed view of one polyline's LibreCAD_MLINE XDATA.
28978struct MLineEntry {
28979 RS_Entity *entity = nullptr;
28980 QString mlineId;
28981 QString styleName;
28982 std::uint32_t styleHandle = 0;
28983 double scale = 1.0;
28984 int justification = 0;
28985 int elementCount = 0;
28986 int elementIndex = 0;
28987 double offset = 0.0;
28988 int openClosed = 1;
28989 int markerCount = 0;
28990 bool isAnchor = false; // i==0 polyline carries baseline + miter
28991 std::vector<DRW_Coord> baselineVerts;
28992 std::vector<DRW_Coord> miterDirs;
28993};
28994
28995struct LWPolylineMeta {
28996 double width = 0.0;
28997 double elevation = 0.0;
28998 double thickness = 0.0;
28999 DRW_Coord extrusion{0.0, 0.0, 1.0};
29000 int vertexCount = 0;
29001 std::vector<double> startWidths;
29002 std::vector<double> endWidths;
29003 std::vector<int> identifiers;
29004};
29005
29006std::optional<LWPolylineMeta> extractLWPolylineMeta(RS_Entity *e) {
29007 if (!e || !e->hasDrwExtData())
29008 return std::nullopt;
29009 const auto &ext = e->getDrwExtData();
29010 bool inGroup = false;
29011 LWPolylineMeta meta;
29012 int seen1040 = 0; // 0=width, 1=elevation, 2=thickness, then vertex widths
29013 bool gotMarker = false;
29014
29015 for (const auto &sp : ext) {
29016 if (!sp) {
29017 if (inGroup)
29018 return std::nullopt;
29019 continue;
29020 }
29021 const int code = sp->code();
29022 if (code == 1001) {
29023 std::string marker;
29024 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
29025 if (inGroup)
29026 return std::nullopt;
29027 inGroup = false;
29028 continue;
29029 }
29030 inGroup = marker == "LibreCAD_LWPOLYLINE";
29031 if (inGroup) {
29032 if (gotMarker)
29033 return std::nullopt;
29034 gotMarker = true;
29035 }
29036 continue;
29037 }
29038 if (!inGroup)
29039 continue;
29040
29041 switch (code) {
29042 case 1040: {
29043 if (sp->type() != DRW_Variant::DOUBLE || !std::isfinite(sp->d_val()))
29044 return std::nullopt;
29045 const double d = sp->d_val();
29046 if (seen1040 == 0)
29047 meta.width = d;
29048 else if (seen1040 == 1)
29049 meta.elevation = d;
29050 else if (seen1040 == 2)
29051 meta.thickness = d;
29052 else if ((seen1040 - 3) % 2 == 0)
29053 meta.startWidths.push_back(d);
29054 else
29055 meta.endWidths.push_back(d);
29056 ++seen1040;
29057 break;
29058 }
29059 case 1010: {
29060 DRW_Coord coordinate;
29061 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
29062 return std::nullopt;
29063 meta.extrusion = coordinate;
29064 break;
29065 }
29066 case 1070: {
29067 std::int64_t value;
29068 if (readXDataInteger(sp.get(), 1070, value, false) !=
29069 XDataReadResult::Valid ||
29070 !fitsXDataInt32(value))
29071 return std::nullopt;
29072 if (meta.vertexCount == 0)
29073 meta.vertexCount = static_cast<int>(value);
29074 break;
29075 }
29076 case 1071: {
29077 std::int64_t value;
29078 if (readXDataInteger(sp.get(), 1071, value) != XDataReadResult::Valid ||
29079 !fitsXDataInt32(value))
29080 return std::nullopt;
29081 meta.identifiers.push_back(static_cast<int>(value));
29082 break;
29083 }
29084 default:
29085 break;
29086 }
29087 }
29088
29089 if (!gotMarker)
29090 return std::nullopt;
29091 return meta;
29092}
29093
29094struct MeshSidecarEntry {
29095 RS_Entity *entity = nullptr;
29096 QString meshId;
29097 QString role;
29098 int elementIndex = -1;
29099 int elementCount = 0;
29100 int roleIndex = -1;
29101 int flags = 0;
29102 int mCount = 0;
29103 int nCount = 0;
29104 int smoothM = 0;
29105 int smoothN = 0;
29106 int curveType = 0;
29107 int stringFieldCount = 0;
29108 int integerFieldCount = 0;
29109 int markerCount = 0;
29110 bool malformedSchema = false;
29111 std::vector<DRW_Coord> sourceVertices;
29112};
29113
29114struct PolyfaceSidecarEntry {
29115 RS_Entity *entity = nullptr;
29116 QString polyfaceId;
29117 int faceIndex = -1;
29118 int faceCount = 0;
29119 int flags = 0;
29120 int vertexCount = 0;
29121 int originalFaceCount = 0;
29122 std::array<int, 4> indices{{0, 0, 0, 0}};
29123 int stringFieldCount = 0;
29124 int integerFieldCount = 0;
29125 int markerCount = 0;
29126 bool malformedSchema = false;
29127 std::vector<DRW_Coord> sourceVertices;
29128};
29129
29130std::vector<RS_Vector> collectPolylineVertices(RS_Polyline *polyline) {
29131 std::vector<RS_Vector> vertices;
29132 if (polyline == nullptr)
29133 return vertices;
29134
29135 const RS_AtomicEntity *lastAtomic = nullptr;
29136 for (RS_Entity *sub :
29137 lc::LC_ContainerTraverser{*polyline, RS2::ResolveNone}.entities()) {
29138 if (sub == nullptr || !sub->isAtomic())
29139 continue;
29140 const auto *atomic = static_cast<const RS_AtomicEntity *>(sub);
29141 vertices.push_back(atomic->getStartpoint());
29142 lastAtomic = atomic;
29143 }
29144 if (lastAtomic != nullptr && !polyline->isClosed())
29145 vertices.push_back(lastAtomic->getEndpoint());
29146
29147 return vertices;
29148}
29149
29150bool pointsMatch2D(const RS_Vector &point, const DRW_Coord &coord) {
29151 return std::abs(point.x - coord.x) <= RS_TOLERANCE1.0e-10 &&
29152 std::abs(point.y - coord.y) <= RS_TOLERANCE1.0e-10;
29153}
29154
29155bool hasFiniteDwgCoordinate(const DRW_Coord &coordinate) {
29156 return std::isfinite(coordinate.x) && std::isfinite(coordinate.y) &&
29157 std::isfinite(coordinate.z);
29158}
29159
29160bool hasFiniteVisibleCoordinate(const RS_Vector &coordinate) {
29161 return std::isfinite(coordinate.x) && std::isfinite(coordinate.y);
29162}
29163
29164bool matchesSidecarPresentation(const RS_Entity *entry,
29165 const RS_Entity *anchor) {
29166 if (entry == nullptr || anchor == nullptr ||
29167 entry->getLayer(false) != anchor->getLayer(false) ||
29168 entry->getFlag(RS2::FlagVisible) != anchor->getFlag(RS2::FlagVisible)) {
29169 return false;
29170 }
29171
29172 const RS_Pen entryPen = entry->getPen(false);
29173 const RS_Pen anchorPen = anchor->getPen(false);
29174 return entryPen == anchorPen && entryPen.getFlags() == anchorPen.getFlags() &&
29175 std::abs(entryPen.getAlpha() - anchorPen.getAlpha()) <= RS_TOLERANCE1.0e-10 &&
29176 std::abs(entryPen.dashOffset() - anchorPen.dashOffset()) <=
29177 RS_TOLERANCE1.0e-10;
29178}
29179
29180std::optional<std::vector<RS_Vector>>
29181collectLinearPolylineVertices(const RS_Entity *entity, bool expectedClosed) {
29182 if (entity == nullptr || entity->rtti() != RS2::EntityPolyline)
29183 return std::nullopt;
29184
29185 const auto *polyline = static_cast<const RS_Polyline *>(entity);
29186 if (polyline->isClosed() != expectedClosed)
29187 return std::nullopt;
29188
29189 std::vector<RS_Vector> vertices;
29190 const RS_Line *lastLine = nullptr;
29191 for (RS_Entity *sub :
29192 lc::LC_ContainerTraverser{*polyline, RS2::ResolveNone}.entities()) {
29193 if (sub == nullptr || sub->rtti() != RS2::EntityLine)
29194 return std::nullopt;
29195 const auto *line = static_cast<const RS_Line *>(sub);
29196 const RS_Vector start = line->getStartpoint();
29197 const RS_Vector end = line->getEndpoint();
29198 if (!hasFiniteVisibleCoordinate(start) || !hasFiniteVisibleCoordinate(end))
29199 return std::nullopt;
29200 vertices.push_back(start);
29201 lastLine = line;
29202 }
29203 if (lastLine == nullptr)
29204 return std::nullopt;
29205
29206 const RS_Vector lastEndpoint = lastLine->getEndpoint();
29207 if (expectedClosed) {
29208 if (vertices.empty() ||
29209 !pointsMatch2D(lastEndpoint, DRW_Coord{vertices.front().x,
29210 vertices.front().y, 0.0})) {
29211 return std::nullopt;
29212 }
29213 } else {
29214 vertices.push_back(lastEndpoint);
29215 }
29216 return vertices;
29217}
29218
29219// duplicateMarker, when non-null, is set only when this entity's extData
29220// carries a SECOND "LibreCAD_POLYLINE_MESH" marker group -- two groups both
29221// claiming to be the one mesh sidecar for this entity, with no way to tell
29222// which is authoritative. That is different from an otherwise-valid group
29223// followed by an unrelated/unrecognized field (extractMeshSidecar still
29224// returns nullopt for both): the latter is a legitimate edit that should
29225// just fall back to writing an ordinary polyline, while the former must not
29226// be silently resolved by picking one of the two candidates.
29227std::optional<MeshSidecarEntry>
29228extractMeshSidecar(RS_Entity *e, bool *duplicateMarker = nullptr) {
29229 if (!e || !e->hasDrwExtData())
29230 return std::nullopt;
29231
29232 MeshSidecarEntry meta;
29233 meta.entity = e;
29234 bool inGroup = false;
29235 int seen1000 = 0;
29236 int seen1070 = 0;
29237
29238 for (const auto &sp : e->getDrwExtData()) {
29239 if (!sp) {
29240 if (inGroup)
29241 return std::nullopt;
29242 continue;
29243 }
29244 const int code = sp->code();
29245 if (code == 1001) {
29246 const bool wasInGroup = inGroup;
29247 inGroup = false;
29248 std::string marker;
29249 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
29250 if (wasInGroup)
29251 return std::nullopt;
29252 continue;
29253 }
29254 inGroup = marker == "LibreCAD_POLYLINE_MESH";
29255 if (inGroup) {
29256 if (meta.markerCount != 0) {
29257 if (duplicateMarker != nullptr)
29258 *duplicateMarker = true;
29259 return std::nullopt;
29260 }
29261 ++meta.markerCount;
29262 }
29263 continue;
29264 }
29265 if (!inGroup)
29266 continue;
29267
29268 switch (code) {
29269 case 1000: {
29270 std::string text;
29271 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
29272 return std::nullopt;
29273 const QString value = QString::fromStdString(text);
29274 if (seen1000 == 0)
29275 meta.meshId = value;
29276 else if (seen1000 == 1)
29277 meta.role = value;
29278 ++seen1000;
29279 ++meta.stringFieldCount;
29280 break;
29281 }
29282 case 1070: {
29283 std::int64_t integerValue;
29284 if (readXDataInteger(sp.get(), 1070, integerValue, false) !=
29285 XDataReadResult::Valid ||
29286 !fitsXDataInt32(integerValue))
29287 return std::nullopt;
29288 const int value = static_cast<int>(integerValue);
29289 if (seen1070 == 0)
29290 meta.elementIndex = value;
29291 else if (seen1070 == 1)
29292 meta.elementCount = value;
29293 else if (seen1070 == 2)
29294 meta.roleIndex = value;
29295 else if (seen1070 == 3)
29296 meta.flags = value;
29297 else if (seen1070 == 4)
29298 meta.mCount = value;
29299 else if (seen1070 == 5)
29300 meta.nCount = value;
29301 else if (seen1070 == 6)
29302 meta.smoothM = value;
29303 else if (seen1070 == 7)
29304 meta.smoothN = value;
29305 else if (seen1070 == 8)
29306 meta.curveType = value;
29307 ++seen1070;
29308 ++meta.integerFieldCount;
29309 break;
29310 }
29311 case 1010: {
29312 DRW_Coord coordinate;
29313 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
29314 return std::nullopt;
29315 meta.sourceVertices.push_back(coordinate);
29316 break;
29317 }
29318 default:
29319 return std::nullopt;
29320 }
29321 }
29322
29323 if (meta.markerCount == 0 || meta.meshId.isEmpty())
29324 return std::nullopt;
29325 return meta;
29326}
29327
29328// See extractMeshSidecar's duplicateMarker comment -- same "two groups, no
29329// way to pick one" distinction, for the LibreCAD_POLYLINE_PFACE marker.
29330std::optional<PolyfaceSidecarEntry>
29331extractPolyfaceSidecar(RS_Entity *e, bool *duplicateMarker = nullptr) {
29332 if (!e || !e->hasDrwExtData())
29333 return std::nullopt;
29334
29335 PolyfaceSidecarEntry meta;
29336 meta.entity = e;
29337 bool inGroup = false;
29338 int seen1070 = 0;
29339
29340 for (const auto &sp : e->getDrwExtData()) {
29341 if (!sp) {
29342 if (inGroup)
29343 return std::nullopt;
29344 continue;
29345 }
29346 const int code = sp->code();
29347 if (code == 1001) {
29348 const bool wasInGroup = inGroup;
29349 inGroup = false;
29350 std::string marker;
29351 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
29352 if (wasInGroup)
29353 return std::nullopt;
29354 continue;
29355 }
29356 inGroup = marker == "LibreCAD_POLYLINE_PFACE";
29357 if (inGroup) {
29358 if (meta.markerCount != 0) {
29359 if (duplicateMarker != nullptr)
29360 *duplicateMarker = true;
29361 return std::nullopt;
29362 }
29363 ++meta.markerCount;
29364 }
29365 continue;
29366 }
29367 if (!inGroup)
29368 continue;
29369
29370 switch (code) {
29371 case 1000: {
29372 std::string text;
29373 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
29374 return std::nullopt;
29375 if (meta.polyfaceId.isEmpty())
29376 meta.polyfaceId = QString::fromStdString(text);
29377 ++meta.stringFieldCount;
29378 break;
29379 }
29380 case 1070: {
29381 std::int64_t integerValue;
29382 if (readXDataInteger(sp.get(), 1070, integerValue, false) !=
29383 XDataReadResult::Valid ||
29384 !fitsXDataInt32(integerValue))
29385 return std::nullopt;
29386 const int value = static_cast<int>(integerValue);
29387 if (seen1070 == 0)
29388 meta.faceIndex = value;
29389 else if (seen1070 == 1)
29390 meta.faceCount = value;
29391 else if (seen1070 == 2)
29392 meta.flags = value;
29393 else if (seen1070 == 3)
29394 meta.vertexCount = value;
29395 else if (seen1070 == 4)
29396 meta.originalFaceCount = value;
29397 else if (seen1070 >= 5 && seen1070 < 9)
29398 meta.indices[seen1070 - 5] = value;
29399 ++seen1070;
29400 ++meta.integerFieldCount;
29401 break;
29402 }
29403 case 1010: {
29404 DRW_Coord coordinate;
29405 if (readXDataCoord(sp.get(), 1010, coordinate) != XDataReadResult::Valid)
29406 return std::nullopt;
29407 meta.sourceVertices.push_back(coordinate);
29408 break;
29409 }
29410 default:
29411 return std::nullopt;
29412 }
29413 }
29414
29415 if (meta.markerCount == 0 || meta.polyfaceId.isEmpty())
29416 return std::nullopt;
29417 return meta;
29418}
29419
29420struct MeshSidecarDescriptor {
29421 const MeshSidecarEntry *anchor = nullptr;
29422 std::vector<const MeshSidecarEntry *> members;
29423 std::size_t sourceVertexCount = 0;
29424};
29425
29426struct PolyfaceSidecarDescriptor {
29427 const PolyfaceSidecarEntry *anchor = nullptr;
29428 std::vector<const PolyfaceSidecarEntry *> members;
29429 std::size_t sourceVertexCount = 0;
29430 std::size_t compoundVertexCount = 0;
29431};
29432
29433bool checkedMeshSidecarCounts(int mCount, int nCount, std::size_t &elementCount,
29434 std::size_t &vertexCount) {
29435 if (mCount <= 0 || nCount <= 0)
29436 return false;
29437
29438 std::uint64_t elements = 0;
29439 std::uint64_t vertices = 0;
29440 if (!dwgSafety::add(static_cast<std::uint64_t>(mCount),
29441 static_cast<std::uint64_t>(nCount), elements) ||
29442 !dwgSafety::multiply(static_cast<std::uint64_t>(mCount),
29443 static_cast<std::uint64_t>(nCount), vertices) ||
29444 elements > static_cast<std::uint64_t>(std::numeric_limits<int>::max()) ||
29445 vertices > dwgSafety::MaxOwnedObjectCount ||
29446 elements > std::numeric_limits<std::size_t>::max() ||
29447 vertices > std::numeric_limits<std::size_t>::max()) {
29448 return false;
29449 }
29450 elementCount = static_cast<std::size_t>(elements);
29451 vertexCount = static_cast<std::size_t>(vertices);
29452 return true;
29453}
29454
29455bool matchesSidecarSourceCoordinates(
29456 const std::vector<DRW_Coord> &coordinates) {
29457 return std::all_of(coordinates.cbegin(), coordinates.cend(),
29458 [](const DRW_Coord &coordinate) {
29459 return hasFiniteDwgCoordinate(coordinate);
29460 });
29461}
29462
29463std::optional<MeshSidecarDescriptor>
29464makeMeshSidecarDescriptor(const std::vector<MeshSidecarEntry> &entries) {
29465 if (entries.empty())
29466 return std::nullopt;
29467
29468 const MeshSidecarEntry *anchor = nullptr;
29469 for (const MeshSidecarEntry &entry : entries) {
29470 if (!entry.sourceVertices.empty()) {
29471 if (anchor != nullptr)
29472 return std::nullopt;
29473 anchor = &entry;
29474 }
29475 }
29476 if (anchor == nullptr || anchor->entity == nullptr ||
29477 anchor->markerCount != 1 || anchor->malformedSchema ||
29478 anchor->stringFieldCount != 2 || anchor->integerFieldCount != 9 ||
29479 anchor->role != QStringLiteral("row")(QString(QtPrivate::qMakeStringPrivate(u"" "row"))) || anchor->elementIndex != 0 ||
29480 anchor->roleIndex != 0 || (anchor->flags & 0x10) == 0 ||
29481 anchor->curveType != 0) {
29482 return std::nullopt;
29483 }
29484
29485 std::size_t expectedElementCount = 0;
29486 std::size_t expectedVertexCount = 0;
29487 if (!checkedMeshSidecarCounts(anchor->mCount, anchor->nCount,
29488 expectedElementCount, expectedVertexCount) ||
29489 anchor->elementCount != static_cast<int>(expectedElementCount) ||
29490 entries.size() != expectedElementCount ||
29491 anchor->sourceVertices.size() != expectedVertexCount ||
29492 !matchesSidecarSourceCoordinates(anchor->sourceVertices)) {
29493 return std::nullopt;
29494 }
29495
29496 MeshSidecarDescriptor descriptor;
29497 descriptor.anchor = anchor;
29498 descriptor.sourceVertexCount = expectedVertexCount;
29499 descriptor.members.resize(expectedElementCount, nullptr);
29500 std::vector<bool> seenRows(static_cast<std::size_t>(anchor->mCount), false);
29501 std::vector<bool> seenColumns(static_cast<std::size_t>(anchor->nCount),
29502 false);
29503 const bool rowsClosed = (anchor->flags & 0x20) != 0;
29504 const bool columnsClosed = (anchor->flags & 0x1) != 0;
29505
29506 for (const MeshSidecarEntry &entry : entries) {
29507 if (entry.entity == nullptr || entry.markerCount != 1 ||
29508 entry.malformedSchema || entry.stringFieldCount != 2 ||
29509 entry.integerFieldCount != 9 || entry.meshId != anchor->meshId ||
29510 entry.flags != anchor->flags || entry.mCount != anchor->mCount ||
29511 entry.nCount != anchor->nCount || entry.smoothM != anchor->smoothM ||
29512 entry.smoothN != anchor->smoothN ||
29513 entry.curveType != anchor->curveType ||
29514 entry.elementCount != anchor->elementCount || entry.elementIndex < 0 ||
29515 static_cast<std::size_t>(entry.elementIndex) >= expectedElementCount ||
29516 (!entry.sourceVertices.empty() && &entry != anchor) ||
29517 !matchesSidecarPresentation(entry.entity, anchor->entity)) {
29518 return std::nullopt;
29519 }
29520
29521 const bool isRow = entry.role == QStringLiteral("row")(QString(QtPrivate::qMakeStringPrivate(u"" "row")));
29522 const bool isColumn = entry.role == QStringLiteral("column")(QString(QtPrivate::qMakeStringPrivate(u"" "column")));
29523 if (isRow) {
29524 if (entry.roleIndex < 0 || entry.roleIndex >= anchor->mCount ||
29525 entry.elementIndex != entry.roleIndex ||
29526 seenRows[static_cast<std::size_t>(entry.roleIndex)]) {
29527 return std::nullopt;
29528 }
29529 const auto visible =
29530 collectLinearPolylineVertices(entry.entity, rowsClosed);
29531 if (!visible ||
29532 visible->size() != static_cast<std::size_t>(anchor->nCount))
29533 return std::nullopt;
29534 for (int column = 0; column < anchor->nCount; ++column) {
29535 const std::size_t sourceIndex =
29536 static_cast<std::size_t>(entry.roleIndex) * anchor->nCount + column;
29537 if (!pointsMatch2D(visible->at(static_cast<std::size_t>(column)),
29538 anchor->sourceVertices[sourceIndex])) {
29539 return std::nullopt;
29540 }
29541 }
29542 seenRows[static_cast<std::size_t>(entry.roleIndex)] = true;
29543 } else if (isColumn) {
29544 if (entry.roleIndex < 0 || entry.roleIndex >= anchor->nCount ||
29545 entry.elementIndex != anchor->mCount + entry.roleIndex ||
29546 seenColumns[static_cast<std::size_t>(entry.roleIndex)]) {
29547 return std::nullopt;
29548 }
29549 const auto visible =
29550 collectLinearPolylineVertices(entry.entity, columnsClosed);
29551 if (!visible ||
29552 visible->size() != static_cast<std::size_t>(anchor->mCount))
29553 return std::nullopt;
29554 for (int row = 0; row < anchor->mCount; ++row) {
29555 const std::size_t sourceIndex =
29556 static_cast<std::size_t>(row) * anchor->nCount + entry.roleIndex;
29557 if (!pointsMatch2D(visible->at(static_cast<std::size_t>(row)),
29558 anchor->sourceVertices[sourceIndex])) {
29559 return std::nullopt;
29560 }
29561 }
29562 seenColumns[static_cast<std::size_t>(entry.roleIndex)] = true;
29563 } else {
29564 return std::nullopt;
29565 }
29566
29567 const std::size_t elementIndex =
29568 static_cast<std::size_t>(entry.elementIndex);
29569 if (descriptor.members[elementIndex] != nullptr)
29570 return std::nullopt;
29571 descriptor.members[elementIndex] = &entry;
29572 }
29573
29574 if (std::any_of(
29575 descriptor.members.cbegin(), descriptor.members.cend(),
29576 [](const MeshSidecarEntry *entry) { return entry == nullptr; }) ||
29577 std::any_of(seenRows.cbegin(), seenRows.cend(),
29578 [](bool seen) { return !seen; }) ||
29579 std::any_of(seenColumns.cbegin(), seenColumns.cend(),
29580 [](bool seen) { return !seen; })) {
29581 return std::nullopt;
29582 }
29583 return descriptor;
29584}
29585
29586std::optional<PolyfaceSidecarDescriptor> makePolyfaceSidecarDescriptor(
29587 const std::vector<PolyfaceSidecarEntry> &entries) {
29588 if (entries.empty())
29589 return std::nullopt;
29590
29591 const PolyfaceSidecarEntry *anchor = nullptr;
29592 for (const PolyfaceSidecarEntry &entry : entries) {
29593 if (!entry.sourceVertices.empty()) {
29594 if (anchor != nullptr)
29595 return std::nullopt;
29596 anchor = &entry;
29597 }
29598 }
29599 if (anchor == nullptr || anchor->entity == nullptr ||
29600 anchor->markerCount != 1 || anchor->malformedSchema ||
29601 anchor->stringFieldCount != 1 || anchor->integerFieldCount != 9 ||
29602 anchor->faceIndex != 0 || anchor->vertexCount <= 0 ||
29603 anchor->faceCount <= 0 || (anchor->flags & 0x40) == 0 ||
29604 anchor->faceCount != anchor->originalFaceCount) {
29605 return std::nullopt;
29606 }
29607
29608 const std::size_t sourceVertexCount =
29609 static_cast<std::size_t>(anchor->vertexCount);
29610 const std::size_t faceCount = static_cast<std::size_t>(anchor->faceCount);
29611 if (sourceVertexCount > dwgSafety::MaxOwnedObjectCount ||
29612 faceCount > dwgSafety::MaxOwnedObjectCount - sourceVertexCount ||
29613 entries.size() != faceCount ||
29614 anchor->sourceVertices.size() != sourceVertexCount ||
29615 !matchesSidecarSourceCoordinates(anchor->sourceVertices)) {
29616 return std::nullopt;
29617 }
29618
29619 PolyfaceSidecarDescriptor descriptor;
29620 descriptor.anchor = anchor;
29621 descriptor.sourceVertexCount = sourceVertexCount;
29622 descriptor.compoundVertexCount = sourceVertexCount + faceCount;
29623 descriptor.members.resize(faceCount, nullptr);
29624
29625 for (const PolyfaceSidecarEntry &entry : entries) {
29626 if (entry.entity == nullptr || entry.markerCount != 1 ||
29627 entry.malformedSchema || entry.stringFieldCount != 1 ||
29628 entry.integerFieldCount != 9 ||
29629 entry.polyfaceId != anchor->polyfaceId ||
29630 entry.flags != anchor->flags ||
29631 entry.vertexCount != anchor->vertexCount ||
29632 entry.faceCount != anchor->faceCount ||
29633 entry.originalFaceCount != anchor->originalFaceCount ||
29634 entry.faceIndex < 0 ||
29635 static_cast<std::size_t>(entry.faceIndex) >= faceCount ||
29636 (!entry.sourceVertices.empty() && &entry != anchor) ||
29637 !matchesSidecarPresentation(entry.entity, anchor->entity)) {
29638 return std::nullopt;
29639 }
29640
29641 const int expectedPoints = entry.indices[3] == 0 ? 3 : 4;
29642 const auto visible = collectLinearPolylineVertices(entry.entity, true);
29643 if (!visible || visible->size() != static_cast<std::size_t>(expectedPoints))
29644 return std::nullopt;
29645 for (int index = 0; index < expectedPoints; ++index) {
29646 const std::int64_t signedIndex = entry.indices[index];
29647 const std::int64_t vertexIndex =
29648 signedIndex < 0 ? -signedIndex : signedIndex;
29649 if (signedIndex == 0 || vertexIndex > anchor->vertexCount ||
29650 !pointsMatch2D(visible->at(static_cast<std::size_t>(index)),
29651 anchor->sourceVertices[static_cast<std::size_t>(
29652 vertexIndex - 1)])) {
29653 return std::nullopt;
29654 }
29655 }
29656
29657 const std::size_t faceIndex = static_cast<std::size_t>(entry.faceIndex);
29658 if (descriptor.members[faceIndex] != nullptr)
29659 return std::nullopt;
29660 descriptor.members[faceIndex] = &entry;
29661 }
29662
29663 if (std::any_of(
29664 descriptor.members.cbegin(), descriptor.members.cend(),
29665 [](const PolyfaceSidecarEntry *entry) { return entry == nullptr; })) {
29666 return std::nullopt;
29667 }
29668 return descriptor;
29669}
29670
29671// Walk the entity's drwExtData (XDATA stream) and extract LibreCAD_MLINE
29672// metadata. Returns nullopt if the marker isn't present. The XDATA layout
29673// matches RS_FilterDXFRW::addMLine's emission schema.
29674std::optional<MLineEntry> extractMLineMeta(RS_Entity *e) {
29675 if (!e || !e->hasDrwExtData())
29676 return std::nullopt;
29677 const auto &ext = e->getDrwExtData();
29678 bool inGroup = false;
29679 MLineEntry m;
29680 int seen1000 = 0; // 0 = mlineId, 1 = styleName
29681 int seen1040 = 0; // 0 = scale, 1 = offset
29682 int seen1070 = 0; // 0 = just, 1 = N, 2 = i, 3 = flags
29683 for (const auto &sp : ext) {
29684 if (!sp) {
29685 if (inGroup)
29686 return std::nullopt;
29687 continue;
29688 }
29689 const int code = sp->code();
29690 if (code == 1001) {
29691 const bool wasInGroup = inGroup;
29692 std::string marker;
29693 if (readXDataMarker(sp.get(), marker) != XDataReadResult::Valid) {
29694 if (wasInGroup)
29695 return std::nullopt;
29696 inGroup = false;
29697 continue;
29698 }
29699 inGroup = marker == "LibreCAD_MLINE";
29700 if (inGroup && ++m.markerCount > 1)
29701 return std::nullopt;
29702 continue;
29703 }
29704 if (!inGroup)
29705 continue;
29706 switch (code) {
29707 case 1000: {
29708 std::string text;
29709 if (readXDataString(sp.get(), 1000, text) != XDataReadResult::Valid)
29710 return std::nullopt;
29711 QString s = QString::fromStdString(text);
29712 if (seen1000 == 0)
29713 m.mlineId = s;
29714 else if (seen1000 == 1)
29715 m.styleName = s;
29716 ++seen1000;
29717 break;
29718 }
29719 case 1040: {
29720 if (sp->type() != DRW_Variant::DOUBLE || !std::isfinite(sp->d_val()))
29721 return std::nullopt;
29722 const double d = sp->d_val();
29723 if (seen1040 == 0)
29724 m.scale = d;
29725 else if (seen1040 == 1)
29726 m.offset = d;
29727 ++seen1040;
29728 break;
29729 }
29730 case 1070: {
29731 if (sp->type() != DRW_Variant::INTEGER)
29732 return std::nullopt;
29733 const int v = static_cast<int>(sp->i_val());
29734 if (seen1070 == 0)
29735 m.justification = v;
29736 else if (seen1070 == 1)
29737 m.elementCount = v;
29738 else if (seen1070 == 2)
29739 m.elementIndex = v;
29740 else if (seen1070 == 3)
29741 m.openClosed = v;
29742 ++seen1070;
29743 break;
29744 }
29745 case 1071: {
29746 std::int64_t value;
29747 if (readXDataInteger(sp.get(), 1071, value) != XDataReadResult::Valid ||
29748 value < 0 ||
29749 static_cast<std::uint64_t>(value) >
29750 std::numeric_limits<std::uint32_t>::max())
29751 return std::nullopt;
29752 m.styleHandle = static_cast<std::uint32_t>(value);
29753 break;
29754 }
29755 case 1011: {
29756 // Anchor-only baseline vertex
29757 DRW_Coord coordinate;
29758 if (readXDataCoord(sp.get(), 1011, coordinate) != XDataReadResult::Valid)
29759 return std::nullopt;
29760 m.baselineVerts.push_back(coordinate);
29761 m.isAnchor = true;
29762 break;
29763 }
29764 case 1013: {
29765 DRW_Coord coordinate;
29766 if (readXDataCoord(sp.get(), 1013, coordinate) != XDataReadResult::Valid)
29767 return std::nullopt;
29768 m.miterDirs.push_back(coordinate);
29769 break;
29770 }
29771 default:
29772 break;
29773 }
29774 }
29775 if (m.mlineId.isEmpty() || m.elementCount == 0)
29776 return std::nullopt;
29777 m.entity = e;
29778 return m;
29779}
29780
29781struct MLineSidecarDescriptor {
29782 const MLineEntry *anchor = nullptr;
29783 std::vector<const MLineEntry *> members;
29784};
29785
29786bool matchesMLineSidecarGeometry(const MLineEntry &entry,
29787 const MLineEntry &anchor) {
29788 if (entry.entity == nullptr || anchor.baselineVerts.empty() ||
29789 anchor.baselineVerts.size() != anchor.miterDirs.size()) {
29790 return false;
29791 }
29792
29793 auto *polyline = static_cast<RS_Polyline *>(entry.entity);
29794 const bool closed = (anchor.openClosed & 0x1) != 0;
29795 if (polyline->isClosed() != closed)
29796 return false;
29797 const std::vector<RS_Vector> visible = collectPolylineVertices(polyline);
29798 if (visible.size() != anchor.baselineVerts.size())
29799 return false;
29800
29801 for (std::size_t i = 0; i < visible.size(); ++i) {
29802 const DRW_Coord &base = anchor.baselineVerts[i];
29803 const DRW_Coord &miter = anchor.miterDirs[i];
29804 if (!hasFiniteDwgCoordinate(base) || !hasFiniteDwgCoordinate(miter))
29805 return false;
29806 const double length = std::hypot(miter.x, miter.y);
29807 if (!std::isfinite(length))
29808 return false;
29809 RS_Vector expected{base.x, base.y};
29810 if (length > RS_TOLERANCE1.0e-10) {
29811 expected.x += miter.x / length * entry.offset;
29812 expected.y += miter.y / length * entry.offset;
29813 }
29814 if (!pointsMatch2D(visible[i], DRW_Coord{expected.x, expected.y, 0.0}))
29815 return false;
29816 }
29817 return true;
29818}
29819
29820std::optional<MLineSidecarDescriptor>
29821makeMLineSidecarDescriptor(std::vector<MLineEntry> &entries) {
29822 if (entries.empty())
29823 return std::nullopt;
29824
29825 const int elementCount = entries.front().elementCount;
29826 if (elementCount <= 0 ||
29827 elementCount > std::numeric_limits<std::uint8_t>::max() ||
29828 static_cast<int>(entries.size()) != elementCount) {
29829 return std::nullopt;
29830 }
29831
29832 std::sort(entries.begin(), entries.end(),
29833 [](const MLineEntry &lhs, const MLineEntry &rhs) {
29834 return lhs.elementIndex < rhs.elementIndex;
29835 });
29836 const MLineEntry &anchor = entries.front();
29837 if (anchor.markerCount != 1 || anchor.elementIndex != 0 || !anchor.isAnchor ||
29838 anchor.baselineVerts.empty() ||
29839 anchor.baselineVerts.size() != anchor.miterDirs.size() ||
29840 anchor.baselineVerts.size() > std::numeric_limits<std::uint16_t>::max()) {
29841 return std::nullopt;
29842 }
29843
29844 MLineSidecarDescriptor descriptor;
29845 descriptor.anchor = &anchor;
29846 descriptor.members.reserve(entries.size());
29847 for (int index = 0; index < elementCount; ++index) {
29848 const MLineEntry &entry = entries[static_cast<std::size_t>(index)];
29849 if (entry.entity == nullptr || entry.markerCount != 1 ||
29850 entry.elementIndex != index || entry.elementCount != elementCount ||
29851 entry.styleName != anchor.styleName ||
29852 entry.styleHandle != anchor.styleHandle ||
29853 entry.justification != anchor.justification ||
29854 entry.openClosed != anchor.openClosed || entry.justification < 0 ||
29855 entry.justification > 2 || entry.openClosed < 0 ||
29856 entry.openClosed > std::numeric_limits<std::uint8_t>::max() ||
29857 !std::isfinite(entry.scale) || !std::isfinite(entry.offset) ||
29858 std::abs(entry.scale - anchor.scale) > RS_TOLERANCE1.0e-10 ||
29859 (index != 0 &&
29860 (!entry.baselineVerts.empty() || !entry.miterDirs.empty())) ||
29861 !matchesMLineSidecarGeometry(entry, anchor)) {
29862 return std::nullopt;
29863 }
29864 descriptor.members.push_back(&entry);
29865 }
29866 return descriptor;
29867}
29868} // namespace
29869
29870void RS_FilterDXFRW::reconstructPolylineSidecars(
29871 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
29872 if (container == nullptr)
29873 return;
29874
29875 std::map<QString, std::vector<MeshSidecarEntry>> meshGroups;
29876 std::map<QString, std::vector<PolyfaceSidecarEntry>> polyfaceGroups;
29877
29878 for (RS_Entity *entity :
29879 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
29880 if (entity == nullptr || entity->getFlag(RS2::FlagDeleted) ||
29881 consumed.find(entity) != consumed.end() ||
29882 entity->rtti() != RS2::EntityPolyline) {
29883 continue;
29884 }
29885 bool duplicateMeshMarker = false;
29886 if (auto meshMeta = extractMeshSidecar(entity, &duplicateMeshMarker)) {
29887 meshGroups[meshMeta->meshId].push_back(std::move(*meshMeta));
29888 continue;
29889 }
29890 if (duplicateMeshMarker) {
29891 // Two groups both claim to be this entity's one mesh sidecar, with no
29892 // way to tell which is authoritative -- refuse rather than silently
29893 // re-emitting the entity as an ordinary polyline and picking one.
29894 m_writeFailed = true;
29895 return;
29896 }
29897 bool duplicatePolyfaceMarker = false;
29898 if (auto polyfaceMeta =
29899 extractPolyfaceSidecar(entity, &duplicatePolyfaceMarker)) {
29900 polyfaceGroups[polyfaceMeta->polyfaceId].push_back(
29901 std::move(*polyfaceMeta));
29902 continue;
29903 }
29904 if (duplicatePolyfaceMarker) {
29905 m_writeFailed = true;
29906 return;
29907 }
29908 }
29909
29910 const auto writeDwgPolylineSidecar =
29911 [this](const RS_Entity *anchor, const auto &entries,
29912 DRW_Polyline &polyline, std::size_t expectedVertexListSize,
29913 int expectedVertexCount, int expectedFaceCount) {
29914 // The descriptor is the source of truth for both the physical child
29915 // sequence and the POLYLINE semantic counts. Refuse before opening a
29916 // writer transaction if the emitter ever drifts from that contract.
29917 if (polyline.vertlist.size() != expectedVertexListSize ||
29918 polyline.vertexcount != expectedVertexCount ||
29919 polyline.facecount != expectedFaceCount) {
29920 return false;
29921 }
29922 const auto identityCheckpoint = checkpointDwgWriteIdentity();
29923 const auto rollback = [&]() {
29924 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
29925 restoreDwgWriteIdentity(identityCheckpoint);
29926 for (const auto *entry : entries)
29927 rollbackDwgEntityWrite(entry->entity);
29928 m_writeFailed = true;
29929 };
29930 const bool failedBefore = m_writeFailed;
29931 if (!m_dwgW->writePolyline(&polyline) || failedBefore ||
29932 m_writeFailed) {
29933 rollback();
29934 return false;
29935 }
29936 if (!commitDwgPolylineCompound(anchor)) {
29937 rollback();
29938 return false;
29939 }
29940 for (const auto *entry : entries) {
29941 if (entry->entity != anchor)
29942 rollbackDwgEntityWrite(entry->entity);
29943 }
29944 return true;
29945 };
29946
29947 for (auto &[meshId, entries] : meshGroups) {
29948 (void)meshId;
29949 const auto descriptor = makeMeshSidecarDescriptor(entries);
29950 if (!descriptor)
29951 continue;
29952 const MeshSidecarEntry *anchor = descriptor->anchor;
29953
29954 DRW_Polyline polyline;
29955 polyline.flags = anchor->flags | 0x10;
29956 polyline.vertexcount = anchor->mCount;
29957 polyline.facecount = anchor->nCount;
29958 polyline.smoothM = anchor->smoothM;
29959 polyline.smoothN = anchor->smoothN;
29960 polyline.curvetype = anchor->curveType;
29961 getEntityAttributes(&polyline, anchor->entity);
29962 for (const DRW_Coord &coord : anchor->sourceVertices) {
29963 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
29964 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Mesh);
29965 polyline.addVertex(vertex);
29966 }
29967
29968 const bool emitted =
29969 m_dwgW != nullptr
29970 ? writeDwgPolylineSidecar(anchor->entity, descriptor->members,
29971 polyline, descriptor->sourceVertexCount,
29972 anchor->mCount, anchor->nCount)
29973 : m_dxfW != nullptr &&
29974 noteDxfWrite(m_dxfW->writePolyline(&polyline));
29975 if (!emitted) {
29976 if (m_dwgW != nullptr)
29977 return;
29978 continue;
29979 }
29980 for (const auto *entry : descriptor->members)
29981 consumed.insert(entry->entity);
29982 }
29983
29984 for (auto &[polyfaceId, entries] : polyfaceGroups) {
29985 (void)polyfaceId;
29986 const auto descriptor = makePolyfaceSidecarDescriptor(entries);
29987 if (!descriptor)
29988 continue;
29989 const PolyfaceSidecarEntry *anchor = descriptor->anchor;
29990
29991 DRW_Polyline polyline;
29992 polyline.flags = anchor->flags | 0x40;
29993 polyline.vertexcount = anchor->vertexCount;
29994 polyline.facecount = anchor->faceCount;
29995 getEntityAttributes(&polyline, anchor->entity);
29996
29997 for (const DRW_Coord &coord : anchor->sourceVertices) {
29998 DRW_Vertex vertex(coord.x, coord.y, coord.z, 0.0);
29999 vertex.flags = 0x40 | 0x80;
30000 vertex.setDwgSubtype(DRW_Vertex::DwgSubtype::Polyface);
30001 polyline.addVertex(vertex);
30002 }
30003 for (const auto *entry : descriptor->members) {
30004 DRW_Vertex face;
30005 face.flags = 0x80;
30006 face.vindex1 = entry->indices[0];
30007 face.vindex2 = entry->indices[1];
30008 face.vindex3 = entry->indices[2];
30009 face.vindex4 = entry->indices[3];
30010 face.setDwgSubtype(DRW_Vertex::DwgSubtype::PolyfaceFace);
30011 polyline.addVertex(face);
30012 }
30013
30014 const bool emitted =
30015 m_dwgW != nullptr
30016 ? writeDwgPolylineSidecar(anchor->entity, descriptor->members,
30017 polyline, descriptor->compoundVertexCount,
30018 anchor->vertexCount, anchor->faceCount)
30019 : m_dxfW != nullptr &&
30020 noteDxfWrite(m_dxfW->writePolyline(&polyline));
30021 if (!emitted) {
30022 if (m_dwgW != nullptr)
30023 return;
30024 continue;
30025 }
30026 for (const auto *entry : descriptor->members)
30027 consumed.insert(entry->entity);
30028 }
30029}
30030
30031void RS_FilterDXFRW::reconstructMLines(RS_EntityContainer *container,
30032 std::set<RS_Entity *> &consumed) {
30033 if (!container)
30034 return;
30035
30036 // First pass: collect all polylines carrying the MLINE marker.
30037 std::map<QString, std::vector<MLineEntry>> groups;
30038 for (RS_Entity *e :
30039 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
30040 if (e->getFlag(RS2::FlagDeleted))
30041 continue;
30042 if (e->rtti() != RS2::EntityPolyline)
30043 continue;
30044 auto m = extractMLineMeta(e);
30045 if (!m)
30046 continue;
30047 groups[m->mlineId].push_back(std::move(*m));
30048 }
30049
30050 // Second pass: reconstruct + emit each complete group.
30051 for (auto &[mlineId, entries] : groups) {
30052 (void)mlineId;
30053 if (entries.empty())
30054 continue;
30055 const auto descriptor = makeMLineSidecarDescriptor(entries);
30056 if (!descriptor) {
30057 // Incomplete or edited groups retain their visible fallback geometry.
30058 continue;
30059 }
30060 const MLineEntry *anchor = descriptor->anchor;
30061 const int N = static_cast<int>(descriptor->members.size());
30062
30063 // Build DRW_MLine from anchor metadata + baseline vertices.
30064 DRW_MLine ml;
30065 getEntityAttributes(&ml, anchor->entity);
30066 ml.styleName = anchor->styleName.toStdString();
30067 std::uint32_t styleSourceHandle = anchor->styleHandle;
30068 if (anchor->styleHandle != 0) {
30069 ml.styleHandle = styleSourceHandle;
30070 } else if (m_dwgW && m_graphic != nullptr) {
30071 const auto &metadata = m_graphic->dwgAdvancedMetadata();
30072 for (const auto &record : metadata.mlineStyles()) {
30073 if (record.handle == 0 ||
30074 record.replayState !=
30075 LC_DwgAdvancedMetadata::ReplayState::ReplayAllowed)
30076 continue;
30077 if (QString::compare(QString::fromUtf8(record.name.c_str()),
30078 anchor->styleName, Qt::CaseInsensitive) == 0) {
30079 styleSourceHandle = record.handle;
30080 ml.styleHandle = styleSourceHandle;
30081 break;
30082 }
30083 }
30084 }
30085 std::uint32_t styleOutputHandle = styleSourceHandle;
30086 if (m_dwgW != nullptr && styleSourceHandle != 0) {
30087 const DwgWriteReferenceStatus status =
30088 resolveDwgWriteObjectHandle(styleSourceHandle, &styleOutputHandle);
30089 const bool resolved = status == DwgWriteReferenceStatus::Resolved ||
30090 status == DwgWriteReferenceStatus::Reserved ||
30091 status == DwgWriteReferenceStatus::Selected ||
30092 status == DwgWriteReferenceStatus::Committed;
30093 if (!resolved || styleOutputHandle == 0)
30094 continue;
30095 ml.styleHandle = styleOutputHandle;
30096 }
30097 ml.scale = anchor->scale;
30098 ml.justification = static_cast<std::uint8_t>(anchor->justification);
30099 ml.openClosed = anchor->openClosed;
30100 ml.numLines = static_cast<std::uint8_t>(N);
30101 ml.numVerts = static_cast<std::uint16_t>(anchor->baselineVerts.size());
30102 if (!anchor->baselineVerts.empty()) {
30103 ml.basePoint = anchor->baselineVerts.front();
30104 }
30105 ml.layer = anchor->entity->getLayer()
30106 ? anchor->entity->getLayer()->getName().toStdString()
30107 : std::string{"0"};
30108 const size_t V = anchor->baselineVerts.size();
30109 const size_t M = anchor->miterDirs.size();
30110 ml.vertlist.reserve(V);
30111 for (size_t i = 0; i < V; ++i) {
30112 DRW_MLineVertex v;
30113 v.position = anchor->baselineVerts[i];
30114 if (i < M)
30115 v.miterDir = anchor->miterDirs[i];
30116 // Default segment params: a single zero parameter per element
30117 // (sufficient for a continuous unbroken multiline).
30118 v.segParms.assign(N, std::vector<double>{0.0});
30119 v.areaFillParms.assign(N, std::vector<double>{});
30120 ml.vertlist.push_back(std::move(v));
30121 }
30122
30123 bool emitted = false;
30124 if (m_dwgW) {
30125 const auto identityCheckpoint = checkpointDwgWriteIdentity();
30126 const std::uint64_t transaction =
30127 m_dwgW->beginDwgWriteTransaction(false, false, false);
30128 const auto rollback = [&]() {
30129 if (transaction != 0)
30130 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
30131 restoreDwgWriteIdentity(identityCheckpoint);
30132 for (const MLineEntry *member : descriptor->members)
30133 rollbackDwgEntityWrite(member->entity);
30134 m_writeFailed = true;
30135 };
30136 if (transaction == 0 || !m_dwgW->writeMLine(&ml) || m_writeFailed) {
30137 rollback();
30138 } else {
30139 const std::uint32_t outputHandle =
30140 m_dwgW->getLastSuccessfulEntityHandle();
30141 const DwgWriteReferenceReceipt styleReceipt{
30142 DwgWriteSourceKind::Entity,
30143 anchor->entity->sourceHandle(),
30144 outputHandle,
30145 "MLINE.STYLE",
30146 0,
30147 DwgWriteSourceKind::Object,
30148 styleSourceHandle,
30149 styleOutputHandle,
30150 true,
30151 DRW::DwgHardPointer};
30152 const bool committed =
30153 outputHandle != 0 && recordDwgEntityFrame(outputHandle) &&
30154 (anchor->entity->sourceHandle() == 0
30155 ? commitDwgGeneratedEntityWrite(outputHandle)
30156 : commitDwgEntityWrite(anchor->entity)) &&
30157 (styleSourceHandle == 0 ||
30158 recordDwgWriteReferenceReceipt(styleReceipt)) &&
30159 m_dwgW->commitDwgWriteTransaction(transaction);
30160 if (!committed) {
30161 rollback();
30162 } else {
30163 for (const MLineEntry *member : descriptor->members) {
30164 if (member != anchor)
30165 rollbackDwgEntityWrite(member->entity);
30166 }
30167 emitted = true;
30168 }
30169 }
30170 } else if (m_dxfW) {
30171 // dxfRW::writeMLine() returns true for "left out" (MLINE has no R12
30172 // record) the same as for "written": that would mark the member
30173 // polylines consumed below and drop them, instead of falling back to
30174 // writing them as plain entities. Take that fallback here instead.
30175 emitted =
30176 m_dxfW->getVersion() > DRW::AC1009 && noteDxfWrite(m_dxfW->writeMLine(&ml));
30177 }
30178
30179 if (!emitted) {
30180 if (m_dwgW != nullptr)
30181 return;
30182 continue;
30183 }
30184 for (const MLineEntry *member : descriptor->members)
30185 consumed.insert(member->entity);
30186 }
30187}
30188
30189namespace {
30190// Find a single F2 type-fidelity sidecar on an RS entity's XDATA, if present.
30191// The marker codes match those written by appendTypeSidecar() at read time.
30192std::optional<TypedConversionSidecar>
30193extractTypedConversionSidecar(RS_Entity *e) {
30194 return parseTypedConversionSidecar(e);
30195}
30196
30197struct HatchExtrusionSidecar {
30198 double elevation = 0.0;
30199 DRW_Coord extrusion{0.0, 0.0, 1.0};
30200};
30201
30202std::optional<HatchExtrusionSidecar>
30203extractHatchExtrusionSidecar(const RS_Entity *entity) {
30204 if (!entity || !entity->hasDrwExtData())
30205 return std::nullopt;
30206
30207 HatchExtrusionSidecar sidecar;
30208 bool inGroup = false;
30209 bool gotElevation = false;
30210 bool gotExtrusion = false;
30211 for (const auto &value : entity->getDrwExtData()) {
30212 if (!value)
30213 continue;
30214 if (value->code() == 1001) {
30215 std::string marker;
30216 inGroup = readXDataString(value.get(), 1001, marker) ==
30217 XDataReadResult::Valid &&
30218 marker == kHatchExtrusionMarker;
30219 continue;
30220 }
30221 if (!inGroup)
30222 continue;
30223 if (value->code() == 1010) {
30224 DRW_Coord point;
30225 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
30226 sidecar.elevation = point.z;
30227 gotElevation = true;
30228 }
30229 } else if (value->code() == 1011) {
30230 DRW_Coord normal;
30231 if (readXDataCoord(value.get(), 1011, normal) == XDataReadResult::Valid) {
30232 sidecar.extrusion = normal;
30233 gotExtrusion = true;
30234 }
30235 }
30236 }
30237 if (!gotElevation || !gotExtrusion)
30238 return std::nullopt;
30239 return sidecar;
30240}
30241
30242void removeHatchExtrusionSidecar(DRW_Entity &entity) {
30243 std::vector<std::shared_ptr<DRW_Variant>> filtered;
30244 bool inGroup = false;
30245 for (const auto &value : entity.extData) {
30246 if (value && value->code() == 1001) {
30247 std::string marker;
30248 inGroup = readXDataString(value.get(), 1001, marker) ==
30249 XDataReadResult::Valid &&
30250 marker == kHatchExtrusionMarker;
30251 if (inGroup)
30252 continue;
30253 }
30254 if (!inGroup)
30255 filtered.push_back(value);
30256 }
30257 entity.extData = std::move(filtered);
30258}
30259
30260struct ImageFrameSidecar {
30261 DRW_Coord insertion;
30262 DRW_Coord uVector;
30263 DRW_Coord vVector;
30264};
30265
30266std::optional<ImageFrameSidecar>
30267extractImageFrameSidecar(const RS_Entity *entity) {
30268 if (!entity || !entity->hasDrwExtData())
30269 return std::nullopt;
30270
30271 ImageFrameSidecar sidecar;
30272 bool gotInsertion = false;
30273 bool gotUVector = false;
30274 bool gotVVector = false;
30275 bool inGroup = false;
30276 for (const auto &value : entity->getDrwExtData()) {
30277 if (!value)
30278 continue;
30279 if (value->code() == 1001) {
30280 std::string marker;
30281 inGroup = readXDataString(value.get(), 1001, marker) ==
30282 XDataReadResult::Valid &&
30283 marker == kImageFrameMarker;
30284 continue;
30285 }
30286 if (!inGroup || value->code() < 1010 || value->code() > 1012)
30287 continue;
30288 DRW_Coord point;
30289 if (readXDataCoord(value.get(), value->code(), point) !=
30290 XDataReadResult::Valid)
30291 continue;
30292 if (value->code() == 1010) {
30293 sidecar.insertion = point;
30294 gotInsertion = true;
30295 } else if (value->code() == 1011) {
30296 sidecar.uVector = point;
30297 gotUVector = true;
30298 } else {
30299 sidecar.vVector = point;
30300 gotVVector = true;
30301 }
30302 }
30303 if (!gotInsertion || !gotUVector || !gotVVector)
30304 return std::nullopt;
30305 return sidecar;
30306}
30307
30308void removeImageFrameSidecar(DRW_Entity &entity) {
30309 std::vector<std::shared_ptr<DRW_Variant>> filtered;
30310 bool inGroup = false;
30311 for (const auto &value : entity.extData) {
30312 if (value && value->code() == 1001) {
30313 std::string marker;
30314 inGroup = readXDataString(value.get(), 1001, marker) ==
30315 XDataReadResult::Valid &&
30316 marker == kImageFrameMarker;
30317 if (inGroup)
30318 continue;
30319 }
30320 if (!inGroup)
30321 filtered.push_back(value);
30322 }
30323 entity.extData = std::move(filtered);
30324}
30325
30326struct TextOcsSidecar {
30327 double baseElevation = 0.0;
30328 double alignmentElevation = 0.0;
30329 DRW_Coord extrusion{0.0, 0.0, 1.0};
30330 double thickness = 0.0;
30331};
30332
30333std::optional<TextOcsSidecar> extractTextOcsSidecar(const RS_Entity *entity) {
30334 if (!entity || !entity->hasDrwExtData())
30335 return std::nullopt;
30336 TextOcsSidecar sidecar;
30337 bool gotBase = false;
30338 bool gotAlignment = false;
30339 bool gotExtrusion = false;
30340 bool gotThickness = false;
30341 bool inGroup = false;
30342 for (const auto &value : entity->getDrwExtData()) {
30343 if (!value)
30344 continue;
30345 if (value->code() == 1001) {
30346 std::string marker;
30347 inGroup = readXDataString(value.get(), 1001, marker) ==
30348 XDataReadResult::Valid &&
30349 marker == kTextOcsMarker;
30350 continue;
30351 }
30352 if (!inGroup)
30353 continue;
30354 if (value->code() == 1010) {
30355 DRW_Coord point;
30356 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
30357 sidecar.baseElevation = point.z;
30358 gotBase = true;
30359 }
30360 } else if (value->code() == 1011) {
30361 DRW_Coord point;
30362 if (readXDataCoord(value.get(), 1011, point) == XDataReadResult::Valid) {
30363 sidecar.alignmentElevation = point.z;
30364 gotAlignment = true;
30365 }
30366 } else if (value->code() == 1012) {
30367 DRW_Coord normal;
30368 if (readXDataCoord(value.get(), 1012, normal) == XDataReadResult::Valid) {
30369 sidecar.extrusion = normal;
30370 gotExtrusion = true;
30371 }
30372 } else if (value->code() == 1040) {
30373 double thickness = 0.0;
30374 if (readXDataDouble(value.get(), 1040, thickness) ==
30375 XDataReadResult::Valid) {
30376 sidecar.thickness = thickness;
30377 gotThickness = true;
30378 }
30379 }
30380 }
30381 if (!gotBase || !gotAlignment || !gotExtrusion || !gotThickness)
30382 return std::nullopt;
30383 return sidecar;
30384}
30385
30386void removeTextOcsSidecar(DRW_Entity &entity) {
30387 std::vector<std::shared_ptr<DRW_Variant>> filtered;
30388 bool inGroup = false;
30389 for (const auto &value : entity.extData) {
30390 if (value && value->code() == 1001) {
30391 std::string marker;
30392 inGroup = readXDataString(value.get(), 1001, marker) ==
30393 XDataReadResult::Valid &&
30394 marker == kTextOcsMarker;
30395 if (inGroup)
30396 continue;
30397 }
30398 if (!inGroup)
30399 filtered.push_back(value);
30400 }
30401 entity.extData = std::move(filtered);
30402}
30403
30404struct MTextOcsSidecar {
30405 double elevation = 0.0;
30406 DRW_Coord xAxis;
30407 DRW_Coord extrusion{0.0, 0.0, 1.0};
30408};
30409
30410std::optional<MTextOcsSidecar> extractMTextOcsSidecar(const RS_Entity *entity) {
30411 if (!entity || !entity->hasDrwExtData())
30412 return std::nullopt;
30413 MTextOcsSidecar sidecar;
30414 bool gotElevation = false;
30415 bool gotXAxis = false;
30416 bool gotExtrusion = false;
30417 bool inGroup = false;
30418 for (const auto &value : entity->getDrwExtData()) {
30419 if (!value)
30420 continue;
30421 if (value->code() == 1001) {
30422 std::string marker;
30423 inGroup = readXDataString(value.get(), 1001, marker) ==
30424 XDataReadResult::Valid &&
30425 marker == kMTextOcsMarker;
30426 continue;
30427 }
30428 if (!inGroup)
30429 continue;
30430 if (value->code() == 1010) {
30431 DRW_Coord point;
30432 if (readXDataCoord(value.get(), 1010, point) == XDataReadResult::Valid) {
30433 sidecar.elevation = point.z;
30434 gotElevation = true;
30435 }
30436 } else if (value->code() == 1011) {
30437 DRW_Coord axis;
30438 if (readXDataCoord(value.get(), 1011, axis) == XDataReadResult::Valid) {
30439 sidecar.xAxis = axis;
30440 gotXAxis = true;
30441 }
30442 } else if (value->code() == 1012) {
30443 DRW_Coord normal;
30444 if (readXDataCoord(value.get(), 1012, normal) == XDataReadResult::Valid) {
30445 sidecar.extrusion = normal;
30446 gotExtrusion = true;
30447 }
30448 }
30449 }
30450 if (!gotElevation || !gotXAxis || !gotExtrusion)
30451 return std::nullopt;
30452 return sidecar;
30453}
30454
30455void removeMTextOcsSidecar(DRW_Entity &entity) {
30456 std::vector<std::shared_ptr<DRW_Variant>> filtered;
30457 bool inGroup = false;
30458 for (const auto &value : entity.extData) {
30459 if (value && value->code() == 1001) {
30460 std::string marker;
30461 inGroup = readXDataString(value.get(), 1001, marker) ==
30462 XDataReadResult::Valid &&
30463 marker == kMTextOcsMarker;
30464 if (inGroup)
30465 continue;
30466 }
30467 if (!inGroup)
30468 filtered.push_back(value);
30469 }
30470 entity.extData = std::move(filtered);
30471}
30472} // namespace
30473
30474void RS_FilterDXFRW::reconstructTypedConversions(
30475 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
30476 if (!container)
30477 return;
30478
30479 for (RS_Entity *e :
30480 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
30481 if (e->getFlag(RS2::FlagDeleted))
30482 continue;
30483 if (consumed.find(e) != consumed.end())
30484 continue;
30485 auto meta = extractTypedConversionSidecar(e);
30486 if (!meta) {
30487 if (hasDwgTypedConversionSidecar(e))
30488 m_writeFailed = true;
30489 continue;
30490 }
30491
30492 // Build the native DRW entity with full geometry. Attributes (layer,
30493 // color, linetype, ...) come from getEntityAttributes(); afterwards the
30494 // F2 sidecar markers are stripped so the rebuilt native type does not
30495 // carry them back out as XDATA.
30496 auto emitNative = [&](DRW_Entity *drw) {
30497 getEntityAttributes(drw, e);
30498 // Drop the F2 sidecar groups from the re-emitted XDATA so the rebuilt
30499 // native type does not carry the marker back out.
30500 std::vector<std::shared_ptr<DRW_Variant>> filtered;
30501 bool inGroup = false;
30502 for (const auto &v : drw->extData) {
30503 if (v && v->code() == 1001) {
30504 std::string app;
30505 inGroup =
30506 readXDataString(v.get(), 1001, app) == XDataReadResult::Valid &&
30507 (app == kPointExtrusionMarker || app == kLineExtrusionMarker ||
30508 app == kCircleOcsMarker || app == kArcOcsMarker ||
30509 app == k3DLineMarker || app == kRayMarker ||
30510 app == kXlineMarker || app == kTraceMarker ||
30511 app == kSolidMarker || app == k3dFaceMarker);
30512 if (inGroup)
30513 continue;
30514 }
30515 if (inGroup)
30516 continue;
30517 filtered.push_back(v);
30518 }
30519 drw->extData = std::move(filtered);
30520 };
30521 const auto writeDwgTyped = [this, e](const auto &write) {
30522 if (m_dwgW == nullptr)
30523 return false;
30524
30525 const auto identityCheckpoint = checkpointDwgWriteIdentity();
30526 const std::uint64_t transaction =
30527 m_dwgW->beginDwgWriteTransaction(false, false, false);
30528 if (transaction == 0) {
30529 m_writeFailed = true;
30530 return false;
30531 }
30532 const auto rollback = [&]() {
30533 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
30534 restoreDwgWriteIdentity(identityCheckpoint);
30535 rollbackDwgEntityWrite(e);
30536 m_writeFailed = true;
30537 };
30538
30539 const bool failedBefore = m_writeFailed;
30540 if (!write() || failedBefore || m_writeFailed) {
30541 rollback();
30542 return false;
30543 }
30544
30545 const std::uint32_t outputHandle =
30546 m_dwgW->getLastSuccessfulEntityHandle();
30547 if (!recordDwgEntityFrame(outputHandle)) {
30548 rollback();
30549 return false;
30550 }
30551 const bool committed = e->sourceHandle() == 0
30552 ? commitDwgGeneratedEntityWrite(outputHandle)
30553 : commitDwgEntityWrite(e);
30554 if (!committed || !m_dwgW->commitDwgWriteTransaction(transaction)) {
30555 rollback();
30556 return false;
30557 }
30558 return true;
30559 };
30560
30561 if (meta->marker == kPointExtrusionMarker) {
30562 auto *source = static_cast<RS_Point *>(e);
30563 DRW_Point point;
30564 point.basePoint = meta->coords[0];
30565 point.basePoint.x = source->getStartpoint().x;
30566 point.basePoint.y = source->getStartpoint().y;
30567 point.extPoint = meta->coords[1];
30568 point.thickness = meta->thickness;
30569 point.xAxisAngle = meta->pointXAxisAngle;
30570 emitNative(&point);
30571 const bool emitted =
30572 m_dwgW
30573 ? writeDwgTyped(
30574 [&point, this] { return m_dwgW->writePoint(&point); })
30575 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writePoint(&point));
30576 if (emitted)
30577 consumed.insert(e);
30578 } else if (meta->marker == k3DLineMarker) {
30579 auto *source = static_cast<RS_Line *>(e);
30580 DRW_3DLine line;
30581 line.basePoint = meta->coords[0];
30582 line.secPoint = meta->coords[1];
30583 line.basePoint.x = source->getStartpoint().x;
30584 line.basePoint.y = source->getStartpoint().y;
30585 line.secPoint.x = source->getEndpoint().x;
30586 line.secPoint.y = source->getEndpoint().y;
30587 line.extPoint = meta->coords[2];
30588 line.thickness = meta->thickness;
30589 emitNative(&line);
30590 // dxfRW::write3DLine() returns true for "left out" (no 3DLINE record
30591 // below R2000) the same as for "written": that would consume the
30592 // source RS_Line below and drop it, instead of falling back to
30593 // writing it as a plain LINE. Take that fallback here instead.
30594 const bool emitted =
30595 m_dwgW
30596 ? writeDwgTyped(
30597 [&line, this] { return m_dwgW->write3DLine(&line); })
30598 : m_dxfW != nullptr && m_dxfW->getVersion() >= DRW::AC1015 &&
30599 noteDxfWrite(m_dxfW->write3DLine(&line));
30600 if (emitted)
30601 consumed.insert(e);
30602 } else if (meta->marker == kLineExtrusionMarker) {
30603 auto *source = static_cast<RS_Line *>(e);
30604 DRW_Line line;
30605 line.basePoint = meta->coords[0];
30606 line.secPoint = meta->coords[1];
30607 line.basePoint.x = source->getStartpoint().x;
30608 line.basePoint.y = source->getStartpoint().y;
30609 line.secPoint.x = source->getEndpoint().x;
30610 line.secPoint.y = source->getEndpoint().y;
30611 line.extPoint = meta->coords[2];
30612 line.thickness = meta->thickness;
30613 emitNative(&line);
30614 const bool emitted =
30615 m_dwgW ? writeDwgTyped(
30616 [&line, this] { return m_dwgW->writeLine(&line); })
30617 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeLine(&line));
30618 if (emitted)
30619 consumed.insert(e);
30620 } else if (meta->marker == kCircleOcsMarker) {
30621 auto *source = static_cast<RS_Circle *>(e);
30622 DRW_Circle circle;
30623 circle.basePoint = meta->coords[0];
30624 circle.basePoint.x = source->getCenter().x;
30625 circle.basePoint.y = source->getCenter().y;
30626 circle.radious = source->getRadius();
30627 circle.extPoint = meta->coords[1];
30628 if (circle.extPoint.x != 0.0 || circle.extPoint.y != 0.0 ||
30629 (circle.extPoint.z != 1.0 && circle.extPoint.z != -1.0)) {
30630 continue;
30631 }
30632 circle.thickness = meta->thickness;
30633 if (circle.extPoint.z < 0.0) {
30634 circle.basePoint.x = -circle.basePoint.x;
30635 }
30636 emitNative(&circle);
30637 const bool emitted =
30638 m_dwgW
30639 ? writeDwgTyped(
30640 [&circle, this] { return m_dwgW->writeCircle(&circle); })
30641 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeCircle(&circle));
30642 if (emitted)
30643 consumed.insert(e);
30644 } else if (meta->marker == kArcOcsMarker) {
30645 auto *source = static_cast<RS_Arc *>(e);
30646 DRW_Arc arc;
30647 arc.basePoint = meta->coords[0];
30648 arc.basePoint.x = source->getCenter().x;
30649 arc.basePoint.y = source->getCenter().y;
30650 arc.radious = source->getRadius();
30651 arc.extPoint = meta->coords[1];
30652 if (arc.extPoint.x != 0.0 || arc.extPoint.y != 0.0 ||
30653 (arc.extPoint.z != 1.0 && arc.extPoint.z != -1.0)) {
30654 continue;
30655 }
30656 arc.thickness = meta->thickness;
30657 const double startAngle =
30658 source->isReversed() ? source->getAngle2() : source->getAngle1();
30659 const double endAngle =
30660 source->isReversed() ? source->getAngle1() : source->getAngle2();
30661 if (arc.extPoint.z < 0.0) {
30662 arc.basePoint.x = -arc.basePoint.x;
30663 arc.staangle = M_PI3.14159265358979323846 - endAngle;
30664 arc.endangle = M_PI3.14159265358979323846 - startAngle;
30665 } else {
30666 arc.staangle = startAngle;
30667 arc.endangle = endAngle;
30668 }
30669 emitNative(&arc);
30670 const bool emitted =
30671 m_dwgW
30672 ? writeDwgTyped([&arc, this] { return m_dwgW->writeArc(&arc); })
30673 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeArc(&arc));
30674 if (emitted)
30675 consumed.insert(e);
30676 } else if (meta->marker == kRayMarker) {
30677 DRW_Ray ray;
30678 ray.basePoint = meta->coords[0];
30679 ray.secPoint = meta->coords[1];
30680 emitNative(&ray);
30681 const bool emitted =
30682 m_dwgW
30683 ? writeDwgTyped([&ray, this] { return m_dwgW->writeRay(&ray); })
30684 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeRay(&ray));
30685 if (emitted)
30686 consumed.insert(e);
30687 } else if (meta->marker == kXlineMarker) {
30688 DRW_Xline xline;
30689 xline.basePoint = meta->coords[0];
30690 xline.secPoint = meta->coords[1];
30691 emitNative(&xline);
30692 const bool emitted =
30693 m_dwgW
30694 ? writeDwgTyped(
30695 [&xline, this] { return m_dwgW->writeXline(&xline); })
30696 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeXline(&xline));
30697 if (emitted)
30698 consumed.insert(e);
30699 } else if (meta->marker == kTraceMarker) {
30700 DRW_Trace trace;
30701 trace.basePoint = meta->coords[0];
30702 trace.secPoint = meta->coords[1];
30703 trace.thirdPoint = meta->coords[2];
30704 trace.fourPoint = meta->coords[3];
30705 trace.thickness = meta->thickness;
30706 emitNative(&trace);
30707 const bool emitted =
30708 m_dwgW
30709 ? writeDwgTyped(
30710 [&trace, this] { return m_dwgW->writeTrace(&trace); })
30711 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeTrace(&trace));
30712 if (emitted)
30713 consumed.insert(e);
30714 } else if (meta->marker == kSolidMarker) {
30715 DRW_Solid solid;
30716 solid.basePoint = meta->coords[0];
30717 solid.secPoint = meta->coords[1];
30718 solid.thirdPoint = meta->coords[2];
30719 solid.fourPoint = meta->coords[3];
30720 solid.thickness = meta->thickness;
30721 emitNative(&solid);
30722 const bool emitted =
30723 m_dwgW
30724 ? writeDwgTyped(
30725 [&solid, this] { return m_dwgW->writeSolid(&solid); })
30726 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->writeSolid(&solid));
30727 if (emitted)
30728 consumed.insert(e);
30729 } else if (meta->marker == k3dFaceMarker) {
30730 DRW_3Dface face;
30731 face.basePoint = meta->coords[0];
30732 face.secPoint = meta->coords[1];
30733 face.thirdPoint = meta->coords[2];
30734 face.fourPoint = meta->coords[3];
30735 face.invisibleflag = meta->flag;
30736 emitNative(&face);
30737 const bool emitted =
30738 m_dwgW
30739 ? writeDwgTyped(
30740 [&face, this] { return m_dwgW->write3dface(&face); })
30741 : m_dxfW != nullptr && noteDxfWrite(m_dxfW->write3dface(&face));
30742 if (emitted)
30743 consumed.insert(e);
30744 }
30745 }
30746}
30747
30748bool RS_FilterDXFRW::reconstructSingleFrameSidecars(
30749 RS_EntityContainer *container, std::set<RS_Entity *> &consumed) {
30750 reconstructTypedConversions(container, consumed);
30751 if (m_writeFailed)
30752 return false;
30753 reconstructUnderlays(container, consumed);
30754 return !m_writeFailed;
30755}
30756
30757void RS_FilterDXFRW::writeEntity(RS_Entity *e) {
30758 // A block attribute's text is written by its INSERT (writeInsert()), or not
30759 // at all (buildInsertAttribPlan()).
30760 if (e == nullptr || hasNoOwnRecord(e))
30761 return;
30762 DwgEntityProductionResult production = DwgEntityProductionResult::SingleFrame;
30763#ifdef DWGSUPPORT1
30764 const bool isDwgWrite = m_dwgW != nullptr;
30765 const bool failedBefore = isDwgWrite && m_writeFailed;
30766 const std::size_t successfulWritesBefore =
30767 isDwgWrite ? m_dwgW->getSuccessfulEntityWriteCount() : 0;
30768 const auto identityCheckpoint =
30769 isDwgWrite ? checkpointDwgWriteIdentity()
30770 : RS_FilterDXFRW_DwgWriteIdentityRegistry::
30771 DwgWriteIdentityCheckpoint{};
30772#endif
30773 switch (e->rtti()) {
30774 case RS2::EntityPoint:
30775 writePoint(static_cast<RS_Point *>(e));
30776 break;
30777 case RS2::EntityLine:
30778 writeLine(static_cast<RS_Line *>(e));
30779 break;
30780 case RS2::EntityCircle:
30781 writeCircle(static_cast<RS_Circle *>(e));
30782 break;
30783 case RS2::EntityArc:
30784 writeArc(static_cast<RS_Arc *>(e));
30785 break;
30786 case RS2::EntitySolid:
30787 writeSolid(static_cast<RS_Solid *>(e));
30788 break;
30789 case RS2::EntityEllipse:
30790 writeEllipse(static_cast<RS_Ellipse *>(e));
30791 break;
30792 case RS2::EntityHyperbola:
30793 writeHyperbola(static_cast<LC_Hyperbola *>(e));
30794 break;
30795 case RS2::EntityPolyline:
30796 writeLWPolyline(static_cast<RS_Polyline *>(e));
30797 break;
30798 case RS2::EntitySpline:
30799 writeSpline(static_cast<RS_Spline *>(e));
30800 break;
30801 case RS2::EntitySplinePoints:
30802 writeSplinePoints(static_cast<LC_SplinePoints *>(e));
30803 break;
30804 case RS2::EntityParabola:
30805 writeParabola(static_cast<LC_Parabola *>(e));
30806 break;
30807 case RS2::EntityDimArrowBlock: {
30808 // An arrowhead is not itself a persistent, saved entity kind (comment on
30809 // LC_DimArrowPoly::doGetNearestPointOnEntity) -- write whatever real
30810 // primitives exportPrimitives() gives for it (only the blockless default
30811 // arrow has any; every other kind resolves to a real RS_Insert well
30812 // before writeEntity ever sees an EntityDimArrowBlock, so long as its
30813 // block exists) with its own pen, on the dimension's own layer, in place
30814 // of it. The primitives have no parent to inherit a layer through
30815 // (getLayer() would return nullptr), so resolve the arrow's own layer
30816 // here instead -- the same layer its sibling LINE/MTEXT/INSERT children
30817 // already write, and the one AutoCAD's own SOLID would be on.
30818 auto *arrow = static_cast<LC_DimArrow *>(e);
30819 const RS_Pen pen = arrow->getPen(false);
30820 RS_Layer *layer = arrow->getLayer();
30821 for (auto &primitive : arrow->exportPrimitives()) {
30822 primitive->setPen(pen);
30823 primitive->setLayer(layer);
30824 writeEntity(primitive.get());
30825 }
30826 break;
30827 }
30828 // case RS2::EntityVertex:
30829 // break;
30830 case RS2::EntityInsert:
30831 writeInsert(static_cast<RS_Insert *>(e));
30832 break;
30833 case RS2::EntityMText:
30834 writeMText(static_cast<RS_MText *>(e));
30835 break;
30836 case RS2::EntityText:
30837 writeText(static_cast<RS_Text *>(e));
30838 break;
30839 case RS2::EntityDimLinear:
30840 case RS2::EntityDimOrdinate:
30841 case RS2::EntityDimAligned:
30842 case RS2::EntityDimAngular:
30843 case RS2::EntityDimRadial:
30844 case RS2::EntityDimDiametric:
30845 case RS2::EntityDimArc:
30846 writeDimension(static_cast<RS_Dimension *>(e));
30847 break;
30848 case RS2::EntityDimLeader:
30849 writeLeader(static_cast<RS_Leader *>(e));
30850 break;
30851 case RS2::EntityTolerance:
30852 writeTolerance(static_cast<LC_Tolerance *>(e));
30853 break;
30854 case RS2::EntityHatch:
30855 writeHatch(static_cast<RS_Hatch *>(e));
30856 break;
30857 case RS2::EntityImage:
30858 writeImage(static_cast<RS_Image *>(e));
30859 break;
30860 case RS2::EntityWipeout:
30861 writeWipeout(static_cast<LC_Wipeout *>(e));
30862 break;
30863 case RS2::EntityMLeader:
30864 production = writeMLeader(static_cast<LC_MLeader *>(e));
30865 break;
30866 default:
30867 break;
30868 }
30869#ifdef DWGSUPPORT1
30870 if (isDwgWrite) {
30871 if (production == DwgEntityProductionResult::SelfCommittedCompound) {
30872 if (failedBefore || m_writeFailed ||
30873 m_dwgW->getSuccessfulEntityWriteCount() <= successfulWritesBefore) {
30874 restoreDwgWriteIdentity(identityCheckpoint);
30875 rollbackDwgEntityWrite(e);
30876 m_writeFailed = true;
30877 }
30878 return;
30879 }
30880 if (production == DwgEntityProductionResult::Failed) {
30881 restoreDwgWriteIdentity(identityCheckpoint);
30882 rollbackDwgEntityWrite(e);
30883 m_writeFailed = true;
30884 return;
30885 }
30886 const bool wroteEntity =
30887 m_dwgW->getSuccessfulEntityWriteCount() > successfulWritesBefore;
30888 if (wroteEntity && !failedBefore && !m_writeFailed) {
30889 const std::uint32_t outputHandle =
30890 m_dwgW->getLastSuccessfulEntityHandle();
30891 if (!recordDwgEntityFrame(outputHandle)) {
30892 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
30893 restoreDwgWriteIdentity(identityCheckpoint);
30894 rollbackDwgEntityWrite(e);
30895 m_writeFailed = true;
30896 return;
30897 }
30898 if (!commitDwgEntityWrite(e)) {
30899 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
30900 restoreDwgWriteIdentity(identityCheckpoint);
30901 rollbackDwgEntityWrite(e);
30902 m_writeFailed = true;
30903 return;
30904 }
30905 if (e->sourceHandle() == 0 &&
30906 !commitDwgGeneratedEntityWrite(outputHandle)) {
30907 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
30908 restoreDwgWriteIdentity(identityCheckpoint);
30909 rollbackDwgEntityWrite(e);
30910 m_writeFailed = true;
30911 return;
30912 }
30913 } else if (wroteEntity) {
30914 // A direct writer call can finish its frame before a later
30915 // reference check latches m_writeFailed. Remove that frame before
30916 // the next callback can observe it.
30917 (void)m_dwgW->rollbackLastDwgObjectWrite(false);
30918 restoreDwgWriteIdentity(identityCheckpoint);
30919 rollbackDwgEntityWrite(e);
30920 m_writeFailed = true;
30921 } else {
30922 rollbackDwgEntityWrite(e);
30923 }
30924 }
30925#endif
30926}
30927
30928void RS_FilterDXFRW::reconstructUnderlays(RS_EntityContainer *container,
30929 std::set<RS_Entity *> &consumed) {
30930 if (!container)
30931 return;
30932 // Single-pass: each polyline carrying LibreCAD_UNDERLAY XDATA
30933 // reconstructs ONE DRW_Underlay (no group/sibling matching like MLINE).
30934 for (RS_Entity *e :
30935 lc::LC_ContainerTraverser{*container, RS2::ResolveNone}.entities()) {
30936 if (e->getFlag(RS2::FlagDeleted))
30937 continue;
30938 if (e->rtti() != RS2::EntityPolyline)
30939 continue;
30940 if (!hasDwgUnderlaySidecar(e))
30941 continue;
30942
30943 DwgUnderlaySidecar sidecar;
30944 if (!parseDwgUnderlaySidecar(e, sidecar)) {
30945 m_writeFailed = true;
30946 continue;
30947 }
30948 DRW_Underlay u = std::move(sidecar.value);
30949 if (u.definitionHandle == 0) {
30950 m_writeFailed = true;
30951 continue;
30952 }
30953 if (m_dwgW != nullptr && m_dwgW->getVersion() <= DRW::AC1021 &&
30954 (!u.inverseClipBoundary.empty() || (u.flags & 0x10) != 0)) {
30955 m_writeFailed = true;
30956 continue;
30957 }
30958 getEntityAttributes(&u, e);
30959
30960 if (!sidecar.hasOriginalClip && !sidecar.noClip) {
30961 // Legacy sidecars did not carry the source clip array. Reconstruct it
30962 // from the preview polyline, but reject a singular transform instead of
30963 // silently replacing a zero scale with one.
30964 if (std::abs(u.scale.x) <= RS_TOLERANCE1.0e-10 ||
30965 std::abs(u.scale.y) <= RS_TOLERANCE1.0e-10) {
30966 m_writeFailed = true;
30967 continue;
30968 }
30969 const auto *pl = static_cast<const RS_Polyline *>(e);
30970 const double cs = std::cos(u.rotation);
30971 const double sn = std::sin(u.rotation);
30972 u.clipBoundary.clear();
30973 for (RS_Entity *sub : *pl) {
30974 if (sub == nullptr || sub->rtti() != RS2::EntityLine) {
30975 m_writeFailed = true;
30976 u.clipBoundary.clear();
30977 break;
30978 }
30979 // Polyline sub-entities are line segments; first endpoint of each
30980 // gives the polygon vertex.
30981 const RS_Vector p = sub->getStartpoint();
30982 const double dx = p.x - u.position.x;
30983 const double dy = p.y - u.position.y;
30984 const double rx = (dx * cs + dy * sn) / u.scale.x;
30985 const double ry = (-dx * sn + dy * cs) / u.scale.y;
30986 if (!std::isfinite(rx) || !std::isfinite(ry)) {
30987 m_writeFailed = true;
30988 u.clipBoundary.clear();
30989 break;
30990 }
30991 u.clipBoundary.emplace_back(rx, ry, 0.0);
30992 }
30993 if (m_writeFailed)
30994 continue;
30995 } else if (sidecar.noClip) {
30996 u.clipBoundary.clear();
30997 }
30998
30999 if (m_dwgW) {
31000 if (e->sourceHandle() == 0) {
31001 // A generated sidecar has no preallocated entity handle and therefore
31002 // cannot enter the class-501-style admission ledger yet.
31003 m_writeFailed = true;
31004 continue;
31005 }
31006 std::uint32_t definitionOutputHandle = 0;
31007 const DwgWriteReferenceStatus definitionStatus =
31008 resolveDwgWriteObjectHandle(u.definitionHandle,
31009 &definitionOutputHandle);
31010 if ((definitionStatus != DwgWriteReferenceStatus::Resolved &&
31011 definitionStatus != DwgWriteReferenceStatus::Reserved &&
31012 definitionStatus != DwgWriteReferenceStatus::Selected &&
31013 definitionStatus != DwgWriteReferenceStatus::Committed) ||
31014 definitionOutputHandle == 0) {
31015 m_writeFailed = true;
31016 continue;
31017 }
31018 const std::uint32_t definitionSourceHandle = u.definitionHandle;
31019 u.definitionHandle = definitionOutputHandle;
31020 const auto identityCheckpoint = checkpointDwgWriteIdentity();
31021 const std::uint64_t transaction =
31022 m_dwgW->beginDwgWriteTransaction(false, false, false);
31023 if (transaction == 0) {
31024 m_writeFailed = true;
31025 continue;
31026 }
31027 const auto rollback = [&]() {
31028 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
31029 restoreDwgWriteIdentity(identityCheckpoint);
31030 rollbackDwgEntityWrite(e);
31031 m_writeFailed = true;
31032 };
31033 const bool failedBefore = m_writeFailed;
31034 const bool wrote = m_dwgW->writeUnderlay(&u);
31035 if (!wrote || failedBefore || m_writeFailed) {
31036 rollback();
31037 } else {
31038 const std::uint32_t outputHandle =
31039 m_dwgW->getLastSuccessfulEntityHandle();
31040 if (!recordDwgEntityFrame(outputHandle)) {
31041 rollback();
31042 } else {
31043 const bool committed =
31044 e->sourceHandle() == 0
31045 ? commitDwgGeneratedEntityWrite(outputHandle)
31046 : commitDwgEntityWrite(e);
31047 const DwgWriteReferenceReceipt referenceReceipt{
31048 DwgWriteSourceKind::Entity,
31049 e->sourceHandle(),
31050 outputHandle,
31051 "UNDERLAY.DEFINITION",
31052 0,
31053 DwgWriteSourceKind::Object,
31054 definitionSourceHandle,
31055 definitionOutputHandle,
31056 true,
31057 2};
31058 if (!committed || !recordDwgWriteReferenceReceipt(referenceReceipt) ||
31059 !m_dwgW->commitDwgWriteTransaction(transaction)) {
31060 (void)rollbackDwgWriteReferenceReceipt(referenceReceipt);
31061 rollback();
31062 } else {
31063 consumed.insert(e);
31064 }
31065 }
31066 }
31067 } else if (m_dxfW) {
31068 noteDxfWrite(m_dxfW->writeUnderlay(&u));
31069 consumed.insert(e);
31070 }
31071 }
31072}
31073
31074/**
31075 * Writes the given Point entity to the file.
31076 */
31077void RS_FilterDXFRW::writePoint(const RS_Point *p) {
31078 DRW_Point point;
31079 getEntityAttributes(&point, p);
31080 point.basePoint.x = p->getStartpoint().x;
31081 point.basePoint.y = p->getStartpoint().y;
31082 if (m_dwgW) {
31083 if (!m_dwgW->writePoint(&point))
31084 m_writeFailed = true;
31085 return;
31086 }
31087 noteDxfWrite(m_dxfW->writePoint(&point));
31088}
31089
31090/**
31091 * Writes the given Line( entity to the file.
31092 */
31093void RS_FilterDXFRW::writeLine(const RS_Line *l) {
31094 DRW_Line line;
31095 getEntityAttributes(&line, l);
31096 line.basePoint.x = l->getStartpoint().x;
31097 line.basePoint.y = l->getStartpoint().y;
31098 line.secPoint.x = l->getEndpoint().x;
31099 line.secPoint.y = l->getEndpoint().y;
31100 if (m_dwgW) {
31101 if (!m_dwgW->writeLine(&line))
31102 m_writeFailed = true;
31103 return;
31104 }
31105 noteDxfWrite(m_dxfW->writeLine(&line));
31106}
31107
31108/**
31109 * Writes the given circle entity to the file.
31110 */
31111void RS_FilterDXFRW::writeCircle(const RS_Circle *c) {
31112 DRW_Circle circle;
31113 getEntityAttributes(&circle, c);
31114 circle.basePoint.x = c->getCenter().x;
31115 circle.basePoint.y = c->getCenter().y;
31116 circle.radious = c->getRadius();
31117 if (m_dwgW) {
31118 if (!m_dwgW->writeCircle(&circle))
31119 m_writeFailed = true;
31120 return;
31121 }
31122 noteDxfWrite(m_dxfW->writeCircle(&circle));
31123}
31124
31125// An arc-length dimension's "∩" symbol (LC_DimArc::updateEntity(),
31126// lc_dimarc.cpp) is drawn as a one-character MText -- a Unicode glyph a
31127// reader may have no font for, and never a linetype-carrying entity at all.
31128// AutoCAD writes it as a real ARC; no two real sample files agreed on its
31129// exact size (0.817 vs 0.933 times text height, the only two available), so
31130// this does not attempt to match that construction. It builds the arc from
31131// the same height, position and angle LC_DimArc already chose for the
31132// glyph, as a semicircle the width of the glyph's own height, open toward
31133// the text -- LibreCAD's own placement, expressed as geometry instead of a
31134// character. Returns false, having written nothing, for any other entity.
31135bool RS_FilterDXFRW::writeDimArcSymbolAsArc(RS_Entity *e) {
31136 if (e == nullptr || e->rtti() != RS2::EntityMText)
31137 return false;
31138 const auto *symbol = static_cast<const RS_MText *>(e);
31139 if (symbol->getText() != QStringLiteral("∩")(QString(QtPrivate::qMakeStringPrivate(u"" "∩"))))
31140 return false;
31141 const double radius = symbol->getHeight() / 2.0;
31142 const double angle = symbol->getAngle();
31143 RS_Arc arc(nullptr, RS_ArcData(symbol->getInsertionPoint(), radius, angle,
31144 angle + M_PI3.14159265358979323846, false));
31145 arc.setPen(symbol->getPen(false));
31146 arc.setLayer(nullptr);
31147 writeArc(&arc);
31148 return true;
31149}
31150
31151/**
31152 * Writes the given arc entity to the file.
31153 */
31154void RS_FilterDXFRW::writeArc(const RS_Arc *a) {
31155 DRW_Arc arc;
31156 getEntityAttributes(&arc, a);
31157 arc.basePoint.x = a->getCenter().x;
31158 arc.basePoint.y = a->getCenter().y;
31159 arc.radious = a->getRadius();
31160 if (a->isReversed()) {
31161 arc.staangle = a->getAngle2();
31162 arc.endangle = a->getAngle1();
31163 } else {
31164 arc.staangle = a->getAngle1();
31165 arc.endangle = a->getAngle2();
31166 }
31167 if (m_dwgW) {
31168 if (!m_dwgW->writeArc(&arc))
31169 m_writeFailed = true;
31170 return;
31171 }
31172 noteDxfWrite(m_dxfW->writeArc(&arc));
31173}
31174
31175/**
31176 * Writes the given polyline entity to the file as lwpolyline.
31177 */
31178void RS_FilterDXFRW::writeLWPolyline(RS_Polyline *l) {
31179 // skip if are empty polyline
31180 if (l->isEmpty()) {
31181 return;
31182 }
31183 // version 12 are old style polyline
31184 if (m_version == 1009) {
31185 writePolyline(l);
31186 return;
31187 }
31188 bool has_ellipse = false;
31189 for (RS_Entity *e = l->firstEntity(RS2::ResolveNone); e;
31190 e = l->nextEntity(RS2::ResolveNone)) {
31191 if (e->rtti() == RS2::EntityEllipse) {
31192 has_ellipse = true;
31193 break;
31194 }
31195 }
31196 if (has_ellipse) {
31197 writePolyline(l);
31198 return;
31199 }
31200 DRW_LWPolyline pol;
31201 RS_Entity *currEntity = nullptr;
31202
31203 RS_AtomicEntity *ae = nullptr;
31204 double bulge = 0.0;
31205
31206 lc::LC_ContainerTraverser traverser{*l, RS2::ResolveNone};
31207 for (RS_Entity *e = traverser.first(); e != nullptr; e = traverser.next()) {
31208 currEntity = e;
31209 // nextEntity = traverser.next();
31210
31211 if (!e->isAtomic()) {
31212 continue;
31213 }
31214 ae = static_cast<RS_AtomicEntity *>(e);
31215
31216 // Write vertex:
31217 if (e->rtti() == RS2::EntityArc) {
31218 bulge = static_cast<RS_Arc *>(e)->getBulge();
31219 } else {
31220 bulge = 0.0;
31221 }
31222 pol.addVertex(
31223 DRW_Vertex2D(ae->getStartpoint().x, ae->getStartpoint().y, bulge));
31224 }
31225 if (l->isClosed()) {
31226 pol.flags = 1;
31227 } else {
31228 ae = static_cast<RS_AtomicEntity *>(currEntity);
31229 if (ae->rtti() == RS2::EntityArc) {
31230 bulge = static_cast<RS_Arc *>(ae)->getBulge();
31231 }
31232 pol.addVertex(
31233 DRW_Vertex2D(ae->getEndpoint().x, ae->getEndpoint().y, bulge));
31234 }
31235 pol.vertexnum = pol.vertlist.size();
31236 getEntityAttributes(&pol, l);
31237 auto lwMeta = extractLWPolylineMeta(l);
31238 if (lwMeta) {
31239 pol.width = lwMeta->width;
31240 pol.elevation = lwMeta->elevation;
31241 pol.thickness = lwMeta->thickness;
31242 pol.extPoint = lwMeta->extrusion;
31243 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
31244 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
31245 if (i < lwMeta->startWidths.size())
31246 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
31247 if (i < lwMeta->endWidths.size())
31248 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
31249 if (i < lwMeta->identifiers.size())
31250 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
31251 }
31252 }
31253 }
31254 if (m_dwgW) {
31255 if (!m_dwgW->writeLWPolyline(&pol))
31256 m_writeFailed = true;
31257 return;
31258 }
31259 noteDxfWrite(m_dxfW->writeLWPolyline(&pol));
31260}
31261
31262/**
31263 * Writes the given polyline entity to the file (old style).
31264 */
31265void RS_FilterDXFRW::writePolyline(const RS_Polyline *p) {
31266 if (p == nullptr)
31267 return;
31268
31269 DRW_Polyline pol;
31270 if (p->isClosed()) {
31271 pol.flags = 1;
31272 }
31273
31274 RS_Entity *nextEntity = nullptr;
31275 RS_AtomicEntity *lastSegment = nullptr;
31276 for (RS_Entity *e = p->firstEntity(RS2::ResolveNone); e != nullptr;
31277 e = nextEntity) {
31278 nextEntity = p->nextEntity(RS2::ResolveNone);
31279
31280 if (!e->isAtomic()) {
31281 continue;
31282 }
31283 RS_AtomicEntity *ae = static_cast<RS_AtomicEntity *>(e);
31284 lastSegment = ae;
31285
31286 // Write vertex:
31287 double bulge = 0.0;
31288 bool isElliptic = false;
31289 double yRadius = 0.0;
31290 switch (e->rtti()) {
31291 case RS2::EntityLine:
31292 break;
31293 case RS2::EntityArc:
31294 bulge = ((RS_Arc *)e)->getBulge();
31295 break;
31296 case RS2::EntityEllipse: {
31297 // Issue #1946: prepare to write elliptic arcs as RS_Arc
31298 RS_Ellipse *ellipse = static_cast<RS_Ellipse *>(e);
31299 auto pair = RS_Polyline::convertToArcPair(ellipse);
31300 std::unique_ptr<RS_Arc> arc{pair.first};
31301 bulge = arc->getBulge();
31302 yRadius = arc->getRadius() * pair.second;
31303 isElliptic = true;
31304 } break;
31305 default:
31306 // should not happen: unknown entity type
31307 continue;
31308 }
31309 pol.addVertex(
31310 DRW_Vertex(ae->getStartpoint().x, ae->getStartpoint().y, 0.0, bulge));
31311 if (isElliptic) {
31312 // Add flag to indicate the vertex should be elliptic
31313 pol.vertlist.back()->extData.push_back(
31314 std::make_shared<DRW_Variant>(1001, "LibreCad"));
31315 // Keep to ellipse minor radius to allow elliptic creation at loading
31316 pol.vertlist.back()->extData.push_back(
31317 std::make_shared<DRW_Variant>(1040, yRadius));
31318 }
31319 }
31320 // Each vertex above is a segment's FROM point (its bulge, when set,
31321 // describes that segment). A closed polyline's last segment returns to the
31322 // first vertex, so nothing further is needed; an open one's last segment
31323 // ends at a point that starts no segment of its own and, left out of this
31324 // loop entirely, was silently dropped -- degenerating a single-segment
31325 // open polyline (e.g. one DXF LINE read back as a 2-point polyline) to one
31326 // point.
31327 if (!p->isClosed() && lastSegment != nullptr) {
31328 pol.addVertex(DRW_Vertex(lastSegment->getEndpoint().x,
31329 lastSegment->getEndpoint().y, 0.0, 0.0));
31330 }
31331 getEntityAttributes(&pol, p);
31332 if (auto lwMeta = extractLWPolylineMeta(const_cast<RS_Polyline *>(p))) {
31333 pol.basePoint.z = lwMeta->elevation;
31334 pol.thickness = lwMeta->thickness;
31335 pol.extPoint = lwMeta->extrusion;
31336 if (lwMeta->vertexCount == static_cast<int>(pol.vertlist.size())) {
31337 for (size_t i = 0; i < pol.vertlist.size(); ++i) {
31338 if (i < lwMeta->startWidths.size())
31339 pol.vertlist[i]->stawidth = lwMeta->startWidths[i];
31340 if (i < lwMeta->endWidths.size())
31341 pol.vertlist[i]->endwidth = lwMeta->endWidths[i];
31342 if (i < lwMeta->identifiers.size())
31343 pol.vertlist[i]->identifier = lwMeta->identifiers[i];
31344 }
31345 }
31346 }
31347 if (m_dwgW) {
31348 if (!m_dwgW->writePolyline(&pol))
31349 m_writeFailed = true;
31350 return;
31351 }
31352 noteDxfWrite(m_dxfW->writePolyline(&pol));
31353}
31354
31355/**
31356 * Writes the given spline entity to the file.
31357 */
31358void RS_FilterDXFRW::writeSpline(RS_Spline *s) {
31359 if (s == nullptr) {
31360 return;
31361 }
31362
31363 if (s->getNumberOfControlPoints() < size_t(s->getDegree() + 1)) {
31364 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
31365 "RS_FilterDXF::writeSpline: "
31366 "Discarding spline: not enough control points given.");
31367 return;
31368 }
31369
31370 // R12 has no SPLINE: a polyline within its export tolerance, or the
31371 // segments the spline is drawn with
31372 if (m_version == 1009) {
31373 // a spline read closed but kept with open ends is closed too, while its
31374 // ends meet: the polyline is closed and repeats no vertex
31375 const RS_SplineData &splineData = s->getData();
31376 const bool closed = s->isClosed() ||
31377 ((splineData.m_closedFlag || splineData.m_periodicFlag) &&
31378 splineCurveCloses(splineData));
31379 std::vector<RS_Vector> vertices;
31380 if (!r12SplineVertices(*s, closed, vertices)) {
31381 // A curve the evaluator cannot bound, as a spline whose knots do not
31382 // match its control points is. The file keeps what the screen shows
31383 // rather than failing over one entity; a spline that draws nothing
31384 // writes nothing, as it did before R12 export had a tolerance.
31385 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31386 "RS_FilterDXFRW::writeSpline: no R12 polyline within "
31387 "tolerance and vertex limit; writing the drawn segments");
31388 vertices.clear();
31389 for (const RS_Entity *e : lc::LC_ContainerTraverser{*s, RS2::ResolveNone}.entities()) {
31390 vertices.push_back(e->getStartpoint());
31391 }
31392 if (vertices.empty()) {
31393 return;
31394 }
31395 if (!closed) {
31396 vertices.push_back(
31397 lc::LC_ContainerTraverser{*s, RS2::ResolveNone}.entities().back()->getEndpoint());
31398 }
31399 }
31400 DRW_Polyline pol;
31401 for (const RS_Vector &v : vertices) {
31402 pol.addVertex(DRW_Vertex(v.x, v.y, 0.0, 0.0));
31403 }
31404 if (closed) {
31405 pol.flags = 1;
31406 }
31407 getEntityAttributes(&pol, s);
31408 noteDxfWrite(m_dxfW->writePolyline(&pol));
31409 return;
31410 }
31411
31412 DRW_Spline sp{};
31413
31414 // dxf spline group code=70
31415 // bit coded: 1: closed; 2: periodic; 4: rational; 8: planar; 16:linear
31416 // A spline read closed or periodic but kept with open ends, as AutoCAD
31417 // writes those with a clamped knot vector, gets its flags back while its
31418 // ends still meet.
31419 sp.flags = (s->isClosed()) ? 0b1011 : 0b1000;
31420 const RS_SplineData &splineData = s->getData();
31421 if (!s->isClosed() && (splineData.m_closedFlag || splineData.m_periodicFlag) &&
31422 splineCurveCloses(splineData)) {
31423 sp.flags |= (splineData.m_closedFlag ? 0x1 : 0) |
31424 (splineData.m_periodicFlag ? 0x2 : 0);
31425 }
31426
31427 // write spline control points:
31428 for (const RS_Vector &v : s->getUnwrappedControlPoints()) {
31429 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
31430 }
31431 sp.weightlist = s->getUnwrappedWeights();
31432 if (std::any_of(sp.weightlist.begin(), sp.weightlist.end(),
31433 differsFromUnitWeight)) {
31434 sp.flags |= 0x04;
31435 }
31436
31437 sp.ncontrol = sp.controllist.size();
31438 sp.degree = s->getDegree();
31439
31440 // knot vector from RS_Spline
31441 sp.knotslist = s->getUnwrappedKnotVector();
31442 sp.nknots = sp.knotslist.size();
31443
31444 getEntityAttributes(&sp, s);
31445 if (m_dwgW) {
31446 if (!m_dwgW->writeSpline(&sp))
31447 m_writeFailed = true;
31448 return;
31449 }
31450 noteDxfWrite(m_dxfW->writeSpline(&sp));
31451}
31452
31453/**
31454 * Writes the given spline entity to the file.
31455 */
31456void RS_FilterDXFRW::writeSplinePoints(LC_SplinePoints *s) {
31457 int nCtrls = s->getNumberOfControlPoints();
31458 auto const &cp = s->getControlPoints();
31459
31460 if (nCtrls < 3) {
31461 if (nCtrls > 1) {
31462 DRW_Line line;
31463 line.basePoint.x = cp.at(0).x;
31464 line.basePoint.y = cp.at(0).y;
31465 line.secPoint.x = cp.at(1).x;
31466 line.secPoint.y = cp.at(1).y;
31467 getEntityAttributes(&line, s);
31468 if (m_dwgW) {
31469 if (!m_dwgW->writeLine(&line))
31470 m_writeFailed = true;
31471 return;
31472 }
31473 noteDxfWrite(m_dxfW->writeLine(&line));
31474 }
31475 return;
31476 }
31477
31478 // version 12 do not support Spline write as polyline
31479 if (m_version == 1009) {
31480 DRW_Polyline pol;
31481 auto const &sp = s->getStrokePoints();
31482
31483 for (size_t i = 0; i < sp.size(); i++) {
31484 pol.addVertex(DRW_Vertex(sp.at(i).x, sp.at(i).y, 0.0, 0.0));
31485 }
31486
31487 if (s->isClosed()) {
31488 pol.flags = 1;
31489 }
31490
31491 getEntityAttributes(&pol, s);
31492 noteDxfWrite(m_dxfW->writePolyline(&pol));
31493 return;
31494 }
31495
31496 DRW_Spline sp;
31497 if (s->isClosed()) {
31498 sp.flags = 11;
31499 } else {
31500 sp.flags = 8;
31501 }
31502
31503 LC_SplinePointsData &data = s->getData();
31504 auto const &fitPoints = data.splinePoints;
31505 const bool writeFitScenario = !data.useControlPoints && fitPoints.size() >= 2;
31506 sp.degree = 2;
31507 sp.nfit = static_cast<std::int32_t>(fitPoints.size());
31508
31509 if (writeFitScenario) {
31510 sp.m_scenario = 2;
31511 sp.m_knotParam = 0;
31512 sp.ncontrol = 0;
31513 sp.nknots = 0;
31514 sp.tolfit = 0.0000001;
31515 const RS_Vector startTangent = fitPoints[1] - fitPoints[0];
31516 const RS_Vector endTangent =
31517 fitPoints.back() - fitPoints[fitPoints.size() - 2];
31518 sp.tgStart = DRW_Coord{startTangent.x, startTangent.y, 0.0};
31519 sp.tgEnd = DRW_Coord{endTangent.x, endTangent.y, 0.0};
31520 } else {
31521 sp.ncontrol = nCtrls;
31522 sp.nknots = nCtrls + 3;
31523 }
31524
31525 // write spline knots:
31526 if (!writeFitScenario) {
31527 for (int i = 1; i <= sp.nknots; i++) {
31528 if (i <= 3) {
31529 sp.knotslist.push_back(0.0);
31530 } else if (i <= nCtrls) {
31531 sp.knotslist.push_back((i - 3.0) / (nCtrls - 2.0));
31532 } else {
31533 sp.knotslist.push_back(1.0);
31534 }
31535 }
31536 }
31537
31538 // write spline control points:
31539 if (!writeFitScenario) {
31540 for (auto const &v : cp) {
31541 sp.controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
31542 }
31543 }
31544
31545 // fit points
31546 for (auto const &v : fitPoints) {
31547 sp.fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y));
31548 }
31549
31550 getEntityAttributes(&sp, s);
31551 if (m_dwgW) {
31552 if (!writeFitScenario) {
31553 sp.fitlist.clear();
31554 sp.nfit = 0;
31555 }
31556 if (!m_dwgW->writeSpline(&sp))
31557 m_writeFailed = true;
31558 return;
31559 }
31560 noteDxfWrite(m_dxfW->writeSpline(&sp));
31561}
31562
31563/**
31564 * Writes the given Ellipse entity to the file.
31565 */
31566void RS_FilterDXFRW::writeEllipse(const RS_Ellipse *s) {
31567 // version 12 do not support Ellipse but are
31568 // converted in polyline by library
31569 DRW_Ellipse el;
31570 getEntityAttributes(&el, s);
31571 el.basePoint.x = s->getCenter().x;
31572 el.basePoint.y = s->getCenter().y;
31573 el.secPoint.x = s->getMajorP().x;
31574 el.secPoint.y = s->getMajorP().y;
31575 el.ratio = s->getRatio();
31576 if (s->isReversed()) {
31577 el.staparam = s->getAngle2();
31578 el.endparam = s->getAngle1();
31579 } else {
31580 el.staparam = s->getAngle1();
31581 el.endparam = s->getAngle2();
31582 }
31583 if (m_dwgW) {
31584 if (!m_dwgW->writeEllipse(&el))
31585 m_writeFailed = true;
31586 return;
31587 }
31588 noteDxfWrite(m_dxfW->writeEllipse(&el));
31589}
31590
31591/**
31592 * Write a hyperbola entity as an exact rational quadratic SPLINE.
31593 *
31594 * Uses LC_HyperbolaSpline to create the standard SPLINE representation.
31595 */
31596void RS_FilterDXFRW::writeHyperbola(LC_Hyperbola *h) {
31597 if (h == nullptr || !h->isValid())
31598 return;
31599 DRW_Spline spl;
31600 getEntityAttributes(&spl, h);
31601 if (LC_HyperbolaSpline::hyperbolaToSpline(h->getData(), spl)) {
31602 if (m_dwgW) {
31603 if (!m_dwgW->writeSpline(&spl))
31604 m_writeFailed = true;
31605 return;
31606 }
31607 if (m_dxfW)
31608 noteDxfWrite(m_dxfW->writeSpline(&spl));
31609 }
31610}
31611
31612/**
31613 * Write a parabola entity as its canonical non-rational quadratic SPLINE.
31614 */
31615void RS_FilterDXFRW::writeParabola(LC_Parabola *p) {
31616 if (p == nullptr || !p->getData().isValid())
31617 return;
31618
31619 DRW_Spline spl;
31620 getEntityAttributes(&spl, p);
31621 if (LC_ParabolaSpline::parabolaToSpline(p->getData(), spl)) {
31622 if (m_dwgW) {
31623 if (!m_dwgW->writeSpline(&spl))
31624 m_writeFailed = true;
31625 return;
31626 }
31627 if (m_dxfW)
31628 noteDxfWrite(m_dxfW->writeSpline(&spl));
31629 }
31630}
31631
31632/**
31633 * Writes the given block insert entity to the file.
31634 */
31635void RS_FilterDXFRW::writeInsert(const RS_Insert *i) {
31636 DRW_Insert in;
31637 getEntityAttributes(&in, i);
31638 in.basePoint.x = i->getInsertionPoint().x;
31639 in.basePoint.y = i->getInsertionPoint().y;
31640 in.basePoint.z = i->getInsertionPoint().z;
31641 in.name = i->getName().toUtf8().data();
31642 // Inserts that bypass the RS_InsertData constructor - default constructed
31643 // data, direct field assignment, or a file that already carried a bad
31644 // value - are corrected at the boundary rather than written out.
31645 const RS_Vector scale = RS_InsertData::usableScale(i->getScale());
31646 in.xscale = scale.x;
31647 in.yscale = scale.y;
31648 in.zscale = scale.z;
31649 in.angle = i->getAngle();
31650 in.colcount = i->getCols();
31651 in.rowcount = i->getRows();
31652 in.colspace = i->getSpacing().x;
31653 in.rowspace = i->getSpacing().y;
31654 const RS_Vector extrusion = i->getData().extrusion;
31655 in.extPoint = DRW_Coord(extrusion.x, extrusion.y, extrusion.z);
31656
31657#ifdef DWGSUPPORT1
31658 DwgWriteReferenceStatus blockStatus = DwgWriteReferenceStatus::Missing;
31659 std::uint32_t blockRecordHandle = 0;
31660 DwgWriteSourceKind blockTargetKind = DwgWriteSourceKind::Block;
31661 std::uint32_t blockSourceHandle = 0;
31662 if (m_dwgW != nullptr) {
31663 if (!isDwgInsertAdmitted(i)) {
31664 m_writeFailed = true;
31665 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31666 "RS_FilterDXFRW: DWG INSERT was not admitted by the "
31667 "frozen block graph %s",
31668 in.name.c_str());
31669 return;
31670 }
31671 blockStatus = resolveDwgWriteBlockName(normalizeDwgTableName(in.name),
31672 &blockRecordHandle);
31673 if (blockStatus != DwgWriteReferenceStatus::LegacySpaceAlias &&
31674 blockStatus != DwgWriteReferenceStatus::Resolved &&
31675 blockStatus != DwgWriteReferenceStatus::Selected &&
31676 blockStatus != DwgWriteReferenceStatus::Committed) {
31677 m_writeFailed = true;
31678 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31679 "RS_FilterDXFRW: rejecting DWG INSERT with unresolved "
31680 "block name %s (status=%d)",
31681 in.name.c_str(), static_cast<int>(blockStatus));
31682 return;
31683 }
31684 in.blockRecH.ref = blockRecordHandle;
31685 if (blockStatus == DwgWriteReferenceStatus::LegacySpaceAlias) {
31686 blockTargetKind = DwgWriteSourceKind::FixedStructural;
31687 } else {
31688 blockSourceHandle = sourceDwgWriteIdentityHandle(
31689 DwgWriteSourceKind::Block, blockRecordHandle);
31690 }
31691 }
31692#endif
31693
31694 // RS_Insert cannot retain ATTRIB children internally: update() rebuilds
31695 // its expanded block cache. Reconstruct the native sequence from the
31696 // sibling texts buildInsertAttribPlan() gave this INSERT, then let the
31697 // libdxfrw writer allocate and link the child handles.
31698 std::vector<const RS_Entity *> attributeTexts;
31699 {
31700 std::vector<std::pair<InsertAttribSidecar, RS_Entity *>> attributes;
31701 const auto planned = m_insertAttribPlan.children.find(i);
31702 if (planned != m_insertAttribPlan.children.end()) {
31703 attributes.reserve(planned->second.size());
31704 for (RS_Entity *candidate : planned->second) {
31705 // buildInsertAttribPlan() admitted only texts whose sidecar parses.
31706 if (auto sidecar = extractInsertAttribSidecar(candidate))
31707 attributes.emplace_back(std::move(*sidecar), candidate);
31708 }
31709 }
31710#ifdef DWGSUPPORT1
31711 // An ATTRIB with a reference of its own (a reactor, an extension
31712 // dictionary) needs its handle before it is written: getEntityAttributes()
31713 // selects an identity for it. In an R2000 owner chain that has to be the
31714 // handle of its place after the INSERT. prepareDwgEntityHandleMap() took
31715 // those handles for a container with source-backed entities; for any
31716 // other they are taken here, in the order the writer would take them.
31717 if (m_dwgW != nullptr && !attributes.empty()) {
31718 auto reserved = m_dwgWriteInsertChildHandlesByEntity.find(i);
31719 if (reserved == m_dwgWriteInsertChildHandlesByEntity.end() &&
31720 m_dwgW->getVersion() <= DRW::AC1015) {
31721 if (in.handle == 0)
31722 in.handle = m_dwgW->allocNextHandle();
31723 // One handle per ATTRIB, then the SEQEND.
31724 std::vector<std::uint32_t> childHandles;
31725 childHandles.reserve(attributes.size() + 1);
31726 for (std::size_t index = 0; index <= attributes.size(); ++index)
31727 childHandles.push_back(m_dwgW->allocNextHandle());
31728 if (in.handle == 0 ||
31729 std::find(childHandles.cbegin(), childHandles.cend(), 0u) !=
31730 childHandles.cend()) {
31731 m_writeFailed = true;
31732 return;
31733 }
31734 reserved = m_dwgWriteInsertChildHandlesByEntity
31735 .emplace(i, std::move(childHandles))
31736 .first;
31737 }
31738 if (reserved != m_dwgWriteInsertChildHandlesByEntity.end() &&
31739 reserved->second.size() == attributes.size() + 1) {
31740 for (std::size_t index = 0; index < attributes.size(); ++index) {
31741 m_dwgWriteGeneratedEntityHandleByEntity.emplace(
31742 attributes[index].second, reserved->second[index]);
31743 }
31744 }
31745 }
31746#endif
31747
31748 // Only an AC1032 file holds the MTEXT of a multi-line attribute, and
31749 // only its DWG form has fields for an annotation-scale payload or a
31750 // column layout.
31751 const bool embedsMText =
31752 (m_dwgW != nullptr ? m_dwgW->getVersion() : m_dxfExportVersion) >=
31753 DRW::AC1032;
31754 const bool writesAttribScaleState = embedsMText && m_dwgW != nullptr;
31755
31756 attributeTexts.reserve(attributes.size());
31757 for (const auto &[sidecar, candidate] : attributes) {
31758 attributeTexts.push_back(candidate);
31759 DRW_Attrib attrib;
31760 getEntityAttributes(&attrib, candidate);
31761 attrib.tag = sidecar.tag;
31762 attrib.attribFlags = static_cast<std::uint8_t>(sidecar.flags);
31763 // A text is hidden when the record of its ATTRIB is invisible, when
31764 // the attribute has the invisible flag, or when its MTEXT is invisible
31765 // (addInsert()). While the text is as the import left it, the record
31766 // gets its own visibility back: the flag alone must not make an
31767 // invisible entity of it. Only a DWG of AC1032 has the visibility of
31768 // the MTEXT; elsewhere the record keeps the text hidden in its stead.
31769 // A visibility changed in the drawing since is written as it is.
31770 const bool mtextHidden = sidecar.hasMText && !sidecar.mtext.visible;
31771 const bool mtextKeepsItsOwn = embedsMText && m_dwgW != nullptr;
31772 const bool shownAsRead = sidecar.recordVisible &&
31773 (sidecar.flags & 0x1) == 0 && !mtextHidden;
31774 if (attrib.visible == shownAsRead) {
31775 attrib.visible =
31776 sidecar.recordVisible && (!mtextHidden || mtextKeepsItsOwn);
31777 }
31778 attrib.m_fieldLength = sidecar.fieldLength;
31779 attrib.attVersion = static_cast<std::uint8_t>(sidecar.attVersion);
31780 attrib.m_attributeType = static_cast<std::uint8_t>(sidecar.attributeType);
31781 attrib.lockPosition = sidecar.lockPosition;
31782 attrib.basePoint = sidecar.basePoint;
31783 attrib.secPoint = sidecar.secPoint;
31784 attrib.extPoint = sidecar.extPoint;
31785 attrib.thickness = sidecar.thickness;
31786
31787 if (candidate->rtti() == RS2::EntityMText) {
31788 const auto *source = static_cast<const RS_MText *>(candidate);
31789 const RS_MTextData data = source->getData();
31790 auto mtext = std::make_unique<DRW_MText>();
31791 mtext->basePoint =
31792 DRW_Coord(data.insertionPoint.x, data.insertionPoint.y,
31793 sidecar.basePoint.z);
31794 mtext->extPoint = sidecar.extPoint;
31795 mtext->height = data.height;
31796 mtext->widthscale = data.width;
31797 mtext->text = toDxfString(data.text, m_dwgW == nullptr).toUtf8().toStdString();
31798 mtext->style = data.style.toUtf8().toStdString();
31799 mtext->angle = data.angle * 180.0 / M_PI3.14159265358979323846;
31800 setMTextLayout(*mtext, data);
31801 DRW_Coord storedXAxis;
31802 bool textEdited = false;
31803 bool angleEdited = false;
31804 // Whether anything changed that the first point of the ATTRIB
31805 // itself depends on; without stored MTEXT fields nothing says no.
31806 bool firstPointEdited = true;
31807 // Whether the annotation-scale payload of the source is written.
31808 bool scaleDataWritten = false;
31809 if (sidecar.hasMText) {
31810 const auto &saved = sidecar.mtext;
31811 DRW_MText original;
31812 original.text = saved.text;
31813 original.textgen = saved.textgen;
31814 original.alignH = static_cast<DRW_Text::HAlign>(saved.alignH);
31815 original.linespacingStyle = static_cast<std::uint16_t>(saved.linespacingStyle);
31816 original.extData = saved.xdata;
31817 original.height = saved.originalHeight;
31818 original.widthscale = saved.originalWidth;
31819 const std::unique_ptr<RS_MText> imported(mtextEntityFromDRW(original));
31820 auto before = imported->getData();
31821 // Position, line spacing, angle and style as imported depend on the
31822 // filter that read the file (a file of an old LibreCAD, the text
31823 // style of its header, the code page) and on a round trip through
31824 // degrees: what the text remembers of them stands in.
31825 before.insertionPoint = RS_Vector(saved.originalPosition.x, saved.originalPosition.y);
31826 before.lineSpacingFactor = saved.originalSpacing;
31827 before.angle = saved.originalAngle;
31828 before.style = QString::fromUtf8(saved.originalStyle.c_str());
31829 textEdited = data.text != before.text;
31830 const bool valignEdited = data.valign != before.valign;
31831 const bool attachmentEdited =
31832 data.halign != before.halign || valignEdited;
31833 const bool directionEdited = data.drawingDirection != before.drawingDirection ||
31834 data.legacyRtlLayout != before.legacyRtlLayout;
31835 const bool spacingEdited = data.lineSpacingStyle != before.lineSpacingStyle ||
31836 data.lineSpacingFactor != before.lineSpacingFactor;
31837 const bool positionEdited = data.insertionPoint != before.insertionPoint;
31838 angleEdited = data.angle != before.angle;
31839 const bool widthEdited = data.width != before.width;
31840 const bool styleEdited = data.style != before.style;
31841 const bool heightEdited = data.height != before.height;
31842 const bool layoutEdited = textEdited || attachmentEdited ||
31843 directionEdited || spacingEdited || positionEdited ||
31844 angleEdited || widthEdited || styleEdited ||
31845 heightEdited;
31846 firstPointEdited = textEdited || attachmentEdited || positionEdited ||
31847 angleEdited || heightEdited || styleEdited;
31848 // The annotation-scale payload and the column layout are opaque
31849 // here. They go out only next to the untouched text they were
31850 // read with, and the payload only if the APPID it names is
31851 // written too; exportReport() says when either stays behind.
31852 // A copy has no source handle, and may come from another drawing,
31853 // where the handle of the APPID names something else.
31854 std::uint32_t annotativeAppHandle = 0;
31855 bool keepScaleState = writesAttribScaleState && !layoutEdited &&
31856 candidate->sourceHandle() != 0;
31857 if (keepScaleState && !saved.annotativeData.empty() &&
31858 !resolveDwgWriteAppIdHandle(saved.annotativeAppHandle,
31859 &annotativeAppHandle)) {
31860 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
31861 "RS_FilterDXFRW: ATTRIB annotation scale data "
31862 "names APPID 0x%X, which is not written",
31863 saved.annotativeAppHandle);
31864 keepScaleState = false;
31865 }
31866 if (!keepScaleState && (!saved.annotativeData.empty() ||
31867 saved.r2018ColumnType != 0))
31868 ++m_exportAttribScaleDataLeftOut;
31869 scaleDataWritten = keepScaleState && !saved.annotativeData.empty();
31870 // RS_MText::setText() takes a leading \A0; \A1; \A2; out of the
31871 // text and makes it the vertical alignment. A stored text with such
31872 // a prefix goes out only next to the vertical alignment it was read
31873 // with; any other stored text keeps its inline codes when only the
31874 // attachment changes. A text read from a DWG may hold what a line
31875 // of a DXF cannot: there the text of the drawing goes in its place.
31876 const bool replayText =
31877 !textEdited &&
31878 (m_dwgW != nullptr || fitsDxfLine(saved.text)) &&
31879 !(valignEdited &&
31880 hasMTextAlignmentPrefix(
31881 toNativeString(QString::fromUtf8(saved.text.c_str()))));
31882 if (replayText)
31883 mtext->text = saved.text;
31884 if (replayText && !attachmentEdited)
31885 mtext->textgen = saved.textgen;
31886 if (!directionEdited)
31887 mtext->alignH = static_cast<DRW_Text::HAlign>(saved.alignH);
31888 if (!spacingEdited)
31889 mtext->interlin = saved.interlin;
31890 // What the source record held besides its layout is replayed as
31891 // far as it is a value. A handle is not, nor a flag that says one
31892 // follows: it named an object of the source file, and in the file
31893 // being written that number is another object or none. The R2018
31894 // repeats of the placement are written last, from what they
31895 // repeat (completeEmbeddedMTextForDwg()).
31896 mtext->m_backgroundFlags = saved.backgroundFlags;
31897 mtext->m_backgroundScale = saved.backgroundScale;
31898 mtext->m_backgroundColor = saved.backgroundColor;
31899 mtext->m_backgroundTrueColor = saved.backgroundTrueColor;
31900 mtext->m_backgroundTransparency = saved.backgroundTransparency;
31901 mtext->m_r2018IsNotAnnotative = saved.r2018IsNotAnnotative;
31902 mtext->m_r2018Version =
31903 static_cast<std::uint16_t>(saved.r2018Version);
31904 mtext->m_r2018DefaultFlag = saved.r2018DefaultFlag;
31905 mtext->m_r2018RectHeight = saved.r2018RectHeight;
31906 mtext->m_r2018ExtentsHeight = saved.r2018ExtentsHeight;
31907 mtext->m_r2018ExtentsWidth = saved.r2018ExtentsWidth;
31908 if (keepScaleState) {
31909 mtext->m_r2018ColumnType =
31910 static_cast<std::uint16_t>(saved.r2018ColumnType);
31911 mtext->m_r2018ColumnCount = saved.r2018ColumnCount;
31912 mtext->m_r2018ColumnWidth = saved.r2018ColumnWidth;
31913 mtext->m_r2018ColumnGutter = saved.r2018ColumnGutter;
31914 mtext->m_r2018ColumnAutoHeight = saved.r2018ColumnAutoHeight;
31915 mtext->m_r2018ColumnFlowReversed = saved.r2018ColumnFlowReversed;
31916 mtext->m_r2018ColumnHeights = saved.r2018ColumnHeights;
31917 mtext->m_r2018AnnotativeData = saved.annotativeData;
31918 mtext->m_r2018AnnotativeUnknown =
31919 static_cast<std::uint16_t>(saved.annotativeUnknown);
31920 mtext->m_r2018AnnotativeAppHandle = annotativeAppHandle;
31921 }
31922 mtext->m_r2018ReallyLocked = saved.reallyLocked;
31923 // A colour-book reference names an AcDbColor object of the source
31924 // file, and its index field holds no colour: without the reference
31925 // the MTEXT is ByLayer.
31926 if (!saved.hasAcDbColorHandle) {
31927 mtext->color = saved.color;
31928 mtext->color24 = saved.color24;
31929 }
31930 mtext->transparency = saved.transparency;
31931 mtext->ltypeScale = saved.ltypeScale;
31932 mtext->lineType = embeddedMTextLineType(saved.lineType);
31933 mtext->visible = saved.visible;
31934 mtext->lWeight =
31935 static_cast<DRW_LW_Conv::lineWidth>(saved.lineWeight);
31936 mtext->basePoint = saved.basePoint;
31937 if (positionEdited) {
31938 mtext->basePoint.x = data.insertionPoint.x;
31939 mtext->basePoint.y = data.insertionPoint.y;
31940 }
31941 mtext->extPoint = saved.extPoint;
31942 storedXAxis = saved.secPoint;
31943 if (layoutEdited) {
31944 mtext->m_r2018ExtentsWidth = source->getUsedTextWidth();
31945 mtext->m_r2018ExtentsHeight = source->getUsedTextHeight();
31946 }
31947 }
31948 mtext->secPoint =
31949 mtextXAxis(data.angle, storedXAxis, angleEdited, m_dwgW != nullptr);
31950 // One marker in one record: the XDATA of an ATTRIB record is the
31951 // ATTRIB's in DXF, and the embedded MTEXT of a DWG has none.
31952 setMTextRtlMarker(attrib, rtlLayoutOf(data));
31953 // The value of the ATTRIB itself. Next to its MTEXT it is the value
31954 // the file came with, which AutoCAD leaves empty; once the text has
31955 // changed, a value that was there follows it. Without the MTEXT it is
31956 // all a reader gets: the text on one line, without codes.
31957 const QString plain = RS_MText::toPlainLine(data.text);
31958 const bool keepStored =
31959 embedsMText ? !textEdited || sidecar.value.empty()
31960 : !textEdited && plain.isEmpty();
31961 // A value read from a DWG may hold what a line of a DXF cannot. The
31962 // stored value is in the spelling of its file and goes out as it is;
31963 // the line is a single-line value like that of any ATTRIB, in which
31964 // a backslash of the text is a backslash and no code. Either is laid
31965 // out below as the text of one line it stands for.
31966 QString shownValue = plain;
31967 if (keepStored && (m_dwgW != nullptr || fitsDxfLine(sidecar.value))) {
31968 attrib.text = sidecar.value;
31969 shownValue = toNativeSingleLineString(
31970 QString::fromUtf8(sidecar.value.c_str()));
31971 } else {
31972 attrib.text = toDxfSingleLineString(plain, m_dwgW == nullptr)
31973 .toUtf8()
31974 .toStdString();
31975 }
31976 attrib.height = data.height;
31977 attrib.style = mtext->style;
31978 // The MTEXT has neither: they are the ATTRIB's own, as read. Of group
31979 // 71 only the two flags there are, backward and upside down.
31980 attrib.widthscale = sidecar.widthFactor;
31981 attrib.textgen = sidecar.textgen & 6;
31982 // attrib.text is final here: the first point is laid out from the
31983 // text it stands for.
31984 setMultilineAttribPlacement(attrib, *mtext, data, sidecar,
31985 firstPointEdited, shownValue);
31986 if (m_dwgW != nullptr) {
31987 // The MTEXT refers to the text style and the layer of its
31988 // attribute by the handles they have in the file being written.
31989 mtext->styleH.ref = dwgWriteHandleByName(
31990 m_dwgWriteTextStyleHandleByName, mtext->style);
31991 if (const std::uint32_t layer = dwgWriteHandleByName(
31992 m_dwgWriteLayerHandleByName, attrib.layer))
31993 mtext->setDwgLayerHandle(layer);
31994 }
31995 if (!embedsMText) {
31996 attrib.attVersion = 0;
31997 attrib.m_attributeType = 1;
31998 } else {
31999 // DRW_Attrib::encodeDwg() embeds the MTEXT in a multi-line ATTRIB
32000 // only; type 0 or 1 would be written as a single line without it.
32001 if (attrib.m_attributeType <= 1)
32002 attrib.m_attributeType = 2;
32003 if (m_dwgW != nullptr) {
32004 // AutoCAD writes class version 0 for a multi-line attribute too.
32005 attrib.attVersion = 0;
32006 completeEmbeddedMTextForDwg(
32007 *mtext,
32008 scaleDataWritten && !sidecar.mtext.r2018IsNotAnnotative);
32009 }
32010 attrib.mtext = std::move(mtext);
32011 }
32012 } else {
32013 const auto *source = static_cast<const RS_Text *>(candidate);
32014 setSingleLineAttribPlacement(attrib, *source);
32015 attrib.height = source->getHeight();
32016 attrib.widthscale = source->getWidthRel();
32017 attrib.text =
32018 toDxfSingleLineString(source->getText(), m_dwgW == nullptr)
32019 .toUtf8()
32020 .toStdString();
32021 attrib.style = source->getStyle().toUtf8().toStdString();
32022 attrib.angle = source->getAngle() * 180.0 / M_PI3.14159265358979323846;
32023 attrib.textgen =
32024 textGenerationFlags(source->getTextGeneration(), sidecar.textgen);
32025 }
32026 // Of one line or of several: in a DWG the ATTRIB names its text style
32027 // by the handle of the style's record.
32028 if (m_dwgW != nullptr) {
32029 attrib.styleH.ref = dwgWriteHandleByName(
32030 m_dwgWriteTextStyleHandleByName, attrib.style);
32031 }
32032 removeInsertAttribSidecar(attrib);
32033 in.attlist.push_back(std::make_shared<DRW_Attrib>(std::move(attrib)));
32034 }
32035 }
32036 if (m_dwgW) {
32037#ifdef DWGSUPPORT1
32038 // Keep the ATTRIB..SEQEND handles taken in prepareDwgEntityHandleMap()
32039 // next to this INSERT's own handle in an R2000 owner chain. The ATTRIBs
32040 // have them already (see above).
32041 const auto childIt = m_dwgWriteInsertChildHandlesByEntity.find(i);
32042 if (childIt != m_dwgWriteInsertChildHandlesByEntity.end() &&
32043 childIt->second.size() == in.attlist.size() + 1) {
32044 for (std::size_t index = 0; index < in.attlist.size(); ++index)
32045 in.attlist[index]->handle = childIt->second[index];
32046 in.seqendH.ref = childIt->second.back();
32047 }
32048#endif
32049 if (!m_dwgW->writeInsert(&in)) {
32050 m_writeFailed = true;
32051 return;
32052 }
32053#ifdef DWGSUPPORT1
32054 // The ATTRIBs are written inside the frame of the INSERT, and no
32055 // writeEntity() commits them: the identities selected for those with
32056 // references of their own are final here. They are no entities of a
32057 // block record, so their source handles are not noted as emitted.
32058 for (const RS_Entity *text : attributeTexts) {
32059 const auto selected = m_dwgWriteEntityHandleByEntity.find(text);
32060 if (selected != m_dwgWriteEntityHandleByEntity.end() &&
32061 !commitDwgWriteIdentity(DwgWriteSourceKind::Entity,
32062 selected->second)) {
32063 m_writeFailed = true;
32064 }
32065 }
32066 const DwgWriteReferenceReceipt receipt{DwgWriteSourceKind::Entity,
32067 i->sourceHandle(),
32068 in.handle,
32069 "INSERT.BLOCK_RECORD",
32070 0,
32071 blockTargetKind,
32072 blockSourceHandle,
32073 blockRecordHandle,
32074 true,
32075 2};
32076 if (!recordDwgWriteReferenceReceipt(receipt))
32077 m_writeFailed = true;
32078#endif
32079 return;
32080 }
32081 noteDxfWrite(m_dxfW->writeInsert(&in));
32082}
32083
32084/**
32085 * Writes the given mText entity to the file.
32086 */
32087void RS_FilterDXFRW::writeMText(const RS_MText *t) {
32088 DRW_Text *text;
32089 DRW_Text txt1;
32090 DRW_MText txt2;
32091
32092 if (m_version == 1009) {
32093 text = &txt1;
32094 } else {
32095 text = &txt2;
32096 }
32097
32098 getEntityAttributes(text, t);
32099 if (m_version != 1009) {
32100 // The x axis the record was read with; zero: it had none.
32101 DRW_Coord storedXAxis;
32102 if (const auto ocs = extractMTextOcsSidecar(t)) {
32103 text->basePoint.z = ocs->elevation;
32104 text->extPoint = ocs->extrusion;
32105 storedXAxis = ocs->xAxis;
32106 removeMTextOcsSidecar(*text);
32107 } else if (t->ownerLineage() != 0) {
32108 // A block attribute no INSERT claims is written on its own, in the
32109 // plane of the MTEXT it was read with.
32110 if (const auto attribute = extractInsertAttribSidecar(t);
32111 attribute && attribute->hasMText) {
32112 text->basePoint.z = attribute->mtext.basePoint.z;
32113 text->extPoint = attribute->mtext.extPoint;
32114 storedXAxis = attribute->mtext.secPoint;
32115 }
32116 }
32117 // A text turned in the drawing since no longer has that direction, and
32118 // a reader takes group 11 for the direction where a record has it.
32119 txt2.secPoint =
32120 mtextXAxis(t->getAngle(), storedXAxis, false, m_dwgW != nullptr);
32121 }
32122 // The private groups of a block attribute's text never leave the program.
32123 removeInsertAttribSidecar(*text);
32124 text->basePoint.x = t->getInsertionPoint().x;
32125 text->basePoint.y = t->getInsertionPoint().y;
32126 text->height = t->getHeight();
32127 text->angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
32128 text->style = t->getStyle().toStdString();
32129
32130 if (m_version == 1009) {
32131 if (t->getHAlign() == RS_MTextData::HALeft) {
32132 text->alignH = DRW_Text::HLeft;
32133 } else if (t->getHAlign() == RS_MTextData::HACenter) {
32134 text->alignH = DRW_Text::HCenter;
32135 } else if (t->getHAlign() == RS_MTextData::HARight) {
32136 text->alignH = DRW_Text::HRight;
32137 }
32138 if (t->getVAlign() == RS_MTextData::VATop) {
32139 text->alignV = DRW_Text::VTop;
32140 } else if (t->getVAlign() == RS_MTextData::VAMiddle) {
32141 text->alignV = DRW_Text::VMiddle;
32142 } else if (t->getVAlign() == RS_MTextData::VABottom) {
32143 text->alignV = DRW_Text::VBaseLine;
32144 }
32145#if QT_VERSION((6<<16)|(9<<8)|(0)) >= QT_VERSION_CHECK(5, 15, 0)((5<<16)|(15<<8)|(0))
32146 QStringList txtList = t->getText().split('\n', Qt::KeepEmptyParts);
32147#else
32148 QStringList txtList = t->getText().split('\n', QString::KeepEmptyParts);
32149#endif
32150 double dist = t->getLineSpacingFactor() * 5 * t->getHeight() / 3;
32151 bool setSec = false;
32152 if (text->alignH != DRW_Text::HLeft ||
32153 text->alignV != DRW_Text::VBaseLine) {
32154 text->secPoint.x = t->getInsertionPoint().x;
32155 text->secPoint.y = t->getInsertionPoint().y;
32156 setSec = true;
32157 }
32158 if (text->alignV == DRW_Text::VTop) {
32159 dist = dist * -1;
32160 }
32161 for (int i = 0; i < txtList.size(); ++i) {
32162 if (!txtList.at(i).isEmpty()) {
32163 text->text = toDxfString(txtList.at(i), m_dwgW == nullptr).toUtf8().data();
32164 RS_Vector inc = RS_Vector::polar(dist * i, t->getAngle() + M_PI_21.57079632679489661923);
32165 if (setSec) {
32166 text->secPoint.x += inc.x;
32167 text->secPoint.y += inc.y;
32168 } else {
32169 text->basePoint.x += inc.x;
32170 text->basePoint.y += inc.y;
32171 }
32172 noteDxfWrite(m_dxfW->writeText(text));
32173 }
32174 }
32175 } else {
32176 const RS_MTextData data = t->getData();
32177 setMTextLayout(txt2, data);
32178 // The RTL preference is round-tripped via XDATA (app id "LibreCad", code
32179 // 1071); on re-import we restore the flag if the marker is present,
32180 // otherwise the entity reads as LTR.
32181 setMTextRtlMarker(*text, rtlLayoutOf(data));
32182
32183 text->text = toDxfString(t->getText(), m_dwgW == nullptr).toUtf8().data();
32184 // text->widthscale =t->getWidth();
32185 text->widthscale = t->getUsedTextWidth(); // getSize().x;
32186 if (t->hasParagraphCommands()) {
32187 // The indentations of a paragraph command are not laid out here, but a
32188 // program that reads this file needs the room for them. A box of width
32189 // 0 does not wrap and stays so; any other is not made narrower.
32190 const double given = t->getWidth();
32191 if (given == 0.0) {
32192 text->widthscale = 0.0;
32193 } else if (std::isfinite(given) && given > text->widthscale) {
32194 text->widthscale = given;
32195 }
32196 }
32197 if (!std::isfinite(text->widthscale) || text->widthscale < 0.0) {
32198 m_writeFailed = true;
32199 LC_ERRRS_Debug::Log(RS_Debug::D_ERROR) << "Cannot export MTEXT with invalid output width";
32200 return;
32201 }
32202
32203 if (m_dwgW) {
32204 text->styleH.ref = dwgWriteHandleByName(m_dwgWriteTextStyleHandleByName,
32205 text->style);
32206 if (!m_dwgW->writeMText(static_cast<DRW_MText *>(text)))
32207 m_writeFailed = true;
32208 return;
32209 }
32210 noteDxfWrite(m_dxfW->writeMText(static_cast<DRW_MText *>(text)));
32211 }
32212}
32213
32214/**
32215 * Writes the given Text entity to the file.
32216 */
32217void RS_FilterDXFRW::writeText(RS_Text *t) {
32218 DRW_Text text;
32219
32220 getEntityAttributes(&text, t);
32221 if (const auto ocs = extractTextOcsSidecar(t)) {
32222 text.basePoint.z = ocs->baseElevation;
32223 text.secPoint.z = ocs->alignmentElevation;
32224 text.extPoint = ocs->extrusion;
32225 text.thickness = ocs->thickness;
32226 removeTextOcsSidecar(text);
32227 }
32228 if (t->ownerLineage() != 0) {
32229 // A block attribute no INSERT claims is written on its own, in the plane
32230 // of the ATTRIB it was read as.
32231 if (const auto attribute = extractInsertAttribSidecar(t)) {
32232 text.basePoint.z = attribute->basePoint.z;
32233 text.secPoint.z = attribute->secPoint.z;
32234 text.extPoint = attribute->extPoint;
32235 text.thickness = attribute->thickness;
32236 }
32237 }
32238 // The groups that carried it are private to this program, whatever put them
32239 // on the text.
32240 removeInsertAttribSidecar(text);
32241 text.basePoint.x = t->getInsertionPoint().x;
32242 text.basePoint.y = t->getInsertionPoint().y;
32243 text.height = t->getHeight();
32244 text.angle = t->getAngle() * 180 / M_PI3.14159265358979323846;
32245 text.style = t->getStyle().toStdString();
32246 text.alignH = (DRW_Text::HAlign)t->getHAlign();
32247 text.alignV = (DRW_Text::VAlign)t->getVAlign();
32248 text.widthscale = t->getWidthRel();
32249
32250 if (text.alignV != DRW_Text::VBaseLine || text.alignH != DRW_Text::HLeft) {
32251 // if (text.alignV != DRW_Text::VBaseLine || text.alignH ==
32252 // DRW_Text::HMiddle) {
32253 // if (text.alignH != DRW_Text::HLeft) {
32254 if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit) {
32255 text.secPoint.x = t->getSecondPoint().x;
32256 text.secPoint.y = t->getSecondPoint().y;
32257 } else {
32258 text.secPoint.x = t->getInsertionPoint().x;
32259 text.secPoint.y = t->getInsertionPoint().y;
32260 }
32261 }
32262
32263 /* if (text.alignH == DRW_Text::HAligned || text.alignH == DRW_Text::HFit)
32264 { text.secPoint.x = t->getSecondPoint().x; text.secPoint.y =
32265 t->getSecondPoint().y;
32266 }*/
32267
32268 if (!t->getText().isEmpty()) {
32269 text.text =
32270 toDxfSingleLineString(t->getText(), m_dwgW == nullptr).toUtf8().data();
32271 if (m_dwgW) {
32272 text.styleH.ref = dwgWriteHandleByName(m_dwgWriteTextStyleHandleByName,
32273 text.style);
32274 if (!m_dwgW->writeText(&text))
32275 m_writeFailed = true;
32276 return;
32277 }
32278 noteDxfWrite(m_dxfW->writeText(&text));
32279 }
32280}
32281
32282/**
32283 * Writes the given dimension entity to the file.
32284 */
32285void RS_FilterDXFRW::writeDimension(RS_Dimension *d) {
32286 QString blkName;
32287 if (m_noNameBlock.contains(d)) {
32288 blkName = m_noNameBlock.take(d);
32289 }
32290
32291 // version 12 are inserts of *D blocks
32292 if (m_version == 1009) {
32293 if (!blkName.isEmpty()) {
32294 DRW_Insert in;
32295 getEntityAttributes(&in, d);
32296 in.basePoint.x = in.basePoint.y = 0.0;
32297 in.basePoint.z = 0.0;
32298 in.name = blkName.toStdString();
32299 in.xscale = in.yscale = 1.0;
32300 in.zscale = 1.0;
32301 in.angle = 0.0;
32302 in.colcount = in.rowcount = 1;
32303 in.colspace = in.rowspace = 0.0;
32304 noteDxfWrite(m_dxfW->writeInsert(&in));
32305 }
32306 return;
32307 }
32308
32309 DRW_Dimension *dim;
32310 int attachmentPoint = 1;
32311 if (d->getHAlign() == RS_MTextData::HALeft) {
32312 attachmentPoint = 1;
32313 } else if (d->getHAlign() == RS_MTextData::HACenter) {
32314 attachmentPoint = 2;
32315 } else if (d->getHAlign() == RS_MTextData::HARight) {
32316 attachmentPoint = 3;
32317 }
32318 if (d->getVAlign() == RS_MTextData::VATop) {
32319 attachmentPoint += 0;
32320 } else if (d->getVAlign() == RS_MTextData::VAMiddle) {
32321 attachmentPoint += 3;
32322 } else if (d->getVAlign() == RS_MTextData::VABottom) {
32323 attachmentPoint += 6;
32324 }
32325
32326 switch (d->rtti()) {
32327 case RS2::EntityDimAligned: {
32328 auto *da = static_cast<RS_DimAligned *>(d);
32329 auto dd = new DRW_DimAligned();
32330 dim = dd;
32331 dim->type = 1 + 32;
32332 dd->setDef1Point(
32333 DRW_Coord(da->getExtensionPoint1().x, da->getExtensionPoint1().y, 0.0));
32334 dd->setDef2Point(
32335 DRW_Coord(da->getExtensionPoint2().x, da->getExtensionPoint2().y, 0.0));
32336 break;
32337 }
32338 case RS2::EntityDimDiametric: {
32339 auto *dr = static_cast<RS_DimDiametric *>(d);
32340 auto dd = new DRW_DimDiametric();
32341 dim = dd;
32342 dim->type = 3 + 32;
32343 dd->setDiameter1Point(
32344 DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
32345 dd->setLeaderLength(dr->getLeader());
32346 break;
32347 }
32348 case RS2::EntityDimRadial: {
32349 auto *dr = static_cast<RS_DimRadial *>(d);
32350 auto *dd = new DRW_DimRadial();
32351 dim = dd;
32352 dim->type = 4 + 32;
32353 dd->setDiameterPoint(
32354 DRW_Coord(dr->getDefinitionPoint().x, dr->getDefinitionPoint().y, 0.0));
32355 dd->setLeaderLength(dr->getLeader());
32356 break;
32357 }
32358 case RS2::EntityDimAngular: {
32359 auto *da = static_cast<RS_DimAngular *>(d);
32360 if (da->getDefinitionPoint3() == da->getGenericData().definitionPoint) {
32361 auto *dd = new DRW_DimAngular3p();
32362 dim = dd;
32363 dim->type = 5 + 32;
32364 dd->setFirstLine(DRW_Coord(da->getDefinitionPoint1().x,
32365 da->getDefinitionPoint1().y, 0.0)); // 13
32366 dd->setSecondLine(DRW_Coord(da->getDefinitionPoint2().x,
32367 da->getDefinitionPoint2().y, 0.0)); // 14
32368 dd->SetVertexPoint(DRW_Coord(da->getDefinitionPoint3().x,
32369 da->getDefinitionPoint3().y, 0.0)); // 15
32370 dd->setDimPoint(DRW_Coord(da->getDefinitionPoint().x,
32371 da->getDefinitionPoint().y, 0.0)); // 10
32372 } else {
32373 auto *dd = new DRW_DimAngular();
32374 dim = dd;
32375 dim->type = 2 + 32;
32376 dd->setFirstLine1(DRW_Coord(da->getDefinitionPoint1().x,
32377 da->getDefinitionPoint1().y, 0.0)); // 13
32378 dd->setFirstLine2(DRW_Coord(da->getDefinitionPoint2().x,
32379 da->getDefinitionPoint2().y, 0.0)); // 14
32380 dd->setSecondLine1(DRW_Coord(da->getDefinitionPoint3().x,
32381 da->getDefinitionPoint3().y, 0.0)); // 15
32382 dd->setDimPoint(DRW_Coord(da->getDefinitionPoint4().x,
32383 da->getDefinitionPoint4().y, 0.0)); // 16
32384 }
32385 break;
32386 }
32387 case RS2::EntityDimOrdinate: {
32388 auto *da = static_cast<LC_DimOrdinate *>(d);
32389 auto *dd = new DRW_DimOrdinate();
32390 dim = dd;
32391 dd->type = 6 + 32;
32392 auto dimOridinateData = da->getEData();
32393 if (dimOridinateData.ordinateForX) {
32394 dd->type = 6 + 64;
32395 }
32396 dd->setOriginPoint(
32397 DRW_Coord(da->getDefinitionPoint().x, da->getDefinitionPoint().y, 0.0));
32398 dd->setSecondLine(
32399 DRW_Coord(da->getLeaderEndPoint().x, da->getLeaderEndPoint().y, 0.0));
32400 dd->setFirstLine(
32401 DRW_Coord(da->getFeaturePoint().x, da->getFeaturePoint().y, 0.0));
32402 break;
32403 }
32404 case RS2::EntityDimArc: {
32405 auto *da = static_cast<LC_DimArc *>(d);
32406 auto *dd = new DRW_DimArc();
32407 dim = dd;
32408 RS_Vector centre = da->getCenter();
32409 double r = da->getRadius();
32410 double a0 = da->getStartAngle();
32411 double a1 = da->getEndAngle();
32412 double amid = (a0 + a1) / 2.0;
32413 dd->setArcCenter(DRW_Coord(centre.x, centre.y, 0.));
32414 dd->setArcDefPoint(DRW_Coord(centre.x + r * std::cos(amid),
32415 centre.y + r * std::sin(amid), 0.));
32416 dd->setExtLine1(DRW_Coord(centre.x + r * std::cos(a0),
32417 centre.y + r * std::sin(a0), 0.));
32418 dd->setExtLine2(DRW_Coord(centre.x + r * std::cos(a1),
32419 centre.y + r * std::sin(a1), 0.));
32420 dd->arcStartAngle = a0;
32421 dd->arcEndAngle = a1;
32422 dd->arcSymbol = da->getArcSymbol();
32423 dd->isPartial = da->getIsPartial();
32424 dd->hasLeader = da->getHasLeader();
32425 if (da->getLeaderPt1().valid)
32426 dd->setLeaderPt1(
32427 DRW_Coord(da->getLeaderPt1().x, da->getLeaderPt1().y, 0.));
32428 if (da->getLeaderPt2().valid)
32429 dd->leaderPt2 = DRW_Coord(da->getLeaderPt2().x, da->getLeaderPt2().y, 0.);
32430 break;
32431 }
32432 default: {
32433 // default to DimLinear
32434 auto dl = static_cast<RS_DimLinear *>(d);
32435 auto dd = new DRW_DimLinear();
32436 dim = dd;
32437 dim->type = 0 + 32;
32438 dd->setDef1Point(
32439 DRW_Coord(dl->getExtensionPoint1().x, dl->getExtensionPoint1().y, 0.0));
32440 dd->setDef2Point(
32441 DRW_Coord(dl->getExtensionPoint2().x, dl->getExtensionPoint2().y, 0.0));
32442 dd->setAngle(RS_Math::rad2deg(dl->getAngle()));
32443 dd->setOblique(dl->getOblique());
32444 break;
32445 }
32446 }
32447 getEntityAttributes(dim, d);
32448 dim->setDefPoint(
32449 DRW_Coord(d->getDefinitionPoint().x, d->getDefinitionPoint().y, 0));
32450 dim->setTextPoint(
32451 DRW_Coord(d->getMiddleOfText().x, d->getMiddleOfText().y, 0));
32452 dim->setStyle(d->getStyle().toUtf8().data());
32453 dim->setAlign(attachmentPoint);
32454 dim->setTextLineStyle(d->getLineSpacingStyle());
32455 dim->setText(toDxfString(d->getLabel(false), m_dwgW == nullptr).toUtf8().data());
32456 dim->setTextLineFactor(d->getLineSpacingFactor());
32457 dim->setHDir(d->getHDir());
32458 dim->setFlipArrow1(d->isFlipArrow1());
32459 dim->setFlipArrow2(d->isFlipArrow2());
32460 if (d->hasUserDefinedTextLocation()) {
32461 dim->type = dim->type + 128;
32462 }
32463 if (!blkName.isEmpty()) {
32464 dim->setName(blkName.toStdString());
32465 }
32466 if (m_dwgW) {
32467 bool hasPredictedClass = false;
32468 if (d->rtti() == RS2::EntityDimArc && d->sourceHandle() != 0) {
32469 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(d);
32470 std::uint16_t classNumber = 0;
32471 bool committed = false;
32472 hasPredictedClass = entityIt != m_dwgWriteEntityHandleByEntity.end() &&
32473 findDwgClassFrameAdmission(
32474 DwgWriteSourceKind::Entity, d->sourceHandle(),
32475 entityIt->second, classNumber, committed) &&
32476 !committed;
32477 if (hasPredictedClass &&
32478 !m_dwgW->setDwgObjectFrameProvenance(classNumber, 0, 0)) {
32479 m_writeFailed = true;
32480 delete dim;
32481 return;
32482 }
32483 }
32484 if (!m_dwgW->writeDimension(dim)) {
32485 if (hasPredictedClass)
32486 m_dwgW->clearDwgObjectFrameProvenance();
32487 m_writeFailed = true;
32488 }
32489 delete dim;
32490 return;
32491 }
32492 LC_DimStyle *override = d->getDimStyleOverride();
32493 if (override != nullptr) {
32494 LC_ExtEntityData extEntityData;
32495 addDimStyleOverrideToExtendedData(&extEntityData, override);
32496 fillEntityExtData(dim->extData, &extEntityData);
32497 }
32498 noteDxfWrite(m_dxfW->writeDimension(dim));
32499 delete dim;
32500}
32501
32502void RS_FilterDXFRW::writeTolerance(LC_Tolerance *t) {
32503 if (t == nullptr)
32504 return;
32505
32506 DRW_Tolerance tol;
32507 getEntityAttributes(&tol, t);
32508 const LC_ToleranceData data = t->getData();
32509 tol.insertionPoint =
32510 DRW_Coord(data.insertionPoint.x, data.insertionPoint.y, 0.0);
32511 tol.xAxisDirectionVector =
32512 DRW_Coord(data.directionVector.x, data.directionVector.y, 0.0);
32513 tol.extPoint = DRW_Coord(0.0, 0.0, 1.0);
32514 tol.text = toDxfString(data.textCode, m_dwgW == nullptr).toUtf8().constData();
32515 const QString style =
32516 data.dimStyleName.isEmpty() ? m_dimStyle : data.dimStyleName;
32517 tol.dimStyleName = style.toUtf8().constData();
32518
32519 if (m_dwgW) {
32520 if (!m_dwgW->writeTolerance(&tol))
32521 m_writeFailed = true;
32522 return;
32523 }
32524 if (m_dxfW) {
32525 noteDxfWrite(m_dxfW->writeTolerance(&tol));
32526 }
32527}
32528
32529/**
32530 * Writes the given leader entity to the file.
32531 */
32532void RS_FilterDXFRW::writeLeader(const RS_Leader *l) {
32533 if (l->count() <= 0) {
32534 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING, "dropping leader with no vertices");
32535 return;
32536 }
32537
32538 DRW_Leader leader;
32539 getEntityAttributes(&leader, l);
32540 const QString styleName = l->getData().styleName;
32541 leader.style =
32542 (styleName.isEmpty() ? "Standard" : styleName.toUtf8().toStdString());
32543 leader.arrow = l->hasArrowHead();
32544 leader.leadertype = 0;
32545 leader.flag = 3;
32546 leader.hookline = 0;
32547 leader.hookflag = 0;
32548 leader.textheight = 1;
32549 leader.textwidth = 10;
32550
32551 RS_Line *li = nullptr;
32552 for (RS_Entity *v :
32553 lc::LC_ContainerTraverser{*l, RS2::ResolveNone}.entities()) {
32554 if (v->rtti() == RS2::EntityLine) {
32555 li = static_cast<RS_Line *>(v);
32556 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
32557 li->getStartpoint().x, li->getStartpoint().y, 0.0));
32558 }
32559 }
32560 if (li) {
32561 leader.vertexlist.push_back(std::make_shared<DRW_Coord>(
32562 li->getEndpoint().x, li->getEndpoint().y, 0.0));
32563 }
32564
32565 if (leader.vertexlist.size() < 2) {
32566 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
32567 "dropping leader with fewer than 2 vertices");
32568 return;
32569 }
32570 leader.vertnum = static_cast<int>(leader.vertexlist.size());
32571
32572 if (m_dwgW) {
32573 if (!m_dwgW->writeLeader(&leader))
32574 m_writeFailed = true;
32575 return;
32576 }
32577 noteDxfWrite(m_dxfW->writeLeader(&leader));
32578}
32579
32580namespace {
32581
32582// Fill the clamped uniform knot vector a control-point spline must carry.
32583//
32584// A DXF SPLINE always stores its knots (group 40), and libdxfrw's payload
32585// validation requires knots == controls + degree + 1
32586// (isValidControlSplineLayout in drw_entities.cpp). Emitting none produced a
32587// boundary edge that dxfRW::validateHatchPayload() rejects, and because a
32588// rejected entity fails the whole write, a drawing containing a
32589// spline-bordered hatch could not be exported to DXF at all.
32590//
32591// The vector generated here is the one the reader used to re-derive for an
32592// empty knot list, so consumers see the same curve as before.
32593void fillClampedUniformKnots(DRW_Spline &drw) {
32594 const std::int32_t degree = drw.degree;
32595 const std::int32_t controls = static_cast<std::int32_t>(drw.controllist.size());
32596 if (degree < 1 || controls < degree + 1)
32597 return;
32598 const std::int32_t spans = controls - degree;
32599 drw.knotslist.clear();
32600 drw.knotslist.reserve(static_cast<std::size_t>(controls + degree + 1));
32601 for (std::int32_t i = 0; i <= degree; ++i)
32602 drw.knotslist.push_back(0.0);
32603 for (std::int32_t i = 1; i < spans; ++i)
32604 drw.knotslist.push_back(static_cast<double>(i));
32605 for (std::int32_t i = 0; i <= degree; ++i)
32606 drw.knotslist.push_back(static_cast<double>(spans));
32607 drw.nknots = static_cast<std::int32_t>(drw.knotslist.size());
32608}
32609
32610// Emit an LC_SplinePoints boundary edge as a degree-2 DRW_Spline. Either
32611// the control net or the fit points are populated depending on the
32612// LC_SplinePointsData mode. See plan §B.1.
32613std::shared_ptr<DRW_Spline>
32614makeDrwSplineFromSplinePoints(const LC_SplinePoints *sp) {
32615 auto drw = std::make_shared<DRW_Spline>();
32616 drw->degree = 2;
32617 // Closed boundary edges set BOTH bit 0 (DXF entity-level "closed")
32618 // AND bit 1 (DWG-boundary / DXF group code 74 "periodic"). The hatch
32619 // boundary read path accepts either bit; this keeps cross-format
32620 // round-trip consistent.
32621 drw->flags = sp->isClosed() ? 0x3 : 0x0;
32622 const auto &d = sp->getData();
32623 if (d.useControlPoints && !d.controlPoints.empty()) {
32624 for (const auto &v : d.controlPoints)
32625 drw->controllist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
32626 drw->ncontrol = static_cast<std::int32_t>(d.controlPoints.size());
32627 fillClampedUniformKnots(*drw);
32628 } else {
32629 for (const auto &v : d.splinePoints)
32630 drw->fitlist.push_back(std::make_shared<DRW_Coord>(v.x, v.y, 0.0));
32631 drw->nfit = static_cast<std::int32_t>(d.splinePoints.size());
32632 }
32633 return drw;
32634}
32635
32636// Emit an RS_Spline (general NURBS, degree 1-3, possibly rational) boundary
32637// edge as a DRW_Spline carrying its full data. Mirrors weight into both
32638// drw->weightlist[i] AND drw->controllist[i]->z so both DXF and DWG
32639// consumers can read it correctly.
32640//
32641// A closed RS_Spline repeats its first degree control points and weights at
32642// the end of its data, with a knot vector to match, so that the data draws
32643// the closed curve over [knots[degree], knots[count]] in any reader. Write it
32644// as it is: getControlPoints() and getWeights() leave the repeats out, and
32645// with the whole knot vector they gave an edge with degree knots too many.
32646std::shared_ptr<DRW_Spline> makeDrwSplineFromRSSpline(const RS_Spline *sp) {
32647 auto drw = std::make_shared<DRW_Spline>();
32648 const auto &sd = sp->getData();
32649 drw->degree = static_cast<std::int32_t>(sd.degree);
32650
32651 const auto &cps = sd.controlPoints;
32652 const auto &ws = sd.weights;
32653 const bool weightsAlign = (ws.size() == cps.size());
32654 const bool isRational =
32655 weightsAlign && std::any_of(ws.begin(), ws.end(), [](double w) {
32656 return std::abs(w - 1.0) > 1e-9;
32657 });
32658 // Set both DXF entity-level closed (bit 0) and hatch-boundary
32659 // periodic (bit 1) for cross-format consistency. See helper above.
32660 drw->flags = (sp->isClosed() ? 0x3 : 0x0) | (isRational ? 0x4 : 0x0);
32661
32662 for (size_t i = 0; i < cps.size(); ++i) {
32663 const double w = (isRational && i < ws.size()) ? ws[i] : 1.0;
32664 drw->controllist.push_back(
32665 std::make_shared<DRW_Coord>(cps[i].x, cps[i].y, w));
32666 }
32667 drw->ncontrol = static_cast<std::int32_t>(cps.size());
32668 if (isRational) {
32669 drw->weightlist = ws;
32670 }
32671 drw->knotslist = sd.knotslist;
32672 drw->nknots = static_cast<std::int32_t>(sd.knotslist.size());
32673 if (drw->knotslist.empty())
32674 fillClampedUniformKnots(*drw);
32675 return drw;
32676}
32677
32678// Convert every atomic edge of `source` into a DRW boundary-edge object and
32679// append it to `loop`. Shared by every path written by the hatch exporter
32680// so the per-edge layout stays in one place. The container that encloses
32681// an RS_Spline is handled by caller lookup: it reaches here as the
32682// (chord-flattened or clone-preserving) container `exportLoops()` built.
32683void appendHatchLoopEdges(DRW_HatchLoop& loop,
32684 const RS_EntityContainer& source) {
32685 std::vector<const RS_Entity*> edges;
32686 for (const RS_Entity* ed :
32687 lc::LC_ContainerTraverser{source, RS2::ResolveNone}.entities()) {
32688 if (ed == nullptr) continue;
32689 if (ed->isContainer() && ed->rtti() != RS2::EntitySpline) {
32690 const auto parts = lc::LC_ContainerTraverser{
32691 *static_cast<const RS_EntityContainer*>(ed),
32692 RS2::ResolveAll}.entities();
32693 edges.insert(edges.end(), parts.begin(), parts.end());
32694 } else {
32695 edges.push_back(ed);
32696 }
32697 }
32698 for (const RS_Entity* ed : edges) {
32699 if (ed->rtti() == RS2::EntityLine) {
32700 const auto* ln = static_cast<const RS_Line*>(ed);
32701 auto line = std::make_shared<DRW_Line>();
32702 line->basePoint.x = ln->getStartpoint().x;
32703 line->basePoint.y = ln->getStartpoint().y;
32704 line->secPoint.x = ln->getEndpoint().x;
32705 line->secPoint.y = ln->getEndpoint().y;
32706 loop.objlist.push_back(line);
32707 } else if (ed->rtti() == RS2::EntityArc) {
32708 const auto* ar = static_cast<const RS_Arc*>(ed);
32709 auto arc = std::make_shared<DRW_Arc>();
32710 arc->basePoint.x = ar->getCenter().x;
32711 arc->basePoint.y = ar->getCenter().y;
32712 arc->radious = ar->getRadius();
32713 if (!ar->isReversed()) {
32714 arc->staangle = ar->getAngle1();
32715 arc->endangle = ar->getAngle2();
32716 arc->isccw = true;
32717 } else {
32718 arc->staangle = 2 * M_PI3.14159265358979323846 - ar->getAngle1();
32719 arc->endangle = 2 * M_PI3.14159265358979323846 - ar->getAngle2();
32720 arc->isccw = false;
32721 }
32722 loop.objlist.push_back(arc);
32723 } else if (ed->rtti() == RS2::EntityCircle) {
32724 const auto* ci = static_cast<const RS_Circle*>(ed);
32725 auto arc = std::make_shared<DRW_Arc>();
32726 arc->basePoint.x = ci->getCenter().x;
32727 arc->basePoint.y = ci->getCenter().y;
32728 arc->radious = ci->getRadius();
32729 arc->staangle = 0.0;
32730 arc->endangle = 2 * M_PI3.14159265358979323846;
32731 arc->isccw = true;
32732 loop.objlist.push_back(arc);
32733 } else if (ed->rtti() == RS2::EntityEllipse) {
32734 const auto* el = static_cast<const RS_Ellipse*>(ed);
32735 auto ell = std::make_shared<DRW_Ellipse>();
32736 ell->basePoint.x = el->getCenter().x;
32737 ell->basePoint.y = el->getCenter().y;
32738 ell->secPoint.x = el->getMajorP().x;
32739 ell->secPoint.y = el->getMajorP().y;
32740 ell->ratio = el->getRatio();
32741 double rot = el->getMajorP().angle();
32742 double startAng =
32743 el->getCenter().angleTo(el->getStartpoint()) - rot;
32744 double endAng = el->getCenter().angleTo(el->getEndpoint()) - rot;
32745 if (startAng < 0) startAng = M_PI3.14159265358979323846 * 2 + startAng;
32746 if (endAng < 0) endAng = M_PI3.14159265358979323846 * 2 + endAng;
32747 ell->staparam = startAng;
32748 ell->endparam = endAng;
32749 ell->isccw = !el->isReversed();
32750 loop.objlist.push_back(ell);
32751 } else if (ed->rtti() == RS2::EntitySplinePoints) {
32752 loop.objlist.push_back(makeDrwSplineFromSplinePoints(
32753 static_cast<const LC_SplinePoints*>(ed)));
32754 } else if (ed->rtti() == RS2::EntitySpline) {
32755 loop.objlist.push_back(makeDrwSplineFromRSSpline(
32756 static_cast<const RS_Spline*>(ed)));
32757 }
32758 }
32759}
32760
32761} // namespace
32762
32763/**
32764 * Writes the given hatch entity to the file.
32765 */
32766void RS_FilterDXFRW::writeHatch(RS_Hatch *h) {
32767 // version 12 are inserts of *U blocks
32768 if (m_version == 1009) {
32769 if (m_noNameBlock.contains(h)) {
32770 DRW_Insert in;
32771 getEntityAttributes(&in, h);
32772 in.basePoint.x = in.basePoint.y = 0.0;
32773 in.basePoint.z = 0.0;
32774 in.name = m_noNameBlock.value(h).toUtf8().data();
32775 in.xscale = in.yscale = 1.0;
32776 in.zscale = 1.0;
32777 in.angle = 0.0;
32778 in.colcount = in.rowcount = 1;
32779 in.colspace = in.rowspace = 0.0;
32780 noteDxfWrite(m_dxfW->writeInsert(&in));
32781 }
32782 return;
32783 }
32784
32785 // Build one boundary path per extracted loop, in WCS, flagged by the
32786 // loop's depth in the extracted tree. Dropped hatches are those whose
32787 // validate() never produced an ordered tree; exportLoops() returns
32788 // empty for them.
32789 auto loops = h->exportLoops();
32790 if (loops.empty()) {
32791 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
32792 "RS_FilterDXF::writeHatch: Dropping Hatch");
32793 return;
32794 }
32795
32796 DRW_Hatch ha;
32797 getEntityAttributes(&ha, h);
32798 if (const auto ocs = extractHatchExtrusionSidecar(h)) {
32799 ha.basePoint.z = ocs->elevation;
32800 ha.extPoint = ocs->extrusion;
32801 removeHatchExtrusionSidecar(ha);
32802 }
32803 ha.solid = h->isSolid();
32804 ha.scale = h->getScale();
32805 ha.angle = h->getAngle();
32806 if (ha.solid) {
32807 ha.name = "SOLID";
32808 } else {
32809 ha.name = h->getPattern().toUtf8().data();
32810 }
32811
32812 // Realized definition lines, pattern type 2, and the approximate XDATA tag.
32813 // ODA File Converter 27.1 deletes every non-solid HATCH written with 78 = 0
32814 // (LibreCAD before this change), so a pattern hatch always carries at least
32815 // one line; a solid hatch keeps 78 = 0 and the stale tag is dropped. The
32816 // 1001 kApplication group under which the tag lives is already registered
32817 // by writeAppId (emitRequired("LibreCad")).
32818 if (ha.solid) {
32819 LC_HatchDefinitionLines::setApproximateTag(ha.extData, false);
32820 ha.hpattern = 1;
32821 ha.hstyle = 0;
32822 ha.doubleflag = 0;
32823 } else {
32824 const LC_HatchDefinitionLines::ExportLines definition =
32825 LC_HatchDefinitionLines::forExport(*h);
32826 ha.patternLines.clear();
32827 ha.patternLines.reserve(definition.lines.size());
32828 for (const auto& line : definition.lines)
32829 ha.patternLines.push_back(line);
32830 ha.deflines = static_cast<int>(ha.patternLines.size());
32831 ha.hpattern = 2;
32832 ha.hstyle = 0;
32833 ha.doubleflag = 0;
32834 LC_HatchDefinitionLines::setApproximateTag(ha.extData,
32835 definition.approximate);
32836 }
32837
32838 for (const auto& loop : loops) {
32839 if (!loop.edges) continue;
32840 auto lData = std::make_shared<DRW_HatchLoop>(
32841 LC_HatchDefinitionLines::boundaryPathFlags(loop.depth));
32842 appendHatchLoopEdges(*lData, *loop.edges);
32843 lData->update();
32844 ha.appendLoop(lData);
32845 }
32846 ha.loopsnum = static_cast<int>(ha.looplist.size());
32847 if (m_dwgW) {
32848 if (!m_dwgW->writeHatch(&ha))
32849 m_writeFailed = true;
32850 return;
32851 }
32852 noteDxfWrite(m_dxfW->writeHatch(&ha));
32853}
32854
32855/**
32856 * Writes the given Solid entity to the file.
32857 */
32858void RS_FilterDXFRW::writeSolid(RS_Solid *s) {
32859 RS_SolidData data;
32860 DRW_Solid solid;
32861 RS_Vector corner;
32862 getEntityAttributes(&solid, s);
32863 corner = s->getCorner(0);
32864 solid.basePoint.x = corner.x;
32865 solid.basePoint.y = corner.y;
32866 corner = s->getCorner(1);
32867 solid.secPoint.x = corner.x;
32868 solid.secPoint.y = corner.y;
32869 corner = s->getCorner(2);
32870 solid.thirdPoint.x = corner.x;
32871 solid.thirdPoint.y = corner.y;
32872 if (s->isTriangle()) {
32873 solid.fourPoint.x = solid.thirdPoint.x;
32874 solid.fourPoint.y = solid.thirdPoint.y;
32875 } else {
32876 corner = s->getCorner(3);
32877 solid.fourPoint.x = corner.x;
32878 solid.fourPoint.y = corner.y;
32879 }
32880 if (m_dwgW) {
32881 if (!m_dwgW->writeSolid(&solid))
32882 m_writeFailed = true;
32883 return;
32884 }
32885 noteDxfWrite(m_dxfW->writeSolid(&solid));
32886}
32887
32888void RS_FilterDXFRW::writeImage(const RS_Image *i) {
32889 if (i == nullptr)
32890 return;
32891
32892 DRW_Image image;
32893 getEntityAttributes(&image, i);
32894
32895 if (const auto frame = extractImageFrameSidecar(i)) {
32896 image.basePoint.z = frame->insertion.z;
32897 image.secPoint.z = frame->uVector.z;
32898 image.vVector.z = frame->vVector.z;
32899 removeImageFrameSidecar(image);
32900 }
32901
32902 image.basePoint.x = i->getInsertionPoint().x;
32903 image.basePoint.y = i->getInsertionPoint().y;
32904 image.secPoint.x = i->getUVector().x;
32905 image.secPoint.y = i->getUVector().y;
32906 image.vVector.x = i->getVVector().x;
32907 image.vVector.y = i->getVVector().y;
32908 image.sizeu = i->getWidth();
32909 image.sizev = i->getHeight();
32910 image.brightness = i->getBrightness();
32911 image.contrast = i->getContrast();
32912 image.fade = i->getFade();
32913
32914 const LC_DwgAdvancedMetadata::RasterImageRecord *rasterRecord = nullptr;
32915 const LC_DwgAdvancedMetadata::ImageDefinitionRecord *definitionRecord =
32916 nullptr;
32917 const LC_DwgAdvancedMetadata::ImageDefinitionReactorRecord *reactorRecord =
32918 nullptr;
32919 DRW_ImageDef definitionTemplate;
32920 DRW_ImageDefinitionReactor reactorTemplate;
32921 if (m_graphic != nullptr) {
32922 const auto &metadata = m_graphic->dwgAdvancedMetadata();
32923 rasterRecord = metadata.findRasterImageByHandle(
32924 static_cast<std::uint32_t>(i->sourceHandle()));
32925 if (rasterRecord == nullptr) {
32926 const std::uint32_t definitionHandle =
32927 static_cast<std::uint32_t>(i->getHandle());
32928 for (const auto &record : metadata.rasterImages()) {
32929 if (record.definitionHandle == definitionHandle) {
32930 rasterRecord = &record;
32931 break;
32932 }
32933 }
32934 }
32935 if (rasterRecord != nullptr) {
32936 image.m_classVersion = rasterRecord->classVersion;
32937 image.m_displayProps = rasterRecord->displayProps;
32938 image.clip = rasterRecord->clip;
32939 image.clipMode = rasterRecord->clipMode;
32940 image.m_clipBoundaryType = rasterRecord->clipBoundaryType;
32941 image.clipPath = rasterRecord->clipPath;
32942 definitionRecord =
32943 metadata.findImageDefinitionByHandle(rasterRecord->definitionHandle);
32944 if (definitionRecord != nullptr)
32945 definitionTemplate = imageDefinitionFromMetadata(*definitionRecord);
32946 reactorRecord = metadata.findImageDefinitionReactorByHandle(
32947 rasterRecord->reactorHandle);
32948 if (reactorRecord != nullptr)
32949 reactorTemplate = imageDefinitionReactorFromMetadata(*reactorRecord);
32950 }
32951 }
32952
32953 std::string fileName = i->getFile().toUtf8().constData();
32954 if (fileName.empty()) {
32955 // IMAGE requires a reachable IMAGEDEF. A valid fallback name also keeps
32956 // multiple unnamed images distinct in the image dictionary.
32957 fileName = "librecad_image_" + std::to_string(i->getId());
32958 }
32959
32960#ifdef DWGSUPPORT1
32961 if (m_dwgW != nullptr) {
32962 if ((definitionRecord != nullptr &&
32963 hasDxfTableAppDataUnsupportedByDwg(definitionTemplate)) ||
32964 (reactorRecord != nullptr &&
32965 hasDxfTableAppDataUnsupportedByDwg(reactorTemplate))) {
32966 m_writeFailed = true;
32967 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
32968 "RS_FilterDXFRW::writeImage: DWG cannot preserve IMAGE "
32969 "custom application groups");
32970 return;
32971 }
32972 if (!m_dwgW->writeImage(
32973 &image, &fileName,
32974 definitionRecord != nullptr ? &definitionTemplate : nullptr,
32975 reactorRecord != nullptr ? &reactorTemplate : nullptr)) {
32976 m_writeFailed = true;
32977 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
32978 "RS_FilterDXFRW::writeImage: DWG writer rejected IMAGE");
32979 }
32980 return;
32981 }
32982#endif
32983 if (m_dxfW == nullptr)
32984 return;
32985
32986 DRW_ImageDef *imgDef =
32987 m_dxfW->writeImage(&image, fileName);
32988 if (imgDef == nullptr) {
32989 // dxfRW::writeImage() also returns nullptr, with no error, to leave
32990 // IMAGE out at R12 and older (counted in leftOut()); only any other
32991 // nullptr is an actual write failure.
32992 if (m_dxfW->getVersion() > DRW::AC1009)
32993 m_writeFailed = true;
32994 }
32995 if (imgDef) {
32996 if (definitionRecord != nullptr) {
32997 imgDef->imgVersion = definitionRecord->classVersion;
32998 imgDef->loaded = definitionRecord->loaded;
32999 imgDef->u = definitionRecord->imageSizeU;
33000 imgDef->v = definitionRecord->imageSizeV;
33001 imgDef->up = definitionRecord->pixelSizeU;
33002 imgDef->vp = definitionRecord->pixelSizeV;
33003 imgDef->resolution = definitionRecord->resolution;
33004 } else {
33005 imgDef->loaded = 1;
33006 imgDef->u = i->getData().size.x;
33007 imgDef->v = i->getData().size.y;
33008 imgDef->up = 1;
33009 imgDef->vp = 1;
33010 imgDef->resolution = 0;
33011 }
33012 }
33013}
33014
33015void RS_FilterDXFRW::writeWipeout(LC_Wipeout *w) {
33016 if (w == nullptr) {
33017 return;
33018 }
33019#ifdef DWGSUPPORT1
33020 if (m_dwgW != nullptr && !isDwgWipeoutAdmitted(w)) {
33021 m_writeFailed = true;
33022 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR, "RS_FilterDXFRW::writeWipeout: DWG "
33023 "cannot preserve external references");
33024 return;
33025 }
33026#endif
33027 DRW_Wipeout img;
33028 getEntityAttributes(&img, w);
33029 const LC_WipeoutData &data = w->getData();
33030 if (data.hasNativeFrame) {
33031 img.basePoint = DRW_Coord(data.insertionPoint.x, data.insertionPoint.y,
33032 data.insertionPoint.z);
33033 img.secPoint = DRW_Coord(data.uPixel.x, data.uPixel.y, data.uPixel.z);
33034 img.vVector = DRW_Coord(data.vPixel.x, data.vPixel.y, data.vPixel.z);
33035 img.sizeu = data.sizeU;
33036 img.sizev = data.sizeV;
33037 img.m_displayProps = data.displayProps;
33038 img.ref = data.imageDefHandle;
33039 img.m_imageDefReactorHandle = data.imageDefReactorHandle;
33040 img.clip = data.clip;
33041 img.brightness = data.brightness;
33042 img.contrast = data.contrast;
33043 img.fade = data.fade;
33044 img.m_clipBoundaryType = data.clipBoundaryType;
33045 img.clipMode = data.clipMode;
33046 img.clipPath.reserve(data.clipPath.size());
33047 for (const RS_Vector &clipPoint : data.clipPath)
33048 img.clipPath.emplace_back(clipPoint.x, clipPoint.y, clipPoint.z);
33049 } else {
33050 // User-created WIPEOUTs have no source frame. Emit a documented canonical
33051 // pixel basis whose inverse maps the world polygon without approximation.
33052 img.basePoint = DRW_Coord(0.0, 0.0, 0.0);
33053 img.secPoint = DRW_Coord(1.0, 0.0, 0.0);
33054 img.vVector = DRW_Coord(0.0, 1.0, 0.0);
33055 img.sizeu = 1.0;
33056 img.sizev = 1.0;
33057 img.clip = 1;
33058 img.brightness = 50;
33059 img.contrast = 50;
33060 img.fade = 0;
33061 img.m_clipBoundaryType = 2;
33062 img.clipMode = false;
33063 img.clipPath.reserve(w->getVertices().size());
33064 for (const RS_Vector &vertex : w->getVertices())
33065 img.clipPath.emplace_back(vertex.x - 0.5, vertex.y - 0.5, vertex.z);
33066 }
33067
33068 constexpr std::size_t kMaxDwgWipeoutClipVertices = 100000u;
33069 if (img.clipPath.size() > kMaxDwgWipeoutClipVertices) {
33070 m_writeFailed = true;
33071 RS_DEBUGRS_Debug::instance()->print(
33072 RS_Debug::D_ERROR,
33073 "RS_FilterDXFRW::writeWipeout: clip path exceeds DWG limit");
33074 return;
33075 }
33076 bool written = false;
33077#ifdef DWGSUPPORT1
33078 if (m_dwgW != nullptr)
33079 written = m_dwgW->writeWipeout(&img);
33080 else
33081#endif
33082 if (m_dxfW != nullptr)
33083 written = m_dxfW->writeWipeout(&img);
33084 if (!written) {
33085 m_writeFailed = true;
33086 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_ERROR,
33087 "RS_FilterDXFRW::writeWipeout: writer rejected entity");
33088 }
33089}
33090
33091/**
33092 * Serialize an LC_MLeader. DWG AC1024+ writes a native text-content
33093 * MULTILEADER subset with context roots, leader lines, and text content.
33094 * DXF keeps the older scalar/context writer. Unsupported complex content
33095 * still falls back to visible LEADER/MTEXT geometry on DWG export.
33096 */
33097RS_FilterDXFRW::DwgEntityProductionResult
33098RS_FilterDXFRW::writeMLeader(LC_MLeader *m) {
33099 if (m == nullptr)
1
Assuming the condition is false
2
Taking false branch
33100 return DwgEntityProductionResult::Failed;
33101 DRW_MLeader e;
33102 getEntityAttributes(&e, m);
33103 const auto &d = m->getData();
33104 e.overrideFlags = 0; // Phase 9 doesn't track override bits;
33105 // emit defaults so a re-read picks the
33106 // entity-level values as authoritative.
33107 e.leaderType = d.leaderType;
33108 e.leaderColor = d.leaderColor;
33109 e.landingDistance = d.landingDistance;
33110 e.defaultArrowHeadSize = d.arrowSize;
33111 e.landingEnabled = d.landingEnabled;
33112 e.doglegEnabled = d.doglegEnabled;
33113 e.styleContentType = d.contentType;
33114 e.scaleFactor = d.scaleFactor;
33115 e.classVersion = 2;
33116 e.context.roots.reserve(d.roots.size());
33117 for (const auto &sourceRoot : d.roots) {
33118 DRW_MLeaderRoot root;
33119 root.isContentValid = sourceRoot.connectionPoint.valid;
33120 root.unknown291 = sourceRoot.direction.valid;
33121 root.connectionPoint =
33122 DRW_Coord(sourceRoot.connectionPoint.x, sourceRoot.connectionPoint.y,
33123 sourceRoot.connectionPoint.z);
33124 root.direction = DRW_Coord(sourceRoot.direction.x, sourceRoot.direction.y,
33125 sourceRoot.direction.z);
33126 root.leaderIndex = static_cast<std::int32_t>(e.context.roots.size());
33127 root.landingDistance = sourceRoot.landingDistance;
33128 root.attachmentDirection =
33129 static_cast<std::uint16_t>(sourceRoot.attachmentDirection);
33130 root.leaderLines.reserve(sourceRoot.leaderLines.size());
33131 for (const auto &sourceLine : sourceRoot.leaderLines) {
33132 DRW_MLeaderLeaderLine line;
33133 line.leaderLineIndex = sourceLine.leaderLineIndex;
33134 line.leaderType = static_cast<std::uint16_t>(d.leaderType);
33135 line.color = d.leaderColor;
33136 line.lineWeight = e.lWeight;
33137 line.arrowSize = d.arrowSize;
33138 line.overrideFlags = 0;
33139 line.points.reserve(sourceLine.points.size());
33140 for (const RS_Vector &point : sourceLine.points)
33141 line.points.emplace_back(point.x, point.y, point.z);
33142 root.leaderLines.push_back(std::move(line));
33143 }
33144 e.context.roots.push_back(std::move(root));
33145 }
33146 e.context.overallScale = d.scaleFactor;
33147 e.context.contentBasePoint = DRW_Coord(
33148 d.contentBasePoint.x, d.contentBasePoint.y, d.contentBasePoint.z);
33149 e.context.textHeight = d.textHeight > 0.0 ? d.textHeight : 1.0;
3
Assuming the condition is false
4
'?' condition is false
33150 e.context.arrowHeadSize = d.arrowSize;
33151 e.context.landingGap = d.landingDistance;
33152 e.context.hasTextContents = d.hasTextContents;
33153 e.context.hasContentsBlock = d.hasBlockContents;
33154 e.context.textLabel = toDxfString(d.textLabel, m_dwgW == nullptr).toUtf8().data();
5
Assuming the condition is false
33155 e.context.textNormal = DRW_Coord(0.0, 0.0, 1.0);
33156 const RS_Vector textLocation =
33157 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
6
Assuming field 'valid' is false
7
'?' condition is false
33158 e.context.textLocation =
33159 DRW_Coord(textLocation.x, textLocation.y, textLocation.z);
33160 e.context.textDirection =
33161 DRW_Coord(std::cos(d.textRotation), std::sin(d.textRotation), 0.0);
33162 e.context.textRotation = d.textRotation;
33163 e.context.boundaryWidth = d.boundaryWidth;
33164 e.context.boundaryHeight = d.boundaryHeight;
33165 e.context.lineSpacingFactor = 1.0;
33166 e.context.lineSpacingStyle = 1;
33167 e.context.textColor = d.textColor;
33168 e.context.alignment = 1;
33169 e.context.flowDirection = 1;
33170 e.context.bgScaleFactor = 1.5;
33171 e.context.basePoint = DRW_Coord(d.basePoint.x, d.basePoint.y, d.basePoint.z);
33172 e.context.baseDirection = DRW_Coord(1.0, 0.0, 0.0);
33173 e.context.baseVertical = DRW_Coord(0.0, 1.0, 0.0);
33174 e.styleHandle.ref = d.dwgStyleHandle;
33175 e.leaderLineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
33176 e.arrowHeadHandle.ref = d.dwgArrowHeadHandle;
33177 e.styleTextStyleHandle.ref = d.dwgTextStyleHandle;
33178 e.styleBlockHandle.ref = d.dwgBlockHandle;
33179 e.context.textStyleHandle.ref = d.dwgTextStyleHandle;
33180 e.context.blockTableRecordHandle.ref = d.dwgBlockHandle;
33181 for (DRW_MLeaderRoot &root : e.context.roots) {
33182 for (DRW_MLeaderLeaderLine &line : root.leaderLines) {
33183 line.lineTypeHandle.ref = d.dwgLeaderLineTypeHandle;
33184 line.arrowHandle.ref = d.dwgArrowHeadHandle;
33185 }
33186 }
33187 if (m_dwgW
7.1
Field 'm_dwgW' is non-null
) {
8
Taking true branch
33188 const DwgMLeaderWriteRoute route =
33189 selectDwgMLeaderWriteRoute(d, m_dwgW->getVersion());
33190 if (route
8.1
'route' is not equal to Invalid
== DwgMLeaderWriteRoute::Invalid) {
33191 m_writeFailed = true;
33192 return DwgEntityProductionResult::Failed;
33193 }
33194 // Generated entities do not yet have a pre-seal class-instance ordinal;
33195 // keep them on the visible fallback route until that descriptor family is
33196 // migrated. Source-backed native output is admitted in writeDwgClasses().
33197 const bool useNative =
33198 route
8.2
'route' is not equal to Native
== DwgMLeaderWriteRoute::Native && m->sourceHandle() != 0;
33199 if (useNative
8.3
'useNative' is false
) {
9
Taking false branch
33200 struct NativeMLeaderReference {
33201 const char *name = nullptr;
33202 std::uint32_t ordinal = 0;
33203 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
33204 std::uint32_t targetSourceHandle = 0;
33205 std::uint32_t targetOutputHandle = 0;
33206 };
33207 std::vector<NativeMLeaderReference> references;
33208 const auto isLiveReference = [](DwgWriteReferenceStatus status) {
33209 return status == DwgWriteReferenceStatus::Resolved ||
33210 status == DwgWriteReferenceStatus::LegacySpaceAlias ||
33211 status == DwgWriteReferenceStatus::Reserved ||
33212 status == DwgWriteReferenceStatus::Selected ||
33213 status == DwgWriteReferenceStatus::Committed;
33214 };
33215 const auto addReference =
33216 [&references](const char *name, std::uint32_t ordinal,
33217 DwgWriteSourceKind targetKind,
33218 std::uint32_t targetSourceHandle,
33219 std::uint32_t targetOutputHandle) {
33220 if (name == nullptr || targetOutputHandle == 0)
33221 return false;
33222 references.push_back({name, ordinal, targetKind, targetSourceHandle,
33223 targetOutputHandle});
33224 return true;
33225 };
33226 const auto resolveObjectReference =
33227 [this, &addReference, &isLiveReference](
33228 std::uint32_t sourceHandle, const char *name,
33229 std::uint32_t ordinal, std::uint32_t &outputHandle) {
33230 outputHandle = 0;
33231 if (sourceHandle == DRW::NoHandle)
33232 return true;
33233 const DwgWriteReferenceStatus status =
33234 resolveDwgWriteObjectHandle(sourceHandle, &outputHandle);
33235 return isLiveReference(status) && outputHandle != 0 &&
33236 addReference(name, ordinal, DwgWriteSourceKind::Object,
33237 sourceHandle, outputHandle);
33238 };
33239 const auto resolveTableReference =
33240 [this, &addReference](
33241 std::uint32_t sourceHandle, const std::string &sourceName,
33242 const std::map<std::string, std::uint32_t> &outputHandles,
33243 const char *name, std::uint32_t ordinal,
33244 std::uint32_t &outputHandle) {
33245 outputHandle = 0;
33246 if (sourceHandle == DRW::NoHandle)
33247 return true;
33248 if (sourceName.empty())
33249 return false;
33250 const auto outputIt =
33251 outputHandles.find(normalizeDwgTableName(sourceName));
33252 if (outputIt == outputHandles.end())
33253 return false;
33254
33255 std::uint32_t plannedHandle = 0;
33256 const DwgWriteIdentityLookup state = lookupDwgWriteIdentity(
33257 DwgWriteSourceKind::TableRecord, sourceHandle, &plannedHandle);
33258 if (state != DwgWriteIdentityLookup::Reserved &&
33259 state != DwgWriteIdentityLookup::Selected &&
33260 state != DwgWriteIdentityLookup::Committed) {
33261 return false;
33262 }
33263 if (plannedHandle == 0 || plannedHandle != outputIt->second)
33264 return false;
33265
33266 outputHandle = plannedHandle;
33267 return addReference(name, ordinal, DwgWriteSourceKind::TableRecord,
33268 sourceHandle, outputHandle);
33269 };
33270 const auto resolveBlockReference =
33271 [this, &addReference, &isLiveReference](
33272 std::uint32_t sourceHandle, const char *name,
33273 std::uint32_t ordinal, std::uint32_t &outputHandle) {
33274 outputHandle = 0;
33275 if (sourceHandle == DRW::NoHandle)
33276 return true;
33277 const DwgWriteReferenceStatus status =
33278 resolveDwgWriteBlockOwner(sourceHandle, &outputHandle);
33279 if (!isLiveReference(status) || outputHandle == 0)
33280 return false;
33281 const bool fixed =
33282 status == DwgWriteReferenceStatus::LegacySpaceAlias;
33283 return addReference(name, ordinal,
33284 fixed ? DwgWriteSourceKind::FixedStructural
33285 : DwgWriteSourceKind::Block,
33286 fixed ? 0 : sourceHandle, outputHandle);
33287 };
33288
33289 const auto &metadata = m_graphic->dwgAdvancedMetadata();
33290 if (!resolveObjectReference(d.dwgStyleHandle, "MLEADER.STYLE", 0,
33291 e.styleHandle.ref) ||
33292 !resolveTableReference(
33293 d.dwgLeaderLineTypeHandle,
33294 metadata.lineTypeNameForHandle(d.dwgLeaderLineTypeHandle),
33295 m_dwgWriteLTypeHandleByName, "MLEADER.LTYPE", 0,
33296 e.leaderLineTypeHandle.ref) ||
33297 !resolveBlockReference(d.dwgArrowHeadHandle, "MLEADER.ARROW", 0,
33298 e.arrowHeadHandle.ref) ||
33299 !resolveTableReference(
33300 d.dwgTextStyleHandle,
33301 metadata.textStyleNameForHandle(d.dwgTextStyleHandle),
33302 m_dwgWriteTextStyleHandleByName, "MLEADER.TEXT_STYLE", 0,
33303 e.styleTextStyleHandle.ref) ||
33304 !resolveBlockReference(d.dwgBlockHandle, "MLEADER.BLOCK", 0,
33305 e.styleBlockHandle.ref)) {
33306 m_writeFailed = true;
33307 return DwgEntityProductionResult::Failed;
33308 }
33309 if (e.context.hasTextContents) {
33310 if (!resolveTableReference(
33311 d.dwgTextStyleHandle,
33312 metadata.textStyleNameForHandle(d.dwgTextStyleHandle),
33313 m_dwgWriteTextStyleHandleByName, "MLEADER.CONTEXT.TEXT_STYLE",
33314 0, e.context.textStyleHandle.ref)) {
33315 m_writeFailed = true;
33316 return DwgEntityProductionResult::Failed;
33317 }
33318 } else if (e.context.hasContentsBlock) {
33319 if (!resolveBlockReference(d.dwgBlockHandle, "MLEADER.CONTEXT.BLOCK", 0,
33320 e.context.blockTableRecordHandle.ref)) {
33321 m_writeFailed = true;
33322 return DwgEntityProductionResult::Failed;
33323 }
33324 }
33325
33326 std::uint32_t lineOrdinal = 0;
33327 for (DRW_MLeaderRoot &root : e.context.roots) {
33328 for (DRW_MLeaderLeaderLine &line : root.leaderLines) {
33329 if (!resolveTableReference(
33330 d.dwgLeaderLineTypeHandle,
33331 metadata.lineTypeNameForHandle(d.dwgLeaderLineTypeHandle),
33332 m_dwgWriteLTypeHandleByName, "MLEADER.LINE.LTYPE",
33333 lineOrdinal, line.lineTypeHandle.ref) ||
33334 !resolveBlockReference(d.dwgArrowHeadHandle, "MLEADER.LINE.ARROW",
33335 lineOrdinal, line.arrowHandle.ref)) {
33336 m_writeFailed = true;
33337 return DwgEntityProductionResult::Failed;
33338 }
33339 ++lineOrdinal;
33340 }
33341 }
33342
33343 const auto identityCheckpoint = checkpointDwgWriteIdentity();
33344 const std::uint64_t nativeTransaction =
33345 m_dwgW->beginDwgWriteTransaction(true, false, false);
33346 const auto rollback = [&]() {
33347 if (nativeTransaction != 0)
33348 (void)m_dwgW->rollbackDwgWriteTransaction(nativeTransaction);
33349 restoreDwgWriteIdentity(identityCheckpoint);
33350 m_writeFailed = true;
33351 };
33352 if (nativeTransaction == 0 || !m_dwgW->writeMLeader(&e)) {
33353 rollback();
33354 return DwgEntityProductionResult::Failed;
33355 }
33356 const std::uint32_t outputHandle =
33357 m_dwgW->getLastSuccessfulEntityHandle();
33358 if (outputHandle == 0 || outputHandle != e.handle) {
33359 rollback();
33360 return DwgEntityProductionResult::Failed;
33361 }
33362 for (const NativeMLeaderReference &reference : references) {
33363 const DwgWriteReferenceReceipt receipt{DwgWriteSourceKind::Entity,
33364 m->sourceHandle(),
33365 outputHandle,
33366 reference.name,
33367 reference.ordinal,
33368 reference.targetKind,
33369 reference.targetSourceHandle,
33370 reference.targetOutputHandle,
33371 true,
33372 DRW::DwgHardPointer};
33373 if (!recordDwgWriteReferenceReceipt(receipt)) {
33374 rollback();
33375 return DwgEntityProductionResult::Failed;
33376 }
33377 }
33378 if (!m_dwgW->commitDwgWriteTransaction(nativeTransaction)) {
33379 rollback();
33380 return DwgEntityProductionResult::Failed;
33381 }
33382 return DwgEntityProductionResult::SingleFrame;
33383 }
33384
33385 const auto identityCheckpoint = checkpointDwgWriteIdentity();
33386 const std::uint64_t transaction =
33387 m_dwgW->beginDwgWriteTransaction(false, false, false);
33388 if (transaction == 0) {
10
Assuming 'transaction' is not equal to 0
11
Taking false branch
33389 m_writeFailed = true;
33390 return DwgEntityProductionResult::Failed;
33391 }
33392 const auto rollback = [&]() {
33393 (void)m_dwgW->rollbackDwgWriteTransaction(transaction);
33394 restoreDwgWriteIdentity(identityCheckpoint);
33395 m_writeFailed = true;
33396 };
33397
33398 bool wroteGeometry = false;
33399 const auto emitFallbackChild = [&](const auto &write) {
33400 if (!write())
38
Calling 'operator()'
33401 return false;
33402 const std::uint32_t outputHandle =
33403 m_dwgW->getLastSuccessfulEntityHandle();
33404 if (outputHandle == 0 || !recordDwgEntityFrame(outputHandle))
33405 return false;
33406
33407 if (!wroteGeometry) {
33408 if (m->sourceHandle() != 0) {
33409 const auto entityIt = m_dwgWriteEntityHandleByEntity.find(m);
33410 if (entityIt == m_dwgWriteEntityHandleByEntity.end() ||
33411 entityIt->second != outputHandle || !commitDwgEntityWrite(m))
33412 return false;
33413 } else if (!commitDwgGeneratedEntityWrite(outputHandle)) {
33414 return false;
33415 }
33416 } else if (!commitDwgGeneratedEntityWrite(outputHandle)) {
33417 return false;
33418 }
33419 wroteGeometry = true;
33420 return true;
33421 };
33422
33423 for (const auto &root : d.roots) {
33424 for (const auto &line : root.leaderLines) {
33425 if (line.points.size() < 2)
12
Assuming the condition is false
13
Taking false branch
33426 continue;
33427 DRW_Leader leader;
33428 getEntityAttributes(&leader, m);
33429 leader.style =
33430 d.styleName.isEmpty() ? "Standard" : d.styleName.toStdString();
14
Assuming the condition is false
15
'?' condition is false
33431 leader.arrow = true;
33432 leader.leadertype = d.leaderType == 2 ? 1 : 0;
16
Assuming field 'leaderType' is not equal to 2
17
'?' condition is false
33433 leader.flag = 3;
33434 leader.hookline = 0;
33435 leader.hookflag = 0;
33436 leader.textheight = d.textHeight > 0.0 ? d.textHeight : 1.0;
18
Assuming the condition is false
19
'?' condition is false
33437 leader.textwidth = d.boundaryWidth > 0.0 ? d.boundaryWidth : 10.0;
20
Assuming the condition is false
21
'?' condition is false
33438 leader.vertnum = static_cast<int>(line.points.size());
33439 leader.vertexlist.reserve(line.points.size());
33440 for (const RS_Vector &point : line.points) {
33441 leader.vertexlist.push_back(
33442 std::make_shared<DRW_Coord>(point.x, point.y, point.z));
33443 }
33444 if (wroteGeometry
21.1
'wroteGeometry' is false
)
22
Taking false branch
33445 leader.handle = 0;
33446 if (!emitFallbackChild(
23
Taking false branch
33447 [&leader, this] { return m_dwgW->writeLeader(&leader); })) {
33448 rollback();
33449 return DwgEntityProductionResult::Failed;
33450 }
33451 }
33452 }
33453 if (d.hasBlockContents) {
24
Assuming field 'hasBlockContents' is false
33454 DRW_Insert insert;
33455 getEntityAttributes(&insert, m);
33456 insert.basePoint =
33457 DRW_Coord(d.blockLocation.x, d.blockLocation.y, d.blockLocation.z);
33458 insert.name = d.blockName.toStdString();
33459 insert.xscale = d.blockScale.x;
33460 insert.yscale = d.blockScale.y;
33461 insert.zscale = d.blockScale.z;
33462 insert.angle = d.blockRotation;
33463 insert.colcount = 1;
33464 insert.rowcount = 1;
33465 insert.colspace = 0.0;
33466 insert.rowspace = 0.0;
33467 insert.extPoint = DRW_Coord(0.0, 0.0, 1.0);
33468 if (wroteGeometry)
33469 insert.handle = 0;
33470 if (!emitFallbackChild(
33471 [&insert, this] { return m_dwgW->writeInsert(&insert); })) {
33472 rollback();
33473 return DwgEntityProductionResult::Failed;
33474 }
33475 }
33476 if (d.hasTextContents && !d.textLabel.isEmpty()) {
25
Assuming field 'hasTextContents' is true
26
Assuming the condition is true
27
Taking true branch
33477 DRW_MText text;
33478 getEntityAttributes(&text, m);
28
Value assigned to field 'm_dwgW'
33479 const RS_Vector insertion =
33480 d.textLocation.valid ? d.textLocation : d.contentBasePoint;
29
Assuming field 'valid' is true
30
'?' condition is true
33481 text.basePoint.x = insertion.x;
33482 text.basePoint.y = insertion.y;
33483 text.basePoint.z = insertion.z;
33484 text.height = d.textHeight > 0.0 ? d.textHeight : 1.0;
31
Assuming the condition is false
32
'?' condition is false
33485 text.angle = d.textRotation * 180.0 / M_PI3.14159265358979323846;
33486 text.style = d.textStyleName.isEmpty() ? "Standard"
33
Assuming the condition is false
34
'?' condition is false
33487 : d.textStyleName.toStdString();
33488 text.text = toDxfString(d.textLabel, m_dwgW == nullptr).toUtf8().data();
35
Assuming pointer value is null
33489 text.widthscale = d.boundaryWidth;
33490 text.interlin = 1.0;
33491 if (wroteGeometry
35.1
'wroteGeometry' is true
)
36
Taking true branch
33492 text.handle = 0;
33493 if (!emitFallbackChild(
37
Calling 'operator()'
33494 [&text, this] { return m_dwgW->writeMText(&text); })) {
39
Called C++ object pointer is null
33495 rollback();
33496 return DwgEntityProductionResult::Failed;
33497 }
33498 }
33499 if (!wroteGeometry) {
33500 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
33501 "dropping MLEADER with no writable leader/text geometry");
33502 rollback();
33503 return DwgEntityProductionResult::Failed;
33504 }
33505
33506 if (!m_dwgW->commitDwgWriteTransaction(transaction)) {
33507 rollback();
33508 return DwgEntityProductionResult::Failed;
33509 }
33510 return DwgEntityProductionResult::SelfCommittedCompound;
33511 }
33512 noteDxfWrite(m_dxfW->writeMultiLeader(&e));
33513 return DwgEntityProductionResult::SingleFrame;
33514}
33515
33516/*void RS_FilterDXFRW::writeEntityContainer(DL_WriterA& dw, RS_EntityContainer*
33517con, const DRW_Entity& attrib) { QString blkName; blkName = "__CE";
33518
33519 // Creating an unique ID from the element ID
33520 int tmp, c=1; // tmp = temporary var c = counter var
33521 tmp = con->getId();
33522
33523 while (true) {
33524 tmp = tmp/c;
33525 blkName.append((char) tmp %10 + 48);
33526 c *= 10;
33527 if (tmp < 10) {
33528 break;
33529 }
33530 }
33531
33532 //Block definition
33533 dw.sectionTables();
33534 dxf.writeBlockRecord(dw);
33535 dw.dxfString( 0, "BLOCK_RECORD");
33536
33537 dw.handle();
33538 dw.dxfHex(330, 1);
33539 dw.dxfString(100, "AcDbSymbolTableRecord");
33540 dw.dxfString(100, "AcDbBlockTableRecord");
33541 dw.dxfString( 2, blkName.toLatin1().data());
33542 dw.dxfHex(340, 0);
33543 dw.dxfString(0, "ENDTAB");
33544
33545 //Block creation
33546 RS_BlockData blkdata(blkName, RS_Vector(0,0), false);
33547
33548 RS_Block* blk = new RS_Block(graphic, blkdata);
33549
33550 for (RS_Entity* e1 = con->firstEntity(); e1 ;
33551 e1 = con->nextEntity() ) {
33552 blk->addEntity(e1);
33553 }
33554 writeBlock(dw, blk);
33555 //delete e1;
33556}*/
33557
33558/**
33559 * Writes the atomic entities of the given container to the file.
33560 */
33561/*void RS_FilterDXFRW::writeAtomicEntities(DL_WriterA& dw, RS_EntityContainer*
33562c, const DRW_Entity& attrib, RS2::ResolveLevel level) {
33563
33564 for (RS_Entity* e=c->firstEntity(level);
33565 e;
33566 e=c->nextEntity(level)) {
33567
33568 writeEntity(dw, e, attrib);
33569 }
33570}*/
33571
33572/**
33573 * Sets the entities attributes according to the attributes
33574 * that come from a DXF file.
33575 */
33576void RS_FilterDXFRW::setEntityAttributes(RS_Entity *entity,
33577 const DRW_Entity *attrib) {
33578 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes");
33579
33580 RS_Pen pen;
33581 pen.setColor(Qt::black);
33582 pen.setLineType(RS2::SolidLine);
33583 // A layer name is an identifier, NOT MTEXT content: do NOT run it through
33584 // toNativeString (which caret-decodes ^X, expands \P/%%c, strips font
33585 // tags). Decoding here corrupts names that legitimately contain '^' and,
33586 // worse, desyncs the entity from its layer record — addLayer() below
33587 // stores the RAW name (lay.name = attrib->layer), so a decoded layName
33588 // would never match it. Use the verbatim UTF-8 name on both paths.
33589 QString layName = QString::fromUtf8(attrib->layer.c_str());
33590 entity->setLayer(importLayerForEntity(layName, attrib->layer));
33591
33592 // Color:
33593 RS_Color col;
33594 if (attrib->color24 >= 0) {
33595 col = RS_Color(attrib->color24 >> 16, attrib->color24 >> 8 & 0xFF,
33596 attrib->color24 & 0xFF);
33597 } else {
33598 col = LC_ColorNumbers::numberToColor(attrib->color);
33599 }
33600 if (!attrib->colorName.empty()) {
33601 col.setColorName(QString::fromUtf8(attrib->colorName.c_str()));
33602 }
33603 pen.setColor(col);
33604
33605 // Linetype:
33606 pen.setLineTypeName(QString::fromUtf8(attrib->lineType.c_str()));
33607
33608 // Width:
33609 pen.setWidth(numberToWidth(attrib->lWeight));
33610
33611 // Transparency (DXF code 440): alpha_raw packs alpha_type in high byte
33612 // (0/1 = ByLayer/ByBlock, inherits parent), 3 = explicit alpha (low
33613 // byte 0..255). RS_Pen.alpha is a float 0..1 where 1.0 is opaque.
33614 // libreDWG common_entity_data.spec:432-446 documents the encoding.
33615 // Only override the pen default when alpha_type == 3.
33616 if (attrib->transparency != DRW::Opaque) {
33617 const unsigned int rawAlpha =
33618 static_cast<unsigned int>(attrib->transparency);
33619 const unsigned int alphaType = (rawAlpha >> 24) & 0xFF;
33620 const unsigned int alphaByte = rawAlpha & 0xFF;
33621 if (alphaType == 3) {
33622 pen.setAlpha(static_cast<float>(alphaByte) / 255.0f);
33623 }
33624 }
33625
33626 entity->setPen(pen);
33627
33628 // Passive metadata sidecars — DXF/DWG fields that don't affect
33629 // rendering or equality but must round-trip on save. Skip the
33630 // default sentinels so unset entities stay unchanged.
33631 if (attrib->material != DRW::MaterialByLayer)
33632 entity->setMaterialHandle(attrib->material);
33633 if (attrib->plotStyle != DRW::DefaultPlotStyle)
33634 entity->setPlotStyleHandle(static_cast<quint32>(attrib->plotStyle));
33635 if (attrib->shadow != DRW::CastAndReceieveShadows)
33636 entity->setShadowMode(static_cast<int>(attrib->shadow));
33637 entity->setShadowHandle(attrib->shadowHandle);
33638 // Visual-style handles are represented by DXF group 348 and by the
33639 // corresponding DWG common-entity flags/handles.
33640 entity->setVisualStyleHandles(attrib->fullVisualStyleHandle,
33641 attrib->faceVisualStyleHandle,
33642 attrib->edgeVisualStyleHandle);
33643 entity->setReactorHandles(attrib->reactorHandles);
33644 entity->setXDictHandle(attrib->xDictHandle);
33645 // Visibility (DXF code 60 / DWG invisible bit) — import side of the
33646 // two-way fix; getEntityAttributes now exports it. (write-review P3 #11)
33647 entity->setVisible(attrib->visible);
33648
33649 // Source DXF/DWG handle (code 5). Lets the writer build an old->new
33650 // handle map for refs that target model entities (F3a; GROUP code-340).
33651 if (attrib->handle != DRW::NoHandle)
33652 entity->setSourceHandle(attrib->handle);
33653
33654 // Preserve any XDATA / EED that came in with the entity. Stored
33655 // verbatim on RS_Entity so a later getEntityAttributes() can spit
33656 // it back out unchanged. The dimension-style override path reads
33657 // the same source independently and is unaffected.
33658 if (!attrib->extData.empty()) {
33659 entity->setDrwExtData(attrib->extData);
33660 }
33661
33662 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::setEntityAttributes: OK");
33663}
33664
33665/**
33666 * Gets the entities attributes as a DL_Attributes object.
33667 */
33668void RS_FilterDXFRW::getEntityAttributes(DRW_Entity *ent,
33669 const RS_Entity *entity) {
33670 // DRW_Entity RS_FilterDXFRW::getEntityAttributes(RS_Entity* /*entity*/) {
33671
33672 // F3: DXF export seeds ent->handle with the entity's SOURCE handle as a
33673 // source->minted remap key (used to resolve GROUP 340 members). DWG export
33674 // uses the preallocated source->minted map so OBJECTS references can point
33675 // at the actual entity handle without colliding with fixed table/object
33676 // handles reserved by the writer.
33677#ifdef DWGSUPPORT1
33678 if (m_dwgW != nullptr) {
33679 ent->handle = 0;
33680 auto entityIt = m_dwgWriteEntityHandleByEntity.find(entity);
33681 // An entity without a source handle may have had its output handle
33682 // taken early to keep an R2000 owner chain contiguous.
33683 const auto generatedIt =
33684 m_dwgWriteGeneratedEntityHandleByEntity.find(entity);
33685 const std::uint32_t generatedHandle =
33686 generatedIt != m_dwgWriteGeneratedEntityHandleByEntity.end()
33687 ? generatedIt->second
33688 : 0;
33689 const bool hasCommonReferences =
33690 entity->materialHandle() != 0 || entity->plotStyleHandle() != 0 ||
33691 entity->shadowHandle() != 0 || entity->fullVisualStyleHandle() != 0 ||
33692 entity->faceVisualStyleHandle() != 0 ||
33693 entity->edgeVisualStyleHandle() != 0 ||
33694 !entity->reactorHandles().empty() || entity->xDictHandle() != 0;
33695 if (entityIt == m_dwgWriteEntityHandleByEntity.end() &&
33696 hasCommonReferences) {
33697 const std::uint32_t sourceHandle = entity->sourceHandle();
33698 const std::uint32_t emittedHandle =
33699 generatedHandle != 0 ? generatedHandle : m_dwgW->allocNextHandle();
33700 if (emittedHandle == 0 ||
33701 !selectDwgWriteIdentity(DwgWriteSourceKind::Entity, sourceHandle,
33702 emittedHandle)) {
33703 m_writeFailed = true;
33704 } else {
33705 entityIt =
33706 m_dwgWriteEntityHandleByEntity.emplace(entity, emittedHandle).first;
33707 if (sourceHandle != 0) {
33708 const bool sourceInserted =
33709 m_dwgWriteEntityHandleRemap.emplace(sourceHandle, emittedHandle)
33710 .second;
33711 if (!sourceInserted)
33712 m_dwgWriteDuplicateEntityHandles.insert(sourceHandle);
33713 }
33714 }
33715 }
33716 if (entityIt != m_dwgWriteEntityHandleByEntity.end()) {
33717 ent->handle = entityIt->second;
33718 } else {
33719 ent->handle = generatedHandle;
33720 }
33721 } else
33722#endif
33723 {
33724 ent->handle = entity->sourceHandle();
33725 }
33726
33727 // Layer:
33728 RS_Layer *layer = entity->getLayer();
33729 QString layerName;
33730 if (layer) {
33731 layerName = layer->getName();
33732 } else {
33733 layerName = "0";
33734 }
33735
33736 RS_Pen pen = entity->getPen(false);
33737
33738#ifdef DWGSUPPORT1
33739 // Entity common references use the global DWG handle namespace. Resolve
33740 // either a preallocated entity handle or a preserved/native OBJECTS
33741 // handle, and reject ambiguous or unavailable targets before encoding.
33742 auto remapDwgReference = [this, ent, entity](
33743 std::uint32_t source, const char *referenceKind,
33744 std::uint32_t referenceOrdinal = 0) {
33745 std::uint32_t outputHandle = 0;
33746 DwgWriteSourceKind targetKind = DwgWriteSourceKind::Object;
33747 const DwgWriteReferenceStatus status =
33748 resolveDwgWriteCommonHandle(source, &outputHandle, &targetKind);
33749 if (status == DwgWriteReferenceStatus::Ambiguous) {
33750 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
33751 "RS_FilterDXFRW: dropping ambiguous DWG %s target 0x%X",
33752 referenceKind, source);
33753 if (!recordDwgWriteOptionalDrop(
33754 DwgWriteSourceKind::Entity, entity->sourceHandle(), ent->handle,
33755 referenceKind, referenceOrdinal, targetKind, source, status,
33756 m_dwgW->getVersion(), "DWG ENTITIES",
33757 "ambiguous common reference"))
33758 m_writeFailed = true;
33759 return std::uint32_t{0};
33760 }
33761 if (status == DwgWriteReferenceStatus::Missing ||
33762 status == DwgWriteReferenceStatus::Suppressed) {
33763 RS_DEBUGRS_Debug::instance()->print(RS_Debug::D_WARNING,
33764 "RS_FilterDXFRW: dropping dangling DWG %s target 0x%X",
33765 referenceKind, source);
33766 if (!recordDwgWriteOptionalDrop(
33767 DwgWriteSourceKind::Entity, entity->sourceHandle(), ent->handle,
33768 referenceKind, referenceOrdinal, targetKind, source, status,
33769 m_dwgW->getVersion(), "DWG ENTITIES",
33770 "unresolved common reference"))
33771 m_writeFailed = true;
33772 return std::uint32_t{0};
33773 }
33774 return outputHandle;
33775 };
33776#endif
33777
33778 // Color:
33779 int exact_rgb;
33780 int color = LC_ColorNumbers::colorToNumber(pen.getColor(), &exact_rgb);
33781 // printf("Color is: %s -> %d\n", pen.getColor().name().toLatin1().data(),
33782 // color);
33783
33784 // Linetype:
33785 QString lineType = lineTypeNameToWrite(pen);
33786
33787 // Width:
33788 DRW_LW_Conv::lineWidth width = widthToNumber(pen.getWidth());
33789
33790 // Verbatim, as the LAYER record and the import (setEntityAttributes) have it
33791 ent->layer = layerName.toUtf8().data();
33792 ent->color = color;
33793 ent->color24 = exact_rgb;
33794 if (pen.getColor().hasColorName()) {
33795 ent->colorName = pen.getColor().colorName().toUtf8().data();
33796 }
33797 ent->lWeight = width;
33798 ent->lineType = lineType.toUtf8().data();
33799
33800 // Transparency export: encode pen alpha < 1.0 as DXF code 440
33801 // (alpha_raw). High byte 0x03 = "explicit alpha", low byte = 0..255.
33802 // Mirrors the import decoder above.
33803 if (pen.getAlpha() < 1.0f) {
33804 int alphaByte = static_cast<int>(pen.getAlpha() * 255.0f + 0.5f);
33805 ent->transparency = (0x03 << 24) | (alphaByte & 0xFF);
33806 }
33807
33808 // Store entity visibility independently of its layer's frozen state.
33809 // Effective visibility is also false when the layer is frozen, but that
33810 // must remain a layer property rather than becoming DXF code 60 / the DWG
33811 // invisible bit on the entity.
33812 ent->visible = entity->getFlag(RS2::FlagVisible);
33813
33814 // Passive metadata sidecars — emit only when overridden. DWG references
33815 // must be remapped after the writer reserves its final handle graph;
33816 // DXF keeps the source handle namespace unchanged.
33817 ent->material = DRW::MaterialByLayer;
33818 ent->plotStyle = DRW::DefaultPlotStyle;
33819 ent->shadow = DRW::CastAndReceieveShadows;
33820 ent->shadowHandle = 0;
33821 ent->fullVisualStyleHandle = 0;
33822 ent->faceVisualStyleHandle = 0;
33823 ent->edgeVisualStyleHandle = 0;
33824 ent->reactorHandles.clear();
33825 ent->xDictHandle = 0;
33826 if (entity->materialHandle() != 0) {
33827#ifdef DWGSUPPORT1
33828 ent->material =
33829 m_dwgW != nullptr
33830 ? remapDwgReference(entity->materialHandle(), "material")
33831 : entity->materialHandle();
33832#else
33833 ent->material = entity->materialHandle();
33834#endif
33835 }
33836 if (entity->plotStyleHandle() != 0) {
33837#ifdef DWGSUPPORT1
33838 const auto handle =
33839 m_dwgW != nullptr
33840 ? remapDwgReference(entity->plotStyleHandle(), "plot-style")
33841 : entity->plotStyleHandle();
33842#else
33843 const auto handle = entity->plotStyleHandle();
33844#endif
33845 ent->plotStyle = static_cast<int>(handle);
33846 }
33847 if (entity->shadowMode() != 0) {
33848 ent->shadow = static_cast<DRW::ShadowMode>(entity->shadowMode());
33849 if (entity->shadowHandle() != 0) {
33850#ifdef DWGSUPPORT1
33851 ent->shadowHandle =
33852 m_dwgW != nullptr
33853 ? remapDwgReference(entity->shadowHandle(), "shadow")
33854 : entity->shadowHandle();
33855#else
33856 ent->shadowHandle = entity->shadowHandle();
33857#endif
33858 }
33859 }
33860 const auto mapVisualStyle = [&](quint32 source, const char *referenceKind) {
33861#ifdef DWGSUPPORT1
33862 return m_dwgW != nullptr ? remapDwgReference(source, referenceKind)
33863 : source;
33864#else
33865 (void)referenceKind;
33866 return source;
33867#endif
33868 };
33869 ent->fullVisualStyleHandle =
33870 mapVisualStyle(entity->fullVisualStyleHandle(), "full visual-style");
33871 ent->faceVisualStyleHandle =
33872 mapVisualStyle(entity->faceVisualStyleHandle(), "face visual-style");
33873 ent->edgeVisualStyleHandle =
33874 mapVisualStyle(entity->edgeVisualStyleHandle(), "edge visual-style");
33875 std::uint32_t reactorOrdinal = 0;
33876 for (quint32 source : entity->reactorHandles()) {
33877#ifdef DWGSUPPORT1
33878 const auto mapped =
33879 m_dwgW != nullptr ? remapDwgReference(source, "reactor", reactorOrdinal)
33880 : source;
33881#else
33882 const auto mapped = source;
33883#endif
33884 if (mapped != 0)
33885 ent->reactorHandles.push_back(mapped);
33886 ++reactorOrdinal;
33887 }
33888 ent->xDictHandle = entity->xDictHandle();
33889#ifdef DWGSUPPORT1
33890 if (m_dwgW != nullptr)
33891 ent->xDictHandle =
33892 remapDwgReference(ent->xDictHandle, "extension-dictionary");
33893#endif
33894
33895 // Re-emit any XDATA / EED that was attached on import. Skipped if
33896 // the dimension-export path (or any other caller) already populated
33897 // ent->extData with its own structured override.
33898 if (entity->hasDrwExtData() && ent->extData.empty()) {
33899 ent->extData = entity->getDrwExtData();
33900 }
33901}
33902
33903bool RS_FilterDXFRW::replaysInDxfExport(const DRW::Version source) const {
33904 return source == DRW::UNKNOWNV || source == m_dxfExportVersion;
33905}
33906
33907/**
33908 * @return The handle an entity or table record names @p source by in the DXF
33909 * being written: the handle the object is written under, or 0 if it is not
33910 * written. Outside a DXF write, @p source itself.
33911 */
33912std::uint32_t RS_FilterDXFRW::dxfReference(std::uint32_t source) const {
33913 if (m_dxfW == nullptr || source == 0)
33914 return source;
33915 // Entities are written under fresh handles, all taken before the first
33916 // entity is written; a handle several entities share names none of them.
33917 const DRW_WritingContext *context = m_dxfW->getWritingContext();
33918 if (context->ambiguousSourceHandles.count(source) != 0)
33919 return 0;
33920 const auto entity = context->sourceHandleToMintedMap.find(source);
33921 if (entity != context->sourceHandleToMintedMap.end())
33922 return entity->second;
33923 const bool codecWritesIt = source == 0xCu || source == 0xDu;
33924 if (m_dxfDroppedReferenceHandles.count(source) != 0 ||
33925 (m_dxfSuppressedObjectHandles.count(source) != 0 && !codecWritesIt))
33926 return 0;
33927 return m_dxfW->remapHandle(source);
33928}
33929
33930/**
33931 * @return Pen with the same attributes as 'attrib'.
33932 */
33933RS_Pen RS_FilterDXFRW::attributesToPen(const DRW_Layer *att) const {
33934 RS_Color col;
33935 if (att->color24 >= 0) {
33936 col = RS_Color(att->color24 >> 16, att->color24 >> 8 & 0xFF,
33937 att->color24 & 0xFF);
33938 } else {
33939 // negative: the layer is turned off (addLayer())
33940 col = LC_ColorNumbers::numberToColor(std::abs(att->color));
33941 }
33942 if (!att->colorName.empty()) {
33943 col.setColorName(QString::fromUtf8(att->colorName.c_str()));
33944 }
33945
33946 // Blanks alone stay solid here: a layer cannot be ByLayer.
33947 const QString lineType = QString::fromUtf8(att->lineType.c_str());
33948 RS_Pen pen(col, numberToWidth(att->lWeight),
33949 LC_LineTypeNames::nameToLineType(lineType));
33950 if (!lineType.trimmed().isEmpty())
33951 pen.setLineTypeName(lineType);
33952 return pen;
33953}
33954
33955void RS_FilterDXFRW::add3dFace(const DRW_3Dface &data) {
33956 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::add3dFace");
33957 RS_PolylineData d(RS_Vector(false), RS_Vector(false), !data.invisibleflag);
33958 auto *polyline = new RS_Polyline(m_currentContainer, d);
33959 setEntityAttributes(polyline, &data);
33960 RS_Vector v1(data.basePoint.x, data.basePoint.y);
33961 RS_Vector v2(data.secPoint.x, data.secPoint.y);
33962 RS_Vector v3(data.thirdPoint.x, data.thirdPoint.y);
33963 RS_Vector v4(data.fourPoint.x, data.fourPoint.y);
33964
33965 polyline->addVertex(v1, 0.0);
33966 polyline->addVertex(v2, 0.0);
33967 polyline->addVertex(v3, 0.0);
33968 polyline->addVertex(v4, 0.0);
33969
33970 // F2 sidecar: the RS_Polyline drops Z + the invisible-edge flags, so store
33971 // all 4 corners with Z and the flag (code 70) for native 3DFACE rebuild.
33972 std::vector<std::shared_ptr<DRW_Variant>> payload;
33973 payload.push_back(std::make_shared<DRW_Variant>(
33974 1010, DRW_Coord(data.basePoint.x, data.basePoint.y, data.basePoint.z)));
33975 payload.push_back(std::make_shared<DRW_Variant>(
33976 1011, DRW_Coord(data.secPoint.x, data.secPoint.y, data.secPoint.z)));
33977 payload.push_back(std::make_shared<DRW_Variant>(
33978 1012,
33979 DRW_Coord(data.thirdPoint.x, data.thirdPoint.y, data.thirdPoint.z)));
33980 payload.push_back(std::make_shared<DRW_Variant>(
33981 1013, DRW_Coord(data.fourPoint.x, data.fourPoint.y, data.fourPoint.z)));
33982 payload.push_back(
33983 std::make_shared<DRW_Variant>(1070, std::int32_t{data.invisibleflag}));
33984 appendTypeSidecar(polyline, k3dFaceMarker, std::move(payload));
33985
33986 m_currentContainer->addEntity(polyline);
33987}
33988
33989void RS_FilterDXFRW::addMesh(const DRW_Mesh &data) {
33990 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::addMesh: %zu vertices, %zu faces",
33991 data.vertices.size(), data.faces.size());
33992 // Render the base-cage faces as closed polylines (LibreCAD is 2D; Z dropped,
33993 // matching add3dFace). Each face is a list of vertex indices into vertices[].
33994 for (const auto &face : data.faces) {
33995 if (face.size() < 2)
33996 continue;
33997 RS_PolylineData d(RS_Vector(false), RS_Vector(false), /*closed=*/true);
33998 auto *polyline = new RS_Polyline(m_currentContainer, d);
33999 setEntityAttributes(polyline, &data);
34000 bool any = false;
34001 for (std::int32_t idx : face) {
34002 if (idx < 0 || static_cast<size_t>(idx) >= data.vertices.size())
34003 continue;
34004 const DRW_Coord &v = data.vertices[static_cast<size_t>(idx)];
34005 polyline->addVertex(RS_Vector(v.x, v.y), 0.0);
34006 any = true;
34007 }
34008 if (any)
34009 m_currentContainer->addEntity(polyline);
34010 else
34011 delete polyline;
34012 }
34013}
34014
34015void RS_FilterDXFRW::addComment(const char *) {
34016 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXF::addComment(const char*) not yet implemented.");
34017}
34018
34019void RS_FilterDXFRW::addPlotSettings(const DRW_PlotSettings *data) {
34020 if (m_graphic == nullptr || data == nullptr)
34021 return;
34022 m_graphic->dwgAdvancedMetadata().addPlotSettings(*data);
34023 LC_PlotSettings *ps = m_graphic->getPlotSettings();
34024 if (ps == nullptr)
34025 return;
34026 // fixme - review and rework this. It should not be stored there from DXF
34027 // point of view! may be use current style name instead of view name..?
34028 ps->setPagesNum(QString::fromStdString(data->plotViewName));
34029 ps->setMarginsInMm(data->marginLeft, data->marginTop, data->marginRight,
34030 data->marginBottom);
34031
34032 ps->setPaperWidthMm(data->paperWidth);
34033 ps->setPaperHeightMm(data->paperHeight);
34034 ps->setCustomPrintScaleDrawingUnitsDenominator(data->drawingUnits);
34035 ps->setCustomPrintScalePaperUnitsNumerator(data->realWorldUnits);
34036 ps->setOriginOffsetXMm(data->paperImageOriginX);
34037 ps->setOriginOffsetYMm(data->paperImageOriginY);
34038 ps->setPlotWindowLowerLeftX(data->windowMinX);
34039 ps->setPlotWindowLowerLeftY(data->windowMinY);
34040 ps->setPlotWindowUpperRightX(data->windowMaxX);
34041 ps->setPlotWindowUpperRightY(data->windowMaxY);
34042 ps->setPlotType(data->plotType);
34043 ps->setPlotLayoutFlag(data->plotLayoutFlags);
34044 ps->setPlotPaperUnits(data->paperUnits);
34045 ps->setPlotRotation(data->plotRotation);
34046 ps->setStandardScaleType(data->scaleType);
34047 ps->setShadePlotMode(data->shadePlotMode);
34048 ps->setShadePlotResolutionMode(data->shadePlotResLevel);
34049 ps->setShadePlotCustomDpi(data->shadePlotCustomDPI);
34050
34051 ps->setStandardScaleFactor(data->scaleFactor);
34052 ps->setPaperImageOriginX(data->paperImageOriginX);
34053 ps->setPaperImageOriginY(data->paperImageOriginY);
34054
34055 ps->setPaperSizeName(QString::fromStdString(data->paperSize));
34056 ps->setCurrentStyleName(QString::fromStdString(data->currentStyleSheet));
34057}
34058
34059namespace {
34060// The DXF/DWG line-weight encoding and LibreCAD's RS2::LineWidth are a
34061// one-to-one mapping. numberToWidth() and widthToNumber() are the two
34062// directions of it; they used to be a pair of 27-case switches that had to be
34063// kept in step by hand, so state the pairs once here instead.
34064struct LineWidthPair {
34065 DRW_LW_Conv::lineWidth drw;
34066 RS2::LineWidth rs;
34067};
34068
34069constexpr LineWidthPair kLineWidthPairs[] = {
34070 {DRW_LW_Conv::widthByLayer, RS2::WidthByLayer},
34071 {DRW_LW_Conv::widthByBlock, RS2::WidthByBlock},
34072 {DRW_LW_Conv::widthDefault, RS2::WidthDefault},
34073 {DRW_LW_Conv::width00, RS2::Width00},
34074 {DRW_LW_Conv::width01, RS2::Width01},
34075 {DRW_LW_Conv::width02, RS2::Width02},
34076 {DRW_LW_Conv::width03, RS2::Width03},
34077 {DRW_LW_Conv::width04, RS2::Width04},
34078 {DRW_LW_Conv::width05, RS2::Width05},
34079 {DRW_LW_Conv::width06, RS2::Width06},
34080 {DRW_LW_Conv::width07, RS2::Width07},
34081 {DRW_LW_Conv::width08, RS2::Width08},
34082 {DRW_LW_Conv::width09, RS2::Width09},
34083 {DRW_LW_Conv::width10, RS2::Width10},
34084 {DRW_LW_Conv::width11, RS2::Width11},
34085 {DRW_LW_Conv::width12, RS2::Width12},
34086 {DRW_LW_Conv::width13, RS2::Width13},
34087 {DRW_LW_Conv::width14, RS2::Width14},
34088 {DRW_LW_Conv::width15, RS2::Width15},
34089 {DRW_LW_Conv::width16, RS2::Width16},
34090 {DRW_LW_Conv::width17, RS2::Width17},
34091 {DRW_LW_Conv::width18, RS2::Width18},
34092 {DRW_LW_Conv::width19, RS2::Width19},
34093 {DRW_LW_Conv::width20, RS2::Width20},
34094 {DRW_LW_Conv::width21, RS2::Width21},
34095 {DRW_LW_Conv::width22, RS2::Width22},
34096 {DRW_LW_Conv::width23, RS2::Width23},
34097};
34098
34099// The pair of switches this table replaced could not carry a duplicate case
34100// label - the compiler rejected it. Keep that guarantee here: both lookups
34101// return the first matching row, so a repeated value on either side would
34102// silently shadow a later one instead of failing the build.
34103constexpr bool lineWidthPairsAreOneToOne() {
34104 constexpr std::size_t count =
34105 sizeof(kLineWidthPairs) / sizeof(kLineWidthPairs[0]);
34106 for (std::size_t i = 0; i < count; ++i) {
34107 for (std::size_t j = i + 1; j < count; ++j) {
34108 if (kLineWidthPairs[i].drw == kLineWidthPairs[j].drw
34109 || kLineWidthPairs[i].rs == kLineWidthPairs[j].rs)
34110 return false;
34111 }
34112 }
34113 return true;
34114}
34115
34116static_assert(lineWidthPairsAreOneToOne(),
34117 "kLineWidthPairs: every DRW_LW_Conv::lineWidth and every "
34118 "RS2::LineWidth may appear once, so neither direction of the "
34119 "lookup can shadow a later row");
34120} // namespace
34121
34122/**
34123 * Converts a DRW_LW_Conv::lineWidth into a RS2::LineWidth.
34124 */
34125RS2::LineWidth RS_FilterDXFRW::numberToWidth(DRW_LW_Conv::lineWidth lw) {
34126 for (const LineWidthPair &pair : kLineWidthPairs) {
34127 if (pair.drw == lw)
34128 return pair.rs;
34129 }
34130 return RS2::WidthDefault;
34131}
34132
34133/**
34134 * Converts a RS2::LineWidth into an DRW_LW_Conv::lineWidth.
34135 */
34136DRW_LW_Conv::lineWidth RS_FilterDXFRW::widthToNumber(RS2::LineWidth width) {
34137 for (const LineWidthPair &pair : kLineWidthPairs) {
34138 if (pair.rs == width)
34139 return pair.drw;
34140 }
34141 return DRW_LW_Conv::widthDefault;
34142}
34143
34144namespace {
34145/**
34146 * Converts a native unicode string into a DXF encoded string.
34147 *
34148 * DXF endoding includes the following special sequences:
34149 * - %%%c for a diameter sign
34150 * - %%%d for a degree sign
34151 * - %%%p for a plus/minus sign
34152 *
34153 * @param paragraphBreaks A line feed is the MTEXT paragraph break "\P".
34154 * Single-line text has no such code: its line feed is a control character
34155 * like any other.
34156 */
34157QString dxfEncodedString(const QString &str, const bool caretCodes,
34158 const bool paragraphBreaks) {
34159 QString res;
34160 res.reserve(str.length() + 16);
34161
34162 for (const QChar &qchar : str) {
34163 switch (qchar.unicode()) {
34164 case 0x0A:
34165 if (paragraphBreaks) {
34166 res.append(uR"(\P)");
34167 } else if (caretCodes) {
34168 res.append(u"^J");
34169 } else {
34170 res.append(qchar);
34171 }
34172 break;
34173 case 0x5E: // '^' starts a caret code (toNativeString); "^ " is a literal one
34174 if (caretCodes) {
34175 res.append(u"^ ");
34176 } else {
34177 res.append(qchar);
34178 }
34179 break;
34180 case 0x2205:
34181 case 0x2300:
34182 res.append(u"%%C");
34183 break;
34184 case 0x00B0:
34185 res.append(u"%%D");
34186 break;
34187 case 0x00B1:
34188 res.append(u"%%P");
34189 break;
34190 default:
34191 if (caretCodes && qchar.unicode() < 0x20) { // control character: caret code, ^I for TAB
34192 res.append(QChar(0x5E));
34193 res.append(QChar(static_cast<ushort>(qchar.unicode() + 0x40)));
34194 } else {
34195 res.append(qchar);
34196 }
34197 break;
34198 }
34199 }
34200 return res;
34201}
34202
34203/**
34204 * Decodes the caret codes of a DXF text value. AutoCAD caret convention:
34205 * ^X → chr((X-64) mod 126); ^Space → '^' literal. Mirrors ezdxf
34206 * tools/text.py:501, so ^J is LF, ^M is CR and ^I is TAB.
34207 */
34208QString decodeCaretCodes(const QString &text) {
34209 QString decoded;
34210 decoded.reserve(text.size());
34211 for (int k = 0; k < text.size(); ++k) {
34212 const QChar ch = text.at(k);
34213 if (ch.unicode() == 0x5E /* '^' */ && k + 1 < text.size()) {
34214 const QChar nx = text.at(k + 1);
34215 if (nx.unicode() == 0x20) {
34216 decoded.append(QChar(0x5E)); // ^space → literal ^
34217 } else {
34218 const int code = (static_cast<int>(nx.unicode()) - 64) % 126;
34219 decoded.append(
34220 QChar(static_cast<ushort>(code < 0 ? code + 126 : code)));
34221 }
34222 ++k; // consume the X
34223 } else {
34224 decoded.append(ch);
34225 }
34226 }
34227 return decoded;
34228}
34229
34230/**
34231 * Replaces the diameter / degree / plus-minus codes — a case-insensitive
34232 * match of %%c / %%C etc. — by their signs.
34233 * (RLZ: Empty_set is 0x2205, diameter is 0x2300 — needs to be in all fonts.)
34234 */
34235void decodeTextSymbols(QString &text) {
34236 text.replace(QLatin1StringView("%%c"), QStringLiteral("⌀")(QString(QtPrivate::qMakeStringPrivate(u"" "⌀"))),
34237 Qt::CaseInsensitive);
34238 text.replace(QLatin1StringView("%%d"), QStringLiteral("°")(QString(QtPrivate::qMakeStringPrivate(u"" "°"))),
34239 Qt::CaseInsensitive);
34240 text.replace(QLatin1StringView("%%p"), QStringLiteral("±")(QString(QtPrivate::qMakeStringPrivate(u"" "±"))),
34241 Qt::CaseInsensitive);
34242}
34243} // namespace
34244
34245QString RS_FilterDXFRW::toDxfString(const QString &str, const bool caretCodes) {
34246 return dxfEncodedString(str, caretCodes, true);
34247}
34248
34249QString RS_FilterDXFRW::toDxfSingleLineString(const QString &str,
34250 const bool caretCodes) {
34251 return dxfEncodedString(str, caretCodes, false);
34252}
34253
34254/**
34255 * Converts a DXF encoded string into a native Unicode string.
34256 */
34257QString RS_FilterDXFRW::toNativeString(const QString &data) {
34258 QString res;
34259 const int n = data.length();
34260
34261 // Ignore font tags:
34262 int j = 0;
34263 for (int i = 0; i < n; ++i) {
34264 // Need at least "{\X" available — bounds-check before any at(i+N).
34265 if (i + 2 >= n)
34266 break;
34267 if (data.at(i).unicode() != 0x7B)
34268 continue; // is '{' ?
34269 if (data.at(i + 1).unicode() != 0x5c)
34270 continue; // and is "{\" ?
34271 const ushort tagChar = data.at(i + 2).unicode();
34272 if (tagChar != 0x66 && tagChar != 0x48 && tagChar != 0x43)
34273 continue; // "\f" "\H" "\C"
34274
34275 // found tag, append parsed part
34276 res.append(data.mid(j, i - j));
34277 qsizetype pos = data.indexOf(QChar(0x7D), i + 3); // find '}'
34278 if (pos < 0)
34279 break; //'}' not found
34280 QString tmp = data.mid(i + 1, pos - i - 1);
34281 do {
34282 qsizetype semi = tmp.indexOf(QChar(0x3B), 0); // find ';'
34283 if (semi < 0)
34284 break; // malformed: no ';' — bail to avoid infinite loop
34285 tmp.remove(0, semi + 1);
34286 } while (tmp.startsWith(QLatin1StringView("\\f")) ||
34287 tmp.startsWith(QLatin1StringView("\\H")) ||
34288 tmp.startsWith(QLatin1StringView("\\C")));
34289 res.append(tmp);
34290 i = pos;
34291 j = pos + 1;
34292 }
34293 res.append(data.mid(j));
34294
34295 // Run BEFORE the \P/\~ pass so ^J (LF), ^M (CR), ^I (TAB) decode first;
34296 // also subsumes the prior hard-coded "^I → 4 spaces" rule with the general
34297 // formula (^I → TAB).
34298 res = decodeCaretCodes(res);
34299
34300 // Replace literal escape sequences. Each replace() mutates in place;
34301 // the prior `res = res.replace(...)` was an unnecessary self-assignment.
34302 res.replace(QLatin1StringView("\\P"), QLatin1StringView("\n")); // line feed
34303 res.replace(QLatin1StringView("\\~"), QLatin1StringView(" ")); // space
34304 decodeTextSymbols(res);
34305
34306 return res;
34307}
34308
34309QString RS_FilterDXFRW::toNativeSingleLineString(const QString &data) {
34310 QString res = decodeCaretCodes(data);
34311 decodeTextSymbols(res);
34312 return res;
34313}
34314
34315/**
34316 * Converts the given number from a DXF file into an AngleFormat enum.
34317 *
34318 * @param num $DIMAUNIT from DXF (0: decimal deg, 1: deg/min/sec, 2: gradians,
34319 * 3: radians, 4: surveyor's units)
34320 *
34321 * @ret Matching AngleFormat enum value.
34322 */
34323RS2::AngleFormat RS_FilterDXFRW::numberToAngleFormat(int num) {
34324 switch (num) {
34325 default:
34326 case 0:
34327 return RS2::DegreesDecimal;
34328 case 1:
34329 return RS2::DegreesMinutesSeconds;
34330 case 2:
34331 return RS2::Gradians;
34332 case 3:
34333 return RS2::Radians;
34334 case 4:
34335 return RS2::Surveyors;
34336 }
34337}
34338
34339/**
34340 * Converts AngleFormat enum to DXF number.
34341 */
34342int RS_FilterDXFRW::angleFormatToNumber(RS2::AngleFormat af) {
34343 switch (af) {
34344 default:
34345 case RS2::DegreesDecimal:
34346 return 0;
34347 case RS2::DegreesMinutesSeconds:
34348 return 1;
34349 case RS2::Gradians:
34350 return 2;
34351 case RS2::Radians:
34352 return 3;
34353 case RS2::Surveyors:
34354 return 4;
34355 }
34356}
34357
34358/**
34359 * converts a DXF unit setting (e.g. INSUNITS) to a unit enum.
34360 */
34361RS2::Unit RS_FilterDXFRW::numberToUnit(int num) {
34362 switch (num) {
34363 default:
34364 case 0:
34365 return RS2::None;
34366 case 1:
34367 return RS2::Inch;
34368 case 2:
34369 return RS2::Foot;
34370 case 3:
34371 return RS2::Mile;
34372 case 4:
34373 return RS2::Millimeter;
34374 case 5:
34375 return RS2::Centimeter;
34376 case 6:
34377 return RS2::Meter;
34378 case 7:
34379 return RS2::Kilometer;
34380 case 8:
34381 return RS2::Microinch;
34382 case 9:
34383 return RS2::Mil;
34384 case 10:
34385 return RS2::Yard;
34386 case 11:
34387 return RS2::Angstrom;
34388 case 12:
34389 return RS2::Nanometer;
34390 case 13:
34391 return RS2::Micron;
34392 case 14:
34393 return RS2::Decimeter;
34394 case 15:
34395 return RS2::Decameter;
34396 case 16:
34397 return RS2::Hectometer;
34398 case 17:
34399 return RS2::Gigameter;
34400 case 18:
34401 return RS2::Astro;
34402 case 19:
34403 return RS2::Lightyear;
34404 case 20:
34405 return RS2::Parsec;
34406 }
34407 return RS2::None;
34408}
34409
34410/**
34411 * Converts a unit enum into a DXF unit number e.g. for INSUNITS.
34412 */
34413int RS_FilterDXFRW::unitToNumber(RS2::Unit unit) {
34414 switch (unit) {
34415 default:
34416 case RS2::None:
34417 return 0;
34418 case RS2::Inch:
34419 return 1;
34420 case RS2::Foot:
34421 return 2;
34422 case RS2::Mile:
34423 return 3;
34424 case RS2::Millimeter:
34425 return 4;
34426 case RS2::Centimeter:
34427 return 5;
34428 case RS2::Meter:
34429 return 6;
34430 case RS2::Kilometer:
34431 return 7;
34432 case RS2::Microinch:
34433 return 8;
34434 case RS2::Mil:
34435 return 9;
34436 case RS2::Yard:
34437 return 10;
34438 case RS2::Angstrom:
34439 return 11;
34440 case RS2::Nanometer:
34441 return 12;
34442 case RS2::Micron:
34443 return 13;
34444 case RS2::Decimeter:
34445 return 14;
34446 case RS2::Decameter:
34447 return 15;
34448 case RS2::Hectometer:
34449 return 16;
34450 case RS2::Gigameter:
34451 return 17;
34452 case RS2::Astro:
34453 return 18;
34454 case RS2::Lightyear:
34455 return 19;
34456 case RS2::Parsec:
34457 return 20;
34458 }
34459 return 0;
34460}
34461
34462/**
34463 * Checks if the given variable is two-dimensional (e.g. $LIMMIN).
34464 */
34465bool RS_FilterDXFRW::isVariableTwoDimensional(const QString &var) {
34466 if (var == "$LIMMIN" || var == "$LIMMAX" || var == "$PLIMMIN" ||
34467 var == "$PLIMMAX" || var == "$GRIDUNIT" || var == "$VIEWCTR") {
34468 return true;
34469 } else {
34470 return false;
34471 }
34472}
34473
34474void RS_FilterDXFRW::addPolylineSegment(
34475 RS_Polyline &polyline, const RS_Vector &prev_pos, const RS_Vector &curr_pos,
34476 const double segmentBulge, const double nextBulge,
34477 const std::vector<std::shared_ptr<DRW_Variant>> &extData) {
34478 bool isLcData = false;
34479 double yRadius = 0.0;
34480
34481 // Issue #1946: LibreCAD may use elliptic arcs internally, but dxf file saving
34482 // uses arcs only. Minor radius of elliptic is saved as DRW_Variant::content
34483 // The content is flagged as "LibreCAD"
34484 for (const std::shared_ptr<DRW_Variant> &var : extData) {
34485 if (var == nullptr)
34486 continue;
34487 if (var->code() == 1001) {
34488 std::string marker;
34489 isLcData = readXDataMarker(var.get(), marker) == XDataReadResult::Valid &&
34490 marker == "LibreCad";
34491 } else if (isLcData && var->code() == 1040) {
34492 double radius = 0.0;
34493 if (readXDataDouble(var.get(), 1040, radius) == XDataReadResult::Valid) {
34494 yRadius = radius;
34495 } else {
34496 isLcData = false;
34497 }
34498 }
34499 }
34500 bool isElliptic = yRadius > RS_TOLERANCE1.0e-10;
34501
34502 if (isElliptic) {
34503 std::unique_ptr<RS_Arc> arc{
34504 RS_Polyline::arcFromBulge(prev_pos, curr_pos, segmentBulge)};
34505 if (arc != nullptr && arc->getRadius() >= RS_TOLERANCE1.0e-10) {
34506 double radius = arc->getRadius();
34507 double scaleRatio = yRadius / radius;
34508 RS_Ellipse *ellipse =
34509 RS_Polyline::convertToEllipse(std::make_pair(arc.get(), scaleRatio));
34510 if (ellipse != nullptr) {
34511 ellipse->setParent(&polyline);
34512 // ellipse->setSelected(polyline.isSelected());
34513 ellipse->setPen(RS_Pen(RS2::FlagInvalid));
34514 ellipse->setLayer(nullptr);
34515 polyline.addEntity(ellipse);
34516 polyline.getData().endpoint = curr_pos;
34517 polyline.setNextBulge(nextBulge);
34518 return;
34519 }
34520 }
34521 }
34522
34523 // A malformed LibreCAD elliptic sidecar must not leave a gap in an
34524 // otherwise valid polyline. addVertex consumes the current outgoing bulge
34525 // and stores the following vertex's bulge for the next segment.
34526 polyline.addVertex(curr_pos, nextBulge, false);
34527}
34528
34529#ifdef DWGSUPPORT1
34530QString RS_FilterDXFRW::printDwgVersion(int v) {
34531 switch (v) {
34532 case DRW::MC00:
34533 return "MC0.0/R1.1";
34534 case DRW::AC12:
34535 return "AC1.2/R1.2";
34536 case DRW::AC14:
34537 return "AC1.4/R1.4";
34538 case DRW::AC150:
34539 return "AC1.50/R2.0";
34540 case DRW::AC210:
34541 return "AC2.10/R2.10";
34542 case DRW::AC1002:
34543 return "AC1002/R2.5";
34544 case DRW::AC1003:
34545 return "AC1003/R2.6";
34546 case DRW::AC1004:
34547 return "AC1004/R9";
34548 case DRW::AC1006:
34549 return "AC1006/R10";
34550 case DRW::AC1009:
34551 return "AC1009/R11-R12";
34552 case DRW::AC1012:
34553 return "AC1012/R13";
34554 case DRW::AC1014:
34555 return "AC1014/R14";
34556 case DRW::AC1015:
34557 return "AC1015/R2000";
34558 case DRW::AC1018:
34559 return "AC1018/R2004";
34560 case DRW::AC1021:
34561 return "AC1021/R2007";
34562 case DRW::AC1024:
34563 return "AC1024/R2010";
34564 case DRW::AC1027:
34565 return "AC1027/R2013";
34566 case DRW::AC1032:
34567 return "AC1032/R2018";
34568 default:
34569 return "unknown";
34570 }
34571}
34572
34573bool RS_FilterDXFRW::sameRawObjectEncodingFamily(DRW::Version src,
34574 DRW::Version tgt) {
34575 // STRICT source==target only. The earlier "encoding family" widening (Alt-A)
34576 // grouped {AC1015,AC1018} and {AC1024,AC1027,AC1032} as cross-version
34577 // replayable, but a deep review proved the raw OBJECT/ENTITY bytes are NOT
34578 // safely replayable across a version boundary: the common object/entity
34579 // header layout differs (AC1015 lacks the R2004 xDictFlag bit and carries
34580 // entity prev/next link handles AC1018 drops; AC1024 lacks the has_ds_data
34581 // bit AC1027/AC1032 add), and even where common headers match the per-version
34582 // object BODY encoding can differ -- the raw bytes were written FOR the
34583 // source version. Replaying them into another version's frame (with a fresh
34584 // valid CRC) yields silently-malformed output. So raw replay is allowed only
34585 // when the target version equals the source version; a cross-version save
34586 // cleanly drops the raw-preserved metadata instead of corrupting it.
34587 return src == tgt;
34588}
34589
34590void RS_FilterDXFRW::printDwgError(int le) {
34591 switch (le) {
34592 case DRW::BAD_UNKNOWN:
34593 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34594 QObject::tr("unknown error opening dwg file"));
34595 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_UNKNOWN");
34596 break;
34597 case DRW::BAD_OPEN:
34598 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("can't open this dwg file"));
34599 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_OPEN");
34600 break;
34601 case DRW::BAD_VERSION:
34602 if (m_dwgVersion != DRW::UNKNOWNV) {
34603 const QString actual = dwgVersionDisplay(m_dwgVersion);
34604 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34605 QObject::tr(
34606 "Cannot open DWG: file is %1; LibreCAD supports %2 and "
34607 "newer. "
34608 "Convert with GNU LibreDWG (dwgread / dwg2dxf) or re-save "
34609 "from a recent CAD tool.")
34610 .arg(actual)
34611 .arg(dwgVersionDisplay(DRW::AC1012)));
34612 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: BAD_VERSION (%s)",
34613 actual.toUtf8().constData());
34614 } else {
34615 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(QObject::tr("unsupported dwg version"));
34616 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_VERSION");
34617 }
34618 break;
34619 case DRW::BAD_READ_METADATA:
34620 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34621 QObject::tr("error reading file metadata in dwg file"));
34622 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
34623 break;
34624 case DRW::BAD_READ_FILE_HEADER:
34625 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34626 QObject::tr("error reading file header in dwg file"));
34627 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_FILE_HEADER");
34628 break;
34629 case DRW::BAD_READ_HEADER:
34630 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34631 QObject::tr("error reading header vars in dwg file"));
34632 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_HEADER");
34633 break;
34634 case DRW::BAD_READ_CLASSES:
34635 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34636 QObject::tr("error reading classes in dwg file"));
34637 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_CLASSES");
34638 break;
34639 case DRW::BAD_READ_HANDLES:
34640 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34641 QObject::tr("error reading offsets in dwg file"));
34642 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
34643 break;
34644 case DRW::BAD_READ_TABLES:
34645 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34646 QObject::tr("error reading tables in dwg file"));
34647 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_TABLES");
34648 break;
34649 case DRW::BAD_READ_BLOCKS:
34650 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34651 QObject::tr("error reading blocks in dwg file"));
34652 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OFFSETS");
34653 break;
34654 case DRW::BAD_READ_ENTITIES:
34655 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34656 QObject::tr("error reading entities in dwg file"));
34657 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_ENTITIES");
34658 break;
34659 case DRW::BAD_READ_OBJECTS:
34660 RS_DIALOGFACTORYRS_DialogFactory::instance()->getFactoryObject()->commandMessage(
34661 QObject::tr("error reading objects in dwg file"));
34662 RS_DEBUGRS_Debug::instance()->print("RS_FilterDXFRW::printDwgError: DRW::BAD_READ_OBJECTS");
34663 break;
34664 default:
34665 break;
34666 }
34667}
34668
34669QString RS_FilterDXFRW::strVal(const DRW_Variant *var) {
34670 if (var == nullptr || var->type() != DRW_Variant::STRING ||
34671 var->content.s == nullptr)
34672 return {};
34673 return QString::fromUtf8(var->content.s->c_str());
34674}
34675
34676LC_DimStyle *RS_FilterDXFRW::createDimStyle(const DRW_Dimstyle &s) {
34677 auto *result = new LC_DimStyle();
34678 QString name = QString::fromUtf8(s.name.c_str());
34679 result->setName(name);
34680
34681 bool styleForEntityType = !result->isBaseStyle();
34682 if (styleForEntityType) {
34683 result->setModifyCheckMode(LC_DimStyle::ModificationAware::ALL);
34684 }
34685
34686 auto arrowStyle = result->arrowhead();
34687
34688 // A block name may come from the legacy plain-name group (5/6/7, always
34689 // present pre-V2000) or from a V2000+ handle to the BLOCK_RECORD (342-344).
34690 // Some writers emit both (the handle is a mirror); others (DIMLDRBLK's 341
34691 // always, and some V2000+ writers for 342-344 too) emit only the handle.
34692 // Resolving a handle needs the BLOCK_RECORD table, which the DIMSTYLE table
34693 // can precede in the file, so it is deferred: apply the immediate name (if
34694 // any) now, and queue the handle to be resolved once the whole file, and
34695 // so every table, has been read (see resolvePendingDimStyleArrowBlocks()).
34696 const auto arrowBlockName = [this](const DRW_Dimstyle &style, const char *key,
34697 const dwgHandle &handle,
34698 std::function<void(const QString &)> apply) {
34699 const DRW_Variant *v = checkedDimStyleVariable(style, key);
34700 if (v != nullptr) {
34701 apply(strVal(v));
34702 }
34703 if (handle.ref != 0) {
34704 m_pendingDimStyleArrowRefs.push_back({handle.ref, std::move(apply)});
34705 }
34706 };
34707
34708 arrowBlockName(s, "$DIMBLK", s.dimblkH, [arrowStyle](const QString &n) {
34709 arrowStyle->setSameBlockName(n);
34710 });
34711 arrowBlockName(s, "$DIMBLK1", s.dimblk1H, [arrowStyle](const QString &n) {
34712 arrowStyle->setArrowHeadBlockNameFirst(n);
34713 });
34714 arrowBlockName(s, "$DIMBLK2", s.dimblk2H, [arrowStyle](const QString &n) {
34715 arrowStyle->setArrowHeadBlockNameSecond(n);
34716 });
34717 DRW_Variant *var = checkedDimStyleVariable(s, "$DIMASZ");
34718 if (var != nullptr) {
34719 arrowStyle->setSize(var->d_val());
34720 }
34721 var = checkedDimStyleVariable(s, "$DIMTSZ");
34722 if (var != nullptr) {
34723 arrowStyle->setTickSize(var->d_val());
34724 }
34725 var = checkedDimStyleVariable(s, "$DIMSAH");
34726 if (var != nullptr) {
34727 arrowStyle->setUseSeparateArrowHeads(var->i_val());
34728 }
34729 var = checkedDimStyleVariable(s, "$DIMSOXD");
34730 if (var != nullptr) {
34731 arrowStyle->setSuppressionsRaw(var->i_val());
34732 }
34733
34734 auto scaleStyle = result->scaling();
34735 var = checkedDimStyleVariable(s, "$DIMSCALE");
34736 if (var != nullptr) {
34737 scaleStyle->setScale(var->d_val());
34738 }
34739 var = checkedDimStyleVariable(s, "$DIMLFAC");
34740 if (var != nullptr) {
34741 scaleStyle->setLinearFactor(var->d_val());
34742 }
34743
34744 // A variable holds the linetype name; the record holds its LTYPE handle.
34745 // No reference means ByBlock, also in a style for one dimension type.
34746 const auto lineTypeName = [&](const char *key, const dwgHandle &handle) {
34747 const DRW_Variant *v = checkedDimStyleVariable(s, key);
34748 const QString name = v != nullptr
34749 ? strVal(v)
34750 : lineTypeNameForHandle(m_graphic, handle.ref);
34751 return name.isEmpty() ? QStringLiteral("ByBlock")(QString(QtPrivate::qMakeStringPrivate(u"" "ByBlock"))) : name;
34752 };
34753
34754 auto extLineStyle = result->extensionLine();
34755
34756 var = checkedDimStyleVariable(s, "$DIMEXO");
34757 if (var != nullptr) {
34758 extLineStyle->setDistanceFromOriginPoint(var->d_val());
34759 }
34760 var = checkedDimStyleVariable(s, "$DIMEXE");
34761 if (var != nullptr) {
34762 extLineStyle->setDistanceBeyondDimLine(var->d_val());
34763 }
34764 var = checkedDimStyleVariable(s, "$DIMFXL");
34765 if (var != nullptr) {
34766 extLineStyle->setFixedLength(var->d_val());
34767 }
34768 var = checkedDimStyleVariable(s, "$DIMFXLON");
34769 if (var != nullptr) {
34770 extLineStyle->setHasFixedLength(var->i_val() == 1);
34771 }
34772 var = checkedDimStyleVariable(s, "$DIMLWE");
34773 if (var != nullptr) {
34774 extLineStyle->setLineWidthRaw(var->i_val());
34775 }
34776 var = checkedDimStyleVariable(s, "$DIMCLRE");
34777 if (var != nullptr) {
34778 extLineStyle->setColor(LC_ColorNumbers::numberToColor(var->i_val()));
34779 }
34780 var = checkedDimStyleVariable(s, "$DIMSE1");
34781 if (var != nullptr) {
34782 extLineStyle->setSuppressFirstRaw(var->i_val());
34783 }
34784 var = checkedDimStyleVariable(s, "$DIMSE2");
34785 if (var != nullptr) {
34786 extLineStyle->setSuppressSecondRaw(var->i_val());
34787 }
34788 extLineStyle->setLineTypeFirst(lineTypeName("$DIMLTEX1", s.dimltex1H));
34789 extLineStyle->setLineTypeSecond(lineTypeName("$DIMLTEX2", s.dimltex2H));
34790
34791 auto dimLineStyle = result->dimensionLine();
34792 var = checkedDimStyleVariable(s, "$DIMLWD");
34793 if (var != nullptr) {
34794 dimLineStyle->setLineWidthRaw(var->i_val());
34795 }
34796 var = checkedDimStyleVariable(s, "$DIMDLE");
34797 if (var != nullptr) {
34798 dimLineStyle->setDistanceBeyondExtLinesForObliqueStroke(var->d_val());
34799 }
34800 var = checkedDimStyleVariable(s, "$DIMDLI");
34801 if (var != nullptr) {
34802 dimLineStyle->setBaselineDimLinesSpacing(var->d_val());
34803 }
34804 var = checkedDimStyleVariable(s, "$DIMGAP");
34805 if (var != nullptr) {
34806 dimLineStyle->setLineGap(var->d_val());
34807 }
34808 var = checkedDimStyleVariable(s, "$DIMCLRD");
34809 if (var != nullptr) {
34810 dimLineStyle->setColor(LC_ColorNumbers::numberToColor(var->i_val()));
34811 }
34812 var = checkedDimStyleVariable(s, "$DIMSD1");
34813 if (var != nullptr) {
34814 dimLineStyle->setSuppressFirstLineRaw(var->i_val());
34815 }
34816 var = checkedDimStyleVariable(s, "$DIMSD2");
34817 if (var != nullptr) {
34818 dimLineStyle->setSuppressSecondLineRaw(var->i_val());
34819 }
34820 var = checkedDimStyleVariable(s, "$DIMTOFL");
34821 if (var != nullptr) {
34822 dimLineStyle->setDrawPolicyForOutsideTextRaw(var->i_val());
34823 }
34824 dimLineStyle->setLineType(lineTypeName("$DIMLTYPE", s.dimltypeH));
34825
34826 auto textStyle = result->text();
34827 var = checkedDimStyleVariable(s, "$DIMTXT");
34828 if (var != nullptr) {
34829 textStyle->setHeight(var->d_val());
34830 }
34831 var = checkedDimStyleVariable(s, "$DIMTXSTY");
34832 if (var != nullptr) {
34833 auto dimtxsty = strVal(var);
34834 prepareTextStyleName(dimtxsty);
34835 textStyle->setStyle(dimtxsty); // fixme - resolve via table?
34836 }
34837 var = checkedDimStyleVariable(s, "$DIMTOH");
34838 if (var != nullptr) {
34839 textStyle->setOrientationOutsideRaw(var->i_val());
34840 }
34841 var = checkedDimStyleVariable(s, "$DIMTIH");
34842 if (var != nullptr) {
34843 textStyle->setOrientationInsideRaw(var->i_val());
34844 }
34845 var = checkedDimStyleVariable(s, "$DIMJUST");
34846 if (var != nullptr) {
34847 textStyle->setHorizontalPositioningRaw(var->i_val());
34848 }
34849 var = checkedDimStyleVariable(s, "$DIMCLRT");
34850 if (var != nullptr) {
34851 textStyle->setColor(LC_ColorNumbers::numberToColor(var->i_val()));
34852 }
34853 var = checkedDimStyleVariable(s, "$DIMTAD");
34854 if (var != nullptr) {
34855 textStyle->setVerticalPositioningRaw(var->i_val());
34856 }
34857 var = checkedDimStyleVariable(s, "$DIMTIX");
34858 if (var != nullptr) {
34859 textStyle->setExtLinesRelativePlacementRaw(var->i_val());
34860 }
34861 var = checkedDimStyleVariable(s, "$DIMTFILL");
34862 if (var != nullptr) {
34863 textStyle->setBackgroundFillModeRaw(var->i_val());
34864 }
34865 var = checkedDimStyleVariable(s, "$DIMTFILLCLR");
34866 if (var != nullptr) {
34867 textStyle->setExplicitBackgroundFillColor(LC_ColorNumbers::numberToColor(var->i_val()));
34868 }
34869 var = checkedDimStyleVariable(s, "$DIMTXTDIRECTION");
34870 if (var != nullptr) {
34871 textStyle->setReadingDirectionRaw(var->i_val());
34872 }
34873 var = checkedDimStyleVariable(s, "$DIMTVP");
34874 if (var != nullptr) {
34875 textStyle->setVerticalDistanceToDimLine(var->d_val());
34876 }
34877 var = checkedDimStyleVariable(s, "$DIMUPT");
34878 if (var != nullptr) {
34879 textStyle->setCursorControlPolicyRaw(var->i_val());
34880 }
34881 var = checkedDimStyleVariable(s, "$DIMTMOVE");
34882 if (var != nullptr) {
34883 textStyle->setPositionMovementPolicyRaw(var->i_val());
34884 }
34885 var = checkedDimStyleVariable(s, "$DIMATFIT");
34886 if (var != nullptr) {
34887 textStyle->setUnsufficientSpacePolicyRaw(var->i_val());
34888 }
34889
34890 auto zerosSuppression = result->zerosSuppression();
34891
34892 var = checkedDimStyleVariable(s, "$DIMZIN");
34893 if (var != nullptr) {
34894 zerosSuppression->setLinearRaw(var->i_val());
34895 }
34896 var = checkedDimStyleVariable(s, "$DIMAZIN");
34897 if (var != nullptr) {
34898 zerosSuppression->setAngularRaw(var->i_val());
34899 }
34900 var = checkedDimStyleVariable(s, "$DIMTZIN");
34901 if (var != nullptr) {
34902 zerosSuppression->setToleranceRaw(var->i_val());
34903 }
34904 var = checkedDimStyleVariable(s, "$DIMALTZ");
34905 if (var != nullptr) {
34906 zerosSuppression->setAltLinearRaw(var->i_val());
34907 }
34908 var = checkedDimStyleVariable(s, "$DIMALTTZ");
34909 if (var != nullptr) {
34910 zerosSuppression->setAltToleranceRaw(var->i_val());
34911 }
34912
34913 auto linearFormat = result->linearFormat();
34914
34915 var = checkedDimStyleVariable(s, "$DIMLUNIT");
34916 if (var != nullptr) {
34917 linearFormat->setFormatRaw(var->i_val());
34918 }
34919 var = checkedDimStyleVariable(s, "$DIMDSEP");
34920 if (var != nullptr) {
34921 linearFormat->setDecimalFormatSeparatorChar(var->i_val());
34922 }
34923 var = checkedDimStyleVariable(s, "$DIMDEC");
34924 if (var != nullptr) {
34925 linearFormat->setDecimalPlaces(var->i_val());
34926 }
34927 var = checkedDimStyleVariable(s, "$DIMPOST");
34928 if (var != nullptr) {
34929 linearFormat->setPrefixOrSuffix(strVal(var));
34930 }
34931 var = checkedDimStyleVariable(s, "$DIMALT");
34932 if (var != nullptr) {
34933 linearFormat->setAlternateUnitsRaw(var->i_val());
34934 }
34935 var = checkedDimStyleVariable(s, "$DIMALTU");
34936 if (var != nullptr) {
34937 linearFormat->setAltFormatRaw(var->i_val());
34938 }
34939 var = checkedDimStyleVariable(s, "$DIMALTD");
34940 if (var != nullptr) {
34941 linearFormat->setAltDecimalPlaces(var->i_val());
34942 }
34943 var = checkedDimStyleVariable(s, "$DIMALTF");
34944 if (var != nullptr) {
34945 linearFormat->setAltUnitsMultiplier(var->d_val());
34946 }
34947 var = checkedDimStyleVariable(s, "$DIMAPOST");
34948 if (var != nullptr) {
34949 linearFormat->setAltPrefixOrSuffix(strVal(var));
34950 }
34951
34952 auto angularFormat = result->angularFormat();
34953 var = checkedDimStyleVariable(s, "$DIMAUNIT");
34954 if (var != nullptr) {
34955 angularFormat->setFormatRaw(var->i_val());
34956 }
34957 var = checkedDimStyleVariable(s, "$DIMADEC");
34958 if (var != nullptr) {
34959 angularFormat->setDecimalPlaces(var->i_val());
34960 }
34961
34962 auto linearRoundOff = result->roundOff();
34963 var = checkedDimStyleVariable(s, "$DIMRND");
34964 if (var != nullptr) {
34965 linearRoundOff->setRoundToValue(var->d_val());
34966 }
34967 var = checkedDimStyleVariable(s, "$DIMALTRND");
34968 if (var != nullptr) {
34969 linearRoundOff->setAltRoundToValue(var->d_val());
34970 }
34971
34972 auto fractionStyle = result->fractions();
34973 var = checkedDimStyleVariable(s, "$DIMFRAC");
34974 if (var != nullptr) {
34975 fractionStyle->setStyleRaw(var->i_val());
34976 }
34977
34978 auto radialStyle = result->radial();
34979 var = checkedDimStyleVariable(s, "$DIMCEN");
34980 if (var != nullptr) {
34981 radialStyle->setCenterMarkOrLineSize(var->d_val());
34982 }
34983
34984 auto toleranceStyle = result->latteralTolerance();
34985
34986 var = checkedDimStyleVariable(s, "$DIMTDEC");
34987 if (var != nullptr) {
34988 toleranceStyle->setDecimalPlaces(var->i_val());
34989 }
34990 var = checkedDimStyleVariable(s, "$DIMALTTD");
34991 if (var != nullptr) {
34992 toleranceStyle->setDecimalPlacesAltDim(var->i_val());
34993 }
34994 var = checkedDimStyleVariable(s, "$DIMTOL");
34995 if (var != nullptr) {
34996 toleranceStyle->setAppendTolerancesToDimText(
34997 var->i_val()); // fixme - review
34998 }
34999 var = checkedDimStyleVariable(s, "$DIMTOLJ");
35000 if (var != nullptr) {
35001 toleranceStyle->setVerticalJustificationRaw(var->i_val());
35002 }
35003 var = checkedDimStyleVariable(s, "$DIMTM");
35004 if (var != nullptr) {
35005 toleranceStyle->setLowerToleranceLimit(var->d_val()); // fixme - review
35006 }
35007 var = checkedDimStyleVariable(s, "$DIMTP");
35008 if (var != nullptr) {
35009 toleranceStyle->setUpperToleranceLimit(var->d_val());
35010 }
35011 var = checkedDimStyleVariable(s, "$DIMTFAC");
35012 if (var != nullptr) {
35013 toleranceStyle->setHeightScaleFactorToDimText(var->d_val());
35014 }
35015 var = checkedDimStyleVariable(s, "$DIMLIM");
35016 if (var != nullptr) {
35017 toleranceStyle->setLimitsAreGeneratedAsDefaultText(
35018 var->i_val()); // fixme - review
35019 }
35020
35021 auto leaderStyle = result->leader();
35022 arrowBlockName(s, "$DIMLDRBLK", s.dimldrblkH, [leaderStyle](const QString &n) {
35023 leaderStyle->setArrowBlockName(n);
35024 });
35025
35026 auto arc = result->arc();
35027 var = checkedDimStyleVariable(s, "$DIMARCSYM");
35028 if (var != nullptr) {
35029 arc->setArcSymbolPositionRaw(var->i_val());
35030 }
35031
35032 // fixme - remove to MLeaderStyle
35033 /*auto mleaderStyle = result->mleader();
35034 var = s.get("$MLEADERSCALE");
35035 if (var != nullptr) {
35036 mleaderStyle->setScale(var->d_val());
35037 }*/
35038 // mleaderStyle->setScale(s.mleaderscale);
35039
35040 parseDimStyleExtData(s, result);
35041
35042 if (styleForEntityType) {
35043 result->setModifyCheckMode(LC_DimStyle::ModificationAware::SET);
35044 }
35045 return result;
35046}
35047
35048bool RS_FilterDXFRW::resolveBlockNameByHandle(std::uint32_t blockHandle,
35049 QString &blockName) const {
35050 // The reading context lives on the DXF reader; it is null on the DWG read
35051 // path (and after the reader is torn down). Resolution by handle is a
35052 // DXF-only facility — bail out safely otherwise.
35053 if (m_dxfR == nullptr)
35054 return false;
35055 std::string name =
35056 m_dxfR->getReadingContext()->resolveBlockRecordName(blockHandle);
35057 if (name.empty()) {
35058 return false;
35059 }
35060 blockName = name.c_str();
35061 return true;
35062}
35063
35064// createDimStyle() queues one entry per DIMSTYLE arrow-block handle it can't
35065// resolve immediately (BLOCK_RECORD, read after DIMSTYLE within TABLES, isn't
35066// available yet). Called once the whole file has been read, while m_dxfR (and
35067// so its reading context) is still alive.
35068void RS_FilterDXFRW::resolvePendingDimStyleArrowBlocks() {
35069 for (const auto &pending : m_pendingDimStyleArrowRefs) {
35070 QString blockName;
35071 if (resolveBlockNameByHandle(pending.handle, blockName)) {
35072 pending.apply(blockName);
35073 }
35074 }
35075 m_pendingDimStyleArrowRefs.clear();
35076}
35077
35078void RS_FilterDXFRW::parseDimStyleExtData(const DRW_Dimstyle &s,
35079 LC_DimStyle *result) {
35080 std::vector<DRW_Variant> tagData;
35081 int currentValType = 0;
35082 QString applicationName = "";
35083 bool expectType = false;
35084 // https://help.autodesk.com/view/OARX/2024/ENU/?guid=GUID-3F0380A5-1C15-464D-BC66-2C5F094BCFB9
35085 // https://documentation.help/AutoCAD-DXF/WS1a9193826455f5ff18cb41610ec0a2e719-7943.htm
35086 for (const auto &v : s.extData) {
35087 if (v == nullptr)
35088 continue;
35089 const int code = v->code();
35090 switch (code) {
35091 case 1001: {
35092 // application name
35093 if (!applicationName.isEmpty()) {
35094 applyParsedDimStyleExtData(result, applicationName, tagData);
35095 tagData.clear();
35096 }
35097 std::string value;
35098 if (readXDataString(v, 1001, value) != XDataReadResult::Valid) {
35099 applicationName.clear();
35100 tagData.clear();
35101 expectType = false;
35102 continue;
35103 }
35104 applicationName = QString::fromStdString(value);
35105 // Each section is "1001 appname" then one or more (1070 field-code,
35106 // value) pairs -- a 392/0 DIMTALN payload, say, or this PR's own
35107 // 380/<handle>. A fresh 1001 starts by expecting the first pair's
35108 // field code.
35109 expectType = true;
35110 continue;
35111 }
35112 case 1002: {
35113 // control name
35114 continue;
35115 }
35116 case 1070: // integer
35117 case 1071: {
35118 // long
35119 std::int64_t integerValue = 0;
35120 if (readXDataInteger(v, code, integerValue) != XDataReadResult::Valid ||
35121 !fitsXDataInt32(integerValue)) {
35122 continue;
35123 }
35124 const int val = static_cast<int>(integerValue);
35125 if (expectType) {
35126 // code of var
35127 currentValType = val;
35128 expectType = false;
35129 } else {
35130 // it fields
35131 auto intVar = DRW_Variant(currentValType, val);
35132 tagData.push_back(intVar);
35133 expectType = true;
35134 }
35135 break;
35136 }
35137 case 1040: // real
35138 case 1041: // distance
35139 case 1042: {
35140 // scale factor
35141 double value = 0.0;
35142 if (readXDataDouble(v, code, value) != XDataReadResult::Valid)
35143 continue;
35144 auto doubleVar = DRW_Variant(currentValType, value);
35145 tagData.push_back(doubleVar);
35146 expectType = true;
35147 break;
35148 }
35149 case 1005: {
35150 // A handle-typed value: ACAD_DSTYLE_DIM_LINETYPE and its _EXT1_/_EXT2_
35151 // siblings pair their 1070 type code with a 1005 handle here, not
35152 // another 1070/1040 value.
35153 std::string handle;
35154 if (readXDataString(v, 1005, handle) != XDataReadResult::Valid)
35155 continue;
35156 auto handleVar = DRW_Variant(currentValType, handle);
35157 tagData.push_back(handleVar);
35158 expectType = true;
35159 break;
35160 }
35161 default:
35162 continue;
35163 }
35164 }
35165 if (!applicationName.isEmpty()) {
35166 // process last app name setion
35167 applyParsedDimStyleExtData(result, applicationName, tagData);
35168 tagData.clear();
35169 }
35170}
35171
35172void RS_FilterDXFRW::applyParsedDimStyleExtData(
35173 const LC_DimStyle *dimStyle, const QString &appName,
35174 const std::vector<DRW_Variant> &vector) {
35175 if (vector.empty()) {
35176 return;
35177 }
35178 const DRW_Variant *var = &vector.at(0);
35179 int code = var->code();
35180 // ACAD_DSTYLE_DIM_LINETYPE and its _EXT1_/_EXT2_ siblings hold the hex
35181 // handle of an LTYPE record, the same reference group 345-347 hold on
35182 // R2007+ (parseDimStyleGroups, "$DIMLTYPE").
35183 const auto dimStyleXDataLineTypeName = [this](const DRW_Variant *v) {
35184 bool ok = false;
35185 const std::uint32_t handle =
35186 QString::fromStdString(v->c_str()).toUInt(&ok, 16);
35187 return ok ? lineTypeNameForHandle(m_graphic, handle) : QString{};
35188 };
35189
35190 if (appName == "ACAD_DSTYLE_DIMJAG") {
35191 if (code == 388) {
35192 // double val = var->d_val();
35193 // fixme - decide where to store it. this is "Jog Height Factor"
35194 // dimStyle->dimensionLine()->set
35195 }
35196
35197 // code 388, float
35198 } else if (appName == "ACAD_DSTYLE_DIMTALN") {
35199 // code 392, int
35200 if (code == 392) {
35201 int val = var->i_val();
35202 dimStyle->latteralTolerance()->setAdjustmentRaw(val);
35203 }
35204 } else if (appName == "ACAD_DSTYLE_DIMBREAK") {
35205 // code 391, float
35206 if (code == 391) {
35207 // double val = var->d_val();
35208 // fixme - decide where to store it. This is "Dimension Break" in acad.
35209 }
35210 } else if (appName == "ACAD_DSTYLE_DIM_LINETYPE") {
35211 // code 380, handle: R2000/R2004 keep a style's own DIMLTYPE here instead
35212 // of in the group 345 that #2928 reads on R2007+ (dimStyleGroup's own
35213 // struct has no field for it at those versions).
35214 if (code == 380 && var->type() == DRW_Variant::STRING) {
35215 const QString name = dimStyleXDataLineTypeName(var);
35216 if (!name.isEmpty())
35217 dimStyle->dimensionLine()->setLineType(name);
35218 }
35219 } else if (appName == "ACAD_DSTYLE_DIM_EXT1_LINETYPE") {
35220 // code 381, handle: the R2000/R2004 form of group 346.
35221 if (code == 381 && var->type() == DRW_Variant::STRING) {
35222 const QString name = dimStyleXDataLineTypeName(var);
35223 if (!name.isEmpty())
35224 dimStyle->extensionLine()->setLineTypeFirst(name);
35225 }
35226 } else if (appName == "ACAD_DSTYLE_DIM_EXT2_LINETYPE") {
35227 // code 382, handle: the R2000/R2004 form of group 347.
35228 if (code == 382 && var->type() == DRW_Variant::STRING) {
35229 const QString name = dimStyleXDataLineTypeName(var);
35230 if (!name.isEmpty())
35231 dimStyle->extensionLine()->setLineTypeSecond(name);
35232 }
35233 }
35234}
35235
35236#endif // DWGSUPPORT